diff --git a/.gitattributes b/.gitattributes
index a9d0ad2153ccf0f5a34763c54d9da5dc3bc76d23..075763dedd62efc52225b8b28b468b8646ec0387 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -52,3 +52,20 @@ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/_qhull.cpython-311-x8
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/_tri.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/backends/_backend_agg.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/backends/_tkagg.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf filter=lfs diff=lfs merge=lfs -text
+outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf filter=lfs diff=lfs merge=lfs -text
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.py
new file mode 100644
index 0000000000000000000000000000000000000000..9bc25913ee0e18c77be987d5c6b2afc602155557
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.py
@@ -0,0 +1,2573 @@
+"""
+Classes for the efficient drawing of large collections of objects that
+share most properties, e.g., a large number of line segments or
+polygons.
+
+The classes are not meant to be as flexible as their single element
+counterparts (e.g., you may not be able to select all line styles) but
+they are meant to be fast for common use cases (e.g., a large set of solid
+line segments).
+"""
+
+import itertools
+import functools
+import math
+from numbers import Number, Real
+import warnings
+
+import numpy as np
+
+import matplotlib as mpl
+from . import (_api, _path, artist, cbook, colorizer as mcolorizer, colors as mcolors,
+ _docstring, hatch as mhatch, lines as mlines, path as mpath, transforms)
+from ._enums import JoinStyle, CapStyle
+
+
+# "color" is excluded; it is a compound setter, and its docstring differs
+# in LineCollection.
+@_api.define_aliases({
+ "antialiased": ["antialiaseds", "aa"],
+ "edgecolor": ["edgecolors", "ec"],
+ "facecolor": ["facecolors", "fc"],
+ "linestyle": ["linestyles", "dashes", "ls"],
+ "linewidth": ["linewidths", "lw"],
+ "offset_transform": ["transOffset"],
+})
+class Collection(mcolorizer.ColorizingArtist):
+ r"""
+ Base class for Collections. Must be subclassed to be usable.
+
+ A Collection represents a sequence of `.Patch`\es that can be drawn
+ more efficiently together than individually. For example, when a single
+ path is being drawn repeatedly at different offsets, the renderer can
+ typically execute a ``draw_marker()`` call much more efficiently than a
+ series of repeated calls to ``draw_path()`` with the offsets put in
+ one-by-one.
+
+ Most properties of a collection can be configured per-element. Therefore,
+ Collections have "plural" versions of many of the properties of a `.Patch`
+ (e.g. `.Collection.get_paths` instead of `.Patch.get_path`). Exceptions are
+ the *zorder*, *hatch*, *pickradius*, *capstyle* and *joinstyle* properties,
+ which can only be set globally for the whole collection.
+
+ Besides these exceptions, all properties can be specified as single values
+ (applying to all elements) or sequences of values. The property of the
+ ``i``\th element of the collection is::
+
+ prop[i % len(prop)]
+
+ Each Collection can optionally be used as its own `.ScalarMappable` by
+ passing the *norm* and *cmap* parameters to its constructor. If the
+ Collection's `.ScalarMappable` matrix ``_A`` has been set (via a call
+ to `.Collection.set_array`), then at draw time this internal scalar
+ mappable will be used to set the ``facecolors`` and ``edgecolors``,
+ ignoring those that were manually passed in.
+ """
+ #: Either a list of 3x3 arrays or an Nx3x3 array (representing N
+ #: transforms), suitable for the `all_transforms` argument to
+ #: `~matplotlib.backend_bases.RendererBase.draw_path_collection`;
+ #: each 3x3 array is used to initialize an
+ #: `~matplotlib.transforms.Affine2D` object.
+ #: Each kind of collection defines this based on its arguments.
+ _transforms = np.empty((0, 3, 3))
+
+ # Whether to draw an edge by default. Set on a
+ # subclass-by-subclass basis.
+ _edge_default = False
+
+ @_docstring.interpd
+ def __init__(self, *,
+ edgecolors=None,
+ facecolors=None,
+ linewidths=None,
+ linestyles='solid',
+ capstyle=None,
+ joinstyle=None,
+ antialiaseds=None,
+ offsets=None,
+ offset_transform=None,
+ norm=None, # optional for ScalarMappable
+ cmap=None, # ditto
+ colorizer=None,
+ pickradius=5.0,
+ hatch=None,
+ urls=None,
+ zorder=1,
+ **kwargs
+ ):
+ """
+ Parameters
+ ----------
+ edgecolors : :mpltype:`color` or list of colors, default: :rc:`patch.edgecolor`
+ Edge color for each patch making up the collection. The special
+ value 'face' can be passed to make the edgecolor match the
+ facecolor.
+ facecolors : :mpltype:`color` or list of colors, default: :rc:`patch.facecolor`
+ Face color for each patch making up the collection.
+ linewidths : float or list of floats, default: :rc:`patch.linewidth`
+ Line width for each patch making up the collection.
+ linestyles : str or tuple or list thereof, default: 'solid'
+ Valid strings are ['solid', 'dashed', 'dashdot', 'dotted', '-',
+ '--', '-.', ':']. Dash tuples should be of the form::
+
+ (offset, onoffseq),
+
+ where *onoffseq* is an even length tuple of on and off ink lengths
+ in points. For examples, see
+ :doc:`/gallery/lines_bars_and_markers/linestyles`.
+ capstyle : `.CapStyle`-like, default: 'butt'
+ Style to use for capping lines for all paths in the collection.
+ Allowed values are %(CapStyle)s.
+ joinstyle : `.JoinStyle`-like, default: 'round'
+ Style to use for joining lines for all paths in the collection.
+ Allowed values are %(JoinStyle)s.
+ antialiaseds : bool or list of bool, default: :rc:`patch.antialiased`
+ Whether each patch in the collection should be drawn with
+ antialiasing.
+ offsets : (float, float) or list thereof, default: (0, 0)
+ A vector by which to translate each patch after rendering (default
+ is no translation). The translation is performed in screen (pixel)
+ coordinates (i.e. after the Artist's transform is applied).
+ offset_transform : `~.Transform`, default: `.IdentityTransform`
+ A single transform which will be applied to each *offsets* vector
+ before it is used.
+ cmap, norm
+ Data normalization and colormapping parameters. See
+ `.ScalarMappable` for a detailed description.
+ hatch : str, optional
+ Hatching pattern to use in filled paths, if any. Valid strings are
+ ['/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*']. See
+ :doc:`/gallery/shapes_and_collections/hatch_style_reference` for
+ the meaning of each hatch type.
+ pickradius : float, default: 5.0
+ If ``pickradius <= 0``, then `.Collection.contains` will return
+ ``True`` whenever the test point is inside of one of the polygons
+ formed by the control points of a Path in the Collection. On the
+ other hand, if it is greater than 0, then we instead check if the
+ test point is contained in a stroke of width ``2*pickradius``
+ following any of the Paths in the Collection.
+ urls : list of str, default: None
+ A URL for each patch to link to once drawn. Currently only works
+ for the SVG backend. See :doc:`/gallery/misc/hyperlinks_sgskip` for
+ examples.
+ zorder : float, default: 1
+ The drawing order, shared by all Patches in the Collection. See
+ :doc:`/gallery/misc/zorder_demo` for all defaults and examples.
+ **kwargs
+ Remaining keyword arguments will be used to set properties as
+ ``Collection.set_{key}(val)`` for each key-value pair in *kwargs*.
+ """
+
+ super().__init__(self._get_colorizer(cmap, norm, colorizer))
+ # list of un-scaled dash patterns
+ # this is needed scaling the dash pattern by linewidth
+ self._us_linestyles = [(0, None)]
+ # list of dash patterns
+ self._linestyles = [(0, None)]
+ # list of unbroadcast/scaled linewidths
+ self._us_lw = [0]
+ self._linewidths = [0]
+
+ self._gapcolor = None # Currently only used by LineCollection.
+
+ # Flags set by _set_mappable_flags: are colors from mapping an array?
+ self._face_is_mapped = None
+ self._edge_is_mapped = None
+ self._mapped_colors = None # calculated in update_scalarmappable
+ self._hatch_color = mcolors.to_rgba(mpl.rcParams['hatch.color'])
+ self._hatch_linewidth = mpl.rcParams['hatch.linewidth']
+ self.set_facecolor(facecolors)
+ self.set_edgecolor(edgecolors)
+ self.set_linewidth(linewidths)
+ self.set_linestyle(linestyles)
+ self.set_antialiased(antialiaseds)
+ self.set_pickradius(pickradius)
+ self.set_urls(urls)
+ self.set_hatch(hatch)
+ self.set_zorder(zorder)
+
+ if capstyle:
+ self.set_capstyle(capstyle)
+ else:
+ self._capstyle = None
+
+ if joinstyle:
+ self.set_joinstyle(joinstyle)
+ else:
+ self._joinstyle = None
+
+ if offsets is not None:
+ offsets = np.asanyarray(offsets, float)
+ # Broadcast (2,) -> (1, 2) but nothing else.
+ if offsets.shape == (2,):
+ offsets = offsets[None, :]
+
+ self._offsets = offsets
+ self._offset_transform = offset_transform
+
+ self._path_effects = None
+ self._internal_update(kwargs)
+ self._paths = None
+
+ def get_paths(self):
+ return self._paths
+
+ def set_paths(self, paths):
+ self._paths = paths
+ self.stale = True
+
+ def get_transforms(self):
+ return self._transforms
+
+ def get_offset_transform(self):
+ """Return the `.Transform` instance used by this artist offset."""
+ if self._offset_transform is None:
+ self._offset_transform = transforms.IdentityTransform()
+ elif (not isinstance(self._offset_transform, transforms.Transform)
+ and hasattr(self._offset_transform, '_as_mpl_transform')):
+ self._offset_transform = \
+ self._offset_transform._as_mpl_transform(self.axes)
+ return self._offset_transform
+
+ def set_offset_transform(self, offset_transform):
+ """
+ Set the artist offset transform.
+
+ Parameters
+ ----------
+ offset_transform : `.Transform`
+ """
+ self._offset_transform = offset_transform
+
+ def get_datalim(self, transData):
+ # Calculate the data limits and return them as a `.Bbox`.
+ #
+ # This operation depends on the transforms for the data in the
+ # collection and whether the collection has offsets:
+ #
+ # 1. offsets = None, transform child of transData: use the paths for
+ # the automatic limits (i.e. for LineCollection in streamline).
+ # 2. offsets != None: offset_transform is child of transData:
+ #
+ # a. transform is child of transData: use the path + offset for
+ # limits (i.e for bar).
+ # b. transform is not a child of transData: just use the offsets
+ # for the limits (i.e. for scatter)
+ #
+ # 3. otherwise return a null Bbox.
+
+ transform = self.get_transform()
+ offset_trf = self.get_offset_transform()
+ if not (isinstance(offset_trf, transforms.IdentityTransform)
+ or offset_trf.contains_branch(transData)):
+ # if the offsets are in some coords other than data,
+ # then don't use them for autoscaling.
+ return transforms.Bbox.null()
+
+ paths = self.get_paths()
+ if not len(paths):
+ # No paths to transform
+ return transforms.Bbox.null()
+
+ if not transform.is_affine:
+ paths = [transform.transform_path_non_affine(p) for p in paths]
+ # Don't convert transform to transform.get_affine() here because
+ # we may have transform.contains_branch(transData) but not
+ # transforms.get_affine().contains_branch(transData). But later,
+ # be careful to only apply the affine part that remains.
+
+ offsets = self.get_offsets()
+
+ if any(transform.contains_branch_seperately(transData)):
+ # collections that are just in data units (like quiver)
+ # can properly have the axes limits set by their shape +
+ # offset. LineCollections that have no offsets can
+ # also use this algorithm (like streamplot).
+ if isinstance(offsets, np.ma.MaskedArray):
+ offsets = offsets.filled(np.nan)
+ # get_path_collection_extents handles nan but not masked arrays
+ return mpath.get_path_collection_extents(
+ transform.get_affine() - transData, paths,
+ self.get_transforms(),
+ offset_trf.transform_non_affine(offsets),
+ offset_trf.get_affine().frozen())
+
+ # NOTE: None is the default case where no offsets were passed in
+ if self._offsets is not None:
+ # this is for collections that have their paths (shapes)
+ # in physical, axes-relative, or figure-relative units
+ # (i.e. like scatter). We can't uniquely set limits based on
+ # those shapes, so we just set the limits based on their
+ # location.
+ offsets = (offset_trf - transData).transform(offsets)
+ # note A-B means A B^{-1}
+ offsets = np.ma.masked_invalid(offsets)
+ if not offsets.mask.all():
+ bbox = transforms.Bbox.null()
+ bbox.update_from_data_xy(offsets)
+ return bbox
+ return transforms.Bbox.null()
+
+ def get_window_extent(self, renderer=None):
+ # TODO: check to ensure that this does not fail for
+ # cases other than scatter plot legend
+ return self.get_datalim(transforms.IdentityTransform())
+
+ def _prepare_points(self):
+ # Helper for drawing and hit testing.
+
+ transform = self.get_transform()
+ offset_trf = self.get_offset_transform()
+ offsets = self.get_offsets()
+ paths = self.get_paths()
+
+ if self.have_units():
+ paths = []
+ for path in self.get_paths():
+ vertices = path.vertices
+ xs, ys = vertices[:, 0], vertices[:, 1]
+ xs = self.convert_xunits(xs)
+ ys = self.convert_yunits(ys)
+ paths.append(mpath.Path(np.column_stack([xs, ys]), path.codes))
+ xs = self.convert_xunits(offsets[:, 0])
+ ys = self.convert_yunits(offsets[:, 1])
+ offsets = np.ma.column_stack([xs, ys])
+
+ if not transform.is_affine:
+ paths = [transform.transform_path_non_affine(path)
+ for path in paths]
+ transform = transform.get_affine()
+ if not offset_trf.is_affine:
+ offsets = offset_trf.transform_non_affine(offsets)
+ # This might have changed an ndarray into a masked array.
+ offset_trf = offset_trf.get_affine()
+
+ if isinstance(offsets, np.ma.MaskedArray):
+ offsets = offsets.filled(np.nan)
+ # Changing from a masked array to nan-filled ndarray
+ # is probably most efficient at this point.
+
+ return transform, offset_trf, offsets, paths
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ if not self.get_visible():
+ return
+ renderer.open_group(self.__class__.__name__, self.get_gid())
+
+ self.update_scalarmappable()
+
+ transform, offset_trf, offsets, paths = self._prepare_points()
+
+ gc = renderer.new_gc()
+ self._set_gc_clip(gc)
+ gc.set_snap(self.get_snap())
+
+ if self._hatch:
+ gc.set_hatch(self._hatch)
+ gc.set_hatch_color(self._hatch_color)
+ gc.set_hatch_linewidth(self._hatch_linewidth)
+
+ if self.get_sketch_params() is not None:
+ gc.set_sketch_params(*self.get_sketch_params())
+
+ if self.get_path_effects():
+ from matplotlib.patheffects import PathEffectRenderer
+ renderer = PathEffectRenderer(self.get_path_effects(), renderer)
+
+ # If the collection is made up of a single shape/color/stroke,
+ # it can be rendered once and blitted multiple times, using
+ # `draw_markers` rather than `draw_path_collection`. This is
+ # *much* faster for Agg, and results in smaller file sizes in
+ # PDF/SVG/PS.
+
+ trans = self.get_transforms()
+ facecolors = self.get_facecolor()
+ edgecolors = self.get_edgecolor()
+ do_single_path_optimization = False
+ if (len(paths) == 1 and len(trans) <= 1 and
+ len(facecolors) == 1 and len(edgecolors) == 1 and
+ len(self._linewidths) == 1 and
+ all(ls[1] is None for ls in self._linestyles) and
+ len(self._antialiaseds) == 1 and len(self._urls) == 1 and
+ self.get_hatch() is None):
+ if len(trans):
+ combined_transform = transforms.Affine2D(trans[0]) + transform
+ else:
+ combined_transform = transform
+ extents = paths[0].get_extents(combined_transform)
+ if (extents.width < self.get_figure(root=True).bbox.width
+ and extents.height < self.get_figure(root=True).bbox.height):
+ do_single_path_optimization = True
+
+ if self._joinstyle:
+ gc.set_joinstyle(self._joinstyle)
+
+ if self._capstyle:
+ gc.set_capstyle(self._capstyle)
+
+ if do_single_path_optimization:
+ gc.set_foreground(tuple(edgecolors[0]))
+ gc.set_linewidth(self._linewidths[0])
+ gc.set_dashes(*self._linestyles[0])
+ gc.set_antialiased(self._antialiaseds[0])
+ gc.set_url(self._urls[0])
+ renderer.draw_markers(
+ gc, paths[0], combined_transform.frozen(),
+ mpath.Path(offsets), offset_trf, tuple(facecolors[0]))
+ else:
+ if self._gapcolor is not None:
+ # First draw paths within the gaps.
+ ipaths, ilinestyles = self._get_inverse_paths_linestyles()
+ renderer.draw_path_collection(
+ gc, transform.frozen(), ipaths,
+ self.get_transforms(), offsets, offset_trf,
+ [mcolors.to_rgba("none")], self._gapcolor,
+ self._linewidths, ilinestyles,
+ self._antialiaseds, self._urls,
+ "screen")
+
+ renderer.draw_path_collection(
+ gc, transform.frozen(), paths,
+ self.get_transforms(), offsets, offset_trf,
+ self.get_facecolor(), self.get_edgecolor(),
+ self._linewidths, self._linestyles,
+ self._antialiaseds, self._urls,
+ "screen") # offset_position, kept for backcompat.
+
+ gc.restore()
+ renderer.close_group(self.__class__.__name__)
+ self.stale = False
+
+ def set_pickradius(self, pickradius):
+ """
+ Set the pick radius used for containment tests.
+
+ Parameters
+ ----------
+ pickradius : float
+ Pick radius, in points.
+ """
+ if not isinstance(pickradius, Real):
+ raise ValueError(
+ f"pickradius must be a real-valued number, not {pickradius!r}")
+ self._pickradius = pickradius
+
+ def get_pickradius(self):
+ return self._pickradius
+
+ def contains(self, mouseevent):
+ """
+ Test whether the mouse event occurred in the collection.
+
+ Returns ``bool, dict(ind=itemlist)``, where every item in itemlist
+ contains the event.
+ """
+ if self._different_canvas(mouseevent) or not self.get_visible():
+ return False, {}
+ pickradius = (
+ float(self._picker)
+ if isinstance(self._picker, Number) and
+ self._picker is not True # the bool, not just nonzero or 1
+ else self._pickradius)
+ if self.axes:
+ self.axes._unstale_viewLim()
+ transform, offset_trf, offsets, paths = self._prepare_points()
+ # Tests if the point is contained on one of the polygons formed
+ # by the control points of each of the paths. A point is considered
+ # "on" a path if it would lie within a stroke of width 2*pickradius
+ # following the path. If pickradius <= 0, then we instead simply check
+ # if the point is *inside* of the path instead.
+ ind = _path.point_in_path_collection(
+ mouseevent.x, mouseevent.y, pickradius,
+ transform.frozen(), paths, self.get_transforms(),
+ offsets, offset_trf, pickradius <= 0)
+ return len(ind) > 0, dict(ind=ind)
+
+ def set_urls(self, urls):
+ """
+ Parameters
+ ----------
+ urls : list of str or None
+
+ Notes
+ -----
+ URLs are currently only implemented by the SVG backend. They are
+ ignored by all other backends.
+ """
+ self._urls = urls if urls is not None else [None]
+ self.stale = True
+
+ def get_urls(self):
+ """
+ Return a list of URLs, one for each element of the collection.
+
+ The list contains *None* for elements without a URL. See
+ :doc:`/gallery/misc/hyperlinks_sgskip` for an example.
+ """
+ return self._urls
+
+ def set_hatch(self, hatch):
+ r"""
+ Set the hatching pattern
+
+ *hatch* can be one of::
+
+ / - diagonal hatching
+ \ - back diagonal
+ | - vertical
+ - - horizontal
+ + - crossed
+ x - crossed diagonal
+ o - small circle
+ O - large circle
+ . - dots
+ * - stars
+
+ Letters can be combined, in which case all the specified
+ hatchings are done. If same letter repeats, it increases the
+ density of hatching of that pattern.
+
+ Unlike other properties such as linewidth and colors, hatching
+ can only be specified for the collection as a whole, not separately
+ for each member.
+
+ Parameters
+ ----------
+ hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'}
+ """
+ # Use validate_hatch(list) after deprecation.
+ mhatch._validate_hatch_pattern(hatch)
+ self._hatch = hatch
+ self.stale = True
+
+ def get_hatch(self):
+ """Return the current hatching pattern."""
+ return self._hatch
+
+ def set_hatch_linewidth(self, lw):
+ """Set the hatch linewidth."""
+ self._hatch_linewidth = lw
+
+ def get_hatch_linewidth(self):
+ """Return the hatch linewidth."""
+ return self._hatch_linewidth
+
+ def set_offsets(self, offsets):
+ """
+ Set the offsets for the collection.
+
+ Parameters
+ ----------
+ offsets : (N, 2) or (2,) array-like
+ """
+ offsets = np.asanyarray(offsets)
+ if offsets.shape == (2,): # Broadcast (2,) -> (1, 2) but nothing else.
+ offsets = offsets[None, :]
+ cstack = (np.ma.column_stack if isinstance(offsets, np.ma.MaskedArray)
+ else np.column_stack)
+ self._offsets = cstack(
+ (np.asanyarray(self.convert_xunits(offsets[:, 0]), float),
+ np.asanyarray(self.convert_yunits(offsets[:, 1]), float)))
+ self.stale = True
+
+ def get_offsets(self):
+ """Return the offsets for the collection."""
+ # Default to zeros in the no-offset (None) case
+ return np.zeros((1, 2)) if self._offsets is None else self._offsets
+
+ def _get_default_linewidth(self):
+ # This may be overridden in a subclass.
+ return mpl.rcParams['patch.linewidth'] # validated as float
+
+ def set_linewidth(self, lw):
+ """
+ Set the linewidth(s) for the collection. *lw* can be a scalar
+ or a sequence; if it is a sequence the patches will cycle
+ through the sequence
+
+ Parameters
+ ----------
+ lw : float or list of floats
+ """
+ if lw is None:
+ lw = self._get_default_linewidth()
+ # get the un-scaled/broadcast lw
+ self._us_lw = np.atleast_1d(lw)
+
+ # scale all of the dash patterns.
+ self._linewidths, self._linestyles = self._bcast_lwls(
+ self._us_lw, self._us_linestyles)
+ self.stale = True
+
+ def set_linestyle(self, ls):
+ """
+ Set the linestyle(s) for the collection.
+
+ =========================== =================
+ linestyle description
+ =========================== =================
+ ``'-'`` or ``'solid'`` solid line
+ ``'--'`` or ``'dashed'`` dashed line
+ ``'-.'`` or ``'dashdot'`` dash-dotted line
+ ``':'`` or ``'dotted'`` dotted line
+ =========================== =================
+
+ Alternatively a dash tuple of the following form can be provided::
+
+ (offset, onoffseq),
+
+ where ``onoffseq`` is an even length tuple of on and off ink in points.
+
+ Parameters
+ ----------
+ ls : str or tuple or list thereof
+ Valid values for individual linestyles include {'-', '--', '-.',
+ ':', '', (offset, on-off-seq)}. See `.Line2D.set_linestyle` for a
+ complete description.
+ """
+ # get the list of raw 'unscaled' dash patterns
+ self._us_linestyles = mlines._get_dash_patterns(ls)
+
+ # broadcast and scale the lw and dash patterns
+ self._linewidths, self._linestyles = self._bcast_lwls(
+ self._us_lw, self._us_linestyles)
+
+ @_docstring.interpd
+ def set_capstyle(self, cs):
+ """
+ Set the `.CapStyle` for the collection (for all its elements).
+
+ Parameters
+ ----------
+ cs : `.CapStyle` or %(CapStyle)s
+ """
+ self._capstyle = CapStyle(cs)
+
+ @_docstring.interpd
+ def get_capstyle(self):
+ """
+ Return the cap style for the collection (for all its elements).
+
+ Returns
+ -------
+ %(CapStyle)s or None
+ """
+ return self._capstyle.name if self._capstyle else None
+
+ @_docstring.interpd
+ def set_joinstyle(self, js):
+ """
+ Set the `.JoinStyle` for the collection (for all its elements).
+
+ Parameters
+ ----------
+ js : `.JoinStyle` or %(JoinStyle)s
+ """
+ self._joinstyle = JoinStyle(js)
+
+ @_docstring.interpd
+ def get_joinstyle(self):
+ """
+ Return the join style for the collection (for all its elements).
+
+ Returns
+ -------
+ %(JoinStyle)s or None
+ """
+ return self._joinstyle.name if self._joinstyle else None
+
+ @staticmethod
+ def _bcast_lwls(linewidths, dashes):
+ """
+ Internal helper function to broadcast + scale ls/lw
+
+ In the collection drawing code, the linewidth and linestyle are cycled
+ through as circular buffers (via ``v[i % len(v)]``). Thus, if we are
+ going to scale the dash pattern at set time (not draw time) we need to
+ do the broadcasting now and expand both lists to be the same length.
+
+ Parameters
+ ----------
+ linewidths : list
+ line widths of collection
+ dashes : list
+ dash specification (offset, (dash pattern tuple))
+
+ Returns
+ -------
+ linewidths, dashes : list
+ Will be the same length, dashes are scaled by paired linewidth
+ """
+ if mpl.rcParams['_internal.classic_mode']:
+ return linewidths, dashes
+ # make sure they are the same length so we can zip them
+ if len(dashes) != len(linewidths):
+ l_dashes = len(dashes)
+ l_lw = len(linewidths)
+ gcd = math.gcd(l_dashes, l_lw)
+ dashes = list(dashes) * (l_lw // gcd)
+ linewidths = list(linewidths) * (l_dashes // gcd)
+
+ # scale the dash patterns
+ dashes = [mlines._scale_dashes(o, d, lw)
+ for (o, d), lw in zip(dashes, linewidths)]
+
+ return linewidths, dashes
+
+ def get_antialiased(self):
+ """
+ Get the antialiasing state for rendering.
+
+ Returns
+ -------
+ array of bools
+ """
+ return self._antialiaseds
+
+ def set_antialiased(self, aa):
+ """
+ Set the antialiasing state for rendering.
+
+ Parameters
+ ----------
+ aa : bool or list of bools
+ """
+ if aa is None:
+ aa = self._get_default_antialiased()
+ self._antialiaseds = np.atleast_1d(np.asarray(aa, bool))
+ self.stale = True
+
+ def _get_default_antialiased(self):
+ # This may be overridden in a subclass.
+ return mpl.rcParams['patch.antialiased']
+
+ def set_color(self, c):
+ """
+ Set both the edgecolor and the facecolor.
+
+ Parameters
+ ----------
+ c : :mpltype:`color` or list of RGBA tuples
+
+ See Also
+ --------
+ Collection.set_facecolor, Collection.set_edgecolor
+ For setting the edge or face color individually.
+ """
+ self.set_facecolor(c)
+ self.set_edgecolor(c)
+
+ def _get_default_facecolor(self):
+ # This may be overridden in a subclass.
+ return mpl.rcParams['patch.facecolor']
+
+ def _set_facecolor(self, c):
+ if c is None:
+ c = self._get_default_facecolor()
+
+ self._facecolors = mcolors.to_rgba_array(c, self._alpha)
+ self.stale = True
+
+ def set_facecolor(self, c):
+ """
+ Set the facecolor(s) of the collection. *c* can be a color (all patches
+ have same color), or a sequence of colors; if it is a sequence the
+ patches will cycle through the sequence.
+
+ If *c* is 'none', the patch will not be filled.
+
+ Parameters
+ ----------
+ c : :mpltype:`color` or list of :mpltype:`color`
+ """
+ if isinstance(c, str) and c.lower() in ("none", "face"):
+ c = c.lower()
+ self._original_facecolor = c
+ self._set_facecolor(c)
+
+ def get_facecolor(self):
+ return self._facecolors
+
+ def get_edgecolor(self):
+ if cbook._str_equal(self._edgecolors, 'face'):
+ return self.get_facecolor()
+ else:
+ return self._edgecolors
+
+ def _get_default_edgecolor(self):
+ # This may be overridden in a subclass.
+ return mpl.rcParams['patch.edgecolor']
+
+ def _set_edgecolor(self, c):
+ set_hatch_color = True
+ if c is None:
+ if (mpl.rcParams['patch.force_edgecolor']
+ or self._edge_default
+ or cbook._str_equal(self._original_facecolor, 'none')):
+ c = self._get_default_edgecolor()
+ else:
+ c = 'none'
+ set_hatch_color = False
+ if cbook._str_lower_equal(c, 'face'):
+ self._edgecolors = 'face'
+ self.stale = True
+ return
+ self._edgecolors = mcolors.to_rgba_array(c, self._alpha)
+ if set_hatch_color and len(self._edgecolors):
+ self._hatch_color = tuple(self._edgecolors[0])
+ self.stale = True
+
+ def set_edgecolor(self, c):
+ """
+ Set the edgecolor(s) of the collection.
+
+ Parameters
+ ----------
+ c : :mpltype:`color` or list of :mpltype:`color` or 'face'
+ The collection edgecolor(s). If a sequence, the patches cycle
+ through it. If 'face', match the facecolor.
+ """
+ # We pass through a default value for use in LineCollection.
+ # This allows us to maintain None as the default indicator in
+ # _original_edgecolor.
+ if isinstance(c, str) and c.lower() in ("none", "face"):
+ c = c.lower()
+ self._original_edgecolor = c
+ self._set_edgecolor(c)
+
+ def set_alpha(self, alpha):
+ """
+ Set the transparency of the collection.
+
+ Parameters
+ ----------
+ alpha : float or array of float or None
+ If not None, *alpha* values must be between 0 and 1, inclusive.
+ If an array is provided, its length must match the number of
+ elements in the collection. Masked values and nans are not
+ supported.
+ """
+ artist.Artist._set_alpha_for_array(self, alpha)
+ self._set_facecolor(self._original_facecolor)
+ self._set_edgecolor(self._original_edgecolor)
+
+ set_alpha.__doc__ = artist.Artist._set_alpha_for_array.__doc__
+
+ def get_linewidth(self):
+ return self._linewidths
+
+ def get_linestyle(self):
+ return self._linestyles
+
+ def _set_mappable_flags(self):
+ """
+ Determine whether edges and/or faces are color-mapped.
+
+ This is a helper for update_scalarmappable.
+ It sets Boolean flags '_edge_is_mapped' and '_face_is_mapped'.
+
+ Returns
+ -------
+ mapping_change : bool
+ True if either flag is True, or if a flag has changed.
+ """
+ # The flags are initialized to None to ensure this returns True
+ # the first time it is called.
+ edge0 = self._edge_is_mapped
+ face0 = self._face_is_mapped
+ # After returning, the flags must be Booleans, not None.
+ self._edge_is_mapped = False
+ self._face_is_mapped = False
+ if self._A is not None:
+ if not cbook._str_equal(self._original_facecolor, 'none'):
+ self._face_is_mapped = True
+ if cbook._str_equal(self._original_edgecolor, 'face'):
+ self._edge_is_mapped = True
+ else:
+ if self._original_edgecolor is None:
+ self._edge_is_mapped = True
+
+ mapped = self._face_is_mapped or self._edge_is_mapped
+ changed = (edge0 is None or face0 is None
+ or self._edge_is_mapped != edge0
+ or self._face_is_mapped != face0)
+ return mapped or changed
+
+ def update_scalarmappable(self):
+ """
+ Update colors from the scalar mappable array, if any.
+
+ Assign colors to edges and faces based on the array and/or
+ colors that were directly set, as appropriate.
+ """
+ if not self._set_mappable_flags():
+ return
+ # Allow possibility to call 'self.set_array(None)'.
+ if self._A is not None:
+ # QuadMesh can map 2d arrays (but pcolormesh supplies 1d array)
+ if self._A.ndim > 1 and not isinstance(self, _MeshData):
+ raise ValueError('Collections can only map rank 1 arrays')
+ if np.iterable(self._alpha):
+ if self._alpha.size != self._A.size:
+ raise ValueError(
+ f'Data array shape, {self._A.shape} '
+ 'is incompatible with alpha array shape, '
+ f'{self._alpha.shape}. '
+ 'This can occur with the deprecated '
+ 'behavior of the "flat" shading option, '
+ 'in which a row and/or column of the data '
+ 'array is dropped.')
+ # pcolormesh, scatter, maybe others flatten their _A
+ self._alpha = self._alpha.reshape(self._A.shape)
+ self._mapped_colors = self.to_rgba(self._A, self._alpha)
+
+ if self._face_is_mapped:
+ self._facecolors = self._mapped_colors
+ else:
+ self._set_facecolor(self._original_facecolor)
+ if self._edge_is_mapped:
+ self._edgecolors = self._mapped_colors
+ else:
+ self._set_edgecolor(self._original_edgecolor)
+ self.stale = True
+
+ def get_fill(self):
+ """Return whether face is colored."""
+ return not cbook._str_lower_equal(self._original_facecolor, "none")
+
+ def update_from(self, other):
+ """Copy properties from other to self."""
+
+ artist.Artist.update_from(self, other)
+ self._antialiaseds = other._antialiaseds
+ self._mapped_colors = other._mapped_colors
+ self._edge_is_mapped = other._edge_is_mapped
+ self._original_edgecolor = other._original_edgecolor
+ self._edgecolors = other._edgecolors
+ self._face_is_mapped = other._face_is_mapped
+ self._original_facecolor = other._original_facecolor
+ self._facecolors = other._facecolors
+ self._linewidths = other._linewidths
+ self._linestyles = other._linestyles
+ self._us_linestyles = other._us_linestyles
+ self._pickradius = other._pickradius
+ self._hatch = other._hatch
+
+ # update_from for scalarmappable
+ self._A = other._A
+ self.norm = other.norm
+ self.cmap = other.cmap
+ self.stale = True
+
+
+class _CollectionWithSizes(Collection):
+ """
+ Base class for collections that have an array of sizes.
+ """
+ _factor = 1.0
+
+ def get_sizes(self):
+ """
+ Return the sizes ('areas') of the elements in the collection.
+
+ Returns
+ -------
+ array
+ The 'area' of each element.
+ """
+ return self._sizes
+
+ def set_sizes(self, sizes, dpi=72.0):
+ """
+ Set the sizes of each member of the collection.
+
+ Parameters
+ ----------
+ sizes : `numpy.ndarray` or None
+ The size to set for each element of the collection. The
+ value is the 'area' of the element.
+ dpi : float, default: 72
+ The dpi of the canvas.
+ """
+ if sizes is None:
+ self._sizes = np.array([])
+ self._transforms = np.empty((0, 3, 3))
+ else:
+ self._sizes = np.asarray(sizes)
+ self._transforms = np.zeros((len(self._sizes), 3, 3))
+ scale = np.sqrt(self._sizes) * dpi / 72.0 * self._factor
+ self._transforms[:, 0, 0] = scale
+ self._transforms[:, 1, 1] = scale
+ self._transforms[:, 2, 2] = 1.0
+ self.stale = True
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
+ super().draw(renderer)
+
+
+class PathCollection(_CollectionWithSizes):
+ r"""
+ A collection of `~.path.Path`\s, as created by e.g. `~.Axes.scatter`.
+ """
+
+ def __init__(self, paths, sizes=None, **kwargs):
+ """
+ Parameters
+ ----------
+ paths : list of `.path.Path`
+ The paths that will make up the `.Collection`.
+ sizes : array-like
+ The factor by which to scale each drawn `~.path.Path`. One unit
+ squared in the Path's data space is scaled to be ``sizes**2``
+ points when rendered.
+ **kwargs
+ Forwarded to `.Collection`.
+ """
+
+ super().__init__(**kwargs)
+ self.set_paths(paths)
+ self.set_sizes(sizes)
+ self.stale = True
+
+ def get_paths(self):
+ return self._paths
+
+ def legend_elements(self, prop="colors", num="auto",
+ fmt=None, func=lambda x: x, **kwargs):
+ """
+ Create legend handles and labels for a PathCollection.
+
+ Each legend handle is a `.Line2D` representing the Path that was drawn,
+ and each label is a string that represents the Path.
+
+ This is useful for obtaining a legend for a `~.Axes.scatter` plot;
+ e.g.::
+
+ scatter = plt.scatter([1, 2, 3], [4, 5, 6], c=[7, 2, 3], num=None)
+ plt.legend(*scatter.legend_elements())
+
+ creates three legend elements, one for each color with the numerical
+ values passed to *c* as the labels.
+
+ Also see the :ref:`automatedlegendcreation` example.
+
+ Parameters
+ ----------
+ prop : {"colors", "sizes"}, default: "colors"
+ If "colors", the legend handles will show the different colors of
+ the collection. If "sizes", the legend will show the different
+ sizes. To set both, use *kwargs* to directly edit the `.Line2D`
+ properties.
+ num : int, None, "auto" (default), array-like, or `~.ticker.Locator`
+ Target number of elements to create.
+ If None, use all unique elements of the mappable array. If an
+ integer, target to use *num* elements in the normed range.
+ If *"auto"*, try to determine which option better suits the nature
+ of the data.
+ The number of created elements may slightly deviate from *num* due
+ to a `~.ticker.Locator` being used to find useful locations.
+ If a list or array, use exactly those elements for the legend.
+ Finally, a `~.ticker.Locator` can be provided.
+ fmt : str, `~matplotlib.ticker.Formatter`, or None (default)
+ The format or formatter to use for the labels. If a string must be
+ a valid input for a `.StrMethodFormatter`. If None (the default),
+ use a `.ScalarFormatter`.
+ func : function, default: ``lambda x: x``
+ Function to calculate the labels. Often the size (or color)
+ argument to `~.Axes.scatter` will have been pre-processed by the
+ user using a function ``s = f(x)`` to make the markers visible;
+ e.g. ``size = np.log10(x)``. Providing the inverse of this
+ function here allows that pre-processing to be inverted, so that
+ the legend labels have the correct values; e.g. ``func = lambda
+ x: 10**x``.
+ **kwargs
+ Allowed keyword arguments are *color* and *size*. E.g. it may be
+ useful to set the color of the markers if *prop="sizes"* is used;
+ similarly to set the size of the markers if *prop="colors"* is
+ used. Any further parameters are passed onto the `.Line2D`
+ instance. This may be useful to e.g. specify a different
+ *markeredgecolor* or *alpha* for the legend handles.
+
+ Returns
+ -------
+ handles : list of `.Line2D`
+ Visual representation of each element of the legend.
+ labels : list of str
+ The string labels for elements of the legend.
+ """
+ handles = []
+ labels = []
+ hasarray = self.get_array() is not None
+ if fmt is None:
+ fmt = mpl.ticker.ScalarFormatter(useOffset=False, useMathText=True)
+ elif isinstance(fmt, str):
+ fmt = mpl.ticker.StrMethodFormatter(fmt)
+ fmt.create_dummy_axis()
+
+ if prop == "colors":
+ if not hasarray:
+ warnings.warn("Collection without array used. Make sure to "
+ "specify the values to be colormapped via the "
+ "`c` argument.")
+ return handles, labels
+ u = np.unique(self.get_array())
+ size = kwargs.pop("size", mpl.rcParams["lines.markersize"])
+ elif prop == "sizes":
+ u = np.unique(self.get_sizes())
+ color = kwargs.pop("color", "k")
+ else:
+ raise ValueError("Valid values for `prop` are 'colors' or "
+ f"'sizes'. You supplied '{prop}' instead.")
+
+ fu = func(u)
+ fmt.axis.set_view_interval(fu.min(), fu.max())
+ fmt.axis.set_data_interval(fu.min(), fu.max())
+ if num == "auto":
+ num = 9
+ if len(u) <= num:
+ num = None
+ if num is None:
+ values = u
+ label_values = func(values)
+ else:
+ if prop == "colors":
+ arr = self.get_array()
+ elif prop == "sizes":
+ arr = self.get_sizes()
+ if isinstance(num, mpl.ticker.Locator):
+ loc = num
+ elif np.iterable(num):
+ loc = mpl.ticker.FixedLocator(num)
+ else:
+ num = int(num)
+ loc = mpl.ticker.MaxNLocator(nbins=num, min_n_ticks=num-1,
+ steps=[1, 2, 2.5, 3, 5, 6, 8, 10])
+ label_values = loc.tick_values(func(arr).min(), func(arr).max())
+ cond = ((label_values >= func(arr).min()) &
+ (label_values <= func(arr).max()))
+ label_values = label_values[cond]
+ yarr = np.linspace(arr.min(), arr.max(), 256)
+ xarr = func(yarr)
+ ix = np.argsort(xarr)
+ values = np.interp(label_values, xarr[ix], yarr[ix])
+
+ kw = {"markeredgewidth": self.get_linewidths()[0],
+ "alpha": self.get_alpha(),
+ **kwargs}
+
+ for val, lab in zip(values, label_values):
+ if prop == "colors":
+ color = self.cmap(self.norm(val))
+ elif prop == "sizes":
+ size = np.sqrt(val)
+ if np.isclose(size, 0.0):
+ continue
+ h = mlines.Line2D([0], [0], ls="", color=color, ms=size,
+ marker=self.get_paths()[0], **kw)
+ handles.append(h)
+ if hasattr(fmt, "set_locs"):
+ fmt.set_locs(label_values)
+ l = fmt(lab)
+ labels.append(l)
+
+ return handles, labels
+
+
+class PolyCollection(_CollectionWithSizes):
+
+ def __init__(self, verts, sizes=None, *, closed=True, **kwargs):
+ """
+ Parameters
+ ----------
+ verts : list of array-like
+ The sequence of polygons [*verts0*, *verts1*, ...] where each
+ element *verts_i* defines the vertices of polygon *i* as a 2D
+ array-like of shape (M, 2).
+ sizes : array-like, default: None
+ Squared scaling factors for the polygons. The coordinates of each
+ polygon *verts_i* are multiplied by the square-root of the
+ corresponding entry in *sizes* (i.e., *sizes* specify the scaling
+ of areas). The scaling is applied before the Artist master
+ transform.
+ closed : bool, default: True
+ Whether the polygon should be closed by adding a CLOSEPOLY
+ connection at the end.
+ **kwargs
+ Forwarded to `.Collection`.
+ """
+ super().__init__(**kwargs)
+ self.set_sizes(sizes)
+ self.set_verts(verts, closed)
+ self.stale = True
+
+ def set_verts(self, verts, closed=True):
+ """
+ Set the vertices of the polygons.
+
+ Parameters
+ ----------
+ verts : list of array-like
+ The sequence of polygons [*verts0*, *verts1*, ...] where each
+ element *verts_i* defines the vertices of polygon *i* as a 2D
+ array-like of shape (M, 2).
+ closed : bool, default: True
+ Whether the polygon should be closed by adding a CLOSEPOLY
+ connection at the end.
+ """
+ self.stale = True
+ if isinstance(verts, np.ma.MaskedArray):
+ verts = verts.astype(float).filled(np.nan)
+
+ # No need to do anything fancy if the path isn't closed.
+ if not closed:
+ self._paths = [mpath.Path(xy) for xy in verts]
+ return
+
+ # Fast path for arrays
+ if isinstance(verts, np.ndarray) and len(verts.shape) == 3:
+ verts_pad = np.concatenate((verts, verts[:, :1]), axis=1)
+ # Creating the codes once is much faster than having Path do it
+ # separately each time by passing closed=True.
+ codes = np.empty(verts_pad.shape[1], dtype=mpath.Path.code_type)
+ codes[:] = mpath.Path.LINETO
+ codes[0] = mpath.Path.MOVETO
+ codes[-1] = mpath.Path.CLOSEPOLY
+ self._paths = [mpath.Path(xy, codes) for xy in verts_pad]
+ return
+
+ self._paths = []
+ for xy in verts:
+ if len(xy):
+ self._paths.append(mpath.Path._create_closed(xy))
+ else:
+ self._paths.append(mpath.Path(xy))
+
+ set_paths = set_verts
+
+ def set_verts_and_codes(self, verts, codes):
+ """Initialize vertices with path codes."""
+ if len(verts) != len(codes):
+ raise ValueError("'codes' must be a 1D list or array "
+ "with the same length of 'verts'")
+ self._paths = [mpath.Path(xy, cds) if len(xy) else mpath.Path(xy)
+ for xy, cds in zip(verts, codes)]
+ self.stale = True
+
+
+class FillBetweenPolyCollection(PolyCollection):
+ """
+ `.PolyCollection` that fills the area between two x- or y-curves.
+ """
+ def __init__(
+ self, t_direction, t, f1, f2, *,
+ where=None, interpolate=False, step=None, **kwargs):
+ """
+ Parameters
+ ----------
+ t_direction : {{'x', 'y'}}
+ The axes on which the variable lies.
+
+ - 'x': the curves are ``(t, f1)`` and ``(t, f2)``.
+ - 'y': the curves are ``(f1, t)`` and ``(f2, t)``.
+
+ t : array-like
+ The ``t_direction`` coordinates of the nodes defining the curves.
+
+ f1 : array-like or float
+ The other coordinates of the nodes defining the first curve.
+
+ f2 : array-like or float
+ The other coordinates of the nodes defining the second curve.
+
+ where : array-like of bool, optional
+ Define *where* to exclude some {dir} regions from being filled.
+ The filled regions are defined by the coordinates ``t[where]``.
+ More precisely, fill between ``t[i]`` and ``t[i+1]`` if
+ ``where[i] and where[i+1]``. Note that this definition implies
+ that an isolated *True* value between two *False* values in *where*
+ will not result in filling. Both sides of the *True* position
+ remain unfilled due to the adjacent *False* values.
+
+ interpolate : bool, default: False
+ This option is only relevant if *where* is used and the two curves
+ are crossing each other.
+
+ Semantically, *where* is often used for *f1* > *f2* or
+ similar. By default, the nodes of the polygon defining the filled
+ region will only be placed at the positions in the *t* array.
+ Such a polygon cannot describe the above semantics close to the
+ intersection. The t-sections containing the intersection are
+ simply clipped.
+
+ Setting *interpolate* to *True* will calculate the actual
+ intersection point and extend the filled region up to this point.
+
+ step : {{'pre', 'post', 'mid'}}, optional
+ Define *step* if the filling should be a step function,
+ i.e. constant in between *t*. The value determines where the
+ step will occur:
+
+ - 'pre': The f value is continued constantly to the left from
+ every *t* position, i.e. the interval ``(t[i-1], t[i]]`` has the
+ value ``f[i]``.
+ - 'post': The y value is continued constantly to the right from
+ every *x* position, i.e. the interval ``[t[i], t[i+1])`` has the
+ value ``f[i]``.
+ - 'mid': Steps occur half-way between the *t* positions.
+
+ **kwargs
+ Forwarded to `.PolyCollection`.
+
+ See Also
+ --------
+ .Axes.fill_between, .Axes.fill_betweenx
+ """
+ self.t_direction = t_direction
+ self._interpolate = interpolate
+ self._step = step
+ verts = self._make_verts(t, f1, f2, where)
+ super().__init__(verts, **kwargs)
+
+ @staticmethod
+ def _f_dir_from_t(t_direction):
+ """The direction that is other than `t_direction`."""
+ if t_direction == "x":
+ return "y"
+ elif t_direction == "y":
+ return "x"
+ else:
+ msg = f"t_direction must be 'x' or 'y', got {t_direction!r}"
+ raise ValueError(msg)
+
+ @property
+ def _f_direction(self):
+ """The direction that is other than `self.t_direction`."""
+ return self._f_dir_from_t(self.t_direction)
+
+ def set_data(self, t, f1, f2, *, where=None):
+ """
+ Set new values for the two bounding curves.
+
+ Parameters
+ ----------
+ t : array-like
+ The ``self.t_direction`` coordinates of the nodes defining the curves.
+
+ f1 : array-like or float
+ The other coordinates of the nodes defining the first curve.
+
+ f2 : array-like or float
+ The other coordinates of the nodes defining the second curve.
+
+ where : array-like of bool, optional
+ Define *where* to exclude some {dir} regions from being filled.
+ The filled regions are defined by the coordinates ``t[where]``.
+ More precisely, fill between ``t[i]`` and ``t[i+1]`` if
+ ``where[i] and where[i+1]``. Note that this definition implies
+ that an isolated *True* value between two *False* values in *where*
+ will not result in filling. Both sides of the *True* position
+ remain unfilled due to the adjacent *False* values.
+
+ See Also
+ --------
+ .PolyCollection.set_verts, .Line2D.set_data
+ """
+ t, f1, f2 = self.axes._fill_between_process_units(
+ self.t_direction, self._f_direction, t, f1, f2)
+
+ verts = self._make_verts(t, f1, f2, where)
+ self.set_verts(verts)
+
+ def get_datalim(self, transData):
+ """Calculate the data limits and return them as a `.Bbox`."""
+ datalim = transforms.Bbox.null()
+ datalim.update_from_data_xy((self.get_transform() - transData).transform(
+ np.concatenate([self._bbox, [self._bbox.minpos]])))
+ return datalim
+
+ def _make_verts(self, t, f1, f2, where):
+ """
+ Make verts that can be forwarded to `.PolyCollection`.
+ """
+ self._validate_shapes(self.t_direction, self._f_direction, t, f1, f2)
+
+ where = self._get_data_mask(t, f1, f2, where)
+ t, f1, f2 = np.broadcast_arrays(np.atleast_1d(t), f1, f2, subok=True)
+
+ self._bbox = transforms.Bbox.null()
+ self._bbox.update_from_data_xy(self._fix_pts_xy_order(np.concatenate([
+ np.stack((t[where], f[where]), axis=-1) for f in (f1, f2)])))
+
+ return [
+ self._make_verts_for_region(t, f1, f2, idx0, idx1)
+ for idx0, idx1 in cbook.contiguous_regions(where)
+ ]
+
+ def _get_data_mask(self, t, f1, f2, where):
+ """
+ Return a bool array, with True at all points that should eventually be rendered.
+
+ The array is True at a point if none of the data inputs
+ *t*, *f1*, *f2* is masked and if the input *where* is true at that point.
+ """
+ if where is None:
+ where = True
+ else:
+ where = np.asarray(where, dtype=bool)
+ if where.size != t.size:
+ msg = "where size ({}) does not match {!r} size ({})".format(
+ where.size, self.t_direction, t.size)
+ raise ValueError(msg)
+ return where & ~functools.reduce(
+ np.logical_or, map(np.ma.getmaskarray, [t, f1, f2]))
+
+ @staticmethod
+ def _validate_shapes(t_dir, f_dir, t, f1, f2):
+ """Validate that t, f1 and f2 are 1-dimensional and have the same length."""
+ names = (d + s for d, s in zip((t_dir, f_dir, f_dir), ("", "1", "2")))
+ for name, array in zip(names, [t, f1, f2]):
+ if array.ndim > 1:
+ raise ValueError(f"{name!r} is not 1-dimensional")
+ if t.size > 1 and array.size > 1 and t.size != array.size:
+ msg = "{!r} has size {}, but {!r} has an unequal size of {}".format(
+ t_dir, t.size, name, array.size)
+ raise ValueError(msg)
+
+ def _make_verts_for_region(self, t, f1, f2, idx0, idx1):
+ """
+ Make ``verts`` for a contiguous region between ``idx0`` and ``idx1``, taking
+ into account ``step`` and ``interpolate``.
+ """
+ t_slice = t[idx0:idx1]
+ f1_slice = f1[idx0:idx1]
+ f2_slice = f2[idx0:idx1]
+ if self._step is not None:
+ step_func = cbook.STEP_LOOKUP_MAP["steps-" + self._step]
+ t_slice, f1_slice, f2_slice = step_func(t_slice, f1_slice, f2_slice)
+
+ if self._interpolate:
+ start = self._get_interpolating_points(t, f1, f2, idx0)
+ end = self._get_interpolating_points(t, f1, f2, idx1)
+ else:
+ # Handle scalar f2 (e.g. 0): the fill should go all
+ # the way down to 0 even if none of the dep1 sample points do.
+ start = t_slice[0], f2_slice[0]
+ end = t_slice[-1], f2_slice[-1]
+
+ pts = np.concatenate((
+ np.asarray([start]),
+ np.stack((t_slice, f1_slice), axis=-1),
+ np.asarray([end]),
+ np.stack((t_slice, f2_slice), axis=-1)[::-1]))
+
+ return self._fix_pts_xy_order(pts)
+
+ @classmethod
+ def _get_interpolating_points(cls, t, f1, f2, idx):
+ """Calculate interpolating points."""
+ im1 = max(idx - 1, 0)
+ t_values = t[im1:idx+1]
+ diff_values = f1[im1:idx+1] - f2[im1:idx+1]
+ f1_values = f1[im1:idx+1]
+
+ if len(diff_values) == 2:
+ if np.ma.is_masked(diff_values[1]):
+ return t[im1], f1[im1]
+ elif np.ma.is_masked(diff_values[0]):
+ return t[idx], f1[idx]
+
+ diff_root_t = cls._get_diff_root(0, diff_values, t_values)
+ diff_root_f = cls._get_diff_root(diff_root_t, t_values, f1_values)
+ return diff_root_t, diff_root_f
+
+ @staticmethod
+ def _get_diff_root(x, xp, fp):
+ """Calculate diff root."""
+ order = xp.argsort()
+ return np.interp(x, xp[order], fp[order])
+
+ def _fix_pts_xy_order(self, pts):
+ """
+ Fix pts calculation results with `self.t_direction`.
+
+ In the workflow, it is assumed that `self.t_direction` is 'x'. If this
+ is not true, we need to exchange the coordinates.
+ """
+ return pts[:, ::-1] if self.t_direction == "y" else pts
+
+
+class RegularPolyCollection(_CollectionWithSizes):
+ """A collection of n-sided regular polygons."""
+
+ _path_generator = mpath.Path.unit_regular_polygon
+ _factor = np.pi ** (-1/2)
+
+ def __init__(self,
+ numsides,
+ *,
+ rotation=0,
+ sizes=(1,),
+ **kwargs):
+ """
+ Parameters
+ ----------
+ numsides : int
+ The number of sides of the polygon.
+ rotation : float
+ The rotation of the polygon in radians.
+ sizes : tuple of float
+ The area of the circle circumscribing the polygon in points^2.
+ **kwargs
+ Forwarded to `.Collection`.
+
+ Examples
+ --------
+ See :doc:`/gallery/event_handling/lasso_demo` for a complete example::
+
+ offsets = np.random.rand(20, 2)
+ facecolors = [cm.jet(x) for x in np.random.rand(20)]
+
+ collection = RegularPolyCollection(
+ numsides=5, # a pentagon
+ rotation=0, sizes=(50,),
+ facecolors=facecolors,
+ edgecolors=("black",),
+ linewidths=(1,),
+ offsets=offsets,
+ offset_transform=ax.transData,
+ )
+ """
+ super().__init__(**kwargs)
+ self.set_sizes(sizes)
+ self._numsides = numsides
+ self._paths = [self._path_generator(numsides)]
+ self._rotation = rotation
+ self.set_transform(transforms.IdentityTransform())
+
+ def get_numsides(self):
+ return self._numsides
+
+ def get_rotation(self):
+ return self._rotation
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
+ self._transforms = [
+ transforms.Affine2D(x).rotate(-self._rotation).get_matrix()
+ for x in self._transforms
+ ]
+ # Explicitly not super().draw, because set_sizes must be called before
+ # updating self._transforms.
+ Collection.draw(self, renderer)
+
+
+class StarPolygonCollection(RegularPolyCollection):
+ """Draw a collection of regular stars with *numsides* points."""
+ _path_generator = mpath.Path.unit_regular_star
+
+
+class AsteriskPolygonCollection(RegularPolyCollection):
+ """Draw a collection of regular asterisks with *numsides* points."""
+ _path_generator = mpath.Path.unit_regular_asterisk
+
+
+class LineCollection(Collection):
+ r"""
+ Represents a sequence of `.Line2D`\s that should be drawn together.
+
+ This class extends `.Collection` to represent a sequence of
+ `.Line2D`\s instead of just a sequence of `.Patch`\s.
+ Just as in `.Collection`, each property of a *LineCollection* may be either
+ a single value or a list of values. This list is then used cyclically for
+ each element of the LineCollection, so the property of the ``i``\th element
+ of the collection is::
+
+ prop[i % len(prop)]
+
+ The properties of each member of a *LineCollection* default to their values
+ in :rc:`lines.*` instead of :rc:`patch.*`, and the property *colors* is
+ added in place of *edgecolors*.
+ """
+
+ _edge_default = True
+
+ def __init__(self, segments, # Can be None.
+ *,
+ zorder=2, # Collection.zorder is 1
+ **kwargs
+ ):
+ """
+ Parameters
+ ----------
+ segments : list of (N, 2) array-like
+ A sequence ``[line0, line1, ...]`` where each line is a (N, 2)-shape
+ array-like containing points::
+
+ line0 = [(x0, y0), (x1, y1), ...]
+
+ Each line can contain a different number of points.
+ linewidths : float or list of float, default: :rc:`lines.linewidth`
+ The width of each line in points.
+ colors : :mpltype:`color` or list of color, default: :rc:`lines.color`
+ A sequence of RGBA tuples (e.g., arbitrary color strings, etc, not
+ allowed).
+ antialiaseds : bool or list of bool, default: :rc:`lines.antialiased`
+ Whether to use antialiasing for each line.
+ zorder : float, default: 2
+ zorder of the lines once drawn.
+
+ facecolors : :mpltype:`color` or list of :mpltype:`color`, default: 'none'
+ When setting *facecolors*, each line is interpreted as a boundary
+ for an area, implicitly closing the path from the last point to the
+ first point. The enclosed area is filled with *facecolor*.
+ In order to manually specify what should count as the "interior" of
+ each line, please use `.PathCollection` instead, where the
+ "interior" can be specified by appropriate usage of
+ `~.path.Path.CLOSEPOLY`.
+
+ **kwargs
+ Forwarded to `.Collection`.
+ """
+ # Unfortunately, mplot3d needs this explicit setting of 'facecolors'.
+ kwargs.setdefault('facecolors', 'none')
+ super().__init__(
+ zorder=zorder,
+ **kwargs)
+ self.set_segments(segments)
+
+ def set_segments(self, segments):
+ if segments is None:
+ return
+
+ self._paths = [mpath.Path(seg) if isinstance(seg, np.ma.MaskedArray)
+ else mpath.Path(np.asarray(seg, float))
+ for seg in segments]
+ self.stale = True
+
+ set_verts = set_segments # for compatibility with PolyCollection
+ set_paths = set_segments
+
+ def get_segments(self):
+ """
+ Returns
+ -------
+ list
+ List of segments in the LineCollection. Each list item contains an
+ array of vertices.
+ """
+ segments = []
+
+ for path in self._paths:
+ vertices = [
+ vertex
+ for vertex, _
+ # Never simplify here, we want to get the data-space values
+ # back and there in no way to know the "right" simplification
+ # threshold so never try.
+ in path.iter_segments(simplify=False)
+ ]
+ vertices = np.asarray(vertices)
+ segments.append(vertices)
+
+ return segments
+
+ def _get_default_linewidth(self):
+ return mpl.rcParams['lines.linewidth']
+
+ def _get_default_antialiased(self):
+ return mpl.rcParams['lines.antialiased']
+
+ def _get_default_edgecolor(self):
+ return mpl.rcParams['lines.color']
+
+ def _get_default_facecolor(self):
+ return 'none'
+
+ def set_alpha(self, alpha):
+ # docstring inherited
+ super().set_alpha(alpha)
+ if self._gapcolor is not None:
+ self.set_gapcolor(self._original_gapcolor)
+
+ def set_color(self, c):
+ """
+ Set the edgecolor(s) of the LineCollection.
+
+ Parameters
+ ----------
+ c : :mpltype:`color` or list of :mpltype:`color`
+ Single color (all lines have same color), or a
+ sequence of RGBA tuples; if it is a sequence the lines will
+ cycle through the sequence.
+ """
+ self.set_edgecolor(c)
+
+ set_colors = set_color
+
+ def get_color(self):
+ return self._edgecolors
+
+ get_colors = get_color # for compatibility with old versions
+
+ def set_gapcolor(self, gapcolor):
+ """
+ Set a color to fill the gaps in the dashed line style.
+
+ .. note::
+
+ Striped lines are created by drawing two interleaved dashed lines.
+ There can be overlaps between those two, which may result in
+ artifacts when using transparency.
+
+ This functionality is experimental and may change.
+
+ Parameters
+ ----------
+ gapcolor : :mpltype:`color` or list of :mpltype:`color` or None
+ The color with which to fill the gaps. If None, the gaps are
+ unfilled.
+ """
+ self._original_gapcolor = gapcolor
+ self._set_gapcolor(gapcolor)
+
+ def _set_gapcolor(self, gapcolor):
+ if gapcolor is not None:
+ gapcolor = mcolors.to_rgba_array(gapcolor, self._alpha)
+ self._gapcolor = gapcolor
+ self.stale = True
+
+ def get_gapcolor(self):
+ return self._gapcolor
+
+ def _get_inverse_paths_linestyles(self):
+ """
+ Returns the path and pattern for the gaps in the non-solid lines.
+
+ This path and pattern is the inverse of the path and pattern used to
+ construct the non-solid lines. For solid lines, we set the inverse path
+ to nans to prevent drawing an inverse line.
+ """
+ path_patterns = [
+ (mpath.Path(np.full((1, 2), np.nan)), ls)
+ if ls == (0, None) else
+ (path, mlines._get_inverse_dash_pattern(*ls))
+ for (path, ls) in
+ zip(self._paths, itertools.cycle(self._linestyles))]
+
+ return zip(*path_patterns)
+
+
+class EventCollection(LineCollection):
+ """
+ A collection of locations along a single axis at which an "event" occurred.
+
+ The events are given by a 1-dimensional array. They do not have an
+ amplitude and are displayed as parallel lines.
+ """
+
+ _edge_default = True
+
+ def __init__(self,
+ positions, # Cannot be None.
+ orientation='horizontal',
+ *,
+ lineoffset=0,
+ linelength=1,
+ linewidth=None,
+ color=None,
+ linestyle='solid',
+ antialiased=None,
+ **kwargs
+ ):
+ """
+ Parameters
+ ----------
+ positions : 1D array-like
+ Each value is an event.
+ orientation : {'horizontal', 'vertical'}, default: 'horizontal'
+ The sequence of events is plotted along this direction.
+ The marker lines of the single events are along the orthogonal
+ direction.
+ lineoffset : float, default: 0
+ The offset of the center of the markers from the origin, in the
+ direction orthogonal to *orientation*.
+ linelength : float, default: 1
+ The total height of the marker (i.e. the marker stretches from
+ ``lineoffset - linelength/2`` to ``lineoffset + linelength/2``).
+ linewidth : float or list thereof, default: :rc:`lines.linewidth`
+ The line width of the event lines, in points.
+ color : :mpltype:`color` or list of :mpltype:`color`, default: :rc:`lines.color`
+ The color of the event lines.
+ linestyle : str or tuple or list thereof, default: 'solid'
+ Valid strings are ['solid', 'dashed', 'dashdot', 'dotted',
+ '-', '--', '-.', ':']. Dash tuples should be of the form::
+
+ (offset, onoffseq),
+
+ where *onoffseq* is an even length tuple of on and off ink
+ in points.
+ antialiased : bool or list thereof, default: :rc:`lines.antialiased`
+ Whether to use antialiasing for drawing the lines.
+ **kwargs
+ Forwarded to `.LineCollection`.
+
+ Examples
+ --------
+ .. plot:: gallery/lines_bars_and_markers/eventcollection_demo.py
+ """
+ super().__init__([],
+ linewidths=linewidth, linestyles=linestyle,
+ colors=color, antialiaseds=antialiased,
+ **kwargs)
+ self._is_horizontal = True # Initial value, may be switched below.
+ self._linelength = linelength
+ self._lineoffset = lineoffset
+ self.set_orientation(orientation)
+ self.set_positions(positions)
+
+ def get_positions(self):
+ """
+ Return an array containing the floating-point values of the positions.
+ """
+ pos = 0 if self.is_horizontal() else 1
+ return [segment[0, pos] for segment in self.get_segments()]
+
+ def set_positions(self, positions):
+ """Set the positions of the events."""
+ if positions is None:
+ positions = []
+ if np.ndim(positions) != 1:
+ raise ValueError('positions must be one-dimensional')
+ lineoffset = self.get_lineoffset()
+ linelength = self.get_linelength()
+ pos_idx = 0 if self.is_horizontal() else 1
+ segments = np.empty((len(positions), 2, 2))
+ segments[:, :, pos_idx] = np.sort(positions)[:, None]
+ segments[:, 0, 1 - pos_idx] = lineoffset + linelength / 2
+ segments[:, 1, 1 - pos_idx] = lineoffset - linelength / 2
+ self.set_segments(segments)
+
+ def add_positions(self, position):
+ """Add one or more events at the specified positions."""
+ if position is None or (hasattr(position, 'len') and
+ len(position) == 0):
+ return
+ positions = self.get_positions()
+ positions = np.hstack([positions, np.asanyarray(position)])
+ self.set_positions(positions)
+ extend_positions = append_positions = add_positions
+
+ def is_horizontal(self):
+ """True if the eventcollection is horizontal, False if vertical."""
+ return self._is_horizontal
+
+ def get_orientation(self):
+ """
+ Return the orientation of the event line ('horizontal' or 'vertical').
+ """
+ return 'horizontal' if self.is_horizontal() else 'vertical'
+
+ def switch_orientation(self):
+ """
+ Switch the orientation of the event line, either from vertical to
+ horizontal or vice versus.
+ """
+ segments = self.get_segments()
+ for i, segment in enumerate(segments):
+ segments[i] = np.fliplr(segment)
+ self.set_segments(segments)
+ self._is_horizontal = not self.is_horizontal()
+ self.stale = True
+
+ def set_orientation(self, orientation):
+ """
+ Set the orientation of the event line.
+
+ Parameters
+ ----------
+ orientation : {'horizontal', 'vertical'}
+ """
+ is_horizontal = _api.check_getitem(
+ {"horizontal": True, "vertical": False},
+ orientation=orientation)
+ if is_horizontal == self.is_horizontal():
+ return
+ self.switch_orientation()
+
+ def get_linelength(self):
+ """Return the length of the lines used to mark each event."""
+ return self._linelength
+
+ def set_linelength(self, linelength):
+ """Set the length of the lines used to mark each event."""
+ if linelength == self.get_linelength():
+ return
+ lineoffset = self.get_lineoffset()
+ segments = self.get_segments()
+ pos = 1 if self.is_horizontal() else 0
+ for segment in segments:
+ segment[0, pos] = lineoffset + linelength / 2.
+ segment[1, pos] = lineoffset - linelength / 2.
+ self.set_segments(segments)
+ self._linelength = linelength
+
+ def get_lineoffset(self):
+ """Return the offset of the lines used to mark each event."""
+ return self._lineoffset
+
+ def set_lineoffset(self, lineoffset):
+ """Set the offset of the lines used to mark each event."""
+ if lineoffset == self.get_lineoffset():
+ return
+ linelength = self.get_linelength()
+ segments = self.get_segments()
+ pos = 1 if self.is_horizontal() else 0
+ for segment in segments:
+ segment[0, pos] = lineoffset + linelength / 2.
+ segment[1, pos] = lineoffset - linelength / 2.
+ self.set_segments(segments)
+ self._lineoffset = lineoffset
+
+ def get_linewidth(self):
+ """Get the width of the lines used to mark each event."""
+ return super().get_linewidth()[0]
+
+ def get_linewidths(self):
+ return super().get_linewidth()
+
+ def get_color(self):
+ """Return the color of the lines used to mark each event."""
+ return self.get_colors()[0]
+
+
+class CircleCollection(_CollectionWithSizes):
+ """A collection of circles, drawn using splines."""
+
+ _factor = np.pi ** (-1/2)
+
+ def __init__(self, sizes, **kwargs):
+ """
+ Parameters
+ ----------
+ sizes : float or array-like
+ The area of each circle in points^2.
+ **kwargs
+ Forwarded to `.Collection`.
+ """
+ super().__init__(**kwargs)
+ self.set_sizes(sizes)
+ self.set_transform(transforms.IdentityTransform())
+ self._paths = [mpath.Path.unit_circle()]
+
+
+class EllipseCollection(Collection):
+ """A collection of ellipses, drawn using splines."""
+
+ def __init__(self, widths, heights, angles, *, units='points', **kwargs):
+ """
+ Parameters
+ ----------
+ widths : array-like
+ The lengths of the first axes (e.g., major axis lengths).
+ heights : array-like
+ The lengths of second axes.
+ angles : array-like
+ The angles of the first axes, degrees CCW from the x-axis.
+ units : {'points', 'inches', 'dots', 'width', 'height', 'x', 'y', 'xy'}
+ The units in which majors and minors are given; 'width' and
+ 'height' refer to the dimensions of the axes, while 'x' and 'y'
+ refer to the *offsets* data units. 'xy' differs from all others in
+ that the angle as plotted varies with the aspect ratio, and equals
+ the specified angle only when the aspect ratio is unity. Hence
+ it behaves the same as the `~.patches.Ellipse` with
+ ``axes.transData`` as its transform.
+ **kwargs
+ Forwarded to `Collection`.
+ """
+ super().__init__(**kwargs)
+ self.set_widths(widths)
+ self.set_heights(heights)
+ self.set_angles(angles)
+ self._units = units
+ self.set_transform(transforms.IdentityTransform())
+ self._transforms = np.empty((0, 3, 3))
+ self._paths = [mpath.Path.unit_circle()]
+
+ def _set_transforms(self):
+ """Calculate transforms immediately before drawing."""
+
+ ax = self.axes
+ fig = self.get_figure(root=False)
+
+ if self._units == 'xy':
+ sc = 1
+ elif self._units == 'x':
+ sc = ax.bbox.width / ax.viewLim.width
+ elif self._units == 'y':
+ sc = ax.bbox.height / ax.viewLim.height
+ elif self._units == 'inches':
+ sc = fig.dpi
+ elif self._units == 'points':
+ sc = fig.dpi / 72.0
+ elif self._units == 'width':
+ sc = ax.bbox.width
+ elif self._units == 'height':
+ sc = ax.bbox.height
+ elif self._units == 'dots':
+ sc = 1.0
+ else:
+ raise ValueError(f'Unrecognized units: {self._units!r}')
+
+ self._transforms = np.zeros((len(self._widths), 3, 3))
+ widths = self._widths * sc
+ heights = self._heights * sc
+ sin_angle = np.sin(self._angles)
+ cos_angle = np.cos(self._angles)
+ self._transforms[:, 0, 0] = widths * cos_angle
+ self._transforms[:, 0, 1] = heights * -sin_angle
+ self._transforms[:, 1, 0] = widths * sin_angle
+ self._transforms[:, 1, 1] = heights * cos_angle
+ self._transforms[:, 2, 2] = 1.0
+
+ _affine = transforms.Affine2D
+ if self._units == 'xy':
+ m = ax.transData.get_affine().get_matrix().copy()
+ m[:2, 2:] = 0
+ self.set_transform(_affine(m))
+
+ def set_widths(self, widths):
+ """Set the lengths of the first axes (e.g., major axis)."""
+ self._widths = 0.5 * np.asarray(widths).ravel()
+ self.stale = True
+
+ def set_heights(self, heights):
+ """Set the lengths of second axes (e.g., minor axes)."""
+ self._heights = 0.5 * np.asarray(heights).ravel()
+ self.stale = True
+
+ def set_angles(self, angles):
+ """Set the angles of the first axes, degrees CCW from the x-axis."""
+ self._angles = np.deg2rad(angles).ravel()
+ self.stale = True
+
+ def get_widths(self):
+ """Get the lengths of the first axes (e.g., major axis)."""
+ return self._widths * 2
+
+ def get_heights(self):
+ """Set the lengths of second axes (e.g., minor axes)."""
+ return self._heights * 2
+
+ def get_angles(self):
+ """Get the angles of the first axes, degrees CCW from the x-axis."""
+ return np.rad2deg(self._angles)
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ self._set_transforms()
+ super().draw(renderer)
+
+
+class PatchCollection(Collection):
+ """
+ A generic collection of patches.
+
+ PatchCollection draws faster than a large number of equivalent individual
+ Patches. It also makes it easier to assign a colormap to a heterogeneous
+ collection of patches.
+ """
+
+ def __init__(self, patches, *, match_original=False, **kwargs):
+ """
+ Parameters
+ ----------
+ patches : list of `.Patch`
+ A sequence of Patch objects. This list may include
+ a heterogeneous assortment of different patch types.
+
+ match_original : bool, default: False
+ If True, use the colors and linewidths of the original
+ patches. If False, new colors may be assigned by
+ providing the standard collection arguments, facecolor,
+ edgecolor, linewidths, norm or cmap.
+
+ **kwargs
+ All other parameters are forwarded to `.Collection`.
+
+ If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds*
+ are None, they default to their `.rcParams` patch setting, in
+ sequence form.
+
+ Notes
+ -----
+ The use of `~matplotlib.cm.ScalarMappable` functionality is optional.
+ If the `~matplotlib.cm.ScalarMappable` matrix ``_A`` has been set (via
+ a call to `~.ScalarMappable.set_array`), at draw time a call to scalar
+ mappable will be made to set the face colors.
+ """
+
+ if match_original:
+ def determine_facecolor(patch):
+ if patch.get_fill():
+ return patch.get_facecolor()
+ return [0, 0, 0, 0]
+
+ kwargs['facecolors'] = [determine_facecolor(p) for p in patches]
+ kwargs['edgecolors'] = [p.get_edgecolor() for p in patches]
+ kwargs['linewidths'] = [p.get_linewidth() for p in patches]
+ kwargs['linestyles'] = [p.get_linestyle() for p in patches]
+ kwargs['antialiaseds'] = [p.get_antialiased() for p in patches]
+
+ super().__init__(**kwargs)
+
+ self.set_paths(patches)
+
+ def set_paths(self, patches):
+ paths = [p.get_transform().transform_path(p.get_path())
+ for p in patches]
+ self._paths = paths
+
+
+class TriMesh(Collection):
+ """
+ Class for the efficient drawing of a triangular mesh using Gouraud shading.
+
+ A triangular mesh is a `~matplotlib.tri.Triangulation` object.
+ """
+ def __init__(self, triangulation, **kwargs):
+ super().__init__(**kwargs)
+ self._triangulation = triangulation
+ self._shading = 'gouraud'
+
+ self._bbox = transforms.Bbox.unit()
+
+ # Unfortunately this requires a copy, unless Triangulation
+ # was rewritten.
+ xy = np.hstack((triangulation.x.reshape(-1, 1),
+ triangulation.y.reshape(-1, 1)))
+ self._bbox.update_from_data_xy(xy)
+
+ def get_paths(self):
+ if self._paths is None:
+ self.set_paths()
+ return self._paths
+
+ def set_paths(self):
+ self._paths = self.convert_mesh_to_paths(self._triangulation)
+
+ @staticmethod
+ def convert_mesh_to_paths(tri):
+ """
+ Convert a given mesh into a sequence of `.Path` objects.
+
+ This function is primarily of use to implementers of backends that do
+ not directly support meshes.
+ """
+ triangles = tri.get_masked_triangles()
+ verts = np.stack((tri.x[triangles], tri.y[triangles]), axis=-1)
+ return [mpath.Path(x) for x in verts]
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ if not self.get_visible():
+ return
+ renderer.open_group(self.__class__.__name__, gid=self.get_gid())
+ transform = self.get_transform()
+
+ # Get a list of triangles and the color at each vertex.
+ tri = self._triangulation
+ triangles = tri.get_masked_triangles()
+
+ verts = np.stack((tri.x[triangles], tri.y[triangles]), axis=-1)
+
+ self.update_scalarmappable()
+ colors = self._facecolors[triangles]
+
+ gc = renderer.new_gc()
+ self._set_gc_clip(gc)
+ gc.set_linewidth(self.get_linewidth()[0])
+ renderer.draw_gouraud_triangles(gc, verts, colors, transform.frozen())
+ gc.restore()
+ renderer.close_group(self.__class__.__name__)
+
+
+class _MeshData:
+ r"""
+ Class for managing the two dimensional coordinates of Quadrilateral meshes
+ and the associated data with them. This class is a mixin and is intended to
+ be used with another collection that will implement the draw separately.
+
+ A quadrilateral mesh is a grid of M by N adjacent quadrilaterals that are
+ defined via a (M+1, N+1) grid of vertices. The quadrilateral (m, n) is
+ defined by the vertices ::
+
+ (m+1, n) ----------- (m+1, n+1)
+ / /
+ / /
+ / /
+ (m, n) -------- (m, n+1)
+
+ The mesh need not be regular and the polygons need not be convex.
+
+ Parameters
+ ----------
+ coordinates : (M+1, N+1, 2) array-like
+ The vertices. ``coordinates[m, n]`` specifies the (x, y) coordinates
+ of vertex (m, n).
+
+ shading : {'flat', 'gouraud'}, default: 'flat'
+ """
+ def __init__(self, coordinates, *, shading='flat'):
+ _api.check_shape((None, None, 2), coordinates=coordinates)
+ self._coordinates = coordinates
+ self._shading = shading
+
+ def set_array(self, A):
+ """
+ Set the data values.
+
+ Parameters
+ ----------
+ A : array-like
+ The mesh data. Supported array shapes are:
+
+ - (M, N) or (M*N,): a mesh with scalar data. The values are mapped
+ to colors using normalization and a colormap. See parameters
+ *norm*, *cmap*, *vmin*, *vmax*.
+ - (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
+ - (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
+ i.e. including transparency.
+
+ If the values are provided as a 2D grid, the shape must match the
+ coordinates grid. If the values are 1D, they are reshaped to 2D.
+ M, N follow from the coordinates grid, where the coordinates grid
+ shape is (M, N) for 'gouraud' *shading* and (M+1, N+1) for 'flat'
+ shading.
+ """
+ height, width = self._coordinates.shape[0:-1]
+ if self._shading == 'flat':
+ h, w = height - 1, width - 1
+ else:
+ h, w = height, width
+ ok_shapes = [(h, w, 3), (h, w, 4), (h, w), (h * w,)]
+ if A is not None:
+ shape = np.shape(A)
+ if shape not in ok_shapes:
+ raise ValueError(
+ f"For X ({width}) and Y ({height}) with {self._shading} "
+ f"shading, A should have shape "
+ f"{' or '.join(map(str, ok_shapes))}, not {A.shape}")
+ return super().set_array(A)
+
+ def get_coordinates(self):
+ """
+ Return the vertices of the mesh as an (M+1, N+1, 2) array.
+
+ M, N are the number of quadrilaterals in the rows / columns of the
+ mesh, corresponding to (M+1, N+1) vertices.
+ The last dimension specifies the components (x, y).
+ """
+ return self._coordinates
+
+ def get_edgecolor(self):
+ # docstring inherited
+ # Note that we want to return an array of shape (N*M, 4)
+ # a flattened RGBA collection
+ return super().get_edgecolor().reshape(-1, 4)
+
+ def get_facecolor(self):
+ # docstring inherited
+ # Note that we want to return an array of shape (N*M, 4)
+ # a flattened RGBA collection
+ return super().get_facecolor().reshape(-1, 4)
+
+ @staticmethod
+ def _convert_mesh_to_paths(coordinates):
+ """
+ Convert a given mesh into a sequence of `.Path` objects.
+
+ This function is primarily of use to implementers of backends that do
+ not directly support quadmeshes.
+ """
+ if isinstance(coordinates, np.ma.MaskedArray):
+ c = coordinates.data
+ else:
+ c = coordinates
+ points = np.concatenate([
+ c[:-1, :-1],
+ c[:-1, 1:],
+ c[1:, 1:],
+ c[1:, :-1],
+ c[:-1, :-1]
+ ], axis=2).reshape((-1, 5, 2))
+ return [mpath.Path(x) for x in points]
+
+ def _convert_mesh_to_triangles(self, coordinates):
+ """
+ Convert a given mesh into a sequence of triangles, each point
+ with its own color. The result can be used to construct a call to
+ `~.RendererBase.draw_gouraud_triangles`.
+ """
+ if isinstance(coordinates, np.ma.MaskedArray):
+ p = coordinates.data
+ else:
+ p = coordinates
+
+ p_a = p[:-1, :-1]
+ p_b = p[:-1, 1:]
+ p_c = p[1:, 1:]
+ p_d = p[1:, :-1]
+ p_center = (p_a + p_b + p_c + p_d) / 4.0
+ triangles = np.concatenate([
+ p_a, p_b, p_center,
+ p_b, p_c, p_center,
+ p_c, p_d, p_center,
+ p_d, p_a, p_center,
+ ], axis=2).reshape((-1, 3, 2))
+
+ c = self.get_facecolor().reshape((*coordinates.shape[:2], 4))
+ z = self.get_array()
+ mask = z.mask if np.ma.is_masked(z) else None
+ if mask is not None:
+ c[mask, 3] = np.nan
+ c_a = c[:-1, :-1]
+ c_b = c[:-1, 1:]
+ c_c = c[1:, 1:]
+ c_d = c[1:, :-1]
+ c_center = (c_a + c_b + c_c + c_d) / 4.0
+ colors = np.concatenate([
+ c_a, c_b, c_center,
+ c_b, c_c, c_center,
+ c_c, c_d, c_center,
+ c_d, c_a, c_center,
+ ], axis=2).reshape((-1, 3, 4))
+ tmask = np.isnan(colors[..., 2, 3])
+ return triangles[~tmask], colors[~tmask]
+
+
+class QuadMesh(_MeshData, Collection):
+ r"""
+ Class for the efficient drawing of a quadrilateral mesh.
+
+ A quadrilateral mesh is a grid of M by N adjacent quadrilaterals that are
+ defined via a (M+1, N+1) grid of vertices. The quadrilateral (m, n) is
+ defined by the vertices ::
+
+ (m+1, n) ----------- (m+1, n+1)
+ / /
+ / /
+ / /
+ (m, n) -------- (m, n+1)
+
+ The mesh need not be regular and the polygons need not be convex.
+
+ Parameters
+ ----------
+ coordinates : (M+1, N+1, 2) array-like
+ The vertices. ``coordinates[m, n]`` specifies the (x, y) coordinates
+ of vertex (m, n).
+
+ antialiased : bool, default: True
+
+ shading : {'flat', 'gouraud'}, default: 'flat'
+
+ Notes
+ -----
+ Unlike other `.Collection`\s, the default *pickradius* of `.QuadMesh` is 0,
+ i.e. `~.Artist.contains` checks whether the test point is within any of the
+ mesh quadrilaterals.
+
+ """
+
+ def __init__(self, coordinates, *, antialiased=True, shading='flat',
+ **kwargs):
+ kwargs.setdefault("pickradius", 0)
+ super().__init__(coordinates=coordinates, shading=shading)
+ Collection.__init__(self, **kwargs)
+
+ self._antialiased = antialiased
+ self._bbox = transforms.Bbox.unit()
+ self._bbox.update_from_data_xy(self._coordinates.reshape(-1, 2))
+ self.set_mouseover(False)
+
+ def get_paths(self):
+ if self._paths is None:
+ self.set_paths()
+ return self._paths
+
+ def set_paths(self):
+ self._paths = self._convert_mesh_to_paths(self._coordinates)
+ self.stale = True
+
+ def get_datalim(self, transData):
+ return (self.get_transform() - transData).transform_bbox(self._bbox)
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ if not self.get_visible():
+ return
+ renderer.open_group(self.__class__.__name__, self.get_gid())
+ transform = self.get_transform()
+ offset_trf = self.get_offset_transform()
+ offsets = self.get_offsets()
+
+ if self.have_units():
+ xs = self.convert_xunits(offsets[:, 0])
+ ys = self.convert_yunits(offsets[:, 1])
+ offsets = np.column_stack([xs, ys])
+
+ self.update_scalarmappable()
+
+ if not transform.is_affine:
+ coordinates = self._coordinates.reshape((-1, 2))
+ coordinates = transform.transform(coordinates)
+ coordinates = coordinates.reshape(self._coordinates.shape)
+ transform = transforms.IdentityTransform()
+ else:
+ coordinates = self._coordinates
+
+ if not offset_trf.is_affine:
+ offsets = offset_trf.transform_non_affine(offsets)
+ offset_trf = offset_trf.get_affine()
+
+ gc = renderer.new_gc()
+ gc.set_snap(self.get_snap())
+ self._set_gc_clip(gc)
+ gc.set_linewidth(self.get_linewidth()[0])
+
+ if self._shading == 'gouraud':
+ triangles, colors = self._convert_mesh_to_triangles(coordinates)
+ renderer.draw_gouraud_triangles(
+ gc, triangles, colors, transform.frozen())
+ else:
+ renderer.draw_quad_mesh(
+ gc, transform.frozen(),
+ coordinates.shape[1] - 1, coordinates.shape[0] - 1,
+ coordinates, offsets, offset_trf,
+ # Backends expect flattened rgba arrays (n*m, 4) for fc and ec
+ self.get_facecolor().reshape((-1, 4)),
+ self._antialiased, self.get_edgecolors().reshape((-1, 4)))
+ gc.restore()
+ renderer.close_group(self.__class__.__name__)
+ self.stale = False
+
+ def get_cursor_data(self, event):
+ contained, info = self.contains(event)
+ if contained and self.get_array() is not None:
+ return self.get_array().ravel()[info["ind"]]
+ return None
+
+
+class PolyQuadMesh(_MeshData, PolyCollection):
+ """
+ Class for drawing a quadrilateral mesh as individual Polygons.
+
+ A quadrilateral mesh is a grid of M by N adjacent quadrilaterals that are
+ defined via a (M+1, N+1) grid of vertices. The quadrilateral (m, n) is
+ defined by the vertices ::
+
+ (m+1, n) ----------- (m+1, n+1)
+ / /
+ / /
+ / /
+ (m, n) -------- (m, n+1)
+
+ The mesh need not be regular and the polygons need not be convex.
+
+ Parameters
+ ----------
+ coordinates : (M+1, N+1, 2) array-like
+ The vertices. ``coordinates[m, n]`` specifies the (x, y) coordinates
+ of vertex (m, n).
+
+ Notes
+ -----
+ Unlike `.QuadMesh`, this class will draw each cell as an individual Polygon.
+ This is significantly slower, but allows for more flexibility when wanting
+ to add additional properties to the cells, such as hatching.
+
+ Another difference from `.QuadMesh` is that if any of the vertices or data
+ of a cell are masked, that Polygon will **not** be drawn and it won't be in
+ the list of paths returned.
+ """
+
+ def __init__(self, coordinates, **kwargs):
+ super().__init__(coordinates=coordinates)
+ PolyCollection.__init__(self, verts=[], **kwargs)
+ # Setting the verts updates the paths of the PolyCollection
+ # This is called after the initializers to make sure the kwargs
+ # have all been processed and available for the masking calculations
+ self._set_unmasked_verts()
+
+ def _get_unmasked_polys(self):
+ """Get the unmasked regions using the coordinates and array"""
+ # mask(X) | mask(Y)
+ mask = np.any(np.ma.getmaskarray(self._coordinates), axis=-1)
+
+ # We want the shape of the polygon, which is the corner of each X/Y array
+ mask = (mask[0:-1, 0:-1] | mask[1:, 1:] | mask[0:-1, 1:] | mask[1:, 0:-1])
+ arr = self.get_array()
+ if arr is not None:
+ arr = np.ma.getmaskarray(arr)
+ if arr.ndim == 3:
+ # RGB(A) case
+ mask |= np.any(arr, axis=-1)
+ elif arr.ndim == 2:
+ mask |= arr
+ else:
+ mask |= arr.reshape(self._coordinates[:-1, :-1, :].shape[:2])
+ return ~mask
+
+ def _set_unmasked_verts(self):
+ X = self._coordinates[..., 0]
+ Y = self._coordinates[..., 1]
+
+ unmask = self._get_unmasked_polys()
+ X1 = np.ma.filled(X[:-1, :-1])[unmask]
+ Y1 = np.ma.filled(Y[:-1, :-1])[unmask]
+ X2 = np.ma.filled(X[1:, :-1])[unmask]
+ Y2 = np.ma.filled(Y[1:, :-1])[unmask]
+ X3 = np.ma.filled(X[1:, 1:])[unmask]
+ Y3 = np.ma.filled(Y[1:, 1:])[unmask]
+ X4 = np.ma.filled(X[:-1, 1:])[unmask]
+ Y4 = np.ma.filled(Y[:-1, 1:])[unmask]
+ npoly = len(X1)
+
+ xy = np.ma.stack([X1, Y1, X2, Y2, X3, Y3, X4, Y4, X1, Y1], axis=-1)
+ verts = xy.reshape((npoly, 5, 2))
+ self.set_verts(verts)
+
+ def get_edgecolor(self):
+ # docstring inherited
+ # We only want to return the facecolors of the polygons
+ # that were drawn.
+ ec = super().get_edgecolor()
+ unmasked_polys = self._get_unmasked_polys().ravel()
+ if len(ec) != len(unmasked_polys):
+ # Mapping is off
+ return ec
+ return ec[unmasked_polys, :]
+
+ def get_facecolor(self):
+ # docstring inherited
+ # We only want to return the facecolors of the polygons
+ # that were drawn.
+ fc = super().get_facecolor()
+ unmasked_polys = self._get_unmasked_polys().ravel()
+ if len(fc) != len(unmasked_polys):
+ # Mapping is off
+ return fc
+ return fc[unmasked_polys, :]
+
+ def set_array(self, A):
+ # docstring inherited
+ prev_unmask = self._get_unmasked_polys()
+ super().set_array(A)
+ # If the mask has changed at all we need to update
+ # the set of Polys that we are drawing
+ if not np.array_equal(prev_unmask, self._get_unmasked_polys()):
+ self._set_unmasked_verts()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..0805adef42935aad06d51d3f3ed093923c02c16b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.pyi
@@ -0,0 +1,262 @@
+from collections.abc import Callable, Iterable, Sequence
+from typing import Literal
+
+import numpy as np
+from numpy.typing import ArrayLike, NDArray
+
+from . import colorizer, transforms
+from .backend_bases import MouseEvent
+from .artist import Artist
+from .colors import Normalize, Colormap
+from .lines import Line2D
+from .path import Path
+from .patches import Patch
+from .ticker import Locator, Formatter
+from .tri import Triangulation
+from .typing import ColorType, LineStyleType, CapStyleType, JoinStyleType
+
+class Collection(colorizer.ColorizingArtist):
+ def __init__(
+ self,
+ *,
+ edgecolors: ColorType | Sequence[ColorType] | None = ...,
+ facecolors: ColorType | Sequence[ColorType] | None = ...,
+ linewidths: float | Sequence[float] | None = ...,
+ linestyles: LineStyleType | Sequence[LineStyleType] = ...,
+ capstyle: CapStyleType | None = ...,
+ joinstyle: JoinStyleType | None = ...,
+ antialiaseds: bool | Sequence[bool] | None = ...,
+ offsets: tuple[float, float] | Sequence[tuple[float, float]] | None = ...,
+ offset_transform: transforms.Transform | None = ...,
+ norm: Normalize | None = ...,
+ cmap: Colormap | None = ...,
+ colorizer: colorizer.Colorizer | None = ...,
+ pickradius: float = ...,
+ hatch: str | None = ...,
+ urls: Sequence[str] | None = ...,
+ zorder: float = ...,
+ **kwargs
+ ) -> None: ...
+ def get_paths(self) -> Sequence[Path]: ...
+ def set_paths(self, paths: Sequence[Path]) -> None: ...
+ def get_transforms(self) -> Sequence[transforms.Transform]: ...
+ def get_offset_transform(self) -> transforms.Transform: ...
+ def set_offset_transform(self, offset_transform: transforms.Transform) -> None: ...
+ def get_datalim(self, transData: transforms.Transform) -> transforms.Bbox: ...
+ def set_pickradius(self, pickradius: float) -> None: ...
+ def get_pickradius(self) -> float: ...
+ def set_urls(self, urls: Sequence[str | None]) -> None: ...
+ def get_urls(self) -> Sequence[str | None]: ...
+ def set_hatch(self, hatch: str) -> None: ...
+ def get_hatch(self) -> str: ...
+ def set_hatch_linewidth(self, lw: float) -> None: ...
+ def get_hatch_linewidth(self) -> float: ...
+ def set_offsets(self, offsets: ArrayLike) -> None: ...
+ def get_offsets(self) -> ArrayLike: ...
+ def set_linewidth(self, lw: float | Sequence[float]) -> None: ...
+ def set_linestyle(self, ls: LineStyleType | Sequence[LineStyleType]) -> None: ...
+ def set_capstyle(self, cs: CapStyleType) -> None: ...
+ def get_capstyle(self) -> Literal["butt", "projecting", "round"] | None: ...
+ def set_joinstyle(self, js: JoinStyleType) -> None: ...
+ def get_joinstyle(self) -> Literal["miter", "round", "bevel"] | None: ...
+ def set_antialiased(self, aa: bool | Sequence[bool]) -> None: ...
+ def get_antialiased(self) -> NDArray[np.bool_]: ...
+ def set_color(self, c: ColorType | Sequence[ColorType]) -> None: ...
+ def set_facecolor(self, c: ColorType | Sequence[ColorType]) -> None: ...
+ def get_facecolor(self) -> ColorType | Sequence[ColorType]: ...
+ def get_edgecolor(self) -> ColorType | Sequence[ColorType]: ...
+ def set_edgecolor(self, c: ColorType | Sequence[ColorType]) -> None: ...
+ def set_alpha(self, alpha: float | Sequence[float] | None) -> None: ...
+ def get_linewidth(self) -> float | Sequence[float]: ...
+ def get_linestyle(self) -> LineStyleType | Sequence[LineStyleType]: ...
+ def update_scalarmappable(self) -> None: ...
+ def get_fill(self) -> bool: ...
+ def update_from(self, other: Artist) -> None: ...
+
+class _CollectionWithSizes(Collection):
+ def get_sizes(self) -> np.ndarray: ...
+ def set_sizes(self, sizes: ArrayLike | None, dpi: float = ...) -> None: ...
+
+class PathCollection(_CollectionWithSizes):
+ def __init__(
+ self, paths: Sequence[Path], sizes: ArrayLike | None = ..., **kwargs
+ ) -> None: ...
+ def set_paths(self, paths: Sequence[Path]) -> None: ...
+ def get_paths(self) -> Sequence[Path]: ...
+ def legend_elements(
+ self,
+ prop: Literal["colors", "sizes"] = ...,
+ num: int | Literal["auto"] | ArrayLike | Locator = ...,
+ fmt: str | Formatter | None = ...,
+ func: Callable[[ArrayLike], ArrayLike] = ...,
+ **kwargs,
+ ) -> tuple[list[Line2D], list[str]]: ...
+
+class PolyCollection(_CollectionWithSizes):
+ def __init__(
+ self,
+ verts: Sequence[ArrayLike],
+ sizes: ArrayLike | None = ...,
+ *,
+ closed: bool = ...,
+ **kwargs
+ ) -> None: ...
+ def set_verts(
+ self, verts: Sequence[ArrayLike | Path], closed: bool = ...
+ ) -> None: ...
+ def set_paths(self, verts: Sequence[Path], closed: bool = ...) -> None: ...
+ def set_verts_and_codes(
+ self, verts: Sequence[ArrayLike | Path], codes: Sequence[int]
+ ) -> None: ...
+
+class FillBetweenPolyCollection(PolyCollection):
+ def __init__(
+ self,
+ t_direction: Literal["x", "y"],
+ t: ArrayLike,
+ f1: ArrayLike,
+ f2: ArrayLike,
+ *,
+ where: Sequence[bool] | None = ...,
+ interpolate: bool = ...,
+ step: Literal["pre", "post", "mid"] | None = ...,
+ **kwargs,
+ ) -> None: ...
+ def set_data(
+ self,
+ t: ArrayLike,
+ f1: ArrayLike,
+ f2: ArrayLike,
+ *,
+ where: Sequence[bool] | None = ...,
+ ) -> None: ...
+ def get_datalim(self, transData: transforms.Transform) -> transforms.Bbox: ...
+
+class RegularPolyCollection(_CollectionWithSizes):
+ def __init__(
+ self, numsides: int, *, rotation: float = ..., sizes: ArrayLike = ..., **kwargs
+ ) -> None: ...
+ def get_numsides(self) -> int: ...
+ def get_rotation(self) -> float: ...
+
+class StarPolygonCollection(RegularPolyCollection): ...
+class AsteriskPolygonCollection(RegularPolyCollection): ...
+
+class LineCollection(Collection):
+ def __init__(
+ self, segments: Sequence[ArrayLike], *, zorder: float = ..., **kwargs
+ ) -> None: ...
+ def set_segments(self, segments: Sequence[ArrayLike] | None) -> None: ...
+ def set_verts(self, segments: Sequence[ArrayLike] | None) -> None: ...
+ def set_paths(self, segments: Sequence[ArrayLike] | None) -> None: ... # type: ignore[override]
+ def get_segments(self) -> list[np.ndarray]: ...
+ def set_color(self, c: ColorType | Sequence[ColorType]) -> None: ...
+ def set_colors(self, c: ColorType | Sequence[ColorType]) -> None: ...
+ def set_gapcolor(self, gapcolor: ColorType | Sequence[ColorType] | None) -> None: ...
+ def get_color(self) -> ColorType | Sequence[ColorType]: ...
+ def get_colors(self) -> ColorType | Sequence[ColorType]: ...
+ def get_gapcolor(self) -> ColorType | Sequence[ColorType] | None: ...
+
+
+class EventCollection(LineCollection):
+ def __init__(
+ self,
+ positions: ArrayLike,
+ orientation: Literal["horizontal", "vertical"] = ...,
+ *,
+ lineoffset: float = ...,
+ linelength: float = ...,
+ linewidth: float | Sequence[float] | None = ...,
+ color: ColorType | Sequence[ColorType] | None = ...,
+ linestyle: LineStyleType | Sequence[LineStyleType] = ...,
+ antialiased: bool | Sequence[bool] | None = ...,
+ **kwargs
+ ) -> None: ...
+ def get_positions(self) -> list[float]: ...
+ def set_positions(self, positions: Sequence[float] | None) -> None: ...
+ def add_positions(self, position: Sequence[float] | None) -> None: ...
+ def extend_positions(self, position: Sequence[float] | None) -> None: ...
+ def append_positions(self, position: Sequence[float] | None) -> None: ...
+ def is_horizontal(self) -> bool: ...
+ def get_orientation(self) -> Literal["horizontal", "vertical"]: ...
+ def switch_orientation(self) -> None: ...
+ def set_orientation(
+ self, orientation: Literal["horizontal", "vertical"]
+ ) -> None: ...
+ def get_linelength(self) -> float | Sequence[float]: ...
+ def set_linelength(self, linelength: float | Sequence[float]) -> None: ...
+ def get_lineoffset(self) -> float: ...
+ def set_lineoffset(self, lineoffset: float) -> None: ...
+ def get_linewidth(self) -> float: ...
+ def get_linewidths(self) -> Sequence[float]: ...
+ def get_color(self) -> ColorType: ...
+
+class CircleCollection(_CollectionWithSizes):
+ def __init__(self, sizes: float | ArrayLike, **kwargs) -> None: ...
+
+class EllipseCollection(Collection):
+ def __init__(
+ self,
+ widths: ArrayLike,
+ heights: ArrayLike,
+ angles: ArrayLike,
+ *,
+ units: Literal[
+ "points", "inches", "dots", "width", "height", "x", "y", "xy"
+ ] = ...,
+ **kwargs
+ ) -> None: ...
+ def set_widths(self, widths: ArrayLike) -> None: ...
+ def set_heights(self, heights: ArrayLike) -> None: ...
+ def set_angles(self, angles: ArrayLike) -> None: ...
+ def get_widths(self) -> ArrayLike: ...
+ def get_heights(self) -> ArrayLike: ...
+ def get_angles(self) -> ArrayLike: ...
+
+class PatchCollection(Collection):
+ def __init__(
+ self, patches: Iterable[Patch], *, match_original: bool = ..., **kwargs
+ ) -> None: ...
+ def set_paths(self, patches: Iterable[Patch]) -> None: ... # type: ignore[override]
+
+class TriMesh(Collection):
+ def __init__(self, triangulation: Triangulation, **kwargs) -> None: ...
+ def get_paths(self) -> list[Path]: ...
+ # Parent class has an argument, perhaps add a noop arg?
+ def set_paths(self) -> None: ... # type: ignore[override]
+ @staticmethod
+ def convert_mesh_to_paths(tri: Triangulation) -> list[Path]: ...
+
+class _MeshData:
+ def __init__(
+ self,
+ coordinates: ArrayLike,
+ *,
+ shading: Literal["flat", "gouraud"] = ...,
+ ) -> None: ...
+ def set_array(self, A: ArrayLike | None) -> None: ...
+ def get_coordinates(self) -> ArrayLike: ...
+ def get_facecolor(self) -> ColorType | Sequence[ColorType]: ...
+ def get_edgecolor(self) -> ColorType | Sequence[ColorType]: ...
+
+class QuadMesh(_MeshData, Collection):
+ def __init__(
+ self,
+ coordinates: ArrayLike,
+ *,
+ antialiased: bool = ...,
+ shading: Literal["flat", "gouraud"] = ...,
+ **kwargs
+ ) -> None: ...
+ def get_paths(self) -> list[Path]: ...
+ # Parent class has an argument, perhaps add a noop arg?
+ def set_paths(self) -> None: ... # type: ignore[override]
+ def get_datalim(self, transData: transforms.Transform) -> transforms.Bbox: ...
+ def get_cursor_data(self, event: MouseEvent) -> float: ...
+
+class PolyQuadMesh(_MeshData, PolyCollection):
+ def __init__(
+ self,
+ coordinates: ArrayLike,
+ **kwargs
+ ) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.py
new file mode 100644
index 0000000000000000000000000000000000000000..1cf3b77f01c230d5c49db68de5e9c85fd3f29a32
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.py
@@ -0,0 +1,1563 @@
+"""
+Colorbars are a visualization of the mapping from scalar values to colors.
+In Matplotlib they are drawn into a dedicated `~.axes.Axes`.
+
+.. note::
+ Colorbars are typically created through `.Figure.colorbar` or its pyplot
+ wrapper `.pyplot.colorbar`, which internally use `.Colorbar` together with
+ `.make_axes_gridspec` (for `.GridSpec`-positioned Axes) or `.make_axes` (for
+ non-`.GridSpec`-positioned Axes).
+
+ End-users most likely won't need to directly use this module's API.
+"""
+
+import logging
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api, cbook, collections, cm, colors, contour, ticker
+import matplotlib.artist as martist
+import matplotlib.patches as mpatches
+import matplotlib.path as mpath
+import matplotlib.spines as mspines
+import matplotlib.transforms as mtransforms
+from matplotlib import _docstring
+
+_log = logging.getLogger(__name__)
+
+_docstring.interpd.register(
+ _make_axes_kw_doc="""
+location : None or {'left', 'right', 'top', 'bottom'}
+ The location, relative to the parent Axes, where the colorbar Axes
+ is created. It also determines the *orientation* of the colorbar
+ (colorbars on the left and right are vertical, colorbars at the top
+ and bottom are horizontal). If None, the location will come from the
+ *orientation* if it is set (vertical colorbars on the right, horizontal
+ ones at the bottom), or default to 'right' if *orientation* is unset.
+
+orientation : None or {'vertical', 'horizontal'}
+ The orientation of the colorbar. It is preferable to set the *location*
+ of the colorbar, as that also determines the *orientation*; passing
+ incompatible values for *location* and *orientation* raises an exception.
+
+fraction : float, default: 0.15
+ Fraction of original Axes to use for colorbar.
+
+shrink : float, default: 1.0
+ Fraction by which to multiply the size of the colorbar.
+
+aspect : float, default: 20
+ Ratio of long to short dimensions.
+
+pad : float, default: 0.05 if vertical, 0.15 if horizontal
+ Fraction of original Axes between colorbar and new image Axes.
+
+anchor : (float, float), optional
+ The anchor point of the colorbar Axes.
+ Defaults to (0.0, 0.5) if vertical; (0.5, 1.0) if horizontal.
+
+panchor : (float, float), or *False*, optional
+ The anchor point of the colorbar parent Axes. If *False*, the parent
+ axes' anchor will be unchanged.
+ Defaults to (1.0, 0.5) if vertical; (0.5, 0.0) if horizontal.""",
+ _colormap_kw_doc="""
+extend : {'neither', 'both', 'min', 'max'}
+ Make pointed end(s) for out-of-range values (unless 'neither'). These are
+ set for a given colormap using the colormap set_under and set_over methods.
+
+extendfrac : {*None*, 'auto', length, lengths}
+ If set to *None*, both the minimum and maximum triangular colorbar
+ extensions will have a length of 5% of the interior colorbar length (this
+ is the default setting).
+
+ If set to 'auto', makes the triangular colorbar extensions the same lengths
+ as the interior boxes (when *spacing* is set to 'uniform') or the same
+ lengths as the respective adjacent interior boxes (when *spacing* is set to
+ 'proportional').
+
+ If a scalar, indicates the length of both the minimum and maximum
+ triangular colorbar extensions as a fraction of the interior colorbar
+ length. A two-element sequence of fractions may also be given, indicating
+ the lengths of the minimum and maximum colorbar extensions respectively as
+ a fraction of the interior colorbar length.
+
+extendrect : bool
+ If *False* the minimum and maximum colorbar extensions will be triangular
+ (the default). If *True* the extensions will be rectangular.
+
+ticks : None or list of ticks or Locator
+ If None, ticks are determined automatically from the input.
+
+format : None or str or Formatter
+ If None, `~.ticker.ScalarFormatter` is used.
+ Format strings, e.g., ``"%4.2e"`` or ``"{x:.2e}"``, are supported.
+ An alternative `~.ticker.Formatter` may be given instead.
+
+drawedges : bool
+ Whether to draw lines at color boundaries.
+
+label : str
+ The label on the colorbar's long axis.
+
+boundaries, values : None or a sequence
+ If unset, the colormap will be displayed on a 0-1 scale.
+ If sequences, *values* must have a length 1 less than *boundaries*. For
+ each region delimited by adjacent entries in *boundaries*, the color mapped
+ to the corresponding value in *values* will be used. The size of each
+ region is determined by the *spacing* parameter.
+ Normally only useful for indexed colors (i.e. ``norm=NoNorm()``) or other
+ unusual circumstances.
+
+spacing : {'uniform', 'proportional'}
+ For discrete colorbars (`.BoundaryNorm` or contours), 'uniform' gives each
+ color the same space; 'proportional' makes the space proportional to the
+ data interval.""")
+
+
+def _set_ticks_on_axis_warn(*args, **kwargs):
+ # a top level function which gets put in at the axes'
+ # set_xticks and set_yticks by Colorbar.__init__.
+ _api.warn_external("Use the colorbar set_ticks() method instead.")
+
+
+class _ColorbarSpine(mspines.Spine):
+ def __init__(self, axes):
+ self._ax = axes
+ super().__init__(axes, 'colorbar', mpath.Path(np.empty((0, 2))))
+ mpatches.Patch.set_transform(self, axes.transAxes)
+
+ def get_window_extent(self, renderer=None):
+ # This Spine has no Axis associated with it, and doesn't need to adjust
+ # its location, so we can directly get the window extent from the
+ # super-super-class.
+ return mpatches.Patch.get_window_extent(self, renderer=renderer)
+
+ def set_xy(self, xy):
+ self._path = mpath.Path(xy, closed=True)
+ self._xy = xy
+ self.stale = True
+
+ def draw(self, renderer):
+ ret = mpatches.Patch.draw(self, renderer)
+ self.stale = False
+ return ret
+
+
+class _ColorbarAxesLocator:
+ """
+ Shrink the Axes if there are triangular or rectangular extends.
+ """
+ def __init__(self, cbar):
+ self._cbar = cbar
+ self._orig_locator = cbar.ax._axes_locator
+
+ def __call__(self, ax, renderer):
+ if self._orig_locator is not None:
+ pos = self._orig_locator(ax, renderer)
+ else:
+ pos = ax.get_position(original=True)
+ if self._cbar.extend == 'neither':
+ return pos
+
+ y, extendlen = self._cbar._proportional_y()
+ if not self._cbar._extend_lower():
+ extendlen[0] = 0
+ if not self._cbar._extend_upper():
+ extendlen[1] = 0
+ len = sum(extendlen) + 1
+ shrink = 1 / len
+ offset = extendlen[0] / len
+ # we need to reset the aspect ratio of the axes to account
+ # of the extends...
+ if hasattr(ax, '_colorbar_info'):
+ aspect = ax._colorbar_info['aspect']
+ else:
+ aspect = False
+ # now shrink and/or offset to take into account the
+ # extend tri/rectangles.
+ if self._cbar.orientation == 'vertical':
+ if aspect:
+ self._cbar.ax.set_box_aspect(aspect*shrink)
+ pos = pos.shrunk(1, shrink).translated(0, offset * pos.height)
+ else:
+ if aspect:
+ self._cbar.ax.set_box_aspect(1/(aspect * shrink))
+ pos = pos.shrunk(shrink, 1).translated(offset * pos.width, 0)
+ return pos
+
+ def get_subplotspec(self):
+ # make tight_layout happy..
+ return (
+ self._cbar.ax.get_subplotspec()
+ or getattr(self._orig_locator, "get_subplotspec", lambda: None)())
+
+
+@_docstring.interpd
+class Colorbar:
+ r"""
+ Draw a colorbar in an existing Axes.
+
+ Typically, colorbars are created using `.Figure.colorbar` or
+ `.pyplot.colorbar` and associated with `.ScalarMappable`\s (such as an
+ `.AxesImage` generated via `~.axes.Axes.imshow`).
+
+ In order to draw a colorbar not associated with other elements in the
+ figure, e.g. when showing a colormap by itself, one can create an empty
+ `.ScalarMappable`, or directly pass *cmap* and *norm* instead of *mappable*
+ to `Colorbar`.
+
+ Useful public methods are :meth:`set_label` and :meth:`add_lines`.
+
+ Attributes
+ ----------
+ ax : `~matplotlib.axes.Axes`
+ The `~.axes.Axes` instance in which the colorbar is drawn.
+ lines : list
+ A list of `.LineCollection` (empty if no lines were drawn).
+ dividers : `.LineCollection`
+ A LineCollection (empty if *drawedges* is ``False``).
+ """
+
+ n_rasterize = 50 # rasterize solids if number of colors >= n_rasterize
+
+ def __init__(
+ self, ax, mappable=None, *,
+ alpha=None,
+ location=None,
+ extend=None,
+ extendfrac=None,
+ extendrect=False,
+ ticks=None,
+ format=None,
+ values=None,
+ boundaries=None,
+ spacing='uniform',
+ drawedges=False,
+ label='',
+ cmap=None, norm=None, # redundant with *mappable*
+ orientation=None, ticklocation='auto', # redundant with *location*
+ ):
+ """
+ Parameters
+ ----------
+ ax : `~matplotlib.axes.Axes`
+ The `~.axes.Axes` instance in which the colorbar is drawn.
+
+ mappable : `.ScalarMappable`
+ The mappable whose colormap and norm will be used.
+
+ To show the colors versus index instead of on a 0-1 scale, set the
+ mappable's norm to ``colors.NoNorm()``.
+
+ alpha : float
+ The colorbar transparency between 0 (transparent) and 1 (opaque).
+
+ location : None or {'left', 'right', 'top', 'bottom'}
+ Set the colorbar's *orientation* and *ticklocation*. Colorbars on
+ the left and right are vertical, colorbars at the top and bottom
+ are horizontal. The *ticklocation* is the same as *location*, so if
+ *location* is 'top', the ticks are on the top. *orientation* and/or
+ *ticklocation* can be provided as well and overrides the value set by
+ *location*, but there will be an error for incompatible combinations.
+
+ .. versionadded:: 3.7
+
+ %(_colormap_kw_doc)s
+
+ Other Parameters
+ ----------------
+ cmap : `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
+ The colormap to use. This parameter is ignored, unless *mappable* is
+ None.
+
+ norm : `~matplotlib.colors.Normalize`
+ The normalization to use. This parameter is ignored, unless *mappable*
+ is None.
+
+ orientation : None or {'vertical', 'horizontal'}
+ If None, use the value determined by *location*. If both
+ *orientation* and *location* are None then defaults to 'vertical'.
+
+ ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
+ The location of the colorbar ticks. The *ticklocation* must match
+ *orientation*. For example, a horizontal colorbar can only have ticks
+ at the top or the bottom. If 'auto', the ticks will be the same as
+ *location*, so a colorbar to the left will have ticks to the left. If
+ *location* is None, the ticks will be at the bottom for a horizontal
+ colorbar and at the right for a vertical.
+ """
+ if mappable is None:
+ mappable = cm.ScalarMappable(norm=norm, cmap=cmap)
+
+ self.mappable = mappable
+ cmap = mappable.cmap
+ norm = mappable.norm
+
+ filled = True
+ if isinstance(mappable, contour.ContourSet):
+ cs = mappable
+ alpha = cs.get_alpha()
+ boundaries = cs._levels
+ values = cs.cvalues
+ extend = cs.extend
+ filled = cs.filled
+ if ticks is None:
+ ticks = ticker.FixedLocator(cs.levels, nbins=10)
+ elif isinstance(mappable, martist.Artist):
+ alpha = mappable.get_alpha()
+
+ mappable.colorbar = self
+ mappable.colorbar_cid = mappable.callbacks.connect(
+ 'changed', self.update_normal)
+
+ location_orientation = _get_orientation_from_location(location)
+
+ _api.check_in_list(
+ [None, 'vertical', 'horizontal'], orientation=orientation)
+ _api.check_in_list(
+ ['auto', 'left', 'right', 'top', 'bottom'],
+ ticklocation=ticklocation)
+ _api.check_in_list(
+ ['uniform', 'proportional'], spacing=spacing)
+
+ if location_orientation is not None and orientation is not None:
+ if location_orientation != orientation:
+ raise TypeError(
+ "location and orientation are mutually exclusive")
+ else:
+ orientation = orientation or location_orientation or "vertical"
+
+ self.ax = ax
+ self.ax._axes_locator = _ColorbarAxesLocator(self)
+
+ if extend is None:
+ if (not isinstance(mappable, contour.ContourSet)
+ and getattr(cmap, 'colorbar_extend', False) is not False):
+ extend = cmap.colorbar_extend
+ elif hasattr(norm, 'extend'):
+ extend = norm.extend
+ else:
+ extend = 'neither'
+ self.alpha = None
+ # Call set_alpha to handle array-like alphas properly
+ self.set_alpha(alpha)
+ self.cmap = cmap
+ self.norm = norm
+ self.values = values
+ self.boundaries = boundaries
+ self.extend = extend
+ self._inside = _api.check_getitem(
+ {'neither': slice(0, None), 'both': slice(1, -1),
+ 'min': slice(1, None), 'max': slice(0, -1)},
+ extend=extend)
+ self.spacing = spacing
+ self.orientation = orientation
+ self.drawedges = drawedges
+ self._filled = filled
+ self.extendfrac = extendfrac
+ self.extendrect = extendrect
+ self._extend_patches = []
+ self.solids = None
+ self.solids_patches = []
+ self.lines = []
+
+ for spine in self.ax.spines.values():
+ spine.set_visible(False)
+ self.outline = self.ax.spines['outline'] = _ColorbarSpine(self.ax)
+
+ self.dividers = collections.LineCollection(
+ [],
+ colors=[mpl.rcParams['axes.edgecolor']],
+ linewidths=[0.5 * mpl.rcParams['axes.linewidth']],
+ clip_on=False)
+ self.ax.add_collection(self.dividers)
+
+ self._locator = None
+ self._minorlocator = None
+ self._formatter = None
+ self._minorformatter = None
+
+ if ticklocation == 'auto':
+ ticklocation = _get_ticklocation_from_orientation(
+ orientation) if location is None else location
+ self.ticklocation = ticklocation
+
+ self.set_label(label)
+ self._reset_locator_formatter_scale()
+
+ if np.iterable(ticks):
+ self._locator = ticker.FixedLocator(ticks, nbins=len(ticks))
+ else:
+ self._locator = ticks
+
+ if isinstance(format, str):
+ # Check format between FormatStrFormatter and StrMethodFormatter
+ try:
+ self._formatter = ticker.FormatStrFormatter(format)
+ _ = self._formatter(0)
+ except (TypeError, ValueError):
+ self._formatter = ticker.StrMethodFormatter(format)
+ else:
+ self._formatter = format # Assume it is a Formatter or None
+ self._draw_all()
+
+ if isinstance(mappable, contour.ContourSet) and not mappable.filled:
+ self.add_lines(mappable)
+
+ # Link the Axes and Colorbar for interactive use
+ self.ax._colorbar = self
+ # Don't navigate on any of these types of mappables
+ if (isinstance(self.norm, (colors.BoundaryNorm, colors.NoNorm)) or
+ isinstance(self.mappable, contour.ContourSet)):
+ self.ax.set_navigate(False)
+
+ # These are the functions that set up interactivity on this colorbar
+ self._interactive_funcs = ["_get_view", "_set_view",
+ "_set_view_from_bbox", "drag_pan"]
+ for x in self._interactive_funcs:
+ setattr(self.ax, x, getattr(self, x))
+ # Set the cla function to the cbar's method to override it
+ self.ax.cla = self._cbar_cla
+ # Callbacks for the extend calculations to handle inverting the axis
+ self._extend_cid1 = self.ax.callbacks.connect(
+ "xlim_changed", self._do_extends)
+ self._extend_cid2 = self.ax.callbacks.connect(
+ "ylim_changed", self._do_extends)
+
+ @property
+ def long_axis(self):
+ """Axis that has decorations (ticks, etc) on it."""
+ if self.orientation == 'vertical':
+ return self.ax.yaxis
+ return self.ax.xaxis
+
+ @property
+ def locator(self):
+ """Major tick `.Locator` for the colorbar."""
+ return self.long_axis.get_major_locator()
+
+ @locator.setter
+ def locator(self, loc):
+ self.long_axis.set_major_locator(loc)
+ self._locator = loc
+
+ @property
+ def minorlocator(self):
+ """Minor tick `.Locator` for the colorbar."""
+ return self.long_axis.get_minor_locator()
+
+ @minorlocator.setter
+ def minorlocator(self, loc):
+ self.long_axis.set_minor_locator(loc)
+ self._minorlocator = loc
+
+ @property
+ def formatter(self):
+ """Major tick label `.Formatter` for the colorbar."""
+ return self.long_axis.get_major_formatter()
+
+ @formatter.setter
+ def formatter(self, fmt):
+ self.long_axis.set_major_formatter(fmt)
+ self._formatter = fmt
+
+ @property
+ def minorformatter(self):
+ """Minor tick `.Formatter` for the colorbar."""
+ return self.long_axis.get_minor_formatter()
+
+ @minorformatter.setter
+ def minorformatter(self, fmt):
+ self.long_axis.set_minor_formatter(fmt)
+ self._minorformatter = fmt
+
+ def _cbar_cla(self):
+ """Function to clear the interactive colorbar state."""
+ for x in self._interactive_funcs:
+ delattr(self.ax, x)
+ # We now restore the old cla() back and can call it directly
+ del self.ax.cla
+ self.ax.cla()
+
+ def update_normal(self, mappable=None):
+ """
+ Update solid patches, lines, etc.
+
+ This is meant to be called when the norm of the image or contour plot
+ to which this colorbar belongs changes.
+
+ If the norm on the mappable is different than before, this resets the
+ locator and formatter for the axis, so if these have been customized,
+ they will need to be customized again. However, if the norm only
+ changes values of *vmin*, *vmax* or *cmap* then the old formatter
+ and locator will be preserved.
+ """
+ if mappable:
+ # The mappable keyword argument exists because
+ # ScalarMappable.changed() emits self.callbacks.process('changed', self)
+ # in contrast, ColorizingArtist (and Colorizer) does not use this keyword.
+ # [ColorizingArtist.changed() emits self.callbacks.process('changed')]
+ # Also, there is no test where self.mappable == mappable is not True
+ # and possibly no use case.
+ # Therefore, the mappable keyword can be deprecated if cm.ScalarMappable
+ # is removed.
+ self.mappable = mappable
+ _log.debug('colorbar update normal %r %r', self.mappable.norm, self.norm)
+ self.set_alpha(self.mappable.get_alpha())
+ self.cmap = self.mappable.cmap
+ if self.mappable.norm != self.norm:
+ self.norm = self.mappable.norm
+ self._reset_locator_formatter_scale()
+
+ self._draw_all()
+ if isinstance(self.mappable, contour.ContourSet):
+ CS = self.mappable
+ if not CS.filled:
+ self.add_lines(CS)
+ self.stale = True
+
+ def _draw_all(self):
+ """
+ Calculate any free parameters based on the current cmap and norm,
+ and do all the drawing.
+ """
+ if self.orientation == 'vertical':
+ if mpl.rcParams['ytick.minor.visible']:
+ self.minorticks_on()
+ else:
+ if mpl.rcParams['xtick.minor.visible']:
+ self.minorticks_on()
+ self.long_axis.set(label_position=self.ticklocation,
+ ticks_position=self.ticklocation)
+ self._short_axis().set_ticks([])
+ self._short_axis().set_ticks([], minor=True)
+
+ # Set self._boundaries and self._values, including extensions.
+ # self._boundaries are the edges of each square of color, and
+ # self._values are the value to map into the norm to get the
+ # color:
+ self._process_values()
+ # Set self.vmin and self.vmax to first and last boundary, excluding
+ # extensions:
+ self.vmin, self.vmax = self._boundaries[self._inside][[0, -1]]
+ # Compute the X/Y mesh.
+ X, Y = self._mesh()
+ # draw the extend triangles, and shrink the inner Axes to accommodate.
+ # also adds the outline path to self.outline spine:
+ self._do_extends()
+ lower, upper = self.vmin, self.vmax
+ if self.long_axis.get_inverted():
+ # If the axis is inverted, we need to swap the vmin/vmax
+ lower, upper = upper, lower
+ if self.orientation == 'vertical':
+ self.ax.set_xlim(0, 1)
+ self.ax.set_ylim(lower, upper)
+ else:
+ self.ax.set_ylim(0, 1)
+ self.ax.set_xlim(lower, upper)
+
+ # set up the tick locators and formatters. A bit complicated because
+ # boundary norms + uniform spacing requires a manual locator.
+ self.update_ticks()
+
+ if self._filled:
+ ind = np.arange(len(self._values))
+ if self._extend_lower():
+ ind = ind[1:]
+ if self._extend_upper():
+ ind = ind[:-1]
+ self._add_solids(X, Y, self._values[ind, np.newaxis])
+
+ def _add_solids(self, X, Y, C):
+ """Draw the colors; optionally add separators."""
+ # Cleanup previously set artists.
+ if self.solids is not None:
+ self.solids.remove()
+ for solid in self.solids_patches:
+ solid.remove()
+ # Add new artist(s), based on mappable type. Use individual patches if
+ # hatching is needed, pcolormesh otherwise.
+ mappable = getattr(self, 'mappable', None)
+ if (isinstance(mappable, contour.ContourSet)
+ and any(hatch is not None for hatch in mappable.hatches)):
+ self._add_solids_patches(X, Y, C, mappable)
+ else:
+ self.solids = self.ax.pcolormesh(
+ X, Y, C, cmap=self.cmap, norm=self.norm, alpha=self.alpha,
+ edgecolors='none', shading='flat')
+ if not self.drawedges:
+ if len(self._y) >= self.n_rasterize:
+ self.solids.set_rasterized(True)
+ self._update_dividers()
+
+ def _update_dividers(self):
+ if not self.drawedges:
+ self.dividers.set_segments([])
+ return
+ # Place all *internal* dividers.
+ if self.orientation == 'vertical':
+ lims = self.ax.get_ylim()
+ bounds = (lims[0] < self._y) & (self._y < lims[1])
+ else:
+ lims = self.ax.get_xlim()
+ bounds = (lims[0] < self._y) & (self._y < lims[1])
+ y = self._y[bounds]
+ # And then add outer dividers if extensions are on.
+ if self._extend_lower():
+ y = np.insert(y, 0, lims[0])
+ if self._extend_upper():
+ y = np.append(y, lims[1])
+ X, Y = np.meshgrid([0, 1], y)
+ if self.orientation == 'vertical':
+ segments = np.dstack([X, Y])
+ else:
+ segments = np.dstack([Y, X])
+ self.dividers.set_segments(segments)
+
+ def _add_solids_patches(self, X, Y, C, mappable):
+ hatches = mappable.hatches * (len(C) + 1) # Have enough hatches.
+ if self._extend_lower():
+ # remove first hatch that goes into the extend patch
+ hatches = hatches[1:]
+ patches = []
+ for i in range(len(X) - 1):
+ xy = np.array([[X[i, 0], Y[i, 1]],
+ [X[i, 1], Y[i, 0]],
+ [X[i + 1, 1], Y[i + 1, 0]],
+ [X[i + 1, 0], Y[i + 1, 1]]])
+ patch = mpatches.PathPatch(mpath.Path(xy),
+ facecolor=self.cmap(self.norm(C[i][0])),
+ hatch=hatches[i], linewidth=0,
+ antialiased=False, alpha=self.alpha)
+ self.ax.add_patch(patch)
+ patches.append(patch)
+ self.solids_patches = patches
+
+ def _do_extends(self, ax=None):
+ """
+ Add the extend tri/rectangles on the outside of the Axes.
+
+ ax is unused, but required due to the callbacks on xlim/ylim changed
+ """
+ # Clean up any previous extend patches
+ for patch in self._extend_patches:
+ patch.remove()
+ self._extend_patches = []
+ # extend lengths are fraction of the *inner* part of colorbar,
+ # not the total colorbar:
+ _, extendlen = self._proportional_y()
+ bot = 0 - (extendlen[0] if self._extend_lower() else 0)
+ top = 1 + (extendlen[1] if self._extend_upper() else 0)
+
+ # xyout is the outline of the colorbar including the extend patches:
+ if not self.extendrect:
+ # triangle:
+ xyout = np.array([[0, 0], [0.5, bot], [1, 0],
+ [1, 1], [0.5, top], [0, 1], [0, 0]])
+ else:
+ # rectangle:
+ xyout = np.array([[0, 0], [0, bot], [1, bot], [1, 0],
+ [1, 1], [1, top], [0, top], [0, 1],
+ [0, 0]])
+
+ if self.orientation == 'horizontal':
+ xyout = xyout[:, ::-1]
+
+ # xyout is the path for the spine:
+ self.outline.set_xy(xyout)
+ if not self._filled:
+ return
+
+ # Make extend triangles or rectangles filled patches. These are
+ # defined in the outer parent axes' coordinates:
+ mappable = getattr(self, 'mappable', None)
+ if (isinstance(mappable, contour.ContourSet)
+ and any(hatch is not None for hatch in mappable.hatches)):
+ hatches = mappable.hatches * (len(self._y) + 1)
+ else:
+ hatches = [None] * (len(self._y) + 1)
+
+ if self._extend_lower():
+ if not self.extendrect:
+ # triangle
+ xy = np.array([[0, 0], [0.5, bot], [1, 0]])
+ else:
+ # rectangle
+ xy = np.array([[0, 0], [0, bot], [1., bot], [1, 0]])
+ if self.orientation == 'horizontal':
+ xy = xy[:, ::-1]
+ # add the patch
+ val = -1 if self.long_axis.get_inverted() else 0
+ color = self.cmap(self.norm(self._values[val]))
+ patch = mpatches.PathPatch(
+ mpath.Path(xy), facecolor=color, alpha=self.alpha,
+ linewidth=0, antialiased=False,
+ transform=self.ax.transAxes,
+ hatch=hatches[0], clip_on=False,
+ # Place it right behind the standard patches, which is
+ # needed if we updated the extends
+ zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
+ self.ax.add_patch(patch)
+ self._extend_patches.append(patch)
+ # remove first hatch that goes into the extend patch
+ hatches = hatches[1:]
+ if self._extend_upper():
+ if not self.extendrect:
+ # triangle
+ xy = np.array([[0, 1], [0.5, top], [1, 1]])
+ else:
+ # rectangle
+ xy = np.array([[0, 1], [0, top], [1, top], [1, 1]])
+ if self.orientation == 'horizontal':
+ xy = xy[:, ::-1]
+ # add the patch
+ val = 0 if self.long_axis.get_inverted() else -1
+ color = self.cmap(self.norm(self._values[val]))
+ hatch_idx = len(self._y) - 1
+ patch = mpatches.PathPatch(
+ mpath.Path(xy), facecolor=color, alpha=self.alpha,
+ linewidth=0, antialiased=False,
+ transform=self.ax.transAxes, hatch=hatches[hatch_idx],
+ clip_on=False,
+ # Place it right behind the standard patches, which is
+ # needed if we updated the extends
+ zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
+ self.ax.add_patch(patch)
+ self._extend_patches.append(patch)
+
+ self._update_dividers()
+
+ def add_lines(self, *args, **kwargs):
+ """
+ Draw lines on the colorbar.
+
+ The lines are appended to the list :attr:`lines`.
+
+ Parameters
+ ----------
+ levels : array-like
+ The positions of the lines.
+ colors : :mpltype:`color` or list of :mpltype:`color`
+ Either a single color applying to all lines or one color value for
+ each line.
+ linewidths : float or array-like
+ Either a single linewidth applying to all lines or one linewidth
+ for each line.
+ erase : bool, default: True
+ Whether to remove any previously added lines.
+
+ Notes
+ -----
+ Alternatively, this method can also be called with the signature
+ ``colorbar.add_lines(contour_set, erase=True)``, in which case
+ *levels*, *colors*, and *linewidths* are taken from *contour_set*.
+ """
+ params = _api.select_matching_signature(
+ [lambda self, CS, erase=True: locals(),
+ lambda self, levels, colors, linewidths, erase=True: locals()],
+ self, *args, **kwargs)
+ if "CS" in params:
+ self, cs, erase = params.values()
+ if not isinstance(cs, contour.ContourSet) or cs.filled:
+ raise ValueError("If a single artist is passed to add_lines, "
+ "it must be a ContourSet of lines")
+ # TODO: Make colorbar lines auto-follow changes in contour lines.
+ return self.add_lines(
+ cs.levels,
+ cs.to_rgba(cs.cvalues, cs.alpha),
+ cs.get_linewidths(),
+ erase=erase)
+ else:
+ self, levels, colors, linewidths, erase = params.values()
+
+ y = self._locate(levels)
+ rtol = (self._y[-1] - self._y[0]) * 1e-10
+ igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol)
+ y = y[igood]
+ if np.iterable(colors):
+ colors = np.asarray(colors)[igood]
+ if np.iterable(linewidths):
+ linewidths = np.asarray(linewidths)[igood]
+ X, Y = np.meshgrid([0, 1], y)
+ if self.orientation == 'vertical':
+ xy = np.stack([X, Y], axis=-1)
+ else:
+ xy = np.stack([Y, X], axis=-1)
+ col = collections.LineCollection(xy, linewidths=linewidths,
+ colors=colors)
+
+ if erase and self.lines:
+ for lc in self.lines:
+ lc.remove()
+ self.lines = []
+ self.lines.append(col)
+
+ # make a clip path that is just a linewidth bigger than the Axes...
+ fac = np.max(linewidths) / 72
+ xy = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
+ inches = self.ax.get_figure().dpi_scale_trans
+ # do in inches:
+ xy = inches.inverted().transform(self.ax.transAxes.transform(xy))
+ xy[[0, 1, 4], 1] -= fac
+ xy[[2, 3], 1] += fac
+ # back to axes units...
+ xy = self.ax.transAxes.inverted().transform(inches.transform(xy))
+ col.set_clip_path(mpath.Path(xy, closed=True),
+ self.ax.transAxes)
+ self.ax.add_collection(col)
+ self.stale = True
+
+ def update_ticks(self):
+ """
+ Set up the ticks and ticklabels. This should not be needed by users.
+ """
+ # Get the locator and formatter; defaults to self._locator if not None.
+ self._get_ticker_locator_formatter()
+ self.long_axis.set_major_locator(self._locator)
+ self.long_axis.set_minor_locator(self._minorlocator)
+ self.long_axis.set_major_formatter(self._formatter)
+
+ def _get_ticker_locator_formatter(self):
+ """
+ Return the ``locator`` and ``formatter`` of the colorbar.
+
+ If they have not been defined (i.e. are *None*), the formatter and
+ locator are retrieved from the axis, or from the value of the
+ boundaries for a boundary norm.
+
+ Called by update_ticks...
+ """
+ locator = self._locator
+ formatter = self._formatter
+ minorlocator = self._minorlocator
+ if isinstance(self.norm, colors.BoundaryNorm):
+ b = self.norm.boundaries
+ if locator is None:
+ locator = ticker.FixedLocator(b, nbins=10)
+ if minorlocator is None:
+ minorlocator = ticker.FixedLocator(b)
+ elif isinstance(self.norm, colors.NoNorm):
+ if locator is None:
+ # put ticks on integers between the boundaries of NoNorm
+ nv = len(self._values)
+ base = 1 + int(nv / 10)
+ locator = ticker.IndexLocator(base=base, offset=.5)
+ elif self.boundaries is not None:
+ b = self._boundaries[self._inside]
+ if locator is None:
+ locator = ticker.FixedLocator(b, nbins=10)
+ else: # most cases:
+ if locator is None:
+ # we haven't set the locator explicitly, so use the default
+ # for this axis:
+ locator = self.long_axis.get_major_locator()
+ if minorlocator is None:
+ minorlocator = self.long_axis.get_minor_locator()
+
+ if minorlocator is None:
+ minorlocator = ticker.NullLocator()
+
+ if formatter is None:
+ formatter = self.long_axis.get_major_formatter()
+
+ self._locator = locator
+ self._formatter = formatter
+ self._minorlocator = minorlocator
+ _log.debug('locator: %r', locator)
+
+ def set_ticks(self, ticks, *, labels=None, minor=False, **kwargs):
+ """
+ Set tick locations.
+
+ Parameters
+ ----------
+ ticks : 1D array-like
+ List of tick locations.
+ labels : list of str, optional
+ List of tick labels. If not set, the labels show the data value.
+ minor : bool, default: False
+ If ``False``, set the major ticks; if ``True``, the minor ticks.
+ **kwargs
+ `.Text` properties for the labels. These take effect only if you
+ pass *labels*. In other cases, please use `~.Axes.tick_params`.
+ """
+ if np.iterable(ticks):
+ self.long_axis.set_ticks(ticks, labels=labels, minor=minor,
+ **kwargs)
+ self._locator = self.long_axis.get_major_locator()
+ else:
+ self._locator = ticks
+ self.long_axis.set_major_locator(self._locator)
+ self.stale = True
+
+ def get_ticks(self, minor=False):
+ """
+ Return the ticks as a list of locations.
+
+ Parameters
+ ----------
+ minor : boolean, default: False
+ if True return the minor ticks.
+ """
+ if minor:
+ return self.long_axis.get_minorticklocs()
+ else:
+ return self.long_axis.get_majorticklocs()
+
+ def set_ticklabels(self, ticklabels, *, minor=False, **kwargs):
+ """
+ [*Discouraged*] Set tick labels.
+
+ .. admonition:: Discouraged
+
+ The use of this method is discouraged, because of the dependency
+ on tick positions. In most cases, you'll want to use
+ ``set_ticks(positions, labels=labels)`` instead.
+
+ If you are using this method, you should always fix the tick
+ positions before, e.g. by using `.Colorbar.set_ticks` or by
+ explicitly setting a `~.ticker.FixedLocator` on the long axis
+ of the colorbar. Otherwise, ticks are free to move and the
+ labels may end up in unexpected positions.
+
+ Parameters
+ ----------
+ ticklabels : sequence of str or of `.Text`
+ Texts for labeling each tick location in the sequence set by
+ `.Colorbar.set_ticks`; the number of labels must match the number
+ of locations.
+
+ update_ticks : bool, default: True
+ This keyword argument is ignored and will be removed.
+ Deprecated
+
+ minor : bool
+ If True, set minor ticks instead of major ticks.
+
+ **kwargs
+ `.Text` properties for the labels.
+ """
+ self.long_axis.set_ticklabels(ticklabels, minor=minor, **kwargs)
+
+ def minorticks_on(self):
+ """
+ Turn on colorbar minor ticks.
+ """
+ self.ax.minorticks_on()
+ self._short_axis().set_minor_locator(ticker.NullLocator())
+
+ def minorticks_off(self):
+ """Turn the minor ticks of the colorbar off."""
+ self._minorlocator = ticker.NullLocator()
+ self.long_axis.set_minor_locator(self._minorlocator)
+
+ def set_label(self, label, *, loc=None, **kwargs):
+ """
+ Add a label to the long axis of the colorbar.
+
+ Parameters
+ ----------
+ label : str
+ The label text.
+ loc : str, optional
+ The location of the label.
+
+ - For horizontal orientation one of {'left', 'center', 'right'}
+ - For vertical orientation one of {'bottom', 'center', 'top'}
+
+ Defaults to :rc:`xaxis.labellocation` or :rc:`yaxis.labellocation`
+ depending on the orientation.
+ **kwargs
+ Keyword arguments are passed to `~.Axes.set_xlabel` /
+ `~.Axes.set_ylabel`.
+ Supported keywords are *labelpad* and `.Text` properties.
+ """
+ if self.orientation == "vertical":
+ self.ax.set_ylabel(label, loc=loc, **kwargs)
+ else:
+ self.ax.set_xlabel(label, loc=loc, **kwargs)
+ self.stale = True
+
+ def set_alpha(self, alpha):
+ """
+ Set the transparency between 0 (transparent) and 1 (opaque).
+
+ If an array is provided, *alpha* will be set to None to use the
+ transparency values associated with the colormap.
+ """
+ self.alpha = None if isinstance(alpha, np.ndarray) else alpha
+
+ def _set_scale(self, scale, **kwargs):
+ """
+ Set the colorbar long axis scale.
+
+ Parameters
+ ----------
+ scale : {"linear", "log", "symlog", "logit", ...} or `.ScaleBase`
+ The axis scale type to apply.
+
+ **kwargs
+ Different keyword arguments are accepted, depending on the scale.
+ See the respective class keyword arguments:
+
+ - `matplotlib.scale.LinearScale`
+ - `matplotlib.scale.LogScale`
+ - `matplotlib.scale.SymmetricalLogScale`
+ - `matplotlib.scale.LogitScale`
+ - `matplotlib.scale.FuncScale`
+ - `matplotlib.scale.AsinhScale`
+
+ Notes
+ -----
+ By default, Matplotlib supports the above-mentioned scales.
+ Additionally, custom scales may be registered using
+ `matplotlib.scale.register_scale`. These scales can then also
+ be used here.
+ """
+ self.long_axis._set_axes_scale(scale, **kwargs)
+
+ def remove(self):
+ """
+ Remove this colorbar from the figure.
+
+ If the colorbar was created with ``use_gridspec=True`` the previous
+ gridspec is restored.
+ """
+ if hasattr(self.ax, '_colorbar_info'):
+ parents = self.ax._colorbar_info['parents']
+ for a in parents:
+ if self.ax in a._colorbars:
+ a._colorbars.remove(self.ax)
+
+ self.ax.remove()
+
+ self.mappable.callbacks.disconnect(self.mappable.colorbar_cid)
+ self.mappable.colorbar = None
+ self.mappable.colorbar_cid = None
+ # Remove the extension callbacks
+ self.ax.callbacks.disconnect(self._extend_cid1)
+ self.ax.callbacks.disconnect(self._extend_cid2)
+
+ try:
+ ax = self.mappable.axes
+ except AttributeError:
+ return
+ try:
+ subplotspec = self.ax.get_subplotspec().get_gridspec()._subplot_spec
+ except AttributeError: # use_gridspec was False
+ pos = ax.get_position(original=True)
+ ax._set_position(pos)
+ else: # use_gridspec was True
+ ax.set_subplotspec(subplotspec)
+
+ def _process_values(self):
+ """
+ Set `_boundaries` and `_values` based on the self.boundaries and
+ self.values if not None, or based on the size of the colormap and
+ the vmin/vmax of the norm.
+ """
+ if self.values is not None:
+ # set self._boundaries from the values...
+ self._values = np.array(self.values)
+ if self.boundaries is None:
+ # bracket values by 1/2 dv:
+ b = np.zeros(len(self.values) + 1)
+ b[1:-1] = 0.5 * (self._values[:-1] + self._values[1:])
+ b[0] = 2.0 * b[1] - b[2]
+ b[-1] = 2.0 * b[-2] - b[-3]
+ self._boundaries = b
+ return
+ self._boundaries = np.array(self.boundaries)
+ return
+
+ # otherwise values are set from the boundaries
+ if isinstance(self.norm, colors.BoundaryNorm):
+ b = self.norm.boundaries
+ elif isinstance(self.norm, colors.NoNorm):
+ # NoNorm has N blocks, so N+1 boundaries, centered on integers:
+ b = np.arange(self.cmap.N + 1) - .5
+ elif self.boundaries is not None:
+ b = self.boundaries
+ else:
+ # otherwise make the boundaries from the size of the cmap:
+ N = self.cmap.N + 1
+ b, _ = self._uniform_y(N)
+ # add extra boundaries if needed:
+ if self._extend_lower():
+ b = np.hstack((b[0] - 1, b))
+ if self._extend_upper():
+ b = np.hstack((b, b[-1] + 1))
+
+ # transform from 0-1 to vmin-vmax:
+ if self.mappable.get_array() is not None:
+ self.mappable.autoscale_None()
+ if not self.norm.scaled():
+ # If we still aren't scaled after autoscaling, use 0, 1 as default
+ self.norm.vmin = 0
+ self.norm.vmax = 1
+ self.norm.vmin, self.norm.vmax = mtransforms.nonsingular(
+ self.norm.vmin, self.norm.vmax, expander=0.1)
+ if (not isinstance(self.norm, colors.BoundaryNorm) and
+ (self.boundaries is None)):
+ b = self.norm.inverse(b)
+
+ self._boundaries = np.asarray(b, dtype=float)
+ self._values = 0.5 * (self._boundaries[:-1] + self._boundaries[1:])
+ if isinstance(self.norm, colors.NoNorm):
+ self._values = (self._values + 0.00001).astype(np.int16)
+
+ def _mesh(self):
+ """
+ Return the coordinate arrays for the colorbar pcolormesh/patches.
+
+ These are scaled between vmin and vmax, and already handle colorbar
+ orientation.
+ """
+ y, _ = self._proportional_y()
+ # Use the vmin and vmax of the colorbar, which may not be the same
+ # as the norm. There are situations where the colormap has a
+ # narrower range than the colorbar and we want to accommodate the
+ # extra contours.
+ if (isinstance(self.norm, (colors.BoundaryNorm, colors.NoNorm))
+ or self.boundaries is not None):
+ # not using a norm.
+ y = y * (self.vmax - self.vmin) + self.vmin
+ else:
+ # Update the norm values in a context manager as it is only
+ # a temporary change and we don't want to propagate any signals
+ # attached to the norm (callbacks.blocked).
+ with (self.norm.callbacks.blocked(),
+ cbook._setattr_cm(self.norm, vmin=self.vmin, vmax=self.vmax)):
+ y = self.norm.inverse(y)
+ self._y = y
+ X, Y = np.meshgrid([0., 1.], y)
+ if self.orientation == 'vertical':
+ return (X, Y)
+ else:
+ return (Y, X)
+
+ def _forward_boundaries(self, x):
+ # map boundaries equally between 0 and 1...
+ b = self._boundaries
+ y = np.interp(x, b, np.linspace(0, 1, len(b)))
+ # the following avoids ticks in the extends:
+ eps = (b[-1] - b[0]) * 1e-6
+ # map these _well_ out of bounds to keep any ticks out
+ # of the extends region...
+ y[x < b[0]-eps] = -1
+ y[x > b[-1]+eps] = 2
+ return y
+
+ def _inverse_boundaries(self, x):
+ # invert the above...
+ b = self._boundaries
+ return np.interp(x, np.linspace(0, 1, len(b)), b)
+
+ def _reset_locator_formatter_scale(self):
+ """
+ Reset the locator et al to defaults. Any user-hardcoded changes
+ need to be re-entered if this gets called (either at init, or when
+ the mappable normal gets changed: Colorbar.update_normal)
+ """
+ self._process_values()
+ self._locator = None
+ self._minorlocator = None
+ self._formatter = None
+ self._minorformatter = None
+ if (isinstance(self.mappable, contour.ContourSet) and
+ isinstance(self.norm, colors.LogNorm)):
+ # if contours have lognorm, give them a log scale...
+ self._set_scale('log')
+ elif (self.boundaries is not None or
+ isinstance(self.norm, colors.BoundaryNorm)):
+ if self.spacing == 'uniform':
+ funcs = (self._forward_boundaries, self._inverse_boundaries)
+ self._set_scale('function', functions=funcs)
+ elif self.spacing == 'proportional':
+ self._set_scale('linear')
+ elif getattr(self.norm, '_scale', None):
+ # use the norm's scale (if it exists and is not None):
+ self._set_scale(self.norm._scale)
+ elif type(self.norm) is colors.Normalize:
+ # plain Normalize:
+ self._set_scale('linear')
+ else:
+ # norm._scale is None or not an attr: derive the scale from
+ # the Norm:
+ funcs = (self.norm, self.norm.inverse)
+ self._set_scale('function', functions=funcs)
+
+ def _locate(self, x):
+ """
+ Given a set of color data values, return their
+ corresponding colorbar data coordinates.
+ """
+ if isinstance(self.norm, (colors.NoNorm, colors.BoundaryNorm)):
+ b = self._boundaries
+ xn = x
+ else:
+ # Do calculations using normalized coordinates so
+ # as to make the interpolation more accurate.
+ b = self.norm(self._boundaries, clip=False).filled()
+ xn = self.norm(x, clip=False).filled()
+
+ bunique = b[self._inside]
+ yunique = self._y
+
+ z = np.interp(xn, bunique, yunique)
+ return z
+
+ # trivial helpers
+
+ def _uniform_y(self, N):
+ """
+ Return colorbar data coordinates for *N* uniformly
+ spaced boundaries, plus extension lengths if required.
+ """
+ automin = automax = 1. / (N - 1.)
+ extendlength = self._get_extension_lengths(self.extendfrac,
+ automin, automax,
+ default=0.05)
+ y = np.linspace(0, 1, N)
+ return y, extendlength
+
+ def _proportional_y(self):
+ """
+ Return colorbar data coordinates for the boundaries of
+ a proportional colorbar, plus extension lengths if required:
+ """
+ if (isinstance(self.norm, colors.BoundaryNorm) or
+ self.boundaries is not None):
+ y = (self._boundaries - self._boundaries[self._inside][0])
+ y = y / (self._boundaries[self._inside][-1] -
+ self._boundaries[self._inside][0])
+ # need yscaled the same as the axes scale to get
+ # the extend lengths.
+ if self.spacing == 'uniform':
+ yscaled = self._forward_boundaries(self._boundaries)
+ else:
+ yscaled = y
+ else:
+ y = self.norm(self._boundaries.copy())
+ y = np.ma.filled(y, np.nan)
+ # the norm and the scale should be the same...
+ yscaled = y
+ y = y[self._inside]
+ yscaled = yscaled[self._inside]
+ # normalize from 0..1:
+ norm = colors.Normalize(y[0], y[-1])
+ y = np.ma.filled(norm(y), np.nan)
+ norm = colors.Normalize(yscaled[0], yscaled[-1])
+ yscaled = np.ma.filled(norm(yscaled), np.nan)
+ # make the lower and upper extend lengths proportional to the lengths
+ # of the first and last boundary spacing (if extendfrac='auto'):
+ automin = yscaled[1] - yscaled[0]
+ automax = yscaled[-1] - yscaled[-2]
+ extendlength = [0, 0]
+ if self._extend_lower() or self._extend_upper():
+ extendlength = self._get_extension_lengths(
+ self.extendfrac, automin, automax, default=0.05)
+ return y, extendlength
+
+ def _get_extension_lengths(self, frac, automin, automax, default=0.05):
+ """
+ Return the lengths of colorbar extensions.
+
+ This is a helper method for _uniform_y and _proportional_y.
+ """
+ # Set the default value.
+ extendlength = np.array([default, default])
+ if isinstance(frac, str):
+ _api.check_in_list(['auto'], extendfrac=frac.lower())
+ # Use the provided values when 'auto' is required.
+ extendlength[:] = [automin, automax]
+ elif frac is not None:
+ try:
+ # Try to set min and max extension fractions directly.
+ extendlength[:] = frac
+ # If frac is a sequence containing None then NaN may
+ # be encountered. This is an error.
+ if np.isnan(extendlength).any():
+ raise ValueError()
+ except (TypeError, ValueError) as err:
+ # Raise an error on encountering an invalid value for frac.
+ raise ValueError('invalid value for extendfrac') from err
+ return extendlength
+
+ def _extend_lower(self):
+ """Return whether the lower limit is open ended."""
+ minmax = "max" if self.long_axis.get_inverted() else "min"
+ return self.extend in ('both', minmax)
+
+ def _extend_upper(self):
+ """Return whether the upper limit is open ended."""
+ minmax = "min" if self.long_axis.get_inverted() else "max"
+ return self.extend in ('both', minmax)
+
+ def _short_axis(self):
+ """Return the short axis"""
+ if self.orientation == 'vertical':
+ return self.ax.xaxis
+ return self.ax.yaxis
+
+ def _get_view(self):
+ # docstring inherited
+ # An interactive view for a colorbar is the norm's vmin/vmax
+ return self.norm.vmin, self.norm.vmax
+
+ def _set_view(self, view):
+ # docstring inherited
+ # An interactive view for a colorbar is the norm's vmin/vmax
+ self.norm.vmin, self.norm.vmax = view
+
+ def _set_view_from_bbox(self, bbox, direction='in',
+ mode=None, twinx=False, twiny=False):
+ # docstring inherited
+ # For colorbars, we use the zoom bbox to scale the norm's vmin/vmax
+ new_xbound, new_ybound = self.ax._prepare_view_from_bbox(
+ bbox, direction=direction, mode=mode, twinx=twinx, twiny=twiny)
+ if self.orientation == 'horizontal':
+ self.norm.vmin, self.norm.vmax = new_xbound
+ elif self.orientation == 'vertical':
+ self.norm.vmin, self.norm.vmax = new_ybound
+
+ def drag_pan(self, button, key, x, y):
+ # docstring inherited
+ points = self.ax._get_pan_points(button, key, x, y)
+ if points is not None:
+ if self.orientation == 'horizontal':
+ self.norm.vmin, self.norm.vmax = points[:, 0]
+ elif self.orientation == 'vertical':
+ self.norm.vmin, self.norm.vmax = points[:, 1]
+
+
+ColorbarBase = Colorbar # Backcompat API
+
+
+def _normalize_location_orientation(location, orientation):
+ if location is None:
+ location = _get_ticklocation_from_orientation(orientation)
+ loc_settings = _api.check_getitem({
+ "left": {"location": "left", "anchor": (1.0, 0.5),
+ "panchor": (0.0, 0.5), "pad": 0.10},
+ "right": {"location": "right", "anchor": (0.0, 0.5),
+ "panchor": (1.0, 0.5), "pad": 0.05},
+ "top": {"location": "top", "anchor": (0.5, 0.0),
+ "panchor": (0.5, 1.0), "pad": 0.05},
+ "bottom": {"location": "bottom", "anchor": (0.5, 1.0),
+ "panchor": (0.5, 0.0), "pad": 0.15},
+ }, location=location)
+ loc_settings["orientation"] = _get_orientation_from_location(location)
+ if orientation is not None and orientation != loc_settings["orientation"]:
+ # Allow the user to pass both if they are consistent.
+ raise TypeError("location and orientation are mutually exclusive")
+ return loc_settings
+
+
+def _get_orientation_from_location(location):
+ return _api.check_getitem(
+ {None: None, "left": "vertical", "right": "vertical",
+ "top": "horizontal", "bottom": "horizontal"}, location=location)
+
+
+def _get_ticklocation_from_orientation(orientation):
+ return _api.check_getitem(
+ {None: "right", "vertical": "right", "horizontal": "bottom"},
+ orientation=orientation)
+
+
+@_docstring.interpd
+def make_axes(parents, location=None, orientation=None, fraction=0.15,
+ shrink=1.0, aspect=20, **kwargs):
+ """
+ Create an `~.axes.Axes` suitable for a colorbar.
+
+ The Axes is placed in the figure of the *parents* Axes, by resizing and
+ repositioning *parents*.
+
+ Parameters
+ ----------
+ parents : `~matplotlib.axes.Axes` or iterable or `numpy.ndarray` of `~.axes.Axes`
+ The Axes to use as parents for placing the colorbar.
+ %(_make_axes_kw_doc)s
+
+ Returns
+ -------
+ cax : `~matplotlib.axes.Axes`
+ The child Axes.
+ kwargs : dict
+ The reduced keyword dictionary to be passed when creating the colorbar
+ instance.
+ """
+ loc_settings = _normalize_location_orientation(location, orientation)
+ # put appropriate values into the kwargs dict for passing back to
+ # the Colorbar class
+ kwargs['orientation'] = loc_settings['orientation']
+ location = kwargs['ticklocation'] = loc_settings['location']
+
+ anchor = kwargs.pop('anchor', loc_settings['anchor'])
+ panchor = kwargs.pop('panchor', loc_settings['panchor'])
+ aspect0 = aspect
+ # turn parents into a list if it is not already. Note we cannot
+ # use .flatten or .ravel as these copy the references rather than
+ # reuse them, leading to a memory leak
+ if isinstance(parents, np.ndarray):
+ parents = list(parents.flat)
+ elif np.iterable(parents):
+ parents = list(parents)
+ else:
+ parents = [parents]
+
+ fig = parents[0].get_figure()
+
+ pad0 = 0.05 if fig.get_constrained_layout() else loc_settings['pad']
+ pad = kwargs.pop('pad', pad0)
+
+ if not all(fig is ax.get_figure() for ax in parents):
+ raise ValueError('Unable to create a colorbar Axes as not all '
+ 'parents share the same figure.')
+
+ # take a bounding box around all of the given Axes
+ parents_bbox = mtransforms.Bbox.union(
+ [ax.get_position(original=True).frozen() for ax in parents])
+
+ pb = parents_bbox
+ if location in ('left', 'right'):
+ if location == 'left':
+ pbcb, _, pb1 = pb.splitx(fraction, fraction + pad)
+ else:
+ pb1, _, pbcb = pb.splitx(1 - fraction - pad, 1 - fraction)
+ pbcb = pbcb.shrunk(1.0, shrink).anchored(anchor, pbcb)
+ else:
+ if location == 'bottom':
+ pbcb, _, pb1 = pb.splity(fraction, fraction + pad)
+ else:
+ pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction)
+ pbcb = pbcb.shrunk(shrink, 1.0).anchored(anchor, pbcb)
+
+ # define the aspect ratio in terms of y's per x rather than x's per y
+ aspect = 1.0 / aspect
+
+ # define a transform which takes us from old axes coordinates to
+ # new axes coordinates
+ shrinking_trans = mtransforms.BboxTransform(parents_bbox, pb1)
+
+ # transform each of the Axes in parents using the new transform
+ for ax in parents:
+ new_posn = shrinking_trans.transform(ax.get_position(original=True))
+ new_posn = mtransforms.Bbox(new_posn)
+ ax._set_position(new_posn)
+ if panchor is not False:
+ ax.set_anchor(panchor)
+
+ cax = fig.add_axes(pbcb, label="")
+ for a in parents:
+ a._colorbars.append(cax) # tell the parent it has a colorbar
+ cax._colorbar_info = dict(
+ parents=parents,
+ location=location,
+ shrink=shrink,
+ anchor=anchor,
+ panchor=panchor,
+ fraction=fraction,
+ aspect=aspect0,
+ pad=pad)
+ # and we need to set the aspect ratio by hand...
+ cax.set_anchor(anchor)
+ cax.set_box_aspect(aspect)
+ cax.set_aspect('auto')
+
+ return cax, kwargs
+
+
+@_docstring.interpd
+def make_axes_gridspec(parent, *, location=None, orientation=None,
+ fraction=0.15, shrink=1.0, aspect=20, **kwargs):
+ """
+ Create an `~.axes.Axes` suitable for a colorbar.
+
+ The Axes is placed in the figure of the *parent* Axes, by resizing and
+ repositioning *parent*.
+
+ This function is similar to `.make_axes` and mostly compatible with it.
+ Primary differences are
+
+ - `.make_axes_gridspec` requires the *parent* to have a subplotspec.
+ - `.make_axes` positions the Axes in figure coordinates;
+ `.make_axes_gridspec` positions it using a subplotspec.
+ - `.make_axes` updates the position of the parent. `.make_axes_gridspec`
+ replaces the parent gridspec with a new one.
+
+ Parameters
+ ----------
+ parent : `~matplotlib.axes.Axes`
+ The Axes to use as parent for placing the colorbar.
+ %(_make_axes_kw_doc)s
+
+ Returns
+ -------
+ cax : `~matplotlib.axes.Axes`
+ The child Axes.
+ kwargs : dict
+ The reduced keyword dictionary to be passed when creating the colorbar
+ instance.
+ """
+
+ loc_settings = _normalize_location_orientation(location, orientation)
+ kwargs['orientation'] = loc_settings['orientation']
+ location = kwargs['ticklocation'] = loc_settings['location']
+
+ aspect0 = aspect
+ anchor = kwargs.pop('anchor', loc_settings['anchor'])
+ panchor = kwargs.pop('panchor', loc_settings['panchor'])
+ pad = kwargs.pop('pad', loc_settings["pad"])
+ wh_space = 2 * pad / (1 - pad)
+
+ if location in ('left', 'right'):
+ gs = parent.get_subplotspec().subgridspec(
+ 3, 2, wspace=wh_space, hspace=0,
+ height_ratios=[(1-anchor[1])*(1-shrink), shrink, anchor[1]*(1-shrink)])
+ if location == 'left':
+ gs.set_width_ratios([fraction, 1 - fraction - pad])
+ ss_main = gs[:, 1]
+ ss_cb = gs[1, 0]
+ else:
+ gs.set_width_ratios([1 - fraction - pad, fraction])
+ ss_main = gs[:, 0]
+ ss_cb = gs[1, 1]
+ else:
+ gs = parent.get_subplotspec().subgridspec(
+ 2, 3, hspace=wh_space, wspace=0,
+ width_ratios=[anchor[0]*(1-shrink), shrink, (1-anchor[0])*(1-shrink)])
+ if location == 'top':
+ gs.set_height_ratios([fraction, 1 - fraction - pad])
+ ss_main = gs[1, :]
+ ss_cb = gs[0, 1]
+ else:
+ gs.set_height_ratios([1 - fraction - pad, fraction])
+ ss_main = gs[0, :]
+ ss_cb = gs[1, 1]
+ aspect = 1 / aspect
+
+ parent.set_subplotspec(ss_main)
+ if panchor is not False:
+ parent.set_anchor(panchor)
+
+ fig = parent.get_figure()
+ cax = fig.add_subplot(ss_cb, label="")
+ parent._colorbars.append(cax) # tell the parent it has a colorbar
+ cax.set_anchor(anchor)
+ cax.set_box_aspect(aspect)
+ cax.set_aspect('auto')
+ cax._colorbar_info = dict(
+ location=location,
+ parents=[parent],
+ shrink=shrink,
+ anchor=anchor,
+ panchor=panchor,
+ fraction=fraction,
+ aspect=aspect0,
+ pad=pad)
+
+ return cax, kwargs
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..07467ca74f3d5a528f8c1c0859fc25a2eb285664
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.pyi
@@ -0,0 +1,139 @@
+import matplotlib.spines as mspines
+from matplotlib import cm, collections, colors, contour, colorizer
+from matplotlib.axes import Axes
+from matplotlib.axis import Axis
+from matplotlib.backend_bases import RendererBase
+from matplotlib.patches import Patch
+from matplotlib.ticker import Locator, Formatter
+from matplotlib.transforms import Bbox
+
+import numpy as np
+from numpy.typing import ArrayLike
+from collections.abc import Sequence
+from typing import Any, Literal, overload
+from .typing import ColorType
+
+class _ColorbarSpine(mspines.Spines):
+ def __init__(self, axes: Axes): ...
+ def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox:...
+ def set_xy(self, xy: ArrayLike) -> None: ...
+ def draw(self, renderer: RendererBase | None) -> None:...
+
+
+class Colorbar:
+ n_rasterize: int
+ mappable: cm.ScalarMappable | colorizer.ColorizingArtist
+ ax: Axes
+ alpha: float | None
+ cmap: colors.Colormap
+ norm: colors.Normalize
+ values: Sequence[float] | None
+ boundaries: Sequence[float] | None
+ extend: Literal["neither", "both", "min", "max"]
+ spacing: Literal["uniform", "proportional"]
+ orientation: Literal["vertical", "horizontal"]
+ drawedges: bool
+ extendfrac: Literal["auto"] | float | Sequence[float] | None
+ extendrect: bool
+ solids: None | collections.QuadMesh
+ solids_patches: list[Patch]
+ lines: list[collections.LineCollection]
+ outline: _ColorbarSpine
+ dividers: collections.LineCollection
+ ticklocation: Literal["left", "right", "top", "bottom"]
+ def __init__(
+ self,
+ ax: Axes,
+ mappable: cm.ScalarMappable | colorizer.ColorizingArtist | None = ...,
+ *,
+ cmap: str | colors.Colormap | None = ...,
+ norm: colors.Normalize | None = ...,
+ alpha: float | None = ...,
+ values: Sequence[float] | None = ...,
+ boundaries: Sequence[float] | None = ...,
+ orientation: Literal["vertical", "horizontal"] | None = ...,
+ ticklocation: Literal["auto", "left", "right", "top", "bottom"] = ...,
+ extend: Literal["neither", "both", "min", "max"] | None = ...,
+ spacing: Literal["uniform", "proportional"] = ...,
+ ticks: Sequence[float] | Locator | None = ...,
+ format: str | Formatter | None = ...,
+ drawedges: bool = ...,
+ extendfrac: Literal["auto"] | float | Sequence[float] | None = ...,
+ extendrect: bool = ...,
+ label: str = ...,
+ location: Literal["left", "right", "top", "bottom"] | None = ...
+ ) -> None: ...
+ @property
+ def long_axis(self) -> Axis: ...
+ @property
+ def locator(self) -> Locator: ...
+ @locator.setter
+ def locator(self, loc: Locator) -> None: ...
+ @property
+ def minorlocator(self) -> Locator: ...
+ @minorlocator.setter
+ def minorlocator(self, loc: Locator) -> None: ...
+ @property
+ def formatter(self) -> Formatter: ...
+ @formatter.setter
+ def formatter(self, fmt: Formatter) -> None: ...
+ @property
+ def minorformatter(self) -> Formatter: ...
+ @minorformatter.setter
+ def minorformatter(self, fmt: Formatter) -> None: ...
+ def update_normal(self, mappable: cm.ScalarMappable | None = ...) -> None: ...
+ @overload
+ def add_lines(self, CS: contour.ContourSet, erase: bool = ...) -> None: ...
+ @overload
+ def add_lines(
+ self,
+ levels: ArrayLike,
+ colors: ColorType | Sequence[ColorType],
+ linewidths: float | ArrayLike,
+ erase: bool = ...,
+ ) -> None: ...
+ def update_ticks(self) -> None: ...
+ def set_ticks(
+ self,
+ ticks: Sequence[float] | Locator,
+ *,
+ labels: Sequence[str] | None = ...,
+ minor: bool = ...,
+ **kwargs
+ ) -> None: ...
+ def get_ticks(self, minor: bool = ...) -> np.ndarray: ...
+ def set_ticklabels(
+ self,
+ ticklabels: Sequence[str],
+ *,
+ minor: bool = ...,
+ **kwargs
+ ) -> None: ...
+ def minorticks_on(self) -> None: ...
+ def minorticks_off(self) -> None: ...
+ def set_label(self, label: str, *, loc: str | None = ..., **kwargs) -> None: ...
+ def set_alpha(self, alpha: float | np.ndarray) -> None: ...
+ def remove(self) -> None: ...
+ def drag_pan(self, button: Any, key: Any, x: float, y: float) -> None: ...
+
+ColorbarBase = Colorbar
+
+def make_axes(
+ parents: Axes | list[Axes] | np.ndarray,
+ location: Literal["left", "right", "top", "bottom"] | None = ...,
+ orientation: Literal["vertical", "horizontal"] | None = ...,
+ fraction: float = ...,
+ shrink: float = ...,
+ aspect: float = ...,
+ **kwargs
+) -> tuple[Axes, dict[str, Any]]: ...
+def make_axes_gridspec(
+ parent: Axes,
+ *,
+ location: Literal["left", "right", "top", "bottom"] | None = ...,
+ orientation: Literal["vertical", "horizontal"] | None = ...,
+ fraction: float = ...,
+ shrink: float = ...,
+ aspect: float = ...,
+ **kwargs
+) -> tuple[Axes, dict[str, Any]]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4223f38980410c1929d598265cd3dab1c3cfcad
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.py
@@ -0,0 +1,703 @@
+"""
+The Colorizer class which handles the data to color pipeline via a
+normalization and a colormap.
+
+.. admonition:: Provisional status of colorizer
+
+ The ``colorizer`` module and classes in this file are considered
+ provisional and may change at any time without a deprecation period.
+
+.. seealso::
+
+ :doc:`/gallery/color/colormap_reference` for a list of builtin colormaps.
+
+ :ref:`colormap-manipulation` for examples of how to make colormaps.
+
+ :ref:`colormaps` for an in-depth discussion of choosing colormaps.
+
+ :ref:`colormapnorms` for more details about data normalization.
+
+"""
+
+import functools
+
+import numpy as np
+from numpy import ma
+
+from matplotlib import _api, colors, cbook, scale, artist
+import matplotlib as mpl
+
+mpl._docstring.interpd.register(
+ colorizer_doc="""\
+colorizer : `~matplotlib.colorizer.Colorizer` or None, default: None
+ The Colorizer object used to map color to data. If None, a Colorizer
+ object is created from a *norm* and *cmap*.""",
+ )
+
+
+class Colorizer:
+ """
+ Data to color pipeline.
+
+ This pipeline is accessible via `.Colorizer.to_rgba` and executed via
+ the `.Colorizer.norm` and `.Colorizer.cmap` attributes.
+
+ Parameters
+ ----------
+ cmap: colorbar.Colorbar or str or None, default: None
+ The colormap used to color data.
+
+ norm: colors.Normalize or str or None, default: None
+ The normalization used to normalize the data
+ """
+ def __init__(self, cmap=None, norm=None):
+
+ self._cmap = None
+ self._set_cmap(cmap)
+
+ self._id_norm = None
+ self._norm = None
+ self.norm = norm
+
+ self.callbacks = cbook.CallbackRegistry(signals=["changed"])
+ self.colorbar = None
+
+ def _scale_norm(self, norm, vmin, vmax, A):
+ """
+ Helper for initial scaling.
+
+ Used by public functions that create a ScalarMappable and support
+ parameters *vmin*, *vmax* and *norm*. This makes sure that a *norm*
+ will take precedence over *vmin*, *vmax*.
+
+ Note that this method does not set the norm.
+ """
+ if vmin is not None or vmax is not None:
+ self.set_clim(vmin, vmax)
+ if isinstance(norm, colors.Normalize):
+ raise ValueError(
+ "Passing a Normalize instance simultaneously with "
+ "vmin/vmax is not supported. Please pass vmin/vmax "
+ "directly to the norm when creating it.")
+
+ # always resolve the autoscaling so we have concrete limits
+ # rather than deferring to draw time.
+ self.autoscale_None(A)
+
+ @property
+ def norm(self):
+ return self._norm
+
+ @norm.setter
+ def norm(self, norm):
+ _api.check_isinstance((colors.Normalize, str, None), norm=norm)
+ if norm is None:
+ norm = colors.Normalize()
+ elif isinstance(norm, str):
+ try:
+ scale_cls = scale._scale_mapping[norm]
+ except KeyError:
+ raise ValueError(
+ "Invalid norm str name; the following values are "
+ f"supported: {', '.join(scale._scale_mapping)}"
+ ) from None
+ norm = _auto_norm_from_scale(scale_cls)()
+
+ if norm is self.norm:
+ # We aren't updating anything
+ return
+
+ in_init = self.norm is None
+ # Remove the current callback and connect to the new one
+ if not in_init:
+ self.norm.callbacks.disconnect(self._id_norm)
+ self._norm = norm
+ self._id_norm = self.norm.callbacks.connect('changed',
+ self.changed)
+ if not in_init:
+ self.changed()
+
+ def to_rgba(self, x, alpha=None, bytes=False, norm=True):
+ """
+ Return a normalized RGBA array corresponding to *x*.
+
+ In the normal case, *x* is a 1D or 2D sequence of scalars, and
+ the corresponding `~numpy.ndarray` of RGBA values will be returned,
+ based on the norm and colormap set for this Colorizer.
+
+ There is one special case, for handling images that are already
+ RGB or RGBA, such as might have been read from an image file.
+ If *x* is an `~numpy.ndarray` with 3 dimensions,
+ and the last dimension is either 3 or 4, then it will be
+ treated as an RGB or RGBA array, and no mapping will be done.
+ The array can be `~numpy.uint8`, or it can be floats with
+ values in the 0-1 range; otherwise a ValueError will be raised.
+ Any NaNs or masked elements will be set to 0 alpha.
+ If the last dimension is 3, the *alpha* kwarg (defaulting to 1)
+ will be used to fill in the transparency. If the last dimension
+ is 4, the *alpha* kwarg is ignored; it does not
+ replace the preexisting alpha. A ValueError will be raised
+ if the third dimension is other than 3 or 4.
+
+ In either case, if *bytes* is *False* (default), the RGBA
+ array will be floats in the 0-1 range; if it is *True*,
+ the returned RGBA array will be `~numpy.uint8` in the 0 to 255 range.
+
+ If norm is False, no normalization of the input data is
+ performed, and it is assumed to be in the range (0-1).
+
+ """
+ # First check for special case, image input:
+ if isinstance(x, np.ndarray) and x.ndim == 3:
+ return self._pass_image_data(x, alpha, bytes, norm)
+
+ # Otherwise run norm -> colormap pipeline
+ x = ma.asarray(x)
+ if norm:
+ x = self.norm(x)
+ rgba = self.cmap(x, alpha=alpha, bytes=bytes)
+ return rgba
+
+ @staticmethod
+ def _pass_image_data(x, alpha=None, bytes=False, norm=True):
+ """
+ Helper function to pass ndarray of shape (...,3) or (..., 4)
+ through `to_rgba()`, see `to_rgba()` for docstring.
+ """
+ if x.shape[2] == 3:
+ if alpha is None:
+ alpha = 1
+ if x.dtype == np.uint8:
+ alpha = np.uint8(alpha * 255)
+ m, n = x.shape[:2]
+ xx = np.empty(shape=(m, n, 4), dtype=x.dtype)
+ xx[:, :, :3] = x
+ xx[:, :, 3] = alpha
+ elif x.shape[2] == 4:
+ xx = x
+ else:
+ raise ValueError("Third dimension must be 3 or 4")
+ if xx.dtype.kind == 'f':
+ # If any of R, G, B, or A is nan, set to 0
+ if np.any(nans := np.isnan(x)):
+ if x.shape[2] == 4:
+ xx = xx.copy()
+ xx[np.any(nans, axis=2), :] = 0
+
+ if norm and (xx.max() > 1 or xx.min() < 0):
+ raise ValueError("Floating point image RGB values "
+ "must be in the 0..1 range.")
+ if bytes:
+ xx = (xx * 255).astype(np.uint8)
+ elif xx.dtype == np.uint8:
+ if not bytes:
+ xx = xx.astype(np.float32) / 255
+ else:
+ raise ValueError("Image RGB array must be uint8 or "
+ "floating point; found %s" % xx.dtype)
+ # Account for any masked entries in the original array
+ # If any of R, G, B, or A are masked for an entry, we set alpha to 0
+ if np.ma.is_masked(x):
+ xx[np.any(np.ma.getmaskarray(x), axis=2), 3] = 0
+ return xx
+
+ def autoscale(self, A):
+ """
+ Autoscale the scalar limits on the norm instance using the
+ current array
+ """
+ if A is None:
+ raise TypeError('You must first set_array for mappable')
+ # If the norm's limits are updated self.changed() will be called
+ # through the callbacks attached to the norm
+ self.norm.autoscale(A)
+
+ def autoscale_None(self, A):
+ """
+ Autoscale the scalar limits on the norm instance using the
+ current array, changing only limits that are None
+ """
+ if A is None:
+ raise TypeError('You must first set_array for mappable')
+ # If the norm's limits are updated self.changed() will be called
+ # through the callbacks attached to the norm
+ self.norm.autoscale_None(A)
+
+ def _set_cmap(self, cmap):
+ """
+ Set the colormap for luminance data.
+
+ Parameters
+ ----------
+ cmap : `.Colormap` or str or None
+ """
+ # bury import to avoid circular imports
+ from matplotlib import cm
+ in_init = self._cmap is None
+ self._cmap = cm._ensure_cmap(cmap)
+ if not in_init:
+ self.changed() # Things are not set up properly yet.
+
+ @property
+ def cmap(self):
+ return self._cmap
+
+ @cmap.setter
+ def cmap(self, cmap):
+ self._set_cmap(cmap)
+
+ def set_clim(self, vmin=None, vmax=None):
+ """
+ Set the norm limits for image scaling.
+
+ Parameters
+ ----------
+ vmin, vmax : float
+ The limits.
+
+ The limits may also be passed as a tuple (*vmin*, *vmax*) as a
+ single positional argument.
+
+ .. ACCEPTS: (vmin: float, vmax: float)
+ """
+ # If the norm's limits are updated self.changed() will be called
+ # through the callbacks attached to the norm, this causes an inconsistent
+ # state, to prevent this blocked context manager is used
+ if vmax is None:
+ try:
+ vmin, vmax = vmin
+ except (TypeError, ValueError):
+ pass
+
+ orig_vmin_vmax = self.norm.vmin, self.norm.vmax
+
+ # Blocked context manager prevents callbacks from being triggered
+ # until both vmin and vmax are updated
+ with self.norm.callbacks.blocked(signal='changed'):
+ if vmin is not None:
+ self.norm.vmin = colors._sanitize_extrema(vmin)
+ if vmax is not None:
+ self.norm.vmax = colors._sanitize_extrema(vmax)
+
+ # emit a update signal if the limits are changed
+ if orig_vmin_vmax != (self.norm.vmin, self.norm.vmax):
+ self.norm.callbacks.process('changed')
+
+ def get_clim(self):
+ """
+ Return the values (min, max) that are mapped to the colormap limits.
+ """
+ return self.norm.vmin, self.norm.vmax
+
+ def changed(self):
+ """
+ Call this whenever the mappable is changed to notify all the
+ callbackSM listeners to the 'changed' signal.
+ """
+ self.callbacks.process('changed')
+ self.stale = True
+
+ @property
+ def vmin(self):
+ return self.get_clim()[0]
+
+ @vmin.setter
+ def vmin(self, vmin):
+ self.set_clim(vmin=vmin)
+
+ @property
+ def vmax(self):
+ return self.get_clim()[1]
+
+ @vmax.setter
+ def vmax(self, vmax):
+ self.set_clim(vmax=vmax)
+
+ @property
+ def clip(self):
+ return self.norm.clip
+
+ @clip.setter
+ def clip(self, clip):
+ self.norm.clip = clip
+
+
+class _ColorizerInterface:
+ """
+ Base class that contains the interface to `Colorizer` objects from
+ a `ColorizingArtist` or `.cm.ScalarMappable`.
+
+ Note: This class only contain functions that interface the .colorizer
+ attribute. Other functions that as shared between `.ColorizingArtist`
+ and `.cm.ScalarMappable` are not included.
+ """
+ def _scale_norm(self, norm, vmin, vmax):
+ self._colorizer._scale_norm(norm, vmin, vmax, self._A)
+
+ def to_rgba(self, x, alpha=None, bytes=False, norm=True):
+ """
+ Return a normalized RGBA array corresponding to *x*.
+
+ In the normal case, *x* is a 1D or 2D sequence of scalars, and
+ the corresponding `~numpy.ndarray` of RGBA values will be returned,
+ based on the norm and colormap set for this Colorizer.
+
+ There is one special case, for handling images that are already
+ RGB or RGBA, such as might have been read from an image file.
+ If *x* is an `~numpy.ndarray` with 3 dimensions,
+ and the last dimension is either 3 or 4, then it will be
+ treated as an RGB or RGBA array, and no mapping will be done.
+ The array can be `~numpy.uint8`, or it can be floats with
+ values in the 0-1 range; otherwise a ValueError will be raised.
+ Any NaNs or masked elements will be set to 0 alpha.
+ If the last dimension is 3, the *alpha* kwarg (defaulting to 1)
+ will be used to fill in the transparency. If the last dimension
+ is 4, the *alpha* kwarg is ignored; it does not
+ replace the preexisting alpha. A ValueError will be raised
+ if the third dimension is other than 3 or 4.
+
+ In either case, if *bytes* is *False* (default), the RGBA
+ array will be floats in the 0-1 range; if it is *True*,
+ the returned RGBA array will be `~numpy.uint8` in the 0 to 255 range.
+
+ If norm is False, no normalization of the input data is
+ performed, and it is assumed to be in the range (0-1).
+
+ """
+ return self._colorizer.to_rgba(x, alpha=alpha, bytes=bytes, norm=norm)
+
+ def get_clim(self):
+ """
+ Return the values (min, max) that are mapped to the colormap limits.
+ """
+ return self._colorizer.get_clim()
+
+ def set_clim(self, vmin=None, vmax=None):
+ """
+ Set the norm limits for image scaling.
+
+ Parameters
+ ----------
+ vmin, vmax : float
+ The limits.
+
+ For scalar data, the limits may also be passed as a
+ tuple (*vmin*, *vmax*) as a single positional argument.
+
+ .. ACCEPTS: (vmin: float, vmax: float)
+ """
+ # If the norm's limits are updated self.changed() will be called
+ # through the callbacks attached to the norm
+ self._colorizer.set_clim(vmin, vmax)
+
+ def get_alpha(self):
+ try:
+ return super().get_alpha()
+ except AttributeError:
+ return 1
+
+ @property
+ def cmap(self):
+ return self._colorizer.cmap
+
+ @cmap.setter
+ def cmap(self, cmap):
+ self._colorizer.cmap = cmap
+
+ def get_cmap(self):
+ """Return the `.Colormap` instance."""
+ return self._colorizer.cmap
+
+ def set_cmap(self, cmap):
+ """
+ Set the colormap for luminance data.
+
+ Parameters
+ ----------
+ cmap : `.Colormap` or str or None
+ """
+ self.cmap = cmap
+
+ @property
+ def norm(self):
+ return self._colorizer.norm
+
+ @norm.setter
+ def norm(self, norm):
+ self._colorizer.norm = norm
+
+ def set_norm(self, norm):
+ """
+ Set the normalization instance.
+
+ Parameters
+ ----------
+ norm : `.Normalize` or str or None
+
+ Notes
+ -----
+ If there are any colorbars using the mappable for this norm, setting
+ the norm of the mappable will reset the norm, locator, and formatters
+ on the colorbar to default.
+ """
+ self.norm = norm
+
+ def autoscale(self):
+ """
+ Autoscale the scalar limits on the norm instance using the
+ current array
+ """
+ self._colorizer.autoscale(self._A)
+
+ def autoscale_None(self):
+ """
+ Autoscale the scalar limits on the norm instance using the
+ current array, changing only limits that are None
+ """
+ self._colorizer.autoscale_None(self._A)
+
+ @property
+ def colorbar(self):
+ """
+ The last colorbar associated with this object. May be None
+ """
+ return self._colorizer.colorbar
+
+ @colorbar.setter
+ def colorbar(self, colorbar):
+ self._colorizer.colorbar = colorbar
+
+ def _format_cursor_data_override(self, data):
+ # This function overwrites Artist.format_cursor_data(). We cannot
+ # implement cm.ScalarMappable.format_cursor_data() directly, because
+ # most cm.ScalarMappable subclasses inherit from Artist first and from
+ # cm.ScalarMappable second, so Artist.format_cursor_data would always
+ # have precedence over cm.ScalarMappable.format_cursor_data.
+
+ # Note if cm.ScalarMappable is depreciated, this functionality should be
+ # implemented as format_cursor_data() on ColorizingArtist.
+ n = self.cmap.N
+ if np.ma.getmask(data):
+ return "[]"
+ normed = self.norm(data)
+ if np.isfinite(normed):
+ if isinstance(self.norm, colors.BoundaryNorm):
+ # not an invertible normalization mapping
+ cur_idx = np.argmin(np.abs(self.norm.boundaries - data))
+ neigh_idx = max(0, cur_idx - 1)
+ # use max diff to prevent delta == 0
+ delta = np.diff(
+ self.norm.boundaries[neigh_idx:cur_idx + 2]
+ ).max()
+ elif self.norm.vmin == self.norm.vmax:
+ # singular norms, use delta of 10% of only value
+ delta = np.abs(self.norm.vmin * .1)
+ else:
+ # Midpoints of neighboring color intervals.
+ neighbors = self.norm.inverse(
+ (int(normed * n) + np.array([0, 1])) / n)
+ delta = abs(neighbors - data).max()
+ g_sig_digits = cbook._g_sig_digits(data, delta)
+ else:
+ g_sig_digits = 3 # Consistent with default below.
+ return f"[{data:-#.{g_sig_digits}g}]"
+
+
+class _ScalarMappable(_ColorizerInterface):
+ """
+ A mixin class to map one or multiple sets of scalar data to RGBA.
+
+ The ScalarMappable applies data normalization before returning RGBA colors from
+ the given `~matplotlib.colors.Colormap`.
+ """
+
+ # _ScalarMappable exists for compatibility with
+ # code written before the introduction of the Colorizer
+ # and ColorizingArtist classes.
+
+ # _ScalarMappable can be depreciated so that ColorizingArtist
+ # inherits directly from _ColorizerInterface.
+ # in this case, the following changes should occur:
+ # __init__() has its functionality moved to ColorizingArtist.
+ # set_array(), get_array(), _get_colorizer() and
+ # _check_exclusionary_keywords() are moved to ColorizingArtist.
+ # changed() can be removed so long as colorbar.Colorbar
+ # is changed to connect to the colorizer instead of the
+ # ScalarMappable/ColorizingArtist,
+ # otherwise changed() can be moved to ColorizingArtist.
+ def __init__(self, norm=None, cmap=None, *, colorizer=None, **kwargs):
+ """
+ Parameters
+ ----------
+ norm : `.Normalize` (or subclass thereof) or str or None
+ The normalizing object which scales data, typically into the
+ interval ``[0, 1]``.
+ If a `str`, a `.Normalize` subclass is dynamically generated based
+ on the scale with the corresponding name.
+ If *None*, *norm* defaults to a *colors.Normalize* object which
+ initializes its scaling based on the first data processed.
+ cmap : str or `~matplotlib.colors.Colormap`
+ The colormap used to map normalized data values to RGBA colors.
+ """
+ super().__init__(**kwargs)
+ self._A = None
+ self._colorizer = self._get_colorizer(colorizer=colorizer, norm=norm, cmap=cmap)
+
+ self.colorbar = None
+ self._id_colorizer = self._colorizer.callbacks.connect('changed', self.changed)
+ self.callbacks = cbook.CallbackRegistry(signals=["changed"])
+
+ def set_array(self, A):
+ """
+ Set the value array from array-like *A*.
+
+ Parameters
+ ----------
+ A : array-like or None
+ The values that are mapped to colors.
+
+ The base class `.ScalarMappable` does not make any assumptions on
+ the dimensionality and shape of the value array *A*.
+ """
+ if A is None:
+ self._A = None
+ return
+
+ A = cbook.safe_masked_invalid(A, copy=True)
+ if not np.can_cast(A.dtype, float, "same_kind"):
+ raise TypeError(f"Image data of dtype {A.dtype} cannot be "
+ "converted to float")
+
+ self._A = A
+ if not self.norm.scaled():
+ self._colorizer.autoscale_None(A)
+
+ def get_array(self):
+ """
+ Return the array of values, that are mapped to colors.
+
+ The base class `.ScalarMappable` does not make any assumptions on
+ the dimensionality and shape of the array.
+ """
+ return self._A
+
+ def changed(self):
+ """
+ Call this whenever the mappable is changed to notify all the
+ callbackSM listeners to the 'changed' signal.
+ """
+ self.callbacks.process('changed', self)
+ self.stale = True
+
+ @staticmethod
+ def _check_exclusionary_keywords(colorizer, **kwargs):
+ """
+ Raises a ValueError if any kwarg is not None while colorizer is not None
+ """
+ if colorizer is not None:
+ if any([val is not None for val in kwargs.values()]):
+ raise ValueError("The `colorizer` keyword cannot be used simultaneously"
+ " with any of the following keywords: "
+ + ", ".join(f'`{key}`' for key in kwargs.keys()))
+
+ @staticmethod
+ def _get_colorizer(cmap, norm, colorizer):
+ if isinstance(colorizer, Colorizer):
+ _ScalarMappable._check_exclusionary_keywords(
+ Colorizer, cmap=cmap, norm=norm
+ )
+ return colorizer
+ return Colorizer(cmap, norm)
+
+# The docstrings here must be generic enough to apply to all relevant methods.
+mpl._docstring.interpd.register(
+ cmap_doc="""\
+cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
+ The Colormap instance or registered colormap name used to map scalar data
+ to colors.""",
+ norm_doc="""\
+norm : str or `~matplotlib.colors.Normalize`, optional
+ The normalization method used to scale scalar data to the [0, 1] range
+ before mapping to colors using *cmap*. By default, a linear scaling is
+ used, mapping the lowest value to 0 and the highest to 1.
+
+ If given, this can be one of the following:
+
+ - An instance of `.Normalize` or one of its subclasses
+ (see :ref:`colormapnorms`).
+ - A scale name, i.e. one of "linear", "log", "symlog", "logit", etc. For a
+ list of available scales, call `matplotlib.scale.get_scale_names()`.
+ In that case, a suitable `.Normalize` subclass is dynamically generated
+ and instantiated.""",
+ vmin_vmax_doc="""\
+vmin, vmax : float, optional
+ When using scalar data and no explicit *norm*, *vmin* and *vmax* define
+ the data range that the colormap covers. By default, the colormap covers
+ the complete value range of the supplied data. It is an error to use
+ *vmin*/*vmax* when a *norm* instance is given (but using a `str` *norm*
+ name together with *vmin*/*vmax* is acceptable).""",
+)
+
+
+class ColorizingArtist(_ScalarMappable, artist.Artist):
+ """
+ Base class for artists that make map data to color using a `.colorizer.Colorizer`.
+
+ The `.colorizer.Colorizer` applies data normalization before
+ returning RGBA colors from a `~matplotlib.colors.Colormap`.
+
+ """
+ def __init__(self, colorizer, **kwargs):
+ """
+ Parameters
+ ----------
+ colorizer : `.colorizer.Colorizer`
+ """
+ _api.check_isinstance(Colorizer, colorizer=colorizer)
+ super().__init__(colorizer=colorizer, **kwargs)
+
+ @property
+ def colorizer(self):
+ return self._colorizer
+
+ @colorizer.setter
+ def colorizer(self, cl):
+ _api.check_isinstance(Colorizer, colorizer=cl)
+ self._colorizer.callbacks.disconnect(self._id_colorizer)
+ self._colorizer = cl
+ self._id_colorizer = cl.callbacks.connect('changed', self.changed)
+
+ def _set_colorizer_check_keywords(self, colorizer, **kwargs):
+ """
+ Raises a ValueError if any kwarg is not None while colorizer is not None.
+ """
+ self._check_exclusionary_keywords(colorizer, **kwargs)
+ self.colorizer = colorizer
+
+
+def _auto_norm_from_scale(scale_cls):
+ """
+ Automatically generate a norm class from *scale_cls*.
+
+ This differs from `.colors.make_norm_from_scale` in the following points:
+
+ - This function is not a class decorator, but directly returns a norm class
+ (as if decorating `.Normalize`).
+ - The scale is automatically constructed with ``nonpositive="mask"``, if it
+ supports such a parameter, to work around the difference in defaults
+ between standard scales (which use "clip") and norms (which use "mask").
+
+ Note that ``make_norm_from_scale`` caches the generated norm classes
+ (not the instances) and reuses them for later calls. For example,
+ ``type(_auto_norm_from_scale("log")) == LogNorm``.
+ """
+ # Actually try to construct an instance, to verify whether
+ # ``nonpositive="mask"`` is supported.
+ try:
+ norm = colors.make_norm_from_scale(
+ functools.partial(scale_cls, nonpositive="mask"))(
+ colors.Normalize)()
+ except TypeError:
+ norm = colors.make_norm_from_scale(scale_cls)(
+ colors.Normalize)()
+ return type(norm)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..8fcce3e5d63ba727efc2cdd18d8a8565b974bac2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.pyi
@@ -0,0 +1,102 @@
+from matplotlib import cbook, colorbar, colors, artist
+
+from typing import overload
+import numpy as np
+from numpy.typing import ArrayLike
+
+
+class Colorizer:
+ colorbar: colorbar.Colorbar | None
+ callbacks: cbook.CallbackRegistry
+ def __init__(
+ self,
+ cmap: str | colors.Colormap | None = ...,
+ norm: str | colors.Normalize | None = ...,
+ ) -> None: ...
+ @property
+ def norm(self) -> colors.Normalize: ...
+ @norm.setter
+ def norm(self, norm: colors.Normalize | str | None) -> None: ...
+ def to_rgba(
+ self,
+ x: np.ndarray,
+ alpha: float | ArrayLike | None = ...,
+ bytes: bool = ...,
+ norm: bool = ...,
+ ) -> np.ndarray: ...
+ def autoscale(self, A: ArrayLike) -> None: ...
+ def autoscale_None(self, A: ArrayLike) -> None: ...
+ @property
+ def cmap(self) -> colors.Colormap: ...
+ @cmap.setter
+ def cmap(self, cmap: colors.Colormap | str | None) -> None: ...
+ def get_clim(self) -> tuple[float, float]: ...
+ def set_clim(self, vmin: float | tuple[float, float] | None = ..., vmax: float | None = ...) -> None: ...
+ def changed(self) -> None: ...
+ @property
+ def vmin(self) -> float | None: ...
+ @vmin.setter
+ def vmin(self, value: float | None) -> None: ...
+ @property
+ def vmax(self) -> float | None: ...
+ @vmax.setter
+ def vmax(self, value: float | None) -> None: ...
+ @property
+ def clip(self) -> bool: ...
+ @clip.setter
+ def clip(self, value: bool) -> None: ...
+
+
+class _ColorizerInterface:
+ cmap: colors.Colormap
+ colorbar: colorbar.Colorbar | None
+ callbacks: cbook.CallbackRegistry
+ def to_rgba(
+ self,
+ x: np.ndarray,
+ alpha: float | ArrayLike | None = ...,
+ bytes: bool = ...,
+ norm: bool = ...,
+ ) -> np.ndarray: ...
+ def get_clim(self) -> tuple[float, float]: ...
+ def set_clim(self, vmin: float | tuple[float, float] | None = ..., vmax: float | None = ...) -> None: ...
+ def get_alpha(self) -> float | None: ...
+ def get_cmap(self) -> colors.Colormap: ...
+ def set_cmap(self, cmap: str | colors.Colormap) -> None: ...
+ @property
+ def norm(self) -> colors.Normalize: ...
+ @norm.setter
+ def norm(self, norm: colors.Normalize | str | None) -> None: ...
+ def set_norm(self, norm: colors.Normalize | str | None) -> None: ...
+ def autoscale(self) -> None: ...
+ def autoscale_None(self) -> None: ...
+
+
+class _ScalarMappable(_ColorizerInterface):
+ def __init__(
+ self,
+ norm: colors.Normalize | None = ...,
+ cmap: str | colors.Colormap | None = ...,
+ *,
+ colorizer: Colorizer | None = ...,
+ **kwargs
+ ) -> None: ...
+ def set_array(self, A: ArrayLike | None) -> None: ...
+ def get_array(self) -> np.ndarray | None: ...
+ def changed(self) -> None: ...
+
+
+class ColorizingArtist(_ScalarMappable, artist.Artist):
+ callbacks: cbook.CallbackRegistry
+ def __init__(
+ self,
+ colorizer: Colorizer,
+ **kwargs
+ ) -> None: ...
+ def set_array(self, A: ArrayLike | None) -> None: ...
+ def get_array(self) -> np.ndarray | None: ...
+ def changed(self) -> None: ...
+ @property
+ def colorizer(self) -> Colorizer: ...
+ @colorizer.setter
+ def colorizer(self, cl: Colorizer) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa53b575b7379bafb524388fad125c8c453f0781
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.py
@@ -0,0 +1,3715 @@
+"""
+A module for converting numbers or color arguments to *RGB* or *RGBA*.
+
+*RGB* and *RGBA* are sequences of, respectively, 3 or 4 floats in the
+range 0-1.
+
+This module includes functions and classes for color specification conversions,
+and for mapping numbers to colors in a 1-D array of colors called a colormap.
+
+Mapping data onto colors using a colormap typically involves two steps: a data
+array is first mapped onto the range 0-1 using a subclass of `Normalize`,
+then this number is mapped to a color using a subclass of `Colormap`. Two
+subclasses of `Colormap` provided here: `LinearSegmentedColormap`, which uses
+piecewise-linear interpolation to define colormaps, and `ListedColormap`, which
+makes a colormap from a list of colors.
+
+.. seealso::
+
+ :ref:`colormap-manipulation` for examples of how to
+ make colormaps and
+
+ :ref:`colormaps` for a list of built-in colormaps.
+
+ :ref:`colormapnorms` for more details about data
+ normalization
+
+ More colormaps are available at palettable_.
+
+The module also provides functions for checking whether an object can be
+interpreted as a color (`is_color_like`), for converting such an object
+to an RGBA tuple (`to_rgba`) or to an HTML-like hex string in the
+"#rrggbb" format (`to_hex`), and a sequence of colors to an (n, 4)
+RGBA array (`to_rgba_array`). Caching is used for efficiency.
+
+Colors that Matplotlib recognizes are listed at
+:ref:`colors_def`.
+
+.. _palettable: https://jiffyclub.github.io/palettable/
+.. _xkcd color survey: https://xkcd.com/color/rgb/
+"""
+
+import base64
+from collections.abc import Sized, Sequence, Mapping
+import functools
+import importlib
+import inspect
+import io
+import itertools
+from numbers import Real
+import re
+
+from PIL import Image
+from PIL.PngImagePlugin import PngInfo
+
+import matplotlib as mpl
+import numpy as np
+from matplotlib import _api, _cm, cbook, scale, _image
+from ._color_data import BASE_COLORS, TABLEAU_COLORS, CSS4_COLORS, XKCD_COLORS
+
+
+class _ColorMapping(dict):
+ def __init__(self, mapping):
+ super().__init__(mapping)
+ self.cache = {}
+
+ def __setitem__(self, key, value):
+ super().__setitem__(key, value)
+ self.cache.clear()
+
+ def __delitem__(self, key):
+ super().__delitem__(key)
+ self.cache.clear()
+
+
+_colors_full_map = {}
+# Set by reverse priority order.
+_colors_full_map.update(XKCD_COLORS)
+_colors_full_map.update({k.replace('grey', 'gray'): v
+ for k, v in XKCD_COLORS.items()
+ if 'grey' in k})
+_colors_full_map.update(CSS4_COLORS)
+_colors_full_map.update(TABLEAU_COLORS)
+_colors_full_map.update({k.replace('gray', 'grey'): v
+ for k, v in TABLEAU_COLORS.items()
+ if 'gray' in k})
+_colors_full_map.update(BASE_COLORS)
+_colors_full_map = _ColorMapping(_colors_full_map)
+
+_REPR_PNG_SIZE = (512, 64)
+_BIVAR_REPR_PNG_SIZE = 256
+
+
+def get_named_colors_mapping():
+ """Return the global mapping of names to named colors."""
+ return _colors_full_map
+
+
+class ColorSequenceRegistry(Mapping):
+ r"""
+ Container for sequences of colors that are known to Matplotlib by name.
+
+ The universal registry instance is `matplotlib.color_sequences`. There
+ should be no need for users to instantiate `.ColorSequenceRegistry`
+ themselves.
+
+ Read access uses a dict-like interface mapping names to lists of colors::
+
+ import matplotlib as mpl
+ colors = mpl.color_sequences['tab10']
+
+ For a list of built in color sequences, see :doc:`/gallery/color/color_sequences`.
+ The returned lists are copies, so that their modification does not change
+ the global definition of the color sequence.
+
+ Additional color sequences can be added via
+ `.ColorSequenceRegistry.register`::
+
+ mpl.color_sequences.register('rgb', ['r', 'g', 'b'])
+ """
+
+ _BUILTIN_COLOR_SEQUENCES = {
+ 'tab10': _cm._tab10_data,
+ 'tab20': _cm._tab20_data,
+ 'tab20b': _cm._tab20b_data,
+ 'tab20c': _cm._tab20c_data,
+ 'Pastel1': _cm._Pastel1_data,
+ 'Pastel2': _cm._Pastel2_data,
+ 'Paired': _cm._Paired_data,
+ 'Accent': _cm._Accent_data,
+ 'Dark2': _cm._Dark2_data,
+ 'Set1': _cm._Set1_data,
+ 'Set2': _cm._Set2_data,
+ 'Set3': _cm._Set3_data,
+ 'petroff10': _cm._petroff10_data,
+ }
+
+ def __init__(self):
+ self._color_sequences = {**self._BUILTIN_COLOR_SEQUENCES}
+
+ def __getitem__(self, item):
+ try:
+ return list(self._color_sequences[item])
+ except KeyError:
+ raise KeyError(f"{item!r} is not a known color sequence name")
+
+ def __iter__(self):
+ return iter(self._color_sequences)
+
+ def __len__(self):
+ return len(self._color_sequences)
+
+ def __str__(self):
+ return ('ColorSequenceRegistry; available colormaps:\n' +
+ ', '.join(f"'{name}'" for name in self))
+
+ def register(self, name, color_list):
+ """
+ Register a new color sequence.
+
+ The color sequence registry stores a copy of the given *color_list*, so
+ that future changes to the original list do not affect the registered
+ color sequence. Think of this as the registry taking a snapshot
+ of *color_list* at registration.
+
+ Parameters
+ ----------
+ name : str
+ The name for the color sequence.
+
+ color_list : list of :mpltype:`color`
+ An iterable returning valid Matplotlib colors when iterating over.
+ Note however that the returned color sequence will always be a
+ list regardless of the input type.
+
+ """
+ if name in self._BUILTIN_COLOR_SEQUENCES:
+ raise ValueError(f"{name!r} is a reserved name for a builtin "
+ "color sequence")
+
+ color_list = list(color_list) # force copy and coerce type to list
+ for color in color_list:
+ try:
+ to_rgba(color)
+ except ValueError:
+ raise ValueError(
+ f"{color!r} is not a valid color specification")
+
+ self._color_sequences[name] = color_list
+
+ def unregister(self, name):
+ """
+ Remove a sequence from the registry.
+
+ You cannot remove built-in color sequences.
+
+ If the name is not registered, returns with no error.
+ """
+ if name in self._BUILTIN_COLOR_SEQUENCES:
+ raise ValueError(
+ f"Cannot unregister builtin color sequence {name!r}")
+ self._color_sequences.pop(name, None)
+
+
+_color_sequences = ColorSequenceRegistry()
+
+
+def _sanitize_extrema(ex):
+ if ex is None:
+ return ex
+ try:
+ ret = ex.item()
+ except AttributeError:
+ ret = float(ex)
+ return ret
+
+_nth_color_re = re.compile(r"\AC[0-9]+\Z")
+
+
+def _is_nth_color(c):
+ """Return whether *c* can be interpreted as an item in the color cycle."""
+ return isinstance(c, str) and _nth_color_re.match(c)
+
+
+def is_color_like(c):
+ """Return whether *c* can be interpreted as an RGB(A) color."""
+ # Special-case nth color syntax because it cannot be parsed during setup.
+ if _is_nth_color(c):
+ return True
+ try:
+ to_rgba(c)
+ except (TypeError, ValueError):
+ return False
+ else:
+ return True
+
+
+def _has_alpha_channel(c):
+ """Return whether *c* is a color with an alpha channel."""
+ # 4-element sequences are interpreted as r, g, b, a
+ return not isinstance(c, str) and len(c) == 4
+
+
+def _check_color_like(**kwargs):
+ """
+ For each *key, value* pair in *kwargs*, check that *value* is color-like.
+ """
+ for k, v in kwargs.items():
+ if not is_color_like(v):
+ raise ValueError(
+ f"{v!r} is not a valid value for {k}: supported inputs are "
+ f"(r, g, b) and (r, g, b, a) 0-1 float tuples; "
+ f"'#rrggbb', '#rrggbbaa', '#rgb', '#rgba' strings; "
+ f"named color strings; "
+ f"string reprs of 0-1 floats for grayscale values; "
+ f"'C0', 'C1', ... strings for colors of the color cycle; "
+ f"and pairs combining one of the above with an alpha value")
+
+
+def same_color(c1, c2):
+ """
+ Return whether the colors *c1* and *c2* are the same.
+
+ *c1*, *c2* can be single colors or lists/arrays of colors.
+ """
+ c1 = to_rgba_array(c1)
+ c2 = to_rgba_array(c2)
+ n1 = max(c1.shape[0], 1) # 'none' results in shape (0, 4), but is 1-elem
+ n2 = max(c2.shape[0], 1) # 'none' results in shape (0, 4), but is 1-elem
+
+ if n1 != n2:
+ raise ValueError('Different number of elements passed.')
+ # The following shape test is needed to correctly handle comparisons with
+ # 'none', which results in a shape (0, 4) array and thus cannot be tested
+ # via value comparison.
+ return c1.shape == c2.shape and (c1 == c2).all()
+
+
+def to_rgba(c, alpha=None):
+ """
+ Convert *c* to an RGBA color.
+
+ Parameters
+ ----------
+ c : Matplotlib color or ``np.ma.masked``
+
+ alpha : float, optional
+ If *alpha* is given, force the alpha value of the returned RGBA tuple
+ to *alpha*.
+
+ If None, the alpha value from *c* is used. If *c* does not have an
+ alpha channel, then alpha defaults to 1.
+
+ *alpha* is ignored for the color value ``"none"`` (case-insensitive),
+ which always maps to ``(0, 0, 0, 0)``.
+
+ Returns
+ -------
+ tuple
+ Tuple of floats ``(r, g, b, a)``, where each channel (red, green, blue,
+ alpha) can assume values between 0 and 1.
+ """
+ if isinstance(c, tuple) and len(c) == 2:
+ if alpha is None:
+ c, alpha = c
+ else:
+ c = c[0]
+ # Special-case nth color syntax because it should not be cached.
+ if _is_nth_color(c):
+ prop_cycler = mpl.rcParams['axes.prop_cycle']
+ colors = prop_cycler.by_key().get('color', ['k'])
+ c = colors[int(c[1:]) % len(colors)]
+ try:
+ rgba = _colors_full_map.cache[c, alpha]
+ except (KeyError, TypeError): # Not in cache, or unhashable.
+ rgba = None
+ if rgba is None: # Suppress exception chaining of cache lookup failure.
+ rgba = _to_rgba_no_colorcycle(c, alpha)
+ try:
+ _colors_full_map.cache[c, alpha] = rgba
+ except TypeError:
+ pass
+ return rgba
+
+
+def _to_rgba_no_colorcycle(c, alpha=None):
+ """
+ Convert *c* to an RGBA color, with no support for color-cycle syntax.
+
+ If *alpha* is given, force the alpha value of the returned RGBA tuple
+ to *alpha*. Otherwise, the alpha value from *c* is used, if it has alpha
+ information, or defaults to 1.
+
+ *alpha* is ignored for the color value ``"none"`` (case-insensitive),
+ which always maps to ``(0, 0, 0, 0)``.
+ """
+ if alpha is not None and not 0 <= alpha <= 1:
+ raise ValueError("'alpha' must be between 0 and 1, inclusive")
+ orig_c = c
+ if c is np.ma.masked:
+ return (0., 0., 0., 0.)
+ if isinstance(c, str):
+ if c.lower() == "none":
+ return (0., 0., 0., 0.)
+ # Named color.
+ try:
+ # This may turn c into a non-string, so we check again below.
+ c = _colors_full_map[c]
+ except KeyError:
+ if len(orig_c) != 1:
+ try:
+ c = _colors_full_map[c.lower()]
+ except KeyError:
+ pass
+ if isinstance(c, str):
+ # hex color in #rrggbb format.
+ match = re.match(r"\A#[a-fA-F0-9]{6}\Z", c)
+ if match:
+ return (tuple(int(n, 16) / 255
+ for n in [c[1:3], c[3:5], c[5:7]])
+ + (alpha if alpha is not None else 1.,))
+ # hex color in #rgb format, shorthand for #rrggbb.
+ match = re.match(r"\A#[a-fA-F0-9]{3}\Z", c)
+ if match:
+ return (tuple(int(n, 16) / 255
+ for n in [c[1]*2, c[2]*2, c[3]*2])
+ + (alpha if alpha is not None else 1.,))
+ # hex color with alpha in #rrggbbaa format.
+ match = re.match(r"\A#[a-fA-F0-9]{8}\Z", c)
+ if match:
+ color = [int(n, 16) / 255
+ for n in [c[1:3], c[3:5], c[5:7], c[7:9]]]
+ if alpha is not None:
+ color[-1] = alpha
+ return tuple(color)
+ # hex color with alpha in #rgba format, shorthand for #rrggbbaa.
+ match = re.match(r"\A#[a-fA-F0-9]{4}\Z", c)
+ if match:
+ color = [int(n, 16) / 255
+ for n in [c[1]*2, c[2]*2, c[3]*2, c[4]*2]]
+ if alpha is not None:
+ color[-1] = alpha
+ return tuple(color)
+ # string gray.
+ try:
+ c = float(c)
+ except ValueError:
+ pass
+ else:
+ if not (0 <= c <= 1):
+ raise ValueError(
+ f"Invalid string grayscale value {orig_c!r}. "
+ f"Value must be within 0-1 range")
+ return c, c, c, alpha if alpha is not None else 1.
+ raise ValueError(f"Invalid RGBA argument: {orig_c!r}")
+ # turn 2-D array into 1-D array
+ if isinstance(c, np.ndarray):
+ if c.ndim == 2 and c.shape[0] == 1:
+ c = c.reshape(-1)
+ # tuple color.
+ if not np.iterable(c):
+ raise ValueError(f"Invalid RGBA argument: {orig_c!r}")
+ if len(c) not in [3, 4]:
+ raise ValueError("RGBA sequence should have length 3 or 4")
+ if not all(isinstance(x, Real) for x in c):
+ # Checks that don't work: `map(float, ...)`, `np.array(..., float)` and
+ # `np.array(...).astype(float)` would all convert "0.5" to 0.5.
+ raise ValueError(f"Invalid RGBA argument: {orig_c!r}")
+ # Return a tuple to prevent the cached value from being modified.
+ c = tuple(map(float, c))
+ if len(c) == 3 and alpha is None:
+ alpha = 1
+ if alpha is not None:
+ c = c[:3] + (alpha,)
+ if any(elem < 0 or elem > 1 for elem in c):
+ raise ValueError("RGBA values should be within 0-1 range")
+ return c
+
+
+def to_rgba_array(c, alpha=None):
+ """
+ Convert *c* to a (n, 4) array of RGBA colors.
+
+ Parameters
+ ----------
+ c : Matplotlib color or array of colors
+ If *c* is a masked array, an `~numpy.ndarray` is returned with a
+ (0, 0, 0, 0) row for each masked value or row in *c*.
+
+ alpha : float or sequence of floats, optional
+ If *alpha* is given, force the alpha value of the returned RGBA tuple
+ to *alpha*.
+
+ If None, the alpha value from *c* is used. If *c* does not have an
+ alpha channel, then alpha defaults to 1.
+
+ *alpha* is ignored for the color value ``"none"`` (case-insensitive),
+ which always maps to ``(0, 0, 0, 0)``.
+
+ If *alpha* is a sequence and *c* is a single color, *c* will be
+ repeated to match the length of *alpha*.
+
+ Returns
+ -------
+ array
+ (n, 4) array of RGBA colors, where each channel (red, green, blue,
+ alpha) can assume values between 0 and 1.
+ """
+ if isinstance(c, tuple) and len(c) == 2 and isinstance(c[1], Real):
+ if alpha is None:
+ c, alpha = c
+ else:
+ c = c[0]
+ # Special-case inputs that are already arrays, for performance. (If the
+ # array has the wrong kind or shape, raise the error during one-at-a-time
+ # conversion.)
+ if np.iterable(alpha):
+ alpha = np.asarray(alpha).ravel()
+ if (isinstance(c, np.ndarray) and c.dtype.kind in "if"
+ and c.ndim == 2 and c.shape[1] in [3, 4]):
+ mask = c.mask.any(axis=1) if np.ma.is_masked(c) else None
+ c = np.ma.getdata(c)
+ if np.iterable(alpha):
+ if c.shape[0] == 1 and alpha.shape[0] > 1:
+ c = np.tile(c, (alpha.shape[0], 1))
+ elif c.shape[0] != alpha.shape[0]:
+ raise ValueError("The number of colors must match the number"
+ " of alpha values if there are more than one"
+ " of each.")
+ if c.shape[1] == 3:
+ result = np.column_stack([c, np.zeros(len(c))])
+ result[:, -1] = alpha if alpha is not None else 1.
+ elif c.shape[1] == 4:
+ result = c.copy()
+ if alpha is not None:
+ result[:, -1] = alpha
+ if mask is not None:
+ result[mask] = 0
+ if np.any((result < 0) | (result > 1)):
+ raise ValueError("RGBA values should be within 0-1 range")
+ return result
+ # Handle single values.
+ # Note that this occurs *after* handling inputs that are already arrays, as
+ # `to_rgba(c, alpha)` (below) is expensive for such inputs, due to the need
+ # to format the array in the ValueError message(!).
+ if cbook._str_lower_equal(c, "none"):
+ return np.zeros((0, 4), float)
+ try:
+ if np.iterable(alpha):
+ return np.array([to_rgba(c, a) for a in alpha], float)
+ else:
+ return np.array([to_rgba(c, alpha)], float)
+ except TypeError:
+ pass
+ except ValueError as e:
+ if e.args == ("'alpha' must be between 0 and 1, inclusive", ):
+ # ValueError is from _to_rgba_no_colorcycle().
+ raise e
+ if isinstance(c, str):
+ raise ValueError(f"{c!r} is not a valid color value.")
+
+ if len(c) == 0:
+ return np.zeros((0, 4), float)
+
+ # Quick path if the whole sequence can be directly converted to a numpy
+ # array in one shot.
+ if isinstance(c, Sequence):
+ lens = {len(cc) if isinstance(cc, (list, tuple)) else -1 for cc in c}
+ if lens == {3}:
+ rgba = np.column_stack([c, np.ones(len(c))])
+ elif lens == {4}:
+ rgba = np.array(c)
+ else:
+ rgba = np.array([to_rgba(cc) for cc in c])
+ else:
+ rgba = np.array([to_rgba(cc) for cc in c])
+
+ if alpha is not None:
+ rgba[:, 3] = alpha
+ if isinstance(c, Sequence):
+ # ensure that an explicit alpha does not overwrite full transparency
+ # for "none"
+ none_mask = [cbook._str_equal(cc, "none") for cc in c]
+ rgba[:, 3][none_mask] = 0
+ return rgba
+
+
+def to_rgb(c):
+ """Convert *c* to an RGB color, silently dropping the alpha channel."""
+ return to_rgba(c)[:3]
+
+
+def to_hex(c, keep_alpha=False):
+ """
+ Convert *c* to a hex color.
+
+ Parameters
+ ----------
+ c : :ref:`color ` or `numpy.ma.masked`
+
+ keep_alpha : bool, default: False
+ If False, use the ``#rrggbb`` format, otherwise use ``#rrggbbaa``.
+
+ Returns
+ -------
+ str
+ ``#rrggbb`` or ``#rrggbbaa`` hex color string
+ """
+ c = to_rgba(c)
+ if not keep_alpha:
+ c = c[:3]
+ return "#" + "".join(format(round(val * 255), "02x") for val in c)
+
+
+### Backwards-compatible color-conversion API
+
+
+cnames = CSS4_COLORS
+hexColorPattern = re.compile(r"\A#[a-fA-F0-9]{6}\Z")
+rgb2hex = to_hex
+hex2color = to_rgb
+
+
+class ColorConverter:
+ """
+ A class only kept for backwards compatibility.
+
+ Its functionality is entirely provided by module-level functions.
+ """
+ colors = _colors_full_map
+ cache = _colors_full_map.cache
+ to_rgb = staticmethod(to_rgb)
+ to_rgba = staticmethod(to_rgba)
+ to_rgba_array = staticmethod(to_rgba_array)
+
+
+colorConverter = ColorConverter()
+
+
+### End of backwards-compatible color-conversion API
+
+
+def _create_lookup_table(N, data, gamma=1.0):
+ r"""
+ Create an *N* -element 1D lookup table.
+
+ This assumes a mapping :math:`f : [0, 1] \rightarrow [0, 1]`. The returned
+ data is an array of N values :math:`y = f(x)` where x is sampled from
+ [0, 1].
+
+ By default (*gamma* = 1) x is equidistantly sampled from [0, 1]. The
+ *gamma* correction factor :math:`\gamma` distorts this equidistant
+ sampling by :math:`x \rightarrow x^\gamma`.
+
+ Parameters
+ ----------
+ N : int
+ The number of elements of the created lookup table; at least 1.
+
+ data : (M, 3) array-like or callable
+ Defines the mapping :math:`f`.
+
+ If a (M, 3) array-like, the rows define values (x, y0, y1). The x
+ values must start with x=0, end with x=1, and all x values be in
+ increasing order.
+
+ A value between :math:`x_i` and :math:`x_{i+1}` is mapped to the range
+ :math:`y^1_{i-1} \ldots y^0_i` by linear interpolation.
+
+ For the simple case of a y-continuous mapping, y0 and y1 are identical.
+
+ The two values of y are to allow for discontinuous mapping functions.
+ E.g. a sawtooth with a period of 0.2 and an amplitude of 1 would be::
+
+ [(0, 1, 0), (0.2, 1, 0), (0.4, 1, 0), ..., [(1, 1, 0)]
+
+ In the special case of ``N == 1``, by convention the returned value
+ is y0 for x == 1.
+
+ If *data* is a callable, it must accept and return numpy arrays::
+
+ data(x : ndarray) -> ndarray
+
+ and map values between 0 - 1 to 0 - 1.
+
+ gamma : float
+ Gamma correction factor for input distribution x of the mapping.
+
+ See also https://en.wikipedia.org/wiki/Gamma_correction.
+
+ Returns
+ -------
+ array
+ The lookup table where ``lut[x * (N-1)]`` gives the closest value
+ for values of x between 0 and 1.
+
+ Notes
+ -----
+ This function is internally used for `.LinearSegmentedColormap`.
+ """
+
+ if callable(data):
+ xind = np.linspace(0, 1, N) ** gamma
+ lut = np.clip(np.array(data(xind), dtype=float), 0, 1)
+ return lut
+
+ try:
+ adata = np.array(data)
+ except Exception as err:
+ raise TypeError("data must be convertible to an array") from err
+ _api.check_shape((None, 3), data=adata)
+
+ x = adata[:, 0]
+ y0 = adata[:, 1]
+ y1 = adata[:, 2]
+
+ if x[0] != 0. or x[-1] != 1.0:
+ raise ValueError(
+ "data mapping points must start with x=0 and end with x=1")
+ if (np.diff(x) < 0).any():
+ raise ValueError("data mapping points must have x in increasing order")
+ # begin generation of lookup table
+ if N == 1:
+ # convention: use the y = f(x=1) value for a 1-element lookup table
+ lut = np.array(y0[-1])
+ else:
+ x = x * (N - 1)
+ xind = (N - 1) * np.linspace(0, 1, N) ** gamma
+ ind = np.searchsorted(x, xind)[1:-1]
+
+ distance = (xind[1:-1] - x[ind - 1]) / (x[ind] - x[ind - 1])
+ lut = np.concatenate([
+ [y1[0]],
+ distance * (y0[ind] - y1[ind - 1]) + y1[ind - 1],
+ [y0[-1]],
+ ])
+ # ensure that the lut is confined to values between 0 and 1 by clipping it
+ return np.clip(lut, 0.0, 1.0)
+
+
+class Colormap:
+ """
+ Baseclass for all scalar to RGBA mappings.
+
+ Typically, Colormap instances are used to convert data values (floats)
+ from the interval ``[0, 1]`` to the RGBA color that the respective
+ Colormap represents. For scaling of data into the ``[0, 1]`` interval see
+ `matplotlib.colors.Normalize`. Subclasses of `matplotlib.cm.ScalarMappable`
+ make heavy use of this ``data -> normalize -> map-to-color`` processing
+ chain.
+ """
+
+ def __init__(self, name, N=256):
+ """
+ Parameters
+ ----------
+ name : str
+ The name of the colormap.
+ N : int
+ The number of RGB quantization levels.
+ """
+ self.name = name
+ self.N = int(N) # ensure that N is always int
+ self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything.
+ self._rgba_under = None
+ self._rgba_over = None
+ self._i_under = self.N
+ self._i_over = self.N + 1
+ self._i_bad = self.N + 2
+ self._isinit = False
+ self.n_variates = 1
+ #: When this colormap exists on a scalar mappable and colorbar_extend
+ #: is not False, colorbar creation will pick up ``colorbar_extend`` as
+ #: the default value for the ``extend`` keyword in the
+ #: `matplotlib.colorbar.Colorbar` constructor.
+ self.colorbar_extend = False
+
+ def __call__(self, X, alpha=None, bytes=False):
+ r"""
+ Parameters
+ ----------
+ X : float or int or array-like
+ The data value(s) to convert to RGBA.
+ For floats, *X* should be in the interval ``[0.0, 1.0]`` to
+ return the RGBA values ``X*100`` percent along the Colormap line.
+ For integers, *X* should be in the interval ``[0, Colormap.N)`` to
+ return RGBA values *indexed* from the Colormap with index ``X``.
+ alpha : float or array-like or None
+ Alpha must be a scalar between 0 and 1, a sequence of such
+ floats with shape matching X, or None.
+ bytes : bool, default: False
+ If False (default), the returned RGBA values will be floats in the
+ interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the
+ interval ``[0, 255]``.
+
+ Returns
+ -------
+ Tuple of RGBA values if X is scalar, otherwise an array of
+ RGBA values with a shape of ``X.shape + (4, )``.
+ """
+ rgba, mask = self._get_rgba_and_mask(X, alpha=alpha, bytes=bytes)
+ if not np.iterable(X):
+ rgba = tuple(rgba)
+ return rgba
+
+ def _get_rgba_and_mask(self, X, alpha=None, bytes=False):
+ r"""
+ Parameters
+ ----------
+ X : float or int or array-like
+ The data value(s) to convert to RGBA.
+ For floats, *X* should be in the interval ``[0.0, 1.0]`` to
+ return the RGBA values ``X*100`` percent along the Colormap line.
+ For integers, *X* should be in the interval ``[0, Colormap.N)`` to
+ return RGBA values *indexed* from the Colormap with index ``X``.
+ alpha : float or array-like or None
+ Alpha must be a scalar between 0 and 1, a sequence of such
+ floats with shape matching X, or None.
+ bytes : bool, default: False
+ If False (default), the returned RGBA values will be floats in the
+ interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the
+ interval ``[0, 255]``.
+
+ Returns
+ -------
+ colors : np.ndarray
+ Array of RGBA values with a shape of ``X.shape + (4, )``.
+ mask : np.ndarray
+ Boolean array with True where the input is ``np.nan`` or masked.
+ """
+ if not self._isinit:
+ self._init()
+
+ xa = np.array(X, copy=True)
+ if not xa.dtype.isnative:
+ # Native byteorder is faster.
+ xa = xa.byteswap().view(xa.dtype.newbyteorder())
+ if xa.dtype.kind == "f":
+ xa *= self.N
+ # xa == 1 (== N after multiplication) is not out of range.
+ xa[xa == self.N] = self.N - 1
+ # Pre-compute the masks before casting to int (which can truncate
+ # negative values to zero or wrap large floats to negative ints).
+ mask_under = xa < 0
+ mask_over = xa >= self.N
+ # If input was masked, get the bad mask from it; else mask out nans.
+ mask_bad = X.mask if np.ma.is_masked(X) else np.isnan(xa)
+ with np.errstate(invalid="ignore"):
+ # We need this cast for unsigned ints as well as floats
+ xa = xa.astype(int)
+ xa[mask_under] = self._i_under
+ xa[mask_over] = self._i_over
+ xa[mask_bad] = self._i_bad
+
+ lut = self._lut
+ if bytes:
+ lut = (lut * 255).astype(np.uint8)
+
+ rgba = lut.take(xa, axis=0, mode='clip')
+
+ if alpha is not None:
+ alpha = np.clip(alpha, 0, 1)
+ if bytes:
+ alpha *= 255 # Will be cast to uint8 upon assignment.
+ if alpha.shape not in [(), xa.shape]:
+ raise ValueError(
+ f"alpha is array-like but its shape {alpha.shape} does "
+ f"not match that of X {xa.shape}")
+ rgba[..., -1] = alpha
+ # If the "bad" color is all zeros, then ignore alpha input.
+ if (lut[-1] == 0).all():
+ rgba[mask_bad] = (0, 0, 0, 0)
+
+ return rgba, mask_bad
+
+ def __copy__(self):
+ cls = self.__class__
+ cmapobject = cls.__new__(cls)
+ cmapobject.__dict__.update(self.__dict__)
+ if self._isinit:
+ cmapobject._lut = np.copy(self._lut)
+ return cmapobject
+
+ def __eq__(self, other):
+ if (not isinstance(other, Colormap) or
+ self.colorbar_extend != other.colorbar_extend):
+ return False
+ # To compare lookup tables the Colormaps have to be initialized
+ if not self._isinit:
+ self._init()
+ if not other._isinit:
+ other._init()
+ return np.array_equal(self._lut, other._lut)
+
+ def get_bad(self):
+ """Get the color for masked values."""
+ if not self._isinit:
+ self._init()
+ return np.array(self._lut[self._i_bad])
+
+ def set_bad(self, color='k', alpha=None):
+ """Set the color for masked values."""
+ self._rgba_bad = to_rgba(color, alpha)
+ if self._isinit:
+ self._set_extremes()
+
+ def get_under(self):
+ """Get the color for low out-of-range values."""
+ if not self._isinit:
+ self._init()
+ return np.array(self._lut[self._i_under])
+
+ def set_under(self, color='k', alpha=None):
+ """Set the color for low out-of-range values."""
+ self._rgba_under = to_rgba(color, alpha)
+ if self._isinit:
+ self._set_extremes()
+
+ def get_over(self):
+ """Get the color for high out-of-range values."""
+ if not self._isinit:
+ self._init()
+ return np.array(self._lut[self._i_over])
+
+ def set_over(self, color='k', alpha=None):
+ """Set the color for high out-of-range values."""
+ self._rgba_over = to_rgba(color, alpha)
+ if self._isinit:
+ self._set_extremes()
+
+ def set_extremes(self, *, bad=None, under=None, over=None):
+ """
+ Set the colors for masked (*bad*) values and, when ``norm.clip =
+ False``, low (*under*) and high (*over*) out-of-range values.
+ """
+ if bad is not None:
+ self.set_bad(bad)
+ if under is not None:
+ self.set_under(under)
+ if over is not None:
+ self.set_over(over)
+
+ def with_extremes(self, *, bad=None, under=None, over=None):
+ """
+ Return a copy of the colormap, for which the colors for masked (*bad*)
+ values and, when ``norm.clip = False``, low (*under*) and high (*over*)
+ out-of-range values, have been set accordingly.
+ """
+ new_cm = self.copy()
+ new_cm.set_extremes(bad=bad, under=under, over=over)
+ return new_cm
+
+ def _set_extremes(self):
+ if self._rgba_under:
+ self._lut[self._i_under] = self._rgba_under
+ else:
+ self._lut[self._i_under] = self._lut[0]
+ if self._rgba_over:
+ self._lut[self._i_over] = self._rgba_over
+ else:
+ self._lut[self._i_over] = self._lut[self.N - 1]
+ self._lut[self._i_bad] = self._rgba_bad
+
+ def _init(self):
+ """Generate the lookup table, ``self._lut``."""
+ raise NotImplementedError("Abstract class only")
+
+ def is_gray(self):
+ """Return whether the colormap is grayscale."""
+ if not self._isinit:
+ self._init()
+ return (np.all(self._lut[:, 0] == self._lut[:, 1]) and
+ np.all(self._lut[:, 0] == self._lut[:, 2]))
+
+ def resampled(self, lutsize):
+ """Return a new colormap with *lutsize* entries."""
+ if hasattr(self, '_resample'):
+ _api.warn_external(
+ "The ability to resample a color map is now public API "
+ f"However the class {type(self)} still only implements "
+ "the previous private _resample method. Please update "
+ "your class."
+ )
+ return self._resample(lutsize)
+
+ raise NotImplementedError()
+
+ def reversed(self, name=None):
+ """
+ Return a reversed instance of the Colormap.
+
+ .. note:: This function is not implemented for the base class.
+
+ Parameters
+ ----------
+ name : str, optional
+ The name for the reversed colormap. If None, the
+ name is set to ``self.name + "_r"``.
+
+ See Also
+ --------
+ LinearSegmentedColormap.reversed
+ ListedColormap.reversed
+ """
+ raise NotImplementedError()
+
+ def _repr_png_(self):
+ """Generate a PNG representation of the Colormap."""
+ X = np.tile(np.linspace(0, 1, _REPR_PNG_SIZE[0]),
+ (_REPR_PNG_SIZE[1], 1))
+ pixels = self(X, bytes=True)
+ png_bytes = io.BytesIO()
+ title = self.name + ' colormap'
+ author = f'Matplotlib v{mpl.__version__}, https://matplotlib.org'
+ pnginfo = PngInfo()
+ pnginfo.add_text('Title', title)
+ pnginfo.add_text('Description', title)
+ pnginfo.add_text('Author', author)
+ pnginfo.add_text('Software', author)
+ Image.fromarray(pixels).save(png_bytes, format='png', pnginfo=pnginfo)
+ return png_bytes.getvalue()
+
+ def _repr_html_(self):
+ """Generate an HTML representation of the Colormap."""
+ png_bytes = self._repr_png_()
+ png_base64 = base64.b64encode(png_bytes).decode('ascii')
+ def color_block(color):
+ hex_color = to_hex(color, keep_alpha=True)
+ return (f'
')
+
+ return (''
+ f'{self.name} '
+ '
'
+ ''
+ ''
+ '
'
+ f'{color_block(self.get_under())} under'
+ '
'
+ '
'
+ f'bad {color_block(self.get_bad())}'
+ '
'
+ '
'
+ f'over {color_block(self.get_over())}'
+ '
'
+ '
')
+
+ def copy(self):
+ """Return a copy of the colormap."""
+ return self.__copy__()
+
+
+class LinearSegmentedColormap(Colormap):
+ """
+ Colormap objects based on lookup tables using linear segments.
+
+ The lookup table is generated using linear interpolation for each
+ primary color, with the 0-1 domain divided into any number of
+ segments.
+ """
+
+ def __init__(self, name, segmentdata, N=256, gamma=1.0):
+ """
+ Create colormap from linear mapping segments
+
+ segmentdata argument is a dictionary with a red, green and blue
+ entries. Each entry should be a list of *x*, *y0*, *y1* tuples,
+ forming rows in a table. Entries for alpha are optional.
+
+ Example: suppose you want red to increase from 0 to 1 over
+ the bottom half, green to do the same over the middle half,
+ and blue over the top half. Then you would use::
+
+ cdict = {'red': [(0.0, 0.0, 0.0),
+ (0.5, 1.0, 1.0),
+ (1.0, 1.0, 1.0)],
+
+ 'green': [(0.0, 0.0, 0.0),
+ (0.25, 0.0, 0.0),
+ (0.75, 1.0, 1.0),
+ (1.0, 1.0, 1.0)],
+
+ 'blue': [(0.0, 0.0, 0.0),
+ (0.5, 0.0, 0.0),
+ (1.0, 1.0, 1.0)]}
+
+ Each row in the table for a given color is a sequence of
+ *x*, *y0*, *y1* tuples. In each sequence, *x* must increase
+ monotonically from 0 to 1. For any input value *z* falling
+ between *x[i]* and *x[i+1]*, the output value of a given color
+ will be linearly interpolated between *y1[i]* and *y0[i+1]*::
+
+ row i: x y0 y1
+ /
+ /
+ row i+1: x y0 y1
+
+ Hence y0 in the first row and y1 in the last row are never used.
+
+ See Also
+ --------
+ LinearSegmentedColormap.from_list
+ Static method; factory function for generating a smoothly-varying
+ LinearSegmentedColormap.
+ """
+ # True only if all colors in map are identical; needed for contouring.
+ self.monochrome = False
+ super().__init__(name, N)
+ self._segmentdata = segmentdata
+ self._gamma = gamma
+
+ def _init(self):
+ self._lut = np.ones((self.N + 3, 4), float)
+ self._lut[:-3, 0] = _create_lookup_table(
+ self.N, self._segmentdata['red'], self._gamma)
+ self._lut[:-3, 1] = _create_lookup_table(
+ self.N, self._segmentdata['green'], self._gamma)
+ self._lut[:-3, 2] = _create_lookup_table(
+ self.N, self._segmentdata['blue'], self._gamma)
+ if 'alpha' in self._segmentdata:
+ self._lut[:-3, 3] = _create_lookup_table(
+ self.N, self._segmentdata['alpha'], 1)
+ self._isinit = True
+ self._set_extremes()
+
+ def set_gamma(self, gamma):
+ """Set a new gamma value and regenerate colormap."""
+ self._gamma = gamma
+ self._init()
+
+ @staticmethod
+ def from_list(name, colors, N=256, gamma=1.0):
+ """
+ Create a `LinearSegmentedColormap` from a list of colors.
+
+ Parameters
+ ----------
+ name : str
+ The name of the colormap.
+ colors : list of :mpltype:`color` or list of (value, color)
+ If only colors are given, they are equidistantly mapped from the
+ range :math:`[0, 1]`; i.e. 0 maps to ``colors[0]`` and 1 maps to
+ ``colors[-1]``.
+ If (value, color) pairs are given, the mapping is from *value*
+ to *color*. This can be used to divide the range unevenly.
+ N : int
+ The number of RGB quantization levels.
+ gamma : float
+ """
+ if not np.iterable(colors):
+ raise ValueError('colors must be iterable')
+
+ if (isinstance(colors[0], Sized) and len(colors[0]) == 2
+ and not isinstance(colors[0], str)):
+ # List of value, color pairs
+ vals, colors = zip(*colors)
+ else:
+ vals = np.linspace(0, 1, len(colors))
+
+ r, g, b, a = to_rgba_array(colors).T
+ cdict = {
+ "red": np.column_stack([vals, r, r]),
+ "green": np.column_stack([vals, g, g]),
+ "blue": np.column_stack([vals, b, b]),
+ "alpha": np.column_stack([vals, a, a]),
+ }
+
+ return LinearSegmentedColormap(name, cdict, N, gamma)
+
+ def resampled(self, lutsize):
+ """Return a new colormap with *lutsize* entries."""
+ new_cmap = LinearSegmentedColormap(self.name, self._segmentdata,
+ lutsize)
+ new_cmap._rgba_over = self._rgba_over
+ new_cmap._rgba_under = self._rgba_under
+ new_cmap._rgba_bad = self._rgba_bad
+ return new_cmap
+
+ # Helper ensuring picklability of the reversed cmap.
+ @staticmethod
+ def _reverser(func, x):
+ return func(1 - x)
+
+ def reversed(self, name=None):
+ """
+ Return a reversed instance of the Colormap.
+
+ Parameters
+ ----------
+ name : str, optional
+ The name for the reversed colormap. If None, the
+ name is set to ``self.name + "_r"``.
+
+ Returns
+ -------
+ LinearSegmentedColormap
+ The reversed colormap.
+ """
+ if name is None:
+ name = self.name + "_r"
+
+ # Using a partial object keeps the cmap picklable.
+ data_r = {key: (functools.partial(self._reverser, data)
+ if callable(data) else
+ [(1.0 - x, y1, y0) for x, y0, y1 in reversed(data)])
+ for key, data in self._segmentdata.items()}
+
+ new_cmap = LinearSegmentedColormap(name, data_r, self.N, self._gamma)
+ # Reverse the over/under values too
+ new_cmap._rgba_over = self._rgba_under
+ new_cmap._rgba_under = self._rgba_over
+ new_cmap._rgba_bad = self._rgba_bad
+ return new_cmap
+
+
+class ListedColormap(Colormap):
+ """
+ Colormap object generated from a list of colors.
+
+ This may be most useful when indexing directly into a colormap,
+ but it can also be used to generate special colormaps for ordinary
+ mapping.
+
+ Parameters
+ ----------
+ colors : list, array
+ Sequence of Matplotlib color specifications (color names or RGB(A)
+ values).
+ name : str, optional
+ String to identify the colormap.
+ N : int, optional
+ Number of entries in the map. The default is *None*, in which case
+ there is one colormap entry for each element in the list of colors.
+ If ::
+
+ N < len(colors)
+
+ the list will be truncated at *N*. If ::
+
+ N > len(colors)
+
+ the list will be extended by repetition.
+ """
+ def __init__(self, colors, name='from_list', N=None):
+ self.monochrome = False # Are all colors identical? (for contour.py)
+ if N is None:
+ self.colors = colors
+ N = len(colors)
+ else:
+ if isinstance(colors, str):
+ self.colors = [colors] * N
+ self.monochrome = True
+ elif np.iterable(colors):
+ if len(colors) == 1:
+ self.monochrome = True
+ self.colors = list(
+ itertools.islice(itertools.cycle(colors), N))
+ else:
+ try:
+ gray = float(colors)
+ except TypeError:
+ pass
+ else:
+ self.colors = [gray] * N
+ self.monochrome = True
+ super().__init__(name, N)
+
+ def _init(self):
+ self._lut = np.zeros((self.N + 3, 4), float)
+ self._lut[:-3] = to_rgba_array(self.colors)
+ self._isinit = True
+ self._set_extremes()
+
+ def resampled(self, lutsize):
+ """Return a new colormap with *lutsize* entries."""
+ colors = self(np.linspace(0, 1, lutsize))
+ new_cmap = ListedColormap(colors, name=self.name)
+ # Keep the over/under values too
+ new_cmap._rgba_over = self._rgba_over
+ new_cmap._rgba_under = self._rgba_under
+ new_cmap._rgba_bad = self._rgba_bad
+ return new_cmap
+
+ def reversed(self, name=None):
+ """
+ Return a reversed instance of the Colormap.
+
+ Parameters
+ ----------
+ name : str, optional
+ The name for the reversed colormap. If None, the
+ name is set to ``self.name + "_r"``.
+
+ Returns
+ -------
+ ListedColormap
+ A reversed instance of the colormap.
+ """
+ if name is None:
+ name = self.name + "_r"
+
+ colors_r = list(reversed(self.colors))
+ new_cmap = ListedColormap(colors_r, name=name, N=self.N)
+ # Reverse the over/under values too
+ new_cmap._rgba_over = self._rgba_under
+ new_cmap._rgba_under = self._rgba_over
+ new_cmap._rgba_bad = self._rgba_bad
+ return new_cmap
+
+
+class MultivarColormap:
+ """
+ Class for holding multiple `~matplotlib.colors.Colormap` for use in a
+ `~matplotlib.cm.ScalarMappable` object
+ """
+ def __init__(self, colormaps, combination_mode, name='multivariate colormap'):
+ """
+ Parameters
+ ----------
+ colormaps: list or tuple of `~matplotlib.colors.Colormap` objects
+ The individual colormaps that are combined
+ combination_mode: str, 'sRGB_add' or 'sRGB_sub'
+ Describe how colormaps are combined in sRGB space
+
+ - If 'sRGB_add' -> Mixing produces brighter colors
+ `sRGB = sum(colors)`
+ - If 'sRGB_sub' -> Mixing produces darker colors
+ `sRGB = 1 - sum(1 - colors)`
+ name : str, optional
+ The name of the colormap family.
+ """
+ self.name = name
+
+ if not np.iterable(colormaps) \
+ or len(colormaps) == 1 \
+ or isinstance(colormaps, str):
+ raise ValueError("A MultivarColormap must have more than one colormap.")
+ colormaps = list(colormaps) # ensure cmaps is a list, i.e. not a tuple
+ for i, cmap in enumerate(colormaps):
+ if isinstance(cmap, str):
+ colormaps[i] = mpl.colormaps[cmap]
+ elif not isinstance(cmap, Colormap):
+ raise ValueError("colormaps must be a list of objects that subclass"
+ " Colormap or a name found in the colormap registry.")
+
+ self._colormaps = colormaps
+ _api.check_in_list(['sRGB_add', 'sRGB_sub'], combination_mode=combination_mode)
+ self._combination_mode = combination_mode
+ self.n_variates = len(colormaps)
+ self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything.
+
+ def __call__(self, X, alpha=None, bytes=False, clip=True):
+ r"""
+ Parameters
+ ----------
+ X : tuple (X0, X1, ...) of length equal to the number of colormaps
+ X0, X1 ...:
+ float or int, `~numpy.ndarray` or scalar
+ The data value(s) to convert to RGBA.
+ For floats, *Xi...* should be in the interval ``[0.0, 1.0]`` to
+ return the RGBA values ``X*100`` percent along the Colormap line.
+ For integers, *Xi...* should be in the interval ``[0, self[i].N)`` to
+ return RGBA values *indexed* from colormap [i] with index ``Xi``, where
+ self[i] is colormap i.
+ alpha : float or array-like or None
+ Alpha must be a scalar between 0 and 1, a sequence of such
+ floats with shape matching *Xi*, or None.
+ bytes : bool, default: False
+ If False (default), the returned RGBA values will be floats in the
+ interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the
+ interval ``[0, 255]``.
+ clip : bool, default: True
+ If True, clip output to 0 to 1
+
+ Returns
+ -------
+ Tuple of RGBA values if X[0] is scalar, otherwise an array of
+ RGBA values with a shape of ``X.shape + (4, )``.
+ """
+
+ if len(X) != len(self):
+ raise ValueError(
+ f'For the selected colormap the data must have a first dimension '
+ f'{len(self)}, not {len(X)}')
+ rgba, mask_bad = self[0]._get_rgba_and_mask(X[0], bytes=False)
+ for c, xx in zip(self[1:], X[1:]):
+ sub_rgba, sub_mask_bad = c._get_rgba_and_mask(xx, bytes=False)
+ rgba[..., :3] += sub_rgba[..., :3] # add colors
+ rgba[..., 3] *= sub_rgba[..., 3] # multiply alpha
+ mask_bad |= sub_mask_bad
+
+ if self.combination_mode == 'sRGB_sub':
+ rgba[..., :3] -= len(self) - 1
+
+ rgba[mask_bad] = self.get_bad()
+
+ if clip:
+ rgba = np.clip(rgba, 0, 1)
+
+ if alpha is not None:
+ if clip:
+ alpha = np.clip(alpha, 0, 1)
+ if np.shape(alpha) not in [(), np.shape(X[0])]:
+ raise ValueError(
+ f"alpha is array-like but its shape {np.shape(alpha)} does "
+ f"not match that of X[0] {np.shape(X[0])}")
+ rgba[..., -1] *= alpha
+
+ if bytes:
+ if not clip:
+ raise ValueError(
+ "clip cannot be false while bytes is true"
+ " as uint8 does not support values below 0"
+ " or above 255.")
+ rgba = (rgba * 255).astype('uint8')
+
+ if not np.iterable(X[0]):
+ rgba = tuple(rgba)
+
+ return rgba
+
+ def copy(self):
+ """Return a copy of the multivarcolormap."""
+ return self.__copy__()
+
+ def __copy__(self):
+ cls = self.__class__
+ cmapobject = cls.__new__(cls)
+ cmapobject.__dict__.update(self.__dict__)
+ cmapobject._colormaps = [cm.copy() for cm in self._colormaps]
+ cmapobject._rgba_bad = np.copy(self._rgba_bad)
+ return cmapobject
+
+ def __eq__(self, other):
+ if not isinstance(other, MultivarColormap):
+ return False
+ if len(self) != len(other):
+ return False
+ for c0, c1 in zip(self, other):
+ if c0 != c1:
+ return False
+ if not all(self._rgba_bad == other._rgba_bad):
+ return False
+ if self.combination_mode != other.combination_mode:
+ return False
+ return True
+
+ def __getitem__(self, item):
+ return self._colormaps[item]
+
+ def __iter__(self):
+ for c in self._colormaps:
+ yield c
+
+ def __len__(self):
+ return len(self._colormaps)
+
+ def __str__(self):
+ return self.name
+
+ def get_bad(self):
+ """Get the color for masked values."""
+ return np.array(self._rgba_bad)
+
+ def resampled(self, lutshape):
+ """
+ Return a new colormap with *lutshape* entries.
+
+ Parameters
+ ----------
+ lutshape : tuple of (`int`, `None`)
+ The tuple must have a length matching the number of variates.
+ For each element in the tuple, if `int`, the corresponding colorbar
+ is resampled, if `None`, the corresponding colorbar is not resampled.
+
+ Returns
+ -------
+ MultivarColormap
+ """
+
+ if not np.iterable(lutshape) or len(lutshape) != len(self):
+ raise ValueError(f"lutshape must be of length {len(self)}")
+ new_cmap = self.copy()
+ for i, s in enumerate(lutshape):
+ if s is not None:
+ new_cmap._colormaps[i] = self[i].resampled(s)
+ return new_cmap
+
+ def with_extremes(self, *, bad=None, under=None, over=None):
+ """
+ Return a copy of the `MultivarColormap` with modified out-of-range attributes.
+
+ The *bad* keyword modifies the copied `MultivarColormap` while *under* and
+ *over* modifies the attributes of the copied component colormaps.
+ Note that *under* and *over* colors are subject to the mixing rules determined
+ by the *combination_mode*.
+
+ Parameters
+ ----------
+ bad: :mpltype:`color`, default: None
+ If Matplotlib color, the bad value is set accordingly in the copy
+
+ under tuple of :mpltype:`color`, default: None
+ If tuple, the `under` value of each component is set with the values
+ from the tuple.
+
+ over tuple of :mpltype:`color`, default: None
+ If tuple, the `over` value of each component is set with the values
+ from the tuple.
+
+ Returns
+ -------
+ MultivarColormap
+ copy of self with attributes set
+
+ """
+ new_cm = self.copy()
+ if bad is not None:
+ new_cm._rgba_bad = to_rgba(bad)
+ if under is not None:
+ if not np.iterable(under) or len(under) != len(new_cm):
+ raise ValueError("*under* must contain a color for each scalar colormap"
+ f" i.e. be of length {len(new_cm)}.")
+ else:
+ for c, b in zip(new_cm, under):
+ c.set_under(b)
+ if over is not None:
+ if not np.iterable(over) or len(over) != len(new_cm):
+ raise ValueError("*over* must contain a color for each scalar colormap"
+ f" i.e. be of length {len(new_cm)}.")
+ else:
+ for c, b in zip(new_cm, over):
+ c.set_over(b)
+ return new_cm
+
+ @property
+ def combination_mode(self):
+ return self._combination_mode
+
+ def _repr_png_(self):
+ """Generate a PNG representation of the Colormap."""
+ X = np.tile(np.linspace(0, 1, _REPR_PNG_SIZE[0]),
+ (_REPR_PNG_SIZE[1], 1))
+ pixels = np.zeros((_REPR_PNG_SIZE[1]*len(self), _REPR_PNG_SIZE[0], 4),
+ dtype=np.uint8)
+ for i, c in enumerate(self):
+ pixels[i*_REPR_PNG_SIZE[1]:(i+1)*_REPR_PNG_SIZE[1], :] = c(X, bytes=True)
+ png_bytes = io.BytesIO()
+ title = self.name + ' multivariate colormap'
+ author = f'Matplotlib v{mpl.__version__}, https://matplotlib.org'
+ pnginfo = PngInfo()
+ pnginfo.add_text('Title', title)
+ pnginfo.add_text('Description', title)
+ pnginfo.add_text('Author', author)
+ pnginfo.add_text('Software', author)
+ Image.fromarray(pixels).save(png_bytes, format='png', pnginfo=pnginfo)
+ return png_bytes.getvalue()
+
+ def _repr_html_(self):
+ """Generate an HTML representation of the MultivarColormap."""
+ return ''.join([c._repr_html_() for c in self._colormaps])
+
+
+class BivarColormap:
+ """
+ Base class for all bivariate to RGBA mappings.
+
+ Designed as a drop-in replacement for Colormap when using a 2D
+ lookup table. To be used with `~matplotlib.cm.ScalarMappable`.
+ """
+
+ def __init__(self, N=256, M=256, shape='square', origin=(0, 0),
+ name='bivariate colormap'):
+ """
+ Parameters
+ ----------
+ N : int, default: 256
+ The number of RGB quantization levels along the first axis.
+ M : int, default: 256
+ The number of RGB quantization levels along the second axis.
+ shape : {'square', 'circle', 'ignore', 'circleignore'}
+
+ - 'square' each variate is clipped to [0,1] independently
+ - 'circle' the variates are clipped radially to the center
+ of the colormap, and a circular mask is applied when the colormap
+ is displayed
+ - 'ignore' the variates are not clipped, but instead assigned the
+ 'outside' color
+ - 'circleignore' a circular mask is applied, but the data is not
+ clipped and instead assigned the 'outside' color
+
+ origin : (float, float), default: (0,0)
+ The relative origin of the colormap. Typically (0, 0), for colormaps
+ that are linear on both axis, and (.5, .5) for circular colormaps.
+ Used when getting 1D colormaps from 2D colormaps.
+ name : str, optional
+ The name of the colormap.
+ """
+
+ self.name = name
+ self.N = int(N) # ensure that N is always int
+ self.M = int(M)
+ _api.check_in_list(['square', 'circle', 'ignore', 'circleignore'], shape=shape)
+ self._shape = shape
+ self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything.
+ self._rgba_outside = (1.0, 0.0, 1.0, 1.0)
+ self._isinit = False
+ self.n_variates = 2
+ self._origin = (float(origin[0]), float(origin[1]))
+ '''#: When this colormap exists on a scalar mappable and colorbar_extend
+ #: is not False, colorbar creation will pick up ``colorbar_extend`` as
+ #: the default value for the ``extend`` keyword in the
+ #: `matplotlib.colorbar.Colorbar` constructor.
+ self.colorbar_extend = False'''
+
+ def __call__(self, X, alpha=None, bytes=False):
+ r"""
+ Parameters
+ ----------
+ X : tuple (X0, X1), X0 and X1: float or int or array-like
+ The data value(s) to convert to RGBA.
+
+ - For floats, *X* should be in the interval ``[0.0, 1.0]`` to
+ return the RGBA values ``X*100`` percent along the Colormap.
+ - For integers, *X* should be in the interval ``[0, Colormap.N)`` to
+ return RGBA values *indexed* from the Colormap with index ``X``.
+
+ alpha : float or array-like or None, default: None
+ Alpha must be a scalar between 0 and 1, a sequence of such
+ floats with shape matching X0, or None.
+ bytes : bool, default: False
+ If False (default), the returned RGBA values will be floats in the
+ interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the
+ interval ``[0, 255]``.
+
+ Returns
+ -------
+ Tuple of RGBA values if X is scalar, otherwise an array of
+ RGBA values with a shape of ``X.shape + (4, )``.
+ """
+
+ if len(X) != 2:
+ raise ValueError(
+ f'For a `BivarColormap` the data must have a first dimension '
+ f'2, not {len(X)}')
+
+ if not self._isinit:
+ self._init()
+
+ X0 = np.ma.array(X[0], copy=True)
+ X1 = np.ma.array(X[1], copy=True)
+ # clip to shape of colormap, circle square, etc.
+ self._clip((X0, X1))
+
+ # Native byteorder is faster.
+ if not X0.dtype.isnative:
+ X0 = X0.byteswap().view(X0.dtype.newbyteorder())
+ if not X1.dtype.isnative:
+ X1 = X1.byteswap().view(X1.dtype.newbyteorder())
+
+ if X0.dtype.kind == "f":
+ X0 *= self.N
+ # xa == 1 (== N after multiplication) is not out of range.
+ X0[X0 == self.N] = self.N - 1
+
+ if X1.dtype.kind == "f":
+ X1 *= self.M
+ # xa == 1 (== N after multiplication) is not out of range.
+ X1[X1 == self.M] = self.M - 1
+
+ # Pre-compute the masks before casting to int (which can truncate)
+ mask_outside = (X0 < 0) | (X1 < 0) | (X0 >= self.N) | (X1 >= self.M)
+ # If input was masked, get the bad mask from it; else mask out nans.
+ mask_bad_0 = X0.mask if np.ma.is_masked(X0) else np.isnan(X0)
+ mask_bad_1 = X1.mask if np.ma.is_masked(X1) else np.isnan(X1)
+ mask_bad = mask_bad_0 | mask_bad_1
+
+ with np.errstate(invalid="ignore"):
+ # We need this cast for unsigned ints as well as floats
+ X0 = X0.astype(int)
+ X1 = X1.astype(int)
+
+ # Set masked values to zero
+ # The corresponding rgb values will be replaced later
+ for X_part in [X0, X1]:
+ X_part[mask_outside] = 0
+ X_part[mask_bad] = 0
+
+ rgba = self._lut[X0, X1]
+ if np.isscalar(X[0]):
+ rgba = np.copy(rgba)
+ rgba[mask_outside] = self._rgba_outside
+ rgba[mask_bad] = self._rgba_bad
+ if bytes:
+ rgba = (rgba * 255).astype(np.uint8)
+ if alpha is not None:
+ alpha = np.clip(alpha, 0, 1)
+ if bytes:
+ alpha *= 255 # Will be cast to uint8 upon assignment.
+ if np.shape(alpha) not in [(), np.shape(X0)]:
+ raise ValueError(
+ f"alpha is array-like but its shape {np.shape(alpha)} does "
+ f"not match that of X[0] {np.shape(X0)}")
+ rgba[..., -1] = alpha
+ # If the "bad" color is all zeros, then ignore alpha input.
+ if (np.array(self._rgba_bad) == 0).all():
+ rgba[mask_bad] = (0, 0, 0, 0)
+
+ if not np.iterable(X[0]):
+ rgba = tuple(rgba)
+ return rgba
+
+ @property
+ def lut(self):
+ """
+ For external access to the lut, i.e. for displaying the cmap.
+ For circular colormaps this returns a lut with a circular mask.
+
+ Internal functions (such as to_rgb()) should use _lut
+ which stores the lut without a circular mask
+ A lut without the circular mask is needed in to_rgb() because the
+ conversion from floats to ints results in some some pixel-requests
+ just outside of the circular mask
+
+ """
+ if not self._isinit:
+ self._init()
+ lut = np.copy(self._lut)
+ if self.shape == 'circle' or self.shape == 'circleignore':
+ n = np.linspace(-1, 1, self.N)
+ m = np.linspace(-1, 1, self.M)
+ radii_sqr = (n**2)[:, np.newaxis] + (m**2)[np.newaxis, :]
+ mask_outside = radii_sqr > 1
+ lut[mask_outside, 3] = 0
+ return lut
+
+ def __copy__(self):
+ cls = self.__class__
+ cmapobject = cls.__new__(cls)
+ cmapobject.__dict__.update(self.__dict__)
+
+ cmapobject._rgba_outside = np.copy(self._rgba_outside)
+ cmapobject._rgba_bad = np.copy(self._rgba_bad)
+ cmapobject._shape = self.shape
+ if self._isinit:
+ cmapobject._lut = np.copy(self._lut)
+ return cmapobject
+
+ def __eq__(self, other):
+ if not isinstance(other, BivarColormap):
+ return False
+ # To compare lookup tables the Colormaps have to be initialized
+ if not self._isinit:
+ self._init()
+ if not other._isinit:
+ other._init()
+ if not np.array_equal(self._lut, other._lut):
+ return False
+ if not np.array_equal(self._rgba_bad, other._rgba_bad):
+ return False
+ if not np.array_equal(self._rgba_outside, other._rgba_outside):
+ return False
+ if self.shape != other.shape:
+ return False
+ return True
+
+ def get_bad(self):
+ """Get the color for masked values."""
+ return self._rgba_bad
+
+ def get_outside(self):
+ """Get the color for out-of-range values."""
+ return self._rgba_outside
+
+ def resampled(self, lutshape, transposed=False):
+ """
+ Return a new colormap with *lutshape* entries.
+
+ Note that this function does not move the origin.
+
+ Parameters
+ ----------
+ lutshape : tuple of ints or None
+ The tuple must be of length 2, and each entry is either an int or None.
+
+ - If an int, the corresponding axis is resampled.
+ - If negative the corresponding axis is resampled in reverse
+ - If -1, the axis is inverted
+ - If 1 or None, the corresponding axis is not resampled.
+
+ transposed : bool, default: False
+ if True, the axes are swapped after resampling
+
+ Returns
+ -------
+ BivarColormap
+ """
+
+ if not np.iterable(lutshape) or len(lutshape) != 2:
+ raise ValueError("lutshape must be of length 2")
+ lutshape = [lutshape[0], lutshape[1]]
+ if lutshape[0] is None or lutshape[0] == 1:
+ lutshape[0] = self.N
+ if lutshape[1] is None or lutshape[1] == 1:
+ lutshape[1] = self.M
+
+ inverted = [False, False]
+ if lutshape[0] < 0:
+ inverted[0] = True
+ lutshape[0] = -lutshape[0]
+ if lutshape[0] == 1:
+ lutshape[0] = self.N
+ if lutshape[1] < 0:
+ inverted[1] = True
+ lutshape[1] = -lutshape[1]
+ if lutshape[1] == 1:
+ lutshape[1] = self.M
+ x_0, x_1 = np.mgrid[0:1:(lutshape[0] * 1j), 0:1:(lutshape[1] * 1j)]
+ if inverted[0]:
+ x_0 = x_0[::-1, :]
+ if inverted[1]:
+ x_1 = x_1[:, ::-1]
+
+ # we need to use shape = 'square' while resampling the colormap.
+ # if the colormap has shape = 'circle' we would otherwise get *outside* in the
+ # resampled colormap
+ shape_memory = self._shape
+ self._shape = 'square'
+ if transposed:
+ new_lut = self((x_1, x_0))
+ new_cmap = BivarColormapFromImage(new_lut, name=self.name,
+ shape=shape_memory,
+ origin=self.origin[::-1])
+ else:
+ new_lut = self((x_0, x_1))
+ new_cmap = BivarColormapFromImage(new_lut, name=self.name,
+ shape=shape_memory,
+ origin=self.origin)
+ self._shape = shape_memory
+
+ new_cmap._rgba_bad = self._rgba_bad
+ new_cmap._rgba_outside = self._rgba_outside
+ return new_cmap
+
+ def reversed(self, axis_0=True, axis_1=True):
+ """
+ Reverses both or one of the axis.
+ """
+ r_0 = -1 if axis_0 else 1
+ r_1 = -1 if axis_1 else 1
+ return self.resampled((r_0, r_1))
+
+ def transposed(self):
+ """
+ Transposes the colormap by swapping the order of the axis
+ """
+ return self.resampled((None, None), transposed=True)
+
+ def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None):
+ """
+ Return a copy of the `BivarColormap` with modified attributes.
+
+ Note that the *outside* color is only relevant if `shape` = 'ignore'
+ or 'circleignore'.
+
+ Parameters
+ ----------
+ bad : None or :mpltype:`color`
+ If Matplotlib color, the *bad* value is set accordingly in the copy
+
+ outside : None or :mpltype:`color`
+ If Matplotlib color and shape is 'ignore' or 'circleignore', values
+ *outside* the colormap are colored accordingly in the copy
+
+ shape : {'square', 'circle', 'ignore', 'circleignore'}
+
+ - If 'square' each variate is clipped to [0,1] independently
+ - If 'circle' the variates are clipped radially to the center
+ of the colormap, and a circular mask is applied when the colormap
+ is displayed
+ - If 'ignore' the variates are not clipped, but instead assigned the
+ *outside* color
+ - If 'circleignore' a circular mask is applied, but the data is not
+ clipped and instead assigned the *outside* color
+
+ origin : (float, float)
+ The relative origin of the colormap. Typically (0, 0), for colormaps
+ that are linear on both axis, and (.5, .5) for circular colormaps.
+ Used when getting 1D colormaps from 2D colormaps.
+
+ Returns
+ -------
+ BivarColormap
+ copy of self with attributes set
+ """
+ new_cm = self.copy()
+ if bad is not None:
+ new_cm._rgba_bad = to_rgba(bad)
+ if outside is not None:
+ new_cm._rgba_outside = to_rgba(outside)
+ if shape is not None:
+ _api.check_in_list(['square', 'circle', 'ignore', 'circleignore'],
+ shape=shape)
+ new_cm._shape = shape
+ if origin is not None:
+ new_cm._origin = (float(origin[0]), float(origin[1]))
+
+ return new_cm
+
+ def _init(self):
+ """Generate the lookup table, ``self._lut``."""
+ raise NotImplementedError("Abstract class only")
+
+ @property
+ def shape(self):
+ return self._shape
+
+ @property
+ def origin(self):
+ return self._origin
+
+ def _clip(self, X):
+ """
+ For internal use when applying a BivarColormap to data.
+ i.e. cm.ScalarMappable().to_rgba()
+ Clips X[0] and X[1] according to 'self.shape'.
+ X is modified in-place.
+
+ Parameters
+ ----------
+ X: np.array
+ array of floats or ints to be clipped
+ shape : {'square', 'circle', 'ignore', 'circleignore'}
+
+ - If 'square' each variate is clipped to [0,1] independently
+ - If 'circle' the variates are clipped radially to the center
+ of the colormap.
+ It is assumed that a circular mask is applied when the colormap
+ is displayed
+ - If 'ignore' the variates are not clipped, but instead assigned the
+ 'outside' color
+ - If 'circleignore' a circular mask is applied, but the data is not clipped
+ and instead assigned the 'outside' color
+
+ """
+ if self.shape == 'square':
+ for X_part, mx in zip(X, (self.N, self.M)):
+ X_part[X_part < 0] = 0
+ if X_part.dtype.kind == "f":
+ X_part[X_part > 1] = 1
+ else:
+ X_part[X_part >= mx] = mx - 1
+
+ elif self.shape == 'ignore':
+ for X_part, mx in zip(X, (self.N, self.M)):
+ X_part[X_part < 0] = -1
+ if X_part.dtype.kind == "f":
+ X_part[X_part > 1] = -1
+ else:
+ X_part[X_part >= mx] = -1
+
+ elif self.shape == 'circle' or self.shape == 'circleignore':
+ for X_part in X:
+ if X_part.dtype.kind != "f":
+ raise NotImplementedError(
+ "Circular bivariate colormaps are only"
+ " implemented for use with with floats")
+ radii_sqr = (X[0] - 0.5)**2 + (X[1] - 0.5)**2
+ mask_outside = radii_sqr > 0.25
+ if self.shape == 'circle':
+ overextend = 2 * np.sqrt(radii_sqr[mask_outside])
+ X[0][mask_outside] = (X[0][mask_outside] - 0.5) / overextend + 0.5
+ X[1][mask_outside] = (X[1][mask_outside] - 0.5) / overextend + 0.5
+ else:
+ X[0][mask_outside] = -1
+ X[1][mask_outside] = -1
+
+ def __getitem__(self, item):
+ """Creates and returns a colorbar along the selected axis"""
+ if not self._isinit:
+ self._init()
+ if item == 0:
+ origin_1_as_int = int(self._origin[1]*self.M)
+ if origin_1_as_int > self.M-1:
+ origin_1_as_int = self.M-1
+ one_d_lut = self._lut[:, origin_1_as_int]
+ new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_0', N=self.N)
+
+ elif item == 1:
+ origin_0_as_int = int(self._origin[0]*self.N)
+ if origin_0_as_int > self.N-1:
+ origin_0_as_int = self.N-1
+ one_d_lut = self._lut[origin_0_as_int, :]
+ new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_1', N=self.M)
+ else:
+ raise KeyError(f"only 0 or 1 are"
+ f" valid keys for BivarColormap, not {item!r}")
+ new_cmap._rgba_bad = self._rgba_bad
+ if self.shape in ['ignore', 'circleignore']:
+ new_cmap.set_over(self._rgba_outside)
+ new_cmap.set_under(self._rgba_outside)
+ return new_cmap
+
+ def _repr_png_(self):
+ """Generate a PNG representation of the BivarColormap."""
+ if not self._isinit:
+ self._init()
+ pixels = self.lut
+ if pixels.shape[0] < _BIVAR_REPR_PNG_SIZE:
+ pixels = np.repeat(pixels,
+ repeats=_BIVAR_REPR_PNG_SIZE//pixels.shape[0],
+ axis=0)[:256, :]
+ if pixels.shape[1] < _BIVAR_REPR_PNG_SIZE:
+ pixels = np.repeat(pixels,
+ repeats=_BIVAR_REPR_PNG_SIZE//pixels.shape[1],
+ axis=1)[:, :256]
+ pixels = (pixels[::-1, :, :] * 255).astype(np.uint8)
+ png_bytes = io.BytesIO()
+ title = self.name + ' BivarColormap'
+ author = f'Matplotlib v{mpl.__version__}, https://matplotlib.org'
+ pnginfo = PngInfo()
+ pnginfo.add_text('Title', title)
+ pnginfo.add_text('Description', title)
+ pnginfo.add_text('Author', author)
+ pnginfo.add_text('Software', author)
+ Image.fromarray(pixels).save(png_bytes, format='png', pnginfo=pnginfo)
+ return png_bytes.getvalue()
+
+ def _repr_html_(self):
+ """Generate an HTML representation of the Colormap."""
+ png_bytes = self._repr_png_()
+ png_base64 = base64.b64encode(png_bytes).decode('ascii')
+ def color_block(color):
+ hex_color = to_hex(color, keep_alpha=True)
+ return (f'
')
+
+ return (''
+ f'{self.name} '
+ '
'
+ ''
+ ''
+ '
'
+ f'{color_block(self.get_outside())} outside'
+ '
'
+ '
'
+ f'bad {color_block(self.get_bad())}'
+ '
')
+
+ def copy(self):
+ """Return a copy of the colormap."""
+ return self.__copy__()
+
+
+class SegmentedBivarColormap(BivarColormap):
+ """
+ BivarColormap object generated by supersampling a regular grid.
+
+ Parameters
+ ----------
+ patch : np.array
+ Patch is required to have a shape (k, l, 3), and will get supersampled
+ to a lut of shape (N, N, 4).
+ N : int
+ The number of RGB quantization levels along each axis.
+ shape : {'square', 'circle', 'ignore', 'circleignore'}
+
+ - If 'square' each variate is clipped to [0,1] independently
+ - If 'circle' the variates are clipped radially to the center
+ of the colormap, and a circular mask is applied when the colormap
+ is displayed
+ - If 'ignore' the variates are not clipped, but instead assigned the
+ 'outside' color
+ - If 'circleignore' a circular mask is applied, but the data is not clipped
+
+ origin : (float, float)
+ The relative origin of the colormap. Typically (0, 0), for colormaps
+ that are linear on both axis, and (.5, .5) for circular colormaps.
+ Used when getting 1D colormaps from 2D colormaps.
+
+ name : str, optional
+ The name of the colormap.
+ """
+
+ def __init__(self, patch, N=256, shape='square', origin=(0, 0),
+ name='segmented bivariate colormap'):
+ _api.check_shape((None, None, 3), patch=patch)
+ self.patch = patch
+ super().__init__(N, N, shape, origin, name=name)
+
+ def _init(self):
+ s = self.patch.shape
+ _patch = np.empty((s[0], s[1], 4))
+ _patch[:, :, :3] = self.patch
+ _patch[:, :, 3] = 1
+ transform = mpl.transforms.Affine2D().translate(-0.5, -0.5)\
+ .scale(self.N / (s[1] - 1), self.N / (s[0] - 1))
+ self._lut = np.empty((self.N, self.N, 4))
+
+ _image.resample(_patch, self._lut, transform, _image.BILINEAR,
+ resample=False, alpha=1)
+ self._isinit = True
+
+
+class BivarColormapFromImage(BivarColormap):
+ """
+ BivarColormap object generated by supersampling a regular grid.
+
+ Parameters
+ ----------
+ lut : nparray of shape (N, M, 3) or (N, M, 4)
+ The look-up-table
+ shape: {'square', 'circle', 'ignore', 'circleignore'}
+
+ - If 'square' each variate is clipped to [0,1] independently
+ - If 'circle' the variates are clipped radially to the center
+ of the colormap, and a circular mask is applied when the colormap
+ is displayed
+ - If 'ignore' the variates are not clipped, but instead assigned the
+ 'outside' color
+ - If 'circleignore' a circular mask is applied, but the data is not clipped
+
+ origin: (float, float)
+ The relative origin of the colormap. Typically (0, 0), for colormaps
+ that are linear on both axis, and (.5, .5) for circular colormaps.
+ Used when getting 1D colormaps from 2D colormaps.
+ name : str, optional
+ The name of the colormap.
+
+ """
+
+ def __init__(self, lut, shape='square', origin=(0, 0), name='from image'):
+ # We can allow for a PIL.Image as input in the following way, but importing
+ # matplotlib.image.pil_to_array() results in a circular import
+ # For now, this function only accepts numpy arrays.
+ # i.e.:
+ # if isinstance(Image, lut):
+ # lut = image.pil_to_array(lut)
+ lut = np.array(lut, copy=True)
+ if lut.ndim != 3 or lut.shape[2] not in (3, 4):
+ raise ValueError("The lut must be an array of shape (n, m, 3) or (n, m, 4)",
+ " or a PIL.image encoded as RGB or RGBA")
+
+ if lut.dtype == np.uint8:
+ lut = lut.astype(np.float32)/255
+ if lut.shape[2] == 3:
+ new_lut = np.empty((lut.shape[0], lut.shape[1], 4), dtype=lut.dtype)
+ new_lut[:, :, :3] = lut
+ new_lut[:, :, 3] = 1.
+ lut = new_lut
+ self._lut = lut
+ super().__init__(lut.shape[0], lut.shape[1], shape, origin, name=name)
+
+ def _init(self):
+ self._isinit = True
+
+
+class Normalize:
+ """
+ A class which, when called, maps values within the interval
+ ``[vmin, vmax]`` linearly to the interval ``[0.0, 1.0]``. The mapping of
+ values outside ``[vmin, vmax]`` depends on *clip*.
+
+ Examples
+ --------
+ ::
+
+ x = [-2, -1, 0, 1, 2]
+
+ norm = mpl.colors.Normalize(vmin=-1, vmax=1, clip=False)
+ norm(x) # [-0.5, 0., 0.5, 1., 1.5]
+ norm = mpl.colors.Normalize(vmin=-1, vmax=1, clip=True)
+ norm(x) # [0., 0., 0.5, 1., 1.]
+
+ See Also
+ --------
+ :ref:`colormapnorms`
+ """
+
+ def __init__(self, vmin=None, vmax=None, clip=False):
+ """
+ Parameters
+ ----------
+ vmin, vmax : float or None
+ Values within the range ``[vmin, vmax]`` from the input data will be
+ linearly mapped to ``[0, 1]``. If either *vmin* or *vmax* is not
+ provided, they default to the minimum and maximum values of the input,
+ respectively.
+
+ clip : bool, default: False
+ Determines the behavior for mapping values outside the range
+ ``[vmin, vmax]``.
+
+ If clipping is off, values outside the range ``[vmin, vmax]`` are
+ also transformed, resulting in values outside ``[0, 1]``. This
+ behavior is usually desirable, as colormaps can mark these *under*
+ and *over* values with specific colors.
+
+ If clipping is on, values below *vmin* are mapped to 0 and values
+ above *vmax* are mapped to 1. Such values become indistinguishable
+ from regular boundary values, which may cause misinterpretation of
+ the data.
+
+ Notes
+ -----
+ If ``vmin == vmax``, input data will be mapped to 0.
+ """
+ self._vmin = _sanitize_extrema(vmin)
+ self._vmax = _sanitize_extrema(vmax)
+ self._clip = clip
+ self._scale = None
+ self.callbacks = cbook.CallbackRegistry(signals=["changed"])
+
+ @property
+ def vmin(self):
+ return self._vmin
+
+ @vmin.setter
+ def vmin(self, value):
+ value = _sanitize_extrema(value)
+ if value != self._vmin:
+ self._vmin = value
+ self._changed()
+
+ @property
+ def vmax(self):
+ return self._vmax
+
+ @vmax.setter
+ def vmax(self, value):
+ value = _sanitize_extrema(value)
+ if value != self._vmax:
+ self._vmax = value
+ self._changed()
+
+ @property
+ def clip(self):
+ return self._clip
+
+ @clip.setter
+ def clip(self, value):
+ if value != self._clip:
+ self._clip = value
+ self._changed()
+
+ def _changed(self):
+ """
+ Call this whenever the norm is changed to notify all the
+ callback listeners to the 'changed' signal.
+ """
+ self.callbacks.process('changed')
+
+ @staticmethod
+ def process_value(value):
+ """
+ Homogenize the input *value* for easy and efficient normalization.
+
+ *value* can be a scalar or sequence.
+
+ Parameters
+ ----------
+ value
+ Data to normalize.
+
+ Returns
+ -------
+ result : masked array
+ Masked array with the same shape as *value*.
+ is_scalar : bool
+ Whether *value* is a scalar.
+
+ Notes
+ -----
+ Float dtypes are preserved; integer types with two bytes or smaller are
+ converted to np.float32, and larger types are converted to np.float64.
+ Preserving float32 when possible, and using in-place operations,
+ greatly improves speed for large arrays.
+ """
+ is_scalar = not np.iterable(value)
+ if is_scalar:
+ value = [value]
+ dtype = np.min_scalar_type(value)
+ if np.issubdtype(dtype, np.integer) or dtype.type is np.bool_:
+ # bool_/int8/int16 -> float32; int32/int64 -> float64
+ dtype = np.promote_types(dtype, np.float32)
+ # ensure data passed in as an ndarray subclass are interpreted as
+ # an ndarray. See issue #6622.
+ mask = np.ma.getmask(value)
+ data = np.asarray(value)
+ result = np.ma.array(data, mask=mask, dtype=dtype, copy=True)
+ return result, is_scalar
+
+ def __call__(self, value, clip=None):
+ """
+ Normalize the data and return the normalized data.
+
+ Parameters
+ ----------
+ value
+ Data to normalize.
+ clip : bool, optional
+ See the description of the parameter *clip* in `.Normalize`.
+
+ If ``None``, defaults to ``self.clip`` (which defaults to
+ ``False``).
+
+ Notes
+ -----
+ If not already initialized, ``self.vmin`` and ``self.vmax`` are
+ initialized using ``self.autoscale_None(value)``.
+ """
+ if clip is None:
+ clip = self.clip
+
+ result, is_scalar = self.process_value(value)
+
+ if self.vmin is None or self.vmax is None:
+ self.autoscale_None(result)
+ # Convert at least to float, without losing precision.
+ (vmin,), _ = self.process_value(self.vmin)
+ (vmax,), _ = self.process_value(self.vmax)
+ if vmin == vmax:
+ result.fill(0) # Or should it be all masked? Or 0.5?
+ elif vmin > vmax:
+ raise ValueError("minvalue must be less than or equal to maxvalue")
+ else:
+ if clip:
+ mask = np.ma.getmask(result)
+ result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax),
+ mask=mask)
+ # ma division is very slow; we can take a shortcut
+ resdat = result.data
+ resdat -= vmin
+ resdat /= (vmax - vmin)
+ result = np.ma.array(resdat, mask=result.mask, copy=False)
+ if is_scalar:
+ result = result[0]
+ return result
+
+ def inverse(self, value):
+ """
+ Maps the normalized value (i.e., index in the colormap) back to image
+ data value.
+
+ Parameters
+ ----------
+ value
+ Normalized value.
+ """
+ if not self.scaled():
+ raise ValueError("Not invertible until both vmin and vmax are set")
+ (vmin,), _ = self.process_value(self.vmin)
+ (vmax,), _ = self.process_value(self.vmax)
+
+ if np.iterable(value):
+ val = np.ma.asarray(value)
+ return vmin + val * (vmax - vmin)
+ else:
+ return vmin + value * (vmax - vmin)
+
+ def autoscale(self, A):
+ """Set *vmin*, *vmax* to min, max of *A*."""
+ with self.callbacks.blocked():
+ # Pause callbacks while we are updating so we only get
+ # a single update signal at the end
+ self.vmin = self.vmax = None
+ self.autoscale_None(A)
+ self._changed()
+
+ def autoscale_None(self, A):
+ """If *vmin* or *vmax* are not set, use the min/max of *A* to set them."""
+ A = np.asanyarray(A)
+
+ if isinstance(A, np.ma.MaskedArray):
+ # we need to make the distinction between an array, False, np.bool_(False)
+ if A.mask is False or not A.mask.shape:
+ A = A.data
+
+ if self.vmin is None and A.size:
+ self.vmin = A.min()
+ if self.vmax is None and A.size:
+ self.vmax = A.max()
+
+ def scaled(self):
+ """Return whether *vmin* and *vmax* are both set."""
+ return self.vmin is not None and self.vmax is not None
+
+
+class TwoSlopeNorm(Normalize):
+ def __init__(self, vcenter, vmin=None, vmax=None):
+ """
+ Normalize data with a set center.
+
+ Useful when mapping data with an unequal rates of change around a
+ conceptual center, e.g., data that range from -2 to 4, with 0 as
+ the midpoint.
+
+ Parameters
+ ----------
+ vcenter : float
+ The data value that defines ``0.5`` in the normalization.
+ vmin : float, optional
+ The data value that defines ``0.0`` in the normalization.
+ Defaults to the min value of the dataset.
+ vmax : float, optional
+ The data value that defines ``1.0`` in the normalization.
+ Defaults to the max value of the dataset.
+
+ Examples
+ --------
+ This maps data value -4000 to 0., 0 to 0.5, and +10000 to 1.0; data
+ between is linearly interpolated::
+
+ >>> import matplotlib.colors as mcolors
+ >>> offset = mcolors.TwoSlopeNorm(vmin=-4000.,
+ ... vcenter=0., vmax=10000)
+ >>> data = [-4000., -2000., 0., 2500., 5000., 7500., 10000.]
+ >>> offset(data)
+ array([0., 0.25, 0.5, 0.625, 0.75, 0.875, 1.0])
+ """
+
+ super().__init__(vmin=vmin, vmax=vmax)
+ self._vcenter = vcenter
+ if vcenter is not None and vmax is not None and vcenter >= vmax:
+ raise ValueError('vmin, vcenter, and vmax must be in '
+ 'ascending order')
+ if vcenter is not None and vmin is not None and vcenter <= vmin:
+ raise ValueError('vmin, vcenter, and vmax must be in '
+ 'ascending order')
+
+ @property
+ def vcenter(self):
+ return self._vcenter
+
+ @vcenter.setter
+ def vcenter(self, value):
+ if value != self._vcenter:
+ self._vcenter = value
+ self._changed()
+
+ def autoscale_None(self, A):
+ """
+ Get vmin and vmax.
+
+ If vcenter isn't in the range [vmin, vmax], either vmin or vmax
+ is expanded so that vcenter lies in the middle of the modified range
+ [vmin, vmax].
+ """
+ super().autoscale_None(A)
+ if self.vmin >= self.vcenter:
+ self.vmin = self.vcenter - (self.vmax - self.vcenter)
+ if self.vmax <= self.vcenter:
+ self.vmax = self.vcenter + (self.vcenter - self.vmin)
+
+ def __call__(self, value, clip=None):
+ """
+ Map value to the interval [0, 1]. The *clip* argument is unused.
+ """
+ result, is_scalar = self.process_value(value)
+ self.autoscale_None(result) # sets self.vmin, self.vmax if None
+
+ if not self.vmin <= self.vcenter <= self.vmax:
+ raise ValueError("vmin, vcenter, vmax must increase monotonically")
+ # note that we must extrapolate for tick locators:
+ result = np.ma.masked_array(
+ np.interp(result, [self.vmin, self.vcenter, self.vmax],
+ [0, 0.5, 1], left=-np.inf, right=np.inf),
+ mask=np.ma.getmask(result))
+ if is_scalar:
+ result = np.atleast_1d(result)[0]
+ return result
+
+ def inverse(self, value):
+ if not self.scaled():
+ raise ValueError("Not invertible until both vmin and vmax are set")
+ (vmin,), _ = self.process_value(self.vmin)
+ (vmax,), _ = self.process_value(self.vmax)
+ (vcenter,), _ = self.process_value(self.vcenter)
+ result = np.interp(value, [0, 0.5, 1], [vmin, vcenter, vmax],
+ left=-np.inf, right=np.inf)
+ return result
+
+
+class CenteredNorm(Normalize):
+ def __init__(self, vcenter=0, halfrange=None, clip=False):
+ """
+ Normalize symmetrical data around a center (0 by default).
+
+ Unlike `TwoSlopeNorm`, `CenteredNorm` applies an equal rate of change
+ around the center.
+
+ Useful when mapping symmetrical data around a conceptual center
+ e.g., data that range from -2 to 4, with 0 as the midpoint, and
+ with equal rates of change around that midpoint.
+
+ Parameters
+ ----------
+ vcenter : float, default: 0
+ The data value that defines ``0.5`` in the normalization.
+ halfrange : float, optional
+ The range of data values that defines a range of ``0.5`` in the
+ normalization, so that *vcenter* - *halfrange* is ``0.0`` and
+ *vcenter* + *halfrange* is ``1.0`` in the normalization.
+ Defaults to the largest absolute difference to *vcenter* for
+ the values in the dataset.
+ clip : bool, default: False
+ Determines the behavior for mapping values outside the range
+ ``[vmin, vmax]``.
+
+ If clipping is off, values outside the range ``[vmin, vmax]`` are
+ also transformed, resulting in values outside ``[0, 1]``. This
+ behavior is usually desirable, as colormaps can mark these *under*
+ and *over* values with specific colors.
+
+ If clipping is on, values below *vmin* are mapped to 0 and values
+ above *vmax* are mapped to 1. Such values become indistinguishable
+ from regular boundary values, which may cause misinterpretation of
+ the data.
+
+ Examples
+ --------
+ This maps data values -2 to 0.25, 0 to 0.5, and 4 to 1.0
+ (assuming equal rates of change above and below 0.0):
+
+ >>> import matplotlib.colors as mcolors
+ >>> norm = mcolors.CenteredNorm(halfrange=4.0)
+ >>> data = [-2., 0., 4.]
+ >>> norm(data)
+ array([0.25, 0.5 , 1. ])
+ """
+ super().__init__(vmin=None, vmax=None, clip=clip)
+ self._vcenter = vcenter
+ # calling the halfrange setter to set vmin and vmax
+ self.halfrange = halfrange
+
+ def autoscale(self, A):
+ """
+ Set *halfrange* to ``max(abs(A-vcenter))``, then set *vmin* and *vmax*.
+ """
+ A = np.asanyarray(A)
+ self.halfrange = max(self._vcenter-A.min(),
+ A.max()-self._vcenter)
+
+ def autoscale_None(self, A):
+ """Set *vmin* and *vmax*."""
+ A = np.asanyarray(A)
+ if self.halfrange is None and A.size:
+ self.autoscale(A)
+
+ @property
+ def vmin(self):
+ return self._vmin
+
+ @vmin.setter
+ def vmin(self, value):
+ value = _sanitize_extrema(value)
+ if value != self._vmin:
+ self._vmin = value
+ self._vmax = 2*self.vcenter - value
+ self._changed()
+
+ @property
+ def vmax(self):
+ return self._vmax
+
+ @vmax.setter
+ def vmax(self, value):
+ value = _sanitize_extrema(value)
+ if value != self._vmax:
+ self._vmax = value
+ self._vmin = 2*self.vcenter - value
+ self._changed()
+
+ @property
+ def vcenter(self):
+ return self._vcenter
+
+ @vcenter.setter
+ def vcenter(self, vcenter):
+ if vcenter != self._vcenter:
+ self._vcenter = vcenter
+ # Trigger an update of the vmin/vmax values through the setter
+ self.halfrange = self.halfrange
+ self._changed()
+
+ @property
+ def halfrange(self):
+ if self.vmin is None or self.vmax is None:
+ return None
+ return (self.vmax - self.vmin) / 2
+
+ @halfrange.setter
+ def halfrange(self, halfrange):
+ if halfrange is None:
+ self.vmin = None
+ self.vmax = None
+ else:
+ self.vmin = self.vcenter - abs(halfrange)
+ self.vmax = self.vcenter + abs(halfrange)
+
+
+def make_norm_from_scale(scale_cls, base_norm_cls=None, *, init=None):
+ """
+ Decorator for building a `.Normalize` subclass from a `~.scale.ScaleBase`
+ subclass.
+
+ After ::
+
+ @make_norm_from_scale(scale_cls)
+ class norm_cls(Normalize):
+ ...
+
+ *norm_cls* is filled with methods so that normalization computations are
+ forwarded to *scale_cls* (i.e., *scale_cls* is the scale that would be used
+ for the colorbar of a mappable normalized with *norm_cls*).
+
+ If *init* is not passed, then the constructor signature of *norm_cls*
+ will be ``norm_cls(vmin=None, vmax=None, clip=False)``; these three
+ parameters will be forwarded to the base class (``Normalize.__init__``),
+ and a *scale_cls* object will be initialized with no arguments (other than
+ a dummy axis).
+
+ If the *scale_cls* constructor takes additional parameters, then *init*
+ should be passed to `make_norm_from_scale`. It is a callable which is
+ *only* used for its signature. First, this signature will become the
+ signature of *norm_cls*. Second, the *norm_cls* constructor will bind the
+ parameters passed to it using this signature, extract the bound *vmin*,
+ *vmax*, and *clip* values, pass those to ``Normalize.__init__``, and
+ forward the remaining bound values (including any defaults defined by the
+ signature) to the *scale_cls* constructor.
+ """
+
+ if base_norm_cls is None:
+ return functools.partial(make_norm_from_scale, scale_cls, init=init)
+
+ if isinstance(scale_cls, functools.partial):
+ scale_args = scale_cls.args
+ scale_kwargs_items = tuple(scale_cls.keywords.items())
+ scale_cls = scale_cls.func
+ else:
+ scale_args = scale_kwargs_items = ()
+
+ if init is None:
+ def init(vmin=None, vmax=None, clip=False): pass
+
+ return _make_norm_from_scale(
+ scale_cls, scale_args, scale_kwargs_items,
+ base_norm_cls, inspect.signature(init))
+
+
+@functools.cache
+def _make_norm_from_scale(
+ scale_cls, scale_args, scale_kwargs_items,
+ base_norm_cls, bound_init_signature,
+):
+ """
+ Helper for `make_norm_from_scale`.
+
+ This function is split out to enable caching (in particular so that
+ different unpickles reuse the same class). In order to do so,
+
+ - ``functools.partial`` *scale_cls* is expanded into ``func, args, kwargs``
+ to allow memoizing returned norms (partial instances always compare
+ unequal, but we can check identity based on ``func, args, kwargs``;
+ - *init* is replaced by *init_signature*, as signatures are picklable,
+ unlike to arbitrary lambdas.
+ """
+
+ class Norm(base_norm_cls):
+ def __reduce__(self):
+ cls = type(self)
+ # If the class is toplevel-accessible, it is possible to directly
+ # pickle it "by name". This is required to support norm classes
+ # defined at a module's toplevel, as the inner base_norm_cls is
+ # otherwise unpicklable (as it gets shadowed by the generated norm
+ # class). If either import or attribute access fails, fall back to
+ # the general path.
+ try:
+ if cls is getattr(importlib.import_module(cls.__module__),
+ cls.__qualname__):
+ return (_create_empty_object_of_class, (cls,), vars(self))
+ except (ImportError, AttributeError):
+ pass
+ return (_picklable_norm_constructor,
+ (scale_cls, scale_args, scale_kwargs_items,
+ base_norm_cls, bound_init_signature),
+ vars(self))
+
+ def __init__(self, *args, **kwargs):
+ ba = bound_init_signature.bind(*args, **kwargs)
+ ba.apply_defaults()
+ super().__init__(
+ **{k: ba.arguments.pop(k) for k in ["vmin", "vmax", "clip"]})
+ self._scale = functools.partial(
+ scale_cls, *scale_args, **dict(scale_kwargs_items))(
+ axis=None, **ba.arguments)
+ self._trf = self._scale.get_transform()
+
+ __init__.__signature__ = bound_init_signature.replace(parameters=[
+ inspect.Parameter("self", inspect.Parameter.POSITIONAL_OR_KEYWORD),
+ *bound_init_signature.parameters.values()])
+
+ def __call__(self, value, clip=None):
+ value, is_scalar = self.process_value(value)
+ if self.vmin is None or self.vmax is None:
+ self.autoscale_None(value)
+ if self.vmin > self.vmax:
+ raise ValueError("vmin must be less or equal to vmax")
+ if self.vmin == self.vmax:
+ return np.full_like(value, 0)
+ if clip is None:
+ clip = self.clip
+ if clip:
+ value = np.clip(value, self.vmin, self.vmax)
+ t_value = self._trf.transform(value).reshape(np.shape(value))
+ t_vmin, t_vmax = self._trf.transform([self.vmin, self.vmax])
+ if not np.isfinite([t_vmin, t_vmax]).all():
+ raise ValueError("Invalid vmin or vmax")
+ t_value -= t_vmin
+ t_value /= (t_vmax - t_vmin)
+ t_value = np.ma.masked_invalid(t_value, copy=False)
+ return t_value[0] if is_scalar else t_value
+
+ def inverse(self, value):
+ if not self.scaled():
+ raise ValueError("Not invertible until scaled")
+ if self.vmin > self.vmax:
+ raise ValueError("vmin must be less or equal to vmax")
+ t_vmin, t_vmax = self._trf.transform([self.vmin, self.vmax])
+ if not np.isfinite([t_vmin, t_vmax]).all():
+ raise ValueError("Invalid vmin or vmax")
+ value, is_scalar = self.process_value(value)
+ rescaled = value * (t_vmax - t_vmin)
+ rescaled += t_vmin
+ value = (self._trf
+ .inverted()
+ .transform(rescaled)
+ .reshape(np.shape(value)))
+ return value[0] if is_scalar else value
+
+ def autoscale_None(self, A):
+ # i.e. A[np.isfinite(...)], but also for non-array A's
+ in_trf_domain = np.extract(np.isfinite(self._trf.transform(A)), A)
+ if in_trf_domain.size == 0:
+ in_trf_domain = np.ma.masked
+ return super().autoscale_None(in_trf_domain)
+
+ if base_norm_cls is Normalize:
+ Norm.__name__ = f"{scale_cls.__name__}Norm"
+ Norm.__qualname__ = f"{scale_cls.__qualname__}Norm"
+ else:
+ Norm.__name__ = base_norm_cls.__name__
+ Norm.__qualname__ = base_norm_cls.__qualname__
+ Norm.__module__ = base_norm_cls.__module__
+ Norm.__doc__ = base_norm_cls.__doc__
+
+ return Norm
+
+
+def _create_empty_object_of_class(cls):
+ return cls.__new__(cls)
+
+
+def _picklable_norm_constructor(*args):
+ return _create_empty_object_of_class(_make_norm_from_scale(*args))
+
+
+@make_norm_from_scale(
+ scale.FuncScale,
+ init=lambda functions, vmin=None, vmax=None, clip=False: None)
+class FuncNorm(Normalize):
+ """
+ Arbitrary normalization using functions for the forward and inverse.
+
+ Parameters
+ ----------
+ functions : (callable, callable)
+ two-tuple of the forward and inverse functions for the normalization.
+ The forward function must be monotonic.
+
+ Both functions must have the signature ::
+
+ def forward(values: array-like) -> array-like
+
+ vmin, vmax : float or None
+ If *vmin* and/or *vmax* is not given, they are initialized from the
+ minimum and maximum value, respectively, of the first input
+ processed; i.e., ``__call__(A)`` calls ``autoscale_None(A)``.
+
+ clip : bool, default: False
+ Determines the behavior for mapping values outside the range
+ ``[vmin, vmax]``.
+
+ If clipping is off, values outside the range ``[vmin, vmax]`` are also
+ transformed by the function, resulting in values outside ``[0, 1]``.
+ This behavior is usually desirable, as colormaps can mark these *under*
+ and *over* values with specific colors.
+
+ If clipping is on, values below *vmin* are mapped to 0 and values above
+ *vmax* are mapped to 1. Such values become indistinguishable from
+ regular boundary values, which may cause misinterpretation of the data.
+ """
+
+
+LogNorm = make_norm_from_scale(
+ functools.partial(scale.LogScale, nonpositive="mask"))(Normalize)
+LogNorm.__name__ = LogNorm.__qualname__ = "LogNorm"
+LogNorm.__doc__ = "Normalize a given value to the 0-1 range on a log scale."
+
+
+@make_norm_from_scale(
+ scale.SymmetricalLogScale,
+ init=lambda linthresh, linscale=1., vmin=None, vmax=None, clip=False, *,
+ base=10: None)
+class SymLogNorm(Normalize):
+ """
+ The symmetrical logarithmic scale is logarithmic in both the
+ positive and negative directions from the origin.
+
+ Since the values close to zero tend toward infinity, there is a
+ need to have a range around zero that is linear. The parameter
+ *linthresh* allows the user to specify the size of this range
+ (-*linthresh*, *linthresh*).
+
+ Parameters
+ ----------
+ linthresh : float
+ The range within which the plot is linear (to avoid having the plot
+ go to infinity around zero).
+ linscale : float, default: 1
+ This allows the linear range (-*linthresh* to *linthresh*) to be
+ stretched relative to the logarithmic range. Its value is the
+ number of decades to use for each half of the linear range. For
+ example, when *linscale* == 1.0 (the default), the space used for
+ the positive and negative halves of the linear range will be equal
+ to one decade in the logarithmic range.
+ base : float, default: 10
+ """
+
+ @property
+ def linthresh(self):
+ return self._scale.linthresh
+
+ @linthresh.setter
+ def linthresh(self, value):
+ self._scale.linthresh = value
+
+
+@make_norm_from_scale(
+ scale.AsinhScale,
+ init=lambda linear_width=1, vmin=None, vmax=None, clip=False: None)
+class AsinhNorm(Normalize):
+ """
+ The inverse hyperbolic sine scale is approximately linear near
+ the origin, but becomes logarithmic for larger positive
+ or negative values. Unlike the `SymLogNorm`, the transition between
+ these linear and logarithmic regions is smooth, which may reduce
+ the risk of visual artifacts.
+
+ .. note::
+
+ This API is provisional and may be revised in the future
+ based on early user feedback.
+
+ Parameters
+ ----------
+ linear_width : float, default: 1
+ The effective width of the linear region, beyond which
+ the transformation becomes asymptotically logarithmic
+ """
+
+ @property
+ def linear_width(self):
+ return self._scale.linear_width
+
+ @linear_width.setter
+ def linear_width(self, value):
+ self._scale.linear_width = value
+
+
+class PowerNorm(Normalize):
+ r"""
+ Linearly map a given value to the 0-1 range and then apply
+ a power-law normalization over that range.
+
+ Parameters
+ ----------
+ gamma : float
+ Power law exponent.
+ vmin, vmax : float or None
+ If *vmin* and/or *vmax* is not given, they are initialized from the
+ minimum and maximum value, respectively, of the first input
+ processed; i.e., ``__call__(A)`` calls ``autoscale_None(A)``.
+ clip : bool, default: False
+ Determines the behavior for mapping values outside the range
+ ``[vmin, vmax]``.
+
+ If clipping is off, values above *vmax* are transformed by the power
+ function, resulting in values above 1, and values below *vmin* are linearly
+ transformed resulting in values below 0. This behavior is usually desirable, as
+ colormaps can mark these *under* and *over* values with specific colors.
+
+ If clipping is on, values below *vmin* are mapped to 0 and values above
+ *vmax* are mapped to 1. Such values become indistinguishable from
+ regular boundary values, which may cause misinterpretation of the data.
+
+ Notes
+ -----
+ The normalization formula is
+
+ .. math::
+
+ \left ( \frac{x - v_{min}}{v_{max} - v_{min}} \right )^{\gamma}
+
+ For input values below *vmin*, gamma is set to one.
+ """
+ def __init__(self, gamma, vmin=None, vmax=None, clip=False):
+ super().__init__(vmin, vmax, clip)
+ self.gamma = gamma
+
+ def __call__(self, value, clip=None):
+ if clip is None:
+ clip = self.clip
+
+ result, is_scalar = self.process_value(value)
+
+ self.autoscale_None(result)
+ gamma = self.gamma
+ vmin, vmax = self.vmin, self.vmax
+ if vmin > vmax:
+ raise ValueError("minvalue must be less than or equal to maxvalue")
+ elif vmin == vmax:
+ result.fill(0)
+ else:
+ if clip:
+ mask = np.ma.getmask(result)
+ result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax),
+ mask=mask)
+ resdat = result.data
+ resdat -= vmin
+ resdat /= (vmax - vmin)
+ resdat[resdat > 0] = np.power(resdat[resdat > 0], gamma)
+
+ result = np.ma.array(resdat, mask=result.mask, copy=False)
+ if is_scalar:
+ result = result[0]
+ return result
+
+ def inverse(self, value):
+ if not self.scaled():
+ raise ValueError("Not invertible until scaled")
+
+ result, is_scalar = self.process_value(value)
+
+ gamma = self.gamma
+ vmin, vmax = self.vmin, self.vmax
+
+ resdat = result.data
+ resdat[resdat > 0] = np.power(resdat[resdat > 0], 1 / gamma)
+ resdat *= (vmax - vmin)
+ resdat += vmin
+
+ result = np.ma.array(resdat, mask=result.mask, copy=False)
+ if is_scalar:
+ result = result[0]
+ return result
+
+
+class BoundaryNorm(Normalize):
+ """
+ Generate a colormap index based on discrete intervals.
+
+ Unlike `Normalize` or `LogNorm`, `BoundaryNorm` maps values to integers
+ instead of to the interval 0-1.
+ """
+
+ # Mapping to the 0-1 interval could have been done via piece-wise linear
+ # interpolation, but using integers seems simpler, and reduces the number
+ # of conversions back and forth between int and float.
+
+ def __init__(self, boundaries, ncolors, clip=False, *, extend='neither'):
+ """
+ Parameters
+ ----------
+ boundaries : array-like
+ Monotonically increasing sequence of at least 2 bin edges: data
+ falling in the n-th bin will be mapped to the n-th color.
+
+ ncolors : int
+ Number of colors in the colormap to be used.
+
+ clip : bool, optional
+ If clip is ``True``, out of range values are mapped to 0 if they
+ are below ``boundaries[0]`` or mapped to ``ncolors - 1`` if they
+ are above ``boundaries[-1]``.
+
+ If clip is ``False``, out of range values are mapped to -1 if
+ they are below ``boundaries[0]`` or mapped to *ncolors* if they are
+ above ``boundaries[-1]``. These are then converted to valid indices
+ by `Colormap.__call__`.
+
+ extend : {'neither', 'both', 'min', 'max'}, default: 'neither'
+ Extend the number of bins to include one or both of the
+ regions beyond the boundaries. For example, if ``extend``
+ is 'min', then the color to which the region between the first
+ pair of boundaries is mapped will be distinct from the first
+ color in the colormap, and by default a
+ `~matplotlib.colorbar.Colorbar` will be drawn with
+ the triangle extension on the left or lower end.
+
+ Notes
+ -----
+ If there are fewer bins (including extensions) than colors, then the
+ color index is chosen by linearly interpolating the ``[0, nbins - 1]``
+ range onto the ``[0, ncolors - 1]`` range, effectively skipping some
+ colors in the middle of the colormap.
+ """
+ if clip and extend != 'neither':
+ raise ValueError("'clip=True' is not compatible with 'extend'")
+ super().__init__(vmin=boundaries[0], vmax=boundaries[-1], clip=clip)
+ self.boundaries = np.asarray(boundaries)
+ self.N = len(self.boundaries)
+ if self.N < 2:
+ raise ValueError("You must provide at least 2 boundaries "
+ f"(1 region) but you passed in {boundaries!r}")
+ self.Ncmap = ncolors
+ self.extend = extend
+
+ self._scale = None # don't use the default scale.
+
+ self._n_regions = self.N - 1 # number of colors needed
+ self._offset = 0
+ if extend in ('min', 'both'):
+ self._n_regions += 1
+ self._offset = 1
+ if extend in ('max', 'both'):
+ self._n_regions += 1
+ if self._n_regions > self.Ncmap:
+ raise ValueError(f"There are {self._n_regions} color bins "
+ "including extensions, but ncolors = "
+ f"{ncolors}; ncolors must equal or exceed the "
+ "number of bins")
+
+ def __call__(self, value, clip=None):
+ """
+ This method behaves similarly to `.Normalize.__call__`, except that it
+ returns integers or arrays of int16.
+ """
+ if clip is None:
+ clip = self.clip
+
+ xx, is_scalar = self.process_value(value)
+ mask = np.ma.getmaskarray(xx)
+ # Fill masked values a value above the upper boundary
+ xx = np.atleast_1d(xx.filled(self.vmax + 1))
+ if clip:
+ np.clip(xx, self.vmin, self.vmax, out=xx)
+ max_col = self.Ncmap - 1
+ else:
+ max_col = self.Ncmap
+ # this gives us the bins in the lookup table in the range
+ # [0, _n_regions - 1] (the offset is set in the init)
+ iret = np.digitize(xx, self.boundaries) - 1 + self._offset
+ # if we have more colors than regions, stretch the region
+ # index computed above to full range of the color bins. This
+ # will make use of the full range (but skip some of the colors
+ # in the middle) such that the first region is mapped to the
+ # first color and the last region is mapped to the last color.
+ if self.Ncmap > self._n_regions:
+ if self._n_regions == 1:
+ # special case the 1 region case, pick the middle color
+ iret[iret == 0] = (self.Ncmap - 1) // 2
+ else:
+ # otherwise linearly remap the values from the region index
+ # to the color index spaces
+ iret = (self.Ncmap - 1) / (self._n_regions - 1) * iret
+ # cast to 16bit integers in all cases
+ iret = iret.astype(np.int16)
+ iret[xx < self.vmin] = -1
+ iret[xx >= self.vmax] = max_col
+ ret = np.ma.array(iret, mask=mask)
+ if is_scalar:
+ ret = int(ret[0]) # assume python scalar
+ return ret
+
+ def inverse(self, value):
+ """
+ Raises
+ ------
+ ValueError
+ BoundaryNorm is not invertible, so calling this method will always
+ raise an error
+ """
+ raise ValueError("BoundaryNorm is not invertible")
+
+
+class NoNorm(Normalize):
+ """
+ Dummy replacement for `Normalize`, for the case where we want to use
+ indices directly in a `~matplotlib.cm.ScalarMappable`.
+ """
+ def __call__(self, value, clip=None):
+ if np.iterable(value):
+ return np.ma.array(value)
+ return value
+
+ def inverse(self, value):
+ if np.iterable(value):
+ return np.ma.array(value)
+ return value
+
+
+def rgb_to_hsv(arr):
+ """
+ Convert an array of float RGB values (in the range [0, 1]) to HSV values.
+
+ Parameters
+ ----------
+ arr : (..., 3) array-like
+ All values must be in the range [0, 1]
+
+ Returns
+ -------
+ (..., 3) `~numpy.ndarray`
+ Colors converted to HSV values in range [0, 1]
+ """
+ arr = np.asarray(arr)
+
+ # check length of the last dimension, should be _some_ sort of rgb
+ if arr.shape[-1] != 3:
+ raise ValueError("Last dimension of input array must be 3; "
+ f"shape {arr.shape} was found.")
+
+ in_shape = arr.shape
+ arr = np.array(
+ arr, copy=False,
+ dtype=np.promote_types(arr.dtype, np.float32), # Don't work on ints.
+ ndmin=2, # In case input was 1D.
+ )
+
+ out = np.zeros_like(arr)
+ arr_max = arr.max(-1)
+ # Check if input is in the expected range
+ if np.any(arr_max > 1):
+ raise ValueError(
+ "Input array must be in the range [0, 1]. "
+ f"Found a maximum value of {arr_max.max()}"
+ )
+
+ if arr.min() < 0:
+ raise ValueError(
+ "Input array must be in the range [0, 1]. "
+ f"Found a minimum value of {arr.min()}"
+ )
+
+ ipos = arr_max > 0
+ delta = np.ptp(arr, -1)
+ s = np.zeros_like(delta)
+ s[ipos] = delta[ipos] / arr_max[ipos]
+ ipos = delta > 0
+ # red is max
+ idx = (arr[..., 0] == arr_max) & ipos
+ out[idx, 0] = (arr[idx, 1] - arr[idx, 2]) / delta[idx]
+ # green is max
+ idx = (arr[..., 1] == arr_max) & ipos
+ out[idx, 0] = 2. + (arr[idx, 2] - arr[idx, 0]) / delta[idx]
+ # blue is max
+ idx = (arr[..., 2] == arr_max) & ipos
+ out[idx, 0] = 4. + (arr[idx, 0] - arr[idx, 1]) / delta[idx]
+
+ out[..., 0] = (out[..., 0] / 6.0) % 1.0
+ out[..., 1] = s
+ out[..., 2] = arr_max
+
+ return out.reshape(in_shape)
+
+
+def hsv_to_rgb(hsv):
+ """
+ Convert HSV values to RGB.
+
+ Parameters
+ ----------
+ hsv : (..., 3) array-like
+ All values assumed to be in range [0, 1]
+
+ Returns
+ -------
+ (..., 3) `~numpy.ndarray`
+ Colors converted to RGB values in range [0, 1]
+ """
+ hsv = np.asarray(hsv)
+
+ # check length of the last dimension, should be _some_ sort of rgb
+ if hsv.shape[-1] != 3:
+ raise ValueError("Last dimension of input array must be 3; "
+ f"shape {hsv.shape} was found.")
+
+ in_shape = hsv.shape
+ hsv = np.array(
+ hsv, copy=False,
+ dtype=np.promote_types(hsv.dtype, np.float32), # Don't work on ints.
+ ndmin=2, # In case input was 1D.
+ )
+
+ h = hsv[..., 0]
+ s = hsv[..., 1]
+ v = hsv[..., 2]
+
+ r = np.empty_like(h)
+ g = np.empty_like(h)
+ b = np.empty_like(h)
+
+ i = (h * 6.0).astype(int)
+ f = (h * 6.0) - i
+ p = v * (1.0 - s)
+ q = v * (1.0 - s * f)
+ t = v * (1.0 - s * (1.0 - f))
+
+ idx = i % 6 == 0
+ r[idx] = v[idx]
+ g[idx] = t[idx]
+ b[idx] = p[idx]
+
+ idx = i == 1
+ r[idx] = q[idx]
+ g[idx] = v[idx]
+ b[idx] = p[idx]
+
+ idx = i == 2
+ r[idx] = p[idx]
+ g[idx] = v[idx]
+ b[idx] = t[idx]
+
+ idx = i == 3
+ r[idx] = p[idx]
+ g[idx] = q[idx]
+ b[idx] = v[idx]
+
+ idx = i == 4
+ r[idx] = t[idx]
+ g[idx] = p[idx]
+ b[idx] = v[idx]
+
+ idx = i == 5
+ r[idx] = v[idx]
+ g[idx] = p[idx]
+ b[idx] = q[idx]
+
+ idx = s == 0
+ r[idx] = v[idx]
+ g[idx] = v[idx]
+ b[idx] = v[idx]
+
+ rgb = np.stack([r, g, b], axis=-1)
+
+ return rgb.reshape(in_shape)
+
+
+def _vector_magnitude(arr):
+ # things that don't work here:
+ # * np.linalg.norm: drops mask from ma.array
+ # * np.sum: drops mask from ma.array unless entire vector is masked
+ sum_sq = 0
+ for i in range(arr.shape[-1]):
+ sum_sq += arr[..., i, np.newaxis] ** 2
+ return np.sqrt(sum_sq)
+
+
+class LightSource:
+ """
+ Create a light source coming from the specified azimuth and elevation.
+ Angles are in degrees, with the azimuth measured
+ clockwise from north and elevation up from the zero plane of the surface.
+
+ `shade` is used to produce "shaded" RGB values for a data array.
+ `shade_rgb` can be used to combine an RGB image with an elevation map.
+ `hillshade` produces an illumination map of a surface.
+ """
+
+ def __init__(self, azdeg=315, altdeg=45, hsv_min_val=0, hsv_max_val=1,
+ hsv_min_sat=1, hsv_max_sat=0):
+ """
+ Specify the azimuth (measured clockwise from south) and altitude
+ (measured up from the plane of the surface) of the light source
+ in degrees.
+
+ Parameters
+ ----------
+ azdeg : float, default: 315 degrees (from the northwest)
+ The azimuth (0-360, degrees clockwise from North) of the light
+ source.
+ altdeg : float, default: 45 degrees
+ The altitude (0-90, degrees up from horizontal) of the light
+ source.
+ hsv_min_val : number, default: 0
+ The minimum value ("v" in "hsv") that the *intensity* map can shift the
+ output image to.
+ hsv_max_val : number, default: 1
+ The maximum value ("v" in "hsv") that the *intensity* map can shift the
+ output image to.
+ hsv_min_sat : number, default: 1
+ The minimum saturation value that the *intensity* map can shift the output
+ image to.
+ hsv_max_sat : number, default: 0
+ The maximum saturation value that the *intensity* map can shift the output
+ image to.
+
+ Notes
+ -----
+ For backwards compatibility, the parameters *hsv_min_val*,
+ *hsv_max_val*, *hsv_min_sat*, and *hsv_max_sat* may be supplied at
+ initialization as well. However, these parameters will only be used if
+ "blend_mode='hsv'" is passed into `shade` or `shade_rgb`.
+ See the documentation for `blend_hsv` for more details.
+ """
+ self.azdeg = azdeg
+ self.altdeg = altdeg
+ self.hsv_min_val = hsv_min_val
+ self.hsv_max_val = hsv_max_val
+ self.hsv_min_sat = hsv_min_sat
+ self.hsv_max_sat = hsv_max_sat
+
+ @property
+ def direction(self):
+ """The unit vector direction towards the light source."""
+ # Azimuth is in degrees clockwise from North. Convert to radians
+ # counterclockwise from East (mathematical notation).
+ az = np.radians(90 - self.azdeg)
+ alt = np.radians(self.altdeg)
+ return np.array([
+ np.cos(az) * np.cos(alt),
+ np.sin(az) * np.cos(alt),
+ np.sin(alt)
+ ])
+
+ def hillshade(self, elevation, vert_exag=1, dx=1, dy=1, fraction=1.):
+ """
+ Calculate the illumination intensity for a surface using the defined
+ azimuth and elevation for the light source.
+
+ This computes the normal vectors for the surface, and then passes them
+ on to `shade_normals`
+
+ Parameters
+ ----------
+ elevation : 2D array-like
+ The height values used to generate an illumination map
+ vert_exag : number, optional
+ The amount to exaggerate the elevation values by when calculating
+ illumination. This can be used either to correct for differences in
+ units between the x-y coordinate system and the elevation
+ coordinate system (e.g. decimal degrees vs. meters) or to
+ exaggerate or de-emphasize topographic effects.
+ dx : number, optional
+ The x-spacing (columns) of the input *elevation* grid.
+ dy : number, optional
+ The y-spacing (rows) of the input *elevation* grid.
+ fraction : number, optional
+ Increases or decreases the contrast of the hillshade. Values
+ greater than one will cause intermediate values to move closer to
+ full illumination or shadow (and clipping any values that move
+ beyond 0 or 1). Note that this is not visually or mathematically
+ the same as vertical exaggeration.
+
+ Returns
+ -------
+ `~numpy.ndarray`
+ A 2D array of illumination values between 0-1, where 0 is
+ completely in shadow and 1 is completely illuminated.
+ """
+
+ # Because most image and raster GIS data has the first row in the array
+ # as the "top" of the image, dy is implicitly negative. This is
+ # consistent to what `imshow` assumes, as well.
+ dy = -dy
+
+ # compute the normal vectors from the partial derivatives
+ e_dy, e_dx = np.gradient(vert_exag * elevation, dy, dx)
+
+ # .view is to keep subclasses
+ normal = np.empty(elevation.shape + (3,)).view(type(elevation))
+ normal[..., 0] = -e_dx
+ normal[..., 1] = -e_dy
+ normal[..., 2] = 1
+ normal /= _vector_magnitude(normal)
+
+ return self.shade_normals(normal, fraction)
+
+ def shade_normals(self, normals, fraction=1.):
+ """
+ Calculate the illumination intensity for the normal vectors of a
+ surface using the defined azimuth and elevation for the light source.
+
+ Imagine an artificial sun placed at infinity in some azimuth and
+ elevation position illuminating our surface. The parts of the surface
+ that slope toward the sun should brighten while those sides facing away
+ should become darker.
+
+ Parameters
+ ----------
+ fraction : number, optional
+ Increases or decreases the contrast of the hillshade. Values
+ greater than one will cause intermediate values to move closer to
+ full illumination or shadow (and clipping any values that move
+ beyond 0 or 1). Note that this is not visually or mathematically
+ the same as vertical exaggeration.
+
+ Returns
+ -------
+ `~numpy.ndarray`
+ A 2D array of illumination values between 0-1, where 0 is
+ completely in shadow and 1 is completely illuminated.
+ """
+
+ intensity = normals.dot(self.direction)
+
+ # Apply contrast stretch
+ imin, imax = intensity.min(), intensity.max()
+ intensity *= fraction
+
+ # Rescale to 0-1, keeping range before contrast stretch
+ # If constant slope, keep relative scaling (i.e. flat should be 0.5,
+ # fully occluded 0, etc.)
+ if (imax - imin) > 1e-6:
+ # Strictly speaking, this is incorrect. Negative values should be
+ # clipped to 0 because they're fully occluded. However, rescaling
+ # in this manner is consistent with the previous implementation and
+ # visually appears better than a "hard" clip.
+ intensity -= imin
+ intensity /= (imax - imin)
+ intensity = np.clip(intensity, 0, 1)
+
+ return intensity
+
+ def shade(self, data, cmap, norm=None, blend_mode='overlay', vmin=None,
+ vmax=None, vert_exag=1, dx=1, dy=1, fraction=1, **kwargs):
+ """
+ Combine colormapped data values with an illumination intensity map
+ (a.k.a. "hillshade") of the values.
+
+ Parameters
+ ----------
+ data : 2D array-like
+ The height values used to generate a shaded map.
+ cmap : `~matplotlib.colors.Colormap`
+ The colormap used to color the *data* array. Note that this must be
+ a `~matplotlib.colors.Colormap` instance. For example, rather than
+ passing in ``cmap='gist_earth'``, use
+ ``cmap=plt.get_cmap('gist_earth')`` instead.
+ norm : `~matplotlib.colors.Normalize` instance, optional
+ The normalization used to scale values before colormapping. If
+ None, the input will be linearly scaled between its min and max.
+ blend_mode : {'hsv', 'overlay', 'soft'} or callable, optional
+ The type of blending used to combine the colormapped data
+ values with the illumination intensity. Default is
+ "overlay". Note that for most topographic surfaces,
+ "overlay" or "soft" appear more visually realistic. If a
+ user-defined function is supplied, it is expected to
+ combine an (M, N, 3) RGB array of floats (ranging 0 to 1) with
+ an (M, N, 1) hillshade array (also 0 to 1). (Call signature
+ ``func(rgb, illum, **kwargs)``) Additional kwargs supplied
+ to this function will be passed on to the *blend_mode*
+ function.
+ vmin : float or None, optional
+ The minimum value used in colormapping *data*. If *None* the
+ minimum value in *data* is used. If *norm* is specified, then this
+ argument will be ignored.
+ vmax : float or None, optional
+ The maximum value used in colormapping *data*. If *None* the
+ maximum value in *data* is used. If *norm* is specified, then this
+ argument will be ignored.
+ vert_exag : number, optional
+ The amount to exaggerate the elevation values by when calculating
+ illumination. This can be used either to correct for differences in
+ units between the x-y coordinate system and the elevation
+ coordinate system (e.g. decimal degrees vs. meters) or to
+ exaggerate or de-emphasize topography.
+ dx : number, optional
+ The x-spacing (columns) of the input *elevation* grid.
+ dy : number, optional
+ The y-spacing (rows) of the input *elevation* grid.
+ fraction : number, optional
+ Increases or decreases the contrast of the hillshade. Values
+ greater than one will cause intermediate values to move closer to
+ full illumination or shadow (and clipping any values that move
+ beyond 0 or 1). Note that this is not visually or mathematically
+ the same as vertical exaggeration.
+ **kwargs
+ Additional kwargs are passed on to the *blend_mode* function.
+
+ Returns
+ -------
+ `~numpy.ndarray`
+ An (M, N, 4) array of floats ranging between 0-1.
+ """
+ if vmin is None:
+ vmin = data.min()
+ if vmax is None:
+ vmax = data.max()
+ if norm is None:
+ norm = Normalize(vmin=vmin, vmax=vmax)
+
+ rgb0 = cmap(norm(data))
+ rgb1 = self.shade_rgb(rgb0, elevation=data, blend_mode=blend_mode,
+ vert_exag=vert_exag, dx=dx, dy=dy,
+ fraction=fraction, **kwargs)
+ # Don't overwrite the alpha channel, if present.
+ rgb0[..., :3] = rgb1[..., :3]
+ return rgb0
+
+ def shade_rgb(self, rgb, elevation, fraction=1., blend_mode='hsv',
+ vert_exag=1, dx=1, dy=1, **kwargs):
+ """
+ Use this light source to adjust the colors of the *rgb* input array to
+ give the impression of a shaded relief map with the given *elevation*.
+
+ Parameters
+ ----------
+ rgb : array-like
+ An (M, N, 3) RGB array, assumed to be in the range of 0 to 1.
+ elevation : array-like
+ An (M, N) array of the height values used to generate a shaded map.
+ fraction : number
+ Increases or decreases the contrast of the hillshade. Values
+ greater than one will cause intermediate values to move closer to
+ full illumination or shadow (and clipping any values that move
+ beyond 0 or 1). Note that this is not visually or mathematically
+ the same as vertical exaggeration.
+ blend_mode : {'hsv', 'overlay', 'soft'} or callable, optional
+ The type of blending used to combine the colormapped data values
+ with the illumination intensity. For backwards compatibility, this
+ defaults to "hsv". Note that for most topographic surfaces,
+ "overlay" or "soft" appear more visually realistic. If a
+ user-defined function is supplied, it is expected to combine an
+ (M, N, 3) RGB array of floats (ranging 0 to 1) with an (M, N, 1)
+ hillshade array (also 0 to 1). (Call signature
+ ``func(rgb, illum, **kwargs)``)
+ Additional kwargs supplied to this function will be passed on to
+ the *blend_mode* function.
+ vert_exag : number, optional
+ The amount to exaggerate the elevation values by when calculating
+ illumination. This can be used either to correct for differences in
+ units between the x-y coordinate system and the elevation
+ coordinate system (e.g. decimal degrees vs. meters) or to
+ exaggerate or de-emphasize topography.
+ dx : number, optional
+ The x-spacing (columns) of the input *elevation* grid.
+ dy : number, optional
+ The y-spacing (rows) of the input *elevation* grid.
+ **kwargs
+ Additional kwargs are passed on to the *blend_mode* function.
+
+ Returns
+ -------
+ `~numpy.ndarray`
+ An (m, n, 3) array of floats ranging between 0-1.
+ """
+ # Calculate the "hillshade" intensity.
+ intensity = self.hillshade(elevation, vert_exag, dx, dy, fraction)
+ intensity = intensity[..., np.newaxis]
+
+ # Blend the hillshade and rgb data using the specified mode
+ lookup = {
+ 'hsv': self.blend_hsv,
+ 'soft': self.blend_soft_light,
+ 'overlay': self.blend_overlay,
+ }
+ if blend_mode in lookup:
+ blend = lookup[blend_mode](rgb, intensity, **kwargs)
+ else:
+ try:
+ blend = blend_mode(rgb, intensity, **kwargs)
+ except TypeError as err:
+ raise ValueError('"blend_mode" must be callable or one of '
+ f'{lookup.keys}') from err
+
+ # Only apply result where hillshade intensity isn't masked
+ if np.ma.is_masked(intensity):
+ mask = intensity.mask[..., 0]
+ for i in range(3):
+ blend[..., i][mask] = rgb[..., i][mask]
+
+ return blend
+
+ def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None,
+ hsv_min_val=None, hsv_min_sat=None):
+ """
+ Take the input data array, convert to HSV values in the given colormap,
+ then adjust those color values to give the impression of a shaded
+ relief map with a specified light source. RGBA values are returned,
+ which can then be used to plot the shaded image with imshow.
+
+ The color of the resulting image will be darkened by moving the (s, v)
+ values (in HSV colorspace) toward (hsv_min_sat, hsv_min_val) in the
+ shaded regions, or lightened by sliding (s, v) toward (hsv_max_sat,
+ hsv_max_val) in regions that are illuminated. The default extremes are
+ chose so that completely shaded points are nearly black (s = 1, v = 0)
+ and completely illuminated points are nearly white (s = 0, v = 1).
+
+ Parameters
+ ----------
+ rgb : `~numpy.ndarray`
+ An (M, N, 3) RGB array of floats ranging from 0 to 1 (color image).
+ intensity : `~numpy.ndarray`
+ An (M, N, 1) array of floats ranging from 0 to 1 (grayscale image).
+ hsv_max_sat : number, optional
+ The maximum saturation value that the *intensity* map can shift the output
+ image to. If not provided, use the value provided upon initialization.
+ hsv_min_sat : number, optional
+ The minimum saturation value that the *intensity* map can shift the output
+ image to. If not provided, use the value provided upon initialization.
+ hsv_max_val : number, optional
+ The maximum value ("v" in "hsv") that the *intensity* map can shift the
+ output image to. If not provided, use the value provided upon
+ initialization.
+ hsv_min_val : number, optional
+ The minimum value ("v" in "hsv") that the *intensity* map can shift the
+ output image to. If not provided, use the value provided upon
+ initialization.
+
+ Returns
+ -------
+ `~numpy.ndarray`
+ An (M, N, 3) RGB array representing the combined images.
+ """
+ # Backward compatibility...
+ if hsv_max_sat is None:
+ hsv_max_sat = self.hsv_max_sat
+ if hsv_max_val is None:
+ hsv_max_val = self.hsv_max_val
+ if hsv_min_sat is None:
+ hsv_min_sat = self.hsv_min_sat
+ if hsv_min_val is None:
+ hsv_min_val = self.hsv_min_val
+
+ # Expects a 2D intensity array scaled between -1 to 1...
+ intensity = intensity[..., 0]
+ intensity = 2 * intensity - 1
+
+ # Convert to rgb, then rgb to hsv
+ hsv = rgb_to_hsv(rgb[:, :, 0:3])
+ hue, sat, val = np.moveaxis(hsv, -1, 0)
+
+ # Modify hsv values (in place) to simulate illumination.
+ # putmask(A, mask, B) <=> A[mask] = B[mask]
+ np.putmask(sat, (np.abs(sat) > 1.e-10) & (intensity > 0),
+ (1 - intensity) * sat + intensity * hsv_max_sat)
+ np.putmask(sat, (np.abs(sat) > 1.e-10) & (intensity < 0),
+ (1 + intensity) * sat - intensity * hsv_min_sat)
+ np.putmask(val, intensity > 0,
+ (1 - intensity) * val + intensity * hsv_max_val)
+ np.putmask(val, intensity < 0,
+ (1 + intensity) * val - intensity * hsv_min_val)
+ np.clip(hsv[:, :, 1:], 0, 1, out=hsv[:, :, 1:])
+
+ # Convert modified hsv back to rgb.
+ return hsv_to_rgb(hsv)
+
+ def blend_soft_light(self, rgb, intensity):
+ """
+ Combine an RGB image with an intensity map using "soft light" blending,
+ using the "pegtop" formula.
+
+ Parameters
+ ----------
+ rgb : `~numpy.ndarray`
+ An (M, N, 3) RGB array of floats ranging from 0 to 1 (color image).
+ intensity : `~numpy.ndarray`
+ An (M, N, 1) array of floats ranging from 0 to 1 (grayscale image).
+
+ Returns
+ -------
+ `~numpy.ndarray`
+ An (M, N, 3) RGB array representing the combined images.
+ """
+ return 2 * intensity * rgb + (1 - 2 * intensity) * rgb**2
+
+ def blend_overlay(self, rgb, intensity):
+ """
+ Combine an RGB image with an intensity map using "overlay" blending.
+
+ Parameters
+ ----------
+ rgb : `~numpy.ndarray`
+ An (M, N, 3) RGB array of floats ranging from 0 to 1 (color image).
+ intensity : `~numpy.ndarray`
+ An (M, N, 1) array of floats ranging from 0 to 1 (grayscale image).
+
+ Returns
+ -------
+ ndarray
+ An (M, N, 3) RGB array representing the combined images.
+ """
+ low = 2 * intensity * rgb
+ high = 1 - 2 * (1 - intensity) * (1 - rgb)
+ return np.where(rgb <= 0.5, low, high)
+
+
+def from_levels_and_colors(levels, colors, extend='neither'):
+ """
+ A helper routine to generate a cmap and a norm instance which
+ behave similar to contourf's levels and colors arguments.
+
+ Parameters
+ ----------
+ levels : sequence of numbers
+ The quantization levels used to construct the `BoundaryNorm`.
+ Value ``v`` is quantized to level ``i`` if ``lev[i] <= v < lev[i+1]``.
+ colors : sequence of colors
+ The fill color to use for each level. If *extend* is "neither" there
+ must be ``n_level - 1`` colors. For an *extend* of "min" or "max" add
+ one extra color, and for an *extend* of "both" add two colors.
+ extend : {'neither', 'min', 'max', 'both'}, optional
+ The behaviour when a value falls out of range of the given levels.
+ See `~.Axes.contourf` for details.
+
+ Returns
+ -------
+ cmap : `~matplotlib.colors.Colormap`
+ norm : `~matplotlib.colors.Normalize`
+ """
+ slice_map = {
+ 'both': slice(1, -1),
+ 'min': slice(1, None),
+ 'max': slice(0, -1),
+ 'neither': slice(0, None),
+ }
+ _api.check_in_list(slice_map, extend=extend)
+ color_slice = slice_map[extend]
+
+ n_data_colors = len(levels) - 1
+ n_expected = n_data_colors + color_slice.start - (color_slice.stop or 0)
+ if len(colors) != n_expected:
+ raise ValueError(
+ f'With extend == {extend!r} and {len(levels)} levels, '
+ f'expected {n_expected} colors, but got {len(colors)}')
+
+ cmap = ListedColormap(colors[color_slice], N=n_data_colors)
+
+ if extend in ['min', 'both']:
+ cmap.set_under(colors[0])
+ else:
+ cmap.set_under('none')
+
+ if extend in ['max', 'both']:
+ cmap.set_over(colors[-1])
+ else:
+ cmap.set_over('none')
+
+ cmap.colorbar_extend = extend
+
+ norm = BoundaryNorm(levels, ncolors=n_data_colors)
+ return cmap, norm
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..6941e3d5d176c39525c27e9a60d6926a18e31e8b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.pyi
@@ -0,0 +1,449 @@
+from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
+from matplotlib import cbook, scale
+import re
+
+from typing import Any, Literal, overload
+from .typing import ColorType
+
+import numpy as np
+from numpy.typing import ArrayLike
+
+# Explicitly export colors dictionaries which are imported in the impl
+BASE_COLORS: dict[str, ColorType]
+CSS4_COLORS: dict[str, ColorType]
+TABLEAU_COLORS: dict[str, ColorType]
+XKCD_COLORS: dict[str, ColorType]
+
+class _ColorMapping(dict[str, ColorType]):
+ cache: dict[tuple[ColorType, float | None], tuple[float, float, float, float]]
+ def __init__(self, mapping) -> None: ...
+ def __setitem__(self, key, value) -> None: ...
+ def __delitem__(self, key) -> None: ...
+
+def get_named_colors_mapping() -> _ColorMapping: ...
+
+class ColorSequenceRegistry(Mapping):
+ def __init__(self) -> None: ...
+ def __getitem__(self, item: str) -> list[ColorType]: ...
+ def __iter__(self) -> Iterator[str]: ...
+ def __len__(self) -> int: ...
+ def register(self, name: str, color_list: Iterable[ColorType]) -> None: ...
+ def unregister(self, name: str) -> None: ...
+
+_color_sequences: ColorSequenceRegistry = ...
+
+def is_color_like(c: Any) -> bool: ...
+def same_color(c1: ColorType, c2: ColorType) -> bool: ...
+def to_rgba(
+ c: ColorType, alpha: float | None = ...
+) -> tuple[float, float, float, float]: ...
+def to_rgba_array(
+ c: ColorType | ArrayLike, alpha: float | ArrayLike | None = ...
+) -> np.ndarray: ...
+def to_rgb(c: ColorType) -> tuple[float, float, float]: ...
+def to_hex(c: ColorType, keep_alpha: bool = ...) -> str: ...
+
+cnames: dict[str, ColorType]
+hexColorPattern: re.Pattern
+rgb2hex = to_hex
+hex2color = to_rgb
+
+class ColorConverter:
+ colors: _ColorMapping
+ cache: dict[tuple[ColorType, float | None], tuple[float, float, float, float]]
+ @staticmethod
+ def to_rgb(c: ColorType) -> tuple[float, float, float]: ...
+ @staticmethod
+ def to_rgba(
+ c: ColorType, alpha: float | None = ...
+ ) -> tuple[float, float, float, float]: ...
+ @staticmethod
+ def to_rgba_array(
+ c: ColorType | ArrayLike, alpha: float | ArrayLike | None = ...
+ ) -> np.ndarray: ...
+
+colorConverter: ColorConverter
+
+class Colormap:
+ name: str
+ N: int
+ colorbar_extend: bool
+ def __init__(self, name: str, N: int = ...) -> None: ...
+ @overload
+ def __call__(
+ self, X: Sequence[float] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ...
+ ) -> np.ndarray: ...
+ @overload
+ def __call__(
+ self, X: float, alpha: float | None = ..., bytes: bool = ...
+ ) -> tuple[float, float, float, float]: ...
+ @overload
+ def __call__(
+ self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ...
+ ) -> tuple[float, float, float, float] | np.ndarray: ...
+ def __copy__(self) -> Colormap: ...
+ def __eq__(self, other: object) -> bool: ...
+ def get_bad(self) -> np.ndarray: ...
+ def set_bad(self, color: ColorType = ..., alpha: float | None = ...) -> None: ...
+ def get_under(self) -> np.ndarray: ...
+ def set_under(self, color: ColorType = ..., alpha: float | None = ...) -> None: ...
+ def get_over(self) -> np.ndarray: ...
+ def set_over(self, color: ColorType = ..., alpha: float | None = ...) -> None: ...
+ def set_extremes(
+ self,
+ *,
+ bad: ColorType | None = ...,
+ under: ColorType | None = ...,
+ over: ColorType | None = ...
+ ) -> None: ...
+ def with_extremes(
+ self,
+ *,
+ bad: ColorType | None = ...,
+ under: ColorType | None = ...,
+ over: ColorType | None = ...
+ ) -> Colormap: ...
+ def is_gray(self) -> bool: ...
+ def resampled(self, lutsize: int) -> Colormap: ...
+ def reversed(self, name: str | None = ...) -> Colormap: ...
+ def _repr_html_(self) -> str: ...
+ def _repr_png_(self) -> bytes: ...
+ def copy(self) -> Colormap: ...
+
+class LinearSegmentedColormap(Colormap):
+ monochrome: bool
+ def __init__(
+ self,
+ name: str,
+ segmentdata: dict[
+ Literal["red", "green", "blue", "alpha"], Sequence[tuple[float, ...]]
+ ],
+ N: int = ...,
+ gamma: float = ...,
+ ) -> None: ...
+ def set_gamma(self, gamma: float) -> None: ...
+ @staticmethod
+ def from_list(
+ name: str, colors: ArrayLike | Sequence[tuple[float, ColorType]], N: int = ..., gamma: float = ...
+ ) -> LinearSegmentedColormap: ...
+ def resampled(self, lutsize: int) -> LinearSegmentedColormap: ...
+ def reversed(self, name: str | None = ...) -> LinearSegmentedColormap: ...
+
+class ListedColormap(Colormap):
+ monochrome: bool
+ colors: ArrayLike | ColorType
+ def __init__(
+ self, colors: ArrayLike | ColorType, name: str = ..., N: int | None = ...
+ ) -> None: ...
+ def resampled(self, lutsize: int) -> ListedColormap: ...
+ def reversed(self, name: str | None = ...) -> ListedColormap: ...
+
+class MultivarColormap:
+ name: str
+ n_variates: int
+ def __init__(self, colormaps: list[Colormap], combination_mode: Literal['sRGB_add', 'sRGB_sub'], name: str = ...) -> None: ...
+ @overload
+ def __call__(
+ self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ...
+ ) -> np.ndarray: ...
+ @overload
+ def __call__(
+ self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ..., clip: bool = ...
+ ) -> tuple[float, float, float, float]: ...
+ @overload
+ def __call__(
+ self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ...
+ ) -> tuple[float, float, float, float] | np.ndarray: ...
+ def copy(self) -> MultivarColormap: ...
+ def __copy__(self) -> MultivarColormap: ...
+ def __eq__(self, other: Any) -> bool: ...
+ def __getitem__(self, item: int) -> Colormap: ...
+ def __iter__(self) -> Iterator[Colormap]: ...
+ def __len__(self) -> int: ...
+ def get_bad(self) -> np.ndarray: ...
+ def resampled(self, lutshape: Sequence[int | None]) -> MultivarColormap: ...
+ def with_extremes(
+ self,
+ *,
+ bad: ColorType | None = ...,
+ under: Sequence[ColorType] | None = ...,
+ over: Sequence[ColorType] | None = ...
+ ) -> MultivarColormap: ...
+ @property
+ def combination_mode(self) -> str: ...
+ def _repr_html_(self) -> str: ...
+ def _repr_png_(self) -> bytes: ...
+
+class BivarColormap:
+ name: str
+ N: int
+ M: int
+ n_variates: int
+ def __init__(
+ self, N: int = ..., M: int | None = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ...,
+ origin: Sequence[float] = ..., name: str = ...
+ ) -> None: ...
+ @overload
+ def __call__(
+ self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ...
+ ) -> np.ndarray: ...
+ @overload
+ def __call__(
+ self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ...
+ ) -> tuple[float, float, float, float]: ...
+ @overload
+ def __call__(
+ self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ...
+ ) -> tuple[float, float, float, float] | np.ndarray: ...
+ @property
+ def lut(self) -> np.ndarray: ...
+ @property
+ def shape(self) -> str: ...
+ @property
+ def origin(self) -> tuple[float, float]: ...
+ def copy(self) -> BivarColormap: ...
+ def __copy__(self) -> BivarColormap: ...
+ def __getitem__(self, item: int) -> Colormap: ...
+ def __eq__(self, other: Any) -> bool: ...
+ def get_bad(self) -> np.ndarray: ...
+ def get_outside(self) -> np.ndarray: ...
+ def resampled(self, lutshape: Sequence[int | None], transposed: bool = ...) -> BivarColormap: ...
+ def transposed(self) -> BivarColormap: ...
+ def reversed(self, axis_0: bool = ..., axis_1: bool = ...) -> BivarColormap: ...
+ def with_extremes(
+ self,
+ *,
+ bad: ColorType | None = ...,
+ outside: ColorType | None = ...,
+ shape: str | None = ...,
+ origin: None | Sequence[float] = ...,
+ ) -> MultivarColormap: ...
+ def _repr_html_(self) -> str: ...
+ def _repr_png_(self) -> bytes: ...
+
+class SegmentedBivarColormap(BivarColormap):
+ def __init__(
+ self, patch: np.ndarray, N: int = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ...,
+ origin: Sequence[float] = ..., name: str = ...
+ ) -> None: ...
+
+class BivarColormapFromImage(BivarColormap):
+ def __init__(
+ self, lut: np.ndarray, shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ...,
+ origin: Sequence[float] = ..., name: str = ...
+ ) -> None: ...
+
+class Normalize:
+ callbacks: cbook.CallbackRegistry
+ def __init__(
+ self, vmin: float | None = ..., vmax: float | None = ..., clip: bool = ...
+ ) -> None: ...
+ @property
+ def vmin(self) -> float | None: ...
+ @vmin.setter
+ def vmin(self, value: float | None) -> None: ...
+ @property
+ def vmax(self) -> float | None: ...
+ @vmax.setter
+ def vmax(self, value: float | None) -> None: ...
+ @property
+ def clip(self) -> bool: ...
+ @clip.setter
+ def clip(self, value: bool) -> None: ...
+ @staticmethod
+ def process_value(value: ArrayLike) -> tuple[np.ma.MaskedArray, bool]: ...
+ @overload
+ def __call__(self, value: float, clip: bool | None = ...) -> float: ...
+ @overload
+ def __call__(self, value: np.ndarray, clip: bool | None = ...) -> np.ma.MaskedArray: ...
+ @overload
+ def __call__(self, value: ArrayLike, clip: bool | None = ...) -> ArrayLike: ...
+ @overload
+ def inverse(self, value: float) -> float: ...
+ @overload
+ def inverse(self, value: np.ndarray) -> np.ma.MaskedArray: ...
+ @overload
+ def inverse(self, value: ArrayLike) -> ArrayLike: ...
+ def autoscale(self, A: ArrayLike) -> None: ...
+ def autoscale_None(self, A: ArrayLike) -> None: ...
+ def scaled(self) -> bool: ...
+
+class TwoSlopeNorm(Normalize):
+ def __init__(
+ self, vcenter: float, vmin: float | None = ..., vmax: float | None = ...
+ ) -> None: ...
+ @property
+ def vcenter(self) -> float: ...
+ @vcenter.setter
+ def vcenter(self, value: float) -> None: ...
+ def autoscale_None(self, A: ArrayLike) -> None: ...
+
+class CenteredNorm(Normalize):
+ def __init__(
+ self, vcenter: float = ..., halfrange: float | None = ..., clip: bool = ...
+ ) -> None: ...
+ @property
+ def vcenter(self) -> float: ...
+ @vcenter.setter
+ def vcenter(self, vcenter: float) -> None: ...
+ @property
+ def halfrange(self) -> float: ...
+ @halfrange.setter
+ def halfrange(self, halfrange: float) -> None: ...
+
+@overload
+def make_norm_from_scale(
+ scale_cls: type[scale.ScaleBase],
+ base_norm_cls: type[Normalize],
+ *,
+ init: Callable | None = ...
+) -> type[Normalize]: ...
+@overload
+def make_norm_from_scale(
+ scale_cls: type[scale.ScaleBase],
+ base_norm_cls: None = ...,
+ *,
+ init: Callable | None = ...
+) -> Callable[[type[Normalize]], type[Normalize]]: ...
+
+class FuncNorm(Normalize):
+ def __init__(
+ self,
+ functions: tuple[Callable, Callable],
+ vmin: float | None = ...,
+ vmax: float | None = ...,
+ clip: bool = ...,
+ ) -> None: ...
+class LogNorm(Normalize): ...
+
+class SymLogNorm(Normalize):
+ def __init__(
+ self,
+ linthresh: float,
+ linscale: float = ...,
+ vmin: float | None = ...,
+ vmax: float | None = ...,
+ clip: bool = ...,
+ *,
+ base: float = ...,
+ ) -> None: ...
+ @property
+ def linthresh(self) -> float: ...
+ @linthresh.setter
+ def linthresh(self, value: float) -> None: ...
+
+class AsinhNorm(Normalize):
+ def __init__(
+ self,
+ linear_width: float = ...,
+ vmin: float | None = ...,
+ vmax: float | None = ...,
+ clip: bool = ...,
+ ) -> None: ...
+ @property
+ def linear_width(self) -> float: ...
+ @linear_width.setter
+ def linear_width(self, value: float) -> None: ...
+
+class PowerNorm(Normalize):
+ gamma: float
+ def __init__(
+ self,
+ gamma: float,
+ vmin: float | None = ...,
+ vmax: float | None = ...,
+ clip: bool = ...,
+ ) -> None: ...
+
+class BoundaryNorm(Normalize):
+ boundaries: np.ndarray
+ N: int
+ Ncmap: int
+ extend: Literal["neither", "both", "min", "max"]
+ def __init__(
+ self,
+ boundaries: ArrayLike,
+ ncolors: int,
+ clip: bool = ...,
+ *,
+ extend: Literal["neither", "both", "min", "max"] = ...
+ ) -> None: ...
+
+class NoNorm(Normalize): ...
+
+def rgb_to_hsv(arr: ArrayLike) -> np.ndarray: ...
+def hsv_to_rgb(hsv: ArrayLike) -> np.ndarray: ...
+
+class LightSource:
+ azdeg: float
+ altdeg: float
+ hsv_min_val: float
+ hsv_max_val: float
+ hsv_min_sat: float
+ hsv_max_sat: float
+ def __init__(
+ self,
+ azdeg: float = ...,
+ altdeg: float = ...,
+ hsv_min_val: float = ...,
+ hsv_max_val: float = ...,
+ hsv_min_sat: float = ...,
+ hsv_max_sat: float = ...,
+ ) -> None: ...
+ @property
+ def direction(self) -> np.ndarray: ...
+ def hillshade(
+ self,
+ elevation: ArrayLike,
+ vert_exag: float = ...,
+ dx: float = ...,
+ dy: float = ...,
+ fraction: float = ...,
+ ) -> np.ndarray: ...
+ def shade_normals(
+ self, normals: np.ndarray, fraction: float = ...
+ ) -> np.ndarray: ...
+ def shade(
+ self,
+ data: ArrayLike,
+ cmap: Colormap,
+ norm: Normalize | None = ...,
+ blend_mode: Literal["hsv", "overlay", "soft"] | Callable = ...,
+ vmin: float | None = ...,
+ vmax: float | None = ...,
+ vert_exag: float = ...,
+ dx: float = ...,
+ dy: float = ...,
+ fraction: float = ...,
+ **kwargs
+ ) -> np.ndarray: ...
+ def shade_rgb(
+ self,
+ rgb: ArrayLike,
+ elevation: ArrayLike,
+ fraction: float = ...,
+ blend_mode: Literal["hsv", "overlay", "soft"] | Callable = ...,
+ vert_exag: float = ...,
+ dx: float = ...,
+ dy: float = ...,
+ **kwargs
+ ) -> np.ndarray: ...
+ def blend_hsv(
+ self,
+ rgb: ArrayLike,
+ intensity: ArrayLike,
+ hsv_max_sat: float | None = ...,
+ hsv_max_val: float | None = ...,
+ hsv_min_val: float | None = ...,
+ hsv_min_sat: float | None = ...,
+ ) -> ArrayLike: ...
+ def blend_soft_light(
+ self, rgb: np.ndarray, intensity: np.ndarray
+ ) -> np.ndarray: ...
+ def blend_overlay(self, rgb: np.ndarray, intensity: np.ndarray) -> np.ndarray: ...
+
+def from_levels_and_colors(
+ levels: Sequence[float],
+ colors: Sequence[ColorType],
+ extend: Literal["neither", "min", "max", "both"] = ...,
+) -> tuple[ListedColormap, BoundaryNorm]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6dd43724f34b219a7d8864542af4dbec81d89ff
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.py
@@ -0,0 +1,141 @@
+from matplotlib import cbook
+from matplotlib.artist import Artist
+
+
+class Container(tuple):
+ """
+ Base class for containers.
+
+ Containers are classes that collect semantically related Artists such as
+ the bars of a bar plot.
+ """
+
+ def __repr__(self):
+ return f"<{type(self).__name__} object of {len(self)} artists>"
+
+ def __new__(cls, *args, **kwargs):
+ return tuple.__new__(cls, args[0])
+
+ def __init__(self, kl, label=None):
+ self._callbacks = cbook.CallbackRegistry(signals=["pchanged"])
+ self._remove_method = None
+ self._label = str(label) if label is not None else None
+
+ def remove(self):
+ for c in cbook.flatten(
+ self, scalarp=lambda x: isinstance(x, Artist)):
+ if c is not None:
+ c.remove()
+ if self._remove_method:
+ self._remove_method(self)
+
+ def get_children(self):
+ return [child for child in cbook.flatten(self) if child is not None]
+
+ get_label = Artist.get_label
+ set_label = Artist.set_label
+ add_callback = Artist.add_callback
+ remove_callback = Artist.remove_callback
+ pchanged = Artist.pchanged
+
+
+class BarContainer(Container):
+ """
+ Container for the artists of bar plots (e.g. created by `.Axes.bar`).
+
+ The container can be treated as a tuple of the *patches* themselves.
+ Additionally, you can access these and further parameters by the
+ attributes.
+
+ Attributes
+ ----------
+ patches : list of :class:`~matplotlib.patches.Rectangle`
+ The artists of the bars.
+
+ errorbar : None or :class:`~matplotlib.container.ErrorbarContainer`
+ A container for the error bar artists if error bars are present.
+ *None* otherwise.
+
+ datavalues : None or array-like
+ The underlying data values corresponding to the bars.
+
+ orientation : {'vertical', 'horizontal'}, default: None
+ If 'vertical', the bars are assumed to be vertical.
+ If 'horizontal', the bars are assumed to be horizontal.
+
+ """
+
+ def __init__(self, patches, errorbar=None, *, datavalues=None,
+ orientation=None, **kwargs):
+ self.patches = patches
+ self.errorbar = errorbar
+ self.datavalues = datavalues
+ self.orientation = orientation
+ super().__init__(patches, **kwargs)
+
+
+class ErrorbarContainer(Container):
+ """
+ Container for the artists of error bars (e.g. created by `.Axes.errorbar`).
+
+ The container can be treated as the *lines* tuple itself.
+ Additionally, you can access these and further parameters by the
+ attributes.
+
+ Attributes
+ ----------
+ lines : tuple
+ Tuple of ``(data_line, caplines, barlinecols)``.
+
+ - data_line : A `~matplotlib.lines.Line2D` instance of x, y plot markers
+ and/or line.
+ - caplines : A tuple of `~matplotlib.lines.Line2D` instances of the error
+ bar caps.
+ - barlinecols : A tuple of `~matplotlib.collections.LineCollection` with the
+ horizontal and vertical error ranges.
+
+ has_xerr, has_yerr : bool
+ ``True`` if the errorbar has x/y errors.
+
+ """
+
+ def __init__(self, lines, has_xerr=False, has_yerr=False, **kwargs):
+ self.lines = lines
+ self.has_xerr = has_xerr
+ self.has_yerr = has_yerr
+ super().__init__(lines, **kwargs)
+
+
+class StemContainer(Container):
+ """
+ Container for the artists created in a :meth:`.Axes.stem` plot.
+
+ The container can be treated like a namedtuple ``(markerline, stemlines,
+ baseline)``.
+
+ Attributes
+ ----------
+ markerline : `~matplotlib.lines.Line2D`
+ The artist of the markers at the stem heads.
+
+ stemlines : `~matplotlib.collections.LineCollection`
+ The artists of the vertical lines for all stems.
+
+ baseline : `~matplotlib.lines.Line2D`
+ The artist of the horizontal baseline.
+ """
+ def __init__(self, markerline_stemlines_baseline, **kwargs):
+ """
+ Parameters
+ ----------
+ markerline_stemlines_baseline : tuple
+ Tuple of ``(markerline, stemlines, baseline)``.
+ ``markerline`` contains the `.Line2D` of the markers,
+ ``stemlines`` is a `.LineCollection` of the main lines,
+ ``baseline`` is the `.Line2D` of the baseline.
+ """
+ markerline, stemlines, baseline = markerline_stemlines_baseline
+ self.markerline = markerline
+ self.stemlines = stemlines
+ self.baseline = baseline
+ super().__init__(markerline_stemlines_baseline, **kwargs)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..c66e7ba4b4c32fc5e27fe06761efb6a3c7f86172
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.pyi
@@ -0,0 +1,56 @@
+from matplotlib.artist import Artist
+from matplotlib.lines import Line2D
+from matplotlib.collections import LineCollection
+from matplotlib.patches import Rectangle
+
+from collections.abc import Callable
+from typing import Any, Literal
+from numpy.typing import ArrayLike
+
+class Container(tuple):
+ def __new__(cls, *args, **kwargs): ...
+ def __init__(self, kl, label: Any | None = ...) -> None: ...
+ def remove(self) -> None: ...
+ def get_children(self) -> list[Artist]: ...
+ def get_label(self) -> str | None: ...
+ def set_label(self, s: Any) -> None: ...
+ def add_callback(self, func: Callable[[Artist], Any]) -> int: ...
+ def remove_callback(self, oid: int) -> None: ...
+ def pchanged(self) -> None: ...
+
+class BarContainer(Container):
+ patches: list[Rectangle]
+ errorbar: None | ErrorbarContainer
+ datavalues: None | ArrayLike
+ orientation: None | Literal["vertical", "horizontal"]
+ def __init__(
+ self,
+ patches: list[Rectangle],
+ errorbar: ErrorbarContainer | None = ...,
+ *,
+ datavalues: ArrayLike | None = ...,
+ orientation: Literal["vertical", "horizontal"] | None = ...,
+ **kwargs
+ ) -> None: ...
+
+class ErrorbarContainer(Container):
+ lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]]
+ has_xerr: bool
+ has_yerr: bool
+ def __init__(
+ self,
+ lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]],
+ has_xerr: bool = ...,
+ has_yerr: bool = ...,
+ **kwargs
+ ) -> None: ...
+
+class StemContainer(Container):
+ markerline: Line2D
+ stemlines: LineCollection
+ baseline: Line2D
+ def __init__(
+ self,
+ markerline_stemlines_baseline: tuple[Line2D, LineCollection, Line2D],
+ **kwargs
+ ) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7318d57812161ab164e3d3e3a62ab13cf020c2c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.py
@@ -0,0 +1,1702 @@
+"""
+Classes to support contour plotting and labelling for the Axes class.
+"""
+
+from contextlib import ExitStack
+import functools
+import math
+from numbers import Integral
+
+import numpy as np
+from numpy import ma
+
+import matplotlib as mpl
+from matplotlib import _api, _docstring
+from matplotlib.backend_bases import MouseButton
+from matplotlib.lines import Line2D
+from matplotlib.path import Path
+from matplotlib.text import Text
+import matplotlib.ticker as ticker
+import matplotlib.cm as cm
+import matplotlib.colors as mcolors
+import matplotlib.collections as mcoll
+import matplotlib.font_manager as font_manager
+import matplotlib.cbook as cbook
+import matplotlib.patches as mpatches
+import matplotlib.transforms as mtransforms
+
+
+def _contour_labeler_event_handler(cs, inline, inline_spacing, event):
+ canvas = cs.axes.get_figure(root=True).canvas
+ is_button = event.name == "button_press_event"
+ is_key = event.name == "key_press_event"
+ # Quit (even if not in infinite mode; this is consistent with
+ # MATLAB and sometimes quite useful, but will require the user to
+ # test how many points were actually returned before using data).
+ if (is_button and event.button == MouseButton.MIDDLE
+ or is_key and event.key in ["escape", "enter"]):
+ canvas.stop_event_loop()
+ # Pop last click.
+ elif (is_button and event.button == MouseButton.RIGHT
+ or is_key and event.key in ["backspace", "delete"]):
+ # Unfortunately, if one is doing inline labels, then there is currently
+ # no way to fix the broken contour - once humpty-dumpty is broken, he
+ # can't be put back together. In inline mode, this does nothing.
+ if not inline:
+ cs.pop_label()
+ canvas.draw()
+ # Add new click.
+ elif (is_button and event.button == MouseButton.LEFT
+ # On macOS/gtk, some keys return None.
+ or is_key and event.key is not None):
+ if cs.axes.contains(event)[0]:
+ cs.add_label_near(event.x, event.y, transform=False,
+ inline=inline, inline_spacing=inline_spacing)
+ canvas.draw()
+
+
+class ContourLabeler:
+ """Mixin to provide labelling capability to `.ContourSet`."""
+
+ def clabel(self, levels=None, *,
+ fontsize=None, inline=True, inline_spacing=5, fmt=None,
+ colors=None, use_clabeltext=False, manual=False,
+ rightside_up=True, zorder=None):
+ """
+ Label a contour plot.
+
+ Adds labels to line contours in this `.ContourSet` (which inherits from
+ this mixin class).
+
+ Parameters
+ ----------
+ levels : array-like, optional
+ A list of level values, that should be labeled. The list must be
+ a subset of ``cs.levels``. If not given, all levels are labeled.
+
+ fontsize : str or float, default: :rc:`font.size`
+ Size in points or relative size e.g., 'smaller', 'x-large'.
+ See `.Text.set_size` for accepted string values.
+
+ colors : :mpltype:`color` or colors or None, default: None
+ The label colors:
+
+ - If *None*, the color of each label matches the color of
+ the corresponding contour.
+
+ - If one string color, e.g., *colors* = 'r' or *colors* =
+ 'red', all labels will be plotted in this color.
+
+ - If a tuple of colors (string, float, RGB, etc), different labels
+ will be plotted in different colors in the order specified.
+
+ inline : bool, default: True
+ If ``True`` the underlying contour is removed where the label is
+ placed.
+
+ inline_spacing : float, default: 5
+ Space in pixels to leave on each side of label when placing inline.
+
+ This spacing will be exact for labels at locations where the
+ contour is straight, less so for labels on curved contours.
+
+ fmt : `.Formatter` or str or callable or dict, optional
+ How the levels are formatted:
+
+ - If a `.Formatter`, it is used to format all levels at once, using
+ its `.Formatter.format_ticks` method.
+ - If a str, it is interpreted as a %-style format string.
+ - If a callable, it is called with one level at a time and should
+ return the corresponding label.
+ - If a dict, it should directly map levels to labels.
+
+ The default is to use a standard `.ScalarFormatter`.
+
+ manual : bool or iterable, default: False
+ If ``True``, contour labels will be placed manually using
+ mouse clicks. Click the first button near a contour to
+ add a label, click the second button (or potentially both
+ mouse buttons at once) to finish adding labels. The third
+ button can be used to remove the last label added, but
+ only if labels are not inline. Alternatively, the keyboard
+ can be used to select label locations (enter to end label
+ placement, delete or backspace act like the third mouse button,
+ and any other key will select a label location).
+
+ *manual* can also be an iterable object of (x, y) tuples.
+ Contour labels will be created as if mouse is clicked at each
+ (x, y) position.
+
+ rightside_up : bool, default: True
+ If ``True``, label rotations will always be plus
+ or minus 90 degrees from level.
+
+ use_clabeltext : bool, default: False
+ If ``True``, use `.Text.set_transform_rotates_text` to ensure that
+ label rotation is updated whenever the Axes aspect changes.
+
+ zorder : float or None, default: ``(2 + contour.get_zorder())``
+ zorder of the contour labels.
+
+ Returns
+ -------
+ labels
+ A list of `.Text` instances for the labels.
+ """
+
+ # Based on the input arguments, clabel() adds a list of "label
+ # specific" attributes to the ContourSet object. These attributes are
+ # all of the form label* and names should be fairly self explanatory.
+ #
+ # Once these attributes are set, clabel passes control to the labels()
+ # method (for automatic label placement) or blocking_input_loop and
+ # _contour_labeler_event_handler (for manual label placement).
+
+ if fmt is None:
+ fmt = ticker.ScalarFormatter(useOffset=False)
+ fmt.create_dummy_axis()
+ self.labelFmt = fmt
+ self._use_clabeltext = use_clabeltext
+ self.labelManual = manual
+ self.rightside_up = rightside_up
+ self._clabel_zorder = 2 + self.get_zorder() if zorder is None else zorder
+
+ if levels is None:
+ levels = self.levels
+ indices = list(range(len(self.cvalues)))
+ else:
+ levlabs = list(levels)
+ indices, levels = [], []
+ for i, lev in enumerate(self.levels):
+ if lev in levlabs:
+ indices.append(i)
+ levels.append(lev)
+ if len(levels) < len(levlabs):
+ raise ValueError(f"Specified levels {levlabs} don't match "
+ f"available levels {self.levels}")
+ self.labelLevelList = levels
+ self.labelIndiceList = indices
+
+ self._label_font_props = font_manager.FontProperties(size=fontsize)
+
+ if colors is None:
+ self.labelMappable = self
+ self.labelCValueList = np.take(self.cvalues, self.labelIndiceList)
+ else:
+ cmap = mcolors.ListedColormap(colors, N=len(self.labelLevelList))
+ self.labelCValueList = list(range(len(self.labelLevelList)))
+ self.labelMappable = cm.ScalarMappable(cmap=cmap,
+ norm=mcolors.NoNorm())
+
+ self.labelXYs = []
+
+ if np.iterable(manual):
+ for x, y in manual:
+ self.add_label_near(x, y, inline, inline_spacing)
+ elif manual:
+ print('Select label locations manually using first mouse button.')
+ print('End manual selection with second mouse button.')
+ if not inline:
+ print('Remove last label by clicking third mouse button.')
+ mpl._blocking_input.blocking_input_loop(
+ self.axes.get_figure(root=True),
+ ["button_press_event", "key_press_event"],
+ timeout=-1, handler=functools.partial(
+ _contour_labeler_event_handler,
+ self, inline, inline_spacing))
+ else:
+ self.labels(inline, inline_spacing)
+
+ return cbook.silent_list('text.Text', self.labelTexts)
+
+ def print_label(self, linecontour, labelwidth):
+ """Return whether a contour is long enough to hold a label."""
+ return (len(linecontour) > 10 * labelwidth
+ or (len(linecontour)
+ and (np.ptp(linecontour, axis=0) > 1.2 * labelwidth).any()))
+
+ def too_close(self, x, y, lw):
+ """Return whether a label is already near this location."""
+ thresh = (1.2 * lw) ** 2
+ return any((x - loc[0]) ** 2 + (y - loc[1]) ** 2 < thresh
+ for loc in self.labelXYs)
+
+ def _get_nth_label_width(self, nth):
+ """Return the width of the *nth* label, in pixels."""
+ fig = self.axes.get_figure(root=False)
+ renderer = fig.get_figure(root=True)._get_renderer()
+ return (Text(0, 0,
+ self.get_text(self.labelLevelList[nth], self.labelFmt),
+ figure=fig, fontproperties=self._label_font_props)
+ .get_window_extent(renderer).width)
+
+ def get_text(self, lev, fmt):
+ """Get the text of the label."""
+ if isinstance(lev, str):
+ return lev
+ elif isinstance(fmt, dict):
+ return fmt.get(lev, '%1.3f')
+ elif callable(getattr(fmt, "format_ticks", None)):
+ return fmt.format_ticks([*self.labelLevelList, lev])[-1]
+ elif callable(fmt):
+ return fmt(lev)
+ else:
+ return fmt % lev
+
+ def locate_label(self, linecontour, labelwidth):
+ """
+ Find good place to draw a label (relatively flat part of the contour).
+ """
+ ctr_size = len(linecontour)
+ n_blocks = int(np.ceil(ctr_size / labelwidth)) if labelwidth > 1 else 1
+ block_size = ctr_size if n_blocks == 1 else int(labelwidth)
+ # Split contour into blocks of length ``block_size``, filling the last
+ # block by cycling the contour start (per `np.resize` semantics). (Due
+ # to cycling, the index returned is taken modulo ctr_size.)
+ xx = np.resize(linecontour[:, 0], (n_blocks, block_size))
+ yy = np.resize(linecontour[:, 1], (n_blocks, block_size))
+ yfirst = yy[:, :1]
+ ylast = yy[:, -1:]
+ xfirst = xx[:, :1]
+ xlast = xx[:, -1:]
+ s = (yfirst - yy) * (xlast - xfirst) - (xfirst - xx) * (ylast - yfirst)
+ l = np.hypot(xlast - xfirst, ylast - yfirst)
+ # Ignore warning that divide by zero throws, as this is a valid option
+ with np.errstate(divide='ignore', invalid='ignore'):
+ distances = (abs(s) / l).sum(axis=-1)
+ # Labels are drawn in the middle of the block (``hbsize``) where the
+ # contour is the closest (per ``distances``) to a straight line, but
+ # not `too_close()` to a preexisting label.
+ hbsize = block_size // 2
+ adist = np.argsort(distances)
+ # If all candidates are `too_close()`, go back to the straightest part
+ # (``adist[0]``).
+ for idx in np.append(adist, adist[0]):
+ x, y = xx[idx, hbsize], yy[idx, hbsize]
+ if not self.too_close(x, y, labelwidth):
+ break
+ return x, y, (idx * block_size + hbsize) % ctr_size
+
+ def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing=5):
+ """
+ Prepare for insertion of a label at index *idx* of *path*.
+
+ Parameters
+ ----------
+ path : Path
+ The path where the label will be inserted, in data space.
+ idx : int
+ The vertex index after which the label will be inserted.
+ screen_pos : (float, float)
+ The position where the label will be inserted, in screen space.
+ lw : float
+ The label width, in screen space.
+ spacing : float
+ Extra spacing around the label, in screen space.
+
+ Returns
+ -------
+ path : Path
+ The path, broken so that the label can be drawn over it.
+ angle : float
+ The rotation of the label.
+
+ Notes
+ -----
+ Both tasks are done together to avoid calculating path lengths multiple times,
+ which is relatively costly.
+
+ The method used here involves computing the path length along the contour in
+ pixel coordinates and then looking (label width / 2) away from central point to
+ determine rotation and then to break contour if desired. The extra spacing is
+ taken into account when breaking the path, but not when computing the angle.
+ """
+ xys = path.vertices
+ codes = path.codes
+
+ # Insert a vertex at idx/pos (converting back to data space), if there isn't yet
+ # a vertex there. With infinite precision one could also always insert the
+ # extra vertex (it will get masked out by the label below anyways), but floating
+ # point inaccuracies (the point can have undergone a data->screen->data
+ # transform loop) can slightly shift the point and e.g. shift the angle computed
+ # below from exactly zero to nonzero.
+ pos = self.get_transform().inverted().transform(screen_pos)
+ if not np.allclose(pos, xys[idx]):
+ xys = np.insert(xys, idx, pos, axis=0)
+ codes = np.insert(codes, idx, Path.LINETO)
+
+ # Find the connected component where the label will be inserted. Note that a
+ # path always starts with a MOVETO, and we consider there's an implicit
+ # MOVETO (closing the last path) at the end.
+ movetos = (codes == Path.MOVETO).nonzero()[0]
+ start = movetos[movetos <= idx][-1]
+ try:
+ stop = movetos[movetos > idx][0]
+ except IndexError:
+ stop = len(codes)
+
+ # Restrict ourselves to the connected component.
+ cc_xys = xys[start:stop]
+ idx -= start
+
+ # If the path is closed, rotate it s.t. it starts at the label.
+ is_closed_path = codes[stop - 1] == Path.CLOSEPOLY
+ if is_closed_path:
+ cc_xys = np.concatenate([cc_xys[idx:-1], cc_xys[:idx+1]])
+ idx = 0
+
+ # Like np.interp, but additionally vectorized over fp.
+ def interp_vec(x, xp, fp): return [np.interp(x, xp, col) for col in fp.T]
+
+ # Use cumulative path lengths ("cpl") as curvilinear coordinate along contour.
+ screen_xys = self.get_transform().transform(cc_xys)
+ path_cpls = np.insert(
+ np.cumsum(np.hypot(*np.diff(screen_xys, axis=0).T)), 0, 0)
+ path_cpls -= path_cpls[idx]
+
+ # Use linear interpolation to get end coordinates of label.
+ target_cpls = np.array([-lw/2, lw/2])
+ if is_closed_path: # For closed paths, target from the other end.
+ target_cpls[0] += (path_cpls[-1] - path_cpls[0])
+ (sx0, sx1), (sy0, sy1) = interp_vec(target_cpls, path_cpls, screen_xys)
+ angle = np.rad2deg(np.arctan2(sy1 - sy0, sx1 - sx0)) # Screen space.
+ if self.rightside_up: # Fix angle so text is never upside-down
+ angle = (angle + 90) % 180 - 90
+
+ target_cpls += [-spacing, +spacing] # Expand range by spacing.
+
+ # Get indices near points of interest; use -1 as out of bounds marker.
+ i0, i1 = np.interp(target_cpls, path_cpls, range(len(path_cpls)),
+ left=-1, right=-1)
+ i0 = math.floor(i0)
+ i1 = math.ceil(i1)
+ (x0, x1), (y0, y1) = interp_vec(target_cpls, path_cpls, cc_xys)
+
+ # Actually break contours (dropping zero-len parts).
+ new_xy_blocks = []
+ new_code_blocks = []
+ if is_closed_path:
+ if i0 != -1 and i1 != -1:
+ # This is probably wrong in the case that the entire contour would
+ # be discarded, but ensures that a valid path is returned and is
+ # consistent with behavior of mpl <3.8
+ points = cc_xys[i1:i0+1]
+ new_xy_blocks.extend([[(x1, y1)], points, [(x0, y0)]])
+ nlines = len(points) + 1
+ new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * nlines])
+ else:
+ if i0 != -1:
+ new_xy_blocks.extend([cc_xys[:i0 + 1], [(x0, y0)]])
+ new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * (i0 + 1)])
+ if i1 != -1:
+ new_xy_blocks.extend([[(x1, y1)], cc_xys[i1:]])
+ new_code_blocks.extend([
+ [Path.MOVETO], [Path.LINETO] * (len(cc_xys) - i1)])
+
+ # Back to the full path.
+ xys = np.concatenate([xys[:start], *new_xy_blocks, xys[stop:]])
+ codes = np.concatenate([codes[:start], *new_code_blocks, codes[stop:]])
+
+ return angle, Path(xys, codes)
+
+ def add_label(self, x, y, rotation, lev, cvalue):
+ """Add a contour label, respecting whether *use_clabeltext* was set."""
+ data_x, data_y = self.axes.transData.inverted().transform((x, y))
+ t = Text(
+ data_x, data_y,
+ text=self.get_text(lev, self.labelFmt),
+ rotation=rotation,
+ horizontalalignment='center', verticalalignment='center',
+ zorder=self._clabel_zorder,
+ color=self.labelMappable.to_rgba(cvalue, alpha=self.get_alpha()),
+ fontproperties=self._label_font_props,
+ clip_box=self.axes.bbox)
+ if self._use_clabeltext:
+ data_rotation, = self.axes.transData.inverted().transform_angles(
+ [rotation], [[x, y]])
+ t.set(rotation=data_rotation, transform_rotates_text=True)
+ self.labelTexts.append(t)
+ self.labelCValues.append(cvalue)
+ self.labelXYs.append((x, y))
+ # Add label to plot here - useful for manual mode label selection
+ self.axes.add_artist(t)
+
+ def add_label_near(self, x, y, inline=True, inline_spacing=5,
+ transform=None):
+ """
+ Add a label near the point ``(x, y)``.
+
+ Parameters
+ ----------
+ x, y : float
+ The approximate location of the label.
+ inline : bool, default: True
+ If *True* remove the segment of the contour beneath the label.
+ inline_spacing : int, default: 5
+ Space in pixels to leave on each side of label when placing
+ inline. This spacing will be exact for labels at locations where
+ the contour is straight, less so for labels on curved contours.
+ transform : `.Transform` or `False`, default: ``self.axes.transData``
+ A transform applied to ``(x, y)`` before labeling. The default
+ causes ``(x, y)`` to be interpreted as data coordinates. `False`
+ is a synonym for `.IdentityTransform`; i.e. ``(x, y)`` should be
+ interpreted as display coordinates.
+ """
+
+ if transform is None:
+ transform = self.axes.transData
+ if transform:
+ x, y = transform.transform((x, y))
+
+ idx_level_min, idx_vtx_min, proj = self._find_nearest_contour(
+ (x, y), self.labelIndiceList)
+ path = self._paths[idx_level_min]
+ level = self.labelIndiceList.index(idx_level_min)
+ label_width = self._get_nth_label_width(level)
+ rotation, path = self._split_path_and_get_label_rotation(
+ path, idx_vtx_min, proj, label_width, inline_spacing)
+ self.add_label(*proj, rotation, self.labelLevelList[idx_level_min],
+ self.labelCValueList[idx_level_min])
+
+ if inline:
+ self._paths[idx_level_min] = path
+
+ def pop_label(self, index=-1):
+ """Defaults to removing last label, but any index can be supplied"""
+ self.labelCValues.pop(index)
+ t = self.labelTexts.pop(index)
+ t.remove()
+
+ def labels(self, inline, inline_spacing):
+ for idx, (icon, lev, cvalue) in enumerate(zip(
+ self.labelIndiceList,
+ self.labelLevelList,
+ self.labelCValueList,
+ )):
+ trans = self.get_transform()
+ label_width = self._get_nth_label_width(idx)
+ additions = []
+ for subpath in self._paths[icon]._iter_connected_components():
+ screen_xys = trans.transform(subpath.vertices)
+ # Check if long enough for a label
+ if self.print_label(screen_xys, label_width):
+ x, y, idx = self.locate_label(screen_xys, label_width)
+ rotation, path = self._split_path_and_get_label_rotation(
+ subpath, idx, (x, y),
+ label_width, inline_spacing)
+ self.add_label(x, y, rotation, lev, cvalue) # Really add label.
+ if inline: # If inline, add new contours
+ additions.append(path)
+ else: # If not adding label, keep old path
+ additions.append(subpath)
+ # After looping over all segments on a contour, replace old path by new one
+ # if inlining.
+ if inline:
+ self._paths[icon] = Path.make_compound_path(*additions)
+
+ def remove(self):
+ super().remove()
+ for text in self.labelTexts:
+ text.remove()
+
+
+def _find_closest_point_on_path(xys, p):
+ """
+ Parameters
+ ----------
+ xys : (N, 2) array-like
+ Coordinates of vertices.
+ p : (float, float)
+ Coordinates of point.
+
+ Returns
+ -------
+ d2min : float
+ Minimum square distance of *p* to *xys*.
+ proj : (float, float)
+ Projection of *p* onto *xys*.
+ imin : (int, int)
+ Consecutive indices of vertices of segment in *xys* where *proj* is.
+ Segments are considered as including their end-points; i.e. if the
+ closest point on the path is a node in *xys* with index *i*, this
+ returns ``(i-1, i)``. For the special case where *xys* is a single
+ point, this returns ``(0, 0)``.
+ """
+ if len(xys) == 1:
+ return (((p - xys[0]) ** 2).sum(), xys[0], (0, 0))
+ dxys = xys[1:] - xys[:-1] # Individual segment vectors.
+ norms = (dxys ** 2).sum(axis=1)
+ norms[norms == 0] = 1 # For zero-length segment, replace 0/0 by 0/1.
+ rel_projs = np.clip( # Project onto each segment in relative 0-1 coords.
+ ((p - xys[:-1]) * dxys).sum(axis=1) / norms,
+ 0, 1)[:, None]
+ projs = xys[:-1] + rel_projs * dxys # Projs. onto each segment, in (x, y).
+ d2s = ((projs - p) ** 2).sum(axis=1) # Squared distances.
+ imin = np.argmin(d2s)
+ return (d2s[imin], projs[imin], (imin, imin+1))
+
+
+_docstring.interpd.register(contour_set_attributes=r"""
+Attributes
+----------
+levels : array
+ The values of the contour levels.
+
+layers : array
+ Same as levels for line contours; half-way between
+ levels for filled contours. See ``ContourSet._process_colors``.
+""")
+
+
+@_docstring.interpd
+class ContourSet(ContourLabeler, mcoll.Collection):
+ """
+ Store a set of contour lines or filled regions.
+
+ User-callable method: `~.Axes.clabel`
+
+ Parameters
+ ----------
+ ax : `~matplotlib.axes.Axes`
+
+ levels : [level0, level1, ..., leveln]
+ A list of floating point numbers indicating the contour levels.
+
+ allsegs : [level0segs, level1segs, ...]
+ List of all the polygon segments for all the *levels*.
+ For contour lines ``len(allsegs) == len(levels)``, and for
+ filled contour regions ``len(allsegs) = len(levels)-1``. The lists
+ should look like ::
+
+ level0segs = [polygon0, polygon1, ...]
+ polygon0 = [[x0, y0], [x1, y1], ...]
+
+ allkinds : ``None`` or [level0kinds, level1kinds, ...]
+ Optional list of all the polygon vertex kinds (code types), as
+ described and used in Path. This is used to allow multiply-
+ connected paths such as holes within filled polygons.
+ If not ``None``, ``len(allkinds) == len(allsegs)``. The lists
+ should look like ::
+
+ level0kinds = [polygon0kinds, ...]
+ polygon0kinds = [vertexcode0, vertexcode1, ...]
+
+ If *allkinds* is not ``None``, usually all polygons for a
+ particular contour level are grouped together so that
+ ``level0segs = [polygon0]`` and ``level0kinds = [polygon0kinds]``.
+
+ **kwargs
+ Keyword arguments are as described in the docstring of
+ `~.Axes.contour`.
+
+ %(contour_set_attributes)s
+ """
+
+ def __init__(self, ax, *args,
+ levels=None, filled=False, linewidths=None, linestyles=None,
+ hatches=(None,), alpha=None, origin=None, extent=None,
+ cmap=None, colors=None, norm=None, vmin=None, vmax=None,
+ colorizer=None, extend='neither', antialiased=None, nchunk=0,
+ locator=None, transform=None, negative_linestyles=None, clip_path=None,
+ **kwargs):
+ """
+ Draw contour lines or filled regions, depending on
+ whether keyword arg *filled* is ``False`` (default) or ``True``.
+
+ Call signature::
+
+ ContourSet(ax, levels, allsegs, [allkinds], **kwargs)
+
+ Parameters
+ ----------
+ ax : `~matplotlib.axes.Axes`
+ The `~.axes.Axes` object to draw on.
+
+ levels : [level0, level1, ..., leveln]
+ A list of floating point numbers indicating the contour
+ levels.
+
+ allsegs : [level0segs, level1segs, ...]
+ List of all the polygon segments for all the *levels*.
+ For contour lines ``len(allsegs) == len(levels)``, and for
+ filled contour regions ``len(allsegs) = len(levels)-1``. The lists
+ should look like ::
+
+ level0segs = [polygon0, polygon1, ...]
+ polygon0 = [[x0, y0], [x1, y1], ...]
+
+ allkinds : [level0kinds, level1kinds, ...], optional
+ Optional list of all the polygon vertex kinds (code types), as
+ described and used in Path. This is used to allow multiply-
+ connected paths such as holes within filled polygons.
+ If not ``None``, ``len(allkinds) == len(allsegs)``. The lists
+ should look like ::
+
+ level0kinds = [polygon0kinds, ...]
+ polygon0kinds = [vertexcode0, vertexcode1, ...]
+
+ If *allkinds* is not ``None``, usually all polygons for a
+ particular contour level are grouped together so that
+ ``level0segs = [polygon0]`` and ``level0kinds = [polygon0kinds]``.
+
+ **kwargs
+ Keyword arguments are as described in the docstring of
+ `~.Axes.contour`.
+ """
+ if antialiased is None and filled:
+ # Eliminate artifacts; we are not stroking the boundaries.
+ antialiased = False
+ # The default for line contours will be taken from the
+ # LineCollection default, which uses :rc:`lines.antialiased`.
+ super().__init__(
+ antialiaseds=antialiased,
+ alpha=alpha,
+ clip_path=clip_path,
+ transform=transform,
+ colorizer=colorizer,
+ )
+ self.axes = ax
+ self.levels = levels
+ self.filled = filled
+ self.hatches = hatches
+ self.origin = origin
+ self.extent = extent
+ self.colors = colors
+ self.extend = extend
+
+ self.nchunk = nchunk
+ self.locator = locator
+
+ if colorizer:
+ self._set_colorizer_check_keywords(colorizer, cmap=cmap,
+ norm=norm, vmin=vmin,
+ vmax=vmax, colors=colors)
+ norm = colorizer.norm
+ cmap = colorizer.cmap
+ if (isinstance(norm, mcolors.LogNorm)
+ or isinstance(self.locator, ticker.LogLocator)):
+ self.logscale = True
+ if norm is None:
+ norm = mcolors.LogNorm()
+ else:
+ self.logscale = False
+
+ _api.check_in_list([None, 'lower', 'upper', 'image'], origin=origin)
+ if self.extent is not None and len(self.extent) != 4:
+ raise ValueError(
+ "If given, 'extent' must be None or (x0, x1, y0, y1)")
+ if self.colors is not None and cmap is not None:
+ raise ValueError('Either colors or cmap must be None')
+ if self.origin == 'image':
+ self.origin = mpl.rcParams['image.origin']
+
+ self._orig_linestyles = linestyles # Only kept for user access.
+ self.negative_linestyles = negative_linestyles
+ # If negative_linestyles was not defined as a keyword argument, define
+ # negative_linestyles with rcParams
+ if self.negative_linestyles is None:
+ self.negative_linestyles = \
+ mpl.rcParams['contour.negative_linestyle']
+
+ kwargs = self._process_args(*args, **kwargs)
+ self._process_levels()
+
+ self._extend_min = self.extend in ['min', 'both']
+ self._extend_max = self.extend in ['max', 'both']
+ if self.colors is not None:
+ if mcolors.is_color_like(self.colors):
+ color_sequence = [self.colors]
+ else:
+ color_sequence = self.colors
+
+ ncolors = len(self.levels)
+ if self.filled:
+ ncolors -= 1
+ i0 = 0
+
+ # Handle the case where colors are given for the extended
+ # parts of the contour.
+
+ use_set_under_over = False
+ # if we are extending the lower end, and we've been given enough
+ # colors then skip the first color in the resulting cmap. For the
+ # extend_max case we don't need to worry about passing more colors
+ # than ncolors as ListedColormap will clip.
+ total_levels = (ncolors +
+ int(self._extend_min) +
+ int(self._extend_max))
+ if (len(color_sequence) == total_levels and
+ (self._extend_min or self._extend_max)):
+ use_set_under_over = True
+ if self._extend_min:
+ i0 = 1
+
+ cmap = mcolors.ListedColormap(color_sequence[i0:None], N=ncolors)
+
+ if use_set_under_over:
+ if self._extend_min:
+ cmap.set_under(color_sequence[0])
+ if self._extend_max:
+ cmap.set_over(color_sequence[-1])
+
+ # label lists must be initialized here
+ self.labelTexts = []
+ self.labelCValues = []
+
+ self.set_cmap(cmap)
+ if norm is not None:
+ self.set_norm(norm)
+ with self.norm.callbacks.blocked(signal="changed"):
+ if vmin is not None:
+ self.norm.vmin = vmin
+ if vmax is not None:
+ self.norm.vmax = vmax
+ self.norm._changed()
+ self._process_colors()
+
+ if self._paths is None:
+ self._paths = self._make_paths_from_contour_generator()
+
+ if self.filled:
+ if linewidths is not None:
+ _api.warn_external('linewidths is ignored by contourf')
+ # Lower and upper contour levels.
+ lowers, uppers = self._get_lowers_and_uppers()
+ self.set(
+ edgecolor="none",
+ # Default zorder taken from Collection
+ zorder=kwargs.pop("zorder", 1),
+ )
+
+ else:
+ self.set(
+ facecolor="none",
+ linewidths=self._process_linewidths(linewidths),
+ linestyle=self._process_linestyles(linestyles),
+ # Default zorder taken from LineCollection, which is higher
+ # than for filled contours so that lines are displayed on top.
+ zorder=kwargs.pop("zorder", 2),
+ label="_nolegend_",
+ )
+
+ self.axes.add_collection(self, autolim=False)
+ self.sticky_edges.x[:] = [self._mins[0], self._maxs[0]]
+ self.sticky_edges.y[:] = [self._mins[1], self._maxs[1]]
+ self.axes.update_datalim([self._mins, self._maxs])
+ self.axes.autoscale_view(tight=True)
+
+ self.changed() # set the colors
+
+ if kwargs:
+ _api.warn_external(
+ 'The following kwargs were not used by contour: ' +
+ ", ".join(map(repr, kwargs))
+ )
+
+ allsegs = property(lambda self: [
+ [subp.vertices for subp in p._iter_connected_components()]
+ for p in self.get_paths()])
+ allkinds = property(lambda self: [
+ [subp.codes for subp in p._iter_connected_components()]
+ for p in self.get_paths()])
+ alpha = property(lambda self: self.get_alpha())
+ linestyles = property(lambda self: self._orig_linestyles)
+
+ def get_transform(self):
+ """Return the `.Transform` instance used by this ContourSet."""
+ if self._transform is None:
+ self._transform = self.axes.transData
+ elif (not isinstance(self._transform, mtransforms.Transform)
+ and hasattr(self._transform, '_as_mpl_transform')):
+ self._transform = self._transform._as_mpl_transform(self.axes)
+ return self._transform
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ # the C object _contour_generator cannot currently be pickled. This
+ # isn't a big issue as it is not actually used once the contour has
+ # been calculated.
+ state['_contour_generator'] = None
+ return state
+
+ def legend_elements(self, variable_name='x', str_format=str):
+ """
+ Return a list of artists and labels suitable for passing through
+ to `~.Axes.legend` which represent this ContourSet.
+
+ The labels have the form "0 < x <= 1" stating the data ranges which
+ the artists represent.
+
+ Parameters
+ ----------
+ variable_name : str
+ The string used inside the inequality used on the labels.
+ str_format : function: float -> str
+ Function used to format the numbers in the labels.
+
+ Returns
+ -------
+ artists : list[`.Artist`]
+ A list of the artists.
+ labels : list[str]
+ A list of the labels.
+ """
+ artists = []
+ labels = []
+
+ if self.filled:
+ lowers, uppers = self._get_lowers_and_uppers()
+ n_levels = len(self._paths)
+ for idx in range(n_levels):
+ artists.append(mpatches.Rectangle(
+ (0, 0), 1, 1,
+ facecolor=self.get_facecolor()[idx],
+ hatch=self.hatches[idx % len(self.hatches)],
+ ))
+ lower = str_format(lowers[idx])
+ upper = str_format(uppers[idx])
+ if idx == 0 and self.extend in ('min', 'both'):
+ labels.append(fr'${variable_name} \leq {lower}s$')
+ elif idx == n_levels - 1 and self.extend in ('max', 'both'):
+ labels.append(fr'${variable_name} > {upper}s$')
+ else:
+ labels.append(fr'${lower} < {variable_name} \leq {upper}$')
+ else:
+ for idx, level in enumerate(self.levels):
+ artists.append(Line2D(
+ [], [],
+ color=self.get_edgecolor()[idx],
+ linewidth=self.get_linewidths()[idx],
+ linestyle=self.get_linestyles()[idx],
+ ))
+ labels.append(fr'${variable_name} = {str_format(level)}$')
+
+ return artists, labels
+
+ def _process_args(self, *args, **kwargs):
+ """
+ Process *args* and *kwargs*; override in derived classes.
+
+ Must set self.levels, self.zmin and self.zmax, and update Axes limits.
+ """
+ self.levels = args[0]
+ allsegs = args[1]
+ allkinds = args[2] if len(args) > 2 else None
+ self.zmax = np.max(self.levels)
+ self.zmin = np.min(self.levels)
+
+ if allkinds is None:
+ allkinds = [[None] * len(segs) for segs in allsegs]
+
+ # Check lengths of levels and allsegs.
+ if self.filled:
+ if len(allsegs) != len(self.levels) - 1:
+ raise ValueError('must be one less number of segments as '
+ 'levels')
+ else:
+ if len(allsegs) != len(self.levels):
+ raise ValueError('must be same number of segments as levels')
+
+ # Check length of allkinds.
+ if len(allkinds) != len(allsegs):
+ raise ValueError('allkinds has different length to allsegs')
+
+ # Determine x, y bounds and update axes data limits.
+ flatseglist = [s for seg in allsegs for s in seg]
+ points = np.concatenate(flatseglist, axis=0)
+ self._mins = points.min(axis=0)
+ self._maxs = points.max(axis=0)
+
+ # Each entry in (allsegs, allkinds) is a list of (segs, kinds): segs is a list
+ # of (N, 2) arrays of xy coordinates, kinds is a list of arrays of corresponding
+ # pathcodes. However, kinds can also be None; in which case all paths in that
+ # list are codeless (this case is normalized above). These lists are used to
+ # construct paths, which then get concatenated.
+ self._paths = [Path.make_compound_path(*map(Path, segs, kinds))
+ for segs, kinds in zip(allsegs, allkinds)]
+
+ return kwargs
+
+ def _make_paths_from_contour_generator(self):
+ """Compute ``paths`` using C extension."""
+ if self._paths is not None:
+ return self._paths
+ cg = self._contour_generator
+ empty_path = Path(np.empty((0, 2)))
+ vertices_and_codes = (
+ map(cg.create_filled_contour, *self._get_lowers_and_uppers())
+ if self.filled else
+ map(cg.create_contour, self.levels))
+ return [Path(np.concatenate(vs), np.concatenate(cs)) if len(vs) else empty_path
+ for vs, cs in vertices_and_codes]
+
+ def _get_lowers_and_uppers(self):
+ """
+ Return ``(lowers, uppers)`` for filled contours.
+ """
+ lowers = self._levels[:-1]
+ if self.zmin == lowers[0]:
+ # Include minimum values in lowest interval
+ lowers = lowers.copy() # so we don't change self._levels
+ if self.logscale:
+ lowers[0] = 0.99 * self.zmin
+ else:
+ lowers[0] -= 1
+ uppers = self._levels[1:]
+ return (lowers, uppers)
+
+ def changed(self):
+ if not hasattr(self, "cvalues"):
+ self._process_colors() # Sets cvalues.
+ # Force an autoscale immediately because self.to_rgba() calls
+ # autoscale_None() internally with the data passed to it,
+ # so if vmin/vmax are not set yet, this would override them with
+ # content from *cvalues* rather than levels like we want
+ self.norm.autoscale_None(self.levels)
+ self.set_array(self.cvalues)
+ self.update_scalarmappable()
+ alphas = np.broadcast_to(self.get_alpha(), len(self.cvalues))
+ for label, cv, alpha in zip(self.labelTexts, self.labelCValues, alphas):
+ label.set_alpha(alpha)
+ label.set_color(self.labelMappable.to_rgba(cv))
+ super().changed()
+
+ def _autolev(self, N):
+ """
+ Select contour levels to span the data.
+
+ The target number of levels, *N*, is used only when the
+ scale is not log and default locator is used.
+
+ We need two more levels for filled contours than for
+ line contours, because for the latter we need to specify
+ the lower and upper boundary of each range. For example,
+ a single contour boundary, say at z = 0, requires only
+ one contour line, but two filled regions, and therefore
+ three levels to provide boundaries for both regions.
+ """
+ if self.locator is None:
+ if self.logscale:
+ self.locator = ticker.LogLocator()
+ else:
+ self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1)
+
+ lev = self.locator.tick_values(self.zmin, self.zmax)
+
+ try:
+ if self.locator._symmetric:
+ return lev
+ except AttributeError:
+ pass
+
+ # Trim excess levels the locator may have supplied.
+ under = np.nonzero(lev < self.zmin)[0]
+ i0 = under[-1] if len(under) else 0
+ over = np.nonzero(lev > self.zmax)[0]
+ i1 = over[0] + 1 if len(over) else len(lev)
+ if self.extend in ('min', 'both'):
+ i0 += 1
+ if self.extend in ('max', 'both'):
+ i1 -= 1
+
+ if i1 - i0 < 3:
+ i0, i1 = 0, len(lev)
+
+ return lev[i0:i1]
+
+ def _process_contour_level_args(self, args, z_dtype):
+ """
+ Determine the contour levels and store in self.levels.
+ """
+ if self.levels is None:
+ if args:
+ levels_arg = args[0]
+ elif np.issubdtype(z_dtype, bool):
+ if self.filled:
+ levels_arg = [0, .5, 1]
+ else:
+ levels_arg = [.5]
+ else:
+ levels_arg = 7 # Default, hard-wired.
+ else:
+ levels_arg = self.levels
+ if isinstance(levels_arg, Integral):
+ self.levels = self._autolev(levels_arg)
+ else:
+ self.levels = np.asarray(levels_arg, np.float64)
+ if self.filled and len(self.levels) < 2:
+ raise ValueError("Filled contours require at least 2 levels.")
+ if len(self.levels) > 1 and np.min(np.diff(self.levels)) <= 0.0:
+ raise ValueError("Contour levels must be increasing")
+
+ def _process_levels(self):
+ """
+ Assign values to :attr:`layers` based on :attr:`levels`,
+ adding extended layers as needed if contours are filled.
+
+ For line contours, layers simply coincide with levels;
+ a line is a thin layer. No extended levels are needed
+ with line contours.
+ """
+ # Make a private _levels to include extended regions; we
+ # want to leave the original levels attribute unchanged.
+ # (Colorbar needs this even for line contours.)
+ self._levels = list(self.levels)
+
+ if self.logscale:
+ lower, upper = 1e-250, 1e250
+ else:
+ lower, upper = -1e250, 1e250
+
+ if self.extend in ('both', 'min'):
+ self._levels.insert(0, lower)
+ if self.extend in ('both', 'max'):
+ self._levels.append(upper)
+ self._levels = np.asarray(self._levels)
+
+ if not self.filled:
+ self.layers = self.levels
+ return
+
+ # Layer values are mid-way between levels in screen space.
+ if self.logscale:
+ # Avoid overflow by taking sqrt before multiplying.
+ self.layers = (np.sqrt(self._levels[:-1])
+ * np.sqrt(self._levels[1:]))
+ else:
+ self.layers = 0.5 * (self._levels[:-1] + self._levels[1:])
+
+ def _process_colors(self):
+ """
+ Color argument processing for contouring.
+
+ Note that we base the colormapping on the contour levels
+ and layers, not on the actual range of the Z values. This
+ means we don't have to worry about bad values in Z, and we
+ always have the full dynamic range available for the selected
+ levels.
+
+ The color is based on the midpoint of the layer, except for
+ extended end layers. By default, the norm vmin and vmax
+ are the extreme values of the non-extended levels. Hence,
+ the layer color extremes are not the extreme values of
+ the colormap itself, but approach those values as the number
+ of levels increases. An advantage of this scheme is that
+ line contours, when added to filled contours, take on
+ colors that are consistent with those of the filled regions;
+ for example, a contour line on the boundary between two
+ regions will have a color intermediate between those
+ of the regions.
+
+ """
+ self.monochrome = self.cmap.monochrome
+ if self.colors is not None:
+ # Generate integers for direct indexing.
+ i0, i1 = 0, len(self.levels)
+ if self.filled:
+ i1 -= 1
+ # Out of range indices for over and under:
+ if self.extend in ('both', 'min'):
+ i0 -= 1
+ if self.extend in ('both', 'max'):
+ i1 += 1
+ self.cvalues = list(range(i0, i1))
+ self.set_norm(mcolors.NoNorm())
+ else:
+ self.cvalues = self.layers
+ self.norm.autoscale_None(self.levels)
+ self.set_array(self.cvalues)
+ self.update_scalarmappable()
+ if self.extend in ('both', 'max', 'min'):
+ self.norm.clip = False
+
+ def _process_linewidths(self, linewidths):
+ Nlev = len(self.levels)
+ if linewidths is None:
+ default_linewidth = mpl.rcParams['contour.linewidth']
+ if default_linewidth is None:
+ default_linewidth = mpl.rcParams['lines.linewidth']
+ return [default_linewidth] * Nlev
+ elif not np.iterable(linewidths):
+ return [linewidths] * Nlev
+ else:
+ linewidths = list(linewidths)
+ return (linewidths * math.ceil(Nlev / len(linewidths)))[:Nlev]
+
+ def _process_linestyles(self, linestyles):
+ Nlev = len(self.levels)
+ if linestyles is None:
+ tlinestyles = ['solid'] * Nlev
+ if self.monochrome:
+ eps = - (self.zmax - self.zmin) * 1e-15
+ for i, lev in enumerate(self.levels):
+ if lev < eps:
+ tlinestyles[i] = self.negative_linestyles
+ else:
+ if isinstance(linestyles, str):
+ tlinestyles = [linestyles] * Nlev
+ elif np.iterable(linestyles):
+ tlinestyles = list(linestyles)
+ if len(tlinestyles) < Nlev:
+ nreps = int(np.ceil(Nlev / len(linestyles)))
+ tlinestyles = tlinestyles * nreps
+ if len(tlinestyles) > Nlev:
+ tlinestyles = tlinestyles[:Nlev]
+ else:
+ raise ValueError("Unrecognized type for linestyles kwarg")
+ return tlinestyles
+
+ def _find_nearest_contour(self, xy, indices=None):
+ """
+ Find the point in the unfilled contour plot that is closest (in screen
+ space) to point *xy*.
+
+ Parameters
+ ----------
+ xy : tuple[float, float]
+ The reference point (in screen space).
+ indices : list of int or None, default: None
+ Indices of contour levels to consider. If None (the default), all levels
+ are considered.
+
+ Returns
+ -------
+ idx_level_min : int
+ The index of the contour level closest to *xy*.
+ idx_vtx_min : int
+ The index of the `.Path` segment closest to *xy* (at that level).
+ proj : (float, float)
+ The point in the contour plot closest to *xy*.
+ """
+
+ # Convert each contour segment to pixel coordinates and then compare the given
+ # point to those coordinates for each contour. This is fast enough in normal
+ # cases, but speedups may be possible.
+
+ if self.filled:
+ raise ValueError("Method does not support filled contours")
+
+ if indices is None:
+ indices = range(len(self._paths))
+
+ d2min = np.inf
+ idx_level_min = idx_vtx_min = proj_min = None
+
+ for idx_level in indices:
+ path = self._paths[idx_level]
+ idx_vtx_start = 0
+ for subpath in path._iter_connected_components():
+ if not len(subpath.vertices):
+ continue
+ lc = self.get_transform().transform(subpath.vertices)
+ d2, proj, leg = _find_closest_point_on_path(lc, xy)
+ if d2 < d2min:
+ d2min = d2
+ idx_level_min = idx_level
+ idx_vtx_min = leg[1] + idx_vtx_start
+ proj_min = proj
+ idx_vtx_start += len(subpath)
+
+ return idx_level_min, idx_vtx_min, proj_min
+
+ def find_nearest_contour(self, x, y, indices=None, pixel=True):
+ """
+ Find the point in the contour plot that is closest to ``(x, y)``.
+
+ This method does not support filled contours.
+
+ Parameters
+ ----------
+ x, y : float
+ The reference point.
+ indices : list of int or None, default: None
+ Indices of contour levels to consider. If None (the default), all
+ levels are considered.
+ pixel : bool, default: True
+ If *True*, measure distance in pixel (screen) space, which is
+ useful for manual contour labeling; else, measure distance in axes
+ space.
+
+ Returns
+ -------
+ path : int
+ The index of the path that is closest to ``(x, y)``. Each path corresponds
+ to one contour level.
+ subpath : int
+ The index within that closest path of the subpath that is closest to
+ ``(x, y)``. Each subpath corresponds to one unbroken contour line.
+ index : int
+ The index of the vertices within that subpath that are closest to
+ ``(x, y)``.
+ xmin, ymin : float
+ The point in the contour plot that is closest to ``(x, y)``.
+ d2 : float
+ The squared distance from ``(xmin, ymin)`` to ``(x, y)``.
+ """
+ segment = index = d2 = None
+
+ with ExitStack() as stack:
+ if not pixel:
+ # _find_nearest_contour works in pixel space. We want axes space, so
+ # effectively disable the transformation here by setting to identity.
+ stack.enter_context(self._cm_set(
+ transform=mtransforms.IdentityTransform()))
+
+ i_level, i_vtx, (xmin, ymin) = self._find_nearest_contour((x, y), indices)
+
+ if i_level is not None:
+ cc_cumlens = np.cumsum(
+ [*map(len, self._paths[i_level]._iter_connected_components())])
+ segment = cc_cumlens.searchsorted(i_vtx, "right")
+ index = i_vtx if segment == 0 else i_vtx - cc_cumlens[segment - 1]
+ d2 = (xmin-x)**2 + (ymin-y)**2
+
+ return (i_level, segment, index, xmin, ymin, d2)
+
+ def draw(self, renderer):
+ paths = self._paths
+ n_paths = len(paths)
+ if not self.filled or all(hatch is None for hatch in self.hatches):
+ super().draw(renderer)
+ return
+ # In presence of hatching, draw contours one at a time.
+ edgecolors = self.get_edgecolors()
+ if edgecolors.size == 0:
+ edgecolors = ("none",)
+ for idx in range(n_paths):
+ with cbook._setattr_cm(self, _paths=[paths[idx]]), self._cm_set(
+ hatch=self.hatches[idx % len(self.hatches)],
+ array=[self.get_array()[idx]],
+ linewidths=[self.get_linewidths()[idx % len(self.get_linewidths())]],
+ linestyles=[self.get_linestyles()[idx % len(self.get_linestyles())]],
+ edgecolors=edgecolors[idx % len(edgecolors)],
+ ):
+ super().draw(renderer)
+
+
+@_docstring.interpd
+class QuadContourSet(ContourSet):
+ """
+ Create and store a set of contour lines or filled regions.
+
+ This class is typically not instantiated directly by the user but by
+ `~.Axes.contour` and `~.Axes.contourf`.
+
+ %(contour_set_attributes)s
+ """
+
+ def _process_args(self, *args, corner_mask=None, algorithm=None, **kwargs):
+ """
+ Process args and kwargs.
+ """
+ if args and isinstance(args[0], QuadContourSet):
+ if self.levels is None:
+ self.levels = args[0].levels
+ self.zmin = args[0].zmin
+ self.zmax = args[0].zmax
+ self._corner_mask = args[0]._corner_mask
+ contour_generator = args[0]._contour_generator
+ self._mins = args[0]._mins
+ self._maxs = args[0]._maxs
+ self._algorithm = args[0]._algorithm
+ else:
+ import contourpy
+
+ if algorithm is None:
+ algorithm = mpl.rcParams['contour.algorithm']
+ mpl.rcParams.validate["contour.algorithm"](algorithm)
+ self._algorithm = algorithm
+
+ if corner_mask is None:
+ if self._algorithm == "mpl2005":
+ # mpl2005 does not support corner_mask=True so if not
+ # specifically requested then disable it.
+ corner_mask = False
+ else:
+ corner_mask = mpl.rcParams['contour.corner_mask']
+ self._corner_mask = corner_mask
+
+ x, y, z = self._contour_args(args, kwargs)
+
+ contour_generator = contourpy.contour_generator(
+ x, y, z, name=self._algorithm, corner_mask=self._corner_mask,
+ line_type=contourpy.LineType.SeparateCode,
+ fill_type=contourpy.FillType.OuterCode,
+ chunk_size=self.nchunk)
+
+ t = self.get_transform()
+
+ # if the transform is not trans data, and some part of it
+ # contains transData, transform the xs and ys to data coordinates
+ if (t != self.axes.transData and
+ any(t.contains_branch_seperately(self.axes.transData))):
+ trans_to_data = t - self.axes.transData
+ pts = np.vstack([x.flat, y.flat]).T
+ transformed_pts = trans_to_data.transform(pts)
+ x = transformed_pts[..., 0]
+ y = transformed_pts[..., 1]
+
+ self._mins = [ma.min(x), ma.min(y)]
+ self._maxs = [ma.max(x), ma.max(y)]
+
+ self._contour_generator = contour_generator
+
+ return kwargs
+
+ def _contour_args(self, args, kwargs):
+ if self.filled:
+ fn = 'contourf'
+ else:
+ fn = 'contour'
+ nargs = len(args)
+
+ if 0 < nargs <= 2:
+ z, *args = args
+ z = ma.asarray(z)
+ x, y = self._initialize_x_y(z)
+ elif 2 < nargs <= 4:
+ x, y, z_orig, *args = args
+ x, y, z = self._check_xyz(x, y, z_orig, kwargs)
+
+ else:
+ raise _api.nargs_error(fn, takes="from 1 to 4", given=nargs)
+ z = ma.masked_invalid(z, copy=False)
+ self.zmax = z.max().astype(float)
+ self.zmin = z.min().astype(float)
+ if self.logscale and self.zmin <= 0:
+ z = ma.masked_where(z <= 0, z)
+ _api.warn_external('Log scale: values of z <= 0 have been masked')
+ self.zmin = z.min().astype(float)
+ self._process_contour_level_args(args, z.dtype)
+ return (x, y, z)
+
+ def _check_xyz(self, x, y, z, kwargs):
+ """
+ Check that the shapes of the input arrays match; if x and y are 1D,
+ convert them to 2D using meshgrid.
+ """
+ x, y = self.axes._process_unit_info([("x", x), ("y", y)], kwargs)
+
+ x = np.asarray(x, dtype=np.float64)
+ y = np.asarray(y, dtype=np.float64)
+ z = ma.asarray(z)
+
+ if z.ndim != 2:
+ raise TypeError(f"Input z must be 2D, not {z.ndim}D")
+ if z.shape[0] < 2 or z.shape[1] < 2:
+ raise TypeError(f"Input z must be at least a (2, 2) shaped array, "
+ f"but has shape {z.shape}")
+ Ny, Nx = z.shape
+
+ if x.ndim != y.ndim:
+ raise TypeError(f"Number of dimensions of x ({x.ndim}) and y "
+ f"({y.ndim}) do not match")
+ if x.ndim == 1:
+ nx, = x.shape
+ ny, = y.shape
+ if nx != Nx:
+ raise TypeError(f"Length of x ({nx}) must match number of "
+ f"columns in z ({Nx})")
+ if ny != Ny:
+ raise TypeError(f"Length of y ({ny}) must match number of "
+ f"rows in z ({Ny})")
+ x, y = np.meshgrid(x, y)
+ elif x.ndim == 2:
+ if x.shape != z.shape:
+ raise TypeError(
+ f"Shapes of x {x.shape} and z {z.shape} do not match")
+ if y.shape != z.shape:
+ raise TypeError(
+ f"Shapes of y {y.shape} and z {z.shape} do not match")
+ else:
+ raise TypeError(f"Inputs x and y must be 1D or 2D, not {x.ndim}D")
+
+ return x, y, z
+
+ def _initialize_x_y(self, z):
+ """
+ Return X, Y arrays such that contour(Z) will match imshow(Z)
+ if origin is not None.
+ The center of pixel Z[i, j] depends on origin:
+ if origin is None, x = j, y = i;
+ if origin is 'lower', x = j + 0.5, y = i + 0.5;
+ if origin is 'upper', x = j + 0.5, y = Nrows - i - 0.5
+ If extent is not None, x and y will be scaled to match,
+ as in imshow.
+ If origin is None and extent is not None, then extent
+ will give the minimum and maximum values of x and y.
+ """
+ if z.ndim != 2:
+ raise TypeError(f"Input z must be 2D, not {z.ndim}D")
+ elif z.shape[0] < 2 or z.shape[1] < 2:
+ raise TypeError(f"Input z must be at least a (2, 2) shaped array, "
+ f"but has shape {z.shape}")
+ else:
+ Ny, Nx = z.shape
+ if self.origin is None: # Not for image-matching.
+ if self.extent is None:
+ return np.meshgrid(np.arange(Nx), np.arange(Ny))
+ else:
+ x0, x1, y0, y1 = self.extent
+ x = np.linspace(x0, x1, Nx)
+ y = np.linspace(y0, y1, Ny)
+ return np.meshgrid(x, y)
+ # Match image behavior:
+ if self.extent is None:
+ x0, x1, y0, y1 = (0, Nx, 0, Ny)
+ else:
+ x0, x1, y0, y1 = self.extent
+ dx = (x1 - x0) / Nx
+ dy = (y1 - y0) / Ny
+ x = x0 + (np.arange(Nx) + 0.5) * dx
+ y = y0 + (np.arange(Ny) + 0.5) * dy
+ if self.origin == 'upper':
+ y = y[::-1]
+ return np.meshgrid(x, y)
+
+
+_docstring.interpd.register(contour_doc="""
+`.contour` and `.contourf` draw contour lines and filled contours,
+respectively. Except as noted, function signatures and return values
+are the same for both versions.
+
+Parameters
+----------
+X, Y : array-like, optional
+ The coordinates of the values in *Z*.
+
+ *X* and *Y* must both be 2D with the same shape as *Z* (e.g.
+ created via `numpy.meshgrid`), or they must both be 1-D such
+ that ``len(X) == N`` is the number of columns in *Z* and
+ ``len(Y) == M`` is the number of rows in *Z*.
+
+ *X* and *Y* must both be ordered monotonically.
+
+ If not given, they are assumed to be integer indices, i.e.
+ ``X = range(N)``, ``Y = range(M)``.
+
+Z : (M, N) array-like
+ The height values over which the contour is drawn. Color-mapping is
+ controlled by *cmap*, *norm*, *vmin*, and *vmax*.
+
+levels : int or array-like, optional
+ Determines the number and positions of the contour lines / regions.
+
+ If an int *n*, use `~matplotlib.ticker.MaxNLocator`, which tries
+ to automatically choose no more than *n+1* "nice" contour levels
+ between minimum and maximum numeric values of *Z*.
+
+ If array-like, draw contour lines at the specified levels.
+ The values must be in increasing order.
+
+Returns
+-------
+`~.contour.QuadContourSet`
+
+Other Parameters
+----------------
+corner_mask : bool, default: :rc:`contour.corner_mask`
+ Enable/disable corner masking, which only has an effect if *Z* is
+ a masked array. If ``False``, any quad touching a masked point is
+ masked out. If ``True``, only the triangular corners of quads
+ nearest those points are always masked out, other triangular
+ corners comprising three unmasked points are contoured as usual.
+
+colors : :mpltype:`color` or list of :mpltype:`color`, optional
+ The colors of the levels, i.e. the lines for `.contour` and the
+ areas for `.contourf`.
+
+ The sequence is cycled for the levels in ascending order. If the
+ sequence is shorter than the number of levels, it's repeated.
+
+ As a shortcut, a single color may be used in place of one-element lists, i.e.
+ ``'red'`` instead of ``['red']`` to color all levels with the same color.
+
+ .. versionchanged:: 3.10
+ Previously a single color had to be expressed as a string, but now any
+ valid color format may be passed.
+
+ By default (value *None*), the colormap specified by *cmap*
+ will be used.
+
+alpha : float, default: 1
+ The alpha blending value, between 0 (transparent) and 1 (opaque).
+
+%(cmap_doc)s
+
+ This parameter is ignored if *colors* is set.
+
+%(norm_doc)s
+
+ This parameter is ignored if *colors* is set.
+
+%(vmin_vmax_doc)s
+
+ If *vmin* or *vmax* are not given, the default color scaling is based on
+ *levels*.
+
+ This parameter is ignored if *colors* is set.
+
+%(colorizer_doc)s
+
+ This parameter is ignored if *colors* is set.
+
+origin : {*None*, 'upper', 'lower', 'image'}, default: None
+ Determines the orientation and exact position of *Z* by specifying
+ the position of ``Z[0, 0]``. This is only relevant, if *X*, *Y*
+ are not given.
+
+ - *None*: ``Z[0, 0]`` is at X=0, Y=0 in the lower left corner.
+ - 'lower': ``Z[0, 0]`` is at X=0.5, Y=0.5 in the lower left corner.
+ - 'upper': ``Z[0, 0]`` is at X=N+0.5, Y=0.5 in the upper left
+ corner.
+ - 'image': Use the value from :rc:`image.origin`.
+
+extent : (x0, x1, y0, y1), optional
+ If *origin* is not *None*, then *extent* is interpreted as in
+ `.imshow`: it gives the outer pixel boundaries. In this case, the
+ position of Z[0, 0] is the center of the pixel, not a corner. If
+ *origin* is *None*, then (*x0*, *y0*) is the position of Z[0, 0],
+ and (*x1*, *y1*) is the position of Z[-1, -1].
+
+ This argument is ignored if *X* and *Y* are specified in the call
+ to contour.
+
+locator : ticker.Locator subclass, optional
+ The locator is used to determine the contour levels if they
+ are not given explicitly via *levels*.
+ Defaults to `~.ticker.MaxNLocator`.
+
+extend : {'neither', 'both', 'min', 'max'}, default: 'neither'
+ Determines the ``contourf``-coloring of values that are outside the
+ *levels* range.
+
+ If 'neither', values outside the *levels* range are not colored.
+ If 'min', 'max' or 'both', color the values below, above or below
+ and above the *levels* range.
+
+ Values below ``min(levels)`` and above ``max(levels)`` are mapped
+ to the under/over values of the `.Colormap`. Note that most
+ colormaps do not have dedicated colors for these by default, so
+ that the over and under values are the edge values of the colormap.
+ You may want to set these values explicitly using
+ `.Colormap.set_under` and `.Colormap.set_over`.
+
+ .. note::
+
+ An existing `.QuadContourSet` does not get notified if
+ properties of its colormap are changed. Therefore, an explicit
+ call `~.ContourSet.changed()` is needed after modifying the
+ colormap. The explicit call can be left out, if a colorbar is
+ assigned to the `.QuadContourSet` because it internally calls
+ `~.ContourSet.changed()`.
+
+ Example::
+
+ x = np.arange(1, 10)
+ y = x.reshape(-1, 1)
+ h = x * y
+
+ cs = plt.contourf(h, levels=[10, 30, 50],
+ colors=['#808080', '#A0A0A0', '#C0C0C0'], extend='both')
+ cs.cmap.set_over('red')
+ cs.cmap.set_under('blue')
+ cs.changed()
+
+xunits, yunits : registered units, optional
+ Override axis units by specifying an instance of a
+ :class:`matplotlib.units.ConversionInterface`.
+
+antialiased : bool, optional
+ Enable antialiasing, overriding the defaults. For
+ filled contours, the default is *False*. For line contours,
+ it is taken from :rc:`lines.antialiased`.
+
+nchunk : int >= 0, optional
+ If 0, no subdivision of the domain. Specify a positive integer to
+ divide the domain into subdomains of *nchunk* by *nchunk* quads.
+ Chunking reduces the maximum length of polygons generated by the
+ contouring algorithm which reduces the rendering workload passed
+ on to the backend and also requires slightly less RAM. It can
+ however introduce rendering artifacts at chunk boundaries depending
+ on the backend, the *antialiased* flag and value of *alpha*.
+
+linewidths : float or array-like, default: :rc:`contour.linewidth`
+ *Only applies to* `.contour`.
+
+ The line width of the contour lines.
+
+ If a number, all levels will be plotted with this linewidth.
+
+ If a sequence, the levels in ascending order will be plotted with
+ the linewidths in the order specified.
+
+ If None, this falls back to :rc:`lines.linewidth`.
+
+linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, optional
+ *Only applies to* `.contour`.
+
+ If *linestyles* is *None*, the default is 'solid' unless the lines are
+ monochrome. In that case, negative contours will instead take their
+ linestyle from the *negative_linestyles* argument.
+
+ *linestyles* can also be an iterable of the above strings specifying a set
+ of linestyles to be used. If this iterable is shorter than the number of
+ contour levels it will be repeated as necessary.
+
+negative_linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, \
+ optional
+ *Only applies to* `.contour`.
+
+ If *linestyles* is *None* and the lines are monochrome, this argument
+ specifies the line style for negative contours.
+
+ If *negative_linestyles* is *None*, the default is taken from
+ :rc:`contour.negative_linestyle`.
+
+ *negative_linestyles* can also be an iterable of the above strings
+ specifying a set of linestyles to be used. If this iterable is shorter than
+ the number of contour levels it will be repeated as necessary.
+
+hatches : list[str], optional
+ *Only applies to* `.contourf`.
+
+ A list of cross hatch patterns to use on the filled areas.
+ If None, no hatching will be added to the contour.
+
+algorithm : {'mpl2005', 'mpl2014', 'serial', 'threaded'}, optional
+ Which contouring algorithm to use to calculate the contour lines and
+ polygons. The algorithms are implemented in
+ `ContourPy `_, consult the
+ `ContourPy documentation `_ for
+ further information.
+
+ The default is taken from :rc:`contour.algorithm`.
+
+clip_path : `~matplotlib.patches.Patch` or `.Path` or `.TransformedPath`
+ Set the clip path. See `~matplotlib.artist.Artist.set_clip_path`.
+
+ .. versionadded:: 3.8
+
+data : indexable object, optional
+ DATA_PARAMETER_PLACEHOLDER
+
+Notes
+-----
+1. `.contourf` differs from the MATLAB version in that it does not draw
+ the polygon edges. To draw edges, add line contours with calls to
+ `.contour`.
+
+2. `.contourf` fills intervals that are closed at the top; that is, for
+ boundaries *z1* and *z2*, the filled region is::
+
+ z1 < Z <= z2
+
+ except for the lowest interval, which is closed on both sides (i.e.
+ it includes the lowest value).
+
+3. `.contour` and `.contourf` use a `marching squares
+ `_ algorithm to
+ compute contour locations. More information can be found in
+ `ContourPy documentation `_.
+""" % _docstring.interpd.params)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..7400fac50993f4560c0fd76d12c530abdd286f26
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.pyi
@@ -0,0 +1,140 @@
+import matplotlib.cm as cm
+from matplotlib.artist import Artist
+from matplotlib.axes import Axes
+from matplotlib.collections import Collection, PathCollection
+from matplotlib.colorizer import Colorizer, ColorizingArtist
+from matplotlib.colors import Colormap, Normalize
+from matplotlib.path import Path
+from matplotlib.patches import Patch
+from matplotlib.text import Text
+from matplotlib.transforms import Transform, TransformedPatchPath, TransformedPath
+from matplotlib.ticker import Locator, Formatter
+
+from numpy.typing import ArrayLike
+import numpy as np
+from collections.abc import Callable, Iterable, Sequence
+from typing import Literal
+from .typing import ColorType
+
+
+
+class ContourLabeler:
+ labelFmt: str | Formatter | Callable[[float], str] | dict[float, str]
+ labelManual: bool | Iterable[tuple[float, float]]
+ rightside_up: bool
+ labelLevelList: list[float]
+ labelIndiceList: list[int]
+ labelMappable: cm.ScalarMappable | ColorizingArtist
+ labelCValueList: list[ColorType]
+ labelXYs: list[tuple[float, float]]
+ def clabel(
+ self,
+ levels: ArrayLike | None = ...,
+ *,
+ fontsize: str | float | None = ...,
+ inline: bool = ...,
+ inline_spacing: float = ...,
+ fmt: str | Formatter | Callable[[float], str] | dict[float, str] | None = ...,
+ colors: ColorType | Sequence[ColorType] | None = ...,
+ use_clabeltext: bool = ...,
+ manual: bool | Iterable[tuple[float, float]] = ...,
+ rightside_up: bool = ...,
+ zorder: float | None = ...
+ ) -> list[Text]: ...
+ def print_label(self, linecontour: ArrayLike, labelwidth: float) -> bool: ...
+ def too_close(self, x: float, y: float, lw: float) -> bool: ...
+ def get_text(
+ self,
+ lev: float,
+ fmt: str | Formatter | Callable[[float], str] | dict[float, str],
+ ) -> str: ...
+ def locate_label(
+ self, linecontour: ArrayLike, labelwidth: float
+ ) -> tuple[float, float, float]: ...
+ def add_label(
+ self, x: float, y: float, rotation: float, lev: float, cvalue: ColorType
+ ) -> None: ...
+ def add_label_near(
+ self,
+ x: float,
+ y: float,
+ inline: bool = ...,
+ inline_spacing: int = ...,
+ transform: Transform | Literal[False] | None = ...,
+ ) -> None: ...
+ def pop_label(self, index: int = ...) -> None: ...
+ def labels(self, inline: bool, inline_spacing: int) -> None: ...
+ def remove(self) -> None: ...
+
+class ContourSet(ContourLabeler, Collection):
+ axes: Axes
+ levels: Iterable[float]
+ filled: bool
+ linewidths: float | ArrayLike | None
+ hatches: Iterable[str | None]
+ origin: Literal["upper", "lower", "image"] | None
+ extent: tuple[float, float, float, float] | None
+ colors: ColorType | Sequence[ColorType]
+ extend: Literal["neither", "both", "min", "max"]
+ nchunk: int
+ locator: Locator | None
+ logscale: bool
+ negative_linestyles: None | Literal[
+ "solid", "dashed", "dashdot", "dotted"
+ ] | Iterable[Literal["solid", "dashed", "dashdot", "dotted"]]
+ clip_path: Patch | Path | TransformedPath | TransformedPatchPath | None
+ labelTexts: list[Text]
+ labelCValues: list[ColorType]
+
+ @property
+ def allkinds(self) -> list[list[np.ndarray | None]]: ...
+ @property
+ def allsegs(self) -> list[list[np.ndarray]]: ...
+ @property
+ def alpha(self) -> float | None: ...
+ @property
+ def linestyles(self) -> (
+ None |
+ Literal["solid", "dashed", "dashdot", "dotted"] |
+ Iterable[Literal["solid", "dashed", "dashdot", "dotted"]]
+ ): ...
+
+ def __init__(
+ self,
+ ax: Axes,
+ *args,
+ levels: Iterable[float] | None = ...,
+ filled: bool = ...,
+ linewidths: float | ArrayLike | None = ...,
+ linestyles: Literal["solid", "dashed", "dashdot", "dotted"]
+ | Iterable[Literal["solid", "dashed", "dashdot", "dotted"]]
+ | None = ...,
+ hatches: Iterable[str | None] = ...,
+ alpha: float | None = ...,
+ origin: Literal["upper", "lower", "image"] | None = ...,
+ extent: tuple[float, float, float, float] | None = ...,
+ cmap: str | Colormap | None = ...,
+ colors: ColorType | Sequence[ColorType] | None = ...,
+ norm: str | Normalize | None = ...,
+ vmin: float | None = ...,
+ vmax: float | None = ...,
+ colorizer: Colorizer | None = ...,
+ extend: Literal["neither", "both", "min", "max"] = ...,
+ antialiased: bool | None = ...,
+ nchunk: int = ...,
+ locator: Locator | None = ...,
+ transform: Transform | None = ...,
+ negative_linestyles: Literal["solid", "dashed", "dashdot", "dotted"]
+ | Iterable[Literal["solid", "dashed", "dashdot", "dotted"]]
+ | None = ...,
+ clip_path: Patch | Path | TransformedPath | TransformedPatchPath | None = ...,
+ **kwargs
+ ) -> None: ...
+ def legend_elements(
+ self, variable_name: str = ..., str_format: Callable[[float], str] = ...
+ ) -> tuple[list[Artist], list[str]]: ...
+ def find_nearest_contour(
+ self, x: float, y: float, indices: Iterable[int] | None = ..., pixel: bool = ...
+ ) -> tuple[int, int, int, float, float, float]: ...
+
+class QuadContourSet(ContourSet): ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dates.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dates.py
new file mode 100644
index 0000000000000000000000000000000000000000..511e1c6df6ccbd8747c76f297e0a6c20947cda3a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dates.py
@@ -0,0 +1,1840 @@
+"""
+Matplotlib provides sophisticated date plotting capabilities, standing on the
+shoulders of python :mod:`datetime` and the add-on module dateutil_.
+
+By default, Matplotlib uses the units machinery described in
+`~matplotlib.units` to convert `datetime.datetime`, and `numpy.datetime64`
+objects when plotted on an x- or y-axis. The user does not
+need to do anything for dates to be formatted, but dates often have strict
+formatting needs, so this module provides many tick locators and formatters.
+A basic example using `numpy.datetime64` is::
+
+ import numpy as np
+
+ times = np.arange(np.datetime64('2001-01-02'),
+ np.datetime64('2002-02-03'), np.timedelta64(75, 'm'))
+ y = np.random.randn(len(times))
+
+ fig, ax = plt.subplots()
+ ax.plot(times, y)
+
+.. seealso::
+
+ - :doc:`/gallery/text_labels_and_annotations/date`
+ - :doc:`/gallery/ticks/date_concise_formatter`
+ - :doc:`/gallery/ticks/date_demo_convert`
+
+.. _date-format:
+
+Matplotlib date format
+----------------------
+
+Matplotlib represents dates using floating point numbers specifying the number
+of days since a default epoch of 1970-01-01 UTC; for example,
+1970-01-01, 06:00 is the floating point number 0.25. The formatters and
+locators require the use of `datetime.datetime` objects, so only dates between
+year 0001 and 9999 can be represented. Microsecond precision
+is achievable for (approximately) 70 years on either side of the epoch, and
+20 microseconds for the rest of the allowable range of dates (year 0001 to
+9999). The epoch can be changed at import time via `.dates.set_epoch` or
+:rc:`date.epoch` to other dates if necessary; see
+:doc:`/gallery/ticks/date_precision_and_epochs` for a discussion.
+
+.. note::
+
+ Before Matplotlib 3.3, the epoch was 0000-12-31 which lost modern
+ microsecond precision and also made the default axis limit of 0 an invalid
+ datetime. In 3.3 the epoch was changed as above. To convert old
+ ordinal floats to the new epoch, users can do::
+
+ new_ordinal = old_ordinal + mdates.date2num(np.datetime64('0000-12-31'))
+
+
+There are a number of helper functions to convert between :mod:`datetime`
+objects and Matplotlib dates:
+
+.. currentmodule:: matplotlib.dates
+
+.. autosummary::
+ :nosignatures:
+
+ datestr2num
+ date2num
+ num2date
+ num2timedelta
+ drange
+ set_epoch
+ get_epoch
+
+.. note::
+
+ Like Python's `datetime.datetime`, Matplotlib uses the Gregorian calendar
+ for all conversions between dates and floating point numbers. This practice
+ is not universal, and calendar differences can cause confusing
+ differences between what Python and Matplotlib give as the number of days
+ since 0001-01-01 and what other software and databases yield. For
+ example, the US Naval Observatory uses a calendar that switches
+ from Julian to Gregorian in October, 1582. Hence, using their
+ calculator, the number of days between 0001-01-01 and 2006-04-01 is
+ 732403, whereas using the Gregorian calendar via the datetime
+ module we find::
+
+ In [1]: date(2006, 4, 1).toordinal() - date(1, 1, 1).toordinal()
+ Out[1]: 732401
+
+All the Matplotlib date converters, locators and formatters are timezone aware.
+If no explicit timezone is provided, :rc:`timezone` is assumed, provided as a
+string. If you want to use a different timezone, pass the *tz* keyword
+argument of `num2date` to any date tick locators or formatters you create. This
+can be either a `datetime.tzinfo` instance or a string with the timezone name
+that can be parsed by `~dateutil.tz.gettz`.
+
+A wide range of specific and general purpose date tick locators and
+formatters are provided in this module. See
+:mod:`matplotlib.ticker` for general information on tick locators
+and formatters. These are described below.
+
+The dateutil_ module provides additional code to handle date ticking, making it
+easy to place ticks on any kinds of dates. See examples below.
+
+.. _dateutil: https://dateutil.readthedocs.io
+
+.. _date-locators:
+
+Date tick locators
+------------------
+
+Most of the date tick locators can locate single or multiple ticks. For example::
+
+ # import constants for the days of the week
+ from matplotlib.dates import MO, TU, WE, TH, FR, SA, SU
+
+ # tick on Mondays every week
+ loc = WeekdayLocator(byweekday=MO, tz=tz)
+
+ # tick on Mondays and Saturdays
+ loc = WeekdayLocator(byweekday=(MO, SA))
+
+In addition, most of the constructors take an interval argument::
+
+ # tick on Mondays every second week
+ loc = WeekdayLocator(byweekday=MO, interval=2)
+
+The rrule locator allows completely general date ticking::
+
+ # tick every 5th easter
+ rule = rrulewrapper(YEARLY, byeaster=1, interval=5)
+ loc = RRuleLocator(rule)
+
+The available date tick locators are:
+
+* `MicrosecondLocator`: Locate microseconds.
+
+* `SecondLocator`: Locate seconds.
+
+* `MinuteLocator`: Locate minutes.
+
+* `HourLocator`: Locate hours.
+
+* `DayLocator`: Locate specified days of the month.
+
+* `WeekdayLocator`: Locate days of the week, e.g., MO, TU.
+
+* `MonthLocator`: Locate months, e.g., 7 for July.
+
+* `YearLocator`: Locate years that are multiples of base.
+
+* `RRuleLocator`: Locate using a `rrulewrapper`.
+ `rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule`
+ which allow almost arbitrary date tick specifications.
+ See :doc:`rrule example `.
+
+* `AutoDateLocator`: On autoscale, this class picks the best `DateLocator`
+ (e.g., `RRuleLocator`) to set the view limits and the tick locations. If
+ called with ``interval_multiples=True`` it will make ticks line up with
+ sensible multiples of the tick intervals. For example, if the interval is
+ 4 hours, it will pick hours 0, 4, 8, etc. as ticks. This behaviour is not
+ guaranteed by default.
+
+.. _date-formatters:
+
+Date formatters
+---------------
+
+The available date formatters are:
+
+* `AutoDateFormatter`: attempts to figure out the best format to use. This is
+ most useful when used with the `AutoDateLocator`.
+
+* `ConciseDateFormatter`: also attempts to figure out the best format to use,
+ and to make the format as compact as possible while still having complete
+ date information. This is most useful when used with the `AutoDateLocator`.
+
+* `DateFormatter`: use `~datetime.datetime.strftime` format strings.
+"""
+
+import datetime
+import functools
+import logging
+import re
+
+from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY,
+ MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
+ SECONDLY)
+from dateutil.relativedelta import relativedelta
+import dateutil.parser
+import dateutil.tz
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api, cbook, ticker, units
+
+__all__ = ('datestr2num', 'date2num', 'num2date', 'num2timedelta', 'drange',
+ 'set_epoch', 'get_epoch', 'DateFormatter', 'ConciseDateFormatter',
+ 'AutoDateFormatter', 'DateLocator', 'RRuleLocator',
+ 'AutoDateLocator', 'YearLocator', 'MonthLocator', 'WeekdayLocator',
+ 'DayLocator', 'HourLocator', 'MinuteLocator',
+ 'SecondLocator', 'MicrosecondLocator',
+ 'rrule', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU',
+ 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY',
+ 'HOURLY', 'MINUTELY', 'SECONDLY', 'MICROSECONDLY', 'relativedelta',
+ 'DateConverter', 'ConciseDateConverter', 'rrulewrapper')
+
+
+_log = logging.getLogger(__name__)
+UTC = datetime.timezone.utc
+
+
+def _get_tzinfo(tz=None):
+ """
+ Generate `~datetime.tzinfo` from a string or return `~datetime.tzinfo`.
+ If None, retrieve the preferred timezone from the rcParams dictionary.
+ """
+ tz = mpl._val_or_rc(tz, 'timezone')
+ if tz == 'UTC':
+ return UTC
+ if isinstance(tz, str):
+ tzinfo = dateutil.tz.gettz(tz)
+ if tzinfo is None:
+ raise ValueError(f"{tz} is not a valid timezone as parsed by"
+ " dateutil.tz.gettz.")
+ return tzinfo
+ if isinstance(tz, datetime.tzinfo):
+ return tz
+ raise TypeError(f"tz must be string or tzinfo subclass, not {tz!r}.")
+
+
+# Time-related constants.
+EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal())
+# EPOCH_OFFSET is not used by matplotlib
+MICROSECONDLY = SECONDLY + 1
+HOURS_PER_DAY = 24.
+MIN_PER_HOUR = 60.
+SEC_PER_MIN = 60.
+MONTHS_PER_YEAR = 12.
+
+DAYS_PER_WEEK = 7.
+DAYS_PER_MONTH = 30.
+DAYS_PER_YEAR = 365.0
+
+MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY
+
+SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR
+SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY
+SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK
+
+MUSECONDS_PER_DAY = 1e6 * SEC_PER_DAY
+
+MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = (
+ MO, TU, WE, TH, FR, SA, SU)
+WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY)
+
+# default epoch: passed to np.datetime64...
+_epoch = None
+
+
+def _reset_epoch_test_example():
+ """
+ Reset the Matplotlib date epoch so it can be set again.
+
+ Only for use in tests and examples.
+ """
+ global _epoch
+ _epoch = None
+
+
+def set_epoch(epoch):
+ """
+ Set the epoch (origin for dates) for datetime calculations.
+
+ The default epoch is :rc:`date.epoch`.
+
+ If microsecond accuracy is desired, the date being plotted needs to be
+ within approximately 70 years of the epoch. Matplotlib internally
+ represents dates as days since the epoch, so floating point dynamic
+ range needs to be within a factor of 2^52.
+
+ `~.dates.set_epoch` must be called before any dates are converted
+ (i.e. near the import section) or a RuntimeError will be raised.
+
+ See also :doc:`/gallery/ticks/date_precision_and_epochs`.
+
+ Parameters
+ ----------
+ epoch : str
+ valid UTC date parsable by `numpy.datetime64` (do not include
+ timezone).
+
+ """
+ global _epoch
+ if _epoch is not None:
+ raise RuntimeError('set_epoch must be called before dates plotted.')
+ _epoch = epoch
+
+
+def get_epoch():
+ """
+ Get the epoch used by `.dates`.
+
+ Returns
+ -------
+ epoch : str
+ String for the epoch (parsable by `numpy.datetime64`).
+ """
+ global _epoch
+
+ _epoch = mpl._val_or_rc(_epoch, 'date.epoch')
+ return _epoch
+
+
+def _dt64_to_ordinalf(d):
+ """
+ Convert `numpy.datetime64` or an `numpy.ndarray` of those types to
+ Gregorian date as UTC float relative to the epoch (see `.get_epoch`).
+ Roundoff is float64 precision. Practically: microseconds for dates
+ between 290301 BC, 294241 AD, milliseconds for larger dates
+ (see `numpy.datetime64`).
+ """
+
+ # the "extra" ensures that we at least allow the dynamic range out to
+ # seconds. That should get out to +/-2e11 years.
+ dseconds = d.astype('datetime64[s]')
+ extra = (d - dseconds).astype('timedelta64[ns]')
+ t0 = np.datetime64(get_epoch(), 's')
+ dt = (dseconds - t0).astype(np.float64)
+ dt += extra.astype(np.float64) / 1.0e9
+ dt = dt / SEC_PER_DAY
+
+ NaT_int = np.datetime64('NaT').astype(np.int64)
+ d_int = d.astype(np.int64)
+ dt[d_int == NaT_int] = np.nan
+ return dt
+
+
+def _from_ordinalf(x, tz=None):
+ """
+ Convert Gregorian float of the date, preserving hours, minutes,
+ seconds and microseconds. Return value is a `.datetime`.
+
+ The input date *x* is a float in ordinal days at UTC, and the output will
+ be the specified `.datetime` object corresponding to that time in
+ timezone *tz*, or if *tz* is ``None``, in the timezone specified in
+ :rc:`timezone`.
+ """
+
+ tz = _get_tzinfo(tz)
+
+ dt = (np.datetime64(get_epoch()) +
+ np.timedelta64(int(np.round(x * MUSECONDS_PER_DAY)), 'us'))
+ if dt < np.datetime64('0001-01-01') or dt >= np.datetime64('10000-01-01'):
+ raise ValueError(f'Date ordinal {x} converts to {dt} (using '
+ f'epoch {get_epoch()}), but Matplotlib dates must be '
+ 'between year 0001 and 9999.')
+ # convert from datetime64 to datetime:
+ dt = dt.tolist()
+
+ # datetime64 is always UTC:
+ dt = dt.replace(tzinfo=dateutil.tz.gettz('UTC'))
+ # but maybe we are working in a different timezone so move.
+ dt = dt.astimezone(tz)
+ # fix round off errors
+ if np.abs(x) > 70 * 365:
+ # if x is big, round off to nearest twenty microseconds.
+ # This avoids floating point roundoff error
+ ms = round(dt.microsecond / 20) * 20
+ if ms == 1000000:
+ dt = dt.replace(microsecond=0) + datetime.timedelta(seconds=1)
+ else:
+ dt = dt.replace(microsecond=ms)
+
+ return dt
+
+
+# a version of _from_ordinalf that can operate on numpy arrays
+_from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf, otypes="O")
+# a version of dateutil.parser.parse that can operate on numpy arrays
+_dateutil_parser_parse_np_vectorized = np.vectorize(dateutil.parser.parse)
+
+
+def datestr2num(d, default=None):
+ """
+ Convert a date string to a datenum using `dateutil.parser.parse`.
+
+ Parameters
+ ----------
+ d : str or sequence of str
+ The dates to convert.
+
+ default : datetime.datetime, optional
+ The default date to use when fields are missing in *d*.
+ """
+ if isinstance(d, str):
+ dt = dateutil.parser.parse(d, default=default)
+ return date2num(dt)
+ else:
+ if default is not None:
+ d = [date2num(dateutil.parser.parse(s, default=default))
+ for s in d]
+ return np.asarray(d)
+ d = np.asarray(d)
+ if not d.size:
+ return d
+ return date2num(_dateutil_parser_parse_np_vectorized(d))
+
+
+def date2num(d):
+ """
+ Convert datetime objects to Matplotlib dates.
+
+ Parameters
+ ----------
+ d : `datetime.datetime` or `numpy.datetime64` or sequences of these
+
+ Returns
+ -------
+ float or sequence of floats
+ Number of days since the epoch. See `.get_epoch` for the
+ epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`. If
+ the epoch is "1970-01-01T00:00:00" (default) then noon Jan 1 1970
+ ("1970-01-01T12:00:00") returns 0.5.
+
+ Notes
+ -----
+ The Gregorian calendar is assumed; this is not universal practice.
+ For details see the module docstring.
+ """
+ # Unpack in case of e.g. Pandas or xarray object
+ d = cbook._unpack_to_numpy(d)
+
+ # make an iterable, but save state to unpack later:
+ iterable = np.iterable(d)
+ if not iterable:
+ d = [d]
+
+ masked = np.ma.is_masked(d)
+ mask = np.ma.getmask(d)
+ d = np.asarray(d)
+
+ # convert to datetime64 arrays, if not already:
+ if not np.issubdtype(d.dtype, np.datetime64):
+ # datetime arrays
+ if not d.size:
+ # deals with an empty array...
+ return d
+ tzi = getattr(d[0], 'tzinfo', None)
+ if tzi is not None:
+ # make datetime naive:
+ d = [dt.astimezone(UTC).replace(tzinfo=None) for dt in d]
+ d = np.asarray(d)
+ d = d.astype('datetime64[us]')
+
+ d = np.ma.masked_array(d, mask=mask) if masked else d
+ d = _dt64_to_ordinalf(d)
+
+ return d if iterable else d[0]
+
+
+def num2date(x, tz=None):
+ """
+ Convert Matplotlib dates to `~datetime.datetime` objects.
+
+ Parameters
+ ----------
+ x : float or sequence of floats
+ Number of days (fraction part represents hours, minutes, seconds)
+ since the epoch. See `.get_epoch` for the
+ epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`.
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Timezone of *x*. If a string, *tz* is passed to `dateutil.tz`.
+
+ Returns
+ -------
+ `~datetime.datetime` or sequence of `~datetime.datetime`
+ Dates are returned in timezone *tz*.
+
+ If *x* is a sequence, a sequence of `~datetime.datetime` objects will
+ be returned.
+
+ Notes
+ -----
+ The Gregorian calendar is assumed; this is not universal practice.
+ For details, see the module docstring.
+ """
+ tz = _get_tzinfo(tz)
+ return _from_ordinalf_np_vectorized(x, tz).tolist()
+
+
+_ordinalf_to_timedelta_np_vectorized = np.vectorize(
+ lambda x: datetime.timedelta(days=x), otypes="O")
+
+
+def num2timedelta(x):
+ """
+ Convert number of days to a `~datetime.timedelta` object.
+
+ If *x* is a sequence, a sequence of `~datetime.timedelta` objects will
+ be returned.
+
+ Parameters
+ ----------
+ x : float, sequence of floats
+ Number of days. The fraction part represents hours, minutes, seconds.
+
+ Returns
+ -------
+ `datetime.timedelta` or list[`datetime.timedelta`]
+ """
+ return _ordinalf_to_timedelta_np_vectorized(x).tolist()
+
+
+def drange(dstart, dend, delta):
+ """
+ Return a sequence of equally spaced Matplotlib dates.
+
+ The dates start at *dstart* and reach up to, but not including *dend*.
+ They are spaced by *delta*.
+
+ Parameters
+ ----------
+ dstart, dend : `~datetime.datetime`
+ The date limits.
+ delta : `datetime.timedelta`
+ Spacing of the dates.
+
+ Returns
+ -------
+ `numpy.array`
+ A list floats representing Matplotlib dates.
+
+ """
+ f1 = date2num(dstart)
+ f2 = date2num(dend)
+ step = delta.total_seconds() / SEC_PER_DAY
+
+ # calculate the difference between dend and dstart in times of delta
+ num = int(np.ceil((f2 - f1) / step))
+
+ # calculate end of the interval which will be generated
+ dinterval_end = dstart + num * delta
+
+ # ensure, that an half open interval will be generated [dstart, dend)
+ if dinterval_end >= dend:
+ # if the endpoint is greater than or equal to dend,
+ # just subtract one delta
+ dinterval_end -= delta
+ num -= 1
+
+ f2 = date2num(dinterval_end) # new float-endpoint
+ return np.linspace(f1, f2, num + 1)
+
+
+def _wrap_in_tex(text):
+ p = r'([a-zA-Z]+)'
+ ret_text = re.sub(p, r'}$\1$\\mathdefault{', text)
+
+ # Braces ensure symbols are not spaced like binary operators.
+ ret_text = ret_text.replace('-', '{-}').replace(':', '{:}')
+ # To not concatenate space between numbers.
+ ret_text = ret_text.replace(' ', r'\;')
+ ret_text = '$\\mathdefault{' + ret_text + '}$'
+ ret_text = ret_text.replace('$\\mathdefault{}$', '')
+ return ret_text
+
+
+## date tick locators and formatters ###
+
+
+class DateFormatter(ticker.Formatter):
+ """
+ Format a tick (in days since the epoch) with a
+ `~datetime.datetime.strftime` format string.
+ """
+
+ def __init__(self, fmt, tz=None, *, usetex=None):
+ """
+ Parameters
+ ----------
+ fmt : str
+ `~datetime.datetime.strftime` format string
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ usetex : bool, default: :rc:`text.usetex`
+ To enable/disable the use of TeX's math mode for rendering the
+ results of the formatter.
+ """
+ self.tz = _get_tzinfo(tz)
+ self.fmt = fmt
+ self._usetex = mpl._val_or_rc(usetex, 'text.usetex')
+
+ def __call__(self, x, pos=0):
+ result = num2date(x, self.tz).strftime(self.fmt)
+ return _wrap_in_tex(result) if self._usetex else result
+
+ def set_tzinfo(self, tz):
+ self.tz = _get_tzinfo(tz)
+
+
+class ConciseDateFormatter(ticker.Formatter):
+ """
+ A `.Formatter` which attempts to figure out the best format to use for the
+ date, and to make it as compact as possible, but still be complete. This is
+ most useful when used with the `AutoDateLocator`::
+
+ >>> locator = AutoDateLocator()
+ >>> formatter = ConciseDateFormatter(locator)
+
+ Parameters
+ ----------
+ locator : `.ticker.Locator`
+ Locator that this axis is using.
+
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone, passed to `.dates.num2date`.
+
+ formats : list of 6 strings, optional
+ Format strings for 6 levels of tick labelling: mostly years,
+ months, days, hours, minutes, and seconds. Strings use
+ the same format codes as `~datetime.datetime.strftime`. Default is
+ ``['%Y', '%b', '%d', '%H:%M', '%H:%M', '%S.%f']``
+
+ zero_formats : list of 6 strings, optional
+ Format strings for tick labels that are "zeros" for a given tick
+ level. For instance, if most ticks are months, ticks around 1 Jan 2005
+ will be labeled "Dec", "2005", "Feb". The default is
+ ``['', '%Y', '%b', '%b-%d', '%H:%M', '%H:%M']``
+
+ offset_formats : list of 6 strings, optional
+ Format strings for the 6 levels that is applied to the "offset"
+ string found on the right side of an x-axis, or top of a y-axis.
+ Combined with the tick labels this should completely specify the
+ date. The default is::
+
+ ['', '%Y', '%Y-%b', '%Y-%b-%d', '%Y-%b-%d', '%Y-%b-%d %H:%M']
+
+ show_offset : bool, default: True
+ Whether to show the offset or not.
+
+ usetex : bool, default: :rc:`text.usetex`
+ To enable/disable the use of TeX's math mode for rendering the results
+ of the formatter.
+
+ Examples
+ --------
+ See :doc:`/gallery/ticks/date_concise_formatter`
+
+ .. plot::
+
+ import datetime
+ import matplotlib.dates as mdates
+
+ base = datetime.datetime(2005, 2, 1)
+ dates = np.array([base + datetime.timedelta(hours=(2 * i))
+ for i in range(732)])
+ N = len(dates)
+ np.random.seed(19680801)
+ y = np.cumsum(np.random.randn(N))
+
+ fig, ax = plt.subplots(constrained_layout=True)
+ locator = mdates.AutoDateLocator()
+ formatter = mdates.ConciseDateFormatter(locator)
+ ax.xaxis.set_major_locator(locator)
+ ax.xaxis.set_major_formatter(formatter)
+
+ ax.plot(dates, y)
+ ax.set_title('Concise Date Formatter')
+
+ """
+
+ def __init__(self, locator, tz=None, formats=None, offset_formats=None,
+ zero_formats=None, show_offset=True, *, usetex=None):
+ """
+ Autoformat the date labels. The default format is used to form an
+ initial string, and then redundant elements are removed.
+ """
+ self._locator = locator
+ self._tz = tz
+ self.defaultfmt = '%Y'
+ # there are 6 levels with each level getting a specific format
+ # 0: mostly years, 1: months, 2: days,
+ # 3: hours, 4: minutes, 5: seconds
+ if formats:
+ if len(formats) != 6:
+ raise ValueError('formats argument must be a list of '
+ '6 format strings (or None)')
+ self.formats = formats
+ else:
+ self.formats = ['%Y', # ticks are mostly years
+ '%b', # ticks are mostly months
+ '%d', # ticks are mostly days
+ '%H:%M', # hrs
+ '%H:%M', # min
+ '%S.%f', # secs
+ ]
+ # fmt for zeros ticks at this level. These are
+ # ticks that should be labeled w/ info the level above.
+ # like 1 Jan can just be labelled "Jan". 02:02:00 can
+ # just be labeled 02:02.
+ if zero_formats:
+ if len(zero_formats) != 6:
+ raise ValueError('zero_formats argument must be a list of '
+ '6 format strings (or None)')
+ self.zero_formats = zero_formats
+ elif formats:
+ # use the users formats for the zero tick formats
+ self.zero_formats = [''] + self.formats[:-1]
+ else:
+ # make the defaults a bit nicer:
+ self.zero_formats = [''] + self.formats[:-1]
+ self.zero_formats[3] = '%b-%d'
+
+ if offset_formats:
+ if len(offset_formats) != 6:
+ raise ValueError('offset_formats argument must be a list of '
+ '6 format strings (or None)')
+ self.offset_formats = offset_formats
+ else:
+ self.offset_formats = ['',
+ '%Y',
+ '%Y-%b',
+ '%Y-%b-%d',
+ '%Y-%b-%d',
+ '%Y-%b-%d %H:%M']
+ self.offset_string = ''
+ self.show_offset = show_offset
+ self._usetex = mpl._val_or_rc(usetex, 'text.usetex')
+
+ def __call__(self, x, pos=None):
+ formatter = DateFormatter(self.defaultfmt, self._tz,
+ usetex=self._usetex)
+ return formatter(x, pos=pos)
+
+ def format_ticks(self, values):
+ tickdatetime = [num2date(value, tz=self._tz) for value in values]
+ tickdate = np.array([tdt.timetuple()[:6] for tdt in tickdatetime])
+
+ # basic algorithm:
+ # 1) only display a part of the date if it changes over the ticks.
+ # 2) don't display the smaller part of the date if:
+ # it is always the same or if it is the start of the
+ # year, month, day etc.
+ # fmt for most ticks at this level
+ fmts = self.formats
+ # format beginnings of days, months, years, etc.
+ zerofmts = self.zero_formats
+ # offset fmt are for the offset in the upper left of the
+ # or lower right of the axis.
+ offsetfmts = self.offset_formats
+ show_offset = self.show_offset
+
+ # determine the level we will label at:
+ # mostly 0: years, 1: months, 2: days,
+ # 3: hours, 4: minutes, 5: seconds, 6: microseconds
+ for level in range(5, -1, -1):
+ unique = np.unique(tickdate[:, level])
+ if len(unique) > 1:
+ # if 1 is included in unique, the year is shown in ticks
+ if level < 2 and np.any(unique == 1):
+ show_offset = False
+ break
+ elif level == 0:
+ # all tickdate are the same, so only micros might be different
+ # set to the most precise (6: microseconds doesn't exist...)
+ level = 5
+
+ # level is the basic level we will label at.
+ # now loop through and decide the actual ticklabels
+ zerovals = [0, 1, 1, 0, 0, 0, 0]
+ labels = [''] * len(tickdate)
+ for nn in range(len(tickdate)):
+ if level < 5:
+ if tickdate[nn][level] == zerovals[level]:
+ fmt = zerofmts[level]
+ else:
+ fmt = fmts[level]
+ else:
+ # special handling for seconds + microseconds
+ if (tickdatetime[nn].second == tickdatetime[nn].microsecond
+ == 0):
+ fmt = zerofmts[level]
+ else:
+ fmt = fmts[level]
+ labels[nn] = tickdatetime[nn].strftime(fmt)
+
+ # special handling of seconds and microseconds:
+ # strip extra zeros and decimal if possible.
+ # this is complicated by two factors. 1) we have some level-4 strings
+ # here (i.e. 03:00, '0.50000', '1.000') 2) we would like to have the
+ # same number of decimals for each string (i.e. 0.5 and 1.0).
+ if level >= 5:
+ trailing_zeros = min(
+ (len(s) - len(s.rstrip('0')) for s in labels if '.' in s),
+ default=None)
+ if trailing_zeros:
+ for nn in range(len(labels)):
+ if '.' in labels[nn]:
+ labels[nn] = labels[nn][:-trailing_zeros].rstrip('.')
+
+ if show_offset:
+ # set the offset string:
+ if (self._locator.axis and
+ self._locator.axis.__name__ in ('xaxis', 'yaxis')
+ and self._locator.axis.get_inverted()):
+ self.offset_string = tickdatetime[0].strftime(offsetfmts[level])
+ else:
+ self.offset_string = tickdatetime[-1].strftime(offsetfmts[level])
+ if self._usetex:
+ self.offset_string = _wrap_in_tex(self.offset_string)
+ else:
+ self.offset_string = ''
+
+ if self._usetex:
+ return [_wrap_in_tex(l) for l in labels]
+ else:
+ return labels
+
+ def get_offset(self):
+ return self.offset_string
+
+ def format_data_short(self, value):
+ return num2date(value, tz=self._tz).strftime('%Y-%m-%d %H:%M:%S')
+
+
+class AutoDateFormatter(ticker.Formatter):
+ """
+ A `.Formatter` which attempts to figure out the best format to use. This
+ is most useful when used with the `AutoDateLocator`.
+
+ `.AutoDateFormatter` has a ``.scale`` dictionary that maps tick scales (the
+ interval in days between one major tick) to format strings; this dictionary
+ defaults to ::
+
+ self.scaled = {
+ DAYS_PER_YEAR: rcParams['date.autoformatter.year'],
+ DAYS_PER_MONTH: rcParams['date.autoformatter.month'],
+ 1: rcParams['date.autoformatter.day'],
+ 1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'],
+ 1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'],
+ 1 / SEC_PER_DAY: rcParams['date.autoformatter.second'],
+ 1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond'],
+ }
+
+ The formatter uses the format string corresponding to the lowest key in
+ the dictionary that is greater or equal to the current scale. Dictionary
+ entries can be customized::
+
+ locator = AutoDateLocator()
+ formatter = AutoDateFormatter(locator)
+ formatter.scaled[1/(24*60)] = '%M:%S' # only show min and sec
+
+ Custom callables can also be used instead of format strings. The following
+ example shows how to use a custom format function to strip trailing zeros
+ from decimal seconds and adds the date to the first ticklabel::
+
+ def my_format_function(x, pos=None):
+ x = matplotlib.dates.num2date(x)
+ if pos == 0:
+ fmt = '%D %H:%M:%S.%f'
+ else:
+ fmt = '%H:%M:%S.%f'
+ label = x.strftime(fmt)
+ label = label.rstrip("0")
+ label = label.rstrip(".")
+ return label
+
+ formatter.scaled[1/(24*60)] = my_format_function
+ """
+
+ # This can be improved by providing some user-level direction on
+ # how to choose the best format (precedence, etc.).
+
+ # Perhaps a 'struct' that has a field for each time-type where a
+ # zero would indicate "don't show" and a number would indicate
+ # "show" with some sort of priority. Same priorities could mean
+ # show all with the same priority.
+
+ # Or more simply, perhaps just a format string for each
+ # possibility...
+
+ def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d', *,
+ usetex=None):
+ """
+ Autoformat the date labels.
+
+ Parameters
+ ----------
+ locator : `.ticker.Locator`
+ Locator that this axis is using.
+
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+
+ defaultfmt : str
+ The default format to use if none of the values in ``self.scaled``
+ are greater than the unit returned by ``locator._get_unit()``.
+
+ usetex : bool, default: :rc:`text.usetex`
+ To enable/disable the use of TeX's math mode for rendering the
+ results of the formatter. If any entries in ``self.scaled`` are set
+ as functions, then it is up to the customized function to enable or
+ disable TeX's math mode itself.
+ """
+ self._locator = locator
+ self._tz = tz
+ self.defaultfmt = defaultfmt
+ self._formatter = DateFormatter(self.defaultfmt, tz)
+ rcParams = mpl.rcParams
+ self._usetex = mpl._val_or_rc(usetex, 'text.usetex')
+ self.scaled = {
+ DAYS_PER_YEAR: rcParams['date.autoformatter.year'],
+ DAYS_PER_MONTH: rcParams['date.autoformatter.month'],
+ 1: rcParams['date.autoformatter.day'],
+ 1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'],
+ 1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'],
+ 1 / SEC_PER_DAY: rcParams['date.autoformatter.second'],
+ 1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond']
+ }
+
+ def _set_locator(self, locator):
+ self._locator = locator
+
+ def __call__(self, x, pos=None):
+ try:
+ locator_unit_scale = float(self._locator._get_unit())
+ except AttributeError:
+ locator_unit_scale = 1
+ # Pick the first scale which is greater than the locator unit.
+ fmt = next((fmt for scale, fmt in sorted(self.scaled.items())
+ if scale >= locator_unit_scale),
+ self.defaultfmt)
+
+ if isinstance(fmt, str):
+ self._formatter = DateFormatter(fmt, self._tz, usetex=self._usetex)
+ result = self._formatter(x, pos)
+ elif callable(fmt):
+ result = fmt(x, pos)
+ else:
+ raise TypeError(f'Unexpected type passed to {self!r}.')
+
+ return result
+
+
+class rrulewrapper:
+ """
+ A simple wrapper around a `dateutil.rrule` allowing flexible
+ date tick specifications.
+ """
+ def __init__(self, freq, tzinfo=None, **kwargs):
+ """
+ Parameters
+ ----------
+ freq : {YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY}
+ Tick frequency. These constants are defined in `dateutil.rrule`,
+ but they are accessible from `matplotlib.dates` as well.
+ tzinfo : `datetime.tzinfo`, optional
+ Time zone information. The default is None.
+ **kwargs
+ Additional keyword arguments are passed to the `dateutil.rrule`.
+ """
+ kwargs['freq'] = freq
+ self._base_tzinfo = tzinfo
+
+ self._update_rrule(**kwargs)
+
+ def set(self, **kwargs):
+ """Set parameters for an existing wrapper."""
+ self._construct.update(kwargs)
+
+ self._update_rrule(**self._construct)
+
+ def _update_rrule(self, **kwargs):
+ tzinfo = self._base_tzinfo
+
+ # rrule does not play nicely with timezones - especially pytz time
+ # zones, it's best to use naive zones and attach timezones once the
+ # datetimes are returned
+ if 'dtstart' in kwargs:
+ dtstart = kwargs['dtstart']
+ if dtstart.tzinfo is not None:
+ if tzinfo is None:
+ tzinfo = dtstart.tzinfo
+ else:
+ dtstart = dtstart.astimezone(tzinfo)
+
+ kwargs['dtstart'] = dtstart.replace(tzinfo=None)
+
+ if 'until' in kwargs:
+ until = kwargs['until']
+ if until.tzinfo is not None:
+ if tzinfo is not None:
+ until = until.astimezone(tzinfo)
+ else:
+ raise ValueError('until cannot be aware if dtstart '
+ 'is naive and tzinfo is None')
+
+ kwargs['until'] = until.replace(tzinfo=None)
+
+ self._construct = kwargs.copy()
+ self._tzinfo = tzinfo
+ self._rrule = rrule(**self._construct)
+
+ def _attach_tzinfo(self, dt, tzinfo):
+ # pytz zones are attached by "localizing" the datetime
+ if hasattr(tzinfo, 'localize'):
+ return tzinfo.localize(dt, is_dst=True)
+
+ return dt.replace(tzinfo=tzinfo)
+
+ def _aware_return_wrapper(self, f, returns_list=False):
+ """Decorator function that allows rrule methods to handle tzinfo."""
+ # This is only necessary if we're actually attaching a tzinfo
+ if self._tzinfo is None:
+ return f
+
+ # All datetime arguments must be naive. If they are not naive, they are
+ # converted to the _tzinfo zone before dropping the zone.
+ def normalize_arg(arg):
+ if isinstance(arg, datetime.datetime) and arg.tzinfo is not None:
+ if arg.tzinfo is not self._tzinfo:
+ arg = arg.astimezone(self._tzinfo)
+
+ return arg.replace(tzinfo=None)
+
+ return arg
+
+ def normalize_args(args, kwargs):
+ args = tuple(normalize_arg(arg) for arg in args)
+ kwargs = {kw: normalize_arg(arg) for kw, arg in kwargs.items()}
+
+ return args, kwargs
+
+ # There are two kinds of functions we care about - ones that return
+ # dates and ones that return lists of dates.
+ if not returns_list:
+ def inner_func(*args, **kwargs):
+ args, kwargs = normalize_args(args, kwargs)
+ dt = f(*args, **kwargs)
+ return self._attach_tzinfo(dt, self._tzinfo)
+ else:
+ def inner_func(*args, **kwargs):
+ args, kwargs = normalize_args(args, kwargs)
+ dts = f(*args, **kwargs)
+ return [self._attach_tzinfo(dt, self._tzinfo) for dt in dts]
+
+ return functools.wraps(f)(inner_func)
+
+ def __getattr__(self, name):
+ if name in self.__dict__:
+ return self.__dict__[name]
+
+ f = getattr(self._rrule, name)
+
+ if name in {'after', 'before'}:
+ return self._aware_return_wrapper(f)
+ elif name in {'xafter', 'xbefore', 'between'}:
+ return self._aware_return_wrapper(f, returns_list=True)
+ else:
+ return f
+
+ def __setstate__(self, state):
+ self.__dict__.update(state)
+
+
+class DateLocator(ticker.Locator):
+ """
+ Determines the tick locations when plotting dates.
+
+ This class is subclassed by other Locators and
+ is not meant to be used on its own.
+ """
+ hms0d = {'byhour': 0, 'byminute': 0, 'bysecond': 0}
+
+ def __init__(self, tz=None):
+ """
+ Parameters
+ ----------
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ self.tz = _get_tzinfo(tz)
+
+ def set_tzinfo(self, tz):
+ """
+ Set timezone info.
+
+ Parameters
+ ----------
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ self.tz = _get_tzinfo(tz)
+
+ def datalim_to_dt(self):
+ """Convert axis data interval to datetime objects."""
+ dmin, dmax = self.axis.get_data_interval()
+ if dmin > dmax:
+ dmin, dmax = dmax, dmin
+
+ return num2date(dmin, self.tz), num2date(dmax, self.tz)
+
+ def viewlim_to_dt(self):
+ """Convert the view interval to datetime objects."""
+ vmin, vmax = self.axis.get_view_interval()
+ if vmin > vmax:
+ vmin, vmax = vmax, vmin
+ return num2date(vmin, self.tz), num2date(vmax, self.tz)
+
+ def _get_unit(self):
+ """
+ Return how many days a unit of the locator is; used for
+ intelligent autoscaling.
+ """
+ return 1
+
+ def _get_interval(self):
+ """
+ Return the number of units for each tick.
+ """
+ return 1
+
+ def nonsingular(self, vmin, vmax):
+ """
+ Given the proposed upper and lower extent, adjust the range
+ if it is too close to being singular (i.e. a range of ~0).
+ """
+ if not np.isfinite(vmin) or not np.isfinite(vmax):
+ # Except if there is no data, then use 1970 as default.
+ return (date2num(datetime.date(1970, 1, 1)),
+ date2num(datetime.date(1970, 1, 2)))
+ if vmax < vmin:
+ vmin, vmax = vmax, vmin
+ unit = self._get_unit()
+ interval = self._get_interval()
+ if abs(vmax - vmin) < 1e-6:
+ vmin -= 2 * unit * interval
+ vmax += 2 * unit * interval
+ return vmin, vmax
+
+
+class RRuleLocator(DateLocator):
+ # use the dateutil rrule instance
+
+ def __init__(self, o, tz=None):
+ super().__init__(tz)
+ self.rule = o
+
+ def __call__(self):
+ # if no data have been set, this will tank with a ValueError
+ try:
+ dmin, dmax = self.viewlim_to_dt()
+ except ValueError:
+ return []
+
+ return self.tick_values(dmin, dmax)
+
+ def tick_values(self, vmin, vmax):
+ start, stop = self._create_rrule(vmin, vmax)
+ dates = self.rule.between(start, stop, True)
+ if len(dates) == 0:
+ return date2num([vmin, vmax])
+ return self.raise_if_exceeds(date2num(dates))
+
+ def _create_rrule(self, vmin, vmax):
+ # set appropriate rrule dtstart and until and return
+ # start and end
+ delta = relativedelta(vmax, vmin)
+
+ # We need to cap at the endpoints of valid datetime
+ try:
+ start = vmin - delta
+ except (ValueError, OverflowError):
+ # cap
+ start = datetime.datetime(1, 1, 1, 0, 0, 0,
+ tzinfo=datetime.timezone.utc)
+
+ try:
+ stop = vmax + delta
+ except (ValueError, OverflowError):
+ # cap
+ stop = datetime.datetime(9999, 12, 31, 23, 59, 59,
+ tzinfo=datetime.timezone.utc)
+
+ self.rule.set(dtstart=start, until=stop)
+
+ return vmin, vmax
+
+ def _get_unit(self):
+ # docstring inherited
+ freq = self.rule._rrule._freq
+ return self.get_unit_generic(freq)
+
+ @staticmethod
+ def get_unit_generic(freq):
+ if freq == YEARLY:
+ return DAYS_PER_YEAR
+ elif freq == MONTHLY:
+ return DAYS_PER_MONTH
+ elif freq == WEEKLY:
+ return DAYS_PER_WEEK
+ elif freq == DAILY:
+ return 1.0
+ elif freq == HOURLY:
+ return 1.0 / HOURS_PER_DAY
+ elif freq == MINUTELY:
+ return 1.0 / MINUTES_PER_DAY
+ elif freq == SECONDLY:
+ return 1.0 / SEC_PER_DAY
+ else:
+ # error
+ return -1 # or should this just return '1'?
+
+ def _get_interval(self):
+ return self.rule._rrule._interval
+
+
+class AutoDateLocator(DateLocator):
+ """
+ On autoscale, this class picks the best `DateLocator` to set the view
+ limits and the tick locations.
+
+ Attributes
+ ----------
+ intervald : dict
+
+ Mapping of tick frequencies to multiples allowed for that ticking.
+ The default is ::
+
+ self.intervald = {
+ YEARLY : [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
+ 1000, 2000, 4000, 5000, 10000],
+ MONTHLY : [1, 2, 3, 4, 6],
+ DAILY : [1, 2, 3, 7, 14, 21],
+ HOURLY : [1, 2, 3, 4, 6, 12],
+ MINUTELY: [1, 5, 10, 15, 30],
+ SECONDLY: [1, 5, 10, 15, 30],
+ MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500,
+ 1000, 2000, 5000, 10000, 20000, 50000,
+ 100000, 200000, 500000, 1000000],
+ }
+
+ where the keys are defined in `dateutil.rrule`.
+
+ The interval is used to specify multiples that are appropriate for
+ the frequency of ticking. For instance, every 7 days is sensible
+ for daily ticks, but for minutes/seconds, 15 or 30 make sense.
+
+ When customizing, you should only modify the values for the existing
+ keys. You should not add or delete entries.
+
+ Example for forcing ticks every 3 hours::
+
+ locator = AutoDateLocator()
+ locator.intervald[HOURLY] = [3] # only show every 3 hours
+ """
+
+ def __init__(self, tz=None, minticks=5, maxticks=None,
+ interval_multiples=True):
+ """
+ Parameters
+ ----------
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ minticks : int
+ The minimum number of ticks desired; controls whether ticks occur
+ yearly, monthly, etc.
+ maxticks : int
+ The maximum number of ticks desired; controls the interval between
+ ticks (ticking every other, every 3, etc.). For fine-grained
+ control, this can be a dictionary mapping individual rrule
+ frequency constants (YEARLY, MONTHLY, etc.) to their own maximum
+ number of ticks. This can be used to keep the number of ticks
+ appropriate to the format chosen in `AutoDateFormatter`. Any
+ frequency not specified in this dictionary is given a default
+ value.
+ interval_multiples : bool, default: True
+ Whether ticks should be chosen to be multiple of the interval,
+ locking them to 'nicer' locations. For example, this will force
+ the ticks to be at hours 0, 6, 12, 18 when hourly ticking is done
+ at 6 hour intervals.
+ """
+ super().__init__(tz=tz)
+ self._freq = YEARLY
+ self._freqs = [YEARLY, MONTHLY, DAILY, HOURLY, MINUTELY,
+ SECONDLY, MICROSECONDLY]
+ self.minticks = minticks
+
+ self.maxticks = {YEARLY: 11, MONTHLY: 12, DAILY: 11, HOURLY: 12,
+ MINUTELY: 11, SECONDLY: 11, MICROSECONDLY: 8}
+ if maxticks is not None:
+ try:
+ self.maxticks.update(maxticks)
+ except TypeError:
+ # Assume we were given an integer. Use this as the maximum
+ # number of ticks for every frequency and create a
+ # dictionary for this
+ self.maxticks = dict.fromkeys(self._freqs, maxticks)
+ self.interval_multiples = interval_multiples
+ self.intervald = {
+ YEARLY: [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
+ 1000, 2000, 4000, 5000, 10000],
+ MONTHLY: [1, 2, 3, 4, 6],
+ DAILY: [1, 2, 3, 7, 14, 21],
+ HOURLY: [1, 2, 3, 4, 6, 12],
+ MINUTELY: [1, 5, 10, 15, 30],
+ SECONDLY: [1, 5, 10, 15, 30],
+ MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000,
+ 5000, 10000, 20000, 50000, 100000, 200000, 500000,
+ 1000000],
+ }
+ if interval_multiples:
+ # Swap "3" for "4" in the DAILY list; If we use 3 we get bad
+ # tick loc for months w/ 31 days: 1, 4, ..., 28, 31, 1
+ # If we use 4 then we get: 1, 5, ... 25, 29, 1
+ self.intervald[DAILY] = [1, 2, 4, 7, 14]
+
+ self._byranges = [None, range(1, 13), range(1, 32),
+ range(0, 24), range(0, 60), range(0, 60), None]
+
+ def __call__(self):
+ # docstring inherited
+ dmin, dmax = self.viewlim_to_dt()
+ locator = self.get_locator(dmin, dmax)
+ return locator()
+
+ def tick_values(self, vmin, vmax):
+ return self.get_locator(vmin, vmax).tick_values(vmin, vmax)
+
+ def nonsingular(self, vmin, vmax):
+ # whatever is thrown at us, we can scale the unit.
+ # But default nonsingular date plots at an ~4 year period.
+ if not np.isfinite(vmin) or not np.isfinite(vmax):
+ # Except if there is no data, then use 1970 as default.
+ return (date2num(datetime.date(1970, 1, 1)),
+ date2num(datetime.date(1970, 1, 2)))
+ if vmax < vmin:
+ vmin, vmax = vmax, vmin
+ if vmin == vmax:
+ vmin = vmin - DAYS_PER_YEAR * 2
+ vmax = vmax + DAYS_PER_YEAR * 2
+ return vmin, vmax
+
+ def _get_unit(self):
+ if self._freq in [MICROSECONDLY]:
+ return 1. / MUSECONDS_PER_DAY
+ else:
+ return RRuleLocator.get_unit_generic(self._freq)
+
+ def get_locator(self, dmin, dmax):
+ """Pick the best locator based on a distance."""
+ delta = relativedelta(dmax, dmin)
+ tdelta = dmax - dmin
+
+ # take absolute difference
+ if dmin > dmax:
+ delta = -delta
+ tdelta = -tdelta
+ # The following uses a mix of calls to relativedelta and timedelta
+ # methods because there is incomplete overlap in the functionality of
+ # these similar functions, and it's best to avoid doing our own math
+ # whenever possible.
+ numYears = float(delta.years)
+ numMonths = numYears * MONTHS_PER_YEAR + delta.months
+ numDays = tdelta.days # Avoids estimates of days/month, days/year.
+ numHours = numDays * HOURS_PER_DAY + delta.hours
+ numMinutes = numHours * MIN_PER_HOUR + delta.minutes
+ numSeconds = np.floor(tdelta.total_seconds())
+ numMicroseconds = np.floor(tdelta.total_seconds() * 1e6)
+
+ nums = [numYears, numMonths, numDays, numHours, numMinutes,
+ numSeconds, numMicroseconds]
+
+ use_rrule_locator = [True] * 6 + [False]
+
+ # Default setting of bymonth, etc. to pass to rrule
+ # [unused (for year), bymonth, bymonthday, byhour, byminute,
+ # bysecond, unused (for microseconds)]
+ byranges = [None, 1, 1, 0, 0, 0, None]
+
+ # Loop over all the frequencies and try to find one that gives at
+ # least a minticks tick positions. Once this is found, look for
+ # an interval from a list specific to that frequency that gives no
+ # more than maxticks tick positions. Also, set up some ranges
+ # (bymonth, etc.) as appropriate to be passed to rrulewrapper.
+ for i, (freq, num) in enumerate(zip(self._freqs, nums)):
+ # If this particular frequency doesn't give enough ticks, continue
+ if num < self.minticks:
+ # Since we're not using this particular frequency, set
+ # the corresponding by_ to None so the rrule can act as
+ # appropriate
+ byranges[i] = None
+ continue
+
+ # Find the first available interval that doesn't give too many
+ # ticks
+ for interval in self.intervald[freq]:
+ if num <= interval * (self.maxticks[freq] - 1):
+ break
+ else:
+ if not (self.interval_multiples and freq == DAILY):
+ _api.warn_external(
+ f"AutoDateLocator was unable to pick an appropriate "
+ f"interval for this date range. It may be necessary "
+ f"to add an interval value to the AutoDateLocator's "
+ f"intervald dictionary. Defaulting to {interval}.")
+
+ # Set some parameters as appropriate
+ self._freq = freq
+
+ if self._byranges[i] and self.interval_multiples:
+ byranges[i] = self._byranges[i][::interval]
+ if i in (DAILY, WEEKLY):
+ if interval == 14:
+ # just make first and 15th. Avoids 30th.
+ byranges[i] = [1, 15]
+ elif interval == 7:
+ byranges[i] = [1, 8, 15, 22]
+
+ interval = 1
+ else:
+ byranges[i] = self._byranges[i]
+ break
+ else:
+ interval = 1
+
+ if (freq == YEARLY) and self.interval_multiples:
+ locator = YearLocator(interval, tz=self.tz)
+ elif use_rrule_locator[i]:
+ _, bymonth, bymonthday, byhour, byminute, bysecond, _ = byranges
+ rrule = rrulewrapper(self._freq, interval=interval,
+ dtstart=dmin, until=dmax,
+ bymonth=bymonth, bymonthday=bymonthday,
+ byhour=byhour, byminute=byminute,
+ bysecond=bysecond)
+
+ locator = RRuleLocator(rrule, tz=self.tz)
+ else:
+ locator = MicrosecondLocator(interval, tz=self.tz)
+ if date2num(dmin) > 70 * 365 and interval < 1000:
+ _api.warn_external(
+ 'Plotting microsecond time intervals for dates far from '
+ f'the epoch (time origin: {get_epoch()}) is not well-'
+ 'supported. See matplotlib.dates.set_epoch to change the '
+ 'epoch.')
+
+ locator.set_axis(self.axis)
+ return locator
+
+
+class YearLocator(RRuleLocator):
+ """
+ Make ticks on a given day of each year that is a multiple of base.
+
+ Examples::
+
+ # Tick every year on Jan 1st
+ locator = YearLocator()
+
+ # Tick every 5 years on July 4th
+ locator = YearLocator(5, month=7, day=4)
+ """
+ def __init__(self, base=1, month=1, day=1, tz=None):
+ """
+ Parameters
+ ----------
+ base : int, default: 1
+ Mark ticks every *base* years.
+ month : int, default: 1
+ The month on which to place the ticks, starting from 1. Default is
+ January.
+ day : int, default: 1
+ The day on which to place the ticks.
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ rule = rrulewrapper(YEARLY, interval=base, bymonth=month,
+ bymonthday=day, **self.hms0d)
+ super().__init__(rule, tz=tz)
+ self.base = ticker._Edge_integer(base, 0)
+
+ def _create_rrule(self, vmin, vmax):
+ # 'start' needs to be a multiple of the interval to create ticks on
+ # interval multiples when the tick frequency is YEARLY
+ ymin = max(self.base.le(vmin.year) * self.base.step, 1)
+ ymax = min(self.base.ge(vmax.year) * self.base.step, 9999)
+
+ c = self.rule._construct
+ replace = {'year': ymin,
+ 'month': c.get('bymonth', 1),
+ 'day': c.get('bymonthday', 1),
+ 'hour': 0, 'minute': 0, 'second': 0}
+
+ start = vmin.replace(**replace)
+ stop = start.replace(year=ymax)
+ self.rule.set(dtstart=start, until=stop)
+
+ return start, stop
+
+
+class MonthLocator(RRuleLocator):
+ """
+ Make ticks on occurrences of each month, e.g., 1, 3, 12.
+ """
+ def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None):
+ """
+ Parameters
+ ----------
+ bymonth : int or list of int, default: all months
+ Ticks will be placed on every month in *bymonth*. Default is
+ ``range(1, 13)``, i.e. every month.
+ bymonthday : int, default: 1
+ The day on which to place the ticks.
+ interval : int, default: 1
+ The interval between each iteration. For example, if
+ ``interval=2``, mark every second occurrence.
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ if bymonth is None:
+ bymonth = range(1, 13)
+
+ rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday,
+ interval=interval, **self.hms0d)
+ super().__init__(rule, tz=tz)
+
+
+class WeekdayLocator(RRuleLocator):
+ """
+ Make ticks on occurrences of each weekday.
+ """
+
+ def __init__(self, byweekday=1, interval=1, tz=None):
+ """
+ Parameters
+ ----------
+ byweekday : int or list of int, default: all days
+ Ticks will be placed on every weekday in *byweekday*. Default is
+ every day.
+
+ Elements of *byweekday* must be one of MO, TU, WE, TH, FR, SA,
+ SU, the constants from :mod:`dateutil.rrule`, which have been
+ imported into the :mod:`matplotlib.dates` namespace.
+ interval : int, default: 1
+ The interval between each iteration. For example, if
+ ``interval=2``, mark every second occurrence.
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ rule = rrulewrapper(DAILY, byweekday=byweekday,
+ interval=interval, **self.hms0d)
+ super().__init__(rule, tz=tz)
+
+
+class DayLocator(RRuleLocator):
+ """
+ Make ticks on occurrences of each day of the month. For example,
+ 1, 15, 30.
+ """
+ def __init__(self, bymonthday=None, interval=1, tz=None):
+ """
+ Parameters
+ ----------
+ bymonthday : int or list of int, default: all days
+ Ticks will be placed on every day in *bymonthday*. Default is
+ ``bymonthday=range(1, 32)``, i.e., every day of the month.
+ interval : int, default: 1
+ The interval between each iteration. For example, if
+ ``interval=2``, mark every second occurrence.
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ if interval != int(interval) or interval < 1:
+ raise ValueError("interval must be an integer greater than 0")
+ if bymonthday is None:
+ bymonthday = range(1, 32)
+
+ rule = rrulewrapper(DAILY, bymonthday=bymonthday,
+ interval=interval, **self.hms0d)
+ super().__init__(rule, tz=tz)
+
+
+class HourLocator(RRuleLocator):
+ """
+ Make ticks on occurrences of each hour.
+ """
+ def __init__(self, byhour=None, interval=1, tz=None):
+ """
+ Parameters
+ ----------
+ byhour : int or list of int, default: all hours
+ Ticks will be placed on every hour in *byhour*. Default is
+ ``byhour=range(24)``, i.e., every hour.
+ interval : int, default: 1
+ The interval between each iteration. For example, if
+ ``interval=2``, mark every second occurrence.
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ if byhour is None:
+ byhour = range(24)
+
+ rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval,
+ byminute=0, bysecond=0)
+ super().__init__(rule, tz=tz)
+
+
+class MinuteLocator(RRuleLocator):
+ """
+ Make ticks on occurrences of each minute.
+ """
+ def __init__(self, byminute=None, interval=1, tz=None):
+ """
+ Parameters
+ ----------
+ byminute : int or list of int, default: all minutes
+ Ticks will be placed on every minute in *byminute*. Default is
+ ``byminute=range(60)``, i.e., every minute.
+ interval : int, default: 1
+ The interval between each iteration. For example, if
+ ``interval=2``, mark every second occurrence.
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ if byminute is None:
+ byminute = range(60)
+
+ rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval,
+ bysecond=0)
+ super().__init__(rule, tz=tz)
+
+
+class SecondLocator(RRuleLocator):
+ """
+ Make ticks on occurrences of each second.
+ """
+ def __init__(self, bysecond=None, interval=1, tz=None):
+ """
+ Parameters
+ ----------
+ bysecond : int or list of int, default: all seconds
+ Ticks will be placed on every second in *bysecond*. Default is
+ ``bysecond = range(60)``, i.e., every second.
+ interval : int, default: 1
+ The interval between each iteration. For example, if
+ ``interval=2``, mark every second occurrence.
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ if bysecond is None:
+ bysecond = range(60)
+
+ rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval)
+ super().__init__(rule, tz=tz)
+
+
+class MicrosecondLocator(DateLocator):
+ """
+ Make ticks on regular intervals of one or more microsecond(s).
+
+ .. note::
+
+ By default, Matplotlib uses a floating point representation of time in
+ days since the epoch, so plotting data with
+ microsecond time resolution does not work well for
+ dates that are far (about 70 years) from the epoch (check with
+ `~.dates.get_epoch`).
+
+ If you want sub-microsecond resolution time plots, it is strongly
+ recommended to use floating point seconds, not datetime-like
+ time representation.
+
+ If you really must use datetime.datetime() or similar and still
+ need microsecond precision, change the time origin via
+ `.dates.set_epoch` to something closer to the dates being plotted.
+ See :doc:`/gallery/ticks/date_precision_and_epochs`.
+
+ """
+ def __init__(self, interval=1, tz=None):
+ """
+ Parameters
+ ----------
+ interval : int, default: 1
+ The interval between each iteration. For example, if
+ ``interval=2``, mark every second occurrence.
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
+ """
+ super().__init__(tz=tz)
+ self._interval = interval
+ self._wrapped_locator = ticker.MultipleLocator(interval)
+
+ def set_axis(self, axis):
+ self._wrapped_locator.set_axis(axis)
+ return super().set_axis(axis)
+
+ def __call__(self):
+ # if no data have been set, this will tank with a ValueError
+ try:
+ dmin, dmax = self.viewlim_to_dt()
+ except ValueError:
+ return []
+
+ return self.tick_values(dmin, dmax)
+
+ def tick_values(self, vmin, vmax):
+ nmin, nmax = date2num((vmin, vmax))
+ t0 = np.floor(nmin)
+ nmax = nmax - t0
+ nmin = nmin - t0
+ nmin *= MUSECONDS_PER_DAY
+ nmax *= MUSECONDS_PER_DAY
+
+ ticks = self._wrapped_locator.tick_values(nmin, nmax)
+
+ ticks = ticks / MUSECONDS_PER_DAY + t0
+ return ticks
+
+ def _get_unit(self):
+ # docstring inherited
+ return 1. / MUSECONDS_PER_DAY
+
+ def _get_interval(self):
+ # docstring inherited
+ return self._interval
+
+
+class DateConverter(units.ConversionInterface):
+ """
+ Converter for `datetime.date` and `datetime.datetime` data, or for
+ date/time data represented as it would be converted by `date2num`.
+
+ The 'unit' tag for such data is None or a `~datetime.tzinfo` instance.
+ """
+
+ def __init__(self, *, interval_multiples=True):
+ self._interval_multiples = interval_multiples
+ super().__init__()
+
+ def axisinfo(self, unit, axis):
+ """
+ Return the `~matplotlib.units.AxisInfo` for *unit*.
+
+ *unit* is a `~datetime.tzinfo` instance or None.
+ The *axis* argument is required but not used.
+ """
+ tz = unit
+
+ majloc = AutoDateLocator(tz=tz,
+ interval_multiples=self._interval_multiples)
+ majfmt = AutoDateFormatter(majloc, tz=tz)
+ datemin = datetime.date(1970, 1, 1)
+ datemax = datetime.date(1970, 1, 2)
+
+ return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
+ default_limits=(datemin, datemax))
+
+ @staticmethod
+ def convert(value, unit, axis):
+ """
+ If *value* is not already a number or sequence of numbers, convert it
+ with `date2num`.
+
+ The *unit* and *axis* arguments are not used.
+ """
+ return date2num(value)
+
+ @staticmethod
+ def default_units(x, axis):
+ """
+ Return the `~datetime.tzinfo` instance of *x* or of its first element,
+ or None
+ """
+ if isinstance(x, np.ndarray):
+ x = x.ravel()
+
+ try:
+ x = cbook._safe_first_finite(x)
+ except (TypeError, StopIteration):
+ pass
+
+ try:
+ return x.tzinfo
+ except AttributeError:
+ pass
+ return None
+
+
+class ConciseDateConverter(DateConverter):
+ # docstring inherited
+
+ def __init__(self, formats=None, zero_formats=None, offset_formats=None,
+ show_offset=True, *, interval_multiples=True):
+ self._formats = formats
+ self._zero_formats = zero_formats
+ self._offset_formats = offset_formats
+ self._show_offset = show_offset
+ self._interval_multiples = interval_multiples
+ super().__init__()
+
+ def axisinfo(self, unit, axis):
+ # docstring inherited
+ tz = unit
+ majloc = AutoDateLocator(tz=tz,
+ interval_multiples=self._interval_multiples)
+ majfmt = ConciseDateFormatter(majloc, tz=tz, formats=self._formats,
+ zero_formats=self._zero_formats,
+ offset_formats=self._offset_formats,
+ show_offset=self._show_offset)
+ datemin = datetime.date(1970, 1, 1)
+ datemax = datetime.date(1970, 1, 2)
+ return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
+ default_limits=(datemin, datemax))
+
+
+class _SwitchableDateConverter:
+ """
+ Helper converter-like object that generates and dispatches to
+ temporary ConciseDateConverter or DateConverter instances based on
+ :rc:`date.converter` and :rc:`date.interval_multiples`.
+ """
+
+ @staticmethod
+ def _get_converter():
+ converter_cls = {
+ "concise": ConciseDateConverter, "auto": DateConverter}[
+ mpl.rcParams["date.converter"]]
+ interval_multiples = mpl.rcParams["date.interval_multiples"]
+ return converter_cls(interval_multiples=interval_multiples)
+
+ def axisinfo(self, *args, **kwargs):
+ return self._get_converter().axisinfo(*args, **kwargs)
+
+ def default_units(self, *args, **kwargs):
+ return self._get_converter().default_units(*args, **kwargs)
+
+ def convert(self, *args, **kwargs):
+ return self._get_converter().convert(*args, **kwargs)
+
+
+units.registry[np.datetime64] = \
+ units.registry[datetime.date] = \
+ units.registry[datetime.datetime] = \
+ _SwitchableDateConverter()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd21367ce73dc39658ab3c671c2ec2ebba5341f3
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.py
@@ -0,0 +1,1139 @@
+"""
+A module for reading dvi files output by TeX. Several limitations make
+this not (currently) useful as a general-purpose dvi preprocessor, but
+it is currently used by the pdf backend for processing usetex text.
+
+Interface::
+
+ with Dvi(filename, 72) as dvi:
+ # iterate over pages:
+ for page in dvi:
+ w, h, d = page.width, page.height, page.descent
+ for x, y, font, glyph, width in page.text:
+ fontname = font.texname
+ pointsize = font.size
+ ...
+ for x, y, height, width in page.boxes:
+ ...
+"""
+
+from collections import namedtuple
+import enum
+from functools import lru_cache, partial, wraps
+import logging
+import os
+from pathlib import Path
+import re
+import struct
+import subprocess
+import sys
+
+import numpy as np
+
+from matplotlib import _api, cbook
+
+_log = logging.getLogger(__name__)
+
+# Many dvi related files are looked for by external processes, require
+# additional parsing, and are used many times per rendering, which is why they
+# are cached using lru_cache().
+
+# Dvi is a bytecode format documented in
+# https://ctan.org/pkg/dvitype
+# https://texdoc.org/serve/dvitype.pdf/0
+#
+# The file consists of a preamble, some number of pages, a postamble,
+# and a finale. Different opcodes are allowed in different contexts,
+# so the Dvi object has a parser state:
+#
+# pre: expecting the preamble
+# outer: between pages (followed by a page or the postamble,
+# also e.g. font definitions are allowed)
+# page: processing a page
+# post_post: state after the postamble (our current implementation
+# just stops reading)
+# finale: the finale (unimplemented in our current implementation)
+
+_dvistate = enum.Enum('DviState', 'pre outer inpage post_post finale')
+
+# The marks on a page consist of text and boxes. A page also has dimensions.
+Page = namedtuple('Page', 'text boxes height width descent')
+Box = namedtuple('Box', 'x y height width')
+
+
+# Also a namedtuple, for backcompat.
+class Text(namedtuple('Text', 'x y font glyph width')):
+ """
+ A glyph in the dvi file.
+
+ The *x* and *y* attributes directly position the glyph. The *font*,
+ *glyph*, and *width* attributes are kept public for back-compatibility,
+ but users wanting to draw the glyph themselves are encouraged to instead
+ load the font specified by `font_path` at `font_size`, warp it with the
+ effects specified by `font_effects`, and load the glyph specified by
+ `glyph_name_or_index`.
+ """
+
+ def _get_pdftexmap_entry(self):
+ return PsfontsMap(find_tex_file("pdftex.map"))[self.font.texname]
+
+ @property
+ def font_path(self):
+ """The `~pathlib.Path` to the font for this glyph."""
+ psfont = self._get_pdftexmap_entry()
+ if psfont.filename is None:
+ raise ValueError("No usable font file found for {} ({}); "
+ "the font may lack a Type-1 version"
+ .format(psfont.psname.decode("ascii"),
+ psfont.texname.decode("ascii")))
+ return Path(psfont.filename)
+
+ @property
+ def font_size(self):
+ """The font size."""
+ return self.font.size
+
+ @property
+ def font_effects(self):
+ """
+ The "font effects" dict for this glyph.
+
+ This dict contains the values for this glyph of SlantFont and
+ ExtendFont (if any), read off :file:`pdftex.map`.
+ """
+ return self._get_pdftexmap_entry().effects
+
+ @property
+ def glyph_name_or_index(self):
+ """
+ Either the glyph name or the native charmap glyph index.
+
+ If :file:`pdftex.map` specifies an encoding for this glyph's font, that
+ is a mapping of glyph indices to Adobe glyph names; use it to convert
+ dvi indices to glyph names. Callers can then convert glyph names to
+ glyph indices (with FT_Get_Name_Index/get_name_index), and load the
+ glyph using FT_Load_Glyph/load_glyph.
+
+ If :file:`pdftex.map` specifies no encoding, the indices directly map
+ to the font's "native" charmap; glyphs should directly load using
+ FT_Load_Char/load_char after selecting the native charmap.
+ """
+ entry = self._get_pdftexmap_entry()
+ return (_parse_enc(entry.encoding)[self.glyph]
+ if entry.encoding is not None else self.glyph)
+
+
+# Opcode argument parsing
+#
+# Each of the following functions takes a Dvi object and delta, which is the
+# difference between the opcode and the minimum opcode with the same meaning.
+# Dvi opcodes often encode the number of argument bytes in this delta.
+_arg_mapping = dict(
+ # raw: Return delta as is.
+ raw=lambda dvi, delta: delta,
+ # u1: Read 1 byte as an unsigned number.
+ u1=lambda dvi, delta: dvi._read_arg(1, signed=False),
+ # u4: Read 4 bytes as an unsigned number.
+ u4=lambda dvi, delta: dvi._read_arg(4, signed=False),
+ # s4: Read 4 bytes as a signed number.
+ s4=lambda dvi, delta: dvi._read_arg(4, signed=True),
+ # slen: Read delta bytes as a signed number, or None if delta is None.
+ slen=lambda dvi, delta: dvi._read_arg(delta, signed=True) if delta else None,
+ # slen1: Read (delta + 1) bytes as a signed number.
+ slen1=lambda dvi, delta: dvi._read_arg(delta + 1, signed=True),
+ # ulen1: Read (delta + 1) bytes as an unsigned number.
+ ulen1=lambda dvi, delta: dvi._read_arg(delta + 1, signed=False),
+ # olen1: Read (delta + 1) bytes as an unsigned number if less than 4 bytes,
+ # as a signed number if 4 bytes.
+ olen1=lambda dvi, delta: dvi._read_arg(delta + 1, signed=(delta == 3)),
+)
+
+
+def _dispatch(table, min, max=None, state=None, args=('raw',)):
+ """
+ Decorator for dispatch by opcode. Sets the values in *table*
+ from *min* to *max* to this method, adds a check that the Dvi state
+ matches *state* if not None, reads arguments from the file according
+ to *args*.
+
+ Parameters
+ ----------
+ table : dict[int, callable]
+ The dispatch table to be filled in.
+
+ min, max : int
+ Range of opcodes that calls the registered function; *max* defaults to
+ *min*.
+
+ state : _dvistate, optional
+ State of the Dvi object in which these opcodes are allowed.
+
+ args : list[str], default: ['raw']
+ Sequence of argument specifications:
+
+ - 'raw': opcode minus minimum
+ - 'u1': read one unsigned byte
+ - 'u4': read four bytes, treat as an unsigned number
+ - 's4': read four bytes, treat as a signed number
+ - 'slen': read (opcode - minimum) bytes, treat as signed
+ - 'slen1': read (opcode - minimum + 1) bytes, treat as signed
+ - 'ulen1': read (opcode - minimum + 1) bytes, treat as unsigned
+ - 'olen1': read (opcode - minimum + 1) bytes, treat as unsigned
+ if under four bytes, signed if four bytes
+ """
+ def decorate(method):
+ get_args = [_arg_mapping[x] for x in args]
+
+ @wraps(method)
+ def wrapper(self, byte):
+ if state is not None and self.state != state:
+ raise ValueError("state precondition failed")
+ return method(self, *[f(self, byte-min) for f in get_args])
+ if max is None:
+ table[min] = wrapper
+ else:
+ for i in range(min, max+1):
+ assert table[i] is None
+ table[i] = wrapper
+ return wrapper
+ return decorate
+
+
+class Dvi:
+ """
+ A reader for a dvi ("device-independent") file, as produced by TeX.
+
+ The current implementation can only iterate through pages in order,
+ and does not even attempt to verify the postamble.
+
+ This class can be used as a context manager to close the underlying
+ file upon exit. Pages can be read via iteration. Here is an overly
+ simple way to extract text without trying to detect whitespace::
+
+ >>> with matplotlib.dviread.Dvi('input.dvi', 72) as dvi:
+ ... for page in dvi:
+ ... print(''.join(chr(t.glyph) for t in page.text))
+ """
+ # dispatch table
+ _dtable = [None] * 256
+ _dispatch = partial(_dispatch, _dtable)
+
+ def __init__(self, filename, dpi):
+ """
+ Read the data from the file named *filename* and convert
+ TeX's internal units to units of *dpi* per inch.
+ *dpi* only sets the units and does not limit the resolution.
+ Use None to return TeX's internal units.
+ """
+ _log.debug('Dvi: %s', filename)
+ self.file = open(filename, 'rb')
+ self.dpi = dpi
+ self.fonts = {}
+ self.state = _dvistate.pre
+ self._missing_font = None
+
+ def __enter__(self):
+ """Context manager enter method, does nothing."""
+ return self
+
+ def __exit__(self, etype, evalue, etrace):
+ """
+ Context manager exit method, closes the underlying file if it is open.
+ """
+ self.close()
+
+ def __iter__(self):
+ """
+ Iterate through the pages of the file.
+
+ Yields
+ ------
+ Page
+ Details of all the text and box objects on the page.
+ The Page tuple contains lists of Text and Box tuples and
+ the page dimensions, and the Text and Box tuples contain
+ coordinates transformed into a standard Cartesian
+ coordinate system at the dpi value given when initializing.
+ The coordinates are floating point numbers, but otherwise
+ precision is not lost and coordinate values are not clipped to
+ integers.
+ """
+ while self._read():
+ yield self._output()
+
+ def close(self):
+ """Close the underlying file if it is open."""
+ if not self.file.closed:
+ self.file.close()
+
+ def _output(self):
+ """
+ Output the text and boxes belonging to the most recent page.
+ page = dvi._output()
+ """
+ minx = miny = np.inf
+ maxx = maxy = -np.inf
+ maxy_pure = -np.inf
+ for elt in self.text + self.boxes:
+ if isinstance(elt, Box):
+ x, y, h, w = elt
+ e = 0 # zero depth
+ else: # glyph
+ x, y, font, g, w = elt
+ h, e = font._height_depth_of(g)
+ minx = min(minx, x)
+ miny = min(miny, y - h)
+ maxx = max(maxx, x + w)
+ maxy = max(maxy, y + e)
+ maxy_pure = max(maxy_pure, y)
+ if self._baseline_v is not None:
+ maxy_pure = self._baseline_v # This should normally be the case.
+ self._baseline_v = None
+
+ if not self.text and not self.boxes: # Avoid infs/nans from inf+/-inf.
+ return Page(text=[], boxes=[], width=0, height=0, descent=0)
+
+ if self.dpi is None:
+ # special case for ease of debugging: output raw dvi coordinates
+ return Page(text=self.text, boxes=self.boxes,
+ width=maxx-minx, height=maxy_pure-miny,
+ descent=maxy-maxy_pure)
+
+ # convert from TeX's "scaled points" to dpi units
+ d = self.dpi / (72.27 * 2**16)
+ descent = (maxy - maxy_pure) * d
+
+ text = [Text((x-minx)*d, (maxy-y)*d - descent, f, g, w*d)
+ for (x, y, f, g, w) in self.text]
+ boxes = [Box((x-minx)*d, (maxy-y)*d - descent, h*d, w*d)
+ for (x, y, h, w) in self.boxes]
+
+ return Page(text=text, boxes=boxes, width=(maxx-minx)*d,
+ height=(maxy_pure-miny)*d, descent=descent)
+
+ def _read(self):
+ """
+ Read one page from the file. Return True if successful,
+ False if there were no more pages.
+ """
+ # Pages appear to start with the sequence
+ # bop (begin of page)
+ # xxx comment
+ # # if using chemformula
+ # down
+ # push
+ # down
+ # # if using xcolor
+ # down
+ # push
+ # down (possibly multiple)
+ # push <= here, v is the baseline position.
+ # etc.
+ # (dviasm is useful to explore this structure.)
+ # Thus, we use the vertical position at the first time the stack depth
+ # reaches 3, while at least three "downs" have been executed (excluding
+ # those popped out (corresponding to the chemformula preamble)), as the
+ # baseline (the "down" count is necessary to handle xcolor).
+ down_stack = [0]
+ self._baseline_v = None
+ while True:
+ byte = self.file.read(1)[0]
+ self._dtable[byte](self, byte)
+ if self._missing_font:
+ raise self._missing_font.to_exception()
+ name = self._dtable[byte].__name__
+ if name == "_push":
+ down_stack.append(down_stack[-1])
+ elif name == "_pop":
+ down_stack.pop()
+ elif name == "_down":
+ down_stack[-1] += 1
+ if (self._baseline_v is None
+ and len(getattr(self, "stack", [])) == 3
+ and down_stack[-1] >= 4):
+ self._baseline_v = self.v
+ if byte == 140: # end of page
+ return True
+ if self.state is _dvistate.post_post: # end of file
+ self.close()
+ return False
+
+ def _read_arg(self, nbytes, signed=False):
+ """
+ Read and return a big-endian integer *nbytes* long.
+ Signedness is determined by the *signed* keyword.
+ """
+ return int.from_bytes(self.file.read(nbytes), "big", signed=signed)
+
+ @_dispatch(min=0, max=127, state=_dvistate.inpage)
+ def _set_char_immediate(self, char):
+ self._put_char_real(char)
+ if isinstance(self.fonts[self.f], cbook._ExceptionInfo):
+ return
+ self.h += self.fonts[self.f]._width_of(char)
+
+ @_dispatch(min=128, max=131, state=_dvistate.inpage, args=('olen1',))
+ def _set_char(self, char):
+ self._put_char_real(char)
+ if isinstance(self.fonts[self.f], cbook._ExceptionInfo):
+ return
+ self.h += self.fonts[self.f]._width_of(char)
+
+ @_dispatch(132, state=_dvistate.inpage, args=('s4', 's4'))
+ def _set_rule(self, a, b):
+ self._put_rule_real(a, b)
+ self.h += b
+
+ @_dispatch(min=133, max=136, state=_dvistate.inpage, args=('olen1',))
+ def _put_char(self, char):
+ self._put_char_real(char)
+
+ def _put_char_real(self, char):
+ font = self.fonts[self.f]
+ if isinstance(font, cbook._ExceptionInfo):
+ self._missing_font = font
+ elif font._vf is None:
+ self.text.append(Text(self.h, self.v, font, char,
+ font._width_of(char)))
+ else:
+ scale = font._scale
+ for x, y, f, g, w in font._vf[char].text:
+ newf = DviFont(scale=_mul2012(scale, f._scale),
+ tfm=f._tfm, texname=f.texname, vf=f._vf)
+ self.text.append(Text(self.h + _mul2012(x, scale),
+ self.v + _mul2012(y, scale),
+ newf, g, newf._width_of(g)))
+ self.boxes.extend([Box(self.h + _mul2012(x, scale),
+ self.v + _mul2012(y, scale),
+ _mul2012(a, scale), _mul2012(b, scale))
+ for x, y, a, b in font._vf[char].boxes])
+
+ @_dispatch(137, state=_dvistate.inpage, args=('s4', 's4'))
+ def _put_rule(self, a, b):
+ self._put_rule_real(a, b)
+
+ def _put_rule_real(self, a, b):
+ if a > 0 and b > 0:
+ self.boxes.append(Box(self.h, self.v, a, b))
+
+ @_dispatch(138)
+ def _nop(self, _):
+ pass
+
+ @_dispatch(139, state=_dvistate.outer, args=('s4',)*11)
+ def _bop(self, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, p):
+ self.state = _dvistate.inpage
+ self.h = self.v = self.w = self.x = self.y = self.z = 0
+ self.stack = []
+ self.text = [] # list of Text objects
+ self.boxes = [] # list of Box objects
+
+ @_dispatch(140, state=_dvistate.inpage)
+ def _eop(self, _):
+ self.state = _dvistate.outer
+ del self.h, self.v, self.w, self.x, self.y, self.z, self.stack
+
+ @_dispatch(141, state=_dvistate.inpage)
+ def _push(self, _):
+ self.stack.append((self.h, self.v, self.w, self.x, self.y, self.z))
+
+ @_dispatch(142, state=_dvistate.inpage)
+ def _pop(self, _):
+ self.h, self.v, self.w, self.x, self.y, self.z = self.stack.pop()
+
+ @_dispatch(min=143, max=146, state=_dvistate.inpage, args=('slen1',))
+ def _right(self, b):
+ self.h += b
+
+ @_dispatch(min=147, max=151, state=_dvistate.inpage, args=('slen',))
+ def _right_w(self, new_w):
+ if new_w is not None:
+ self.w = new_w
+ self.h += self.w
+
+ @_dispatch(min=152, max=156, state=_dvistate.inpage, args=('slen',))
+ def _right_x(self, new_x):
+ if new_x is not None:
+ self.x = new_x
+ self.h += self.x
+
+ @_dispatch(min=157, max=160, state=_dvistate.inpage, args=('slen1',))
+ def _down(self, a):
+ self.v += a
+
+ @_dispatch(min=161, max=165, state=_dvistate.inpage, args=('slen',))
+ def _down_y(self, new_y):
+ if new_y is not None:
+ self.y = new_y
+ self.v += self.y
+
+ @_dispatch(min=166, max=170, state=_dvistate.inpage, args=('slen',))
+ def _down_z(self, new_z):
+ if new_z is not None:
+ self.z = new_z
+ self.v += self.z
+
+ @_dispatch(min=171, max=234, state=_dvistate.inpage)
+ def _fnt_num_immediate(self, k):
+ self.f = k
+
+ @_dispatch(min=235, max=238, state=_dvistate.inpage, args=('olen1',))
+ def _fnt_num(self, new_f):
+ self.f = new_f
+
+ @_dispatch(min=239, max=242, args=('ulen1',))
+ def _xxx(self, datalen):
+ special = self.file.read(datalen)
+ _log.debug(
+ 'Dvi._xxx: encountered special: %s',
+ ''.join([chr(ch) if 32 <= ch < 127 else '<%02x>' % ch
+ for ch in special]))
+
+ @_dispatch(min=243, max=246, args=('olen1', 'u4', 'u4', 'u4', 'u1', 'u1'))
+ def _fnt_def(self, k, c, s, d, a, l):
+ self._fnt_def_real(k, c, s, d, a, l)
+
+ def _fnt_def_real(self, k, c, s, d, a, l):
+ n = self.file.read(a + l)
+ fontname = n[-l:].decode('ascii')
+ try:
+ tfm = _tfmfile(fontname)
+ except FileNotFoundError as exc:
+ # Explicitly allow defining missing fonts for Vf support; we only
+ # register an error when trying to load a glyph from a missing font
+ # and throw that error in Dvi._read. For Vf, _finalize_packet
+ # checks whether a missing glyph has been used, and in that case
+ # skips the glyph definition.
+ self.fonts[k] = cbook._ExceptionInfo.from_exception(exc)
+ return
+ if c != 0 and tfm.checksum != 0 and c != tfm.checksum:
+ raise ValueError(f'tfm checksum mismatch: {n}')
+ try:
+ vf = _vffile(fontname)
+ except FileNotFoundError:
+ vf = None
+ self.fonts[k] = DviFont(scale=s, tfm=tfm, texname=n, vf=vf)
+
+ @_dispatch(247, state=_dvistate.pre, args=('u1', 'u4', 'u4', 'u4', 'u1'))
+ def _pre(self, i, num, den, mag, k):
+ self.file.read(k) # comment in the dvi file
+ if i != 2:
+ raise ValueError(f"Unknown dvi format {i}")
+ if num != 25400000 or den != 7227 * 2**16:
+ raise ValueError("Nonstandard units in dvi file")
+ # meaning: TeX always uses those exact values, so it
+ # should be enough for us to support those
+ # (There are 72.27 pt to an inch so 7227 pt =
+ # 7227 * 2**16 sp to 100 in. The numerator is multiplied
+ # by 10^5 to get units of 10**-7 meters.)
+ if mag != 1000:
+ raise ValueError("Nonstandard magnification in dvi file")
+ # meaning: LaTeX seems to frown on setting \mag, so
+ # I think we can assume this is constant
+ self.state = _dvistate.outer
+
+ @_dispatch(248, state=_dvistate.outer)
+ def _post(self, _):
+ self.state = _dvistate.post_post
+ # TODO: actually read the postamble and finale?
+ # currently post_post just triggers closing the file
+
+ @_dispatch(249)
+ def _post_post(self, _):
+ raise NotImplementedError
+
+ @_dispatch(min=250, max=255)
+ def _malformed(self, offset):
+ raise ValueError(f"unknown command: byte {250 + offset}")
+
+
+class DviFont:
+ """
+ Encapsulation of a font that a DVI file can refer to.
+
+ This class holds a font's texname and size, supports comparison,
+ and knows the widths of glyphs in the same units as the AFM file.
+ There are also internal attributes (for use by dviread.py) that
+ are *not* used for comparison.
+
+ The size is in Adobe points (converted from TeX points).
+
+ Parameters
+ ----------
+ scale : float
+ Factor by which the font is scaled from its natural size.
+ tfm : Tfm
+ TeX font metrics for this font
+ texname : bytes
+ Name of the font as used internally by TeX and friends, as an ASCII
+ bytestring. This is usually very different from any external font
+ names; `PsfontsMap` can be used to find the external name of the font.
+ vf : Vf
+ A TeX "virtual font" file, or None if this font is not virtual.
+
+ Attributes
+ ----------
+ texname : bytes
+ size : float
+ Size of the font in Adobe points, converted from the slightly
+ smaller TeX points.
+ widths : list
+ Widths of glyphs in glyph-space units, typically 1/1000ths of
+ the point size.
+
+ """
+ __slots__ = ('texname', 'size', 'widths', '_scale', '_vf', '_tfm')
+
+ def __init__(self, scale, tfm, texname, vf):
+ _api.check_isinstance(bytes, texname=texname)
+ self._scale = scale
+ self._tfm = tfm
+ self.texname = texname
+ self._vf = vf
+ self.size = scale * (72.0 / (72.27 * 2**16))
+ try:
+ nchars = max(tfm.width) + 1
+ except ValueError:
+ nchars = 0
+ self.widths = [(1000*tfm.width.get(char, 0)) >> 20
+ for char in range(nchars)]
+
+ def __eq__(self, other):
+ return (type(self) is type(other)
+ and self.texname == other.texname and self.size == other.size)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ return f"<{type(self).__name__}: {self.texname}>"
+
+ def _width_of(self, char):
+ """Width of char in dvi units."""
+ width = self._tfm.width.get(char, None)
+ if width is not None:
+ return _mul2012(width, self._scale)
+ _log.debug('No width for char %d in font %s.', char, self.texname)
+ return 0
+
+ def _height_depth_of(self, char):
+ """Height and depth of char in dvi units."""
+ result = []
+ for metric, name in ((self._tfm.height, "height"),
+ (self._tfm.depth, "depth")):
+ value = metric.get(char, None)
+ if value is None:
+ _log.debug('No %s for char %d in font %s',
+ name, char, self.texname)
+ result.append(0)
+ else:
+ result.append(_mul2012(value, self._scale))
+ # cmsyXX (symbols font) glyph 0 ("minus") has a nonzero descent
+ # so that TeX aligns equations properly
+ # (https://tex.stackexchange.com/q/526103/)
+ # but we actually care about the rasterization depth to align
+ # the dvipng-generated images.
+ if re.match(br'^cmsy\d+$', self.texname) and char == 0:
+ result[-1] = 0
+ return result
+
+
+class Vf(Dvi):
+ r"""
+ A virtual font (\*.vf file) containing subroutines for dvi files.
+
+ Parameters
+ ----------
+ filename : str or path-like
+
+ Notes
+ -----
+ The virtual font format is a derivative of dvi:
+ http://mirrors.ctan.org/info/knuth/virtual-fonts
+ This class reuses some of the machinery of `Dvi`
+ but replaces the `_read` loop and dispatch mechanism.
+
+ Examples
+ --------
+ ::
+
+ vf = Vf(filename)
+ glyph = vf[code]
+ glyph.text, glyph.boxes, glyph.width
+ """
+
+ def __init__(self, filename):
+ super().__init__(filename, 0)
+ try:
+ self._first_font = None
+ self._chars = {}
+ self._read()
+ finally:
+ self.close()
+
+ def __getitem__(self, code):
+ return self._chars[code]
+
+ def _read(self):
+ """
+ Read one page from the file. Return True if successful,
+ False if there were no more pages.
+ """
+ packet_char = packet_ends = None
+ packet_len = packet_width = None
+ while True:
+ byte = self.file.read(1)[0]
+ # If we are in a packet, execute the dvi instructions
+ if self.state is _dvistate.inpage:
+ byte_at = self.file.tell()-1
+ if byte_at == packet_ends:
+ self._finalize_packet(packet_char, packet_width)
+ packet_len = packet_char = packet_width = None
+ # fall through to out-of-packet code
+ elif byte_at > packet_ends:
+ raise ValueError("Packet length mismatch in vf file")
+ else:
+ if byte in (139, 140) or byte >= 243:
+ raise ValueError(f"Inappropriate opcode {byte} in vf file")
+ Dvi._dtable[byte](self, byte)
+ continue
+
+ # We are outside a packet
+ if byte < 242: # a short packet (length given by byte)
+ packet_len = byte
+ packet_char = self._read_arg(1)
+ packet_width = self._read_arg(3)
+ packet_ends = self._init_packet(byte)
+ self.state = _dvistate.inpage
+ elif byte == 242: # a long packet
+ packet_len = self._read_arg(4)
+ packet_char = self._read_arg(4)
+ packet_width = self._read_arg(4)
+ self._init_packet(packet_len)
+ elif 243 <= byte <= 246:
+ k = self._read_arg(byte - 242, byte == 246)
+ c = self._read_arg(4)
+ s = self._read_arg(4)
+ d = self._read_arg(4)
+ a = self._read_arg(1)
+ l = self._read_arg(1)
+ self._fnt_def_real(k, c, s, d, a, l)
+ if self._first_font is None:
+ self._first_font = k
+ elif byte == 247: # preamble
+ i = self._read_arg(1)
+ k = self._read_arg(1)
+ x = self.file.read(k)
+ cs = self._read_arg(4)
+ ds = self._read_arg(4)
+ self._pre(i, x, cs, ds)
+ elif byte == 248: # postamble (just some number of 248s)
+ break
+ else:
+ raise ValueError(f"Unknown vf opcode {byte}")
+
+ def _init_packet(self, pl):
+ if self.state != _dvistate.outer:
+ raise ValueError("Misplaced packet in vf file")
+ self.h = self.v = self.w = self.x = self.y = self.z = 0
+ self.stack = []
+ self.text = []
+ self.boxes = []
+ self.f = self._first_font
+ self._missing_font = None
+ return self.file.tell() + pl
+
+ def _finalize_packet(self, packet_char, packet_width):
+ if not self._missing_font: # Otherwise we don't have full glyph definition.
+ self._chars[packet_char] = Page(
+ text=self.text, boxes=self.boxes, width=packet_width,
+ height=None, descent=None)
+ self.state = _dvistate.outer
+
+ def _pre(self, i, x, cs, ds):
+ if self.state is not _dvistate.pre:
+ raise ValueError("pre command in middle of vf file")
+ if i != 202:
+ raise ValueError(f"Unknown vf format {i}")
+ if len(x):
+ _log.debug('vf file comment: %s', x)
+ self.state = _dvistate.outer
+ # cs = checksum, ds = design size
+
+
+def _mul2012(num1, num2):
+ """Multiply two numbers in 20.12 fixed point format."""
+ # Separated into a function because >> has surprising precedence
+ return (num1*num2) >> 20
+
+
+class Tfm:
+ """
+ A TeX Font Metric file.
+
+ This implementation covers only the bare minimum needed by the Dvi class.
+
+ Parameters
+ ----------
+ filename : str or path-like
+
+ Attributes
+ ----------
+ checksum : int
+ Used for verifying against the dvi file.
+ design_size : int
+ Design size of the font (unknown units)
+ width, height, depth : dict
+ Dimensions of each character, need to be scaled by the factor
+ specified in the dvi file. These are dicts because indexing may
+ not start from 0.
+ """
+ __slots__ = ('checksum', 'design_size', 'width', 'height', 'depth')
+
+ def __init__(self, filename):
+ _log.debug('opening tfm file %s', filename)
+ with open(filename, 'rb') as file:
+ header1 = file.read(24)
+ lh, bc, ec, nw, nh, nd = struct.unpack('!6H', header1[2:14])
+ _log.debug('lh=%d, bc=%d, ec=%d, nw=%d, nh=%d, nd=%d',
+ lh, bc, ec, nw, nh, nd)
+ header2 = file.read(4*lh)
+ self.checksum, self.design_size = struct.unpack('!2I', header2[:8])
+ # there is also encoding information etc.
+ char_info = file.read(4*(ec-bc+1))
+ widths = struct.unpack(f'!{nw}i', file.read(4*nw))
+ heights = struct.unpack(f'!{nh}i', file.read(4*nh))
+ depths = struct.unpack(f'!{nd}i', file.read(4*nd))
+ self.width = {}
+ self.height = {}
+ self.depth = {}
+ for idx, char in enumerate(range(bc, ec+1)):
+ byte0 = char_info[4*idx]
+ byte1 = char_info[4*idx+1]
+ self.width[char] = widths[byte0]
+ self.height[char] = heights[byte1 >> 4]
+ self.depth[char] = depths[byte1 & 0xf]
+
+
+PsFont = namedtuple('PsFont', 'texname psname effects encoding filename')
+
+
+class PsfontsMap:
+ """
+ A psfonts.map formatted file, mapping TeX fonts to PS fonts.
+
+ Parameters
+ ----------
+ filename : str or path-like
+
+ Notes
+ -----
+ For historical reasons, TeX knows many Type-1 fonts by different
+ names than the outside world. (For one thing, the names have to
+ fit in eight characters.) Also, TeX's native fonts are not Type-1
+ but Metafont, which is nontrivial to convert to PostScript except
+ as a bitmap. While high-quality conversions to Type-1 format exist
+ and are shipped with modern TeX distributions, we need to know
+ which Type-1 fonts are the counterparts of which native fonts. For
+ these reasons a mapping is needed from internal font names to font
+ file names.
+
+ A texmf tree typically includes mapping files called e.g.
+ :file:`psfonts.map`, :file:`pdftex.map`, or :file:`dvipdfm.map`.
+ The file :file:`psfonts.map` is used by :program:`dvips`,
+ :file:`pdftex.map` by :program:`pdfTeX`, and :file:`dvipdfm.map`
+ by :program:`dvipdfm`. :file:`psfonts.map` might avoid embedding
+ the 35 PostScript fonts (i.e., have no filename for them, as in
+ the Times-Bold example above), while the pdf-related files perhaps
+ only avoid the "Base 14" pdf fonts. But the user may have
+ configured these files differently.
+
+ Examples
+ --------
+ >>> map = PsfontsMap(find_tex_file('pdftex.map'))
+ >>> entry = map[b'ptmbo8r']
+ >>> entry.texname
+ b'ptmbo8r'
+ >>> entry.psname
+ b'Times-Bold'
+ >>> entry.encoding
+ '/usr/local/texlive/2008/texmf-dist/fonts/enc/dvips/base/8r.enc'
+ >>> entry.effects
+ {'slant': 0.16700000000000001}
+ >>> entry.filename
+ """
+ __slots__ = ('_filename', '_unparsed', '_parsed')
+
+ # Create a filename -> PsfontsMap cache, so that calling
+ # `PsfontsMap(filename)` with the same filename a second time immediately
+ # returns the same object.
+ @lru_cache
+ def __new__(cls, filename):
+ self = object.__new__(cls)
+ self._filename = os.fsdecode(filename)
+ # Some TeX distributions have enormous pdftex.map files which would
+ # take hundreds of milliseconds to parse, but it is easy enough to just
+ # store the unparsed lines (keyed by the first word, which is the
+ # texname) and parse them on-demand.
+ with open(filename, 'rb') as file:
+ self._unparsed = {}
+ for line in file:
+ tfmname = line.split(b' ', 1)[0]
+ self._unparsed.setdefault(tfmname, []).append(line)
+ self._parsed = {}
+ return self
+
+ def __getitem__(self, texname):
+ assert isinstance(texname, bytes)
+ if texname in self._unparsed:
+ for line in self._unparsed.pop(texname):
+ if self._parse_and_cache_line(line):
+ break
+ try:
+ return self._parsed[texname]
+ except KeyError:
+ raise LookupError(
+ f"An associated PostScript font (required by Matplotlib) "
+ f"could not be found for TeX font {texname.decode('ascii')!r} "
+ f"in {self._filename!r}; this problem can often be solved by "
+ f"installing a suitable PostScript font package in your TeX "
+ f"package manager") from None
+
+ def _parse_and_cache_line(self, line):
+ """
+ Parse a line in the font mapping file.
+
+ The format is (partially) documented at
+ http://mirrors.ctan.org/systems/doc/pdftex/manual/pdftex-a.pdf
+ https://tug.org/texinfohtml/dvips.html#psfonts_002emap
+ Each line can have the following fields:
+
+ - tfmname (first, only required field),
+ - psname (defaults to tfmname, must come immediately after tfmname if
+ present),
+ - fontflags (integer, must come immediately after psname if present,
+ ignored by us),
+ - special (SlantFont and ExtendFont, only field that is double-quoted),
+ - fontfile, encodingfile (optional, prefixed by <, <<, or <[; << always
+ precedes a font, <[ always precedes an encoding, < can precede either
+ but then an encoding file must have extension .enc; < and << also
+ request different font subsetting behaviors but we ignore that; < can
+ be separated from the filename by whitespace).
+
+ special, fontfile, and encodingfile can appear in any order.
+ """
+ # If the map file specifies multiple encodings for a font, we
+ # follow pdfTeX in choosing the last one specified. Such
+ # entries are probably mistakes but they have occurred.
+ # https://tex.stackexchange.com/q/10826/
+
+ if not line or line.startswith((b" ", b"%", b"*", b";", b"#")):
+ return
+ tfmname = basename = special = encodingfile = fontfile = None
+ is_subsetted = is_t1 = is_truetype = False
+ matches = re.finditer(br'"([^"]*)(?:"|$)|(\S+)', line)
+ for match in matches:
+ quoted, unquoted = match.groups()
+ if unquoted:
+ if unquoted.startswith(b"<<"): # font
+ fontfile = unquoted[2:]
+ elif unquoted.startswith(b"<["): # encoding
+ encodingfile = unquoted[2:]
+ elif unquoted.startswith(b"<"): # font or encoding
+ word = (
+ # foo
+ unquoted[1:]
+ # < by itself => read the next word
+ or next(filter(None, next(matches).groups())))
+ if word.endswith(b".enc"):
+ encodingfile = word
+ else:
+ fontfile = word
+ is_subsetted = True
+ elif tfmname is None:
+ tfmname = unquoted
+ elif basename is None:
+ basename = unquoted
+ elif quoted:
+ special = quoted
+ effects = {}
+ if special:
+ words = reversed(special.split())
+ for word in words:
+ if word == b"SlantFont":
+ effects["slant"] = float(next(words))
+ elif word == b"ExtendFont":
+ effects["extend"] = float(next(words))
+
+ # Verify some properties of the line that would cause it to be ignored
+ # otherwise.
+ if fontfile is not None:
+ if fontfile.endswith((b".ttf", b".ttc")):
+ is_truetype = True
+ elif not fontfile.endswith(b".otf"):
+ is_t1 = True
+ elif basename is not None:
+ is_t1 = True
+ if is_truetype and is_subsetted and encodingfile is None:
+ return
+ if not is_t1 and ("slant" in effects or "extend" in effects):
+ return
+ if abs(effects.get("slant", 0)) > 1:
+ return
+ if abs(effects.get("extend", 0)) > 2:
+ return
+
+ if basename is None:
+ basename = tfmname
+ if encodingfile is not None:
+ encodingfile = find_tex_file(encodingfile)
+ if fontfile is not None:
+ fontfile = find_tex_file(fontfile)
+ self._parsed[tfmname] = PsFont(
+ texname=tfmname, psname=basename, effects=effects,
+ encoding=encodingfile, filename=fontfile)
+ return True
+
+
+def _parse_enc(path):
+ r"""
+ Parse a \*.enc file referenced from a psfonts.map style file.
+
+ The format supported by this function is a tiny subset of PostScript.
+
+ Parameters
+ ----------
+ path : `os.PathLike`
+
+ Returns
+ -------
+ list
+ The nth entry of the list is the PostScript glyph name of the nth
+ glyph.
+ """
+ no_comments = re.sub("%.*", "", Path(path).read_text(encoding="ascii"))
+ array = re.search(r"(?s)\[(.*)\]", no_comments).group(1)
+ lines = [line for line in array.split() if line]
+ if all(line.startswith("/") for line in lines):
+ return [line[1:] for line in lines]
+ else:
+ raise ValueError(f"Failed to parse {path} as Postscript encoding")
+
+
+class _LuatexKpsewhich:
+ @lru_cache # A singleton.
+ def __new__(cls):
+ self = object.__new__(cls)
+ self._proc = self._new_proc()
+ return self
+
+ def _new_proc(self):
+ return subprocess.Popen(
+ ["luatex", "--luaonly",
+ str(cbook._get_data_path("kpsewhich.lua"))],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+
+ def search(self, filename):
+ if self._proc.poll() is not None: # Dead, restart it.
+ self._proc = self._new_proc()
+ self._proc.stdin.write(os.fsencode(filename) + b"\n")
+ self._proc.stdin.flush()
+ out = self._proc.stdout.readline().rstrip()
+ return None if out == b"nil" else os.fsdecode(out)
+
+
+@lru_cache
+def find_tex_file(filename):
+ """
+ Find a file in the texmf tree using kpathsea_.
+
+ The kpathsea library, provided by most existing TeX distributions, both
+ on Unix-like systems and on Windows (MikTeX), is invoked via a long-lived
+ luatex process if luatex is installed, or via kpsewhich otherwise.
+
+ .. _kpathsea: https://www.tug.org/kpathsea/
+
+ Parameters
+ ----------
+ filename : str or path-like
+
+ Raises
+ ------
+ FileNotFoundError
+ If the file is not found.
+ """
+
+ # we expect these to always be ascii encoded, but use utf-8
+ # out of caution
+ if isinstance(filename, bytes):
+ filename = filename.decode('utf-8', errors='replace')
+
+ try:
+ lk = _LuatexKpsewhich()
+ except FileNotFoundError:
+ lk = None # Fallback to directly calling kpsewhich, as below.
+
+ if lk:
+ path = lk.search(filename)
+ else:
+ if sys.platform == 'win32':
+ # On Windows only, kpathsea can use utf-8 for cmd args and output.
+ # The `command_line_encoding` environment variable is set to force
+ # it to always use utf-8 encoding. See Matplotlib issue #11848.
+ kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'},
+ 'encoding': 'utf-8'}
+ else: # On POSIX, run through the equivalent of os.fsdecode().
+ kwargs = {'encoding': sys.getfilesystemencoding(),
+ 'errors': 'surrogateescape'}
+
+ try:
+ path = (cbook._check_and_log_subprocess(['kpsewhich', filename],
+ _log, **kwargs)
+ .rstrip('\n'))
+ except (FileNotFoundError, RuntimeError):
+ path = None
+
+ if path:
+ return path
+ else:
+ raise FileNotFoundError(
+ f"Matplotlib's TeX implementation searched for a file named "
+ f"{filename!r} in your texmf tree, but could not find it")
+
+
+@lru_cache
+def _fontfile(cls, suffix, texname):
+ return cls(find_tex_file(texname + suffix))
+
+
+_tfmfile = partial(_fontfile, Tfm, ".tfm")
+_vffile = partial(_fontfile, Vf, ".vf")
+
+
+if __name__ == '__main__':
+ from argparse import ArgumentParser
+ import itertools
+
+ parser = ArgumentParser()
+ parser.add_argument("filename")
+ parser.add_argument("dpi", nargs="?", type=float, default=None)
+ args = parser.parse_args()
+ with Dvi(args.filename, args.dpi) as dvi:
+ fontmap = PsfontsMap(find_tex_file('pdftex.map'))
+ for page in dvi:
+ print(f"=== new page === "
+ f"(w: {page.width}, h: {page.height}, d: {page.descent})")
+ for font, group in itertools.groupby(
+ page.text, lambda text: text.font):
+ print(f"font: {font.texname.decode('latin-1')!r}\t"
+ f"scale: {font._scale / 2 ** 20}")
+ print("x", "y", "glyph", "chr", "w", "(glyphs)", sep="\t")
+ for text in group:
+ print(text.x, text.y, text.glyph,
+ chr(text.glyph) if chr(text.glyph).isprintable()
+ else ".",
+ text.width, sep="\t")
+ if page.boxes:
+ print("x", "y", "h", "w", "", "(boxes)", sep="\t")
+ for box in page.boxes:
+ print(box.x, box.y, box.height, box.width, sep="\t")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..8073ee9fbff852bc3583cb79ade6be5aab7e58d5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.pyi
@@ -0,0 +1,89 @@
+from pathlib import Path
+import io
+import os
+from enum import Enum
+from collections.abc import Generator
+
+from typing import NamedTuple
+from typing_extensions import Self # < Py 3.11
+
+class _dvistate(Enum):
+ pre = ...
+ outer = ...
+ inpage = ...
+ post_post = ...
+ finale = ...
+
+class Page(NamedTuple):
+ text: list[Text]
+ boxes: list[Box]
+ height: int
+ width: int
+ descent: int
+
+class Box(NamedTuple):
+ x: int
+ y: int
+ height: int
+ width: int
+
+class Text(NamedTuple):
+ x: int
+ y: int
+ font: DviFont
+ glyph: int
+ width: int
+ @property
+ def font_path(self) -> Path: ...
+ @property
+ def font_size(self) -> float: ...
+ @property
+ def font_effects(self) -> dict[str, float]: ...
+ @property
+ def glyph_name_or_index(self) -> int | str: ...
+
+class Dvi:
+ file: io.BufferedReader
+ dpi: float | None
+ fonts: dict[int, DviFont]
+ state: _dvistate
+ def __init__(self, filename: str | os.PathLike, dpi: float | None) -> None: ...
+ def __enter__(self) -> Self: ...
+ def __exit__(self, etype, evalue, etrace) -> None: ...
+ def __iter__(self) -> Generator[Page, None, None]: ...
+ def close(self) -> None: ...
+
+class DviFont:
+ texname: bytes
+ size: float
+ widths: list[int]
+ def __init__(
+ self, scale: float, tfm: Tfm, texname: bytes, vf: Vf | None
+ ) -> None: ...
+ def __eq__(self, other: object) -> bool: ...
+ def __ne__(self, other: object) -> bool: ...
+
+class Vf(Dvi):
+ def __init__(self, filename: str | os.PathLike) -> None: ...
+ def __getitem__(self, code: int) -> Page: ...
+
+class Tfm:
+ checksum: int
+ design_size: int
+ width: dict[int, int]
+ height: dict[int, int]
+ depth: dict[int, int]
+ def __init__(self, filename: str | os.PathLike) -> None: ...
+
+class PsFont(NamedTuple):
+ texname: bytes
+ psname: bytes
+ effects: dict[str, float]
+ encoding: None | bytes
+ filename: str
+
+class PsfontsMap:
+ def __new__(cls, filename: str | os.PathLike) -> Self: ...
+ def __getitem__(self, texname: bytes) -> PsFont: ...
+
+def find_tex_file(filename: str | os.PathLike) -> str: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.py
new file mode 100644
index 0000000000000000000000000000000000000000..08914172718989e02025312dc5ba86bad5559989
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.py
@@ -0,0 +1,3726 @@
+"""
+`matplotlib.figure` implements the following classes:
+
+`Figure`
+ Top level `~matplotlib.artist.Artist`, which holds all plot elements.
+ Many methods are implemented in `FigureBase`.
+
+`SubFigure`
+ A logical figure inside a figure, usually added to a figure (or parent `SubFigure`)
+ with `Figure.add_subfigure` or `Figure.subfigures` methods.
+
+Figures are typically created using pyplot methods `~.pyplot.figure`,
+`~.pyplot.subplots`, and `~.pyplot.subplot_mosaic`.
+
+.. plot::
+ :include-source:
+
+ fig, ax = plt.subplots(figsize=(2, 2), facecolor='lightskyblue',
+ layout='constrained')
+ fig.suptitle('Figure')
+ ax.set_title('Axes', loc='left', fontstyle='oblique', fontsize='medium')
+
+Some situations call for directly instantiating a `~.figure.Figure` class,
+usually inside an application of some sort (see :ref:`user_interfaces` for a
+list of examples) . More information about Figures can be found at
+:ref:`figure-intro`.
+"""
+
+from contextlib import ExitStack
+import inspect
+import itertools
+import functools
+import logging
+from numbers import Integral
+import threading
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _blocking_input, backend_bases, _docstring, projections
+from matplotlib.artist import (
+ Artist, allow_rasterization, _finalize_rasterization)
+from matplotlib.backend_bases import (
+ DrawEvent, FigureCanvasBase, NonGuiException, MouseButton, _get_renderer)
+import matplotlib._api as _api
+import matplotlib.cbook as cbook
+import matplotlib.colorbar as cbar
+import matplotlib.image as mimage
+
+from matplotlib.axes import Axes
+from matplotlib.gridspec import GridSpec, SubplotParams
+from matplotlib.layout_engine import (
+ ConstrainedLayoutEngine, TightLayoutEngine, LayoutEngine,
+ PlaceHolderLayoutEngine
+)
+import matplotlib.legend as mlegend
+from matplotlib.patches import Rectangle
+from matplotlib.text import Text
+from matplotlib.transforms import (Affine2D, Bbox, BboxTransformTo,
+ TransformedBbox)
+
+_log = logging.getLogger(__name__)
+
+
+def _stale_figure_callback(self, val):
+ if (fig := self.get_figure(root=False)) is not None:
+ fig.stale = val
+
+
+class _AxesStack:
+ """
+ Helper class to track Axes in a figure.
+
+ Axes are tracked both in the order in which they have been added
+ (``self._axes`` insertion/iteration order) and in the separate "gca" stack
+ (which is the index to which they map in the ``self._axes`` dict).
+ """
+
+ def __init__(self):
+ self._axes = {} # Mapping of Axes to "gca" order.
+ self._counter = itertools.count()
+
+ def as_list(self):
+ """List the Axes that have been added to the figure."""
+ return [*self._axes] # This relies on dict preserving order.
+
+ def remove(self, a):
+ """Remove the Axes from the stack."""
+ self._axes.pop(a)
+
+ def bubble(self, a):
+ """Move an Axes, which must already exist in the stack, to the top."""
+ if a not in self._axes:
+ raise ValueError("Axes has not been added yet")
+ self._axes[a] = next(self._counter)
+
+ def add(self, a):
+ """Add an Axes to the stack, ignoring it if already present."""
+ if a not in self._axes:
+ self._axes[a] = next(self._counter)
+
+ def current(self):
+ """Return the active Axes, or None if the stack is empty."""
+ return max(self._axes, key=self._axes.__getitem__, default=None)
+
+ def __getstate__(self):
+ return {
+ **vars(self),
+ "_counter": max(self._axes.values(), default=0)
+ }
+
+ def __setstate__(self, state):
+ next_counter = state.pop('_counter')
+ vars(self).update(state)
+ self._counter = itertools.count(next_counter)
+
+
+class FigureBase(Artist):
+ """
+ Base class for `.Figure` and `.SubFigure` containing the methods that add
+ artists to the figure or subfigure, create Axes, etc.
+ """
+ def __init__(self, **kwargs):
+ super().__init__()
+ # remove the non-figure artist _axes property
+ # as it makes no sense for a figure to be _in_ an Axes
+ # this is used by the property methods in the artist base class
+ # which are over-ridden in this class
+ del self._axes
+
+ self._suptitle = None
+ self._supxlabel = None
+ self._supylabel = None
+
+ # groupers to keep track of x, y labels and title we want to align.
+ # see self.align_xlabels, self.align_ylabels,
+ # self.align_titles, and axis._get_tick_boxes_siblings
+ self._align_label_groups = {
+ "x": cbook.Grouper(),
+ "y": cbook.Grouper(),
+ "title": cbook.Grouper()
+ }
+
+ self._localaxes = [] # track all Axes
+ self.artists = []
+ self.lines = []
+ self.patches = []
+ self.texts = []
+ self.images = []
+ self.legends = []
+ self.subfigs = []
+ self.stale = True
+ self.suppressComposite = None
+ self.set(**kwargs)
+
+ def _get_draw_artists(self, renderer):
+ """Also runs apply_aspect"""
+ artists = self.get_children()
+
+ artists.remove(self.patch)
+ artists = sorted(
+ (artist for artist in artists if not artist.get_animated()),
+ key=lambda artist: artist.get_zorder())
+ for ax in self._localaxes:
+ locator = ax.get_axes_locator()
+ ax.apply_aspect(locator(ax, renderer) if locator else None)
+
+ for child in ax.get_children():
+ if hasattr(child, 'apply_aspect'):
+ locator = child.get_axes_locator()
+ child.apply_aspect(
+ locator(child, renderer) if locator else None)
+ return artists
+
+ def autofmt_xdate(
+ self, bottom=0.2, rotation=30, ha='right', which='major'):
+ """
+ Date ticklabels often overlap, so it is useful to rotate them
+ and right align them. Also, a common use case is a number of
+ subplots with shared x-axis where the x-axis is date data. The
+ ticklabels are often long, and it helps to rotate them on the
+ bottom subplot and turn them off on other subplots, as well as
+ turn off xlabels.
+
+ Parameters
+ ----------
+ bottom : float, default: 0.2
+ The bottom of the subplots for `subplots_adjust`.
+ rotation : float, default: 30 degrees
+ The rotation angle of the xtick labels in degrees.
+ ha : {'left', 'center', 'right'}, default: 'right'
+ The horizontal alignment of the xticklabels.
+ which : {'major', 'minor', 'both'}, default: 'major'
+ Selects which ticklabels to rotate.
+ """
+ _api.check_in_list(['major', 'minor', 'both'], which=which)
+ axes = [ax for ax in self.axes if ax._label != '']
+ allsubplots = all(ax.get_subplotspec() for ax in axes)
+ if len(axes) == 1:
+ for label in self.axes[0].get_xticklabels(which=which):
+ label.set_ha(ha)
+ label.set_rotation(rotation)
+ else:
+ if allsubplots:
+ for ax in axes:
+ if ax.get_subplotspec().is_last_row():
+ for label in ax.get_xticklabels(which=which):
+ label.set_ha(ha)
+ label.set_rotation(rotation)
+ else:
+ for label in ax.get_xticklabels(which=which):
+ label.set_visible(False)
+ ax.set_xlabel('')
+
+ engine = self.get_layout_engine()
+ if allsubplots and (engine is None or engine.adjust_compatible):
+ self.subplots_adjust(bottom=bottom)
+ self.stale = True
+
+ def get_children(self):
+ """Get a list of artists contained in the figure."""
+ return [self.patch,
+ *self.artists,
+ *self._localaxes,
+ *self.lines,
+ *self.patches,
+ *self.texts,
+ *self.images,
+ *self.legends,
+ *self.subfigs]
+
+ def get_figure(self, root=None):
+ """
+ Return the `.Figure` or `.SubFigure` instance the (Sub)Figure belongs to.
+
+ Parameters
+ ----------
+ root : bool, default=True
+ If False, return the (Sub)Figure this artist is on. If True,
+ return the root Figure for a nested tree of SubFigures.
+
+ .. deprecated:: 3.10
+
+ From version 3.12 *root* will default to False.
+ """
+ if self._root_figure is self:
+ # Top level Figure
+ return self
+
+ if self._parent is self._root_figure:
+ # Return early to prevent the deprecation warning when *root* does not
+ # matter
+ return self._parent
+
+ if root is None:
+ # When deprecation expires, consider removing the docstring and just
+ # inheriting the one from Artist.
+ message = ('From Matplotlib 3.12 SubFigure.get_figure will by default '
+ 'return the direct parent figure, which may be a SubFigure. '
+ 'To suppress this warning, pass the root parameter. Pass '
+ '`True` to maintain the old behavior and `False` to opt-in to '
+ 'the future behavior.')
+ _api.warn_deprecated('3.10', message=message)
+ root = True
+
+ if root:
+ return self._root_figure
+
+ return self._parent
+
+ def set_figure(self, fig):
+ """
+ .. deprecated:: 3.10
+ Currently this method will raise an exception if *fig* is anything other
+ than the root `.Figure` this (Sub)Figure is on. In future it will always
+ raise an exception.
+ """
+ no_switch = ("The parent and root figures of a (Sub)Figure are set at "
+ "instantiation and cannot be changed.")
+ if fig is self._root_figure:
+ _api.warn_deprecated(
+ "3.10",
+ message=(f"{no_switch} From Matplotlib 3.12 this operation will raise "
+ "an exception."))
+ return
+
+ raise ValueError(no_switch)
+
+ figure = property(functools.partial(get_figure, root=True), set_figure,
+ doc=("The root `Figure`. To get the parent of a `SubFigure`, "
+ "use the `get_figure` method."))
+
+ def contains(self, mouseevent):
+ """
+ Test whether the mouse event occurred on the figure.
+
+ Returns
+ -------
+ bool, {}
+ """
+ if self._different_canvas(mouseevent):
+ return False, {}
+ inside = self.bbox.contains(mouseevent.x, mouseevent.y)
+ return inside, {}
+
+ def get_window_extent(self, renderer=None):
+ # docstring inherited
+ return self.bbox
+
+ def _suplabels(self, t, info, **kwargs):
+ """
+ Add a centered %(name)s to the figure.
+
+ Parameters
+ ----------
+ t : str
+ The %(name)s text.
+ x : float, default: %(x0)s
+ The x location of the text in figure coordinates.
+ y : float, default: %(y0)s
+ The y location of the text in figure coordinates.
+ horizontalalignment, ha : {'center', 'left', 'right'}, default: %(ha)s
+ The horizontal alignment of the text relative to (*x*, *y*).
+ verticalalignment, va : {'top', 'center', 'bottom', 'baseline'}, \
+default: %(va)s
+ The vertical alignment of the text relative to (*x*, *y*).
+ fontsize, size : default: :rc:`figure.%(rc)ssize`
+ The font size of the text. See `.Text.set_size` for possible
+ values.
+ fontweight, weight : default: :rc:`figure.%(rc)sweight`
+ The font weight of the text. See `.Text.set_weight` for possible
+ values.
+
+ Returns
+ -------
+ text
+ The `.Text` instance of the %(name)s.
+
+ Other Parameters
+ ----------------
+ fontproperties : None or dict, optional
+ A dict of font properties. If *fontproperties* is given the
+ default values for font size and weight are taken from the
+ `.FontProperties` defaults. :rc:`figure.%(rc)ssize` and
+ :rc:`figure.%(rc)sweight` are ignored in this case.
+
+ **kwargs
+ Additional kwargs are `matplotlib.text.Text` properties.
+ """
+
+ x = kwargs.pop('x', None)
+ y = kwargs.pop('y', None)
+ if info['name'] in ['_supxlabel', '_suptitle']:
+ autopos = y is None
+ elif info['name'] == '_supylabel':
+ autopos = x is None
+ if x is None:
+ x = info['x0']
+ if y is None:
+ y = info['y0']
+
+ kwargs = cbook.normalize_kwargs(kwargs, Text)
+ kwargs.setdefault('horizontalalignment', info['ha'])
+ kwargs.setdefault('verticalalignment', info['va'])
+ kwargs.setdefault('rotation', info['rotation'])
+
+ if 'fontproperties' not in kwargs:
+ kwargs.setdefault('fontsize', mpl.rcParams[info['size']])
+ kwargs.setdefault('fontweight', mpl.rcParams[info['weight']])
+
+ suplab = getattr(self, info['name'])
+ if suplab is not None:
+ suplab.set_text(t)
+ suplab.set_position((x, y))
+ suplab.set(**kwargs)
+ else:
+ suplab = self.text(x, y, t, **kwargs)
+ setattr(self, info['name'], suplab)
+ suplab._autopos = autopos
+ self.stale = True
+ return suplab
+
+ @_docstring.Substitution(x0=0.5, y0=0.98, name='super title', ha='center',
+ va='top', rc='title')
+ @_docstring.copy(_suplabels)
+ def suptitle(self, t, **kwargs):
+ # docstring from _suplabels...
+ info = {'name': '_suptitle', 'x0': 0.5, 'y0': 0.98,
+ 'ha': 'center', 'va': 'top', 'rotation': 0,
+ 'size': 'figure.titlesize', 'weight': 'figure.titleweight'}
+ return self._suplabels(t, info, **kwargs)
+
+ def get_suptitle(self):
+ """Return the suptitle as string or an empty string if not set."""
+ text_obj = self._suptitle
+ return "" if text_obj is None else text_obj.get_text()
+
+ @_docstring.Substitution(x0=0.5, y0=0.01, name='super xlabel', ha='center',
+ va='bottom', rc='label')
+ @_docstring.copy(_suplabels)
+ def supxlabel(self, t, **kwargs):
+ # docstring from _suplabels...
+ info = {'name': '_supxlabel', 'x0': 0.5, 'y0': 0.01,
+ 'ha': 'center', 'va': 'bottom', 'rotation': 0,
+ 'size': 'figure.labelsize', 'weight': 'figure.labelweight'}
+ return self._suplabels(t, info, **kwargs)
+
+ def get_supxlabel(self):
+ """Return the supxlabel as string or an empty string if not set."""
+ text_obj = self._supxlabel
+ return "" if text_obj is None else text_obj.get_text()
+
+ @_docstring.Substitution(x0=0.02, y0=0.5, name='super ylabel', ha='left',
+ va='center', rc='label')
+ @_docstring.copy(_suplabels)
+ def supylabel(self, t, **kwargs):
+ # docstring from _suplabels...
+ info = {'name': '_supylabel', 'x0': 0.02, 'y0': 0.5,
+ 'ha': 'left', 'va': 'center', 'rotation': 'vertical',
+ 'rotation_mode': 'anchor', 'size': 'figure.labelsize',
+ 'weight': 'figure.labelweight'}
+ return self._suplabels(t, info, **kwargs)
+
+ def get_supylabel(self):
+ """Return the supylabel as string or an empty string if not set."""
+ text_obj = self._supylabel
+ return "" if text_obj is None else text_obj.get_text()
+
+ def get_edgecolor(self):
+ """Get the edge color of the Figure rectangle."""
+ return self.patch.get_edgecolor()
+
+ def get_facecolor(self):
+ """Get the face color of the Figure rectangle."""
+ return self.patch.get_facecolor()
+
+ def get_frameon(self):
+ """
+ Return the figure's background patch visibility, i.e.
+ whether the figure background will be drawn. Equivalent to
+ ``Figure.patch.get_visible()``.
+ """
+ return self.patch.get_visible()
+
+ def set_linewidth(self, linewidth):
+ """
+ Set the line width of the Figure rectangle.
+
+ Parameters
+ ----------
+ linewidth : number
+ """
+ self.patch.set_linewidth(linewidth)
+
+ def get_linewidth(self):
+ """
+ Get the line width of the Figure rectangle.
+ """
+ return self.patch.get_linewidth()
+
+ def set_edgecolor(self, color):
+ """
+ Set the edge color of the Figure rectangle.
+
+ Parameters
+ ----------
+ color : :mpltype:`color`
+ """
+ self.patch.set_edgecolor(color)
+
+ def set_facecolor(self, color):
+ """
+ Set the face color of the Figure rectangle.
+
+ Parameters
+ ----------
+ color : :mpltype:`color`
+ """
+ self.patch.set_facecolor(color)
+
+ def set_frameon(self, b):
+ """
+ Set the figure's background patch visibility, i.e.
+ whether the figure background will be drawn. Equivalent to
+ ``Figure.patch.set_visible()``.
+
+ Parameters
+ ----------
+ b : bool
+ """
+ self.patch.set_visible(b)
+ self.stale = True
+
+ frameon = property(get_frameon, set_frameon)
+
+ def add_artist(self, artist, clip=False):
+ """
+ Add an `.Artist` to the figure.
+
+ Usually artists are added to `~.axes.Axes` objects using
+ `.Axes.add_artist`; this method can be used in the rare cases where
+ one needs to add artists directly to the figure instead.
+
+ Parameters
+ ----------
+ artist : `~matplotlib.artist.Artist`
+ The artist to add to the figure. If the added artist has no
+ transform previously set, its transform will be set to
+ ``figure.transSubfigure``.
+ clip : bool, default: False
+ Whether the added artist should be clipped by the figure patch.
+
+ Returns
+ -------
+ `~matplotlib.artist.Artist`
+ The added artist.
+ """
+ artist.set_figure(self)
+ self.artists.append(artist)
+ artist._remove_method = self.artists.remove
+
+ if not artist.is_transform_set():
+ artist.set_transform(self.transSubfigure)
+
+ if clip and artist.get_clip_path() is None:
+ artist.set_clip_path(self.patch)
+
+ self.stale = True
+ return artist
+
+ @_docstring.interpd
+ def add_axes(self, *args, **kwargs):
+ """
+ Add an `~.axes.Axes` to the figure.
+
+ Call signatures::
+
+ add_axes(rect, projection=None, polar=False, **kwargs)
+ add_axes(ax)
+
+ Parameters
+ ----------
+ rect : tuple (left, bottom, width, height)
+ The dimensions (left, bottom, width, height) of the new
+ `~.axes.Axes`. All quantities are in fractions of figure width and
+ height.
+
+ projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \
+'polar', 'rectilinear', str}, optional
+ The projection type of the `~.axes.Axes`. *str* is the name of
+ a custom projection, see `~matplotlib.projections`. The default
+ None results in a 'rectilinear' projection.
+
+ polar : bool, default: False
+ If True, equivalent to projection='polar'.
+
+ axes_class : subclass type of `~.axes.Axes`, optional
+ The `.axes.Axes` subclass that is instantiated. This parameter
+ is incompatible with *projection* and *polar*. See
+ :ref:`axisartist_users-guide-index` for examples.
+
+ sharex, sharey : `~matplotlib.axes.Axes`, optional
+ Share the x or y `~matplotlib.axis` with sharex and/or sharey.
+ The axis will have the same limits, ticks, and scale as the axis
+ of the shared Axes.
+
+ label : str
+ A label for the returned Axes.
+
+ Returns
+ -------
+ `~.axes.Axes`, or a subclass of `~.axes.Axes`
+ The returned Axes class depends on the projection used. It is
+ `~.axes.Axes` if rectilinear projection is used and
+ `.projections.polar.PolarAxes` if polar projection is used.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ This method also takes the keyword arguments for
+ the returned Axes class. The keyword arguments for the
+ rectilinear Axes class `~.axes.Axes` can be found in
+ the following table but there might also be other keyword
+ arguments if another projection is used, see the actual Axes
+ class.
+
+ %(Axes:kwdoc)s
+
+ Notes
+ -----
+ In rare circumstances, `.add_axes` may be called with a single
+ argument, an Axes instance already created in the present figure but
+ not in the figure's list of Axes.
+
+ See Also
+ --------
+ .Figure.add_subplot
+ .pyplot.subplot
+ .pyplot.axes
+ .Figure.subplots
+ .pyplot.subplots
+
+ Examples
+ --------
+ Some simple examples::
+
+ rect = l, b, w, h
+ fig = plt.figure()
+ fig.add_axes(rect)
+ fig.add_axes(rect, frameon=False, facecolor='g')
+ fig.add_axes(rect, polar=True)
+ ax = fig.add_axes(rect, projection='polar')
+ fig.delaxes(ax)
+ fig.add_axes(ax)
+ """
+
+ if not len(args) and 'rect' not in kwargs:
+ raise TypeError("add_axes() missing 1 required positional argument: 'rect'")
+ elif 'rect' in kwargs:
+ if len(args):
+ raise TypeError("add_axes() got multiple values for argument 'rect'")
+ args = (kwargs.pop('rect'), )
+ if len(args) != 1:
+ raise _api.nargs_error("add_axes", 1, len(args))
+
+ if isinstance(args[0], Axes):
+ a, = args
+ key = a._projection_init
+ if a.get_figure(root=False) is not self:
+ raise ValueError(
+ "The Axes must have been created in the present figure")
+ else:
+ rect, = args
+ if not np.isfinite(rect).all():
+ raise ValueError(f'all entries in rect must be finite not {rect}')
+ projection_class, pkw = self._process_projection_requirements(**kwargs)
+
+ # create the new Axes using the Axes class given
+ a = projection_class(self, rect, **pkw)
+ key = (projection_class, pkw)
+
+ return self._add_axes_internal(a, key)
+
+ @_docstring.interpd
+ def add_subplot(self, *args, **kwargs):
+ """
+ Add an `~.axes.Axes` to the figure as part of a subplot arrangement.
+
+ Call signatures::
+
+ add_subplot(nrows, ncols, index, **kwargs)
+ add_subplot(pos, **kwargs)
+ add_subplot(ax)
+ add_subplot()
+
+ Parameters
+ ----------
+ *args : int, (int, int, *index*), or `.SubplotSpec`, default: (1, 1, 1)
+ The position of the subplot described by one of
+
+ - Three integers (*nrows*, *ncols*, *index*). The subplot will
+ take the *index* position on a grid with *nrows* rows and
+ *ncols* columns. *index* starts at 1 in the upper left corner
+ and increases to the right. *index* can also be a two-tuple
+ specifying the (*first*, *last*) indices (1-based, and including
+ *last*) of the subplot, e.g., ``fig.add_subplot(3, 1, (1, 2))``
+ makes a subplot that spans the upper 2/3 of the figure.
+ - A 3-digit integer. The digits are interpreted as if given
+ separately as three single-digit integers, i.e.
+ ``fig.add_subplot(235)`` is the same as
+ ``fig.add_subplot(2, 3, 5)``. Note that this can only be used
+ if there are no more than 9 subplots.
+ - A `.SubplotSpec`.
+
+ In rare circumstances, `.add_subplot` may be called with a single
+ argument, a subplot Axes instance already created in the
+ present figure but not in the figure's list of Axes.
+
+ projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \
+'polar', 'rectilinear', str}, optional
+ The projection type of the subplot (`~.axes.Axes`). *str* is the
+ name of a custom projection, see `~matplotlib.projections`. The
+ default None results in a 'rectilinear' projection.
+
+ polar : bool, default: False
+ If True, equivalent to projection='polar'.
+
+ axes_class : subclass type of `~.axes.Axes`, optional
+ The `.axes.Axes` subclass that is instantiated. This parameter
+ is incompatible with *projection* and *polar*. See
+ :ref:`axisartist_users-guide-index` for examples.
+
+ sharex, sharey : `~matplotlib.axes.Axes`, optional
+ Share the x or y `~matplotlib.axis` with sharex and/or sharey.
+ The axis will have the same limits, ticks, and scale as the axis
+ of the shared Axes.
+
+ label : str
+ A label for the returned Axes.
+
+ Returns
+ -------
+ `~.axes.Axes`
+
+ The Axes of the subplot. The returned Axes can actually be an
+ instance of a subclass, such as `.projections.polar.PolarAxes` for
+ polar projections.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ This method also takes the keyword arguments for the returned Axes
+ base class; except for the *figure* argument. The keyword arguments
+ for the rectilinear base class `~.axes.Axes` can be found in
+ the following table but there might also be other keyword
+ arguments if another projection is used.
+
+ %(Axes:kwdoc)s
+
+ See Also
+ --------
+ .Figure.add_axes
+ .pyplot.subplot
+ .pyplot.axes
+ .Figure.subplots
+ .pyplot.subplots
+
+ Examples
+ --------
+ ::
+
+ fig = plt.figure()
+
+ fig.add_subplot(231)
+ ax1 = fig.add_subplot(2, 3, 1) # equivalent but more general
+
+ fig.add_subplot(232, frameon=False) # subplot with no frame
+ fig.add_subplot(233, projection='polar') # polar subplot
+ fig.add_subplot(234, sharex=ax1) # subplot sharing x-axis with ax1
+ fig.add_subplot(235, facecolor="red") # red subplot
+
+ ax1.remove() # delete ax1 from the figure
+ fig.add_subplot(ax1) # add ax1 back to the figure
+ """
+ if 'figure' in kwargs:
+ # Axes itself allows for a 'figure' kwarg, but since we want to
+ # bind the created Axes to self, it is not allowed here.
+ raise _api.kwarg_error("add_subplot", "figure")
+
+ if (len(args) == 1
+ and isinstance(args[0], mpl.axes._base._AxesBase)
+ and args[0].get_subplotspec()):
+ ax = args[0]
+ key = ax._projection_init
+ if ax.get_figure(root=False) is not self:
+ raise ValueError("The Axes must have been created in "
+ "the present figure")
+ else:
+ if not args:
+ args = (1, 1, 1)
+ # Normalize correct ijk values to (i, j, k) here so that
+ # add_subplot(211) == add_subplot(2, 1, 1). Invalid values will
+ # trigger errors later (via SubplotSpec._from_subplot_args).
+ if (len(args) == 1 and isinstance(args[0], Integral)
+ and 100 <= args[0] <= 999):
+ args = tuple(map(int, str(args[0])))
+ projection_class, pkw = self._process_projection_requirements(**kwargs)
+ ax = projection_class(self, *args, **pkw)
+ key = (projection_class, pkw)
+ return self._add_axes_internal(ax, key)
+
+ def _add_axes_internal(self, ax, key):
+ """Private helper for `add_axes` and `add_subplot`."""
+ self._axstack.add(ax)
+ if ax not in self._localaxes:
+ self._localaxes.append(ax)
+ self.sca(ax)
+ ax._remove_method = self.delaxes
+ # this is to support plt.subplot's re-selection logic
+ ax._projection_init = key
+ self.stale = True
+ ax.stale_callback = _stale_figure_callback
+ return ax
+
+ def subplots(self, nrows=1, ncols=1, *, sharex=False, sharey=False,
+ squeeze=True, width_ratios=None, height_ratios=None,
+ subplot_kw=None, gridspec_kw=None):
+ """
+ Add a set of subplots to this figure.
+
+ This utility wrapper makes it convenient to create common layouts of
+ subplots in a single call.
+
+ Parameters
+ ----------
+ nrows, ncols : int, default: 1
+ Number of rows/columns of the subplot grid.
+
+ sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False
+ Controls sharing of x-axis (*sharex*) or y-axis (*sharey*):
+
+ - True or 'all': x- or y-axis will be shared among all subplots.
+ - False or 'none': each subplot x- or y-axis will be independent.
+ - 'row': each subplot row will share an x- or y-axis.
+ - 'col': each subplot column will share an x- or y-axis.
+
+ When subplots have a shared x-axis along a column, only the x tick
+ labels of the bottom subplot are created. Similarly, when subplots
+ have a shared y-axis along a row, only the y tick labels of the
+ first column subplot are created. To later turn other subplots'
+ ticklabels on, use `~matplotlib.axes.Axes.tick_params`.
+
+ When subplots have a shared axis that has units, calling
+ `.Axis.set_units` will update each axis with the new units.
+
+ Note that it is not possible to unshare axes.
+
+ squeeze : bool, default: True
+ - If True, extra dimensions are squeezed out from the returned
+ array of Axes:
+
+ - if only one subplot is constructed (nrows=ncols=1), the
+ resulting single Axes object is returned as a scalar.
+ - for Nx1 or 1xM subplots, the returned object is a 1D numpy
+ object array of Axes objects.
+ - for NxM, subplots with N>1 and M>1 are returned as a 2D array.
+
+ - If False, no squeezing at all is done: the returned Axes object
+ is always a 2D array containing Axes instances, even if it ends
+ up being 1x1.
+
+ width_ratios : array-like of length *ncols*, optional
+ Defines the relative widths of the columns. Each column gets a
+ relative width of ``width_ratios[i] / sum(width_ratios)``.
+ If not given, all columns will have the same width. Equivalent
+ to ``gridspec_kw={'width_ratios': [...]}``.
+
+ height_ratios : array-like of length *nrows*, optional
+ Defines the relative heights of the rows. Each row gets a
+ relative height of ``height_ratios[i] / sum(height_ratios)``.
+ If not given, all rows will have the same height. Equivalent
+ to ``gridspec_kw={'height_ratios': [...]}``.
+
+ subplot_kw : dict, optional
+ Dict with keywords passed to the `.Figure.add_subplot` call used to
+ create each subplot.
+
+ gridspec_kw : dict, optional
+ Dict with keywords passed to the
+ `~matplotlib.gridspec.GridSpec` constructor used to create
+ the grid the subplots are placed on.
+
+ Returns
+ -------
+ `~.axes.Axes` or array of Axes
+ Either a single `~matplotlib.axes.Axes` object or an array of Axes
+ objects if more than one subplot was created. The dimensions of the
+ resulting array can be controlled with the *squeeze* keyword, see
+ above.
+
+ See Also
+ --------
+ .pyplot.subplots
+ .Figure.add_subplot
+ .pyplot.subplot
+
+ Examples
+ --------
+ ::
+
+ # First create some toy data:
+ x = np.linspace(0, 2*np.pi, 400)
+ y = np.sin(x**2)
+
+ # Create a figure
+ fig = plt.figure()
+
+ # Create a subplot
+ ax = fig.subplots()
+ ax.plot(x, y)
+ ax.set_title('Simple plot')
+
+ # Create two subplots and unpack the output array immediately
+ ax1, ax2 = fig.subplots(1, 2, sharey=True)
+ ax1.plot(x, y)
+ ax1.set_title('Sharing Y axis')
+ ax2.scatter(x, y)
+
+ # Create four polar Axes and access them through the returned array
+ axes = fig.subplots(2, 2, subplot_kw=dict(projection='polar'))
+ axes[0, 0].plot(x, y)
+ axes[1, 1].scatter(x, y)
+
+ # Share an X-axis with each column of subplots
+ fig.subplots(2, 2, sharex='col')
+
+ # Share a Y-axis with each row of subplots
+ fig.subplots(2, 2, sharey='row')
+
+ # Share both X- and Y-axes with all subplots
+ fig.subplots(2, 2, sharex='all', sharey='all')
+
+ # Note that this is the same as
+ fig.subplots(2, 2, sharex=True, sharey=True)
+ """
+ gridspec_kw = dict(gridspec_kw or {})
+ if height_ratios is not None:
+ if 'height_ratios' in gridspec_kw:
+ raise ValueError("'height_ratios' must not be defined both as "
+ "parameter and as key in 'gridspec_kw'")
+ gridspec_kw['height_ratios'] = height_ratios
+ if width_ratios is not None:
+ if 'width_ratios' in gridspec_kw:
+ raise ValueError("'width_ratios' must not be defined both as "
+ "parameter and as key in 'gridspec_kw'")
+ gridspec_kw['width_ratios'] = width_ratios
+
+ gs = self.add_gridspec(nrows, ncols, figure=self, **gridspec_kw)
+ axs = gs.subplots(sharex=sharex, sharey=sharey, squeeze=squeeze,
+ subplot_kw=subplot_kw)
+ return axs
+
+ def delaxes(self, ax):
+ """
+ Remove the `~.axes.Axes` *ax* from the figure; update the current Axes.
+ """
+ self._remove_axes(ax, owners=[self._axstack, self._localaxes])
+
+ def _remove_axes(self, ax, owners):
+ """
+ Common helper for removal of standard Axes (via delaxes) and of child Axes.
+
+ Parameters
+ ----------
+ ax : `~.AxesBase`
+ The Axes to remove.
+ owners
+ List of objects (list or _AxesStack) "owning" the Axes, from which the Axes
+ will be remove()d.
+ """
+ for owner in owners:
+ owner.remove(ax)
+
+ self._axobservers.process("_axes_change_event", self)
+ self.stale = True
+ self._root_figure.canvas.release_mouse(ax)
+
+ for name in ax._axis_names: # Break link between any shared Axes
+ grouper = ax._shared_axes[name]
+ siblings = [other for other in grouper.get_siblings(ax) if other is not ax]
+ if not siblings: # Axes was not shared along this axis; we're done.
+ continue
+ grouper.remove(ax)
+ # Formatters and locators may previously have been associated with the now
+ # removed axis. Update them to point to an axis still there (we can pick
+ # any of them, and use the first sibling).
+ remaining_axis = siblings[0]._axis_map[name]
+ remaining_axis.get_major_formatter().set_axis(remaining_axis)
+ remaining_axis.get_major_locator().set_axis(remaining_axis)
+ remaining_axis.get_minor_formatter().set_axis(remaining_axis)
+ remaining_axis.get_minor_locator().set_axis(remaining_axis)
+
+ ax._twinned_axes.remove(ax) # Break link between any twinned Axes.
+
+ def clear(self, keep_observers=False):
+ """
+ Clear the figure.
+
+ Parameters
+ ----------
+ keep_observers : bool, default: False
+ Set *keep_observers* to True if, for example,
+ a gui widget is tracking the Axes in the figure.
+ """
+ self.suppressComposite = None
+
+ # first clear the Axes in any subfigures
+ for subfig in self.subfigs:
+ subfig.clear(keep_observers=keep_observers)
+ self.subfigs = []
+
+ for ax in tuple(self.axes): # Iterate over the copy.
+ ax.clear()
+ self.delaxes(ax) # Remove ax from self._axstack.
+
+ self.artists = []
+ self.lines = []
+ self.patches = []
+ self.texts = []
+ self.images = []
+ self.legends = []
+ if not keep_observers:
+ self._axobservers = cbook.CallbackRegistry()
+ self._suptitle = None
+ self._supxlabel = None
+ self._supylabel = None
+
+ self.stale = True
+
+ # synonym for `clear`.
+ def clf(self, keep_observers=False):
+ """
+ [*Discouraged*] Alias for the `clear()` method.
+
+ .. admonition:: Discouraged
+
+ The use of ``clf()`` is discouraged. Use ``clear()`` instead.
+
+ Parameters
+ ----------
+ keep_observers : bool, default: False
+ Set *keep_observers* to True if, for example,
+ a gui widget is tracking the Axes in the figure.
+ """
+ return self.clear(keep_observers=keep_observers)
+
+ # Note: the docstring below is modified with replace for the pyplot
+ # version of this function because the method name differs (plt.figlegend)
+ # the replacements are:
+ # " legend(" -> " figlegend(" for the signatures
+ # "fig.legend(" -> "plt.figlegend" for the code examples
+ # "ax.plot" -> "plt.plot" for consistency in using pyplot when able
+ @_docstring.interpd
+ def legend(self, *args, **kwargs):
+ """
+ Place a legend on the figure.
+
+ Call signatures::
+
+ legend()
+ legend(handles, labels)
+ legend(handles=handles)
+ legend(labels)
+
+ The call signatures correspond to the following different ways to use
+ this method:
+
+ **1. Automatic detection of elements to be shown in the legend**
+
+ The elements to be added to the legend are automatically determined,
+ when you do not pass in any extra arguments.
+
+ In this case, the labels are taken from the artist. You can specify
+ them either at artist creation or by calling the
+ :meth:`~.Artist.set_label` method on the artist::
+
+ ax.plot([1, 2, 3], label='Inline label')
+ fig.legend()
+
+ or::
+
+ line, = ax.plot([1, 2, 3])
+ line.set_label('Label via method')
+ fig.legend()
+
+ Specific lines can be excluded from the automatic legend element
+ selection by defining a label starting with an underscore.
+ This is default for all artists, so calling `.Figure.legend` without
+ any arguments and without setting the labels manually will result in
+ no legend being drawn.
+
+
+ **2. Explicitly listing the artists and labels in the legend**
+
+ For full control of which artists have a legend entry, it is possible
+ to pass an iterable of legend artists followed by an iterable of
+ legend labels respectively::
+
+ fig.legend([line1, line2, line3], ['label1', 'label2', 'label3'])
+
+
+ **3. Explicitly listing the artists in the legend**
+
+ This is similar to 2, but the labels are taken from the artists'
+ label properties. Example::
+
+ line1, = ax1.plot([1, 2, 3], label='label1')
+ line2, = ax2.plot([1, 2, 3], label='label2')
+ fig.legend(handles=[line1, line2])
+
+
+ **4. Labeling existing plot elements**
+
+ .. admonition:: Discouraged
+
+ This call signature is discouraged, because the relation between
+ plot elements and labels is only implicit by their order and can
+ easily be mixed up.
+
+ To make a legend for all artists on all Axes, call this function with
+ an iterable of strings, one for each legend item. For example::
+
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+ ax1.plot([1, 3, 5], color='blue')
+ ax2.plot([2, 4, 6], color='red')
+ fig.legend(['the blues', 'the reds'])
+
+
+ Parameters
+ ----------
+ handles : list of `.Artist`, optional
+ A list of Artists (lines, patches) to be added to the legend.
+ Use this together with *labels*, if you need full control on what
+ is shown in the legend and the automatic mechanism described above
+ is not sufficient.
+
+ The length of handles and labels should be the same in this
+ case. If they are not, they are truncated to the smaller length.
+
+ labels : list of str, optional
+ A list of labels to show next to the artists.
+ Use this together with *handles*, if you need full control on what
+ is shown in the legend and the automatic mechanism described above
+ is not sufficient.
+
+ Returns
+ -------
+ `~matplotlib.legend.Legend`
+
+ Other Parameters
+ ----------------
+ %(_legend_kw_figure)s
+
+ See Also
+ --------
+ .Axes.legend
+
+ Notes
+ -----
+ Some artists are not supported by this function. See
+ :ref:`legend_guide` for details.
+ """
+
+ handles, labels, kwargs = mlegend._parse_legend_args(self.axes, *args, **kwargs)
+ # explicitly set the bbox transform if the user hasn't.
+ kwargs.setdefault("bbox_transform", self.transSubfigure)
+ l = mlegend.Legend(self, handles, labels, **kwargs)
+ self.legends.append(l)
+ l._remove_method = self.legends.remove
+ self.stale = True
+ return l
+
+ @_docstring.interpd
+ def text(self, x, y, s, fontdict=None, **kwargs):
+ """
+ Add text to figure.
+
+ Parameters
+ ----------
+ x, y : float
+ The position to place the text. By default, this is in figure
+ coordinates, floats in [0, 1]. The coordinate system can be changed
+ using the *transform* keyword.
+
+ s : str
+ The text string.
+
+ fontdict : dict, optional
+ A dictionary to override the default text properties. If not given,
+ the defaults are determined by :rc:`font.*`. Properties passed as
+ *kwargs* override the corresponding ones given in *fontdict*.
+
+ Returns
+ -------
+ `~.text.Text`
+
+ Other Parameters
+ ----------------
+ **kwargs : `~matplotlib.text.Text` properties
+ Other miscellaneous text parameters.
+
+ %(Text:kwdoc)s
+
+ See Also
+ --------
+ .Axes.text
+ .pyplot.text
+ """
+ effective_kwargs = {
+ 'transform': self.transSubfigure,
+ **(fontdict if fontdict is not None else {}),
+ **kwargs,
+ }
+ text = Text(x=x, y=y, text=s, **effective_kwargs)
+ text.set_figure(self)
+ text.stale_callback = _stale_figure_callback
+
+ self.texts.append(text)
+ text._remove_method = self.texts.remove
+ self.stale = True
+ return text
+
+ @_docstring.interpd
+ def colorbar(
+ self, mappable, cax=None, ax=None, use_gridspec=True, **kwargs):
+ """
+ Add a colorbar to a plot.
+
+ Parameters
+ ----------
+ mappable
+ The `matplotlib.cm.ScalarMappable` (i.e., `.AxesImage`,
+ `.ContourSet`, etc.) described by this colorbar. This argument is
+ mandatory for the `.Figure.colorbar` method but optional for the
+ `.pyplot.colorbar` function, which sets the default to the current
+ image.
+
+ Note that one can create a `.ScalarMappable` "on-the-fly" to
+ generate colorbars not attached to a previously drawn artist, e.g.
+ ::
+
+ fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax)
+
+ cax : `~matplotlib.axes.Axes`, optional
+ Axes into which the colorbar will be drawn. If `None`, then a new
+ Axes is created and the space for it will be stolen from the Axes(s)
+ specified in *ax*.
+
+ ax : `~matplotlib.axes.Axes` or iterable or `numpy.ndarray` of Axes, optional
+ The one or more parent Axes from which space for a new colorbar Axes
+ will be stolen. This parameter is only used if *cax* is not set.
+
+ Defaults to the Axes that contains the mappable used to create the
+ colorbar.
+
+ use_gridspec : bool, optional
+ If *cax* is ``None``, a new *cax* is created as an instance of
+ Axes. If *ax* is positioned with a subplotspec and *use_gridspec*
+ is ``True``, then *cax* is also positioned with a subplotspec.
+
+ Returns
+ -------
+ colorbar : `~matplotlib.colorbar.Colorbar`
+
+ Other Parameters
+ ----------------
+ %(_make_axes_kw_doc)s
+ %(_colormap_kw_doc)s
+
+ Notes
+ -----
+ If *mappable* is a `~.contour.ContourSet`, its *extend* kwarg is
+ included automatically.
+
+ The *shrink* kwarg provides a simple way to scale the colorbar with
+ respect to the Axes. Note that if *cax* is specified, it determines the
+ size of the colorbar, and *shrink* and *aspect* are ignored.
+
+ For more precise control, you can manually specify the positions of the
+ axes objects in which the mappable and the colorbar are drawn. In this
+ case, do not use any of the Axes properties kwargs.
+
+ It is known that some vector graphics viewers (svg and pdf) render
+ white gaps between segments of the colorbar. This is due to bugs in
+ the viewers, not Matplotlib. As a workaround, the colorbar can be
+ rendered with overlapping segments::
+
+ cbar = colorbar()
+ cbar.solids.set_edgecolor("face")
+ draw()
+
+ However, this has negative consequences in other circumstances, e.g.
+ with semi-transparent images (alpha < 1) and colorbar extensions;
+ therefore, this workaround is not used by default (see issue #1188).
+
+ """
+
+ if ax is None:
+ ax = getattr(mappable, "axes", None)
+
+ if cax is None:
+ if ax is None:
+ raise ValueError(
+ 'Unable to determine Axes to steal space for Colorbar. '
+ 'Either provide the *cax* argument to use as the Axes for '
+ 'the Colorbar, provide the *ax* argument to steal space '
+ 'from it, or add *mappable* to an Axes.')
+ fig = ( # Figure of first Axes; logic copied from make_axes.
+ [*ax.flat] if isinstance(ax, np.ndarray)
+ else [*ax] if np.iterable(ax)
+ else [ax])[0].get_figure(root=False)
+ current_ax = fig.gca()
+ if (fig.get_layout_engine() is not None and
+ not fig.get_layout_engine().colorbar_gridspec):
+ use_gridspec = False
+ if (use_gridspec
+ and isinstance(ax, mpl.axes._base._AxesBase)
+ and ax.get_subplotspec()):
+ cax, kwargs = cbar.make_axes_gridspec(ax, **kwargs)
+ else:
+ cax, kwargs = cbar.make_axes(ax, **kwargs)
+ # make_axes calls add_{axes,subplot} which changes gca; undo that.
+ fig.sca(current_ax)
+ cax.grid(visible=False, which='both', axis='both')
+
+ if (hasattr(mappable, "get_figure") and
+ (mappable_host_fig := mappable.get_figure(root=True)) is not None):
+ # Warn in case of mismatch
+ if mappable_host_fig is not self._root_figure:
+ _api.warn_external(
+ f'Adding colorbar to a different Figure '
+ f'{repr(mappable_host_fig)} than '
+ f'{repr(self._root_figure)} which '
+ f'fig.colorbar is called on.')
+
+ NON_COLORBAR_KEYS = [ # remove kws that cannot be passed to Colorbar
+ 'fraction', 'pad', 'shrink', 'aspect', 'anchor', 'panchor']
+ cb = cbar.Colorbar(cax, mappable, **{
+ k: v for k, v in kwargs.items() if k not in NON_COLORBAR_KEYS})
+ cax.get_figure(root=False).stale = True
+ return cb
+
+ def subplots_adjust(self, left=None, bottom=None, right=None, top=None,
+ wspace=None, hspace=None):
+ """
+ Adjust the subplot layout parameters.
+
+ Unset parameters are left unmodified; initial values are given by
+ :rc:`figure.subplot.[name]`.
+
+ .. plot:: _embedded_plots/figure_subplots_adjust.py
+
+ Parameters
+ ----------
+ left : float, optional
+ The position of the left edge of the subplots,
+ as a fraction of the figure width.
+ right : float, optional
+ The position of the right edge of the subplots,
+ as a fraction of the figure width.
+ bottom : float, optional
+ The position of the bottom edge of the subplots,
+ as a fraction of the figure height.
+ top : float, optional
+ The position of the top edge of the subplots,
+ as a fraction of the figure height.
+ wspace : float, optional
+ The width of the padding between subplots,
+ as a fraction of the average Axes width.
+ hspace : float, optional
+ The height of the padding between subplots,
+ as a fraction of the average Axes height.
+ """
+ if (self.get_layout_engine() is not None and
+ not self.get_layout_engine().adjust_compatible):
+ _api.warn_external(
+ "This figure was using a layout engine that is "
+ "incompatible with subplots_adjust and/or tight_layout; "
+ "not calling subplots_adjust.")
+ return
+ self.subplotpars.update(left, bottom, right, top, wspace, hspace)
+ for ax in self.axes:
+ if ax.get_subplotspec() is not None:
+ ax._set_position(ax.get_subplotspec().get_position(self))
+ self.stale = True
+
+ def align_xlabels(self, axs=None):
+ """
+ Align the xlabels of subplots in the same subplot row if label
+ alignment is being done automatically (i.e. the label position is
+ not manually set).
+
+ Alignment persists for draw events after this is called.
+
+ If a label is on the bottom, it is aligned with labels on Axes that
+ also have their label on the bottom and that have the same
+ bottom-most subplot row. If the label is on the top,
+ it is aligned with labels on Axes with the same top-most row.
+
+ Parameters
+ ----------
+ axs : list of `~matplotlib.axes.Axes`
+ Optional list of (or `~numpy.ndarray`) `~matplotlib.axes.Axes`
+ to align the xlabels.
+ Default is to align all Axes on the figure.
+
+ See Also
+ --------
+ matplotlib.figure.Figure.align_ylabels
+ matplotlib.figure.Figure.align_titles
+ matplotlib.figure.Figure.align_labels
+
+ Notes
+ -----
+ This assumes that all Axes in ``axs`` are from the same `.GridSpec`,
+ so that their `.SubplotSpec` positions correspond to figure positions.
+
+ Examples
+ --------
+ Example with rotated xtick labels::
+
+ fig, axs = plt.subplots(1, 2)
+ for tick in axs[0].get_xticklabels():
+ tick.set_rotation(55)
+ axs[0].set_xlabel('XLabel 0')
+ axs[1].set_xlabel('XLabel 1')
+ fig.align_xlabels()
+ """
+ if axs is None:
+ axs = self.axes
+ axs = [ax for ax in np.ravel(axs) if ax.get_subplotspec() is not None]
+ for ax in axs:
+ _log.debug(' Working on: %s', ax.get_xlabel())
+ rowspan = ax.get_subplotspec().rowspan
+ pos = ax.xaxis.get_label_position() # top or bottom
+ # Search through other Axes for label positions that are same as
+ # this one and that share the appropriate row number.
+ # Add to a grouper associated with each Axes of siblings.
+ # This list is inspected in `axis.draw` by
+ # `axis._update_label_position`.
+ for axc in axs:
+ if axc.xaxis.get_label_position() == pos:
+ rowspanc = axc.get_subplotspec().rowspan
+ if (pos == 'top' and rowspan.start == rowspanc.start or
+ pos == 'bottom' and rowspan.stop == rowspanc.stop):
+ # grouper for groups of xlabels to align
+ self._align_label_groups['x'].join(ax, axc)
+
+ def align_ylabels(self, axs=None):
+ """
+ Align the ylabels of subplots in the same subplot column if label
+ alignment is being done automatically (i.e. the label position is
+ not manually set).
+
+ Alignment persists for draw events after this is called.
+
+ If a label is on the left, it is aligned with labels on Axes that
+ also have their label on the left and that have the same
+ left-most subplot column. If the label is on the right,
+ it is aligned with labels on Axes with the same right-most column.
+
+ Parameters
+ ----------
+ axs : list of `~matplotlib.axes.Axes`
+ Optional list (or `~numpy.ndarray`) of `~matplotlib.axes.Axes`
+ to align the ylabels.
+ Default is to align all Axes on the figure.
+
+ See Also
+ --------
+ matplotlib.figure.Figure.align_xlabels
+ matplotlib.figure.Figure.align_titles
+ matplotlib.figure.Figure.align_labels
+
+ Notes
+ -----
+ This assumes that all Axes in ``axs`` are from the same `.GridSpec`,
+ so that their `.SubplotSpec` positions correspond to figure positions.
+
+ Examples
+ --------
+ Example with large yticks labels::
+
+ fig, axs = plt.subplots(2, 1)
+ axs[0].plot(np.arange(0, 1000, 50))
+ axs[0].set_ylabel('YLabel 0')
+ axs[1].set_ylabel('YLabel 1')
+ fig.align_ylabels()
+ """
+ if axs is None:
+ axs = self.axes
+ axs = [ax for ax in np.ravel(axs) if ax.get_subplotspec() is not None]
+ for ax in axs:
+ _log.debug(' Working on: %s', ax.get_ylabel())
+ colspan = ax.get_subplotspec().colspan
+ pos = ax.yaxis.get_label_position() # left or right
+ # Search through other Axes for label positions that are same as
+ # this one and that share the appropriate column number.
+ # Add to a list associated with each Axes of siblings.
+ # This list is inspected in `axis.draw` by
+ # `axis._update_label_position`.
+ for axc in axs:
+ if axc.yaxis.get_label_position() == pos:
+ colspanc = axc.get_subplotspec().colspan
+ if (pos == 'left' and colspan.start == colspanc.start or
+ pos == 'right' and colspan.stop == colspanc.stop):
+ # grouper for groups of ylabels to align
+ self._align_label_groups['y'].join(ax, axc)
+
+ def align_titles(self, axs=None):
+ """
+ Align the titles of subplots in the same subplot row if title
+ alignment is being done automatically (i.e. the title position is
+ not manually set).
+
+ Alignment persists for draw events after this is called.
+
+ Parameters
+ ----------
+ axs : list of `~matplotlib.axes.Axes`
+ Optional list of (or ndarray) `~matplotlib.axes.Axes`
+ to align the titles.
+ Default is to align all Axes on the figure.
+
+ See Also
+ --------
+ matplotlib.figure.Figure.align_xlabels
+ matplotlib.figure.Figure.align_ylabels
+ matplotlib.figure.Figure.align_labels
+
+ Notes
+ -----
+ This assumes that all Axes in ``axs`` are from the same `.GridSpec`,
+ so that their `.SubplotSpec` positions correspond to figure positions.
+
+ Examples
+ --------
+ Example with titles::
+
+ fig, axs = plt.subplots(1, 2)
+ axs[0].set_aspect('equal')
+ axs[0].set_title('Title 0')
+ axs[1].set_title('Title 1')
+ fig.align_titles()
+ """
+ if axs is None:
+ axs = self.axes
+ axs = [ax for ax in np.ravel(axs) if ax.get_subplotspec() is not None]
+ for ax in axs:
+ _log.debug(' Working on: %s', ax.get_title())
+ rowspan = ax.get_subplotspec().rowspan
+ for axc in axs:
+ rowspanc = axc.get_subplotspec().rowspan
+ if (rowspan.start == rowspanc.start):
+ self._align_label_groups['title'].join(ax, axc)
+
+ def align_labels(self, axs=None):
+ """
+ Align the xlabels and ylabels of subplots with the same subplots
+ row or column (respectively) if label alignment is being
+ done automatically (i.e. the label position is not manually set).
+
+ Alignment persists for draw events after this is called.
+
+ Parameters
+ ----------
+ axs : list of `~matplotlib.axes.Axes`
+ Optional list (or `~numpy.ndarray`) of `~matplotlib.axes.Axes`
+ to align the labels.
+ Default is to align all Axes on the figure.
+
+ See Also
+ --------
+ matplotlib.figure.Figure.align_xlabels
+ matplotlib.figure.Figure.align_ylabels
+ matplotlib.figure.Figure.align_titles
+
+ Notes
+ -----
+ This assumes that all Axes in ``axs`` are from the same `.GridSpec`,
+ so that their `.SubplotSpec` positions correspond to figure positions.
+ """
+ self.align_xlabels(axs=axs)
+ self.align_ylabels(axs=axs)
+
+ def add_gridspec(self, nrows=1, ncols=1, **kwargs):
+ """
+ Low-level API for creating a `.GridSpec` that has this figure as a parent.
+
+ This is a low-level API, allowing you to create a gridspec and
+ subsequently add subplots based on the gridspec. Most users do
+ not need that freedom and should use the higher-level methods
+ `~.Figure.subplots` or `~.Figure.subplot_mosaic`.
+
+ Parameters
+ ----------
+ nrows : int, default: 1
+ Number of rows in grid.
+
+ ncols : int, default: 1
+ Number of columns in grid.
+
+ Returns
+ -------
+ `.GridSpec`
+
+ Other Parameters
+ ----------------
+ **kwargs
+ Keyword arguments are passed to `.GridSpec`.
+
+ See Also
+ --------
+ matplotlib.pyplot.subplots
+
+ Examples
+ --------
+ Adding a subplot that spans two rows::
+
+ fig = plt.figure()
+ gs = fig.add_gridspec(2, 2)
+ ax1 = fig.add_subplot(gs[0, 0])
+ ax2 = fig.add_subplot(gs[1, 0])
+ # spans two rows:
+ ax3 = fig.add_subplot(gs[:, 1])
+
+ """
+
+ _ = kwargs.pop('figure', None) # pop in case user has added this...
+ gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, **kwargs)
+ return gs
+
+ def subfigures(self, nrows=1, ncols=1, squeeze=True,
+ wspace=None, hspace=None,
+ width_ratios=None, height_ratios=None,
+ **kwargs):
+ """
+ Add a set of subfigures to this figure or subfigure.
+
+ A subfigure has the same artist methods as a figure, and is logically
+ the same as a figure, but cannot print itself.
+ See :doc:`/gallery/subplots_axes_and_figures/subfigures`.
+
+ .. versionchanged:: 3.10
+ subfigures are now added in row-major order.
+
+ Parameters
+ ----------
+ nrows, ncols : int, default: 1
+ Number of rows/columns of the subfigure grid.
+
+ squeeze : bool, default: True
+ If True, extra dimensions are squeezed out from the returned
+ array of subfigures.
+
+ wspace, hspace : float, default: None
+ The amount of width/height reserved for space between subfigures,
+ expressed as a fraction of the average subfigure width/height.
+ If not given, the values will be inferred from rcParams if using
+ constrained layout (see `~.ConstrainedLayoutEngine`), or zero if
+ not using a layout engine.
+
+ width_ratios : array-like of length *ncols*, optional
+ Defines the relative widths of the columns. Each column gets a
+ relative width of ``width_ratios[i] / sum(width_ratios)``.
+ If not given, all columns will have the same width.
+
+ height_ratios : array-like of length *nrows*, optional
+ Defines the relative heights of the rows. Each row gets a
+ relative height of ``height_ratios[i] / sum(height_ratios)``.
+ If not given, all rows will have the same height.
+ """
+ gs = GridSpec(nrows=nrows, ncols=ncols, figure=self,
+ wspace=wspace, hspace=hspace,
+ width_ratios=width_ratios,
+ height_ratios=height_ratios,
+ left=0, right=1, bottom=0, top=1)
+
+ sfarr = np.empty((nrows, ncols), dtype=object)
+ for i in range(nrows):
+ for j in range(ncols):
+ sfarr[i, j] = self.add_subfigure(gs[i, j], **kwargs)
+
+ if self.get_layout_engine() is None and (wspace is not None or
+ hspace is not None):
+ # Gridspec wspace and hspace is ignored on subfigure instantiation,
+ # and no space is left. So need to account for it here if required.
+ bottoms, tops, lefts, rights = gs.get_grid_positions(self)
+ for sfrow, bottom, top in zip(sfarr, bottoms, tops):
+ for sf, left, right in zip(sfrow, lefts, rights):
+ bbox = Bbox.from_extents(left, bottom, right, top)
+ sf._redo_transform_rel_fig(bbox=bbox)
+
+ if squeeze:
+ # Discarding unneeded dimensions that equal 1. If we only have one
+ # subfigure, just return it instead of a 1-element array.
+ return sfarr.item() if sfarr.size == 1 else sfarr.squeeze()
+ else:
+ # Returned axis array will be always 2-d, even if nrows=ncols=1.
+ return sfarr
+
+ def add_subfigure(self, subplotspec, **kwargs):
+ """
+ Add a `.SubFigure` to the figure as part of a subplot arrangement.
+
+ Parameters
+ ----------
+ subplotspec : `.gridspec.SubplotSpec`
+ Defines the region in a parent gridspec where the subfigure will
+ be placed.
+
+ Returns
+ -------
+ `.SubFigure`
+
+ Other Parameters
+ ----------------
+ **kwargs
+ Are passed to the `.SubFigure` object.
+
+ See Also
+ --------
+ .Figure.subfigures
+ """
+ sf = SubFigure(self, subplotspec, **kwargs)
+ self.subfigs += [sf]
+ sf._remove_method = self.subfigs.remove
+ sf.stale_callback = _stale_figure_callback
+ self.stale = True
+ return sf
+
+ def sca(self, a):
+ """Set the current Axes to be *a* and return *a*."""
+ self._axstack.bubble(a)
+ self._axobservers.process("_axes_change_event", self)
+ return a
+
+ def gca(self):
+ """
+ Get the current Axes.
+
+ If there is currently no Axes on this Figure, a new one is created
+ using `.Figure.add_subplot`. (To test whether there is currently an
+ Axes on a Figure, check whether ``figure.axes`` is empty. To test
+ whether there is currently a Figure on the pyplot figure stack, check
+ whether `.pyplot.get_fignums()` is empty.)
+ """
+ ax = self._axstack.current()
+ return ax if ax is not None else self.add_subplot()
+
+ def _gci(self):
+ # Helper for `~matplotlib.pyplot.gci`. Do not use elsewhere.
+ """
+ Get the current colorable artist.
+
+ Specifically, returns the current `.ScalarMappable` instance (`.Image`
+ created by `imshow` or `figimage`, `.Collection` created by `pcolor` or
+ `scatter`, etc.), or *None* if no such instance has been defined.
+
+ The current image is an attribute of the current Axes, or the nearest
+ earlier Axes in the current figure that contains an image.
+
+ Notes
+ -----
+ Historically, the only colorable artists were images; hence the name
+ ``gci`` (get current image).
+ """
+ # Look first for an image in the current Axes.
+ ax = self._axstack.current()
+ if ax is None:
+ return None
+ im = ax._gci()
+ if im is not None:
+ return im
+ # If there is no image in the current Axes, search for
+ # one in a previously created Axes. Whether this makes
+ # sense is debatable, but it is the documented behavior.
+ for ax in reversed(self.axes):
+ im = ax._gci()
+ if im is not None:
+ return im
+ return None
+
+ def _process_projection_requirements(self, *, axes_class=None, polar=False,
+ projection=None, **kwargs):
+ """
+ Handle the args/kwargs to add_axes/add_subplot/gca, returning::
+
+ (axes_proj_class, proj_class_kwargs)
+
+ which can be used for new Axes initialization/identification.
+ """
+ if axes_class is not None:
+ if polar or projection is not None:
+ raise ValueError(
+ "Cannot combine 'axes_class' and 'projection' or 'polar'")
+ projection_class = axes_class
+ else:
+
+ if polar:
+ if projection is not None and projection != 'polar':
+ raise ValueError(
+ f"polar={polar}, yet projection={projection!r}. "
+ "Only one of these arguments should be supplied."
+ )
+ projection = 'polar'
+
+ if isinstance(projection, str) or projection is None:
+ projection_class = projections.get_projection_class(projection)
+ elif hasattr(projection, '_as_mpl_axes'):
+ projection_class, extra_kwargs = projection._as_mpl_axes()
+ kwargs.update(**extra_kwargs)
+ else:
+ raise TypeError(
+ f"projection must be a string, None or implement a "
+ f"_as_mpl_axes method, not {projection!r}")
+ return projection_class, kwargs
+
+ def get_default_bbox_extra_artists(self):
+ """
+ Return a list of Artists typically used in `.Figure.get_tightbbox`.
+ """
+ bbox_artists = [artist for artist in self.get_children()
+ if (artist.get_visible() and artist.get_in_layout())]
+ for ax in self.axes:
+ if ax.get_visible():
+ bbox_artists.extend(ax.get_default_bbox_extra_artists())
+ return bbox_artists
+
+ def get_tightbbox(self, renderer=None, *, bbox_extra_artists=None):
+ """
+ Return a (tight) bounding box of the figure *in inches*.
+
+ Note that `.FigureBase` differs from all other artists, which return
+ their `.Bbox` in pixels.
+
+ Artists that have ``artist.set_in_layout(False)`` are not included
+ in the bbox.
+
+ Parameters
+ ----------
+ renderer : `.RendererBase` subclass
+ Renderer that will be used to draw the figures (i.e.
+ ``fig.canvas.get_renderer()``)
+
+ bbox_extra_artists : list of `.Artist` or ``None``
+ List of artists to include in the tight bounding box. If
+ ``None`` (default), then all artist children of each Axes are
+ included in the tight bounding box.
+
+ Returns
+ -------
+ `.BboxBase`
+ containing the bounding box (in figure inches).
+ """
+
+ if renderer is None:
+ renderer = self.get_figure(root=True)._get_renderer()
+
+ bb = []
+ if bbox_extra_artists is None:
+ artists = [artist for artist in self.get_children()
+ if (artist not in self.axes and artist.get_visible()
+ and artist.get_in_layout())]
+ else:
+ artists = bbox_extra_artists
+
+ for a in artists:
+ bbox = a.get_tightbbox(renderer)
+ if bbox is not None:
+ bb.append(bbox)
+
+ for ax in self.axes:
+ if ax.get_visible():
+ # some Axes don't take the bbox_extra_artists kwarg so we
+ # need this conditional....
+ try:
+ bbox = ax.get_tightbbox(
+ renderer, bbox_extra_artists=bbox_extra_artists)
+ except TypeError:
+ bbox = ax.get_tightbbox(renderer)
+ bb.append(bbox)
+ bb = [b for b in bb
+ if (np.isfinite(b.width) and np.isfinite(b.height)
+ and (b.width != 0 or b.height != 0))]
+
+ isfigure = hasattr(self, 'bbox_inches')
+ if len(bb) == 0:
+ if isfigure:
+ return self.bbox_inches
+ else:
+ # subfigures do not have bbox_inches, but do have a bbox
+ bb = [self.bbox]
+
+ _bbox = Bbox.union(bb)
+
+ if isfigure:
+ # transform from pixels to inches...
+ _bbox = TransformedBbox(_bbox, self.dpi_scale_trans.inverted())
+
+ return _bbox
+
+ @staticmethod
+ def _norm_per_subplot_kw(per_subplot_kw):
+ expanded = {}
+ for k, v in per_subplot_kw.items():
+ if isinstance(k, tuple):
+ for sub_key in k:
+ if sub_key in expanded:
+ raise ValueError(f'The key {sub_key!r} appears multiple times.')
+ expanded[sub_key] = v
+ else:
+ if k in expanded:
+ raise ValueError(f'The key {k!r} appears multiple times.')
+ expanded[k] = v
+ return expanded
+
+ @staticmethod
+ def _normalize_grid_string(layout):
+ if '\n' not in layout:
+ # single-line string
+ return [list(ln) for ln in layout.split(';')]
+ else:
+ # multi-line string
+ layout = inspect.cleandoc(layout)
+ return [list(ln) for ln in layout.strip('\n').split('\n')]
+
+ def subplot_mosaic(self, mosaic, *, sharex=False, sharey=False,
+ width_ratios=None, height_ratios=None,
+ empty_sentinel='.',
+ subplot_kw=None, per_subplot_kw=None, gridspec_kw=None):
+ """
+ Build a layout of Axes based on ASCII art or nested lists.
+
+ This is a helper function to build complex GridSpec layouts visually.
+
+ See :ref:`mosaic`
+ for an example and full API documentation
+
+ Parameters
+ ----------
+ mosaic : list of list of {hashable or nested} or str
+
+ A visual layout of how you want your Axes to be arranged
+ labeled as strings. For example ::
+
+ x = [['A panel', 'A panel', 'edge'],
+ ['C panel', '.', 'edge']]
+
+ produces 4 Axes:
+
+ - 'A panel' which is 1 row high and spans the first two columns
+ - 'edge' which is 2 rows high and is on the right edge
+ - 'C panel' which in 1 row and 1 column wide in the bottom left
+ - a blank space 1 row and 1 column wide in the bottom center
+
+ Any of the entries in the layout can be a list of lists
+ of the same form to create nested layouts.
+
+ If input is a str, then it can either be a multi-line string of
+ the form ::
+
+ '''
+ AAE
+ C.E
+ '''
+
+ where each character is a column and each line is a row. Or it
+ can be a single-line string where rows are separated by ``;``::
+
+ 'AB;CC'
+
+ The string notation allows only single character Axes labels and
+ does not support nesting but is very terse.
+
+ The Axes identifiers may be `str` or a non-iterable hashable
+ object (e.g. `tuple` s may not be used).
+
+ sharex, sharey : bool, default: False
+ If True, the x-axis (*sharex*) or y-axis (*sharey*) will be shared
+ among all subplots. In that case, tick label visibility and axis
+ units behave as for `subplots`. If False, each subplot's x- or
+ y-axis will be independent.
+
+ width_ratios : array-like of length *ncols*, optional
+ Defines the relative widths of the columns. Each column gets a
+ relative width of ``width_ratios[i] / sum(width_ratios)``.
+ If not given, all columns will have the same width. Equivalent
+ to ``gridspec_kw={'width_ratios': [...]}``. In the case of nested
+ layouts, this argument applies only to the outer layout.
+
+ height_ratios : array-like of length *nrows*, optional
+ Defines the relative heights of the rows. Each row gets a
+ relative height of ``height_ratios[i] / sum(height_ratios)``.
+ If not given, all rows will have the same height. Equivalent
+ to ``gridspec_kw={'height_ratios': [...]}``. In the case of nested
+ layouts, this argument applies only to the outer layout.
+
+ subplot_kw : dict, optional
+ Dictionary with keywords passed to the `.Figure.add_subplot` call
+ used to create each subplot. These values may be overridden by
+ values in *per_subplot_kw*.
+
+ per_subplot_kw : dict, optional
+ A dictionary mapping the Axes identifiers or tuples of identifiers
+ to a dictionary of keyword arguments to be passed to the
+ `.Figure.add_subplot` call used to create each subplot. The values
+ in these dictionaries have precedence over the values in
+ *subplot_kw*.
+
+ If *mosaic* is a string, and thus all keys are single characters,
+ it is possible to use a single string instead of a tuple as keys;
+ i.e. ``"AB"`` is equivalent to ``("A", "B")``.
+
+ .. versionadded:: 3.7
+
+ gridspec_kw : dict, optional
+ Dictionary with keywords passed to the `.GridSpec` constructor used
+ to create the grid the subplots are placed on. In the case of
+ nested layouts, this argument applies only to the outer layout.
+ For more complex layouts, users should use `.Figure.subfigures`
+ to create the nesting.
+
+ empty_sentinel : object, optional
+ Entry in the layout to mean "leave this space empty". Defaults
+ to ``'.'``. Note, if *layout* is a string, it is processed via
+ `inspect.cleandoc` to remove leading white space, which may
+ interfere with using white-space as the empty sentinel.
+
+ Returns
+ -------
+ dict[label, Axes]
+ A dictionary mapping the labels to the Axes objects. The order of
+ the Axes is left-to-right and top-to-bottom of their position in the
+ total layout.
+
+ """
+ subplot_kw = subplot_kw or {}
+ gridspec_kw = dict(gridspec_kw or {})
+ per_subplot_kw = per_subplot_kw or {}
+
+ if height_ratios is not None:
+ if 'height_ratios' in gridspec_kw:
+ raise ValueError("'height_ratios' must not be defined both as "
+ "parameter and as key in 'gridspec_kw'")
+ gridspec_kw['height_ratios'] = height_ratios
+ if width_ratios is not None:
+ if 'width_ratios' in gridspec_kw:
+ raise ValueError("'width_ratios' must not be defined both as "
+ "parameter and as key in 'gridspec_kw'")
+ gridspec_kw['width_ratios'] = width_ratios
+
+ # special-case string input
+ if isinstance(mosaic, str):
+ mosaic = self._normalize_grid_string(mosaic)
+ per_subplot_kw = {
+ tuple(k): v for k, v in per_subplot_kw.items()
+ }
+
+ per_subplot_kw = self._norm_per_subplot_kw(per_subplot_kw)
+
+ # Only accept strict bools to allow a possible future API expansion.
+ _api.check_isinstance(bool, sharex=sharex, sharey=sharey)
+
+ def _make_array(inp):
+ """
+ Convert input into 2D array
+
+ We need to have this internal function rather than
+ ``np.asarray(..., dtype=object)`` so that a list of lists
+ of lists does not get converted to an array of dimension > 2.
+
+ Returns
+ -------
+ 2D object array
+ """
+ r0, *rest = inp
+ if isinstance(r0, str):
+ raise ValueError('List mosaic specification must be 2D')
+ for j, r in enumerate(rest, start=1):
+ if isinstance(r, str):
+ raise ValueError('List mosaic specification must be 2D')
+ if len(r0) != len(r):
+ raise ValueError(
+ "All of the rows must be the same length, however "
+ f"the first row ({r0!r}) has length {len(r0)} "
+ f"and row {j} ({r!r}) has length {len(r)}."
+ )
+ out = np.zeros((len(inp), len(r0)), dtype=object)
+ for j, r in enumerate(inp):
+ for k, v in enumerate(r):
+ out[j, k] = v
+ return out
+
+ def _identify_keys_and_nested(mosaic):
+ """
+ Given a 2D object array, identify unique IDs and nested mosaics
+
+ Parameters
+ ----------
+ mosaic : 2D object array
+
+ Returns
+ -------
+ unique_ids : tuple
+ The unique non-sub mosaic entries in this mosaic
+ nested : dict[tuple[int, int], 2D object array]
+ """
+ # make sure we preserve the user supplied order
+ unique_ids = cbook._OrderedSet()
+ nested = {}
+ for j, row in enumerate(mosaic):
+ for k, v in enumerate(row):
+ if v == empty_sentinel:
+ continue
+ elif not cbook.is_scalar_or_string(v):
+ nested[(j, k)] = _make_array(v)
+ else:
+ unique_ids.add(v)
+
+ return tuple(unique_ids), nested
+
+ def _do_layout(gs, mosaic, unique_ids, nested):
+ """
+ Recursively do the mosaic.
+
+ Parameters
+ ----------
+ gs : GridSpec
+ mosaic : 2D object array
+ The input converted to a 2D array for this level.
+ unique_ids : tuple
+ The identified scalar labels at this level of nesting.
+ nested : dict[tuple[int, int]], 2D object array
+ The identified nested mosaics, if any.
+
+ Returns
+ -------
+ dict[label, Axes]
+ A flat dict of all of the Axes created.
+ """
+ output = dict()
+
+ # we need to merge together the Axes at this level and the Axes
+ # in the (recursively) nested sub-mosaics so that we can add
+ # them to the figure in the "natural" order if you were to
+ # ravel in c-order all of the Axes that will be created
+ #
+ # This will stash the upper left index of each object (axes or
+ # nested mosaic) at this level
+ this_level = dict()
+
+ # go through the unique keys,
+ for name in unique_ids:
+ # sort out where each axes starts/ends
+ indx = np.argwhere(mosaic == name)
+ start_row, start_col = np.min(indx, axis=0)
+ end_row, end_col = np.max(indx, axis=0) + 1
+ # and construct the slice object
+ slc = (slice(start_row, end_row), slice(start_col, end_col))
+ # some light error checking
+ if (mosaic[slc] != name).any():
+ raise ValueError(
+ f"While trying to layout\n{mosaic!r}\n"
+ f"we found that the label {name!r} specifies a "
+ "non-rectangular or non-contiguous area.")
+ # and stash this slice for later
+ this_level[(start_row, start_col)] = (name, slc, 'axes')
+
+ # do the same thing for the nested mosaics (simpler because these
+ # cannot be spans yet!)
+ for (j, k), nested_mosaic in nested.items():
+ this_level[(j, k)] = (None, nested_mosaic, 'nested')
+
+ # now go through the things in this level and add them
+ # in order left-to-right top-to-bottom
+ for key in sorted(this_level):
+ name, arg, method = this_level[key]
+ # we are doing some hokey function dispatch here based
+ # on the 'method' string stashed above to sort out if this
+ # element is an Axes or a nested mosaic.
+ if method == 'axes':
+ slc = arg
+ # add a single Axes
+ if name in output:
+ raise ValueError(f"There are duplicate keys {name} "
+ f"in the layout\n{mosaic!r}")
+ ax = self.add_subplot(
+ gs[slc], **{
+ 'label': str(name),
+ **subplot_kw,
+ **per_subplot_kw.get(name, {})
+ }
+ )
+ output[name] = ax
+ elif method == 'nested':
+ nested_mosaic = arg
+ j, k = key
+ # recursively add the nested mosaic
+ rows, cols = nested_mosaic.shape
+ nested_output = _do_layout(
+ gs[j, k].subgridspec(rows, cols),
+ nested_mosaic,
+ *_identify_keys_and_nested(nested_mosaic)
+ )
+ overlap = set(output) & set(nested_output)
+ if overlap:
+ raise ValueError(
+ f"There are duplicate keys {overlap} "
+ f"between the outer layout\n{mosaic!r}\n"
+ f"and the nested layout\n{nested_mosaic}"
+ )
+ output.update(nested_output)
+ else:
+ raise RuntimeError("This should never happen")
+ return output
+
+ mosaic = _make_array(mosaic)
+ rows, cols = mosaic.shape
+ gs = self.add_gridspec(rows, cols, **gridspec_kw)
+ ret = _do_layout(gs, mosaic, *_identify_keys_and_nested(mosaic))
+ ax0 = next(iter(ret.values()))
+ for ax in ret.values():
+ if sharex:
+ ax.sharex(ax0)
+ ax._label_outer_xaxis(skip_non_rectangular_axes=True)
+ if sharey:
+ ax.sharey(ax0)
+ ax._label_outer_yaxis(skip_non_rectangular_axes=True)
+ if extra := set(per_subplot_kw) - set(ret):
+ raise ValueError(
+ f"The keys {extra} are in *per_subplot_kw* "
+ "but not in the mosaic."
+ )
+ return ret
+
+ def _set_artist_props(self, a):
+ if a != self:
+ a.set_figure(self)
+ a.stale_callback = _stale_figure_callback
+ a.set_transform(self.transSubfigure)
+
+
+@_docstring.interpd
+class SubFigure(FigureBase):
+ """
+ Logical figure that can be placed inside a figure.
+
+ See :ref:`figure-api-subfigure` for an index of methods on this class.
+ Typically instantiated using `.Figure.add_subfigure` or
+ `.SubFigure.add_subfigure`, or `.SubFigure.subfigures`. A subfigure has
+ the same methods as a figure except for those particularly tied to the size
+ or dpi of the figure, and is confined to a prescribed region of the figure.
+ For example the following puts two subfigures side-by-side::
+
+ fig = plt.figure()
+ sfigs = fig.subfigures(1, 2)
+ axsL = sfigs[0].subplots(1, 2)
+ axsR = sfigs[1].subplots(2, 1)
+
+ See :doc:`/gallery/subplots_axes_and_figures/subfigures`
+ """
+
+ def __init__(self, parent, subplotspec, *,
+ facecolor=None,
+ edgecolor=None,
+ linewidth=0.0,
+ frameon=None,
+ **kwargs):
+ """
+ Parameters
+ ----------
+ parent : `.Figure` or `.SubFigure`
+ Figure or subfigure that contains the SubFigure. SubFigures
+ can be nested.
+
+ subplotspec : `.gridspec.SubplotSpec`
+ Defines the region in a parent gridspec where the subfigure will
+ be placed.
+
+ facecolor : default: ``"none"``
+ The figure patch face color; transparent by default.
+
+ 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.
+
+ Other Parameters
+ ----------------
+ **kwargs : `.SubFigure` properties, optional
+
+ %(SubFigure:kwdoc)s
+ """
+ super().__init__(**kwargs)
+ if facecolor is None:
+ facecolor = "none"
+ if edgecolor is None:
+ edgecolor = mpl.rcParams['figure.edgecolor']
+ if frameon is None:
+ frameon = mpl.rcParams['figure.frameon']
+
+ self._subplotspec = subplotspec
+ self._parent = parent
+ self._root_figure = parent._root_figure
+
+ # subfigures use the parent axstack
+ self._axstack = parent._axstack
+ self.subplotpars = parent.subplotpars
+ self.dpi_scale_trans = parent.dpi_scale_trans
+ self._axobservers = parent._axobservers
+ self.transFigure = parent.transFigure
+ self.bbox_relative = Bbox.null()
+ self._redo_transform_rel_fig()
+ self.figbbox = self._parent.figbbox
+ self.bbox = TransformedBbox(self.bbox_relative,
+ self._parent.transSubfigure)
+ self.transSubfigure = BboxTransformTo(self.bbox)
+
+ self.patch = Rectangle(
+ xy=(0, 0), width=1, height=1, visible=frameon,
+ facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth,
+ # Don't let the figure patch influence bbox calculation.
+ in_layout=False, transform=self.transSubfigure)
+ self._set_artist_props(self.patch)
+ self.patch.set_antialiased(False)
+
+ @property
+ def canvas(self):
+ return self._parent.canvas
+
+ @property
+ def dpi(self):
+ return self._parent.dpi
+
+ @dpi.setter
+ def dpi(self, value):
+ self._parent.dpi = value
+
+ def get_dpi(self):
+ """
+ Return the resolution of the parent figure in dots-per-inch as a float.
+ """
+ return self._parent.dpi
+
+ def set_dpi(self, val):
+ """
+ Set the resolution of parent figure in dots-per-inch.
+
+ Parameters
+ ----------
+ val : float
+ """
+ self._parent.dpi = val
+ self.stale = True
+
+ def _get_renderer(self):
+ return self._parent._get_renderer()
+
+ def _redo_transform_rel_fig(self, bbox=None):
+ """
+ Make the transSubfigure bbox relative to Figure transform.
+
+ Parameters
+ ----------
+ bbox : bbox or None
+ If not None, then the bbox is used for relative bounding box.
+ Otherwise, it is calculated from the subplotspec.
+ """
+ if bbox is not None:
+ self.bbox_relative.p0 = bbox.p0
+ self.bbox_relative.p1 = bbox.p1
+ return
+ # need to figure out *where* this subplotspec is.
+ gs = self._subplotspec.get_gridspec()
+ wr = np.asarray(gs.get_width_ratios())
+ hr = np.asarray(gs.get_height_ratios())
+ dx = wr[self._subplotspec.colspan].sum() / wr.sum()
+ dy = hr[self._subplotspec.rowspan].sum() / hr.sum()
+ x0 = wr[:self._subplotspec.colspan.start].sum() / wr.sum()
+ y0 = 1 - hr[:self._subplotspec.rowspan.stop].sum() / hr.sum()
+ self.bbox_relative.p0 = (x0, y0)
+ self.bbox_relative.p1 = (x0 + dx, y0 + dy)
+
+ def get_constrained_layout(self):
+ """
+ Return whether constrained layout is being used.
+
+ See :ref:`constrainedlayout_guide`.
+ """
+ return self._parent.get_constrained_layout()
+
+ def get_constrained_layout_pads(self, relative=False):
+ """
+ Get padding for ``constrained_layout``.
+
+ Returns a list of ``w_pad, h_pad`` in inches and
+ ``wspace`` and ``hspace`` as fractions of the subplot.
+
+ See :ref:`constrainedlayout_guide`.
+
+ Parameters
+ ----------
+ relative : bool
+ If `True`, then convert from inches to figure relative.
+ """
+ return self._parent.get_constrained_layout_pads(relative=relative)
+
+ def get_layout_engine(self):
+ return self._parent.get_layout_engine()
+
+ @property
+ def axes(self):
+ """
+ List of Axes in the SubFigure. You can access and modify the Axes
+ in the SubFigure through this list.
+
+ Modifying this list has no effect. Instead, use `~.SubFigure.add_axes`,
+ `~.SubFigure.add_subplot` or `~.SubFigure.delaxes` to add or remove an
+ Axes.
+
+ Note: The `.SubFigure.axes` property and `~.SubFigure.get_axes` method
+ are equivalent.
+ """
+ return self._localaxes[:]
+
+ get_axes = axes.fget
+
+ def draw(self, renderer):
+ # docstring inherited
+
+ # draw the figure bounding box, perhaps none for white figure
+ if not self.get_visible():
+ return
+
+ artists = self._get_draw_artists(renderer)
+
+ try:
+ renderer.open_group('subfigure', gid=self.get_gid())
+ self.patch.draw(renderer)
+ mimage._draw_list_compositing_images(
+ renderer, self, artists, self.get_figure(root=True).suppressComposite)
+ renderer.close_group('subfigure')
+
+ finally:
+ self.stale = False
+
+
+@_docstring.interpd
+class Figure(FigureBase):
+ """
+ The top level container for all the plot elements.
+
+ See `matplotlib.figure` for an index of class methods.
+
+ Attributes
+ ----------
+ patch
+ The `.Rectangle` instance representing the figure background patch.
+
+ suppressComposite
+ For multiple images, the figure will make composite images
+ depending on the renderer option_image_nocomposite function. If
+ *suppressComposite* is a boolean, this will override the renderer.
+ """
+
+ # we want to cache the fonts and mathtext at a global level so that when
+ # multiple figures are created we can reuse them. This helps with a bug on
+ # windows where the creation of too many figures leads to too many open
+ # file handles and improves the performance of parsing mathtext. However,
+ # these global caches are not thread safe. The solution here is to let the
+ # Figure acquire a shared lock at the start of the draw, and release it when it
+ # is done. This allows multiple renderers to share the cached fonts and
+ # parsed text, but only one figure can draw at a time and so the font cache
+ # and mathtext cache are used by only one renderer at a time.
+
+ _render_lock = threading.RLock()
+
+ def __str__(self):
+ return "Figure(%gx%g)" % tuple(self.bbox.size)
+
+ def __repr__(self):
+ return "<{clsname} size {h:g}x{w:g} with {naxes} Axes>".format(
+ clsname=self.__class__.__name__,
+ h=self.bbox.size[0], w=self.bbox.size[1],
+ naxes=len(self.axes),
+ )
+
+ def __init__(self,
+ figsize=None,
+ dpi=None,
+ *,
+ facecolor=None,
+ edgecolor=None,
+ linewidth=0.0,
+ frameon=None,
+ subplotpars=None, # rc figure.subplot.*
+ tight_layout=None, # rc figure.autolayout
+ constrained_layout=None, # rc figure.constrained_layout.use
+ layout=None,
+ **kwargs
+ ):
+ """
+ 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`.
+
+ .. admonition:: 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'``.
+
+ .. admonition:: 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` properties, optional
+
+ %(Figure:kwdoc)s
+ """
+ super().__init__(**kwargs)
+ self._root_figure = self
+ self._layout_engine = None
+
+ if layout is not None:
+ if (tight_layout is not None):
+ _api.warn_external(
+ "The Figure parameters 'layout' and 'tight_layout' cannot "
+ "be used together. Please use 'layout' only.")
+ if (constrained_layout is not None):
+ _api.warn_external(
+ "The Figure parameters 'layout' and 'constrained_layout' "
+ "cannot be used together. Please use 'layout' only.")
+ self.set_layout_engine(layout=layout)
+ elif tight_layout is not None:
+ if constrained_layout is not None:
+ _api.warn_external(
+ "The Figure parameters 'tight_layout' and "
+ "'constrained_layout' cannot be used together. Please use "
+ "'layout' parameter")
+ self.set_layout_engine(layout='tight')
+ if isinstance(tight_layout, dict):
+ self.get_layout_engine().set(**tight_layout)
+ elif constrained_layout is not None:
+ if isinstance(constrained_layout, dict):
+ self.set_layout_engine(layout='constrained')
+ self.get_layout_engine().set(**constrained_layout)
+ elif constrained_layout:
+ self.set_layout_engine(layout='constrained')
+
+ else:
+ # everything is None, so use default:
+ self.set_layout_engine(layout=layout)
+
+ # Callbacks traditionally associated with the canvas (and exposed with
+ # a proxy property), but that actually need to be on the figure for
+ # pickling.
+ self._canvas_callbacks = cbook.CallbackRegistry(
+ signals=FigureCanvasBase.events)
+ connect = self._canvas_callbacks._connect_picklable
+ self._mouse_key_ids = [
+ connect('key_press_event', backend_bases._key_handler),
+ connect('key_release_event', backend_bases._key_handler),
+ connect('key_release_event', backend_bases._key_handler),
+ connect('button_press_event', backend_bases._mouse_handler),
+ connect('button_release_event', backend_bases._mouse_handler),
+ connect('scroll_event', backend_bases._mouse_handler),
+ connect('motion_notify_event', backend_bases._mouse_handler),
+ ]
+ self._button_pick_id = connect('button_press_event', self.pick)
+ self._scroll_pick_id = connect('scroll_event', self.pick)
+
+ if figsize is None:
+ figsize = mpl.rcParams['figure.figsize']
+ if dpi is None:
+ dpi = mpl.rcParams['figure.dpi']
+ if facecolor is None:
+ facecolor = mpl.rcParams['figure.facecolor']
+ if edgecolor is None:
+ edgecolor = mpl.rcParams['figure.edgecolor']
+ if frameon is None:
+ frameon = mpl.rcParams['figure.frameon']
+
+ if not np.isfinite(figsize).all() or (np.array(figsize) < 0).any():
+ raise ValueError('figure size must be positive finite not '
+ f'{figsize}')
+ self.bbox_inches = Bbox.from_bounds(0, 0, *figsize)
+
+ self.dpi_scale_trans = Affine2D().scale(dpi)
+ # do not use property as it will trigger
+ self._dpi = dpi
+ self.bbox = TransformedBbox(self.bbox_inches, self.dpi_scale_trans)
+ self.figbbox = self.bbox
+ self.transFigure = BboxTransformTo(self.bbox)
+ self.transSubfigure = self.transFigure
+
+ self.patch = Rectangle(
+ xy=(0, 0), width=1, height=1, visible=frameon,
+ facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth,
+ # Don't let the figure patch influence bbox calculation.
+ in_layout=False)
+ self._set_artist_props(self.patch)
+ self.patch.set_antialiased(False)
+
+ FigureCanvasBase(self) # Set self.canvas.
+
+ if subplotpars is None:
+ subplotpars = SubplotParams()
+
+ self.subplotpars = subplotpars
+
+ self._axstack = _AxesStack() # track all figure Axes and current Axes
+ self.clear()
+
+ def pick(self, mouseevent):
+ if not self.canvas.widgetlock.locked():
+ super().pick(mouseevent)
+
+ def _check_layout_engines_compat(self, old, new):
+ """
+ Helper for set_layout engine
+
+ If the figure has used the old engine and added a colorbar then the
+ value of colorbar_gridspec must be the same on the new engine.
+ """
+ if old is None or new is None:
+ return True
+ if old.colorbar_gridspec == new.colorbar_gridspec:
+ return True
+ # colorbar layout different, so check if any colorbars are on the
+ # figure...
+ for ax in self.axes:
+ if hasattr(ax, '_colorbar'):
+ # colorbars list themselves as a colorbar.
+ return False
+ return True
+
+ def set_layout_engine(self, layout=None, **kwargs):
+ """
+ Set the layout engine for this figure.
+
+ Parameters
+ ----------
+ layout : {'constrained', 'compressed', 'tight', 'none', `.LayoutEngine`, None}
+
+ - 'constrained' will use `~.ConstrainedLayoutEngine`
+ - 'compressed' will also use `~.ConstrainedLayoutEngine`, but with
+ a correction that attempts to make a good layout for fixed-aspect
+ ratio Axes.
+ - 'tight' uses `~.TightLayoutEngine`
+ - 'none' removes layout engine.
+
+ If a `.LayoutEngine` instance, that instance will be used.
+
+ If `None`, the behavior is controlled by :rc:`figure.autolayout`
+ (which if `True` behaves as if 'tight' was passed) and
+ :rc:`figure.constrained_layout.use` (which if `True` behaves as if
+ 'constrained' was passed). If both are `True`,
+ :rc:`figure.autolayout` takes priority.
+
+ Users and libraries can define their own layout engines and pass
+ the instance directly as well.
+
+ **kwargs
+ The keyword arguments are passed to the layout engine to set things
+ like padding and margin sizes. Only used if *layout* is a string.
+
+ """
+ if layout is None:
+ if mpl.rcParams['figure.autolayout']:
+ layout = 'tight'
+ elif mpl.rcParams['figure.constrained_layout.use']:
+ layout = 'constrained'
+ else:
+ self._layout_engine = None
+ return
+ if layout == 'tight':
+ new_layout_engine = TightLayoutEngine(**kwargs)
+ elif layout == 'constrained':
+ new_layout_engine = ConstrainedLayoutEngine(**kwargs)
+ elif layout == 'compressed':
+ new_layout_engine = ConstrainedLayoutEngine(compress=True,
+ **kwargs)
+ elif layout == 'none':
+ if self._layout_engine is not None:
+ new_layout_engine = PlaceHolderLayoutEngine(
+ self._layout_engine.adjust_compatible,
+ self._layout_engine.colorbar_gridspec
+ )
+ else:
+ new_layout_engine = None
+ elif isinstance(layout, LayoutEngine):
+ new_layout_engine = layout
+ else:
+ raise ValueError(f"Invalid value for 'layout': {layout!r}")
+
+ if self._check_layout_engines_compat(self._layout_engine,
+ new_layout_engine):
+ self._layout_engine = new_layout_engine
+ else:
+ raise RuntimeError('Colorbar layout of new layout engine not '
+ 'compatible with old engine, and a colorbar '
+ 'has been created. Engine not changed.')
+
+ def get_layout_engine(self):
+ return self._layout_engine
+
+ # TODO: I'd like to dynamically add the _repr_html_ method
+ # to the figure in the right context, but then IPython doesn't
+ # use it, for some reason.
+
+ def _repr_html_(self):
+ # We can't use "isinstance" here, because then we'd end up importing
+ # webagg unconditionally.
+ if 'WebAgg' in type(self.canvas).__name__:
+ from matplotlib.backends import backend_webagg
+ return backend_webagg.ipython_inline_display(self)
+
+ def show(self, warn=True):
+ """
+ If using a GUI backend with pyplot, display the figure window.
+
+ If the figure was not created using `~.pyplot.figure`, it will lack
+ a `~.backend_bases.FigureManagerBase`, and this method will raise an
+ AttributeError.
+
+ .. warning::
+
+ This does not manage an GUI event loop. Consequently, the figure
+ may only be shown briefly or not shown at all if you or your
+ environment are not managing an event loop.
+
+ Use cases for `.Figure.show` include running this from a GUI
+ application (where there is persistently an event loop running) or
+ from a shell, like IPython, that install an input hook to allow the
+ interactive shell to accept input while the figure is also being
+ shown and interactive. Some, but not all, GUI toolkits will
+ register an input hook on import. See :ref:`cp_integration` for
+ more details.
+
+ If you're in a shell without input hook integration or executing a
+ python script, you should use `matplotlib.pyplot.show` with
+ ``block=True`` instead, which takes care of starting and running
+ the event loop for you.
+
+ Parameters
+ ----------
+ warn : bool, default: True
+ If ``True`` and we are not running headless (i.e. on Linux with an
+ unset DISPLAY), issue warning when called on a non-GUI backend.
+
+ """
+ if self.canvas.manager is None:
+ raise AttributeError(
+ "Figure.show works only for figures managed by pyplot, "
+ "normally created by pyplot.figure()")
+ try:
+ self.canvas.manager.show()
+ except NonGuiException as exc:
+ if warn:
+ _api.warn_external(str(exc))
+
+ @property
+ def axes(self):
+ """
+ List of Axes in the Figure. You can access and modify the Axes in the
+ Figure through this list.
+
+ Do not modify the list itself. Instead, use `~Figure.add_axes`,
+ `~.Figure.add_subplot` or `~.Figure.delaxes` to add or remove an Axes.
+
+ Note: The `.Figure.axes` property and `~.Figure.get_axes` method are
+ equivalent.
+ """
+ return self._axstack.as_list()
+
+ get_axes = axes.fget
+
+ @property
+ def number(self):
+ """The figure id, used to identify figures in `.pyplot`."""
+ # Historically, pyplot dynamically added a number attribute to figure.
+ # However, this number must stay in sync with the figure manager.
+ # AFAICS overwriting the number attribute does not have the desired
+ # effect for pyplot. But there are some repos in GitHub that do change
+ # number. So let's take it slow and properly migrate away from writing.
+ #
+ # Making the dynamic attribute private and wrapping it in a property
+ # allows to maintain current behavior and deprecate write-access.
+ #
+ # When the deprecation expires, there's no need for duplicate state
+ # anymore and the private _number attribute can be replaced by
+ # `self.canvas.manager.num` if that exists and None otherwise.
+ if hasattr(self, '_number'):
+ return self._number
+ else:
+ raise AttributeError(
+ "'Figure' object has no attribute 'number'. In the future this"
+ "will change to returning 'None' instead.")
+
+ @number.setter
+ def number(self, num):
+ _api.warn_deprecated(
+ "3.10",
+ message="Changing 'Figure.number' is deprecated since %(since)s and "
+ "will raise an error starting %(removal)s")
+ self._number = num
+
+ def _get_renderer(self):
+ if hasattr(self.canvas, 'get_renderer'):
+ return self.canvas.get_renderer()
+ else:
+ return _get_renderer(self)
+
+ def _get_dpi(self):
+ return self._dpi
+
+ def _set_dpi(self, dpi, forward=True):
+ """
+ Parameters
+ ----------
+ dpi : float
+
+ forward : bool
+ Passed on to `~.Figure.set_size_inches`
+ """
+ if dpi == self._dpi:
+ # We don't want to cause undue events in backends.
+ return
+ self._dpi = dpi
+ self.dpi_scale_trans.clear().scale(dpi)
+ w, h = self.get_size_inches()
+ self.set_size_inches(w, h, forward=forward)
+
+ dpi = property(_get_dpi, _set_dpi, doc="The resolution in dots per inch.")
+
+ def get_tight_layout(self):
+ """Return whether `.Figure.tight_layout` is called when drawing."""
+ return isinstance(self.get_layout_engine(), TightLayoutEngine)
+
+ @_api.deprecated("3.6", alternative="set_layout_engine",
+ pending=True)
+ def set_tight_layout(self, tight):
+ """
+ Set whether and how `.Figure.tight_layout` is called when drawing.
+
+ Parameters
+ ----------
+ tight : bool or dict with keys "pad", "w_pad", "h_pad", "rect" or None
+ If a bool, sets whether to call `.Figure.tight_layout` upon drawing.
+ If ``None``, use :rc:`figure.autolayout` instead.
+ If a dict, pass it as kwargs to `.Figure.tight_layout`, overriding the
+ default paddings.
+ """
+ if tight is None:
+ tight = mpl.rcParams['figure.autolayout']
+ _tight = 'tight' if bool(tight) else 'none'
+ _tight_parameters = tight if isinstance(tight, dict) else {}
+ self.set_layout_engine(_tight, **_tight_parameters)
+ self.stale = True
+
+ def get_constrained_layout(self):
+ """
+ Return whether constrained layout is being used.
+
+ See :ref:`constrainedlayout_guide`.
+ """
+ return isinstance(self.get_layout_engine(), ConstrainedLayoutEngine)
+
+ @_api.deprecated("3.6", alternative="set_layout_engine('constrained')",
+ pending=True)
+ def set_constrained_layout(self, constrained):
+ """
+ Set whether ``constrained_layout`` is used upon drawing.
+
+ If None, :rc:`figure.constrained_layout.use` value will be used.
+
+ When providing a dict containing the keys ``w_pad``, ``h_pad``
+ the default ``constrained_layout`` paddings will be
+ overridden. These pads are in inches and default to 3.0/72.0.
+ ``w_pad`` is the width padding and ``h_pad`` is the height padding.
+
+ Parameters
+ ----------
+ constrained : bool or dict or None
+ """
+ if constrained is None:
+ constrained = mpl.rcParams['figure.constrained_layout.use']
+ _constrained = 'constrained' if bool(constrained) else 'none'
+ _parameters = constrained if isinstance(constrained, dict) else {}
+ self.set_layout_engine(_constrained, **_parameters)
+ self.stale = True
+
+ @_api.deprecated(
+ "3.6", alternative="figure.get_layout_engine().set()",
+ pending=True)
+ def set_constrained_layout_pads(self, **kwargs):
+ """
+ Set padding for ``constrained_layout``.
+
+ Tip: The parameters can be passed from a dictionary by using
+ ``fig.set_constrained_layout(**pad_dict)``.
+
+ See :ref:`constrainedlayout_guide`.
+
+ Parameters
+ ----------
+ w_pad : float, default: :rc:`figure.constrained_layout.w_pad`
+ Width padding in inches. This is the pad around Axes
+ and is meant to make sure there is enough room for fonts to
+ look good. Defaults to 3 pts = 0.04167 inches
+
+ h_pad : float, default: :rc:`figure.constrained_layout.h_pad`
+ Height padding in inches. Defaults to 3 pts.
+
+ wspace : float, default: :rc:`figure.constrained_layout.wspace`
+ Width padding between subplots, expressed as a fraction of the
+ subplot width. The total padding ends up being w_pad + wspace.
+
+ hspace : float, default: :rc:`figure.constrained_layout.hspace`
+ Height padding between subplots, expressed as a fraction of the
+ subplot width. The total padding ends up being h_pad + hspace.
+
+ """
+ if isinstance(self.get_layout_engine(), ConstrainedLayoutEngine):
+ self.get_layout_engine().set(**kwargs)
+
+ @_api.deprecated("3.6", alternative="fig.get_layout_engine().get()",
+ pending=True)
+ def get_constrained_layout_pads(self, relative=False):
+ """
+ Get padding for ``constrained_layout``.
+
+ Returns a list of ``w_pad, h_pad`` in inches and
+ ``wspace`` and ``hspace`` as fractions of the subplot.
+ All values are None if ``constrained_layout`` is not used.
+
+ See :ref:`constrainedlayout_guide`.
+
+ Parameters
+ ----------
+ relative : bool
+ If `True`, then convert from inches to figure relative.
+ """
+ if not isinstance(self.get_layout_engine(), ConstrainedLayoutEngine):
+ return None, None, None, None
+ info = self.get_layout_engine().get()
+ w_pad = info['w_pad']
+ h_pad = info['h_pad']
+ wspace = info['wspace']
+ hspace = info['hspace']
+
+ if relative and (w_pad is not None or h_pad is not None):
+ renderer = self._get_renderer()
+ dpi = renderer.dpi
+ w_pad = w_pad * dpi / renderer.width
+ h_pad = h_pad * dpi / renderer.height
+
+ return w_pad, h_pad, wspace, hspace
+
+ def set_canvas(self, canvas):
+ """
+ Set the canvas that contains the figure
+
+ Parameters
+ ----------
+ canvas : FigureCanvas
+ """
+ self.canvas = canvas
+
+ @_docstring.interpd
+ def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None,
+ vmin=None, vmax=None, origin=None, resize=False, *,
+ colorizer=None, **kwargs):
+ """
+ Add a non-resampled image to the figure.
+
+ The image is attached to the lower or upper left corner depending on
+ *origin*.
+
+ Parameters
+ ----------
+ X
+ The image data. This is an array of one of the following shapes:
+
+ - (M, N): an image with scalar data. Color-mapping is controlled
+ by *cmap*, *norm*, *vmin*, and *vmax*.
+ - (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
+ - (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
+ i.e. including transparency.
+
+ xo, yo : int
+ The *x*/*y* image offset in pixels.
+
+ alpha : None or float
+ The alpha blending value.
+
+ %(cmap_doc)s
+
+ This parameter is ignored if *X* is RGB(A).
+
+ %(norm_doc)s
+
+ This parameter is ignored if *X* is RGB(A).
+
+ %(vmin_vmax_doc)s
+
+ This parameter is ignored if *X* is RGB(A).
+
+ origin : {'upper', 'lower'}, default: :rc:`image.origin`
+ Indicates where the [0, 0] index of the array is in the upper left
+ or lower left corner of the Axes.
+
+ resize : bool
+ If *True*, resize the figure to match the given image size.
+
+ %(colorizer_doc)s
+
+ This parameter is ignored if *X* is RGB(A).
+
+ Returns
+ -------
+ `matplotlib.image.FigureImage`
+
+ Other Parameters
+ ----------------
+ **kwargs
+ Additional kwargs are `.Artist` kwargs passed on to `.FigureImage`.
+
+ Notes
+ -----
+ figimage complements the Axes image (`~matplotlib.axes.Axes.imshow`)
+ which will be resampled to fit the current Axes. If you want
+ a resampled image to fill the entire figure, you can define an
+ `~matplotlib.axes.Axes` with extent [0, 0, 1, 1].
+
+ Examples
+ --------
+ ::
+
+ f = plt.figure()
+ nx = int(f.get_figwidth() * f.dpi)
+ ny = int(f.get_figheight() * f.dpi)
+ data = np.random.random((ny, nx))
+ f.figimage(data)
+ plt.show()
+ """
+ if resize:
+ dpi = self.get_dpi()
+ figsize = [x / dpi for x in (X.shape[1], X.shape[0])]
+ self.set_size_inches(figsize, forward=True)
+
+ im = mimage.FigureImage(self, cmap=cmap, norm=norm,
+ colorizer=colorizer,
+ offsetx=xo, offsety=yo,
+ origin=origin, **kwargs)
+ im.stale_callback = _stale_figure_callback
+
+ im.set_array(X)
+ im.set_alpha(alpha)
+ if norm is None:
+ im._check_exclusionary_keywords(colorizer, vmin=vmin, vmax=vmax)
+ im.set_clim(vmin, vmax)
+ self.images.append(im)
+ im._remove_method = self.images.remove
+ self.stale = True
+ return im
+
+ def set_size_inches(self, w, h=None, forward=True):
+ """
+ Set the figure size in inches.
+
+ Call signatures::
+
+ fig.set_size_inches(w, h) # OR
+ fig.set_size_inches((w, h))
+
+ Parameters
+ ----------
+ w : (float, float) or float
+ Width and height in inches (if height not specified as a separate
+ argument) or width.
+ h : float
+ Height in inches.
+ forward : bool, default: True
+ If ``True``, the canvas size is automatically updated, e.g.,
+ you can resize the figure window from the shell.
+
+ See Also
+ --------
+ matplotlib.figure.Figure.get_size_inches
+ matplotlib.figure.Figure.set_figwidth
+ matplotlib.figure.Figure.set_figheight
+
+ Notes
+ -----
+ To transform from pixels to inches divide by `Figure.dpi`.
+ """
+ if h is None: # Got called with a single pair as argument.
+ w, h = w
+ size = np.array([w, h])
+ if not np.isfinite(size).all() or (size < 0).any():
+ raise ValueError(f'figure size must be positive finite not {size}')
+ self.bbox_inches.p1 = size
+ if forward:
+ manager = self.canvas.manager
+ if manager is not None:
+ manager.resize(*(size * self.dpi).astype(int))
+ self.stale = True
+
+ def get_size_inches(self):
+ """
+ Return the current size of the figure in inches.
+
+ Returns
+ -------
+ ndarray
+ The size (width, height) of the figure in inches.
+
+ See Also
+ --------
+ matplotlib.figure.Figure.set_size_inches
+ matplotlib.figure.Figure.get_figwidth
+ matplotlib.figure.Figure.get_figheight
+
+ Notes
+ -----
+ The size in pixels can be obtained by multiplying with `Figure.dpi`.
+ """
+ return np.array(self.bbox_inches.p1)
+
+ def get_figwidth(self):
+ """Return the figure width in inches."""
+ return self.bbox_inches.width
+
+ def get_figheight(self):
+ """Return the figure height in inches."""
+ return self.bbox_inches.height
+
+ def get_dpi(self):
+ """Return the resolution in dots per inch as a float."""
+ return self.dpi
+
+ def set_dpi(self, val):
+ """
+ Set the resolution of the figure in dots-per-inch.
+
+ Parameters
+ ----------
+ val : float
+ """
+ self.dpi = val
+ self.stale = True
+
+ def set_figwidth(self, val, forward=True):
+ """
+ Set the width of the figure in inches.
+
+ Parameters
+ ----------
+ val : float
+ forward : bool
+ See `set_size_inches`.
+
+ See Also
+ --------
+ matplotlib.figure.Figure.set_figheight
+ matplotlib.figure.Figure.set_size_inches
+ """
+ self.set_size_inches(val, self.get_figheight(), forward=forward)
+
+ def set_figheight(self, val, forward=True):
+ """
+ Set the height of the figure in inches.
+
+ Parameters
+ ----------
+ val : float
+ forward : bool
+ See `set_size_inches`.
+
+ See Also
+ --------
+ matplotlib.figure.Figure.set_figwidth
+ matplotlib.figure.Figure.set_size_inches
+ """
+ self.set_size_inches(self.get_figwidth(), val, forward=forward)
+
+ def clear(self, keep_observers=False):
+ # docstring inherited
+ super().clear(keep_observers=keep_observers)
+ # FigureBase.clear does not clear toolbars, as
+ # only Figure can have toolbars
+ toolbar = self.canvas.toolbar
+ if toolbar is not None:
+ toolbar.update()
+
+ @_finalize_rasterization
+ @allow_rasterization
+ def draw(self, renderer):
+ # docstring inherited
+ if not self.get_visible():
+ return
+
+ with self._render_lock:
+
+ artists = self._get_draw_artists(renderer)
+ try:
+ renderer.open_group('figure', gid=self.get_gid())
+ if self.axes and self.get_layout_engine() is not None:
+ try:
+ self.get_layout_engine().execute(self)
+ except ValueError:
+ pass
+ # ValueError can occur when resizing a window.
+
+ self.patch.draw(renderer)
+ mimage._draw_list_compositing_images(
+ renderer, self, artists, self.suppressComposite)
+
+ renderer.close_group('figure')
+ finally:
+ self.stale = False
+
+ DrawEvent("draw_event", self.canvas, renderer)._process()
+
+ def draw_without_rendering(self):
+ """
+ Draw the figure with no output. Useful to get the final size of
+ artists that require a draw before their size is known (e.g. text).
+ """
+ renderer = _get_renderer(self)
+ with renderer._draw_disabled():
+ self.draw(renderer)
+
+ def draw_artist(self, a):
+ """
+ Draw `.Artist` *a* only.
+ """
+ a.draw(self.canvas.get_renderer())
+
+ def __getstate__(self):
+ state = super().__getstate__()
+
+ # The canvas cannot currently be pickled, but this has the benefit
+ # of meaning that a figure can be detached from one canvas, and
+ # re-attached to another.
+ state.pop("canvas")
+
+ # discard any changes to the dpi due to pixel ratio changes
+ state["_dpi"] = state.get('_original_dpi', state['_dpi'])
+
+ # add version information to the state
+ state['__mpl_version__'] = mpl.__version__
+
+ # check whether the figure manager (if any) is registered with pyplot
+ from matplotlib import _pylab_helpers
+ if self.canvas.manager in _pylab_helpers.Gcf.figs.values():
+ state['_restore_to_pylab'] = True
+ return state
+
+ def __setstate__(self, state):
+ version = state.pop('__mpl_version__')
+ restore_to_pylab = state.pop('_restore_to_pylab', False)
+
+ if version != mpl.__version__:
+ _api.warn_external(
+ f"This figure was saved with matplotlib version {version} and "
+ f"loaded with {mpl.__version__} so may not function correctly."
+ )
+ self.__dict__ = state
+
+ # re-initialise some of the unstored state information
+ FigureCanvasBase(self) # Set self.canvas.
+
+ if restore_to_pylab:
+ # lazy import to avoid circularity
+ import matplotlib.pyplot as plt
+ import matplotlib._pylab_helpers as pylab_helpers
+ allnums = plt.get_fignums()
+ num = max(allnums) + 1 if allnums else 1
+ backend = plt._get_backend_mod()
+ mgr = backend.new_figure_manager_given_figure(num, self)
+ pylab_helpers.Gcf._set_new_active_manager(mgr)
+ plt.draw_if_interactive()
+
+ self.stale = True
+
+ def add_axobserver(self, func):
+ """Whenever the Axes state change, ``func(self)`` will be called."""
+ # Connect a wrapper lambda and not func itself, to avoid it being
+ # weakref-collected.
+ self._axobservers.connect("_axes_change_event", lambda arg: func(arg))
+
+ def savefig(self, fname, *, transparent=None, **kwargs):
+ """
+ Save the current figure as an image or vector graphic to a file.
+
+ Call signature::
+
+ savefig(fname, *, transparent=None, dpi='figure', format=None,
+ metadata=None, bbox_inches=None, pad_inches=0.1,
+ facecolor='auto', edgecolor='auto', backend=None,
+ **kwargs
+ )
+
+ The available output formats depend on the backend being used.
+
+ Parameters
+ ----------
+ fname : str or path-like or binary file-like
+ A path, or a Python file-like object, or
+ possibly some backend-dependent object such as
+ `matplotlib.backends.backend_pdf.PdfPages`.
+
+ If *format* is set, it determines the output format, and the file
+ is saved as *fname*. Note that *fname* is used verbatim, and there
+ is no attempt to make the extension, if any, of *fname* match
+ *format*, and no extension is appended.
+
+ If *format* is not set, then the format is inferred from the
+ extension of *fname*, if there is one. If *format* is not
+ set and *fname* has no extension, then the file is saved with
+ :rc:`savefig.format` and the appropriate extension is appended to
+ *fname*.
+
+ Other Parameters
+ ----------------
+ transparent : bool, default: :rc:`savefig.transparent`
+ If *True*, the Axes patches will all be transparent; the
+ Figure patch will also be transparent unless *facecolor*
+ and/or *edgecolor* are specified via kwargs.
+
+ If *False* has no effect and the color of the Axes and
+ Figure patches are unchanged (unless the Figure patch
+ is specified via the *facecolor* and/or *edgecolor* keyword
+ arguments in which case those colors are used).
+
+ The transparency of these patches will be restored to their
+ original values upon exit of this function.
+
+ This is useful, for example, for displaying
+ a plot on top of a colored background on a web page.
+
+ dpi : float or 'figure', default: :rc:`savefig.dpi`
+ The resolution in dots per inch. If 'figure', use the figure's
+ dpi value.
+
+ format : str
+ The file format, e.g. 'png', 'pdf', 'svg', ... The behavior when
+ this is unset is documented under *fname*.
+
+ metadata : dict, optional
+ Key/value pairs to store in the image metadata. The supported keys
+ and defaults depend on the image format and backend:
+
+ - 'png' with Agg backend: See the parameter ``metadata`` of
+ `~.FigureCanvasAgg.print_png`.
+ - 'pdf' with pdf backend: See the parameter ``metadata`` of
+ `~.backend_pdf.PdfPages`.
+ - 'svg' with svg backend: See the parameter ``metadata`` of
+ `~.FigureCanvasSVG.print_svg`.
+ - 'eps' and 'ps' with PS backend: Only 'Creator' is supported.
+
+ Not supported for 'pgf', 'raw', and 'rgba' as those formats do not support
+ embedding metadata.
+ Does not currently support 'jpg', 'tiff', or 'webp', but may include
+ embedding EXIF metadata in the future.
+
+ bbox_inches : str or `.Bbox`, default: :rc:`savefig.bbox`
+ Bounding box in inches: only the given portion of the figure is
+ saved. If 'tight', try to figure out the tight bbox of the figure.
+
+ pad_inches : float or 'layout', default: :rc:`savefig.pad_inches`
+ Amount of padding in inches around the figure when bbox_inches is
+ 'tight'. If 'layout' use the padding from the constrained or
+ compressed layout engine; ignored if one of those engines is not in
+ use.
+
+ facecolor : :mpltype:`color` or 'auto', default: :rc:`savefig.facecolor`
+ The facecolor of the figure. If 'auto', use the current figure
+ facecolor.
+
+ edgecolor : :mpltype:`color` or 'auto', default: :rc:`savefig.edgecolor`
+ The edgecolor of the figure. If 'auto', use the current figure
+ edgecolor.
+
+ backend : str, optional
+ Use a non-default backend to render the file, e.g. to render a
+ png file with the "cairo" backend rather than the default "agg",
+ or a pdf file with the "pgf" backend rather than the default
+ "pdf". Note that the default backend is normally sufficient. See
+ :ref:`the-builtin-backends` for a list of valid backends for each
+ file format. Custom backends can be referenced as "module://...".
+
+ orientation : {'landscape', 'portrait'}
+ Currently only supported by the postscript backend.
+
+ papertype : str
+ One of 'letter', 'legal', 'executive', 'ledger', 'a0' through
+ 'a10', 'b0' through 'b10'. Only supported for postscript
+ output.
+
+ bbox_extra_artists : list of `~matplotlib.artist.Artist`, optional
+ A list of extra artists that will be considered when the
+ tight bbox is calculated.
+
+ pil_kwargs : dict, optional
+ Additional keyword arguments that are passed to
+ `PIL.Image.Image.save` when saving the figure.
+
+ """
+
+ kwargs.setdefault('dpi', mpl.rcParams['savefig.dpi'])
+ if transparent is None:
+ transparent = mpl.rcParams['savefig.transparent']
+
+ with ExitStack() as stack:
+ if transparent:
+ def _recursively_make_subfig_transparent(exit_stack, subfig):
+ exit_stack.enter_context(
+ subfig.patch._cm_set(
+ facecolor="none", edgecolor="none"))
+ for ax in subfig.axes:
+ exit_stack.enter_context(
+ ax.patch._cm_set(
+ facecolor="none", edgecolor="none"))
+ for sub_subfig in subfig.subfigs:
+ _recursively_make_subfig_transparent(
+ exit_stack, sub_subfig)
+
+ def _recursively_make_axes_transparent(exit_stack, ax):
+ exit_stack.enter_context(
+ ax.patch._cm_set(facecolor="none", edgecolor="none"))
+ for child_ax in ax.child_axes:
+ exit_stack.enter_context(
+ child_ax.patch._cm_set(
+ facecolor="none", edgecolor="none"))
+ for child_childax in ax.child_axes:
+ _recursively_make_axes_transparent(
+ exit_stack, child_childax)
+
+ kwargs.setdefault('facecolor', 'none')
+ kwargs.setdefault('edgecolor', 'none')
+ # set subfigure to appear transparent in printed image
+ for subfig in self.subfigs:
+ _recursively_make_subfig_transparent(stack, subfig)
+ # set Axes to be transparent
+ for ax in self.axes:
+ _recursively_make_axes_transparent(stack, ax)
+ self.canvas.print_figure(fname, **kwargs)
+
+ def ginput(self, n=1, timeout=30, show_clicks=True,
+ mouse_add=MouseButton.LEFT,
+ mouse_pop=MouseButton.RIGHT,
+ mouse_stop=MouseButton.MIDDLE):
+ """
+ Blocking call to interact with a figure.
+
+ Wait until the user clicks *n* times on the figure, and return the
+ coordinates of each click in a list.
+
+ There are three possible interactions:
+
+ - Add a point.
+ - Remove the most recently added point.
+ - Stop the interaction and return the points added so far.
+
+ The actions are assigned to mouse buttons via the arguments
+ *mouse_add*, *mouse_pop* and *mouse_stop*.
+
+ Parameters
+ ----------
+ n : int, default: 1
+ Number of mouse clicks to accumulate. If negative, accumulate
+ clicks until the input is terminated manually.
+ timeout : float, default: 30 seconds
+ Number of seconds to wait before timing out. If zero or negative
+ will never time out.
+ show_clicks : bool, default: True
+ If True, show a red cross at the location of each click.
+ mouse_add : `.MouseButton` or None, default: `.MouseButton.LEFT`
+ Mouse button used to add points.
+ mouse_pop : `.MouseButton` or None, default: `.MouseButton.RIGHT`
+ Mouse button used to remove the most recently added point.
+ mouse_stop : `.MouseButton` or None, default: `.MouseButton.MIDDLE`
+ Mouse button used to stop input.
+
+ Returns
+ -------
+ list of tuples
+ A list of the clicked (x, y) coordinates.
+
+ Notes
+ -----
+ The keyboard can also be used to select points in case your mouse
+ does not have one or more of the buttons. The delete and backspace
+ keys act like right-clicking (i.e., remove last point), the enter key
+ terminates input and any other key (not already used by the window
+ manager) selects a point.
+ """
+ clicks = []
+ marks = []
+
+ def handler(event):
+ is_button = event.name == "button_press_event"
+ is_key = event.name == "key_press_event"
+ # Quit (even if not in infinite mode; this is consistent with
+ # MATLAB and sometimes quite useful, but will require the user to
+ # test how many points were actually returned before using data).
+ if (is_button and event.button == mouse_stop
+ or is_key and event.key in ["escape", "enter"]):
+ self.canvas.stop_event_loop()
+ # Pop last click.
+ elif (is_button and event.button == mouse_pop
+ or is_key and event.key in ["backspace", "delete"]):
+ if clicks:
+ clicks.pop()
+ if show_clicks:
+ marks.pop().remove()
+ self.canvas.draw()
+ # Add new click.
+ elif (is_button and event.button == mouse_add
+ # On macOS/gtk, some keys return None.
+ or is_key and event.key is not None):
+ if event.inaxes:
+ clicks.append((event.xdata, event.ydata))
+ _log.info("input %i: %f, %f",
+ len(clicks), event.xdata, event.ydata)
+ if show_clicks:
+ line = mpl.lines.Line2D([event.xdata], [event.ydata],
+ marker="+", color="r")
+ event.inaxes.add_line(line)
+ marks.append(line)
+ self.canvas.draw()
+ if len(clicks) == n and n > 0:
+ self.canvas.stop_event_loop()
+
+ _blocking_input.blocking_input_loop(
+ self, ["button_press_event", "key_press_event"], timeout, handler)
+
+ # Cleanup.
+ for mark in marks:
+ mark.remove()
+ self.canvas.draw()
+
+ return clicks
+
+ def waitforbuttonpress(self, timeout=-1):
+ """
+ Blocking call to interact with the figure.
+
+ Wait for user input and return True if a key was pressed, False if a
+ mouse button was pressed and None if no input was given within
+ *timeout* seconds. Negative values deactivate *timeout*.
+ """
+ event = None
+
+ def handler(ev):
+ nonlocal event
+ event = ev
+ self.canvas.stop_event_loop()
+
+ _blocking_input.blocking_input_loop(
+ self, ["button_press_event", "key_press_event"], timeout, handler)
+
+ return None if event is None else event.name == "key_press_event"
+
+ def tight_layout(self, *, pad=1.08, h_pad=None, w_pad=None, rect=None):
+ """
+ Adjust the padding between and around subplots.
+
+ To exclude an artist on the Axes from the bounding box calculation
+ that determines the subplot parameters (i.e. legend, or annotation),
+ set ``a.set_in_layout(False)`` for that artist.
+
+ Parameters
+ ----------
+ pad : float, default: 1.08
+ Padding between the figure edge and the edges of subplots,
+ as a fraction of the font size.
+ h_pad, w_pad : float, default: *pad*
+ Padding (height/width) between edges of adjacent subplots,
+ as a fraction of the font size.
+ rect : tuple (left, bottom, right, top), default: (0, 0, 1, 1)
+ A rectangle in normalized figure coordinates into which the whole
+ subplots area (including labels) will fit.
+
+ See Also
+ --------
+ .Figure.set_layout_engine
+ .pyplot.tight_layout
+ """
+ # note that here we do not permanently set the figures engine to
+ # tight_layout but rather just perform the layout in place and remove
+ # any previous engines.
+ engine = TightLayoutEngine(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
+ try:
+ previous_engine = self.get_layout_engine()
+ self.set_layout_engine(engine)
+ engine.execute(self)
+ if previous_engine is not None and not isinstance(
+ previous_engine, (TightLayoutEngine, PlaceHolderLayoutEngine)
+ ):
+ _api.warn_external('The figure layout has changed to tight')
+ finally:
+ self.set_layout_engine('none')
+
+
+def figaspect(arg):
+ """
+ Calculate the width and height for a figure with a specified aspect ratio.
+
+ While the height is taken from :rc:`figure.figsize`, the width is
+ adjusted to match the desired aspect ratio. Additionally, it is ensured
+ that the width is in the range [4., 16.] and the height is in the range
+ [2., 16.]. If necessary, the default height is adjusted to ensure this.
+
+ Parameters
+ ----------
+ arg : float or 2D array
+ If a float, this defines the aspect ratio (i.e. the ratio height /
+ width).
+ In case of an array the aspect ratio is number of rows / number of
+ columns, so that the array could be fitted in the figure undistorted.
+
+ Returns
+ -------
+ size : (2,) array
+ The width and height of the figure in inches.
+
+ Notes
+ -----
+ If you want to create an Axes within the figure, that still preserves the
+ aspect ratio, be sure to create it with equal width and height. See
+ examples below.
+
+ Thanks to Fernando Perez for this function.
+
+ Examples
+ --------
+ Make a figure twice as tall as it is wide::
+
+ w, h = figaspect(2.)
+ fig = Figure(figsize=(w, h))
+ ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
+ ax.imshow(A, **kwargs)
+
+ Make a figure with the proper aspect for an array::
+
+ A = rand(5, 3)
+ w, h = figaspect(A)
+ fig = Figure(figsize=(w, h))
+ ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
+ ax.imshow(A, **kwargs)
+ """
+
+ isarray = hasattr(arg, 'shape') and not np.isscalar(arg)
+
+ # min/max sizes to respect when autoscaling. If John likes the idea, they
+ # could become rc parameters, for now they're hardwired.
+ figsize_min = np.array((4.0, 2.0)) # min length for width/height
+ figsize_max = np.array((16.0, 16.0)) # max length for width/height
+
+ # Extract the aspect ratio of the array
+ if isarray:
+ nr, nc = arg.shape[:2]
+ arr_ratio = nr / nc
+ else:
+ arr_ratio = arg
+
+ # Height of user figure defaults
+ fig_height = mpl.rcParams['figure.figsize'][1]
+
+ # New size for the figure, keeping the aspect ratio of the caller
+ newsize = np.array((fig_height / arr_ratio, fig_height))
+
+ # Sanity checks, don't drop either dimension below figsize_min
+ newsize /= min(1.0, *(newsize / figsize_min))
+
+ # Avoid humongous windows as well
+ newsize /= max(1.0, *(newsize / figsize_max))
+
+ # Finally, if we have a really funky aspect ratio, break it but respect
+ # the min/max dimensions (we don't want figures 10 feet tall!)
+ newsize = np.clip(newsize, figsize_min, figsize_max)
+ return newsize
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..64685ac58dfe430543af64aedc8ddfda1be7386b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.pyi
@@ -0,0 +1,428 @@
+from collections.abc import Callable, Hashable, Iterable, Sequence
+import os
+from typing import Any, IO, Literal, TypeVar, overload
+
+import numpy as np
+from numpy.typing import ArrayLike
+
+from matplotlib.artist import Artist
+from matplotlib.axes import Axes
+from matplotlib.backend_bases import (
+ FigureCanvasBase,
+ MouseButton,
+ MouseEvent,
+ RendererBase,
+)
+from matplotlib.colors import Colormap, Normalize
+from matplotlib.colorbar import Colorbar
+from matplotlib.colorizer import ColorizingArtist, Colorizer
+from matplotlib.cm import ScalarMappable
+from matplotlib.gridspec import GridSpec, SubplotSpec, SubplotParams as SubplotParams
+from matplotlib.image import _ImageBase, FigureImage
+from matplotlib.layout_engine import LayoutEngine
+from matplotlib.legend import Legend
+from matplotlib.lines import Line2D
+from matplotlib.patches import Rectangle, Patch
+from matplotlib.text import Text
+from matplotlib.transforms import Affine2D, Bbox, BboxBase, Transform
+from mpl_toolkits.mplot3d import Axes3D
+
+from .typing import ColorType, HashableList
+
+_T = TypeVar("_T")
+
+class FigureBase(Artist):
+ artists: list[Artist]
+ lines: list[Line2D]
+ patches: list[Patch]
+ texts: list[Text]
+ images: list[_ImageBase]
+ legends: list[Legend]
+ subfigs: list[SubFigure]
+ stale: bool
+ suppressComposite: bool | None
+ def __init__(self, **kwargs) -> None: ...
+ def autofmt_xdate(
+ self,
+ bottom: float = ...,
+ rotation: int = ...,
+ ha: Literal["left", "center", "right"] = ...,
+ which: Literal["major", "minor", "both"] = ...,
+ ) -> None: ...
+ def get_children(self) -> list[Artist]: ...
+ def contains(self, mouseevent: MouseEvent) -> tuple[bool, dict[Any, Any]]: ...
+ def suptitle(self, t: str, **kwargs) -> Text: ...
+ def get_suptitle(self) -> str: ...
+ def supxlabel(self, t: str, **kwargs) -> Text: ...
+ def get_supxlabel(self) -> str: ...
+ def supylabel(self, t: str, **kwargs) -> Text: ...
+ def get_supylabel(self) -> str: ...
+ def get_edgecolor(self) -> ColorType: ...
+ def get_facecolor(self) -> ColorType: ...
+ def get_frameon(self) -> bool: ...
+ def set_linewidth(self, linewidth: float) -> None: ...
+ def get_linewidth(self) -> float: ...
+ def set_edgecolor(self, color: ColorType) -> None: ...
+ def set_facecolor(self, color: ColorType) -> None: ...
+ @overload
+ def get_figure(self, root: Literal[True]) -> Figure: ...
+ @overload
+ def get_figure(self, root: Literal[False]) -> Figure | SubFigure: ...
+ @overload
+ def get_figure(self, root: bool = ...) -> Figure | SubFigure: ...
+ def set_frameon(self, b: bool) -> None: ...
+ @property
+ def frameon(self) -> bool: ...
+ @frameon.setter
+ def frameon(self, b: bool) -> None: ...
+ def add_artist(self, artist: Artist, clip: bool = ...) -> Artist: ...
+ @overload
+ def add_axes(self, ax: Axes) -> Axes: ...
+ @overload
+ def add_axes(
+ self,
+ rect: tuple[float, float, float, float],
+ projection: None | str = ...,
+ polar: bool = ...,
+ **kwargs
+ ) -> Axes: ...
+
+ # TODO: docstring indicates SubplotSpec a valid arg, but none of the listed signatures appear to be that
+ @overload
+ def add_subplot(self, *args: Any, projection: Literal["3d"], **kwargs: Any) -> Axes3D: ...
+ @overload
+ def add_subplot(
+ self, nrows: int, ncols: int, index: int | tuple[int, int], **kwargs: Any
+ ) -> Axes: ...
+ @overload
+ def add_subplot(self, pos: int, **kwargs: Any) -> Axes: ...
+ @overload
+ def add_subplot(self, ax: Axes, **kwargs: Any) -> Axes: ...
+ @overload
+ def add_subplot(self, ax: SubplotSpec, **kwargs: Any) -> Axes: ...
+ @overload
+ def add_subplot(self, **kwargs: Any) -> Axes: ...
+
+ @overload
+ def subplots(
+ self,
+ nrows: Literal[1] = ...,
+ ncols: Literal[1] = ...,
+ *,
+ sharex: bool | Literal["none", "all", "row", "col"] = ...,
+ sharey: bool | Literal["none", "all", "row", "col"] = ...,
+ squeeze: Literal[True] = ...,
+ width_ratios: Sequence[float] | None = ...,
+ height_ratios: Sequence[float] | None = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ ) -> Axes: ...
+ @overload
+ def subplots(
+ self,
+ nrows: int = ...,
+ ncols: int = ...,
+ *,
+ sharex: bool | Literal["none", "all", "row", "col"] = ...,
+ sharey: bool | Literal["none", "all", "row", "col"] = ...,
+ squeeze: Literal[False],
+ width_ratios: Sequence[float] | None = ...,
+ height_ratios: Sequence[float] | None = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ ) -> np.ndarray: ... # TODO numpy/numpy#24738
+ @overload
+ def subplots(
+ self,
+ nrows: int = ...,
+ ncols: int = ...,
+ *,
+ sharex: bool | Literal["none", "all", "row", "col"] = ...,
+ sharey: bool | Literal["none", "all", "row", "col"] = ...,
+ squeeze: bool = ...,
+ width_ratios: Sequence[float] | None = ...,
+ height_ratios: Sequence[float] | None = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ ) -> Any: ...
+ def delaxes(self, ax: Axes) -> None: ...
+ def clear(self, keep_observers: bool = ...) -> None: ...
+ def clf(self, keep_observers: bool = ...) -> None: ...
+
+ @overload
+ def legend(self) -> Legend: ...
+ @overload
+ def legend(self, handles: Iterable[Artist], labels: Iterable[str], **kwargs) -> Legend: ...
+ @overload
+ def legend(self, *, handles: Iterable[Artist], **kwargs) -> Legend: ...
+ @overload
+ def legend(self, labels: Iterable[str], **kwargs) -> Legend: ...
+ @overload
+ def legend(self, **kwargs) -> Legend: ...
+
+ def text(
+ self,
+ x: float,
+ y: float,
+ s: str,
+ fontdict: dict[str, Any] | None = ...,
+ **kwargs
+ ) -> Text: ...
+ def colorbar(
+ self,
+ mappable: ScalarMappable | ColorizingArtist,
+ cax: Axes | None = ...,
+ ax: Axes | Iterable[Axes] | None = ...,
+ use_gridspec: bool = ...,
+ **kwargs
+ ) -> Colorbar: ...
+ def subplots_adjust(
+ self,
+ left: float | None = ...,
+ bottom: float | None = ...,
+ right: float | None = ...,
+ top: float | None = ...,
+ wspace: float | None = ...,
+ hspace: float | None = ...,
+ ) -> None: ...
+ def align_xlabels(self, axs: Iterable[Axes] | None = ...) -> None: ...
+ def align_ylabels(self, axs: Iterable[Axes] | None = ...) -> None: ...
+ def align_titles(self, axs: Iterable[Axes] | None = ...) -> None: ...
+ def align_labels(self, axs: Iterable[Axes] | None = ...) -> None: ...
+ def add_gridspec(self, nrows: int = ..., ncols: int = ..., **kwargs) -> GridSpec: ...
+ @overload
+ def subfigures(
+ self,
+ nrows: int = ...,
+ ncols: int = ...,
+ squeeze: Literal[False] = ...,
+ wspace: float | None = ...,
+ hspace: float | None = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ **kwargs
+ ) -> np.ndarray: ...
+ @overload
+ def subfigures(
+ self,
+ nrows: int = ...,
+ ncols: int = ...,
+ squeeze: Literal[True] = ...,
+ wspace: float | None = ...,
+ hspace: float | None = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ **kwargs
+ ) -> np.ndarray | SubFigure: ...
+ def add_subfigure(self, subplotspec: SubplotSpec, **kwargs) -> SubFigure: ...
+ def sca(self, a: Axes) -> Axes: ...
+ def gca(self) -> Axes: ...
+ def _gci(self) -> ColorizingArtist | None: ...
+ def _process_projection_requirements(
+ self, *, axes_class=None, polar=False, projection=None, **kwargs
+ ) -> tuple[type[Axes], dict[str, Any]]: ...
+ def get_default_bbox_extra_artists(self) -> list[Artist]: ...
+ def get_tightbbox(
+ self,
+ renderer: RendererBase | None = ...,
+ *,
+ bbox_extra_artists: Iterable[Artist] | None = ...,
+ ) -> Bbox: ...
+ @overload
+ def subplot_mosaic(
+ self,
+ mosaic: str,
+ *,
+ sharex: bool = ...,
+ sharey: bool = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ empty_sentinel: str = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ ) -> dict[str, Axes]: ...
+ @overload
+ def subplot_mosaic(
+ self,
+ mosaic: list[HashableList[_T]],
+ *,
+ sharex: bool = ...,
+ sharey: bool = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ empty_sentinel: _T = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ ) -> dict[_T, Axes]: ...
+ @overload
+ def subplot_mosaic(
+ self,
+ mosaic: list[HashableList[Hashable]],
+ *,
+ sharex: bool = ...,
+ sharey: bool = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ empty_sentinel: Any = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ ) -> dict[Hashable, Axes]: ...
+
+class SubFigure(FigureBase):
+ @property
+ def figure(self) -> Figure: ...
+ subplotpars: SubplotParams
+ dpi_scale_trans: Affine2D
+ transFigure: Transform
+ bbox_relative: Bbox
+ figbbox: BboxBase
+ bbox: BboxBase
+ transSubfigure: Transform
+ patch: Rectangle
+ def __init__(
+ self,
+ parent: Figure | SubFigure,
+ subplotspec: SubplotSpec,
+ *,
+ facecolor: ColorType | None = ...,
+ edgecolor: ColorType | None = ...,
+ linewidth: float = ...,
+ frameon: bool | None = ...,
+ **kwargs
+ ) -> None: ...
+ @property
+ def canvas(self) -> FigureCanvasBase: ...
+ @property
+ def dpi(self) -> float: ...
+ @dpi.setter
+ def dpi(self, value: float) -> None: ...
+ def get_dpi(self) -> float: ...
+ def set_dpi(self, val) -> None: ...
+ def get_constrained_layout(self) -> bool: ...
+ def get_constrained_layout_pads(
+ self, relative: bool = ...
+ ) -> tuple[float, float, float, float]: ...
+ def get_layout_engine(self) -> LayoutEngine: ...
+ @property # type: ignore[misc]
+ def axes(self) -> list[Axes]: ... # type: ignore[override]
+ def get_axes(self) -> list[Axes]: ...
+
+class Figure(FigureBase):
+ @property
+ def figure(self) -> Figure: ...
+ bbox_inches: Bbox
+ dpi_scale_trans: Affine2D
+ bbox: BboxBase
+ figbbox: BboxBase
+ transFigure: Transform
+ transSubfigure: Transform
+ patch: Rectangle
+ subplotpars: SubplotParams
+ def __init__(
+ self,
+ figsize: tuple[float, float] | None = ...,
+ dpi: float | None = ...,
+ *,
+ facecolor: ColorType | None = ...,
+ edgecolor: ColorType | None = ...,
+ linewidth: float = ...,
+ frameon: bool | None = ...,
+ subplotpars: SubplotParams | None = ...,
+ tight_layout: bool | dict[str, Any] | None = ...,
+ constrained_layout: bool | dict[str, Any] | None = ...,
+ layout: Literal["constrained", "compressed", "tight"]
+ | LayoutEngine
+ | None = ...,
+ **kwargs
+ ) -> None: ...
+ def pick(self, mouseevent: MouseEvent) -> None: ...
+ def set_layout_engine(
+ self,
+ layout: Literal["constrained", "compressed", "tight", "none"]
+ | LayoutEngine
+ | None = ...,
+ **kwargs
+ ) -> None: ...
+ def get_layout_engine(self) -> LayoutEngine | None: ...
+ def _repr_html_(self) -> str | None: ...
+ def show(self, warn: bool = ...) -> None: ...
+ @property
+ def number(self) -> int | str: ...
+ @number.setter
+ def number(self, num: int | str) -> None: ...
+ @property # type: ignore[misc]
+ def axes(self) -> list[Axes]: ... # type: ignore[override]
+ def get_axes(self) -> list[Axes]: ...
+ @property
+ def dpi(self) -> float: ...
+ @dpi.setter
+ def dpi(self, dpi: float) -> None: ...
+ def get_tight_layout(self) -> bool: ...
+ def get_constrained_layout_pads(
+ self, relative: bool = ...
+ ) -> tuple[float, float, float, float]: ...
+ def get_constrained_layout(self) -> bool: ...
+ canvas: FigureCanvasBase
+ def set_canvas(self, canvas: FigureCanvasBase) -> None: ...
+ def figimage(
+ self,
+ X: ArrayLike,
+ xo: int = ...,
+ yo: int = ...,
+ alpha: float | None = ...,
+ norm: str | Normalize | None = ...,
+ cmap: str | Colormap | None = ...,
+ vmin: float | None = ...,
+ vmax: float | None = ...,
+ origin: Literal["upper", "lower"] | None = ...,
+ resize: bool = ...,
+ *,
+ colorizer: Colorizer | None = ...,
+ **kwargs
+ ) -> FigureImage: ...
+ def set_size_inches(
+ self, w: float | tuple[float, float], h: float | None = ..., forward: bool = ...
+ ) -> None: ...
+ def get_size_inches(self) -> np.ndarray: ...
+ def get_figwidth(self) -> float: ...
+ def get_figheight(self) -> float: ...
+ def get_dpi(self) -> float: ...
+ def set_dpi(self, val: float) -> None: ...
+ def set_figwidth(self, val: float, forward: bool = ...) -> None: ...
+ def set_figheight(self, val: float, forward: bool = ...) -> None: ...
+ def clear(self, keep_observers: bool = ...) -> None: ...
+ def draw_without_rendering(self) -> None: ...
+ def draw_artist(self, a: Artist) -> None: ...
+ def add_axobserver(self, func: Callable[[Figure], Any]) -> None: ...
+ def savefig(
+ self,
+ fname: str | os.PathLike | IO,
+ *,
+ transparent: bool | None = ...,
+ **kwargs
+ ) -> None: ...
+ def ginput(
+ self,
+ n: int = ...,
+ timeout: float = ...,
+ show_clicks: bool = ...,
+ mouse_add: MouseButton = ...,
+ mouse_pop: MouseButton = ...,
+ mouse_stop: MouseButton = ...,
+ ) -> list[tuple[int, int]]: ...
+ def waitforbuttonpress(self, timeout: float = ...) -> None | bool: ...
+ def tight_layout(
+ self,
+ *,
+ pad: float = ...,
+ h_pad: float | None = ...,
+ w_pad: float | None = ...,
+ rect: tuple[float, float, float, float] | None = ...
+ ) -> None: ...
+
+def figaspect(
+ arg: float | ArrayLike,
+) -> np.ndarray[tuple[Literal[2]], np.dtype[np.float64]]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..9aa8dccde444b8998f34ded5cd9cfe07e7b46f6a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.py
@@ -0,0 +1,1645 @@
+"""
+A module for finding, managing, and using fonts across platforms.
+
+This module provides a single `FontManager` instance, ``fontManager``, that can
+be shared across backends and platforms. The `findfont`
+function returns the best TrueType (TTF) font file in the local or
+system font path that matches the specified `FontProperties`
+instance. The `FontManager` also handles Adobe Font Metrics
+(AFM) font files for use by the PostScript backend.
+The `FontManager.addfont` function adds a custom font from a file without
+installing it into your operating system.
+
+The design is based on the `W3C Cascading Style Sheet, Level 1 (CSS1)
+font specification `_.
+Future versions may implement the Level 2 or 2.1 specifications.
+"""
+
+# KNOWN ISSUES
+#
+# - documentation
+# - font variant is untested
+# - font stretch is incomplete
+# - font size is incomplete
+# - default font algorithm needs improvement and testing
+# - setWeights function needs improvement
+# - 'light' is an invalid weight value, remove it.
+
+from __future__ import annotations
+
+from base64 import b64encode
+import copy
+import dataclasses
+from functools import lru_cache
+import functools
+from io import BytesIO
+import json
+import logging
+from numbers import Number
+import os
+from pathlib import Path
+import plistlib
+import re
+import subprocess
+import sys
+import threading
+
+import matplotlib as mpl
+from matplotlib import _api, _afm, cbook, ft2font
+from matplotlib._fontconfig_pattern import (
+ parse_fontconfig_pattern, generate_fontconfig_pattern)
+from matplotlib.rcsetup import _validators
+
+_log = logging.getLogger(__name__)
+
+font_scalings = {
+ 'xx-small': 0.579,
+ 'x-small': 0.694,
+ 'small': 0.833,
+ 'medium': 1.0,
+ 'large': 1.200,
+ 'x-large': 1.440,
+ 'xx-large': 1.728,
+ 'larger': 1.2,
+ 'smaller': 0.833,
+ None: 1.0,
+}
+stretch_dict = {
+ 'ultra-condensed': 100,
+ 'extra-condensed': 200,
+ 'condensed': 300,
+ 'semi-condensed': 400,
+ 'normal': 500,
+ 'semi-expanded': 600,
+ 'semi-extended': 600,
+ 'expanded': 700,
+ 'extended': 700,
+ 'extra-expanded': 800,
+ 'extra-extended': 800,
+ 'ultra-expanded': 900,
+ 'ultra-extended': 900,
+}
+weight_dict = {
+ 'ultralight': 100,
+ 'light': 200,
+ 'normal': 400,
+ 'regular': 400,
+ 'book': 400,
+ 'medium': 500,
+ 'roman': 500,
+ 'semibold': 600,
+ 'demibold': 600,
+ 'demi': 600,
+ 'bold': 700,
+ 'heavy': 800,
+ 'extra bold': 800,
+ 'black': 900,
+}
+_weight_regexes = [
+ # From fontconfig's FcFreeTypeQueryFaceInternal; not the same as
+ # weight_dict!
+ ("thin", 100),
+ ("extralight", 200),
+ ("ultralight", 200),
+ ("demilight", 350),
+ ("semilight", 350),
+ ("light", 300), # Needs to come *after* demi/semilight!
+ ("book", 380),
+ ("regular", 400),
+ ("normal", 400),
+ ("medium", 500),
+ ("demibold", 600),
+ ("demi", 600),
+ ("semibold", 600),
+ ("extrabold", 800),
+ ("superbold", 800),
+ ("ultrabold", 800),
+ ("bold", 700), # Needs to come *after* extra/super/ultrabold!
+ ("ultrablack", 1000),
+ ("superblack", 1000),
+ ("extrablack", 1000),
+ (r"\bultra", 1000),
+ ("black", 900), # Needs to come *after* ultra/super/extrablack!
+ ("heavy", 900),
+]
+font_family_aliases = {
+ 'serif',
+ 'sans-serif',
+ 'sans serif',
+ 'cursive',
+ 'fantasy',
+ 'monospace',
+ 'sans',
+}
+
+# OS Font paths
+try:
+ _HOME = Path.home()
+except Exception: # Exceptions thrown by home() are not specified...
+ _HOME = Path(os.devnull) # Just an arbitrary path with no children.
+MSFolders = \
+ r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
+MSFontDirectories = [
+ r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts',
+ r'SOFTWARE\Microsoft\Windows\CurrentVersion\Fonts']
+MSUserFontDirectories = [
+ str(_HOME / 'AppData/Local/Microsoft/Windows/Fonts'),
+ str(_HOME / 'AppData/Roaming/Microsoft/Windows/Fonts'),
+]
+X11FontDirectories = [
+ # an old standard installation point
+ "/usr/X11R6/lib/X11/fonts/TTF/",
+ "/usr/X11/lib/X11/fonts",
+ # here is the new standard location for fonts
+ "/usr/share/fonts/",
+ # documented as a good place to install new fonts
+ "/usr/local/share/fonts/",
+ # common application, not really useful
+ "/usr/lib/openoffice/share/fonts/truetype/",
+ # user fonts
+ str((Path(os.environ.get('XDG_DATA_HOME') or _HOME / ".local/share"))
+ / "fonts"),
+ str(_HOME / ".fonts"),
+]
+OSXFontDirectories = [
+ "/Library/Fonts/",
+ "/Network/Library/Fonts/",
+ "/System/Library/Fonts/",
+ # fonts installed via MacPorts
+ "/opt/local/share/fonts",
+ # user fonts
+ str(_HOME / "Library/Fonts"),
+]
+
+
+def get_fontext_synonyms(fontext):
+ """
+ Return a list of file extensions that are synonyms for
+ the given file extension *fileext*.
+ """
+ return {
+ 'afm': ['afm'],
+ 'otf': ['otf', 'ttc', 'ttf'],
+ 'ttc': ['otf', 'ttc', 'ttf'],
+ 'ttf': ['otf', 'ttc', 'ttf'],
+ }[fontext]
+
+
+def list_fonts(directory, extensions):
+ """
+ Return a list of all fonts matching any of the extensions, found
+ recursively under the directory.
+ """
+ extensions = ["." + ext for ext in extensions]
+ return [os.path.join(dirpath, filename)
+ # os.walk ignores access errors, unlike Path.glob.
+ for dirpath, _, filenames in os.walk(directory)
+ for filename in filenames
+ if Path(filename).suffix.lower() in extensions]
+
+
+def win32FontDirectory():
+ r"""
+ Return the user-specified font directory for Win32. This is
+ looked up from the registry key ::
+
+ \\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Fonts
+
+ If the key is not found, ``%WINDIR%\Fonts`` will be returned.
+ """ # noqa: E501
+ import winreg
+ try:
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, MSFolders) as user:
+ return winreg.QueryValueEx(user, 'Fonts')[0]
+ except OSError:
+ return os.path.join(os.environ['WINDIR'], 'Fonts')
+
+
+def _get_win32_installed_fonts():
+ """List the font paths known to the Windows registry."""
+ import winreg
+ items = set()
+ # Search and resolve fonts listed in the registry.
+ for domain, base_dirs in [
+ (winreg.HKEY_LOCAL_MACHINE, [win32FontDirectory()]), # System.
+ (winreg.HKEY_CURRENT_USER, MSUserFontDirectories), # User.
+ ]:
+ for base_dir in base_dirs:
+ for reg_path in MSFontDirectories:
+ try:
+ with winreg.OpenKey(domain, reg_path) as local:
+ for j in range(winreg.QueryInfoKey(local)[1]):
+ # value may contain the filename of the font or its
+ # absolute path.
+ key, value, tp = winreg.EnumValue(local, j)
+ if not isinstance(value, str):
+ continue
+ try:
+ # If value contains already an absolute path,
+ # then it is not changed further.
+ path = Path(base_dir, value).resolve()
+ except RuntimeError:
+ # Don't fail with invalid entries.
+ continue
+ items.add(path)
+ except (OSError, MemoryError):
+ continue
+ return items
+
+
+@lru_cache
+def _get_fontconfig_fonts():
+ """Cache and list the font paths known to ``fc-list``."""
+ try:
+ if b'--format' not in subprocess.check_output(['fc-list', '--help']):
+ _log.warning( # fontconfig 2.7 implemented --format.
+ 'Matplotlib needs fontconfig>=2.7 to query system fonts.')
+ return []
+ out = subprocess.check_output(['fc-list', '--format=%{file}\\n'])
+ except (OSError, subprocess.CalledProcessError):
+ return []
+ return [Path(os.fsdecode(fname)) for fname in out.split(b'\n')]
+
+
+@lru_cache
+def _get_macos_fonts():
+ """Cache and list the font paths known to ``system_profiler SPFontsDataType``."""
+ try:
+ d, = plistlib.loads(
+ subprocess.check_output(["system_profiler", "-xml", "SPFontsDataType"]))
+ except (OSError, subprocess.CalledProcessError, plistlib.InvalidFileException):
+ return []
+ return [Path(entry["path"]) for entry in d["_items"]]
+
+
+def findSystemFonts(fontpaths=None, fontext='ttf'):
+ """
+ Search for fonts in the specified font paths. If no paths are
+ given, will use a standard set of system paths, as well as the
+ list of fonts tracked by fontconfig if fontconfig is installed and
+ available. A list of TrueType fonts are returned by default with
+ AFM fonts as an option.
+ """
+ fontfiles = set()
+ fontexts = get_fontext_synonyms(fontext)
+
+ if fontpaths is None:
+ if sys.platform == 'win32':
+ installed_fonts = _get_win32_installed_fonts()
+ fontpaths = []
+ else:
+ installed_fonts = _get_fontconfig_fonts()
+ if sys.platform == 'darwin':
+ installed_fonts += _get_macos_fonts()
+ fontpaths = [*X11FontDirectories, *OSXFontDirectories]
+ else:
+ fontpaths = X11FontDirectories
+ fontfiles.update(str(path) for path in installed_fonts
+ if path.suffix.lower()[1:] in fontexts)
+
+ elif isinstance(fontpaths, str):
+ fontpaths = [fontpaths]
+
+ for path in fontpaths:
+ fontfiles.update(map(os.path.abspath, list_fonts(path, fontexts)))
+
+ return [fname for fname in fontfiles if os.path.exists(fname)]
+
+
+@dataclasses.dataclass(frozen=True)
+class FontEntry:
+ """
+ A class for storing Font properties.
+
+ It is used when populating the font lookup dictionary.
+ """
+
+ fname: str = ''
+ name: str = ''
+ style: str = 'normal'
+ variant: str = 'normal'
+ weight: str | int = 'normal'
+ stretch: str = 'normal'
+ size: str = 'medium'
+
+ def _repr_html_(self) -> str:
+ png_stream = self._repr_png_()
+ png_b64 = b64encode(png_stream).decode()
+ return f" "
+
+ def _repr_png_(self) -> bytes:
+ from matplotlib.figure import Figure # Circular import.
+ fig = Figure()
+ font_path = Path(self.fname) if self.fname != '' else None
+ fig.text(0, 0, self.name, font=font_path)
+ with BytesIO() as buf:
+ fig.savefig(buf, bbox_inches='tight', transparent=True)
+ return buf.getvalue()
+
+
+def ttfFontProperty(font):
+ """
+ Extract information from a TrueType font file.
+
+ Parameters
+ ----------
+ font : `.FT2Font`
+ The TrueType font file from which information will be extracted.
+
+ Returns
+ -------
+ `FontEntry`
+ The extracted font properties.
+
+ """
+ name = font.family_name
+
+ # Styles are: italic, oblique, and normal (default)
+
+ sfnt = font.get_sfnt()
+ mac_key = (1, # platform: macintosh
+ 0, # id: roman
+ 0) # langid: english
+ ms_key = (3, # platform: microsoft
+ 1, # id: unicode_cs
+ 0x0409) # langid: english_united_states
+
+ # These tables are actually mac_roman-encoded, but mac_roman support may be
+ # missing in some alternative Python implementations and we are only going
+ # to look for ASCII substrings, where any ASCII-compatible encoding works
+ # - or big-endian UTF-16, since important Microsoft fonts use that.
+ sfnt2 = (sfnt.get((*mac_key, 2), b'').decode('latin-1').lower() or
+ sfnt.get((*ms_key, 2), b'').decode('utf_16_be').lower())
+ sfnt4 = (sfnt.get((*mac_key, 4), b'').decode('latin-1').lower() or
+ sfnt.get((*ms_key, 4), b'').decode('utf_16_be').lower())
+
+ if sfnt4.find('oblique') >= 0:
+ style = 'oblique'
+ elif sfnt4.find('italic') >= 0:
+ style = 'italic'
+ elif sfnt2.find('regular') >= 0:
+ style = 'normal'
+ elif ft2font.StyleFlags.ITALIC in font.style_flags:
+ style = 'italic'
+ else:
+ style = 'normal'
+
+ # Variants are: small-caps and normal (default)
+
+ # !!!! Untested
+ if name.lower() in ['capitals', 'small-caps']:
+ variant = 'small-caps'
+ else:
+ variant = 'normal'
+
+ # The weight-guessing algorithm is directly translated from fontconfig
+ # 2.13.1's FcFreeTypeQueryFaceInternal (fcfreetype.c).
+ wws_subfamily = 22
+ typographic_subfamily = 16
+ font_subfamily = 2
+ styles = [
+ sfnt.get((*mac_key, wws_subfamily), b'').decode('latin-1'),
+ sfnt.get((*mac_key, typographic_subfamily), b'').decode('latin-1'),
+ sfnt.get((*mac_key, font_subfamily), b'').decode('latin-1'),
+ sfnt.get((*ms_key, wws_subfamily), b'').decode('utf-16-be'),
+ sfnt.get((*ms_key, typographic_subfamily), b'').decode('utf-16-be'),
+ sfnt.get((*ms_key, font_subfamily), b'').decode('utf-16-be'),
+ ]
+ styles = [*filter(None, styles)] or [font.style_name]
+
+ def get_weight(): # From fontconfig's FcFreeTypeQueryFaceInternal.
+ # OS/2 table weight.
+ os2 = font.get_sfnt_table("OS/2")
+ if os2 and os2["version"] != 0xffff:
+ return os2["usWeightClass"]
+ # PostScript font info weight.
+ try:
+ ps_font_info_weight = (
+ font.get_ps_font_info()["weight"].replace(" ", "") or "")
+ except ValueError:
+ pass
+ else:
+ for regex, weight in _weight_regexes:
+ if re.fullmatch(regex, ps_font_info_weight, re.I):
+ return weight
+ # Style name weight.
+ for style in styles:
+ style = style.replace(" ", "")
+ for regex, weight in _weight_regexes:
+ if re.search(regex, style, re.I):
+ return weight
+ if ft2font.StyleFlags.BOLD in font.style_flags:
+ return 700 # "bold"
+ return 500 # "medium", not "regular"!
+
+ weight = int(get_weight())
+
+ # Stretch can be absolute and relative
+ # Absolute stretches are: ultra-condensed, extra-condensed, condensed,
+ # semi-condensed, normal, semi-expanded, expanded, extra-expanded,
+ # and ultra-expanded.
+ # Relative stretches are: wider, narrower
+ # Child value is: inherit
+
+ if any(word in sfnt4 for word in ['narrow', 'condensed', 'cond']):
+ stretch = 'condensed'
+ elif 'demi cond' in sfnt4:
+ stretch = 'semi-condensed'
+ elif any(word in sfnt4 for word in ['wide', 'expanded', 'extended']):
+ stretch = 'expanded'
+ else:
+ stretch = 'normal'
+
+ # Sizes can be absolute and relative.
+ # Absolute sizes are: xx-small, x-small, small, medium, large, x-large,
+ # and xx-large.
+ # Relative sizes are: larger, smaller
+ # Length value is an absolute font size, e.g., 12pt
+ # Percentage values are in 'em's. Most robust specification.
+
+ if not font.scalable:
+ raise NotImplementedError("Non-scalable fonts are not supported")
+ size = 'scalable'
+
+ return FontEntry(font.fname, name, style, variant, weight, stretch, size)
+
+
+def afmFontProperty(fontpath, font):
+ """
+ Extract information from an AFM font file.
+
+ Parameters
+ ----------
+ fontpath : str
+ The filename corresponding to *font*.
+ font : AFM
+ The AFM font file from which information will be extracted.
+
+ Returns
+ -------
+ `FontEntry`
+ The extracted font properties.
+ """
+
+ name = font.get_familyname()
+ fontname = font.get_fontname().lower()
+
+ # Styles are: italic, oblique, and normal (default)
+
+ if font.get_angle() != 0 or 'italic' in name.lower():
+ style = 'italic'
+ elif 'oblique' in name.lower():
+ style = 'oblique'
+ else:
+ style = 'normal'
+
+ # Variants are: small-caps and normal (default)
+
+ # !!!! Untested
+ if name.lower() in ['capitals', 'small-caps']:
+ variant = 'small-caps'
+ else:
+ variant = 'normal'
+
+ weight = font.get_weight().lower()
+ if weight not in weight_dict:
+ weight = 'normal'
+
+ # Stretch can be absolute and relative
+ # Absolute stretches are: ultra-condensed, extra-condensed, condensed,
+ # semi-condensed, normal, semi-expanded, expanded, extra-expanded,
+ # and ultra-expanded.
+ # Relative stretches are: wider, narrower
+ # Child value is: inherit
+ if 'demi cond' in fontname:
+ stretch = 'semi-condensed'
+ elif any(word in fontname for word in ['narrow', 'cond']):
+ stretch = 'condensed'
+ elif any(word in fontname for word in ['wide', 'expanded', 'extended']):
+ stretch = 'expanded'
+ else:
+ stretch = 'normal'
+
+ # Sizes can be absolute and relative.
+ # Absolute sizes are: xx-small, x-small, small, medium, large, x-large,
+ # and xx-large.
+ # Relative sizes are: larger, smaller
+ # Length value is an absolute font size, e.g., 12pt
+ # Percentage values are in 'em's. Most robust specification.
+
+ # All AFM fonts are apparently scalable.
+
+ size = 'scalable'
+
+ return FontEntry(fontpath, name, style, variant, weight, stretch, size)
+
+
+def _cleanup_fontproperties_init(init_method):
+ """
+ A decorator to limit the call signature to single a positional argument
+ or alternatively only keyword arguments.
+
+ We still accept but deprecate all other call signatures.
+
+ When the deprecation expires we can switch the signature to::
+
+ __init__(self, pattern=None, /, *, family=None, style=None, ...)
+
+ plus a runtime check that pattern is not used alongside with the
+ keyword arguments. This results eventually in the two possible
+ call signatures::
+
+ FontProperties(pattern)
+ FontProperties(family=..., size=..., ...)
+
+ """
+ @functools.wraps(init_method)
+ def wrapper(self, *args, **kwargs):
+ # multiple args with at least some positional ones
+ if len(args) > 1 or len(args) == 1 and kwargs:
+ # Note: Both cases were previously handled as individual properties.
+ # Therefore, we do not mention the case of font properties here.
+ _api.warn_deprecated(
+ "3.10",
+ message="Passing individual properties to FontProperties() "
+ "positionally was deprecated in Matplotlib %(since)s and "
+ "will be removed in %(removal)s. Please pass all properties "
+ "via keyword arguments."
+ )
+ # single non-string arg -> clearly a family not a pattern
+ if len(args) == 1 and not kwargs and not cbook.is_scalar_or_string(args[0]):
+ # Case font-family list passed as single argument
+ _api.warn_deprecated(
+ "3.10",
+ message="Passing family as positional argument to FontProperties() "
+ "was deprecated in Matplotlib %(since)s and will be removed "
+ "in %(removal)s. Please pass family names as keyword"
+ "argument."
+ )
+ # Note on single string arg:
+ # This has been interpreted as pattern so far. We are already raising if a
+ # non-pattern compatible family string was given. Therefore, we do not need
+ # to warn for this case.
+ return init_method(self, *args, **kwargs)
+
+ return wrapper
+
+
+class FontProperties:
+ """
+ A class for storing and manipulating font properties.
+
+ The font properties are the six properties described in the
+ `W3C Cascading Style Sheet, Level 1
+ `_ font
+ specification and *math_fontfamily* for math fonts:
+
+ - family: A list of font names in decreasing order of priority.
+ The items may include a generic font family name, either 'sans-serif',
+ 'serif', 'cursive', 'fantasy', or 'monospace'. In that case, the actual
+ font to be used will be looked up from the associated rcParam during the
+ search process in `.findfont`. Default: :rc:`font.family`
+
+ - style: Either 'normal', 'italic' or 'oblique'.
+ Default: :rc:`font.style`
+
+ - variant: Either 'normal' or 'small-caps'.
+ Default: :rc:`font.variant`
+
+ - stretch: A numeric value in the range 0-1000 or one of
+ 'ultra-condensed', 'extra-condensed', 'condensed',
+ 'semi-condensed', 'normal', 'semi-expanded', 'expanded',
+ 'extra-expanded' or 'ultra-expanded'. Default: :rc:`font.stretch`
+
+ - weight: A numeric value in the range 0-1000 or one of
+ 'ultralight', 'light', 'normal', 'regular', 'book', 'medium',
+ 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy',
+ 'extra bold', 'black'. Default: :rc:`font.weight`
+
+ - size: Either a relative value of 'xx-small', 'x-small',
+ 'small', 'medium', 'large', 'x-large', 'xx-large' or an
+ absolute font size, e.g., 10. Default: :rc:`font.size`
+
+ - math_fontfamily: The family of fonts used to render math text.
+ Supported values are: 'dejavusans', 'dejavuserif', 'cm',
+ 'stix', 'stixsans' and 'custom'. Default: :rc:`mathtext.fontset`
+
+ Alternatively, a font may be specified using the absolute path to a font
+ file, by using the *fname* kwarg. However, in this case, it is typically
+ simpler to just pass the path (as a `pathlib.Path`, not a `str`) to the
+ *font* kwarg of the `.Text` object.
+
+ The preferred usage of font sizes is to use the relative values,
+ e.g., 'large', instead of absolute font sizes, e.g., 12. This
+ approach allows all text sizes to be made larger or smaller based
+ on the font manager's default font size.
+
+ This class accepts a single positional string as fontconfig_ pattern_,
+ or alternatively individual properties as keyword arguments::
+
+ FontProperties(pattern)
+ FontProperties(*, family=None, style=None, variant=None, ...)
+
+ This support does not depend on fontconfig; we are merely borrowing its
+ pattern syntax for use here.
+
+ .. _fontconfig: https://www.freedesktop.org/wiki/Software/fontconfig/
+ .. _pattern:
+ https://www.freedesktop.org/software/fontconfig/fontconfig-user.html
+
+ Note that Matplotlib's internal font manager and fontconfig use a
+ different algorithm to lookup fonts, so the results of the same pattern
+ may be different in Matplotlib than in other applications that use
+ fontconfig.
+ """
+
+ @_cleanup_fontproperties_init
+ def __init__(self, family=None, style=None, variant=None, weight=None,
+ stretch=None, size=None,
+ fname=None, # if set, it's a hardcoded filename to use
+ math_fontfamily=None):
+ self.set_family(family)
+ self.set_style(style)
+ self.set_variant(variant)
+ self.set_weight(weight)
+ self.set_stretch(stretch)
+ self.set_file(fname)
+ self.set_size(size)
+ self.set_math_fontfamily(math_fontfamily)
+ # Treat family as a fontconfig pattern if it is the only parameter
+ # provided. Even in that case, call the other setters first to set
+ # attributes not specified by the pattern to the rcParams defaults.
+ if (isinstance(family, str)
+ and style is None and variant is None and weight is None
+ and stretch is None and size is None and fname is None):
+ self.set_fontconfig_pattern(family)
+
+ @classmethod
+ def _from_any(cls, arg):
+ """
+ Generic constructor which can build a `.FontProperties` from any of the
+ following:
+
+ - a `.FontProperties`: it is passed through as is;
+ - `None`: a `.FontProperties` using rc values is used;
+ - an `os.PathLike`: it is used as path to the font file;
+ - a `str`: it is parsed as a fontconfig pattern;
+ - a `dict`: it is passed as ``**kwargs`` to `.FontProperties`.
+ """
+ if arg is None:
+ return cls()
+ elif isinstance(arg, cls):
+ return arg
+ elif isinstance(arg, os.PathLike):
+ return cls(fname=arg)
+ elif isinstance(arg, str):
+ return cls(arg)
+ else:
+ return cls(**arg)
+
+ def __hash__(self):
+ l = (tuple(self.get_family()),
+ self.get_slant(),
+ self.get_variant(),
+ self.get_weight(),
+ self.get_stretch(),
+ self.get_size(),
+ self.get_file(),
+ self.get_math_fontfamily())
+ return hash(l)
+
+ def __eq__(self, other):
+ return hash(self) == hash(other)
+
+ def __str__(self):
+ return self.get_fontconfig_pattern()
+
+ def get_family(self):
+ """
+ Return a list of individual font family names or generic family names.
+
+ The font families or generic font families (which will be resolved
+ from their respective rcParams when searching for a matching font) in
+ the order of preference.
+ """
+ return self._family
+
+ def get_name(self):
+ """
+ Return the name of the font that best matches the font properties.
+ """
+ return get_font(findfont(self)).family_name
+
+ def get_style(self):
+ """
+ Return the font style. Values are: 'normal', 'italic' or 'oblique'.
+ """
+ return self._slant
+
+ def get_variant(self):
+ """
+ Return the font variant. Values are: 'normal' or 'small-caps'.
+ """
+ return self._variant
+
+ def get_weight(self):
+ """
+ Set the font weight. Options are: A numeric value in the
+ range 0-1000 or one of 'light', 'normal', 'regular', 'book',
+ 'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold',
+ 'heavy', 'extra bold', 'black'
+ """
+ return self._weight
+
+ def get_stretch(self):
+ """
+ Return the font stretch or width. Options are: 'ultra-condensed',
+ 'extra-condensed', 'condensed', 'semi-condensed', 'normal',
+ 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'.
+ """
+ return self._stretch
+
+ def get_size(self):
+ """
+ Return the font size.
+ """
+ return self._size
+
+ def get_file(self):
+ """
+ Return the filename of the associated font.
+ """
+ return self._file
+
+ def get_fontconfig_pattern(self):
+ """
+ Get a fontconfig_ pattern_ suitable for looking up the font as
+ specified with fontconfig's ``fc-match`` utility.
+
+ This support does not depend on fontconfig; we are merely borrowing its
+ pattern syntax for use here.
+ """
+ return generate_fontconfig_pattern(self)
+
+ def set_family(self, family):
+ """
+ Change the font family. Can be either an alias (generic name
+ is CSS parlance), such as: 'serif', 'sans-serif', 'cursive',
+ 'fantasy', or 'monospace', a real font name or a list of real
+ font names. Real font names are not supported when
+ :rc:`text.usetex` is `True`. Default: :rc:`font.family`
+ """
+ if family is None:
+ family = mpl.rcParams['font.family']
+ if isinstance(family, str):
+ family = [family]
+ self._family = family
+
+ def set_style(self, style):
+ """
+ Set the font style.
+
+ Parameters
+ ----------
+ style : {'normal', 'italic', 'oblique'}, default: :rc:`font.style`
+ """
+ if style is None:
+ style = mpl.rcParams['font.style']
+ _api.check_in_list(['normal', 'italic', 'oblique'], style=style)
+ self._slant = style
+
+ def set_variant(self, variant):
+ """
+ Set the font variant.
+
+ Parameters
+ ----------
+ variant : {'normal', 'small-caps'}, default: :rc:`font.variant`
+ """
+ if variant is None:
+ variant = mpl.rcParams['font.variant']
+ _api.check_in_list(['normal', 'small-caps'], variant=variant)
+ self._variant = variant
+
+ def set_weight(self, weight):
+ """
+ Set the font weight.
+
+ Parameters
+ ----------
+ weight : int or {'ultralight', 'light', 'normal', 'regular', 'book', \
+'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', \
+'extra bold', 'black'}, default: :rc:`font.weight`
+ If int, must be in the range 0-1000.
+ """
+ if weight is None:
+ weight = mpl.rcParams['font.weight']
+ if weight in weight_dict:
+ self._weight = weight
+ return
+ try:
+ weight = int(weight)
+ except ValueError:
+ pass
+ else:
+ if 0 <= weight <= 1000:
+ self._weight = weight
+ return
+ raise ValueError(f"{weight=} is invalid")
+
+ def set_stretch(self, stretch):
+ """
+ Set the font stretch or width.
+
+ Parameters
+ ----------
+ stretch : int or {'ultra-condensed', 'extra-condensed', 'condensed', \
+'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded', \
+'ultra-expanded'}, default: :rc:`font.stretch`
+ If int, must be in the range 0-1000.
+ """
+ if stretch is None:
+ stretch = mpl.rcParams['font.stretch']
+ if stretch in stretch_dict:
+ self._stretch = stretch
+ return
+ try:
+ stretch = int(stretch)
+ except ValueError:
+ pass
+ else:
+ if 0 <= stretch <= 1000:
+ self._stretch = stretch
+ return
+ raise ValueError(f"{stretch=} is invalid")
+
+ def set_size(self, size):
+ """
+ Set the font size.
+
+ Parameters
+ ----------
+ size : float or {'xx-small', 'x-small', 'small', 'medium', \
+'large', 'x-large', 'xx-large'}, default: :rc:`font.size`
+ If a float, the font size in points. The string values denote
+ sizes relative to the default font size.
+ """
+ if size is None:
+ size = mpl.rcParams['font.size']
+ try:
+ size = float(size)
+ except ValueError:
+ try:
+ scale = font_scalings[size]
+ except KeyError as err:
+ raise ValueError(
+ "Size is invalid. Valid font size are "
+ + ", ".join(map(str, font_scalings))) from err
+ else:
+ size = scale * FontManager.get_default_size()
+ if size < 1.0:
+ _log.info('Fontsize %1.2f < 1.0 pt not allowed by FreeType. '
+ 'Setting fontsize = 1 pt', size)
+ size = 1.0
+ self._size = size
+
+ def set_file(self, file):
+ """
+ Set the filename of the fontfile to use. In this case, all
+ other properties will be ignored.
+ """
+ self._file = os.fspath(file) if file is not None else None
+
+ def set_fontconfig_pattern(self, pattern):
+ """
+ Set the properties by parsing a fontconfig_ *pattern*.
+
+ This support does not depend on fontconfig; we are merely borrowing its
+ pattern syntax for use here.
+ """
+ for key, val in parse_fontconfig_pattern(pattern).items():
+ if type(val) is list:
+ getattr(self, "set_" + key)(val[0])
+ else:
+ getattr(self, "set_" + key)(val)
+
+ def get_math_fontfamily(self):
+ """
+ Return the name of the font family used for math text.
+
+ The default font is :rc:`mathtext.fontset`.
+ """
+ return self._math_fontfamily
+
+ def set_math_fontfamily(self, fontfamily):
+ """
+ Set the font family for text in math mode.
+
+ If not set explicitly, :rc:`mathtext.fontset` will be used.
+
+ Parameters
+ ----------
+ fontfamily : str
+ The name of the font family.
+
+ Available font families are defined in the
+ :ref:`default matplotlibrc file `.
+
+ See Also
+ --------
+ .text.Text.get_math_fontfamily
+ """
+ if fontfamily is None:
+ fontfamily = mpl.rcParams['mathtext.fontset']
+ else:
+ valid_fonts = _validators['mathtext.fontset'].valid.values()
+ # _check_in_list() Validates the parameter math_fontfamily as
+ # if it were passed to rcParams['mathtext.fontset']
+ _api.check_in_list(valid_fonts, math_fontfamily=fontfamily)
+ self._math_fontfamily = fontfamily
+
+ def copy(self):
+ """Return a copy of self."""
+ return copy.copy(self)
+
+ # Aliases
+ set_name = set_family
+ get_slant = get_style
+ set_slant = set_style
+ get_size_in_points = get_size
+
+
+class _JSONEncoder(json.JSONEncoder):
+ def default(self, o):
+ if isinstance(o, FontManager):
+ return dict(o.__dict__, __class__='FontManager')
+ elif isinstance(o, FontEntry):
+ d = dict(o.__dict__, __class__='FontEntry')
+ try:
+ # Cache paths of fonts shipped with Matplotlib relative to the
+ # Matplotlib data path, which helps in the presence of venvs.
+ d["fname"] = str(Path(d["fname"]).relative_to(mpl.get_data_path()))
+ except ValueError:
+ pass
+ return d
+ else:
+ return super().default(o)
+
+
+def _json_decode(o):
+ cls = o.pop('__class__', None)
+ if cls is None:
+ return o
+ elif cls == 'FontManager':
+ r = FontManager.__new__(FontManager)
+ r.__dict__.update(o)
+ return r
+ elif cls == 'FontEntry':
+ if not os.path.isabs(o['fname']):
+ o['fname'] = os.path.join(mpl.get_data_path(), o['fname'])
+ r = FontEntry(**o)
+ return r
+ else:
+ raise ValueError("Don't know how to deserialize __class__=%s" % cls)
+
+
+def json_dump(data, filename):
+ """
+ Dump `FontManager` *data* as JSON to the file named *filename*.
+
+ See Also
+ --------
+ json_load
+
+ Notes
+ -----
+ File paths that are children of the Matplotlib data path (typically, fonts
+ shipped with Matplotlib) are stored relative to that data path (to remain
+ valid across virtualenvs).
+
+ This function temporarily locks the output file to prevent multiple
+ processes from overwriting one another's output.
+ """
+ try:
+ with cbook._lock_path(filename), open(filename, 'w') as fh:
+ json.dump(data, fh, cls=_JSONEncoder, indent=2)
+ except OSError as e:
+ _log.warning('Could not save font_manager cache %s', e)
+
+
+def json_load(filename):
+ """
+ Load a `FontManager` from the JSON file named *filename*.
+
+ See Also
+ --------
+ json_dump
+ """
+ with open(filename) as fh:
+ return json.load(fh, object_hook=_json_decode)
+
+
+class FontManager:
+ """
+ On import, the `FontManager` singleton instance creates a list of ttf and
+ afm fonts and caches their `FontProperties`. The `FontManager.findfont`
+ method does a nearest neighbor search to find the font that most closely
+ matches the specification. If no good enough match is found, the default
+ font is returned.
+
+ Fonts added with the `FontManager.addfont` method will not persist in the
+ cache; therefore, `addfont` will need to be called every time Matplotlib is
+ imported. This method should only be used if and when a font cannot be
+ installed on your operating system by other means.
+
+ Notes
+ -----
+ The `FontManager.addfont` method must be called on the global `FontManager`
+ instance.
+
+ Example usage::
+
+ import matplotlib.pyplot as plt
+ from matplotlib import font_manager
+
+ font_dirs = ["/resources/fonts"] # The path to the custom font file.
+ font_files = font_manager.findSystemFonts(fontpaths=font_dirs)
+
+ for font_file in font_files:
+ font_manager.fontManager.addfont(font_file)
+ """
+ # Increment this version number whenever the font cache data
+ # format or behavior has changed and requires an existing font
+ # cache files to be rebuilt.
+ __version__ = 390
+
+ def __init__(self, size=None, weight='normal'):
+ self._version = self.__version__
+
+ self.__default_weight = weight
+ self.default_size = size
+
+ # Create list of font paths.
+ paths = [cbook._get_data_path('fonts', subdir)
+ for subdir in ['ttf', 'afm', 'pdfcorefonts']]
+ _log.debug('font search path %s', paths)
+
+ self.defaultFamily = {
+ 'ttf': 'DejaVu Sans',
+ 'afm': 'Helvetica'}
+
+ self.afmlist = []
+ self.ttflist = []
+
+ # Delay the warning by 5s.
+ timer = threading.Timer(5, lambda: _log.warning(
+ 'Matplotlib is building the font cache; this may take a moment.'))
+ timer.start()
+ try:
+ for fontext in ["afm", "ttf"]:
+ for path in [*findSystemFonts(paths, fontext=fontext),
+ *findSystemFonts(fontext=fontext)]:
+ try:
+ self.addfont(path)
+ except OSError as exc:
+ _log.info("Failed to open font file %s: %s", path, exc)
+ except Exception as exc:
+ _log.info("Failed to extract font properties from %s: "
+ "%s", path, exc)
+ finally:
+ timer.cancel()
+
+ def addfont(self, path):
+ """
+ Cache the properties of the font at *path* to make it available to the
+ `FontManager`. The type of font is inferred from the path suffix.
+
+ Parameters
+ ----------
+ path : str or path-like
+
+ Notes
+ -----
+ This method is useful for adding a custom font without installing it in
+ your operating system. See the `FontManager` singleton instance for
+ usage and caveats about this function.
+ """
+ # Convert to string in case of a path as
+ # afmFontProperty and FT2Font expect this
+ path = os.fsdecode(path)
+ if Path(path).suffix.lower() == ".afm":
+ with open(path, "rb") as fh:
+ font = _afm.AFM(fh)
+ prop = afmFontProperty(path, font)
+ self.afmlist.append(prop)
+ else:
+ font = ft2font.FT2Font(path)
+ prop = ttfFontProperty(font)
+ self.ttflist.append(prop)
+ self._findfont_cached.cache_clear()
+
+ @property
+ def defaultFont(self):
+ # Lazily evaluated (findfont then caches the result) to avoid including
+ # the venv path in the json serialization.
+ return {ext: self.findfont(family, fontext=ext)
+ for ext, family in self.defaultFamily.items()}
+
+ def get_default_weight(self):
+ """
+ Return the default font weight.
+ """
+ return self.__default_weight
+
+ @staticmethod
+ def get_default_size():
+ """
+ Return the default font size.
+ """
+ return mpl.rcParams['font.size']
+
+ def set_default_weight(self, weight):
+ """
+ Set the default font weight. The initial value is 'normal'.
+ """
+ self.__default_weight = weight
+
+ @staticmethod
+ def _expand_aliases(family):
+ if family in ('sans', 'sans serif'):
+ family = 'sans-serif'
+ return mpl.rcParams['font.' + family]
+
+ # Each of the scoring functions below should return a value between
+ # 0.0 (perfect match) and 1.0 (terrible match)
+ def score_family(self, families, family2):
+ """
+ Return a match score between the list of font families in
+ *families* and the font family name *family2*.
+
+ An exact match at the head of the list returns 0.0.
+
+ A match further down the list will return between 0 and 1.
+
+ No match will return 1.0.
+ """
+ if not isinstance(families, (list, tuple)):
+ families = [families]
+ elif len(families) == 0:
+ return 1.0
+ family2 = family2.lower()
+ step = 1 / len(families)
+ for i, family1 in enumerate(families):
+ family1 = family1.lower()
+ if family1 in font_family_aliases:
+ options = [*map(str.lower, self._expand_aliases(family1))]
+ if family2 in options:
+ idx = options.index(family2)
+ return (i + (idx / len(options))) * step
+ elif family1 == family2:
+ # The score should be weighted by where in the
+ # list the font was found.
+ return i * step
+ return 1.0
+
+ def score_style(self, style1, style2):
+ """
+ Return a match score between *style1* and *style2*.
+
+ An exact match returns 0.0.
+
+ A match between 'italic' and 'oblique' returns 0.1.
+
+ No match returns 1.0.
+ """
+ if style1 == style2:
+ return 0.0
+ elif (style1 in ('italic', 'oblique')
+ and style2 in ('italic', 'oblique')):
+ return 0.1
+ return 1.0
+
+ def score_variant(self, variant1, variant2):
+ """
+ Return a match score between *variant1* and *variant2*.
+
+ An exact match returns 0.0, otherwise 1.0.
+ """
+ if variant1 == variant2:
+ return 0.0
+ else:
+ return 1.0
+
+ def score_stretch(self, stretch1, stretch2):
+ """
+ Return a match score between *stretch1* and *stretch2*.
+
+ The result is the absolute value of the difference between the
+ CSS numeric values of *stretch1* and *stretch2*, normalized
+ between 0.0 and 1.0.
+ """
+ try:
+ stretchval1 = int(stretch1)
+ except ValueError:
+ stretchval1 = stretch_dict.get(stretch1, 500)
+ try:
+ stretchval2 = int(stretch2)
+ except ValueError:
+ stretchval2 = stretch_dict.get(stretch2, 500)
+ return abs(stretchval1 - stretchval2) / 1000.0
+
+ def score_weight(self, weight1, weight2):
+ """
+ Return a match score between *weight1* and *weight2*.
+
+ The result is 0.0 if both weight1 and weight 2 are given as strings
+ and have the same value.
+
+ Otherwise, the result is the absolute value of the difference between
+ the CSS numeric values of *weight1* and *weight2*, normalized between
+ 0.05 and 1.0.
+ """
+ # exact match of the weight names, e.g. weight1 == weight2 == "regular"
+ if cbook._str_equal(weight1, weight2):
+ return 0.0
+ w1 = weight1 if isinstance(weight1, Number) else weight_dict[weight1]
+ w2 = weight2 if isinstance(weight2, Number) else weight_dict[weight2]
+ return 0.95 * (abs(w1 - w2) / 1000) + 0.05
+
+ def score_size(self, size1, size2):
+ """
+ Return a match score between *size1* and *size2*.
+
+ If *size2* (the size specified in the font file) is 'scalable', this
+ function always returns 0.0, since any font size can be generated.
+
+ Otherwise, the result is the absolute distance between *size1* and
+ *size2*, normalized so that the usual range of font sizes (6pt -
+ 72pt) will lie between 0.0 and 1.0.
+ """
+ if size2 == 'scalable':
+ return 0.0
+ # Size value should have already been
+ try:
+ sizeval1 = float(size1)
+ except ValueError:
+ sizeval1 = self.default_size * font_scalings[size1]
+ try:
+ sizeval2 = float(size2)
+ except ValueError:
+ return 1.0
+ return abs(sizeval1 - sizeval2) / 72
+
+ def findfont(self, prop, fontext='ttf', directory=None,
+ fallback_to_default=True, rebuild_if_missing=True):
+ """
+ Find the path to the font file most closely matching the given font properties.
+
+ Parameters
+ ----------
+ prop : str or `~matplotlib.font_manager.FontProperties`
+ The font properties to search for. This can be either a
+ `.FontProperties` object or a string defining a
+ `fontconfig patterns`_.
+
+ fontext : {'ttf', 'afm'}, default: 'ttf'
+ The extension of the font file:
+
+ - 'ttf': TrueType and OpenType fonts (.ttf, .ttc, .otf)
+ - 'afm': Adobe Font Metrics (.afm)
+
+ directory : str, optional
+ If given, only search this directory and its subdirectories.
+
+ fallback_to_default : bool
+ If True, will fall back to the default font family (usually
+ "DejaVu Sans" or "Helvetica") if the first lookup hard-fails.
+
+ rebuild_if_missing : bool
+ Whether to rebuild the font cache and search again if the first
+ match appears to point to a nonexisting font (i.e., the font cache
+ contains outdated entries).
+
+ Returns
+ -------
+ str
+ The filename of the best matching font.
+
+ Notes
+ -----
+ This performs a nearest neighbor search. Each font is given a
+ similarity score to the target font properties. The first font with
+ the highest score is returned. If no matches below a certain
+ threshold are found, the default font (usually DejaVu Sans) is
+ returned.
+
+ The result is cached, so subsequent lookups don't have to
+ perform the O(n) nearest neighbor search.
+
+ See the `W3C Cascading Style Sheet, Level 1
+ `_ documentation
+ for a description of the font finding algorithm.
+
+ .. _fontconfig patterns:
+ https://www.freedesktop.org/software/fontconfig/fontconfig-user.html
+ """
+ # Pass the relevant rcParams (and the font manager, as `self`) to
+ # _findfont_cached so to prevent using a stale cache entry after an
+ # rcParam was changed.
+ rc_params = tuple(tuple(mpl.rcParams[key]) for key in [
+ "font.serif", "font.sans-serif", "font.cursive", "font.fantasy",
+ "font.monospace"])
+ ret = self._findfont_cached(
+ prop, fontext, directory, fallback_to_default, rebuild_if_missing,
+ rc_params)
+ if isinstance(ret, cbook._ExceptionInfo):
+ raise ret.to_exception()
+ return ret
+
+ def get_font_names(self):
+ """Return the list of available fonts."""
+ return list({font.name for font in self.ttflist})
+
+ def _find_fonts_by_props(self, prop, fontext='ttf', directory=None,
+ fallback_to_default=True, rebuild_if_missing=True):
+ """
+ Find the paths to the font files most closely matching the given properties.
+
+ Parameters
+ ----------
+ prop : str or `~matplotlib.font_manager.FontProperties`
+ The font properties to search for. This can be either a
+ `.FontProperties` object or a string defining a
+ `fontconfig patterns`_.
+
+ fontext : {'ttf', 'afm'}, default: 'ttf'
+ The extension of the font file:
+
+ - 'ttf': TrueType and OpenType fonts (.ttf, .ttc, .otf)
+ - 'afm': Adobe Font Metrics (.afm)
+
+ directory : str, optional
+ If given, only search this directory and its subdirectories.
+
+ fallback_to_default : bool
+ If True, will fall back to the default font family (usually
+ "DejaVu Sans" or "Helvetica") if none of the families were found.
+
+ rebuild_if_missing : bool
+ Whether to rebuild the font cache and search again if the first
+ match appears to point to a nonexisting font (i.e., the font cache
+ contains outdated entries).
+
+ Returns
+ -------
+ list[str]
+ The paths of the fonts found.
+
+ Notes
+ -----
+ This is an extension/wrapper of the original findfont API, which only
+ returns a single font for given font properties. Instead, this API
+ returns a list of filepaths of multiple fonts which closely match the
+ given font properties. Since this internally uses the original API,
+ there's no change to the logic of performing the nearest neighbor
+ search. See `findfont` for more details.
+ """
+
+ prop = FontProperties._from_any(prop)
+
+ fpaths = []
+ for family in prop.get_family():
+ cprop = prop.copy()
+ cprop.set_family(family) # set current prop's family
+
+ try:
+ fpaths.append(
+ self.findfont(
+ cprop, fontext, directory,
+ fallback_to_default=False, # don't fallback to default
+ rebuild_if_missing=rebuild_if_missing,
+ )
+ )
+ except ValueError:
+ if family in font_family_aliases:
+ _log.warning(
+ "findfont: Generic family %r not found because "
+ "none of the following families were found: %s",
+ family, ", ".join(self._expand_aliases(family))
+ )
+ else:
+ _log.warning("findfont: Font family %r not found.", family)
+
+ # only add default family if no other font was found and
+ # fallback_to_default is enabled
+ if not fpaths:
+ if fallback_to_default:
+ dfamily = self.defaultFamily[fontext]
+ cprop = prop.copy()
+ cprop.set_family(dfamily)
+ fpaths.append(
+ self.findfont(
+ cprop, fontext, directory,
+ fallback_to_default=True,
+ rebuild_if_missing=rebuild_if_missing,
+ )
+ )
+ else:
+ raise ValueError("Failed to find any font, and fallback "
+ "to the default font was disabled")
+
+ return fpaths
+
+ @lru_cache(1024)
+ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
+ rebuild_if_missing, rc_params):
+
+ prop = FontProperties._from_any(prop)
+
+ fname = prop.get_file()
+ if fname is not None:
+ return fname
+
+ if fontext == 'afm':
+ fontlist = self.afmlist
+ else:
+ fontlist = self.ttflist
+
+ best_score = 1e64
+ best_font = None
+
+ _log.debug('findfont: Matching %s.', prop)
+ for font in fontlist:
+ if (directory is not None and
+ Path(directory) not in Path(font.fname).parents):
+ continue
+ # Matching family should have top priority, so multiply it by 10.
+ score = (self.score_family(prop.get_family(), font.name) * 10
+ + self.score_style(prop.get_style(), font.style)
+ + self.score_variant(prop.get_variant(), font.variant)
+ + self.score_weight(prop.get_weight(), font.weight)
+ + self.score_stretch(prop.get_stretch(), font.stretch)
+ + self.score_size(prop.get_size(), font.size))
+ _log.debug('findfont: score(%s) = %s', font, score)
+ if score < best_score:
+ best_score = score
+ best_font = font
+ if score == 0:
+ break
+
+ if best_font is None or best_score >= 10.0:
+ if fallback_to_default:
+ _log.warning(
+ 'findfont: Font family %s not found. Falling back to %s.',
+ prop.get_family(), self.defaultFamily[fontext])
+ for family in map(str.lower, prop.get_family()):
+ if family in font_family_aliases:
+ _log.warning(
+ "findfont: Generic family %r not found because "
+ "none of the following families were found: %s",
+ family, ", ".join(self._expand_aliases(family)))
+ default_prop = prop.copy()
+ default_prop.set_family(self.defaultFamily[fontext])
+ return self.findfont(default_prop, fontext, directory,
+ fallback_to_default=False)
+ else:
+ # This return instead of raise is intentional, as we wish to
+ # cache that it was not found, which will not occur if it was
+ # actually raised.
+ return cbook._ExceptionInfo(
+ ValueError,
+ f"Failed to find font {prop}, and fallback to the default font was "
+ f"disabled"
+ )
+ else:
+ _log.debug('findfont: Matching %s to %s (%r) with score of %f.',
+ prop, best_font.name, best_font.fname, best_score)
+ result = best_font.fname
+
+ if not os.path.isfile(result):
+ if rebuild_if_missing:
+ _log.info(
+ 'findfont: Found a missing font file. Rebuilding cache.')
+ new_fm = _load_fontmanager(try_read_cache=False)
+ # Replace self by the new fontmanager, because users may have
+ # a reference to this specific instance.
+ # TODO: _load_fontmanager should really be (used by) a method
+ # modifying the instance in place.
+ vars(self).update(vars(new_fm))
+ return self.findfont(
+ prop, fontext, directory, rebuild_if_missing=False)
+ else:
+ # This return instead of raise is intentional, as we wish to
+ # cache that it was not found, which will not occur if it was
+ # actually raised.
+ return cbook._ExceptionInfo(ValueError, "No valid font could be found")
+
+ return _cached_realpath(result)
+
+
+@lru_cache
+def is_opentype_cff_font(filename):
+ """
+ Return whether the given font is a Postscript Compact Font Format Font
+ embedded in an OpenType wrapper. Used by the PostScript and PDF backends
+ that cannot subset these fonts.
+ """
+ if os.path.splitext(filename)[1].lower() == '.otf':
+ with open(filename, 'rb') as fd:
+ return fd.read(4) == b"OTTO"
+ else:
+ return False
+
+
+@lru_cache(64)
+def _get_font(font_filepaths, hinting_factor, *, _kerning_factor, thread_id):
+ first_fontpath, *rest = font_filepaths
+ return ft2font.FT2Font(
+ first_fontpath, hinting_factor,
+ _fallback_list=[
+ ft2font.FT2Font(
+ fpath, hinting_factor,
+ _kerning_factor=_kerning_factor
+ )
+ for fpath in rest
+ ],
+ _kerning_factor=_kerning_factor
+ )
+
+
+# FT2Font objects cannot be used across fork()s because they reference the same
+# FT_Library object. While invalidating *all* existing FT2Fonts after a fork
+# would be too complicated to be worth it, the main way FT2Fonts get reused is
+# via the cache of _get_font, which we can empty upon forking (not on Windows,
+# which has no fork() or register_at_fork()).
+if hasattr(os, "register_at_fork"):
+ os.register_at_fork(after_in_child=_get_font.cache_clear)
+
+
+@lru_cache(64)
+def _cached_realpath(path):
+ # Resolving the path avoids embedding the font twice in pdf/ps output if a
+ # single font is selected using two different relative paths.
+ return os.path.realpath(path)
+
+
+def get_font(font_filepaths, hinting_factor=None):
+ """
+ Get an `.ft2font.FT2Font` object given a list of file paths.
+
+ Parameters
+ ----------
+ font_filepaths : Iterable[str, Path, bytes], str, Path, bytes
+ Relative or absolute paths to the font files to be used.
+
+ If a single string, bytes, or `pathlib.Path`, then it will be treated
+ as a list with that entry only.
+
+ If more than one filepath is passed, then the returned FT2Font object
+ will fall back through the fonts, in the order given, to find a needed
+ glyph.
+
+ Returns
+ -------
+ `.ft2font.FT2Font`
+
+ """
+ if isinstance(font_filepaths, (str, Path, bytes)):
+ paths = (_cached_realpath(font_filepaths),)
+ else:
+ paths = tuple(_cached_realpath(fname) for fname in font_filepaths)
+
+ if hinting_factor is None:
+ hinting_factor = mpl.rcParams['text.hinting_factor']
+
+ return _get_font(
+ # must be a tuple to be cached
+ paths,
+ hinting_factor,
+ _kerning_factor=mpl.rcParams['text.kerning_factor'],
+ # also key on the thread ID to prevent segfaults with multi-threading
+ thread_id=threading.get_ident()
+ )
+
+
+def _load_fontmanager(*, try_read_cache=True):
+ fm_path = Path(
+ mpl.get_cachedir(), f"fontlist-v{FontManager.__version__}.json")
+ if try_read_cache:
+ try:
+ fm = json_load(fm_path)
+ except Exception:
+ pass
+ else:
+ if getattr(fm, "_version", object()) == FontManager.__version__:
+ _log.debug("Using fontManager instance from %s", fm_path)
+ return fm
+ fm = FontManager()
+ json_dump(fm, fm_path)
+ _log.info("generated new fontManager")
+ return fm
+
+
+fontManager = _load_fontmanager()
+findfont = fontManager.findfont
+get_font_names = fontManager.get_font_names
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..48d0e362d5994923e4b6e9e963e96177c8bc7fcb
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.pyi
@@ -0,0 +1,136 @@
+from dataclasses import dataclass
+import os
+
+from matplotlib._afm import AFM
+from matplotlib import ft2font
+
+from pathlib import Path
+
+from collections.abc import Iterable
+from typing import Any, Literal
+
+font_scalings: dict[str | None, float]
+stretch_dict: dict[str, int]
+weight_dict: dict[str, int]
+font_family_aliases: set[str]
+MSFolders: str
+MSFontDirectories: list[str]
+MSUserFontDirectories: list[str]
+X11FontDirectories: list[str]
+OSXFontDirectories: list[str]
+
+def get_fontext_synonyms(fontext: str) -> list[str]: ...
+def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ...
+def win32FontDirectory() -> str: ...
+def _get_fontconfig_fonts() -> list[Path]: ...
+def findSystemFonts(
+ fontpaths: Iterable[str | os.PathLike | Path] | None = ..., fontext: str = ...
+) -> list[str]: ...
+@dataclass
+class FontEntry:
+ fname: str = ...
+ name: str = ...
+ style: str = ...
+ variant: str = ...
+ weight: str | int = ...
+ stretch: str = ...
+ size: str = ...
+ def _repr_html_(self) -> str: ...
+ def _repr_png_(self) -> bytes: ...
+
+def ttfFontProperty(font: ft2font.FT2Font) -> FontEntry: ...
+def afmFontProperty(fontpath: str, font: AFM) -> FontEntry: ...
+
+class FontProperties:
+ def __init__(
+ self,
+ family: str | Iterable[str] | None = ...,
+ style: Literal["normal", "italic", "oblique"] | None = ...,
+ variant: Literal["normal", "small-caps"] | None = ...,
+ weight: int | str | None = ...,
+ stretch: int | str | None = ...,
+ size: float | str | None = ...,
+ fname: str | os.PathLike | Path | None = ...,
+ math_fontfamily: str | None = ...,
+ ) -> None: ...
+ def __hash__(self) -> int: ...
+ def __eq__(self, other: object) -> bool: ...
+ def get_family(self) -> list[str]: ...
+ def get_name(self) -> str: ...
+ def get_style(self) -> Literal["normal", "italic", "oblique"]: ...
+ def get_variant(self) -> Literal["normal", "small-caps"]: ...
+ def get_weight(self) -> int | str: ...
+ def get_stretch(self) -> int | str: ...
+ def get_size(self) -> float: ...
+ def get_file(self) -> str | bytes | None: ...
+ def get_fontconfig_pattern(self) -> dict[str, list[Any]]: ...
+ def set_family(self, family: str | Iterable[str] | None) -> None: ...
+ def set_style(
+ self, style: Literal["normal", "italic", "oblique"] | None
+ ) -> None: ...
+ def set_variant(self, variant: Literal["normal", "small-caps"] | None) -> None: ...
+ def set_weight(self, weight: int | str | None) -> None: ...
+ def set_stretch(self, stretch: int | str | None) -> None: ...
+ def set_size(self, size: float | str | None) -> None: ...
+ def set_file(self, file: str | os.PathLike | Path | None) -> None: ...
+ def set_fontconfig_pattern(self, pattern: str) -> None: ...
+ def get_math_fontfamily(self) -> str: ...
+ def set_math_fontfamily(self, fontfamily: str | None) -> None: ...
+ def copy(self) -> FontProperties: ...
+ # Aliases
+ set_name = set_family
+ get_slant = get_style
+ set_slant = set_style
+ get_size_in_points = get_size
+
+def json_dump(data: FontManager, filename: str | Path | os.PathLike) -> None: ...
+def json_load(filename: str | Path | os.PathLike) -> FontManager: ...
+
+class FontManager:
+ __version__: int
+ default_size: float | None
+ defaultFamily: dict[str, str]
+ afmlist: list[FontEntry]
+ ttflist: list[FontEntry]
+ def __init__(self, size: float | None = ..., weight: str = ...) -> None: ...
+ def addfont(self, path: str | Path | os.PathLike) -> None: ...
+ @property
+ def defaultFont(self) -> dict[str, str]: ...
+ def get_default_weight(self) -> str: ...
+ @staticmethod
+ def get_default_size() -> float: ...
+ def set_default_weight(self, weight: str) -> None: ...
+ def score_family(
+ self, families: str | list[str] | tuple[str], family2: str
+ ) -> float: ...
+ def score_style(self, style1: str, style2: str) -> float: ...
+ def score_variant(self, variant1: str, variant2: str) -> float: ...
+ def score_stretch(self, stretch1: str | int, stretch2: str | int) -> float: ...
+ def score_weight(self, weight1: str | float, weight2: str | float) -> float: ...
+ def score_size(self, size1: str | float, size2: str | float) -> float: ...
+ def findfont(
+ self,
+ prop: str | FontProperties,
+ fontext: Literal["ttf", "afm"] = ...,
+ directory: str | None = ...,
+ fallback_to_default: bool = ...,
+ rebuild_if_missing: bool = ...,
+ ) -> str: ...
+ def get_font_names(self) -> list[str]: ...
+
+def is_opentype_cff_font(filename: str) -> bool: ...
+def get_font(
+ font_filepaths: Iterable[str | Path | bytes] | str | Path | bytes,
+ hinting_factor: int | None = ...,
+) -> ft2font.FT2Font: ...
+
+fontManager: FontManager
+
+def findfont(
+ prop: str | FontProperties,
+ fontext: Literal["ttf", "afm"] = ...,
+ directory: str | None = ...,
+ fallback_to_default: bool = ...,
+ rebuild_if_missing: bool = ...,
+) -> str: ...
+def get_font_names() -> list[str]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.cpython-311-x86_64-linux-gnu.so b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.cpython-311-x86_64-linux-gnu.so
new file mode 100644
index 0000000000000000000000000000000000000000..a124bc0926d97614673e3d981722118ac5883737
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.cpython-311-x86_64-linux-gnu.so
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:19f8050e94615789419cfcaeb674e459b448c35c5f4572830d3479e59235ff8c
+size 1279544
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..37281afeaafa751790af911a422aedebece7133e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.pyi
@@ -0,0 +1,312 @@
+from enum import Enum, Flag
+import sys
+from typing import BinaryIO, Literal, TypedDict, final, overload, cast
+from typing_extensions import Buffer # < Py 3.12
+
+import numpy as np
+from numpy.typing import NDArray
+
+__freetype_build_type__: str
+__freetype_version__: str
+
+class FaceFlags(Flag):
+ SCALABLE = cast(int, ...)
+ FIXED_SIZES = cast(int, ...)
+ FIXED_WIDTH = cast(int, ...)
+ SFNT = cast(int, ...)
+ HORIZONTAL = cast(int, ...)
+ VERTICAL = cast(int, ...)
+ KERNING = cast(int, ...)
+ FAST_GLYPHS = cast(int, ...)
+ MULTIPLE_MASTERS = cast(int, ...)
+ GLYPH_NAMES = cast(int, ...)
+ EXTERNAL_STREAM = cast(int, ...)
+ HINTER = cast(int, ...)
+ CID_KEYED = cast(int, ...)
+ TRICKY = cast(int, ...)
+ COLOR = cast(int, ...)
+ # VARIATION = cast(int, ...) # FT 2.9
+ # SVG = cast(int, ...) # FT 2.12
+ # SBIX = cast(int, ...) # FT 2.12
+ # SBIX_OVERLAY = cast(int, ...) # FT 2.12
+
+class Kerning(Enum):
+ DEFAULT = cast(int, ...)
+ UNFITTED = cast(int, ...)
+ UNSCALED = cast(int, ...)
+
+class LoadFlags(Flag):
+ DEFAULT = cast(int, ...)
+ NO_SCALE = cast(int, ...)
+ NO_HINTING = cast(int, ...)
+ RENDER = cast(int, ...)
+ NO_BITMAP = cast(int, ...)
+ VERTICAL_LAYOUT = cast(int, ...)
+ FORCE_AUTOHINT = cast(int, ...)
+ CROP_BITMAP = cast(int, ...)
+ PEDANTIC = cast(int, ...)
+ IGNORE_GLOBAL_ADVANCE_WIDTH = cast(int, ...)
+ NO_RECURSE = cast(int, ...)
+ IGNORE_TRANSFORM = cast(int, ...)
+ MONOCHROME = cast(int, ...)
+ LINEAR_DESIGN = cast(int, ...)
+ NO_AUTOHINT = cast(int, ...)
+ COLOR = cast(int, ...)
+ COMPUTE_METRICS = cast(int, ...) # FT 2.6.1
+ # BITMAP_METRICS_ONLY = cast(int, ...) # FT 2.7.1
+ # NO_SVG = cast(int, ...) # FT 2.13.1
+ # The following should be unique, but the above can be OR'd together.
+ TARGET_NORMAL = cast(int, ...)
+ TARGET_LIGHT = cast(int, ...)
+ TARGET_MONO = cast(int, ...)
+ TARGET_LCD = cast(int, ...)
+ TARGET_LCD_V = cast(int, ...)
+
+class StyleFlags(Flag):
+ NORMAL = cast(int, ...)
+ ITALIC = cast(int, ...)
+ BOLD = cast(int, ...)
+
+class _SfntHeadDict(TypedDict):
+ version: tuple[int, int]
+ fontRevision: tuple[int, int]
+ checkSumAdjustment: int
+ magicNumber: int
+ flags: int
+ unitsPerEm: int
+ created: tuple[int, int]
+ modified: tuple[int, int]
+ xMin: int
+ yMin: int
+ xMax: int
+ yMax: int
+ macStyle: int
+ lowestRecPPEM: int
+ fontDirectionHint: int
+ indexToLocFormat: int
+ glyphDataFormat: int
+
+class _SfntMaxpDict(TypedDict):
+ version: tuple[int, int]
+ numGlyphs: int
+ maxPoints: int
+ maxContours: int
+ maxComponentPoints: int
+ maxComponentContours: int
+ maxZones: int
+ maxTwilightPoints: int
+ maxStorage: int
+ maxFunctionDefs: int
+ maxInstructionDefs: int
+ maxStackElements: int
+ maxSizeOfInstructions: int
+ maxComponentElements: int
+ maxComponentDepth: int
+
+class _SfntOs2Dict(TypedDict):
+ version: int
+ xAvgCharWidth: int
+ usWeightClass: int
+ usWidthClass: int
+ fsType: int
+ ySubscriptXSize: int
+ ySubscriptYSize: int
+ ySubscriptXOffset: int
+ ySubscriptYOffset: int
+ ySuperscriptXSize: int
+ ySuperscriptYSize: int
+ ySuperscriptXOffset: int
+ ySuperscriptYOffset: int
+ yStrikeoutSize: int
+ yStrikeoutPosition: int
+ sFamilyClass: int
+ panose: bytes
+ ulCharRange: tuple[int, int, int, int]
+ achVendID: bytes
+ fsSelection: int
+ fsFirstCharIndex: int
+ fsLastCharIndex: int
+
+class _SfntHheaDict(TypedDict):
+ version: tuple[int, int]
+ ascent: int
+ descent: int
+ lineGap: int
+ advanceWidthMax: int
+ minLeftBearing: int
+ minRightBearing: int
+ xMaxExtent: int
+ caretSlopeRise: int
+ caretSlopeRun: int
+ caretOffset: int
+ metricDataFormat: int
+ numOfLongHorMetrics: int
+
+class _SfntVheaDict(TypedDict):
+ version: tuple[int, int]
+ vertTypoAscender: int
+ vertTypoDescender: int
+ vertTypoLineGap: int
+ advanceHeightMax: int
+ minTopSideBearing: int
+ minBottomSizeBearing: int
+ yMaxExtent: int
+ caretSlopeRise: int
+ caretSlopeRun: int
+ caretOffset: int
+ metricDataFormat: int
+ numOfLongVerMetrics: int
+
+class _SfntPostDict(TypedDict):
+ format: tuple[int, int]
+ italicAngle: tuple[int, int]
+ underlinePosition: int
+ underlineThickness: int
+ isFixedPitch: int
+ minMemType42: int
+ maxMemType42: int
+ minMemType1: int
+ maxMemType1: int
+
+class _SfntPcltDict(TypedDict):
+ version: tuple[int, int]
+ fontNumber: int
+ pitch: int
+ xHeight: int
+ style: int
+ typeFamily: int
+ capHeight: int
+ symbolSet: int
+ typeFace: bytes
+ characterComplement: bytes
+ strokeWeight: int
+ widthType: int
+ serifStyle: int
+
+@final
+class FT2Font(Buffer):
+ def __init__(
+ self,
+ filename: str | BinaryIO,
+ hinting_factor: int = ...,
+ *,
+ _fallback_list: list[FT2Font] | None = ...,
+ _kerning_factor: int = ...
+ ) -> None: ...
+ if sys.version_info[:2] >= (3, 12):
+ def __buffer__(self, flags: int) -> memoryview: ...
+ def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ...
+ def clear(self) -> None: ...
+ def draw_glyph_to_bitmap(
+ self, image: FT2Image, x: int, y: int, glyph: Glyph, antialiased: bool = ...
+ ) -> None: ...
+ def draw_glyphs_to_bitmap(self, antialiased: bool = ...) -> None: ...
+ def get_bitmap_offset(self) -> tuple[int, int]: ...
+ def get_char_index(self, codepoint: int) -> int: ...
+ def get_charmap(self) -> dict[int, int]: ...
+ def get_descent(self) -> int: ...
+ def get_glyph_name(self, index: int) -> str: ...
+ def get_image(self) -> NDArray[np.uint8]: ...
+ def get_kerning(self, left: int, right: int, mode: Kerning) -> int: ...
+ def get_name_index(self, name: str) -> int: ...
+ def get_num_glyphs(self) -> int: ...
+ def get_path(self) -> tuple[NDArray[np.float64], NDArray[np.int8]]: ...
+ def get_ps_font_info(
+ self,
+ ) -> tuple[str, str, str, str, str, int, int, int, int]: ...
+ def get_sfnt(self) -> dict[tuple[int, int, int, int], bytes]: ...
+ @overload
+ def get_sfnt_table(self, name: Literal["head"]) -> _SfntHeadDict | None: ...
+ @overload
+ def get_sfnt_table(self, name: Literal["maxp"]) -> _SfntMaxpDict | None: ...
+ @overload
+ def get_sfnt_table(self, name: Literal["OS/2"]) -> _SfntOs2Dict | None: ...
+ @overload
+ def get_sfnt_table(self, name: Literal["hhea"]) -> _SfntHheaDict | None: ...
+ @overload
+ def get_sfnt_table(self, name: Literal["vhea"]) -> _SfntVheaDict | None: ...
+ @overload
+ def get_sfnt_table(self, name: Literal["post"]) -> _SfntPostDict | None: ...
+ @overload
+ def get_sfnt_table(self, name: Literal["pclt"]) -> _SfntPcltDict | None: ...
+ def get_width_height(self) -> tuple[int, int]: ...
+ def load_char(self, charcode: int, flags: LoadFlags = ...) -> Glyph: ...
+ def load_glyph(self, glyphindex: int, flags: LoadFlags = ...) -> Glyph: ...
+ def select_charmap(self, i: int) -> None: ...
+ def set_charmap(self, i: int) -> None: ...
+ def set_size(self, ptsize: float, dpi: float) -> None: ...
+ def set_text(
+ self, string: str, angle: float = ..., flags: LoadFlags = ...
+ ) -> NDArray[np.float64]: ...
+ @property
+ def ascender(self) -> int: ...
+ @property
+ def bbox(self) -> tuple[int, int, int, int]: ...
+ @property
+ def descender(self) -> int: ...
+ @property
+ def face_flags(self) -> FaceFlags: ...
+ @property
+ def family_name(self) -> str: ...
+ @property
+ def fname(self) -> str: ...
+ @property
+ def height(self) -> int: ...
+ @property
+ def max_advance_height(self) -> int: ...
+ @property
+ def max_advance_width(self) -> int: ...
+ @property
+ def num_charmaps(self) -> int: ...
+ @property
+ def num_faces(self) -> int: ...
+ @property
+ def num_fixed_sizes(self) -> int: ...
+ @property
+ def num_glyphs(self) -> int: ...
+ @property
+ def num_named_instances(self) -> int: ...
+ @property
+ def postscript_name(self) -> str: ...
+ @property
+ def scalable(self) -> bool: ...
+ @property
+ def style_flags(self) -> StyleFlags: ...
+ @property
+ def style_name(self) -> str: ...
+ @property
+ def underline_position(self) -> int: ...
+ @property
+ def underline_thickness(self) -> int: ...
+ @property
+ def units_per_EM(self) -> int: ...
+
+@final
+class FT2Image(Buffer):
+ def __init__(self, width: int, height: int) -> None: ...
+ def draw_rect_filled(self, x0: int, y0: int, x1: int, y1: int) -> None: ...
+ if sys.version_info[:2] >= (3, 12):
+ def __buffer__(self, flags: int) -> memoryview: ...
+
+@final
+class Glyph:
+ @property
+ def width(self) -> int: ...
+ @property
+ def height(self) -> int: ...
+ @property
+ def horiBearingX(self) -> int: ...
+ @property
+ def horiBearingY(self) -> int: ...
+ @property
+ def horiAdvance(self) -> int: ...
+ @property
+ def linearHoriAdvance(self) -> int: ...
+ @property
+ def vertBearingX(self) -> int: ...
+ @property
+ def vertBearingY(self) -> int: ...
+ @property
+ def vertAdvance(self) -> int: ...
+ @property
+ def bbox(self) -> tuple[int, int, int, int]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.py
new file mode 100644
index 0000000000000000000000000000000000000000..06f0b2f7f781d291ca6159fd7ad8333d75dc6abb
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.py
@@ -0,0 +1,788 @@
+r"""
+:mod:`~matplotlib.gridspec` contains classes that help to layout multiple
+`~.axes.Axes` in a grid-like pattern within a figure.
+
+The `GridSpec` specifies the overall grid structure. Individual cells within
+the grid are referenced by `SubplotSpec`\s.
+
+Often, users need not access this module directly, and can use higher-level
+methods like `~.pyplot.subplots`, `~.pyplot.subplot_mosaic` and
+`~.Figure.subfigures`. See the tutorial :ref:`arranging_axes` for a guide.
+"""
+
+import copy
+import logging
+from numbers import Integral
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api, _pylab_helpers, _tight_layout
+from matplotlib.transforms import Bbox
+
+_log = logging.getLogger(__name__)
+
+
+class GridSpecBase:
+ """
+ A base class of GridSpec that specifies the geometry of the grid
+ that a subplot will be placed.
+ """
+
+ def __init__(self, nrows, ncols, height_ratios=None, width_ratios=None):
+ """
+ Parameters
+ ----------
+ nrows, ncols : int
+ The number of rows and columns of the grid.
+ width_ratios : array-like of length *ncols*, optional
+ Defines the relative widths of the columns. Each column gets a
+ relative width of ``width_ratios[i] / sum(width_ratios)``.
+ If not given, all columns will have the same width.
+ height_ratios : array-like of length *nrows*, optional
+ Defines the relative heights of the rows. Each row gets a
+ relative height of ``height_ratios[i] / sum(height_ratios)``.
+ If not given, all rows will have the same height.
+ """
+ if not isinstance(nrows, Integral) or nrows <= 0:
+ raise ValueError(
+ f"Number of rows must be a positive integer, not {nrows!r}")
+ if not isinstance(ncols, Integral) or ncols <= 0:
+ raise ValueError(
+ f"Number of columns must be a positive integer, not {ncols!r}")
+ self._nrows, self._ncols = nrows, ncols
+ self.set_height_ratios(height_ratios)
+ self.set_width_ratios(width_ratios)
+
+ def __repr__(self):
+ height_arg = (f', height_ratios={self._row_height_ratios!r}'
+ if len(set(self._row_height_ratios)) != 1 else '')
+ width_arg = (f', width_ratios={self._col_width_ratios!r}'
+ if len(set(self._col_width_ratios)) != 1 else '')
+ return '{clsname}({nrows}, {ncols}{optionals})'.format(
+ clsname=self.__class__.__name__,
+ nrows=self._nrows,
+ ncols=self._ncols,
+ optionals=height_arg + width_arg,
+ )
+
+ nrows = property(lambda self: self._nrows,
+ doc="The number of rows in the grid.")
+ ncols = property(lambda self: self._ncols,
+ doc="The number of columns in the grid.")
+
+ def get_geometry(self):
+ """
+ Return a tuple containing the number of rows and columns in the grid.
+ """
+ return self._nrows, self._ncols
+
+ def get_subplot_params(self, figure=None):
+ # Must be implemented in subclasses
+ pass
+
+ def new_subplotspec(self, loc, rowspan=1, colspan=1):
+ """
+ Create and return a `.SubplotSpec` instance.
+
+ Parameters
+ ----------
+ loc : (int, int)
+ The position of the subplot in the grid as
+ ``(row_index, column_index)``.
+ rowspan, colspan : int, default: 1
+ The number of rows and columns the subplot should span in the grid.
+ """
+ loc1, loc2 = loc
+ subplotspec = self[loc1:loc1+rowspan, loc2:loc2+colspan]
+ return subplotspec
+
+ def set_width_ratios(self, width_ratios):
+ """
+ Set the relative widths of the columns.
+
+ *width_ratios* must be of length *ncols*. Each column gets a relative
+ width of ``width_ratios[i] / sum(width_ratios)``.
+ """
+ if width_ratios is None:
+ width_ratios = [1] * self._ncols
+ elif len(width_ratios) != self._ncols:
+ raise ValueError('Expected the given number of width ratios to '
+ 'match the number of columns of the grid')
+ self._col_width_ratios = width_ratios
+
+ def get_width_ratios(self):
+ """
+ Return the width ratios.
+
+ This is *None* if no width ratios have been set explicitly.
+ """
+ return self._col_width_ratios
+
+ def set_height_ratios(self, height_ratios):
+ """
+ Set the relative heights of the rows.
+
+ *height_ratios* must be of length *nrows*. Each row gets a relative
+ height of ``height_ratios[i] / sum(height_ratios)``.
+ """
+ if height_ratios is None:
+ height_ratios = [1] * self._nrows
+ elif len(height_ratios) != self._nrows:
+ raise ValueError('Expected the given number of height ratios to '
+ 'match the number of rows of the grid')
+ self._row_height_ratios = height_ratios
+
+ def get_height_ratios(self):
+ """
+ Return the height ratios.
+
+ This is *None* if no height ratios have been set explicitly.
+ """
+ return self._row_height_ratios
+
+ def get_grid_positions(self, fig):
+ """
+ Return the positions of the grid cells in figure coordinates.
+
+ Parameters
+ ----------
+ fig : `~matplotlib.figure.Figure`
+ The figure the grid should be applied to. The subplot parameters
+ (margins and spacing between subplots) are taken from *fig*.
+
+ Returns
+ -------
+ bottoms, tops, lefts, rights : array
+ The bottom, top, left, right positions of the grid cells in
+ figure coordinates.
+ """
+ nrows, ncols = self.get_geometry()
+ subplot_params = self.get_subplot_params(fig)
+ left = subplot_params.left
+ right = subplot_params.right
+ bottom = subplot_params.bottom
+ top = subplot_params.top
+ wspace = subplot_params.wspace
+ hspace = subplot_params.hspace
+ tot_width = right - left
+ tot_height = top - bottom
+
+ # calculate accumulated heights of columns
+ cell_h = tot_height / (nrows + hspace*(nrows-1))
+ sep_h = hspace * cell_h
+ norm = cell_h * nrows / sum(self._row_height_ratios)
+ cell_heights = [r * norm for r in self._row_height_ratios]
+ sep_heights = [0] + ([sep_h] * (nrows-1))
+ cell_hs = np.cumsum(np.column_stack([sep_heights, cell_heights]).flat)
+
+ # calculate accumulated widths of rows
+ cell_w = tot_width / (ncols + wspace*(ncols-1))
+ sep_w = wspace * cell_w
+ norm = cell_w * ncols / sum(self._col_width_ratios)
+ cell_widths = [r * norm for r in self._col_width_ratios]
+ sep_widths = [0] + ([sep_w] * (ncols-1))
+ cell_ws = np.cumsum(np.column_stack([sep_widths, cell_widths]).flat)
+
+ fig_tops, fig_bottoms = (top - cell_hs).reshape((-1, 2)).T
+ fig_lefts, fig_rights = (left + cell_ws).reshape((-1, 2)).T
+ return fig_bottoms, fig_tops, fig_lefts, fig_rights
+
+ @staticmethod
+ def _check_gridspec_exists(figure, nrows, ncols):
+ """
+ Check if the figure already has a gridspec with these dimensions,
+ or create a new one
+ """
+ for ax in figure.get_axes():
+ gs = ax.get_gridspec()
+ if gs is not None:
+ if hasattr(gs, 'get_topmost_subplotspec'):
+ # This is needed for colorbar gridspec layouts.
+ # This is probably OK because this whole logic tree
+ # is for when the user is doing simple things with the
+ # add_subplot command. For complicated layouts
+ # like subgridspecs the proper gridspec is passed in...
+ gs = gs.get_topmost_subplotspec().get_gridspec()
+ if gs.get_geometry() == (nrows, ncols):
+ return gs
+ # else gridspec not found:
+ return GridSpec(nrows, ncols, figure=figure)
+
+ def __getitem__(self, key):
+ """Create and return a `.SubplotSpec` instance."""
+ nrows, ncols = self.get_geometry()
+
+ def _normalize(key, size, axis): # Includes last index.
+ orig_key = key
+ if isinstance(key, slice):
+ start, stop, _ = key.indices(size)
+ if stop > start:
+ return start, stop - 1
+ raise IndexError("GridSpec slice would result in no space "
+ "allocated for subplot")
+ else:
+ if key < 0:
+ key = key + size
+ if 0 <= key < size:
+ return key, key
+ elif axis is not None:
+ raise IndexError(f"index {orig_key} is out of bounds for "
+ f"axis {axis} with size {size}")
+ else: # flat index
+ raise IndexError(f"index {orig_key} is out of bounds for "
+ f"GridSpec with size {size}")
+
+ if isinstance(key, tuple):
+ try:
+ k1, k2 = key
+ except ValueError as err:
+ raise ValueError("Unrecognized subplot spec") from err
+ num1, num2 = np.ravel_multi_index(
+ [_normalize(k1, nrows, 0), _normalize(k2, ncols, 1)],
+ (nrows, ncols))
+ else: # Single key
+ num1, num2 = _normalize(key, nrows * ncols, None)
+
+ return SubplotSpec(self, num1, num2)
+
+ def subplots(self, *, sharex=False, sharey=False, squeeze=True,
+ subplot_kw=None):
+ """
+ Add all subplots specified by this `GridSpec` to its parent figure.
+
+ See `.Figure.subplots` for detailed documentation.
+ """
+
+ figure = self.figure
+
+ if figure is None:
+ raise ValueError("GridSpec.subplots() only works for GridSpecs "
+ "created with a parent figure")
+
+ if not isinstance(sharex, str):
+ sharex = "all" if sharex else "none"
+ if not isinstance(sharey, str):
+ sharey = "all" if sharey else "none"
+
+ _api.check_in_list(["all", "row", "col", "none", False, True],
+ sharex=sharex, sharey=sharey)
+ if subplot_kw is None:
+ subplot_kw = {}
+ # don't mutate kwargs passed by user...
+ subplot_kw = subplot_kw.copy()
+
+ # Create array to hold all Axes.
+ axarr = np.empty((self._nrows, self._ncols), dtype=object)
+ for row in range(self._nrows):
+ for col in range(self._ncols):
+ shared_with = {"none": None, "all": axarr[0, 0],
+ "row": axarr[row, 0], "col": axarr[0, col]}
+ subplot_kw["sharex"] = shared_with[sharex]
+ subplot_kw["sharey"] = shared_with[sharey]
+ axarr[row, col] = figure.add_subplot(
+ self[row, col], **subplot_kw)
+
+ # turn off redundant tick labeling
+ if sharex in ["col", "all"]:
+ for ax in axarr.flat:
+ ax._label_outer_xaxis(skip_non_rectangular_axes=True)
+ if sharey in ["row", "all"]:
+ for ax in axarr.flat:
+ ax._label_outer_yaxis(skip_non_rectangular_axes=True)
+
+ if squeeze:
+ # Discarding unneeded dimensions that equal 1. If we only have one
+ # subplot, just return it instead of a 1-element array.
+ return axarr.item() if axarr.size == 1 else axarr.squeeze()
+ else:
+ # Returned axis array will be always 2-d, even if nrows=ncols=1.
+ return axarr
+
+
+class GridSpec(GridSpecBase):
+ """
+ A grid layout to place subplots within a figure.
+
+ The location of the grid cells is determined in a similar way to
+ `.SubplotParams` using *left*, *right*, *top*, *bottom*, *wspace*
+ and *hspace*.
+
+ Indexing a GridSpec instance returns a `.SubplotSpec`.
+ """
+ def __init__(self, nrows, ncols, figure=None,
+ left=None, bottom=None, right=None, top=None,
+ wspace=None, hspace=None,
+ width_ratios=None, height_ratios=None):
+ """
+ Parameters
+ ----------
+ nrows, ncols : int
+ The number of rows and columns of the grid.
+
+ figure : `.Figure`, optional
+ Only used for constrained layout to create a proper layoutgrid.
+
+ left, right, top, bottom : float, optional
+ Extent of the subplots as a fraction of figure width or height.
+ Left cannot be larger than right, and bottom cannot be larger than
+ top. If not given, the values will be inferred from a figure or
+ rcParams at draw time. See also `GridSpec.get_subplot_params`.
+
+ wspace : float, optional
+ The amount of width reserved for space between subplots,
+ expressed as a fraction of the average axis width.
+ If not given, the values will be inferred from a figure or
+ rcParams when necessary. See also `GridSpec.get_subplot_params`.
+
+ hspace : float, optional
+ The amount of height reserved for space between subplots,
+ expressed as a fraction of the average axis height.
+ If not given, the values will be inferred from a figure or
+ rcParams when necessary. See also `GridSpec.get_subplot_params`.
+
+ width_ratios : array-like of length *ncols*, optional
+ Defines the relative widths of the columns. Each column gets a
+ relative width of ``width_ratios[i] / sum(width_ratios)``.
+ If not given, all columns will have the same width.
+
+ height_ratios : array-like of length *nrows*, optional
+ Defines the relative heights of the rows. Each row gets a
+ relative height of ``height_ratios[i] / sum(height_ratios)``.
+ If not given, all rows will have the same height.
+
+ """
+ self.left = left
+ self.bottom = bottom
+ self.right = right
+ self.top = top
+ self.wspace = wspace
+ self.hspace = hspace
+ self.figure = figure
+
+ super().__init__(nrows, ncols,
+ width_ratios=width_ratios,
+ height_ratios=height_ratios)
+
+ _AllowedKeys = ["left", "bottom", "right", "top", "wspace", "hspace"]
+
+ def update(self, **kwargs):
+ """
+ Update the subplot parameters of the grid.
+
+ Parameters that are not explicitly given are not changed. Setting a
+ parameter to *None* resets it to :rc:`figure.subplot.*`.
+
+ Parameters
+ ----------
+ left, right, top, bottom : float or None, optional
+ Extent of the subplots as a fraction of figure width or height.
+ wspace, hspace : float, optional
+ Spacing between the subplots as a fraction of the average subplot
+ width / height.
+ """
+ for k, v in kwargs.items():
+ if k in self._AllowedKeys:
+ setattr(self, k, v)
+ else:
+ raise AttributeError(f"{k} is an unknown keyword")
+ for figmanager in _pylab_helpers.Gcf.figs.values():
+ for ax in figmanager.canvas.figure.axes:
+ if ax.get_subplotspec() is not None:
+ ss = ax.get_subplotspec().get_topmost_subplotspec()
+ if ss.get_gridspec() == self:
+ fig = ax.get_figure(root=False)
+ ax._set_position(ax.get_subplotspec().get_position(fig))
+
+ def get_subplot_params(self, figure=None):
+ """
+ Return the `.SubplotParams` for the GridSpec.
+
+ In order of precedence the values are taken from
+
+ - non-*None* attributes of the GridSpec
+ - the provided *figure*
+ - :rc:`figure.subplot.*`
+
+ Note that the ``figure`` attribute of the GridSpec is always ignored.
+ """
+ if figure is None:
+ kw = {k: mpl.rcParams["figure.subplot."+k]
+ for k in self._AllowedKeys}
+ subplotpars = SubplotParams(**kw)
+ else:
+ subplotpars = copy.copy(figure.subplotpars)
+
+ subplotpars.update(**{k: getattr(self, k) for k in self._AllowedKeys})
+
+ return subplotpars
+
+ def locally_modified_subplot_params(self):
+ """
+ Return a list of the names of the subplot parameters explicitly set
+ in the GridSpec.
+
+ This is a subset of the attributes of `.SubplotParams`.
+ """
+ return [k for k in self._AllowedKeys if getattr(self, k)]
+
+ def tight_layout(self, figure, renderer=None,
+ pad=1.08, h_pad=None, w_pad=None, rect=None):
+ """
+ Adjust subplot parameters to give specified padding.
+
+ Parameters
+ ----------
+ figure : `.Figure`
+ The figure.
+ renderer : `.RendererBase` subclass, optional
+ The renderer to be used.
+ pad : float
+ Padding between the figure edge and the edges of subplots, as a
+ fraction of the font-size.
+ h_pad, w_pad : float, optional
+ Padding (height/width) between edges of adjacent subplots.
+ Defaults to *pad*.
+ rect : tuple (left, bottom, right, top), default: None
+ (left, bottom, right, top) rectangle in normalized figure
+ coordinates that the whole subplots area (including labels) will
+ fit into. Default (None) is the whole figure.
+ """
+ if renderer is None:
+ renderer = figure._get_renderer()
+ kwargs = _tight_layout.get_tight_layout_figure(
+ figure, figure.axes,
+ _tight_layout.get_subplotspec_list(figure.axes, grid_spec=self),
+ renderer, pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
+ if kwargs:
+ self.update(**kwargs)
+
+
+class GridSpecFromSubplotSpec(GridSpecBase):
+ """
+ GridSpec whose subplot layout parameters are inherited from the
+ location specified by a given SubplotSpec.
+ """
+ def __init__(self, nrows, ncols,
+ subplot_spec,
+ wspace=None, hspace=None,
+ height_ratios=None, width_ratios=None):
+ """
+ Parameters
+ ----------
+ nrows, ncols : int
+ Number of rows and number of columns of the grid.
+ subplot_spec : SubplotSpec
+ Spec from which the layout parameters are inherited.
+ wspace, hspace : float, optional
+ See `GridSpec` for more details. If not specified default values
+ (from the figure or rcParams) are used.
+ height_ratios : array-like of length *nrows*, optional
+ See `GridSpecBase` for details.
+ width_ratios : array-like of length *ncols*, optional
+ See `GridSpecBase` for details.
+ """
+ self._wspace = wspace
+ self._hspace = hspace
+ if isinstance(subplot_spec, SubplotSpec):
+ self._subplot_spec = subplot_spec
+ else:
+ raise TypeError(
+ "subplot_spec must be type SubplotSpec, "
+ "usually from GridSpec, or axes.get_subplotspec.")
+ self.figure = self._subplot_spec.get_gridspec().figure
+ super().__init__(nrows, ncols,
+ width_ratios=width_ratios,
+ height_ratios=height_ratios)
+
+ def get_subplot_params(self, figure=None):
+ """Return a dictionary of subplot layout parameters."""
+ hspace = (self._hspace if self._hspace is not None
+ else figure.subplotpars.hspace if figure is not None
+ else mpl.rcParams["figure.subplot.hspace"])
+ wspace = (self._wspace if self._wspace is not None
+ else figure.subplotpars.wspace if figure is not None
+ else mpl.rcParams["figure.subplot.wspace"])
+
+ figbox = self._subplot_spec.get_position(figure)
+ left, bottom, right, top = figbox.extents
+
+ return SubplotParams(left=left, right=right,
+ bottom=bottom, top=top,
+ wspace=wspace, hspace=hspace)
+
+ def get_topmost_subplotspec(self):
+ """
+ Return the topmost `.SubplotSpec` instance associated with the subplot.
+ """
+ return self._subplot_spec.get_topmost_subplotspec()
+
+
+class SubplotSpec:
+ """
+ The location of a subplot in a `GridSpec`.
+
+ .. note::
+
+ Likely, you will never instantiate a `SubplotSpec` yourself. Instead,
+ you will typically obtain one from a `GridSpec` using item-access.
+
+ Parameters
+ ----------
+ gridspec : `~matplotlib.gridspec.GridSpec`
+ The GridSpec, which the subplot is referencing.
+ num1, num2 : int
+ The subplot will occupy the *num1*-th cell of the given
+ *gridspec*. If *num2* is provided, the subplot will span between
+ *num1*-th cell and *num2*-th cell **inclusive**.
+
+ The index starts from 0.
+ """
+ def __init__(self, gridspec, num1, num2=None):
+ self._gridspec = gridspec
+ self.num1 = num1
+ self.num2 = num2
+
+ def __repr__(self):
+ return (f"{self.get_gridspec()}["
+ f"{self.rowspan.start}:{self.rowspan.stop}, "
+ f"{self.colspan.start}:{self.colspan.stop}]")
+
+ @staticmethod
+ def _from_subplot_args(figure, args):
+ """
+ Construct a `.SubplotSpec` from a parent `.Figure` and either
+
+ - a `.SubplotSpec` -- returned as is;
+ - one or three numbers -- a MATLAB-style subplot specifier.
+ """
+ if len(args) == 1:
+ arg, = args
+ if isinstance(arg, SubplotSpec):
+ return arg
+ elif not isinstance(arg, Integral):
+ raise ValueError(
+ f"Single argument to subplot must be a three-digit "
+ f"integer, not {arg!r}")
+ try:
+ rows, cols, num = map(int, str(arg))
+ except ValueError:
+ raise ValueError(
+ f"Single argument to subplot must be a three-digit "
+ f"integer, not {arg!r}") from None
+ elif len(args) == 3:
+ rows, cols, num = args
+ else:
+ raise _api.nargs_error("subplot", takes="1 or 3", given=len(args))
+
+ gs = GridSpec._check_gridspec_exists(figure, rows, cols)
+ if gs is None:
+ gs = GridSpec(rows, cols, figure=figure)
+ if isinstance(num, tuple) and len(num) == 2:
+ if not all(isinstance(n, Integral) for n in num):
+ raise ValueError(
+ f"Subplot specifier tuple must contain integers, not {num}"
+ )
+ i, j = num
+ else:
+ if not isinstance(num, Integral) or num < 1 or num > rows*cols:
+ raise ValueError(
+ f"num must be an integer with 1 <= num <= {rows*cols}, "
+ f"not {num!r}"
+ )
+ i = j = num
+ return gs[i-1:j]
+
+ # num2 is a property only to handle the case where it is None and someone
+ # mutates num1.
+
+ @property
+ def num2(self):
+ return self.num1 if self._num2 is None else self._num2
+
+ @num2.setter
+ def num2(self, value):
+ self._num2 = value
+
+ def get_gridspec(self):
+ return self._gridspec
+
+ def get_geometry(self):
+ """
+ Return the subplot geometry as tuple ``(n_rows, n_cols, start, stop)``.
+
+ The indices *start* and *stop* define the range of the subplot within
+ the `GridSpec`. *stop* is inclusive (i.e. for a single cell
+ ``start == stop``).
+ """
+ rows, cols = self.get_gridspec().get_geometry()
+ return rows, cols, self.num1, self.num2
+
+ @property
+ def rowspan(self):
+ """The rows spanned by this subplot, as a `range` object."""
+ ncols = self.get_gridspec().ncols
+ return range(self.num1 // ncols, self.num2 // ncols + 1)
+
+ @property
+ def colspan(self):
+ """The columns spanned by this subplot, as a `range` object."""
+ ncols = self.get_gridspec().ncols
+ # We explicitly support num2 referring to a column on num1's *left*, so
+ # we must sort the column indices here so that the range makes sense.
+ c1, c2 = sorted([self.num1 % ncols, self.num2 % ncols])
+ return range(c1, c2 + 1)
+
+ def is_first_row(self):
+ return self.rowspan.start == 0
+
+ def is_last_row(self):
+ return self.rowspan.stop == self.get_gridspec().nrows
+
+ def is_first_col(self):
+ return self.colspan.start == 0
+
+ def is_last_col(self):
+ return self.colspan.stop == self.get_gridspec().ncols
+
+ def get_position(self, figure):
+ """
+ Update the subplot position from ``figure.subplotpars``.
+ """
+ gridspec = self.get_gridspec()
+ nrows, ncols = gridspec.get_geometry()
+ rows, cols = np.unravel_index([self.num1, self.num2], (nrows, ncols))
+ fig_bottoms, fig_tops, fig_lefts, fig_rights = \
+ gridspec.get_grid_positions(figure)
+
+ fig_bottom = fig_bottoms[rows].min()
+ fig_top = fig_tops[rows].max()
+ fig_left = fig_lefts[cols].min()
+ fig_right = fig_rights[cols].max()
+ return Bbox.from_extents(fig_left, fig_bottom, fig_right, fig_top)
+
+ def get_topmost_subplotspec(self):
+ """
+ Return the topmost `SubplotSpec` instance associated with the subplot.
+ """
+ gridspec = self.get_gridspec()
+ if hasattr(gridspec, "get_topmost_subplotspec"):
+ return gridspec.get_topmost_subplotspec()
+ else:
+ return self
+
+ def __eq__(self, other):
+ """
+ Two SubplotSpecs are considered equal if they refer to the same
+ position(s) in the same `GridSpec`.
+ """
+ # other may not even have the attributes we are checking.
+ return ((self._gridspec, self.num1, self.num2)
+ == (getattr(other, "_gridspec", object()),
+ getattr(other, "num1", object()),
+ getattr(other, "num2", object())))
+
+ def __hash__(self):
+ return hash((self._gridspec, self.num1, self.num2))
+
+ def subgridspec(self, nrows, ncols, **kwargs):
+ """
+ Create a GridSpec within this subplot.
+
+ The created `.GridSpecFromSubplotSpec` will have this `SubplotSpec` as
+ a parent.
+
+ Parameters
+ ----------
+ nrows : int
+ Number of rows in grid.
+
+ ncols : int
+ Number of columns in grid.
+
+ Returns
+ -------
+ `.GridSpecFromSubplotSpec`
+
+ Other Parameters
+ ----------------
+ **kwargs
+ All other parameters are passed to `.GridSpecFromSubplotSpec`.
+
+ See Also
+ --------
+ matplotlib.pyplot.subplots
+
+ Examples
+ --------
+ Adding three subplots in the space occupied by a single subplot::
+
+ fig = plt.figure()
+ gs0 = fig.add_gridspec(3, 1)
+ ax1 = fig.add_subplot(gs0[0])
+ ax2 = fig.add_subplot(gs0[1])
+ gssub = gs0[2].subgridspec(1, 3)
+ for i in range(3):
+ fig.add_subplot(gssub[0, i])
+ """
+ return GridSpecFromSubplotSpec(nrows, ncols, self, **kwargs)
+
+
+class SubplotParams:
+ """
+ Parameters defining the positioning of a subplots grid in a figure.
+ """
+
+ def __init__(self, left=None, bottom=None, right=None, top=None,
+ wspace=None, hspace=None):
+ """
+ Defaults are given by :rc:`figure.subplot.[name]`.
+
+ Parameters
+ ----------
+ left : float
+ The position of the left edge of the subplots,
+ as a fraction of the figure width.
+ right : float
+ The position of the right edge of the subplots,
+ as a fraction of the figure width.
+ bottom : float
+ The position of the bottom edge of the subplots,
+ as a fraction of the figure height.
+ top : float
+ The position of the top edge of the subplots,
+ as a fraction of the figure height.
+ wspace : float
+ The width of the padding between subplots,
+ as a fraction of the average Axes width.
+ hspace : float
+ The height of the padding between subplots,
+ as a fraction of the average Axes height.
+ """
+ for key in ["left", "bottom", "right", "top", "wspace", "hspace"]:
+ setattr(self, key, mpl.rcParams[f"figure.subplot.{key}"])
+ self.update(left, bottom, right, top, wspace, hspace)
+
+ def update(self, left=None, bottom=None, right=None, top=None,
+ wspace=None, hspace=None):
+ """
+ Update the dimensions of the passed parameters. *None* means unchanged.
+ """
+ if ((left if left is not None else self.left)
+ >= (right if right is not None else self.right)):
+ raise ValueError('left cannot be >= right')
+ if ((bottom if bottom is not None else self.bottom)
+ >= (top if top is not None else self.top)):
+ raise ValueError('bottom cannot be >= top')
+ if left is not None:
+ self.left = left
+ if right is not None:
+ self.right = right
+ if bottom is not None:
+ self.bottom = bottom
+ if top is not None:
+ self.top = top
+ if wspace is not None:
+ self.wspace = wspace
+ if hspace is not None:
+ self.hspace = hspace
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..3bf13ee17c4eb3bdb7d6a00a171e7db233260d7e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.pyi
@@ -0,0 +1,160 @@
+from typing import Any, Literal, overload
+
+from numpy.typing import ArrayLike
+import numpy as np
+
+from matplotlib.axes import Axes
+from matplotlib.backend_bases import RendererBase
+from matplotlib.figure import Figure
+from matplotlib.transforms import Bbox
+
+class GridSpecBase:
+ def __init__(
+ self,
+ nrows: int,
+ ncols: int,
+ height_ratios: ArrayLike | None = ...,
+ width_ratios: ArrayLike | None = ...,
+ ) -> None: ...
+ @property
+ def nrows(self) -> int: ...
+ @property
+ def ncols(self) -> int: ...
+ def get_geometry(self) -> tuple[int, int]: ...
+ def get_subplot_params(self, figure: Figure | None = ...) -> SubplotParams: ...
+ def new_subplotspec(
+ self, loc: tuple[int, int], rowspan: int = ..., colspan: int = ...
+ ) -> SubplotSpec: ...
+ def set_width_ratios(self, width_ratios: ArrayLike | None) -> None: ...
+ def get_width_ratios(self) -> ArrayLike: ...
+ def set_height_ratios(self, height_ratios: ArrayLike | None) -> None: ...
+ def get_height_ratios(self) -> ArrayLike: ...
+ def get_grid_positions(
+ self, fig: Figure
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: ...
+ @staticmethod
+ def _check_gridspec_exists(figure: Figure, nrows: int, ncols: int) -> GridSpec: ...
+ def __getitem__(
+ self, key: tuple[int | slice, int | slice] | slice | int
+ ) -> SubplotSpec: ...
+ @overload
+ def subplots(
+ self,
+ *,
+ sharex: bool | Literal["all", "row", "col", "none"] = ...,
+ sharey: bool | Literal["all", "row", "col", "none"] = ...,
+ squeeze: Literal[False],
+ subplot_kw: dict[str, Any] | None = ...
+ ) -> np.ndarray: ...
+ @overload
+ def subplots(
+ self,
+ *,
+ sharex: bool | Literal["all", "row", "col", "none"] = ...,
+ sharey: bool | Literal["all", "row", "col", "none"] = ...,
+ squeeze: Literal[True] = ...,
+ subplot_kw: dict[str, Any] | None = ...
+ ) -> np.ndarray | Axes: ...
+
+class GridSpec(GridSpecBase):
+ left: float | None
+ bottom: float | None
+ right: float | None
+ top: float | None
+ wspace: float | None
+ hspace: float | None
+ figure: Figure | None
+ def __init__(
+ self,
+ nrows: int,
+ ncols: int,
+ figure: Figure | None = ...,
+ left: float | None = ...,
+ bottom: float | None = ...,
+ right: float | None = ...,
+ top: float | None = ...,
+ wspace: float | None = ...,
+ hspace: float | None = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ ) -> None: ...
+ def update(self, **kwargs: float | None) -> None: ...
+ def locally_modified_subplot_params(self) -> list[str]: ...
+ def tight_layout(
+ self,
+ figure: Figure,
+ renderer: RendererBase | None = ...,
+ pad: float = ...,
+ h_pad: float | None = ...,
+ w_pad: float | None = ...,
+ rect: tuple[float, float, float, float] | None = ...,
+ ) -> None: ...
+
+class GridSpecFromSubplotSpec(GridSpecBase):
+ figure: Figure | None
+ def __init__(
+ self,
+ nrows: int,
+ ncols: int,
+ subplot_spec: SubplotSpec,
+ wspace: float | None = ...,
+ hspace: float | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ width_ratios: ArrayLike | None = ...,
+ ) -> None: ...
+ def get_topmost_subplotspec(self) -> SubplotSpec: ...
+
+class SubplotSpec:
+ num1: int
+ def __init__(
+ self, gridspec: GridSpecBase, num1: int, num2: int | None = ...
+ ) -> None: ...
+ @staticmethod
+ def _from_subplot_args(figure, args): ...
+ @property
+ def num2(self) -> int: ...
+ @num2.setter
+ def num2(self, value: int) -> None: ...
+ def get_gridspec(self) -> GridSpecBase: ...
+ def get_geometry(self) -> tuple[int, int, int, int]: ...
+ @property
+ def rowspan(self) -> range: ...
+ @property
+ def colspan(self) -> range: ...
+ def is_first_row(self) -> bool: ...
+ def is_last_row(self) -> bool: ...
+ def is_first_col(self) -> bool: ...
+ def is_last_col(self) -> bool: ...
+ def get_position(self, figure: Figure) -> Bbox: ...
+ def get_topmost_subplotspec(self) -> SubplotSpec: ...
+ def __eq__(self, other: object) -> bool: ...
+ def __hash__(self) -> int: ...
+ def subgridspec(
+ self, nrows: int, ncols: int, **kwargs
+ ) -> GridSpecFromSubplotSpec: ...
+
+class SubplotParams:
+ def __init__(
+ self,
+ left: float | None = ...,
+ bottom: float | None = ...,
+ right: float | None = ...,
+ top: float | None = ...,
+ wspace: float | None = ...,
+ hspace: float | None = ...,
+ ) -> None: ...
+ left: float
+ right: float
+ bottom: float
+ top: float
+ wspace: float
+ hspace: float
+ def update(
+ self,
+ left: float | None = ...,
+ bottom: float | None = ...,
+ right: float | None = ...,
+ top: float | None = ...,
+ wspace: float | None = ...,
+ hspace: float | None = ...,
+ ) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.py
new file mode 100644
index 0000000000000000000000000000000000000000..0cbd042e1628bdfa2172ca1f64eb146d8f8e54fb
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.py
@@ -0,0 +1,225 @@
+"""Contains classes for generating hatch patterns."""
+
+import numpy as np
+
+from matplotlib import _api
+from matplotlib.path import Path
+
+
+class HatchPatternBase:
+ """The base class for a hatch pattern."""
+ pass
+
+
+class HorizontalHatch(HatchPatternBase):
+ def __init__(self, hatch, density):
+ self.num_lines = int((hatch.count('-') + hatch.count('+')) * density)
+ self.num_vertices = self.num_lines * 2
+
+ def set_vertices_and_codes(self, vertices, codes):
+ steps, stepsize = np.linspace(0.0, 1.0, self.num_lines, False,
+ retstep=True)
+ steps += stepsize / 2.
+ vertices[0::2, 0] = 0.0
+ vertices[0::2, 1] = steps
+ vertices[1::2, 0] = 1.0
+ vertices[1::2, 1] = steps
+ codes[0::2] = Path.MOVETO
+ codes[1::2] = Path.LINETO
+
+
+class VerticalHatch(HatchPatternBase):
+ def __init__(self, hatch, density):
+ self.num_lines = int((hatch.count('|') + hatch.count('+')) * density)
+ self.num_vertices = self.num_lines * 2
+
+ def set_vertices_and_codes(self, vertices, codes):
+ steps, stepsize = np.linspace(0.0, 1.0, self.num_lines, False,
+ retstep=True)
+ steps += stepsize / 2.
+ vertices[0::2, 0] = steps
+ vertices[0::2, 1] = 0.0
+ vertices[1::2, 0] = steps
+ vertices[1::2, 1] = 1.0
+ codes[0::2] = Path.MOVETO
+ codes[1::2] = Path.LINETO
+
+
+class NorthEastHatch(HatchPatternBase):
+ def __init__(self, hatch, density):
+ self.num_lines = int(
+ (hatch.count('/') + hatch.count('x') + hatch.count('X')) * density)
+ if self.num_lines:
+ self.num_vertices = (self.num_lines + 1) * 2
+ else:
+ self.num_vertices = 0
+
+ def set_vertices_and_codes(self, vertices, codes):
+ steps = np.linspace(-0.5, 0.5, self.num_lines + 1)
+ vertices[0::2, 0] = 0.0 + steps
+ vertices[0::2, 1] = 0.0 - steps
+ vertices[1::2, 0] = 1.0 + steps
+ vertices[1::2, 1] = 1.0 - steps
+ codes[0::2] = Path.MOVETO
+ codes[1::2] = Path.LINETO
+
+
+class SouthEastHatch(HatchPatternBase):
+ def __init__(self, hatch, density):
+ self.num_lines = int(
+ (hatch.count('\\') + hatch.count('x') + hatch.count('X'))
+ * density)
+ if self.num_lines:
+ self.num_vertices = (self.num_lines + 1) * 2
+ else:
+ self.num_vertices = 0
+
+ def set_vertices_and_codes(self, vertices, codes):
+ steps = np.linspace(-0.5, 0.5, self.num_lines + 1)
+ vertices[0::2, 0] = 0.0 + steps
+ vertices[0::2, 1] = 1.0 + steps
+ vertices[1::2, 0] = 1.0 + steps
+ vertices[1::2, 1] = 0.0 + steps
+ codes[0::2] = Path.MOVETO
+ codes[1::2] = Path.LINETO
+
+
+class Shapes(HatchPatternBase):
+ filled = False
+
+ def __init__(self, hatch, density):
+ if self.num_rows == 0:
+ self.num_shapes = 0
+ self.num_vertices = 0
+ else:
+ self.num_shapes = ((self.num_rows // 2 + 1) * (self.num_rows + 1) +
+ (self.num_rows // 2) * self.num_rows)
+ self.num_vertices = (self.num_shapes *
+ len(self.shape_vertices) *
+ (1 if self.filled else 2))
+
+ def set_vertices_and_codes(self, vertices, codes):
+ offset = 1.0 / self.num_rows
+ shape_vertices = self.shape_vertices * offset * self.size
+ shape_codes = self.shape_codes
+ if not self.filled:
+ shape_vertices = np.concatenate( # Forward, then backward.
+ [shape_vertices, shape_vertices[::-1] * 0.9])
+ shape_codes = np.concatenate([shape_codes, shape_codes])
+ vertices_parts = []
+ codes_parts = []
+ for row in range(self.num_rows + 1):
+ if row % 2 == 0:
+ cols = np.linspace(0, 1, self.num_rows + 1)
+ else:
+ cols = np.linspace(offset / 2, 1 - offset / 2, self.num_rows)
+ row_pos = row * offset
+ for col_pos in cols:
+ vertices_parts.append(shape_vertices + [col_pos, row_pos])
+ codes_parts.append(shape_codes)
+ np.concatenate(vertices_parts, out=vertices)
+ np.concatenate(codes_parts, out=codes)
+
+
+class Circles(Shapes):
+ def __init__(self, hatch, density):
+ path = Path.unit_circle()
+ self.shape_vertices = path.vertices
+ self.shape_codes = path.codes
+ super().__init__(hatch, density)
+
+
+class SmallCircles(Circles):
+ size = 0.2
+
+ def __init__(self, hatch, density):
+ self.num_rows = (hatch.count('o')) * density
+ super().__init__(hatch, density)
+
+
+class LargeCircles(Circles):
+ size = 0.35
+
+ def __init__(self, hatch, density):
+ self.num_rows = (hatch.count('O')) * density
+ super().__init__(hatch, density)
+
+
+class SmallFilledCircles(Circles):
+ size = 0.1
+ filled = True
+
+ def __init__(self, hatch, density):
+ self.num_rows = (hatch.count('.')) * density
+ super().__init__(hatch, density)
+
+
+class Stars(Shapes):
+ size = 1.0 / 3.0
+ filled = True
+
+ def __init__(self, hatch, density):
+ self.num_rows = (hatch.count('*')) * density
+ path = Path.unit_regular_star(5)
+ self.shape_vertices = path.vertices
+ self.shape_codes = np.full(len(self.shape_vertices), Path.LINETO,
+ dtype=Path.code_type)
+ self.shape_codes[0] = Path.MOVETO
+ super().__init__(hatch, density)
+
+_hatch_types = [
+ HorizontalHatch,
+ VerticalHatch,
+ NorthEastHatch,
+ SouthEastHatch,
+ SmallCircles,
+ LargeCircles,
+ SmallFilledCircles,
+ Stars
+ ]
+
+
+def _validate_hatch_pattern(hatch):
+ valid_hatch_patterns = set(r'-+|/\xXoO.*')
+ if hatch is not None:
+ invalids = set(hatch).difference(valid_hatch_patterns)
+ if invalids:
+ valid = ''.join(sorted(valid_hatch_patterns))
+ invalids = ''.join(sorted(invalids))
+ _api.warn_deprecated(
+ '3.4',
+ removal='3.11', # one release after custom hatches (#20690)
+ message=f'hatch must consist of a string of "{valid}" or '
+ 'None, but found the following invalid values '
+ f'"{invalids}". Passing invalid values is deprecated '
+ 'since %(since)s and will become an error in %(removal)s.'
+ )
+
+
+def get_path(hatchpattern, density=6):
+ """
+ Given a hatch specifier, *hatchpattern*, generates Path to render
+ the hatch in a unit square. *density* is the number of lines per
+ unit square.
+ """
+ density = int(density)
+
+ patterns = [hatch_type(hatchpattern, density)
+ for hatch_type in _hatch_types]
+ num_vertices = sum([pattern.num_vertices for pattern in patterns])
+
+ if num_vertices == 0:
+ return Path(np.empty((0, 2)))
+
+ vertices = np.empty((num_vertices, 2))
+ codes = np.empty(num_vertices, Path.code_type)
+
+ cursor = 0
+ for pattern in patterns:
+ if pattern.num_vertices != 0:
+ vertices_chunk = vertices[cursor:cursor + pattern.num_vertices]
+ codes_chunk = codes[cursor:cursor + pattern.num_vertices]
+ pattern.set_vertices_and_codes(vertices_chunk, codes_chunk)
+ cursor += pattern.num_vertices
+
+ return Path(vertices, codes)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..348cf5214984bd0ba23b3953b2e79300d0e04e3f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.pyi
@@ -0,0 +1,68 @@
+from matplotlib.path import Path
+
+import numpy as np
+from numpy.typing import ArrayLike
+
+class HatchPatternBase: ...
+
+class HorizontalHatch(HatchPatternBase):
+ num_lines: int
+ num_vertices: int
+ def __init__(self, hatch: str, density: int) -> None: ...
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
+
+class VerticalHatch(HatchPatternBase):
+ num_lines: int
+ num_vertices: int
+ def __init__(self, hatch: str, density: int) -> None: ...
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
+
+class NorthEastHatch(HatchPatternBase):
+ num_lines: int
+ num_vertices: int
+ def __init__(self, hatch: str, density: int) -> None: ...
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
+
+class SouthEastHatch(HatchPatternBase):
+ num_lines: int
+ num_vertices: int
+ def __init__(self, hatch: str, density: int) -> None: ...
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
+
+class Shapes(HatchPatternBase):
+ filled: bool
+ num_shapes: int
+ num_vertices: int
+ def __init__(self, hatch: str, density: int) -> None: ...
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
+
+class Circles(Shapes):
+ shape_vertices: np.ndarray
+ shape_codes: np.ndarray
+ def __init__(self, hatch: str, density: int) -> None: ...
+
+class SmallCircles(Circles):
+ size: float
+ num_rows: int
+ def __init__(self, hatch: str, density: int) -> None: ...
+
+class LargeCircles(Circles):
+ size: float
+ num_rows: int
+ def __init__(self, hatch: str, density: int) -> None: ...
+
+class SmallFilledCircles(Circles):
+ size: float
+ filled: bool
+ num_rows: int
+ def __init__(self, hatch: str, density: int) -> None: ...
+
+class Stars(Shapes):
+ size: float
+ filled: bool
+ num_rows: int
+ shape_vertices: np.ndarray
+ shape_codes: np.ndarray
+ def __init__(self, hatch: str, density: int) -> None: ...
+
+def get_path(hatchpattern: str, density: int = ...) -> Path: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/image.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/image.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf92ec985ae25ace261ae286c65935fa769f3dca
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/image.py
@@ -0,0 +1,1786 @@
+"""
+The image module supports basic image loading, rescaling and display
+operations.
+"""
+
+import math
+import os
+import logging
+from pathlib import Path
+import warnings
+
+import numpy as np
+import PIL.Image
+import PIL.PngImagePlugin
+
+import matplotlib as mpl
+from matplotlib import _api, cbook
+# For clarity, names from _image are given explicitly in this module
+from matplotlib import _image
+# For user convenience, the names from _image are also imported into
+# the image namespace
+from matplotlib._image import * # noqa: F401, F403
+import matplotlib.artist as martist
+import matplotlib.colorizer as mcolorizer
+from matplotlib.backend_bases import FigureCanvasBase
+import matplotlib.colors as mcolors
+from matplotlib.transforms import (
+ Affine2D, BboxBase, Bbox, BboxTransform, BboxTransformTo,
+ IdentityTransform, TransformedBbox)
+
+_log = logging.getLogger(__name__)
+
+# map interpolation strings to module constants
+_interpd_ = {
+ 'auto': _image.NEAREST, # this will use nearest or Hanning...
+ 'none': _image.NEAREST, # fall back to nearest when not supported
+ 'nearest': _image.NEAREST,
+ 'bilinear': _image.BILINEAR,
+ 'bicubic': _image.BICUBIC,
+ 'spline16': _image.SPLINE16,
+ 'spline36': _image.SPLINE36,
+ 'hanning': _image.HANNING,
+ 'hamming': _image.HAMMING,
+ 'hermite': _image.HERMITE,
+ 'kaiser': _image.KAISER,
+ 'quadric': _image.QUADRIC,
+ 'catrom': _image.CATROM,
+ 'gaussian': _image.GAUSSIAN,
+ 'bessel': _image.BESSEL,
+ 'mitchell': _image.MITCHELL,
+ 'sinc': _image.SINC,
+ 'lanczos': _image.LANCZOS,
+ 'blackman': _image.BLACKMAN,
+ 'antialiased': _image.NEAREST, # this will use nearest or Hanning...
+}
+
+interpolations_names = set(_interpd_)
+
+
+def composite_images(images, renderer, magnification=1.0):
+ """
+ Composite a number of RGBA images into one. The images are
+ composited in the order in which they appear in the *images* list.
+
+ Parameters
+ ----------
+ images : list of Images
+ Each must have a `make_image` method. For each image,
+ `can_composite` should return `True`, though this is not
+ enforced by this function. Each image must have a purely
+ affine transformation with no shear.
+
+ renderer : `.RendererBase`
+
+ magnification : float, default: 1
+ The additional magnification to apply for the renderer in use.
+
+ Returns
+ -------
+ image : (M, N, 4) `numpy.uint8` array
+ The composited RGBA image.
+ offset_x, offset_y : float
+ The (left, bottom) offset where the composited image should be placed
+ in the output figure.
+ """
+ if len(images) == 0:
+ return np.empty((0, 0, 4), dtype=np.uint8), 0, 0
+
+ parts = []
+ bboxes = []
+ for image in images:
+ data, x, y, trans = image.make_image(renderer, magnification)
+ if data is not None:
+ x *= magnification
+ y *= magnification
+ parts.append((data, x, y, image._get_scalar_alpha()))
+ bboxes.append(
+ Bbox([[x, y], [x + data.shape[1], y + data.shape[0]]]))
+
+ if len(parts) == 0:
+ return np.empty((0, 0, 4), dtype=np.uint8), 0, 0
+
+ bbox = Bbox.union(bboxes)
+
+ output = np.zeros(
+ (int(bbox.height), int(bbox.width), 4), dtype=np.uint8)
+
+ for data, x, y, alpha in parts:
+ trans = Affine2D().translate(x - bbox.x0, y - bbox.y0)
+ _image.resample(data, output, trans, _image.NEAREST,
+ resample=False, alpha=alpha)
+
+ return output, bbox.x0 / magnification, bbox.y0 / magnification
+
+
+def _draw_list_compositing_images(
+ renderer, parent, artists, suppress_composite=None):
+ """
+ Draw a sorted list of artists, compositing images into a single
+ image where possible.
+
+ For internal Matplotlib use only: It is here to reduce duplication
+ between `Figure.draw` and `Axes.draw`, but otherwise should not be
+ generally useful.
+ """
+ has_images = any(isinstance(x, _ImageBase) for x in artists)
+
+ # override the renderer default if suppressComposite is not None
+ not_composite = (suppress_composite if suppress_composite is not None
+ else renderer.option_image_nocomposite())
+
+ if not_composite or not has_images:
+ for a in artists:
+ a.draw(renderer)
+ else:
+ # Composite any adjacent images together
+ image_group = []
+ mag = renderer.get_image_magnification()
+
+ def flush_images():
+ if len(image_group) == 1:
+ image_group[0].draw(renderer)
+ elif len(image_group) > 1:
+ data, l, b = composite_images(image_group, renderer, mag)
+ if data.size != 0:
+ gc = renderer.new_gc()
+ gc.set_clip_rectangle(parent.bbox)
+ gc.set_clip_path(parent.get_clip_path())
+ renderer.draw_image(gc, round(l), round(b), data)
+ gc.restore()
+ del image_group[:]
+
+ for a in artists:
+ if (isinstance(a, _ImageBase) and a.can_composite() and
+ a.get_clip_on() and not a.get_clip_path()):
+ image_group.append(a)
+ else:
+ flush_images()
+ a.draw(renderer)
+ flush_images()
+
+
+def _resample(
+ image_obj, data, out_shape, transform, *, resample=None, alpha=1):
+ """
+ Convenience wrapper around `._image.resample` to resample *data* to
+ *out_shape* (with a third dimension if *data* is RGBA) that takes care of
+ allocating the output array and fetching the relevant properties from the
+ Image object *image_obj*.
+ """
+ # AGG can only handle coordinates smaller than 24-bit signed integers,
+ # so raise errors if the input data is larger than _image.resample can
+ # handle.
+ msg = ('Data with more than {n} cannot be accurately displayed. '
+ 'Downsampling to less than {n} before displaying. '
+ 'To remove this warning, manually downsample your data.')
+ if data.shape[1] > 2**23:
+ warnings.warn(msg.format(n='2**23 columns'))
+ step = int(np.ceil(data.shape[1] / 2**23))
+ data = data[:, ::step]
+ transform = Affine2D().scale(step, 1) + transform
+ if data.shape[0] > 2**24:
+ warnings.warn(msg.format(n='2**24 rows'))
+ step = int(np.ceil(data.shape[0] / 2**24))
+ data = data[::step, :]
+ transform = Affine2D().scale(1, step) + transform
+ # decide if we need to apply anti-aliasing if the data is upsampled:
+ # compare the number of displayed pixels to the number of
+ # the data pixels.
+ interpolation = image_obj.get_interpolation()
+ if interpolation in ['antialiased', 'auto']:
+ # don't antialias if upsampling by an integer number or
+ # if zooming in more than a factor of 3
+ pos = np.array([[0, 0], [data.shape[1], data.shape[0]]])
+ disp = transform.transform(pos)
+ dispx = np.abs(np.diff(disp[:, 0]))
+ dispy = np.abs(np.diff(disp[:, 1]))
+ if ((dispx > 3 * data.shape[1] or
+ dispx == data.shape[1] or
+ dispx == 2 * data.shape[1]) and
+ (dispy > 3 * data.shape[0] or
+ dispy == data.shape[0] or
+ dispy == 2 * data.shape[0])):
+ interpolation = 'nearest'
+ else:
+ interpolation = 'hanning'
+ out = np.zeros(out_shape + data.shape[2:], data.dtype) # 2D->2D, 3D->3D.
+ if resample is None:
+ resample = image_obj.get_resample()
+ _image.resample(data, out, transform,
+ _interpd_[interpolation],
+ resample,
+ alpha,
+ image_obj.get_filternorm(),
+ image_obj.get_filterrad())
+ return out
+
+
+def _rgb_to_rgba(A):
+ """
+ Convert an RGB image to RGBA, as required by the image resample C++
+ extension.
+ """
+ rgba = np.zeros((A.shape[0], A.shape[1], 4), dtype=A.dtype)
+ rgba[:, :, :3] = A
+ if rgba.dtype == np.uint8:
+ rgba[:, :, 3] = 255
+ else:
+ rgba[:, :, 3] = 1.0
+ return rgba
+
+
+class _ImageBase(mcolorizer.ColorizingArtist):
+ """
+ Base class for images.
+
+ interpolation and cmap default to their rc settings
+
+ cmap is a colors.Colormap instance
+ norm is a colors.Normalize instance to map luminance to 0-1
+
+ extent is data axes (left, right, bottom, top) for making image plots
+ registered with data plots. Default is to label the pixel
+ centers with the zero-based row and column indices.
+
+ Additional kwargs are matplotlib.artist properties
+ """
+ zorder = 0
+
+ def __init__(self, ax,
+ cmap=None,
+ norm=None,
+ colorizer=None,
+ interpolation=None,
+ origin=None,
+ filternorm=True,
+ filterrad=4.0,
+ resample=False,
+ *,
+ interpolation_stage=None,
+ **kwargs
+ ):
+ super().__init__(self._get_colorizer(cmap, norm, colorizer))
+ if origin is None:
+ origin = mpl.rcParams['image.origin']
+ _api.check_in_list(["upper", "lower"], origin=origin)
+ self.origin = origin
+ self.set_filternorm(filternorm)
+ self.set_filterrad(filterrad)
+ self.set_interpolation(interpolation)
+ self.set_interpolation_stage(interpolation_stage)
+ self.set_resample(resample)
+ self.axes = ax
+
+ self._imcache = None
+
+ self._internal_update(kwargs)
+
+ def __str__(self):
+ try:
+ shape = self.get_shape()
+ return f"{type(self).__name__}(shape={shape!r})"
+ except RuntimeError:
+ return type(self).__name__
+
+ def __getstate__(self):
+ # Save some space on the pickle by not saving the cache.
+ return {**super().__getstate__(), "_imcache": None}
+
+ def get_size(self):
+ """Return the size of the image as tuple (numrows, numcols)."""
+ return self.get_shape()[:2]
+
+ def get_shape(self):
+ """
+ Return the shape of the image as tuple (numrows, numcols, channels).
+ """
+ if self._A is None:
+ raise RuntimeError('You must first set the image array')
+
+ return self._A.shape
+
+ def set_alpha(self, alpha):
+ """
+ Set the alpha value used for blending - not supported on all backends.
+
+ Parameters
+ ----------
+ alpha : float or 2D array-like or None
+ """
+ martist.Artist._set_alpha_for_array(self, alpha)
+ if np.ndim(alpha) not in (0, 2):
+ raise TypeError('alpha must be a float, two-dimensional '
+ 'array, or None')
+ self._imcache = None
+
+ def _get_scalar_alpha(self):
+ """
+ Get a scalar alpha value to be applied to the artist as a whole.
+
+ If the alpha value is a matrix, the method returns 1.0 because pixels
+ have individual alpha values (see `~._ImageBase._make_image` for
+ details). If the alpha value is a scalar, the method returns said value
+ to be applied to the artist as a whole because pixels do not have
+ individual alpha values.
+ """
+ return 1.0 if self._alpha is None or np.ndim(self._alpha) > 0 \
+ else self._alpha
+
+ def changed(self):
+ """
+ Call this whenever the mappable is changed so observers can update.
+ """
+ self._imcache = None
+ super().changed()
+
+ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
+ unsampled=False, round_to_pixel_border=True):
+ """
+ Normalize, rescale, and colormap the image *A* from the given *in_bbox*
+ (in data space), to the given *out_bbox* (in pixel space) clipped to
+ the given *clip_bbox* (also in pixel space), and magnified by the
+ *magnification* factor.
+
+ Parameters
+ ----------
+ A : ndarray
+
+ - a (M, N) array interpreted as scalar (greyscale) image,
+ with one of the dtypes `~numpy.float32`, `~numpy.float64`,
+ `~numpy.float128`, `~numpy.uint16` or `~numpy.uint8`.
+ - (M, N, 4) RGBA image with a dtype of `~numpy.float32`,
+ `~numpy.float64`, `~numpy.float128`, or `~numpy.uint8`.
+
+ in_bbox : `~matplotlib.transforms.Bbox`
+
+ out_bbox : `~matplotlib.transforms.Bbox`
+
+ clip_bbox : `~matplotlib.transforms.Bbox`
+
+ magnification : float, default: 1
+
+ unsampled : bool, default: False
+ If True, the image will not be scaled, but an appropriate
+ affine transformation will be returned instead.
+
+ round_to_pixel_border : bool, default: True
+ If True, the output image size will be rounded to the nearest pixel
+ boundary. This makes the images align correctly with the Axes.
+ It should not be used if exact scaling is needed, such as for
+ `.FigureImage`.
+
+ Returns
+ -------
+ image : (M, N, 4) `numpy.uint8` array
+ The RGBA image, resampled unless *unsampled* is True.
+ x, y : float
+ The upper left corner where the image should be drawn, in pixel
+ space.
+ trans : `~matplotlib.transforms.Affine2D`
+ The affine transformation from image to pixel space.
+ """
+ if A is None:
+ raise RuntimeError('You must first set the image '
+ 'array or the image attribute')
+ if A.size == 0:
+ raise RuntimeError("_make_image must get a non-empty image. "
+ "Your Artist's draw method must filter before "
+ "this method is called.")
+
+ clipped_bbox = Bbox.intersection(out_bbox, clip_bbox)
+
+ if clipped_bbox is None:
+ return None, 0, 0, None
+
+ out_width_base = clipped_bbox.width * magnification
+ out_height_base = clipped_bbox.height * magnification
+
+ if out_width_base == 0 or out_height_base == 0:
+ return None, 0, 0, None
+
+ if self.origin == 'upper':
+ # Flip the input image using a transform. This avoids the
+ # problem with flipping the array, which results in a copy
+ # when it is converted to contiguous in the C wrapper
+ t0 = Affine2D().translate(0, -A.shape[0]).scale(1, -1)
+ else:
+ t0 = IdentityTransform()
+
+ t0 += (
+ Affine2D()
+ .scale(
+ in_bbox.width / A.shape[1],
+ in_bbox.height / A.shape[0])
+ .translate(in_bbox.x0, in_bbox.y0)
+ + self.get_transform())
+
+ t = (t0
+ + (Affine2D()
+ .translate(-clipped_bbox.x0, -clipped_bbox.y0)
+ .scale(magnification)))
+
+ # So that the image is aligned with the edge of the Axes, we want to
+ # round up the output width to the next integer. This also means
+ # scaling the transform slightly to account for the extra subpixel.
+ if ((not unsampled) and t.is_affine and round_to_pixel_border and
+ (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)):
+ out_width = math.ceil(out_width_base)
+ out_height = math.ceil(out_height_base)
+ extra_width = (out_width - out_width_base) / out_width_base
+ extra_height = (out_height - out_height_base) / out_height_base
+ t += Affine2D().scale(1.0 + extra_width, 1.0 + extra_height)
+ else:
+ out_width = int(out_width_base)
+ out_height = int(out_height_base)
+ out_shape = (out_height, out_width)
+
+ if not unsampled:
+ if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in (3, 4)):
+ raise ValueError(f"Invalid shape {A.shape} for image data")
+
+ # if antialiased, this needs to change as window sizes
+ # change:
+ interpolation_stage = self._interpolation_stage
+ if interpolation_stage in ['antialiased', 'auto']:
+ pos = np.array([[0, 0], [A.shape[1], A.shape[0]]])
+ disp = t.transform(pos)
+ dispx = np.abs(np.diff(disp[:, 0])) / A.shape[1]
+ dispy = np.abs(np.diff(disp[:, 1])) / A.shape[0]
+ if (dispx < 3) or (dispy < 3):
+ interpolation_stage = 'rgba'
+ else:
+ interpolation_stage = 'data'
+
+ if A.ndim == 2 and interpolation_stage == 'data':
+ # if we are a 2D array, then we are running through the
+ # norm + colormap transformation. However, in general the
+ # input data is not going to match the size on the screen so we
+ # have to resample to the correct number of pixels
+
+ if A.dtype.kind == 'f': # Float dtype: scale to same dtype.
+ scaled_dtype = np.dtype("f8" if A.dtype.itemsize > 4 else "f4")
+ if scaled_dtype.itemsize < A.dtype.itemsize:
+ _api.warn_external(f"Casting input data from {A.dtype}"
+ f" to {scaled_dtype} for imshow.")
+ else: # Int dtype, likely.
+ # TODO slice input array first
+ # Scale to appropriately sized float: use float32 if the
+ # dynamic range is small, to limit the memory footprint.
+ da = A.max().astype("f8") - A.min().astype("f8")
+ scaled_dtype = "f8" if da > 1e8 else "f4"
+
+ # resample the input data to the correct resolution and shape
+ A_resampled = _resample(self, A.astype(scaled_dtype), out_shape, t)
+
+ # if using NoNorm, cast back to the original datatype
+ if isinstance(self.norm, mcolors.NoNorm):
+ A_resampled = A_resampled.astype(A.dtype)
+
+ # Compute out_mask (what screen pixels include "bad" data
+ # pixels) and out_alpha (to what extent screen pixels are
+ # covered by data pixels: 0 outside the data extent, 1 inside
+ # (even for bad data), and intermediate values at the edges).
+ mask = (np.where(A.mask, np.float32(np.nan), np.float32(1))
+ if A.mask.shape == A.shape # nontrivial mask
+ else np.ones_like(A, np.float32))
+ # we always have to interpolate the mask to account for
+ # non-affine transformations
+ out_alpha = _resample(self, mask, out_shape, t, resample=True)
+ del mask # Make sure we don't use mask anymore!
+ out_mask = np.isnan(out_alpha)
+ out_alpha[out_mask] = 1
+ # Apply the pixel-by-pixel alpha values if present
+ alpha = self.get_alpha()
+ if alpha is not None and np.ndim(alpha) > 0:
+ out_alpha *= _resample(self, alpha, out_shape, t, resample=True)
+ # mask and run through the norm
+ resampled_masked = np.ma.masked_array(A_resampled, out_mask)
+ output = self.norm(resampled_masked)
+ else:
+ if A.ndim == 2: # interpolation_stage = 'rgba'
+ self.norm.autoscale_None(A)
+ A = self.to_rgba(A)
+ alpha = self.get_alpha()
+ if alpha is None: # alpha parameter not specified
+ if A.shape[2] == 3: # image has no alpha channel
+ output_alpha = 255 if A.dtype == np.uint8 else 1.0
+ else:
+ output_alpha = _resample( # resample alpha channel
+ self, A[..., 3], out_shape, t)
+ output = _resample( # resample rgb channels
+ self, _rgb_to_rgba(A[..., :3]), out_shape, t)
+ elif np.ndim(alpha) > 0: # Array alpha
+ # user-specified array alpha overrides the existing alpha channel
+ output_alpha = _resample(self, alpha, out_shape, t)
+ output = _resample(
+ self, _rgb_to_rgba(A[..., :3]), out_shape, t)
+ else: # Scalar alpha
+ if A.shape[2] == 3: # broadcast scalar alpha
+ output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha
+ else: # or apply scalar alpha to existing alpha channel
+ output_alpha = _resample(self, A[..., 3], out_shape, t) * alpha
+ output = _resample(
+ self, _rgb_to_rgba(A[..., :3]), out_shape, t)
+ output[..., 3] = output_alpha # recombine rgb and alpha
+
+ # output is now either a 2D array of normed (int or float) data
+ # or an RGBA array of re-sampled input
+ output = self.to_rgba(output, bytes=True, norm=False)
+ # output is now a correctly sized RGBA array of uint8
+
+ # Apply alpha *after* if the input was greyscale without a mask
+ if A.ndim == 2:
+ alpha = self._get_scalar_alpha()
+ alpha_channel = output[:, :, 3]
+ alpha_channel[:] = ( # Assignment will cast to uint8.
+ alpha_channel.astype(np.float32) * out_alpha * alpha)
+
+ else:
+ if self._imcache is None:
+ self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2))
+ output = self._imcache
+
+ # Subset the input image to only the part that will be displayed.
+ subset = TransformedBbox(clip_bbox, t0.inverted()).frozen()
+ output = output[
+ int(max(subset.ymin, 0)):
+ int(min(subset.ymax + 1, output.shape[0])),
+ int(max(subset.xmin, 0)):
+ int(min(subset.xmax + 1, output.shape[1]))]
+
+ t = Affine2D().translate(
+ int(max(subset.xmin, 0)), int(max(subset.ymin, 0))) + t
+
+ return output, clipped_bbox.x0, clipped_bbox.y0, t
+
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
+ """
+ Normalize, rescale, and colormap this image's data for rendering using
+ *renderer*, with the given *magnification*.
+
+ If *unsampled* is True, the image will not be scaled, but an
+ appropriate affine transformation will be returned instead.
+
+ Returns
+ -------
+ image : (M, N, 4) `numpy.uint8` array
+ The RGBA image, resampled unless *unsampled* is True.
+ x, y : float
+ The upper left corner where the image should be drawn, in pixel
+ space.
+ trans : `~matplotlib.transforms.Affine2D`
+ The affine transformation from image to pixel space.
+ """
+ raise NotImplementedError('The make_image method must be overridden')
+
+ def _check_unsampled_image(self):
+ """
+ Return whether the image is better to be drawn unsampled.
+
+ The derived class needs to override it.
+ """
+ return False
+
+ @martist.allow_rasterization
+ def draw(self, renderer):
+ # if not visible, declare victory and return
+ if not self.get_visible():
+ self.stale = False
+ return
+ # for empty images, there is nothing to draw!
+ if self.get_array().size == 0:
+ self.stale = False
+ return
+ # actually render the image.
+ gc = renderer.new_gc()
+ self._set_gc_clip(gc)
+ gc.set_alpha(self._get_scalar_alpha())
+ gc.set_url(self.get_url())
+ gc.set_gid(self.get_gid())
+ if (renderer.option_scale_image() # Renderer supports transform kwarg.
+ and self._check_unsampled_image()
+ and self.get_transform().is_affine):
+ im, l, b, trans = self.make_image(renderer, unsampled=True)
+ if im is not None:
+ trans = Affine2D().scale(im.shape[1], im.shape[0]) + trans
+ renderer.draw_image(gc, l, b, im, trans)
+ else:
+ im, l, b, trans = self.make_image(
+ renderer, renderer.get_image_magnification())
+ if im is not None:
+ renderer.draw_image(gc, l, b, im)
+ gc.restore()
+ self.stale = False
+
+ def contains(self, mouseevent):
+ """Test whether the mouse event occurred within the image."""
+ if (self._different_canvas(mouseevent)
+ # This doesn't work for figimage.
+ or not self.axes.contains(mouseevent)[0]):
+ return False, {}
+ # TODO: make sure this is consistent with patch and patch
+ # collection on nonlinear transformed coordinates.
+ # TODO: consider returning image coordinates (shouldn't
+ # be too difficult given that the image is rectilinear
+ trans = self.get_transform().inverted()
+ x, y = trans.transform([mouseevent.x, mouseevent.y])
+ xmin, xmax, ymin, ymax = self.get_extent()
+ # This checks xmin <= x <= xmax *or* xmax <= x <= xmin.
+ inside = (x is not None and (x - xmin) * (x - xmax) <= 0
+ and y is not None and (y - ymin) * (y - ymax) <= 0)
+ return inside, {}
+
+ def write_png(self, fname):
+ """Write the image to png file *fname*."""
+ im = self.to_rgba(self._A[::-1] if self.origin == 'lower' else self._A,
+ bytes=True, norm=True)
+ PIL.Image.fromarray(im).save(fname, format="png")
+
+ @staticmethod
+ def _normalize_image_array(A):
+ """
+ Check validity of image-like input *A* and normalize it to a format suitable for
+ Image subclasses.
+ """
+ A = cbook.safe_masked_invalid(A, copy=True)
+ if A.dtype != np.uint8 and not np.can_cast(A.dtype, float, "same_kind"):
+ raise TypeError(f"Image data of dtype {A.dtype} cannot be "
+ f"converted to float")
+ if A.ndim == 3 and A.shape[-1] == 1:
+ A = A.squeeze(-1) # If just (M, N, 1), assume scalar and apply colormap.
+ if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in [3, 4]):
+ raise TypeError(f"Invalid shape {A.shape} for image data")
+ if A.ndim == 3:
+ # If the input data has values outside the valid range (after
+ # normalisation), we issue a warning and then clip X to the bounds
+ # - otherwise casting wraps extreme values, hiding outliers and
+ # making reliable interpretation impossible.
+ high = 255 if np.issubdtype(A.dtype, np.integer) else 1
+ if A.min() < 0 or high < A.max():
+ _log.warning(
+ 'Clipping input data to the valid range for imshow with '
+ 'RGB data ([0..1] for floats or [0..255] for integers). '
+ 'Got range [%s..%s].',
+ A.min(), A.max()
+ )
+ A = np.clip(A, 0, high)
+ # Cast unsupported integer types to uint8
+ if A.dtype != np.uint8 and np.issubdtype(A.dtype, np.integer):
+ A = A.astype(np.uint8)
+ return A
+
+ def set_data(self, A):
+ """
+ Set the image array.
+
+ Note that this function does *not* update the normalization used.
+
+ Parameters
+ ----------
+ A : array-like or `PIL.Image.Image`
+ """
+ if isinstance(A, PIL.Image.Image):
+ A = pil_to_array(A) # Needed e.g. to apply png palette.
+ self._A = self._normalize_image_array(A)
+ self._imcache = None
+ self.stale = True
+
+ def set_array(self, A):
+ """
+ Retained for backwards compatibility - use set_data instead.
+
+ Parameters
+ ----------
+ A : array-like
+ """
+ # This also needs to be here to override the inherited
+ # cm.ScalarMappable.set_array method so it is not invoked by mistake.
+ self.set_data(A)
+
+ def get_interpolation(self):
+ """
+ Return the interpolation method the image uses when resizing.
+
+ One of 'auto', 'antialiased', 'nearest', 'bilinear', 'bicubic',
+ 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser',
+ 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos',
+ or 'none'.
+ """
+ return self._interpolation
+
+ def set_interpolation(self, s):
+ """
+ Set the interpolation method the image uses when resizing.
+
+ If None, use :rc:`image.interpolation`. If 'none', the image is
+ shown as is without interpolating. 'none' is only supported in
+ agg, ps and pdf backends and will fall back to 'nearest' mode
+ for other backends.
+
+ Parameters
+ ----------
+ s : {'auto', 'nearest', 'bilinear', 'bicubic', 'spline16', \
+'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', \
+'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'none'} or None
+ """
+ s = mpl._val_or_rc(s, 'image.interpolation').lower()
+ _api.check_in_list(interpolations_names, interpolation=s)
+ self._interpolation = s
+ self.stale = True
+
+ def get_interpolation_stage(self):
+ """
+ Return when interpolation happens during the transform to RGBA.
+
+ One of 'data', 'rgba', 'auto'.
+ """
+ return self._interpolation_stage
+
+ def set_interpolation_stage(self, s):
+ """
+ Set when interpolation happens during the transform to RGBA.
+
+ Parameters
+ ----------
+ s : {'data', 'rgba', 'auto'} or None
+ Whether to apply up/downsampling interpolation in data or RGBA
+ space. If None, use :rc:`image.interpolation_stage`.
+ If 'auto' we will check upsampling rate and if less
+ than 3 then use 'rgba', otherwise use 'data'.
+ """
+ s = mpl._val_or_rc(s, 'image.interpolation_stage')
+ _api.check_in_list(['data', 'rgba', 'auto'], s=s)
+ self._interpolation_stage = s
+ self.stale = True
+
+ def can_composite(self):
+ """Return whether the image can be composited with its neighbors."""
+ trans = self.get_transform()
+ return (
+ self._interpolation != 'none' and
+ trans.is_affine and
+ trans.is_separable)
+
+ def set_resample(self, v):
+ """
+ Set whether image resampling is used.
+
+ Parameters
+ ----------
+ v : bool or None
+ If None, use :rc:`image.resample`.
+ """
+ v = mpl._val_or_rc(v, 'image.resample')
+ self._resample = v
+ self.stale = True
+
+ def get_resample(self):
+ """Return whether image resampling is used."""
+ return self._resample
+
+ def set_filternorm(self, filternorm):
+ """
+ Set whether the resize filter normalizes the weights.
+
+ See help for `~.Axes.imshow`.
+
+ Parameters
+ ----------
+ filternorm : bool
+ """
+ self._filternorm = bool(filternorm)
+ self.stale = True
+
+ def get_filternorm(self):
+ """Return whether the resize filter normalizes the weights."""
+ return self._filternorm
+
+ def set_filterrad(self, filterrad):
+ """
+ Set the resize filter radius only applicable to some
+ interpolation schemes -- see help for imshow
+
+ Parameters
+ ----------
+ filterrad : positive float
+ """
+ r = float(filterrad)
+ if r <= 0:
+ raise ValueError("The filter radius must be a positive number")
+ self._filterrad = r
+ self.stale = True
+
+ def get_filterrad(self):
+ """Return the filterrad setting."""
+ return self._filterrad
+
+
+class AxesImage(_ImageBase):
+ """
+ An image with pixels on a regular grid, attached to an Axes.
+
+ Parameters
+ ----------
+ ax : `~matplotlib.axes.Axes`
+ The Axes the image will belong to.
+ cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
+ The Colormap instance or registered colormap name used to map scalar
+ data to colors.
+ norm : str or `~matplotlib.colors.Normalize`
+ Maps luminance to 0-1.
+ interpolation : str, default: :rc:`image.interpolation`
+ Supported values are 'none', 'auto', 'nearest', 'bilinear',
+ 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',
+ 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell',
+ 'sinc', 'lanczos', 'blackman'.
+ interpolation_stage : {'data', 'rgba'}, default: 'data'
+ If 'data', interpolation
+ is carried out on the data provided by the user. If 'rgba', the
+ interpolation is carried out after the colormapping has been
+ applied (visual interpolation).
+ origin : {'upper', 'lower'}, default: :rc:`image.origin`
+ Place the [0, 0] index of the array in the upper left or lower left
+ corner of the Axes. The convention 'upper' is typically used for
+ matrices and images.
+ extent : tuple, optional
+ The data axes (left, right, bottom, top) for making image plots
+ registered with data plots. Default is to label the pixel
+ centers with the zero-based row and column indices.
+ filternorm : bool, default: True
+ A parameter for the antigrain image resize filter
+ (see the antigrain documentation).
+ If filternorm is set, the filter normalizes integer values and corrects
+ the rounding errors. It doesn't do anything with the source floating
+ point values, it corrects only integers according to the rule of 1.0
+ which means that any sum of pixel weights must be equal to 1.0. So,
+ the filter function must produce a graph of the proper shape.
+ filterrad : float > 0, default: 4
+ The filter radius for filters that have a radius parameter, i.e. when
+ interpolation is one of: 'sinc', 'lanczos' or 'blackman'.
+ resample : bool, default: False
+ When True, use a full resampling method. When False, only resample when
+ the output image is larger than the input image.
+ **kwargs : `~matplotlib.artist.Artist` properties
+ """
+
+ def __init__(self, ax,
+ *,
+ cmap=None,
+ norm=None,
+ colorizer=None,
+ interpolation=None,
+ origin=None,
+ extent=None,
+ filternorm=True,
+ filterrad=4.0,
+ resample=False,
+ interpolation_stage=None,
+ **kwargs
+ ):
+
+ self._extent = extent
+
+ super().__init__(
+ ax,
+ cmap=cmap,
+ norm=norm,
+ colorizer=colorizer,
+ interpolation=interpolation,
+ origin=origin,
+ filternorm=filternorm,
+ filterrad=filterrad,
+ resample=resample,
+ interpolation_stage=interpolation_stage,
+ **kwargs
+ )
+
+ def get_window_extent(self, renderer=None):
+ x0, x1, y0, y1 = self._extent
+ bbox = Bbox.from_extents([x0, y0, x1, y1])
+ return bbox.transformed(self.get_transform())
+
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
+ # docstring inherited
+ trans = self.get_transform()
+ # image is created in the canvas coordinate.
+ x1, x2, y1, y2 = self.get_extent()
+ bbox = Bbox(np.array([[x1, y1], [x2, y2]]))
+ transformed_bbox = TransformedBbox(bbox, trans)
+ clip = ((self.get_clip_box() or self.axes.bbox) if self.get_clip_on()
+ else self.get_figure(root=True).bbox)
+ return self._make_image(self._A, bbox, transformed_bbox, clip,
+ magnification, unsampled=unsampled)
+
+ def _check_unsampled_image(self):
+ """Return whether the image would be better drawn unsampled."""
+ return self.get_interpolation() == "none"
+
+ def set_extent(self, extent, **kwargs):
+ """
+ Set the image extent.
+
+ Parameters
+ ----------
+ extent : 4-tuple of float
+ The position and size of the image as tuple
+ ``(left, right, bottom, top)`` in data coordinates.
+ **kwargs
+ Other parameters from which unit info (i.e., the *xunits*,
+ *yunits*, *zunits* (for 3D Axes), *runits* and *thetaunits* (for
+ polar Axes) entries are applied, if present.
+
+ Notes
+ -----
+ This updates `.Axes.dataLim`, and, if autoscaling, sets `.Axes.viewLim`
+ to tightly fit the image, regardless of `~.Axes.dataLim`. Autoscaling
+ state is not changed, so a subsequent call to `.Axes.autoscale_view`
+ will redo the autoscaling in accord with `~.Axes.dataLim`.
+ """
+ (xmin, xmax), (ymin, ymax) = self.axes._process_unit_info(
+ [("x", [extent[0], extent[1]]),
+ ("y", [extent[2], extent[3]])],
+ kwargs)
+ if kwargs:
+ raise _api.kwarg_error("set_extent", kwargs)
+ xmin = self.axes._validate_converted_limits(
+ xmin, self.convert_xunits)
+ xmax = self.axes._validate_converted_limits(
+ xmax, self.convert_xunits)
+ ymin = self.axes._validate_converted_limits(
+ ymin, self.convert_yunits)
+ ymax = self.axes._validate_converted_limits(
+ ymax, self.convert_yunits)
+ extent = [xmin, xmax, ymin, ymax]
+
+ self._extent = extent
+ corners = (xmin, ymin), (xmax, ymax)
+ self.axes.update_datalim(corners)
+ self.sticky_edges.x[:] = [xmin, xmax]
+ self.sticky_edges.y[:] = [ymin, ymax]
+ if self.axes.get_autoscalex_on():
+ self.axes.set_xlim((xmin, xmax), auto=None)
+ if self.axes.get_autoscaley_on():
+ self.axes.set_ylim((ymin, ymax), auto=None)
+ self.stale = True
+
+ def get_extent(self):
+ """Return the image extent as tuple (left, right, bottom, top)."""
+ if self._extent is not None:
+ return self._extent
+ else:
+ sz = self.get_size()
+ numrows, numcols = sz
+ if self.origin == 'upper':
+ return (-0.5, numcols-0.5, numrows-0.5, -0.5)
+ else:
+ return (-0.5, numcols-0.5, -0.5, numrows-0.5)
+
+ def get_cursor_data(self, event):
+ """
+ Return the image value at the event position or *None* if the event is
+ outside the image.
+
+ See Also
+ --------
+ matplotlib.artist.Artist.get_cursor_data
+ """
+ xmin, xmax, ymin, ymax = self.get_extent()
+ if self.origin == 'upper':
+ ymin, ymax = ymax, ymin
+ arr = self.get_array()
+ data_extent = Bbox([[xmin, ymin], [xmax, ymax]])
+ array_extent = Bbox([[0, 0], [arr.shape[1], arr.shape[0]]])
+ trans = self.get_transform().inverted()
+ trans += BboxTransform(boxin=data_extent, boxout=array_extent)
+ point = trans.transform([event.x, event.y])
+ if any(np.isnan(point)):
+ return None
+ j, i = point.astype(int)
+ # Clip the coordinates at array bounds
+ if not (0 <= i < arr.shape[0]) or not (0 <= j < arr.shape[1]):
+ return None
+ else:
+ return arr[i, j]
+
+
+class NonUniformImage(AxesImage):
+ """
+ An image with pixels on a rectilinear grid.
+
+ In contrast to `.AxesImage`, where pixels are on a regular grid,
+ NonUniformImage allows rows and columns with individual heights / widths.
+
+ See also :doc:`/gallery/images_contours_and_fields/image_nonuniform`.
+ """
+
+ def __init__(self, ax, *, interpolation='nearest', **kwargs):
+ """
+ Parameters
+ ----------
+ ax : `~matplotlib.axes.Axes`
+ The Axes the image will belong to.
+ interpolation : {'nearest', 'bilinear'}, default: 'nearest'
+ The interpolation scheme used in the resampling.
+ **kwargs
+ All other keyword arguments are identical to those of `.AxesImage`.
+ """
+ super().__init__(ax, **kwargs)
+ self.set_interpolation(interpolation)
+
+ def _check_unsampled_image(self):
+ """Return False. Do not use unsampled image."""
+ return False
+
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
+ # docstring inherited
+ if self._A is None:
+ raise RuntimeError('You must first set the image array')
+ if unsampled:
+ raise ValueError('unsampled not supported on NonUniformImage')
+ A = self._A
+ if A.ndim == 2:
+ if A.dtype != np.uint8:
+ A = self.to_rgba(A, bytes=True)
+ else:
+ A = np.repeat(A[:, :, np.newaxis], 4, 2)
+ A[:, :, 3] = 255
+ else:
+ if A.dtype != np.uint8:
+ A = (255*A).astype(np.uint8)
+ if A.shape[2] == 3:
+ B = np.zeros(tuple([*A.shape[0:2], 4]), np.uint8)
+ B[:, :, 0:3] = A
+ B[:, :, 3] = 255
+ A = B
+ l, b, r, t = self.axes.bbox.extents
+ width = int(((round(r) + 0.5) - (round(l) - 0.5)) * magnification)
+ height = int(((round(t) + 0.5) - (round(b) - 0.5)) * magnification)
+
+ invertedTransform = self.axes.transData.inverted()
+ x_pix = invertedTransform.transform(
+ [(x, b) for x in np.linspace(l, r, width)])[:, 0]
+ y_pix = invertedTransform.transform(
+ [(l, y) for y in np.linspace(b, t, height)])[:, 1]
+
+ if self._interpolation == "nearest":
+ x_mid = (self._Ax[:-1] + self._Ax[1:]) / 2
+ y_mid = (self._Ay[:-1] + self._Ay[1:]) / 2
+ x_int = x_mid.searchsorted(x_pix)
+ y_int = y_mid.searchsorted(y_pix)
+ # The following is equal to `A[y_int[:, None], x_int[None, :]]`,
+ # but many times faster. Both casting to uint32 (to have an
+ # effectively 1D array) and manual index flattening matter.
+ im = (
+ np.ascontiguousarray(A).view(np.uint32).ravel()[
+ np.add.outer(y_int * A.shape[1], x_int)]
+ .view(np.uint8).reshape((height, width, 4)))
+ else: # self._interpolation == "bilinear"
+ # Use np.interp to compute x_int/x_float has similar speed.
+ x_int = np.clip(
+ self._Ax.searchsorted(x_pix) - 1, 0, len(self._Ax) - 2)
+ y_int = np.clip(
+ self._Ay.searchsorted(y_pix) - 1, 0, len(self._Ay) - 2)
+ idx_int = np.add.outer(y_int * A.shape[1], x_int)
+ x_frac = np.clip(
+ np.divide(x_pix - self._Ax[x_int], np.diff(self._Ax)[x_int],
+ dtype=np.float32), # Downcasting helps with speed.
+ 0, 1)
+ y_frac = np.clip(
+ np.divide(y_pix - self._Ay[y_int], np.diff(self._Ay)[y_int],
+ dtype=np.float32),
+ 0, 1)
+ f00 = np.outer(1 - y_frac, 1 - x_frac)
+ f10 = np.outer(y_frac, 1 - x_frac)
+ f01 = np.outer(1 - y_frac, x_frac)
+ f11 = np.outer(y_frac, x_frac)
+ im = np.empty((height, width, 4), np.uint8)
+ for chan in range(4):
+ ac = A[:, :, chan].reshape(-1) # reshape(-1) avoids a copy.
+ # Shifting the buffer start (`ac[offset:]`) avoids an array
+ # addition (`ac[idx_int + offset]`).
+ buf = f00 * ac[idx_int]
+ buf += f10 * ac[A.shape[1]:][idx_int]
+ buf += f01 * ac[1:][idx_int]
+ buf += f11 * ac[A.shape[1] + 1:][idx_int]
+ im[:, :, chan] = buf # Implicitly casts to uint8.
+ return im, l, b, IdentityTransform()
+
+ def set_data(self, x, y, A):
+ """
+ Set the grid for the pixel centers, and the pixel values.
+
+ Parameters
+ ----------
+ x, y : 1D array-like
+ Monotonic arrays of shapes (N,) and (M,), respectively, specifying
+ pixel centers.
+ A : array-like
+ (M, N) `~numpy.ndarray` or masked array of values to be
+ colormapped, or (M, N, 3) RGB array, or (M, N, 4) RGBA array.
+ """
+ A = self._normalize_image_array(A)
+ x = np.array(x, np.float32)
+ y = np.array(y, np.float32)
+ if not (x.ndim == y.ndim == 1 and A.shape[:2] == y.shape + x.shape):
+ raise TypeError("Axes don't match array shape")
+ self._A = A
+ self._Ax = x
+ self._Ay = y
+ self._imcache = None
+ self.stale = True
+
+ def set_array(self, *args):
+ raise NotImplementedError('Method not supported')
+
+ def set_interpolation(self, s):
+ """
+ Parameters
+ ----------
+ s : {'nearest', 'bilinear'} or None
+ If None, use :rc:`image.interpolation`.
+ """
+ if s is not None and s not in ('nearest', 'bilinear'):
+ raise NotImplementedError('Only nearest neighbor and '
+ 'bilinear interpolations are supported')
+ super().set_interpolation(s)
+
+ def get_extent(self):
+ if self._A is None:
+ raise RuntimeError('Must set data first')
+ return self._Ax[0], self._Ax[-1], self._Ay[0], self._Ay[-1]
+
+ def set_filternorm(self, filternorm):
+ pass
+
+ def set_filterrad(self, filterrad):
+ pass
+
+ def set_norm(self, norm):
+ if self._A is not None:
+ raise RuntimeError('Cannot change colors after loading data')
+ super().set_norm(norm)
+
+ def set_cmap(self, cmap):
+ if self._A is not None:
+ raise RuntimeError('Cannot change colors after loading data')
+ super().set_cmap(cmap)
+
+ def get_cursor_data(self, event):
+ # docstring inherited
+ x, y = event.xdata, event.ydata
+ if (x < self._Ax[0] or x > self._Ax[-1] or
+ y < self._Ay[0] or y > self._Ay[-1]):
+ return None
+ j = np.searchsorted(self._Ax, x) - 1
+ i = np.searchsorted(self._Ay, y) - 1
+ return self._A[i, j]
+
+
+class PcolorImage(AxesImage):
+ """
+ Make a pcolor-style plot with an irregular rectangular grid.
+
+ This uses a variation of the original irregular image code,
+ and it is used by pcolorfast for the corresponding grid type.
+ """
+
+ def __init__(self, ax,
+ x=None,
+ y=None,
+ A=None,
+ *,
+ cmap=None,
+ norm=None,
+ colorizer=None,
+ **kwargs
+ ):
+ """
+ Parameters
+ ----------
+ ax : `~matplotlib.axes.Axes`
+ The Axes the image will belong to.
+ x, y : 1D array-like, optional
+ Monotonic arrays of length N+1 and M+1, respectively, specifying
+ rectangle boundaries. If not given, will default to
+ ``range(N + 1)`` and ``range(M + 1)``, respectively.
+ A : array-like
+ The data to be color-coded. The interpretation depends on the
+ shape:
+
+ - (M, N) `~numpy.ndarray` or masked array: values to be colormapped
+ - (M, N, 3): RGB array
+ - (M, N, 4): RGBA array
+
+ cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
+ The Colormap instance or registered colormap name used to map
+ scalar data to colors.
+ norm : str or `~matplotlib.colors.Normalize`
+ Maps luminance to 0-1.
+ **kwargs : `~matplotlib.artist.Artist` properties
+ """
+ super().__init__(ax, norm=norm, cmap=cmap, colorizer=colorizer)
+ self._internal_update(kwargs)
+ if A is not None:
+ self.set_data(x, y, A)
+
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
+ # docstring inherited
+ if self._A is None:
+ raise RuntimeError('You must first set the image array')
+ if unsampled:
+ raise ValueError('unsampled not supported on PColorImage')
+
+ if self._imcache is None:
+ A = self.to_rgba(self._A, bytes=True)
+ self._imcache = np.pad(A, [(1, 1), (1, 1), (0, 0)], "constant")
+ padded_A = self._imcache
+ bg = mcolors.to_rgba(self.axes.patch.get_facecolor(), 0)
+ bg = (np.array(bg) * 255).astype(np.uint8)
+ if (padded_A[0, 0] != bg).all():
+ padded_A[[0, -1], :] = padded_A[:, [0, -1]] = bg
+
+ l, b, r, t = self.axes.bbox.extents
+ width = (round(r) + 0.5) - (round(l) - 0.5)
+ height = (round(t) + 0.5) - (round(b) - 0.5)
+ width = round(width * magnification)
+ height = round(height * magnification)
+ vl = self.axes.viewLim
+
+ x_pix = np.linspace(vl.x0, vl.x1, width)
+ y_pix = np.linspace(vl.y0, vl.y1, height)
+ x_int = self._Ax.searchsorted(x_pix)
+ y_int = self._Ay.searchsorted(y_pix)
+ im = ( # See comment in NonUniformImage.make_image re: performance.
+ padded_A.view(np.uint32).ravel()[
+ np.add.outer(y_int * padded_A.shape[1], x_int)]
+ .view(np.uint8).reshape((height, width, 4)))
+ return im, l, b, IdentityTransform()
+
+ def _check_unsampled_image(self):
+ return False
+
+ def set_data(self, x, y, A):
+ """
+ Set the grid for the rectangle boundaries, and the data values.
+
+ Parameters
+ ----------
+ x, y : 1D array-like, optional
+ Monotonic arrays of length N+1 and M+1, respectively, specifying
+ rectangle boundaries. If not given, will default to
+ ``range(N + 1)`` and ``range(M + 1)``, respectively.
+ A : array-like
+ The data to be color-coded. The interpretation depends on the
+ shape:
+
+ - (M, N) `~numpy.ndarray` or masked array: values to be colormapped
+ - (M, N, 3): RGB array
+ - (M, N, 4): RGBA array
+ """
+ A = self._normalize_image_array(A)
+ x = np.arange(0., A.shape[1] + 1) if x is None else np.array(x, float).ravel()
+ y = np.arange(0., A.shape[0] + 1) if y is None else np.array(y, float).ravel()
+ if A.shape[:2] != (y.size - 1, x.size - 1):
+ raise ValueError(
+ "Axes don't match array shape. Got %s, expected %s." %
+ (A.shape[:2], (y.size - 1, x.size - 1)))
+ # For efficient cursor readout, ensure x and y are increasing.
+ if x[-1] < x[0]:
+ x = x[::-1]
+ A = A[:, ::-1]
+ if y[-1] < y[0]:
+ y = y[::-1]
+ A = A[::-1]
+ self._A = A
+ self._Ax = x
+ self._Ay = y
+ self._imcache = None
+ self.stale = True
+
+ def set_array(self, *args):
+ raise NotImplementedError('Method not supported')
+
+ def get_cursor_data(self, event):
+ # docstring inherited
+ x, y = event.xdata, event.ydata
+ if (x < self._Ax[0] or x > self._Ax[-1] or
+ y < self._Ay[0] or y > self._Ay[-1]):
+ return None
+ j = np.searchsorted(self._Ax, x) - 1
+ i = np.searchsorted(self._Ay, y) - 1
+ return self._A[i, j]
+
+
+class FigureImage(_ImageBase):
+ """An image attached to a figure."""
+
+ zorder = 0
+
+ _interpolation = 'nearest'
+
+ def __init__(self, fig,
+ *,
+ cmap=None,
+ norm=None,
+ colorizer=None,
+ offsetx=0,
+ offsety=0,
+ origin=None,
+ **kwargs
+ ):
+ """
+ cmap is a colors.Colormap instance
+ norm is a colors.Normalize instance to map luminance to 0-1
+
+ kwargs are an optional list of Artist keyword args
+ """
+ super().__init__(
+ None,
+ norm=norm,
+ cmap=cmap,
+ colorizer=colorizer,
+ origin=origin
+ )
+ self.set_figure(fig)
+ self.ox = offsetx
+ self.oy = offsety
+ self._internal_update(kwargs)
+ self.magnification = 1.0
+
+ def get_extent(self):
+ """Return the image extent as tuple (left, right, bottom, top)."""
+ numrows, numcols = self.get_size()
+ return (-0.5 + self.ox, numcols-0.5 + self.ox,
+ -0.5 + self.oy, numrows-0.5 + self.oy)
+
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
+ # docstring inherited
+ fig = self.get_figure(root=True)
+ fac = renderer.dpi/fig.dpi
+ # fac here is to account for pdf, eps, svg backends where
+ # figure.dpi is set to 72. This means we need to scale the
+ # image (using magnification) and offset it appropriately.
+ bbox = Bbox([[self.ox/fac, self.oy/fac],
+ [(self.ox/fac + self._A.shape[1]),
+ (self.oy/fac + self._A.shape[0])]])
+ width, height = fig.get_size_inches()
+ width *= renderer.dpi
+ height *= renderer.dpi
+ clip = Bbox([[0, 0], [width, height]])
+ return self._make_image(
+ self._A, bbox, bbox, clip, magnification=magnification / fac,
+ unsampled=unsampled, round_to_pixel_border=False)
+
+ def set_data(self, A):
+ """Set the image array."""
+ super().set_data(A)
+ self.stale = True
+
+
+class BboxImage(_ImageBase):
+ """The Image class whose size is determined by the given bbox."""
+
+ def __init__(self, bbox,
+ *,
+ cmap=None,
+ norm=None,
+ colorizer=None,
+ interpolation=None,
+ origin=None,
+ filternorm=True,
+ filterrad=4.0,
+ resample=False,
+ **kwargs
+ ):
+ """
+ cmap is a colors.Colormap instance
+ norm is a colors.Normalize instance to map luminance to 0-1
+
+ kwargs are an optional list of Artist keyword args
+ """
+ super().__init__(
+ None,
+ cmap=cmap,
+ norm=norm,
+ colorizer=colorizer,
+ interpolation=interpolation,
+ origin=origin,
+ filternorm=filternorm,
+ filterrad=filterrad,
+ resample=resample,
+ **kwargs
+ )
+ self.bbox = bbox
+
+ def get_window_extent(self, renderer=None):
+ if renderer is None:
+ renderer = self.get_figure()._get_renderer()
+
+ if isinstance(self.bbox, BboxBase):
+ return self.bbox
+ elif callable(self.bbox):
+ return self.bbox(renderer)
+ else:
+ raise ValueError("Unknown type of bbox")
+
+ def contains(self, mouseevent):
+ """Test whether the mouse event occurred within the image."""
+ if self._different_canvas(mouseevent) or not self.get_visible():
+ return False, {}
+ x, y = mouseevent.x, mouseevent.y
+ inside = self.get_window_extent().contains(x, y)
+ return inside, {}
+
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
+ # docstring inherited
+ width, height = renderer.get_canvas_width_height()
+ bbox_in = self.get_window_extent(renderer).frozen()
+ bbox_in._points /= [width, height]
+ bbox_out = self.get_window_extent(renderer)
+ clip = Bbox([[0, 0], [width, height]])
+ self._transform = BboxTransformTo(clip)
+ return self._make_image(
+ self._A,
+ bbox_in, bbox_out, clip, magnification, unsampled=unsampled)
+
+
+def imread(fname, format=None):
+ """
+ Read an image from a file into an array.
+
+ .. note::
+
+ This function exists for historical reasons. It is recommended to
+ use `PIL.Image.open` instead for loading images.
+
+ Parameters
+ ----------
+ fname : str or file-like
+ The image file to read: a filename, a URL or a file-like object opened
+ in read-binary mode.
+
+ Passing a URL is deprecated. Please open the URL
+ for reading and pass the result to Pillow, e.g. with
+ ``np.array(PIL.Image.open(urllib.request.urlopen(url)))``.
+ format : str, optional
+ The image file format assumed for reading the data. The image is
+ loaded as a PNG file if *format* is set to "png", if *fname* is a path
+ or opened file with a ".png" extension, or if it is a URL. In all
+ other cases, *format* is ignored and the format is auto-detected by
+ `PIL.Image.open`.
+
+ Returns
+ -------
+ `numpy.array`
+ The image data. The returned array has shape
+
+ - (M, N) for grayscale images.
+ - (M, N, 3) for RGB images.
+ - (M, N, 4) for RGBA images.
+
+ PNG images are returned as float arrays (0-1). All other formats are
+ returned as int arrays, with a bit depth determined by the file's
+ contents.
+ """
+ # hide imports to speed initial import on systems with slow linkers
+ from urllib import parse
+
+ if format is None:
+ if isinstance(fname, str):
+ parsed = parse.urlparse(fname)
+ # If the string is a URL (Windows paths appear as if they have a
+ # length-1 scheme), assume png.
+ if len(parsed.scheme) > 1:
+ ext = 'png'
+ else:
+ ext = Path(fname).suffix.lower()[1:]
+ elif hasattr(fname, 'geturl'): # Returned by urlopen().
+ # We could try to parse the url's path and use the extension, but
+ # returning png is consistent with the block above. Note that this
+ # if clause has to come before checking for fname.name as
+ # urlopen("file:///...") also has a name attribute (with the fixed
+ # value "").
+ ext = 'png'
+ elif hasattr(fname, 'name'):
+ ext = Path(fname.name).suffix.lower()[1:]
+ else:
+ ext = 'png'
+ else:
+ ext = format
+ img_open = (
+ PIL.PngImagePlugin.PngImageFile if ext == 'png' else PIL.Image.open)
+ if isinstance(fname, str) and len(parse.urlparse(fname).scheme) > 1:
+ # Pillow doesn't handle URLs directly.
+ raise ValueError(
+ "Please open the URL for reading and pass the "
+ "result to Pillow, e.g. with "
+ "``np.array(PIL.Image.open(urllib.request.urlopen(url)))``."
+ )
+ with img_open(fname) as image:
+ return (_pil_png_to_float_array(image)
+ if isinstance(image, PIL.PngImagePlugin.PngImageFile) else
+ pil_to_array(image))
+
+
+def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
+ origin=None, dpi=100, *, metadata=None, pil_kwargs=None):
+ """
+ Colormap and save an array as an image file.
+
+ RGB(A) images are passed through. Single channel images will be
+ colormapped according to *cmap* and *norm*.
+
+ .. note::
+
+ If you want to save a single channel image as gray scale please use an
+ image I/O library (such as pillow, tifffile, or imageio) directly.
+
+ Parameters
+ ----------
+ fname : str or path-like or file-like
+ A path or a file-like object to store the image in.
+ If *format* is not set, then the output format is inferred from the
+ extension of *fname*, if any, and from :rc:`savefig.format` otherwise.
+ If *format* is set, it determines the output format.
+ arr : array-like
+ The image data. Accepts NumPy arrays or sequences
+ (e.g., lists or tuples). The shape can be one of
+ MxN (luminance), MxNx3 (RGB) or MxNx4 (RGBA).
+ vmin, vmax : float, optional
+ *vmin* and *vmax* set the color scaling for the image by fixing the
+ values that map to the colormap color limits. If either *vmin*
+ or *vmax* is None, that limit is determined from the *arr*
+ min/max value.
+ cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
+ A Colormap instance or registered colormap name. The colormap
+ maps scalar data to colors. It is ignored for RGB(A) data.
+ format : str, optional
+ The file format, e.g. 'png', 'pdf', 'svg', ... The behavior when this
+ is unset is documented under *fname*.
+ origin : {'upper', 'lower'}, default: :rc:`image.origin`
+ Indicates whether the ``(0, 0)`` index of the array is in the upper
+ left or lower left corner of the Axes.
+ dpi : float
+ The DPI to store in the metadata of the file. This does not affect the
+ resolution of the output image. Depending on file format, this may be
+ rounded to the nearest integer.
+ metadata : dict, optional
+ Metadata in the image file. The supported keys depend on the output
+ format, see the documentation of the respective backends for more
+ information.
+ Currently only supported for "png", "pdf", "ps", "eps", and "svg".
+ pil_kwargs : dict, optional
+ Keyword arguments passed to `PIL.Image.Image.save`. If the 'pnginfo'
+ key is present, it completely overrides *metadata*, including the
+ default 'Software' key.
+ """
+ from matplotlib.figure import Figure
+
+ # Normalizing input (e.g., list or tuples) to NumPy array if needed
+ arr = np.asanyarray(arr)
+
+ if isinstance(fname, os.PathLike):
+ fname = os.fspath(fname)
+ if format is None:
+ format = (Path(fname).suffix[1:] if isinstance(fname, str)
+ else mpl.rcParams["savefig.format"]).lower()
+ if format in ["pdf", "ps", "eps", "svg"]:
+ # Vector formats that are not handled by PIL.
+ if pil_kwargs is not None:
+ raise ValueError(
+ f"Cannot use 'pil_kwargs' when saving to {format}")
+ fig = Figure(dpi=dpi, frameon=False)
+ fig.figimage(arr, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin,
+ resize=True)
+ fig.savefig(fname, dpi=dpi, format=format, transparent=True,
+ metadata=metadata)
+ else:
+ # Don't bother creating an image; this avoids rounding errors on the
+ # size when dividing and then multiplying by dpi.
+ if origin is None:
+ origin = mpl.rcParams["image.origin"]
+ else:
+ _api.check_in_list(('upper', 'lower'), origin=origin)
+ if origin == "lower":
+ arr = arr[::-1]
+ if (isinstance(arr, memoryview) and arr.format == "B"
+ and arr.ndim == 3 and arr.shape[-1] == 4):
+ # Such an ``arr`` would also be handled fine by sm.to_rgba below
+ # (after casting with asarray), but it is useful to special-case it
+ # because that's what backend_agg passes, and can be in fact used
+ # as is, saving a few operations.
+ rgba = arr
+ else:
+ sm = mcolorizer.Colorizer(cmap=cmap)
+ sm.set_clim(vmin, vmax)
+ rgba = sm.to_rgba(arr, bytes=True)
+ if pil_kwargs is None:
+ pil_kwargs = {}
+ else:
+ # we modify this below, so make a copy (don't modify caller's dict)
+ pil_kwargs = pil_kwargs.copy()
+ pil_shape = (rgba.shape[1], rgba.shape[0])
+ rgba = np.require(rgba, requirements='C')
+ image = PIL.Image.frombuffer(
+ "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1)
+ if format == "png":
+ # Only use the metadata kwarg if pnginfo is not set, because the
+ # semantics of duplicate keys in pnginfo is unclear.
+ if "pnginfo" in pil_kwargs:
+ if metadata:
+ _api.warn_external("'metadata' is overridden by the "
+ "'pnginfo' entry in 'pil_kwargs'.")
+ else:
+ metadata = {
+ "Software": (f"Matplotlib version{mpl.__version__}, "
+ f"https://matplotlib.org/"),
+ **(metadata if metadata is not None else {}),
+ }
+ pil_kwargs["pnginfo"] = pnginfo = PIL.PngImagePlugin.PngInfo()
+ for k, v in metadata.items():
+ if v is not None:
+ pnginfo.add_text(k, v)
+ elif metadata is not None:
+ raise ValueError(f"metadata not supported for format {format!r}")
+ if format in ["jpg", "jpeg"]:
+ format = "jpeg" # Pillow doesn't recognize "jpg".
+ facecolor = mpl.rcParams["savefig.facecolor"]
+ if cbook._str_equal(facecolor, "auto"):
+ facecolor = mpl.rcParams["figure.facecolor"]
+ color = tuple(int(x * 255) for x in mcolors.to_rgb(facecolor))
+ background = PIL.Image.new("RGB", pil_shape, color)
+ background.paste(image, image)
+ image = background
+ pil_kwargs.setdefault("format", format)
+ pil_kwargs.setdefault("dpi", (dpi, dpi))
+ image.save(fname, **pil_kwargs)
+
+
+def pil_to_array(pilImage):
+ """
+ Load a `PIL image`_ and return it as a numpy int array.
+
+ .. _PIL image: https://pillow.readthedocs.io/en/latest/reference/Image.html
+
+ Returns
+ -------
+ numpy.array
+
+ The array shape depends on the image type:
+
+ - (M, N) for grayscale images.
+ - (M, N, 3) for RGB images.
+ - (M, N, 4) for RGBA images.
+ """
+ if pilImage.mode in ['RGBA', 'RGBX', 'RGB', 'L']:
+ # return MxNx4 RGBA, MxNx3 RBA, or MxN luminance array
+ return np.asarray(pilImage)
+ elif pilImage.mode.startswith('I;16'):
+ # return MxN luminance array of uint16
+ raw = pilImage.tobytes('raw', pilImage.mode)
+ if pilImage.mode.endswith('B'):
+ x = np.frombuffer(raw, '>u2')
+ else:
+ x = np.frombuffer(raw, ' None: ...
+
+#
+# END names re-exported from matplotlib._image.
+#
+
+interpolations_names: set[str]
+
+def composite_images(
+ images: Sequence[_ImageBase], renderer: RendererBase, magnification: float = ...
+) -> tuple[np.ndarray, float, float]: ...
+
+class _ImageBase(colorizer.ColorizingArtist):
+ zorder: float
+ origin: Literal["upper", "lower"]
+ axes: Axes
+ def __init__(
+ self,
+ ax: Axes,
+ cmap: str | Colormap | None = ...,
+ norm: str | Normalize | None = ...,
+ colorizer: Colorizer | None = ...,
+ interpolation: str | None = ...,
+ origin: Literal["upper", "lower"] | None = ...,
+ filternorm: bool = ...,
+ filterrad: float = ...,
+ resample: bool | None = ...,
+ *,
+ interpolation_stage: Literal["data", "rgba", "auto"] | None = ...,
+ **kwargs
+ ) -> None: ...
+ def get_size(self) -> tuple[int, int]: ...
+ def set_alpha(self, alpha: float | ArrayLike | None) -> None: ...
+ def changed(self) -> None: ...
+ def make_image(
+ self, renderer: RendererBase, magnification: float = ..., unsampled: bool = ...
+ ) -> tuple[np.ndarray, float, float, Affine2D]: ...
+ def draw(self, renderer: RendererBase) -> None: ...
+ def write_png(self, fname: str | pathlib.Path | BinaryIO) -> None: ...
+ def set_data(self, A: ArrayLike | None) -> None: ...
+ def set_array(self, A: ArrayLike | None) -> None: ...
+ def get_shape(self) -> tuple[int, int, int]: ...
+ def get_interpolation(self) -> str: ...
+ def set_interpolation(self, s: str | None) -> None: ...
+ def get_interpolation_stage(self) -> Literal["data", "rgba", "auto"]: ...
+ def set_interpolation_stage(self, s: Literal["data", "rgba", "auto"]) -> None: ...
+ def can_composite(self) -> bool: ...
+ def set_resample(self, v: bool | None) -> None: ...
+ def get_resample(self) -> bool: ...
+ def set_filternorm(self, filternorm: bool) -> None: ...
+ def get_filternorm(self) -> bool: ...
+ def set_filterrad(self, filterrad: float) -> None: ...
+ def get_filterrad(self) -> float: ...
+
+class AxesImage(_ImageBase):
+ def __init__(
+ self,
+ ax: Axes,
+ *,
+ cmap: str | Colormap | None = ...,
+ norm: str | Normalize | None = ...,
+ colorizer: Colorizer | None = ...,
+ interpolation: str | None = ...,
+ origin: Literal["upper", "lower"] | None = ...,
+ extent: tuple[float, float, float, float] | None = ...,
+ filternorm: bool = ...,
+ filterrad: float = ...,
+ resample: bool = ...,
+ interpolation_stage: Literal["data", "rgba", "auto"] | None = ...,
+ **kwargs
+ ) -> None: ...
+ def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ...
+ def make_image(
+ self, renderer: RendererBase, magnification: float = ..., unsampled: bool = ...
+ ) -> tuple[np.ndarray, float, float, Affine2D]: ...
+ def set_extent(
+ self, extent: tuple[float, float, float, float], **kwargs
+ ) -> None: ...
+ def get_extent(self) -> tuple[float, float, float, float]: ...
+ def get_cursor_data(self, event: MouseEvent) -> None | float: ...
+
+class NonUniformImage(AxesImage):
+ mouseover: bool
+ def __init__(
+ self, ax: Axes, *, interpolation: Literal["nearest", "bilinear"] = ..., **kwargs
+ ) -> None: ...
+ def set_data(self, x: ArrayLike, y: ArrayLike, A: ArrayLike) -> None: ... # type: ignore[override]
+ # more limited interpolation available here than base class
+ def set_interpolation(self, s: Literal["nearest", "bilinear"]) -> None: ... # type: ignore[override]
+
+class PcolorImage(AxesImage):
+ def __init__(
+ self,
+ ax: Axes,
+ x: ArrayLike | None = ...,
+ y: ArrayLike | None = ...,
+ A: ArrayLike | None = ...,
+ *,
+ cmap: str | Colormap | None = ...,
+ norm: str | Normalize | None = ...,
+ colorizer: Colorizer | None = ...,
+ **kwargs
+ ) -> None: ...
+ def set_data(self, x: ArrayLike, y: ArrayLike, A: ArrayLike) -> None: ... # type: ignore[override]
+
+class FigureImage(_ImageBase):
+ zorder: float
+ figure: Figure
+ ox: float
+ oy: float
+ magnification: float
+ def __init__(
+ self,
+ fig: Figure,
+ *,
+ cmap: str | Colormap | None = ...,
+ norm: str | Normalize | None = ...,
+ colorizer: Colorizer | None = ...,
+ offsetx: int = ...,
+ offsety: int = ...,
+ origin: Literal["upper", "lower"] | None = ...,
+ **kwargs
+ ) -> None: ...
+ def get_extent(self) -> tuple[float, float, float, float]: ...
+
+class BboxImage(_ImageBase):
+ bbox: BboxBase
+ def __init__(
+ self,
+ bbox: BboxBase | Callable[[RendererBase | None], Bbox],
+ *,
+ cmap: str | Colormap | None = ...,
+ norm: str | Normalize | None = ...,
+ colorizer: Colorizer | None = ...,
+ interpolation: str | None = ...,
+ origin: Literal["upper", "lower"] | None = ...,
+ filternorm: bool = ...,
+ filterrad: float = ...,
+ resample: bool = ...,
+ **kwargs
+ ) -> None: ...
+ def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ...
+
+def imread(
+ fname: str | pathlib.Path | BinaryIO, format: str | None = ...
+) -> np.ndarray: ...
+def imsave(
+ fname: str | os.PathLike | BinaryIO,
+ arr: ArrayLike,
+ vmin: float | None = ...,
+ vmax: float | None = ...,
+ cmap: str | Colormap | None = ...,
+ format: str | None = ...,
+ origin: Literal["upper", "lower"] | None = ...,
+ dpi: float = ...,
+ *,
+ metadata: dict[str, str] | None = ...,
+ pil_kwargs: dict[str, Any] | None = ...
+) -> None: ...
+def pil_to_array(pilImage: PIL.Image.Image) -> np.ndarray: ...
+def thumbnail(
+ infile: str | BinaryIO,
+ thumbfile: str | BinaryIO,
+ scale: float = ...,
+ interpolation: str = ...,
+ preview: bool = ...,
+) -> Figure: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.py
new file mode 100644
index 0000000000000000000000000000000000000000..bab69491303e698ff6ec05a7d95ae88d1d213ac3
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.py
@@ -0,0 +1,269 @@
+"""
+The inset module defines the InsetIndicator class, which draws the rectangle and
+connectors required for `.Axes.indicate_inset` and `.Axes.indicate_inset_zoom`.
+"""
+
+from . import _api, artist, transforms
+from matplotlib.patches import ConnectionPatch, PathPatch, Rectangle
+from matplotlib.path import Path
+
+
+_shared_properties = ('alpha', 'edgecolor', 'linestyle', 'linewidth')
+
+
+class InsetIndicator(artist.Artist):
+ """
+ An artist to highlight an area of interest.
+
+ An inset indicator is a rectangle on the plot at the position indicated by
+ *bounds* that optionally has lines that connect the rectangle to an inset
+ Axes (`.Axes.inset_axes`).
+
+ .. versionadded:: 3.10
+ """
+ zorder = 4.99
+
+ def __init__(self, bounds=None, inset_ax=None, zorder=None, **kwargs):
+ """
+ Parameters
+ ----------
+ bounds : [x0, y0, width, height], optional
+ Lower-left corner of rectangle to be marked, and its width
+ and height. If not set, the bounds will be calculated from the
+ data limits of inset_ax, which must be supplied.
+
+ inset_ax : `~.axes.Axes`, optional
+ An optional inset Axes to draw connecting lines to. Two lines are
+ drawn connecting the indicator box to the inset Axes on corners
+ chosen so as to not overlap with the indicator box.
+
+ zorder : float, default: 4.99
+ Drawing order of the rectangle and connector lines. The default,
+ 4.99, is just below the default level of inset Axes.
+
+ **kwargs
+ Other keyword arguments are passed on to the `.Rectangle` patch.
+ """
+ if bounds is None and inset_ax is None:
+ raise ValueError("At least one of bounds or inset_ax must be supplied")
+
+ self._inset_ax = inset_ax
+
+ if bounds is None:
+ # Work out bounds from inset_ax
+ self._auto_update_bounds = True
+ bounds = self._bounds_from_inset_ax()
+ else:
+ self._auto_update_bounds = False
+
+ x, y, width, height = bounds
+
+ self._rectangle = Rectangle((x, y), width, height, clip_on=False, **kwargs)
+
+ # Connector positions cannot be calculated till the artist has been added
+ # to an axes, so just make an empty list for now.
+ self._connectors = []
+
+ super().__init__()
+ self.set_zorder(zorder)
+
+ # Initial style properties for the artist should match the rectangle.
+ for prop in _shared_properties:
+ setattr(self, f'_{prop}', artist.getp(self._rectangle, prop))
+
+ def _shared_setter(self, prop, val):
+ """
+ Helper function to set the same style property on the artist and its children.
+ """
+ setattr(self, f'_{prop}', val)
+
+ artist.setp([self._rectangle, *self._connectors], prop, val)
+
+ def set_alpha(self, alpha):
+ # docstring inherited
+ self._shared_setter('alpha', alpha)
+
+ def set_edgecolor(self, color):
+ """
+ Set the edge color of the rectangle and the connectors.
+
+ Parameters
+ ----------
+ color : :mpltype:`color` or None
+ """
+ self._shared_setter('edgecolor', color)
+
+ def set_color(self, c):
+ """
+ Set the edgecolor of the rectangle and the connectors, and the
+ facecolor for the rectangle.
+
+ Parameters
+ ----------
+ c : :mpltype:`color`
+ """
+ self._shared_setter('edgecolor', c)
+ self._shared_setter('facecolor', c)
+
+ def set_linewidth(self, w):
+ """
+ Set the linewidth in points of the rectangle and the connectors.
+
+ Parameters
+ ----------
+ w : float or None
+ """
+ self._shared_setter('linewidth', w)
+
+ def set_linestyle(self, ls):
+ """
+ Set the linestyle of the rectangle and the connectors.
+
+ ========================================== =================
+ linestyle description
+ ========================================== =================
+ ``'-'`` or ``'solid'`` solid line
+ ``'--'`` or ``'dashed'`` dashed line
+ ``'-.'`` or ``'dashdot'`` dash-dotted line
+ ``':'`` or ``'dotted'`` dotted line
+ ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing
+ ========================================== =================
+
+ Alternatively a dash tuple of the following form can be provided::
+
+ (offset, onoffseq)
+
+ where ``onoffseq`` is an even length tuple of on and off ink in points.
+
+ Parameters
+ ----------
+ ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
+ The line style.
+ """
+ self._shared_setter('linestyle', ls)
+
+ def _bounds_from_inset_ax(self):
+ xlim = self._inset_ax.get_xlim()
+ ylim = self._inset_ax.get_ylim()
+ return (xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0])
+
+ def _update_connectors(self):
+ (x, y) = self._rectangle.get_xy()
+ width = self._rectangle.get_width()
+ height = self._rectangle.get_height()
+
+ existing_connectors = self._connectors or [None] * 4
+
+ # connect the inset_axes to the rectangle
+ for xy_inset_ax, existing in zip([(0, 0), (0, 1), (1, 0), (1, 1)],
+ existing_connectors):
+ # inset_ax positions are in axes coordinates
+ # The 0, 1 values define the four edges if the inset_ax
+ # lower_left, upper_left, lower_right upper_right.
+ ex, ey = xy_inset_ax
+ if self.axes.xaxis.get_inverted():
+ ex = 1 - ex
+ if self.axes.yaxis.get_inverted():
+ ey = 1 - ey
+ xy_data = x + ex * width, y + ey * height
+ if existing is None:
+ # Create new connection patch with styles inherited from the
+ # parent artist.
+ p = ConnectionPatch(
+ xyA=xy_inset_ax, coordsA=self._inset_ax.transAxes,
+ xyB=xy_data, coordsB=self.axes.transData,
+ arrowstyle="-",
+ edgecolor=self._edgecolor, alpha=self.get_alpha(),
+ linestyle=self._linestyle, linewidth=self._linewidth)
+ self._connectors.append(p)
+ else:
+ # Only update positioning of existing connection patch. We
+ # do not want to override any style settings made by the user.
+ existing.xy1 = xy_inset_ax
+ existing.xy2 = xy_data
+ existing.coords1 = self._inset_ax.transAxes
+ existing.coords2 = self.axes.transData
+
+ if existing is None:
+ # decide which two of the lines to keep visible....
+ pos = self._inset_ax.get_position()
+ bboxins = pos.transformed(self.get_figure(root=False).transSubfigure)
+ rectbbox = transforms.Bbox.from_bounds(x, y, width, height).transformed(
+ self._rectangle.get_transform())
+ x0 = rectbbox.x0 < bboxins.x0
+ x1 = rectbbox.x1 < bboxins.x1
+ y0 = rectbbox.y0 < bboxins.y0
+ y1 = rectbbox.y1 < bboxins.y1
+ self._connectors[0].set_visible(x0 ^ y0)
+ self._connectors[1].set_visible(x0 == y1)
+ self._connectors[2].set_visible(x1 == y0)
+ self._connectors[3].set_visible(x1 ^ y1)
+
+ @property
+ def rectangle(self):
+ """`.Rectangle`: the indicator frame."""
+ return self._rectangle
+
+ @property
+ def connectors(self):
+ """
+ 4-tuple of `.patches.ConnectionPatch` or None
+ The four connector lines connecting to (lower_left, upper_left,
+ lower_right upper_right) corners of *inset_ax*. Two lines are
+ set with visibility to *False*, but the user can set the
+ visibility to True if the automatic choice is not deemed correct.
+ """
+ if self._inset_ax is None:
+ return
+
+ if self._auto_update_bounds:
+ self._rectangle.set_bounds(self._bounds_from_inset_ax())
+ self._update_connectors()
+ return tuple(self._connectors)
+
+ def draw(self, renderer):
+ # docstring inherited
+ conn_same_style = []
+
+ # Figure out which connectors have the same style as the box, so should
+ # be drawn as a single path.
+ for conn in self.connectors or []:
+ if conn.get_visible():
+ drawn = False
+ for s in _shared_properties:
+ if artist.getp(self._rectangle, s) != artist.getp(conn, s):
+ # Draw this connector by itself
+ conn.draw(renderer)
+ drawn = True
+ break
+
+ if not drawn:
+ # Connector has same style as box.
+ conn_same_style.append(conn)
+
+ if conn_same_style:
+ # Since at least one connector has the same style as the rectangle, draw
+ # them as a compound path.
+ artists = [self._rectangle] + conn_same_style
+ paths = [a.get_transform().transform_path(a.get_path()) for a in artists]
+ path = Path.make_compound_path(*paths)
+
+ # Create a temporary patch to draw the path.
+ p = PathPatch(path)
+ p.update_from(self._rectangle)
+ p.set_transform(transforms.IdentityTransform())
+ p.draw(renderer)
+
+ return
+
+ # Just draw the rectangle
+ self._rectangle.draw(renderer)
+
+ @_api.deprecated(
+ '3.10',
+ message=('Since Matplotlib 3.10 indicate_inset_[zoom] returns a single '
+ 'InsetIndicator artist with a rectangle property and a connectors '
+ 'property. From 3.12 it will no longer be possible to unpack the '
+ 'return value into two elements.'))
+ def __getitem__(self, key):
+ return [self._rectangle, self.connectors][key]
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..e895fd7be27c40ad07167aeca73fc7380b4d0d23
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.pyi
@@ -0,0 +1,25 @@
+from . import artist
+from .axes import Axes
+from .backend_bases import RendererBase
+from .patches import ConnectionPatch, Rectangle
+
+from .typing import ColorType, LineStyleType
+
+class InsetIndicator(artist.Artist):
+ def __init__(
+ self,
+ bounds: tuple[float, float, float, float] | None = ...,
+ inset_ax: Axes | None = ...,
+ zorder: float | None = ...,
+ **kwargs
+ ) -> None: ...
+ def set_alpha(self, alpha: float | None) -> None: ...
+ def set_edgecolor(self, color: ColorType | None) -> None: ...
+ def set_color(self, c: ColorType | None) -> None: ...
+ def set_linewidth(self, w: float | None) -> None: ...
+ def set_linestyle(self, ls: LineStyleType | None) -> None: ...
+ @property
+ def rectangle(self) -> Rectangle: ...
+ @property
+ def connectors(self) -> tuple[ConnectionPatch, ConnectionPatch, ConnectionPatch, ConnectionPatch] | None: ...
+ def draw(self, renderer: RendererBase) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a3276b53371b5a99a3226399e281e4f184258a6
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.py
@@ -0,0 +1,309 @@
+"""
+Classes to layout elements in a `.Figure`.
+
+Figures have a ``layout_engine`` property that holds a subclass of
+`~.LayoutEngine` defined here (or *None* for no layout). At draw time
+``figure.get_layout_engine().execute()`` is called, the goal of which is
+usually to rearrange Axes on the figure to produce a pleasing layout. This is
+like a ``draw`` callback but with two differences. First, when printing we
+disable the layout engine for the final draw. Second, it is useful to know the
+layout engine while the figure is being created. In particular, colorbars are
+made differently with different layout engines (for historical reasons).
+
+Matplotlib has two built-in layout engines:
+
+- `.TightLayoutEngine` was the first layout engine added to Matplotlib.
+ See also :ref:`tight_layout_guide`.
+- `.ConstrainedLayoutEngine` is more modern and generally gives better results.
+ See also :ref:`constrainedlayout_guide`.
+
+Third parties can create their own layout engine by subclassing `.LayoutEngine`.
+"""
+
+from contextlib import nullcontext
+
+import matplotlib as mpl
+
+from matplotlib._constrained_layout import do_constrained_layout
+from matplotlib._tight_layout import (get_subplotspec_list,
+ get_tight_layout_figure)
+
+
+class LayoutEngine:
+ """
+ Base class for Matplotlib layout engines.
+
+ A layout engine can be passed to a figure at instantiation or at any time
+ with `~.figure.Figure.set_layout_engine`. Once attached to a figure, the
+ layout engine ``execute`` function is called at draw time by
+ `~.figure.Figure.draw`, providing a special draw-time hook.
+
+ .. note::
+
+ However, note that layout engines affect the creation of colorbars, so
+ `~.figure.Figure.set_layout_engine` should be called before any
+ colorbars are created.
+
+ Currently, there are two properties of `LayoutEngine` classes that are
+ consulted while manipulating the figure:
+
+ - ``engine.colorbar_gridspec`` tells `.Figure.colorbar` whether to make the
+ axes using the gridspec method (see `.colorbar.make_axes_gridspec`) or
+ not (see `.colorbar.make_axes`);
+ - ``engine.adjust_compatible`` stops `.Figure.subplots_adjust` from being
+ run if it is not compatible with the layout engine.
+
+ To implement a custom `LayoutEngine`:
+
+ 1. override ``_adjust_compatible`` and ``_colorbar_gridspec``
+ 2. override `LayoutEngine.set` to update *self._params*
+ 3. override `LayoutEngine.execute` with your implementation
+
+ """
+ # override these in subclass
+ _adjust_compatible = None
+ _colorbar_gridspec = None
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self._params = {}
+
+ def set(self, **kwargs):
+ """
+ Set the parameters for the layout engine.
+ """
+ raise NotImplementedError
+
+ @property
+ def colorbar_gridspec(self):
+ """
+ Return a boolean if the layout engine creates colorbars using a
+ gridspec.
+ """
+ if self._colorbar_gridspec is None:
+ raise NotImplementedError
+ return self._colorbar_gridspec
+
+ @property
+ def adjust_compatible(self):
+ """
+ Return a boolean if the layout engine is compatible with
+ `~.Figure.subplots_adjust`.
+ """
+ if self._adjust_compatible is None:
+ raise NotImplementedError
+ return self._adjust_compatible
+
+ def get(self):
+ """
+ Return copy of the parameters for the layout engine.
+ """
+ return dict(self._params)
+
+ def execute(self, fig):
+ """
+ Execute the layout on the figure given by *fig*.
+ """
+ # subclasses must implement this.
+ raise NotImplementedError
+
+
+class PlaceHolderLayoutEngine(LayoutEngine):
+ """
+ This layout engine does not adjust the figure layout at all.
+
+ The purpose of this `.LayoutEngine` is to act as a placeholder when the user removes
+ a layout engine to ensure an incompatible `.LayoutEngine` cannot be set later.
+
+ Parameters
+ ----------
+ adjust_compatible, colorbar_gridspec : bool
+ Allow the PlaceHolderLayoutEngine to mirror the behavior of whatever
+ layout engine it is replacing.
+
+ """
+ def __init__(self, adjust_compatible, colorbar_gridspec, **kwargs):
+ self._adjust_compatible = adjust_compatible
+ self._colorbar_gridspec = colorbar_gridspec
+ super().__init__(**kwargs)
+
+ def execute(self, fig):
+ """
+ Do nothing.
+ """
+ return
+
+
+class TightLayoutEngine(LayoutEngine):
+ """
+ Implements the ``tight_layout`` geometry management. See
+ :ref:`tight_layout_guide` for details.
+ """
+ _adjust_compatible = True
+ _colorbar_gridspec = True
+
+ def __init__(self, *, pad=1.08, h_pad=None, w_pad=None,
+ rect=(0, 0, 1, 1), **kwargs):
+ """
+ Initialize tight_layout engine.
+
+ Parameters
+ ----------
+ pad : float, default: 1.08
+ Padding between the figure edge and the edges of subplots, as a
+ fraction of the font size.
+ h_pad, w_pad : float
+ Padding (height/width) between edges of adjacent subplots.
+ Defaults to *pad*.
+ rect : tuple (left, bottom, right, top), default: (0, 0, 1, 1).
+ rectangle in normalized figure coordinates that the subplots
+ (including labels) will fit into.
+ """
+ super().__init__(**kwargs)
+ for td in ['pad', 'h_pad', 'w_pad', 'rect']:
+ # initialize these in case None is passed in above:
+ self._params[td] = None
+ self.set(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
+
+ def execute(self, fig):
+ """
+ Execute tight_layout.
+
+ This decides the subplot parameters given the padding that
+ will allow the Axes labels to not be covered by other labels
+ and Axes.
+
+ Parameters
+ ----------
+ fig : `.Figure` to perform layout on.
+
+ See Also
+ --------
+ .figure.Figure.tight_layout
+ .pyplot.tight_layout
+ """
+ info = self._params
+ renderer = fig._get_renderer()
+ with getattr(renderer, "_draw_disabled", nullcontext)():
+ kwargs = get_tight_layout_figure(
+ fig, fig.axes, get_subplotspec_list(fig.axes), renderer,
+ pad=info['pad'], h_pad=info['h_pad'], w_pad=info['w_pad'],
+ rect=info['rect'])
+ if kwargs:
+ fig.subplots_adjust(**kwargs)
+
+ def set(self, *, pad=None, w_pad=None, h_pad=None, rect=None):
+ """
+ Set the pads for tight_layout.
+
+ Parameters
+ ----------
+ pad : float
+ Padding between the figure edge and the edges of subplots, as a
+ fraction of the font size.
+ w_pad, h_pad : float
+ Padding (width/height) between edges of adjacent subplots.
+ Defaults to *pad*.
+ rect : tuple (left, bottom, right, top)
+ rectangle in normalized figure coordinates that the subplots
+ (including labels) will fit into.
+ """
+ for td in self.set.__kwdefaults__:
+ if locals()[td] is not None:
+ self._params[td] = locals()[td]
+
+
+class ConstrainedLayoutEngine(LayoutEngine):
+ """
+ Implements the ``constrained_layout`` geometry management. See
+ :ref:`constrainedlayout_guide` for details.
+ """
+
+ _adjust_compatible = False
+ _colorbar_gridspec = False
+
+ def __init__(self, *, h_pad=None, w_pad=None,
+ hspace=None, wspace=None, rect=(0, 0, 1, 1),
+ compress=False, **kwargs):
+ """
+ Initialize ``constrained_layout`` settings.
+
+ Parameters
+ ----------
+ h_pad, w_pad : float
+ Padding around the Axes elements in inches.
+ Default to :rc:`figure.constrained_layout.h_pad` and
+ :rc:`figure.constrained_layout.w_pad`.
+ hspace, wspace : float
+ Fraction of the figure to dedicate to space between the
+ axes. These are evenly spread between the gaps between the Axes.
+ A value of 0.2 for a three-column layout would have a space
+ of 0.1 of the figure width between each column.
+ If h/wspace < h/w_pad, then the pads are used instead.
+ Default to :rc:`figure.constrained_layout.hspace` and
+ :rc:`figure.constrained_layout.wspace`.
+ rect : tuple of 4 floats
+ Rectangle in figure coordinates to perform constrained layout in
+ (left, bottom, width, height), each from 0-1.
+ compress : bool
+ Whether to shift Axes so that white space in between them is
+ removed. This is useful for simple grids of fixed-aspect Axes (e.g.
+ a grid of images). See :ref:`compressed_layout`.
+ """
+ super().__init__(**kwargs)
+ # set the defaults:
+ self.set(w_pad=mpl.rcParams['figure.constrained_layout.w_pad'],
+ h_pad=mpl.rcParams['figure.constrained_layout.h_pad'],
+ wspace=mpl.rcParams['figure.constrained_layout.wspace'],
+ hspace=mpl.rcParams['figure.constrained_layout.hspace'],
+ rect=(0, 0, 1, 1))
+ # set anything that was passed in (None will be ignored):
+ self.set(w_pad=w_pad, h_pad=h_pad, wspace=wspace, hspace=hspace,
+ rect=rect)
+ self._compress = compress
+
+ def execute(self, fig):
+ """
+ Perform constrained_layout and move and resize Axes accordingly.
+
+ Parameters
+ ----------
+ fig : `.Figure` to perform layout on.
+ """
+ width, height = fig.get_size_inches()
+ # pads are relative to the current state of the figure...
+ w_pad = self._params['w_pad'] / width
+ h_pad = self._params['h_pad'] / height
+
+ return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
+ wspace=self._params['wspace'],
+ hspace=self._params['hspace'],
+ rect=self._params['rect'],
+ compress=self._compress)
+
+ def set(self, *, h_pad=None, w_pad=None,
+ hspace=None, wspace=None, rect=None):
+ """
+ Set the pads for constrained_layout.
+
+ Parameters
+ ----------
+ h_pad, w_pad : float
+ Padding around the Axes elements in inches.
+ Default to :rc:`figure.constrained_layout.h_pad` and
+ :rc:`figure.constrained_layout.w_pad`.
+ hspace, wspace : float
+ Fraction of the figure to dedicate to space between the
+ axes. These are evenly spread between the gaps between the Axes.
+ A value of 0.2 for a three-column layout would have a space
+ of 0.1 of the figure width between each column.
+ If h/wspace < h/w_pad, then the pads are used instead.
+ Default to :rc:`figure.constrained_layout.hspace` and
+ :rc:`figure.constrained_layout.wspace`.
+ rect : tuple of 4 floats
+ Rectangle in figure coordinates to perform constrained layout in
+ (left, bottom, width, height), each from 0-1.
+ """
+ for td in self.set.__kwdefaults__:
+ if locals()[td] is not None:
+ self._params[td] = locals()[td]
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..5b8c812ff47f53913ff2812b58fcaf3652985a86
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.pyi
@@ -0,0 +1,62 @@
+from matplotlib.figure import Figure
+
+from typing import Any
+
+class LayoutEngine:
+ def __init__(self, **kwargs: Any) -> None: ...
+ def set(self) -> None: ...
+ @property
+ def colorbar_gridspec(self) -> bool: ...
+ @property
+ def adjust_compatible(self) -> bool: ...
+ def get(self) -> dict[str, Any]: ...
+ def execute(self, fig: Figure) -> None: ...
+
+class PlaceHolderLayoutEngine(LayoutEngine):
+ def __init__(
+ self, adjust_compatible: bool, colorbar_gridspec: bool, **kwargs: Any
+ ) -> None: ...
+ def execute(self, fig: Figure) -> None: ...
+
+class TightLayoutEngine(LayoutEngine):
+ def __init__(
+ self,
+ *,
+ pad: float = ...,
+ h_pad: float | None = ...,
+ w_pad: float | None = ...,
+ rect: tuple[float, float, float, float] = ...,
+ **kwargs: Any
+ ) -> None: ...
+ def execute(self, fig: Figure) -> None: ...
+ def set(
+ self,
+ *,
+ pad: float | None = ...,
+ w_pad: float | None = ...,
+ h_pad: float | None = ...,
+ rect: tuple[float, float, float, float] | None = ...
+ ) -> None: ...
+
+class ConstrainedLayoutEngine(LayoutEngine):
+ def __init__(
+ self,
+ *,
+ h_pad: float | None = ...,
+ w_pad: float | None = ...,
+ hspace: float | None = ...,
+ wspace: float | None = ...,
+ rect: tuple[float, float, float, float] = ...,
+ compress: bool = ...,
+ **kwargs: Any
+ ) -> None: ...
+ def execute(self, fig: Figure) -> Any: ...
+ def set(
+ self,
+ *,
+ h_pad: float | None = ...,
+ w_pad: float | None = ...,
+ hspace: float | None = ...,
+ wspace: float | None = ...,
+ rect: tuple[float, float, float, float] | None = ...
+ ) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d8238c307ef3825ac1d9ffde26ff0c4874b20b8
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.py
@@ -0,0 +1,1371 @@
+"""
+The legend module defines the Legend class, which is responsible for
+drawing legends associated with Axes and/or figures.
+
+.. important::
+
+ It is unlikely that you would ever create a Legend instance manually.
+ Most users would normally create a legend via the `~.Axes.legend`
+ function. For more details on legends there is also a :ref:`legend guide
+ `.
+
+The `Legend` class is a container of legend handles and legend texts.
+
+The legend handler map specifies how to create legend handles from artists
+(lines, patches, etc.) in the Axes or figures. Default legend handlers are
+defined in the :mod:`~matplotlib.legend_handler` module. While not all artist
+types are covered by the default legend handlers, custom legend handlers can be
+defined to support arbitrary objects.
+
+See the :ref`` for more
+information.
+"""
+
+import itertools
+import logging
+import numbers
+import time
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api, _docstring, cbook, colors, offsetbox
+from matplotlib.artist import Artist, allow_rasterization
+from matplotlib.cbook import silent_list
+from matplotlib.font_manager import FontProperties
+from matplotlib.lines import Line2D
+from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch,
+ StepPatch)
+from matplotlib.collections import (
+ Collection, CircleCollection, LineCollection, PathCollection,
+ PolyCollection, RegularPolyCollection)
+from matplotlib.text import Text
+from matplotlib.transforms import Bbox, BboxBase, TransformedBbox
+from matplotlib.transforms import BboxTransformTo, BboxTransformFrom
+from matplotlib.offsetbox import (
+ AnchoredOffsetbox, DraggableOffsetBox,
+ HPacker, VPacker,
+ DrawingArea, TextArea,
+)
+from matplotlib.container import ErrorbarContainer, BarContainer, StemContainer
+from . import legend_handler
+
+
+class DraggableLegend(DraggableOffsetBox):
+ def __init__(self, legend, use_blit=False, update="loc"):
+ """
+ Wrapper around a `.Legend` to support mouse dragging.
+
+ Parameters
+ ----------
+ legend : `.Legend`
+ The `.Legend` instance to wrap.
+ use_blit : bool, optional
+ Use blitting for faster image composition. For details see
+ :ref:`func-animation`.
+ update : {'loc', 'bbox'}, optional
+ If "loc", update the *loc* parameter of the legend upon finalizing.
+ If "bbox", update the *bbox_to_anchor* parameter.
+ """
+ self.legend = legend
+
+ _api.check_in_list(["loc", "bbox"], update=update)
+ self._update = update
+
+ super().__init__(legend, legend._legend_box, use_blit=use_blit)
+
+ def finalize_offset(self):
+ if self._update == "loc":
+ self._update_loc(self.get_loc_in_canvas())
+ elif self._update == "bbox":
+ self._update_bbox_to_anchor(self.get_loc_in_canvas())
+
+ def _update_loc(self, loc_in_canvas):
+ bbox = self.legend.get_bbox_to_anchor()
+ # if bbox has zero width or height, the transformation is
+ # ill-defined. Fall back to the default bbox_to_anchor.
+ if bbox.width == 0 or bbox.height == 0:
+ self.legend.set_bbox_to_anchor(None)
+ bbox = self.legend.get_bbox_to_anchor()
+ _bbox_transform = BboxTransformFrom(bbox)
+ self.legend._loc = tuple(_bbox_transform.transform(loc_in_canvas))
+
+ def _update_bbox_to_anchor(self, loc_in_canvas):
+ loc_in_bbox = self.legend.axes.transAxes.transform(loc_in_canvas)
+ self.legend.set_bbox_to_anchor(loc_in_bbox)
+
+
+_legend_kw_doc_base = """
+bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats
+ Box that is used to position the legend in conjunction with *loc*.
+ Defaults to ``axes.bbox`` (if called as a method to `.Axes.legend`) or
+ ``figure.bbox`` (if ``figure.legend``). This argument allows arbitrary
+ placement of the legend.
+
+ Bbox coordinates are interpreted in the coordinate system given by
+ *bbox_transform*, with the default transform
+ Axes or Figure coordinates, depending on which ``legend`` is called.
+
+ If a 4-tuple or `.BboxBase` is given, then it specifies the bbox
+ ``(x, y, width, height)`` that the legend is placed in.
+ To put the legend in the best location in the bottom right
+ quadrant of the Axes (or figure)::
+
+ loc='best', bbox_to_anchor=(0.5, 0., 0.5, 0.5)
+
+ A 2-tuple ``(x, y)`` places the corner of the legend specified by *loc* at
+ x, y. For example, to put the legend's upper right-hand corner in the
+ center of the Axes (or figure) the following keywords can be used::
+
+ loc='upper right', bbox_to_anchor=(0.5, 0.5)
+
+ncols : int, default: 1
+ The number of columns that the legend has.
+
+ For backward compatibility, the spelling *ncol* is also supported
+ but it is discouraged. If both are given, *ncols* takes precedence.
+
+prop : None or `~matplotlib.font_manager.FontProperties` or dict
+ The font properties of the legend. If None (default), the current
+ :data:`matplotlib.rcParams` will be used.
+
+fontsize : int or {'xx-small', 'x-small', 'small', 'medium', 'large', \
+'x-large', 'xx-large'}
+ The font size of the legend. If the value is numeric the size will be the
+ absolute font size in points. String values are relative to the current
+ default font size. This argument is only used if *prop* is not specified.
+
+labelcolor : str or list, default: :rc:`legend.labelcolor`
+ The color of the text in the legend. Either a valid color string
+ (for example, 'red'), or a list of color strings. The labelcolor can
+ also be made to match the color of the line or marker using 'linecolor',
+ 'markerfacecolor' (or 'mfc'), or 'markeredgecolor' (or 'mec').
+
+ Labelcolor can be set globally using :rc:`legend.labelcolor`. If None,
+ use :rc:`text.color`.
+
+numpoints : int, default: :rc:`legend.numpoints`
+ The number of marker points in the legend when creating a legend
+ entry for a `.Line2D` (line).
+
+scatterpoints : int, default: :rc:`legend.scatterpoints`
+ The number of marker points in the legend when creating
+ a legend entry for a `.PathCollection` (scatter plot).
+
+scatteryoffsets : iterable of floats, default: ``[0.375, 0.5, 0.3125]``
+ The vertical offset (relative to the font size) for the markers
+ created for a scatter plot legend entry. 0.0 is at the base the
+ legend text, and 1.0 is at the top. To draw all markers at the
+ same height, set to ``[0.5]``.
+
+markerscale : float, default: :rc:`legend.markerscale`
+ The relative size of legend markers compared to the originally drawn ones.
+
+markerfirst : bool, default: True
+ If *True*, legend marker is placed to the left of the legend label.
+ If *False*, legend marker is placed to the right of the legend label.
+
+reverse : bool, default: False
+ If *True*, the legend labels are displayed in reverse order from the input.
+ If *False*, the legend labels are displayed in the same order as the input.
+
+ .. versionadded:: 3.7
+
+frameon : bool, default: :rc:`legend.frameon`
+ Whether the legend should be drawn on a patch (frame).
+
+fancybox : bool, default: :rc:`legend.fancybox`
+ Whether round edges should be enabled around the `.FancyBboxPatch` which
+ makes up the legend's background.
+
+shadow : None, bool or dict, default: :rc:`legend.shadow`
+ Whether to draw a shadow behind the legend.
+ The shadow can be configured using `.Patch` keywords.
+ Customization via :rc:`legend.shadow` is currently not supported.
+
+framealpha : float, default: :rc:`legend.framealpha`
+ The alpha transparency of the legend's background.
+ If *shadow* is activated and *framealpha* is ``None``, the default value is
+ ignored.
+
+facecolor : "inherit" or color, default: :rc:`legend.facecolor`
+ The legend's background color.
+ If ``"inherit"``, use :rc:`axes.facecolor`.
+
+edgecolor : "inherit" or color, default: :rc:`legend.edgecolor`
+ The legend's background patch edge color.
+ If ``"inherit"``, use :rc:`axes.edgecolor`.
+
+mode : {"expand", None}
+ If *mode* is set to ``"expand"`` the legend will be horizontally
+ expanded to fill the Axes area (or *bbox_to_anchor* if defines
+ the legend's size).
+
+bbox_transform : None or `~matplotlib.transforms.Transform`
+ The transform for the bounding box (*bbox_to_anchor*). For a value
+ of ``None`` (default) the Axes'
+ :data:`~matplotlib.axes.Axes.transAxes` transform will be used.
+
+title : str or None
+ The legend's title. Default is no title (``None``).
+
+title_fontproperties : None or `~matplotlib.font_manager.FontProperties` or dict
+ The font properties of the legend's title. If None (default), the
+ *title_fontsize* argument will be used if present; if *title_fontsize* is
+ also None, the current :rc:`legend.title_fontsize` will be used.
+
+title_fontsize : int or {'xx-small', 'x-small', 'small', 'medium', 'large', \
+'x-large', 'xx-large'}, default: :rc:`legend.title_fontsize`
+ The font size of the legend's title.
+ Note: This cannot be combined with *title_fontproperties*. If you want
+ to set the fontsize alongside other font properties, use the *size*
+ parameter in *title_fontproperties*.
+
+alignment : {'center', 'left', 'right'}, default: 'center'
+ The alignment of the legend title and the box of entries. The entries
+ are aligned as a single block, so that markers always lined up.
+
+borderpad : float, default: :rc:`legend.borderpad`
+ The fractional whitespace inside the legend border, in font-size units.
+
+labelspacing : float, default: :rc:`legend.labelspacing`
+ The vertical space between the legend entries, in font-size units.
+
+handlelength : float, default: :rc:`legend.handlelength`
+ The length of the legend handles, in font-size units.
+
+handleheight : float, default: :rc:`legend.handleheight`
+ The height of the legend handles, in font-size units.
+
+handletextpad : float, default: :rc:`legend.handletextpad`
+ The pad between the legend handle and text, in font-size units.
+
+borderaxespad : float, default: :rc:`legend.borderaxespad`
+ The pad between the Axes and legend border, in font-size units.
+
+columnspacing : float, default: :rc:`legend.columnspacing`
+ The spacing between columns, in font-size units.
+
+handler_map : dict or None
+ The custom dictionary mapping instances or types to a legend
+ handler. This *handler_map* updates the default handler map
+ found at `matplotlib.legend.Legend.get_legend_handler_map`.
+
+draggable : bool, default: False
+ Whether the legend can be dragged with the mouse.
+"""
+
+_loc_doc_base = """
+loc : str or pair of floats, default: {default}
+ The location of the legend.
+
+ The strings ``'upper left'``, ``'upper right'``, ``'lower left'``,
+ ``'lower right'`` place the legend at the corresponding corner of the
+ {parent}.
+
+ The strings ``'upper center'``, ``'lower center'``, ``'center left'``,
+ ``'center right'`` place the legend at the center of the corresponding edge
+ of the {parent}.
+
+ The string ``'center'`` places the legend at the center of the {parent}.
+{best}
+ The location can also be a 2-tuple giving the coordinates of the lower-left
+ corner of the legend in {parent} coordinates (in which case *bbox_to_anchor*
+ will be ignored).
+
+ For back-compatibility, ``'center right'`` (but no other location) can also
+ be spelled ``'right'``, and each "string" location can also be given as a
+ numeric value:
+
+ ================== =============
+ Location String Location Code
+ ================== =============
+ 'best' (Axes only) 0
+ 'upper right' 1
+ 'upper left' 2
+ 'lower left' 3
+ 'lower right' 4
+ 'right' 5
+ 'center left' 6
+ 'center right' 7
+ 'lower center' 8
+ 'upper center' 9
+ 'center' 10
+ ================== =============
+ {outside}"""
+
+_loc_doc_best = """
+ The string ``'best'`` places the legend at the location, among the nine
+ locations defined so far, with the minimum overlap with other drawn
+ artists. This option can be quite slow for plots with large amounts of
+ data; your plotting speed may benefit from providing a specific location.
+"""
+
+_legend_kw_axes_st = (
+ _loc_doc_base.format(parent='axes', default=':rc:`legend.loc`',
+ best=_loc_doc_best, outside='') +
+ _legend_kw_doc_base)
+_docstring.interpd.register(_legend_kw_axes=_legend_kw_axes_st)
+
+_outside_doc = """
+ If a figure is using the constrained layout manager, the string codes
+ of the *loc* keyword argument can get better layout behaviour using the
+ prefix 'outside'. There is ambiguity at the corners, so 'outside
+ upper right' will make space for the legend above the rest of the
+ axes in the layout, and 'outside right upper' will make space on the
+ right side of the layout. In addition to the values of *loc*
+ listed above, we have 'outside right upper', 'outside right lower',
+ 'outside left upper', and 'outside left lower'. See
+ :ref:`legend_guide` for more details.
+"""
+
+_legend_kw_figure_st = (
+ _loc_doc_base.format(parent='figure', default="'upper right'",
+ best='', outside=_outside_doc) +
+ _legend_kw_doc_base)
+_docstring.interpd.register(_legend_kw_figure=_legend_kw_figure_st)
+
+_legend_kw_both_st = (
+ _loc_doc_base.format(parent='axes/figure',
+ default=":rc:`legend.loc` for Axes, 'upper right' for Figure",
+ best=_loc_doc_best, outside=_outside_doc) +
+ _legend_kw_doc_base)
+_docstring.interpd.register(_legend_kw_doc=_legend_kw_both_st)
+
+_legend_kw_set_loc_st = (
+ _loc_doc_base.format(parent='axes/figure',
+ default=":rc:`legend.loc` for Axes, 'upper right' for Figure",
+ best=_loc_doc_best, outside=_outside_doc))
+_docstring.interpd.register(_legend_kw_set_loc_doc=_legend_kw_set_loc_st)
+
+
+class Legend(Artist):
+ """
+ Place a legend on the figure/axes.
+ """
+
+ # 'best' is only implemented for Axes legends
+ codes = {'best': 0, **AnchoredOffsetbox.codes}
+ zorder = 5
+
+ def __str__(self):
+ return "Legend"
+
+ @_docstring.interpd
+ def __init__(
+ self, parent, handles, labels,
+ *,
+ loc=None,
+ numpoints=None, # number of points in the legend line
+ markerscale=None, # relative size of legend markers vs. original
+ markerfirst=True, # left/right ordering of legend marker and label
+ reverse=False, # reverse ordering of legend marker and label
+ scatterpoints=None, # number of scatter points
+ scatteryoffsets=None,
+ prop=None, # properties for the legend texts
+ fontsize=None, # keyword to set font size directly
+ labelcolor=None, # keyword to set the text color
+
+ # spacing & pad defined as a fraction of the font-size
+ borderpad=None, # whitespace inside the legend border
+ labelspacing=None, # vertical space between the legend entries
+ handlelength=None, # length of the legend handles
+ handleheight=None, # height of the legend handles
+ handletextpad=None, # pad between the legend handle and text
+ borderaxespad=None, # pad between the Axes and legend border
+ columnspacing=None, # spacing between columns
+
+ ncols=1, # number of columns
+ mode=None, # horizontal distribution of columns: None or "expand"
+
+ fancybox=None, # True: fancy box, False: rounded box, None: rcParam
+ shadow=None,
+ title=None, # legend title
+ title_fontsize=None, # legend title font size
+ framealpha=None, # set frame alpha
+ edgecolor=None, # frame patch edgecolor
+ facecolor=None, # frame patch facecolor
+
+ bbox_to_anchor=None, # bbox to which the legend will be anchored
+ bbox_transform=None, # transform for the bbox
+ frameon=None, # draw frame
+ handler_map=None,
+ title_fontproperties=None, # properties for the legend title
+ alignment="center", # control the alignment within the legend box
+ ncol=1, # synonym for ncols (backward compatibility)
+ draggable=False # whether the legend can be dragged with the mouse
+ ):
+ """
+ Parameters
+ ----------
+ parent : `~matplotlib.axes.Axes` or `.Figure`
+ The artist that contains the legend.
+
+ handles : list of (`.Artist` or tuple of `.Artist`)
+ A list of Artists (lines, patches) to be added to the legend.
+
+ labels : list of str
+ A list of labels to show next to the artists. The length of handles
+ and labels should be the same. If they are not, they are truncated
+ to the length of the shorter list.
+
+ Other Parameters
+ ----------------
+ %(_legend_kw_doc)s
+
+ Attributes
+ ----------
+ legend_handles
+ List of `.Artist` objects added as legend entries.
+
+ .. versionadded:: 3.7
+ """
+ # local import only to avoid circularity
+ from matplotlib.axes import Axes
+ from matplotlib.figure import FigureBase
+
+ super().__init__()
+
+ if prop is None:
+ self.prop = FontProperties(size=mpl._val_or_rc(fontsize, "legend.fontsize"))
+ else:
+ self.prop = FontProperties._from_any(prop)
+ if isinstance(prop, dict) and "size" not in prop:
+ self.prop.set_size(mpl.rcParams["legend.fontsize"])
+
+ self._fontsize = self.prop.get_size_in_points()
+
+ self.texts = []
+ self.legend_handles = []
+ self._legend_title_box = None
+
+ #: A dictionary with the extra handler mappings for this Legend
+ #: instance.
+ self._custom_handler_map = handler_map
+
+ self.numpoints = mpl._val_or_rc(numpoints, 'legend.numpoints')
+ self.markerscale = mpl._val_or_rc(markerscale, 'legend.markerscale')
+ self.scatterpoints = mpl._val_or_rc(scatterpoints, 'legend.scatterpoints')
+ self.borderpad = mpl._val_or_rc(borderpad, 'legend.borderpad')
+ self.labelspacing = mpl._val_or_rc(labelspacing, 'legend.labelspacing')
+ self.handlelength = mpl._val_or_rc(handlelength, 'legend.handlelength')
+ self.handleheight = mpl._val_or_rc(handleheight, 'legend.handleheight')
+ self.handletextpad = mpl._val_or_rc(handletextpad, 'legend.handletextpad')
+ self.borderaxespad = mpl._val_or_rc(borderaxespad, 'legend.borderaxespad')
+ self.columnspacing = mpl._val_or_rc(columnspacing, 'legend.columnspacing')
+ self.shadow = mpl._val_or_rc(shadow, 'legend.shadow')
+
+ if reverse:
+ labels = [*reversed(labels)]
+ handles = [*reversed(handles)]
+
+ handles = list(handles)
+ if len(handles) < 2:
+ ncols = 1
+ self._ncols = ncols if ncols != 1 else ncol
+
+ if self.numpoints <= 0:
+ raise ValueError("numpoints must be > 0; it was %d" % numpoints)
+
+ # introduce y-offset for handles of the scatter plot
+ if scatteryoffsets is None:
+ self._scatteryoffsets = np.array([3. / 8., 4. / 8., 2.5 / 8.])
+ else:
+ self._scatteryoffsets = np.asarray(scatteryoffsets)
+ reps = self.scatterpoints // len(self._scatteryoffsets) + 1
+ self._scatteryoffsets = np.tile(self._scatteryoffsets,
+ reps)[:self.scatterpoints]
+
+ # _legend_box is a VPacker instance that contains all
+ # legend items and will be initialized from _init_legend_box()
+ # method.
+ self._legend_box = None
+
+ if isinstance(parent, Axes):
+ self.isaxes = True
+ self.axes = parent
+ self.set_figure(parent.get_figure(root=False))
+ elif isinstance(parent, FigureBase):
+ self.isaxes = False
+ self.set_figure(parent)
+ else:
+ raise TypeError(
+ "Legend needs either Axes or FigureBase as parent"
+ )
+ self.parent = parent
+
+ self._mode = mode
+ self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
+
+ # Figure out if self.shadow is valid
+ # If shadow was None, rcParams loads False
+ # So it shouldn't be None here
+
+ self._shadow_props = {'ox': 2, 'oy': -2} # default location offsets
+ if isinstance(self.shadow, dict):
+ self._shadow_props.update(self.shadow)
+ self.shadow = True
+ elif self.shadow in (0, 1, True, False):
+ self.shadow = bool(self.shadow)
+ else:
+ raise ValueError(
+ 'Legend shadow must be a dict or bool, not '
+ f'{self.shadow!r} of type {type(self.shadow)}.'
+ )
+
+ # We use FancyBboxPatch to draw a legend frame. The location
+ # and size of the box will be updated during the drawing time.
+
+ facecolor = mpl._val_or_rc(facecolor, "legend.facecolor")
+ if facecolor == 'inherit':
+ facecolor = mpl.rcParams["axes.facecolor"]
+
+ edgecolor = mpl._val_or_rc(edgecolor, "legend.edgecolor")
+ if edgecolor == 'inherit':
+ edgecolor = mpl.rcParams["axes.edgecolor"]
+
+ fancybox = mpl._val_or_rc(fancybox, "legend.fancybox")
+
+ self.legendPatch = FancyBboxPatch(
+ xy=(0, 0), width=1, height=1,
+ facecolor=facecolor, edgecolor=edgecolor,
+ # If shadow is used, default to alpha=1 (#8943).
+ alpha=(framealpha if framealpha is not None
+ else 1 if shadow
+ else mpl.rcParams["legend.framealpha"]),
+ # The width and height of the legendPatch will be set (in draw())
+ # to the length that includes the padding. Thus we set pad=0 here.
+ boxstyle=("round,pad=0,rounding_size=0.2" if fancybox
+ else "square,pad=0"),
+ mutation_scale=self._fontsize,
+ snap=True,
+ visible=mpl._val_or_rc(frameon, "legend.frameon")
+ )
+ self._set_artist_props(self.legendPatch)
+
+ _api.check_in_list(["center", "left", "right"], alignment=alignment)
+ self._alignment = alignment
+
+ # init with null renderer
+ self._init_legend_box(handles, labels, markerfirst)
+
+ # Set legend location
+ self.set_loc(loc)
+
+ # figure out title font properties:
+ if title_fontsize is not None and title_fontproperties is not None:
+ raise ValueError(
+ "title_fontsize and title_fontproperties can't be specified "
+ "at the same time. Only use one of them. ")
+ title_prop_fp = FontProperties._from_any(title_fontproperties)
+ if isinstance(title_fontproperties, dict):
+ if "size" not in title_fontproperties:
+ title_fontsize = mpl.rcParams["legend.title_fontsize"]
+ title_prop_fp.set_size(title_fontsize)
+ elif title_fontsize is not None:
+ title_prop_fp.set_size(title_fontsize)
+ elif not isinstance(title_fontproperties, FontProperties):
+ title_fontsize = mpl.rcParams["legend.title_fontsize"]
+ title_prop_fp.set_size(title_fontsize)
+
+ self.set_title(title, prop=title_prop_fp)
+
+ self._draggable = None
+ self.set_draggable(state=draggable)
+
+ # set the text color
+
+ color_getters = { # getter function depends on line or patch
+ 'linecolor': ['get_color', 'get_facecolor'],
+ 'markerfacecolor': ['get_markerfacecolor', 'get_facecolor'],
+ 'mfc': ['get_markerfacecolor', 'get_facecolor'],
+ 'markeredgecolor': ['get_markeredgecolor', 'get_edgecolor'],
+ 'mec': ['get_markeredgecolor', 'get_edgecolor'],
+ }
+ labelcolor = mpl._val_or_rc(labelcolor, 'legend.labelcolor')
+ if labelcolor is None:
+ labelcolor = mpl.rcParams['text.color']
+ if isinstance(labelcolor, str) and labelcolor in color_getters:
+ getter_names = color_getters[labelcolor]
+ for handle, text in zip(self.legend_handles, self.texts):
+ try:
+ if handle.get_array() is not None:
+ continue
+ except AttributeError:
+ pass
+ for getter_name in getter_names:
+ try:
+ color = getattr(handle, getter_name)()
+ if isinstance(color, np.ndarray):
+ if (
+ color.shape[0] == 1
+ or np.isclose(color, color[0]).all()
+ ):
+ text.set_color(color[0])
+ else:
+ pass
+ else:
+ text.set_color(color)
+ break
+ except AttributeError:
+ pass
+ elif cbook._str_equal(labelcolor, 'none'):
+ for text in self.texts:
+ text.set_color(labelcolor)
+ elif np.iterable(labelcolor):
+ for text, color in zip(self.texts,
+ itertools.cycle(
+ colors.to_rgba_array(labelcolor))):
+ text.set_color(color)
+ else:
+ raise ValueError(f"Invalid labelcolor: {labelcolor!r}")
+
+ def _set_artist_props(self, a):
+ """
+ Set the boilerplate props for artists added to Axes.
+ """
+ a.set_figure(self.get_figure(root=False))
+ if self.isaxes:
+ a.axes = self.axes
+
+ a.set_transform(self.get_transform())
+
+ @_docstring.interpd
+ def set_loc(self, loc=None):
+ """
+ Set the location of the legend.
+
+ .. versionadded:: 3.8
+
+ Parameters
+ ----------
+ %(_legend_kw_set_loc_doc)s
+ """
+ loc0 = loc
+ self._loc_used_default = loc is None
+ if loc is None:
+ loc = mpl.rcParams["legend.loc"]
+ if not self.isaxes and loc in [0, 'best']:
+ loc = 'upper right'
+
+ type_err_message = ("loc must be string, coordinate tuple, or"
+ f" an integer 0-10, not {loc!r}")
+
+ # handle outside legends:
+ self._outside_loc = None
+ if isinstance(loc, str):
+ if loc.split()[0] == 'outside':
+ # strip outside:
+ loc = loc.split('outside ')[1]
+ # strip "center" at the beginning
+ self._outside_loc = loc.replace('center ', '')
+ # strip first
+ self._outside_loc = self._outside_loc.split()[0]
+ locs = loc.split()
+ if len(locs) > 1 and locs[0] in ('right', 'left'):
+ # locs doesn't accept "left upper", etc, so swap
+ if locs[0] != 'center':
+ locs = locs[::-1]
+ loc = locs[0] + ' ' + locs[1]
+ # check that loc is in acceptable strings
+ loc = _api.check_getitem(self.codes, loc=loc)
+ elif np.iterable(loc):
+ # coerce iterable into tuple
+ loc = tuple(loc)
+ # validate the tuple represents Real coordinates
+ if len(loc) != 2 or not all(isinstance(e, numbers.Real) for e in loc):
+ raise ValueError(type_err_message)
+ elif isinstance(loc, int):
+ # validate the integer represents a string numeric value
+ if loc < 0 or loc > 10:
+ raise ValueError(type_err_message)
+ else:
+ # all other cases are invalid values of loc
+ raise ValueError(type_err_message)
+
+ if self.isaxes and self._outside_loc:
+ raise ValueError(
+ f"'outside' option for loc='{loc0}' keyword argument only "
+ "works for figure legends")
+
+ if not self.isaxes and loc == 0:
+ raise ValueError(
+ "Automatic legend placement (loc='best') not implemented for "
+ "figure legend")
+
+ tmp = self._loc_used_default
+ self._set_loc(loc)
+ self._loc_used_default = tmp # ignore changes done by _set_loc
+
+ def _set_loc(self, loc):
+ # find_offset function will be provided to _legend_box and
+ # _legend_box will draw itself at the location of the return
+ # value of the find_offset.
+ self._loc_used_default = False
+ self._loc_real = loc
+ self.stale = True
+ self._legend_box.set_offset(self._findoffset)
+
+ def set_ncols(self, ncols):
+ """Set the number of columns."""
+ self._ncols = ncols
+
+ def _get_loc(self):
+ return self._loc_real
+
+ _loc = property(_get_loc, _set_loc)
+
+ def _findoffset(self, width, height, xdescent, ydescent, renderer):
+ """Helper function to locate the legend."""
+
+ if self._loc == 0: # "best".
+ x, y = self._find_best_position(width, height, renderer)
+ elif self._loc in Legend.codes.values(): # Fixed location.
+ bbox = Bbox.from_bounds(0, 0, width, height)
+ x, y = self._get_anchored_bbox(self._loc, bbox,
+ self.get_bbox_to_anchor(),
+ renderer)
+ else: # Axes or figure coordinates.
+ fx, fy = self._loc
+ bbox = self.get_bbox_to_anchor()
+ x, y = bbox.x0 + bbox.width * fx, bbox.y0 + bbox.height * fy
+
+ return x + xdescent, y + ydescent
+
+ @allow_rasterization
+ def draw(self, renderer):
+ # docstring inherited
+ if not self.get_visible():
+ return
+
+ renderer.open_group('legend', gid=self.get_gid())
+
+ fontsize = renderer.points_to_pixels(self._fontsize)
+
+ # if mode == fill, set the width of the legend_box to the
+ # width of the parent (minus pads)
+ if self._mode in ["expand"]:
+ pad = 2 * (self.borderaxespad + self.borderpad) * fontsize
+ self._legend_box.set_width(self.get_bbox_to_anchor().width - pad)
+
+ # update the location and size of the legend. This needs to
+ # be done in any case to clip the figure right.
+ bbox = self._legend_box.get_window_extent(renderer)
+ self.legendPatch.set_bounds(bbox.bounds)
+ self.legendPatch.set_mutation_scale(fontsize)
+
+ # self.shadow is validated in __init__
+ # So by here it is a bool and self._shadow_props contains any configs
+
+ if self.shadow:
+ Shadow(self.legendPatch, **self._shadow_props).draw(renderer)
+
+ self.legendPatch.draw(renderer)
+ self._legend_box.draw(renderer)
+
+ renderer.close_group('legend')
+ self.stale = False
+
+ # _default_handler_map defines the default mapping between plot
+ # elements and the legend handlers.
+
+ _default_handler_map = {
+ StemContainer: legend_handler.HandlerStem(),
+ ErrorbarContainer: legend_handler.HandlerErrorbar(),
+ Line2D: legend_handler.HandlerLine2D(),
+ Patch: legend_handler.HandlerPatch(),
+ StepPatch: legend_handler.HandlerStepPatch(),
+ LineCollection: legend_handler.HandlerLineCollection(),
+ RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(),
+ CircleCollection: legend_handler.HandlerCircleCollection(),
+ BarContainer: legend_handler.HandlerPatch(
+ update_func=legend_handler.update_from_first_child),
+ tuple: legend_handler.HandlerTuple(),
+ PathCollection: legend_handler.HandlerPathCollection(),
+ PolyCollection: legend_handler.HandlerPolyCollection()
+ }
+
+ # (get|set|update)_default_handler_maps are public interfaces to
+ # modify the default handler map.
+
+ @classmethod
+ def get_default_handler_map(cls):
+ """Return the global default handler map, shared by all legends."""
+ return cls._default_handler_map
+
+ @classmethod
+ def set_default_handler_map(cls, handler_map):
+ """Set the global default handler map, shared by all legends."""
+ cls._default_handler_map = handler_map
+
+ @classmethod
+ def update_default_handler_map(cls, handler_map):
+ """Update the global default handler map, shared by all legends."""
+ cls._default_handler_map.update(handler_map)
+
+ def get_legend_handler_map(self):
+ """Return this legend instance's handler map."""
+ default_handler_map = self.get_default_handler_map()
+ return ({**default_handler_map, **self._custom_handler_map}
+ if self._custom_handler_map else default_handler_map)
+
+ @staticmethod
+ def get_legend_handler(legend_handler_map, orig_handle):
+ """
+ Return a legend handler from *legend_handler_map* that
+ corresponds to *orig_handler*.
+
+ *legend_handler_map* should be a dictionary object (that is
+ returned by the get_legend_handler_map method).
+
+ It first checks if the *orig_handle* itself is a key in the
+ *legend_handler_map* and return the associated value.
+ Otherwise, it checks for each of the classes in its
+ method-resolution-order. If no matching key is found, it
+ returns ``None``.
+ """
+ try:
+ return legend_handler_map[orig_handle]
+ except (TypeError, KeyError): # TypeError if unhashable.
+ pass
+ for handle_type in type(orig_handle).mro():
+ try:
+ return legend_handler_map[handle_type]
+ except KeyError:
+ pass
+ return None
+
+ def _init_legend_box(self, handles, labels, markerfirst=True):
+ """
+ Initialize the legend_box. The legend_box is an instance of
+ the OffsetBox, which is packed with legend handles and
+ texts. Once packed, their location is calculated during the
+ drawing time.
+ """
+
+ fontsize = self._fontsize
+
+ # legend_box is a HPacker, horizontally packed with columns.
+ # Each column is a VPacker, vertically packed with legend items.
+ # Each legend item is a HPacker packed with:
+ # - handlebox: a DrawingArea which contains the legend handle.
+ # - labelbox: a TextArea which contains the legend text.
+
+ text_list = [] # the list of text instances
+ handle_list = [] # the list of handle instances
+ handles_and_labels = []
+
+ # The approximate height and descent of text. These values are
+ # only used for plotting the legend handle.
+ descent = 0.35 * fontsize * (self.handleheight - 0.7) # heuristic.
+ height = fontsize * self.handleheight - descent
+ # each handle needs to be drawn inside a box of (x, y, w, h) =
+ # (0, -descent, width, height). And their coordinates should
+ # be given in the display coordinates.
+
+ # The transformation of each handle will be automatically set
+ # to self.get_transform(). If the artist does not use its
+ # default transform (e.g., Collections), you need to
+ # manually set their transform to the self.get_transform().
+ legend_handler_map = self.get_legend_handler_map()
+
+ for orig_handle, label in zip(handles, labels):
+ handler = self.get_legend_handler(legend_handler_map, orig_handle)
+ if handler is None:
+ _api.warn_external(
+ "Legend does not support handles for "
+ f"{type(orig_handle).__name__} "
+ "instances.\nA proxy artist may be used "
+ "instead.\nSee: https://matplotlib.org/"
+ "stable/users/explain/axes/legend_guide.html"
+ "#controlling-the-legend-entries")
+ # No handle for this artist, so we just defer to None.
+ handle_list.append(None)
+ else:
+ textbox = TextArea(label, multilinebaseline=True,
+ textprops=dict(
+ verticalalignment='baseline',
+ horizontalalignment='left',
+ fontproperties=self.prop))
+ handlebox = DrawingArea(width=self.handlelength * fontsize,
+ height=height,
+ xdescent=0., ydescent=descent)
+
+ text_list.append(textbox._text)
+ # Create the artist for the legend which represents the
+ # original artist/handle.
+ handle_list.append(handler.legend_artist(self, orig_handle,
+ fontsize, handlebox))
+ handles_and_labels.append((handlebox, textbox))
+
+ columnbox = []
+ # array_split splits n handles_and_labels into ncols columns, with the
+ # first n%ncols columns having an extra entry. filter(len, ...)
+ # handles the case where n < ncols: the last ncols-n columns are empty
+ # and get filtered out.
+ for handles_and_labels_column in filter(
+ len, np.array_split(handles_and_labels, self._ncols)):
+ # pack handlebox and labelbox into itembox
+ itemboxes = [HPacker(pad=0,
+ sep=self.handletextpad * fontsize,
+ children=[h, t] if markerfirst else [t, h],
+ align="baseline")
+ for h, t in handles_and_labels_column]
+ # pack columnbox
+ alignment = "baseline" if markerfirst else "right"
+ columnbox.append(VPacker(pad=0,
+ sep=self.labelspacing * fontsize,
+ align=alignment,
+ children=itemboxes))
+
+ mode = "expand" if self._mode == "expand" else "fixed"
+ sep = self.columnspacing * fontsize
+ self._legend_handle_box = HPacker(pad=0,
+ sep=sep, align="baseline",
+ mode=mode,
+ children=columnbox)
+ self._legend_title_box = TextArea("")
+ self._legend_box = VPacker(pad=self.borderpad * fontsize,
+ sep=self.labelspacing * fontsize,
+ align=self._alignment,
+ children=[self._legend_title_box,
+ self._legend_handle_box])
+ self._legend_box.set_figure(self.get_figure(root=False))
+ self._legend_box.axes = self.axes
+ self.texts = text_list
+ self.legend_handles = handle_list
+
+ def _auto_legend_data(self, renderer):
+ """
+ Return display coordinates for hit testing for "best" positioning.
+
+ Returns
+ -------
+ bboxes
+ List of bounding boxes of all patches.
+ lines
+ List of `.Path` corresponding to each line.
+ offsets
+ List of (x, y) offsets of all collection.
+ """
+ assert self.isaxes # always holds, as this is only called internally
+ bboxes = []
+ lines = []
+ offsets = []
+ for artist in self.parent._children:
+ if isinstance(artist, Line2D):
+ lines.append(
+ artist.get_transform().transform_path(artist.get_path()))
+ elif isinstance(artist, Rectangle):
+ bboxes.append(
+ artist.get_bbox().transformed(artist.get_data_transform()))
+ elif isinstance(artist, Patch):
+ lines.append(
+ artist.get_transform().transform_path(artist.get_path()))
+ elif isinstance(artist, PolyCollection):
+ lines.extend(artist.get_transform().transform_path(path)
+ for path in artist.get_paths())
+ elif isinstance(artist, Collection):
+ transform, transOffset, hoffsets, _ = artist._prepare_points()
+ if len(hoffsets):
+ offsets.extend(transOffset.transform(hoffsets))
+ elif isinstance(artist, Text):
+ bboxes.append(artist.get_window_extent(renderer))
+
+ return bboxes, lines, offsets
+
+ def get_children(self):
+ # docstring inherited
+ return [self._legend_box, self.get_frame()]
+
+ def get_frame(self):
+ """Return the `~.patches.Rectangle` used to frame the legend."""
+ return self.legendPatch
+
+ def get_lines(self):
+ r"""Return the list of `~.lines.Line2D`\s in the legend."""
+ return [h for h in self.legend_handles if isinstance(h, Line2D)]
+
+ def get_patches(self):
+ r"""Return the list of `~.patches.Patch`\s in the legend."""
+ return silent_list('Patch',
+ [h for h in self.legend_handles
+ if isinstance(h, Patch)])
+
+ def get_texts(self):
+ r"""Return the list of `~.text.Text`\s in the legend."""
+ return silent_list('Text', self.texts)
+
+ def set_alignment(self, alignment):
+ """
+ Set the alignment of the legend title and the box of entries.
+
+ The entries are aligned as a single block, so that markers always
+ lined up.
+
+ Parameters
+ ----------
+ alignment : {'center', 'left', 'right'}.
+
+ """
+ _api.check_in_list(["center", "left", "right"], alignment=alignment)
+ self._alignment = alignment
+ self._legend_box.align = alignment
+
+ def get_alignment(self):
+ """Get the alignment value of the legend box"""
+ return self._legend_box.align
+
+ def set_title(self, title, prop=None):
+ """
+ Set legend title and title style.
+
+ Parameters
+ ----------
+ title : str
+ The legend title.
+
+ prop : `.font_manager.FontProperties` or `str` or `pathlib.Path`
+ The font properties of the legend title.
+ If a `str`, it is interpreted as a fontconfig pattern parsed by
+ `.FontProperties`. If a `pathlib.Path`, it is interpreted as the
+ absolute path to a font file.
+
+ """
+ self._legend_title_box._text.set_text(title)
+ if title:
+ self._legend_title_box._text.set_visible(True)
+ self._legend_title_box.set_visible(True)
+ else:
+ self._legend_title_box._text.set_visible(False)
+ self._legend_title_box.set_visible(False)
+
+ if prop is not None:
+ self._legend_title_box._text.set_fontproperties(prop)
+
+ self.stale = True
+
+ def get_title(self):
+ """Return the `.Text` instance for the legend title."""
+ return self._legend_title_box._text
+
+ def get_window_extent(self, renderer=None):
+ # docstring inherited
+ if renderer is None:
+ renderer = self.get_figure(root=True)._get_renderer()
+ return self._legend_box.get_window_extent(renderer=renderer)
+
+ def get_tightbbox(self, renderer=None):
+ # docstring inherited
+ return self._legend_box.get_window_extent(renderer)
+
+ def get_frame_on(self):
+ """Get whether the legend box patch is drawn."""
+ return self.legendPatch.get_visible()
+
+ def set_frame_on(self, b):
+ """
+ Set whether the legend box patch is drawn.
+
+ Parameters
+ ----------
+ b : bool
+ """
+ self.legendPatch.set_visible(b)
+ self.stale = True
+
+ draw_frame = set_frame_on # Backcompat alias.
+
+ def get_bbox_to_anchor(self):
+ """Return the bbox that the legend will be anchored to."""
+ if self._bbox_to_anchor is None:
+ return self.parent.bbox
+ else:
+ return self._bbox_to_anchor
+
+ def set_bbox_to_anchor(self, bbox, transform=None):
+ """
+ Set the bbox that the legend will be anchored to.
+
+ Parameters
+ ----------
+ bbox : `~matplotlib.transforms.BboxBase` or tuple
+ The bounding box can be specified in the following ways:
+
+ - A `.BboxBase` instance
+ - A tuple of ``(left, bottom, width, height)`` in the given
+ transform (normalized axes coordinate if None)
+ - A tuple of ``(left, bottom)`` where the width and height will be
+ assumed to be zero.
+ - *None*, to remove the bbox anchoring, and use the parent bbox.
+
+ transform : `~matplotlib.transforms.Transform`, optional
+ A transform to apply to the bounding box. If not specified, this
+ will use a transform to the bounding box of the parent.
+ """
+ if bbox is None:
+ self._bbox_to_anchor = None
+ return
+ elif isinstance(bbox, BboxBase):
+ self._bbox_to_anchor = bbox
+ else:
+ try:
+ l = len(bbox)
+ except TypeError as err:
+ raise ValueError(f"Invalid bbox: {bbox}") from err
+
+ if l == 2:
+ bbox = [bbox[0], bbox[1], 0, 0]
+
+ self._bbox_to_anchor = Bbox.from_bounds(*bbox)
+
+ if transform is None:
+ transform = BboxTransformTo(self.parent.bbox)
+
+ self._bbox_to_anchor = TransformedBbox(self._bbox_to_anchor,
+ transform)
+ self.stale = True
+
+ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer):
+ """
+ Place the *bbox* inside the *parentbbox* according to a given
+ location code. Return the (x, y) coordinate of the bbox.
+
+ Parameters
+ ----------
+ loc : int
+ A location code in range(1, 11). This corresponds to the possible
+ values for ``self._loc``, excluding "best".
+ bbox : `~matplotlib.transforms.Bbox`
+ bbox to be placed, in display coordinates.
+ parentbbox : `~matplotlib.transforms.Bbox`
+ A parent box which will contain the bbox, in display coordinates.
+ """
+ return offsetbox._get_anchored_bbox(
+ loc, bbox, parentbbox,
+ self.borderaxespad * renderer.points_to_pixels(self._fontsize))
+
+ def _find_best_position(self, width, height, renderer):
+ """Determine the best location to place the legend."""
+ assert self.isaxes # always holds, as this is only called internally
+
+ start_time = time.perf_counter()
+
+ bboxes, lines, offsets = self._auto_legend_data(renderer)
+
+ bbox = Bbox.from_bounds(0, 0, width, height)
+
+ candidates = []
+ for idx in range(1, len(self.codes)):
+ l, b = self._get_anchored_bbox(idx, bbox,
+ self.get_bbox_to_anchor(),
+ renderer)
+ legendBox = Bbox.from_bounds(l, b, width, height)
+ # XXX TODO: If markers are present, it would be good to take them
+ # into account when checking vertex overlaps in the next line.
+ badness = (sum(legendBox.count_contains(line.vertices)
+ for line in lines)
+ + legendBox.count_contains(offsets)
+ + legendBox.count_overlaps(bboxes)
+ + sum(line.intersects_bbox(legendBox, filled=False)
+ for line in lines))
+ # Include the index to favor lower codes in case of a tie.
+ candidates.append((badness, idx, (l, b)))
+ if badness == 0:
+ break
+
+ _, _, (l, b) = min(candidates)
+
+ if self._loc_used_default and time.perf_counter() - start_time > 1:
+ _api.warn_external(
+ 'Creating legend with loc="best" can be slow with large '
+ 'amounts of data.')
+
+ return l, b
+
+ def contains(self, mouseevent):
+ return self.legendPatch.contains(mouseevent)
+
+ def set_draggable(self, state, use_blit=False, update='loc'):
+ """
+ Enable or disable mouse dragging support of the legend.
+
+ Parameters
+ ----------
+ state : bool
+ Whether mouse dragging is enabled.
+ use_blit : bool, optional
+ Use blitting for faster image composition. For details see
+ :ref:`func-animation`.
+ update : {'loc', 'bbox'}, optional
+ The legend parameter to be changed when dragged:
+
+ - 'loc': update the *loc* parameter of the legend
+ - 'bbox': update the *bbox_to_anchor* parameter of the legend
+
+ Returns
+ -------
+ `.DraggableLegend` or *None*
+ If *state* is ``True`` this returns the `.DraggableLegend` helper
+ instance. Otherwise this returns *None*.
+ """
+ if state:
+ if self._draggable is None:
+ self._draggable = DraggableLegend(self,
+ use_blit,
+ update=update)
+ else:
+ if self._draggable is not None:
+ self._draggable.disconnect()
+ self._draggable = None
+ return self._draggable
+
+ def get_draggable(self):
+ """Return ``True`` if the legend is draggable, ``False`` otherwise."""
+ return self._draggable is not None
+
+
+# Helper functions to parse legend arguments for both `figure.legend` and
+# `axes.legend`:
+def _get_legend_handles(axs, legend_handler_map=None):
+ """Yield artists that can be used as handles in a legend."""
+ handles_original = []
+ for ax in axs:
+ handles_original += [
+ *(a for a in ax._children
+ if isinstance(a, (Line2D, Patch, Collection, Text))),
+ *ax.containers]
+ # support parasite Axes:
+ if hasattr(ax, 'parasites'):
+ for axx in ax.parasites:
+ handles_original += [
+ *(a for a in axx._children
+ if isinstance(a, (Line2D, Patch, Collection, Text))),
+ *axx.containers]
+
+ handler_map = {**Legend.get_default_handler_map(),
+ **(legend_handler_map or {})}
+ has_handler = Legend.get_legend_handler
+ for handle in handles_original:
+ label = handle.get_label()
+ if label != '_nolegend_' and has_handler(handler_map, handle):
+ yield handle
+ elif (label and not label.startswith('_') and
+ not has_handler(handler_map, handle)):
+ _api.warn_external(
+ "Legend does not support handles for "
+ f"{type(handle).__name__} "
+ "instances.\nSee: https://matplotlib.org/stable/"
+ "tutorials/intermediate/legend_guide.html"
+ "#implementing-a-custom-legend-handler")
+ continue
+
+
+def _get_legend_handles_labels(axs, legend_handler_map=None):
+ """Return handles and labels for legend."""
+ handles = []
+ labels = []
+ for handle in _get_legend_handles(axs, legend_handler_map):
+ label = handle.get_label()
+ if label and not label.startswith('_'):
+ handles.append(handle)
+ labels.append(label)
+ return handles, labels
+
+
+def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs):
+ """
+ Get the handles and labels from the calls to either ``figure.legend``
+ or ``axes.legend``.
+
+ The parser is a bit involved because we support::
+
+ legend()
+ legend(labels)
+ legend(handles, labels)
+ legend(labels=labels)
+ legend(handles=handles)
+ legend(handles=handles, labels=labels)
+
+ The behavior for a mixture of positional and keyword handles and labels
+ is undefined and issues a warning; it will be an error in the future.
+
+ Parameters
+ ----------
+ axs : list of `.Axes`
+ If handles are not given explicitly, the artists in these Axes are
+ used as handles.
+ *args : tuple
+ Positional parameters passed to ``legend()``.
+ handles
+ The value of the keyword argument ``legend(handles=...)``, or *None*
+ if that keyword argument was not used.
+ labels
+ The value of the keyword argument ``legend(labels=...)``, or *None*
+ if that keyword argument was not used.
+ **kwargs
+ All other keyword arguments passed to ``legend()``.
+
+ Returns
+ -------
+ handles : list of (`.Artist` or tuple of `.Artist`)
+ The legend handles.
+ labels : list of str
+ The legend labels.
+ kwargs : dict
+ *kwargs* with keywords handles and labels removed.
+
+ """
+ log = logging.getLogger(__name__)
+
+ handlers = kwargs.get('handler_map')
+
+ if (handles is not None or labels is not None) and args:
+ _api.warn_deprecated("3.9", message=(
+ "You have mixed positional and keyword arguments, some input may "
+ "be discarded. This is deprecated since %(since)s and will "
+ "become an error in %(removal)s."))
+
+ if (hasattr(handles, "__len__") and
+ hasattr(labels, "__len__") and
+ len(handles) != len(labels)):
+ _api.warn_external(f"Mismatched number of handles and labels: "
+ f"len(handles) = {len(handles)} "
+ f"len(labels) = {len(labels)}")
+ # if got both handles and labels as kwargs, make same length
+ if handles and labels:
+ handles, labels = zip(*zip(handles, labels))
+
+ elif handles is not None and labels is None:
+ labels = [handle.get_label() for handle in handles]
+
+ elif labels is not None and handles is None:
+ # Get as many handles as there are labels.
+ handles = [handle for handle, label
+ in zip(_get_legend_handles(axs, handlers), labels)]
+
+ elif len(args) == 0: # 0 args: automatically detect labels and handles.
+ handles, labels = _get_legend_handles_labels(axs, handlers)
+ if not handles:
+ _api.warn_external(
+ "No artists with labels found to put in legend. Note that "
+ "artists whose label start with an underscore are ignored "
+ "when legend() is called with no argument.")
+
+ elif len(args) == 1: # 1 arg: user defined labels, automatic handle detection.
+ labels, = args
+ if any(isinstance(l, Artist) for l in labels):
+ raise TypeError("A single argument passed to legend() must be a "
+ "list of labels, but found an Artist in there.")
+
+ # Get as many handles as there are labels.
+ handles = [handle for handle, label
+ in zip(_get_legend_handles(axs, handlers), labels)]
+
+ elif len(args) == 2: # 2 args: user defined handles and labels.
+ handles, labels = args[:2]
+
+ else:
+ raise _api.nargs_error('legend', '0-2', len(args))
+
+ return handles, labels, kwargs
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..dde5882da69d0c866586ad199ed4c3012adc1f01
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.pyi
@@ -0,0 +1,152 @@
+from matplotlib.axes import Axes
+from matplotlib.artist import Artist
+from matplotlib.backend_bases import MouseEvent
+from matplotlib.figure import Figure
+from matplotlib.font_manager import FontProperties
+from matplotlib.legend_handler import HandlerBase
+from matplotlib.lines import Line2D
+from matplotlib.offsetbox import (
+ DraggableOffsetBox,
+)
+from matplotlib.patches import FancyBboxPatch, Patch, Rectangle
+from matplotlib.text import Text
+from matplotlib.transforms import (
+ BboxBase,
+ Transform,
+)
+
+
+import pathlib
+from collections.abc import Iterable
+from typing import Any, Literal, overload
+from .typing import ColorType
+
+class DraggableLegend(DraggableOffsetBox):
+ legend: Legend
+ def __init__(
+ self, legend: Legend, use_blit: bool = ..., update: Literal["loc", "bbox"] = ...
+ ) -> None: ...
+ def finalize_offset(self) -> None: ...
+
+class Legend(Artist):
+ codes: dict[str, int]
+ zorder: float
+ prop: FontProperties
+ texts: list[Text]
+ legend_handles: list[Artist | None]
+ numpoints: int
+ markerscale: float
+ scatterpoints: int
+ borderpad: float
+ labelspacing: float
+ handlelength: float
+ handleheight: float
+ handletextpad: float
+ borderaxespad: float
+ columnspacing: float
+ shadow: bool
+ isaxes: bool
+ axes: Axes
+ parent: Axes | Figure
+ legendPatch: FancyBboxPatch
+ def __init__(
+ self,
+ parent: Axes | Figure,
+ handles: Iterable[Artist | tuple[Artist, ...]],
+ labels: Iterable[str],
+ *,
+ loc: str | tuple[float, float] | int | None = ...,
+ numpoints: int | None = ...,
+ markerscale: float | None = ...,
+ markerfirst: bool = ...,
+ reverse: bool = ...,
+ scatterpoints: int | None = ...,
+ scatteryoffsets: Iterable[float] | None = ...,
+ prop: FontProperties | dict[str, Any] | None = ...,
+ fontsize: float | str | None = ...,
+ labelcolor: ColorType
+ | Iterable[ColorType]
+ | Literal["linecolor", "markerfacecolor", "mfc", "markeredgecolor", "mec"]
+ | None = ...,
+ borderpad: float | None = ...,
+ labelspacing: float | None = ...,
+ handlelength: float | None = ...,
+ handleheight: float | None = ...,
+ handletextpad: float | None = ...,
+ borderaxespad: float | None = ...,
+ columnspacing: float | None = ...,
+ ncols: int = ...,
+ mode: Literal["expand"] | None = ...,
+ fancybox: bool | None = ...,
+ shadow: bool | dict[str, Any] | None = ...,
+ title: str | None = ...,
+ title_fontsize: float | None = ...,
+ framealpha: float | None = ...,
+ edgecolor: Literal["inherit"] | ColorType | None = ...,
+ facecolor: Literal["inherit"] | ColorType | None = ...,
+ bbox_to_anchor: BboxBase
+ | tuple[float, float]
+ | tuple[float, float, float, float]
+ | None = ...,
+ bbox_transform: Transform | None = ...,
+ frameon: bool | None = ...,
+ handler_map: dict[Artist | type, HandlerBase] | None = ...,
+ title_fontproperties: FontProperties | dict[str, Any] | None = ...,
+ alignment: Literal["center", "left", "right"] = ...,
+ ncol: int = ...,
+ draggable: bool = ...
+ ) -> None: ...
+ def contains(self, mouseevent: MouseEvent) -> tuple[bool, dict[Any, Any]]: ...
+ def set_ncols(self, ncols: int) -> None: ...
+ @classmethod
+ def get_default_handler_map(cls) -> dict[type, HandlerBase]: ...
+ @classmethod
+ def set_default_handler_map(cls, handler_map: dict[type, HandlerBase]) -> None: ...
+ @classmethod
+ def update_default_handler_map(
+ cls, handler_map: dict[type, HandlerBase]
+ ) -> None: ...
+ def get_legend_handler_map(self) -> dict[type, HandlerBase]: ...
+ @staticmethod
+ def get_legend_handler(
+ legend_handler_map: dict[type, HandlerBase], orig_handle: Any
+ ) -> HandlerBase | None: ...
+ def get_children(self) -> list[Artist]: ...
+ def get_frame(self) -> Rectangle: ...
+ def get_lines(self) -> list[Line2D]: ...
+ def get_patches(self) -> list[Patch]: ...
+ def get_texts(self) -> list[Text]: ...
+ def set_alignment(self, alignment: Literal["center", "left", "right"]) -> None: ...
+ def get_alignment(self) -> Literal["center", "left", "right"]: ...
+ def set_loc(self, loc: str | tuple[float, float] | int | None = ...) -> None: ...
+ def set_title(
+ self, title: str, prop: FontProperties | str | pathlib.Path | None = ...
+ ) -> None: ...
+ def get_title(self) -> Text: ...
+ def get_frame_on(self) -> bool: ...
+ def set_frame_on(self, b: bool) -> None: ...
+ draw_frame = set_frame_on
+ def get_bbox_to_anchor(self) -> BboxBase: ...
+ def set_bbox_to_anchor(
+ self,
+ bbox: BboxBase
+ | tuple[float, float]
+ | tuple[float, float, float, float]
+ | None,
+ transform: Transform | None = ...
+ ) -> None: ...
+ @overload
+ def set_draggable(
+ self,
+ state: Literal[True],
+ use_blit: bool = ...,
+ update: Literal["loc", "bbox"] = ...,
+ ) -> DraggableLegend: ...
+ @overload
+ def set_draggable(
+ self,
+ state: Literal[False],
+ use_blit: bool = ...,
+ update: Literal["loc", "bbox"] = ...,
+ ) -> None: ...
+ def get_draggable(self) -> bool: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7c9c419622915b9834a5cb25a663f5d7ea23f43
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.py
@@ -0,0 +1,812 @@
+"""
+Default legend handlers.
+
+.. important::
+
+ This is a low-level legend API, which most end users do not need.
+
+ We recommend that you are familiar with the :ref:`legend guide
+ ` before reading this documentation.
+
+Legend handlers are expected to be a callable object with a following
+signature::
+
+ legend_handler(legend, orig_handle, fontsize, handlebox)
+
+Where *legend* is the legend itself, *orig_handle* is the original
+plot, *fontsize* is the fontsize in pixels, and *handlebox* is an
+`.OffsetBox` instance. Within the call, you should create relevant
+artists (using relevant properties from the *legend* and/or
+*orig_handle*) and add them into the *handlebox*. The artists need to
+be scaled according to the *fontsize* (note that the size is in pixels,
+i.e., this is dpi-scaled value).
+
+This module includes definition of several legend handler classes
+derived from the base class (HandlerBase) with the following method::
+
+ def legend_artist(self, legend, orig_handle, fontsize, handlebox)
+"""
+
+from itertools import cycle
+
+import numpy as np
+
+from matplotlib import cbook
+from matplotlib.lines import Line2D
+from matplotlib.patches import Rectangle
+import matplotlib.collections as mcoll
+
+
+def update_from_first_child(tgt, src):
+ first_child = next(iter(src.get_children()), None)
+ if first_child is not None:
+ tgt.update_from(first_child)
+
+
+class HandlerBase:
+ """
+ A base class for default legend handlers.
+
+ The derived classes are meant to override *create_artists* method, which
+ has the following signature::
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize,
+ trans):
+
+ The overridden method needs to create artists of the given
+ transform that fits in the given dimension (xdescent, ydescent,
+ width, height) that are scaled by fontsize if necessary.
+
+ """
+ def __init__(self, xpad=0., ypad=0., update_func=None):
+ """
+ Parameters
+ ----------
+ xpad : float, optional
+ Padding in x-direction.
+ ypad : float, optional
+ Padding in y-direction.
+ update_func : callable, optional
+ Function for updating the legend handler properties from another
+ legend handler, used by `~HandlerBase.update_prop`.
+ """
+ self._xpad, self._ypad = xpad, ypad
+ self._update_prop_func = update_func
+
+ def _update_prop(self, legend_handle, orig_handle):
+ if self._update_prop_func is None:
+ self._default_update_prop(legend_handle, orig_handle)
+ else:
+ self._update_prop_func(legend_handle, orig_handle)
+
+ def _default_update_prop(self, legend_handle, orig_handle):
+ legend_handle.update_from(orig_handle)
+
+ def update_prop(self, legend_handle, orig_handle, legend):
+
+ self._update_prop(legend_handle, orig_handle)
+
+ legend._set_artist_props(legend_handle)
+ legend_handle.set_clip_box(None)
+ legend_handle.set_clip_path(None)
+
+ def adjust_drawing_area(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize,
+ ):
+ xdescent = xdescent - self._xpad * fontsize
+ ydescent = ydescent - self._ypad * fontsize
+ width = width - self._xpad * fontsize
+ height = height - self._ypad * fontsize
+ return xdescent, ydescent, width, height
+
+ def legend_artist(self, legend, orig_handle,
+ fontsize, handlebox):
+ """
+ Return the artist that this HandlerBase generates for the given
+ original artist/handle.
+
+ Parameters
+ ----------
+ legend : `~matplotlib.legend.Legend`
+ The legend for which these legend artists are being created.
+ orig_handle : :class:`matplotlib.artist.Artist` or similar
+ The object for which these legend artists are being created.
+ fontsize : int
+ The fontsize in pixels. The artists being created should
+ be scaled according to the given fontsize.
+ handlebox : `~matplotlib.offsetbox.OffsetBox`
+ The box which has been created to hold this legend entry's
+ artists. Artists created in the `legend_artist` method must
+ be added to this handlebox inside this method.
+
+ """
+ xdescent, ydescent, width, height = self.adjust_drawing_area(
+ legend, orig_handle,
+ handlebox.xdescent, handlebox.ydescent,
+ handlebox.width, handlebox.height,
+ fontsize)
+ artists = self.create_artists(legend, orig_handle,
+ xdescent, ydescent, width, height,
+ fontsize, handlebox.get_transform())
+
+ # create_artists will return a list of artists.
+ for a in artists:
+ handlebox.add_artist(a)
+
+ # we only return the first artist
+ return artists[0]
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize,
+ trans):
+ """
+ Return the legend artists generated.
+
+ Parameters
+ ----------
+ legend : `~matplotlib.legend.Legend`
+ The legend for which these legend artists are being created.
+ orig_handle : `~matplotlib.artist.Artist` or similar
+ The object for which these legend artists are being created.
+ xdescent, ydescent, width, height : int
+ The rectangle (*xdescent*, *ydescent*, *width*, *height*) that the
+ legend artists being created should fit within.
+ fontsize : int
+ The fontsize in pixels. The legend artists being created should
+ be scaled according to the given fontsize.
+ trans : `~matplotlib.transforms.Transform`
+ The transform that is applied to the legend artists being created.
+ Typically from unit coordinates in the handler box to screen
+ coordinates.
+ """
+ raise NotImplementedError('Derived must override')
+
+
+class HandlerNpoints(HandlerBase):
+ """
+ A legend handler that shows *numpoints* points in the legend entry.
+ """
+
+ def __init__(self, marker_pad=0.3, numpoints=None, **kwargs):
+ """
+ Parameters
+ ----------
+ marker_pad : float
+ Padding between points in legend entry.
+ numpoints : int
+ Number of points to show in legend entry.
+ **kwargs
+ Keyword arguments forwarded to `.HandlerBase`.
+ """
+ super().__init__(**kwargs)
+
+ self._numpoints = numpoints
+ self._marker_pad = marker_pad
+
+ def get_numpoints(self, legend):
+ if self._numpoints is None:
+ return legend.numpoints
+ else:
+ return self._numpoints
+
+ def get_xdata(self, legend, xdescent, ydescent, width, height, fontsize):
+ numpoints = self.get_numpoints(legend)
+ if numpoints > 1:
+ # we put some pad here to compensate the size of the marker
+ pad = self._marker_pad * fontsize
+ xdata = np.linspace(-xdescent + pad,
+ -xdescent + width - pad,
+ numpoints)
+ xdata_marker = xdata
+ else:
+ xdata = [-xdescent, -xdescent + width]
+ xdata_marker = [-xdescent + 0.5 * width]
+ return xdata, xdata_marker
+
+
+class HandlerNpointsYoffsets(HandlerNpoints):
+ """
+ A legend handler that shows *numpoints* in the legend, and allows them to
+ be individually offset in the y-direction.
+ """
+
+ def __init__(self, numpoints=None, yoffsets=None, **kwargs):
+ """
+ Parameters
+ ----------
+ numpoints : int
+ Number of points to show in legend entry.
+ yoffsets : array of floats
+ Length *numpoints* list of y offsets for each point in
+ legend entry.
+ **kwargs
+ Keyword arguments forwarded to `.HandlerNpoints`.
+ """
+ super().__init__(numpoints=numpoints, **kwargs)
+ self._yoffsets = yoffsets
+
+ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
+ if self._yoffsets is None:
+ ydata = height * legend._scatteryoffsets
+ else:
+ ydata = height * np.asarray(self._yoffsets)
+
+ return ydata
+
+
+class HandlerLine2DCompound(HandlerNpoints):
+ """
+ Original handler for `.Line2D` instances, that relies on combining
+ a line-only with a marker-only artist. May be deprecated in the future.
+ """
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize,
+ trans):
+ # docstring inherited
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
+ width, height, fontsize)
+
+ ydata = np.full_like(xdata, ((height - ydescent) / 2))
+ legline = Line2D(xdata, ydata)
+
+ self.update_prop(legline, orig_handle, legend)
+ legline.set_drawstyle('default')
+ legline.set_marker("")
+
+ legline_marker = Line2D(xdata_marker, ydata[:len(xdata_marker)])
+ self.update_prop(legline_marker, orig_handle, legend)
+ legline_marker.set_linestyle('None')
+ if legend.markerscale != 1:
+ newsz = legline_marker.get_markersize() * legend.markerscale
+ legline_marker.set_markersize(newsz)
+ # we don't want to add this to the return list because
+ # the texts and handles are assumed to be in one-to-one
+ # correspondence.
+ legline._legmarker = legline_marker
+
+ legline.set_transform(trans)
+ legline_marker.set_transform(trans)
+
+ return [legline, legline_marker]
+
+
+class HandlerLine2D(HandlerNpoints):
+ """
+ Handler for `.Line2D` instances.
+
+ See Also
+ --------
+ HandlerLine2DCompound : An earlier handler implementation, which used one
+ artist for the line and another for the marker(s).
+ """
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize,
+ trans):
+ # docstring inherited
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
+ width, height, fontsize)
+
+ markevery = None
+ if self.get_numpoints(legend) == 1:
+ # Special case: one wants a single marker in the center
+ # and a line that extends on both sides. One will use a
+ # 3 points line, but only mark the #1 (i.e. middle) point.
+ xdata = np.linspace(xdata[0], xdata[-1], 3)
+ markevery = [1]
+
+ ydata = np.full_like(xdata, (height - ydescent) / 2)
+ legline = Line2D(xdata, ydata, markevery=markevery)
+
+ self.update_prop(legline, orig_handle, legend)
+
+ if legend.markerscale != 1:
+ newsz = legline.get_markersize() * legend.markerscale
+ legline.set_markersize(newsz)
+
+ legline.set_transform(trans)
+
+ return [legline]
+
+
+class HandlerPatch(HandlerBase):
+ """
+ Handler for `.Patch` instances.
+ """
+
+ def __init__(self, patch_func=None, **kwargs):
+ """
+ Parameters
+ ----------
+ patch_func : callable, optional
+ The function that creates the legend key artist.
+ *patch_func* should have the signature::
+
+ def patch_func(legend=legend, orig_handle=orig_handle,
+ xdescent=xdescent, ydescent=ydescent,
+ width=width, height=height, fontsize=fontsize)
+
+ Subsequently, the created artist will have its ``update_prop``
+ method called and the appropriate transform will be applied.
+
+ **kwargs
+ Keyword arguments forwarded to `.HandlerBase`.
+ """
+ super().__init__(**kwargs)
+ self._patch_func = patch_func
+
+ def _create_patch(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize):
+ if self._patch_func is None:
+ p = Rectangle(xy=(-xdescent, -ydescent),
+ width=width, height=height)
+ else:
+ p = self._patch_func(legend=legend, orig_handle=orig_handle,
+ xdescent=xdescent, ydescent=ydescent,
+ width=width, height=height, fontsize=fontsize)
+ return p
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize, trans):
+ # docstring inherited
+ p = self._create_patch(legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize)
+ self.update_prop(p, orig_handle, legend)
+ p.set_transform(trans)
+ return [p]
+
+
+class HandlerStepPatch(HandlerBase):
+ """
+ Handler for `~.matplotlib.patches.StepPatch` instances.
+ """
+
+ @staticmethod
+ def _create_patch(orig_handle, xdescent, ydescent, width, height):
+ return Rectangle(xy=(-xdescent, -ydescent), width=width,
+ height=height, color=orig_handle.get_facecolor())
+
+ @staticmethod
+ def _create_line(orig_handle, width, height):
+ # Unfilled StepPatch should show as a line
+ legline = Line2D([0, width], [height/2, height/2],
+ color=orig_handle.get_edgecolor(),
+ linestyle=orig_handle.get_linestyle(),
+ linewidth=orig_handle.get_linewidth(),
+ )
+
+ # Overwrite manually because patch and line properties don't mix
+ legline.set_drawstyle('default')
+ legline.set_marker("")
+ return legline
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize, trans):
+ # docstring inherited
+ if orig_handle.get_fill() or (orig_handle.get_hatch() is not None):
+ p = self._create_patch(orig_handle, xdescent, ydescent, width,
+ height)
+ self.update_prop(p, orig_handle, legend)
+ else:
+ p = self._create_line(orig_handle, width, height)
+ p.set_transform(trans)
+ return [p]
+
+
+class HandlerLineCollection(HandlerLine2D):
+ """
+ Handler for `.LineCollection` instances.
+ """
+ def get_numpoints(self, legend):
+ if self._numpoints is None:
+ return legend.scatterpoints
+ else:
+ return self._numpoints
+
+ def _default_update_prop(self, legend_handle, orig_handle):
+ lw = orig_handle.get_linewidths()[0]
+ dashes = orig_handle._us_linestyles[0]
+ color = orig_handle.get_colors()[0]
+ legend_handle.set_color(color)
+ legend_handle.set_linestyle(dashes)
+ legend_handle.set_linewidth(lw)
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize, trans):
+ # docstring inherited
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
+ width, height, fontsize)
+ ydata = np.full_like(xdata, (height - ydescent) / 2)
+ legline = Line2D(xdata, ydata)
+
+ self.update_prop(legline, orig_handle, legend)
+ legline.set_transform(trans)
+
+ return [legline]
+
+
+class HandlerRegularPolyCollection(HandlerNpointsYoffsets):
+ r"""Handler for `.RegularPolyCollection`\s."""
+
+ def __init__(self, yoffsets=None, sizes=None, **kwargs):
+ super().__init__(yoffsets=yoffsets, **kwargs)
+
+ self._sizes = sizes
+
+ def get_numpoints(self, legend):
+ if self._numpoints is None:
+ return legend.scatterpoints
+ else:
+ return self._numpoints
+
+ def get_sizes(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize):
+ if self._sizes is None:
+ handle_sizes = orig_handle.get_sizes()
+ if not len(handle_sizes):
+ handle_sizes = [1]
+ size_max = max(handle_sizes) * legend.markerscale ** 2
+ size_min = min(handle_sizes) * legend.markerscale ** 2
+
+ numpoints = self.get_numpoints(legend)
+ if numpoints < 4:
+ sizes = [.5 * (size_max + size_min), size_max,
+ size_min][:numpoints]
+ else:
+ rng = (size_max - size_min)
+ sizes = rng * np.linspace(0, 1, numpoints) + size_min
+ else:
+ sizes = self._sizes
+
+ return sizes
+
+ def update_prop(self, legend_handle, orig_handle, legend):
+
+ self._update_prop(legend_handle, orig_handle)
+
+ legend_handle.set_figure(legend.get_figure(root=False))
+ # legend._set_artist_props(legend_handle)
+ legend_handle.set_clip_box(None)
+ legend_handle.set_clip_path(None)
+
+ def create_collection(self, orig_handle, sizes, offsets, offset_transform):
+ return type(orig_handle)(
+ orig_handle.get_numsides(),
+ rotation=orig_handle.get_rotation(), sizes=sizes,
+ offsets=offsets, offset_transform=offset_transform,
+ )
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize,
+ trans):
+ # docstring inherited
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
+ width, height, fontsize)
+
+ ydata = self.get_ydata(legend, xdescent, ydescent,
+ width, height, fontsize)
+
+ sizes = self.get_sizes(legend, orig_handle, xdescent, ydescent,
+ width, height, fontsize)
+
+ p = self.create_collection(
+ orig_handle, sizes,
+ offsets=list(zip(xdata_marker, ydata)), offset_transform=trans)
+
+ self.update_prop(p, orig_handle, legend)
+ p.set_offset_transform(trans)
+ return [p]
+
+
+class HandlerPathCollection(HandlerRegularPolyCollection):
+ r"""Handler for `.PathCollection`\s, which are used by `~.Axes.scatter`."""
+
+ def create_collection(self, orig_handle, sizes, offsets, offset_transform):
+ return type(orig_handle)(
+ [orig_handle.get_paths()[0]], sizes=sizes,
+ offsets=offsets, offset_transform=offset_transform,
+ )
+
+
+class HandlerCircleCollection(HandlerRegularPolyCollection):
+ r"""Handler for `.CircleCollection`\s."""
+
+ def create_collection(self, orig_handle, sizes, offsets, offset_transform):
+ return type(orig_handle)(
+ sizes, offsets=offsets, offset_transform=offset_transform)
+
+
+class HandlerErrorbar(HandlerLine2D):
+ """Handler for Errorbars."""
+
+ def __init__(self, xerr_size=0.5, yerr_size=None,
+ marker_pad=0.3, numpoints=None, **kwargs):
+
+ self._xerr_size = xerr_size
+ self._yerr_size = yerr_size
+
+ super().__init__(marker_pad=marker_pad, numpoints=numpoints, **kwargs)
+
+ def get_err_size(self, legend, xdescent, ydescent,
+ width, height, fontsize):
+ xerr_size = self._xerr_size * fontsize
+
+ if self._yerr_size is None:
+ yerr_size = xerr_size
+ else:
+ yerr_size = self._yerr_size * fontsize
+
+ return xerr_size, yerr_size
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize,
+ trans):
+ # docstring inherited
+ plotlines, caplines, barlinecols = orig_handle
+
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
+ width, height, fontsize)
+
+ ydata = np.full_like(xdata, (height - ydescent) / 2)
+ legline = Line2D(xdata, ydata)
+
+ xdata_marker = np.asarray(xdata_marker)
+ ydata_marker = np.asarray(ydata[:len(xdata_marker)])
+
+ xerr_size, yerr_size = self.get_err_size(legend, xdescent, ydescent,
+ width, height, fontsize)
+
+ legline_marker = Line2D(xdata_marker, ydata_marker)
+
+ # when plotlines are None (only errorbars are drawn), we just
+ # make legline invisible.
+ if plotlines is None:
+ legline.set_visible(False)
+ legline_marker.set_visible(False)
+ else:
+ self.update_prop(legline, plotlines, legend)
+
+ legline.set_drawstyle('default')
+ legline.set_marker('none')
+
+ self.update_prop(legline_marker, plotlines, legend)
+ legline_marker.set_linestyle('None')
+
+ if legend.markerscale != 1:
+ newsz = legline_marker.get_markersize() * legend.markerscale
+ legline_marker.set_markersize(newsz)
+
+ handle_barlinecols = []
+ handle_caplines = []
+
+ if orig_handle.has_xerr:
+ verts = [((x - xerr_size, y), (x + xerr_size, y))
+ for x, y in zip(xdata_marker, ydata_marker)]
+ coll = mcoll.LineCollection(verts)
+ self.update_prop(coll, barlinecols[0], legend)
+ handle_barlinecols.append(coll)
+
+ if caplines:
+ capline_left = Line2D(xdata_marker - xerr_size, ydata_marker)
+ capline_right = Line2D(xdata_marker + xerr_size, ydata_marker)
+ self.update_prop(capline_left, caplines[0], legend)
+ self.update_prop(capline_right, caplines[0], legend)
+ capline_left.set_marker("|")
+ capline_right.set_marker("|")
+
+ handle_caplines.append(capline_left)
+ handle_caplines.append(capline_right)
+
+ if orig_handle.has_yerr:
+ verts = [((x, y - yerr_size), (x, y + yerr_size))
+ for x, y in zip(xdata_marker, ydata_marker)]
+ coll = mcoll.LineCollection(verts)
+ self.update_prop(coll, barlinecols[0], legend)
+ handle_barlinecols.append(coll)
+
+ if caplines:
+ capline_left = Line2D(xdata_marker, ydata_marker - yerr_size)
+ capline_right = Line2D(xdata_marker, ydata_marker + yerr_size)
+ self.update_prop(capline_left, caplines[0], legend)
+ self.update_prop(capline_right, caplines[0], legend)
+ capline_left.set_marker("_")
+ capline_right.set_marker("_")
+
+ handle_caplines.append(capline_left)
+ handle_caplines.append(capline_right)
+
+ artists = [
+ *handle_barlinecols, *handle_caplines, legline, legline_marker,
+ ]
+ for artist in artists:
+ artist.set_transform(trans)
+ return artists
+
+
+class HandlerStem(HandlerNpointsYoffsets):
+ """
+ Handler for plots produced by `~.Axes.stem`.
+ """
+
+ def __init__(self, marker_pad=0.3, numpoints=None,
+ bottom=None, yoffsets=None, **kwargs):
+ """
+ Parameters
+ ----------
+ marker_pad : float, default: 0.3
+ Padding between points in legend entry.
+ numpoints : int, optional
+ Number of points to show in legend entry.
+ bottom : float, optional
+
+ yoffsets : array of floats, optional
+ Length *numpoints* list of y offsets for each point in
+ legend entry.
+ **kwargs
+ Keyword arguments forwarded to `.HandlerNpointsYoffsets`.
+ """
+ super().__init__(marker_pad=marker_pad, numpoints=numpoints,
+ yoffsets=yoffsets, **kwargs)
+ self._bottom = bottom
+
+ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
+ if self._yoffsets is None:
+ ydata = height * (0.5 * legend._scatteryoffsets + 0.5)
+ else:
+ ydata = height * np.asarray(self._yoffsets)
+
+ return ydata
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize,
+ trans):
+ # docstring inherited
+ markerline, stemlines, baseline = orig_handle
+ # Check to see if the stemcontainer is storing lines as a list or a
+ # LineCollection. Eventually using a list will be removed, and this
+ # logic can also be removed.
+ using_linecoll = isinstance(stemlines, mcoll.LineCollection)
+
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
+ width, height, fontsize)
+
+ ydata = self.get_ydata(legend, xdescent, ydescent,
+ width, height, fontsize)
+
+ if self._bottom is None:
+ bottom = 0.
+ else:
+ bottom = self._bottom
+
+ leg_markerline = Line2D(xdata_marker, ydata[:len(xdata_marker)])
+ self.update_prop(leg_markerline, markerline, legend)
+
+ leg_stemlines = [Line2D([x, x], [bottom, y])
+ for x, y in zip(xdata_marker, ydata)]
+
+ if using_linecoll:
+ # change the function used by update_prop() from the default
+ # to one that handles LineCollection
+ with cbook._setattr_cm(
+ self, _update_prop_func=self._copy_collection_props):
+ for line in leg_stemlines:
+ self.update_prop(line, stemlines, legend)
+
+ else:
+ for lm, m in zip(leg_stemlines, stemlines):
+ self.update_prop(lm, m, legend)
+
+ leg_baseline = Line2D([np.min(xdata), np.max(xdata)],
+ [bottom, bottom])
+ self.update_prop(leg_baseline, baseline, legend)
+
+ artists = [*leg_stemlines, leg_baseline, leg_markerline]
+ for artist in artists:
+ artist.set_transform(trans)
+ return artists
+
+ def _copy_collection_props(self, legend_handle, orig_handle):
+ """
+ Copy properties from the `.LineCollection` *orig_handle* to the
+ `.Line2D` *legend_handle*.
+ """
+ legend_handle.set_color(orig_handle.get_color()[0])
+ legend_handle.set_linestyle(orig_handle.get_linestyle()[0])
+
+
+class HandlerTuple(HandlerBase):
+ """
+ Handler for Tuple.
+ """
+
+ def __init__(self, ndivide=1, pad=None, **kwargs):
+ """
+ Parameters
+ ----------
+ ndivide : int or None, default: 1
+ The number of sections to divide the legend area into. If None,
+ use the length of the input tuple.
+ pad : float, default: :rc:`legend.borderpad`
+ Padding in units of fraction of font size.
+ **kwargs
+ Keyword arguments forwarded to `.HandlerBase`.
+ """
+ self._ndivide = ndivide
+ self._pad = pad
+ super().__init__(**kwargs)
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize,
+ trans):
+ # docstring inherited
+ handler_map = legend.get_legend_handler_map()
+
+ if self._ndivide is None:
+ ndivide = len(orig_handle)
+ else:
+ ndivide = self._ndivide
+
+ if self._pad is None:
+ pad = legend.borderpad * fontsize
+ else:
+ pad = self._pad * fontsize
+
+ if ndivide > 1:
+ width = (width - pad * (ndivide - 1)) / ndivide
+
+ xds_cycle = cycle(xdescent - (width + pad) * np.arange(ndivide))
+
+ a_list = []
+ for handle1 in orig_handle:
+ handler = legend.get_legend_handler(handler_map, handle1)
+ _a_list = handler.create_artists(
+ legend, handle1,
+ next(xds_cycle), ydescent, width, height, fontsize, trans)
+ a_list.extend(_a_list)
+
+ return a_list
+
+
+class HandlerPolyCollection(HandlerBase):
+ """
+ Handler for `.PolyCollection` used in `~.Axes.fill_between` and
+ `~.Axes.stackplot`.
+ """
+ def _update_prop(self, legend_handle, orig_handle):
+ def first_color(colors):
+ if colors.size == 0:
+ return (0, 0, 0, 0)
+ return tuple(colors[0])
+
+ def get_first(prop_array):
+ if len(prop_array):
+ return prop_array[0]
+ else:
+ return None
+
+ # orig_handle is a PolyCollection and legend_handle is a Patch.
+ # Directly set Patch color attributes (must be RGBA tuples).
+ legend_handle._facecolor = first_color(orig_handle.get_facecolor())
+ legend_handle._edgecolor = first_color(orig_handle.get_edgecolor())
+ legend_handle._original_facecolor = orig_handle._original_facecolor
+ legend_handle._original_edgecolor = orig_handle._original_edgecolor
+ legend_handle._fill = orig_handle.get_fill()
+ legend_handle._hatch = orig_handle.get_hatch()
+ # Hatch color is anomalous in having no getters and setters.
+ legend_handle._hatch_color = orig_handle._hatch_color
+ # Setters are fine for the remaining attributes.
+ legend_handle.set_linewidth(get_first(orig_handle.get_linewidths()))
+ legend_handle.set_linestyle(get_first(orig_handle.get_linestyles()))
+ legend_handle.set_transform(get_first(orig_handle.get_transforms()))
+ # Alpha is already taken into account by the color attributes.
+
+ def create_artists(self, legend, orig_handle,
+ xdescent, ydescent, width, height, fontsize, trans):
+ # docstring inherited
+ p = Rectangle(xy=(-xdescent, -ydescent),
+ width=width, height=height)
+ self.update_prop(p, orig_handle, legend)
+ p.set_transform(trans)
+ return [p]
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..db028a136a48ed5fe58f80b56854288564316851
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.pyi
@@ -0,0 +1,294 @@
+from collections.abc import Callable, Sequence
+from matplotlib.artist import Artist
+from matplotlib.legend import Legend
+from matplotlib.offsetbox import OffsetBox
+from matplotlib.transforms import Transform
+
+from typing import TypeVar
+
+from numpy.typing import ArrayLike
+
+def update_from_first_child(tgt: Artist, src: Artist) -> None: ...
+
+class HandlerBase:
+ def __init__(
+ self,
+ xpad: float = ...,
+ ypad: float = ...,
+ update_func: Callable[[Artist, Artist], None] | None = ...,
+ ) -> None: ...
+ def update_prop(
+ self, legend_handle: Artist, orig_handle: Artist, legend: Legend
+ ) -> None: ...
+ def adjust_drawing_area(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ ) -> tuple[float, float, float, float]: ...
+ def legend_artist(
+ self, legend: Legend, orig_handle: Artist, fontsize: float, handlebox: OffsetBox
+ ) -> Artist: ...
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+class HandlerNpoints(HandlerBase):
+ def __init__(
+ self, marker_pad: float = ..., numpoints: int | None = ..., **kwargs
+ ) -> None: ...
+ def get_numpoints(self, legend: Legend) -> int | None: ...
+ def get_xdata(
+ self,
+ legend: Legend,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ ) -> tuple[ArrayLike, ArrayLike]: ...
+
+class HandlerNpointsYoffsets(HandlerNpoints):
+ def __init__(
+ self,
+ numpoints: int | None = ...,
+ yoffsets: Sequence[float] | None = ...,
+ **kwargs
+ ) -> None: ...
+ def get_ydata(
+ self,
+ legend: Legend,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ ) -> ArrayLike: ...
+
+class HandlerLine2DCompound(HandlerNpoints):
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+class HandlerLine2D(HandlerNpoints):
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+class HandlerPatch(HandlerBase):
+ def __init__(self, patch_func: Callable | None = ..., **kwargs) -> None: ...
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+class HandlerStepPatch(HandlerBase):
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+class HandlerLineCollection(HandlerLine2D):
+ def get_numpoints(self, legend: Legend) -> int: ...
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+_T = TypeVar("_T", bound=Artist)
+
+class HandlerRegularPolyCollection(HandlerNpointsYoffsets):
+ def __init__(
+ self,
+ yoffsets: Sequence[float] | None = ...,
+ sizes: Sequence[float] | None = ...,
+ **kwargs
+ ) -> None: ...
+ def get_numpoints(self, legend: Legend) -> int: ...
+ def get_sizes(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ ) -> Sequence[float]: ...
+ def update_prop(
+ self, legend_handle, orig_handle: Artist, legend: Legend
+ ) -> None: ...
+ def create_collection(
+ self,
+ orig_handle: _T,
+ sizes: Sequence[float] | None,
+ offsets: Sequence[float] | None,
+ offset_transform: Transform,
+ ) -> _T: ...
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+class HandlerPathCollection(HandlerRegularPolyCollection):
+ def create_collection(
+ self,
+ orig_handle: _T,
+ sizes: Sequence[float] | None,
+ offsets: Sequence[float] | None,
+ offset_transform: Transform,
+ ) -> _T: ...
+
+class HandlerCircleCollection(HandlerRegularPolyCollection):
+ def create_collection(
+ self,
+ orig_handle: _T,
+ sizes: Sequence[float] | None,
+ offsets: Sequence[float] | None,
+ offset_transform: Transform,
+ ) -> _T: ...
+
+class HandlerErrorbar(HandlerLine2D):
+ def __init__(
+ self,
+ xerr_size: float = ...,
+ yerr_size: float | None = ...,
+ marker_pad: float = ...,
+ numpoints: int | None = ...,
+ **kwargs
+ ) -> None: ...
+ def get_err_size(
+ self,
+ legend: Legend,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ ) -> tuple[float, float]: ...
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+class HandlerStem(HandlerNpointsYoffsets):
+ def __init__(
+ self,
+ marker_pad: float = ...,
+ numpoints: int | None = ...,
+ bottom: float | None = ...,
+ yoffsets: Sequence[float] | None = ...,
+ **kwargs
+ ) -> None: ...
+ def get_ydata(
+ self,
+ legend: Legend,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ ) -> ArrayLike: ...
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+class HandlerTuple(HandlerBase):
+ def __init__(
+ self, ndivide: int | None = ..., pad: float | None = ..., **kwargs
+ ) -> None: ...
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
+
+class HandlerPolyCollection(HandlerBase):
+ def create_artists(
+ self,
+ legend: Legend,
+ orig_handle: Artist,
+ xdescent: float,
+ ydescent: float,
+ width: float,
+ height: float,
+ fontsize: float,
+ trans: Transform,
+ ) -> Sequence[Artist]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.py
new file mode 100644
index 0000000000000000000000000000000000000000..21dd91b89f49348c0165ef351e6f1494fed549e3
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.py
@@ -0,0 +1,1720 @@
+"""
+2D lines with support for a variety of line styles, markers, colors, etc.
+"""
+
+import copy
+
+from numbers import Integral, Number, Real
+import logging
+
+import numpy as np
+
+import matplotlib as mpl
+from . import _api, cbook, colors as mcolors, _docstring
+from .artist import Artist, allow_rasterization
+from .cbook import (
+ _to_unmasked_float_array, ls_mapper, ls_mapper_r, STEP_LOOKUP_MAP)
+from .markers import MarkerStyle
+from .path import Path
+from .transforms import Bbox, BboxTransformTo, TransformedPath
+from ._enums import JoinStyle, CapStyle
+
+# Imported here for backward compatibility, even though they don't
+# really belong.
+from . import _path
+from .markers import ( # noqa
+ CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN,
+ CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE,
+ TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN)
+
+_log = logging.getLogger(__name__)
+
+
+def _get_dash_pattern(style):
+ """Convert linestyle to dash pattern."""
+ # go from short hand -> full strings
+ if isinstance(style, str):
+ style = ls_mapper.get(style, style)
+ # un-dashed styles
+ if style in ['solid', 'None']:
+ offset = 0
+ dashes = None
+ # dashed styles
+ elif style in ['dashed', 'dashdot', 'dotted']:
+ offset = 0
+ dashes = tuple(mpl.rcParams[f'lines.{style}_pattern'])
+ #
+ elif isinstance(style, tuple):
+ offset, dashes = style
+ if offset is None:
+ raise ValueError(f'Unrecognized linestyle: {style!r}')
+ else:
+ raise ValueError(f'Unrecognized linestyle: {style!r}')
+
+ # normalize offset to be positive and shorter than the dash cycle
+ if dashes is not None:
+ dsum = sum(dashes)
+ if dsum:
+ offset %= dsum
+
+ return offset, dashes
+
+
+def _get_dash_patterns(styles):
+ """Convert linestyle or sequence of linestyles to list of dash patterns."""
+ try:
+ patterns = [_get_dash_pattern(styles)]
+ except ValueError:
+ try:
+ patterns = [_get_dash_pattern(x) for x in styles]
+ except ValueError as err:
+ emsg = f'Do not know how to convert {styles!r} to dashes'
+ raise ValueError(emsg) from err
+
+ return patterns
+
+
+def _get_inverse_dash_pattern(offset, dashes):
+ """Return the inverse of the given dash pattern, for filling the gaps."""
+ # Define the inverse pattern by moving the last gap to the start of the
+ # sequence.
+ gaps = dashes[-1:] + dashes[:-1]
+ # Set the offset so that this new first segment is skipped
+ # (see backend_bases.GraphicsContextBase.set_dashes for offset definition).
+ offset_gaps = offset + dashes[-1]
+
+ return offset_gaps, gaps
+
+
+def _scale_dashes(offset, dashes, lw):
+ if not mpl.rcParams['lines.scale_dashes']:
+ return offset, dashes
+ scaled_offset = offset * lw
+ scaled_dashes = ([x * lw if x is not None else None for x in dashes]
+ if dashes is not None else None)
+ return scaled_offset, scaled_dashes
+
+
+def segment_hits(cx, cy, x, y, radius):
+ """
+ Return the indices of the segments in the polyline with coordinates (*cx*,
+ *cy*) that are within a distance *radius* of the point (*x*, *y*).
+ """
+ # Process single points specially
+ if len(x) <= 1:
+ res, = np.nonzero((cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2)
+ return res
+
+ # We need to lop the last element off a lot.
+ xr, yr = x[:-1], y[:-1]
+
+ # Only look at line segments whose nearest point to C on the line
+ # lies within the segment.
+ dx, dy = x[1:] - xr, y[1:] - yr
+ Lnorm_sq = dx ** 2 + dy ** 2 # Possibly want to eliminate Lnorm==0
+ u = ((cx - xr) * dx + (cy - yr) * dy) / Lnorm_sq
+ candidates = (u >= 0) & (u <= 1)
+
+ # Note that there is a little area near one side of each point
+ # which will be near neither segment, and another which will
+ # be near both, depending on the angle of the lines. The
+ # following radius test eliminates these ambiguities.
+ point_hits = (cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2
+ candidates = candidates & ~(point_hits[:-1] | point_hits[1:])
+
+ # For those candidates which remain, determine how far they lie away
+ # from the line.
+ px, py = xr + u * dx, yr + u * dy
+ line_hits = (cx - px) ** 2 + (cy - py) ** 2 <= radius ** 2
+ line_hits = line_hits & candidates
+ points, = point_hits.ravel().nonzero()
+ lines, = line_hits.ravel().nonzero()
+ return np.concatenate((points, lines))
+
+
+def _mark_every_path(markevery, tpath, affine, ax):
+ """
+ Helper function that sorts out how to deal the input
+ `markevery` and returns the points where markers should be drawn.
+
+ Takes in the `markevery` value and the line path and returns the
+ sub-sampled path.
+ """
+ # pull out the two bits of data we want from the path
+ codes, verts = tpath.codes, tpath.vertices
+
+ def _slice_or_none(in_v, slc):
+ """Helper function to cope with `codes` being an ndarray or `None`."""
+ if in_v is None:
+ return None
+ return in_v[slc]
+
+ # if just an int, assume starting at 0 and make a tuple
+ if isinstance(markevery, Integral):
+ markevery = (0, markevery)
+ # if just a float, assume starting at 0.0 and make a tuple
+ elif isinstance(markevery, Real):
+ markevery = (0.0, markevery)
+
+ if isinstance(markevery, tuple):
+ if len(markevery) != 2:
+ raise ValueError('`markevery` is a tuple but its len is not 2; '
+ f'markevery={markevery}')
+ start, step = markevery
+ # if step is an int, old behavior
+ if isinstance(step, Integral):
+ # tuple of 2 int is for backwards compatibility,
+ if not isinstance(start, Integral):
+ raise ValueError(
+ '`markevery` is a tuple with len 2 and second element is '
+ 'an int, but the first element is not an int; '
+ f'markevery={markevery}')
+ # just return, we are done here
+
+ return Path(verts[slice(start, None, step)],
+ _slice_or_none(codes, slice(start, None, step)))
+
+ elif isinstance(step, Real):
+ if not isinstance(start, Real):
+ raise ValueError(
+ '`markevery` is a tuple with len 2 and second element is '
+ 'a float, but the first element is not a float or an int; '
+ f'markevery={markevery}')
+ if ax is None:
+ raise ValueError(
+ "markevery is specified relative to the Axes size, but "
+ "the line does not have a Axes as parent")
+
+ # calc cumulative distance along path (in display coords):
+ fin = np.isfinite(verts).all(axis=1)
+ fverts = verts[fin]
+ disp_coords = affine.transform(fverts)
+
+ delta = np.empty((len(disp_coords), 2))
+ delta[0, :] = 0
+ delta[1:, :] = disp_coords[1:, :] - disp_coords[:-1, :]
+ delta = np.hypot(*delta.T).cumsum()
+ # calc distance between markers along path based on the Axes
+ # bounding box diagonal being a distance of unity:
+ (x0, y0), (x1, y1) = ax.transAxes.transform([[0, 0], [1, 1]])
+ scale = np.hypot(x1 - x0, y1 - y0)
+ marker_delta = np.arange(start * scale, delta[-1], step * scale)
+ # find closest actual data point that is closest to
+ # the theoretical distance along the path:
+ inds = np.abs(delta[np.newaxis, :] - marker_delta[:, np.newaxis])
+ inds = inds.argmin(axis=1)
+ inds = np.unique(inds)
+ # return, we are done here
+ return Path(fverts[inds], _slice_or_none(codes, inds))
+ else:
+ raise ValueError(
+ f"markevery={markevery!r} is a tuple with len 2, but its "
+ f"second element is not an int or a float")
+
+ elif isinstance(markevery, slice):
+ # mazol tov, it's already a slice, just return
+ return Path(verts[markevery], _slice_or_none(codes, markevery))
+
+ elif np.iterable(markevery):
+ # fancy indexing
+ try:
+ return Path(verts[markevery], _slice_or_none(codes, markevery))
+ except (ValueError, IndexError) as err:
+ raise ValueError(
+ f"markevery={markevery!r} is iterable but not a valid numpy "
+ f"fancy index") from err
+ else:
+ raise ValueError(f"markevery={markevery!r} is not a recognized value")
+
+
+@_docstring.interpd
+@_api.define_aliases({
+ "antialiased": ["aa"],
+ "color": ["c"],
+ "drawstyle": ["ds"],
+ "linestyle": ["ls"],
+ "linewidth": ["lw"],
+ "markeredgecolor": ["mec"],
+ "markeredgewidth": ["mew"],
+ "markerfacecolor": ["mfc"],
+ "markerfacecoloralt": ["mfcalt"],
+ "markersize": ["ms"],
+})
+class Line2D(Artist):
+ """
+ A line - the line can have both a solid linestyle connecting all
+ the vertices, and a marker at each vertex. Additionally, the
+ drawing of the solid line is influenced by the drawstyle, e.g., one
+ can create "stepped" lines in various styles.
+ """
+
+ lineStyles = _lineStyles = { # hidden names deprecated
+ '-': '_draw_solid',
+ '--': '_draw_dashed',
+ '-.': '_draw_dash_dot',
+ ':': '_draw_dotted',
+ 'None': '_draw_nothing',
+ ' ': '_draw_nothing',
+ '': '_draw_nothing',
+ }
+
+ _drawStyles_l = {
+ 'default': '_draw_lines',
+ 'steps-mid': '_draw_steps_mid',
+ 'steps-pre': '_draw_steps_pre',
+ 'steps-post': '_draw_steps_post',
+ }
+
+ _drawStyles_s = {
+ 'steps': '_draw_steps_pre',
+ }
+
+ # drawStyles should now be deprecated.
+ drawStyles = {**_drawStyles_l, **_drawStyles_s}
+ # Need a list ordered with long names first:
+ drawStyleKeys = [*_drawStyles_l, *_drawStyles_s]
+
+ # Referenced here to maintain API. These are defined in
+ # MarkerStyle
+ markers = MarkerStyle.markers
+ filled_markers = MarkerStyle.filled_markers
+ fillStyles = MarkerStyle.fillstyles
+
+ zorder = 2
+
+ _subslice_optim_min_size = 1000
+
+ def __str__(self):
+ if self._label != "":
+ return f"Line2D({self._label})"
+ elif self._x is None:
+ return "Line2D()"
+ elif len(self._x) > 3:
+ return "Line2D(({:g},{:g}),({:g},{:g}),...,({:g},{:g}))".format(
+ self._x[0], self._y[0],
+ self._x[1], self._y[1],
+ self._x[-1], self._y[-1])
+ else:
+ return "Line2D(%s)" % ",".join(
+ map("({:g},{:g})".format, self._x, self._y))
+
+ def __init__(self, xdata, ydata, *,
+ linewidth=None, # all Nones default to rc
+ linestyle=None,
+ color=None,
+ gapcolor=None,
+ marker=None,
+ markersize=None,
+ markeredgewidth=None,
+ markeredgecolor=None,
+ markerfacecolor=None,
+ markerfacecoloralt='none',
+ fillstyle=None,
+ antialiased=None,
+ dash_capstyle=None,
+ solid_capstyle=None,
+ dash_joinstyle=None,
+ solid_joinstyle=None,
+ pickradius=5,
+ drawstyle=None,
+ markevery=None,
+ **kwargs
+ ):
+ """
+ Create a `.Line2D` instance with *x* and *y* data in sequences of
+ *xdata*, *ydata*.
+
+ Additional keyword arguments are `.Line2D` properties:
+
+ %(Line2D:kwdoc)s
+
+ See :meth:`set_linestyle` for a description of the line styles,
+ :meth:`set_marker` for a description of the markers, and
+ :meth:`set_drawstyle` for a description of the draw styles.
+
+ """
+ super().__init__()
+
+ # Convert sequences to NumPy arrays.
+ if not np.iterable(xdata):
+ raise RuntimeError('xdata must be a sequence')
+ if not np.iterable(ydata):
+ raise RuntimeError('ydata must be a sequence')
+
+ if linewidth is None:
+ linewidth = mpl.rcParams['lines.linewidth']
+
+ if linestyle is None:
+ linestyle = mpl.rcParams['lines.linestyle']
+ if marker is None:
+ marker = mpl.rcParams['lines.marker']
+ if color is None:
+ color = mpl.rcParams['lines.color']
+
+ if markersize is None:
+ markersize = mpl.rcParams['lines.markersize']
+ if antialiased is None:
+ antialiased = mpl.rcParams['lines.antialiased']
+ if dash_capstyle is None:
+ dash_capstyle = mpl.rcParams['lines.dash_capstyle']
+ if dash_joinstyle is None:
+ dash_joinstyle = mpl.rcParams['lines.dash_joinstyle']
+ if solid_capstyle is None:
+ solid_capstyle = mpl.rcParams['lines.solid_capstyle']
+ if solid_joinstyle is None:
+ solid_joinstyle = mpl.rcParams['lines.solid_joinstyle']
+
+ if drawstyle is None:
+ drawstyle = 'default'
+
+ self._dashcapstyle = None
+ self._dashjoinstyle = None
+ self._solidjoinstyle = None
+ self._solidcapstyle = None
+ self.set_dash_capstyle(dash_capstyle)
+ self.set_dash_joinstyle(dash_joinstyle)
+ self.set_solid_capstyle(solid_capstyle)
+ self.set_solid_joinstyle(solid_joinstyle)
+
+ self._linestyles = None
+ self._drawstyle = None
+ self._linewidth = linewidth
+ self._unscaled_dash_pattern = (0, None) # offset, dash
+ self._dash_pattern = (0, None) # offset, dash (scaled by linewidth)
+
+ self.set_linewidth(linewidth)
+ self.set_linestyle(linestyle)
+ self.set_drawstyle(drawstyle)
+
+ self._color = None
+ self.set_color(color)
+ if marker is None:
+ marker = 'none' # Default.
+ if not isinstance(marker, MarkerStyle):
+ self._marker = MarkerStyle(marker, fillstyle)
+ else:
+ self._marker = marker
+
+ self._gapcolor = None
+ self.set_gapcolor(gapcolor)
+
+ self._markevery = None
+ self._markersize = None
+ self._antialiased = None
+
+ self.set_markevery(markevery)
+ self.set_antialiased(antialiased)
+ self.set_markersize(markersize)
+
+ self._markeredgecolor = None
+ self._markeredgewidth = None
+ self._markerfacecolor = None
+ self._markerfacecoloralt = None
+
+ self.set_markerfacecolor(markerfacecolor) # Normalizes None to rc.
+ self.set_markerfacecoloralt(markerfacecoloralt)
+ self.set_markeredgecolor(markeredgecolor) # Normalizes None to rc.
+ self.set_markeredgewidth(markeredgewidth)
+
+ # update kwargs before updating data to give the caller a
+ # chance to init axes (and hence unit support)
+ self._internal_update(kwargs)
+ self.pickradius = pickradius
+ self.ind_offset = 0
+ if (isinstance(self._picker, Number) and
+ not isinstance(self._picker, bool)):
+ self._pickradius = self._picker
+
+ self._xorig = np.asarray([])
+ self._yorig = np.asarray([])
+ self._invalidx = True
+ self._invalidy = True
+ self._x = None
+ self._y = None
+ self._xy = None
+ self._path = None
+ self._transformed_path = None
+ self._subslice = False
+ self._x_filled = None # used in subslicing; only x is needed
+
+ self.set_data(xdata, ydata)
+
+ def contains(self, mouseevent):
+ """
+ Test whether *mouseevent* occurred on the line.
+
+ An event is deemed to have occurred "on" the line if it is less
+ than ``self.pickradius`` (default: 5 points) away from it. Use
+ `~.Line2D.get_pickradius` or `~.Line2D.set_pickradius` to get or set
+ the pick radius.
+
+ Parameters
+ ----------
+ mouseevent : `~matplotlib.backend_bases.MouseEvent`
+
+ Returns
+ -------
+ contains : bool
+ Whether any values are within the radius.
+ details : dict
+ A dictionary ``{'ind': pointlist}``, where *pointlist* is a
+ list of points of the line that are within the pickradius around
+ the event position.
+
+ TODO: sort returned indices by distance
+ """
+ if self._different_canvas(mouseevent):
+ return False, {}
+
+ # Make sure we have data to plot
+ if self._invalidy or self._invalidx:
+ self.recache()
+ if len(self._xy) == 0:
+ return False, {}
+
+ # Convert points to pixels
+ transformed_path = self._get_transformed_path()
+ path, affine = transformed_path.get_transformed_path_and_affine()
+ path = affine.transform_path(path)
+ xy = path.vertices
+ xt = xy[:, 0]
+ yt = xy[:, 1]
+
+ # Convert pick radius from points to pixels
+ fig = self.get_figure(root=True)
+ if fig is None:
+ _log.warning('no figure set when check if mouse is on line')
+ pixels = self._pickradius
+ else:
+ pixels = fig.dpi / 72. * self._pickradius
+
+ # The math involved in checking for containment (here and inside of
+ # segment_hits) assumes that it is OK to overflow, so temporarily set
+ # the error flags accordingly.
+ with np.errstate(all='ignore'):
+ # Check for collision
+ if self._linestyle in ['None', None]:
+ # If no line, return the nearby point(s)
+ ind, = np.nonzero(
+ (xt - mouseevent.x) ** 2 + (yt - mouseevent.y) ** 2
+ <= pixels ** 2)
+ else:
+ # If line, return the nearby segment(s)
+ ind = segment_hits(mouseevent.x, mouseevent.y, xt, yt, pixels)
+ if self._drawstyle.startswith("steps"):
+ ind //= 2
+
+ ind += self.ind_offset
+
+ # Return the point(s) within radius
+ return len(ind) > 0, dict(ind=ind)
+
+ def get_pickradius(self):
+ """
+ Return the pick radius used for containment tests.
+
+ See `.contains` for more details.
+ """
+ return self._pickradius
+
+ def set_pickradius(self, pickradius):
+ """
+ Set the pick radius used for containment tests.
+
+ See `.contains` for more details.
+
+ Parameters
+ ----------
+ pickradius : float
+ Pick radius, in points.
+ """
+ if not isinstance(pickradius, Real) or pickradius < 0:
+ raise ValueError("pick radius should be a distance")
+ self._pickradius = pickradius
+
+ pickradius = property(get_pickradius, set_pickradius)
+
+ def get_fillstyle(self):
+ """
+ Return the marker fill style.
+
+ See also `~.Line2D.set_fillstyle`.
+ """
+ return self._marker.get_fillstyle()
+
+ def set_fillstyle(self, fs):
+ """
+ Set the marker fill style.
+
+ Parameters
+ ----------
+ fs : {'full', 'left', 'right', 'bottom', 'top', 'none'}
+ Possible values:
+
+ - 'full': Fill the whole marker with the *markerfacecolor*.
+ - 'left', 'right', 'bottom', 'top': Fill the marker half at
+ the given side with the *markerfacecolor*. The other
+ half of the marker is filled with *markerfacecoloralt*.
+ - 'none': No filling.
+
+ For examples see :ref:`marker_fill_styles`.
+ """
+ self.set_marker(MarkerStyle(self._marker.get_marker(), fs))
+ self.stale = True
+
+ def set_markevery(self, every):
+ """
+ Set the markevery property to subsample the plot when using markers.
+
+ e.g., if ``every=5``, every 5-th marker will be plotted.
+
+ Parameters
+ ----------
+ every : None or int or (int, int) or slice or list[int] or float or \
+(float, float) or list[bool]
+ Which markers to plot.
+
+ - ``every=None``: every point will be plotted.
+ - ``every=N``: every N-th marker will be plotted starting with
+ marker 0.
+ - ``every=(start, N)``: every N-th marker, starting at index
+ *start*, will be plotted.
+ - ``every=slice(start, end, N)``: every N-th marker, starting at
+ index *start*, up to but not including index *end*, will be
+ plotted.
+ - ``every=[i, j, m, ...]``: only markers at the given indices
+ will be plotted.
+ - ``every=[True, False, True, ...]``: only positions that are True
+ will be plotted. The list must have the same length as the data
+ points.
+ - ``every=0.1``, (i.e. a float): markers will be spaced at
+ approximately equal visual distances along the line; the distance
+ along the line between markers is determined by multiplying the
+ display-coordinate distance of the Axes bounding-box diagonal
+ by the value of *every*.
+ - ``every=(0.5, 0.1)`` (i.e. a length-2 tuple of float): similar
+ to ``every=0.1`` but the first marker will be offset along the
+ line by 0.5 multiplied by the
+ display-coordinate-diagonal-distance along the line.
+
+ For examples see
+ :doc:`/gallery/lines_bars_and_markers/markevery_demo`.
+
+ Notes
+ -----
+ Setting *markevery* will still only draw markers at actual data points.
+ While the float argument form aims for uniform visual spacing, it has
+ to coerce from the ideal spacing to the nearest available data point.
+ Depending on the number and distribution of data points, the result
+ may still not look evenly spaced.
+
+ When using a start offset to specify the first marker, the offset will
+ be from the first data point which may be different from the first
+ the visible data point if the plot is zoomed in.
+
+ If zooming in on a plot when using float arguments then the actual
+ data points that have markers will change because the distance between
+ markers is always determined from the display-coordinates
+ axes-bounding-box-diagonal regardless of the actual axes data limits.
+
+ """
+ self._markevery = every
+ self.stale = True
+
+ def get_markevery(self):
+ """
+ Return the markevery setting for marker subsampling.
+
+ See also `~.Line2D.set_markevery`.
+ """
+ return self._markevery
+
+ def set_picker(self, p):
+ """
+ Set the event picker details for the line.
+
+ Parameters
+ ----------
+ p : float or callable[[Artist, Event], tuple[bool, dict]]
+ If a float, it is used as the pick radius in points.
+ """
+ if not callable(p):
+ self.set_pickradius(p)
+ self._picker = p
+
+ def get_bbox(self):
+ """Get the bounding box of this line."""
+ bbox = Bbox([[0, 0], [0, 0]])
+ bbox.update_from_data_xy(self.get_xydata())
+ return bbox
+
+ def get_window_extent(self, renderer=None):
+ bbox = Bbox([[0, 0], [0, 0]])
+ trans_data_to_xy = self.get_transform().transform
+ bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()),
+ ignore=True)
+ # correct for marker size, if any
+ if self._marker:
+ ms = (self._markersize / 72.0 * self.get_figure(root=True).dpi) * 0.5
+ bbox = bbox.padded(ms)
+ return bbox
+
+ def set_data(self, *args):
+ """
+ Set the x and y data.
+
+ Parameters
+ ----------
+ *args : (2, N) array or two 1D arrays
+
+ See Also
+ --------
+ set_xdata
+ set_ydata
+ """
+ if len(args) == 1:
+ (x, y), = args
+ else:
+ x, y = args
+
+ self.set_xdata(x)
+ self.set_ydata(y)
+
+ def recache_always(self):
+ self.recache(always=True)
+
+ def recache(self, always=False):
+ if always or self._invalidx:
+ xconv = self.convert_xunits(self._xorig)
+ x = _to_unmasked_float_array(xconv).ravel()
+ else:
+ x = self._x
+ if always or self._invalidy:
+ yconv = self.convert_yunits(self._yorig)
+ y = _to_unmasked_float_array(yconv).ravel()
+ else:
+ y = self._y
+
+ self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float)
+ self._x, self._y = self._xy.T # views
+
+ self._subslice = False
+ if (self.axes
+ and len(x) > self._subslice_optim_min_size
+ and _path.is_sorted_and_has_non_nan(x)
+ and self.axes.name == 'rectilinear'
+ and self.axes.get_xscale() == 'linear'
+ and self._markevery is None
+ and self.get_clip_on()
+ and self.get_transform() == self.axes.transData):
+ self._subslice = True
+ nanmask = np.isnan(x)
+ if nanmask.any():
+ self._x_filled = self._x.copy()
+ indices = np.arange(len(x))
+ self._x_filled[nanmask] = np.interp(
+ indices[nanmask], indices[~nanmask], self._x[~nanmask])
+ else:
+ self._x_filled = self._x
+
+ if self._path is not None:
+ interpolation_steps = self._path._interpolation_steps
+ else:
+ interpolation_steps = 1
+ xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T)
+ self._path = Path(np.asarray(xy).T,
+ _interpolation_steps=interpolation_steps)
+ self._transformed_path = None
+ self._invalidx = False
+ self._invalidy = False
+
+ def _transform_path(self, subslice=None):
+ """
+ Put a TransformedPath instance at self._transformed_path;
+ all invalidation of the transform is then handled by the
+ TransformedPath instance.
+ """
+ # Masked arrays are now handled by the Path class itself
+ if subslice is not None:
+ xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy[subslice, :].T)
+ _path = Path(np.asarray(xy).T,
+ _interpolation_steps=self._path._interpolation_steps)
+ else:
+ _path = self._path
+ self._transformed_path = TransformedPath(_path, self.get_transform())
+
+ def _get_transformed_path(self):
+ """Return this line's `~matplotlib.transforms.TransformedPath`."""
+ if self._transformed_path is None:
+ self._transform_path()
+ return self._transformed_path
+
+ def set_transform(self, t):
+ # docstring inherited
+ self._invalidx = True
+ self._invalidy = True
+ super().set_transform(t)
+
+ @allow_rasterization
+ def draw(self, renderer):
+ # docstring inherited
+
+ if not self.get_visible():
+ return
+
+ if self._invalidy or self._invalidx:
+ self.recache()
+ self.ind_offset = 0 # Needed for contains() method.
+ if self._subslice and self.axes:
+ x0, x1 = self.axes.get_xbound()
+ i0 = self._x_filled.searchsorted(x0, 'left')
+ i1 = self._x_filled.searchsorted(x1, 'right')
+ subslice = slice(max(i0 - 1, 0), i1 + 1)
+ self.ind_offset = subslice.start
+ self._transform_path(subslice)
+ else:
+ subslice = None
+
+ if self.get_path_effects():
+ from matplotlib.patheffects import PathEffectRenderer
+ renderer = PathEffectRenderer(self.get_path_effects(), renderer)
+
+ renderer.open_group('line2d', self.get_gid())
+ if self._lineStyles[self._linestyle] != '_draw_nothing':
+ tpath, affine = (self._get_transformed_path()
+ .get_transformed_path_and_affine())
+ if len(tpath.vertices):
+ gc = renderer.new_gc()
+ self._set_gc_clip(gc)
+ gc.set_url(self.get_url())
+
+ gc.set_antialiased(self._antialiased)
+ gc.set_linewidth(self._linewidth)
+
+ if self.is_dashed():
+ cap = self._dashcapstyle
+ join = self._dashjoinstyle
+ else:
+ cap = self._solidcapstyle
+ join = self._solidjoinstyle
+ gc.set_joinstyle(join)
+ gc.set_capstyle(cap)
+ gc.set_snap(self.get_snap())
+ if self.get_sketch_params() is not None:
+ gc.set_sketch_params(*self.get_sketch_params())
+
+ # We first draw a path within the gaps if needed.
+ if self.is_dashed() and self._gapcolor is not None:
+ lc_rgba = mcolors.to_rgba(self._gapcolor, self._alpha)
+ gc.set_foreground(lc_rgba, isRGBA=True)
+
+ offset_gaps, gaps = _get_inverse_dash_pattern(
+ *self._dash_pattern)
+
+ gc.set_dashes(offset_gaps, gaps)
+ renderer.draw_path(gc, tpath, affine.frozen())
+
+ lc_rgba = mcolors.to_rgba(self._color, self._alpha)
+ gc.set_foreground(lc_rgba, isRGBA=True)
+
+ gc.set_dashes(*self._dash_pattern)
+ renderer.draw_path(gc, tpath, affine.frozen())
+ gc.restore()
+
+ if self._marker and self._markersize > 0:
+ gc = renderer.new_gc()
+ self._set_gc_clip(gc)
+ gc.set_url(self.get_url())
+ gc.set_linewidth(self._markeredgewidth)
+ gc.set_antialiased(self._antialiased)
+
+ ec_rgba = mcolors.to_rgba(
+ self.get_markeredgecolor(), self._alpha)
+ fc_rgba = mcolors.to_rgba(
+ self._get_markerfacecolor(), self._alpha)
+ fcalt_rgba = mcolors.to_rgba(
+ self._get_markerfacecolor(alt=True), self._alpha)
+ # If the edgecolor is "auto", it is set according to the *line*
+ # color but inherits the alpha value of the *face* color, if any.
+ if (cbook._str_equal(self._markeredgecolor, "auto")
+ and not cbook._str_lower_equal(
+ self.get_markerfacecolor(), "none")):
+ ec_rgba = ec_rgba[:3] + (fc_rgba[3],)
+ gc.set_foreground(ec_rgba, isRGBA=True)
+ if self.get_sketch_params() is not None:
+ scale, length, randomness = self.get_sketch_params()
+ gc.set_sketch_params(scale/2, length/2, 2*randomness)
+
+ marker = self._marker
+
+ # Markers *must* be drawn ignoring the drawstyle (but don't pay the
+ # recaching if drawstyle is already "default").
+ if self.get_drawstyle() != "default":
+ with cbook._setattr_cm(
+ self, _drawstyle="default", _transformed_path=None):
+ self.recache()
+ self._transform_path(subslice)
+ tpath, affine = (self._get_transformed_path()
+ .get_transformed_points_and_affine())
+ else:
+ tpath, affine = (self._get_transformed_path()
+ .get_transformed_points_and_affine())
+
+ if len(tpath.vertices):
+ # subsample the markers if markevery is not None
+ markevery = self.get_markevery()
+ if markevery is not None:
+ subsampled = _mark_every_path(
+ markevery, tpath, affine, self.axes)
+ else:
+ subsampled = tpath
+
+ snap = marker.get_snap_threshold()
+ if isinstance(snap, Real):
+ snap = renderer.points_to_pixels(self._markersize) >= snap
+ gc.set_snap(snap)
+ gc.set_joinstyle(marker.get_joinstyle())
+ gc.set_capstyle(marker.get_capstyle())
+ marker_path = marker.get_path()
+ marker_trans = marker.get_transform()
+ w = renderer.points_to_pixels(self._markersize)
+
+ if cbook._str_equal(marker.get_marker(), ","):
+ gc.set_linewidth(0)
+ else:
+ # Don't scale for pixels, and don't stroke them
+ marker_trans = marker_trans.scale(w)
+ renderer.draw_markers(gc, marker_path, marker_trans,
+ subsampled, affine.frozen(),
+ fc_rgba)
+
+ alt_marker_path = marker.get_alt_path()
+ if alt_marker_path:
+ alt_marker_trans = marker.get_alt_transform()
+ alt_marker_trans = alt_marker_trans.scale(w)
+ renderer.draw_markers(
+ gc, alt_marker_path, alt_marker_trans, subsampled,
+ affine.frozen(), fcalt_rgba)
+
+ gc.restore()
+
+ renderer.close_group('line2d')
+ self.stale = False
+
+ def get_antialiased(self):
+ """Return whether antialiased rendering is used."""
+ return self._antialiased
+
+ def get_color(self):
+ """
+ Return the line color.
+
+ See also `~.Line2D.set_color`.
+ """
+ return self._color
+
+ def get_drawstyle(self):
+ """
+ Return the drawstyle.
+
+ See also `~.Line2D.set_drawstyle`.
+ """
+ return self._drawstyle
+
+ def get_gapcolor(self):
+ """
+ Return the line gapcolor.
+
+ See also `~.Line2D.set_gapcolor`.
+ """
+ return self._gapcolor
+
+ def get_linestyle(self):
+ """
+ Return the linestyle.
+
+ See also `~.Line2D.set_linestyle`.
+ """
+ return self._linestyle
+
+ def get_linewidth(self):
+ """
+ Return the linewidth in points.
+
+ See also `~.Line2D.set_linewidth`.
+ """
+ return self._linewidth
+
+ def get_marker(self):
+ """
+ Return the line marker.
+
+ See also `~.Line2D.set_marker`.
+ """
+ return self._marker.get_marker()
+
+ def get_markeredgecolor(self):
+ """
+ Return the marker edge color.
+
+ See also `~.Line2D.set_markeredgecolor`.
+ """
+ mec = self._markeredgecolor
+ if cbook._str_equal(mec, 'auto'):
+ if mpl.rcParams['_internal.classic_mode']:
+ if self._marker.get_marker() in ('.', ','):
+ return self._color
+ if (self._marker.is_filled()
+ and self._marker.get_fillstyle() != 'none'):
+ return 'k' # Bad hard-wired default...
+ return self._color
+ else:
+ return mec
+
+ def get_markeredgewidth(self):
+ """
+ Return the marker edge width in points.
+
+ See also `~.Line2D.set_markeredgewidth`.
+ """
+ return self._markeredgewidth
+
+ def _get_markerfacecolor(self, alt=False):
+ if self._marker.get_fillstyle() == 'none':
+ return 'none'
+ fc = self._markerfacecoloralt if alt else self._markerfacecolor
+ if cbook._str_lower_equal(fc, 'auto'):
+ return self._color
+ else:
+ return fc
+
+ def get_markerfacecolor(self):
+ """
+ Return the marker face color.
+
+ See also `~.Line2D.set_markerfacecolor`.
+ """
+ return self._get_markerfacecolor(alt=False)
+
+ def get_markerfacecoloralt(self):
+ """
+ Return the alternate marker face color.
+
+ See also `~.Line2D.set_markerfacecoloralt`.
+ """
+ return self._get_markerfacecolor(alt=True)
+
+ def get_markersize(self):
+ """
+ Return the marker size in points.
+
+ See also `~.Line2D.set_markersize`.
+ """
+ return self._markersize
+
+ def get_data(self, orig=True):
+ """
+ Return the line data as an ``(xdata, ydata)`` pair.
+
+ If *orig* is *True*, return the original data.
+ """
+ return self.get_xdata(orig=orig), self.get_ydata(orig=orig)
+
+ def get_xdata(self, orig=True):
+ """
+ Return the xdata.
+
+ If *orig* is *True*, return the original data, else the
+ processed data.
+ """
+ if orig:
+ return self._xorig
+ if self._invalidx:
+ self.recache()
+ return self._x
+
+ def get_ydata(self, orig=True):
+ """
+ Return the ydata.
+
+ If *orig* is *True*, return the original data, else the
+ processed data.
+ """
+ if orig:
+ return self._yorig
+ if self._invalidy:
+ self.recache()
+ return self._y
+
+ def get_path(self):
+ """Return the `~matplotlib.path.Path` associated with this line."""
+ if self._invalidy or self._invalidx:
+ self.recache()
+ return self._path
+
+ def get_xydata(self):
+ """Return the *xy* data as a (N, 2) array."""
+ if self._invalidy or self._invalidx:
+ self.recache()
+ return self._xy
+
+ def set_antialiased(self, b):
+ """
+ Set whether to use antialiased rendering.
+
+ Parameters
+ ----------
+ b : bool
+ """
+ if self._antialiased != b:
+ self.stale = True
+ self._antialiased = b
+
+ def set_color(self, color):
+ """
+ Set the color of the line.
+
+ Parameters
+ ----------
+ color : :mpltype:`color`
+ """
+ mcolors._check_color_like(color=color)
+ self._color = color
+ self.stale = True
+
+ def set_drawstyle(self, drawstyle):
+ """
+ Set the drawstyle of the plot.
+
+ The drawstyle determines how the points are connected.
+
+ Parameters
+ ----------
+ drawstyle : {'default', 'steps', 'steps-pre', 'steps-mid', \
+'steps-post'}, default: 'default'
+ For 'default', the points are connected with straight lines.
+
+ The steps variants connect the points with step-like lines,
+ i.e. horizontal lines with vertical steps. They differ in the
+ location of the step:
+
+ - 'steps-pre': The step is at the beginning of the line segment,
+ i.e. the line will be at the y-value of point to the right.
+ - 'steps-mid': The step is halfway between the points.
+ - 'steps-post: The step is at the end of the line segment,
+ i.e. the line will be at the y-value of the point to the left.
+ - 'steps' is equal to 'steps-pre' and is maintained for
+ backward-compatibility.
+
+ For examples see :doc:`/gallery/lines_bars_and_markers/step_demo`.
+ """
+ if drawstyle is None:
+ drawstyle = 'default'
+ _api.check_in_list(self.drawStyles, drawstyle=drawstyle)
+ if self._drawstyle != drawstyle:
+ self.stale = True
+ # invalidate to trigger a recache of the path
+ self._invalidx = True
+ self._drawstyle = drawstyle
+
+ def set_gapcolor(self, gapcolor):
+ """
+ Set a color to fill the gaps in the dashed line style.
+
+ .. note::
+
+ Striped lines are created by drawing two interleaved dashed lines.
+ There can be overlaps between those two, which may result in
+ artifacts when using transparency.
+
+ This functionality is experimental and may change.
+
+ Parameters
+ ----------
+ gapcolor : :mpltype:`color` or None
+ The color with which to fill the gaps. If None, the gaps are
+ unfilled.
+ """
+ if gapcolor is not None:
+ mcolors._check_color_like(color=gapcolor)
+ self._gapcolor = gapcolor
+ self.stale = True
+
+ def set_linewidth(self, w):
+ """
+ Set the line width in points.
+
+ Parameters
+ ----------
+ w : float
+ Line width, in points.
+ """
+ w = float(w)
+ if self._linewidth != w:
+ self.stale = True
+ self._linewidth = w
+ self._dash_pattern = _scale_dashes(*self._unscaled_dash_pattern, w)
+
+ def set_linestyle(self, ls):
+ """
+ Set the linestyle of the line.
+
+ Parameters
+ ----------
+ ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
+ Possible values:
+
+ - A string:
+
+ ========================================== =================
+ linestyle description
+ ========================================== =================
+ ``'-'`` or ``'solid'`` solid line
+ ``'--'`` or ``'dashed'`` dashed line
+ ``'-.'`` or ``'dashdot'`` dash-dotted line
+ ``':'`` or ``'dotted'`` dotted line
+ ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing
+ ========================================== =================
+
+ - Alternatively a dash tuple of the following form can be
+ provided::
+
+ (offset, onoffseq)
+
+ where ``onoffseq`` is an even length tuple of on and off ink
+ in points. See also :meth:`set_dashes`.
+
+ For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`.
+ """
+ if isinstance(ls, str):
+ if ls in [' ', '', 'none']:
+ ls = 'None'
+ _api.check_in_list([*self._lineStyles, *ls_mapper_r], ls=ls)
+ if ls not in self._lineStyles:
+ ls = ls_mapper_r[ls]
+ self._linestyle = ls
+ else:
+ self._linestyle = '--'
+ self._unscaled_dash_pattern = _get_dash_pattern(ls)
+ self._dash_pattern = _scale_dashes(
+ *self._unscaled_dash_pattern, self._linewidth)
+ self.stale = True
+
+ @_docstring.interpd
+ def set_marker(self, marker):
+ """
+ Set the line marker.
+
+ Parameters
+ ----------
+ marker : marker style string, `~.path.Path` or `~.markers.MarkerStyle`
+ See `~matplotlib.markers` for full description of possible
+ arguments.
+ """
+ self._marker = MarkerStyle(marker, self._marker.get_fillstyle())
+ self.stale = True
+
+ def _set_markercolor(self, name, has_rcdefault, val):
+ if val is None:
+ val = mpl.rcParams[f"lines.{name}"] if has_rcdefault else "auto"
+ attr = f"_{name}"
+ current = getattr(self, attr)
+ if current is None:
+ self.stale = True
+ else:
+ neq = current != val
+ # Much faster than `np.any(current != val)` if no arrays are used.
+ if neq.any() if isinstance(neq, np.ndarray) else neq:
+ self.stale = True
+ setattr(self, attr, val)
+
+ def set_markeredgecolor(self, ec):
+ """
+ Set the marker edge color.
+
+ Parameters
+ ----------
+ ec : :mpltype:`color`
+ """
+ self._set_markercolor("markeredgecolor", True, ec)
+
+ def set_markerfacecolor(self, fc):
+ """
+ Set the marker face color.
+
+ Parameters
+ ----------
+ fc : :mpltype:`color`
+ """
+ self._set_markercolor("markerfacecolor", True, fc)
+
+ def set_markerfacecoloralt(self, fc):
+ """
+ Set the alternate marker face color.
+
+ Parameters
+ ----------
+ fc : :mpltype:`color`
+ """
+ self._set_markercolor("markerfacecoloralt", False, fc)
+
+ def set_markeredgewidth(self, ew):
+ """
+ Set the marker edge width in points.
+
+ Parameters
+ ----------
+ ew : float
+ Marker edge width, in points.
+ """
+ if ew is None:
+ ew = mpl.rcParams['lines.markeredgewidth']
+ if self._markeredgewidth != ew:
+ self.stale = True
+ self._markeredgewidth = ew
+
+ def set_markersize(self, sz):
+ """
+ Set the marker size in points.
+
+ Parameters
+ ----------
+ sz : float
+ Marker size, in points.
+ """
+ sz = float(sz)
+ if self._markersize != sz:
+ self.stale = True
+ self._markersize = sz
+
+ def set_xdata(self, x):
+ """
+ Set the data array for x.
+
+ Parameters
+ ----------
+ x : 1D array
+
+ See Also
+ --------
+ set_data
+ set_ydata
+ """
+ if not np.iterable(x):
+ raise RuntimeError('x must be a sequence')
+ self._xorig = copy.copy(x)
+ self._invalidx = True
+ self.stale = True
+
+ def set_ydata(self, y):
+ """
+ Set the data array for y.
+
+ Parameters
+ ----------
+ y : 1D array
+
+ See Also
+ --------
+ set_data
+ set_xdata
+ """
+ if not np.iterable(y):
+ raise RuntimeError('y must be a sequence')
+ self._yorig = copy.copy(y)
+ self._invalidy = True
+ self.stale = True
+
+ def set_dashes(self, seq):
+ """
+ Set the dash sequence.
+
+ The dash sequence is a sequence of floats of even length describing
+ the length of dashes and spaces in points.
+
+ For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point
+ dashes separated by 2 point spaces.
+
+ See also `~.Line2D.set_gapcolor`, which allows those spaces to be
+ filled with a color.
+
+ Parameters
+ ----------
+ seq : sequence of floats (on/off ink in points) or (None, None)
+ If *seq* is empty or ``(None, None)``, the linestyle will be set
+ to solid.
+ """
+ if seq == (None, None) or len(seq) == 0:
+ self.set_linestyle('-')
+ else:
+ self.set_linestyle((0, seq))
+
+ def update_from(self, other):
+ """Copy properties from *other* to self."""
+ super().update_from(other)
+ self._linestyle = other._linestyle
+ self._linewidth = other._linewidth
+ self._color = other._color
+ self._gapcolor = other._gapcolor
+ self._markersize = other._markersize
+ self._markerfacecolor = other._markerfacecolor
+ self._markerfacecoloralt = other._markerfacecoloralt
+ self._markeredgecolor = other._markeredgecolor
+ self._markeredgewidth = other._markeredgewidth
+ self._unscaled_dash_pattern = other._unscaled_dash_pattern
+ self._dash_pattern = other._dash_pattern
+ self._dashcapstyle = other._dashcapstyle
+ self._dashjoinstyle = other._dashjoinstyle
+ self._solidcapstyle = other._solidcapstyle
+ self._solidjoinstyle = other._solidjoinstyle
+
+ self._linestyle = other._linestyle
+ self._marker = MarkerStyle(marker=other._marker)
+ self._drawstyle = other._drawstyle
+
+ @_docstring.interpd
+ def set_dash_joinstyle(self, s):
+ """
+ How to join segments of the line if it `~Line2D.is_dashed`.
+
+ The default joinstyle is :rc:`lines.dash_joinstyle`.
+
+ Parameters
+ ----------
+ s : `.JoinStyle` or %(JoinStyle)s
+ """
+ js = JoinStyle(s)
+ if self._dashjoinstyle != js:
+ self.stale = True
+ self._dashjoinstyle = js
+
+ @_docstring.interpd
+ def set_solid_joinstyle(self, s):
+ """
+ How to join segments if the line is solid (not `~Line2D.is_dashed`).
+
+ The default joinstyle is :rc:`lines.solid_joinstyle`.
+
+ Parameters
+ ----------
+ s : `.JoinStyle` or %(JoinStyle)s
+ """
+ js = JoinStyle(s)
+ if self._solidjoinstyle != js:
+ self.stale = True
+ self._solidjoinstyle = js
+
+ def get_dash_joinstyle(self):
+ """
+ Return the `.JoinStyle` for dashed lines.
+
+ See also `~.Line2D.set_dash_joinstyle`.
+ """
+ return self._dashjoinstyle.name
+
+ def get_solid_joinstyle(self):
+ """
+ Return the `.JoinStyle` for solid lines.
+
+ See also `~.Line2D.set_solid_joinstyle`.
+ """
+ return self._solidjoinstyle.name
+
+ @_docstring.interpd
+ def set_dash_capstyle(self, s):
+ """
+ How to draw the end caps if the line is `~Line2D.is_dashed`.
+
+ The default capstyle is :rc:`lines.dash_capstyle`.
+
+ Parameters
+ ----------
+ s : `.CapStyle` or %(CapStyle)s
+ """
+ cs = CapStyle(s)
+ if self._dashcapstyle != cs:
+ self.stale = True
+ self._dashcapstyle = cs
+
+ @_docstring.interpd
+ def set_solid_capstyle(self, s):
+ """
+ How to draw the end caps if the line is solid (not `~Line2D.is_dashed`)
+
+ The default capstyle is :rc:`lines.solid_capstyle`.
+
+ Parameters
+ ----------
+ s : `.CapStyle` or %(CapStyle)s
+ """
+ cs = CapStyle(s)
+ if self._solidcapstyle != cs:
+ self.stale = True
+ self._solidcapstyle = cs
+
+ def get_dash_capstyle(self):
+ """
+ Return the `.CapStyle` for dashed lines.
+
+ See also `~.Line2D.set_dash_capstyle`.
+ """
+ return self._dashcapstyle.name
+
+ def get_solid_capstyle(self):
+ """
+ Return the `.CapStyle` for solid lines.
+
+ See also `~.Line2D.set_solid_capstyle`.
+ """
+ return self._solidcapstyle.name
+
+ def is_dashed(self):
+ """
+ Return whether line has a dashed linestyle.
+
+ A custom linestyle is assumed to be dashed, we do not inspect the
+ ``onoffseq`` directly.
+
+ See also `~.Line2D.set_linestyle`.
+ """
+ return self._linestyle in ('--', '-.', ':')
+
+
+class AxLine(Line2D):
+ """
+ A helper class that implements `~.Axes.axline`, by recomputing the artist
+ transform at draw time.
+ """
+
+ def __init__(self, xy1, xy2, slope, **kwargs):
+ """
+ Parameters
+ ----------
+ xy1 : (float, float)
+ The first set of (x, y) coordinates for the line to pass through.
+ xy2 : (float, float) or None
+ The second set of (x, y) coordinates for the line to pass through.
+ Both *xy2* and *slope* must be passed, but one of them must be None.
+ slope : float or None
+ The slope of the line. Both *xy2* and *slope* must be passed, but one of
+ them must be None.
+ """
+ super().__init__([0, 1], [0, 1], **kwargs)
+
+ if (xy2 is None and slope is None or
+ xy2 is not None and slope is not None):
+ raise TypeError(
+ "Exactly one of 'xy2' and 'slope' must be given")
+
+ self._slope = slope
+ self._xy1 = xy1
+ self._xy2 = xy2
+
+ def get_transform(self):
+ ax = self.axes
+ points_transform = self._transform - ax.transData + ax.transScale
+
+ if self._xy2 is not None:
+ # two points were given
+ (x1, y1), (x2, y2) = \
+ points_transform.transform([self._xy1, self._xy2])
+ dx = x2 - x1
+ dy = y2 - y1
+ if dx == 0:
+ if dy == 0:
+ raise ValueError(
+ f"Cannot draw a line through two identical points "
+ f"(x={(x1, x2)}, y={(y1, y2)})")
+ slope = np.inf
+ else:
+ slope = dy / dx
+ else:
+ # one point and a slope were given
+ x1, y1 = points_transform.transform(self._xy1)
+ slope = self._slope
+ (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim)
+ # General case: find intersections with view limits in either
+ # direction, and draw between the middle two points.
+ if slope == 0:
+ start = vxlo, y1
+ stop = vxhi, y1
+ elif np.isinf(slope):
+ start = x1, vylo
+ stop = x1, vyhi
+ else:
+ _, start, stop, _ = sorted([
+ (vxlo, y1 + (vxlo - x1) * slope),
+ (vxhi, y1 + (vxhi - x1) * slope),
+ (x1 + (vylo - y1) / slope, vylo),
+ (x1 + (vyhi - y1) / slope, vyhi),
+ ])
+ return (BboxTransformTo(Bbox([start, stop]))
+ + ax.transLimits + ax.transAxes)
+
+ def draw(self, renderer):
+ self._transformed_path = None # Force regen.
+ super().draw(renderer)
+
+ def get_xy1(self):
+ """Return the *xy1* value of the line."""
+ return self._xy1
+
+ def get_xy2(self):
+ """Return the *xy2* value of the line."""
+ return self._xy2
+
+ def get_slope(self):
+ """Return the *slope* value of the line."""
+ return self._slope
+
+ def set_xy1(self, *args, **kwargs):
+ """
+ Set the *xy1* value of the line.
+
+ Parameters
+ ----------
+ xy1 : tuple[float, float]
+ Points for the line to pass through.
+ """
+ params = _api.select_matching_signature([
+ lambda self, x, y: locals(), lambda self, xy1: locals(),
+ ], self, *args, **kwargs)
+ if "x" in params:
+ _api.warn_deprecated("3.10", message=(
+ "Passing x and y separately to AxLine.set_xy1 is deprecated since "
+ "%(since)s; pass them as a single tuple instead."))
+ xy1 = params["x"], params["y"]
+ else:
+ xy1 = params["xy1"]
+ self._xy1 = xy1
+
+ def set_xy2(self, *args, **kwargs):
+ """
+ Set the *xy2* value of the line.
+
+ .. note::
+
+ You can only set *xy2* if the line was created using the *xy2*
+ parameter. If the line was created using *slope*, please use
+ `~.AxLine.set_slope`.
+
+ Parameters
+ ----------
+ xy2 : tuple[float, float]
+ Points for the line to pass through.
+ """
+ if self._slope is None:
+ params = _api.select_matching_signature([
+ lambda self, x, y: locals(), lambda self, xy2: locals(),
+ ], self, *args, **kwargs)
+ if "x" in params:
+ _api.warn_deprecated("3.10", message=(
+ "Passing x and y separately to AxLine.set_xy2 is deprecated since "
+ "%(since)s; pass them as a single tuple instead."))
+ xy2 = params["x"], params["y"]
+ else:
+ xy2 = params["xy2"]
+ self._xy2 = xy2
+ else:
+ raise ValueError("Cannot set an 'xy2' value while 'slope' is set;"
+ " they differ but their functionalities overlap")
+
+ def set_slope(self, slope):
+ """
+ Set the *slope* value of the line.
+
+ .. note::
+
+ You can only set *slope* if the line was created using the *slope*
+ parameter. If the line was created using *xy2*, please use
+ `~.AxLine.set_xy2`.
+
+ Parameters
+ ----------
+ slope : float
+ The slope of the line.
+ """
+ if self._xy2 is None:
+ self._slope = slope
+ else:
+ raise ValueError("Cannot set a 'slope' value while 'xy2' is set;"
+ " they differ but their functionalities overlap")
+
+
+class VertexSelector:
+ """
+ Manage the callbacks to maintain a list of selected vertices for `.Line2D`.
+ Derived classes should override the `process_selected` method to do
+ something with the picks.
+
+ Here is an example which highlights the selected verts with red circles::
+
+ import numpy as np
+ import matplotlib.pyplot as plt
+ import matplotlib.lines as lines
+
+ class HighlightSelected(lines.VertexSelector):
+ def __init__(self, line, fmt='ro', **kwargs):
+ super().__init__(line)
+ self.markers, = self.axes.plot([], [], fmt, **kwargs)
+
+ def process_selected(self, ind, xs, ys):
+ self.markers.set_data(xs, ys)
+ self.canvas.draw()
+
+ fig, ax = plt.subplots()
+ x, y = np.random.rand(2, 30)
+ line, = ax.plot(x, y, 'bs-', picker=5)
+
+ selector = HighlightSelected(line)
+ plt.show()
+ """
+
+ def __init__(self, line):
+ """
+ Parameters
+ ----------
+ line : `~matplotlib.lines.Line2D`
+ The line must already have been added to an `~.axes.Axes` and must
+ have its picker property set.
+ """
+ if line.axes is None:
+ raise RuntimeError('You must first add the line to the Axes')
+ if line.get_picker() is None:
+ raise RuntimeError('You must first set the picker property '
+ 'of the line')
+ self.axes = line.axes
+ self.line = line
+ self.cid = self.canvas.callbacks._connect_picklable(
+ 'pick_event', self.onpick)
+ self.ind = set()
+
+ canvas = property(lambda self: self.axes.get_figure(root=True).canvas)
+
+ def process_selected(self, ind, xs, ys):
+ """
+ Default "do nothing" implementation of the `process_selected` method.
+
+ Parameters
+ ----------
+ ind : list of int
+ The indices of the selected vertices.
+ xs, ys : array-like
+ The coordinates of the selected vertices.
+ """
+ pass
+
+ def onpick(self, event):
+ """When the line is picked, update the set of selected indices."""
+ if event.artist is not self.line:
+ return
+ self.ind ^= set(event.ind)
+ ind = sorted(self.ind)
+ xdata, ydata = self.line.get_data()
+ self.process_selected(ind, xdata[ind], ydata[ind])
+
+
+lineStyles = Line2D._lineStyles
+lineMarkers = MarkerStyle.markers
+drawStyles = Line2D.drawStyles
+fillStyles = MarkerStyle.fillstyles
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..7989a03dae3a0680f63511a0d5347517753a2d1e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.pyi
@@ -0,0 +1,153 @@
+from .artist import Artist
+from .axes import Axes
+from .backend_bases import MouseEvent, FigureCanvasBase
+from .path import Path
+from .transforms import Bbox
+
+from collections.abc import Callable, Sequence
+from typing import Any, Literal, overload
+from .typing import (
+ ColorType,
+ DrawStyleType,
+ FillStyleType,
+ LineStyleType,
+ CapStyleType,
+ JoinStyleType,
+ MarkEveryType,
+ MarkerType,
+)
+from numpy.typing import ArrayLike
+
+def segment_hits(
+ cx: ArrayLike, cy: ArrayLike, x: ArrayLike, y: ArrayLike, radius: ArrayLike
+) -> ArrayLike: ...
+
+class Line2D(Artist):
+ lineStyles: dict[str, str]
+ drawStyles: dict[str, str]
+ drawStyleKeys: list[str]
+ markers: dict[str | int, str]
+ filled_markers: tuple[str, ...]
+ fillStyles: tuple[str, ...]
+ zorder: float
+ ind_offset: float
+ def __init__(
+ self,
+ xdata: ArrayLike,
+ ydata: ArrayLike,
+ *,
+ linewidth: float | None = ...,
+ linestyle: LineStyleType | None = ...,
+ color: ColorType | None = ...,
+ gapcolor: ColorType | None = ...,
+ marker: MarkerType | None = ...,
+ markersize: float | None = ...,
+ markeredgewidth: float | None = ...,
+ markeredgecolor: ColorType | None = ...,
+ markerfacecolor: ColorType | None = ...,
+ markerfacecoloralt: ColorType = ...,
+ fillstyle: FillStyleType | None = ...,
+ antialiased: bool | None = ...,
+ dash_capstyle: CapStyleType | None = ...,
+ solid_capstyle: CapStyleType | None = ...,
+ dash_joinstyle: JoinStyleType | None = ...,
+ solid_joinstyle: JoinStyleType | None = ...,
+ pickradius: float = ...,
+ drawstyle: DrawStyleType | None = ...,
+ markevery: MarkEveryType | None = ...,
+ **kwargs
+ ) -> None: ...
+ def contains(self, mouseevent: MouseEvent) -> tuple[bool, dict]: ...
+ def get_pickradius(self) -> float: ...
+ def set_pickradius(self, pickradius: float) -> None: ...
+ pickradius: float
+ def get_fillstyle(self) -> FillStyleType: ...
+ stale: bool
+ def set_fillstyle(self, fs: FillStyleType) -> None: ...
+ def set_markevery(self, every: MarkEveryType) -> None: ...
+ def get_markevery(self) -> MarkEveryType: ...
+ def set_picker(
+ self, p: None | bool | float | Callable[[Artist, MouseEvent], tuple[bool, dict]]
+ ) -> None: ...
+ def get_bbox(self) -> Bbox: ...
+ @overload
+ def set_data(self, args: ArrayLike) -> None: ...
+ @overload
+ def set_data(self, x: ArrayLike, y: ArrayLike) -> None: ...
+ def recache_always(self) -> None: ...
+ def recache(self, always: bool = ...) -> None: ...
+ def get_antialiased(self) -> bool: ...
+ def get_color(self) -> ColorType: ...
+ def get_drawstyle(self) -> DrawStyleType: ...
+ def get_gapcolor(self) -> ColorType: ...
+ def get_linestyle(self) -> LineStyleType: ...
+ def get_linewidth(self) -> float: ...
+ def get_marker(self) -> MarkerType: ...
+ def get_markeredgecolor(self) -> ColorType: ...
+ def get_markeredgewidth(self) -> float: ...
+ def get_markerfacecolor(self) -> ColorType: ...
+ def get_markerfacecoloralt(self) -> ColorType: ...
+ def get_markersize(self) -> float: ...
+ def get_data(self, orig: bool = ...) -> tuple[ArrayLike, ArrayLike]: ...
+ def get_xdata(self, orig: bool = ...) -> ArrayLike: ...
+ def get_ydata(self, orig: bool = ...) -> ArrayLike: ...
+ def get_path(self) -> Path: ...
+ def get_xydata(self) -> ArrayLike: ...
+ def set_antialiased(self, b: bool) -> None: ...
+ def set_color(self, color: ColorType) -> None: ...
+ def set_drawstyle(self, drawstyle: DrawStyleType | None) -> None: ...
+ def set_gapcolor(self, gapcolor: ColorType | None) -> None: ...
+ def set_linewidth(self, w: float) -> None: ...
+ def set_linestyle(self, ls: LineStyleType) -> None: ...
+ def set_marker(self, marker: MarkerType) -> None: ...
+ def set_markeredgecolor(self, ec: ColorType | None) -> None: ...
+ def set_markerfacecolor(self, fc: ColorType | None) -> None: ...
+ def set_markerfacecoloralt(self, fc: ColorType | None) -> None: ...
+ def set_markeredgewidth(self, ew: float | None) -> None: ...
+ def set_markersize(self, sz: float) -> None: ...
+ def set_xdata(self, x: ArrayLike) -> None: ...
+ def set_ydata(self, y: ArrayLike) -> None: ...
+ def set_dashes(self, seq: Sequence[float] | tuple[None, None]) -> None: ...
+ def update_from(self, other: Artist) -> None: ...
+ def set_dash_joinstyle(self, s: JoinStyleType) -> None: ...
+ def set_solid_joinstyle(self, s: JoinStyleType) -> None: ...
+ def get_dash_joinstyle(self) -> Literal["miter", "round", "bevel"]: ...
+ def get_solid_joinstyle(self) -> Literal["miter", "round", "bevel"]: ...
+ def set_dash_capstyle(self, s: CapStyleType) -> None: ...
+ def set_solid_capstyle(self, s: CapStyleType) -> None: ...
+ def get_dash_capstyle(self) -> Literal["butt", "projecting", "round"]: ...
+ def get_solid_capstyle(self) -> Literal["butt", "projecting", "round"]: ...
+ def is_dashed(self) -> bool: ...
+
+class AxLine(Line2D):
+ def __init__(
+ self,
+ xy1: tuple[float, float],
+ xy2: tuple[float, float] | None,
+ slope: float | None,
+ **kwargs
+ ) -> None: ...
+ def get_xy1(self) -> tuple[float, float] | None: ...
+ def get_xy2(self) -> tuple[float, float] | None: ...
+ def get_slope(self) -> float: ...
+ def set_xy1(self, xy1: tuple[float, float]) -> None: ...
+ def set_xy2(self, xy2: tuple[float, float]) -> None: ...
+ def set_slope(self, slope: float) -> None: ...
+
+class VertexSelector:
+ axes: Axes
+ line: Line2D
+ cid: int
+ ind: set[int]
+ def __init__(self, line: Line2D) -> None: ...
+ @property
+ def canvas(self) -> FigureCanvasBase: ...
+ def process_selected(
+ self, ind: Sequence[int], xs: ArrayLike, ys: ArrayLike
+ ) -> None: ...
+ def onpick(self, event: Any) -> None: ...
+
+lineStyles: dict[str, str]
+lineMarkers: dict[str | int, str]
+drawStyles: dict[str, str]
+fillStyles: tuple[FillStyleType, ...]
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa5e66e73ade4926c518905c0575e6bf99dfcb24
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.py
@@ -0,0 +1,908 @@
+r"""
+Functions to handle markers; used by the marker functionality of
+`~matplotlib.axes.Axes.plot`, `~matplotlib.axes.Axes.scatter`, and
+`~matplotlib.axes.Axes.errorbar`.
+
+All possible markers are defined here:
+
+============================== ====== =========================================
+marker symbol description
+============================== ====== =========================================
+``"."`` |m00| point
+``","`` |m01| pixel
+``"o"`` |m02| circle
+``"v"`` |m03| triangle_down
+``"^"`` |m04| triangle_up
+``"<"`` |m05| triangle_left
+``">"`` |m06| triangle_right
+``"1"`` |m07| tri_down
+``"2"`` |m08| tri_up
+``"3"`` |m09| tri_left
+``"4"`` |m10| tri_right
+``"8"`` |m11| octagon
+``"s"`` |m12| square
+``"p"`` |m13| pentagon
+``"P"`` |m23| plus (filled)
+``"*"`` |m14| star
+``"h"`` |m15| hexagon1
+``"H"`` |m16| hexagon2
+``"+"`` |m17| plus
+``"x"`` |m18| x
+``"X"`` |m24| x (filled)
+``"D"`` |m19| diamond
+``"d"`` |m20| thin_diamond
+``"|"`` |m21| vline
+``"_"`` |m22| hline
+``0`` (``TICKLEFT``) |m25| tickleft
+``1`` (``TICKRIGHT``) |m26| tickright
+``2`` (``TICKUP``) |m27| tickup
+``3`` (``TICKDOWN``) |m28| tickdown
+``4`` (``CARETLEFT``) |m29| caretleft
+``5`` (``CARETRIGHT``) |m30| caretright
+``6`` (``CARETUP``) |m31| caretup
+``7`` (``CARETDOWN``) |m32| caretdown
+``8`` (``CARETLEFTBASE``) |m33| caretleft (centered at base)
+``9`` (``CARETRIGHTBASE``) |m34| caretright (centered at base)
+``10`` (``CARETUPBASE``) |m35| caretup (centered at base)
+``11`` (``CARETDOWNBASE``) |m36| caretdown (centered at base)
+``"none"`` or ``"None"`` nothing
+``" "`` or ``""`` nothing
+``"$...$"`` |m37| Render the string using mathtext.
+ E.g ``"$f$"`` for marker showing the
+ letter ``f``.
+``verts`` A list of (x, y) pairs used for Path
+ vertices. The center of the marker is
+ located at (0, 0) and the size is
+ normalized, such that the created path
+ is encapsulated inside the unit cell.
+``path`` A `~matplotlib.path.Path` instance.
+``(numsides, 0, angle)`` A regular polygon with ``numsides``
+ sides, rotated by ``angle``.
+``(numsides, 1, angle)`` A star-like symbol with ``numsides``
+ sides, rotated by ``angle``.
+``(numsides, 2, angle)`` An asterisk with ``numsides`` sides,
+ rotated by ``angle``.
+============================== ====== =========================================
+
+Note that special symbols can be defined via the
+:ref:`STIX math font `,
+e.g. ``"$\u266B$"``. For an overview over the STIX font symbols refer to the
+`STIX font table `_.
+Also see the :doc:`/gallery/text_labels_and_annotations/stix_fonts_demo`.
+
+Integer numbers from ``0`` to ``11`` create lines and triangles. Those are
+equally accessible via capitalized variables, like ``CARETDOWNBASE``.
+Hence the following are equivalent::
+
+ plt.plot([1, 2, 3], marker=11)
+ plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE)
+
+Markers join and cap styles can be customized by creating a new instance of
+MarkerStyle.
+A MarkerStyle can also have a custom `~matplotlib.transforms.Transform`
+allowing it to be arbitrarily rotated or offset.
+
+Examples showing the use of markers:
+
+* :doc:`/gallery/lines_bars_and_markers/marker_reference`
+* :doc:`/gallery/lines_bars_and_markers/scatter_star_poly`
+* :doc:`/gallery/lines_bars_and_markers/multivariate_marker_plot`
+
+.. |m00| image:: /_static/markers/m00.png
+.. |m01| image:: /_static/markers/m01.png
+.. |m02| image:: /_static/markers/m02.png
+.. |m03| image:: /_static/markers/m03.png
+.. |m04| image:: /_static/markers/m04.png
+.. |m05| image:: /_static/markers/m05.png
+.. |m06| image:: /_static/markers/m06.png
+.. |m07| image:: /_static/markers/m07.png
+.. |m08| image:: /_static/markers/m08.png
+.. |m09| image:: /_static/markers/m09.png
+.. |m10| image:: /_static/markers/m10.png
+.. |m11| image:: /_static/markers/m11.png
+.. |m12| image:: /_static/markers/m12.png
+.. |m13| image:: /_static/markers/m13.png
+.. |m14| image:: /_static/markers/m14.png
+.. |m15| image:: /_static/markers/m15.png
+.. |m16| image:: /_static/markers/m16.png
+.. |m17| image:: /_static/markers/m17.png
+.. |m18| image:: /_static/markers/m18.png
+.. |m19| image:: /_static/markers/m19.png
+.. |m20| image:: /_static/markers/m20.png
+.. |m21| image:: /_static/markers/m21.png
+.. |m22| image:: /_static/markers/m22.png
+.. |m23| image:: /_static/markers/m23.png
+.. |m24| image:: /_static/markers/m24.png
+.. |m25| image:: /_static/markers/m25.png
+.. |m26| image:: /_static/markers/m26.png
+.. |m27| image:: /_static/markers/m27.png
+.. |m28| image:: /_static/markers/m28.png
+.. |m29| image:: /_static/markers/m29.png
+.. |m30| image:: /_static/markers/m30.png
+.. |m31| image:: /_static/markers/m31.png
+.. |m32| image:: /_static/markers/m32.png
+.. |m33| image:: /_static/markers/m33.png
+.. |m34| image:: /_static/markers/m34.png
+.. |m35| image:: /_static/markers/m35.png
+.. |m36| image:: /_static/markers/m36.png
+.. |m37| image:: /_static/markers/m37.png
+"""
+import copy
+
+from collections.abc import Sized
+
+import numpy as np
+
+import matplotlib as mpl
+from . import _api, cbook
+from .path import Path
+from .transforms import IdentityTransform, Affine2D
+from ._enums import JoinStyle, CapStyle
+
+# special-purpose marker identifiers:
+(TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN,
+ CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN,
+ CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE) = range(12)
+
+_empty_path = Path(np.empty((0, 2)))
+
+
+class MarkerStyle:
+ """
+ A class representing marker types.
+
+ Instances are immutable. If you need to change anything, create a new
+ instance.
+
+ Attributes
+ ----------
+ markers : dict
+ All known markers.
+ filled_markers : tuple
+ All known filled markers. This is a subset of *markers*.
+ fillstyles : tuple
+ The supported fillstyles.
+ """
+
+ markers = {
+ '.': 'point',
+ ',': 'pixel',
+ 'o': 'circle',
+ 'v': 'triangle_down',
+ '^': 'triangle_up',
+ '<': 'triangle_left',
+ '>': 'triangle_right',
+ '1': 'tri_down',
+ '2': 'tri_up',
+ '3': 'tri_left',
+ '4': 'tri_right',
+ '8': 'octagon',
+ 's': 'square',
+ 'p': 'pentagon',
+ '*': 'star',
+ 'h': 'hexagon1',
+ 'H': 'hexagon2',
+ '+': 'plus',
+ 'x': 'x',
+ 'D': 'diamond',
+ 'd': 'thin_diamond',
+ '|': 'vline',
+ '_': 'hline',
+ 'P': 'plus_filled',
+ 'X': 'x_filled',
+ TICKLEFT: 'tickleft',
+ TICKRIGHT: 'tickright',
+ TICKUP: 'tickup',
+ TICKDOWN: 'tickdown',
+ CARETLEFT: 'caretleft',
+ CARETRIGHT: 'caretright',
+ CARETUP: 'caretup',
+ CARETDOWN: 'caretdown',
+ CARETLEFTBASE: 'caretleftbase',
+ CARETRIGHTBASE: 'caretrightbase',
+ CARETUPBASE: 'caretupbase',
+ CARETDOWNBASE: 'caretdownbase',
+ "None": 'nothing',
+ "none": 'nothing',
+ ' ': 'nothing',
+ '': 'nothing'
+ }
+
+ # Just used for informational purposes. is_filled()
+ # is calculated in the _set_* functions.
+ filled_markers = (
+ '.', 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd',
+ 'P', 'X')
+
+ fillstyles = ('full', 'left', 'right', 'bottom', 'top', 'none')
+ _half_fillstyles = ('left', 'right', 'bottom', 'top')
+
+ def __init__(self, marker,
+ fillstyle=None, transform=None, capstyle=None, joinstyle=None):
+ """
+ Parameters
+ ----------
+ marker : str, array-like, Path, MarkerStyle
+ - Another instance of `MarkerStyle` copies the details of that *marker*.
+ - For other possible marker values, see the module docstring
+ `matplotlib.markers`.
+
+ fillstyle : str, default: :rc:`markers.fillstyle`
+ One of 'full', 'left', 'right', 'bottom', 'top', 'none'.
+
+ transform : `~matplotlib.transforms.Transform`, optional
+ Transform that will be combined with the native transform of the
+ marker.
+
+ capstyle : `.CapStyle` or %(CapStyle)s, optional
+ Cap style that will override the default cap style of the marker.
+
+ joinstyle : `.JoinStyle` or %(JoinStyle)s, optional
+ Join style that will override the default join style of the marker.
+ """
+ self._marker_function = None
+ self._user_transform = transform
+ self._user_capstyle = CapStyle(capstyle) if capstyle is not None else None
+ self._user_joinstyle = JoinStyle(joinstyle) if joinstyle is not None else None
+ self._set_fillstyle(fillstyle)
+ self._set_marker(marker)
+
+ def _recache(self):
+ if self._marker_function is None:
+ return
+ self._path = _empty_path
+ self._transform = IdentityTransform()
+ self._alt_path = None
+ self._alt_transform = None
+ self._snap_threshold = None
+ self._joinstyle = JoinStyle.round
+ self._capstyle = self._user_capstyle or CapStyle.butt
+ # Initial guess: Assume the marker is filled unless the fillstyle is
+ # set to 'none'. The marker function will override this for unfilled
+ # markers.
+ self._filled = self._fillstyle != 'none'
+ self._marker_function()
+
+ def __bool__(self):
+ return bool(len(self._path.vertices))
+
+ def is_filled(self):
+ return self._filled
+
+ def get_fillstyle(self):
+ return self._fillstyle
+
+ def _set_fillstyle(self, fillstyle):
+ """
+ Set the fillstyle.
+
+ Parameters
+ ----------
+ fillstyle : {'full', 'left', 'right', 'bottom', 'top', 'none'}
+ The part of the marker surface that is colored with
+ markerfacecolor.
+ """
+ if fillstyle is None:
+ fillstyle = mpl.rcParams['markers.fillstyle']
+ _api.check_in_list(self.fillstyles, fillstyle=fillstyle)
+ self._fillstyle = fillstyle
+
+ def get_joinstyle(self):
+ return self._joinstyle.name
+
+ def get_capstyle(self):
+ return self._capstyle.name
+
+ def get_marker(self):
+ return self._marker
+
+ def _set_marker(self, marker):
+ """
+ Set the marker.
+
+ Parameters
+ ----------
+ marker : str, array-like, Path, MarkerStyle
+ - Another instance of `MarkerStyle` copies the details of that *marker*.
+ - For other possible marker values see the module docstring
+ `matplotlib.markers`.
+ """
+ if isinstance(marker, str) and cbook.is_math_text(marker):
+ self._marker_function = self._set_mathtext_path
+ elif isinstance(marker, (int, str)) and marker in self.markers:
+ self._marker_function = getattr(self, '_set_' + self.markers[marker])
+ elif (isinstance(marker, np.ndarray) and marker.ndim == 2 and
+ marker.shape[1] == 2):
+ self._marker_function = self._set_vertices
+ elif isinstance(marker, Path):
+ self._marker_function = self._set_path_marker
+ elif (isinstance(marker, Sized) and len(marker) in (2, 3) and
+ marker[1] in (0, 1, 2)):
+ self._marker_function = self._set_tuple_marker
+ elif isinstance(marker, MarkerStyle):
+ self.__dict__ = copy.deepcopy(marker.__dict__)
+ else:
+ try:
+ Path(marker)
+ self._marker_function = self._set_vertices
+ except ValueError as err:
+ raise ValueError(
+ f'Unrecognized marker style {marker!r}') from err
+
+ if not isinstance(marker, MarkerStyle):
+ self._marker = marker
+ self._recache()
+
+ def get_path(self):
+ """
+ Return a `.Path` for the primary part of the marker.
+
+ For unfilled markers this is the whole marker, for filled markers,
+ this is the area to be drawn with *markerfacecolor*.
+ """
+ return self._path
+
+ def get_transform(self):
+ """
+ Return the transform to be applied to the `.Path` from
+ `MarkerStyle.get_path()`.
+ """
+ if self._user_transform is None:
+ return self._transform.frozen()
+ else:
+ return (self._transform + self._user_transform).frozen()
+
+ def get_alt_path(self):
+ """
+ Return a `.Path` for the alternate part of the marker.
+
+ For unfilled markers, this is *None*; for filled markers, this is the
+ area to be drawn with *markerfacecoloralt*.
+ """
+ return self._alt_path
+
+ def get_alt_transform(self):
+ """
+ Return the transform to be applied to the `.Path` from
+ `MarkerStyle.get_alt_path()`.
+ """
+ if self._user_transform is None:
+ return self._alt_transform.frozen()
+ else:
+ return (self._alt_transform + self._user_transform).frozen()
+
+ def get_snap_threshold(self):
+ return self._snap_threshold
+
+ def get_user_transform(self):
+ """Return user supplied part of marker transform."""
+ if self._user_transform is not None:
+ return self._user_transform.frozen()
+
+ def transformed(self, transform):
+ """
+ Return a new version of this marker with the transform applied.
+
+ Parameters
+ ----------
+ transform : `~matplotlib.transforms.Affine2D`
+ Transform will be combined with current user supplied transform.
+ """
+ new_marker = MarkerStyle(self)
+ if new_marker._user_transform is not None:
+ new_marker._user_transform += transform
+ else:
+ new_marker._user_transform = transform
+ return new_marker
+
+ def rotated(self, *, deg=None, rad=None):
+ """
+ Return a new version of this marker rotated by specified angle.
+
+ Parameters
+ ----------
+ deg : float, optional
+ Rotation angle in degrees.
+
+ rad : float, optional
+ Rotation angle in radians.
+
+ .. note:: You must specify exactly one of deg or rad.
+ """
+ if deg is None and rad is None:
+ raise ValueError('One of deg or rad is required')
+ if deg is not None and rad is not None:
+ raise ValueError('Only one of deg and rad can be supplied')
+ new_marker = MarkerStyle(self)
+ if new_marker._user_transform is None:
+ new_marker._user_transform = Affine2D()
+
+ if deg is not None:
+ new_marker._user_transform.rotate_deg(deg)
+ if rad is not None:
+ new_marker._user_transform.rotate(rad)
+
+ return new_marker
+
+ def scaled(self, sx, sy=None):
+ """
+ Return new marker scaled by specified scale factors.
+
+ If *sy* is not given, the same scale is applied in both the *x*- and
+ *y*-directions.
+
+ Parameters
+ ----------
+ sx : float
+ *X*-direction scaling factor.
+ sy : float, optional
+ *Y*-direction scaling factor.
+ """
+ if sy is None:
+ sy = sx
+
+ new_marker = MarkerStyle(self)
+ _transform = new_marker._user_transform or Affine2D()
+ new_marker._user_transform = _transform.scale(sx, sy)
+ return new_marker
+
+ def _set_nothing(self):
+ self._filled = False
+
+ def _set_custom_marker(self, path):
+ rescale = np.max(np.abs(path.vertices)) # max of x's and y's.
+ self._transform = Affine2D().scale(0.5 / rescale)
+ self._path = path
+
+ def _set_path_marker(self):
+ self._set_custom_marker(self._marker)
+
+ def _set_vertices(self):
+ self._set_custom_marker(Path(self._marker))
+
+ def _set_tuple_marker(self):
+ marker = self._marker
+ if len(marker) == 2:
+ numsides, rotation = marker[0], 0.0
+ elif len(marker) == 3:
+ numsides, rotation = marker[0], marker[2]
+ symstyle = marker[1]
+ if symstyle == 0:
+ self._path = Path.unit_regular_polygon(numsides)
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+ elif symstyle == 1:
+ self._path = Path.unit_regular_star(numsides)
+ self._joinstyle = self._user_joinstyle or JoinStyle.bevel
+ elif symstyle == 2:
+ self._path = Path.unit_regular_asterisk(numsides)
+ self._filled = False
+ self._joinstyle = self._user_joinstyle or JoinStyle.bevel
+ else:
+ raise ValueError(f"Unexpected tuple marker: {marker}")
+ self._transform = Affine2D().scale(0.5).rotate_deg(rotation)
+
+ def _set_mathtext_path(self):
+ """
+ Draw mathtext markers '$...$' using `.TextPath` object.
+
+ Submitted by tcb
+ """
+ from matplotlib.text import TextPath
+
+ # again, the properties could be initialised just once outside
+ # this function
+ text = TextPath(xy=(0, 0), s=self.get_marker(),
+ usetex=mpl.rcParams['text.usetex'])
+ if len(text.vertices) == 0:
+ return
+
+ bbox = text.get_extents()
+ max_dim = max(bbox.width, bbox.height)
+ self._transform = (
+ Affine2D()
+ .translate(-bbox.xmin + 0.5 * -bbox.width, -bbox.ymin + 0.5 * -bbox.height)
+ .scale(1.0 / max_dim))
+ self._path = text
+ self._snap = False
+
+ def _half_fill(self):
+ return self.get_fillstyle() in self._half_fillstyles
+
+ def _set_circle(self, size=1.0):
+ self._transform = Affine2D().scale(0.5 * size)
+ self._snap_threshold = np.inf
+ if not self._half_fill():
+ self._path = Path.unit_circle()
+ else:
+ self._path = self._alt_path = Path.unit_circle_righthalf()
+ fs = self.get_fillstyle()
+ self._transform.rotate_deg(
+ {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs])
+ self._alt_transform = self._transform.frozen().rotate_deg(180.)
+
+ def _set_point(self):
+ self._set_circle(size=0.5)
+
+ def _set_pixel(self):
+ self._path = Path.unit_rectangle()
+ # Ideally, you'd want -0.5, -0.5 here, but then the snapping
+ # algorithm in the Agg backend will round this to a 2x2
+ # rectangle from (-1, -1) to (1, 1). By offsetting it
+ # slightly, we can force it to be (0, 0) to (1, 1), which both
+ # makes it only be a single pixel and places it correctly
+ # aligned to 1-width stroking (i.e. the ticks). This hack is
+ # the best of a number of bad alternatives, mainly because the
+ # backends are not aware of what marker is actually being used
+ # beyond just its path data.
+ self._transform = Affine2D().translate(-0.49999, -0.49999)
+ self._snap_threshold = None
+
+ _triangle_path = Path._create_closed([[0, 1], [-1, -1], [1, -1]])
+ # Going down halfway looks to small. Golden ratio is too far.
+ _triangle_path_u = Path._create_closed([[0, 1], [-3/5, -1/5], [3/5, -1/5]])
+ _triangle_path_d = Path._create_closed(
+ [[-3/5, -1/5], [3/5, -1/5], [1, -1], [-1, -1]])
+ _triangle_path_l = Path._create_closed([[0, 1], [0, -1], [-1, -1]])
+ _triangle_path_r = Path._create_closed([[0, 1], [0, -1], [1, -1]])
+
+ def _set_triangle(self, rot, skip):
+ self._transform = Affine2D().scale(0.5).rotate_deg(rot)
+ self._snap_threshold = 5.0
+
+ if not self._half_fill():
+ self._path = self._triangle_path
+ else:
+ mpaths = [self._triangle_path_u,
+ self._triangle_path_l,
+ self._triangle_path_d,
+ self._triangle_path_r]
+
+ fs = self.get_fillstyle()
+ if fs == 'top':
+ self._path = mpaths[(0 + skip) % 4]
+ self._alt_path = mpaths[(2 + skip) % 4]
+ elif fs == 'bottom':
+ self._path = mpaths[(2 + skip) % 4]
+ self._alt_path = mpaths[(0 + skip) % 4]
+ elif fs == 'left':
+ self._path = mpaths[(1 + skip) % 4]
+ self._alt_path = mpaths[(3 + skip) % 4]
+ else:
+ self._path = mpaths[(3 + skip) % 4]
+ self._alt_path = mpaths[(1 + skip) % 4]
+
+ self._alt_transform = self._transform
+
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+
+ def _set_triangle_up(self):
+ return self._set_triangle(0.0, 0)
+
+ def _set_triangle_down(self):
+ return self._set_triangle(180.0, 2)
+
+ def _set_triangle_left(self):
+ return self._set_triangle(90.0, 3)
+
+ def _set_triangle_right(self):
+ return self._set_triangle(270.0, 1)
+
+ def _set_square(self):
+ self._transform = Affine2D().translate(-0.5, -0.5)
+ self._snap_threshold = 2.0
+ if not self._half_fill():
+ self._path = Path.unit_rectangle()
+ else:
+ # Build a bottom filled square out of two rectangles, one filled.
+ self._path = Path([[0.0, 0.0], [1.0, 0.0], [1.0, 0.5],
+ [0.0, 0.5], [0.0, 0.0]])
+ self._alt_path = Path([[0.0, 0.5], [1.0, 0.5], [1.0, 1.0],
+ [0.0, 1.0], [0.0, 0.5]])
+ fs = self.get_fillstyle()
+ rotate = {'bottom': 0, 'right': 90, 'top': 180, 'left': 270}[fs]
+ self._transform.rotate_deg(rotate)
+ self._alt_transform = self._transform
+
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+
+ def _set_diamond(self):
+ self._transform = Affine2D().translate(-0.5, -0.5).rotate_deg(45)
+ self._snap_threshold = 5.0
+ if not self._half_fill():
+ self._path = Path.unit_rectangle()
+ else:
+ self._path = Path([[0, 0], [1, 0], [1, 1], [0, 0]])
+ self._alt_path = Path([[0, 0], [0, 1], [1, 1], [0, 0]])
+ fs = self.get_fillstyle()
+ rotate = {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs]
+ self._transform.rotate_deg(rotate)
+ self._alt_transform = self._transform
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+
+ def _set_thin_diamond(self):
+ self._set_diamond()
+ self._transform.scale(0.6, 1.0)
+
+ def _set_pentagon(self):
+ self._transform = Affine2D().scale(0.5)
+ self._snap_threshold = 5.0
+
+ polypath = Path.unit_regular_polygon(5)
+
+ if not self._half_fill():
+ self._path = polypath
+ else:
+ verts = polypath.vertices
+ y = (1 + np.sqrt(5)) / 4.
+ top = Path(verts[[0, 1, 4, 0]])
+ bottom = Path(verts[[1, 2, 3, 4, 1]])
+ left = Path([verts[0], verts[1], verts[2], [0, -y], verts[0]])
+ right = Path([verts[0], verts[4], verts[3], [0, -y], verts[0]])
+ self._path, self._alt_path = {
+ 'top': (top, bottom), 'bottom': (bottom, top),
+ 'left': (left, right), 'right': (right, left),
+ }[self.get_fillstyle()]
+ self._alt_transform = self._transform
+
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+
+ def _set_star(self):
+ self._transform = Affine2D().scale(0.5)
+ self._snap_threshold = 5.0
+
+ polypath = Path.unit_regular_star(5, innerCircle=0.381966)
+
+ if not self._half_fill():
+ self._path = polypath
+ else:
+ verts = polypath.vertices
+ top = Path(np.concatenate([verts[0:4], verts[7:10], verts[0:1]]))
+ bottom = Path(np.concatenate([verts[3:8], verts[3:4]]))
+ left = Path(np.concatenate([verts[0:6], verts[0:1]]))
+ right = Path(np.concatenate([verts[0:1], verts[5:10], verts[0:1]]))
+ self._path, self._alt_path = {
+ 'top': (top, bottom), 'bottom': (bottom, top),
+ 'left': (left, right), 'right': (right, left),
+ }[self.get_fillstyle()]
+ self._alt_transform = self._transform
+
+ self._joinstyle = self._user_joinstyle or JoinStyle.bevel
+
+ def _set_hexagon1(self):
+ self._transform = Affine2D().scale(0.5)
+ self._snap_threshold = None
+
+ polypath = Path.unit_regular_polygon(6)
+
+ if not self._half_fill():
+ self._path = polypath
+ else:
+ verts = polypath.vertices
+ # not drawing inside lines
+ x = np.abs(np.cos(5 * np.pi / 6.))
+ top = Path(np.concatenate([[(-x, 0)], verts[[1, 0, 5]], [(x, 0)]]))
+ bottom = Path(np.concatenate([[(-x, 0)], verts[2:5], [(x, 0)]]))
+ left = Path(verts[0:4])
+ right = Path(verts[[0, 5, 4, 3]])
+ self._path, self._alt_path = {
+ 'top': (top, bottom), 'bottom': (bottom, top),
+ 'left': (left, right), 'right': (right, left),
+ }[self.get_fillstyle()]
+ self._alt_transform = self._transform
+
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+
+ def _set_hexagon2(self):
+ self._transform = Affine2D().scale(0.5).rotate_deg(30)
+ self._snap_threshold = None
+
+ polypath = Path.unit_regular_polygon(6)
+
+ if not self._half_fill():
+ self._path = polypath
+ else:
+ verts = polypath.vertices
+ # not drawing inside lines
+ x, y = np.sqrt(3) / 4, 3 / 4.
+ top = Path(verts[[1, 0, 5, 4, 1]])
+ bottom = Path(verts[1:5])
+ left = Path(np.concatenate([
+ [(x, y)], verts[:3], [(-x, -y), (x, y)]]))
+ right = Path(np.concatenate([
+ [(x, y)], verts[5:2:-1], [(-x, -y)]]))
+ self._path, self._alt_path = {
+ 'top': (top, bottom), 'bottom': (bottom, top),
+ 'left': (left, right), 'right': (right, left),
+ }[self.get_fillstyle()]
+ self._alt_transform = self._transform
+
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+
+ def _set_octagon(self):
+ self._transform = Affine2D().scale(0.5)
+ self._snap_threshold = 5.0
+
+ polypath = Path.unit_regular_polygon(8)
+
+ if not self._half_fill():
+ self._transform.rotate_deg(22.5)
+ self._path = polypath
+ else:
+ x = np.sqrt(2.) / 4.
+ self._path = self._alt_path = Path(
+ [[0, -1], [0, 1], [-x, 1], [-1, x],
+ [-1, -x], [-x, -1], [0, -1]])
+ fs = self.get_fillstyle()
+ self._transform.rotate_deg(
+ {'left': 0, 'bottom': 90, 'right': 180, 'top': 270}[fs])
+ self._alt_transform = self._transform.frozen().rotate_deg(180.0)
+
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+
+ _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]])
+
+ def _set_vline(self):
+ self._transform = Affine2D().scale(0.5)
+ self._snap_threshold = 1.0
+ self._filled = False
+ self._path = self._line_marker_path
+
+ def _set_hline(self):
+ self._set_vline()
+ self._transform = self._transform.rotate_deg(90)
+
+ _tickhoriz_path = Path([[0.0, 0.0], [1.0, 0.0]])
+
+ def _set_tickleft(self):
+ self._transform = Affine2D().scale(-1.0, 1.0)
+ self._snap_threshold = 1.0
+ self._filled = False
+ self._path = self._tickhoriz_path
+
+ def _set_tickright(self):
+ self._transform = Affine2D().scale(1.0, 1.0)
+ self._snap_threshold = 1.0
+ self._filled = False
+ self._path = self._tickhoriz_path
+
+ _tickvert_path = Path([[-0.0, 0.0], [-0.0, 1.0]])
+
+ def _set_tickup(self):
+ self._transform = Affine2D().scale(1.0, 1.0)
+ self._snap_threshold = 1.0
+ self._filled = False
+ self._path = self._tickvert_path
+
+ def _set_tickdown(self):
+ self._transform = Affine2D().scale(1.0, -1.0)
+ self._snap_threshold = 1.0
+ self._filled = False
+ self._path = self._tickvert_path
+
+ _tri_path = Path([[0.0, 0.0], [0.0, -1.0],
+ [0.0, 0.0], [0.8, 0.5],
+ [0.0, 0.0], [-0.8, 0.5]],
+ [Path.MOVETO, Path.LINETO,
+ Path.MOVETO, Path.LINETO,
+ Path.MOVETO, Path.LINETO])
+
+ def _set_tri_down(self):
+ self._transform = Affine2D().scale(0.5)
+ self._snap_threshold = 5.0
+ self._filled = False
+ self._path = self._tri_path
+
+ def _set_tri_up(self):
+ self._set_tri_down()
+ self._transform = self._transform.rotate_deg(180)
+
+ def _set_tri_left(self):
+ self._set_tri_down()
+ self._transform = self._transform.rotate_deg(270)
+
+ def _set_tri_right(self):
+ self._set_tri_down()
+ self._transform = self._transform.rotate_deg(90)
+
+ _caret_path = Path([[-1.0, 1.5], [0.0, 0.0], [1.0, 1.5]])
+
+ def _set_caretdown(self):
+ self._transform = Affine2D().scale(0.5)
+ self._snap_threshold = 3.0
+ self._filled = False
+ self._path = self._caret_path
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+
+ def _set_caretup(self):
+ self._set_caretdown()
+ self._transform = self._transform.rotate_deg(180)
+
+ def _set_caretleft(self):
+ self._set_caretdown()
+ self._transform = self._transform.rotate_deg(270)
+
+ def _set_caretright(self):
+ self._set_caretdown()
+ self._transform = self._transform.rotate_deg(90)
+
+ _caret_path_base = Path([[-1.0, 0.0], [0.0, -1.5], [1.0, 0]])
+
+ def _set_caretdownbase(self):
+ self._set_caretdown()
+ self._path = self._caret_path_base
+
+ def _set_caretupbase(self):
+ self._set_caretdownbase()
+ self._transform = self._transform.rotate_deg(180)
+
+ def _set_caretleftbase(self):
+ self._set_caretdownbase()
+ self._transform = self._transform.rotate_deg(270)
+
+ def _set_caretrightbase(self):
+ self._set_caretdownbase()
+ self._transform = self._transform.rotate_deg(90)
+
+ _plus_path = Path([[-1.0, 0.0], [1.0, 0.0],
+ [0.0, -1.0], [0.0, 1.0]],
+ [Path.MOVETO, Path.LINETO,
+ Path.MOVETO, Path.LINETO])
+
+ def _set_plus(self):
+ self._transform = Affine2D().scale(0.5)
+ self._snap_threshold = 1.0
+ self._filled = False
+ self._path = self._plus_path
+
+ _x_path = Path([[-1.0, -1.0], [1.0, 1.0],
+ [-1.0, 1.0], [1.0, -1.0]],
+ [Path.MOVETO, Path.LINETO,
+ Path.MOVETO, Path.LINETO])
+
+ def _set_x(self):
+ self._transform = Affine2D().scale(0.5)
+ self._snap_threshold = 3.0
+ self._filled = False
+ self._path = self._x_path
+
+ _plus_filled_path = Path._create_closed(np.array([
+ (-1, -3), (+1, -3), (+1, -1), (+3, -1), (+3, +1), (+1, +1),
+ (+1, +3), (-1, +3), (-1, +1), (-3, +1), (-3, -1), (-1, -1)]) / 6)
+ _plus_filled_path_t = Path._create_closed(np.array([
+ (+3, 0), (+3, +1), (+1, +1), (+1, +3),
+ (-1, +3), (-1, +1), (-3, +1), (-3, 0)]) / 6)
+
+ def _set_plus_filled(self):
+ self._transform = Affine2D()
+ self._snap_threshold = 5.0
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+ if not self._half_fill():
+ self._path = self._plus_filled_path
+ else:
+ # Rotate top half path to support all partitions
+ self._path = self._alt_path = self._plus_filled_path_t
+ fs = self.get_fillstyle()
+ self._transform.rotate_deg(
+ {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs])
+ self._alt_transform = self._transform.frozen().rotate_deg(180)
+
+ _x_filled_path = Path._create_closed(np.array([
+ (-1, -2), (0, -1), (+1, -2), (+2, -1), (+1, 0), (+2, +1),
+ (+1, +2), (0, +1), (-1, +2), (-2, +1), (-1, 0), (-2, -1)]) / 4)
+ _x_filled_path_t = Path._create_closed(np.array([
+ (+1, 0), (+2, +1), (+1, +2), (0, +1),
+ (-1, +2), (-2, +1), (-1, 0)]) / 4)
+
+ def _set_x_filled(self):
+ self._transform = Affine2D()
+ self._snap_threshold = 5.0
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
+ if not self._half_fill():
+ self._path = self._x_filled_path
+ else:
+ # Rotate top half path to support all partitions
+ self._path = self._alt_path = self._x_filled_path_t
+ fs = self.get_fillstyle()
+ self._transform.rotate_deg(
+ {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs])
+ self._alt_transform = self._transform.frozen().rotate_deg(180)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..bd2f7dbcaf0ce4a774d743551ce8b3d5e5e1621c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.pyi
@@ -0,0 +1,51 @@
+from typing import Literal
+
+from .path import Path
+from .transforms import Affine2D, Transform
+
+from numpy.typing import ArrayLike
+from .typing import CapStyleType, FillStyleType, JoinStyleType
+
+TICKLEFT: int
+TICKRIGHT: int
+TICKUP: int
+TICKDOWN: int
+CARETLEFT: int
+CARETRIGHT: int
+CARETUP: int
+CARETDOWN: int
+CARETLEFTBASE: int
+CARETRIGHTBASE: int
+CARETUPBASE: int
+CARETDOWNBASE: int
+
+class MarkerStyle:
+ markers: dict[str | int, str]
+ filled_markers: tuple[str, ...]
+ fillstyles: tuple[FillStyleType, ...]
+
+ def __init__(
+ self,
+ marker: str | ArrayLike | Path | MarkerStyle,
+ fillstyle: FillStyleType | None = ...,
+ transform: Transform | None = ...,
+ capstyle: CapStyleType | None = ...,
+ joinstyle: JoinStyleType | None = ...,
+ ) -> None: ...
+ def __bool__(self) -> bool: ...
+ def is_filled(self) -> bool: ...
+ def get_fillstyle(self) -> FillStyleType: ...
+ def get_joinstyle(self) -> Literal["miter", "round", "bevel"]: ...
+ def get_capstyle(self) -> Literal["butt", "projecting", "round"]: ...
+ def get_marker(self) -> str | ArrayLike | Path | None: ...
+ def get_path(self) -> Path: ...
+ def get_transform(self) -> Transform: ...
+ def get_alt_path(self) -> Path | None: ...
+ def get_alt_transform(self) -> Transform: ...
+ def get_snap_threshold(self) -> float | None: ...
+ def get_user_transform(self) -> Transform | None: ...
+ def transformed(self, transform: Affine2D) -> MarkerStyle: ...
+ def rotated(
+ self, *, deg: float | None = ..., rad: float | None = ...
+ ) -> MarkerStyle: ...
+ def scaled(self, sx: float, sy: float | None = ...) -> MarkerStyle: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.py
new file mode 100644
index 0000000000000000000000000000000000000000..a88c35c15676b027c773eead1e9d0a8b13270768
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.py
@@ -0,0 +1,140 @@
+r"""
+A module for parsing a subset of the TeX math syntax and rendering it to a
+Matplotlib backend.
+
+For a tutorial of its usage, see :ref:`mathtext`. This
+document is primarily concerned with implementation details.
+
+The module uses pyparsing_ to parse the TeX expression.
+
+.. _pyparsing: https://pypi.org/project/pyparsing/
+
+The Bakoma distribution of the TeX Computer Modern fonts, and STIX
+fonts are supported. There is experimental support for using
+arbitrary fonts, but results may vary without proper tweaking and
+metrics for those fonts.
+"""
+
+import functools
+import logging
+
+import matplotlib as mpl
+from matplotlib import _api, _mathtext
+from matplotlib.ft2font import LoadFlags
+from matplotlib.font_manager import FontProperties
+from ._mathtext import ( # noqa: F401, reexported API
+ RasterParse, VectorParse, get_unicode_index)
+
+_log = logging.getLogger(__name__)
+
+
+get_unicode_index.__module__ = __name__
+
+##############################################################################
+# MAIN
+
+
+class MathTextParser:
+ _parser = None
+ _font_type_mapping = {
+ 'cm': _mathtext.BakomaFonts,
+ 'dejavuserif': _mathtext.DejaVuSerifFonts,
+ 'dejavusans': _mathtext.DejaVuSansFonts,
+ 'stix': _mathtext.StixFonts,
+ 'stixsans': _mathtext.StixSansFonts,
+ 'custom': _mathtext.UnicodeFonts,
+ }
+
+ def __init__(self, output):
+ """
+ Create a MathTextParser for the given backend *output*.
+
+ Parameters
+ ----------
+ output : {"path", "agg"}
+ Whether to return a `VectorParse` ("path") or a
+ `RasterParse` ("agg", or its synonym "macosx").
+ """
+ self._output_type = _api.check_getitem(
+ {"path": "vector", "agg": "raster", "macosx": "raster"},
+ output=output.lower())
+
+ def parse(self, s, dpi=72, prop=None, *, antialiased=None):
+ """
+ Parse the given math expression *s* at the given *dpi*. If *prop* is
+ provided, it is a `.FontProperties` object specifying the "default"
+ font to use in the math expression, used for all non-math text.
+
+ The results are cached, so multiple calls to `parse`
+ with the same expression should be fast.
+
+ Depending on the *output* type, this returns either a `VectorParse` or
+ a `RasterParse`.
+ """
+ # lru_cache can't decorate parse() directly because prop is
+ # mutable, so we key the cache using an internal copy (see
+ # Text._get_text_metrics_with_cache for a similar case); likewise,
+ # we need to check the mutable state of the text.antialiased and
+ # text.hinting rcParams.
+ prop = prop.copy() if prop is not None else None
+ antialiased = mpl._val_or_rc(antialiased, 'text.antialiased')
+ from matplotlib.backends import backend_agg
+ load_glyph_flags = {
+ "vector": LoadFlags.NO_HINTING,
+ "raster": backend_agg.get_hinting_flag(),
+ }[self._output_type]
+ return self._parse_cached(s, dpi, prop, antialiased, load_glyph_flags)
+
+ @functools.lru_cache(50)
+ def _parse_cached(self, s, dpi, prop, antialiased, load_glyph_flags):
+ if prop is None:
+ prop = FontProperties()
+ fontset_class = _api.check_getitem(
+ self._font_type_mapping, fontset=prop.get_math_fontfamily())
+ fontset = fontset_class(prop, load_glyph_flags)
+ fontsize = prop.get_size_in_points()
+
+ if self._parser is None: # Cache the parser globally.
+ self.__class__._parser = _mathtext.Parser()
+
+ box = self._parser.parse(s, fontset, fontsize, dpi)
+ output = _mathtext.ship(box)
+ if self._output_type == "vector":
+ return output.to_vector()
+ elif self._output_type == "raster":
+ return output.to_raster(antialiased=antialiased)
+
+
+def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None,
+ *, color=None):
+ """
+ Given a math expression, renders it in a closely-clipped bounding
+ box to an image file.
+
+ Parameters
+ ----------
+ s : str
+ A math expression. The math portion must be enclosed in dollar signs.
+ filename_or_obj : str or path-like or file-like
+ Where to write the image data.
+ prop : `.FontProperties`, optional
+ The size and style of the text.
+ dpi : float, optional
+ The output dpi. If not set, the dpi is determined as for
+ `.Figure.savefig`.
+ format : str, optional
+ The output format, e.g., 'svg', 'pdf', 'ps' or 'png'. If not set, the
+ format is determined as for `.Figure.savefig`.
+ color : str, optional
+ Foreground color, defaults to :rc:`text.color`.
+ """
+ from matplotlib import figure
+
+ parser = MathTextParser('path')
+ width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
+
+ fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
+ fig.text(0, depth/height, s, fontproperties=prop, color=color)
+ fig.savefig(filename_or_obj, dpi=dpi, format=format)
+
+ return depth
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..607501a275c67941e2a67883dc9c99a887ac0581
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.pyi
@@ -0,0 +1,33 @@
+import os
+from typing import Generic, IO, Literal, TypeVar, overload
+
+from matplotlib.font_manager import FontProperties
+from matplotlib.typing import ColorType
+
+# Re-exported API from _mathtext.
+from ._mathtext import (
+ RasterParse as RasterParse,
+ VectorParse as VectorParse,
+ get_unicode_index as get_unicode_index,
+)
+
+_ParseType = TypeVar("_ParseType", RasterParse, VectorParse)
+
+class MathTextParser(Generic[_ParseType]):
+ @overload
+ def __init__(self: MathTextParser[VectorParse], output: Literal["path"]) -> None: ...
+ @overload
+ def __init__(self: MathTextParser[RasterParse], output: Literal["agg", "raster", "macosx"]) -> None: ...
+ def parse(
+ self, s: str, dpi: float = ..., prop: FontProperties | None = ..., *, antialiased: bool | None = ...
+ ) -> _ParseType: ...
+
+def math_to_image(
+ s: str,
+ filename_or_obj: str | os.PathLike | IO,
+ prop: FontProperties | None = ...,
+ dpi: float | None = ...,
+ format: str | None = ...,
+ *,
+ color: ColorType | None = ...
+) -> float: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.py
new file mode 100644
index 0000000000000000000000000000000000000000..8326ac186e31bbc29d9027e64ae65f999a6f73dd
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.py
@@ -0,0 +1,913 @@
+"""
+Numerical Python functions written for compatibility with MATLAB
+commands with the same names. Most numerical Python functions can be found in
+the `NumPy`_ and `SciPy`_ libraries. What remains here is code for performing
+spectral computations and kernel density estimations.
+
+.. _NumPy: https://numpy.org
+.. _SciPy: https://www.scipy.org
+
+Spectral functions
+------------------
+
+`cohere`
+ Coherence (normalized cross spectral density)
+
+`csd`
+ Cross spectral density using Welch's average periodogram
+
+`detrend`
+ Remove the mean or best fit line from an array
+
+`psd`
+ Power spectral density using Welch's average periodogram
+
+`specgram`
+ Spectrogram (spectrum over segments of time)
+
+`complex_spectrum`
+ Return the complex-valued frequency spectrum of a signal
+
+`magnitude_spectrum`
+ Return the magnitude of the frequency spectrum of a signal
+
+`angle_spectrum`
+ Return the angle (wrapped phase) of the frequency spectrum of a signal
+
+`phase_spectrum`
+ Return the phase (unwrapped angle) of the frequency spectrum of a signal
+
+`detrend_mean`
+ Remove the mean from a line.
+
+`detrend_linear`
+ Remove the best fit line from a line.
+
+`detrend_none`
+ Return the original line.
+"""
+
+import functools
+from numbers import Number
+
+import numpy as np
+
+from matplotlib import _api, _docstring, cbook
+
+
+def window_hanning(x):
+ """
+ Return *x* times the Hanning (or Hann) window of len(*x*).
+
+ See Also
+ --------
+ window_none : Another window algorithm.
+ """
+ return np.hanning(len(x))*x
+
+
+def window_none(x):
+ """
+ No window function; simply return *x*.
+
+ See Also
+ --------
+ window_hanning : Another window algorithm.
+ """
+ return x
+
+
+def detrend(x, key=None, axis=None):
+ """
+ Return *x* with its trend removed.
+
+ Parameters
+ ----------
+ x : array or sequence
+ Array or sequence containing the data.
+
+ key : {'default', 'constant', 'mean', 'linear', 'none'} or function
+ The detrending algorithm to use. 'default', 'mean', and 'constant' are
+ the same as `detrend_mean`. 'linear' is the same as `detrend_linear`.
+ 'none' is the same as `detrend_none`. The default is 'mean'. See the
+ corresponding functions for more details regarding the algorithms. Can
+ also be a function that carries out the detrend operation.
+
+ axis : int
+ The axis along which to do the detrending.
+
+ See Also
+ --------
+ detrend_mean : Implementation of the 'mean' algorithm.
+ detrend_linear : Implementation of the 'linear' algorithm.
+ detrend_none : Implementation of the 'none' algorithm.
+ """
+ if key is None or key in ['constant', 'mean', 'default']:
+ return detrend(x, key=detrend_mean, axis=axis)
+ elif key == 'linear':
+ return detrend(x, key=detrend_linear, axis=axis)
+ elif key == 'none':
+ return detrend(x, key=detrend_none, axis=axis)
+ elif callable(key):
+ x = np.asarray(x)
+ if axis is not None and axis + 1 > x.ndim:
+ raise ValueError(f'axis(={axis}) out of bounds')
+ if (axis is None and x.ndim == 0) or (not axis and x.ndim == 1):
+ return key(x)
+ # try to use the 'axis' argument if the function supports it,
+ # otherwise use apply_along_axis to do it
+ try:
+ return key(x, axis=axis)
+ except TypeError:
+ return np.apply_along_axis(key, axis=axis, arr=x)
+ else:
+ raise ValueError(
+ f"Unknown value for key: {key!r}, must be one of: 'default', "
+ f"'constant', 'mean', 'linear', or a function")
+
+
+def detrend_mean(x, axis=None):
+ """
+ Return *x* minus the mean(*x*).
+
+ Parameters
+ ----------
+ x : array or sequence
+ Array or sequence containing the data
+ Can have any dimensionality
+
+ axis : int
+ The axis along which to take the mean. See `numpy.mean` for a
+ description of this argument.
+
+ See Also
+ --------
+ detrend_linear : Another detrend algorithm.
+ detrend_none : Another detrend algorithm.
+ detrend : A wrapper around all the detrend algorithms.
+ """
+ x = np.asarray(x)
+
+ if axis is not None and axis+1 > x.ndim:
+ raise ValueError('axis(=%s) out of bounds' % axis)
+
+ return x - x.mean(axis, keepdims=True)
+
+
+def detrend_none(x, axis=None):
+ """
+ Return *x*: no detrending.
+
+ Parameters
+ ----------
+ x : any object
+ An object containing the data
+
+ axis : int
+ This parameter is ignored.
+ It is included for compatibility with detrend_mean
+
+ See Also
+ --------
+ detrend_mean : Another detrend algorithm.
+ detrend_linear : Another detrend algorithm.
+ detrend : A wrapper around all the detrend algorithms.
+ """
+ return x
+
+
+def detrend_linear(y):
+ """
+ Return *x* minus best fit line; 'linear' detrending.
+
+ Parameters
+ ----------
+ y : 0-D or 1-D array or sequence
+ Array or sequence containing the data
+
+ See Also
+ --------
+ detrend_mean : Another detrend algorithm.
+ detrend_none : Another detrend algorithm.
+ detrend : A wrapper around all the detrend algorithms.
+ """
+ # This is faster than an algorithm based on linalg.lstsq.
+ y = np.asarray(y)
+
+ if y.ndim > 1:
+ raise ValueError('y cannot have ndim > 1')
+
+ # short-circuit 0-D array.
+ if not y.ndim:
+ return np.array(0., dtype=y.dtype)
+
+ x = np.arange(y.size, dtype=float)
+
+ C = np.cov(x, y, bias=1)
+ b = C[0, 1]/C[0, 0]
+
+ a = y.mean() - b*x.mean()
+ return y - (b*x + a)
+
+
+def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None,
+ window=None, noverlap=None, pad_to=None,
+ sides=None, scale_by_freq=None, mode=None):
+ """
+ Private helper implementing the common parts between the psd, csd,
+ spectrogram and complex, magnitude, angle, and phase spectrums.
+ """
+ if y is None:
+ # if y is None use x for y
+ same_data = True
+ else:
+ # The checks for if y is x are so that we can use the same function to
+ # implement the core of psd(), csd(), and spectrogram() without doing
+ # extra calculations. We return the unaveraged Pxy, freqs, and t.
+ same_data = y is x
+
+ if Fs is None:
+ Fs = 2
+ if noverlap is None:
+ noverlap = 0
+ if detrend_func is None:
+ detrend_func = detrend_none
+ if window is None:
+ window = window_hanning
+
+ # if NFFT is set to None use the whole signal
+ if NFFT is None:
+ NFFT = 256
+
+ if noverlap >= NFFT:
+ raise ValueError('noverlap must be less than NFFT')
+
+ if mode is None or mode == 'default':
+ mode = 'psd'
+ _api.check_in_list(
+ ['default', 'psd', 'complex', 'magnitude', 'angle', 'phase'],
+ mode=mode)
+
+ if not same_data and mode != 'psd':
+ raise ValueError("x and y must be equal if mode is not 'psd'")
+
+ # Make sure we're dealing with a numpy array. If y and x were the same
+ # object to start with, keep them that way
+ x = np.asarray(x)
+ if not same_data:
+ y = np.asarray(y)
+
+ if sides is None or sides == 'default':
+ if np.iscomplexobj(x):
+ sides = 'twosided'
+ else:
+ sides = 'onesided'
+ _api.check_in_list(['default', 'onesided', 'twosided'], sides=sides)
+
+ # zero pad x and y up to NFFT if they are shorter than NFFT
+ if len(x) < NFFT:
+ n = len(x)
+ x = np.resize(x, NFFT)
+ x[n:] = 0
+
+ if not same_data and len(y) < NFFT:
+ n = len(y)
+ y = np.resize(y, NFFT)
+ y[n:] = 0
+
+ if pad_to is None:
+ pad_to = NFFT
+
+ if mode != 'psd':
+ scale_by_freq = False
+ elif scale_by_freq is None:
+ scale_by_freq = True
+
+ # For real x, ignore the negative frequencies unless told otherwise
+ if sides == 'twosided':
+ numFreqs = pad_to
+ if pad_to % 2:
+ freqcenter = (pad_to - 1)//2 + 1
+ else:
+ freqcenter = pad_to//2
+ scaling_factor = 1.
+ elif sides == 'onesided':
+ if pad_to % 2:
+ numFreqs = (pad_to + 1)//2
+ else:
+ numFreqs = pad_to//2 + 1
+ scaling_factor = 2.
+
+ if not np.iterable(window):
+ window = window(np.ones(NFFT, x.dtype))
+ if len(window) != NFFT:
+ raise ValueError(
+ "The window length must match the data's first dimension")
+
+ result = np.lib.stride_tricks.sliding_window_view(
+ x, NFFT, axis=0)[::NFFT - noverlap].T
+ result = detrend(result, detrend_func, axis=0)
+ result = result * window.reshape((-1, 1))
+ result = np.fft.fft(result, n=pad_to, axis=0)[:numFreqs, :]
+ freqs = np.fft.fftfreq(pad_to, 1/Fs)[:numFreqs]
+
+ if not same_data:
+ # if same_data is False, mode must be 'psd'
+ resultY = np.lib.stride_tricks.sliding_window_view(
+ y, NFFT, axis=0)[::NFFT - noverlap].T
+ resultY = detrend(resultY, detrend_func, axis=0)
+ resultY = resultY * window.reshape((-1, 1))
+ resultY = np.fft.fft(resultY, n=pad_to, axis=0)[:numFreqs, :]
+ result = np.conj(result) * resultY
+ elif mode == 'psd':
+ result = np.conj(result) * result
+ elif mode == 'magnitude':
+ result = np.abs(result) / window.sum()
+ elif mode == 'angle' or mode == 'phase':
+ # we unwrap the phase later to handle the onesided vs. twosided case
+ result = np.angle(result)
+ elif mode == 'complex':
+ result /= window.sum()
+
+ if mode == 'psd':
+
+ # Also include scaling factors for one-sided densities and dividing by
+ # the sampling frequency, if desired. Scale everything, except the DC
+ # component and the NFFT/2 component:
+
+ # if we have a even number of frequencies, don't scale NFFT/2
+ if not NFFT % 2:
+ slc = slice(1, -1, None)
+ # if we have an odd number, just don't scale DC
+ else:
+ slc = slice(1, None, None)
+
+ result[slc] *= scaling_factor
+
+ # MATLAB divides by the sampling frequency so that density function
+ # has units of dB/Hz and can be integrated by the plotted frequency
+ # values. Perform the same scaling here.
+ if scale_by_freq:
+ result /= Fs
+ # Scale the spectrum by the norm of the window to compensate for
+ # windowing loss; see Bendat & Piersol Sec 11.5.2.
+ result /= (window**2).sum()
+ else:
+ # In this case, preserve power in the segment, not amplitude
+ result /= window.sum()**2
+
+ t = np.arange(NFFT/2, len(x) - NFFT/2 + 1, NFFT - noverlap)/Fs
+
+ if sides == 'twosided':
+ # center the frequency range at zero
+ freqs = np.roll(freqs, -freqcenter, axis=0)
+ result = np.roll(result, -freqcenter, axis=0)
+ elif not pad_to % 2:
+ # get the last value correctly, it is negative otherwise
+ freqs[-1] *= -1
+
+ # we unwrap the phase here to handle the onesided vs. twosided case
+ if mode == 'phase':
+ result = np.unwrap(result, axis=0)
+
+ return result, freqs, t
+
+
+def _single_spectrum_helper(
+ mode, x, Fs=None, window=None, pad_to=None, sides=None):
+ """
+ Private helper implementing the commonality between the complex, magnitude,
+ angle, and phase spectrums.
+ """
+ _api.check_in_list(['complex', 'magnitude', 'angle', 'phase'], mode=mode)
+
+ if pad_to is None:
+ pad_to = len(x)
+
+ spec, freqs, _ = _spectral_helper(x=x, y=None, NFFT=len(x), Fs=Fs,
+ detrend_func=detrend_none, window=window,
+ noverlap=0, pad_to=pad_to,
+ sides=sides,
+ scale_by_freq=False,
+ mode=mode)
+ if mode != 'complex':
+ spec = spec.real
+
+ if spec.ndim == 2 and spec.shape[1] == 1:
+ spec = spec[:, 0]
+
+ return spec, freqs
+
+
+# Split out these keyword docs so that they can be used elsewhere
+_docstring.interpd.register(
+ Spectral="""\
+Fs : float, default: 2
+ The sampling frequency (samples per time unit). It is used to calculate
+ the Fourier frequencies, *freqs*, in cycles per time unit.
+
+window : callable or ndarray, default: `.window_hanning`
+ A function or a vector of length *NFFT*. To create window vectors see
+ `.window_hanning`, `.window_none`, `numpy.blackman`, `numpy.hamming`,
+ `numpy.bartlett`, `scipy.signal`, `scipy.signal.get_window`, etc. If a
+ function is passed as the argument, it must take a data segment as an
+ argument and return the windowed version of the segment.
+
+sides : {'default', 'onesided', 'twosided'}, optional
+ Which sides of the spectrum to return. 'default' is one-sided for real
+ data and two-sided for complex data. 'onesided' forces the return of a
+ one-sided spectrum, while 'twosided' forces two-sided.""",
+
+ Single_Spectrum="""\
+pad_to : int, optional
+ The number of points to which the data segment is padded when performing
+ the FFT. While not increasing the actual resolution of the spectrum (the
+ minimum distance between resolvable peaks), this can give more points in
+ the plot, allowing for more detail. This corresponds to the *n* parameter
+ in the call to `~numpy.fft.fft`. The default is None, which sets *pad_to*
+ equal to the length of the input signal (i.e. no padding).""",
+
+ PSD="""\
+pad_to : int, optional
+ The number of points to which the data segment is padded when performing
+ the FFT. This can be different from *NFFT*, which specifies the number
+ of data points used. While not increasing the actual resolution of the
+ spectrum (the minimum distance between resolvable peaks), this can give
+ more points in the plot, allowing for more detail. This corresponds to
+ the *n* parameter in the call to `~numpy.fft.fft`. The default is None,
+ which sets *pad_to* equal to *NFFT*
+
+NFFT : int, default: 256
+ The number of data points used in each block for the FFT. A power 2 is
+ most efficient. This should *NOT* be used to get zero padding, or the
+ scaling of the result will be incorrect; use *pad_to* for this instead.
+
+detrend : {'none', 'mean', 'linear'} or callable, default: 'none'
+ The function applied to each segment before fft-ing, designed to remove
+ the mean or linear trend. Unlike in MATLAB, where the *detrend* parameter
+ is a vector, in Matplotlib it is a function. The :mod:`~matplotlib.mlab`
+ module defines `.detrend_none`, `.detrend_mean`, and `.detrend_linear`,
+ but you can use a custom function as well. You can also use a string to
+ choose one of the functions: 'none' calls `.detrend_none`. 'mean' calls
+ `.detrend_mean`. 'linear' calls `.detrend_linear`.
+
+scale_by_freq : bool, default: True
+ Whether the resulting density values should be scaled by the scaling
+ frequency, which gives density in units of 1/Hz. This allows for
+ integration over the returned frequency values. The default is True for
+ MATLAB compatibility.""")
+
+
+@_docstring.interpd
+def psd(x, NFFT=None, Fs=None, detrend=None, window=None,
+ noverlap=None, pad_to=None, sides=None, scale_by_freq=None):
+ r"""
+ Compute the power spectral density.
+
+ The power spectral density :math:`P_{xx}` by Welch's average
+ periodogram method. The vector *x* is divided into *NFFT* length
+ segments. Each segment is detrended by function *detrend* and
+ windowed by function *window*. *noverlap* gives the length of
+ the overlap between segments. The :math:`|\mathrm{fft}(i)|^2`
+ of each segment :math:`i` are averaged to compute :math:`P_{xx}`.
+
+ If len(*x*) < *NFFT*, it will be zero padded to *NFFT*.
+
+ Parameters
+ ----------
+ x : 1-D array or sequence
+ Array or sequence containing the data
+
+ %(Spectral)s
+
+ %(PSD)s
+
+ noverlap : int, default: 0 (no overlap)
+ The number of points of overlap between segments.
+
+ Returns
+ -------
+ Pxx : 1-D array
+ The values for the power spectrum :math:`P_{xx}` (real valued)
+
+ freqs : 1-D array
+ The frequencies corresponding to the elements in *Pxx*
+
+ References
+ ----------
+ Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John
+ Wiley & Sons (1986)
+
+ See Also
+ --------
+ specgram
+ `specgram` differs in the default overlap; in not returning the mean of
+ the segment periodograms; and in returning the times of the segments.
+
+ magnitude_spectrum : returns the magnitude spectrum.
+
+ csd : returns the spectral density between two signals.
+ """
+ Pxx, freqs = csd(x=x, y=None, NFFT=NFFT, Fs=Fs, detrend=detrend,
+ window=window, noverlap=noverlap, pad_to=pad_to,
+ sides=sides, scale_by_freq=scale_by_freq)
+ return Pxx.real, freqs
+
+
+@_docstring.interpd
+def csd(x, y, NFFT=None, Fs=None, detrend=None, window=None,
+ noverlap=None, pad_to=None, sides=None, scale_by_freq=None):
+ """
+ Compute the cross-spectral density.
+
+ The cross spectral density :math:`P_{xy}` by Welch's average
+ periodogram method. The vectors *x* and *y* are divided into
+ *NFFT* length segments. Each segment is detrended by function
+ *detrend* and windowed by function *window*. *noverlap* gives
+ the length of the overlap between segments. The product of
+ the direct FFTs of *x* and *y* are averaged over each segment
+ to compute :math:`P_{xy}`, with a scaling to correct for power
+ loss due to windowing.
+
+ If len(*x*) < *NFFT* or len(*y*) < *NFFT*, they will be zero
+ padded to *NFFT*.
+
+ Parameters
+ ----------
+ x, y : 1-D arrays or sequences
+ Arrays or sequences containing the data
+
+ %(Spectral)s
+
+ %(PSD)s
+
+ noverlap : int, default: 0 (no overlap)
+ The number of points of overlap between segments.
+
+ Returns
+ -------
+ Pxy : 1-D array
+ The values for the cross spectrum :math:`P_{xy}` before scaling (real
+ valued)
+
+ freqs : 1-D array
+ The frequencies corresponding to the elements in *Pxy*
+
+ References
+ ----------
+ Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John
+ Wiley & Sons (1986)
+
+ See Also
+ --------
+ psd : equivalent to setting ``y = x``.
+ """
+ if NFFT is None:
+ NFFT = 256
+ Pxy, freqs, _ = _spectral_helper(x=x, y=y, NFFT=NFFT, Fs=Fs,
+ detrend_func=detrend, window=window,
+ noverlap=noverlap, pad_to=pad_to,
+ sides=sides, scale_by_freq=scale_by_freq,
+ mode='psd')
+
+ if Pxy.ndim == 2:
+ if Pxy.shape[1] > 1:
+ Pxy = Pxy.mean(axis=1)
+ else:
+ Pxy = Pxy[:, 0]
+ return Pxy, freqs
+
+
+_single_spectrum_docs = """\
+Compute the {quantity} of *x*.
+Data is padded to a length of *pad_to* and the windowing function *window* is
+applied to the signal.
+
+Parameters
+----------
+x : 1-D array or sequence
+ Array or sequence containing the data
+
+{Spectral}
+
+{Single_Spectrum}
+
+Returns
+-------
+spectrum : 1-D array
+ The {quantity}.
+freqs : 1-D array
+ The frequencies corresponding to the elements in *spectrum*.
+
+See Also
+--------
+psd
+ Returns the power spectral density.
+complex_spectrum
+ Returns the complex-valued frequency spectrum.
+magnitude_spectrum
+ Returns the absolute value of the `complex_spectrum`.
+angle_spectrum
+ Returns the angle of the `complex_spectrum`.
+phase_spectrum
+ Returns the phase (unwrapped angle) of the `complex_spectrum`.
+specgram
+ Can return the complex spectrum of segments within the signal.
+"""
+
+
+complex_spectrum = functools.partial(_single_spectrum_helper, "complex")
+complex_spectrum.__doc__ = _single_spectrum_docs.format(
+ quantity="complex-valued frequency spectrum",
+ **_docstring.interpd.params)
+magnitude_spectrum = functools.partial(_single_spectrum_helper, "magnitude")
+magnitude_spectrum.__doc__ = _single_spectrum_docs.format(
+ quantity="magnitude (absolute value) of the frequency spectrum",
+ **_docstring.interpd.params)
+angle_spectrum = functools.partial(_single_spectrum_helper, "angle")
+angle_spectrum.__doc__ = _single_spectrum_docs.format(
+ quantity="angle of the frequency spectrum (wrapped phase spectrum)",
+ **_docstring.interpd.params)
+phase_spectrum = functools.partial(_single_spectrum_helper, "phase")
+phase_spectrum.__doc__ = _single_spectrum_docs.format(
+ quantity="phase of the frequency spectrum (unwrapped phase spectrum)",
+ **_docstring.interpd.params)
+
+
+@_docstring.interpd
+def specgram(x, NFFT=None, Fs=None, detrend=None, window=None,
+ noverlap=None, pad_to=None, sides=None, scale_by_freq=None,
+ mode=None):
+ """
+ Compute a spectrogram.
+
+ Compute and plot a spectrogram of data in *x*. Data are split into
+ *NFFT* length segments and the spectrum of each section is
+ computed. The windowing function *window* is applied to each
+ segment, and the amount of overlap of each segment is
+ specified with *noverlap*.
+
+ Parameters
+ ----------
+ x : array-like
+ 1-D array or sequence.
+
+ %(Spectral)s
+
+ %(PSD)s
+
+ noverlap : int, default: 128
+ The number of points of overlap between blocks.
+ mode : str, default: 'psd'
+ What sort of spectrum to use:
+ 'psd'
+ Returns the power spectral density.
+ 'complex'
+ Returns the complex-valued frequency spectrum.
+ 'magnitude'
+ Returns the magnitude spectrum.
+ 'angle'
+ Returns the phase spectrum without unwrapping.
+ 'phase'
+ Returns the phase spectrum with unwrapping.
+
+ Returns
+ -------
+ spectrum : array-like
+ 2D array, columns are the periodograms of successive segments.
+
+ freqs : array-like
+ 1-D array, frequencies corresponding to the rows in *spectrum*.
+
+ t : array-like
+ 1-D array, the times corresponding to midpoints of segments
+ (i.e the columns in *spectrum*).
+
+ See Also
+ --------
+ psd : differs in the overlap and in the return values.
+ complex_spectrum : similar, but with complex valued frequencies.
+ magnitude_spectrum : similar single segment when *mode* is 'magnitude'.
+ angle_spectrum : similar to single segment when *mode* is 'angle'.
+ phase_spectrum : similar to single segment when *mode* is 'phase'.
+
+ Notes
+ -----
+ *detrend* and *scale_by_freq* only apply when *mode* is set to 'psd'.
+
+ """
+ if noverlap is None:
+ noverlap = 128 # default in _spectral_helper() is noverlap = 0
+ if NFFT is None:
+ NFFT = 256 # same default as in _spectral_helper()
+ if len(x) <= NFFT:
+ _api.warn_external("Only one segment is calculated since parameter "
+ f"NFFT (={NFFT}) >= signal length (={len(x)}).")
+
+ spec, freqs, t = _spectral_helper(x=x, y=None, NFFT=NFFT, Fs=Fs,
+ detrend_func=detrend, window=window,
+ noverlap=noverlap, pad_to=pad_to,
+ sides=sides,
+ scale_by_freq=scale_by_freq,
+ mode=mode)
+
+ if mode != 'complex':
+ spec = spec.real # Needed since helper implements generically
+
+ return spec, freqs, t
+
+
+@_docstring.interpd
+def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning,
+ noverlap=0, pad_to=None, sides='default', scale_by_freq=None):
+ r"""
+ The coherence between *x* and *y*. Coherence is the normalized
+ cross spectral density:
+
+ .. math::
+
+ C_{xy} = \frac{|P_{xy}|^2}{P_{xx}P_{yy}}
+
+ Parameters
+ ----------
+ x, y
+ Array or sequence containing the data
+
+ %(Spectral)s
+
+ %(PSD)s
+
+ noverlap : int, default: 0 (no overlap)
+ The number of points of overlap between segments.
+
+ Returns
+ -------
+ Cxy : 1-D array
+ The coherence vector.
+ freqs : 1-D array
+ The frequencies for the elements in *Cxy*.
+
+ See Also
+ --------
+ :func:`psd`, :func:`csd` :
+ For information about the methods used to compute :math:`P_{xy}`,
+ :math:`P_{xx}` and :math:`P_{yy}`.
+ """
+ if len(x) < 2 * NFFT:
+ raise ValueError(
+ "Coherence is calculated by averaging over *NFFT* length "
+ "segments. Your signal is too short for your choice of *NFFT*.")
+ Pxx, f = psd(x, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
+ scale_by_freq)
+ Pyy, f = psd(y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
+ scale_by_freq)
+ Pxy, f = csd(x, y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
+ scale_by_freq)
+ Cxy = np.abs(Pxy) ** 2 / (Pxx * Pyy)
+ return Cxy, f
+
+
+class GaussianKDE:
+ """
+ Representation of a kernel-density estimate using Gaussian kernels.
+
+ Parameters
+ ----------
+ dataset : array-like
+ Datapoints to estimate from. In case of univariate data this is a 1-D
+ array, otherwise a 2D array with shape (# of dims, # of data).
+ bw_method : {'scott', 'silverman'} or float or callable, optional
+ The method used to calculate the estimator bandwidth. If a
+ float, this will be used directly as `kde.factor`. If a
+ callable, it should take a `GaussianKDE` instance as only
+ parameter and return a float. If None (default), 'scott' is used.
+
+ Attributes
+ ----------
+ dataset : ndarray
+ The dataset passed to the constructor.
+ dim : int
+ Number of dimensions.
+ num_dp : int
+ Number of datapoints.
+ factor : float
+ The bandwidth factor, obtained from `kde.covariance_factor`, with which
+ the covariance matrix is multiplied.
+ covariance : ndarray
+ The covariance matrix of *dataset*, scaled by the calculated bandwidth
+ (`kde.factor`).
+ inv_cov : ndarray
+ The inverse of *covariance*.
+
+ Methods
+ -------
+ kde.evaluate(points) : ndarray
+ Evaluate the estimated pdf on a provided set of points.
+ kde(points) : ndarray
+ Same as kde.evaluate(points)
+ """
+
+ # This implementation with minor modification was too good to pass up.
+ # from scipy: https://github.com/scipy/scipy/blob/master/scipy/stats/kde.py
+
+ def __init__(self, dataset, bw_method=None):
+ self.dataset = np.atleast_2d(dataset)
+ if not np.array(self.dataset).size > 1:
+ raise ValueError("`dataset` input should have multiple elements.")
+
+ self.dim, self.num_dp = np.array(self.dataset).shape
+
+ if bw_method is None:
+ pass
+ elif cbook._str_equal(bw_method, 'scott'):
+ self.covariance_factor = self.scotts_factor
+ elif cbook._str_equal(bw_method, 'silverman'):
+ self.covariance_factor = self.silverman_factor
+ elif isinstance(bw_method, Number):
+ self._bw_method = 'use constant'
+ self.covariance_factor = lambda: bw_method
+ elif callable(bw_method):
+ self._bw_method = bw_method
+ self.covariance_factor = lambda: self._bw_method(self)
+ else:
+ raise ValueError("`bw_method` should be 'scott', 'silverman', a "
+ "scalar or a callable")
+
+ # Computes the covariance matrix for each Gaussian kernel using
+ # covariance_factor().
+
+ self.factor = self.covariance_factor()
+ # Cache covariance and inverse covariance of the data
+ if not hasattr(self, '_data_inv_cov'):
+ self.data_covariance = np.atleast_2d(
+ np.cov(
+ self.dataset,
+ rowvar=1,
+ bias=False))
+ self.data_inv_cov = np.linalg.inv(self.data_covariance)
+
+ self.covariance = self.data_covariance * self.factor ** 2
+ self.inv_cov = self.data_inv_cov / self.factor ** 2
+ self.norm_factor = (np.sqrt(np.linalg.det(2 * np.pi * self.covariance))
+ * self.num_dp)
+
+ def scotts_factor(self):
+ return np.power(self.num_dp, -1. / (self.dim + 4))
+
+ def silverman_factor(self):
+ return np.power(
+ self.num_dp * (self.dim + 2.0) / 4.0, -1. / (self.dim + 4))
+
+ # Default method to calculate bandwidth, can be overwritten by subclass
+ covariance_factor = scotts_factor
+
+ def evaluate(self, points):
+ """
+ Evaluate the estimated pdf on a set of points.
+
+ Parameters
+ ----------
+ points : (# of dimensions, # of points)-array
+ Alternatively, a (# of dimensions,) vector can be passed in and
+ treated as a single point.
+
+ Returns
+ -------
+ (# of points,)-array
+ The values at each point.
+
+ Raises
+ ------
+ ValueError : if the dimensionality of the input points is different
+ than the dimensionality of the KDE.
+
+ """
+ points = np.atleast_2d(points)
+
+ dim, num_m = np.array(points).shape
+ if dim != self.dim:
+ raise ValueError(f"points have dimension {dim}, dataset has "
+ f"dimension {self.dim}")
+
+ result = np.zeros(num_m)
+
+ if num_m >= self.num_dp:
+ # there are more points than data, so loop over data
+ for i in range(self.num_dp):
+ diff = self.dataset[:, i, np.newaxis] - points
+ tdiff = np.dot(self.inv_cov, diff)
+ energy = np.sum(diff * tdiff, axis=0) / 2.0
+ result = result + np.exp(-energy)
+ else:
+ # loop over points
+ for i in range(num_m):
+ diff = self.dataset - points[:, i, np.newaxis]
+ tdiff = np.dot(self.inv_cov, diff)
+ energy = np.sum(diff * tdiff, axis=0) / 2.0
+ result[i] = np.sum(np.exp(-energy), axis=0)
+
+ result = result / self.norm_factor
+
+ return result
+
+ __call__ = evaluate
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..1f23288dd10b1d8c4be54c0117bc6e34352d33c7
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.pyi
@@ -0,0 +1,100 @@
+from collections.abc import Callable
+import functools
+from typing import Literal
+
+import numpy as np
+from numpy.typing import ArrayLike
+
+def window_hanning(x: ArrayLike) -> ArrayLike: ...
+def window_none(x: ArrayLike) -> ArrayLike: ...
+def detrend(
+ x: ArrayLike,
+ key: Literal["default", "constant", "mean", "linear", "none"]
+ | Callable[[ArrayLike, int | None], ArrayLike]
+ | None = ...,
+ axis: int | None = ...,
+) -> ArrayLike: ...
+def detrend_mean(x: ArrayLike, axis: int | None = ...) -> ArrayLike: ...
+def detrend_none(x: ArrayLike, axis: int | None = ...) -> ArrayLike: ...
+def detrend_linear(y: ArrayLike) -> ArrayLike: ...
+def psd(
+ x: ArrayLike,
+ NFFT: int | None = ...,
+ Fs: float | None = ...,
+ detrend: Literal["none", "mean", "linear"]
+ | Callable[[ArrayLike, int | None], ArrayLike]
+ | None = ...,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = ...,
+ noverlap: int | None = ...,
+ pad_to: int | None = ...,
+ sides: Literal["default", "onesided", "twosided"] | None = ...,
+ scale_by_freq: bool | None = ...,
+) -> tuple[ArrayLike, ArrayLike]: ...
+def csd(
+ x: ArrayLike,
+ y: ArrayLike | None,
+ NFFT: int | None = ...,
+ Fs: float | None = ...,
+ detrend: Literal["none", "mean", "linear"]
+ | Callable[[ArrayLike, int | None], ArrayLike]
+ | None = ...,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = ...,
+ noverlap: int | None = ...,
+ pad_to: int | None = ...,
+ sides: Literal["default", "onesided", "twosided"] | None = ...,
+ scale_by_freq: bool | None = ...,
+) -> tuple[ArrayLike, ArrayLike]: ...
+
+complex_spectrum = functools.partial(tuple[ArrayLike, ArrayLike])
+magnitude_spectrum = functools.partial(tuple[ArrayLike, ArrayLike])
+angle_spectrum = functools.partial(tuple[ArrayLike, ArrayLike])
+phase_spectrum = functools.partial(tuple[ArrayLike, ArrayLike])
+
+def specgram(
+ x: ArrayLike,
+ NFFT: int | None = ...,
+ Fs: float | None = ...,
+ detrend: Literal["none", "mean", "linear"] | Callable[[ArrayLike, int | None], ArrayLike] | None = ...,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = ...,
+ noverlap: int | None = ...,
+ pad_to: int | None = ...,
+ sides: Literal["default", "onesided", "twosided"] | None = ...,
+ scale_by_freq: bool | None = ...,
+ mode: Literal["psd", "complex", "magnitude", "angle", "phase"] | None = ...,
+) -> tuple[ArrayLike, ArrayLike, ArrayLike]: ...
+def cohere(
+ x: ArrayLike,
+ y: ArrayLike,
+ NFFT: int = ...,
+ Fs: float = ...,
+ detrend: Literal["none", "mean", "linear"] | Callable[[ArrayLike, int | None], ArrayLike] = ...,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike = ...,
+ noverlap: int = ...,
+ pad_to: int | None = ...,
+ sides: Literal["default", "onesided", "twosided"] = ...,
+ scale_by_freq: bool | None = ...,
+) -> tuple[ArrayLike, ArrayLike]: ...
+
+class GaussianKDE:
+ dataset: ArrayLike
+ dim: int
+ num_dp: int
+ factor: float
+ data_covariance: ArrayLike
+ data_inv_cov: ArrayLike
+ covariance: ArrayLike
+ inv_cov: ArrayLike
+ norm_factor: float
+ def __init__(
+ self,
+ dataset: ArrayLike,
+ bw_method: Literal["scott", "silverman"]
+ | float
+ | Callable[[GaussianKDE], float]
+ | None = ...,
+ ) -> None: ...
+ def scotts_factor(self) -> float: ...
+ def silverman_factor(self) -> float: ...
+ def covariance_factor(self) -> float: ...
+ def evaluate(self, points: ArrayLike) -> np.ndarray: ...
+ def __call__(self, points: ArrayLike) -> np.ndarray: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmex10.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmex10.afm
new file mode 100644
index 0000000000000000000000000000000000000000..b9e318ff7f15fe985750b79c63ff0c8d7cdf6e11
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmex10.afm
@@ -0,0 +1,220 @@
+StartFontMetrics 2.0
+Comment Creation Date: Thu Jun 21 22:23:20 1990
+Comment UniqueID 5000774
+FontName CMEX10
+EncodingScheme FontSpecific
+FullName CMEX10
+FamilyName Computer Modern
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+Version 1.00
+Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
+Comment Computer Modern fonts were designed by Donald E. Knuth
+FontBBox -24 -2960 1454 772
+XHeight 430.556
+Comment CapHeight 0
+Ascender 750
+Comment Descender -1760
+Descender -2960
+Comment FontID CMEX
+Comment DesignSize 10 (pts)
+Comment CharacterCodingScheme TeX math extension
+Comment Space 0 0 0
+Comment ExtraSpace 0
+Comment Quad 1000
+Comment DefaultRuleThickness 40
+Comment BigOpSpacing 111.111 166.667 200 600 100
+Comment Ascendible characters (74) % macro - PS charname
+Comment Ascending 0, 16, 18, 32, 48 % ( - parenleft
+Comment Ascending 1, 17, 19, 33, 49 % ) - parenright
+Comment Ascending 2, 104, 20, 34, 50 % [ - bracketleft
+Comment Ascending 3, 105, 21, 35, 51 % ] - bracketright
+Comment Ascending 4, 106, 22, 36, 52 % lfloor - floorleft
+Comment Ascending 5, 107, 23, 37, 53 % rfloor - floorright
+Comment Ascending 6, 108, 24, 38, 54 % lceil - ceilingleft
+Comment Ascending 7, 109, 25, 39, 55 % rceil - ceilingright
+Comment Ascending 8, 110, 26, 40, 56 % { - braceleft
+Comment Ascending 9, 111, 27, 41, 57 % } - braceright
+Comment Ascending 10, 68, 28, 42 % < - anglebracketleft
+Comment Ascending 11, 69, 29, 43 % > - anglebracketright
+Comment Ascending 14, 46, 30, 44 % / - slash
+Comment Ascending 15, 47, 31, 45 % \ - backslash
+Comment Ascending 70, 71 % bigsqcup - unionsq
+Comment Ascending 72, 73 % oint - contintegral
+Comment Ascending 74, 75 % bigodot - circledot
+Comment Ascending 76, 77 % bigoplus - circleplus
+Comment Ascending 78, 79 % bigotimes - circlemultiply
+Comment Ascending 80, 88 % sum - summation
+Comment Ascending 81, 89 % prod - product
+Comment Ascending 82, 90 % int - integral
+Comment Ascending 83, 91 % bigcup - union
+Comment Ascending 84, 92 % bigcap - intersection
+Comment Ascending 85, 93 % biguplus - unionmulti
+Comment Ascending 86, 94 % bigwedge - logicaland
+Comment Ascending 87, 95 % bigvee - logicalor
+Comment Ascending 96, 97 % coprod - coproduct
+Comment Ascending 98, 99, 100 % widehat - hatwide
+Comment Ascending 101, 102, 103 % widetilde - tildewide
+Comment Ascending 112, 113, 114, 115, 116 % radical - sqrt
+Comment Extensible characters (28)
+Comment Extensible 12 top 0 mid 0 bot 0 rep 12 % vert - thin bar
+Comment Extensible 13 top 0 mid 0 bot 0 rep 13 % Vert - thin double bar
+Comment Extensible 48 top 48 mid 0 bot 64 rep 66 % ( - parenleft
+Comment Extensible 49 top 49 mid 0 bot 65 rep 67 % ) - parenright
+Comment Extensible 50 top 50 mid 0 bot 52 rep 54 % [ - bracketleft
+Comment Extensible 51 top 51 mid 0 bot 53 rep 55 % ] - bracketright
+Comment Extensible 52 top 0 mid 0 bot 52 rep 54 % lfloor - floorleft
+Comment Extensible 53 top 0 mid 0 bot 53 rep 55 % rfloor - floorright
+Comment Extensible 54 top 50 mid 0 bot 0 rep 54 % lceil - ceilingleft
+Comment Extensible 55 top 51 mid 0 bot 0 rep 55 % rceil - ceilingright
+Comment Extensible 56 top 56 mid 60 bot 58 rep 62 % { - braceleft
+Comment Extensible 57 top 57 mid 61 bot 59 rep 62 % } - braceright
+Comment Extensible 58 top 56 mid 0 bot 58 rep 62 % lgroup
+Comment Extensible 59 top 57 mid 0 bot 59 rep 62 % rgroup
+Comment Extensible 60 top 0 mid 0 bot 0 rep 63 % arrowvert
+Comment Extensible 61 top 0 mid 0 bot 0 rep 119 % Arrowvert
+Comment Extensible 62 top 0 mid 0 bot 0 rep 62 % bracevert
+Comment Extensible 63 top 120 mid 0 bot 121 rep 63 % updownarrow
+Comment Extensible 64 top 56 mid 0 bot 59 rep 62 % lmoustache
+Comment Extensible 65 top 57 mid 0 bot 58 rep 62 % rmoustache
+Comment Extensible 66 top 0 mid 0 bot 0 rep 66 % parenleftexten
+Comment Extensible 67 top 0 mid 0 bot 0 rep 67 % parenrightexten
+Comment Extensible 116 top 118 mid 0 bot 116 rep 117 % radical
+Comment Extensible 119 top 126 mid 0 bot 127 rep 119 % Updownarrow
+Comment Extensible 120 top 120 mid 0 bot 0 rep 63 % uparrow
+Comment Extensible 121 top 0 mid 0 bot 121 rep 63 % downarrow
+Comment Extensible 126 top 126 mid 0 bot 0 rep 119 % Uparrow
+Comment Extensible 127 top 0 mid 0 bot 127 rep 119 % Downarrow
+StartCharMetrics 129
+C 0 ; WX 458.333 ; N parenleftbig ; B 152 -1159 413 40 ;
+C 1 ; WX 458.333 ; N parenrightbig ; B 44 -1159 305 40 ;
+C 2 ; WX 416.667 ; N bracketleftbig ; B 202 -1159 394 40 ;
+C 3 ; WX 416.667 ; N bracketrightbig ; B 22 -1159 214 40 ;
+C 4 ; WX 472.222 ; N floorleftbig ; B 202 -1159 449 40 ;
+C 5 ; WX 472.222 ; N floorrightbig ; B 22 -1159 269 40 ;
+C 6 ; WX 472.222 ; N ceilingleftbig ; B 202 -1159 449 40 ;
+C 7 ; WX 472.222 ; N ceilingrightbig ; B 22 -1159 269 40 ;
+C 8 ; WX 583.333 ; N braceleftbig ; B 113 -1159 469 40 ;
+C 9 ; WX 583.333 ; N bracerightbig ; B 113 -1159 469 40 ;
+C 10 ; WX 472.222 ; N angbracketleftbig ; B 98 -1160 393 40 ;
+C 11 ; WX 472.222 ; N angbracketrightbig ; B 78 -1160 373 40 ;
+C 12 ; WX 333.333 ; N vextendsingle ; B 145 -621 188 21 ;
+C 13 ; WX 555.556 ; N vextenddouble ; B 145 -621 410 21 ;
+C 14 ; WX 577.778 ; N slashbig ; B 56 -1159 521 40 ;
+C 15 ; WX 577.778 ; N backslashbig ; B 56 -1159 521 40 ;
+C 16 ; WX 597.222 ; N parenleftBig ; B 180 -1759 560 40 ;
+C 17 ; WX 597.222 ; N parenrightBig ; B 36 -1759 416 40 ;
+C 18 ; WX 736.111 ; N parenleftbigg ; B 208 -2359 700 40 ;
+C 19 ; WX 736.111 ; N parenrightbigg ; B 35 -2359 527 40 ;
+C 20 ; WX 527.778 ; N bracketleftbigg ; B 250 -2359 513 40 ;
+C 21 ; WX 527.778 ; N bracketrightbigg ; B 14 -2359 277 40 ;
+C 22 ; WX 583.333 ; N floorleftbigg ; B 250 -2359 568 40 ;
+C 23 ; WX 583.333 ; N floorrightbigg ; B 14 -2359 332 40 ;
+C 24 ; WX 583.333 ; N ceilingleftbigg ; B 250 -2359 568 40 ;
+C 25 ; WX 583.333 ; N ceilingrightbigg ; B 14 -2359 332 40 ;
+C 26 ; WX 750 ; N braceleftbigg ; B 131 -2359 618 40 ;
+C 27 ; WX 750 ; N bracerightbigg ; B 131 -2359 618 40 ;
+C 28 ; WX 750 ; N angbracketleftbigg ; B 125 -2359 652 40 ;
+C 29 ; WX 750 ; N angbracketrightbigg ; B 97 -2359 624 40 ;
+C 30 ; WX 1044.44 ; N slashbigg ; B 56 -2359 987 40 ;
+C 31 ; WX 1044.44 ; N backslashbigg ; B 56 -2359 987 40 ;
+C 32 ; WX 791.667 ; N parenleftBigg ; B 236 -2959 757 40 ;
+C 33 ; WX 791.667 ; N parenrightBigg ; B 34 -2959 555 40 ;
+C 34 ; WX 583.333 ; N bracketleftBigg ; B 275 -2959 571 40 ;
+C 35 ; WX 583.333 ; N bracketrightBigg ; B 11 -2959 307 40 ;
+C 36 ; WX 638.889 ; N floorleftBigg ; B 275 -2959 627 40 ;
+C 37 ; WX 638.889 ; N floorrightBigg ; B 11 -2959 363 40 ;
+C 38 ; WX 638.889 ; N ceilingleftBigg ; B 275 -2959 627 40 ;
+C 39 ; WX 638.889 ; N ceilingrightBigg ; B 11 -2959 363 40 ;
+C 40 ; WX 805.556 ; N braceleftBigg ; B 144 -2959 661 40 ;
+C 41 ; WX 805.556 ; N bracerightBigg ; B 144 -2959 661 40 ;
+C 42 ; WX 805.556 ; N angbracketleftBigg ; B 139 -2960 697 40 ;
+C 43 ; WX 805.556 ; N angbracketrightBigg ; B 108 -2960 666 40 ;
+C 44 ; WX 1277.78 ; N slashBigg ; B 56 -2959 1221 40 ;
+C 45 ; WX 1277.78 ; N backslashBigg ; B 56 -2959 1221 40 ;
+C 46 ; WX 811.111 ; N slashBig ; B 56 -1759 754 40 ;
+C 47 ; WX 811.111 ; N backslashBig ; B 56 -1759 754 40 ;
+C 48 ; WX 875 ; N parenlefttp ; B 291 -1770 842 39 ;
+C 49 ; WX 875 ; N parenrighttp ; B 32 -1770 583 39 ;
+C 50 ; WX 666.667 ; N bracketlefttp ; B 326 -1760 659 39 ;
+C 51 ; WX 666.667 ; N bracketrighttp ; B 7 -1760 340 39 ;
+C 52 ; WX 666.667 ; N bracketleftbt ; B 326 -1759 659 40 ;
+C 53 ; WX 666.667 ; N bracketrightbt ; B 7 -1759 340 40 ;
+C 54 ; WX 666.667 ; N bracketleftex ; B 326 -601 395 1 ;
+C 55 ; WX 666.667 ; N bracketrightex ; B 271 -601 340 1 ;
+C 56 ; WX 888.889 ; N bracelefttp ; B 384 -910 718 -1 ;
+C 57 ; WX 888.889 ; N bracerighttp ; B 170 -910 504 -1 ;
+C 58 ; WX 888.889 ; N braceleftbt ; B 384 -899 718 10 ;
+C 59 ; WX 888.889 ; N bracerightbt ; B 170 -899 504 10 ;
+C 60 ; WX 888.889 ; N braceleftmid ; B 170 -1810 504 10 ;
+C 61 ; WX 888.889 ; N bracerightmid ; B 384 -1810 718 10 ;
+C 62 ; WX 888.889 ; N braceex ; B 384 -310 504 10 ;
+C 63 ; WX 666.667 ; N arrowvertex ; B 312 -601 355 1 ;
+C 64 ; WX 875 ; N parenleftbt ; B 291 -1759 842 50 ;
+C 65 ; WX 875 ; N parenrightbt ; B 32 -1759 583 50 ;
+C 66 ; WX 875 ; N parenleftex ; B 291 -610 402 10 ;
+C 67 ; WX 875 ; N parenrightex ; B 472 -610 583 10 ;
+C 68 ; WX 611.111 ; N angbracketleftBig ; B 112 -1759 522 40 ;
+C 69 ; WX 611.111 ; N angbracketrightBig ; B 88 -1759 498 40 ;
+C 70 ; WX 833.333 ; N unionsqtext ; B 56 -1000 776 0 ;
+C 71 ; WX 1111.11 ; N unionsqdisplay ; B 56 -1400 1054 0 ;
+C 72 ; WX 472.222 ; N contintegraltext ; B 56 -1111 609 0 ;
+C 73 ; WX 555.556 ; N contintegraldisplay ; B 56 -2222 943 0 ;
+C 74 ; WX 1111.11 ; N circledottext ; B 56 -1000 1054 0 ;
+C 75 ; WX 1511.11 ; N circledotdisplay ; B 56 -1400 1454 0 ;
+C 76 ; WX 1111.11 ; N circleplustext ; B 56 -1000 1054 0 ;
+C 77 ; WX 1511.11 ; N circleplusdisplay ; B 56 -1400 1454 0 ;
+C 78 ; WX 1111.11 ; N circlemultiplytext ; B 56 -1000 1054 0 ;
+C 79 ; WX 1511.11 ; N circlemultiplydisplay ; B 56 -1400 1454 0 ;
+C 80 ; WX 1055.56 ; N summationtext ; B 56 -1000 999 0 ;
+C 81 ; WX 944.444 ; N producttext ; B 56 -1000 887 0 ;
+C 82 ; WX 472.222 ; N integraltext ; B 56 -1111 609 0 ;
+C 83 ; WX 833.333 ; N uniontext ; B 56 -1000 776 0 ;
+C 84 ; WX 833.333 ; N intersectiontext ; B 56 -1000 776 0 ;
+C 85 ; WX 833.333 ; N unionmultitext ; B 56 -1000 776 0 ;
+C 86 ; WX 833.333 ; N logicalandtext ; B 56 -1000 776 0 ;
+C 87 ; WX 833.333 ; N logicalortext ; B 56 -1000 776 0 ;
+C 88 ; WX 1444.44 ; N summationdisplay ; B 56 -1400 1387 0 ;
+C 89 ; WX 1277.78 ; N productdisplay ; B 56 -1400 1221 0 ;
+C 90 ; WX 555.556 ; N integraldisplay ; B 56 -2222 943 0 ;
+C 91 ; WX 1111.11 ; N uniondisplay ; B 56 -1400 1054 0 ;
+C 92 ; WX 1111.11 ; N intersectiondisplay ; B 56 -1400 1054 0 ;
+C 93 ; WX 1111.11 ; N unionmultidisplay ; B 56 -1400 1054 0 ;
+C 94 ; WX 1111.11 ; N logicalanddisplay ; B 56 -1400 1054 0 ;
+C 95 ; WX 1111.11 ; N logicalordisplay ; B 56 -1400 1054 0 ;
+C 96 ; WX 944.444 ; N coproducttext ; B 56 -1000 887 0 ;
+C 97 ; WX 1277.78 ; N coproductdisplay ; B 56 -1400 1221 0 ;
+C 98 ; WX 555.556 ; N hatwide ; B -5 562 561 744 ;
+C 99 ; WX 1000 ; N hatwider ; B -4 575 1003 772 ;
+C 100 ; WX 1444.44 ; N hatwidest ; B -3 575 1446 772 ;
+C 101 ; WX 555.556 ; N tildewide ; B 0 608 555 722 ;
+C 102 ; WX 1000 ; N tildewider ; B 0 624 999 750 ;
+C 103 ; WX 1444.44 ; N tildewidest ; B 0 623 1443 750 ;
+C 104 ; WX 472.222 ; N bracketleftBig ; B 226 -1759 453 40 ;
+C 105 ; WX 472.222 ; N bracketrightBig ; B 18 -1759 245 40 ;
+C 106 ; WX 527.778 ; N floorleftBig ; B 226 -1759 509 40 ;
+C 107 ; WX 527.778 ; N floorrightBig ; B 18 -1759 301 40 ;
+C 108 ; WX 527.778 ; N ceilingleftBig ; B 226 -1759 509 40 ;
+C 109 ; WX 527.778 ; N ceilingrightBig ; B 18 -1759 301 40 ;
+C 110 ; WX 666.667 ; N braceleftBig ; B 119 -1759 547 40 ;
+C 111 ; WX 666.667 ; N bracerightBig ; B 119 -1759 547 40 ;
+C 112 ; WX 1000 ; N radicalbig ; B 110 -1160 1020 40 ;
+C 113 ; WX 1000 ; N radicalBig ; B 110 -1760 1020 40 ;
+C 114 ; WX 1000 ; N radicalbigg ; B 111 -2360 1020 40 ;
+C 115 ; WX 1000 ; N radicalBigg ; B 111 -2960 1020 40 ;
+C 116 ; WX 1055.56 ; N radicalbt ; B 111 -1800 742 20 ;
+C 117 ; WX 1055.56 ; N radicalvertex ; B 702 -620 742 20 ;
+C 118 ; WX 1055.56 ; N radicaltp ; B 702 -580 1076 40 ;
+C 119 ; WX 777.778 ; N arrowvertexdbl ; B 257 -601 521 1 ;
+C 120 ; WX 666.667 ; N arrowtp ; B 111 -600 556 0 ;
+C 121 ; WX 666.667 ; N arrowbt ; B 111 -600 556 0 ;
+C 122 ; WX 450 ; N bracehtipdownleft ; B -24 -214 460 120 ;
+C 123 ; WX 450 ; N bracehtipdownright ; B -10 -214 474 120 ;
+C 124 ; WX 450 ; N bracehtipupleft ; B -24 0 460 334 ;
+C 125 ; WX 450 ; N bracehtipupright ; B -10 0 474 334 ;
+C 126 ; WX 777.778 ; N arrowdbltp ; B 56 -600 722 -1 ;
+C 127 ; WX 777.778 ; N arrowdblbt ; B 56 -599 722 0 ;
+C -1 ; WX 333.333 ; N space ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmmi10.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmmi10.afm
new file mode 100644
index 0000000000000000000000000000000000000000..f47d6ba04d6c94d7bf378263056df54c6a838143
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmmi10.afm
@@ -0,0 +1,326 @@
+StartFontMetrics 2.0
+Comment Creation Date: Thu Jun 21 22:23:22 1990
+Comment UniqueID 5000785
+FontName CMMI10
+EncodingScheme FontSpecific
+FullName CMMI10
+FamilyName Computer Modern
+Weight Medium
+ItalicAngle -14.04
+IsFixedPitch false
+Version 1.00A
+Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
+Comment Computer Modern fonts were designed by Donald E. Knuth
+FontBBox -32 -250 1048 750
+CapHeight 683.333
+XHeight 430.556
+Ascender 694.444
+Descender -194.444
+Comment FontID CMMI
+Comment DesignSize 10 (pts)
+Comment CharacterCodingScheme TeX math italic
+Comment Space 0 0 0
+Comment Quad 1000
+StartCharMetrics 129
+C 0 ; WX 615.276 ; N Gamma ; B 39 0 723 680 ;
+C 1 ; WX 833.333 ; N Delta ; B 49 0 787 716 ;
+C 2 ; WX 762.774 ; N Theta ; B 50 -22 739 705 ;
+C 3 ; WX 694.444 ; N Lambda ; B 35 0 666 716 ;
+C 4 ; WX 742.361 ; N Xi ; B 53 0 777 677 ;
+C 5 ; WX 831.25 ; N Pi ; B 39 0 880 680 ;
+C 6 ; WX 779.861 ; N Sigma ; B 59 0 807 683 ;
+C 7 ; WX 583.333 ; N Upsilon ; B 29 0 700 705 ;
+C 8 ; WX 666.667 ; N Phi ; B 24 0 642 683 ;
+C 9 ; WX 612.221 ; N Psi ; B 28 0 692 683 ;
+C 10 ; WX 772.396 ; N Omega ; B 80 0 785 705 ;
+C 11 ; WX 639.7 ; N alpha ; B 41 -11 601 442 ;
+C 12 ; WX 565.625 ; N beta ; B 25 -194 590 705 ;
+C 13 ; WX 517.73 ; N gamma ; B 18 -215 542 442 ;
+C 14 ; WX 444.444 ; N delta ; B 41 -12 452 705 ;
+C 15 ; WX 405.902 ; N epsilon1 ; B 47 -11 376 431 ;
+C 16 ; WX 437.5 ; N zeta ; B 47 -205 474 697 ;
+C 17 ; WX 496.53 ; N eta ; B 29 -216 496 442 ;
+C 18 ; WX 469.442 ; N theta ; B 42 -11 455 705 ;
+C 19 ; WX 353.935 ; N iota ; B 56 -11 324 442 ;
+C 20 ; WX 576.159 ; N kappa ; B 55 -11 546 442 ;
+C 21 ; WX 583.333 ; N lambda ; B 53 -13 547 694 ;
+C 22 ; WX 602.548 ; N mu ; B 30 -216 572 442 ;
+C 23 ; WX 493.981 ; N nu ; B 53 0 524 442 ;
+C 24 ; WX 437.5 ; N xi ; B 24 -205 446 697 ;
+C 25 ; WX 570.025 ; N pi ; B 27 -11 567 431 ;
+C 26 ; WX 517.014 ; N rho ; B 30 -216 502 442 ;
+C 27 ; WX 571.429 ; N sigma ; B 38 -11 567 431 ;
+C 28 ; WX 437.153 ; N tau ; B 27 -12 511 431 ;
+C 29 ; WX 540.278 ; N upsilon ; B 29 -11 524 443 ;
+C 30 ; WX 595.833 ; N phi ; B 49 -205 573 694 ;
+C 31 ; WX 625.691 ; N chi ; B 32 -205 594 442 ;
+C 32 ; WX 651.39 ; N psi ; B 29 -205 635 694 ;
+C 33 ; WX 622.453 ; N omega ; B 13 -11 605 443 ;
+C 34 ; WX 466.316 ; N epsilon ; B 27 -22 428 453 ;
+C 35 ; WX 591.438 ; N theta1 ; B 29 -11 561 705 ;
+C 36 ; WX 828.125 ; N pi1 ; B 27 -11 817 431 ;
+C 37 ; WX 517.014 ; N rho1 ; B 74 -194 502 442 ;
+C 38 ; WX 362.846 ; N sigma1 ; B 32 -108 408 442 ;
+C 39 ; WX 654.165 ; N phi1 ; B 50 -218 619 442 ;
+C 40 ; WX 1000 ; N arrowlefttophalf ; B 56 230 943 428 ;
+C 41 ; WX 1000 ; N arrowleftbothalf ; B 56 72 943 270 ;
+C 42 ; WX 1000 ; N arrowrighttophalf ; B 56 230 943 428 ;
+C 43 ; WX 1000 ; N arrowrightbothalf ; B 56 72 943 270 ;
+C 44 ; WX 277.778 ; N arrowhookleft ; B 56 230 221 464 ;
+C 45 ; WX 277.778 ; N arrowhookright ; B 56 230 221 464 ;
+C 46 ; WX 500 ; N triangleright ; B 27 -4 472 504 ;
+C 47 ; WX 500 ; N triangleleft ; B 27 -4 472 504 ;
+C 48 ; WX 500 ; N zerooldstyle ; B 40 -22 459 453 ;
+C 49 ; WX 500 ; N oneoldstyle ; B 92 0 418 453 ;
+C 50 ; WX 500 ; N twooldstyle ; B 44 0 449 453 ;
+C 51 ; WX 500 ; N threeoldstyle ; B 42 -216 457 453 ;
+C 52 ; WX 500 ; N fouroldstyle ; B 28 -194 471 464 ;
+C 53 ; WX 500 ; N fiveoldstyle ; B 50 -216 449 453 ;
+C 54 ; WX 500 ; N sixoldstyle ; B 42 -22 457 666 ;
+C 55 ; WX 500 ; N sevenoldstyle ; B 56 -216 485 463 ;
+C 56 ; WX 500 ; N eightoldstyle ; B 42 -22 457 666 ;
+C 57 ; WX 500 ; N nineoldstyle ; B 42 -216 457 453 ;
+C 58 ; WX 277.778 ; N period ; B 86 0 192 106 ;
+C 59 ; WX 277.778 ; N comma ; B 86 -193 203 106 ;
+C 60 ; WX 777.778 ; N less ; B 83 -39 694 539 ;
+C 61 ; WX 500 ; N slash ; B 56 -250 443 750 ;
+C 62 ; WX 777.778 ; N greater ; B 83 -39 694 539 ;
+C 63 ; WX 500 ; N star ; B 4 16 496 486 ;
+C 64 ; WX 530.902 ; N partialdiff ; B 40 -22 566 716 ;
+C 65 ; WX 750 ; N A ; B 35 0 722 716 ;
+C 66 ; WX 758.508 ; N B ; B 42 0 756 683 ;
+C 67 ; WX 714.72 ; N C ; B 51 -22 759 705 ;
+C 68 ; WX 827.915 ; N D ; B 41 0 803 683 ;
+C 69 ; WX 738.193 ; N E ; B 39 0 765 680 ;
+C 70 ; WX 643.055 ; N F ; B 39 0 751 680 ;
+C 71 ; WX 786.247 ; N G ; B 51 -22 760 705 ;
+C 72 ; WX 831.25 ; N H ; B 39 0 881 683 ;
+C 73 ; WX 439.583 ; N I ; B 34 0 498 683 ;
+C 74 ; WX 554.512 ; N J ; B 73 -22 633 683 ;
+C 75 ; WX 849.305 ; N K ; B 39 0 889 683 ;
+C 76 ; WX 680.556 ; N L ; B 39 0 643 683 ;
+C 77 ; WX 970.138 ; N M ; B 43 0 1044 683 ;
+C 78 ; WX 803.471 ; N N ; B 39 0 881 683 ;
+C 79 ; WX 762.774 ; N O ; B 50 -22 739 705 ;
+C 80 ; WX 642.012 ; N P ; B 41 0 753 683 ;
+C 81 ; WX 790.553 ; N Q ; B 50 -194 739 705 ;
+C 82 ; WX 759.288 ; N R ; B 41 -22 755 683 ;
+C 83 ; WX 613.193 ; N S ; B 53 -22 645 705 ;
+C 84 ; WX 584.375 ; N T ; B 24 0 704 677 ;
+C 85 ; WX 682.776 ; N U ; B 68 -22 760 683 ;
+C 86 ; WX 583.333 ; N V ; B 56 -22 769 683 ;
+C 87 ; WX 944.444 ; N W ; B 55 -22 1048 683 ;
+C 88 ; WX 828.472 ; N X ; B 27 0 851 683 ;
+C 89 ; WX 580.556 ; N Y ; B 34 0 762 683 ;
+C 90 ; WX 682.638 ; N Z ; B 59 0 722 683 ;
+C 91 ; WX 388.889 ; N flat ; B 56 -22 332 750 ;
+C 92 ; WX 388.889 ; N natural ; B 79 -217 309 728 ;
+C 93 ; WX 388.889 ; N sharp ; B 56 -216 332 716 ;
+C 94 ; WX 1000 ; N slurbelow ; B 56 133 943 371 ;
+C 95 ; WX 1000 ; N slurabove ; B 56 130 943 381 ;
+C 96 ; WX 416.667 ; N lscript ; B 11 -12 398 705 ;
+C 97 ; WX 528.588 ; N a ; B 40 -11 498 442 ;
+C 98 ; WX 429.165 ; N b ; B 47 -11 415 694 ;
+C 99 ; WX 432.755 ; N c ; B 41 -11 430 442 ;
+C 100 ; WX 520.486 ; N d ; B 40 -11 517 694 ;
+C 101 ; WX 465.625 ; N e ; B 46 -11 430 442 ;
+C 102 ; WX 489.583 ; N f ; B 53 -205 552 705 ;
+C 103 ; WX 476.967 ; N g ; B 16 -205 474 442 ;
+C 104 ; WX 576.159 ; N h ; B 55 -11 546 694 ;
+C 105 ; WX 344.511 ; N i ; B 29 -11 293 661 ;
+C 106 ; WX 411.805 ; N j ; B -13 -205 397 661 ;
+C 107 ; WX 520.602 ; N k ; B 55 -11 508 694 ;
+C 108 ; WX 298.378 ; N l ; B 46 -11 260 694 ;
+C 109 ; WX 878.012 ; N m ; B 29 -11 848 442 ;
+C 110 ; WX 600.233 ; N n ; B 29 -11 571 442 ;
+C 111 ; WX 484.721 ; N o ; B 41 -11 469 442 ;
+C 112 ; WX 503.125 ; N p ; B -32 -194 490 442 ;
+C 113 ; WX 446.412 ; N q ; B 40 -194 453 442 ;
+C 114 ; WX 451.158 ; N r ; B 29 -11 436 442 ;
+C 115 ; WX 468.75 ; N s ; B 52 -11 419 442 ;
+C 116 ; WX 361.111 ; N t ; B 23 -11 330 626 ;
+C 117 ; WX 572.456 ; N u ; B 29 -11 543 442 ;
+C 118 ; WX 484.722 ; N v ; B 29 -11 468 443 ;
+C 119 ; WX 715.916 ; N w ; B 29 -11 691 443 ;
+C 120 ; WX 571.527 ; N x ; B 29 -11 527 442 ;
+C 121 ; WX 490.28 ; N y ; B 29 -205 490 442 ;
+C 122 ; WX 465.048 ; N z ; B 43 -11 467 442 ;
+C 123 ; WX 322.454 ; N dotlessi ; B 29 -11 293 442 ;
+C 124 ; WX 384.028 ; N dotlessj ; B -13 -205 360 442 ;
+C 125 ; WX 636.457 ; N weierstrass ; B 76 -216 618 453 ;
+C 126 ; WX 500 ; N vector ; B 182 516 625 714 ;
+C 127 ; WX 277.778 ; N tie ; B 264 538 651 665 ;
+C -1 ; WX 333.333 ; N space ; B 0 0 0 0 ;
+EndCharMetrics
+Comment The following are bogus kern pairs for TeX positioning of accents
+StartKernData
+StartKernPairs 166
+KPX Gamma slash -55.556
+KPX Gamma comma -111.111
+KPX Gamma period -111.111
+KPX Gamma tie 83.333
+KPX Delta tie 166.667
+KPX Theta tie 83.333
+KPX Lambda tie 166.667
+KPX Xi tie 83.333
+KPX Pi slash -55.556
+KPX Pi comma -55.556
+KPX Pi period -55.556
+KPX Pi tie 55.556
+KPX Sigma tie 83.333
+KPX Upsilon slash -55.556
+KPX Upsilon comma -111.111
+KPX Upsilon period -111.111
+KPX Upsilon tie 55.556
+KPX Phi tie 83.333
+KPX Psi slash -55.556
+KPX Psi comma -55.556
+KPX Psi period -55.556
+KPX Psi tie 55.556
+KPX Omega tie 83.333
+KPX alpha tie 27.778
+KPX beta tie 83.333
+KPX delta comma -55.556
+KPX delta period -55.556
+KPX delta tie 55.556
+KPX epsilon1 tie 55.556
+KPX zeta tie 83.333
+KPX eta tie 55.556
+KPX theta tie 83.333
+KPX iota tie 55.556
+KPX mu tie 27.778
+KPX nu comma -55.556
+KPX nu period -55.556
+KPX nu tie 27.778
+KPX xi tie 111.111
+KPX rho tie 83.333
+KPX sigma comma -55.556
+KPX sigma period -55.556
+KPX tau comma -55.556
+KPX tau period -55.556
+KPX tau tie 27.778
+KPX upsilon tie 27.778
+KPX phi tie 83.333
+KPX chi tie 55.556
+KPX psi tie 111.111
+KPX epsilon tie 83.333
+KPX theta1 tie 83.333
+KPX rho1 tie 83.333
+KPX sigma1 tie 83.333
+KPX phi1 tie 83.333
+KPX slash Delta -55.556
+KPX slash A -55.556
+KPX slash M -55.556
+KPX slash N -55.556
+KPX slash Y 55.556
+KPX slash Z -55.556
+KPX partialdiff tie 83.333
+KPX A tie 138.889
+KPX B tie 83.333
+KPX C slash -27.778
+KPX C comma -55.556
+KPX C period -55.556
+KPX C tie 83.333
+KPX D tie 55.556
+KPX E tie 83.333
+KPX F slash -55.556
+KPX F comma -111.111
+KPX F period -111.111
+KPX F tie 83.333
+KPX G tie 83.333
+KPX H slash -55.556
+KPX H comma -55.556
+KPX H period -55.556
+KPX H tie 55.556
+KPX I tie 111.111
+KPX J slash -55.556
+KPX J comma -111.111
+KPX J period -111.111
+KPX J tie 166.667
+KPX K slash -55.556
+KPX K comma -55.556
+KPX K period -55.556
+KPX K tie 55.556
+KPX L tie 27.778
+KPX M slash -55.556
+KPX M comma -55.556
+KPX M period -55.556
+KPX M tie 83.333
+KPX N slash -83.333
+KPX N slash -27.778
+KPX N comma -55.556
+KPX N period -55.556
+KPX N tie 83.333
+KPX O tie 83.333
+KPX P slash -55.556
+KPX P comma -111.111
+KPX P period -111.111
+KPX P tie 83.333
+KPX Q tie 83.333
+KPX R tie 83.333
+KPX S slash -55.556
+KPX S comma -55.556
+KPX S period -55.556
+KPX S tie 83.333
+KPX T slash -27.778
+KPX T comma -55.556
+KPX T period -55.556
+KPX T tie 83.333
+KPX U comma -111.111
+KPX U period -111.111
+KPX U slash -55.556
+KPX U tie 27.778
+KPX V comma -166.667
+KPX V period -166.667
+KPX V slash -111.111
+KPX W comma -166.667
+KPX W period -166.667
+KPX W slash -111.111
+KPX X slash -83.333
+KPX X slash -27.778
+KPX X comma -55.556
+KPX X period -55.556
+KPX X tie 83.333
+KPX Y comma -166.667
+KPX Y period -166.667
+KPX Y slash -111.111
+KPX Z slash -55.556
+KPX Z comma -55.556
+KPX Z period -55.556
+KPX Z tie 83.333
+KPX lscript tie 111.111
+KPX c tie 55.556
+KPX d Y 55.556
+KPX d Z -55.556
+KPX d j -111.111
+KPX d f -166.667
+KPX d tie 166.667
+KPX e tie 55.556
+KPX f comma -55.556
+KPX f period -55.556
+KPX f tie 166.667
+KPX g tie 27.778
+KPX h tie -27.778
+KPX j comma -55.556
+KPX j period -55.556
+KPX l tie 83.333
+KPX o tie 55.556
+KPX p tie 83.333
+KPX q tie 83.333
+KPX r comma -55.556
+KPX r period -55.556
+KPX r tie 55.556
+KPX s tie 55.556
+KPX t tie 83.333
+KPX u tie 27.778
+KPX v tie 27.778
+KPX w tie 83.333
+KPX x tie 27.778
+KPX y tie 55.556
+KPX z tie 55.556
+KPX dotlessi tie 27.778
+KPX dotlessj tie 83.333
+KPX weierstrass tie 111.111
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmr10.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmr10.afm
new file mode 100644
index 0000000000000000000000000000000000000000..4d586fe6ff39e83c73fa0358b31737ce4ac9fb98
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmr10.afm
@@ -0,0 +1,343 @@
+StartFontMetrics 2.0
+Comment Creation Date: Thu Jun 21 22:23:28 1990
+Comment UniqueID 5000793
+FontName CMR10
+EncodingScheme FontSpecific
+FullName CMR10
+FamilyName Computer Modern
+Weight Medium
+ItalicAngle 0.0
+IsFixedPitch false
+Version 1.00B
+Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
+Comment Computer Modern fonts were designed by Donald E. Knuth
+FontBBox -40 -250 1009 969
+CapHeight 683.333
+XHeight 430.556
+Ascender 694.444
+Descender -194.444
+Comment FontID CMR
+Comment DesignSize 10 (pts)
+Comment CharacterCodingScheme TeX text
+Comment Space 333.333 166.667 111.111
+Comment ExtraSpace 111.111
+Comment Quad 1000
+StartCharMetrics 129
+C 0 ; WX 625 ; N Gamma ; B 33 0 582 680 ;
+C 1 ; WX 833.333 ; N Delta ; B 47 0 785 716 ;
+C 2 ; WX 777.778 ; N Theta ; B 56 -22 721 705 ;
+C 3 ; WX 694.444 ; N Lambda ; B 32 0 661 716 ;
+C 4 ; WX 666.667 ; N Xi ; B 42 0 624 677 ;
+C 5 ; WX 750 ; N Pi ; B 33 0 716 680 ;
+C 6 ; WX 722.222 ; N Sigma ; B 56 0 665 683 ;
+C 7 ; WX 777.778 ; N Upsilon ; B 56 0 721 705 ;
+C 8 ; WX 722.222 ; N Phi ; B 56 0 665 683 ;
+C 9 ; WX 777.778 ; N Psi ; B 57 0 720 683 ;
+C 10 ; WX 722.222 ; N Omega ; B 44 0 677 705 ;
+C 11 ; WX 583.333 ; N ff ; B 27 0 628 705 ; L i ffi ; L l ffl ;
+C 12 ; WX 555.556 ; N fi ; B 27 0 527 705 ;
+C 13 ; WX 555.556 ; N fl ; B 27 0 527 705 ;
+C 14 ; WX 833.333 ; N ffi ; B 27 0 804 705 ;
+C 15 ; WX 833.333 ; N ffl ; B 27 0 804 705 ;
+C 16 ; WX 277.778 ; N dotlessi ; B 33 0 247 442 ;
+C 17 ; WX 305.556 ; N dotlessj ; B -40 -205 210 442 ;
+C 18 ; WX 500 ; N grave ; B 107 510 293 698 ;
+C 19 ; WX 500 ; N acute ; B 206 510 392 698 ;
+C 20 ; WX 500 ; N caron ; B 118 516 381 638 ;
+C 21 ; WX 500 ; N breve ; B 100 522 399 694 ;
+C 22 ; WX 500 ; N macron ; B 69 559 430 590 ;
+C 23 ; WX 750 ; N ring ; B 279 541 470 716 ;
+C 24 ; WX 444.444 ; N cedilla ; B 131 -203 367 -22 ;
+C 25 ; WX 500 ; N germandbls ; B 28 -11 471 705 ;
+C 26 ; WX 722.222 ; N ae ; B 45 -11 693 448 ;
+C 27 ; WX 777.778 ; N oe ; B 28 -11 749 448 ;
+C 28 ; WX 500 ; N oslash ; B 35 -102 464 534 ;
+C 29 ; WX 902.778 ; N AE ; B 32 0 874 683 ;
+C 30 ; WX 1013.89 ; N OE ; B 70 -22 985 705 ;
+C 31 ; WX 777.778 ; N Oslash ; B 56 -56 721 739 ;
+C 32 ; WX 277.778 ; N suppress ; B 27 280 262 392 ;
+C 33 ; WX 277.778 ; N exclam ; B 86 0 192 716 ; L quoteleft exclamdown ;
+C 34 ; WX 500 ; N quotedblright ; B 33 395 347 694 ;
+C 35 ; WX 833.333 ; N numbersign ; B 56 -194 776 694 ;
+C 36 ; WX 500 ; N dollar ; B 56 -56 443 750 ;
+C 37 ; WX 833.333 ; N percent ; B 56 -56 776 750 ;
+C 38 ; WX 777.778 ; N ampersand ; B 42 -22 727 716 ;
+C 39 ; WX 277.778 ; N quoteright ; B 86 395 206 694 ; L quoteright quotedblright ;
+C 40 ; WX 388.889 ; N parenleft ; B 99 -250 331 750 ;
+C 41 ; WX 388.889 ; N parenright ; B 57 -250 289 750 ;
+C 42 ; WX 500 ; N asterisk ; B 65 319 434 750 ;
+C 43 ; WX 777.778 ; N plus ; B 56 -83 721 583 ;
+C 44 ; WX 277.778 ; N comma ; B 86 -193 203 106 ;
+C 45 ; WX 333.333 ; N hyphen ; B 11 187 276 245 ; L hyphen endash ;
+C 46 ; WX 277.778 ; N period ; B 86 0 192 106 ;
+C 47 ; WX 500 ; N slash ; B 56 -250 443 750 ;
+C 48 ; WX 500 ; N zero ; B 39 -22 460 666 ;
+C 49 ; WX 500 ; N one ; B 89 0 419 666 ;
+C 50 ; WX 500 ; N two ; B 50 0 449 666 ;
+C 51 ; WX 500 ; N three ; B 42 -22 457 666 ;
+C 52 ; WX 500 ; N four ; B 28 0 471 677 ;
+C 53 ; WX 500 ; N five ; B 50 -22 449 666 ;
+C 54 ; WX 500 ; N six ; B 42 -22 457 666 ;
+C 55 ; WX 500 ; N seven ; B 56 -22 485 676 ;
+C 56 ; WX 500 ; N eight ; B 42 -22 457 666 ;
+C 57 ; WX 500 ; N nine ; B 42 -22 457 666 ;
+C 58 ; WX 277.778 ; N colon ; B 86 0 192 431 ;
+C 59 ; WX 277.778 ; N semicolon ; B 86 -193 195 431 ;
+C 60 ; WX 277.778 ; N exclamdown ; B 86 -216 192 500 ;
+C 61 ; WX 777.778 ; N equal ; B 56 133 721 367 ;
+C 62 ; WX 472.222 ; N questiondown ; B 56 -205 415 500 ;
+C 63 ; WX 472.222 ; N question ; B 56 0 415 705 ; L quoteleft questiondown ;
+C 64 ; WX 777.778 ; N at ; B 56 -11 721 705 ;
+C 65 ; WX 750 ; N A ; B 32 0 717 716 ;
+C 66 ; WX 708.333 ; N B ; B 36 0 651 683 ;
+C 67 ; WX 722.222 ; N C ; B 56 -22 665 705 ;
+C 68 ; WX 763.889 ; N D ; B 35 0 707 683 ;
+C 69 ; WX 680.556 ; N E ; B 33 0 652 680 ;
+C 70 ; WX 652.778 ; N F ; B 33 0 610 680 ;
+C 71 ; WX 784.722 ; N G ; B 56 -22 735 705 ;
+C 72 ; WX 750 ; N H ; B 33 0 716 683 ;
+C 73 ; WX 361.111 ; N I ; B 28 0 333 683 ;
+C 74 ; WX 513.889 ; N J ; B 41 -22 465 683 ;
+C 75 ; WX 777.778 ; N K ; B 33 0 736 683 ;
+C 76 ; WX 625 ; N L ; B 33 0 582 683 ;
+C 77 ; WX 916.667 ; N M ; B 37 0 879 683 ;
+C 78 ; WX 750 ; N N ; B 33 0 716 683 ;
+C 79 ; WX 777.778 ; N O ; B 56 -22 721 705 ;
+C 80 ; WX 680.556 ; N P ; B 35 0 624 683 ;
+C 81 ; WX 777.778 ; N Q ; B 56 -194 727 705 ;
+C 82 ; WX 736.111 ; N R ; B 35 -22 732 683 ;
+C 83 ; WX 555.556 ; N S ; B 56 -22 499 705 ;
+C 84 ; WX 722.222 ; N T ; B 36 0 685 677 ;
+C 85 ; WX 750 ; N U ; B 33 -22 716 683 ;
+C 86 ; WX 750 ; N V ; B 19 -22 730 683 ;
+C 87 ; WX 1027.78 ; N W ; B 18 -22 1009 683 ;
+C 88 ; WX 750 ; N X ; B 24 0 726 683 ;
+C 89 ; WX 750 ; N Y ; B 11 0 738 683 ;
+C 90 ; WX 611.111 ; N Z ; B 56 0 560 683 ;
+C 91 ; WX 277.778 ; N bracketleft ; B 118 -250 255 750 ;
+C 92 ; WX 500 ; N quotedblleft ; B 152 394 466 693 ;
+C 93 ; WX 277.778 ; N bracketright ; B 22 -250 159 750 ;
+C 94 ; WX 500 ; N circumflex ; B 116 540 383 694 ;
+C 95 ; WX 277.778 ; N dotaccent ; B 85 563 192 669 ;
+C 96 ; WX 277.778 ; N quoteleft ; B 72 394 192 693 ; L quoteleft quotedblleft ;
+C 97 ; WX 500 ; N a ; B 42 -11 493 448 ;
+C 98 ; WX 555.556 ; N b ; B 28 -11 521 694 ;
+C 99 ; WX 444.444 ; N c ; B 34 -11 415 448 ;
+C 100 ; WX 555.556 ; N d ; B 34 -11 527 694 ;
+C 101 ; WX 444.444 ; N e ; B 28 -11 415 448 ;
+C 102 ; WX 305.556 ; N f ; B 33 0 357 705 ; L i fi ; L f ff ; L l fl ;
+C 103 ; WX 500 ; N g ; B 28 -206 485 453 ;
+C 104 ; WX 555.556 ; N h ; B 32 0 535 694 ;
+C 105 ; WX 277.778 ; N i ; B 33 0 247 669 ;
+C 106 ; WX 305.556 ; N j ; B -40 -205 210 669 ;
+C 107 ; WX 527.778 ; N k ; B 28 0 511 694 ;
+C 108 ; WX 277.778 ; N l ; B 33 0 255 694 ;
+C 109 ; WX 833.333 ; N m ; B 32 0 813 442 ;
+C 110 ; WX 555.556 ; N n ; B 32 0 535 442 ;
+C 111 ; WX 500 ; N o ; B 28 -11 471 448 ;
+C 112 ; WX 555.556 ; N p ; B 28 -194 521 442 ;
+C 113 ; WX 527.778 ; N q ; B 34 -194 527 442 ;
+C 114 ; WX 391.667 ; N r ; B 28 0 364 442 ;
+C 115 ; WX 394.444 ; N s ; B 33 -11 360 448 ;
+C 116 ; WX 388.889 ; N t ; B 19 -11 332 615 ;
+C 117 ; WX 555.556 ; N u ; B 32 -11 535 442 ;
+C 118 ; WX 527.778 ; N v ; B 19 -11 508 431 ;
+C 119 ; WX 722.222 ; N w ; B 18 -11 703 431 ;
+C 120 ; WX 527.778 ; N x ; B 12 0 516 431 ;
+C 121 ; WX 527.778 ; N y ; B 19 -205 508 431 ;
+C 122 ; WX 444.444 ; N z ; B 28 0 401 431 ;
+C 123 ; WX 500 ; N endash ; B 0 255 499 277 ; L hyphen emdash ;
+C 124 ; WX 1000 ; N emdash ; B 0 255 999 277 ;
+C 125 ; WX 500 ; N hungarumlaut ; B 128 513 420 699 ;
+C 126 ; WX 500 ; N tilde ; B 83 575 416 668 ;
+C 127 ; WX 500 ; N dieresis ; B 103 569 396 669 ;
+C -1 ; WX 333.333 ; N space ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 183
+KPX ff quoteright 77.778
+KPX ff question 77.778
+KPX ff exclam 77.778
+KPX ff parenright 77.778
+KPX ff bracketright 77.778
+KPX suppress l -277.778
+KPX suppress L -319.444
+KPX quoteright question 111.111
+KPX quoteright exclam 111.111
+KPX A t -27.778
+KPX A C -27.778
+KPX A O -27.778
+KPX A G -27.778
+KPX A U -27.778
+KPX A Q -27.778
+KPX A T -83.333
+KPX A Y -83.333
+KPX A V -111.111
+KPX A W -111.111
+KPX D X -27.778
+KPX D W -27.778
+KPX D A -27.778
+KPX D V -27.778
+KPX D Y -27.778
+KPX F o -83.333
+KPX F e -83.333
+KPX F u -83.333
+KPX F r -83.333
+KPX F a -83.333
+KPX F A -111.111
+KPX F O -27.778
+KPX F C -27.778
+KPX F G -27.778
+KPX F Q -27.778
+KPX I I 27.778
+KPX K O -27.778
+KPX K C -27.778
+KPX K G -27.778
+KPX K Q -27.778
+KPX L T -83.333
+KPX L Y -83.333
+KPX L V -111.111
+KPX L W -111.111
+KPX O X -27.778
+KPX O W -27.778
+KPX O A -27.778
+KPX O V -27.778
+KPX O Y -27.778
+KPX P A -83.333
+KPX P o -27.778
+KPX P e -27.778
+KPX P a -27.778
+KPX P period -83.333
+KPX P comma -83.333
+KPX R t -27.778
+KPX R C -27.778
+KPX R O -27.778
+KPX R G -27.778
+KPX R U -27.778
+KPX R Q -27.778
+KPX R T -83.333
+KPX R Y -83.333
+KPX R V -111.111
+KPX R W -111.111
+KPX T y -27.778
+KPX T e -83.333
+KPX T o -83.333
+KPX T r -83.333
+KPX T a -83.333
+KPX T A -83.333
+KPX T u -83.333
+KPX V o -83.333
+KPX V e -83.333
+KPX V u -83.333
+KPX V r -83.333
+KPX V a -83.333
+KPX V A -111.111
+KPX V O -27.778
+KPX V C -27.778
+KPX V G -27.778
+KPX V Q -27.778
+KPX W o -83.333
+KPX W e -83.333
+KPX W u -83.333
+KPX W r -83.333
+KPX W a -83.333
+KPX W A -111.111
+KPX W O -27.778
+KPX W C -27.778
+KPX W G -27.778
+KPX W Q -27.778
+KPX X O -27.778
+KPX X C -27.778
+KPX X G -27.778
+KPX X Q -27.778
+KPX Y e -83.333
+KPX Y o -83.333
+KPX Y r -83.333
+KPX Y a -83.333
+KPX Y A -83.333
+KPX Y u -83.333
+KPX a v -27.778
+KPX a j 55.556
+KPX a y -27.778
+KPX a w -27.778
+KPX b e 27.778
+KPX b o 27.778
+KPX b x -27.778
+KPX b d 27.778
+KPX b c 27.778
+KPX b q 27.778
+KPX b v -27.778
+KPX b j 55.556
+KPX b y -27.778
+KPX b w -27.778
+KPX c h -27.778
+KPX c k -27.778
+KPX f quoteright 77.778
+KPX f question 77.778
+KPX f exclam 77.778
+KPX f parenright 77.778
+KPX f bracketright 77.778
+KPX g j 27.778
+KPX h t -27.778
+KPX h u -27.778
+KPX h b -27.778
+KPX h y -27.778
+KPX h v -27.778
+KPX h w -27.778
+KPX k a -55.556
+KPX k e -27.778
+KPX k a -27.778
+KPX k o -27.778
+KPX k c -27.778
+KPX m t -27.778
+KPX m u -27.778
+KPX m b -27.778
+KPX m y -27.778
+KPX m v -27.778
+KPX m w -27.778
+KPX n t -27.778
+KPX n u -27.778
+KPX n b -27.778
+KPX n y -27.778
+KPX n v -27.778
+KPX n w -27.778
+KPX o e 27.778
+KPX o o 27.778
+KPX o x -27.778
+KPX o d 27.778
+KPX o c 27.778
+KPX o q 27.778
+KPX o v -27.778
+KPX o j 55.556
+KPX o y -27.778
+KPX o w -27.778
+KPX p e 27.778
+KPX p o 27.778
+KPX p x -27.778
+KPX p d 27.778
+KPX p c 27.778
+KPX p q 27.778
+KPX p v -27.778
+KPX p j 55.556
+KPX p y -27.778
+KPX p w -27.778
+KPX t y -27.778
+KPX t w -27.778
+KPX u w -27.778
+KPX v a -55.556
+KPX v e -27.778
+KPX v a -27.778
+KPX v o -27.778
+KPX v c -27.778
+KPX w e -27.778
+KPX w a -27.778
+KPX w o -27.778
+KPX w c -27.778
+KPX y o -27.778
+KPX y e -27.778
+KPX y a -27.778
+KPX y period -83.333
+KPX y comma -83.333
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmsy10.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmsy10.afm
new file mode 100644
index 0000000000000000000000000000000000000000..09e9487d14dfa274356f1d1fb923ccff9685d05b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmsy10.afm
@@ -0,0 +1,195 @@
+StartFontMetrics 2.0
+Comment Creation Date: Thu Jun 21 22:23:44 1990
+Comment UniqueID 5000820
+FontName CMSY10
+EncodingScheme FontSpecific
+FullName CMSY10
+FamilyName Computer Modern
+Weight Medium
+ItalicAngle -14.035
+IsFixedPitch false
+Version 1.00
+Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
+Comment Computer Modern fonts were designed by Donald E. Knuth
+FontBBox -29 -960 1116 775
+CapHeight 683.333
+XHeight 430.556
+Ascender 694.444
+Descender -960
+Comment FontID CMSY
+Comment DesignSize 10 (pts)
+Comment CharacterCodingScheme TeX math symbols
+Comment Space 0 0 0
+Comment ExtraSpace 0
+Comment Quad 1000
+Comment Num 676.508 393.732 443.731
+Comment Denom 685.951 344.841
+Comment Sup 412.892 362.892 288.889
+Comment Sub 150 247.217
+Comment Supdrop 386.108
+Comment Subdrop 50
+Comment Delim 2390 1010
+Comment Axisheight 250
+StartCharMetrics 129
+C 0 ; WX 777.778 ; N minus ; B 83 230 694 270 ;
+C 1 ; WX 277.778 ; N periodcentered ; B 86 197 192 303 ;
+C 2 ; WX 777.778 ; N multiply ; B 147 9 630 491 ;
+C 3 ; WX 500 ; N asteriskmath ; B 65 34 434 465 ;
+C 4 ; WX 777.778 ; N divide ; B 56 -30 722 530 ;
+C 5 ; WX 500 ; N diamondmath ; B 11 11 489 489 ;
+C 6 ; WX 777.778 ; N plusminus ; B 56 0 721 666 ;
+C 7 ; WX 777.778 ; N minusplus ; B 56 -166 721 500 ;
+C 8 ; WX 777.778 ; N circleplus ; B 56 -83 721 583 ;
+C 9 ; WX 777.778 ; N circleminus ; B 56 -83 721 583 ;
+C 10 ; WX 777.778 ; N circlemultiply ; B 56 -83 721 583 ;
+C 11 ; WX 777.778 ; N circledivide ; B 56 -83 721 583 ;
+C 12 ; WX 777.778 ; N circledot ; B 56 -83 721 583 ;
+C 13 ; WX 1000 ; N circlecopyrt ; B 56 -216 943 716 ;
+C 14 ; WX 500 ; N openbullet ; B 56 56 443 444 ;
+C 15 ; WX 500 ; N bullet ; B 56 56 443 444 ;
+C 16 ; WX 777.778 ; N equivasymptotic ; B 56 16 721 484 ;
+C 17 ; WX 777.778 ; N equivalence ; B 56 36 721 464 ;
+C 18 ; WX 777.778 ; N reflexsubset ; B 83 -137 694 636 ;
+C 19 ; WX 777.778 ; N reflexsuperset ; B 83 -137 694 636 ;
+C 20 ; WX 777.778 ; N lessequal ; B 83 -137 694 636 ;
+C 21 ; WX 777.778 ; N greaterequal ; B 83 -137 694 636 ;
+C 22 ; WX 777.778 ; N precedesequal ; B 83 -137 694 636 ;
+C 23 ; WX 777.778 ; N followsequal ; B 83 -137 694 636 ;
+C 24 ; WX 777.778 ; N similar ; B 56 133 721 367 ;
+C 25 ; WX 777.778 ; N approxequal ; B 56 56 721 483 ;
+C 26 ; WX 777.778 ; N propersubset ; B 83 -40 694 540 ;
+C 27 ; WX 777.778 ; N propersuperset ; B 83 -40 694 540 ;
+C 28 ; WX 1000 ; N lessmuch ; B 56 -66 943 566 ;
+C 29 ; WX 1000 ; N greatermuch ; B 56 -66 943 566 ;
+C 30 ; WX 777.778 ; N precedes ; B 83 -40 694 539 ;
+C 31 ; WX 777.778 ; N follows ; B 83 -40 694 539 ;
+C 32 ; WX 1000 ; N arrowleft ; B 57 72 943 428 ;
+C 33 ; WX 1000 ; N arrowright ; B 56 72 942 428 ;
+C 34 ; WX 500 ; N arrowup ; B 72 -194 428 693 ;
+C 35 ; WX 500 ; N arrowdown ; B 72 -193 428 694 ;
+C 36 ; WX 1000 ; N arrowboth ; B 57 72 942 428 ;
+C 37 ; WX 1000 ; N arrownortheast ; B 56 -193 946 697 ;
+C 38 ; WX 1000 ; N arrowsoutheast ; B 56 -197 946 693 ;
+C 39 ; WX 777.778 ; N similarequal ; B 56 36 721 464 ;
+C 40 ; WX 1000 ; N arrowdblleft ; B 57 -25 943 525 ;
+C 41 ; WX 1000 ; N arrowdblright ; B 56 -25 942 525 ;
+C 42 ; WX 611.111 ; N arrowdblup ; B 30 -194 580 694 ;
+C 43 ; WX 611.111 ; N arrowdbldown ; B 30 -194 580 694 ;
+C 44 ; WX 1000 ; N arrowdblboth ; B 35 -25 964 525 ;
+C 45 ; WX 1000 ; N arrownorthwest ; B 53 -193 943 697 ;
+C 46 ; WX 1000 ; N arrowsouthwest ; B 53 -197 943 693 ;
+C 47 ; WX 777.778 ; N proportional ; B 56 -11 722 442 ;
+C 48 ; WX 275 ; N prime ; B 29 45 262 559 ;
+C 49 ; WX 1000 ; N infinity ; B 56 -11 943 442 ;
+C 50 ; WX 666.667 ; N element ; B 83 -40 583 540 ;
+C 51 ; WX 666.667 ; N owner ; B 83 -40 583 540 ;
+C 52 ; WX 888.889 ; N triangle ; B 59 0 829 716 ;
+C 53 ; WX 888.889 ; N triangleinv ; B 59 -216 829 500 ;
+C 54 ; WX 0 ; N negationslash ; B 139 -216 638 716 ;
+C 55 ; WX 0 ; N mapsto ; B 56 64 124 436 ;
+C 56 ; WX 555.556 ; N universal ; B 0 -22 556 694 ;
+C 57 ; WX 555.556 ; N existential ; B 56 0 499 694 ;
+C 58 ; WX 666.667 ; N logicalnot ; B 56 89 610 356 ;
+C 59 ; WX 500 ; N emptyset ; B 47 -78 452 772 ;
+C 60 ; WX 722.222 ; N Rfractur ; B 46 -22 714 716 ;
+C 61 ; WX 722.222 ; N Ifractur ; B 56 -11 693 705 ;
+C 62 ; WX 777.778 ; N latticetop ; B 56 0 722 666 ;
+C 63 ; WX 777.778 ; N perpendicular ; B 56 0 722 666 ;
+C 64 ; WX 611.111 ; N aleph ; B 56 0 554 693 ;
+C 65 ; WX 798.469 ; N A ; B 27 -50 798 722 ;
+C 66 ; WX 656.808 ; N B ; B 30 -22 665 706 ;
+C 67 ; WX 526.527 ; N C ; B 12 -24 534 705 ;
+C 68 ; WX 771.391 ; N D ; B 20 0 766 683 ;
+C 69 ; WX 527.778 ; N E ; B 28 -22 565 705 ;
+C 70 ; WX 718.75 ; N F ; B 17 -33 829 683 ;
+C 71 ; WX 594.864 ; N G ; B 44 -119 601 705 ;
+C 72 ; WX 844.516 ; N H ; B 20 -47 818 683 ;
+C 73 ; WX 544.513 ; N I ; B -24 0 635 683 ;
+C 74 ; WX 677.778 ; N J ; B 47 -119 840 683 ;
+C 75 ; WX 761.949 ; N K ; B 30 -22 733 705 ;
+C 76 ; WX 689.723 ; N L ; B 31 -22 656 705 ;
+C 77 ; WX 1200.9 ; N M ; B 27 -50 1116 705 ;
+C 78 ; WX 820.489 ; N N ; B -29 -50 978 775 ;
+C 79 ; WX 796.112 ; N O ; B 57 -22 777 705 ;
+C 80 ; WX 695.558 ; N P ; B 20 -50 733 683 ;
+C 81 ; WX 816.667 ; N Q ; B 113 -124 788 705 ;
+C 82 ; WX 847.502 ; N R ; B 20 -22 837 683 ;
+C 83 ; WX 605.556 ; N S ; B 18 -22 642 705 ;
+C 84 ; WX 544.643 ; N T ; B 29 0 798 717 ;
+C 85 ; WX 625.83 ; N U ; B -17 -28 688 683 ;
+C 86 ; WX 612.781 ; N V ; B 35 -45 660 683 ;
+C 87 ; WX 987.782 ; N W ; B 35 -45 1036 683 ;
+C 88 ; WX 713.295 ; N X ; B 50 0 808 683 ;
+C 89 ; WX 668.335 ; N Y ; B 31 -135 717 683 ;
+C 90 ; WX 724.724 ; N Z ; B 37 0 767 683 ;
+C 91 ; WX 666.667 ; N union ; B 56 -22 610 598 ;
+C 92 ; WX 666.667 ; N intersection ; B 56 -22 610 598 ;
+C 93 ; WX 666.667 ; N unionmulti ; B 56 -22 610 598 ;
+C 94 ; WX 666.667 ; N logicaland ; B 56 -22 610 598 ;
+C 95 ; WX 666.667 ; N logicalor ; B 56 -22 610 598 ;
+C 96 ; WX 611.111 ; N turnstileleft ; B 56 0 554 694 ;
+C 97 ; WX 611.111 ; N turnstileright ; B 56 0 554 694 ;
+C 98 ; WX 444.444 ; N floorleft ; B 174 -250 422 750 ;
+C 99 ; WX 444.444 ; N floorright ; B 21 -250 269 750 ;
+C 100 ; WX 444.444 ; N ceilingleft ; B 174 -250 422 750 ;
+C 101 ; WX 444.444 ; N ceilingright ; B 21 -250 269 750 ;
+C 102 ; WX 500 ; N braceleft ; B 72 -250 427 750 ;
+C 103 ; WX 500 ; N braceright ; B 72 -250 427 750 ;
+C 104 ; WX 388.889 ; N angbracketleft ; B 110 -250 332 750 ;
+C 105 ; WX 388.889 ; N angbracketright ; B 56 -250 278 750 ;
+C 106 ; WX 277.778 ; N bar ; B 119 -250 159 750 ;
+C 107 ; WX 500 ; N bardbl ; B 132 -250 367 750 ;
+C 108 ; WX 500 ; N arrowbothv ; B 72 -272 428 772 ;
+C 109 ; WX 611.111 ; N arrowdblbothv ; B 30 -272 580 772 ;
+C 110 ; WX 500 ; N backslash ; B 56 -250 443 750 ;
+C 111 ; WX 277.778 ; N wreathproduct ; B 56 -83 221 583 ;
+C 112 ; WX 833.333 ; N radical ; B 73 -960 853 40 ;
+C 113 ; WX 750 ; N coproduct ; B 36 0 713 683 ;
+C 114 ; WX 833.333 ; N nabla ; B 47 -33 785 683 ;
+C 115 ; WX 416.667 ; N integral ; B 56 -216 471 716 ;
+C 116 ; WX 666.667 ; N unionsq ; B 61 0 605 598 ;
+C 117 ; WX 666.667 ; N intersectionsq ; B 61 0 605 598 ;
+C 118 ; WX 777.778 ; N subsetsqequal ; B 83 -137 714 636 ;
+C 119 ; WX 777.778 ; N supersetsqequal ; B 63 -137 694 636 ;
+C 120 ; WX 444.444 ; N section ; B 69 -205 374 705 ;
+C 121 ; WX 444.444 ; N dagger ; B 56 -216 387 705 ;
+C 122 ; WX 444.444 ; N daggerdbl ; B 56 -205 387 705 ;
+C 123 ; WX 611.111 ; N paragraph ; B 56 -194 582 694 ;
+C 124 ; WX 777.778 ; N club ; B 28 -130 750 727 ;
+C 125 ; WX 777.778 ; N diamond ; B 56 -163 722 727 ;
+C 126 ; WX 777.778 ; N heart ; B 56 -33 722 716 ;
+C 127 ; WX 777.778 ; N spade ; B 56 -130 722 727 ;
+C -1 ; WX 333.333 ; N space ; B 0 0 0 0 ;
+EndCharMetrics
+Comment The following are bogus kern pairs for TeX positioning of accents
+StartKernData
+StartKernPairs 26
+KPX A prime 194.444
+KPX B prime 138.889
+KPX C prime 138.889
+KPX D prime 83.333
+KPX E prime 111.111
+KPX F prime 111.111
+KPX G prime 111.111
+KPX H prime 111.111
+KPX I prime 27.778
+KPX J prime 166.667
+KPX K prime 55.556
+KPX L prime 138.889
+KPX M prime 138.889
+KPX N prime 83.333
+KPX O prime 111.111
+KPX P prime 83.333
+KPX Q prime 111.111
+KPX R prime 83.333
+KPX S prime 138.889
+KPX T prime 27.778
+KPX U prime 83.333
+KPX V prime 27.778
+KPX W prime 83.333
+KPX X prime 138.889
+KPX Y prime 83.333
+KPX Z prime 138.889
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmtt10.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmtt10.afm
new file mode 100644
index 0000000000000000000000000000000000000000..d6ec19b090843b46a1810ff09aa0749c51cb2f8a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmtt10.afm
@@ -0,0 +1,156 @@
+StartFontMetrics 2.0
+Comment Creation Date: Thu Jun 21 22:23:51 1990
+Comment UniqueID 5000832
+FontName CMTT10
+EncodingScheme FontSpecific
+FullName CMTT10
+FamilyName Computer Modern
+Weight Medium
+ItalicAngle 0.0
+IsFixedPitch true
+Version 1.00B
+Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
+Comment Computer Modern fonts were designed by Donald E. Knuth
+FontBBox -4 -235 731 800
+CapHeight 611.111
+XHeight 430.556
+Ascender 611.111
+Descender -222.222
+Comment FontID CMTT
+Comment DesignSize 10 (pts)
+Comment CharacterCodingScheme TeX typewriter text
+Comment Space 525 0 0
+Comment ExtraSpace 525
+Comment Quad 1050
+StartCharMetrics 129
+C 0 ; WX 525 ; N Gamma ; B 32 0 488 611 ;
+C 1 ; WX 525 ; N Delta ; B 34 0 490 623 ;
+C 2 ; WX 525 ; N Theta ; B 56 -11 468 622 ;
+C 3 ; WX 525 ; N Lambda ; B 29 0 495 623 ;
+C 4 ; WX 525 ; N Xi ; B 33 0 491 611 ;
+C 5 ; WX 525 ; N Pi ; B 22 0 502 611 ;
+C 6 ; WX 525 ; N Sigma ; B 40 0 484 611 ;
+C 7 ; WX 525 ; N Upsilon ; B 38 0 486 622 ;
+C 8 ; WX 525 ; N Phi ; B 40 0 484 611 ;
+C 9 ; WX 525 ; N Psi ; B 38 0 486 611 ;
+C 10 ; WX 525 ; N Omega ; B 32 0 492 622 ;
+C 11 ; WX 525 ; N arrowup ; B 59 0 465 611 ;
+C 12 ; WX 525 ; N arrowdown ; B 59 0 465 611 ;
+C 13 ; WX 525 ; N quotesingle ; B 217 328 309 622 ;
+C 14 ; WX 525 ; N exclamdown ; B 212 -233 312 389 ;
+C 15 ; WX 525 ; N questiondown ; B 62 -228 462 389 ;
+C 16 ; WX 525 ; N dotlessi ; B 78 0 455 431 ;
+C 17 ; WX 525 ; N dotlessj ; B 48 -228 368 431 ;
+C 18 ; WX 525 ; N grave ; B 117 477 329 611 ;
+C 19 ; WX 525 ; N acute ; B 195 477 407 611 ;
+C 20 ; WX 525 ; N caron ; B 101 454 423 572 ;
+C 21 ; WX 525 ; N breve ; B 86 498 438 611 ;
+C 22 ; WX 525 ; N macron ; B 73 514 451 577 ;
+C 23 ; WX 525 ; N ring ; B 181 499 343 619 ;
+C 24 ; WX 525 ; N cedilla ; B 162 -208 428 45 ;
+C 25 ; WX 525 ; N germandbls ; B 17 -6 495 617 ;
+C 26 ; WX 525 ; N ae ; B 33 -6 504 440 ;
+C 27 ; WX 525 ; N oe ; B 19 -6 505 440 ;
+C 28 ; WX 525 ; N oslash ; B 43 -140 481 571 ;
+C 29 ; WX 525 ; N AE ; B 23 0 499 611 ;
+C 30 ; WX 525 ; N OE ; B 29 -11 502 622 ;
+C 31 ; WX 525 ; N Oslash ; B 56 -85 468 696 ;
+C 32 ; WX 525 ; N visiblespace ; B 44 -132 480 240 ;
+C 33 ; WX 525 ; N exclam ; B 212 0 312 622 ; L quoteleft exclamdown ;
+C 34 ; WX 525 ; N quotedbl ; B 126 328 398 622 ;
+C 35 ; WX 525 ; N numbersign ; B 35 0 489 611 ;
+C 36 ; WX 525 ; N dollar ; B 58 -83 466 694 ;
+C 37 ; WX 525 ; N percent ; B 35 -83 489 694 ;
+C 38 ; WX 525 ; N ampersand ; B 28 -11 490 622 ;
+C 39 ; WX 525 ; N quoteright ; B 180 302 341 611 ;
+C 40 ; WX 525 ; N parenleft ; B 173 -82 437 694 ;
+C 41 ; WX 525 ; N parenright ; B 88 -82 352 694 ;
+C 42 ; WX 525 ; N asterisk ; B 68 90 456 521 ;
+C 43 ; WX 525 ; N plus ; B 38 81 486 531 ;
+C 44 ; WX 525 ; N comma ; B 180 -139 346 125 ;
+C 45 ; WX 525 ; N hyphen ; B 56 271 468 341 ;
+C 46 ; WX 525 ; N period ; B 200 0 325 125 ;
+C 47 ; WX 525 ; N slash ; B 58 -83 466 694 ;
+C 48 ; WX 525 ; N zero ; B 50 -11 474 622 ;
+C 49 ; WX 525 ; N one ; B 105 0 442 622 ;
+C 50 ; WX 525 ; N two ; B 52 0 472 622 ;
+C 51 ; WX 525 ; N three ; B 44 -11 480 622 ;
+C 52 ; WX 525 ; N four ; B 29 0 495 623 ;
+C 53 ; WX 525 ; N five ; B 52 -11 472 611 ;
+C 54 ; WX 525 ; N six ; B 53 -11 471 622 ;
+C 55 ; WX 525 ; N seven ; B 44 -11 480 627 ;
+C 56 ; WX 525 ; N eight ; B 44 -11 480 622 ;
+C 57 ; WX 525 ; N nine ; B 53 -11 471 622 ;
+C 58 ; WX 525 ; N colon ; B 200 0 325 431 ;
+C 59 ; WX 525 ; N semicolon ; B 180 -139 330 431 ;
+C 60 ; WX 525 ; N less ; B 56 56 468 556 ;
+C 61 ; WX 525 ; N equal ; B 38 195 486 417 ;
+C 62 ; WX 525 ; N greater ; B 56 56 468 556 ;
+C 63 ; WX 525 ; N question ; B 62 0 462 617 ; L quoteleft questiondown ;
+C 64 ; WX 525 ; N at ; B 44 -6 480 617 ;
+C 65 ; WX 525 ; N A ; B 27 0 497 623 ;
+C 66 ; WX 525 ; N B ; B 23 0 482 611 ;
+C 67 ; WX 525 ; N C ; B 40 -11 484 622 ;
+C 68 ; WX 525 ; N D ; B 19 0 485 611 ;
+C 69 ; WX 525 ; N E ; B 26 0 502 611 ;
+C 70 ; WX 525 ; N F ; B 28 0 490 611 ;
+C 71 ; WX 525 ; N G ; B 38 -11 496 622 ;
+C 72 ; WX 525 ; N H ; B 22 0 502 611 ;
+C 73 ; WX 525 ; N I ; B 79 0 446 611 ;
+C 74 ; WX 525 ; N J ; B 71 -11 478 611 ;
+C 75 ; WX 525 ; N K ; B 26 0 495 611 ;
+C 76 ; WX 525 ; N L ; B 32 0 488 611 ;
+C 77 ; WX 525 ; N M ; B 17 0 507 611 ;
+C 78 ; WX 525 ; N N ; B 28 0 496 611 ;
+C 79 ; WX 525 ; N O ; B 56 -11 468 622 ;
+C 80 ; WX 525 ; N P ; B 26 0 480 611 ;
+C 81 ; WX 525 ; N Q ; B 56 -139 468 622 ;
+C 82 ; WX 525 ; N R ; B 22 -11 522 611 ;
+C 83 ; WX 525 ; N S ; B 52 -11 472 622 ;
+C 84 ; WX 525 ; N T ; B 26 0 498 611 ;
+C 85 ; WX 525 ; N U ; B 4 -11 520 611 ;
+C 86 ; WX 525 ; N V ; B 18 -8 506 611 ;
+C 87 ; WX 525 ; N W ; B 11 -8 513 611 ;
+C 88 ; WX 525 ; N X ; B 27 0 496 611 ;
+C 89 ; WX 525 ; N Y ; B 19 0 505 611 ;
+C 90 ; WX 525 ; N Z ; B 48 0 481 611 ;
+C 91 ; WX 525 ; N bracketleft ; B 222 -83 483 694 ;
+C 92 ; WX 525 ; N backslash ; B 58 -83 466 694 ;
+C 93 ; WX 525 ; N bracketright ; B 41 -83 302 694 ;
+C 94 ; WX 525 ; N asciicircum ; B 100 471 424 611 ;
+C 95 ; WX 525 ; N underscore ; B 56 -95 468 -25 ;
+C 96 ; WX 525 ; N quoteleft ; B 183 372 344 681 ;
+C 97 ; WX 525 ; N a ; B 55 -6 524 440 ;
+C 98 ; WX 525 ; N b ; B 12 -6 488 611 ;
+C 99 ; WX 525 ; N c ; B 73 -6 466 440 ;
+C 100 ; WX 525 ; N d ; B 36 -6 512 611 ;
+C 101 ; WX 525 ; N e ; B 55 -6 464 440 ;
+C 102 ; WX 525 ; N f ; B 42 0 437 617 ;
+C 103 ; WX 525 ; N g ; B 29 -229 509 442 ;
+C 104 ; WX 525 ; N h ; B 12 0 512 611 ;
+C 105 ; WX 525 ; N i ; B 78 0 455 612 ;
+C 106 ; WX 525 ; N j ; B 48 -228 368 612 ;
+C 107 ; WX 525 ; N k ; B 21 0 508 611 ;
+C 108 ; WX 525 ; N l ; B 58 0 467 611 ;
+C 109 ; WX 525 ; N m ; B -4 0 516 437 ;
+C 110 ; WX 525 ; N n ; B 12 0 512 437 ;
+C 111 ; WX 525 ; N o ; B 57 -6 467 440 ;
+C 112 ; WX 525 ; N p ; B 12 -222 488 437 ;
+C 113 ; WX 525 ; N q ; B 40 -222 537 437 ;
+C 114 ; WX 525 ; N r ; B 32 0 487 437 ;
+C 115 ; WX 525 ; N s ; B 72 -6 459 440 ;
+C 116 ; WX 525 ; N t ; B 25 -6 449 554 ;
+C 117 ; WX 525 ; N u ; B 12 -6 512 431 ;
+C 118 ; WX 525 ; N v ; B 24 -4 500 431 ;
+C 119 ; WX 525 ; N w ; B 16 -4 508 431 ;
+C 120 ; WX 525 ; N x ; B 27 0 496 431 ;
+C 121 ; WX 525 ; N y ; B 26 -228 500 431 ;
+C 122 ; WX 525 ; N z ; B 33 0 475 431 ;
+C 123 ; WX 525 ; N braceleft ; B 57 -83 467 694 ;
+C 124 ; WX 525 ; N bar ; B 227 -83 297 694 ;
+C 125 ; WX 525 ; N braceright ; B 57 -83 467 694 ;
+C 126 ; WX 525 ; N asciitilde ; B 87 491 437 611 ;
+C 127 ; WX 525 ; N dieresis ; B 110 512 414 612 ;
+C -1 ; WX 525 ; N space ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagd8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagd8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..69eebba18ee05121f10e4b682eb6443ded4a72e1
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagd8a.afm
@@ -0,0 +1,576 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Mar 4 13:46:34 1991
+Comment UniqueID 34370
+Comment VMusage 24954 31846
+FontName AvantGarde-Demi
+FullName ITC Avant Garde Gothic Demi
+FamilyName ITC Avant Garde Gothic
+Weight Demi
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -123 -251 1222 1021
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.ITC Avant Garde Gothic is a registered trademark of International Typeface Corporation.
+EncodingScheme AdobeStandardEncoding
+CapHeight 740
+XHeight 555
+Ascender 740
+Descender -185
+StartCharMetrics 228
+C 32 ; WX 280 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 280 ; N exclam ; B 73 0 206 740 ;
+C 34 ; WX 360 ; N quotedbl ; B 19 444 341 740 ;
+C 35 ; WX 560 ; N numbersign ; B 29 0 525 700 ;
+C 36 ; WX 560 ; N dollar ; B 58 -86 501 857 ;
+C 37 ; WX 860 ; N percent ; B 36 -15 822 755 ;
+C 38 ; WX 680 ; N ampersand ; B 34 -15 665 755 ;
+C 39 ; WX 280 ; N quoteright ; B 72 466 205 740 ;
+C 40 ; WX 380 ; N parenleft ; B 74 -157 350 754 ;
+C 41 ; WX 380 ; N parenright ; B 37 -157 313 754 ;
+C 42 ; WX 440 ; N asterisk ; B 67 457 374 755 ;
+C 43 ; WX 600 ; N plus ; B 48 0 552 506 ;
+C 44 ; WX 280 ; N comma ; B 73 -141 206 133 ;
+C 45 ; WX 420 ; N hyphen ; B 71 230 349 348 ;
+C 46 ; WX 280 ; N period ; B 73 0 206 133 ;
+C 47 ; WX 460 ; N slash ; B 6 -100 454 740 ;
+C 48 ; WX 560 ; N zero ; B 32 -15 529 755 ;
+C 49 ; WX 560 ; N one ; B 137 0 363 740 ;
+C 50 ; WX 560 ; N two ; B 36 0 523 755 ;
+C 51 ; WX 560 ; N three ; B 28 -15 532 755 ;
+C 52 ; WX 560 ; N four ; B 15 0 545 740 ;
+C 53 ; WX 560 ; N five ; B 25 -15 535 740 ;
+C 54 ; WX 560 ; N six ; B 23 -15 536 739 ;
+C 55 ; WX 560 ; N seven ; B 62 0 498 740 ;
+C 56 ; WX 560 ; N eight ; B 33 -15 527 755 ;
+C 57 ; WX 560 ; N nine ; B 24 0 537 754 ;
+C 58 ; WX 280 ; N colon ; B 73 0 206 555 ;
+C 59 ; WX 280 ; N semicolon ; B 73 -141 206 555 ;
+C 60 ; WX 600 ; N less ; B 46 -8 554 514 ;
+C 61 ; WX 600 ; N equal ; B 48 81 552 425 ;
+C 62 ; WX 600 ; N greater ; B 46 -8 554 514 ;
+C 63 ; WX 560 ; N question ; B 38 0 491 755 ;
+C 64 ; WX 740 ; N at ; B 50 -12 750 712 ;
+C 65 ; WX 740 ; N A ; B 7 0 732 740 ;
+C 66 ; WX 580 ; N B ; B 70 0 551 740 ;
+C 67 ; WX 780 ; N C ; B 34 -15 766 755 ;
+C 68 ; WX 700 ; N D ; B 63 0 657 740 ;
+C 69 ; WX 520 ; N E ; B 61 0 459 740 ;
+C 70 ; WX 480 ; N F ; B 61 0 438 740 ;
+C 71 ; WX 840 ; N G ; B 27 -15 817 755 ;
+C 72 ; WX 680 ; N H ; B 71 0 610 740 ;
+C 73 ; WX 280 ; N I ; B 72 0 209 740 ;
+C 74 ; WX 480 ; N J ; B 2 -15 409 740 ;
+C 75 ; WX 620 ; N K ; B 89 0 620 740 ;
+C 76 ; WX 440 ; N L ; B 72 0 435 740 ;
+C 77 ; WX 900 ; N M ; B 63 0 837 740 ;
+C 78 ; WX 740 ; N N ; B 70 0 671 740 ;
+C 79 ; WX 840 ; N O ; B 33 -15 807 755 ;
+C 80 ; WX 560 ; N P ; B 72 0 545 740 ;
+C 81 ; WX 840 ; N Q ; B 32 -15 824 755 ;
+C 82 ; WX 580 ; N R ; B 64 0 565 740 ;
+C 83 ; WX 520 ; N S ; B 12 -15 493 755 ;
+C 84 ; WX 420 ; N T ; B 6 0 418 740 ;
+C 85 ; WX 640 ; N U ; B 55 -15 585 740 ;
+C 86 ; WX 700 ; N V ; B 8 0 695 740 ;
+C 87 ; WX 900 ; N W ; B 7 0 899 740 ;
+C 88 ; WX 680 ; N X ; B 4 0 676 740 ;
+C 89 ; WX 620 ; N Y ; B -2 0 622 740 ;
+C 90 ; WX 500 ; N Z ; B 19 0 481 740 ;
+C 91 ; WX 320 ; N bracketleft ; B 66 -157 284 754 ;
+C 92 ; WX 640 ; N backslash ; B 96 -100 544 740 ;
+C 93 ; WX 320 ; N bracketright ; B 36 -157 254 754 ;
+C 94 ; WX 600 ; N asciicircum ; B 73 375 527 740 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 280 ; N quoteleft ; B 72 466 205 740 ;
+C 97 ; WX 660 ; N a ; B 27 -18 613 574 ;
+C 98 ; WX 660 ; N b ; B 47 -18 632 740 ;
+C 99 ; WX 640 ; N c ; B 37 -18 610 574 ;
+C 100 ; WX 660 ; N d ; B 34 -18 618 740 ;
+C 101 ; WX 640 ; N e ; B 31 -18 610 577 ;
+C 102 ; WX 280 ; N f ; B 15 0 280 755 ; L i fi ; L l fl ;
+C 103 ; WX 660 ; N g ; B 32 -226 623 574 ;
+C 104 ; WX 600 ; N h ; B 54 0 546 740 ;
+C 105 ; WX 240 ; N i ; B 53 0 186 740 ;
+C 106 ; WX 260 ; N j ; B 16 -185 205 740 ;
+C 107 ; WX 580 ; N k ; B 80 0 571 740 ;
+C 108 ; WX 240 ; N l ; B 54 0 187 740 ;
+C 109 ; WX 940 ; N m ; B 54 0 887 574 ;
+C 110 ; WX 600 ; N n ; B 54 0 547 574 ;
+C 111 ; WX 640 ; N o ; B 25 -18 615 574 ;
+C 112 ; WX 660 ; N p ; B 47 -185 629 574 ;
+C 113 ; WX 660 ; N q ; B 31 -185 613 574 ;
+C 114 ; WX 320 ; N r ; B 63 0 317 574 ;
+C 115 ; WX 440 ; N s ; B 19 -18 421 574 ;
+C 116 ; WX 300 ; N t ; B 21 0 299 740 ;
+C 117 ; WX 600 ; N u ; B 50 -18 544 555 ;
+C 118 ; WX 560 ; N v ; B 3 0 556 555 ;
+C 119 ; WX 800 ; N w ; B 11 0 789 555 ;
+C 120 ; WX 560 ; N x ; B 3 0 556 555 ;
+C 121 ; WX 580 ; N y ; B 8 -185 571 555 ;
+C 122 ; WX 460 ; N z ; B 20 0 442 555 ;
+C 123 ; WX 340 ; N braceleft ; B -3 -191 317 747 ;
+C 124 ; WX 600 ; N bar ; B 233 -100 366 740 ;
+C 125 ; WX 340 ; N braceright ; B 23 -191 343 747 ;
+C 126 ; WX 600 ; N asciitilde ; B 67 160 533 347 ;
+C 161 ; WX 280 ; N exclamdown ; B 74 -185 207 555 ;
+C 162 ; WX 560 ; N cent ; B 43 39 517 715 ;
+C 163 ; WX 560 ; N sterling ; B -2 0 562 755 ;
+C 164 ; WX 160 ; N fraction ; B -123 0 282 740 ;
+C 165 ; WX 560 ; N yen ; B -10 0 570 740 ;
+C 166 ; WX 560 ; N florin ; B 0 -151 512 824 ;
+C 167 ; WX 560 ; N section ; B 28 -158 530 755 ;
+C 168 ; WX 560 ; N currency ; B 27 69 534 577 ;
+C 169 ; WX 220 ; N quotesingle ; B 44 444 177 740 ;
+C 170 ; WX 480 ; N quotedblleft ; B 70 466 410 740 ;
+C 171 ; WX 460 ; N guillemotleft ; B 61 108 400 469 ;
+C 172 ; WX 240 ; N guilsinglleft ; B 50 108 190 469 ;
+C 173 ; WX 240 ; N guilsinglright ; B 50 108 190 469 ;
+C 174 ; WX 520 ; N fi ; B 25 0 461 755 ;
+C 175 ; WX 520 ; N fl ; B 25 0 461 755 ;
+C 177 ; WX 500 ; N endash ; B 35 230 465 348 ;
+C 178 ; WX 560 ; N dagger ; B 51 -142 509 740 ;
+C 179 ; WX 560 ; N daggerdbl ; B 51 -142 509 740 ;
+C 180 ; WX 280 ; N periodcentered ; B 73 187 206 320 ;
+C 182 ; WX 600 ; N paragraph ; B -7 -103 607 740 ;
+C 183 ; WX 600 ; N bullet ; B 148 222 453 532 ;
+C 184 ; WX 280 ; N quotesinglbase ; B 72 -141 205 133 ;
+C 185 ; WX 480 ; N quotedblbase ; B 70 -141 410 133 ;
+C 186 ; WX 480 ; N quotedblright ; B 70 466 410 740 ;
+C 187 ; WX 460 ; N guillemotright ; B 61 108 400 469 ;
+C 188 ; WX 1000 ; N ellipsis ; B 100 0 899 133 ;
+C 189 ; WX 1280 ; N perthousand ; B 36 -15 1222 755 ;
+C 191 ; WX 560 ; N questiondown ; B 68 -200 521 555 ;
+C 193 ; WX 420 ; N grave ; B 50 624 329 851 ;
+C 194 ; WX 420 ; N acute ; B 91 624 370 849 ;
+C 195 ; WX 540 ; N circumflex ; B 71 636 470 774 ;
+C 196 ; WX 480 ; N tilde ; B 44 636 437 767 ;
+C 197 ; WX 420 ; N macron ; B 72 648 349 759 ;
+C 198 ; WX 480 ; N breve ; B 42 633 439 770 ;
+C 199 ; WX 280 ; N dotaccent ; B 74 636 207 769 ;
+C 200 ; WX 500 ; N dieresis ; B 78 636 422 769 ;
+C 202 ; WX 360 ; N ring ; B 73 619 288 834 ;
+C 203 ; WX 340 ; N cedilla ; B 98 -251 298 6 ;
+C 205 ; WX 700 ; N hungarumlaut ; B 132 610 609 862 ;
+C 206 ; WX 340 ; N ogonek ; B 79 -195 262 9 ;
+C 207 ; WX 540 ; N caron ; B 71 636 470 774 ;
+C 208 ; WX 1000 ; N emdash ; B 35 230 965 348 ;
+C 225 ; WX 900 ; N AE ; B -5 0 824 740 ;
+C 227 ; WX 360 ; N ordfeminine ; B 19 438 334 755 ;
+C 232 ; WX 480 ; N Lslash ; B 26 0 460 740 ;
+C 233 ; WX 840 ; N Oslash ; B 33 -71 807 814 ;
+C 234 ; WX 1060 ; N OE ; B 37 -15 1007 755 ;
+C 235 ; WX 360 ; N ordmasculine ; B 23 438 338 755 ;
+C 241 ; WX 1080 ; N ae ; B 29 -18 1048 574 ;
+C 245 ; WX 240 ; N dotlessi ; B 53 0 186 555 ;
+C 248 ; WX 320 ; N lslash ; B 34 0 305 740 ;
+C 249 ; WX 660 ; N oslash ; B 35 -50 625 608 ;
+C 250 ; WX 1080 ; N oe ; B 30 -18 1050 574 ;
+C 251 ; WX 600 ; N germandbls ; B 51 -18 585 755 ;
+C -1 ; WX 640 ; N ecircumflex ; B 31 -18 610 774 ;
+C -1 ; WX 640 ; N edieresis ; B 31 -18 610 769 ;
+C -1 ; WX 660 ; N aacute ; B 27 -18 613 849 ;
+C -1 ; WX 740 ; N registered ; B -12 -12 752 752 ;
+C -1 ; WX 240 ; N icircumflex ; B -79 0 320 774 ;
+C -1 ; WX 600 ; N udieresis ; B 50 -18 544 769 ;
+C -1 ; WX 640 ; N ograve ; B 25 -18 615 851 ;
+C -1 ; WX 600 ; N uacute ; B 50 -18 544 849 ;
+C -1 ; WX 600 ; N ucircumflex ; B 50 -18 544 774 ;
+C -1 ; WX 740 ; N Aacute ; B 7 0 732 1019 ;
+C -1 ; WX 240 ; N igrave ; B -65 0 214 851 ;
+C -1 ; WX 280 ; N Icircumflex ; B -59 0 340 944 ;
+C -1 ; WX 640 ; N ccedilla ; B 37 -251 610 574 ;
+C -1 ; WX 660 ; N adieresis ; B 27 -18 613 769 ;
+C -1 ; WX 520 ; N Ecircumflex ; B 61 0 460 944 ;
+C -1 ; WX 440 ; N scaron ; B 19 -18 421 774 ;
+C -1 ; WX 660 ; N thorn ; B 47 -185 629 740 ;
+C -1 ; WX 1000 ; N trademark ; B 9 296 821 740 ;
+C -1 ; WX 640 ; N egrave ; B 31 -18 610 851 ;
+C -1 ; WX 336 ; N threesuperior ; B 8 287 328 749 ;
+C -1 ; WX 460 ; N zcaron ; B 20 0 455 774 ;
+C -1 ; WX 660 ; N atilde ; B 27 -18 613 767 ;
+C -1 ; WX 660 ; N aring ; B 27 -18 613 834 ;
+C -1 ; WX 640 ; N ocircumflex ; B 25 -18 615 774 ;
+C -1 ; WX 520 ; N Edieresis ; B 61 0 459 939 ;
+C -1 ; WX 840 ; N threequarters ; B 18 0 803 749 ;
+C -1 ; WX 580 ; N ydieresis ; B 8 -185 571 769 ;
+C -1 ; WX 580 ; N yacute ; B 8 -185 571 849 ;
+C -1 ; WX 240 ; N iacute ; B 26 0 305 849 ;
+C -1 ; WX 740 ; N Acircumflex ; B 7 0 732 944 ;
+C -1 ; WX 640 ; N Uacute ; B 55 -15 585 1019 ;
+C -1 ; WX 640 ; N eacute ; B 31 -18 610 849 ;
+C -1 ; WX 840 ; N Ograve ; B 33 -15 807 1021 ;
+C -1 ; WX 660 ; N agrave ; B 27 -18 613 851 ;
+C -1 ; WX 640 ; N Udieresis ; B 55 -15 585 939 ;
+C -1 ; WX 660 ; N acircumflex ; B 27 -18 613 774 ;
+C -1 ; WX 280 ; N Igrave ; B -45 0 234 1021 ;
+C -1 ; WX 336 ; N twosuperior ; B 13 296 322 749 ;
+C -1 ; WX 640 ; N Ugrave ; B 55 -15 585 1021 ;
+C -1 ; WX 840 ; N onequarter ; B 92 0 746 740 ;
+C -1 ; WX 640 ; N Ucircumflex ; B 55 -15 585 944 ;
+C -1 ; WX 520 ; N Scaron ; B 12 -15 493 944 ;
+C -1 ; WX 280 ; N Idieresis ; B -32 0 312 939 ;
+C -1 ; WX 240 ; N idieresis ; B -52 0 292 769 ;
+C -1 ; WX 520 ; N Egrave ; B 61 0 459 1021 ;
+C -1 ; WX 840 ; N Oacute ; B 33 -15 807 1019 ;
+C -1 ; WX 600 ; N divide ; B 48 -20 552 526 ;
+C -1 ; WX 740 ; N Atilde ; B 7 0 732 937 ;
+C -1 ; WX 740 ; N Aring ; B 7 0 732 969 ;
+C -1 ; WX 840 ; N Odieresis ; B 33 -15 807 939 ;
+C -1 ; WX 740 ; N Adieresis ; B 7 0 732 939 ;
+C -1 ; WX 740 ; N Ntilde ; B 70 0 671 937 ;
+C -1 ; WX 500 ; N Zcaron ; B 19 0 481 944 ;
+C -1 ; WX 560 ; N Thorn ; B 72 0 545 740 ;
+C -1 ; WX 280 ; N Iacute ; B 46 0 325 1019 ;
+C -1 ; WX 600 ; N plusminus ; B 48 -62 552 556 ;
+C -1 ; WX 600 ; N multiply ; B 59 12 541 494 ;
+C -1 ; WX 520 ; N Eacute ; B 61 0 459 1019 ;
+C -1 ; WX 620 ; N Ydieresis ; B -2 0 622 939 ;
+C -1 ; WX 336 ; N onesuperior ; B 72 296 223 740 ;
+C -1 ; WX 600 ; N ugrave ; B 50 -18 544 851 ;
+C -1 ; WX 600 ; N logicalnot ; B 48 108 552 425 ;
+C -1 ; WX 600 ; N ntilde ; B 54 0 547 767 ;
+C -1 ; WX 840 ; N Otilde ; B 33 -15 807 937 ;
+C -1 ; WX 640 ; N otilde ; B 25 -18 615 767 ;
+C -1 ; WX 780 ; N Ccedilla ; B 34 -251 766 755 ;
+C -1 ; WX 740 ; N Agrave ; B 7 0 732 1021 ;
+C -1 ; WX 840 ; N onehalf ; B 62 0 771 740 ;
+C -1 ; WX 742 ; N Eth ; B 25 0 691 740 ;
+C -1 ; WX 400 ; N degree ; B 57 426 343 712 ;
+C -1 ; WX 620 ; N Yacute ; B -2 0 622 1019 ;
+C -1 ; WX 840 ; N Ocircumflex ; B 33 -15 807 944 ;
+C -1 ; WX 640 ; N oacute ; B 25 -18 615 849 ;
+C -1 ; WX 576 ; N mu ; B 38 -187 539 555 ;
+C -1 ; WX 600 ; N minus ; B 48 193 552 313 ;
+C -1 ; WX 640 ; N eth ; B 27 -18 616 754 ;
+C -1 ; WX 640 ; N odieresis ; B 25 -18 615 769 ;
+C -1 ; WX 740 ; N copyright ; B -12 -12 752 752 ;
+C -1 ; WX 600 ; N brokenbar ; B 233 -100 366 740 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 218
+
+KPX A y -50
+KPX A w -65
+KPX A v -70
+KPX A u -20
+KPX A quoteright -90
+KPX A Y -80
+KPX A W -60
+KPX A V -102
+KPX A U -40
+KPX A T -25
+KPX A Q -50
+KPX A O -50
+KPX A G -40
+KPX A C -40
+
+KPX B A -10
+
+KPX C A -40
+
+KPX D period -20
+KPX D comma -20
+KPX D Y -45
+KPX D W -25
+KPX D V -50
+KPX D A -50
+
+KPX F period -129
+KPX F e -20
+KPX F comma -162
+KPX F a -20
+KPX F A -75
+
+KPX G period -20
+KPX G comma -20
+KPX G Y -15
+
+KPX J period -15
+KPX J a -20
+KPX J A -30
+
+KPX K y -20
+KPX K u -15
+KPX K o -45
+KPX K e -40
+KPX K O -30
+
+KPX L y -23
+KPX L quoteright -30
+KPX L quotedblright -30
+KPX L Y -80
+KPX L W -55
+KPX L V -85
+KPX L T -46
+
+KPX O period -30
+KPX O comma -30
+KPX O Y -30
+KPX O X -30
+KPX O W -20
+KPX O V -45
+KPX O T -15
+KPX O A -60
+
+KPX P period -200
+KPX P o -20
+KPX P e -20
+KPX P comma -220
+KPX P a -20
+KPX P A -100
+
+KPX Q comma 20
+
+KPX R W 25
+KPX R V -10
+KPX R U 25
+KPX R T 40
+KPX R O 25
+
+KPX S comma 20
+
+KPX T y -10
+KPX T w -55
+KPX T u -46
+KPX T semicolon -29
+KPX T r -30
+KPX T period -91
+KPX T o -49
+KPX T hyphen -75
+KPX T e -49
+KPX T comma -82
+KPX T colon -15
+KPX T a -70
+KPX T O -15
+KPX T A -25
+
+KPX U period -20
+KPX U comma -20
+KPX U A -40
+
+KPX V u -55
+KPX V semicolon -33
+KPX V period -145
+KPX V o -101
+KPX V i -15
+KPX V hyphen -75
+KPX V e -101
+KPX V comma -145
+KPX V colon -18
+KPX V a -95
+KPX V O -45
+KPX V G -20
+KPX V A -102
+
+KPX W y -15
+KPX W u -30
+KPX W semicolon -33
+KPX W period -106
+KPX W o -46
+KPX W i -10
+KPX W hyphen -35
+KPX W e -47
+KPX W comma -106
+KPX W colon -15
+KPX W a -50
+KPX W O -20
+KPX W A -58
+
+KPX Y u -52
+KPX Y semicolon -23
+KPX Y period -145
+KPX Y o -89
+KPX Y hyphen -100
+KPX Y e -89
+KPX Y comma -145
+KPX Y colon -10
+KPX Y a -93
+KPX Y O -30
+KPX Y A -80
+
+KPX a t 5
+KPX a p 20
+KPX a b 5
+
+KPX b y -20
+KPX b v -20
+
+KPX c y -20
+KPX c l -15
+KPX c k -15
+
+KPX comma space -50
+KPX comma quoteright -70
+KPX comma quotedblright -70
+
+KPX e y -20
+KPX e x -20
+KPX e w -20
+KPX e v -20
+
+KPX f period -40
+KPX f o -20
+KPX f l -15
+KPX f i -15
+KPX f f -20
+KPX f dotlessi -15
+KPX f comma -40
+KPX f a -15
+
+KPX g i 25
+KPX g a 15
+
+KPX h y -30
+
+KPX k y -5
+KPX k o -30
+KPX k e -40
+
+KPX m y -20
+KPX m u -20
+
+KPX n y -15
+KPX n v -30
+
+KPX o y -20
+KPX o x -30
+KPX o w -20
+KPX o v -30
+
+KPX p y -20
+
+KPX period space -50
+KPX period quoteright -70
+KPX period quotedblright -70
+
+KPX quotedblleft A -50
+
+KPX quotedblright space -50
+
+KPX quoteleft quoteleft -80
+KPX quoteleft A -50
+
+KPX quoteright v -10
+KPX quoteright t 10
+KPX quoteright space -50
+KPX quoteright s -15
+KPX quoteright r -20
+KPX quoteright quoteright -80
+KPX quoteright d -50
+
+KPX r y 40
+KPX r v 40
+KPX r u 20
+KPX r t 20
+KPX r s 20
+KPX r q -8
+KPX r period -73
+KPX r p 20
+KPX r o -15
+KPX r n 21
+KPX r m 15
+KPX r l 20
+KPX r k 5
+KPX r i 20
+KPX r hyphen -60
+KPX r g 1
+KPX r e -4
+KPX r d -6
+KPX r comma -75
+KPX r c -7
+
+KPX s period 20
+KPX s comma 20
+
+KPX space quoteleft -50
+KPX space quotedblleft -50
+KPX space Y -60
+KPX space W -25
+KPX space V -80
+KPX space T -25
+KPX space A -20
+
+KPX v period -90
+KPX v o -20
+KPX v e -20
+KPX v comma -90
+KPX v a -30
+
+KPX w period -90
+KPX w o -30
+KPX w e -20
+KPX w comma -90
+KPX w a -30
+
+KPX x e -20
+
+KPX y period -100
+KPX y o -30
+KPX y e -20
+KPX y comma -100
+KPX y c -35
+KPX y a -30
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 170 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 100 170 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 120 170 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 160 170 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 190 135 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 130 170 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 50 170 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex -10 170 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 10 170 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 50 170 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -45 170 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -130 170 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -110 170 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -95 170 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 130 170 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 210 170 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 150 170 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 170 170 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 210 170 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 180 170 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron -10 170 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 145 170 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 50 170 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 70 170 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 75 170 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 135 170 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 60 170 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 5 170 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 120 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 60 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 80 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 120 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 150 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 90 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 110 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 50 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 70 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 110 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -65 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -150 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -130 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -115 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 60 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 110 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 50 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 70 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 110 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 80 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron -50 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 125 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 30 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 50 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 55 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 115 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 40 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron -15 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagdo8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagdo8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..c348b117779eec742c6fea344f7b097d0a64329e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagdo8a.afm
@@ -0,0 +1,576 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Mar 4 13:49:44 1991
+Comment UniqueID 34373
+Comment VMusage 6550 39938
+FontName AvantGarde-DemiOblique
+FullName ITC Avant Garde Gothic Demi Oblique
+FamilyName ITC Avant Garde Gothic
+Weight Demi
+ItalicAngle -10.5
+IsFixedPitch false
+FontBBox -123 -251 1256 1021
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.ITC Avant Garde Gothic is a registered trademark of International Typeface Corporation.
+EncodingScheme AdobeStandardEncoding
+CapHeight 740
+XHeight 555
+Ascender 740
+Descender -185
+StartCharMetrics 228
+C 32 ; WX 280 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 280 ; N exclam ; B 73 0 343 740 ;
+C 34 ; WX 360 ; N quotedbl ; B 127 444 478 740 ;
+C 35 ; WX 560 ; N numbersign ; B 66 0 618 700 ;
+C 36 ; WX 560 ; N dollar ; B 99 -86 582 857 ;
+C 37 ; WX 860 ; N percent ; B 139 -15 856 755 ;
+C 38 ; WX 680 ; N ampersand ; B 71 -15 742 755 ;
+C 39 ; WX 280 ; N quoteright ; B 159 466 342 740 ;
+C 40 ; WX 380 ; N parenleft ; B 120 -157 490 754 ;
+C 41 ; WX 380 ; N parenright ; B 8 -157 378 754 ;
+C 42 ; WX 440 ; N asterisk ; B 174 457 492 755 ;
+C 43 ; WX 600 ; N plus ; B 84 0 610 506 ;
+C 44 ; WX 280 ; N comma ; B 48 -141 231 133 ;
+C 45 ; WX 420 ; N hyphen ; B 114 230 413 348 ;
+C 46 ; WX 280 ; N period ; B 73 0 231 133 ;
+C 47 ; WX 460 ; N slash ; B -13 -100 591 740 ;
+C 48 ; WX 560 ; N zero ; B 70 -15 628 755 ;
+C 49 ; WX 560 ; N one ; B 230 0 500 740 ;
+C 50 ; WX 560 ; N two ; B 44 0 622 755 ;
+C 51 ; WX 560 ; N three ; B 67 -15 585 755 ;
+C 52 ; WX 560 ; N four ; B 36 0 604 740 ;
+C 53 ; WX 560 ; N five ; B 64 -15 600 740 ;
+C 54 ; WX 560 ; N six ; B 64 -15 587 739 ;
+C 55 ; WX 560 ; N seven ; B 83 0 635 740 ;
+C 56 ; WX 560 ; N eight ; B 71 -15 590 755 ;
+C 57 ; WX 560 ; N nine ; B 110 0 633 754 ;
+C 58 ; WX 280 ; N colon ; B 73 0 309 555 ;
+C 59 ; WX 280 ; N semicolon ; B 48 -141 309 555 ;
+C 60 ; WX 600 ; N less ; B 84 -8 649 514 ;
+C 61 ; WX 600 ; N equal ; B 63 81 631 425 ;
+C 62 ; WX 600 ; N greater ; B 45 -8 610 514 ;
+C 63 ; WX 560 ; N question ; B 135 0 593 755 ;
+C 64 ; WX 740 ; N at ; B 109 -12 832 712 ;
+C 65 ; WX 740 ; N A ; B 7 0 732 740 ;
+C 66 ; WX 580 ; N B ; B 70 0 610 740 ;
+C 67 ; WX 780 ; N C ; B 97 -15 864 755 ;
+C 68 ; WX 700 ; N D ; B 63 0 732 740 ;
+C 69 ; WX 520 ; N E ; B 61 0 596 740 ;
+C 70 ; WX 480 ; N F ; B 61 0 575 740 ;
+C 71 ; WX 840 ; N G ; B 89 -15 887 755 ;
+C 72 ; WX 680 ; N H ; B 71 0 747 740 ;
+C 73 ; WX 280 ; N I ; B 72 0 346 740 ;
+C 74 ; WX 480 ; N J ; B 34 -15 546 740 ;
+C 75 ; WX 620 ; N K ; B 89 0 757 740 ;
+C 76 ; WX 440 ; N L ; B 72 0 459 740 ;
+C 77 ; WX 900 ; N M ; B 63 0 974 740 ;
+C 78 ; WX 740 ; N N ; B 70 0 808 740 ;
+C 79 ; WX 840 ; N O ; B 95 -15 882 755 ;
+C 80 ; WX 560 ; N P ; B 72 0 645 740 ;
+C 81 ; WX 840 ; N Q ; B 94 -15 882 755 ;
+C 82 ; WX 580 ; N R ; B 64 0 656 740 ;
+C 83 ; WX 520 ; N S ; B 49 -15 578 755 ;
+C 84 ; WX 420 ; N T ; B 119 0 555 740 ;
+C 85 ; WX 640 ; N U ; B 97 -15 722 740 ;
+C 86 ; WX 700 ; N V ; B 145 0 832 740 ;
+C 87 ; WX 900 ; N W ; B 144 0 1036 740 ;
+C 88 ; WX 680 ; N X ; B 4 0 813 740 ;
+C 89 ; WX 620 ; N Y ; B 135 0 759 740 ;
+C 90 ; WX 500 ; N Z ; B 19 0 599 740 ;
+C 91 ; WX 320 ; N bracketleft ; B 89 -157 424 754 ;
+C 92 ; WX 640 ; N backslash ; B 233 -100 525 740 ;
+C 93 ; WX 320 ; N bracketright ; B 7 -157 342 754 ;
+C 94 ; WX 600 ; N asciicircum ; B 142 375 596 740 ;
+C 95 ; WX 500 ; N underscore ; B -23 -125 486 -75 ;
+C 96 ; WX 280 ; N quoteleft ; B 158 466 341 740 ;
+C 97 ; WX 660 ; N a ; B 73 -18 716 574 ;
+C 98 ; WX 660 ; N b ; B 47 -18 689 740 ;
+C 99 ; WX 640 ; N c ; B 84 -18 679 574 ;
+C 100 ; WX 660 ; N d ; B 80 -18 755 740 ;
+C 101 ; WX 640 ; N e ; B 77 -18 667 577 ;
+C 102 ; WX 280 ; N f ; B 62 0 420 755 ; L i fi ; L l fl ;
+C 103 ; WX 660 ; N g ; B 33 -226 726 574 ;
+C 104 ; WX 600 ; N h ; B 54 0 614 740 ;
+C 105 ; WX 240 ; N i ; B 53 0 323 740 ;
+C 106 ; WX 260 ; N j ; B -18 -185 342 740 ;
+C 107 ; WX 580 ; N k ; B 80 0 648 740 ;
+C 108 ; WX 240 ; N l ; B 54 0 324 740 ;
+C 109 ; WX 940 ; N m ; B 54 0 954 574 ;
+C 110 ; WX 600 ; N n ; B 54 0 613 574 ;
+C 111 ; WX 640 ; N o ; B 71 -18 672 574 ;
+C 112 ; WX 660 ; N p ; B 13 -185 686 574 ;
+C 113 ; WX 660 ; N q ; B 78 -185 716 574 ;
+C 114 ; WX 320 ; N r ; B 63 0 423 574 ;
+C 115 ; WX 440 ; N s ; B 49 -18 483 574 ;
+C 116 ; WX 300 ; N t ; B 86 0 402 740 ;
+C 117 ; WX 600 ; N u ; B 87 -18 647 555 ;
+C 118 ; WX 560 ; N v ; B 106 0 659 555 ;
+C 119 ; WX 800 ; N w ; B 114 0 892 555 ;
+C 120 ; WX 560 ; N x ; B 3 0 632 555 ;
+C 121 ; WX 580 ; N y ; B 75 -185 674 555 ;
+C 122 ; WX 460 ; N z ; B 20 0 528 555 ;
+C 123 ; WX 340 ; N braceleft ; B 40 -191 455 747 ;
+C 124 ; WX 600 ; N bar ; B 214 -100 503 740 ;
+C 125 ; WX 340 ; N braceright ; B -12 -191 405 747 ;
+C 126 ; WX 600 ; N asciitilde ; B 114 160 579 347 ;
+C 161 ; WX 280 ; N exclamdown ; B 40 -185 310 555 ;
+C 162 ; WX 560 ; N cent ; B 110 39 599 715 ;
+C 163 ; WX 560 ; N sterling ; B 38 0 615 755 ;
+C 164 ; WX 160 ; N fraction ; B -123 0 419 740 ;
+C 165 ; WX 560 ; N yen ; B 83 0 707 740 ;
+C 166 ; WX 560 ; N florin ; B -27 -151 664 824 ;
+C 167 ; WX 560 ; N section ; B 65 -158 602 755 ;
+C 168 ; WX 560 ; N currency ; B 53 69 628 577 ;
+C 169 ; WX 220 ; N quotesingle ; B 152 444 314 740 ;
+C 170 ; WX 480 ; N quotedblleft ; B 156 466 546 740 ;
+C 171 ; WX 460 ; N guillemotleft ; B 105 108 487 469 ;
+C 172 ; WX 240 ; N guilsinglleft ; B 94 108 277 469 ;
+C 173 ; WX 240 ; N guilsinglright ; B 70 108 253 469 ;
+C 174 ; WX 520 ; N fi ; B 72 0 598 755 ;
+C 175 ; WX 520 ; N fl ; B 72 0 598 755 ;
+C 177 ; WX 500 ; N endash ; B 78 230 529 348 ;
+C 178 ; WX 560 ; N dagger ; B 133 -142 612 740 ;
+C 179 ; WX 560 ; N daggerdbl ; B 63 -142 618 740 ;
+C 180 ; WX 280 ; N periodcentered ; B 108 187 265 320 ;
+C 182 ; WX 600 ; N paragraph ; B 90 -103 744 740 ;
+C 183 ; WX 600 ; N bullet ; B 215 222 526 532 ;
+C 184 ; WX 280 ; N quotesinglbase ; B 47 -141 230 133 ;
+C 185 ; WX 480 ; N quotedblbase ; B 45 -141 435 133 ;
+C 186 ; WX 480 ; N quotedblright ; B 157 466 547 740 ;
+C 187 ; WX 460 ; N guillemotright ; B 81 108 463 469 ;
+C 188 ; WX 1000 ; N ellipsis ; B 100 0 924 133 ;
+C 189 ; WX 1280 ; N perthousand ; B 139 -15 1256 755 ;
+C 191 ; WX 560 ; N questiondown ; B 69 -200 527 555 ;
+C 193 ; WX 420 ; N grave ; B 189 624 462 851 ;
+C 194 ; WX 420 ; N acute ; B 224 624 508 849 ;
+C 195 ; WX 540 ; N circumflex ; B 189 636 588 774 ;
+C 196 ; WX 480 ; N tilde ; B 178 636 564 767 ;
+C 197 ; WX 420 ; N macron ; B 192 648 490 759 ;
+C 198 ; WX 480 ; N breve ; B 185 633 582 770 ;
+C 199 ; WX 280 ; N dotaccent ; B 192 636 350 769 ;
+C 200 ; WX 500 ; N dieresis ; B 196 636 565 769 ;
+C 202 ; WX 360 ; N ring ; B 206 619 424 834 ;
+C 203 ; WX 340 ; N cedilla ; B 67 -251 272 6 ;
+C 205 ; WX 700 ; N hungarumlaut ; B 258 610 754 862 ;
+C 206 ; WX 340 ; N ogonek ; B 59 -195 243 9 ;
+C 207 ; WX 540 ; N caron ; B 214 636 613 774 ;
+C 208 ; WX 1000 ; N emdash ; B 78 230 1029 348 ;
+C 225 ; WX 900 ; N AE ; B -5 0 961 740 ;
+C 227 ; WX 360 ; N ordfeminine ; B 127 438 472 755 ;
+C 232 ; WX 480 ; N Lslash ; B 68 0 484 740 ;
+C 233 ; WX 840 ; N Oslash ; B 94 -71 891 814 ;
+C 234 ; WX 1060 ; N OE ; B 98 -15 1144 755 ;
+C 235 ; WX 360 ; N ordmasculine ; B 131 438 451 755 ;
+C 241 ; WX 1080 ; N ae ; B 75 -18 1105 574 ;
+C 245 ; WX 240 ; N dotlessi ; B 53 0 289 555 ;
+C 248 ; WX 320 ; N lslash ; B 74 0 404 740 ;
+C 249 ; WX 660 ; N oslash ; B 81 -50 685 608 ;
+C 250 ; WX 1080 ; N oe ; B 76 -18 1108 574 ;
+C 251 ; WX 600 ; N germandbls ; B 51 -18 629 755 ;
+C -1 ; WX 640 ; N ecircumflex ; B 77 -18 667 774 ;
+C -1 ; WX 640 ; N edieresis ; B 77 -18 667 769 ;
+C -1 ; WX 660 ; N aacute ; B 73 -18 716 849 ;
+C -1 ; WX 740 ; N registered ; B 50 -12 827 752 ;
+C -1 ; WX 240 ; N icircumflex ; B 39 0 438 774 ;
+C -1 ; WX 600 ; N udieresis ; B 87 -18 647 769 ;
+C -1 ; WX 640 ; N ograve ; B 71 -18 672 851 ;
+C -1 ; WX 600 ; N uacute ; B 87 -18 647 849 ;
+C -1 ; WX 600 ; N ucircumflex ; B 87 -18 647 774 ;
+C -1 ; WX 740 ; N Aacute ; B 7 0 732 1019 ;
+C -1 ; WX 240 ; N igrave ; B 53 0 347 851 ;
+C -1 ; WX 280 ; N Icircumflex ; B 72 0 489 944 ;
+C -1 ; WX 640 ; N ccedilla ; B 83 -251 679 574 ;
+C -1 ; WX 660 ; N adieresis ; B 73 -18 716 769 ;
+C -1 ; WX 520 ; N Ecircumflex ; B 61 0 609 944 ;
+C -1 ; WX 440 ; N scaron ; B 49 -18 563 774 ;
+C -1 ; WX 660 ; N thorn ; B 13 -185 686 740 ;
+C -1 ; WX 1000 ; N trademark ; B 131 296 958 740 ;
+C -1 ; WX 640 ; N egrave ; B 77 -18 667 851 ;
+C -1 ; WX 336 ; N threesuperior ; B 87 287 413 749 ;
+C -1 ; WX 460 ; N zcaron ; B 20 0 598 774 ;
+C -1 ; WX 660 ; N atilde ; B 73 -18 716 767 ;
+C -1 ; WX 660 ; N aring ; B 73 -18 716 834 ;
+C -1 ; WX 640 ; N ocircumflex ; B 71 -18 672 774 ;
+C -1 ; WX 520 ; N Edieresis ; B 61 0 606 939 ;
+C -1 ; WX 840 ; N threequarters ; B 97 0 836 749 ;
+C -1 ; WX 580 ; N ydieresis ; B 75 -185 674 769 ;
+C -1 ; WX 580 ; N yacute ; B 75 -185 674 849 ;
+C -1 ; WX 240 ; N iacute ; B 53 0 443 849 ;
+C -1 ; WX 740 ; N Acircumflex ; B 7 0 732 944 ;
+C -1 ; WX 640 ; N Uacute ; B 97 -15 722 1019 ;
+C -1 ; WX 640 ; N eacute ; B 77 -18 667 849 ;
+C -1 ; WX 840 ; N Ograve ; B 95 -15 882 1021 ;
+C -1 ; WX 660 ; N agrave ; B 73 -18 716 851 ;
+C -1 ; WX 640 ; N Udieresis ; B 97 -15 722 939 ;
+C -1 ; WX 660 ; N acircumflex ; B 73 -18 716 774 ;
+C -1 ; WX 280 ; N Igrave ; B 72 0 398 1021 ;
+C -1 ; WX 336 ; N twosuperior ; B 73 296 436 749 ;
+C -1 ; WX 640 ; N Ugrave ; B 97 -15 722 1021 ;
+C -1 ; WX 840 ; N onequarter ; B 187 0 779 740 ;
+C -1 ; WX 640 ; N Ucircumflex ; B 97 -15 722 944 ;
+C -1 ; WX 520 ; N Scaron ; B 49 -15 635 944 ;
+C -1 ; WX 280 ; N Idieresis ; B 72 0 486 939 ;
+C -1 ; WX 240 ; N idieresis ; B 53 0 435 769 ;
+C -1 ; WX 520 ; N Egrave ; B 61 0 596 1021 ;
+C -1 ; WX 840 ; N Oacute ; B 95 -15 882 1019 ;
+C -1 ; WX 600 ; N divide ; B 84 -20 610 526 ;
+C -1 ; WX 740 ; N Atilde ; B 7 0 732 937 ;
+C -1 ; WX 740 ; N Aring ; B 7 0 732 969 ;
+C -1 ; WX 840 ; N Odieresis ; B 95 -15 882 939 ;
+C -1 ; WX 740 ; N Adieresis ; B 7 0 732 939 ;
+C -1 ; WX 740 ; N Ntilde ; B 70 0 808 937 ;
+C -1 ; WX 500 ; N Zcaron ; B 19 0 650 944 ;
+C -1 ; WX 560 ; N Thorn ; B 72 0 619 740 ;
+C -1 ; WX 280 ; N Iacute ; B 72 0 494 1019 ;
+C -1 ; WX 600 ; N plusminus ; B 37 -62 626 556 ;
+C -1 ; WX 600 ; N multiply ; B 76 12 617 494 ;
+C -1 ; WX 520 ; N Eacute ; B 61 0 596 1019 ;
+C -1 ; WX 620 ; N Ydieresis ; B 135 0 759 939 ;
+C -1 ; WX 336 ; N onesuperior ; B 182 296 360 740 ;
+C -1 ; WX 600 ; N ugrave ; B 87 -18 647 851 ;
+C -1 ; WX 600 ; N logicalnot ; B 105 108 631 425 ;
+C -1 ; WX 600 ; N ntilde ; B 54 0 624 767 ;
+C -1 ; WX 840 ; N Otilde ; B 95 -15 882 937 ;
+C -1 ; WX 640 ; N otilde ; B 71 -18 672 767 ;
+C -1 ; WX 780 ; N Ccedilla ; B 97 -251 864 755 ;
+C -1 ; WX 740 ; N Agrave ; B 7 0 732 1021 ;
+C -1 ; WX 840 ; N onehalf ; B 157 0 830 740 ;
+C -1 ; WX 742 ; N Eth ; B 83 0 766 740 ;
+C -1 ; WX 400 ; N degree ; B 160 426 451 712 ;
+C -1 ; WX 620 ; N Yacute ; B 135 0 759 1019 ;
+C -1 ; WX 840 ; N Ocircumflex ; B 95 -15 882 944 ;
+C -1 ; WX 640 ; N oacute ; B 71 -18 672 849 ;
+C -1 ; WX 576 ; N mu ; B 3 -187 642 555 ;
+C -1 ; WX 600 ; N minus ; B 84 193 610 313 ;
+C -1 ; WX 640 ; N eth ; B 73 -18 699 754 ;
+C -1 ; WX 640 ; N odieresis ; B 71 -18 672 769 ;
+C -1 ; WX 740 ; N copyright ; B 50 -12 827 752 ;
+C -1 ; WX 600 ; N brokenbar ; B 214 -100 503 740 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 218
+
+KPX A y -50
+KPX A w -65
+KPX A v -70
+KPX A u -20
+KPX A quoteright -90
+KPX A Y -80
+KPX A W -60
+KPX A V -102
+KPX A U -40
+KPX A T -25
+KPX A Q -50
+KPX A O -50
+KPX A G -40
+KPX A C -40
+
+KPX B A -10
+
+KPX C A -40
+
+KPX D period -20
+KPX D comma -20
+KPX D Y -45
+KPX D W -25
+KPX D V -50
+KPX D A -50
+
+KPX F period -129
+KPX F e -20
+KPX F comma -162
+KPX F a -20
+KPX F A -75
+
+KPX G period -20
+KPX G comma -20
+KPX G Y -15
+
+KPX J period -15
+KPX J a -20
+KPX J A -30
+
+KPX K y -20
+KPX K u -15
+KPX K o -45
+KPX K e -40
+KPX K O -30
+
+KPX L y -23
+KPX L quoteright -30
+KPX L quotedblright -30
+KPX L Y -80
+KPX L W -55
+KPX L V -85
+KPX L T -46
+
+KPX O period -30
+KPX O comma -30
+KPX O Y -30
+KPX O X -30
+KPX O W -20
+KPX O V -45
+KPX O T -15
+KPX O A -60
+
+KPX P period -200
+KPX P o -20
+KPX P e -20
+KPX P comma -220
+KPX P a -20
+KPX P A -100
+
+KPX Q comma 20
+
+KPX R W 25
+KPX R V -10
+KPX R U 25
+KPX R T 40
+KPX R O 25
+
+KPX S comma 20
+
+KPX T y -10
+KPX T w -55
+KPX T u -46
+KPX T semicolon -29
+KPX T r -30
+KPX T period -91
+KPX T o -49
+KPX T hyphen -75
+KPX T e -49
+KPX T comma -82
+KPX T colon -15
+KPX T a -70
+KPX T O -15
+KPX T A -25
+
+KPX U period -20
+KPX U comma -20
+KPX U A -40
+
+KPX V u -55
+KPX V semicolon -33
+KPX V period -145
+KPX V o -101
+KPX V i -15
+KPX V hyphen -75
+KPX V e -101
+KPX V comma -145
+KPX V colon -18
+KPX V a -95
+KPX V O -45
+KPX V G -20
+KPX V A -102
+
+KPX W y -15
+KPX W u -30
+KPX W semicolon -33
+KPX W period -106
+KPX W o -46
+KPX W i -10
+KPX W hyphen -35
+KPX W e -47
+KPX W comma -106
+KPX W colon -15
+KPX W a -50
+KPX W O -20
+KPX W A -58
+
+KPX Y u -52
+KPX Y semicolon -23
+KPX Y period -145
+KPX Y o -89
+KPX Y hyphen -100
+KPX Y e -89
+KPX Y comma -145
+KPX Y colon -10
+KPX Y a -93
+KPX Y O -30
+KPX Y A -80
+
+KPX a t 5
+KPX a p 20
+KPX a b 5
+
+KPX b y -20
+KPX b v -20
+
+KPX c y -20
+KPX c l -15
+KPX c k -15
+
+KPX comma space -50
+KPX comma quoteright -70
+KPX comma quotedblright -70
+
+KPX e y -20
+KPX e x -20
+KPX e w -20
+KPX e v -20
+
+KPX f period -40
+KPX f o -20
+KPX f l -15
+KPX f i -15
+KPX f f -20
+KPX f dotlessi -15
+KPX f comma -40
+KPX f a -15
+
+KPX g i 25
+KPX g a 15
+
+KPX h y -30
+
+KPX k y -5
+KPX k o -30
+KPX k e -40
+
+KPX m y -20
+KPX m u -20
+
+KPX n y -15
+KPX n v -30
+
+KPX o y -20
+KPX o x -30
+KPX o w -20
+KPX o v -30
+
+KPX p y -20
+
+KPX period space -50
+KPX period quoteright -70
+KPX period quotedblright -70
+
+KPX quotedblleft A -50
+
+KPX quotedblright space -50
+
+KPX quoteleft quoteleft -80
+KPX quoteleft A -50
+
+KPX quoteright v -10
+KPX quoteright t 10
+KPX quoteright space -50
+KPX quoteright s -15
+KPX quoteright r -20
+KPX quoteright quoteright -80
+KPX quoteright d -50
+
+KPX r y 40
+KPX r v 40
+KPX r u 20
+KPX r t 20
+KPX r s 20
+KPX r q -8
+KPX r period -73
+KPX r p 20
+KPX r o -15
+KPX r n 21
+KPX r m 15
+KPX r l 20
+KPX r k 5
+KPX r i 20
+KPX r hyphen -60
+KPX r g 1
+KPX r e -4
+KPX r d -6
+KPX r comma -75
+KPX r c -7
+
+KPX s period 20
+KPX s comma 20
+
+KPX space quoteleft -50
+KPX space quotedblleft -50
+KPX space Y -60
+KPX space W -25
+KPX space V -80
+KPX space T -25
+KPX space A -20
+
+KPX v period -90
+KPX v o -20
+KPX v e -20
+KPX v comma -90
+KPX v a -30
+
+KPX w period -90
+KPX w o -30
+KPX w e -20
+KPX w comma -90
+KPX w a -30
+
+KPX x e -20
+
+KPX y period -100
+KPX y o -30
+KPX y e -20
+KPX y comma -100
+KPX y c -35
+KPX y a -30
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 192 170 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 132 170 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 152 170 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 192 170 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 215 135 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 162 170 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 82 170 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 22 170 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 42 170 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 82 170 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -13 170 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -98 170 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -78 170 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -63 170 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 162 170 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 242 170 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 182 170 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 202 170 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 242 170 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 212 170 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 22 170 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 177 170 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 82 170 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 102 170 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 107 170 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 167 170 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 92 170 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 37 170 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 120 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 60 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 80 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 120 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 150 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 90 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 110 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 50 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 70 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 110 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -65 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -150 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -130 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -115 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 60 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 110 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 50 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 70 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 110 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 80 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron -50 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 125 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 30 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 50 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 55 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 115 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 40 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron -15 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagk8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagk8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..53b03bbb88f46f55090cd5fdd37da4ceb98be3ce
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagk8a.afm
@@ -0,0 +1,573 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Mar 4 13:37:31 1991
+Comment UniqueID 34364
+Comment VMusage 24225 31117
+FontName AvantGarde-Book
+FullName ITC Avant Garde Gothic Book
+FamilyName ITC Avant Garde Gothic
+Weight Book
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -113 -222 1148 955
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.006
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.ITC Avant Garde Gothic is a registered trademark of International Typeface Corporation.
+EncodingScheme AdobeStandardEncoding
+CapHeight 740
+XHeight 547
+Ascender 740
+Descender -192
+StartCharMetrics 228
+C 32 ; WX 277 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 295 ; N exclam ; B 111 0 185 740 ;
+C 34 ; WX 309 ; N quotedbl ; B 36 444 273 740 ;
+C 35 ; WX 554 ; N numbersign ; B 33 0 521 740 ;
+C 36 ; WX 554 ; N dollar ; B 70 -70 485 811 ;
+C 37 ; WX 775 ; N percent ; B 21 -13 753 751 ;
+C 38 ; WX 757 ; N ampersand ; B 56 -12 736 753 ;
+C 39 ; WX 351 ; N quoteright ; B 94 546 256 740 ;
+C 40 ; WX 369 ; N parenleft ; B 47 -205 355 757 ;
+C 41 ; WX 369 ; N parenright ; B 14 -205 322 757 ;
+C 42 ; WX 425 ; N asterisk ; B 58 446 367 740 ;
+C 43 ; WX 606 ; N plus ; B 51 0 555 506 ;
+C 44 ; WX 277 ; N comma ; B 14 -67 176 126 ;
+C 45 ; WX 332 ; N hyphen ; B 30 248 302 315 ;
+C 46 ; WX 277 ; N period ; B 102 0 176 126 ;
+C 47 ; WX 437 ; N slash ; B 44 -100 403 740 ;
+C 48 ; WX 554 ; N zero ; B 29 -13 525 753 ;
+C 49 ; WX 554 ; N one ; B 135 0 336 740 ;
+C 50 ; WX 554 ; N two ; B 40 0 514 753 ;
+C 51 ; WX 554 ; N three ; B 34 -13 506 753 ;
+C 52 ; WX 554 ; N four ; B 14 0 528 740 ;
+C 53 ; WX 554 ; N five ; B 26 -13 530 740 ;
+C 54 ; WX 554 ; N six ; B 24 -13 530 739 ;
+C 55 ; WX 554 ; N seven ; B 63 0 491 740 ;
+C 56 ; WX 554 ; N eight ; B 41 -13 513 753 ;
+C 57 ; WX 554 ; N nine ; B 24 0 530 752 ;
+C 58 ; WX 277 ; N colon ; B 102 0 176 548 ;
+C 59 ; WX 277 ; N semicolon ; B 14 -67 176 548 ;
+C 60 ; WX 606 ; N less ; B 46 -8 554 514 ;
+C 61 ; WX 606 ; N equal ; B 51 118 555 388 ;
+C 62 ; WX 606 ; N greater ; B 52 -8 560 514 ;
+C 63 ; WX 591 ; N question ; B 64 0 526 752 ;
+C 64 ; WX 867 ; N at ; B 65 -13 803 753 ;
+C 65 ; WX 740 ; N A ; B 12 0 729 740 ;
+C 66 ; WX 574 ; N B ; B 74 0 544 740 ;
+C 67 ; WX 813 ; N C ; B 43 -13 771 752 ;
+C 68 ; WX 744 ; N D ; B 74 0 699 740 ;
+C 69 ; WX 536 ; N E ; B 70 0 475 740 ;
+C 70 ; WX 485 ; N F ; B 70 0 444 740 ;
+C 71 ; WX 872 ; N G ; B 40 -13 828 753 ;
+C 72 ; WX 683 ; N H ; B 76 0 607 740 ;
+C 73 ; WX 226 ; N I ; B 76 0 150 740 ;
+C 74 ; WX 482 ; N J ; B 6 -13 402 740 ;
+C 75 ; WX 591 ; N K ; B 81 0 591 740 ;
+C 76 ; WX 462 ; N L ; B 82 0 462 740 ;
+C 77 ; WX 919 ; N M ; B 76 0 843 740 ;
+C 78 ; WX 740 ; N N ; B 75 0 664 740 ;
+C 79 ; WX 869 ; N O ; B 43 -13 826 753 ;
+C 80 ; WX 592 ; N P ; B 75 0 564 740 ;
+C 81 ; WX 871 ; N Q ; B 40 -13 837 753 ;
+C 82 ; WX 607 ; N R ; B 70 0 572 740 ;
+C 83 ; WX 498 ; N S ; B 22 -13 473 753 ;
+C 84 ; WX 426 ; N T ; B 6 0 419 740 ;
+C 85 ; WX 655 ; N U ; B 75 -13 579 740 ;
+C 86 ; WX 702 ; N V ; B 8 0 693 740 ;
+C 87 ; WX 960 ; N W ; B 11 0 950 740 ;
+C 88 ; WX 609 ; N X ; B 8 0 602 740 ;
+C 89 ; WX 592 ; N Y ; B 1 0 592 740 ;
+C 90 ; WX 480 ; N Z ; B 12 0 470 740 ;
+C 91 ; WX 351 ; N bracketleft ; B 133 -179 337 753 ;
+C 92 ; WX 605 ; N backslash ; B 118 -100 477 740 ;
+C 93 ; WX 351 ; N bracketright ; B 14 -179 218 753 ;
+C 94 ; WX 606 ; N asciicircum ; B 53 307 553 740 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 351 ; N quoteleft ; B 95 546 257 740 ;
+C 97 ; WX 683 ; N a ; B 42 -13 621 561 ;
+C 98 ; WX 682 ; N b ; B 68 -13 647 740 ;
+C 99 ; WX 647 ; N c ; B 41 -13 607 561 ;
+C 100 ; WX 685 ; N d ; B 39 -13 618 740 ;
+C 101 ; WX 650 ; N e ; B 38 -13 608 561 ;
+C 102 ; WX 314 ; N f ; B 19 0 314 753 ; L i fi ; L l fl ;
+C 103 ; WX 673 ; N g ; B 37 -215 606 561 ;
+C 104 ; WX 610 ; N h ; B 62 0 543 740 ;
+C 105 ; WX 200 ; N i ; B 65 0 135 740 ;
+C 106 ; WX 203 ; N j ; B -44 -192 137 740 ;
+C 107 ; WX 502 ; N k ; B 70 0 498 740 ;
+C 108 ; WX 200 ; N l ; B 65 0 135 740 ;
+C 109 ; WX 938 ; N m ; B 66 0 872 561 ;
+C 110 ; WX 610 ; N n ; B 65 0 546 561 ;
+C 111 ; WX 655 ; N o ; B 42 -13 614 561 ;
+C 112 ; WX 682 ; N p ; B 64 -192 643 561 ;
+C 113 ; WX 682 ; N q ; B 37 -192 616 561 ;
+C 114 ; WX 301 ; N r ; B 65 0 291 561 ;
+C 115 ; WX 388 ; N s ; B 24 -13 364 561 ;
+C 116 ; WX 339 ; N t ; B 14 0 330 740 ;
+C 117 ; WX 608 ; N u ; B 62 -13 541 547 ;
+C 118 ; WX 554 ; N v ; B 7 0 546 547 ;
+C 119 ; WX 831 ; N w ; B 13 0 820 547 ;
+C 120 ; WX 480 ; N x ; B 12 0 468 547 ;
+C 121 ; WX 536 ; N y ; B 15 -192 523 547 ;
+C 122 ; WX 425 ; N z ; B 10 0 415 547 ;
+C 123 ; WX 351 ; N braceleft ; B 70 -189 331 740 ;
+C 124 ; WX 672 ; N bar ; B 299 -100 373 740 ;
+C 125 ; WX 351 ; N braceright ; B 20 -189 281 740 ;
+C 126 ; WX 606 ; N asciitilde ; B 72 179 534 319 ;
+C 161 ; WX 295 ; N exclamdown ; B 110 -192 184 548 ;
+C 162 ; WX 554 ; N cent ; B 48 62 510 707 ;
+C 163 ; WX 554 ; N sterling ; B 4 0 552 753 ;
+C 164 ; WX 166 ; N fraction ; B -113 0 280 740 ;
+C 165 ; WX 554 ; N yen ; B 4 0 550 740 ;
+C 166 ; WX 554 ; N florin ; B -12 -153 518 818 ;
+C 167 ; WX 615 ; N section ; B 85 -141 529 753 ;
+C 168 ; WX 554 ; N currency ; B 8 42 546 580 ;
+C 169 ; WX 198 ; N quotesingle ; B 59 444 140 740 ;
+C 170 ; WX 502 ; N quotedblleft ; B 97 546 406 740 ;
+C 171 ; WX 425 ; N guillemotleft ; B 40 81 386 481 ;
+C 172 ; WX 251 ; N guilsinglleft ; B 40 81 212 481 ;
+C 173 ; WX 251 ; N guilsinglright ; B 39 81 211 481 ;
+C 174 ; WX 487 ; N fi ; B 19 0 422 753 ;
+C 175 ; WX 485 ; N fl ; B 19 0 420 753 ;
+C 177 ; WX 500 ; N endash ; B 35 248 465 315 ;
+C 178 ; WX 553 ; N dagger ; B 59 -133 493 740 ;
+C 179 ; WX 553 ; N daggerdbl ; B 59 -133 493 740 ;
+C 180 ; WX 277 ; N periodcentered ; B 102 190 176 316 ;
+C 182 ; WX 564 ; N paragraph ; B 22 -110 551 740 ;
+C 183 ; WX 606 ; N bullet ; B 150 222 455 532 ;
+C 184 ; WX 354 ; N quotesinglbase ; B 89 -68 251 126 ;
+C 185 ; WX 502 ; N quotedblbase ; B 89 -68 399 126 ;
+C 186 ; WX 484 ; N quotedblright ; B 96 546 405 740 ;
+C 187 ; WX 425 ; N guillemotright ; B 39 81 385 481 ;
+C 188 ; WX 1000 ; N ellipsis ; B 130 0 870 126 ;
+C 189 ; WX 1174 ; N perthousand ; B 25 -13 1148 751 ;
+C 191 ; WX 591 ; N questiondown ; B 65 -205 527 548 ;
+C 193 ; WX 378 ; N grave ; B 69 619 300 786 ;
+C 194 ; WX 375 ; N acute ; B 78 619 309 786 ;
+C 195 ; WX 502 ; N circumflex ; B 74 639 428 764 ;
+C 196 ; WX 439 ; N tilde ; B 47 651 392 754 ;
+C 197 ; WX 485 ; N macron ; B 73 669 411 736 ;
+C 198 ; WX 453 ; N breve ; B 52 651 401 754 ;
+C 199 ; WX 222 ; N dotaccent ; B 74 639 148 765 ;
+C 200 ; WX 369 ; N dieresis ; B 73 639 295 765 ;
+C 202 ; WX 332 ; N ring ; B 62 600 269 807 ;
+C 203 ; WX 324 ; N cedilla ; B 80 -222 254 0 ;
+C 205 ; WX 552 ; N hungarumlaut ; B 119 605 453 800 ;
+C 206 ; WX 302 ; N ogonek ; B 73 -191 228 0 ;
+C 207 ; WX 502 ; N caron ; B 68 639 423 764 ;
+C 208 ; WX 1000 ; N emdash ; B 35 248 965 315 ;
+C 225 ; WX 992 ; N AE ; B -20 0 907 740 ;
+C 227 ; WX 369 ; N ordfeminine ; B -3 407 356 753 ;
+C 232 ; WX 517 ; N Lslash ; B 59 0 517 740 ;
+C 233 ; WX 868 ; N Oslash ; B 43 -83 826 819 ;
+C 234 ; WX 1194 ; N OE ; B 45 -13 1142 753 ;
+C 235 ; WX 369 ; N ordmasculine ; B 12 407 356 753 ;
+C 241 ; WX 1157 ; N ae ; B 34 -13 1113 561 ;
+C 245 ; WX 200 ; N dotlessi ; B 65 0 135 547 ;
+C 248 ; WX 300 ; N lslash ; B 43 0 259 740 ;
+C 249 ; WX 653 ; N oslash ; B 41 -64 613 614 ;
+C 250 ; WX 1137 ; N oe ; B 34 -13 1104 561 ;
+C 251 ; WX 554 ; N germandbls ; B 61 -13 525 753 ;
+C -1 ; WX 650 ; N ecircumflex ; B 38 -13 608 764 ;
+C -1 ; WX 650 ; N edieresis ; B 38 -13 608 765 ;
+C -1 ; WX 683 ; N aacute ; B 42 -13 621 786 ;
+C -1 ; WX 747 ; N registered ; B -9 -12 755 752 ;
+C -1 ; WX 200 ; N icircumflex ; B -77 0 277 764 ;
+C -1 ; WX 608 ; N udieresis ; B 62 -13 541 765 ;
+C -1 ; WX 655 ; N ograve ; B 42 -13 614 786 ;
+C -1 ; WX 608 ; N uacute ; B 62 -13 541 786 ;
+C -1 ; WX 608 ; N ucircumflex ; B 62 -13 541 764 ;
+C -1 ; WX 740 ; N Aacute ; B 12 0 729 949 ;
+C -1 ; WX 200 ; N igrave ; B -60 0 171 786 ;
+C -1 ; WX 226 ; N Icircumflex ; B -64 0 290 927 ;
+C -1 ; WX 647 ; N ccedilla ; B 41 -222 607 561 ;
+C -1 ; WX 683 ; N adieresis ; B 42 -13 621 765 ;
+C -1 ; WX 536 ; N Ecircumflex ; B 70 0 475 927 ;
+C -1 ; WX 388 ; N scaron ; B 11 -13 366 764 ;
+C -1 ; WX 682 ; N thorn ; B 64 -192 643 740 ;
+C -1 ; WX 1000 ; N trademark ; B 9 296 816 740 ;
+C -1 ; WX 650 ; N egrave ; B 38 -13 608 786 ;
+C -1 ; WX 332 ; N threesuperior ; B 18 289 318 747 ;
+C -1 ; WX 425 ; N zcaron ; B 10 0 415 764 ;
+C -1 ; WX 683 ; N atilde ; B 42 -13 621 754 ;
+C -1 ; WX 683 ; N aring ; B 42 -13 621 807 ;
+C -1 ; WX 655 ; N ocircumflex ; B 42 -13 614 764 ;
+C -1 ; WX 536 ; N Edieresis ; B 70 0 475 928 ;
+C -1 ; WX 831 ; N threequarters ; B 46 0 784 747 ;
+C -1 ; WX 536 ; N ydieresis ; B 15 -192 523 765 ;
+C -1 ; WX 536 ; N yacute ; B 15 -192 523 786 ;
+C -1 ; WX 200 ; N iacute ; B 31 0 262 786 ;
+C -1 ; WX 740 ; N Acircumflex ; B 12 0 729 927 ;
+C -1 ; WX 655 ; N Uacute ; B 75 -13 579 949 ;
+C -1 ; WX 650 ; N eacute ; B 38 -13 608 786 ;
+C -1 ; WX 869 ; N Ograve ; B 43 -13 826 949 ;
+C -1 ; WX 683 ; N agrave ; B 42 -13 621 786 ;
+C -1 ; WX 655 ; N Udieresis ; B 75 -13 579 928 ;
+C -1 ; WX 683 ; N acircumflex ; B 42 -13 621 764 ;
+C -1 ; WX 226 ; N Igrave ; B -47 0 184 949 ;
+C -1 ; WX 332 ; N twosuperior ; B 19 296 318 747 ;
+C -1 ; WX 655 ; N Ugrave ; B 75 -13 579 949 ;
+C -1 ; WX 831 ; N onequarter ; B 100 0 729 740 ;
+C -1 ; WX 655 ; N Ucircumflex ; B 75 -13 579 927 ;
+C -1 ; WX 498 ; N Scaron ; B 22 -13 473 927 ;
+C -1 ; WX 226 ; N Idieresis ; B 2 0 224 928 ;
+C -1 ; WX 200 ; N idieresis ; B -11 0 211 765 ;
+C -1 ; WX 536 ; N Egrave ; B 70 0 475 949 ;
+C -1 ; WX 869 ; N Oacute ; B 43 -13 826 949 ;
+C -1 ; WX 606 ; N divide ; B 51 -13 555 519 ;
+C -1 ; WX 740 ; N Atilde ; B 12 0 729 917 ;
+C -1 ; WX 740 ; N Aring ; B 12 0 729 955 ;
+C -1 ; WX 869 ; N Odieresis ; B 43 -13 826 928 ;
+C -1 ; WX 740 ; N Adieresis ; B 12 0 729 928 ;
+C -1 ; WX 740 ; N Ntilde ; B 75 0 664 917 ;
+C -1 ; WX 480 ; N Zcaron ; B 12 0 470 927 ;
+C -1 ; WX 592 ; N Thorn ; B 60 0 549 740 ;
+C -1 ; WX 226 ; N Iacute ; B 44 0 275 949 ;
+C -1 ; WX 606 ; N plusminus ; B 51 -24 555 518 ;
+C -1 ; WX 606 ; N multiply ; B 74 24 533 482 ;
+C -1 ; WX 536 ; N Eacute ; B 70 0 475 949 ;
+C -1 ; WX 592 ; N Ydieresis ; B 1 0 592 928 ;
+C -1 ; WX 332 ; N onesuperior ; B 63 296 198 740 ;
+C -1 ; WX 608 ; N ugrave ; B 62 -13 541 786 ;
+C -1 ; WX 606 ; N logicalnot ; B 51 109 555 388 ;
+C -1 ; WX 610 ; N ntilde ; B 65 0 546 754 ;
+C -1 ; WX 869 ; N Otilde ; B 43 -13 826 917 ;
+C -1 ; WX 655 ; N otilde ; B 42 -13 614 754 ;
+C -1 ; WX 813 ; N Ccedilla ; B 43 -222 771 752 ;
+C -1 ; WX 740 ; N Agrave ; B 12 0 729 949 ;
+C -1 ; WX 831 ; N onehalf ; B 81 0 750 740 ;
+C -1 ; WX 790 ; N Eth ; B 40 0 739 740 ;
+C -1 ; WX 400 ; N degree ; B 56 421 344 709 ;
+C -1 ; WX 592 ; N Yacute ; B 1 0 592 949 ;
+C -1 ; WX 869 ; N Ocircumflex ; B 43 -13 826 927 ;
+C -1 ; WX 655 ; N oacute ; B 42 -13 614 786 ;
+C -1 ; WX 608 ; N mu ; B 80 -184 527 547 ;
+C -1 ; WX 606 ; N minus ; B 51 219 555 287 ;
+C -1 ; WX 655 ; N eth ; B 42 -12 614 753 ;
+C -1 ; WX 655 ; N odieresis ; B 42 -13 614 765 ;
+C -1 ; WX 747 ; N copyright ; B -9 -12 755 752 ;
+C -1 ; WX 672 ; N brokenbar ; B 299 -100 373 740 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 216
+
+KPX A y -62
+KPX A w -65
+KPX A v -70
+KPX A u -20
+KPX A quoteright -100
+KPX A quotedblright -100
+KPX A Y -92
+KPX A W -60
+KPX A V -102
+KPX A U -40
+KPX A T -45
+KPX A Q -40
+KPX A O -50
+KPX A G -40
+KPX A C -40
+
+KPX B A -10
+
+KPX C A -40
+
+KPX D period -20
+KPX D comma -20
+KPX D Y -30
+KPX D W -10
+KPX D V -50
+KPX D A -50
+
+KPX F period -160
+KPX F e -20
+KPX F comma -180
+KPX F a -20
+KPX F A -75
+
+KPX G period -20
+KPX G comma -20
+KPX G Y -20
+
+KPX J period -15
+KPX J a -20
+KPX J A -30
+
+KPX K o -15
+KPX K e -20
+KPX K O -20
+
+KPX L y -23
+KPX L quoteright -130
+KPX L quotedblright -130
+KPX L Y -91
+KPX L W -67
+KPX L V -113
+KPX L T -46
+
+KPX O period -30
+KPX O comma -30
+KPX O Y -30
+KPX O X -30
+KPX O W -20
+KPX O V -60
+KPX O T -30
+KPX O A -60
+
+KPX P period -300
+KPX P o -60
+KPX P e -20
+KPX P comma -280
+KPX P a -20
+KPX P A -114
+
+KPX Q comma 20
+
+KPX R Y -10
+KPX R W 10
+KPX R V -10
+KPX R T 6
+
+KPX S comma 20
+
+KPX T y -50
+KPX T w -55
+KPX T u -46
+KPX T semicolon -29
+KPX T r -30
+KPX T period -91
+KPX T o -70
+KPX T i 10
+KPX T hyphen -75
+KPX T e -49
+KPX T comma -82
+KPX T colon -15
+KPX T a -90
+KPX T O -30
+KPX T A -45
+
+KPX U period -20
+KPX U comma -20
+KPX U A -40
+
+KPX V u -40
+KPX V semicolon -33
+KPX V period -165
+KPX V o -101
+KPX V i -5
+KPX V hyphen -75
+KPX V e -101
+KPX V comma -145
+KPX V colon -18
+KPX V a -104
+KPX V O -60
+KPX V G -20
+KPX V A -102
+
+KPX W y -2
+KPX W u -30
+KPX W semicolon -33
+KPX W period -106
+KPX W o -46
+KPX W i 6
+KPX W hyphen -35
+KPX W e -47
+KPX W comma -106
+KPX W colon -15
+KPX W a -50
+KPX W O -20
+KPX W A -58
+
+KPX Y u -52
+KPX Y semicolon -23
+KPX Y period -175
+KPX Y o -89
+KPX Y hyphen -85
+KPX Y e -89
+KPX Y comma -145
+KPX Y colon -10
+KPX Y a -93
+KPX Y O -30
+KPX Y A -92
+
+KPX a p 20
+KPX a b 20
+
+KPX b y -20
+KPX b v -20
+
+KPX c y -20
+KPX c k -15
+
+KPX comma space -110
+KPX comma quoteright -120
+KPX comma quotedblright -120
+
+KPX e y -20
+KPX e w -20
+KPX e v -20
+
+KPX f period -50
+KPX f o -40
+KPX f l -30
+KPX f i -34
+KPX f f -60
+KPX f e -20
+KPX f dotlessi -34
+KPX f comma -50
+KPX f a -40
+
+KPX g a -15
+
+KPX h y -30
+
+KPX k y -5
+KPX k e -15
+
+KPX m y -20
+KPX m u -20
+KPX m a -20
+
+KPX n y -15
+KPX n v -20
+
+KPX o y -20
+KPX o x -15
+KPX o w -20
+KPX o v -30
+
+KPX p y -20
+
+KPX period space -110
+KPX period quoteright -120
+KPX period quotedblright -120
+
+KPX quotedblleft quoteleft -35
+KPX quotedblleft A -100
+
+KPX quotedblright space -110
+
+KPX quoteleft quoteleft -203
+KPX quoteleft A -100
+
+KPX quoteright v -30
+KPX quoteright t 10
+KPX quoteright space -110
+KPX quoteright s -15
+KPX quoteright r -20
+KPX quoteright quoteright -203
+KPX quoteright quotedblright -35
+KPX quoteright d -110
+
+KPX r y 40
+KPX r v 40
+KPX r u 20
+KPX r t 20
+KPX r s 20
+KPX r q -8
+KPX r period -73
+KPX r p 20
+KPX r o -20
+KPX r n 21
+KPX r m 28
+KPX r l 20
+KPX r k 20
+KPX r i 20
+KPX r hyphen -60
+KPX r g -15
+KPX r e -4
+KPX r d -6
+KPX r comma -75
+KPX r c -20
+KPX r a -20
+
+KPX s period 20
+KPX s comma 20
+
+KPX space quoteleft -110
+KPX space quotedblleft -110
+KPX space Y -60
+KPX space W -25
+KPX space V -50
+KPX space T -25
+KPX space A -20
+
+KPX v period -130
+KPX v o -30
+KPX v e -20
+KPX v comma -100
+KPX v a -30
+
+KPX w period -100
+KPX w o -30
+KPX w h 15
+KPX w e -20
+KPX w comma -90
+KPX w a -30
+
+KPX y period -125
+KPX y o -30
+KPX y e -20
+KPX y comma -110
+KPX y a -30
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 183 163 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 119 163 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 186 163 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 181 163 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 204 148 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 151 163 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 81 163 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 17 163 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 84 163 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 79 163 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -34 163 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -138 163 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -71 163 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -116 163 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 151 163 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 247 163 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 184 163 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 250 163 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 246 163 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 215 163 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron -2 163 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 160 163 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 77 163 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 143 163 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 119 163 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 129 163 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 112 163 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron -11 163 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 154 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 91 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 157 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 153 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 176 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 122 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 138 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 74 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 141 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 136 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -47 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -151 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -84 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -129 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 86 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 140 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 77 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 143 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 139 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 108 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron -57 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 137 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 53 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 120 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 95 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 101 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 84 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron -38 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagko8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagko8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..e0e75f384e46861cd4989f2cf0bb1b26dbabc3a9
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagko8a.afm
@@ -0,0 +1,573 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Mar 4 13:41:11 1991
+Comment UniqueID 34367
+Comment VMusage 6555 39267
+FontName AvantGarde-BookOblique
+FullName ITC Avant Garde Gothic Book Oblique
+FamilyName ITC Avant Garde Gothic
+Weight Book
+ItalicAngle -10.5
+IsFixedPitch false
+FontBBox -113 -222 1279 955
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.006
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.ITC Avant Garde Gothic is a registered trademark of International Typeface Corporation.
+EncodingScheme AdobeStandardEncoding
+CapHeight 740
+XHeight 547
+Ascender 740
+Descender -192
+StartCharMetrics 228
+C 32 ; WX 277 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 295 ; N exclam ; B 111 0 322 740 ;
+C 34 ; WX 309 ; N quotedbl ; B 130 444 410 740 ;
+C 35 ; WX 554 ; N numbersign ; B 71 0 620 740 ;
+C 36 ; WX 554 ; N dollar ; B 107 -70 581 811 ;
+C 37 ; WX 775 ; N percent ; B 124 -13 787 751 ;
+C 38 ; WX 757 ; N ampersand ; B 92 -12 775 753 ;
+C 39 ; WX 351 ; N quoteright ; B 195 546 393 740 ;
+C 40 ; WX 369 ; N parenleft ; B 89 -205 495 757 ;
+C 41 ; WX 369 ; N parenright ; B -24 -205 382 757 ;
+C 42 ; WX 425 ; N asterisk ; B 170 446 479 740 ;
+C 43 ; WX 606 ; N plus ; B 92 0 608 506 ;
+C 44 ; WX 277 ; N comma ; B 2 -67 199 126 ;
+C 45 ; WX 332 ; N hyphen ; B 76 248 360 315 ;
+C 46 ; WX 277 ; N period ; B 102 0 199 126 ;
+C 47 ; WX 437 ; N slash ; B 25 -100 540 740 ;
+C 48 ; WX 554 ; N zero ; B 71 -13 622 753 ;
+C 49 ; WX 554 ; N one ; B 260 0 473 740 ;
+C 50 ; WX 554 ; N two ; B 40 0 615 753 ;
+C 51 ; WX 554 ; N three ; B 73 -13 565 753 ;
+C 52 ; WX 554 ; N four ; B 39 0 598 740 ;
+C 53 ; WX 554 ; N five ; B 69 -13 605 740 ;
+C 54 ; WX 554 ; N six ; B 65 -13 580 739 ;
+C 55 ; WX 554 ; N seven ; B 110 0 628 740 ;
+C 56 ; WX 554 ; N eight ; B 77 -13 580 753 ;
+C 57 ; WX 554 ; N nine ; B 111 0 626 752 ;
+C 58 ; WX 277 ; N colon ; B 102 0 278 548 ;
+C 59 ; WX 277 ; N semicolon ; B 2 -67 278 548 ;
+C 60 ; WX 606 ; N less ; B 87 -8 649 514 ;
+C 61 ; WX 606 ; N equal ; B 73 118 627 388 ;
+C 62 ; WX 606 ; N greater ; B 51 -8 613 514 ;
+C 63 ; WX 591 ; N question ; B 158 0 628 752 ;
+C 64 ; WX 867 ; N at ; B 126 -13 888 753 ;
+C 65 ; WX 740 ; N A ; B 12 0 729 740 ;
+C 66 ; WX 574 ; N B ; B 74 0 606 740 ;
+C 67 ; WX 813 ; N C ; B 105 -13 870 752 ;
+C 68 ; WX 744 ; N D ; B 74 0 773 740 ;
+C 69 ; WX 536 ; N E ; B 70 0 612 740 ;
+C 70 ; WX 485 ; N F ; B 70 0 581 740 ;
+C 71 ; WX 872 ; N G ; B 103 -13 891 753 ;
+C 72 ; WX 683 ; N H ; B 76 0 744 740 ;
+C 73 ; WX 226 ; N I ; B 76 0 287 740 ;
+C 74 ; WX 482 ; N J ; B 37 -13 539 740 ;
+C 75 ; WX 591 ; N K ; B 81 0 728 740 ;
+C 76 ; WX 462 ; N L ; B 82 0 474 740 ;
+C 77 ; WX 919 ; N M ; B 76 0 980 740 ;
+C 78 ; WX 740 ; N N ; B 75 0 801 740 ;
+C 79 ; WX 869 ; N O ; B 105 -13 901 753 ;
+C 80 ; WX 592 ; N P ; B 75 0 664 740 ;
+C 81 ; WX 871 ; N Q ; B 102 -13 912 753 ;
+C 82 ; WX 607 ; N R ; B 70 0 669 740 ;
+C 83 ; WX 498 ; N S ; B 57 -13 561 753 ;
+C 84 ; WX 426 ; N T ; B 131 0 556 740 ;
+C 85 ; WX 655 ; N U ; B 118 -13 716 740 ;
+C 86 ; WX 702 ; N V ; B 145 0 830 740 ;
+C 87 ; WX 960 ; N W ; B 148 0 1087 740 ;
+C 88 ; WX 609 ; N X ; B 8 0 724 740 ;
+C 89 ; WX 592 ; N Y ; B 138 0 729 740 ;
+C 90 ; WX 480 ; N Z ; B 12 0 596 740 ;
+C 91 ; WX 351 ; N bracketleft ; B 145 -179 477 753 ;
+C 92 ; WX 605 ; N backslash ; B 255 -100 458 740 ;
+C 93 ; WX 351 ; N bracketright ; B -19 -179 312 753 ;
+C 94 ; WX 606 ; N asciicircum ; B 110 307 610 740 ;
+C 95 ; WX 500 ; N underscore ; B -23 -125 486 -75 ;
+C 96 ; WX 351 ; N quoteleft ; B 232 546 358 740 ;
+C 97 ; WX 683 ; N a ; B 88 -13 722 561 ;
+C 98 ; WX 682 ; N b ; B 68 -13 703 740 ;
+C 99 ; WX 647 ; N c ; B 87 -13 678 561 ;
+C 100 ; WX 685 ; N d ; B 85 -13 755 740 ;
+C 101 ; WX 650 ; N e ; B 84 -13 664 561 ;
+C 102 ; WX 314 ; N f ; B 104 0 454 753 ; L i fi ; L l fl ;
+C 103 ; WX 673 ; N g ; B 56 -215 707 561 ;
+C 104 ; WX 610 ; N h ; B 62 0 606 740 ;
+C 105 ; WX 200 ; N i ; B 65 0 272 740 ;
+C 106 ; WX 203 ; N j ; B -80 -192 274 740 ;
+C 107 ; WX 502 ; N k ; B 70 0 588 740 ;
+C 108 ; WX 200 ; N l ; B 65 0 272 740 ;
+C 109 ; WX 938 ; N m ; B 66 0 938 561 ;
+C 110 ; WX 610 ; N n ; B 65 0 609 561 ;
+C 111 ; WX 655 ; N o ; B 88 -13 669 561 ;
+C 112 ; WX 682 ; N p ; B 28 -192 699 561 ;
+C 113 ; WX 682 ; N q ; B 83 -192 717 561 ;
+C 114 ; WX 301 ; N r ; B 65 0 395 561 ;
+C 115 ; WX 388 ; N s ; B 49 -13 424 561 ;
+C 116 ; WX 339 ; N t ; B 104 0 431 740 ;
+C 117 ; WX 608 ; N u ; B 100 -13 642 547 ;
+C 118 ; WX 554 ; N v ; B 108 0 647 547 ;
+C 119 ; WX 831 ; N w ; B 114 0 921 547 ;
+C 120 ; WX 480 ; N x ; B 12 0 569 547 ;
+C 121 ; WX 536 ; N y ; B 97 -192 624 547 ;
+C 122 ; WX 425 ; N z ; B 10 0 498 547 ;
+C 123 ; WX 351 ; N braceleft ; B 115 -189 468 740 ;
+C 124 ; WX 672 ; N bar ; B 280 -100 510 740 ;
+C 125 ; WX 351 ; N braceright ; B -15 -189 338 740 ;
+C 126 ; WX 606 ; N asciitilde ; B 114 179 584 319 ;
+C 161 ; WX 295 ; N exclamdown ; B 74 -192 286 548 ;
+C 162 ; WX 554 ; N cent ; B 115 62 596 707 ;
+C 163 ; WX 554 ; N sterling ; B 29 0 614 753 ;
+C 164 ; WX 166 ; N fraction ; B -113 0 417 740 ;
+C 165 ; WX 554 ; N yen ; B 75 0 687 740 ;
+C 166 ; WX 554 ; N florin ; B -39 -153 669 818 ;
+C 167 ; WX 615 ; N section ; B 118 -141 597 753 ;
+C 168 ; WX 554 ; N currency ; B 24 42 645 580 ;
+C 169 ; WX 198 ; N quotesingle ; B 153 444 277 740 ;
+C 170 ; WX 502 ; N quotedblleft ; B 234 546 507 740 ;
+C 171 ; WX 425 ; N guillemotleft ; B 92 81 469 481 ;
+C 172 ; WX 251 ; N guilsinglleft ; B 92 81 295 481 ;
+C 173 ; WX 251 ; N guilsinglright ; B 60 81 263 481 ;
+C 174 ; WX 487 ; N fi ; B 104 0 559 753 ;
+C 175 ; WX 485 ; N fl ; B 104 0 557 753 ;
+C 177 ; WX 500 ; N endash ; B 81 248 523 315 ;
+C 178 ; WX 553 ; N dagger ; B 146 -133 593 740 ;
+C 179 ; WX 553 ; N daggerdbl ; B 72 -133 593 740 ;
+C 180 ; WX 277 ; N periodcentered ; B 137 190 235 316 ;
+C 182 ; WX 564 ; N paragraph ; B 119 -110 688 740 ;
+C 183 ; WX 606 ; N bullet ; B 217 222 528 532 ;
+C 184 ; WX 354 ; N quotesinglbase ; B 76 -68 274 126 ;
+C 185 ; WX 502 ; N quotedblbase ; B 76 -68 422 126 ;
+C 186 ; WX 484 ; N quotedblright ; B 197 546 542 740 ;
+C 187 ; WX 425 ; N guillemotright ; B 60 81 437 481 ;
+C 188 ; WX 1000 ; N ellipsis ; B 130 0 893 126 ;
+C 189 ; WX 1174 ; N perthousand ; B 128 -13 1182 751 ;
+C 191 ; WX 591 ; N questiondown ; B 64 -205 534 548 ;
+C 193 ; WX 378 ; N grave ; B 204 619 425 786 ;
+C 194 ; WX 375 ; N acute ; B 203 619 444 786 ;
+C 195 ; WX 502 ; N circumflex ; B 192 639 546 764 ;
+C 196 ; WX 439 ; N tilde ; B 179 651 520 754 ;
+C 197 ; WX 485 ; N macron ; B 197 669 547 736 ;
+C 198 ; WX 453 ; N breve ; B 192 651 541 754 ;
+C 199 ; WX 222 ; N dotaccent ; B 192 639 290 765 ;
+C 200 ; WX 369 ; N dieresis ; B 191 639 437 765 ;
+C 202 ; WX 332 ; N ring ; B 191 600 401 807 ;
+C 203 ; WX 324 ; N cedilla ; B 52 -222 231 0 ;
+C 205 ; WX 552 ; N hungarumlaut ; B 239 605 594 800 ;
+C 206 ; WX 302 ; N ogonek ; B 53 -191 202 0 ;
+C 207 ; WX 502 ; N caron ; B 210 639 565 764 ;
+C 208 ; WX 1000 ; N emdash ; B 81 248 1023 315 ;
+C 225 ; WX 992 ; N AE ; B -20 0 1044 740 ;
+C 227 ; WX 369 ; N ordfeminine ; B 102 407 494 753 ;
+C 232 ; WX 517 ; N Lslash ; B 107 0 529 740 ;
+C 233 ; WX 868 ; N Oslash ; B 76 -83 929 819 ;
+C 234 ; WX 1194 ; N OE ; B 107 -13 1279 753 ;
+C 235 ; WX 369 ; N ordmasculine ; B 116 407 466 753 ;
+C 241 ; WX 1157 ; N ae ; B 80 -13 1169 561 ;
+C 245 ; WX 200 ; N dotlessi ; B 65 0 236 547 ;
+C 248 ; WX 300 ; N lslash ; B 95 0 354 740 ;
+C 249 ; WX 653 ; N oslash ; B 51 -64 703 614 ;
+C 250 ; WX 1137 ; N oe ; B 80 -13 1160 561 ;
+C 251 ; WX 554 ; N germandbls ; B 61 -13 578 753 ;
+C -1 ; WX 650 ; N ecircumflex ; B 84 -13 664 764 ;
+C -1 ; WX 650 ; N edieresis ; B 84 -13 664 765 ;
+C -1 ; WX 683 ; N aacute ; B 88 -13 722 786 ;
+C -1 ; WX 747 ; N registered ; B 53 -12 830 752 ;
+C -1 ; WX 200 ; N icircumflex ; B 41 0 395 764 ;
+C -1 ; WX 608 ; N udieresis ; B 100 -13 642 765 ;
+C -1 ; WX 655 ; N ograve ; B 88 -13 669 786 ;
+C -1 ; WX 608 ; N uacute ; B 100 -13 642 786 ;
+C -1 ; WX 608 ; N ucircumflex ; B 100 -13 642 764 ;
+C -1 ; WX 740 ; N Aacute ; B 12 0 729 949 ;
+C -1 ; WX 200 ; N igrave ; B 65 0 296 786 ;
+C -1 ; WX 226 ; N Icircumflex ; B 76 0 439 927 ;
+C -1 ; WX 647 ; N ccedilla ; B 87 -222 678 561 ;
+C -1 ; WX 683 ; N adieresis ; B 88 -13 722 765 ;
+C -1 ; WX 536 ; N Ecircumflex ; B 70 0 612 927 ;
+C -1 ; WX 388 ; N scaron ; B 49 -13 508 764 ;
+C -1 ; WX 682 ; N thorn ; B 28 -192 699 740 ;
+C -1 ; WX 1000 ; N trademark ; B 137 296 953 740 ;
+C -1 ; WX 650 ; N egrave ; B 84 -13 664 786 ;
+C -1 ; WX 332 ; N threesuperior ; B 98 289 408 747 ;
+C -1 ; WX 425 ; N zcaron ; B 10 0 527 764 ;
+C -1 ; WX 683 ; N atilde ; B 88 -13 722 754 ;
+C -1 ; WX 683 ; N aring ; B 88 -13 722 807 ;
+C -1 ; WX 655 ; N ocircumflex ; B 88 -13 669 764 ;
+C -1 ; WX 536 ; N Edieresis ; B 70 0 612 928 ;
+C -1 ; WX 831 ; N threequarters ; B 126 0 825 747 ;
+C -1 ; WX 536 ; N ydieresis ; B 97 -192 624 765 ;
+C -1 ; WX 536 ; N yacute ; B 97 -192 624 786 ;
+C -1 ; WX 200 ; N iacute ; B 65 0 397 786 ;
+C -1 ; WX 740 ; N Acircumflex ; B 12 0 729 927 ;
+C -1 ; WX 655 ; N Uacute ; B 118 -13 716 949 ;
+C -1 ; WX 650 ; N eacute ; B 84 -13 664 786 ;
+C -1 ; WX 869 ; N Ograve ; B 105 -13 901 949 ;
+C -1 ; WX 683 ; N agrave ; B 88 -13 722 786 ;
+C -1 ; WX 655 ; N Udieresis ; B 118 -13 716 928 ;
+C -1 ; WX 683 ; N acircumflex ; B 88 -13 722 764 ;
+C -1 ; WX 226 ; N Igrave ; B 76 0 340 949 ;
+C -1 ; WX 332 ; N twosuperior ; B 74 296 433 747 ;
+C -1 ; WX 655 ; N Ugrave ; B 118 -13 716 949 ;
+C -1 ; WX 831 ; N onequarter ; B 183 0 770 740 ;
+C -1 ; WX 655 ; N Ucircumflex ; B 118 -13 716 927 ;
+C -1 ; WX 498 ; N Scaron ; B 57 -13 593 927 ;
+C -1 ; WX 226 ; N Idieresis ; B 76 0 396 928 ;
+C -1 ; WX 200 ; N idieresis ; B 65 0 353 765 ;
+C -1 ; WX 536 ; N Egrave ; B 70 0 612 949 ;
+C -1 ; WX 869 ; N Oacute ; B 105 -13 901 949 ;
+C -1 ; WX 606 ; N divide ; B 92 -13 608 519 ;
+C -1 ; WX 740 ; N Atilde ; B 12 0 729 917 ;
+C -1 ; WX 740 ; N Aring ; B 12 0 729 955 ;
+C -1 ; WX 869 ; N Odieresis ; B 105 -13 901 928 ;
+C -1 ; WX 740 ; N Adieresis ; B 12 0 729 928 ;
+C -1 ; WX 740 ; N Ntilde ; B 75 0 801 917 ;
+C -1 ; WX 480 ; N Zcaron ; B 12 0 596 927 ;
+C -1 ; WX 592 ; N Thorn ; B 60 0 621 740 ;
+C -1 ; WX 226 ; N Iacute ; B 76 0 440 949 ;
+C -1 ; WX 606 ; N plusminus ; B 47 -24 618 518 ;
+C -1 ; WX 606 ; N multiply ; B 87 24 612 482 ;
+C -1 ; WX 536 ; N Eacute ; B 70 0 612 949 ;
+C -1 ; WX 592 ; N Ydieresis ; B 138 0 729 928 ;
+C -1 ; WX 332 ; N onesuperior ; B 190 296 335 740 ;
+C -1 ; WX 608 ; N ugrave ; B 100 -13 642 786 ;
+C -1 ; WX 606 ; N logicalnot ; B 110 109 627 388 ;
+C -1 ; WX 610 ; N ntilde ; B 65 0 609 754 ;
+C -1 ; WX 869 ; N Otilde ; B 105 -13 901 917 ;
+C -1 ; WX 655 ; N otilde ; B 88 -13 669 754 ;
+C -1 ; WX 813 ; N Ccedilla ; B 105 -222 870 752 ;
+C -1 ; WX 740 ; N Agrave ; B 12 0 729 949 ;
+C -1 ; WX 831 ; N onehalf ; B 164 0 810 740 ;
+C -1 ; WX 790 ; N Eth ; B 104 0 813 740 ;
+C -1 ; WX 400 ; N degree ; B 158 421 451 709 ;
+C -1 ; WX 592 ; N Yacute ; B 138 0 729 949 ;
+C -1 ; WX 869 ; N Ocircumflex ; B 105 -13 901 927 ;
+C -1 ; WX 655 ; N oacute ; B 88 -13 669 786 ;
+C -1 ; WX 608 ; N mu ; B 46 -184 628 547 ;
+C -1 ; WX 606 ; N minus ; B 92 219 608 287 ;
+C -1 ; WX 655 ; N eth ; B 88 -12 675 753 ;
+C -1 ; WX 655 ; N odieresis ; B 88 -13 669 765 ;
+C -1 ; WX 747 ; N copyright ; B 53 -12 830 752 ;
+C -1 ; WX 672 ; N brokenbar ; B 280 -100 510 740 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 216
+
+KPX A y -62
+KPX A w -65
+KPX A v -70
+KPX A u -20
+KPX A quoteright -100
+KPX A quotedblright -100
+KPX A Y -92
+KPX A W -60
+KPX A V -102
+KPX A U -40
+KPX A T -45
+KPX A Q -40
+KPX A O -50
+KPX A G -40
+KPX A C -40
+
+KPX B A -10
+
+KPX C A -40
+
+KPX D period -20
+KPX D comma -20
+KPX D Y -30
+KPX D W -10
+KPX D V -50
+KPX D A -50
+
+KPX F period -160
+KPX F e -20
+KPX F comma -180
+KPX F a -20
+KPX F A -75
+
+KPX G period -20
+KPX G comma -20
+KPX G Y -20
+
+KPX J period -15
+KPX J a -20
+KPX J A -30
+
+KPX K o -15
+KPX K e -20
+KPX K O -20
+
+KPX L y -23
+KPX L quoteright -130
+KPX L quotedblright -130
+KPX L Y -91
+KPX L W -67
+KPX L V -113
+KPX L T -46
+
+KPX O period -30
+KPX O comma -30
+KPX O Y -30
+KPX O X -30
+KPX O W -20
+KPX O V -60
+KPX O T -30
+KPX O A -60
+
+KPX P period -300
+KPX P o -60
+KPX P e -20
+KPX P comma -280
+KPX P a -20
+KPX P A -114
+
+KPX Q comma 20
+
+KPX R Y -10
+KPX R W 10
+KPX R V -10
+KPX R T 6
+
+KPX S comma 20
+
+KPX T y -50
+KPX T w -55
+KPX T u -46
+KPX T semicolon -29
+KPX T r -30
+KPX T period -91
+KPX T o -70
+KPX T i 10
+KPX T hyphen -75
+KPX T e -49
+KPX T comma -82
+KPX T colon -15
+KPX T a -90
+KPX T O -30
+KPX T A -45
+
+KPX U period -20
+KPX U comma -20
+KPX U A -40
+
+KPX V u -40
+KPX V semicolon -33
+KPX V period -165
+KPX V o -101
+KPX V i -5
+KPX V hyphen -75
+KPX V e -101
+KPX V comma -145
+KPX V colon -18
+KPX V a -104
+KPX V O -60
+KPX V G -20
+KPX V A -102
+
+KPX W y -2
+KPX W u -30
+KPX W semicolon -33
+KPX W period -106
+KPX W o -46
+KPX W i 6
+KPX W hyphen -35
+KPX W e -47
+KPX W comma -106
+KPX W colon -15
+KPX W a -50
+KPX W O -20
+KPX W A -58
+
+KPX Y u -52
+KPX Y semicolon -23
+KPX Y period -175
+KPX Y o -89
+KPX Y hyphen -85
+KPX Y e -89
+KPX Y comma -145
+KPX Y colon -10
+KPX Y a -93
+KPX Y O -30
+KPX Y A -92
+
+KPX a p 20
+KPX a b 20
+
+KPX b y -20
+KPX b v -20
+
+KPX c y -20
+KPX c k -15
+
+KPX comma space -110
+KPX comma quoteright -120
+KPX comma quotedblright -120
+
+KPX e y -20
+KPX e w -20
+KPX e v -20
+
+KPX f period -50
+KPX f o -40
+KPX f l -30
+KPX f i -34
+KPX f f -60
+KPX f e -20
+KPX f dotlessi -34
+KPX f comma -50
+KPX f a -40
+
+KPX g a -15
+
+KPX h y -30
+
+KPX k y -5
+KPX k e -15
+
+KPX m y -20
+KPX m u -20
+KPX m a -20
+
+KPX n y -15
+KPX n v -20
+
+KPX o y -20
+KPX o x -15
+KPX o w -20
+KPX o v -30
+
+KPX p y -20
+
+KPX period space -110
+KPX period quoteright -120
+KPX period quotedblright -120
+
+KPX quotedblleft quoteleft -35
+KPX quotedblleft A -100
+
+KPX quotedblright space -110
+
+KPX quoteleft quoteleft -203
+KPX quoteleft A -100
+
+KPX quoteright v -30
+KPX quoteright t 10
+KPX quoteright space -110
+KPX quoteright s -15
+KPX quoteright r -20
+KPX quoteright quoteright -203
+KPX quoteright quotedblright -35
+KPX quoteright d -110
+
+KPX r y 40
+KPX r v 40
+KPX r u 20
+KPX r t 20
+KPX r s 20
+KPX r q -8
+KPX r period -73
+KPX r p 20
+KPX r o -20
+KPX r n 21
+KPX r m 28
+KPX r l 20
+KPX r k 20
+KPX r i 20
+KPX r hyphen -60
+KPX r g -15
+KPX r e -4
+KPX r d -6
+KPX r comma -75
+KPX r c -20
+KPX r a -20
+
+KPX s period 20
+KPX s comma 20
+
+KPX space quoteleft -110
+KPX space quotedblleft -110
+KPX space Y -60
+KPX space W -25
+KPX space V -50
+KPX space T -25
+KPX space A -20
+
+KPX v period -130
+KPX v o -30
+KPX v e -20
+KPX v comma -100
+KPX v a -30
+
+KPX w period -100
+KPX w o -30
+KPX w h 15
+KPX w e -20
+KPX w comma -90
+KPX w a -30
+
+KPX y period -125
+KPX y o -30
+KPX y e -20
+KPX y comma -110
+KPX y a -30
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 213 163 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 149 163 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 216 163 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 211 163 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 231 148 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 181 163 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 111 163 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 47 163 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 114 163 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 109 163 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -4 163 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -108 163 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -41 163 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -86 163 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 181 163 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 277 163 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 214 163 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 280 163 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 276 163 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 245 163 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 28 163 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 190 163 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 107 163 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 173 163 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 149 163 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 159 163 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 142 163 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 19 163 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 154 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 91 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 157 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 153 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 176 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 122 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 138 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 74 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 141 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 136 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -47 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -151 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -84 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -129 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 86 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 140 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 77 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 143 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 139 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 108 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron -57 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 137 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 53 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 120 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 95 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 101 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 84 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron -38 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkd8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkd8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..036be6d8cd8f4f816740b278f6b23d01c7ede083
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkd8a.afm
@@ -0,0 +1,415 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1992 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue Jan 21 16:13:29 1992
+Comment UniqueID 37831
+Comment VMusage 31983 38875
+FontName Bookman-Demi
+FullName ITC Bookman Demi
+FamilyName ITC Bookman
+Weight Demi
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -194 -250 1346 934
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.004
+Notice Copyright (c) 1985, 1987, 1989, 1992 Adobe Systems Incorporated. All Rights Reserved.ITC Bookman is a registered trademark of International Typeface Corporation.
+EncodingScheme AdobeStandardEncoding
+CapHeight 681
+XHeight 502
+Ascender 725
+Descender -212
+StartCharMetrics 228
+C 32 ; WX 340 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 360 ; N exclam ; B 82 -8 282 698 ;
+C 34 ; WX 420 ; N quotedbl ; B 11 379 369 698 ;
+C 35 ; WX 660 ; N numbersign ; B 84 0 576 681 ;
+C 36 ; WX 660 ; N dollar ; B 48 -119 620 805 ;
+C 37 ; WX 940 ; N percent ; B 12 -8 924 698 ;
+C 38 ; WX 800 ; N ampersand ; B 21 -17 772 698 ;
+C 39 ; WX 320 ; N quoteright ; B 82 440 242 698 ;
+C 40 ; WX 320 ; N parenleft ; B 48 -150 289 749 ;
+C 41 ; WX 320 ; N parenright ; B 20 -150 262 749 ;
+C 42 ; WX 460 ; N asterisk ; B 62 317 405 697 ;
+C 43 ; WX 600 ; N plus ; B 51 9 555 514 ;
+C 44 ; WX 340 ; N comma ; B 78 -124 257 162 ;
+C 45 ; WX 360 ; N hyphen ; B 20 210 340 318 ;
+C 46 ; WX 340 ; N period ; B 76 -8 258 172 ;
+C 47 ; WX 600 ; N slash ; B 50 -149 555 725 ;
+C 48 ; WX 660 ; N zero ; B 30 -17 639 698 ;
+C 49 ; WX 660 ; N one ; B 137 0 568 681 ;
+C 50 ; WX 660 ; N two ; B 41 0 628 698 ;
+C 51 ; WX 660 ; N three ; B 37 -17 631 698 ;
+C 52 ; WX 660 ; N four ; B 19 0 649 681 ;
+C 53 ; WX 660 ; N five ; B 44 -17 623 723 ;
+C 54 ; WX 660 ; N six ; B 34 -17 634 698 ;
+C 55 ; WX 660 ; N seven ; B 36 0 632 681 ;
+C 56 ; WX 660 ; N eight ; B 36 -17 633 698 ;
+C 57 ; WX 660 ; N nine ; B 33 -17 636 698 ;
+C 58 ; WX 340 ; N colon ; B 76 -8 258 515 ;
+C 59 ; WX 340 ; N semicolon ; B 75 -124 259 515 ;
+C 60 ; WX 600 ; N less ; B 49 -9 558 542 ;
+C 61 ; WX 600 ; N equal ; B 51 109 555 421 ;
+C 62 ; WX 600 ; N greater ; B 48 -9 557 542 ;
+C 63 ; WX 660 ; N question ; B 61 -8 608 698 ;
+C 64 ; WX 820 ; N at ; B 60 -17 758 698 ;
+C 65 ; WX 720 ; N A ; B -34 0 763 681 ;
+C 66 ; WX 720 ; N B ; B 20 0 693 681 ;
+C 67 ; WX 740 ; N C ; B 35 -17 724 698 ;
+C 68 ; WX 780 ; N D ; B 20 0 748 681 ;
+C 69 ; WX 720 ; N E ; B 20 0 724 681 ;
+C 70 ; WX 680 ; N F ; B 20 0 686 681 ;
+C 71 ; WX 780 ; N G ; B 35 -17 773 698 ;
+C 72 ; WX 820 ; N H ; B 20 0 800 681 ;
+C 73 ; WX 400 ; N I ; B 20 0 379 681 ;
+C 74 ; WX 640 ; N J ; B -12 -17 622 681 ;
+C 75 ; WX 800 ; N K ; B 20 0 796 681 ;
+C 76 ; WX 640 ; N L ; B 20 0 668 681 ;
+C 77 ; WX 940 ; N M ; B 20 0 924 681 ;
+C 78 ; WX 740 ; N N ; B 20 0 724 681 ;
+C 79 ; WX 800 ; N O ; B 35 -17 769 698 ;
+C 80 ; WX 660 ; N P ; B 20 0 658 681 ;
+C 81 ; WX 800 ; N Q ; B 35 -226 775 698 ;
+C 82 ; WX 780 ; N R ; B 20 0 783 681 ;
+C 83 ; WX 660 ; N S ; B 21 -17 639 698 ;
+C 84 ; WX 700 ; N T ; B -4 0 703 681 ;
+C 85 ; WX 740 ; N U ; B 15 -17 724 681 ;
+C 86 ; WX 720 ; N V ; B -20 0 730 681 ;
+C 87 ; WX 940 ; N W ; B -20 0 963 681 ;
+C 88 ; WX 780 ; N X ; B 1 0 770 681 ;
+C 89 ; WX 700 ; N Y ; B -20 0 718 681 ;
+C 90 ; WX 640 ; N Z ; B 6 0 635 681 ;
+C 91 ; WX 300 ; N bracketleft ; B 75 -138 285 725 ;
+C 92 ; WX 600 ; N backslash ; B 50 0 555 725 ;
+C 93 ; WX 300 ; N bracketright ; B 21 -138 231 725 ;
+C 94 ; WX 600 ; N asciicircum ; B 52 281 554 681 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 320 ; N quoteleft ; B 82 440 242 698 ;
+C 97 ; WX 580 ; N a ; B 28 -8 588 515 ;
+C 98 ; WX 600 ; N b ; B -20 -8 568 725 ;
+C 99 ; WX 580 ; N c ; B 31 -8 550 515 ;
+C 100 ; WX 640 ; N d ; B 31 -8 622 725 ;
+C 101 ; WX 580 ; N e ; B 31 -8 548 515 ;
+C 102 ; WX 380 ; N f ; B 22 0 461 741 ; L i fi ; L l fl ;
+C 103 ; WX 580 ; N g ; B 9 -243 583 595 ;
+C 104 ; WX 680 ; N h ; B 22 0 654 725 ;
+C 105 ; WX 360 ; N i ; B 22 0 335 729 ;
+C 106 ; WX 340 ; N j ; B -94 -221 278 729 ;
+C 107 ; WX 660 ; N k ; B 22 0 643 725 ;
+C 108 ; WX 340 ; N l ; B 9 0 322 725 ;
+C 109 ; WX 1000 ; N m ; B 22 0 980 515 ;
+C 110 ; WX 680 ; N n ; B 22 0 652 515 ;
+C 111 ; WX 620 ; N o ; B 31 -8 585 515 ;
+C 112 ; WX 640 ; N p ; B 22 -212 611 515 ;
+C 113 ; WX 620 ; N q ; B 31 -212 633 515 ;
+C 114 ; WX 460 ; N r ; B 22 0 462 502 ;
+C 115 ; WX 520 ; N s ; B 22 -8 492 515 ;
+C 116 ; WX 460 ; N t ; B 22 -8 445 660 ;
+C 117 ; WX 660 ; N u ; B 22 -8 653 502 ;
+C 118 ; WX 600 ; N v ; B -6 0 593 502 ;
+C 119 ; WX 800 ; N w ; B -6 0 810 502 ;
+C 120 ; WX 600 ; N x ; B 8 0 591 502 ;
+C 121 ; WX 620 ; N y ; B 6 -221 613 502 ;
+C 122 ; WX 560 ; N z ; B 22 0 547 502 ;
+C 123 ; WX 320 ; N braceleft ; B 14 -139 301 726 ;
+C 124 ; WX 600 ; N bar ; B 243 -250 362 750 ;
+C 125 ; WX 320 ; N braceright ; B 15 -140 302 725 ;
+C 126 ; WX 600 ; N asciitilde ; B 51 162 555 368 ;
+C 161 ; WX 360 ; N exclamdown ; B 84 -191 284 515 ;
+C 162 ; WX 660 ; N cent ; B 133 17 535 674 ;
+C 163 ; WX 660 ; N sterling ; B 10 -17 659 698 ;
+C 164 ; WX 120 ; N fraction ; B -194 0 312 681 ;
+C 165 ; WX 660 ; N yen ; B -28 0 696 681 ;
+C 166 ; WX 660 ; N florin ; B -46 -209 674 749 ;
+C 167 ; WX 600 ; N section ; B 36 -153 560 698 ;
+C 168 ; WX 660 ; N currency ; B 77 88 584 593 ;
+C 169 ; WX 240 ; N quotesingle ; B 42 379 178 698 ;
+C 170 ; WX 540 ; N quotedblleft ; B 82 439 449 698 ;
+C 171 ; WX 400 ; N guillemotleft ; B 34 101 360 457 ;
+C 172 ; WX 220 ; N guilsinglleft ; B 34 101 188 457 ;
+C 173 ; WX 220 ; N guilsinglright ; B 34 101 188 457 ;
+C 174 ; WX 740 ; N fi ; B 22 0 710 741 ;
+C 175 ; WX 740 ; N fl ; B 22 0 710 741 ;
+C 177 ; WX 500 ; N endash ; B -25 212 525 318 ;
+C 178 ; WX 440 ; N dagger ; B 33 -156 398 698 ;
+C 179 ; WX 380 ; N daggerdbl ; B 8 -156 380 698 ;
+C 180 ; WX 340 ; N periodcentered ; B 76 175 258 355 ;
+C 182 ; WX 800 ; N paragraph ; B 51 0 698 681 ;
+C 183 ; WX 460 ; N bullet ; B 60 170 404 511 ;
+C 184 ; WX 320 ; N quotesinglbase ; B 82 -114 242 144 ;
+C 185 ; WX 540 ; N quotedblbase ; B 82 -114 450 144 ;
+C 186 ; WX 540 ; N quotedblright ; B 82 440 449 698 ;
+C 187 ; WX 400 ; N guillemotright ; B 34 101 360 457 ;
+C 188 ; WX 1000 ; N ellipsis ; B 76 -8 924 172 ;
+C 189 ; WX 1360 ; N perthousand ; B 12 -8 1346 698 ;
+C 191 ; WX 660 ; N questiondown ; B 62 -191 609 515 ;
+C 193 ; WX 400 ; N grave ; B 68 547 327 730 ;
+C 194 ; WX 400 ; N acute ; B 68 547 327 731 ;
+C 195 ; WX 500 ; N circumflex ; B 68 555 430 731 ;
+C 196 ; WX 480 ; N tilde ; B 69 556 421 691 ;
+C 197 ; WX 460 ; N macron ; B 68 577 383 663 ;
+C 198 ; WX 500 ; N breve ; B 68 553 429 722 ;
+C 199 ; WX 320 ; N dotaccent ; B 68 536 259 730 ;
+C 200 ; WX 500 ; N dieresis ; B 68 560 441 698 ;
+C 202 ; WX 340 ; N ring ; B 68 552 275 755 ;
+C 203 ; WX 360 ; N cedilla ; B 68 -213 284 0 ;
+C 205 ; WX 440 ; N hungarumlaut ; B 68 554 365 741 ;
+C 206 ; WX 320 ; N ogonek ; B 68 -163 246 0 ;
+C 207 ; WX 500 ; N caron ; B 68 541 430 717 ;
+C 208 ; WX 1000 ; N emdash ; B -25 212 1025 318 ;
+C 225 ; WX 1140 ; N AE ; B -34 0 1149 681 ;
+C 227 ; WX 400 ; N ordfeminine ; B 27 383 396 698 ;
+C 232 ; WX 640 ; N Lslash ; B 20 0 668 681 ;
+C 233 ; WX 800 ; N Oslash ; B 35 -110 771 781 ;
+C 234 ; WX 1220 ; N OE ; B 35 -17 1219 698 ;
+C 235 ; WX 400 ; N ordmasculine ; B 17 383 383 698 ;
+C 241 ; WX 880 ; N ae ; B 28 -8 852 515 ;
+C 245 ; WX 360 ; N dotlessi ; B 22 0 335 502 ;
+C 248 ; WX 340 ; N lslash ; B 9 0 322 725 ;
+C 249 ; WX 620 ; N oslash ; B 31 -40 586 551 ;
+C 250 ; WX 940 ; N oe ; B 31 -8 908 515 ;
+C 251 ; WX 660 ; N germandbls ; B -61 -91 644 699 ;
+C -1 ; WX 580 ; N ecircumflex ; B 31 -8 548 731 ;
+C -1 ; WX 580 ; N edieresis ; B 31 -8 548 698 ;
+C -1 ; WX 580 ; N aacute ; B 28 -8 588 731 ;
+C -1 ; WX 740 ; N registered ; B 23 -17 723 698 ;
+C -1 ; WX 360 ; N icircumflex ; B -2 0 360 731 ;
+C -1 ; WX 660 ; N udieresis ; B 22 -8 653 698 ;
+C -1 ; WX 620 ; N ograve ; B 31 -8 585 730 ;
+C -1 ; WX 660 ; N uacute ; B 22 -8 653 731 ;
+C -1 ; WX 660 ; N ucircumflex ; B 22 -8 653 731 ;
+C -1 ; WX 720 ; N Aacute ; B -34 0 763 910 ;
+C -1 ; WX 360 ; N igrave ; B 22 0 335 730 ;
+C -1 ; WX 400 ; N Icircumflex ; B 18 0 380 910 ;
+C -1 ; WX 580 ; N ccedilla ; B 31 -213 550 515 ;
+C -1 ; WX 580 ; N adieresis ; B 28 -8 588 698 ;
+C -1 ; WX 720 ; N Ecircumflex ; B 20 0 724 910 ;
+C -1 ; WX 520 ; N scaron ; B 22 -8 492 717 ;
+C -1 ; WX 640 ; N thorn ; B 22 -212 611 725 ;
+C -1 ; WX 980 ; N trademark ; B 42 277 982 681 ;
+C -1 ; WX 580 ; N egrave ; B 31 -8 548 730 ;
+C -1 ; WX 396 ; N threesuperior ; B 5 269 391 698 ;
+C -1 ; WX 560 ; N zcaron ; B 22 0 547 717 ;
+C -1 ; WX 580 ; N atilde ; B 28 -8 588 691 ;
+C -1 ; WX 580 ; N aring ; B 28 -8 588 755 ;
+C -1 ; WX 620 ; N ocircumflex ; B 31 -8 585 731 ;
+C -1 ; WX 720 ; N Edieresis ; B 20 0 724 877 ;
+C -1 ; WX 990 ; N threequarters ; B 15 0 967 692 ;
+C -1 ; WX 620 ; N ydieresis ; B 6 -221 613 698 ;
+C -1 ; WX 620 ; N yacute ; B 6 -221 613 731 ;
+C -1 ; WX 360 ; N iacute ; B 22 0 335 731 ;
+C -1 ; WX 720 ; N Acircumflex ; B -34 0 763 910 ;
+C -1 ; WX 740 ; N Uacute ; B 15 -17 724 910 ;
+C -1 ; WX 580 ; N eacute ; B 31 -8 548 731 ;
+C -1 ; WX 800 ; N Ograve ; B 35 -17 769 909 ;
+C -1 ; WX 580 ; N agrave ; B 28 -8 588 730 ;
+C -1 ; WX 740 ; N Udieresis ; B 15 -17 724 877 ;
+C -1 ; WX 580 ; N acircumflex ; B 28 -8 588 731 ;
+C -1 ; WX 400 ; N Igrave ; B 20 0 379 909 ;
+C -1 ; WX 396 ; N twosuperior ; B 14 279 396 698 ;
+C -1 ; WX 740 ; N Ugrave ; B 15 -17 724 909 ;
+C -1 ; WX 990 ; N onequarter ; B 65 0 967 681 ;
+C -1 ; WX 740 ; N Ucircumflex ; B 15 -17 724 910 ;
+C -1 ; WX 660 ; N Scaron ; B 21 -17 639 896 ;
+C -1 ; WX 400 ; N Idieresis ; B 18 0 391 877 ;
+C -1 ; WX 360 ; N idieresis ; B -2 0 371 698 ;
+C -1 ; WX 720 ; N Egrave ; B 20 0 724 909 ;
+C -1 ; WX 800 ; N Oacute ; B 35 -17 769 910 ;
+C -1 ; WX 600 ; N divide ; B 51 9 555 521 ;
+C -1 ; WX 720 ; N Atilde ; B -34 0 763 870 ;
+C -1 ; WX 720 ; N Aring ; B -34 0 763 934 ;
+C -1 ; WX 800 ; N Odieresis ; B 35 -17 769 877 ;
+C -1 ; WX 720 ; N Adieresis ; B -34 0 763 877 ;
+C -1 ; WX 740 ; N Ntilde ; B 20 0 724 870 ;
+C -1 ; WX 640 ; N Zcaron ; B 6 0 635 896 ;
+C -1 ; WX 660 ; N Thorn ; B 20 0 658 681 ;
+C -1 ; WX 400 ; N Iacute ; B 20 0 379 910 ;
+C -1 ; WX 600 ; N plusminus ; B 51 0 555 514 ;
+C -1 ; WX 600 ; N multiply ; B 48 10 552 514 ;
+C -1 ; WX 720 ; N Eacute ; B 20 0 724 910 ;
+C -1 ; WX 700 ; N Ydieresis ; B -20 0 718 877 ;
+C -1 ; WX 396 ; N onesuperior ; B 65 279 345 687 ;
+C -1 ; WX 660 ; N ugrave ; B 22 -8 653 730 ;
+C -1 ; WX 600 ; N logicalnot ; B 51 129 555 421 ;
+C -1 ; WX 680 ; N ntilde ; B 22 0 652 691 ;
+C -1 ; WX 800 ; N Otilde ; B 35 -17 769 870 ;
+C -1 ; WX 620 ; N otilde ; B 31 -8 585 691 ;
+C -1 ; WX 740 ; N Ccedilla ; B 35 -213 724 698 ;
+C -1 ; WX 720 ; N Agrave ; B -34 0 763 909 ;
+C -1 ; WX 990 ; N onehalf ; B 65 0 980 681 ;
+C -1 ; WX 780 ; N Eth ; B 20 0 748 681 ;
+C -1 ; WX 400 ; N degree ; B 50 398 350 698 ;
+C -1 ; WX 700 ; N Yacute ; B -20 0 718 910 ;
+C -1 ; WX 800 ; N Ocircumflex ; B 35 -17 769 910 ;
+C -1 ; WX 620 ; N oacute ; B 31 -8 585 731 ;
+C -1 ; WX 660 ; N mu ; B 22 -221 653 502 ;
+C -1 ; WX 600 ; N minus ; B 51 207 555 323 ;
+C -1 ; WX 620 ; N eth ; B 31 -8 585 741 ;
+C -1 ; WX 620 ; N odieresis ; B 31 -8 585 698 ;
+C -1 ; WX 740 ; N copyright ; B 23 -17 723 698 ;
+C -1 ; WX 600 ; N brokenbar ; B 243 -175 362 675 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 90
+
+KPX A y -1
+KPX A w -9
+KPX A v -8
+KPX A Y -52
+KPX A W -20
+KPX A V -68
+KPX A T -40
+
+KPX F period -132
+KPX F comma -130
+KPX F A -59
+
+KPX L y 19
+KPX L Y -35
+KPX L W -41
+KPX L V -50
+KPX L T -4
+
+KPX P period -128
+KPX P comma -129
+KPX P A -46
+
+KPX R y -8
+KPX R Y -20
+KPX R W -24
+KPX R V -29
+KPX R T -4
+
+KPX T semicolon 5
+KPX T s -10
+KPX T r 27
+KPX T period -122
+KPX T o -28
+KPX T i 27
+KPX T hyphen -10
+KPX T e -29
+KPX T comma -122
+KPX T colon 7
+KPX T c -29
+KPX T a -24
+KPX T A -42
+
+KPX V y 12
+KPX V u -11
+KPX V semicolon -38
+KPX V r -15
+KPX V period -105
+KPX V o -79
+KPX V i 15
+KPX V hyphen -10
+KPX V e -80
+KPX V comma -103
+KPX V colon -37
+KPX V a -74
+KPX V A -88
+
+KPX W y 12
+KPX W u -11
+KPX W semicolon -38
+KPX W r -15
+KPX W period -105
+KPX W o -78
+KPX W i 15
+KPX W hyphen -10
+KPX W e -79
+KPX W comma -103
+KPX W colon -37
+KPX W a -73
+KPX W A -60
+
+KPX Y v 24
+KPX Y u -13
+KPX Y semicolon -34
+KPX Y q -66
+KPX Y period -105
+KPX Y p -23
+KPX Y o -66
+KPX Y i 2
+KPX Y hyphen -10
+KPX Y e -67
+KPX Y comma -103
+KPX Y colon -32
+KPX Y a -60
+KPX Y A -56
+
+KPX f f 21
+
+KPX r q -9
+KPX r period -102
+KPX r o -9
+KPX r n 20
+KPX r m 20
+KPX r hyphen -10
+KPX r h -23
+KPX r g -9
+KPX r f 20
+KPX r e -10
+KPX r d -10
+KPX r comma -101
+KPX r c -9
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 179 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 110 179 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 110 179 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 160 179 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 190 179 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 120 179 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 160 179 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 110 179 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 110 179 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 160 179 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 0 179 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -50 179 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -50 179 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 0 179 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 130 179 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 200 179 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 150 179 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 150 179 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 200 179 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 160 179 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 80 179 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 170 179 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 120 179 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 120 179 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 170 179 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 150 179 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 100 179 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 70 179 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 90 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 40 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 40 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 90 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 100 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 30 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 90 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 40 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 40 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 90 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -20 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -70 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -70 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -20 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 80 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 110 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 60 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 60 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 110 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 50 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 10 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 130 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 80 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 80 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 130 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 110 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 60 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 30 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkdi8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkdi8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..c2da47a2a4998aef6e2b9679b1be895855e74130
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkdi8a.afm
@@ -0,0 +1,417 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1992 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue Jan 21 16:12:43 1992
+Comment UniqueID 37832
+Comment VMusage 32139 39031
+FontName Bookman-DemiItalic
+FullName ITC Bookman Demi Italic
+FamilyName ITC Bookman
+Weight Demi
+ItalicAngle -10
+IsFixedPitch false
+FontBBox -231 -250 1333 941
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.004
+Notice Copyright (c) 1985, 1987, 1989, 1992 Adobe Systems Incorporated. All Rights Reserved.ITC Bookman is a registered trademark of International Typeface Corporation.
+EncodingScheme AdobeStandardEncoding
+CapHeight 681
+XHeight 515
+Ascender 732
+Descender -213
+StartCharMetrics 228
+C 32 ; WX 340 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 320 ; N exclam ; B 86 -8 366 698 ;
+C 34 ; WX 380 ; N quotedbl ; B 140 371 507 697 ;
+C 35 ; WX 680 ; N numbersign ; B 157 0 649 681 ;
+C 36 ; WX 680 ; N dollar ; B 45 -164 697 790 ;
+C 37 ; WX 880 ; N percent ; B 106 -17 899 698 ;
+C 38 ; WX 980 ; N ampersand ; B 48 -17 1016 698 ;
+C 39 ; WX 320 ; N quoteright ; B 171 420 349 698 ;
+C 40 ; WX 260 ; N parenleft ; B 31 -134 388 741 ;
+C 41 ; WX 260 ; N parenright ; B -35 -134 322 741 ;
+C 42 ; WX 460 ; N asterisk ; B 126 346 508 698 ;
+C 43 ; WX 600 ; N plus ; B 91 9 595 514 ;
+C 44 ; WX 340 ; N comma ; B 100 -124 298 185 ;
+C 45 ; WX 280 ; N hyphen ; B 59 218 319 313 ;
+C 46 ; WX 340 ; N period ; B 106 -8 296 177 ;
+C 47 ; WX 360 ; N slash ; B 9 -106 502 742 ;
+C 48 ; WX 680 ; N zero ; B 87 -17 703 698 ;
+C 49 ; WX 680 ; N one ; B 123 0 565 681 ;
+C 50 ; WX 680 ; N two ; B 67 0 674 698 ;
+C 51 ; WX 680 ; N three ; B 72 -17 683 698 ;
+C 52 ; WX 680 ; N four ; B 63 0 708 681 ;
+C 53 ; WX 680 ; N five ; B 78 -17 669 681 ;
+C 54 ; WX 680 ; N six ; B 88 -17 704 698 ;
+C 55 ; WX 680 ; N seven ; B 123 0 739 681 ;
+C 56 ; WX 680 ; N eight ; B 68 -17 686 698 ;
+C 57 ; WX 680 ; N nine ; B 71 -17 712 698 ;
+C 58 ; WX 340 ; N colon ; B 106 -8 356 515 ;
+C 59 ; WX 340 ; N semicolon ; B 100 -124 352 515 ;
+C 60 ; WX 620 ; N less ; B 79 -9 588 540 ;
+C 61 ; WX 600 ; N equal ; B 91 109 595 421 ;
+C 62 ; WX 620 ; N greater ; B 89 -9 598 540 ;
+C 63 ; WX 620 ; N question ; B 145 -8 668 698 ;
+C 64 ; WX 780 ; N at ; B 80 -17 790 698 ;
+C 65 ; WX 720 ; N A ; B -27 0 769 681 ;
+C 66 ; WX 720 ; N B ; B 14 0 762 681 ;
+C 67 ; WX 700 ; N C ; B 78 -17 754 698 ;
+C 68 ; WX 760 ; N D ; B 14 0 805 681 ;
+C 69 ; WX 720 ; N E ; B 14 0 777 681 ;
+C 70 ; WX 660 ; N F ; B 14 0 763 681 ;
+C 71 ; WX 760 ; N G ; B 77 -17 828 698 ;
+C 72 ; WX 800 ; N H ; B 14 0 910 681 ;
+C 73 ; WX 380 ; N I ; B 14 0 485 681 ;
+C 74 ; WX 620 ; N J ; B 8 -17 721 681 ;
+C 75 ; WX 780 ; N K ; B 14 0 879 681 ;
+C 76 ; WX 640 ; N L ; B 14 0 725 681 ;
+C 77 ; WX 860 ; N M ; B 14 0 970 681 ;
+C 78 ; WX 740 ; N N ; B 14 0 845 681 ;
+C 79 ; WX 760 ; N O ; B 78 -17 806 698 ;
+C 80 ; WX 640 ; N P ; B -6 0 724 681 ;
+C 81 ; WX 760 ; N Q ; B 37 -213 805 698 ;
+C 82 ; WX 740 ; N R ; B 14 0 765 681 ;
+C 83 ; WX 700 ; N S ; B 59 -17 731 698 ;
+C 84 ; WX 700 ; N T ; B 70 0 802 681 ;
+C 85 ; WX 740 ; N U ; B 112 -17 855 681 ;
+C 86 ; WX 660 ; N V ; B 72 0 819 681 ;
+C 87 ; WX 1000 ; N W ; B 72 0 1090 681 ;
+C 88 ; WX 740 ; N X ; B -7 0 835 681 ;
+C 89 ; WX 660 ; N Y ; B 72 0 817 681 ;
+C 90 ; WX 680 ; N Z ; B 23 0 740 681 ;
+C 91 ; WX 260 ; N bracketleft ; B 9 -118 374 741 ;
+C 92 ; WX 580 ; N backslash ; B 73 0 575 741 ;
+C 93 ; WX 260 ; N bracketright ; B -18 -118 347 741 ;
+C 94 ; WX 620 ; N asciicircum ; B 92 281 594 681 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 320 ; N quoteleft ; B 155 420 333 698 ;
+C 97 ; WX 680 ; N a ; B 84 -8 735 515 ;
+C 98 ; WX 600 ; N b ; B 57 -8 633 732 ;
+C 99 ; WX 560 ; N c ; B 58 -8 597 515 ;
+C 100 ; WX 680 ; N d ; B 60 -8 714 732 ;
+C 101 ; WX 560 ; N e ; B 59 -8 596 515 ;
+C 102 ; WX 420 ; N f ; B -192 -213 641 741 ; L i fi ; L l fl ;
+C 103 ; WX 620 ; N g ; B 21 -213 669 515 ;
+C 104 ; WX 700 ; N h ; B 93 -8 736 732 ;
+C 105 ; WX 380 ; N i ; B 83 -8 420 755 ;
+C 106 ; WX 320 ; N j ; B -160 -213 392 755 ;
+C 107 ; WX 700 ; N k ; B 97 -8 732 732 ;
+C 108 ; WX 380 ; N l ; B 109 -8 410 732 ;
+C 109 ; WX 960 ; N m ; B 83 -8 996 515 ;
+C 110 ; WX 680 ; N n ; B 83 -8 715 515 ;
+C 111 ; WX 600 ; N o ; B 59 -8 627 515 ;
+C 112 ; WX 660 ; N p ; B -24 -213 682 515 ;
+C 113 ; WX 620 ; N q ; B 60 -213 640 515 ;
+C 114 ; WX 500 ; N r ; B 84 0 582 515 ;
+C 115 ; WX 540 ; N s ; B 32 -8 573 515 ;
+C 116 ; WX 440 ; N t ; B 106 -8 488 658 ;
+C 117 ; WX 680 ; N u ; B 83 -8 720 507 ;
+C 118 ; WX 540 ; N v ; B 56 -8 572 515 ;
+C 119 ; WX 860 ; N w ; B 56 -8 891 515 ;
+C 120 ; WX 620 ; N x ; B 10 -8 654 515 ;
+C 121 ; WX 600 ; N y ; B 25 -213 642 507 ;
+C 122 ; WX 560 ; N z ; B 36 -8 586 515 ;
+C 123 ; WX 300 ; N braceleft ; B 49 -123 413 742 ;
+C 124 ; WX 620 ; N bar ; B 303 -250 422 750 ;
+C 125 ; WX 300 ; N braceright ; B -8 -114 356 751 ;
+C 126 ; WX 620 ; N asciitilde ; B 101 162 605 368 ;
+C 161 ; WX 320 ; N exclamdown ; B 64 -191 344 515 ;
+C 162 ; WX 680 ; N cent ; B 161 25 616 718 ;
+C 163 ; WX 680 ; N sterling ; B 0 -17 787 698 ;
+C 164 ; WX 120 ; N fraction ; B -144 0 382 681 ;
+C 165 ; WX 680 ; N yen ; B 92 0 782 681 ;
+C 166 ; WX 680 ; N florin ; B -28 -199 743 741 ;
+C 167 ; WX 620 ; N section ; B 46 -137 638 698 ;
+C 168 ; WX 680 ; N currency ; B 148 85 637 571 ;
+C 169 ; WX 180 ; N quotesingle ; B 126 370 295 696 ;
+C 170 ; WX 520 ; N quotedblleft ; B 156 420 545 698 ;
+C 171 ; WX 380 ; N guillemotleft ; B 62 84 406 503 ;
+C 172 ; WX 220 ; N guilsinglleft ; B 62 84 249 503 ;
+C 173 ; WX 220 ; N guilsinglright ; B 62 84 249 503 ;
+C 174 ; WX 820 ; N fi ; B -191 -213 850 741 ;
+C 175 ; WX 820 ; N fl ; B -191 -213 850 741 ;
+C 177 ; WX 500 ; N endash ; B 40 219 573 311 ;
+C 178 ; WX 420 ; N dagger ; B 89 -137 466 698 ;
+C 179 ; WX 420 ; N daggerdbl ; B 79 -137 486 698 ;
+C 180 ; WX 340 ; N periodcentered ; B 126 173 316 358 ;
+C 182 ; WX 680 ; N paragraph ; B 137 0 715 681 ;
+C 183 ; WX 360 ; N bullet ; B 60 170 404 511 ;
+C 184 ; WX 300 ; N quotesinglbase ; B 106 -112 284 166 ;
+C 185 ; WX 520 ; N quotedblbase ; B 106 -112 495 166 ;
+C 186 ; WX 520 ; N quotedblright ; B 171 420 560 698 ;
+C 187 ; WX 380 ; N guillemotright ; B 62 84 406 503 ;
+C 188 ; WX 1000 ; N ellipsis ; B 86 -8 942 177 ;
+C 189 ; WX 1360 ; N perthousand ; B 106 -17 1333 698 ;
+C 191 ; WX 620 ; N questiondown ; B 83 -189 606 515 ;
+C 193 ; WX 380 ; N grave ; B 193 566 424 771 ;
+C 194 ; WX 340 ; N acute ; B 176 566 407 771 ;
+C 195 ; WX 480 ; N circumflex ; B 183 582 523 749 ;
+C 196 ; WX 480 ; N tilde ; B 178 587 533 709 ;
+C 197 ; WX 480 ; N macron ; B 177 603 531 691 ;
+C 198 ; WX 460 ; N breve ; B 177 577 516 707 ;
+C 199 ; WX 380 ; N dotaccent ; B 180 570 345 734 ;
+C 200 ; WX 520 ; N dieresis ; B 180 570 569 734 ;
+C 202 ; WX 360 ; N ring ; B 185 558 406 775 ;
+C 203 ; WX 360 ; N cedilla ; B 68 -220 289 -8 ;
+C 205 ; WX 560 ; N hungarumlaut ; B 181 560 616 775 ;
+C 206 ; WX 320 ; N ogonek ; B 68 -182 253 0 ;
+C 207 ; WX 480 ; N caron ; B 183 582 523 749 ;
+C 208 ; WX 1000 ; N emdash ; B 40 219 1073 311 ;
+C 225 ; WX 1140 ; N AE ; B -27 0 1207 681 ;
+C 227 ; WX 440 ; N ordfeminine ; B 118 400 495 685 ;
+C 232 ; WX 640 ; N Lslash ; B 14 0 724 681 ;
+C 233 ; WX 760 ; N Oslash ; B 21 -29 847 725 ;
+C 234 ; WX 1180 ; N OE ; B 94 -17 1245 698 ;
+C 235 ; WX 440 ; N ordmasculine ; B 127 400 455 685 ;
+C 241 ; WX 880 ; N ae ; B 39 -8 913 515 ;
+C 245 ; WX 380 ; N dotlessi ; B 83 -8 420 507 ;
+C 248 ; WX 380 ; N lslash ; B 63 -8 412 732 ;
+C 249 ; WX 600 ; N oslash ; B 17 -54 661 571 ;
+C 250 ; WX 920 ; N oe ; B 48 -8 961 515 ;
+C 251 ; WX 660 ; N germandbls ; B -231 -213 702 741 ;
+C -1 ; WX 560 ; N ecircumflex ; B 59 -8 596 749 ;
+C -1 ; WX 560 ; N edieresis ; B 59 -8 596 734 ;
+C -1 ; WX 680 ; N aacute ; B 84 -8 735 771 ;
+C -1 ; WX 780 ; N registered ; B 83 -17 783 698 ;
+C -1 ; WX 380 ; N icircumflex ; B 83 -8 433 749 ;
+C -1 ; WX 680 ; N udieresis ; B 83 -8 720 734 ;
+C -1 ; WX 600 ; N ograve ; B 59 -8 627 771 ;
+C -1 ; WX 680 ; N uacute ; B 83 -8 720 771 ;
+C -1 ; WX 680 ; N ucircumflex ; B 83 -8 720 749 ;
+C -1 ; WX 720 ; N Aacute ; B -27 0 769 937 ;
+C -1 ; WX 380 ; N igrave ; B 83 -8 424 771 ;
+C -1 ; WX 380 ; N Icircumflex ; B 14 0 493 915 ;
+C -1 ; WX 560 ; N ccedilla ; B 58 -220 597 515 ;
+C -1 ; WX 680 ; N adieresis ; B 84 -8 735 734 ;
+C -1 ; WX 720 ; N Ecircumflex ; B 14 0 777 915 ;
+C -1 ; WX 540 ; N scaron ; B 32 -8 573 749 ;
+C -1 ; WX 660 ; N thorn ; B -24 -213 682 732 ;
+C -1 ; WX 940 ; N trademark ; B 42 277 982 681 ;
+C -1 ; WX 560 ; N egrave ; B 59 -8 596 771 ;
+C -1 ; WX 408 ; N threesuperior ; B 86 269 483 698 ;
+C -1 ; WX 560 ; N zcaron ; B 36 -8 586 749 ;
+C -1 ; WX 680 ; N atilde ; B 84 -8 735 709 ;
+C -1 ; WX 680 ; N aring ; B 84 -8 735 775 ;
+C -1 ; WX 600 ; N ocircumflex ; B 59 -8 627 749 ;
+C -1 ; WX 720 ; N Edieresis ; B 14 0 777 900 ;
+C -1 ; WX 1020 ; N threequarters ; B 86 0 1054 691 ;
+C -1 ; WX 600 ; N ydieresis ; B 25 -213 642 734 ;
+C -1 ; WX 600 ; N yacute ; B 25 -213 642 771 ;
+C -1 ; WX 380 ; N iacute ; B 83 -8 420 771 ;
+C -1 ; WX 720 ; N Acircumflex ; B -27 0 769 915 ;
+C -1 ; WX 740 ; N Uacute ; B 112 -17 855 937 ;
+C -1 ; WX 560 ; N eacute ; B 59 -8 596 771 ;
+C -1 ; WX 760 ; N Ograve ; B 78 -17 806 937 ;
+C -1 ; WX 680 ; N agrave ; B 84 -8 735 771 ;
+C -1 ; WX 740 ; N Udieresis ; B 112 -17 855 900 ;
+C -1 ; WX 680 ; N acircumflex ; B 84 -8 735 749 ;
+C -1 ; WX 380 ; N Igrave ; B 14 0 485 937 ;
+C -1 ; WX 408 ; N twosuperior ; B 91 279 485 698 ;
+C -1 ; WX 740 ; N Ugrave ; B 112 -17 855 937 ;
+C -1 ; WX 1020 ; N onequarter ; B 118 0 1054 681 ;
+C -1 ; WX 740 ; N Ucircumflex ; B 112 -17 855 915 ;
+C -1 ; WX 700 ; N Scaron ; B 59 -17 731 915 ;
+C -1 ; WX 380 ; N Idieresis ; B 14 0 499 900 ;
+C -1 ; WX 380 ; N idieresis ; B 83 -8 479 734 ;
+C -1 ; WX 720 ; N Egrave ; B 14 0 777 937 ;
+C -1 ; WX 760 ; N Oacute ; B 78 -17 806 937 ;
+C -1 ; WX 600 ; N divide ; B 91 9 595 521 ;
+C -1 ; WX 720 ; N Atilde ; B -27 0 769 875 ;
+C -1 ; WX 720 ; N Aring ; B -27 0 769 941 ;
+C -1 ; WX 760 ; N Odieresis ; B 78 -17 806 900 ;
+C -1 ; WX 720 ; N Adieresis ; B -27 0 769 900 ;
+C -1 ; WX 740 ; N Ntilde ; B 14 0 845 875 ;
+C -1 ; WX 680 ; N Zcaron ; B 23 0 740 915 ;
+C -1 ; WX 640 ; N Thorn ; B -6 0 701 681 ;
+C -1 ; WX 380 ; N Iacute ; B 14 0 485 937 ;
+C -1 ; WX 600 ; N plusminus ; B 91 0 595 514 ;
+C -1 ; WX 600 ; N multiply ; B 91 10 595 514 ;
+C -1 ; WX 720 ; N Eacute ; B 14 0 777 937 ;
+C -1 ; WX 660 ; N Ydieresis ; B 72 0 817 900 ;
+C -1 ; WX 408 ; N onesuperior ; B 118 279 406 688 ;
+C -1 ; WX 680 ; N ugrave ; B 83 -8 720 771 ;
+C -1 ; WX 620 ; N logicalnot ; B 81 129 585 421 ;
+C -1 ; WX 680 ; N ntilde ; B 83 -8 715 709 ;
+C -1 ; WX 760 ; N Otilde ; B 78 -17 806 875 ;
+C -1 ; WX 600 ; N otilde ; B 59 -8 627 709 ;
+C -1 ; WX 700 ; N Ccedilla ; B 78 -220 754 698 ;
+C -1 ; WX 720 ; N Agrave ; B -27 0 769 937 ;
+C -1 ; WX 1020 ; N onehalf ; B 118 0 1036 681 ;
+C -1 ; WX 760 ; N Eth ; B 14 0 805 681 ;
+C -1 ; WX 400 ; N degree ; B 130 398 430 698 ;
+C -1 ; WX 660 ; N Yacute ; B 72 0 817 937 ;
+C -1 ; WX 760 ; N Ocircumflex ; B 78 -17 806 915 ;
+C -1 ; WX 600 ; N oacute ; B 59 -8 627 771 ;
+C -1 ; WX 680 ; N mu ; B 54 -213 720 507 ;
+C -1 ; WX 600 ; N minus ; B 91 207 595 323 ;
+C -1 ; WX 600 ; N eth ; B 59 -8 662 741 ;
+C -1 ; WX 600 ; N odieresis ; B 59 -8 627 734 ;
+C -1 ; WX 780 ; N copyright ; B 83 -17 783 698 ;
+C -1 ; WX 620 ; N brokenbar ; B 303 -175 422 675 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 92
+
+KPX A y 20
+KPX A w 20
+KPX A v 20
+KPX A Y -25
+KPX A W -35
+KPX A V -40
+KPX A T -17
+
+KPX F period -105
+KPX F comma -98
+KPX F A -35
+
+KPX L y 62
+KPX L Y -5
+KPX L W -15
+KPX L V -19
+KPX L T -26
+
+KPX P period -105
+KPX P comma -98
+KPX P A -31
+
+KPX R y 27
+KPX R Y 4
+KPX R W -4
+KPX R V -8
+KPX R T -3
+
+KPX T y 56
+KPX T w 69
+KPX T u 42
+KPX T semicolon 31
+KPX T s -1
+KPX T r 41
+KPX T period -107
+KPX T o -5
+KPX T i 42
+KPX T hyphen -20
+KPX T e -10
+KPX T comma -100
+KPX T colon 26
+KPX T c -8
+KPX T a -8
+KPX T A -42
+
+KPX V y 17
+KPX V u -1
+KPX V semicolon -22
+KPX V r 2
+KPX V period -115
+KPX V o -50
+KPX V i 32
+KPX V hyphen -20
+KPX V e -50
+KPX V comma -137
+KPX V colon -28
+KPX V a -50
+KPX V A -50
+
+KPX W y -51
+KPX W u -69
+KPX W semicolon -81
+KPX W r -66
+KPX W period -183
+KPX W o -100
+KPX W i -36
+KPX W hyphen -22
+KPX W e -100
+KPX W comma -201
+KPX W colon -86
+KPX W a -100
+KPX W A -77
+
+KPX Y v 26
+KPX Y u -1
+KPX Y semicolon -4
+KPX Y q -43
+KPX Y period -113
+KPX Y o -41
+KPX Y i 20
+KPX Y hyphen -20
+KPX Y e -46
+KPX Y comma -106
+KPX Y colon -9
+KPX Y a -45
+KPX Y A -30
+
+KPX f f 10
+
+KPX r q -3
+KPX r period -120
+KPX r o -1
+KPX r n 39
+KPX r m 39
+KPX r hyphen -20
+KPX r h -35
+KPX r g -23
+KPX r f 42
+KPX r e -6
+KPX r d -3
+KPX r comma -113
+KPX r c -5
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 190 166 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 120 166 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 100 166 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 170 166 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 200 166 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 120 166 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 190 166 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 120 166 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 100 166 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 170 166 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 20 166 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -30 166 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -70 166 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 0 166 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 130 166 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 210 166 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 140 166 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 140 166 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 190 166 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 140 166 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 110 166 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 200 166 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 130 166 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 130 166 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 180 166 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 160 166 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 70 166 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 100 166 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 170 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 100 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 80 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 150 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 160 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 100 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 110 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 60 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 20 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 90 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute 0 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -90 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -90 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave 0 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 60 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 130 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 60 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 40 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 110 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 60 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 30 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 170 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 100 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 80 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 150 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 130 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 40 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 40 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkl8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkl8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..8b79ea71060d631c9d18175924029b583c3fd9e1
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkl8a.afm
@@ -0,0 +1,407 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1992 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue Jan 21 16:15:53 1992
+Comment UniqueID 37833
+Comment VMusage 32321 39213
+FontName Bookman-Light
+FullName ITC Bookman Light
+FamilyName ITC Bookman
+Weight Light
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -188 -251 1266 908
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.004
+Notice Copyright (c) 1985, 1987, 1989, 1992 Adobe Systems Incorporated. All Rights Reserved.ITC Bookman is a registered trademark of International Typeface Corporation.
+EncodingScheme AdobeStandardEncoding
+CapHeight 681
+XHeight 484
+Ascender 717
+Descender -228
+StartCharMetrics 228
+C 32 ; WX 320 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 300 ; N exclam ; B 75 -8 219 698 ;
+C 34 ; WX 380 ; N quotedbl ; B 56 458 323 698 ;
+C 35 ; WX 620 ; N numbersign ; B 65 0 556 681 ;
+C 36 ; WX 620 ; N dollar ; B 34 -109 593 791 ;
+C 37 ; WX 900 ; N percent ; B 22 -8 873 698 ;
+C 38 ; WX 800 ; N ampersand ; B 45 -17 787 698 ;
+C 39 ; WX 220 ; N quoteright ; B 46 480 178 698 ;
+C 40 ; WX 300 ; N parenleft ; B 76 -145 278 727 ;
+C 41 ; WX 300 ; N parenright ; B 17 -146 219 727 ;
+C 42 ; WX 440 ; N asterisk ; B 54 325 391 698 ;
+C 43 ; WX 600 ; N plus ; B 51 8 555 513 ;
+C 44 ; WX 320 ; N comma ; B 90 -114 223 114 ;
+C 45 ; WX 400 ; N hyphen ; B 50 232 350 292 ;
+C 46 ; WX 320 ; N period ; B 92 -8 220 123 ;
+C 47 ; WX 600 ; N slash ; B 74 -149 532 717 ;
+C 48 ; WX 620 ; N zero ; B 40 -17 586 698 ;
+C 49 ; WX 620 ; N one ; B 160 0 501 681 ;
+C 50 ; WX 620 ; N two ; B 42 0 576 698 ;
+C 51 ; WX 620 ; N three ; B 40 -17 576 698 ;
+C 52 ; WX 620 ; N four ; B 25 0 600 681 ;
+C 53 ; WX 620 ; N five ; B 60 -17 584 717 ;
+C 54 ; WX 620 ; N six ; B 45 -17 590 698 ;
+C 55 ; WX 620 ; N seven ; B 60 0 586 681 ;
+C 56 ; WX 620 ; N eight ; B 44 -17 583 698 ;
+C 57 ; WX 620 ; N nine ; B 37 -17 576 698 ;
+C 58 ; WX 320 ; N colon ; B 92 -8 220 494 ;
+C 59 ; WX 320 ; N semicolon ; B 90 -114 223 494 ;
+C 60 ; WX 600 ; N less ; B 49 -2 558 526 ;
+C 61 ; WX 600 ; N equal ; B 51 126 555 398 ;
+C 62 ; WX 600 ; N greater ; B 48 -2 557 526 ;
+C 63 ; WX 540 ; N question ; B 27 -8 514 698 ;
+C 64 ; WX 820 ; N at ; B 55 -17 755 698 ;
+C 65 ; WX 680 ; N A ; B -37 0 714 681 ;
+C 66 ; WX 740 ; N B ; B 31 0 702 681 ;
+C 67 ; WX 740 ; N C ; B 44 -17 702 698 ;
+C 68 ; WX 800 ; N D ; B 31 0 752 681 ;
+C 69 ; WX 720 ; N E ; B 31 0 705 681 ;
+C 70 ; WX 640 ; N F ; B 31 0 654 681 ;
+C 71 ; WX 800 ; N G ; B 44 -17 778 698 ;
+C 72 ; WX 800 ; N H ; B 31 0 769 681 ;
+C 73 ; WX 340 ; N I ; B 31 0 301 681 ;
+C 74 ; WX 600 ; N J ; B -23 -17 567 681 ;
+C 75 ; WX 720 ; N K ; B 31 0 750 681 ;
+C 76 ; WX 600 ; N L ; B 31 0 629 681 ;
+C 77 ; WX 920 ; N M ; B 26 0 894 681 ;
+C 78 ; WX 740 ; N N ; B 26 0 722 681 ;
+C 79 ; WX 800 ; N O ; B 44 -17 758 698 ;
+C 80 ; WX 620 ; N P ; B 31 0 613 681 ;
+C 81 ; WX 820 ; N Q ; B 44 -189 769 698 ;
+C 82 ; WX 720 ; N R ; B 31 0 757 681 ;
+C 83 ; WX 660 ; N S ; B 28 -17 634 698 ;
+C 84 ; WX 620 ; N T ; B -37 0 656 681 ;
+C 85 ; WX 780 ; N U ; B 25 -17 754 681 ;
+C 86 ; WX 700 ; N V ; B -30 0 725 681 ;
+C 87 ; WX 960 ; N W ; B -30 0 984 681 ;
+C 88 ; WX 720 ; N X ; B -30 0 755 681 ;
+C 89 ; WX 640 ; N Y ; B -30 0 666 681 ;
+C 90 ; WX 640 ; N Z ; B 10 0 656 681 ;
+C 91 ; WX 300 ; N bracketleft ; B 92 -136 258 717 ;
+C 92 ; WX 600 ; N backslash ; B 74 0 532 717 ;
+C 93 ; WX 300 ; N bracketright ; B 41 -136 207 717 ;
+C 94 ; WX 600 ; N asciicircum ; B 52 276 554 681 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 220 ; N quoteleft ; B 46 479 178 698 ;
+C 97 ; WX 580 ; N a ; B 35 -8 587 494 ;
+C 98 ; WX 620 ; N b ; B -2 -8 582 717 ;
+C 99 ; WX 520 ; N c ; B 37 -8 498 494 ;
+C 100 ; WX 620 ; N d ; B 37 -8 591 717 ;
+C 101 ; WX 520 ; N e ; B 37 -8 491 494 ;
+C 102 ; WX 320 ; N f ; B 20 0 414 734 ; L i fi ; L l fl ;
+C 103 ; WX 540 ; N g ; B 17 -243 542 567 ;
+C 104 ; WX 660 ; N h ; B 20 0 643 717 ;
+C 105 ; WX 300 ; N i ; B 20 0 288 654 ;
+C 106 ; WX 300 ; N j ; B -109 -251 214 654 ;
+C 107 ; WX 620 ; N k ; B 20 0 628 717 ;
+C 108 ; WX 300 ; N l ; B 20 0 286 717 ;
+C 109 ; WX 940 ; N m ; B 17 0 928 494 ;
+C 110 ; WX 660 ; N n ; B 20 0 649 494 ;
+C 111 ; WX 560 ; N o ; B 37 -8 526 494 ;
+C 112 ; WX 620 ; N p ; B 20 -228 583 494 ;
+C 113 ; WX 580 ; N q ; B 37 -228 589 494 ;
+C 114 ; WX 440 ; N r ; B 20 0 447 494 ;
+C 115 ; WX 520 ; N s ; B 40 -8 487 494 ;
+C 116 ; WX 380 ; N t ; B 20 -8 388 667 ;
+C 117 ; WX 680 ; N u ; B 20 -8 653 484 ;
+C 118 ; WX 520 ; N v ; B -23 0 534 484 ;
+C 119 ; WX 780 ; N w ; B -19 0 804 484 ;
+C 120 ; WX 560 ; N x ; B -16 0 576 484 ;
+C 121 ; WX 540 ; N y ; B -23 -236 549 484 ;
+C 122 ; WX 480 ; N z ; B 7 0 476 484 ;
+C 123 ; WX 280 ; N braceleft ; B 21 -136 260 717 ;
+C 124 ; WX 600 ; N bar ; B 264 -250 342 750 ;
+C 125 ; WX 280 ; N braceright ; B 21 -136 260 717 ;
+C 126 ; WX 600 ; N asciitilde ; B 52 173 556 352 ;
+C 161 ; WX 300 ; N exclamdown ; B 75 -214 219 494 ;
+C 162 ; WX 620 ; N cent ; B 116 20 511 651 ;
+C 163 ; WX 620 ; N sterling ; B 8 -17 631 698 ;
+C 164 ; WX 140 ; N fraction ; B -188 0 335 681 ;
+C 165 ; WX 620 ; N yen ; B -22 0 647 681 ;
+C 166 ; WX 620 ; N florin ; B -29 -155 633 749 ;
+C 167 ; WX 520 ; N section ; B 33 -178 486 698 ;
+C 168 ; WX 620 ; N currency ; B 58 89 563 591 ;
+C 169 ; WX 220 ; N quotesingle ; B 67 458 153 698 ;
+C 170 ; WX 400 ; N quotedblleft ; B 46 479 348 698 ;
+C 171 ; WX 360 ; N guillemotleft ; B 51 89 312 437 ;
+C 172 ; WX 240 ; N guilsinglleft ; B 51 89 189 437 ;
+C 173 ; WX 240 ; N guilsinglright ; B 51 89 189 437 ;
+C 174 ; WX 620 ; N fi ; B 20 0 608 734 ;
+C 175 ; WX 620 ; N fl ; B 20 0 606 734 ;
+C 177 ; WX 500 ; N endash ; B -15 232 515 292 ;
+C 178 ; WX 540 ; N dagger ; B 79 -156 455 698 ;
+C 179 ; WX 540 ; N daggerdbl ; B 79 -156 455 698 ;
+C 180 ; WX 320 ; N periodcentered ; B 92 196 220 327 ;
+C 182 ; WX 600 ; N paragraph ; B 14 0 577 681 ;
+C 183 ; WX 460 ; N bullet ; B 60 170 404 511 ;
+C 184 ; WX 220 ; N quotesinglbase ; B 46 -108 178 110 ;
+C 185 ; WX 400 ; N quotedblbase ; B 46 -108 348 110 ;
+C 186 ; WX 400 ; N quotedblright ; B 46 480 348 698 ;
+C 187 ; WX 360 ; N guillemotright ; B 51 89 312 437 ;
+C 188 ; WX 1000 ; N ellipsis ; B 101 -8 898 123 ;
+C 189 ; WX 1280 ; N perthousand ; B 22 -8 1266 698 ;
+C 191 ; WX 540 ; N questiondown ; B 23 -217 510 494 ;
+C 193 ; WX 340 ; N grave ; B 68 571 274 689 ;
+C 194 ; WX 340 ; N acute ; B 68 571 274 689 ;
+C 195 ; WX 420 ; N circumflex ; B 68 567 352 685 ;
+C 196 ; WX 440 ; N tilde ; B 68 572 375 661 ;
+C 197 ; WX 440 ; N macron ; B 68 587 364 635 ;
+C 198 ; WX 460 ; N breve ; B 68 568 396 687 ;
+C 199 ; WX 260 ; N dotaccent ; B 68 552 186 672 ;
+C 200 ; WX 420 ; N dieresis ; B 68 552 349 674 ;
+C 202 ; WX 320 ; N ring ; B 68 546 252 731 ;
+C 203 ; WX 320 ; N cedilla ; B 68 -200 257 0 ;
+C 205 ; WX 380 ; N hungarumlaut ; B 68 538 311 698 ;
+C 206 ; WX 320 ; N ogonek ; B 68 -145 245 0 ;
+C 207 ; WX 420 ; N caron ; B 68 554 352 672 ;
+C 208 ; WX 1000 ; N emdash ; B -15 232 1015 292 ;
+C 225 ; WX 1260 ; N AE ; B -36 0 1250 681 ;
+C 227 ; WX 420 ; N ordfeminine ; B 49 395 393 698 ;
+C 232 ; WX 600 ; N Lslash ; B 31 0 629 681 ;
+C 233 ; WX 800 ; N Oslash ; B 44 -53 758 733 ;
+C 234 ; WX 1240 ; N OE ; B 44 -17 1214 698 ;
+C 235 ; WX 420 ; N ordmasculine ; B 56 394 361 698 ;
+C 241 ; WX 860 ; N ae ; B 35 -8 832 494 ;
+C 245 ; WX 300 ; N dotlessi ; B 20 0 288 484 ;
+C 248 ; WX 320 ; N lslash ; B 20 0 291 717 ;
+C 249 ; WX 560 ; N oslash ; B 37 -40 526 534 ;
+C 250 ; WX 900 ; N oe ; B 37 -8 876 494 ;
+C 251 ; WX 660 ; N germandbls ; B -109 -110 614 698 ;
+C -1 ; WX 520 ; N ecircumflex ; B 37 -8 491 685 ;
+C -1 ; WX 520 ; N edieresis ; B 37 -8 491 674 ;
+C -1 ; WX 580 ; N aacute ; B 35 -8 587 689 ;
+C -1 ; WX 740 ; N registered ; B 23 -17 723 698 ;
+C -1 ; WX 300 ; N icircumflex ; B 8 0 292 685 ;
+C -1 ; WX 680 ; N udieresis ; B 20 -8 653 674 ;
+C -1 ; WX 560 ; N ograve ; B 37 -8 526 689 ;
+C -1 ; WX 680 ; N uacute ; B 20 -8 653 689 ;
+C -1 ; WX 680 ; N ucircumflex ; B 20 -8 653 685 ;
+C -1 ; WX 680 ; N Aacute ; B -37 0 714 866 ;
+C -1 ; WX 300 ; N igrave ; B 20 0 288 689 ;
+C -1 ; WX 340 ; N Icircumflex ; B 28 0 312 862 ;
+C -1 ; WX 520 ; N ccedilla ; B 37 -200 498 494 ;
+C -1 ; WX 580 ; N adieresis ; B 35 -8 587 674 ;
+C -1 ; WX 720 ; N Ecircumflex ; B 31 0 705 862 ;
+C -1 ; WX 520 ; N scaron ; B 40 -8 487 672 ;
+C -1 ; WX 620 ; N thorn ; B 20 -228 583 717 ;
+C -1 ; WX 980 ; N trademark ; B 34 277 930 681 ;
+C -1 ; WX 520 ; N egrave ; B 37 -8 491 689 ;
+C -1 ; WX 372 ; N threesuperior ; B 12 269 360 698 ;
+C -1 ; WX 480 ; N zcaron ; B 7 0 476 672 ;
+C -1 ; WX 580 ; N atilde ; B 35 -8 587 661 ;
+C -1 ; WX 580 ; N aring ; B 35 -8 587 731 ;
+C -1 ; WX 560 ; N ocircumflex ; B 37 -8 526 685 ;
+C -1 ; WX 720 ; N Edieresis ; B 31 0 705 851 ;
+C -1 ; WX 930 ; N threequarters ; B 52 0 889 691 ;
+C -1 ; WX 540 ; N ydieresis ; B -23 -236 549 674 ;
+C -1 ; WX 540 ; N yacute ; B -23 -236 549 689 ;
+C -1 ; WX 300 ; N iacute ; B 20 0 288 689 ;
+C -1 ; WX 680 ; N Acircumflex ; B -37 0 714 862 ;
+C -1 ; WX 780 ; N Uacute ; B 25 -17 754 866 ;
+C -1 ; WX 520 ; N eacute ; B 37 -8 491 689 ;
+C -1 ; WX 800 ; N Ograve ; B 44 -17 758 866 ;
+C -1 ; WX 580 ; N agrave ; B 35 -8 587 689 ;
+C -1 ; WX 780 ; N Udieresis ; B 25 -17 754 851 ;
+C -1 ; WX 580 ; N acircumflex ; B 35 -8 587 685 ;
+C -1 ; WX 340 ; N Igrave ; B 31 0 301 866 ;
+C -1 ; WX 372 ; N twosuperior ; B 20 279 367 698 ;
+C -1 ; WX 780 ; N Ugrave ; B 25 -17 754 866 ;
+C -1 ; WX 930 ; N onequarter ; B 80 0 869 681 ;
+C -1 ; WX 780 ; N Ucircumflex ; B 25 -17 754 862 ;
+C -1 ; WX 660 ; N Scaron ; B 28 -17 634 849 ;
+C -1 ; WX 340 ; N Idieresis ; B 28 0 309 851 ;
+C -1 ; WX 300 ; N idieresis ; B 8 0 289 674 ;
+C -1 ; WX 720 ; N Egrave ; B 31 0 705 866 ;
+C -1 ; WX 800 ; N Oacute ; B 44 -17 758 866 ;
+C -1 ; WX 600 ; N divide ; B 51 10 555 514 ;
+C -1 ; WX 680 ; N Atilde ; B -37 0 714 838 ;
+C -1 ; WX 680 ; N Aring ; B -37 0 714 908 ;
+C -1 ; WX 800 ; N Odieresis ; B 44 -17 758 851 ;
+C -1 ; WX 680 ; N Adieresis ; B -37 0 714 851 ;
+C -1 ; WX 740 ; N Ntilde ; B 26 0 722 838 ;
+C -1 ; WX 640 ; N Zcaron ; B 10 0 656 849 ;
+C -1 ; WX 620 ; N Thorn ; B 31 0 613 681 ;
+C -1 ; WX 340 ; N Iacute ; B 31 0 301 866 ;
+C -1 ; WX 600 ; N plusminus ; B 51 0 555 513 ;
+C -1 ; WX 600 ; N multiply ; B 51 9 555 513 ;
+C -1 ; WX 720 ; N Eacute ; B 31 0 705 866 ;
+C -1 ; WX 640 ; N Ydieresis ; B -30 0 666 851 ;
+C -1 ; WX 372 ; N onesuperior ; B 80 279 302 688 ;
+C -1 ; WX 680 ; N ugrave ; B 20 -8 653 689 ;
+C -1 ; WX 600 ; N logicalnot ; B 51 128 555 398 ;
+C -1 ; WX 660 ; N ntilde ; B 20 0 649 661 ;
+C -1 ; WX 800 ; N Otilde ; B 44 -17 758 838 ;
+C -1 ; WX 560 ; N otilde ; B 37 -8 526 661 ;
+C -1 ; WX 740 ; N Ccedilla ; B 44 -200 702 698 ;
+C -1 ; WX 680 ; N Agrave ; B -37 0 714 866 ;
+C -1 ; WX 930 ; N onehalf ; B 80 0 885 681 ;
+C -1 ; WX 800 ; N Eth ; B 31 0 752 681 ;
+C -1 ; WX 400 ; N degree ; B 50 398 350 698 ;
+C -1 ; WX 640 ; N Yacute ; B -30 0 666 866 ;
+C -1 ; WX 800 ; N Ocircumflex ; B 44 -17 758 862 ;
+C -1 ; WX 560 ; N oacute ; B 37 -8 526 689 ;
+C -1 ; WX 680 ; N mu ; B 20 -251 653 484 ;
+C -1 ; WX 600 ; N minus ; B 51 224 555 300 ;
+C -1 ; WX 560 ; N eth ; B 37 -8 526 734 ;
+C -1 ; WX 560 ; N odieresis ; B 37 -8 526 674 ;
+C -1 ; WX 740 ; N copyright ; B 24 -17 724 698 ;
+C -1 ; WX 600 ; N brokenbar ; B 264 -175 342 675 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 82
+
+KPX A y 32
+KPX A w 4
+KPX A v 7
+KPX A Y -35
+KPX A W -40
+KPX A V -56
+KPX A T 1
+
+KPX F period -46
+KPX F comma -41
+KPX F A -21
+
+KPX L y 79
+KPX L Y 13
+KPX L W 1
+KPX L V -4
+KPX L T 28
+
+KPX P period -60
+KPX P comma -55
+KPX P A -8
+
+KPX R y 59
+KPX R Y 26
+KPX R W 13
+KPX R V 8
+KPX R T 71
+
+KPX T s 16
+KPX T r 38
+KPX T period -33
+KPX T o 15
+KPX T i 42
+KPX T hyphen 90
+KPX T e 13
+KPX T comma -28
+KPX T c 14
+KPX T a 17
+KPX T A 1
+
+KPX V y 15
+KPX V u -38
+KPX V r -41
+KPX V period -40
+KPX V o -71
+KPX V i -20
+KPX V hyphen 11
+KPX V e -72
+KPX V comma -34
+KPX V a -69
+KPX V A -66
+
+KPX W y 15
+KPX W u -38
+KPX W r -41
+KPX W period -40
+KPX W o -68
+KPX W i -20
+KPX W hyphen 11
+KPX W e -69
+KPX W comma -34
+KPX W a -66
+KPX W A -64
+
+KPX Y v 15
+KPX Y u -38
+KPX Y q -55
+KPX Y period -40
+KPX Y p -31
+KPX Y o -57
+KPX Y i -37
+KPX Y hyphen 11
+KPX Y e -58
+KPX Y comma -34
+KPX Y a -54
+KPX Y A -53
+
+KPX f f 29
+
+KPX r q 9
+KPX r period -64
+KPX r o 8
+KPX r n 31
+KPX r m 31
+KPX r hyphen 70
+KPX r h -21
+KPX r g -4
+KPX r f 33
+KPX r e 7
+KPX r d 7
+KPX r comma -58
+KPX r c 7
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 200 177 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 130 177 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 130 177 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 140 177 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 180 177 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 120 177 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 220 177 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 150 177 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 150 177 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 160 177 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 20 177 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -40 177 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -40 177 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -20 177 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 150 177 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 260 177 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 190 177 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 190 177 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 200 177 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 180 177 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 120 177 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 250 177 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 180 177 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 180 177 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 190 177 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 150 177 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 110 177 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 110 177 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 120 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 80 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 80 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 120 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 130 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 70 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 90 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 50 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 50 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 90 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -20 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -60 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -60 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -20 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 110 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 110 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 70 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 70 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 110 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 60 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 50 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 170 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 130 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 130 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 170 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 100 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 60 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 30 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkli8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkli8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..419c319a2573a5e18827715df01719dd3f701f7d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pbkli8a.afm
@@ -0,0 +1,410 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1992 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue Jan 21 16:12:06 1992
+Comment UniqueID 37830
+Comment VMusage 33139 40031
+FontName Bookman-LightItalic
+FullName ITC Bookman Light Italic
+FamilyName ITC Bookman
+Weight Light
+ItalicAngle -10
+IsFixedPitch false
+FontBBox -228 -250 1269 883
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.004
+Notice Copyright (c) 1985, 1987, 1989, 1992 Adobe Systems Incorporated. All Rights Reserved.ITC Bookman is a registered trademark of International Typeface Corporation.
+EncodingScheme AdobeStandardEncoding
+CapHeight 681
+XHeight 494
+Ascender 717
+Descender -212
+StartCharMetrics 228
+C 32 ; WX 300 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 320 ; N exclam ; B 103 -8 342 698 ;
+C 34 ; WX 360 ; N quotedbl ; B 107 468 402 698 ;
+C 35 ; WX 620 ; N numbersign ; B 107 0 598 681 ;
+C 36 ; WX 620 ; N dollar ; B 78 -85 619 762 ;
+C 37 ; WX 800 ; N percent ; B 56 -8 811 691 ;
+C 38 ; WX 820 ; N ampersand ; B 65 -18 848 698 ;
+C 39 ; WX 280 ; N quoteright ; B 148 470 288 698 ;
+C 40 ; WX 280 ; N parenleft ; B 96 -146 383 727 ;
+C 41 ; WX 280 ; N parenright ; B -8 -146 279 727 ;
+C 42 ; WX 440 ; N asterisk ; B 139 324 505 698 ;
+C 43 ; WX 600 ; N plus ; B 91 43 595 548 ;
+C 44 ; WX 300 ; N comma ; B 88 -115 227 112 ;
+C 45 ; WX 320 ; N hyphen ; B 78 269 336 325 ;
+C 46 ; WX 300 ; N period ; B 96 -8 231 127 ;
+C 47 ; WX 600 ; N slash ; B 104 -149 562 717 ;
+C 48 ; WX 620 ; N zero ; B 86 -17 646 698 ;
+C 49 ; WX 620 ; N one ; B 154 0 500 681 ;
+C 50 ; WX 620 ; N two ; B 66 0 636 698 ;
+C 51 ; WX 620 ; N three ; B 55 -17 622 698 ;
+C 52 ; WX 620 ; N four ; B 69 0 634 681 ;
+C 53 ; WX 620 ; N five ; B 70 -17 614 681 ;
+C 54 ; WX 620 ; N six ; B 89 -17 657 698 ;
+C 55 ; WX 620 ; N seven ; B 143 0 672 681 ;
+C 56 ; WX 620 ; N eight ; B 61 -17 655 698 ;
+C 57 ; WX 620 ; N nine ; B 77 -17 649 698 ;
+C 58 ; WX 300 ; N colon ; B 96 -8 292 494 ;
+C 59 ; WX 300 ; N semicolon ; B 88 -114 292 494 ;
+C 60 ; WX 600 ; N less ; B 79 33 588 561 ;
+C 61 ; WX 600 ; N equal ; B 91 161 595 433 ;
+C 62 ; WX 600 ; N greater ; B 93 33 602 561 ;
+C 63 ; WX 540 ; N question ; B 114 -8 604 698 ;
+C 64 ; WX 780 ; N at ; B 102 -17 802 698 ;
+C 65 ; WX 700 ; N A ; B -25 0 720 681 ;
+C 66 ; WX 720 ; N B ; B 21 0 746 681 ;
+C 67 ; WX 720 ; N C ; B 88 -17 746 698 ;
+C 68 ; WX 740 ; N D ; B 21 0 782 681 ;
+C 69 ; WX 680 ; N E ; B 21 0 736 681 ;
+C 70 ; WX 620 ; N F ; B 21 0 743 681 ;
+C 71 ; WX 760 ; N G ; B 88 -17 813 698 ;
+C 72 ; WX 800 ; N H ; B 21 0 888 681 ;
+C 73 ; WX 320 ; N I ; B 21 0 412 681 ;
+C 74 ; WX 560 ; N J ; B -2 -17 666 681 ;
+C 75 ; WX 720 ; N K ; B 21 0 804 681 ;
+C 76 ; WX 580 ; N L ; B 21 0 656 681 ;
+C 77 ; WX 860 ; N M ; B 18 0 956 681 ;
+C 78 ; WX 720 ; N N ; B 18 0 823 681 ;
+C 79 ; WX 760 ; N O ; B 88 -17 799 698 ;
+C 80 ; WX 600 ; N P ; B 21 0 681 681 ;
+C 81 ; WX 780 ; N Q ; B 61 -191 812 698 ;
+C 82 ; WX 700 ; N R ; B 21 0 736 681 ;
+C 83 ; WX 640 ; N S ; B 61 -17 668 698 ;
+C 84 ; WX 600 ; N T ; B 50 0 725 681 ;
+C 85 ; WX 720 ; N U ; B 118 -17 842 681 ;
+C 86 ; WX 680 ; N V ; B 87 0 815 681 ;
+C 87 ; WX 960 ; N W ; B 87 0 1095 681 ;
+C 88 ; WX 700 ; N X ; B -25 0 815 681 ;
+C 89 ; WX 660 ; N Y ; B 87 0 809 681 ;
+C 90 ; WX 580 ; N Z ; B 8 0 695 681 ;
+C 91 ; WX 260 ; N bracketleft ; B 56 -136 351 717 ;
+C 92 ; WX 600 ; N backslash ; B 84 0 542 717 ;
+C 93 ; WX 260 ; N bracketright ; B 15 -136 309 717 ;
+C 94 ; WX 600 ; N asciicircum ; B 97 276 599 681 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 280 ; N quoteleft ; B 191 470 330 698 ;
+C 97 ; WX 620 ; N a ; B 71 -8 686 494 ;
+C 98 ; WX 600 ; N b ; B 88 -8 621 717 ;
+C 99 ; WX 480 ; N c ; B 65 -8 522 494 ;
+C 100 ; WX 640 ; N d ; B 65 -8 695 717 ;
+C 101 ; WX 540 ; N e ; B 65 -8 575 494 ;
+C 102 ; WX 340 ; N f ; B -160 -218 557 725 ; L i fi ; L l fl ;
+C 103 ; WX 560 ; N g ; B 4 -221 581 494 ;
+C 104 ; WX 620 ; N h ; B 88 -8 689 717 ;
+C 105 ; WX 280 ; N i ; B 88 -8 351 663 ;
+C 106 ; WX 280 ; N j ; B -200 -221 308 663 ;
+C 107 ; WX 600 ; N k ; B 88 -8 657 717 ;
+C 108 ; WX 280 ; N l ; B 100 -8 342 717 ;
+C 109 ; WX 880 ; N m ; B 88 -8 952 494 ;
+C 110 ; WX 620 ; N n ; B 88 -8 673 494 ;
+C 111 ; WX 540 ; N o ; B 65 -8 572 494 ;
+C 112 ; WX 600 ; N p ; B -24 -212 620 494 ;
+C 113 ; WX 560 ; N q ; B 65 -212 584 494 ;
+C 114 ; WX 400 ; N r ; B 88 0 481 494 ;
+C 115 ; WX 540 ; N s ; B 65 -8 547 494 ;
+C 116 ; WX 340 ; N t ; B 88 -8 411 664 ;
+C 117 ; WX 620 ; N u ; B 88 -8 686 484 ;
+C 118 ; WX 540 ; N v ; B 88 -8 562 494 ;
+C 119 ; WX 880 ; N w ; B 88 -8 893 494 ;
+C 120 ; WX 540 ; N x ; B 9 -8 626 494 ;
+C 121 ; WX 600 ; N y ; B 60 -221 609 484 ;
+C 122 ; WX 520 ; N z ; B 38 -8 561 494 ;
+C 123 ; WX 360 ; N braceleft ; B 122 -191 442 717 ;
+C 124 ; WX 600 ; N bar ; B 294 -250 372 750 ;
+C 125 ; WX 380 ; N braceright ; B 13 -191 333 717 ;
+C 126 ; WX 600 ; N asciitilde ; B 91 207 595 386 ;
+C 161 ; WX 320 ; N exclamdown ; B 73 -213 301 494 ;
+C 162 ; WX 620 ; N cent ; B 148 -29 596 715 ;
+C 163 ; WX 620 ; N sterling ; B 4 -17 702 698 ;
+C 164 ; WX 20 ; N fraction ; B -228 0 323 681 ;
+C 165 ; WX 620 ; N yen ; B 71 0 735 681 ;
+C 166 ; WX 620 ; N florin ; B -26 -218 692 725 ;
+C 167 ; WX 620 ; N section ; B 38 -178 638 698 ;
+C 168 ; WX 620 ; N currency ; B 100 89 605 591 ;
+C 169 ; WX 200 ; N quotesingle ; B 99 473 247 698 ;
+C 170 ; WX 440 ; N quotedblleft ; B 191 470 493 698 ;
+C 171 ; WX 300 ; N guillemotleft ; B 70 129 313 434 ;
+C 172 ; WX 180 ; N guilsinglleft ; B 75 129 208 434 ;
+C 173 ; WX 180 ; N guilsinglright ; B 70 129 203 434 ;
+C 174 ; WX 640 ; N fi ; B -159 -222 709 725 ;
+C 175 ; WX 660 ; N fl ; B -159 -218 713 725 ;
+C 177 ; WX 500 ; N endash ; B 33 269 561 325 ;
+C 178 ; WX 620 ; N dagger ; B 192 -130 570 698 ;
+C 179 ; WX 620 ; N daggerdbl ; B 144 -122 566 698 ;
+C 180 ; WX 300 ; N periodcentered ; B 137 229 272 364 ;
+C 182 ; WX 620 ; N paragraph ; B 112 0 718 681 ;
+C 183 ; WX 460 ; N bullet ; B 100 170 444 511 ;
+C 184 ; WX 320 ; N quotesinglbase ; B 87 -114 226 113 ;
+C 185 ; WX 480 ; N quotedblbase ; B 87 -114 390 113 ;
+C 186 ; WX 440 ; N quotedblright ; B 148 470 451 698 ;
+C 187 ; WX 300 ; N guillemotright ; B 60 129 303 434 ;
+C 188 ; WX 1000 ; N ellipsis ; B 99 -8 900 127 ;
+C 189 ; WX 1180 ; N perthousand ; B 56 -8 1199 691 ;
+C 191 ; WX 540 ; N questiondown ; B 18 -212 508 494 ;
+C 193 ; WX 340 ; N grave ; B 182 551 377 706 ;
+C 194 ; WX 320 ; N acute ; B 178 551 373 706 ;
+C 195 ; WX 440 ; N circumflex ; B 176 571 479 685 ;
+C 196 ; WX 440 ; N tilde ; B 180 586 488 671 ;
+C 197 ; WX 440 ; N macron ; B 178 599 484 658 ;
+C 198 ; WX 440 ; N breve ; B 191 577 500 680 ;
+C 199 ; WX 260 ; N dotaccent ; B 169 543 290 664 ;
+C 200 ; WX 420 ; N dieresis ; B 185 569 467 688 ;
+C 202 ; WX 300 ; N ring ; B 178 551 334 706 ;
+C 203 ; WX 320 ; N cedilla ; B 45 -178 240 0 ;
+C 205 ; WX 340 ; N hungarumlaut ; B 167 547 402 738 ;
+C 206 ; WX 260 ; N ogonek ; B 51 -173 184 0 ;
+C 207 ; WX 440 ; N caron ; B 178 571 481 684 ;
+C 208 ; WX 1000 ; N emdash ; B 33 269 1061 325 ;
+C 225 ; WX 1220 ; N AE ; B -45 0 1269 681 ;
+C 227 ; WX 440 ; N ordfeminine ; B 130 396 513 698 ;
+C 232 ; WX 580 ; N Lslash ; B 21 0 656 681 ;
+C 233 ; WX 760 ; N Oslash ; B 88 -95 799 777 ;
+C 234 ; WX 1180 ; N OE ; B 88 -17 1237 698 ;
+C 235 ; WX 400 ; N ordmasculine ; B 139 396 455 698 ;
+C 241 ; WX 880 ; N ae ; B 71 -8 918 494 ;
+C 245 ; WX 280 ; N dotlessi ; B 88 -8 351 484 ;
+C 248 ; WX 340 ; N lslash ; B 50 -8 398 717 ;
+C 249 ; WX 540 ; N oslash ; B 65 -49 571 532 ;
+C 250 ; WX 900 ; N oe ; B 65 -8 948 494 ;
+C 251 ; WX 620 ; N germandbls ; B -121 -111 653 698 ;
+C -1 ; WX 540 ; N ecircumflex ; B 65 -8 575 685 ;
+C -1 ; WX 540 ; N edieresis ; B 65 -8 575 688 ;
+C -1 ; WX 620 ; N aacute ; B 71 -8 686 706 ;
+C -1 ; WX 740 ; N registered ; B 84 -17 784 698 ;
+C -1 ; WX 280 ; N icircumflex ; B 76 -8 379 685 ;
+C -1 ; WX 620 ; N udieresis ; B 88 -8 686 688 ;
+C -1 ; WX 540 ; N ograve ; B 65 -8 572 706 ;
+C -1 ; WX 620 ; N uacute ; B 88 -8 686 706 ;
+C -1 ; WX 620 ; N ucircumflex ; B 88 -8 686 685 ;
+C -1 ; WX 700 ; N Aacute ; B -25 0 720 883 ;
+C -1 ; WX 280 ; N igrave ; B 88 -8 351 706 ;
+C -1 ; WX 320 ; N Icircumflex ; B 21 0 449 862 ;
+C -1 ; WX 480 ; N ccedilla ; B 65 -178 522 494 ;
+C -1 ; WX 620 ; N adieresis ; B 71 -8 686 688 ;
+C -1 ; WX 680 ; N Ecircumflex ; B 21 0 736 862 ;
+C -1 ; WX 540 ; N scaron ; B 65 -8 547 684 ;
+C -1 ; WX 600 ; N thorn ; B -24 -212 620 717 ;
+C -1 ; WX 980 ; N trademark ; B 69 277 965 681 ;
+C -1 ; WX 540 ; N egrave ; B 65 -8 575 706 ;
+C -1 ; WX 372 ; N threesuperior ; B 70 269 439 698 ;
+C -1 ; WX 520 ; N zcaron ; B 38 -8 561 684 ;
+C -1 ; WX 620 ; N atilde ; B 71 -8 686 671 ;
+C -1 ; WX 620 ; N aring ; B 71 -8 686 706 ;
+C -1 ; WX 540 ; N ocircumflex ; B 65 -8 572 685 ;
+C -1 ; WX 680 ; N Edieresis ; B 21 0 736 865 ;
+C -1 ; WX 930 ; N threequarters ; B 99 0 913 691 ;
+C -1 ; WX 600 ; N ydieresis ; B 60 -221 609 688 ;
+C -1 ; WX 600 ; N yacute ; B 60 -221 609 706 ;
+C -1 ; WX 280 ; N iacute ; B 88 -8 351 706 ;
+C -1 ; WX 700 ; N Acircumflex ; B -25 0 720 862 ;
+C -1 ; WX 720 ; N Uacute ; B 118 -17 842 883 ;
+C -1 ; WX 540 ; N eacute ; B 65 -8 575 706 ;
+C -1 ; WX 760 ; N Ograve ; B 88 -17 799 883 ;
+C -1 ; WX 620 ; N agrave ; B 71 -8 686 706 ;
+C -1 ; WX 720 ; N Udieresis ; B 118 -17 842 865 ;
+C -1 ; WX 620 ; N acircumflex ; B 71 -8 686 685 ;
+C -1 ; WX 320 ; N Igrave ; B 21 0 412 883 ;
+C -1 ; WX 372 ; N twosuperior ; B 68 279 439 698 ;
+C -1 ; WX 720 ; N Ugrave ; B 118 -17 842 883 ;
+C -1 ; WX 930 ; N onequarter ; B 91 0 913 681 ;
+C -1 ; WX 720 ; N Ucircumflex ; B 118 -17 842 862 ;
+C -1 ; WX 640 ; N Scaron ; B 61 -17 668 861 ;
+C -1 ; WX 320 ; N Idieresis ; B 21 0 447 865 ;
+C -1 ; WX 280 ; N idieresis ; B 88 -8 377 688 ;
+C -1 ; WX 680 ; N Egrave ; B 21 0 736 883 ;
+C -1 ; WX 760 ; N Oacute ; B 88 -17 799 883 ;
+C -1 ; WX 600 ; N divide ; B 91 46 595 548 ;
+C -1 ; WX 700 ; N Atilde ; B -25 0 720 848 ;
+C -1 ; WX 700 ; N Aring ; B -25 0 720 883 ;
+C -1 ; WX 760 ; N Odieresis ; B 88 -17 799 865 ;
+C -1 ; WX 700 ; N Adieresis ; B -25 0 720 865 ;
+C -1 ; WX 720 ; N Ntilde ; B 18 0 823 848 ;
+C -1 ; WX 580 ; N Zcaron ; B 8 0 695 861 ;
+C -1 ; WX 600 ; N Thorn ; B 21 0 656 681 ;
+C -1 ; WX 320 ; N Iacute ; B 21 0 412 883 ;
+C -1 ; WX 600 ; N plusminus ; B 91 0 595 548 ;
+C -1 ; WX 600 ; N multiply ; B 91 44 595 548 ;
+C -1 ; WX 680 ; N Eacute ; B 21 0 736 883 ;
+C -1 ; WX 660 ; N Ydieresis ; B 87 0 809 865 ;
+C -1 ; WX 372 ; N onesuperior ; B 114 279 339 688 ;
+C -1 ; WX 620 ; N ugrave ; B 88 -8 686 706 ;
+C -1 ; WX 600 ; N logicalnot ; B 91 163 595 433 ;
+C -1 ; WX 620 ; N ntilde ; B 88 -8 673 671 ;
+C -1 ; WX 760 ; N Otilde ; B 88 -17 799 848 ;
+C -1 ; WX 540 ; N otilde ; B 65 -8 572 671 ;
+C -1 ; WX 720 ; N Ccedilla ; B 88 -178 746 698 ;
+C -1 ; WX 700 ; N Agrave ; B -25 0 720 883 ;
+C -1 ; WX 930 ; N onehalf ; B 91 0 925 681 ;
+C -1 ; WX 740 ; N Eth ; B 21 0 782 681 ;
+C -1 ; WX 400 ; N degree ; B 120 398 420 698 ;
+C -1 ; WX 660 ; N Yacute ; B 87 0 809 883 ;
+C -1 ; WX 760 ; N Ocircumflex ; B 88 -17 799 862 ;
+C -1 ; WX 540 ; N oacute ; B 65 -8 572 706 ;
+C -1 ; WX 620 ; N mu ; B 53 -221 686 484 ;
+C -1 ; WX 600 ; N minus ; B 91 259 595 335 ;
+C -1 ; WX 540 ; N eth ; B 65 -8 642 725 ;
+C -1 ; WX 540 ; N odieresis ; B 65 -8 572 688 ;
+C -1 ; WX 740 ; N copyright ; B 84 -17 784 698 ;
+C -1 ; WX 600 ; N brokenbar ; B 294 -175 372 675 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 85
+
+KPX A Y -62
+KPX A W -73
+KPX A V -78
+KPX A T -5
+
+KPX F period -97
+KPX F comma -98
+KPX F A -16
+
+KPX L y 20
+KPX L Y 7
+KPX L W 9
+KPX L V 4
+
+KPX P period -105
+KPX P comma -106
+KPX P A -30
+
+KPX R Y 11
+KPX R W 2
+KPX R V 2
+KPX R T 65
+
+KPX T semicolon 48
+KPX T s -7
+KPX T r 67
+KPX T period -78
+KPX T o 14
+KPX T i 71
+KPX T hyphen 20
+KPX T e 10
+KPX T comma -79
+KPX T colon 48
+KPX T c 16
+KPX T a 9
+KPX T A -14
+
+KPX V y -14
+KPX V u -10
+KPX V semicolon -44
+KPX V r -20
+KPX V period -100
+KPX V o -70
+KPX V i 3
+KPX V hyphen 20
+KPX V e -70
+KPX V comma -109
+KPX V colon -35
+KPX V a -70
+KPX V A -70
+
+KPX W y -14
+KPX W u -20
+KPX W semicolon -42
+KPX W r -30
+KPX W period -100
+KPX W o -60
+KPX W i 3
+KPX W hyphen 20
+KPX W e -60
+KPX W comma -109
+KPX W colon -35
+KPX W a -60
+KPX W A -60
+
+KPX Y v -19
+KPX Y u -31
+KPX Y semicolon -40
+KPX Y q -72
+KPX Y period -100
+KPX Y p -37
+KPX Y o -75
+KPX Y i -11
+KPX Y hyphen 20
+KPX Y e -78
+KPX Y comma -109
+KPX Y colon -35
+KPX Y a -79
+KPX Y A -82
+
+KPX f f -19
+
+KPX r q -14
+KPX r period -134
+KPX r o -10
+KPX r n 38
+KPX r m 37
+KPX r hyphen 20
+KPX r h -20
+KPX r g -3
+KPX r f -9
+KPX r e -15
+KPX r d -9
+KPX r comma -143
+KPX r c -8
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 200 177 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 130 177 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 140 177 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 160 177 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 220 177 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 130 177 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 210 177 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 140 177 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 150 177 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 150 177 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 30 177 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -30 177 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -20 177 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -30 177 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 130 177 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 250 177 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 190 177 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 200 177 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 210 177 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 190 177 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 100 177 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 230 177 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 170 177 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 180 177 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 170 177 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 200 177 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 140 177 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 70 177 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 120 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 70 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 80 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 110 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 140 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 60 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 90 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 30 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 40 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 80 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -40 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -100 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -90 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -60 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 60 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 80 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 20 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 40 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 80 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 30 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 30 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 120 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 60 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 70 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 110 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 140 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 70 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 20 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrb8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrb8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..baf3a5151223b4d04a260c7045f063dd6520fa95
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrb8a.afm
@@ -0,0 +1,344 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1990, 1991, Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Tue Sep 17 14:02:41 1991
+Comment UniqueID 36384
+Comment VMusage 31992 40360
+FontName Courier-Bold
+FullName Courier Bold
+FamilyName Courier
+Weight Bold
+ItalicAngle 0
+IsFixedPitch true
+FontBBox -113 -250 749 801
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.004
+Notice Copyright (c) 1989, 1990, 1991, Adobe Systems Incorporated. All rights reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 562
+XHeight 439
+Ascender 626
+Descender -142
+StartCharMetrics 260
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 202 -15 398 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 135 277 465 562 ;
+C 35 ; WX 600 ; N numbersign ; B 56 -45 544 651 ;
+C 36 ; WX 600 ; N dollar ; B 82 -126 519 666 ;
+C 37 ; WX 600 ; N percent ; B 5 -15 595 616 ;
+C 38 ; WX 600 ; N ampersand ; B 36 -15 546 543 ;
+C 39 ; WX 600 ; N quoteright ; B 171 277 423 562 ;
+C 40 ; WX 600 ; N parenleft ; B 219 -102 461 616 ;
+C 41 ; WX 600 ; N parenright ; B 139 -102 381 616 ;
+C 42 ; WX 600 ; N asterisk ; B 91 219 509 601 ;
+C 43 ; WX 600 ; N plus ; B 71 39 529 478 ;
+C 44 ; WX 600 ; N comma ; B 123 -111 393 174 ;
+C 45 ; WX 600 ; N hyphen ; B 100 203 500 313 ;
+C 46 ; WX 600 ; N period ; B 192 -15 408 171 ;
+C 47 ; WX 600 ; N slash ; B 98 -77 502 626 ;
+C 48 ; WX 600 ; N zero ; B 87 -15 513 616 ;
+C 49 ; WX 600 ; N one ; B 81 0 539 616 ;
+C 50 ; WX 600 ; N two ; B 61 0 499 616 ;
+C 51 ; WX 600 ; N three ; B 63 -15 501 616 ;
+C 52 ; WX 600 ; N four ; B 53 0 507 616 ;
+C 53 ; WX 600 ; N five ; B 70 -15 521 601 ;
+C 54 ; WX 600 ; N six ; B 90 -15 521 616 ;
+C 55 ; WX 600 ; N seven ; B 55 0 494 601 ;
+C 56 ; WX 600 ; N eight ; B 83 -15 517 616 ;
+C 57 ; WX 600 ; N nine ; B 79 -15 510 616 ;
+C 58 ; WX 600 ; N colon ; B 191 -15 407 425 ;
+C 59 ; WX 600 ; N semicolon ; B 123 -111 408 425 ;
+C 60 ; WX 600 ; N less ; B 66 15 523 501 ;
+C 61 ; WX 600 ; N equal ; B 71 118 529 398 ;
+C 62 ; WX 600 ; N greater ; B 77 15 534 501 ;
+C 63 ; WX 600 ; N question ; B 98 -14 501 580 ;
+C 64 ; WX 600 ; N at ; B 16 -15 584 616 ;
+C 65 ; WX 600 ; N A ; B -9 0 609 562 ;
+C 66 ; WX 600 ; N B ; B 30 0 573 562 ;
+C 67 ; WX 600 ; N C ; B 22 -18 560 580 ;
+C 68 ; WX 600 ; N D ; B 30 0 594 562 ;
+C 69 ; WX 600 ; N E ; B 25 0 560 562 ;
+C 70 ; WX 600 ; N F ; B 39 0 570 562 ;
+C 71 ; WX 600 ; N G ; B 22 -18 594 580 ;
+C 72 ; WX 600 ; N H ; B 20 0 580 562 ;
+C 73 ; WX 600 ; N I ; B 77 0 523 562 ;
+C 74 ; WX 600 ; N J ; B 37 -18 601 562 ;
+C 75 ; WX 600 ; N K ; B 21 0 599 562 ;
+C 76 ; WX 600 ; N L ; B 39 0 578 562 ;
+C 77 ; WX 600 ; N M ; B -2 0 602 562 ;
+C 78 ; WX 600 ; N N ; B 8 -12 610 562 ;
+C 79 ; WX 600 ; N O ; B 22 -18 578 580 ;
+C 80 ; WX 600 ; N P ; B 48 0 559 562 ;
+C 81 ; WX 600 ; N Q ; B 32 -138 578 580 ;
+C 82 ; WX 600 ; N R ; B 24 0 599 562 ;
+C 83 ; WX 600 ; N S ; B 47 -22 553 582 ;
+C 84 ; WX 600 ; N T ; B 21 0 579 562 ;
+C 85 ; WX 600 ; N U ; B 4 -18 596 562 ;
+C 86 ; WX 600 ; N V ; B -13 0 613 562 ;
+C 87 ; WX 600 ; N W ; B -18 0 618 562 ;
+C 88 ; WX 600 ; N X ; B 12 0 588 562 ;
+C 89 ; WX 600 ; N Y ; B 12 0 589 562 ;
+C 90 ; WX 600 ; N Z ; B 62 0 539 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 245 -102 475 616 ;
+C 92 ; WX 600 ; N backslash ; B 99 -77 503 626 ;
+C 93 ; WX 600 ; N bracketright ; B 125 -102 355 616 ;
+C 94 ; WX 600 ; N asciicircum ; B 108 250 492 616 ;
+C 95 ; WX 600 ; N underscore ; B 0 -125 600 -75 ;
+C 96 ; WX 600 ; N quoteleft ; B 178 277 428 562 ;
+C 97 ; WX 600 ; N a ; B 35 -15 570 454 ;
+C 98 ; WX 600 ; N b ; B 0 -15 584 626 ;
+C 99 ; WX 600 ; N c ; B 40 -15 545 459 ;
+C 100 ; WX 600 ; N d ; B 20 -15 591 626 ;
+C 101 ; WX 600 ; N e ; B 40 -15 563 454 ;
+C 102 ; WX 600 ; N f ; B 83 0 547 626 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 30 -146 580 454 ;
+C 104 ; WX 600 ; N h ; B 5 0 592 626 ;
+C 105 ; WX 600 ; N i ; B 77 0 523 658 ;
+C 106 ; WX 600 ; N j ; B 63 -146 440 658 ;
+C 107 ; WX 600 ; N k ; B 20 0 585 626 ;
+C 108 ; WX 600 ; N l ; B 77 0 523 626 ;
+C 109 ; WX 600 ; N m ; B -22 0 626 454 ;
+C 110 ; WX 600 ; N n ; B 18 0 592 454 ;
+C 111 ; WX 600 ; N o ; B 30 -15 570 454 ;
+C 112 ; WX 600 ; N p ; B -1 -142 570 454 ;
+C 113 ; WX 600 ; N q ; B 20 -142 591 454 ;
+C 114 ; WX 600 ; N r ; B 47 0 580 454 ;
+C 115 ; WX 600 ; N s ; B 68 -17 535 459 ;
+C 116 ; WX 600 ; N t ; B 47 -15 532 562 ;
+C 117 ; WX 600 ; N u ; B -1 -15 569 439 ;
+C 118 ; WX 600 ; N v ; B -1 0 601 439 ;
+C 119 ; WX 600 ; N w ; B -18 0 618 439 ;
+C 120 ; WX 600 ; N x ; B 6 0 594 439 ;
+C 121 ; WX 600 ; N y ; B -4 -142 601 439 ;
+C 122 ; WX 600 ; N z ; B 81 0 520 439 ;
+C 123 ; WX 600 ; N braceleft ; B 160 -102 464 616 ;
+C 124 ; WX 600 ; N bar ; B 255 -250 345 750 ;
+C 125 ; WX 600 ; N braceright ; B 136 -102 440 616 ;
+C 126 ; WX 600 ; N asciitilde ; B 71 153 530 356 ;
+C 161 ; WX 600 ; N exclamdown ; B 202 -146 398 449 ;
+C 162 ; WX 600 ; N cent ; B 66 -49 518 614 ;
+C 163 ; WX 600 ; N sterling ; B 72 -28 558 611 ;
+C 164 ; WX 600 ; N fraction ; B 25 -60 576 661 ;
+C 165 ; WX 600 ; N yen ; B 10 0 590 562 ;
+C 166 ; WX 600 ; N florin ; B -30 -131 572 616 ;
+C 167 ; WX 600 ; N section ; B 83 -70 517 580 ;
+C 168 ; WX 600 ; N currency ; B 54 49 546 517 ;
+C 169 ; WX 600 ; N quotesingle ; B 227 277 373 562 ;
+C 170 ; WX 600 ; N quotedblleft ; B 71 277 535 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 8 70 553 446 ;
+C 172 ; WX 600 ; N guilsinglleft ; B 141 70 459 446 ;
+C 173 ; WX 600 ; N guilsinglright ; B 141 70 459 446 ;
+C 174 ; WX 600 ; N fi ; B 12 0 593 626 ;
+C 175 ; WX 600 ; N fl ; B 12 0 593 626 ;
+C 177 ; WX 600 ; N endash ; B 65 203 535 313 ;
+C 178 ; WX 600 ; N dagger ; B 106 -70 494 580 ;
+C 179 ; WX 600 ; N daggerdbl ; B 106 -70 494 580 ;
+C 180 ; WX 600 ; N periodcentered ; B 196 165 404 351 ;
+C 182 ; WX 600 ; N paragraph ; B 6 -70 576 580 ;
+C 183 ; WX 600 ; N bullet ; B 140 132 460 430 ;
+C 184 ; WX 600 ; N quotesinglbase ; B 175 -142 427 143 ;
+C 185 ; WX 600 ; N quotedblbase ; B 65 -142 529 143 ;
+C 186 ; WX 600 ; N quotedblright ; B 61 277 525 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 47 70 592 446 ;
+C 188 ; WX 600 ; N ellipsis ; B 26 -15 574 116 ;
+C 189 ; WX 600 ; N perthousand ; B -113 -15 713 616 ;
+C 191 ; WX 600 ; N questiondown ; B 99 -146 502 449 ;
+C 193 ; WX 600 ; N grave ; B 132 508 395 661 ;
+C 194 ; WX 600 ; N acute ; B 205 508 468 661 ;
+C 195 ; WX 600 ; N circumflex ; B 103 483 497 657 ;
+C 196 ; WX 600 ; N tilde ; B 89 493 512 636 ;
+C 197 ; WX 600 ; N macron ; B 88 505 512 585 ;
+C 198 ; WX 600 ; N breve ; B 83 468 517 631 ;
+C 199 ; WX 600 ; N dotaccent ; B 230 485 370 625 ;
+C 200 ; WX 600 ; N dieresis ; B 128 485 472 625 ;
+C 202 ; WX 600 ; N ring ; B 198 481 402 678 ;
+C 203 ; WX 600 ; N cedilla ; B 205 -206 387 0 ;
+C 205 ; WX 600 ; N hungarumlaut ; B 68 488 588 661 ;
+C 206 ; WX 600 ; N ogonek ; B 169 -199 367 0 ;
+C 207 ; WX 600 ; N caron ; B 103 493 497 667 ;
+C 208 ; WX 600 ; N emdash ; B -10 203 610 313 ;
+C 225 ; WX 600 ; N AE ; B -29 0 602 562 ;
+C 227 ; WX 600 ; N ordfeminine ; B 147 196 453 580 ;
+C 232 ; WX 600 ; N Lslash ; B 39 0 578 562 ;
+C 233 ; WX 600 ; N Oslash ; B 22 -22 578 584 ;
+C 234 ; WX 600 ; N OE ; B -25 0 595 562 ;
+C 235 ; WX 600 ; N ordmasculine ; B 147 196 453 580 ;
+C 241 ; WX 600 ; N ae ; B -4 -15 601 454 ;
+C 245 ; WX 600 ; N dotlessi ; B 77 0 523 439 ;
+C 248 ; WX 600 ; N lslash ; B 77 0 523 626 ;
+C 249 ; WX 600 ; N oslash ; B 30 -24 570 463 ;
+C 250 ; WX 600 ; N oe ; B -18 -15 611 454 ;
+C 251 ; WX 600 ; N germandbls ; B 22 -15 596 626 ;
+C -1 ; WX 600 ; N Odieresis ; B 22 -18 578 748 ;
+C -1 ; WX 600 ; N logicalnot ; B 71 103 529 413 ;
+C -1 ; WX 600 ; N minus ; B 71 203 529 313 ;
+C -1 ; WX 600 ; N merge ; B 137 -15 464 487 ;
+C -1 ; WX 600 ; N degree ; B 86 243 474 616 ;
+C -1 ; WX 600 ; N dectab ; B 8 0 592 320 ;
+C -1 ; WX 600 ; N ll ; B -12 0 600 626 ;
+C -1 ; WX 600 ; N IJ ; B -8 -18 622 562 ;
+C -1 ; WX 600 ; N Eacute ; B 25 0 560 784 ;
+C -1 ; WX 600 ; N Ocircumflex ; B 22 -18 578 780 ;
+C -1 ; WX 600 ; N ucircumflex ; B -1 -15 569 657 ;
+C -1 ; WX 600 ; N left ; B 65 44 535 371 ;
+C -1 ; WX 600 ; N threesuperior ; B 138 222 433 616 ;
+C -1 ; WX 600 ; N up ; B 136 0 463 447 ;
+C -1 ; WX 600 ; N multiply ; B 81 39 520 478 ;
+C -1 ; WX 600 ; N Scaron ; B 47 -22 553 790 ;
+C -1 ; WX 600 ; N tab ; B 19 0 581 562 ;
+C -1 ; WX 600 ; N Ucircumflex ; B 4 -18 596 780 ;
+C -1 ; WX 600 ; N divide ; B 71 16 529 500 ;
+C -1 ; WX 600 ; N Acircumflex ; B -9 0 609 780 ;
+C -1 ; WX 600 ; N eacute ; B 40 -15 563 661 ;
+C -1 ; WX 600 ; N uacute ; B -1 -15 569 661 ;
+C -1 ; WX 600 ; N Aacute ; B -9 0 609 784 ;
+C -1 ; WX 600 ; N copyright ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N twosuperior ; B 143 230 436 616 ;
+C -1 ; WX 600 ; N Ecircumflex ; B 25 0 560 780 ;
+C -1 ; WX 600 ; N ntilde ; B 18 0 592 636 ;
+C -1 ; WX 600 ; N down ; B 137 -15 464 439 ;
+C -1 ; WX 600 ; N center ; B 40 14 560 580 ;
+C -1 ; WX 600 ; N onesuperior ; B 153 230 447 616 ;
+C -1 ; WX 600 ; N ij ; B 6 -146 574 658 ;
+C -1 ; WX 600 ; N edieresis ; B 40 -15 563 625 ;
+C -1 ; WX 600 ; N graybox ; B 76 0 525 599 ;
+C -1 ; WX 600 ; N odieresis ; B 30 -15 570 625 ;
+C -1 ; WX 600 ; N Ograve ; B 22 -18 578 784 ;
+C -1 ; WX 600 ; N threequarters ; B -47 -60 648 661 ;
+C -1 ; WX 600 ; N plusminus ; B 71 24 529 515 ;
+C -1 ; WX 600 ; N prescription ; B 24 -15 599 562 ;
+C -1 ; WX 600 ; N eth ; B 58 -27 543 626 ;
+C -1 ; WX 600 ; N largebullet ; B 248 229 352 333 ;
+C -1 ; WX 600 ; N egrave ; B 40 -15 563 661 ;
+C -1 ; WX 600 ; N ccedilla ; B 40 -206 545 459 ;
+C -1 ; WX 600 ; N notegraphic ; B 77 -15 523 572 ;
+C -1 ; WX 600 ; N Udieresis ; B 4 -18 596 748 ;
+C -1 ; WX 600 ; N Gcaron ; B 22 -18 594 790 ;
+C -1 ; WX 600 ; N arrowdown ; B 144 -15 456 608 ;
+C -1 ; WX 600 ; N format ; B 5 -146 115 601 ;
+C -1 ; WX 600 ; N Otilde ; B 22 -18 578 759 ;
+C -1 ; WX 600 ; N Idieresis ; B 77 0 523 748 ;
+C -1 ; WX 600 ; N adieresis ; B 35 -15 570 625 ;
+C -1 ; WX 600 ; N ecircumflex ; B 40 -15 563 657 ;
+C -1 ; WX 600 ; N Eth ; B 30 0 594 562 ;
+C -1 ; WX 600 ; N onequarter ; B -56 -60 656 661 ;
+C -1 ; WX 600 ; N LL ; B -45 0 645 562 ;
+C -1 ; WX 600 ; N agrave ; B 35 -15 570 661 ;
+C -1 ; WX 600 ; N Zcaron ; B 62 0 539 790 ;
+C -1 ; WX 600 ; N Scedilla ; B 47 -206 553 582 ;
+C -1 ; WX 600 ; N Idot ; B 77 0 523 748 ;
+C -1 ; WX 600 ; N Iacute ; B 77 0 523 784 ;
+C -1 ; WX 600 ; N indent ; B 65 45 535 372 ;
+C -1 ; WX 600 ; N Ugrave ; B 4 -18 596 784 ;
+C -1 ; WX 600 ; N scaron ; B 68 -17 535 667 ;
+C -1 ; WX 600 ; N overscore ; B 0 579 600 629 ;
+C -1 ; WX 600 ; N Aring ; B -9 0 609 801 ;
+C -1 ; WX 600 ; N Ccedilla ; B 22 -206 560 580 ;
+C -1 ; WX 600 ; N Igrave ; B 77 0 523 784 ;
+C -1 ; WX 600 ; N brokenbar ; B 255 -175 345 675 ;
+C -1 ; WX 600 ; N Oacute ; B 22 -18 578 784 ;
+C -1 ; WX 600 ; N otilde ; B 30 -15 570 636 ;
+C -1 ; WX 600 ; N Yacute ; B 12 0 589 784 ;
+C -1 ; WX 600 ; N lira ; B 72 -28 558 611 ;
+C -1 ; WX 600 ; N Icircumflex ; B 77 0 523 780 ;
+C -1 ; WX 600 ; N Atilde ; B -9 0 609 759 ;
+C -1 ; WX 600 ; N Uacute ; B 4 -18 596 784 ;
+C -1 ; WX 600 ; N Ydieresis ; B 12 0 589 748 ;
+C -1 ; WX 600 ; N ydieresis ; B -4 -142 601 625 ;
+C -1 ; WX 600 ; N idieresis ; B 77 0 523 625 ;
+C -1 ; WX 600 ; N Adieresis ; B -9 0 609 748 ;
+C -1 ; WX 600 ; N mu ; B -1 -142 569 439 ;
+C -1 ; WX 600 ; N trademark ; B -9 230 749 562 ;
+C -1 ; WX 600 ; N oacute ; B 30 -15 570 661 ;
+C -1 ; WX 600 ; N acircumflex ; B 35 -15 570 657 ;
+C -1 ; WX 600 ; N Agrave ; B -9 0 609 784 ;
+C -1 ; WX 600 ; N return ; B 19 0 581 562 ;
+C -1 ; WX 600 ; N atilde ; B 35 -15 570 636 ;
+C -1 ; WX 600 ; N square ; B 19 0 581 562 ;
+C -1 ; WX 600 ; N registered ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N stop ; B 19 0 581 562 ;
+C -1 ; WX 600 ; N udieresis ; B -1 -15 569 625 ;
+C -1 ; WX 600 ; N arrowup ; B 144 3 456 626 ;
+C -1 ; WX 600 ; N igrave ; B 77 0 523 661 ;
+C -1 ; WX 600 ; N Edieresis ; B 25 0 560 748 ;
+C -1 ; WX 600 ; N zcaron ; B 81 0 520 667 ;
+C -1 ; WX 600 ; N arrowboth ; B -24 143 624 455 ;
+C -1 ; WX 600 ; N gcaron ; B 30 -146 580 667 ;
+C -1 ; WX 600 ; N arrowleft ; B -24 143 634 455 ;
+C -1 ; WX 600 ; N aacute ; B 35 -15 570 661 ;
+C -1 ; WX 600 ; N ocircumflex ; B 30 -15 570 657 ;
+C -1 ; WX 600 ; N scedilla ; B 68 -206 535 459 ;
+C -1 ; WX 600 ; N ograve ; B 30 -15 570 661 ;
+C -1 ; WX 600 ; N onehalf ; B -47 -60 648 661 ;
+C -1 ; WX 600 ; N ugrave ; B -1 -15 569 661 ;
+C -1 ; WX 600 ; N Ntilde ; B 8 -12 610 759 ;
+C -1 ; WX 600 ; N iacute ; B 77 0 523 661 ;
+C -1 ; WX 600 ; N arrowright ; B -34 143 624 455 ;
+C -1 ; WX 600 ; N Thorn ; B 48 0 557 562 ;
+C -1 ; WX 600 ; N Egrave ; B 25 0 560 784 ;
+C -1 ; WX 600 ; N thorn ; B -14 -142 570 626 ;
+C -1 ; WX 600 ; N aring ; B 35 -15 570 678 ;
+C -1 ; WX 600 ; N yacute ; B -4 -142 601 661 ;
+C -1 ; WX 600 ; N icircumflex ; B 63 0 523 657 ;
+EndCharMetrics
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 30 123 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex -30 123 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis -20 123 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave -50 123 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring -10 123 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde -30 123 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 30 123 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 0 123 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 0 123 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 0 123 ;
+CC Gcaron 2 ; PCC G 0 0 ; PCC caron 10 123 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 0 123 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 0 123 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 0 123 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 0 123 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 0 123 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 0 123 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 0 123 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 0 123 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 0 123 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 0 123 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 0 123 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 30 123 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 0 123 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 0 123 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave -30 123 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 30 123 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 0 123 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 0 123 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 0 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex -20 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis -10 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave -30 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 0 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 0 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 0 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 0 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 0 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 0 0 ;
+CC gcaron 2 ; PCC g 0 0 ; PCC caron -40 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute 0 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -40 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -40 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave 0 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 0 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 0 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 0 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 0 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 0 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 0 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 0 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 0 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex -20 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis -20 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave -30 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 30 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 10 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 0 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrbo8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrbo8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..6e2c74225117543fa560feaac96827de161f027f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrbo8a.afm
@@ -0,0 +1,344 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1990, 1991, Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Tue Sep 17 14:13:24 1991
+Comment UniqueID 36389
+Comment VMusage 10055 54684
+FontName Courier-BoldOblique
+FullName Courier Bold Oblique
+FamilyName Courier
+Weight Bold
+ItalicAngle -12
+IsFixedPitch true
+FontBBox -56 -250 868 801
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.004
+Notice Copyright (c) 1989, 1990, 1991, Adobe Systems Incorporated. All rights reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 562
+XHeight 439
+Ascender 626
+Descender -142
+StartCharMetrics 260
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 216 -15 495 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 212 277 584 562 ;
+C 35 ; WX 600 ; N numbersign ; B 88 -45 640 651 ;
+C 36 ; WX 600 ; N dollar ; B 87 -126 629 666 ;
+C 37 ; WX 600 ; N percent ; B 102 -15 624 616 ;
+C 38 ; WX 600 ; N ampersand ; B 62 -15 594 543 ;
+C 39 ; WX 600 ; N quoteright ; B 230 277 542 562 ;
+C 40 ; WX 600 ; N parenleft ; B 266 -102 592 616 ;
+C 41 ; WX 600 ; N parenright ; B 117 -102 444 616 ;
+C 42 ; WX 600 ; N asterisk ; B 179 219 597 601 ;
+C 43 ; WX 600 ; N plus ; B 114 39 596 478 ;
+C 44 ; WX 600 ; N comma ; B 99 -111 430 174 ;
+C 45 ; WX 600 ; N hyphen ; B 143 203 567 313 ;
+C 46 ; WX 600 ; N period ; B 207 -15 426 171 ;
+C 47 ; WX 600 ; N slash ; B 91 -77 626 626 ;
+C 48 ; WX 600 ; N zero ; B 136 -15 592 616 ;
+C 49 ; WX 600 ; N one ; B 93 0 561 616 ;
+C 50 ; WX 600 ; N two ; B 61 0 593 616 ;
+C 51 ; WX 600 ; N three ; B 72 -15 571 616 ;
+C 52 ; WX 600 ; N four ; B 82 0 558 616 ;
+C 53 ; WX 600 ; N five ; B 77 -15 621 601 ;
+C 54 ; WX 600 ; N six ; B 136 -15 652 616 ;
+C 55 ; WX 600 ; N seven ; B 147 0 622 601 ;
+C 56 ; WX 600 ; N eight ; B 115 -15 604 616 ;
+C 57 ; WX 600 ; N nine ; B 76 -15 592 616 ;
+C 58 ; WX 600 ; N colon ; B 206 -15 479 425 ;
+C 59 ; WX 600 ; N semicolon ; B 99 -111 480 425 ;
+C 60 ; WX 600 ; N less ; B 121 15 612 501 ;
+C 61 ; WX 600 ; N equal ; B 96 118 614 398 ;
+C 62 ; WX 600 ; N greater ; B 97 15 589 501 ;
+C 63 ; WX 600 ; N question ; B 183 -14 591 580 ;
+C 64 ; WX 600 ; N at ; B 66 -15 641 616 ;
+C 65 ; WX 600 ; N A ; B -9 0 631 562 ;
+C 66 ; WX 600 ; N B ; B 30 0 629 562 ;
+C 67 ; WX 600 ; N C ; B 75 -18 674 580 ;
+C 68 ; WX 600 ; N D ; B 30 0 664 562 ;
+C 69 ; WX 600 ; N E ; B 25 0 669 562 ;
+C 70 ; WX 600 ; N F ; B 39 0 683 562 ;
+C 71 ; WX 600 ; N G ; B 75 -18 674 580 ;
+C 72 ; WX 600 ; N H ; B 20 0 699 562 ;
+C 73 ; WX 600 ; N I ; B 77 0 642 562 ;
+C 74 ; WX 600 ; N J ; B 59 -18 720 562 ;
+C 75 ; WX 600 ; N K ; B 21 0 691 562 ;
+C 76 ; WX 600 ; N L ; B 39 0 635 562 ;
+C 77 ; WX 600 ; N M ; B -2 0 721 562 ;
+C 78 ; WX 600 ; N N ; B 8 -12 729 562 ;
+C 79 ; WX 600 ; N O ; B 74 -18 645 580 ;
+C 80 ; WX 600 ; N P ; B 48 0 642 562 ;
+C 81 ; WX 600 ; N Q ; B 84 -138 636 580 ;
+C 82 ; WX 600 ; N R ; B 24 0 617 562 ;
+C 83 ; WX 600 ; N S ; B 54 -22 672 582 ;
+C 84 ; WX 600 ; N T ; B 86 0 678 562 ;
+C 85 ; WX 600 ; N U ; B 101 -18 715 562 ;
+C 86 ; WX 600 ; N V ; B 84 0 732 562 ;
+C 87 ; WX 600 ; N W ; B 84 0 737 562 ;
+C 88 ; WX 600 ; N X ; B 12 0 689 562 ;
+C 89 ; WX 600 ; N Y ; B 109 0 708 562 ;
+C 90 ; WX 600 ; N Z ; B 62 0 636 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 223 -102 606 616 ;
+C 92 ; WX 600 ; N backslash ; B 223 -77 496 626 ;
+C 93 ; WX 600 ; N bracketright ; B 103 -102 486 616 ;
+C 94 ; WX 600 ; N asciicircum ; B 171 250 555 616 ;
+C 95 ; WX 600 ; N underscore ; B -27 -125 584 -75 ;
+C 96 ; WX 600 ; N quoteleft ; B 297 277 487 562 ;
+C 97 ; WX 600 ; N a ; B 62 -15 592 454 ;
+C 98 ; WX 600 ; N b ; B 13 -15 636 626 ;
+C 99 ; WX 600 ; N c ; B 81 -15 631 459 ;
+C 100 ; WX 600 ; N d ; B 61 -15 644 626 ;
+C 101 ; WX 600 ; N e ; B 81 -15 604 454 ;
+C 102 ; WX 600 ; N f ; B 83 0 677 626 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 41 -146 673 454 ;
+C 104 ; WX 600 ; N h ; B 18 0 614 626 ;
+C 105 ; WX 600 ; N i ; B 77 0 545 658 ;
+C 106 ; WX 600 ; N j ; B 37 -146 580 658 ;
+C 107 ; WX 600 ; N k ; B 33 0 642 626 ;
+C 108 ; WX 600 ; N l ; B 77 0 545 626 ;
+C 109 ; WX 600 ; N m ; B -22 0 648 454 ;
+C 110 ; WX 600 ; N n ; B 18 0 614 454 ;
+C 111 ; WX 600 ; N o ; B 71 -15 622 454 ;
+C 112 ; WX 600 ; N p ; B -31 -142 622 454 ;
+C 113 ; WX 600 ; N q ; B 61 -142 684 454 ;
+C 114 ; WX 600 ; N r ; B 47 0 654 454 ;
+C 115 ; WX 600 ; N s ; B 67 -17 607 459 ;
+C 116 ; WX 600 ; N t ; B 118 -15 566 562 ;
+C 117 ; WX 600 ; N u ; B 70 -15 591 439 ;
+C 118 ; WX 600 ; N v ; B 70 0 694 439 ;
+C 119 ; WX 600 ; N w ; B 53 0 711 439 ;
+C 120 ; WX 600 ; N x ; B 6 0 670 439 ;
+C 121 ; WX 600 ; N y ; B -20 -142 694 439 ;
+C 122 ; WX 600 ; N z ; B 81 0 613 439 ;
+C 123 ; WX 600 ; N braceleft ; B 204 -102 595 616 ;
+C 124 ; WX 600 ; N bar ; B 202 -250 504 750 ;
+C 125 ; WX 600 ; N braceright ; B 114 -102 506 616 ;
+C 126 ; WX 600 ; N asciitilde ; B 120 153 589 356 ;
+C 161 ; WX 600 ; N exclamdown ; B 197 -146 477 449 ;
+C 162 ; WX 600 ; N cent ; B 121 -49 604 614 ;
+C 163 ; WX 600 ; N sterling ; B 107 -28 650 611 ;
+C 164 ; WX 600 ; N fraction ; B 22 -60 707 661 ;
+C 165 ; WX 600 ; N yen ; B 98 0 709 562 ;
+C 166 ; WX 600 ; N florin ; B -56 -131 701 616 ;
+C 167 ; WX 600 ; N section ; B 74 -70 619 580 ;
+C 168 ; WX 600 ; N currency ; B 77 49 643 517 ;
+C 169 ; WX 600 ; N quotesingle ; B 304 277 492 562 ;
+C 170 ; WX 600 ; N quotedblleft ; B 190 277 594 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 63 70 638 446 ;
+C 172 ; WX 600 ; N guilsinglleft ; B 196 70 544 446 ;
+C 173 ; WX 600 ; N guilsinglright ; B 166 70 514 446 ;
+C 174 ; WX 600 ; N fi ; B 12 0 643 626 ;
+C 175 ; WX 600 ; N fl ; B 12 0 643 626 ;
+C 177 ; WX 600 ; N endash ; B 108 203 602 313 ;
+C 178 ; WX 600 ; N dagger ; B 176 -70 586 580 ;
+C 179 ; WX 600 ; N daggerdbl ; B 122 -70 586 580 ;
+C 180 ; WX 600 ; N periodcentered ; B 249 165 461 351 ;
+C 182 ; WX 600 ; N paragraph ; B 61 -70 699 580 ;
+C 183 ; WX 600 ; N bullet ; B 197 132 523 430 ;
+C 184 ; WX 600 ; N quotesinglbase ; B 145 -142 457 143 ;
+C 185 ; WX 600 ; N quotedblbase ; B 35 -142 559 143 ;
+C 186 ; WX 600 ; N quotedblright ; B 120 277 644 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 72 70 647 446 ;
+C 188 ; WX 600 ; N ellipsis ; B 35 -15 586 116 ;
+C 189 ; WX 600 ; N perthousand ; B -44 -15 742 616 ;
+C 191 ; WX 600 ; N questiondown ; B 101 -146 509 449 ;
+C 193 ; WX 600 ; N grave ; B 272 508 503 661 ;
+C 194 ; WX 600 ; N acute ; B 313 508 608 661 ;
+C 195 ; WX 600 ; N circumflex ; B 212 483 606 657 ;
+C 196 ; WX 600 ; N tilde ; B 200 493 642 636 ;
+C 197 ; WX 600 ; N macron ; B 195 505 636 585 ;
+C 198 ; WX 600 ; N breve ; B 217 468 651 631 ;
+C 199 ; WX 600 ; N dotaccent ; B 346 485 490 625 ;
+C 200 ; WX 600 ; N dieresis ; B 244 485 592 625 ;
+C 202 ; WX 600 ; N ring ; B 319 481 528 678 ;
+C 203 ; WX 600 ; N cedilla ; B 169 -206 367 0 ;
+C 205 ; WX 600 ; N hungarumlaut ; B 172 488 728 661 ;
+C 206 ; WX 600 ; N ogonek ; B 144 -199 350 0 ;
+C 207 ; WX 600 ; N caron ; B 238 493 632 667 ;
+C 208 ; WX 600 ; N emdash ; B 33 203 677 313 ;
+C 225 ; WX 600 ; N AE ; B -29 0 707 562 ;
+C 227 ; WX 600 ; N ordfeminine ; B 189 196 526 580 ;
+C 232 ; WX 600 ; N Lslash ; B 39 0 635 562 ;
+C 233 ; WX 600 ; N Oslash ; B 48 -22 672 584 ;
+C 234 ; WX 600 ; N OE ; B 26 0 700 562 ;
+C 235 ; WX 600 ; N ordmasculine ; B 189 196 542 580 ;
+C 241 ; WX 600 ; N ae ; B 21 -15 651 454 ;
+C 245 ; WX 600 ; N dotlessi ; B 77 0 545 439 ;
+C 248 ; WX 600 ; N lslash ; B 77 0 578 626 ;
+C 249 ; WX 600 ; N oslash ; B 55 -24 637 463 ;
+C 250 ; WX 600 ; N oe ; B 19 -15 661 454 ;
+C 251 ; WX 600 ; N germandbls ; B 22 -15 628 626 ;
+C -1 ; WX 600 ; N Odieresis ; B 74 -18 645 748 ;
+C -1 ; WX 600 ; N logicalnot ; B 135 103 617 413 ;
+C -1 ; WX 600 ; N minus ; B 114 203 596 313 ;
+C -1 ; WX 600 ; N merge ; B 168 -15 533 487 ;
+C -1 ; WX 600 ; N degree ; B 173 243 569 616 ;
+C -1 ; WX 600 ; N dectab ; B 8 0 615 320 ;
+C -1 ; WX 600 ; N ll ; B 1 0 653 626 ;
+C -1 ; WX 600 ; N IJ ; B -8 -18 741 562 ;
+C -1 ; WX 600 ; N Eacute ; B 25 0 669 784 ;
+C -1 ; WX 600 ; N Ocircumflex ; B 74 -18 645 780 ;
+C -1 ; WX 600 ; N ucircumflex ; B 70 -15 591 657 ;
+C -1 ; WX 600 ; N left ; B 109 44 589 371 ;
+C -1 ; WX 600 ; N threesuperior ; B 193 222 525 616 ;
+C -1 ; WX 600 ; N up ; B 196 0 523 447 ;
+C -1 ; WX 600 ; N multiply ; B 105 39 606 478 ;
+C -1 ; WX 600 ; N Scaron ; B 54 -22 672 790 ;
+C -1 ; WX 600 ; N tab ; B 19 0 641 562 ;
+C -1 ; WX 600 ; N Ucircumflex ; B 101 -18 715 780 ;
+C -1 ; WX 600 ; N divide ; B 114 16 596 500 ;
+C -1 ; WX 600 ; N Acircumflex ; B -9 0 631 780 ;
+C -1 ; WX 600 ; N eacute ; B 81 -15 608 661 ;
+C -1 ; WX 600 ; N uacute ; B 70 -15 608 661 ;
+C -1 ; WX 600 ; N Aacute ; B -9 0 665 784 ;
+C -1 ; WX 600 ; N copyright ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N twosuperior ; B 192 230 541 616 ;
+C -1 ; WX 600 ; N Ecircumflex ; B 25 0 669 780 ;
+C -1 ; WX 600 ; N ntilde ; B 18 0 642 636 ;
+C -1 ; WX 600 ; N down ; B 168 -15 496 439 ;
+C -1 ; WX 600 ; N center ; B 103 14 623 580 ;
+C -1 ; WX 600 ; N onesuperior ; B 213 230 514 616 ;
+C -1 ; WX 600 ; N ij ; B 6 -146 714 658 ;
+C -1 ; WX 600 ; N edieresis ; B 81 -15 604 625 ;
+C -1 ; WX 600 ; N graybox ; B 76 0 652 599 ;
+C -1 ; WX 600 ; N odieresis ; B 71 -15 622 625 ;
+C -1 ; WX 600 ; N Ograve ; B 74 -18 645 784 ;
+C -1 ; WX 600 ; N threequarters ; B 8 -60 698 661 ;
+C -1 ; WX 600 ; N plusminus ; B 76 24 614 515 ;
+C -1 ; WX 600 ; N prescription ; B 24 -15 632 562 ;
+C -1 ; WX 600 ; N eth ; B 93 -27 661 626 ;
+C -1 ; WX 600 ; N largebullet ; B 307 229 413 333 ;
+C -1 ; WX 600 ; N egrave ; B 81 -15 604 661 ;
+C -1 ; WX 600 ; N ccedilla ; B 81 -206 631 459 ;
+C -1 ; WX 600 ; N notegraphic ; B 91 -15 619 572 ;
+C -1 ; WX 600 ; N Udieresis ; B 101 -18 715 748 ;
+C -1 ; WX 600 ; N Gcaron ; B 75 -18 674 790 ;
+C -1 ; WX 600 ; N arrowdown ; B 174 -15 486 608 ;
+C -1 ; WX 600 ; N format ; B -26 -146 243 601 ;
+C -1 ; WX 600 ; N Otilde ; B 74 -18 668 759 ;
+C -1 ; WX 600 ; N Idieresis ; B 77 0 642 748 ;
+C -1 ; WX 600 ; N adieresis ; B 62 -15 592 625 ;
+C -1 ; WX 600 ; N ecircumflex ; B 81 -15 606 657 ;
+C -1 ; WX 600 ; N Eth ; B 30 0 664 562 ;
+C -1 ; WX 600 ; N onequarter ; B 14 -60 706 661 ;
+C -1 ; WX 600 ; N LL ; B -45 0 694 562 ;
+C -1 ; WX 600 ; N agrave ; B 62 -15 592 661 ;
+C -1 ; WX 600 ; N Zcaron ; B 62 0 659 790 ;
+C -1 ; WX 600 ; N Scedilla ; B 54 -206 672 582 ;
+C -1 ; WX 600 ; N Idot ; B 77 0 642 748 ;
+C -1 ; WX 600 ; N Iacute ; B 77 0 642 784 ;
+C -1 ; WX 600 ; N indent ; B 99 45 579 372 ;
+C -1 ; WX 600 ; N Ugrave ; B 101 -18 715 784 ;
+C -1 ; WX 600 ; N scaron ; B 67 -17 632 667 ;
+C -1 ; WX 600 ; N overscore ; B 123 579 734 629 ;
+C -1 ; WX 600 ; N Aring ; B -9 0 631 801 ;
+C -1 ; WX 600 ; N Ccedilla ; B 74 -206 674 580 ;
+C -1 ; WX 600 ; N Igrave ; B 77 0 642 784 ;
+C -1 ; WX 600 ; N brokenbar ; B 218 -175 488 675 ;
+C -1 ; WX 600 ; N Oacute ; B 74 -18 645 784 ;
+C -1 ; WX 600 ; N otilde ; B 71 -15 642 636 ;
+C -1 ; WX 600 ; N Yacute ; B 109 0 708 784 ;
+C -1 ; WX 600 ; N lira ; B 107 -28 650 611 ;
+C -1 ; WX 600 ; N Icircumflex ; B 77 0 642 780 ;
+C -1 ; WX 600 ; N Atilde ; B -9 0 638 759 ;
+C -1 ; WX 600 ; N Uacute ; B 101 -18 715 784 ;
+C -1 ; WX 600 ; N Ydieresis ; B 109 0 708 748 ;
+C -1 ; WX 600 ; N ydieresis ; B -20 -142 694 625 ;
+C -1 ; WX 600 ; N idieresis ; B 77 0 552 625 ;
+C -1 ; WX 600 ; N Adieresis ; B -9 0 631 748 ;
+C -1 ; WX 600 ; N mu ; B 50 -142 591 439 ;
+C -1 ; WX 600 ; N trademark ; B 86 230 868 562 ;
+C -1 ; WX 600 ; N oacute ; B 71 -15 622 661 ;
+C -1 ; WX 600 ; N acircumflex ; B 62 -15 592 657 ;
+C -1 ; WX 600 ; N Agrave ; B -9 0 631 784 ;
+C -1 ; WX 600 ; N return ; B 79 0 700 562 ;
+C -1 ; WX 600 ; N atilde ; B 62 -15 642 636 ;
+C -1 ; WX 600 ; N square ; B 19 0 700 562 ;
+C -1 ; WX 600 ; N registered ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N stop ; B 19 0 700 562 ;
+C -1 ; WX 600 ; N udieresis ; B 70 -15 591 625 ;
+C -1 ; WX 600 ; N arrowup ; B 244 3 556 626 ;
+C -1 ; WX 600 ; N igrave ; B 77 0 545 661 ;
+C -1 ; WX 600 ; N Edieresis ; B 25 0 669 748 ;
+C -1 ; WX 600 ; N zcaron ; B 81 0 632 667 ;
+C -1 ; WX 600 ; N arrowboth ; B 40 143 688 455 ;
+C -1 ; WX 600 ; N gcaron ; B 41 -146 673 667 ;
+C -1 ; WX 600 ; N arrowleft ; B 40 143 708 455 ;
+C -1 ; WX 600 ; N aacute ; B 62 -15 608 661 ;
+C -1 ; WX 600 ; N ocircumflex ; B 71 -15 622 657 ;
+C -1 ; WX 600 ; N scedilla ; B 67 -206 607 459 ;
+C -1 ; WX 600 ; N ograve ; B 71 -15 622 661 ;
+C -1 ; WX 600 ; N onehalf ; B 23 -60 715 661 ;
+C -1 ; WX 600 ; N ugrave ; B 70 -15 591 661 ;
+C -1 ; WX 600 ; N Ntilde ; B 8 -12 729 759 ;
+C -1 ; WX 600 ; N iacute ; B 77 0 608 661 ;
+C -1 ; WX 600 ; N arrowright ; B 20 143 688 455 ;
+C -1 ; WX 600 ; N Thorn ; B 48 0 619 562 ;
+C -1 ; WX 600 ; N Egrave ; B 25 0 669 784 ;
+C -1 ; WX 600 ; N thorn ; B -31 -142 622 626 ;
+C -1 ; WX 600 ; N aring ; B 62 -15 592 678 ;
+C -1 ; WX 600 ; N yacute ; B -20 -142 694 661 ;
+C -1 ; WX 600 ; N icircumflex ; B 77 0 566 657 ;
+EndCharMetrics
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 56 123 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex -4 123 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 6 123 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave -24 123 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 16 123 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde -4 123 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 56 123 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 26 123 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 26 123 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 26 123 ;
+CC Gcaron 2 ; PCC G 0 0 ; PCC caron 36 123 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 26 123 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 26 123 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 26 123 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 26 123 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 26 123 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 26 123 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 26 123 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 26 123 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 26 123 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 26 123 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 26 123 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 56 123 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 26 123 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 26 123 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave -4 123 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 56 123 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 26 123 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 26 123 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 0 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex -20 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis -10 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave -30 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 0 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 0 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 0 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 0 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 0 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 0 0 ;
+CC gcaron 2 ; PCC g 0 0 ; PCC caron -40 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute 0 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -40 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -40 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave 0 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 0 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 0 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 0 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 0 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 0 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 0 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 0 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 0 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex -20 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis -20 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave -30 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 30 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 10 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 0 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrr8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrr8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..f60ec9433e3df13b2053db49dc70cb3ba20efdf2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrr8a.afm
@@ -0,0 +1,344 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1990, 1991 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Tue Sep 17 07:47:21 1991
+Comment UniqueID 36347
+Comment VMusage 31037 39405
+FontName Courier
+FullName Courier
+FamilyName Courier
+Weight Medium
+ItalicAngle 0
+IsFixedPitch true
+FontBBox -28 -250 628 805
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.004
+Notice Copyright (c) 1989, 1990, 1991 Adobe Systems Incorporated. All rights reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 562
+XHeight 426
+Ascender 629
+Descender -157
+StartCharMetrics 260
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 236 -15 364 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 187 328 413 562 ;
+C 35 ; WX 600 ; N numbersign ; B 93 -32 507 639 ;
+C 36 ; WX 600 ; N dollar ; B 105 -126 496 662 ;
+C 37 ; WX 600 ; N percent ; B 81 -15 518 622 ;
+C 38 ; WX 600 ; N ampersand ; B 63 -15 538 543 ;
+C 39 ; WX 600 ; N quoteright ; B 213 328 376 562 ;
+C 40 ; WX 600 ; N parenleft ; B 269 -108 440 622 ;
+C 41 ; WX 600 ; N parenright ; B 160 -108 331 622 ;
+C 42 ; WX 600 ; N asterisk ; B 116 257 484 607 ;
+C 43 ; WX 600 ; N plus ; B 80 44 520 470 ;
+C 44 ; WX 600 ; N comma ; B 181 -112 344 122 ;
+C 45 ; WX 600 ; N hyphen ; B 103 231 497 285 ;
+C 46 ; WX 600 ; N period ; B 229 -15 371 109 ;
+C 47 ; WX 600 ; N slash ; B 125 -80 475 629 ;
+C 48 ; WX 600 ; N zero ; B 106 -15 494 622 ;
+C 49 ; WX 600 ; N one ; B 96 0 505 622 ;
+C 50 ; WX 600 ; N two ; B 70 0 471 622 ;
+C 51 ; WX 600 ; N three ; B 75 -15 466 622 ;
+C 52 ; WX 600 ; N four ; B 78 0 500 622 ;
+C 53 ; WX 600 ; N five ; B 92 -15 497 607 ;
+C 54 ; WX 600 ; N six ; B 111 -15 497 622 ;
+C 55 ; WX 600 ; N seven ; B 82 0 483 607 ;
+C 56 ; WX 600 ; N eight ; B 102 -15 498 622 ;
+C 57 ; WX 600 ; N nine ; B 96 -15 489 622 ;
+C 58 ; WX 600 ; N colon ; B 229 -15 371 385 ;
+C 59 ; WX 600 ; N semicolon ; B 181 -112 371 385 ;
+C 60 ; WX 600 ; N less ; B 41 42 519 472 ;
+C 61 ; WX 600 ; N equal ; B 80 138 520 376 ;
+C 62 ; WX 600 ; N greater ; B 66 42 544 472 ;
+C 63 ; WX 600 ; N question ; B 129 -15 492 572 ;
+C 64 ; WX 600 ; N at ; B 77 -15 533 622 ;
+C 65 ; WX 600 ; N A ; B 3 0 597 562 ;
+C 66 ; WX 600 ; N B ; B 43 0 559 562 ;
+C 67 ; WX 600 ; N C ; B 41 -18 540 580 ;
+C 68 ; WX 600 ; N D ; B 43 0 574 562 ;
+C 69 ; WX 600 ; N E ; B 53 0 550 562 ;
+C 70 ; WX 600 ; N F ; B 53 0 545 562 ;
+C 71 ; WX 600 ; N G ; B 31 -18 575 580 ;
+C 72 ; WX 600 ; N H ; B 32 0 568 562 ;
+C 73 ; WX 600 ; N I ; B 96 0 504 562 ;
+C 74 ; WX 600 ; N J ; B 34 -18 566 562 ;
+C 75 ; WX 600 ; N K ; B 38 0 582 562 ;
+C 76 ; WX 600 ; N L ; B 47 0 554 562 ;
+C 77 ; WX 600 ; N M ; B 4 0 596 562 ;
+C 78 ; WX 600 ; N N ; B 7 -13 593 562 ;
+C 79 ; WX 600 ; N O ; B 43 -18 557 580 ;
+C 80 ; WX 600 ; N P ; B 79 0 558 562 ;
+C 81 ; WX 600 ; N Q ; B 43 -138 557 580 ;
+C 82 ; WX 600 ; N R ; B 38 0 588 562 ;
+C 83 ; WX 600 ; N S ; B 72 -20 529 580 ;
+C 84 ; WX 600 ; N T ; B 38 0 563 562 ;
+C 85 ; WX 600 ; N U ; B 17 -18 583 562 ;
+C 86 ; WX 600 ; N V ; B -4 -13 604 562 ;
+C 87 ; WX 600 ; N W ; B -3 -13 603 562 ;
+C 88 ; WX 600 ; N X ; B 23 0 577 562 ;
+C 89 ; WX 600 ; N Y ; B 24 0 576 562 ;
+C 90 ; WX 600 ; N Z ; B 86 0 514 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 269 -108 442 622 ;
+C 92 ; WX 600 ; N backslash ; B 118 -80 482 629 ;
+C 93 ; WX 600 ; N bracketright ; B 158 -108 331 622 ;
+C 94 ; WX 600 ; N asciicircum ; B 94 354 506 622 ;
+C 95 ; WX 600 ; N underscore ; B 0 -125 600 -75 ;
+C 96 ; WX 600 ; N quoteleft ; B 224 328 387 562 ;
+C 97 ; WX 600 ; N a ; B 53 -15 559 441 ;
+C 98 ; WX 600 ; N b ; B 14 -15 575 629 ;
+C 99 ; WX 600 ; N c ; B 66 -15 529 441 ;
+C 100 ; WX 600 ; N d ; B 45 -15 591 629 ;
+C 101 ; WX 600 ; N e ; B 66 -15 548 441 ;
+C 102 ; WX 600 ; N f ; B 114 0 531 629 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 45 -157 566 441 ;
+C 104 ; WX 600 ; N h ; B 18 0 582 629 ;
+C 105 ; WX 600 ; N i ; B 95 0 505 657 ;
+C 106 ; WX 600 ; N j ; B 82 -157 410 657 ;
+C 107 ; WX 600 ; N k ; B 43 0 580 629 ;
+C 108 ; WX 600 ; N l ; B 95 0 505 629 ;
+C 109 ; WX 600 ; N m ; B -5 0 605 441 ;
+C 110 ; WX 600 ; N n ; B 26 0 575 441 ;
+C 111 ; WX 600 ; N o ; B 62 -15 538 441 ;
+C 112 ; WX 600 ; N p ; B 9 -157 555 441 ;
+C 113 ; WX 600 ; N q ; B 45 -157 591 441 ;
+C 114 ; WX 600 ; N r ; B 60 0 559 441 ;
+C 115 ; WX 600 ; N s ; B 80 -15 513 441 ;
+C 116 ; WX 600 ; N t ; B 87 -15 530 561 ;
+C 117 ; WX 600 ; N u ; B 21 -15 562 426 ;
+C 118 ; WX 600 ; N v ; B 10 -10 590 426 ;
+C 119 ; WX 600 ; N w ; B -4 -10 604 426 ;
+C 120 ; WX 600 ; N x ; B 20 0 580 426 ;
+C 121 ; WX 600 ; N y ; B 7 -157 592 426 ;
+C 122 ; WX 600 ; N z ; B 99 0 502 426 ;
+C 123 ; WX 600 ; N braceleft ; B 182 -108 437 622 ;
+C 124 ; WX 600 ; N bar ; B 275 -250 326 750 ;
+C 125 ; WX 600 ; N braceright ; B 163 -108 418 622 ;
+C 126 ; WX 600 ; N asciitilde ; B 63 197 540 320 ;
+C 161 ; WX 600 ; N exclamdown ; B 236 -157 364 430 ;
+C 162 ; WX 600 ; N cent ; B 96 -49 500 614 ;
+C 163 ; WX 600 ; N sterling ; B 84 -21 521 611 ;
+C 164 ; WX 600 ; N fraction ; B 92 -57 509 665 ;
+C 165 ; WX 600 ; N yen ; B 26 0 574 562 ;
+C 166 ; WX 600 ; N florin ; B 4 -143 539 622 ;
+C 167 ; WX 600 ; N section ; B 113 -78 488 580 ;
+C 168 ; WX 600 ; N currency ; B 73 58 527 506 ;
+C 169 ; WX 600 ; N quotesingle ; B 259 328 341 562 ;
+C 170 ; WX 600 ; N quotedblleft ; B 143 328 471 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 37 70 563 446 ;
+C 172 ; WX 600 ; N guilsinglleft ; B 149 70 451 446 ;
+C 173 ; WX 600 ; N guilsinglright ; B 149 70 451 446 ;
+C 174 ; WX 600 ; N fi ; B 3 0 597 629 ;
+C 175 ; WX 600 ; N fl ; B 3 0 597 629 ;
+C 177 ; WX 600 ; N endash ; B 75 231 525 285 ;
+C 178 ; WX 600 ; N dagger ; B 141 -78 459 580 ;
+C 179 ; WX 600 ; N daggerdbl ; B 141 -78 459 580 ;
+C 180 ; WX 600 ; N periodcentered ; B 222 189 378 327 ;
+C 182 ; WX 600 ; N paragraph ; B 50 -78 511 562 ;
+C 183 ; WX 600 ; N bullet ; B 172 130 428 383 ;
+C 184 ; WX 600 ; N quotesinglbase ; B 213 -134 376 100 ;
+C 185 ; WX 600 ; N quotedblbase ; B 143 -134 457 100 ;
+C 186 ; WX 600 ; N quotedblright ; B 143 328 457 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 37 70 563 446 ;
+C 188 ; WX 600 ; N ellipsis ; B 37 -15 563 111 ;
+C 189 ; WX 600 ; N perthousand ; B 3 -15 600 622 ;
+C 191 ; WX 600 ; N questiondown ; B 108 -157 471 430 ;
+C 193 ; WX 600 ; N grave ; B 151 497 378 672 ;
+C 194 ; WX 600 ; N acute ; B 242 497 469 672 ;
+C 195 ; WX 600 ; N circumflex ; B 124 477 476 654 ;
+C 196 ; WX 600 ; N tilde ; B 105 489 503 606 ;
+C 197 ; WX 600 ; N macron ; B 120 525 480 565 ;
+C 198 ; WX 600 ; N breve ; B 153 501 447 609 ;
+C 199 ; WX 600 ; N dotaccent ; B 249 477 352 580 ;
+C 200 ; WX 600 ; N dieresis ; B 148 492 453 595 ;
+C 202 ; WX 600 ; N ring ; B 218 463 382 627 ;
+C 203 ; WX 600 ; N cedilla ; B 224 -151 362 10 ;
+C 205 ; WX 600 ; N hungarumlaut ; B 133 497 540 672 ;
+C 206 ; WX 600 ; N ogonek ; B 227 -151 370 0 ;
+C 207 ; WX 600 ; N caron ; B 124 492 476 669 ;
+C 208 ; WX 600 ; N emdash ; B 0 231 600 285 ;
+C 225 ; WX 600 ; N AE ; B 3 0 550 562 ;
+C 227 ; WX 600 ; N ordfeminine ; B 156 249 442 580 ;
+C 232 ; WX 600 ; N Lslash ; B 47 0 554 562 ;
+C 233 ; WX 600 ; N Oslash ; B 43 -80 557 629 ;
+C 234 ; WX 600 ; N OE ; B 7 0 567 562 ;
+C 235 ; WX 600 ; N ordmasculine ; B 157 249 443 580 ;
+C 241 ; WX 600 ; N ae ; B 19 -15 570 441 ;
+C 245 ; WX 600 ; N dotlessi ; B 95 0 505 426 ;
+C 248 ; WX 600 ; N lslash ; B 95 0 505 629 ;
+C 249 ; WX 600 ; N oslash ; B 62 -80 538 506 ;
+C 250 ; WX 600 ; N oe ; B 19 -15 559 441 ;
+C 251 ; WX 600 ; N germandbls ; B 48 -15 588 629 ;
+C -1 ; WX 600 ; N Odieresis ; B 43 -18 557 731 ;
+C -1 ; WX 600 ; N logicalnot ; B 87 108 513 369 ;
+C -1 ; WX 600 ; N minus ; B 80 232 520 283 ;
+C -1 ; WX 600 ; N merge ; B 160 -15 440 436 ;
+C -1 ; WX 600 ; N degree ; B 123 269 477 622 ;
+C -1 ; WX 600 ; N dectab ; B 18 0 582 227 ;
+C -1 ; WX 600 ; N ll ; B 18 0 567 629 ;
+C -1 ; WX 600 ; N IJ ; B 32 -18 583 562 ;
+C -1 ; WX 600 ; N Eacute ; B 53 0 550 793 ;
+C -1 ; WX 600 ; N Ocircumflex ; B 43 -18 557 775 ;
+C -1 ; WX 600 ; N ucircumflex ; B 21 -15 562 654 ;
+C -1 ; WX 600 ; N left ; B 70 68 530 348 ;
+C -1 ; WX 600 ; N threesuperior ; B 155 240 406 622 ;
+C -1 ; WX 600 ; N up ; B 160 0 440 437 ;
+C -1 ; WX 600 ; N multiply ; B 87 43 515 470 ;
+C -1 ; WX 600 ; N Scaron ; B 72 -20 529 805 ;
+C -1 ; WX 600 ; N tab ; B 19 0 581 562 ;
+C -1 ; WX 600 ; N Ucircumflex ; B 17 -18 583 775 ;
+C -1 ; WX 600 ; N divide ; B 87 48 513 467 ;
+C -1 ; WX 600 ; N Acircumflex ; B 3 0 597 775 ;
+C -1 ; WX 600 ; N eacute ; B 66 -15 548 672 ;
+C -1 ; WX 600 ; N uacute ; B 21 -15 562 672 ;
+C -1 ; WX 600 ; N Aacute ; B 3 0 597 793 ;
+C -1 ; WX 600 ; N copyright ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N twosuperior ; B 177 249 424 622 ;
+C -1 ; WX 600 ; N Ecircumflex ; B 53 0 550 775 ;
+C -1 ; WX 600 ; N ntilde ; B 26 0 575 606 ;
+C -1 ; WX 600 ; N down ; B 160 -15 440 426 ;
+C -1 ; WX 600 ; N center ; B 40 14 560 580 ;
+C -1 ; WX 600 ; N onesuperior ; B 172 249 428 622 ;
+C -1 ; WX 600 ; N ij ; B 37 -157 490 657 ;
+C -1 ; WX 600 ; N edieresis ; B 66 -15 548 595 ;
+C -1 ; WX 600 ; N graybox ; B 76 0 525 599 ;
+C -1 ; WX 600 ; N odieresis ; B 62 -15 538 595 ;
+C -1 ; WX 600 ; N Ograve ; B 43 -18 557 793 ;
+C -1 ; WX 600 ; N threequarters ; B 8 -56 593 666 ;
+C -1 ; WX 600 ; N plusminus ; B 87 44 513 558 ;
+C -1 ; WX 600 ; N prescription ; B 27 -15 577 562 ;
+C -1 ; WX 600 ; N eth ; B 62 -15 538 629 ;
+C -1 ; WX 600 ; N largebullet ; B 261 220 339 297 ;
+C -1 ; WX 600 ; N egrave ; B 66 -15 548 672 ;
+C -1 ; WX 600 ; N ccedilla ; B 66 -151 529 441 ;
+C -1 ; WX 600 ; N notegraphic ; B 136 -15 464 572 ;
+C -1 ; WX 600 ; N Udieresis ; B 17 -18 583 731 ;
+C -1 ; WX 600 ; N Gcaron ; B 31 -18 575 805 ;
+C -1 ; WX 600 ; N arrowdown ; B 116 -15 484 608 ;
+C -1 ; WX 600 ; N format ; B 5 -157 56 607 ;
+C -1 ; WX 600 ; N Otilde ; B 43 -18 557 732 ;
+C -1 ; WX 600 ; N Idieresis ; B 96 0 504 731 ;
+C -1 ; WX 600 ; N adieresis ; B 53 -15 559 595 ;
+C -1 ; WX 600 ; N ecircumflex ; B 66 -15 548 654 ;
+C -1 ; WX 600 ; N Eth ; B 30 0 574 562 ;
+C -1 ; WX 600 ; N onequarter ; B 0 -57 600 665 ;
+C -1 ; WX 600 ; N LL ; B 8 0 592 562 ;
+C -1 ; WX 600 ; N agrave ; B 53 -15 559 672 ;
+C -1 ; WX 600 ; N Zcaron ; B 86 0 514 805 ;
+C -1 ; WX 600 ; N Scedilla ; B 72 -151 529 580 ;
+C -1 ; WX 600 ; N Idot ; B 96 0 504 716 ;
+C -1 ; WX 600 ; N Iacute ; B 96 0 504 793 ;
+C -1 ; WX 600 ; N indent ; B 70 68 530 348 ;
+C -1 ; WX 600 ; N Ugrave ; B 17 -18 583 793 ;
+C -1 ; WX 600 ; N scaron ; B 80 -15 513 669 ;
+C -1 ; WX 600 ; N overscore ; B 0 579 600 629 ;
+C -1 ; WX 600 ; N Aring ; B 3 0 597 753 ;
+C -1 ; WX 600 ; N Ccedilla ; B 41 -151 540 580 ;
+C -1 ; WX 600 ; N Igrave ; B 96 0 504 793 ;
+C -1 ; WX 600 ; N brokenbar ; B 275 -175 326 675 ;
+C -1 ; WX 600 ; N Oacute ; B 43 -18 557 793 ;
+C -1 ; WX 600 ; N otilde ; B 62 -15 538 606 ;
+C -1 ; WX 600 ; N Yacute ; B 24 0 576 793 ;
+C -1 ; WX 600 ; N lira ; B 73 -21 521 611 ;
+C -1 ; WX 600 ; N Icircumflex ; B 96 0 504 775 ;
+C -1 ; WX 600 ; N Atilde ; B 3 0 597 732 ;
+C -1 ; WX 600 ; N Uacute ; B 17 -18 583 793 ;
+C -1 ; WX 600 ; N Ydieresis ; B 24 0 576 731 ;
+C -1 ; WX 600 ; N ydieresis ; B 7 -157 592 595 ;
+C -1 ; WX 600 ; N idieresis ; B 95 0 505 595 ;
+C -1 ; WX 600 ; N Adieresis ; B 3 0 597 731 ;
+C -1 ; WX 600 ; N mu ; B 21 -157 562 426 ;
+C -1 ; WX 600 ; N trademark ; B -23 263 623 562 ;
+C -1 ; WX 600 ; N oacute ; B 62 -15 538 672 ;
+C -1 ; WX 600 ; N acircumflex ; B 53 -15 559 654 ;
+C -1 ; WX 600 ; N Agrave ; B 3 0 597 793 ;
+C -1 ; WX 600 ; N return ; B 19 0 581 562 ;
+C -1 ; WX 600 ; N atilde ; B 53 -15 559 606 ;
+C -1 ; WX 600 ; N square ; B 19 0 581 562 ;
+C -1 ; WX 600 ; N registered ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N stop ; B 19 0 581 562 ;
+C -1 ; WX 600 ; N udieresis ; B 21 -15 562 595 ;
+C -1 ; WX 600 ; N arrowup ; B 116 0 484 623 ;
+C -1 ; WX 600 ; N igrave ; B 95 0 505 672 ;
+C -1 ; WX 600 ; N Edieresis ; B 53 0 550 731 ;
+C -1 ; WX 600 ; N zcaron ; B 99 0 502 669 ;
+C -1 ; WX 600 ; N arrowboth ; B -28 115 628 483 ;
+C -1 ; WX 600 ; N gcaron ; B 45 -157 566 669 ;
+C -1 ; WX 600 ; N arrowleft ; B -24 115 624 483 ;
+C -1 ; WX 600 ; N aacute ; B 53 -15 559 672 ;
+C -1 ; WX 600 ; N ocircumflex ; B 62 -15 538 654 ;
+C -1 ; WX 600 ; N scedilla ; B 80 -151 513 441 ;
+C -1 ; WX 600 ; N ograve ; B 62 -15 538 672 ;
+C -1 ; WX 600 ; N onehalf ; B 0 -57 611 665 ;
+C -1 ; WX 600 ; N ugrave ; B 21 -15 562 672 ;
+C -1 ; WX 600 ; N Ntilde ; B 7 -13 593 732 ;
+C -1 ; WX 600 ; N iacute ; B 95 0 505 672 ;
+C -1 ; WX 600 ; N arrowright ; B -24 115 624 483 ;
+C -1 ; WX 600 ; N Thorn ; B 79 0 538 562 ;
+C -1 ; WX 600 ; N Egrave ; B 53 0 550 793 ;
+C -1 ; WX 600 ; N thorn ; B -6 -157 555 629 ;
+C -1 ; WX 600 ; N aring ; B 53 -15 559 627 ;
+C -1 ; WX 600 ; N yacute ; B 7 -157 592 672 ;
+C -1 ; WX 600 ; N icircumflex ; B 94 0 505 654 ;
+EndCharMetrics
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 20 121 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex -30 121 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis -30 136 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave -30 121 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring -15 126 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 0 126 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 30 121 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 0 121 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 0 136 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 0 121 ;
+CC Gcaron 2 ; PCC G 0 0 ; PCC caron 0 136 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 0 121 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 0 121 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 0 136 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 0 121 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 0 126 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 0 121 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 0 121 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 0 136 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 0 121 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 0 126 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 30 136 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 30 121 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 0 121 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 0 136 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave -30 121 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 30 121 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 0 136 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 0 136 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 0 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 0 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 0 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 0 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 0 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 0 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 0 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 0 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 0 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 0 0 ;
+CC gcaron 2 ; PCC g 0 0 ; PCC caron -30 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute 0 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -30 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -30 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -30 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 0 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 0 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 0 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 0 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 0 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 0 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 0 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute -10 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex -10 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 0 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave -30 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute -20 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis -10 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 10 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrro8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrro8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..b053a4ca2d68b6a96f7363418509622bc3cfc8dc
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pcrro8a.afm
@@ -0,0 +1,344 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1990, 1991 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Tue Sep 17 09:42:19 1991
+Comment UniqueID 36350
+Comment VMusage 9174 52297
+FontName Courier-Oblique
+FullName Courier Oblique
+FamilyName Courier
+Weight Medium
+ItalicAngle -12
+IsFixedPitch true
+FontBBox -28 -250 742 805
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.004
+Notice Copyright (c) 1989, 1990, 1991 Adobe Systems Incorporated. All rights reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 562
+XHeight 426
+Ascender 629
+Descender -157
+StartCharMetrics 260
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 243 -15 464 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 273 328 532 562 ;
+C 35 ; WX 600 ; N numbersign ; B 133 -32 596 639 ;
+C 36 ; WX 600 ; N dollar ; B 108 -126 596 662 ;
+C 37 ; WX 600 ; N percent ; B 134 -15 599 622 ;
+C 38 ; WX 600 ; N ampersand ; B 87 -15 580 543 ;
+C 39 ; WX 600 ; N quoteright ; B 283 328 495 562 ;
+C 40 ; WX 600 ; N parenleft ; B 313 -108 572 622 ;
+C 41 ; WX 600 ; N parenright ; B 137 -108 396 622 ;
+C 42 ; WX 600 ; N asterisk ; B 212 257 580 607 ;
+C 43 ; WX 600 ; N plus ; B 129 44 580 470 ;
+C 44 ; WX 600 ; N comma ; B 157 -112 370 122 ;
+C 45 ; WX 600 ; N hyphen ; B 152 231 558 285 ;
+C 46 ; WX 600 ; N period ; B 238 -15 382 109 ;
+C 47 ; WX 600 ; N slash ; B 112 -80 604 629 ;
+C 48 ; WX 600 ; N zero ; B 154 -15 575 622 ;
+C 49 ; WX 600 ; N one ; B 98 0 515 622 ;
+C 50 ; WX 600 ; N two ; B 70 0 568 622 ;
+C 51 ; WX 600 ; N three ; B 82 -15 538 622 ;
+C 52 ; WX 600 ; N four ; B 108 0 541 622 ;
+C 53 ; WX 600 ; N five ; B 99 -15 589 607 ;
+C 54 ; WX 600 ; N six ; B 155 -15 629 622 ;
+C 55 ; WX 600 ; N seven ; B 182 0 612 607 ;
+C 56 ; WX 600 ; N eight ; B 132 -15 588 622 ;
+C 57 ; WX 600 ; N nine ; B 93 -15 574 622 ;
+C 58 ; WX 600 ; N colon ; B 238 -15 441 385 ;
+C 59 ; WX 600 ; N semicolon ; B 157 -112 441 385 ;
+C 60 ; WX 600 ; N less ; B 96 42 610 472 ;
+C 61 ; WX 600 ; N equal ; B 109 138 600 376 ;
+C 62 ; WX 600 ; N greater ; B 85 42 599 472 ;
+C 63 ; WX 600 ; N question ; B 222 -15 583 572 ;
+C 64 ; WX 600 ; N at ; B 127 -15 582 622 ;
+C 65 ; WX 600 ; N A ; B 3 0 607 562 ;
+C 66 ; WX 600 ; N B ; B 43 0 616 562 ;
+C 67 ; WX 600 ; N C ; B 93 -18 655 580 ;
+C 68 ; WX 600 ; N D ; B 43 0 645 562 ;
+C 69 ; WX 600 ; N E ; B 53 0 660 562 ;
+C 70 ; WX 600 ; N F ; B 53 0 660 562 ;
+C 71 ; WX 600 ; N G ; B 83 -18 645 580 ;
+C 72 ; WX 600 ; N H ; B 32 0 687 562 ;
+C 73 ; WX 600 ; N I ; B 96 0 623 562 ;
+C 74 ; WX 600 ; N J ; B 52 -18 685 562 ;
+C 75 ; WX 600 ; N K ; B 38 0 671 562 ;
+C 76 ; WX 600 ; N L ; B 47 0 607 562 ;
+C 77 ; WX 600 ; N M ; B 4 0 715 562 ;
+C 78 ; WX 600 ; N N ; B 7 -13 712 562 ;
+C 79 ; WX 600 ; N O ; B 94 -18 625 580 ;
+C 80 ; WX 600 ; N P ; B 79 0 644 562 ;
+C 81 ; WX 600 ; N Q ; B 95 -138 625 580 ;
+C 82 ; WX 600 ; N R ; B 38 0 598 562 ;
+C 83 ; WX 600 ; N S ; B 76 -20 650 580 ;
+C 84 ; WX 600 ; N T ; B 108 0 665 562 ;
+C 85 ; WX 600 ; N U ; B 125 -18 702 562 ;
+C 86 ; WX 600 ; N V ; B 105 -13 723 562 ;
+C 87 ; WX 600 ; N W ; B 106 -13 722 562 ;
+C 88 ; WX 600 ; N X ; B 23 0 675 562 ;
+C 89 ; WX 600 ; N Y ; B 133 0 695 562 ;
+C 90 ; WX 600 ; N Z ; B 86 0 610 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 246 -108 574 622 ;
+C 92 ; WX 600 ; N backslash ; B 249 -80 468 629 ;
+C 93 ; WX 600 ; N bracketright ; B 135 -108 463 622 ;
+C 94 ; WX 600 ; N asciicircum ; B 175 354 587 622 ;
+C 95 ; WX 600 ; N underscore ; B -27 -125 584 -75 ;
+C 96 ; WX 600 ; N quoteleft ; B 343 328 457 562 ;
+C 97 ; WX 600 ; N a ; B 76 -15 569 441 ;
+C 98 ; WX 600 ; N b ; B 29 -15 625 629 ;
+C 99 ; WX 600 ; N c ; B 106 -15 608 441 ;
+C 100 ; WX 600 ; N d ; B 85 -15 640 629 ;
+C 101 ; WX 600 ; N e ; B 106 -15 598 441 ;
+C 102 ; WX 600 ; N f ; B 114 0 662 629 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 61 -157 657 441 ;
+C 104 ; WX 600 ; N h ; B 33 0 592 629 ;
+C 105 ; WX 600 ; N i ; B 95 0 515 657 ;
+C 106 ; WX 600 ; N j ; B 52 -157 550 657 ;
+C 107 ; WX 600 ; N k ; B 58 0 633 629 ;
+C 108 ; WX 600 ; N l ; B 95 0 515 629 ;
+C 109 ; WX 600 ; N m ; B -5 0 615 441 ;
+C 110 ; WX 600 ; N n ; B 26 0 585 441 ;
+C 111 ; WX 600 ; N o ; B 102 -15 588 441 ;
+C 112 ; WX 600 ; N p ; B -24 -157 605 441 ;
+C 113 ; WX 600 ; N q ; B 85 -157 682 441 ;
+C 114 ; WX 600 ; N r ; B 60 0 636 441 ;
+C 115 ; WX 600 ; N s ; B 78 -15 584 441 ;
+C 116 ; WX 600 ; N t ; B 167 -15 561 561 ;
+C 117 ; WX 600 ; N u ; B 101 -15 572 426 ;
+C 118 ; WX 600 ; N v ; B 90 -10 681 426 ;
+C 119 ; WX 600 ; N w ; B 76 -10 695 426 ;
+C 120 ; WX 600 ; N x ; B 20 0 655 426 ;
+C 121 ; WX 600 ; N y ; B -4 -157 683 426 ;
+C 122 ; WX 600 ; N z ; B 99 0 593 426 ;
+C 123 ; WX 600 ; N braceleft ; B 233 -108 569 622 ;
+C 124 ; WX 600 ; N bar ; B 222 -250 485 750 ;
+C 125 ; WX 600 ; N braceright ; B 140 -108 477 622 ;
+C 126 ; WX 600 ; N asciitilde ; B 116 197 600 320 ;
+C 161 ; WX 600 ; N exclamdown ; B 225 -157 445 430 ;
+C 162 ; WX 600 ; N cent ; B 151 -49 588 614 ;
+C 163 ; WX 600 ; N sterling ; B 124 -21 621 611 ;
+C 164 ; WX 600 ; N fraction ; B 84 -57 646 665 ;
+C 165 ; WX 600 ; N yen ; B 120 0 693 562 ;
+C 166 ; WX 600 ; N florin ; B -26 -143 671 622 ;
+C 167 ; WX 600 ; N section ; B 104 -78 590 580 ;
+C 168 ; WX 600 ; N currency ; B 94 58 628 506 ;
+C 169 ; WX 600 ; N quotesingle ; B 345 328 460 562 ;
+C 170 ; WX 600 ; N quotedblleft ; B 262 328 541 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 92 70 652 446 ;
+C 172 ; WX 600 ; N guilsinglleft ; B 204 70 540 446 ;
+C 173 ; WX 600 ; N guilsinglright ; B 170 70 506 446 ;
+C 174 ; WX 600 ; N fi ; B 3 0 619 629 ;
+C 175 ; WX 600 ; N fl ; B 3 0 619 629 ;
+C 177 ; WX 600 ; N endash ; B 124 231 586 285 ;
+C 178 ; WX 600 ; N dagger ; B 217 -78 546 580 ;
+C 179 ; WX 600 ; N daggerdbl ; B 163 -78 546 580 ;
+C 180 ; WX 600 ; N periodcentered ; B 275 189 434 327 ;
+C 182 ; WX 600 ; N paragraph ; B 100 -78 630 562 ;
+C 183 ; WX 600 ; N bullet ; B 224 130 485 383 ;
+C 184 ; WX 600 ; N quotesinglbase ; B 185 -134 397 100 ;
+C 185 ; WX 600 ; N quotedblbase ; B 115 -134 478 100 ;
+C 186 ; WX 600 ; N quotedblright ; B 213 328 576 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 58 70 618 446 ;
+C 188 ; WX 600 ; N ellipsis ; B 46 -15 575 111 ;
+C 189 ; WX 600 ; N perthousand ; B 59 -15 627 622 ;
+C 191 ; WX 600 ; N questiondown ; B 105 -157 466 430 ;
+C 193 ; WX 600 ; N grave ; B 294 497 484 672 ;
+C 194 ; WX 600 ; N acute ; B 348 497 612 672 ;
+C 195 ; WX 600 ; N circumflex ; B 229 477 581 654 ;
+C 196 ; WX 600 ; N tilde ; B 212 489 629 606 ;
+C 197 ; WX 600 ; N macron ; B 232 525 600 565 ;
+C 198 ; WX 600 ; N breve ; B 279 501 576 609 ;
+C 199 ; WX 600 ; N dotaccent ; B 360 477 466 580 ;
+C 200 ; WX 600 ; N dieresis ; B 262 492 570 595 ;
+C 202 ; WX 600 ; N ring ; B 332 463 500 627 ;
+C 203 ; WX 600 ; N cedilla ; B 197 -151 344 10 ;
+C 205 ; WX 600 ; N hungarumlaut ; B 239 497 683 672 ;
+C 206 ; WX 600 ; N ogonek ; B 207 -151 348 0 ;
+C 207 ; WX 600 ; N caron ; B 262 492 614 669 ;
+C 208 ; WX 600 ; N emdash ; B 49 231 661 285 ;
+C 225 ; WX 600 ; N AE ; B 3 0 655 562 ;
+C 227 ; WX 600 ; N ordfeminine ; B 209 249 512 580 ;
+C 232 ; WX 600 ; N Lslash ; B 47 0 607 562 ;
+C 233 ; WX 600 ; N Oslash ; B 94 -80 625 629 ;
+C 234 ; WX 600 ; N OE ; B 59 0 672 562 ;
+C 235 ; WX 600 ; N ordmasculine ; B 210 249 535 580 ;
+C 241 ; WX 600 ; N ae ; B 41 -15 626 441 ;
+C 245 ; WX 600 ; N dotlessi ; B 95 0 515 426 ;
+C 248 ; WX 600 ; N lslash ; B 95 0 583 629 ;
+C 249 ; WX 600 ; N oslash ; B 102 -80 588 506 ;
+C 250 ; WX 600 ; N oe ; B 54 -15 615 441 ;
+C 251 ; WX 600 ; N germandbls ; B 48 -15 617 629 ;
+C -1 ; WX 600 ; N Odieresis ; B 94 -18 625 731 ;
+C -1 ; WX 600 ; N logicalnot ; B 155 108 591 369 ;
+C -1 ; WX 600 ; N minus ; B 129 232 580 283 ;
+C -1 ; WX 600 ; N merge ; B 187 -15 503 436 ;
+C -1 ; WX 600 ; N degree ; B 214 269 576 622 ;
+C -1 ; WX 600 ; N dectab ; B 18 0 593 227 ;
+C -1 ; WX 600 ; N ll ; B 33 0 616 629 ;
+C -1 ; WX 600 ; N IJ ; B 32 -18 702 562 ;
+C -1 ; WX 600 ; N Eacute ; B 53 0 668 793 ;
+C -1 ; WX 600 ; N Ocircumflex ; B 94 -18 625 775 ;
+C -1 ; WX 600 ; N ucircumflex ; B 101 -15 572 654 ;
+C -1 ; WX 600 ; N left ; B 114 68 580 348 ;
+C -1 ; WX 600 ; N threesuperior ; B 213 240 501 622 ;
+C -1 ; WX 600 ; N up ; B 223 0 503 437 ;
+C -1 ; WX 600 ; N multiply ; B 103 43 607 470 ;
+C -1 ; WX 600 ; N Scaron ; B 76 -20 673 805 ;
+C -1 ; WX 600 ; N tab ; B 19 0 641 562 ;
+C -1 ; WX 600 ; N Ucircumflex ; B 125 -18 702 775 ;
+C -1 ; WX 600 ; N divide ; B 136 48 573 467 ;
+C -1 ; WX 600 ; N Acircumflex ; B 3 0 607 775 ;
+C -1 ; WX 600 ; N eacute ; B 106 -15 612 672 ;
+C -1 ; WX 600 ; N uacute ; B 101 -15 602 672 ;
+C -1 ; WX 600 ; N Aacute ; B 3 0 658 793 ;
+C -1 ; WX 600 ; N copyright ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N twosuperior ; B 230 249 535 622 ;
+C -1 ; WX 600 ; N Ecircumflex ; B 53 0 660 775 ;
+C -1 ; WX 600 ; N ntilde ; B 26 0 629 606 ;
+C -1 ; WX 600 ; N down ; B 187 -15 467 426 ;
+C -1 ; WX 600 ; N center ; B 103 14 623 580 ;
+C -1 ; WX 600 ; N onesuperior ; B 231 249 491 622 ;
+C -1 ; WX 600 ; N ij ; B 37 -157 630 657 ;
+C -1 ; WX 600 ; N edieresis ; B 106 -15 598 595 ;
+C -1 ; WX 600 ; N graybox ; B 76 0 652 599 ;
+C -1 ; WX 600 ; N odieresis ; B 102 -15 588 595 ;
+C -1 ; WX 600 ; N Ograve ; B 94 -18 625 793 ;
+C -1 ; WX 600 ; N threequarters ; B 73 -56 659 666 ;
+C -1 ; WX 600 ; N plusminus ; B 96 44 594 558 ;
+C -1 ; WX 600 ; N prescription ; B 27 -15 617 562 ;
+C -1 ; WX 600 ; N eth ; B 102 -15 639 629 ;
+C -1 ; WX 600 ; N largebullet ; B 315 220 395 297 ;
+C -1 ; WX 600 ; N egrave ; B 106 -15 598 672 ;
+C -1 ; WX 600 ; N ccedilla ; B 106 -151 614 441 ;
+C -1 ; WX 600 ; N notegraphic ; B 143 -15 564 572 ;
+C -1 ; WX 600 ; N Udieresis ; B 125 -18 702 731 ;
+C -1 ; WX 600 ; N Gcaron ; B 83 -18 645 805 ;
+C -1 ; WX 600 ; N arrowdown ; B 152 -15 520 608 ;
+C -1 ; WX 600 ; N format ; B -28 -157 185 607 ;
+C -1 ; WX 600 ; N Otilde ; B 94 -18 656 732 ;
+C -1 ; WX 600 ; N Idieresis ; B 96 0 623 731 ;
+C -1 ; WX 600 ; N adieresis ; B 76 -15 570 595 ;
+C -1 ; WX 600 ; N ecircumflex ; B 106 -15 598 654 ;
+C -1 ; WX 600 ; N Eth ; B 43 0 645 562 ;
+C -1 ; WX 600 ; N onequarter ; B 65 -57 674 665 ;
+C -1 ; WX 600 ; N LL ; B 8 0 647 562 ;
+C -1 ; WX 600 ; N agrave ; B 76 -15 569 672 ;
+C -1 ; WX 600 ; N Zcaron ; B 86 0 643 805 ;
+C -1 ; WX 600 ; N Scedilla ; B 76 -151 650 580 ;
+C -1 ; WX 600 ; N Idot ; B 96 0 623 716 ;
+C -1 ; WX 600 ; N Iacute ; B 96 0 638 793 ;
+C -1 ; WX 600 ; N indent ; B 108 68 574 348 ;
+C -1 ; WX 600 ; N Ugrave ; B 125 -18 702 793 ;
+C -1 ; WX 600 ; N scaron ; B 78 -15 614 669 ;
+C -1 ; WX 600 ; N overscore ; B 123 579 734 629 ;
+C -1 ; WX 600 ; N Aring ; B 3 0 607 753 ;
+C -1 ; WX 600 ; N Ccedilla ; B 93 -151 658 580 ;
+C -1 ; WX 600 ; N Igrave ; B 96 0 623 793 ;
+C -1 ; WX 600 ; N brokenbar ; B 238 -175 469 675 ;
+C -1 ; WX 600 ; N Oacute ; B 94 -18 638 793 ;
+C -1 ; WX 600 ; N otilde ; B 102 -15 629 606 ;
+C -1 ; WX 600 ; N Yacute ; B 133 0 695 793 ;
+C -1 ; WX 600 ; N lira ; B 118 -21 621 611 ;
+C -1 ; WX 600 ; N Icircumflex ; B 96 0 623 775 ;
+C -1 ; WX 600 ; N Atilde ; B 3 0 656 732 ;
+C -1 ; WX 600 ; N Uacute ; B 125 -18 702 793 ;
+C -1 ; WX 600 ; N Ydieresis ; B 133 0 695 731 ;
+C -1 ; WX 600 ; N ydieresis ; B -4 -157 683 595 ;
+C -1 ; WX 600 ; N idieresis ; B 95 0 540 595 ;
+C -1 ; WX 600 ; N Adieresis ; B 3 0 607 731 ;
+C -1 ; WX 600 ; N mu ; B 72 -157 572 426 ;
+C -1 ; WX 600 ; N trademark ; B 75 263 742 562 ;
+C -1 ; WX 600 ; N oacute ; B 102 -15 612 672 ;
+C -1 ; WX 600 ; N acircumflex ; B 76 -15 581 654 ;
+C -1 ; WX 600 ; N Agrave ; B 3 0 607 793 ;
+C -1 ; WX 600 ; N return ; B 79 0 700 562 ;
+C -1 ; WX 600 ; N atilde ; B 76 -15 629 606 ;
+C -1 ; WX 600 ; N square ; B 19 0 700 562 ;
+C -1 ; WX 600 ; N registered ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N stop ; B 19 0 700 562 ;
+C -1 ; WX 600 ; N udieresis ; B 101 -15 572 595 ;
+C -1 ; WX 600 ; N arrowup ; B 209 0 577 623 ;
+C -1 ; WX 600 ; N igrave ; B 95 0 515 672 ;
+C -1 ; WX 600 ; N Edieresis ; B 53 0 660 731 ;
+C -1 ; WX 600 ; N zcaron ; B 99 0 624 669 ;
+C -1 ; WX 600 ; N arrowboth ; B 36 115 692 483 ;
+C -1 ; WX 600 ; N gcaron ; B 61 -157 657 669 ;
+C -1 ; WX 600 ; N arrowleft ; B 40 115 693 483 ;
+C -1 ; WX 600 ; N aacute ; B 76 -15 612 672 ;
+C -1 ; WX 600 ; N ocircumflex ; B 102 -15 588 654 ;
+C -1 ; WX 600 ; N scedilla ; B 78 -151 584 441 ;
+C -1 ; WX 600 ; N ograve ; B 102 -15 588 672 ;
+C -1 ; WX 600 ; N onehalf ; B 65 -57 669 665 ;
+C -1 ; WX 600 ; N ugrave ; B 101 -15 572 672 ;
+C -1 ; WX 600 ; N Ntilde ; B 7 -13 712 732 ;
+C -1 ; WX 600 ; N iacute ; B 95 0 612 672 ;
+C -1 ; WX 600 ; N arrowright ; B 34 115 688 483 ;
+C -1 ; WX 600 ; N Thorn ; B 79 0 606 562 ;
+C -1 ; WX 600 ; N Egrave ; B 53 0 660 793 ;
+C -1 ; WX 600 ; N thorn ; B -24 -157 605 629 ;
+C -1 ; WX 600 ; N aring ; B 76 -15 569 627 ;
+C -1 ; WX 600 ; N yacute ; B -4 -157 683 672 ;
+C -1 ; WX 600 ; N icircumflex ; B 95 0 551 654 ;
+EndCharMetrics
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 46 121 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex -4 121 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis -1 136 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave -4 121 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 12 126 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 27 126 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 56 121 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 26 121 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 29 136 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 26 121 ;
+CC Gcaron 2 ; PCC G 0 0 ; PCC caron 29 136 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 26 121 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 26 121 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 29 136 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 26 121 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 27 126 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 26 121 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 26 121 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 29 136 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 26 121 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 27 126 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 59 136 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 56 121 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 26 121 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 29 136 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave -4 121 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 56 121 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 29 136 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 29 136 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 0 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 0 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 0 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 0 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 0 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 0 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 0 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 0 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 0 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 0 0 ;
+CC gcaron 2 ; PCC g 0 0 ; PCC caron -30 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute 0 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -30 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -30 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -30 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 0 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 0 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 0 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 0 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 0 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 0 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 0 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute -10 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex -10 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 0 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave -30 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute -20 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis -10 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 10 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvb8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvb8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..a1e1b33c403a0bb0b7b9326d49237be7e0ad6d7d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvb8a.afm
@@ -0,0 +1,570 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu Mar 15 09:43:00 1990
+Comment UniqueID 28357
+Comment VMusage 26878 33770
+FontName Helvetica-Bold
+FullName Helvetica Bold
+FamilyName Helvetica
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -170 -228 1003 962
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 532
+Ascender 718
+Descender -207
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 90 0 244 718 ;
+C 34 ; WX 474 ; N quotedbl ; B 98 447 376 718 ;
+C 35 ; WX 556 ; N numbersign ; B 18 0 538 698 ;
+C 36 ; WX 556 ; N dollar ; B 30 -115 523 775 ;
+C 37 ; WX 889 ; N percent ; B 28 -19 861 710 ;
+C 38 ; WX 722 ; N ampersand ; B 54 -19 701 718 ;
+C 39 ; WX 278 ; N quoteright ; B 69 445 209 718 ;
+C 40 ; WX 333 ; N parenleft ; B 35 -208 314 734 ;
+C 41 ; WX 333 ; N parenright ; B 19 -208 298 734 ;
+C 42 ; WX 389 ; N asterisk ; B 27 387 362 718 ;
+C 43 ; WX 584 ; N plus ; B 40 0 544 506 ;
+C 44 ; WX 278 ; N comma ; B 64 -168 214 146 ;
+C 45 ; WX 333 ; N hyphen ; B 27 215 306 345 ;
+C 46 ; WX 278 ; N period ; B 64 0 214 146 ;
+C 47 ; WX 278 ; N slash ; B -33 -19 311 737 ;
+C 48 ; WX 556 ; N zero ; B 32 -19 524 710 ;
+C 49 ; WX 556 ; N one ; B 69 0 378 710 ;
+C 50 ; WX 556 ; N two ; B 26 0 511 710 ;
+C 51 ; WX 556 ; N three ; B 27 -19 516 710 ;
+C 52 ; WX 556 ; N four ; B 27 0 526 710 ;
+C 53 ; WX 556 ; N five ; B 27 -19 516 698 ;
+C 54 ; WX 556 ; N six ; B 31 -19 520 710 ;
+C 55 ; WX 556 ; N seven ; B 25 0 528 698 ;
+C 56 ; WX 556 ; N eight ; B 32 -19 524 710 ;
+C 57 ; WX 556 ; N nine ; B 30 -19 522 710 ;
+C 58 ; WX 333 ; N colon ; B 92 0 242 512 ;
+C 59 ; WX 333 ; N semicolon ; B 92 -168 242 512 ;
+C 60 ; WX 584 ; N less ; B 38 -8 546 514 ;
+C 61 ; WX 584 ; N equal ; B 40 87 544 419 ;
+C 62 ; WX 584 ; N greater ; B 38 -8 546 514 ;
+C 63 ; WX 611 ; N question ; B 60 0 556 727 ;
+C 64 ; WX 975 ; N at ; B 118 -19 856 737 ;
+C 65 ; WX 722 ; N A ; B 20 0 702 718 ;
+C 66 ; WX 722 ; N B ; B 76 0 669 718 ;
+C 67 ; WX 722 ; N C ; B 44 -19 684 737 ;
+C 68 ; WX 722 ; N D ; B 76 0 685 718 ;
+C 69 ; WX 667 ; N E ; B 76 0 621 718 ;
+C 70 ; WX 611 ; N F ; B 76 0 587 718 ;
+C 71 ; WX 778 ; N G ; B 44 -19 713 737 ;
+C 72 ; WX 722 ; N H ; B 71 0 651 718 ;
+C 73 ; WX 278 ; N I ; B 64 0 214 718 ;
+C 74 ; WX 556 ; N J ; B 22 -18 484 718 ;
+C 75 ; WX 722 ; N K ; B 87 0 722 718 ;
+C 76 ; WX 611 ; N L ; B 76 0 583 718 ;
+C 77 ; WX 833 ; N M ; B 69 0 765 718 ;
+C 78 ; WX 722 ; N N ; B 69 0 654 718 ;
+C 79 ; WX 778 ; N O ; B 44 -19 734 737 ;
+C 80 ; WX 667 ; N P ; B 76 0 627 718 ;
+C 81 ; WX 778 ; N Q ; B 44 -52 737 737 ;
+C 82 ; WX 722 ; N R ; B 76 0 677 718 ;
+C 83 ; WX 667 ; N S ; B 39 -19 629 737 ;
+C 84 ; WX 611 ; N T ; B 14 0 598 718 ;
+C 85 ; WX 722 ; N U ; B 72 -19 651 718 ;
+C 86 ; WX 667 ; N V ; B 19 0 648 718 ;
+C 87 ; WX 944 ; N W ; B 16 0 929 718 ;
+C 88 ; WX 667 ; N X ; B 14 0 653 718 ;
+C 89 ; WX 667 ; N Y ; B 15 0 653 718 ;
+C 90 ; WX 611 ; N Z ; B 25 0 586 718 ;
+C 91 ; WX 333 ; N bracketleft ; B 63 -196 309 722 ;
+C 92 ; WX 278 ; N backslash ; B -33 -19 311 737 ;
+C 93 ; WX 333 ; N bracketright ; B 24 -196 270 722 ;
+C 94 ; WX 584 ; N asciicircum ; B 62 323 522 698 ;
+C 95 ; WX 556 ; N underscore ; B 0 -125 556 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 69 454 209 727 ;
+C 97 ; WX 556 ; N a ; B 29 -14 527 546 ;
+C 98 ; WX 611 ; N b ; B 61 -14 578 718 ;
+C 99 ; WX 556 ; N c ; B 34 -14 524 546 ;
+C 100 ; WX 611 ; N d ; B 34 -14 551 718 ;
+C 101 ; WX 556 ; N e ; B 23 -14 528 546 ;
+C 102 ; WX 333 ; N f ; B 10 0 318 727 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B 40 -217 553 546 ;
+C 104 ; WX 611 ; N h ; B 65 0 546 718 ;
+C 105 ; WX 278 ; N i ; B 69 0 209 725 ;
+C 106 ; WX 278 ; N j ; B 3 -214 209 725 ;
+C 107 ; WX 556 ; N k ; B 69 0 562 718 ;
+C 108 ; WX 278 ; N l ; B 69 0 209 718 ;
+C 109 ; WX 889 ; N m ; B 64 0 826 546 ;
+C 110 ; WX 611 ; N n ; B 65 0 546 546 ;
+C 111 ; WX 611 ; N o ; B 34 -14 578 546 ;
+C 112 ; WX 611 ; N p ; B 62 -207 578 546 ;
+C 113 ; WX 611 ; N q ; B 34 -207 552 546 ;
+C 114 ; WX 389 ; N r ; B 64 0 373 546 ;
+C 115 ; WX 556 ; N s ; B 30 -14 519 546 ;
+C 116 ; WX 333 ; N t ; B 10 -6 309 676 ;
+C 117 ; WX 611 ; N u ; B 66 -14 545 532 ;
+C 118 ; WX 556 ; N v ; B 13 0 543 532 ;
+C 119 ; WX 778 ; N w ; B 10 0 769 532 ;
+C 120 ; WX 556 ; N x ; B 15 0 541 532 ;
+C 121 ; WX 556 ; N y ; B 10 -214 539 532 ;
+C 122 ; WX 500 ; N z ; B 20 0 480 532 ;
+C 123 ; WX 389 ; N braceleft ; B 48 -196 365 722 ;
+C 124 ; WX 280 ; N bar ; B 84 -19 196 737 ;
+C 125 ; WX 389 ; N braceright ; B 24 -196 341 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 61 163 523 343 ;
+C 161 ; WX 333 ; N exclamdown ; B 90 -186 244 532 ;
+C 162 ; WX 556 ; N cent ; B 34 -118 524 628 ;
+C 163 ; WX 556 ; N sterling ; B 28 -16 541 718 ;
+C 164 ; WX 167 ; N fraction ; B -170 -19 336 710 ;
+C 165 ; WX 556 ; N yen ; B -9 0 565 698 ;
+C 166 ; WX 556 ; N florin ; B -10 -210 516 737 ;
+C 167 ; WX 556 ; N section ; B 34 -184 522 727 ;
+C 168 ; WX 556 ; N currency ; B -3 76 559 636 ;
+C 169 ; WX 238 ; N quotesingle ; B 70 447 168 718 ;
+C 170 ; WX 500 ; N quotedblleft ; B 64 454 436 727 ;
+C 171 ; WX 556 ; N guillemotleft ; B 88 76 468 484 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 83 76 250 484 ;
+C 173 ; WX 333 ; N guilsinglright ; B 83 76 250 484 ;
+C 174 ; WX 611 ; N fi ; B 10 0 542 727 ;
+C 175 ; WX 611 ; N fl ; B 10 0 542 727 ;
+C 177 ; WX 556 ; N endash ; B 0 227 556 333 ;
+C 178 ; WX 556 ; N dagger ; B 36 -171 520 718 ;
+C 179 ; WX 556 ; N daggerdbl ; B 36 -171 520 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 58 172 220 334 ;
+C 182 ; WX 556 ; N paragraph ; B -8 -191 539 700 ;
+C 183 ; WX 350 ; N bullet ; B 10 194 340 524 ;
+C 184 ; WX 278 ; N quotesinglbase ; B 69 -146 209 127 ;
+C 185 ; WX 500 ; N quotedblbase ; B 64 -146 436 127 ;
+C 186 ; WX 500 ; N quotedblright ; B 64 445 436 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 88 76 468 484 ;
+C 188 ; WX 1000 ; N ellipsis ; B 92 0 908 146 ;
+C 189 ; WX 1000 ; N perthousand ; B -3 -19 1003 710 ;
+C 191 ; WX 611 ; N questiondown ; B 55 -195 551 532 ;
+C 193 ; WX 333 ; N grave ; B -23 604 225 750 ;
+C 194 ; WX 333 ; N acute ; B 108 604 356 750 ;
+C 195 ; WX 333 ; N circumflex ; B -10 604 343 750 ;
+C 196 ; WX 333 ; N tilde ; B -17 610 350 737 ;
+C 197 ; WX 333 ; N macron ; B -6 604 339 678 ;
+C 198 ; WX 333 ; N breve ; B -2 604 335 750 ;
+C 199 ; WX 333 ; N dotaccent ; B 104 614 230 729 ;
+C 200 ; WX 333 ; N dieresis ; B 6 614 327 729 ;
+C 202 ; WX 333 ; N ring ; B 59 568 275 776 ;
+C 203 ; WX 333 ; N cedilla ; B 6 -228 245 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 9 604 486 750 ;
+C 206 ; WX 333 ; N ogonek ; B 71 -228 304 0 ;
+C 207 ; WX 333 ; N caron ; B -10 604 343 750 ;
+C 208 ; WX 1000 ; N emdash ; B 0 227 1000 333 ;
+C 225 ; WX 1000 ; N AE ; B 5 0 954 718 ;
+C 227 ; WX 370 ; N ordfeminine ; B 22 276 347 737 ;
+C 232 ; WX 611 ; N Lslash ; B -20 0 583 718 ;
+C 233 ; WX 778 ; N Oslash ; B 33 -27 744 745 ;
+C 234 ; WX 1000 ; N OE ; B 37 -19 961 737 ;
+C 235 ; WX 365 ; N ordmasculine ; B 6 276 360 737 ;
+C 241 ; WX 889 ; N ae ; B 29 -14 858 546 ;
+C 245 ; WX 278 ; N dotlessi ; B 69 0 209 532 ;
+C 248 ; WX 278 ; N lslash ; B -18 0 296 718 ;
+C 249 ; WX 611 ; N oslash ; B 22 -29 589 560 ;
+C 250 ; WX 944 ; N oe ; B 34 -14 912 546 ;
+C 251 ; WX 611 ; N germandbls ; B 69 -14 579 731 ;
+C -1 ; WX 611 ; N Zcaron ; B 25 0 586 936 ;
+C -1 ; WX 556 ; N ccedilla ; B 34 -228 524 546 ;
+C -1 ; WX 556 ; N ydieresis ; B 10 -214 539 729 ;
+C -1 ; WX 556 ; N atilde ; B 29 -14 527 737 ;
+C -1 ; WX 278 ; N icircumflex ; B -37 0 316 750 ;
+C -1 ; WX 333 ; N threesuperior ; B 8 271 326 710 ;
+C -1 ; WX 556 ; N ecircumflex ; B 23 -14 528 750 ;
+C -1 ; WX 611 ; N thorn ; B 62 -208 578 718 ;
+C -1 ; WX 556 ; N egrave ; B 23 -14 528 750 ;
+C -1 ; WX 333 ; N twosuperior ; B 9 283 324 710 ;
+C -1 ; WX 556 ; N eacute ; B 23 -14 528 750 ;
+C -1 ; WX 611 ; N otilde ; B 34 -14 578 737 ;
+C -1 ; WX 722 ; N Aacute ; B 20 0 702 936 ;
+C -1 ; WX 611 ; N ocircumflex ; B 34 -14 578 750 ;
+C -1 ; WX 556 ; N yacute ; B 10 -214 539 750 ;
+C -1 ; WX 611 ; N udieresis ; B 66 -14 545 729 ;
+C -1 ; WX 834 ; N threequarters ; B 16 -19 799 710 ;
+C -1 ; WX 556 ; N acircumflex ; B 29 -14 527 750 ;
+C -1 ; WX 722 ; N Eth ; B -5 0 685 718 ;
+C -1 ; WX 556 ; N edieresis ; B 23 -14 528 729 ;
+C -1 ; WX 611 ; N ugrave ; B 66 -14 545 750 ;
+C -1 ; WX 1000 ; N trademark ; B 44 306 956 718 ;
+C -1 ; WX 611 ; N ograve ; B 34 -14 578 750 ;
+C -1 ; WX 556 ; N scaron ; B 30 -14 519 750 ;
+C -1 ; WX 278 ; N Idieresis ; B -21 0 300 915 ;
+C -1 ; WX 611 ; N uacute ; B 66 -14 545 750 ;
+C -1 ; WX 556 ; N agrave ; B 29 -14 527 750 ;
+C -1 ; WX 611 ; N ntilde ; B 65 0 546 737 ;
+C -1 ; WX 556 ; N aring ; B 29 -14 527 776 ;
+C -1 ; WX 500 ; N zcaron ; B 20 0 480 750 ;
+C -1 ; WX 278 ; N Icircumflex ; B -37 0 316 936 ;
+C -1 ; WX 722 ; N Ntilde ; B 69 0 654 923 ;
+C -1 ; WX 611 ; N ucircumflex ; B 66 -14 545 750 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 76 0 621 936 ;
+C -1 ; WX 278 ; N Iacute ; B 64 0 329 936 ;
+C -1 ; WX 722 ; N Ccedilla ; B 44 -228 684 737 ;
+C -1 ; WX 778 ; N Odieresis ; B 44 -19 734 915 ;
+C -1 ; WX 667 ; N Scaron ; B 39 -19 629 936 ;
+C -1 ; WX 667 ; N Edieresis ; B 76 0 621 915 ;
+C -1 ; WX 278 ; N Igrave ; B -50 0 214 936 ;
+C -1 ; WX 556 ; N adieresis ; B 29 -14 527 729 ;
+C -1 ; WX 778 ; N Ograve ; B 44 -19 734 936 ;
+C -1 ; WX 667 ; N Egrave ; B 76 0 621 936 ;
+C -1 ; WX 667 ; N Ydieresis ; B 15 0 653 915 ;
+C -1 ; WX 737 ; N registered ; B -11 -19 748 737 ;
+C -1 ; WX 778 ; N Otilde ; B 44 -19 734 923 ;
+C -1 ; WX 834 ; N onequarter ; B 26 -19 766 710 ;
+C -1 ; WX 722 ; N Ugrave ; B 72 -19 651 936 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 72 -19 651 936 ;
+C -1 ; WX 667 ; N Thorn ; B 76 0 627 718 ;
+C -1 ; WX 584 ; N divide ; B 40 -42 544 548 ;
+C -1 ; WX 722 ; N Atilde ; B 20 0 702 923 ;
+C -1 ; WX 722 ; N Uacute ; B 72 -19 651 936 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 44 -19 734 936 ;
+C -1 ; WX 584 ; N logicalnot ; B 40 108 544 419 ;
+C -1 ; WX 722 ; N Aring ; B 20 0 702 962 ;
+C -1 ; WX 278 ; N idieresis ; B -21 0 300 729 ;
+C -1 ; WX 278 ; N iacute ; B 69 0 329 750 ;
+C -1 ; WX 556 ; N aacute ; B 29 -14 527 750 ;
+C -1 ; WX 584 ; N plusminus ; B 40 0 544 506 ;
+C -1 ; WX 584 ; N multiply ; B 40 1 545 505 ;
+C -1 ; WX 722 ; N Udieresis ; B 72 -19 651 915 ;
+C -1 ; WX 584 ; N minus ; B 40 197 544 309 ;
+C -1 ; WX 333 ; N onesuperior ; B 26 283 237 710 ;
+C -1 ; WX 667 ; N Eacute ; B 76 0 621 936 ;
+C -1 ; WX 722 ; N Acircumflex ; B 20 0 702 936 ;
+C -1 ; WX 737 ; N copyright ; B -11 -19 749 737 ;
+C -1 ; WX 722 ; N Agrave ; B 20 0 702 936 ;
+C -1 ; WX 611 ; N odieresis ; B 34 -14 578 729 ;
+C -1 ; WX 611 ; N oacute ; B 34 -14 578 750 ;
+C -1 ; WX 400 ; N degree ; B 57 426 343 712 ;
+C -1 ; WX 278 ; N igrave ; B -50 0 209 750 ;
+C -1 ; WX 611 ; N mu ; B 66 -207 545 532 ;
+C -1 ; WX 778 ; N Oacute ; B 44 -19 734 936 ;
+C -1 ; WX 611 ; N eth ; B 34 -14 578 737 ;
+C -1 ; WX 722 ; N Adieresis ; B 20 0 702 915 ;
+C -1 ; WX 667 ; N Yacute ; B 15 0 653 936 ;
+C -1 ; WX 280 ; N brokenbar ; B 84 -19 196 737 ;
+C -1 ; WX 834 ; N onehalf ; B 26 -19 794 710 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 209
+
+KPX A y -30
+KPX A w -30
+KPX A v -40
+KPX A u -30
+KPX A Y -110
+KPX A W -60
+KPX A V -80
+KPX A U -50
+KPX A T -90
+KPX A Q -40
+KPX A O -40
+KPX A G -50
+KPX A C -40
+
+KPX B U -10
+KPX B A -30
+
+KPX D period -30
+KPX D comma -30
+KPX D Y -70
+KPX D W -40
+KPX D V -40
+KPX D A -40
+
+KPX F period -100
+KPX F comma -100
+KPX F a -20
+KPX F A -80
+
+KPX J u -20
+KPX J period -20
+KPX J comma -20
+KPX J A -20
+
+KPX K y -40
+KPX K u -30
+KPX K o -35
+KPX K e -15
+KPX K O -30
+
+KPX L y -30
+KPX L quoteright -140
+KPX L quotedblright -140
+KPX L Y -120
+KPX L W -80
+KPX L V -110
+KPX L T -90
+
+KPX O period -40
+KPX O comma -40
+KPX O Y -70
+KPX O X -50
+KPX O W -50
+KPX O V -50
+KPX O T -40
+KPX O A -50
+
+KPX P period -120
+KPX P o -40
+KPX P e -30
+KPX P comma -120
+KPX P a -30
+KPX P A -100
+
+KPX Q period 20
+KPX Q comma 20
+KPX Q U -10
+
+KPX R Y -50
+KPX R W -40
+KPX R V -50
+KPX R U -20
+KPX R T -20
+KPX R O -20
+
+KPX T y -60
+KPX T w -60
+KPX T u -90
+KPX T semicolon -40
+KPX T r -80
+KPX T period -80
+KPX T o -80
+KPX T hyphen -120
+KPX T e -60
+KPX T comma -80
+KPX T colon -40
+KPX T a -80
+KPX T O -40
+KPX T A -90
+
+KPX U period -30
+KPX U comma -30
+KPX U A -50
+
+KPX V u -60
+KPX V semicolon -40
+KPX V period -120
+KPX V o -90
+KPX V hyphen -80
+KPX V e -50
+KPX V comma -120
+KPX V colon -40
+KPX V a -60
+KPX V O -50
+KPX V G -50
+KPX V A -80
+
+KPX W y -20
+KPX W u -45
+KPX W semicolon -10
+KPX W period -80
+KPX W o -60
+KPX W hyphen -40
+KPX W e -35
+KPX W comma -80
+KPX W colon -10
+KPX W a -40
+KPX W O -20
+KPX W A -60
+
+KPX Y u -100
+KPX Y semicolon -50
+KPX Y period -100
+KPX Y o -100
+KPX Y e -80
+KPX Y comma -100
+KPX Y colon -50
+KPX Y a -90
+KPX Y O -70
+KPX Y A -110
+
+KPX a y -20
+KPX a w -15
+KPX a v -15
+KPX a g -10
+
+KPX b y -20
+KPX b v -20
+KPX b u -20
+KPX b l -10
+
+KPX c y -10
+KPX c l -20
+KPX c k -20
+KPX c h -10
+
+KPX colon space -40
+
+KPX comma space -40
+KPX comma quoteright -120
+KPX comma quotedblright -120
+
+KPX d y -15
+KPX d w -15
+KPX d v -15
+KPX d d -10
+
+KPX e y -15
+KPX e x -15
+KPX e w -15
+KPX e v -15
+KPX e period 20
+KPX e comma 10
+
+KPX f quoteright 30
+KPX f quotedblright 30
+KPX f period -10
+KPX f o -20
+KPX f e -10
+KPX f comma -10
+
+KPX g g -10
+KPX g e 10
+
+KPX h y -20
+
+KPX k o -15
+
+KPX l y -15
+KPX l w -15
+
+KPX m y -30
+KPX m u -20
+
+KPX n y -20
+KPX n v -40
+KPX n u -10
+
+KPX o y -20
+KPX o x -30
+KPX o w -15
+KPX o v -20
+
+KPX p y -15
+
+KPX period space -40
+KPX period quoteright -120
+KPX period quotedblright -120
+
+KPX quotedblright space -80
+
+KPX quoteleft quoteleft -46
+
+KPX quoteright v -20
+KPX quoteright space -80
+KPX quoteright s -60
+KPX quoteright r -40
+KPX quoteright quoteright -46
+KPX quoteright l -20
+KPX quoteright d -80
+
+KPX r y 10
+KPX r v 10
+KPX r t 20
+KPX r s -15
+KPX r q -20
+KPX r period -60
+KPX r o -20
+KPX r hyphen -20
+KPX r g -15
+KPX r d -20
+KPX r comma -60
+KPX r c -20
+
+KPX s w -15
+
+KPX semicolon space -40
+
+KPX space quoteleft -60
+KPX space quotedblleft -80
+KPX space Y -120
+KPX space W -80
+KPX space V -80
+KPX space T -100
+
+KPX v period -80
+KPX v o -30
+KPX v comma -80
+KPX v a -20
+
+KPX w period -40
+KPX w o -20
+KPX w comma -40
+
+KPX x e -10
+
+KPX y period -80
+KPX y o -25
+KPX y e -10
+KPX y comma -80
+KPX y a -30
+
+KPX z e 10
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 195 186 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 195 186 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 195 186 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 195 186 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 195 186 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 195 186 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 215 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 167 186 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 167 186 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 167 186 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 167 186 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -27 186 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -27 186 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -27 186 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -27 186 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 195 186 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 223 186 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 223 186 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 223 186 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 223 186 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 223 186 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 167 186 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 195 186 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 195 186 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 195 186 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 195 186 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 167 186 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 167 186 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 139 186 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 112 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 112 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 112 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 112 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 112 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 112 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 132 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 112 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 112 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 112 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 112 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -27 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -27 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -27 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -27 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 139 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 139 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 139 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 139 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 139 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 139 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 112 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 139 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 139 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 139 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 139 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 112 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 112 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 84 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvb8an.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvb8an.afm
new file mode 100644
index 0000000000000000000000000000000000000000..b7c69698bb23e52391ab7f64fcb1cf4d440ffd16
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvb8an.afm
@@ -0,0 +1,570 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu Mar 15 11:47:27 1990
+Comment UniqueID 28398
+Comment VMusage 7614 43068
+FontName Helvetica-Narrow-Bold
+FullName Helvetica Narrow Bold
+FamilyName Helvetica
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -139 -228 822 962
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 532
+Ascender 718
+Descender -207
+StartCharMetrics 228
+C 32 ; WX 228 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 273 ; N exclam ; B 74 0 200 718 ;
+C 34 ; WX 389 ; N quotedbl ; B 80 447 308 718 ;
+C 35 ; WX 456 ; N numbersign ; B 15 0 441 698 ;
+C 36 ; WX 456 ; N dollar ; B 25 -115 429 775 ;
+C 37 ; WX 729 ; N percent ; B 23 -19 706 710 ;
+C 38 ; WX 592 ; N ampersand ; B 44 -19 575 718 ;
+C 39 ; WX 228 ; N quoteright ; B 57 445 171 718 ;
+C 40 ; WX 273 ; N parenleft ; B 29 -208 257 734 ;
+C 41 ; WX 273 ; N parenright ; B 16 -208 244 734 ;
+C 42 ; WX 319 ; N asterisk ; B 22 387 297 718 ;
+C 43 ; WX 479 ; N plus ; B 33 0 446 506 ;
+C 44 ; WX 228 ; N comma ; B 52 -168 175 146 ;
+C 45 ; WX 273 ; N hyphen ; B 22 215 251 345 ;
+C 46 ; WX 228 ; N period ; B 52 0 175 146 ;
+C 47 ; WX 228 ; N slash ; B -27 -19 255 737 ;
+C 48 ; WX 456 ; N zero ; B 26 -19 430 710 ;
+C 49 ; WX 456 ; N one ; B 57 0 310 710 ;
+C 50 ; WX 456 ; N two ; B 21 0 419 710 ;
+C 51 ; WX 456 ; N three ; B 22 -19 423 710 ;
+C 52 ; WX 456 ; N four ; B 22 0 431 710 ;
+C 53 ; WX 456 ; N five ; B 22 -19 423 698 ;
+C 54 ; WX 456 ; N six ; B 25 -19 426 710 ;
+C 55 ; WX 456 ; N seven ; B 20 0 433 698 ;
+C 56 ; WX 456 ; N eight ; B 26 -19 430 710 ;
+C 57 ; WX 456 ; N nine ; B 25 -19 428 710 ;
+C 58 ; WX 273 ; N colon ; B 75 0 198 512 ;
+C 59 ; WX 273 ; N semicolon ; B 75 -168 198 512 ;
+C 60 ; WX 479 ; N less ; B 31 -8 448 514 ;
+C 61 ; WX 479 ; N equal ; B 33 87 446 419 ;
+C 62 ; WX 479 ; N greater ; B 31 -8 448 514 ;
+C 63 ; WX 501 ; N question ; B 49 0 456 727 ;
+C 64 ; WX 800 ; N at ; B 97 -19 702 737 ;
+C 65 ; WX 592 ; N A ; B 16 0 576 718 ;
+C 66 ; WX 592 ; N B ; B 62 0 549 718 ;
+C 67 ; WX 592 ; N C ; B 36 -19 561 737 ;
+C 68 ; WX 592 ; N D ; B 62 0 562 718 ;
+C 69 ; WX 547 ; N E ; B 62 0 509 718 ;
+C 70 ; WX 501 ; N F ; B 62 0 481 718 ;
+C 71 ; WX 638 ; N G ; B 36 -19 585 737 ;
+C 72 ; WX 592 ; N H ; B 58 0 534 718 ;
+C 73 ; WX 228 ; N I ; B 52 0 175 718 ;
+C 74 ; WX 456 ; N J ; B 18 -18 397 718 ;
+C 75 ; WX 592 ; N K ; B 71 0 592 718 ;
+C 76 ; WX 501 ; N L ; B 62 0 478 718 ;
+C 77 ; WX 683 ; N M ; B 57 0 627 718 ;
+C 78 ; WX 592 ; N N ; B 57 0 536 718 ;
+C 79 ; WX 638 ; N O ; B 36 -19 602 737 ;
+C 80 ; WX 547 ; N P ; B 62 0 514 718 ;
+C 81 ; WX 638 ; N Q ; B 36 -52 604 737 ;
+C 82 ; WX 592 ; N R ; B 62 0 555 718 ;
+C 83 ; WX 547 ; N S ; B 32 -19 516 737 ;
+C 84 ; WX 501 ; N T ; B 11 0 490 718 ;
+C 85 ; WX 592 ; N U ; B 59 -19 534 718 ;
+C 86 ; WX 547 ; N V ; B 16 0 531 718 ;
+C 87 ; WX 774 ; N W ; B 13 0 762 718 ;
+C 88 ; WX 547 ; N X ; B 11 0 535 718 ;
+C 89 ; WX 547 ; N Y ; B 12 0 535 718 ;
+C 90 ; WX 501 ; N Z ; B 20 0 481 718 ;
+C 91 ; WX 273 ; N bracketleft ; B 52 -196 253 722 ;
+C 92 ; WX 228 ; N backslash ; B -27 -19 255 737 ;
+C 93 ; WX 273 ; N bracketright ; B 20 -196 221 722 ;
+C 94 ; WX 479 ; N asciicircum ; B 51 323 428 698 ;
+C 95 ; WX 456 ; N underscore ; B 0 -125 456 -75 ;
+C 96 ; WX 228 ; N quoteleft ; B 57 454 171 727 ;
+C 97 ; WX 456 ; N a ; B 24 -14 432 546 ;
+C 98 ; WX 501 ; N b ; B 50 -14 474 718 ;
+C 99 ; WX 456 ; N c ; B 28 -14 430 546 ;
+C 100 ; WX 501 ; N d ; B 28 -14 452 718 ;
+C 101 ; WX 456 ; N e ; B 19 -14 433 546 ;
+C 102 ; WX 273 ; N f ; B 8 0 261 727 ; L i fi ; L l fl ;
+C 103 ; WX 501 ; N g ; B 33 -217 453 546 ;
+C 104 ; WX 501 ; N h ; B 53 0 448 718 ;
+C 105 ; WX 228 ; N i ; B 57 0 171 725 ;
+C 106 ; WX 228 ; N j ; B 2 -214 171 725 ;
+C 107 ; WX 456 ; N k ; B 57 0 461 718 ;
+C 108 ; WX 228 ; N l ; B 57 0 171 718 ;
+C 109 ; WX 729 ; N m ; B 52 0 677 546 ;
+C 110 ; WX 501 ; N n ; B 53 0 448 546 ;
+C 111 ; WX 501 ; N o ; B 28 -14 474 546 ;
+C 112 ; WX 501 ; N p ; B 51 -207 474 546 ;
+C 113 ; WX 501 ; N q ; B 28 -207 453 546 ;
+C 114 ; WX 319 ; N r ; B 52 0 306 546 ;
+C 115 ; WX 456 ; N s ; B 25 -14 426 546 ;
+C 116 ; WX 273 ; N t ; B 8 -6 253 676 ;
+C 117 ; WX 501 ; N u ; B 54 -14 447 532 ;
+C 118 ; WX 456 ; N v ; B 11 0 445 532 ;
+C 119 ; WX 638 ; N w ; B 8 0 631 532 ;
+C 120 ; WX 456 ; N x ; B 12 0 444 532 ;
+C 121 ; WX 456 ; N y ; B 8 -214 442 532 ;
+C 122 ; WX 410 ; N z ; B 16 0 394 532 ;
+C 123 ; WX 319 ; N braceleft ; B 39 -196 299 722 ;
+C 124 ; WX 230 ; N bar ; B 69 -19 161 737 ;
+C 125 ; WX 319 ; N braceright ; B 20 -196 280 722 ;
+C 126 ; WX 479 ; N asciitilde ; B 50 163 429 343 ;
+C 161 ; WX 273 ; N exclamdown ; B 74 -186 200 532 ;
+C 162 ; WX 456 ; N cent ; B 28 -118 430 628 ;
+C 163 ; WX 456 ; N sterling ; B 23 -16 444 718 ;
+C 164 ; WX 137 ; N fraction ; B -139 -19 276 710 ;
+C 165 ; WX 456 ; N yen ; B -7 0 463 698 ;
+C 166 ; WX 456 ; N florin ; B -8 -210 423 737 ;
+C 167 ; WX 456 ; N section ; B 28 -184 428 727 ;
+C 168 ; WX 456 ; N currency ; B -2 76 458 636 ;
+C 169 ; WX 195 ; N quotesingle ; B 57 447 138 718 ;
+C 170 ; WX 410 ; N quotedblleft ; B 52 454 358 727 ;
+C 171 ; WX 456 ; N guillemotleft ; B 72 76 384 484 ;
+C 172 ; WX 273 ; N guilsinglleft ; B 68 76 205 484 ;
+C 173 ; WX 273 ; N guilsinglright ; B 68 76 205 484 ;
+C 174 ; WX 501 ; N fi ; B 8 0 444 727 ;
+C 175 ; WX 501 ; N fl ; B 8 0 444 727 ;
+C 177 ; WX 456 ; N endash ; B 0 227 456 333 ;
+C 178 ; WX 456 ; N dagger ; B 30 -171 426 718 ;
+C 179 ; WX 456 ; N daggerdbl ; B 30 -171 426 718 ;
+C 180 ; WX 228 ; N periodcentered ; B 48 172 180 334 ;
+C 182 ; WX 456 ; N paragraph ; B -7 -191 442 700 ;
+C 183 ; WX 287 ; N bullet ; B 8 194 279 524 ;
+C 184 ; WX 228 ; N quotesinglbase ; B 57 -146 171 127 ;
+C 185 ; WX 410 ; N quotedblbase ; B 52 -146 358 127 ;
+C 186 ; WX 410 ; N quotedblright ; B 52 445 358 718 ;
+C 187 ; WX 456 ; N guillemotright ; B 72 76 384 484 ;
+C 188 ; WX 820 ; N ellipsis ; B 75 0 745 146 ;
+C 189 ; WX 820 ; N perthousand ; B -2 -19 822 710 ;
+C 191 ; WX 501 ; N questiondown ; B 45 -195 452 532 ;
+C 193 ; WX 273 ; N grave ; B -19 604 184 750 ;
+C 194 ; WX 273 ; N acute ; B 89 604 292 750 ;
+C 195 ; WX 273 ; N circumflex ; B -8 604 281 750 ;
+C 196 ; WX 273 ; N tilde ; B -14 610 287 737 ;
+C 197 ; WX 273 ; N macron ; B -5 604 278 678 ;
+C 198 ; WX 273 ; N breve ; B -2 604 275 750 ;
+C 199 ; WX 273 ; N dotaccent ; B 85 614 189 729 ;
+C 200 ; WX 273 ; N dieresis ; B 5 614 268 729 ;
+C 202 ; WX 273 ; N ring ; B 48 568 225 776 ;
+C 203 ; WX 273 ; N cedilla ; B 5 -228 201 0 ;
+C 205 ; WX 273 ; N hungarumlaut ; B 7 604 399 750 ;
+C 206 ; WX 273 ; N ogonek ; B 58 -228 249 0 ;
+C 207 ; WX 273 ; N caron ; B -8 604 281 750 ;
+C 208 ; WX 820 ; N emdash ; B 0 227 820 333 ;
+C 225 ; WX 820 ; N AE ; B 4 0 782 718 ;
+C 227 ; WX 303 ; N ordfeminine ; B 18 276 285 737 ;
+C 232 ; WX 501 ; N Lslash ; B -16 0 478 718 ;
+C 233 ; WX 638 ; N Oslash ; B 27 -27 610 745 ;
+C 234 ; WX 820 ; N OE ; B 30 -19 788 737 ;
+C 235 ; WX 299 ; N ordmasculine ; B 5 276 295 737 ;
+C 241 ; WX 729 ; N ae ; B 24 -14 704 546 ;
+C 245 ; WX 228 ; N dotlessi ; B 57 0 171 532 ;
+C 248 ; WX 228 ; N lslash ; B -15 0 243 718 ;
+C 249 ; WX 501 ; N oslash ; B 18 -29 483 560 ;
+C 250 ; WX 774 ; N oe ; B 28 -14 748 546 ;
+C 251 ; WX 501 ; N germandbls ; B 57 -14 475 731 ;
+C -1 ; WX 501 ; N Zcaron ; B 20 0 481 936 ;
+C -1 ; WX 456 ; N ccedilla ; B 28 -228 430 546 ;
+C -1 ; WX 456 ; N ydieresis ; B 8 -214 442 729 ;
+C -1 ; WX 456 ; N atilde ; B 24 -14 432 737 ;
+C -1 ; WX 228 ; N icircumflex ; B -30 0 259 750 ;
+C -1 ; WX 273 ; N threesuperior ; B 7 271 267 710 ;
+C -1 ; WX 456 ; N ecircumflex ; B 19 -14 433 750 ;
+C -1 ; WX 501 ; N thorn ; B 51 -208 474 718 ;
+C -1 ; WX 456 ; N egrave ; B 19 -14 433 750 ;
+C -1 ; WX 273 ; N twosuperior ; B 7 283 266 710 ;
+C -1 ; WX 456 ; N eacute ; B 19 -14 433 750 ;
+C -1 ; WX 501 ; N otilde ; B 28 -14 474 737 ;
+C -1 ; WX 592 ; N Aacute ; B 16 0 576 936 ;
+C -1 ; WX 501 ; N ocircumflex ; B 28 -14 474 750 ;
+C -1 ; WX 456 ; N yacute ; B 8 -214 442 750 ;
+C -1 ; WX 501 ; N udieresis ; B 54 -14 447 729 ;
+C -1 ; WX 684 ; N threequarters ; B 13 -19 655 710 ;
+C -1 ; WX 456 ; N acircumflex ; B 24 -14 432 750 ;
+C -1 ; WX 592 ; N Eth ; B -4 0 562 718 ;
+C -1 ; WX 456 ; N edieresis ; B 19 -14 433 729 ;
+C -1 ; WX 501 ; N ugrave ; B 54 -14 447 750 ;
+C -1 ; WX 820 ; N trademark ; B 36 306 784 718 ;
+C -1 ; WX 501 ; N ograve ; B 28 -14 474 750 ;
+C -1 ; WX 456 ; N scaron ; B 25 -14 426 750 ;
+C -1 ; WX 228 ; N Idieresis ; B -17 0 246 915 ;
+C -1 ; WX 501 ; N uacute ; B 54 -14 447 750 ;
+C -1 ; WX 456 ; N agrave ; B 24 -14 432 750 ;
+C -1 ; WX 501 ; N ntilde ; B 53 0 448 737 ;
+C -1 ; WX 456 ; N aring ; B 24 -14 432 776 ;
+C -1 ; WX 410 ; N zcaron ; B 16 0 394 750 ;
+C -1 ; WX 228 ; N Icircumflex ; B -30 0 259 936 ;
+C -1 ; WX 592 ; N Ntilde ; B 57 0 536 923 ;
+C -1 ; WX 501 ; N ucircumflex ; B 54 -14 447 750 ;
+C -1 ; WX 547 ; N Ecircumflex ; B 62 0 509 936 ;
+C -1 ; WX 228 ; N Iacute ; B 52 0 270 936 ;
+C -1 ; WX 592 ; N Ccedilla ; B 36 -228 561 737 ;
+C -1 ; WX 638 ; N Odieresis ; B 36 -19 602 915 ;
+C -1 ; WX 547 ; N Scaron ; B 32 -19 516 936 ;
+C -1 ; WX 547 ; N Edieresis ; B 62 0 509 915 ;
+C -1 ; WX 228 ; N Igrave ; B -41 0 175 936 ;
+C -1 ; WX 456 ; N adieresis ; B 24 -14 432 729 ;
+C -1 ; WX 638 ; N Ograve ; B 36 -19 602 936 ;
+C -1 ; WX 547 ; N Egrave ; B 62 0 509 936 ;
+C -1 ; WX 547 ; N Ydieresis ; B 12 0 535 915 ;
+C -1 ; WX 604 ; N registered ; B -9 -19 613 737 ;
+C -1 ; WX 638 ; N Otilde ; B 36 -19 602 923 ;
+C -1 ; WX 684 ; N onequarter ; B 21 -19 628 710 ;
+C -1 ; WX 592 ; N Ugrave ; B 59 -19 534 936 ;
+C -1 ; WX 592 ; N Ucircumflex ; B 59 -19 534 936 ;
+C -1 ; WX 547 ; N Thorn ; B 62 0 514 718 ;
+C -1 ; WX 479 ; N divide ; B 33 -42 446 548 ;
+C -1 ; WX 592 ; N Atilde ; B 16 0 576 923 ;
+C -1 ; WX 592 ; N Uacute ; B 59 -19 534 936 ;
+C -1 ; WX 638 ; N Ocircumflex ; B 36 -19 602 936 ;
+C -1 ; WX 479 ; N logicalnot ; B 33 108 446 419 ;
+C -1 ; WX 592 ; N Aring ; B 16 0 576 962 ;
+C -1 ; WX 228 ; N idieresis ; B -17 0 246 729 ;
+C -1 ; WX 228 ; N iacute ; B 57 0 270 750 ;
+C -1 ; WX 456 ; N aacute ; B 24 -14 432 750 ;
+C -1 ; WX 479 ; N plusminus ; B 33 0 446 506 ;
+C -1 ; WX 479 ; N multiply ; B 33 1 447 505 ;
+C -1 ; WX 592 ; N Udieresis ; B 59 -19 534 915 ;
+C -1 ; WX 479 ; N minus ; B 33 197 446 309 ;
+C -1 ; WX 273 ; N onesuperior ; B 21 283 194 710 ;
+C -1 ; WX 547 ; N Eacute ; B 62 0 509 936 ;
+C -1 ; WX 592 ; N Acircumflex ; B 16 0 576 936 ;
+C -1 ; WX 604 ; N copyright ; B -9 -19 614 737 ;
+C -1 ; WX 592 ; N Agrave ; B 16 0 576 936 ;
+C -1 ; WX 501 ; N odieresis ; B 28 -14 474 729 ;
+C -1 ; WX 501 ; N oacute ; B 28 -14 474 750 ;
+C -1 ; WX 328 ; N degree ; B 47 426 281 712 ;
+C -1 ; WX 228 ; N igrave ; B -41 0 171 750 ;
+C -1 ; WX 501 ; N mu ; B 54 -207 447 532 ;
+C -1 ; WX 638 ; N Oacute ; B 36 -19 602 936 ;
+C -1 ; WX 501 ; N eth ; B 28 -14 474 737 ;
+C -1 ; WX 592 ; N Adieresis ; B 16 0 576 915 ;
+C -1 ; WX 547 ; N Yacute ; B 12 0 535 936 ;
+C -1 ; WX 230 ; N brokenbar ; B 69 -19 161 737 ;
+C -1 ; WX 684 ; N onehalf ; B 21 -19 651 710 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 209
+
+KPX A y -24
+KPX A w -24
+KPX A v -32
+KPX A u -24
+KPX A Y -89
+KPX A W -48
+KPX A V -65
+KPX A U -40
+KPX A T -73
+KPX A Q -32
+KPX A O -32
+KPX A G -40
+KPX A C -32
+
+KPX B U -7
+KPX B A -24
+
+KPX D period -24
+KPX D comma -24
+KPX D Y -56
+KPX D W -32
+KPX D V -32
+KPX D A -32
+
+KPX F period -81
+KPX F comma -81
+KPX F a -15
+KPX F A -65
+
+KPX J u -15
+KPX J period -15
+KPX J comma -15
+KPX J A -15
+
+KPX K y -32
+KPX K u -24
+KPX K o -28
+KPX K e -11
+KPX K O -24
+
+KPX L y -24
+KPX L quoteright -114
+KPX L quotedblright -114
+KPX L Y -97
+KPX L W -65
+KPX L V -89
+KPX L T -73
+
+KPX O period -32
+KPX O comma -32
+KPX O Y -56
+KPX O X -40
+KPX O W -40
+KPX O V -40
+KPX O T -32
+KPX O A -40
+
+KPX P period -97
+KPX P o -32
+KPX P e -24
+KPX P comma -97
+KPX P a -24
+KPX P A -81
+
+KPX Q period 16
+KPX Q comma 16
+KPX Q U -7
+
+KPX R Y -40
+KPX R W -32
+KPX R V -40
+KPX R U -15
+KPX R T -15
+KPX R O -15
+
+KPX T y -48
+KPX T w -48
+KPX T u -73
+KPX T semicolon -32
+KPX T r -65
+KPX T period -65
+KPX T o -65
+KPX T hyphen -97
+KPX T e -48
+KPX T comma -65
+KPX T colon -32
+KPX T a -65
+KPX T O -32
+KPX T A -73
+
+KPX U period -24
+KPX U comma -24
+KPX U A -40
+
+KPX V u -48
+KPX V semicolon -32
+KPX V period -97
+KPX V o -73
+KPX V hyphen -65
+KPX V e -40
+KPX V comma -97
+KPX V colon -32
+KPX V a -48
+KPX V O -40
+KPX V G -40
+KPX V A -65
+
+KPX W y -15
+KPX W u -36
+KPX W semicolon -7
+KPX W period -65
+KPX W o -48
+KPX W hyphen -32
+KPX W e -28
+KPX W comma -65
+KPX W colon -7
+KPX W a -32
+KPX W O -15
+KPX W A -48
+
+KPX Y u -81
+KPX Y semicolon -40
+KPX Y period -81
+KPX Y o -81
+KPX Y e -65
+KPX Y comma -81
+KPX Y colon -40
+KPX Y a -73
+KPX Y O -56
+KPX Y A -89
+
+KPX a y -15
+KPX a w -11
+KPX a v -11
+KPX a g -7
+
+KPX b y -15
+KPX b v -15
+KPX b u -15
+KPX b l -7
+
+KPX c y -7
+KPX c l -15
+KPX c k -15
+KPX c h -7
+
+KPX colon space -32
+
+KPX comma space -32
+KPX comma quoteright -97
+KPX comma quotedblright -97
+
+KPX d y -11
+KPX d w -11
+KPX d v -11
+KPX d d -7
+
+KPX e y -11
+KPX e x -11
+KPX e w -11
+KPX e v -11
+KPX e period 16
+KPX e comma 8
+
+KPX f quoteright 25
+KPX f quotedblright 25
+KPX f period -7
+KPX f o -15
+KPX f e -7
+KPX f comma -7
+
+KPX g g -7
+KPX g e 8
+
+KPX h y -15
+
+KPX k o -11
+
+KPX l y -11
+KPX l w -11
+
+KPX m y -24
+KPX m u -15
+
+KPX n y -15
+KPX n v -32
+KPX n u -7
+
+KPX o y -15
+KPX o x -24
+KPX o w -11
+KPX o v -15
+
+KPX p y -11
+
+KPX period space -32
+KPX period quoteright -97
+KPX period quotedblright -97
+
+KPX quotedblright space -65
+
+KPX quoteleft quoteleft -37
+
+KPX quoteright v -15
+KPX quoteright space -65
+KPX quoteright s -48
+KPX quoteright r -32
+KPX quoteright quoteright -37
+KPX quoteright l -15
+KPX quoteright d -65
+
+KPX r y 8
+KPX r v 8
+KPX r t 16
+KPX r s -11
+KPX r q -15
+KPX r period -48
+KPX r o -15
+KPX r hyphen -15
+KPX r g -11
+KPX r d -15
+KPX r comma -48
+KPX r c -15
+
+KPX s w -11
+
+KPX semicolon space -32
+
+KPX space quoteleft -48
+KPX space quotedblleft -65
+KPX space Y -97
+KPX space W -65
+KPX space V -65
+KPX space T -81
+
+KPX v period -65
+KPX v o -24
+KPX v comma -65
+KPX v a -15
+
+KPX w period -32
+KPX w o -15
+KPX w comma -32
+
+KPX x e -7
+
+KPX y period -65
+KPX y o -20
+KPX y e -7
+KPX y comma -65
+KPX y a -24
+
+KPX z e 8
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 186 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 160 186 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 160 186 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 160 186 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 160 186 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 160 186 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 176 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 137 186 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 137 186 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 137 186 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 137 186 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -22 186 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -22 186 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -22 186 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -22 186 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 160 186 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 183 186 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 183 186 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 183 186 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 183 186 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 183 186 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 137 186 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 160 186 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 160 186 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 160 186 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 160 186 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 137 186 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 137 186 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 114 186 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 92 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 92 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 92 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 92 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 92 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 92 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 108 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 92 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 92 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 92 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 92 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -22 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -22 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -22 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -22 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 114 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 114 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 114 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 114 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 114 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 114 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 92 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 114 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 114 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 114 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 114 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 92 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 92 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 69 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvbo8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvbo8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..b6cff415fd0cf51bded4747ecb5570f6054ac938
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvbo8a.afm
@@ -0,0 +1,570 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu Mar 15 10:44:33 1990
+Comment UniqueID 28371
+Comment VMusage 7614 43068
+FontName Helvetica-BoldOblique
+FullName Helvetica Bold Oblique
+FamilyName Helvetica
+Weight Bold
+ItalicAngle -12
+IsFixedPitch false
+FontBBox -174 -228 1114 962
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 532
+Ascender 718
+Descender -207
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 94 0 397 718 ;
+C 34 ; WX 474 ; N quotedbl ; B 193 447 529 718 ;
+C 35 ; WX 556 ; N numbersign ; B 60 0 644 698 ;
+C 36 ; WX 556 ; N dollar ; B 67 -115 622 775 ;
+C 37 ; WX 889 ; N percent ; B 136 -19 901 710 ;
+C 38 ; WX 722 ; N ampersand ; B 89 -19 732 718 ;
+C 39 ; WX 278 ; N quoteright ; B 167 445 362 718 ;
+C 40 ; WX 333 ; N parenleft ; B 76 -208 470 734 ;
+C 41 ; WX 333 ; N parenright ; B -25 -208 369 734 ;
+C 42 ; WX 389 ; N asterisk ; B 146 387 481 718 ;
+C 43 ; WX 584 ; N plus ; B 82 0 610 506 ;
+C 44 ; WX 278 ; N comma ; B 28 -168 245 146 ;
+C 45 ; WX 333 ; N hyphen ; B 73 215 379 345 ;
+C 46 ; WX 278 ; N period ; B 64 0 245 146 ;
+C 47 ; WX 278 ; N slash ; B -37 -19 468 737 ;
+C 48 ; WX 556 ; N zero ; B 86 -19 617 710 ;
+C 49 ; WX 556 ; N one ; B 173 0 529 710 ;
+C 50 ; WX 556 ; N two ; B 26 0 619 710 ;
+C 51 ; WX 556 ; N three ; B 65 -19 608 710 ;
+C 52 ; WX 556 ; N four ; B 60 0 598 710 ;
+C 53 ; WX 556 ; N five ; B 64 -19 636 698 ;
+C 54 ; WX 556 ; N six ; B 85 -19 619 710 ;
+C 55 ; WX 556 ; N seven ; B 125 0 676 698 ;
+C 56 ; WX 556 ; N eight ; B 69 -19 616 710 ;
+C 57 ; WX 556 ; N nine ; B 78 -19 615 710 ;
+C 58 ; WX 333 ; N colon ; B 92 0 351 512 ;
+C 59 ; WX 333 ; N semicolon ; B 56 -168 351 512 ;
+C 60 ; WX 584 ; N less ; B 82 -8 655 514 ;
+C 61 ; WX 584 ; N equal ; B 58 87 633 419 ;
+C 62 ; WX 584 ; N greater ; B 36 -8 609 514 ;
+C 63 ; WX 611 ; N question ; B 165 0 671 727 ;
+C 64 ; WX 975 ; N at ; B 186 -19 954 737 ;
+C 65 ; WX 722 ; N A ; B 20 0 702 718 ;
+C 66 ; WX 722 ; N B ; B 76 0 764 718 ;
+C 67 ; WX 722 ; N C ; B 107 -19 789 737 ;
+C 68 ; WX 722 ; N D ; B 76 0 777 718 ;
+C 69 ; WX 667 ; N E ; B 76 0 757 718 ;
+C 70 ; WX 611 ; N F ; B 76 0 740 718 ;
+C 71 ; WX 778 ; N G ; B 108 -19 817 737 ;
+C 72 ; WX 722 ; N H ; B 71 0 804 718 ;
+C 73 ; WX 278 ; N I ; B 64 0 367 718 ;
+C 74 ; WX 556 ; N J ; B 60 -18 637 718 ;
+C 75 ; WX 722 ; N K ; B 87 0 858 718 ;
+C 76 ; WX 611 ; N L ; B 76 0 611 718 ;
+C 77 ; WX 833 ; N M ; B 69 0 918 718 ;
+C 78 ; WX 722 ; N N ; B 69 0 807 718 ;
+C 79 ; WX 778 ; N O ; B 107 -19 823 737 ;
+C 80 ; WX 667 ; N P ; B 76 0 738 718 ;
+C 81 ; WX 778 ; N Q ; B 107 -52 823 737 ;
+C 82 ; WX 722 ; N R ; B 76 0 778 718 ;
+C 83 ; WX 667 ; N S ; B 81 -19 718 737 ;
+C 84 ; WX 611 ; N T ; B 140 0 751 718 ;
+C 85 ; WX 722 ; N U ; B 116 -19 804 718 ;
+C 86 ; WX 667 ; N V ; B 172 0 801 718 ;
+C 87 ; WX 944 ; N W ; B 169 0 1082 718 ;
+C 88 ; WX 667 ; N X ; B 14 0 791 718 ;
+C 89 ; WX 667 ; N Y ; B 168 0 806 718 ;
+C 90 ; WX 611 ; N Z ; B 25 0 737 718 ;
+C 91 ; WX 333 ; N bracketleft ; B 21 -196 462 722 ;
+C 92 ; WX 278 ; N backslash ; B 124 -19 307 737 ;
+C 93 ; WX 333 ; N bracketright ; B -18 -196 423 722 ;
+C 94 ; WX 584 ; N asciicircum ; B 131 323 591 698 ;
+C 95 ; WX 556 ; N underscore ; B -27 -125 540 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 165 454 361 727 ;
+C 97 ; WX 556 ; N a ; B 55 -14 583 546 ;
+C 98 ; WX 611 ; N b ; B 61 -14 645 718 ;
+C 99 ; WX 556 ; N c ; B 79 -14 599 546 ;
+C 100 ; WX 611 ; N d ; B 82 -14 704 718 ;
+C 101 ; WX 556 ; N e ; B 70 -14 593 546 ;
+C 102 ; WX 333 ; N f ; B 87 0 469 727 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B 38 -217 666 546 ;
+C 104 ; WX 611 ; N h ; B 65 0 629 718 ;
+C 105 ; WX 278 ; N i ; B 69 0 363 725 ;
+C 106 ; WX 278 ; N j ; B -42 -214 363 725 ;
+C 107 ; WX 556 ; N k ; B 69 0 670 718 ;
+C 108 ; WX 278 ; N l ; B 69 0 362 718 ;
+C 109 ; WX 889 ; N m ; B 64 0 909 546 ;
+C 110 ; WX 611 ; N n ; B 65 0 629 546 ;
+C 111 ; WX 611 ; N o ; B 82 -14 643 546 ;
+C 112 ; WX 611 ; N p ; B 18 -207 645 546 ;
+C 113 ; WX 611 ; N q ; B 80 -207 665 546 ;
+C 114 ; WX 389 ; N r ; B 64 0 489 546 ;
+C 115 ; WX 556 ; N s ; B 63 -14 584 546 ;
+C 116 ; WX 333 ; N t ; B 100 -6 422 676 ;
+C 117 ; WX 611 ; N u ; B 98 -14 658 532 ;
+C 118 ; WX 556 ; N v ; B 126 0 656 532 ;
+C 119 ; WX 778 ; N w ; B 123 0 882 532 ;
+C 120 ; WX 556 ; N x ; B 15 0 648 532 ;
+C 121 ; WX 556 ; N y ; B 42 -214 652 532 ;
+C 122 ; WX 500 ; N z ; B 20 0 583 532 ;
+C 123 ; WX 389 ; N braceleft ; B 94 -196 518 722 ;
+C 124 ; WX 280 ; N bar ; B 80 -19 353 737 ;
+C 125 ; WX 389 ; N braceright ; B -18 -196 407 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 115 163 577 343 ;
+C 161 ; WX 333 ; N exclamdown ; B 50 -186 353 532 ;
+C 162 ; WX 556 ; N cent ; B 79 -118 599 628 ;
+C 163 ; WX 556 ; N sterling ; B 50 -16 635 718 ;
+C 164 ; WX 167 ; N fraction ; B -174 -19 487 710 ;
+C 165 ; WX 556 ; N yen ; B 60 0 713 698 ;
+C 166 ; WX 556 ; N florin ; B -50 -210 669 737 ;
+C 167 ; WX 556 ; N section ; B 61 -184 598 727 ;
+C 168 ; WX 556 ; N currency ; B 27 76 680 636 ;
+C 169 ; WX 238 ; N quotesingle ; B 165 447 321 718 ;
+C 170 ; WX 500 ; N quotedblleft ; B 160 454 588 727 ;
+C 171 ; WX 556 ; N guillemotleft ; B 135 76 571 484 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 130 76 353 484 ;
+C 173 ; WX 333 ; N guilsinglright ; B 99 76 322 484 ;
+C 174 ; WX 611 ; N fi ; B 87 0 696 727 ;
+C 175 ; WX 611 ; N fl ; B 87 0 695 727 ;
+C 177 ; WX 556 ; N endash ; B 48 227 627 333 ;
+C 178 ; WX 556 ; N dagger ; B 118 -171 626 718 ;
+C 179 ; WX 556 ; N daggerdbl ; B 46 -171 628 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 110 172 276 334 ;
+C 182 ; WX 556 ; N paragraph ; B 98 -191 688 700 ;
+C 183 ; WX 350 ; N bullet ; B 83 194 420 524 ;
+C 184 ; WX 278 ; N quotesinglbase ; B 41 -146 236 127 ;
+C 185 ; WX 500 ; N quotedblbase ; B 36 -146 463 127 ;
+C 186 ; WX 500 ; N quotedblright ; B 162 445 589 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 104 76 540 484 ;
+C 188 ; WX 1000 ; N ellipsis ; B 92 0 939 146 ;
+C 189 ; WX 1000 ; N perthousand ; B 76 -19 1038 710 ;
+C 191 ; WX 611 ; N questiondown ; B 53 -195 559 532 ;
+C 193 ; WX 333 ; N grave ; B 136 604 353 750 ;
+C 194 ; WX 333 ; N acute ; B 236 604 515 750 ;
+C 195 ; WX 333 ; N circumflex ; B 118 604 471 750 ;
+C 196 ; WX 333 ; N tilde ; B 113 610 507 737 ;
+C 197 ; WX 333 ; N macron ; B 122 604 483 678 ;
+C 198 ; WX 333 ; N breve ; B 156 604 494 750 ;
+C 199 ; WX 333 ; N dotaccent ; B 235 614 385 729 ;
+C 200 ; WX 333 ; N dieresis ; B 137 614 482 729 ;
+C 202 ; WX 333 ; N ring ; B 200 568 420 776 ;
+C 203 ; WX 333 ; N cedilla ; B -37 -228 220 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 137 604 645 750 ;
+C 206 ; WX 333 ; N ogonek ; B 41 -228 264 0 ;
+C 207 ; WX 333 ; N caron ; B 149 604 502 750 ;
+C 208 ; WX 1000 ; N emdash ; B 48 227 1071 333 ;
+C 225 ; WX 1000 ; N AE ; B 5 0 1100 718 ;
+C 227 ; WX 370 ; N ordfeminine ; B 92 276 465 737 ;
+C 232 ; WX 611 ; N Lslash ; B 34 0 611 718 ;
+C 233 ; WX 778 ; N Oslash ; B 35 -27 894 745 ;
+C 234 ; WX 1000 ; N OE ; B 99 -19 1114 737 ;
+C 235 ; WX 365 ; N ordmasculine ; B 92 276 485 737 ;
+C 241 ; WX 889 ; N ae ; B 56 -14 923 546 ;
+C 245 ; WX 278 ; N dotlessi ; B 69 0 322 532 ;
+C 248 ; WX 278 ; N lslash ; B 40 0 407 718 ;
+C 249 ; WX 611 ; N oslash ; B 22 -29 701 560 ;
+C 250 ; WX 944 ; N oe ; B 82 -14 977 546 ;
+C 251 ; WX 611 ; N germandbls ; B 69 -14 657 731 ;
+C -1 ; WX 611 ; N Zcaron ; B 25 0 737 936 ;
+C -1 ; WX 556 ; N ccedilla ; B 79 -228 599 546 ;
+C -1 ; WX 556 ; N ydieresis ; B 42 -214 652 729 ;
+C -1 ; WX 556 ; N atilde ; B 55 -14 619 737 ;
+C -1 ; WX 278 ; N icircumflex ; B 69 0 444 750 ;
+C -1 ; WX 333 ; N threesuperior ; B 91 271 441 710 ;
+C -1 ; WX 556 ; N ecircumflex ; B 70 -14 593 750 ;
+C -1 ; WX 611 ; N thorn ; B 18 -208 645 718 ;
+C -1 ; WX 556 ; N egrave ; B 70 -14 593 750 ;
+C -1 ; WX 333 ; N twosuperior ; B 69 283 449 710 ;
+C -1 ; WX 556 ; N eacute ; B 70 -14 627 750 ;
+C -1 ; WX 611 ; N otilde ; B 82 -14 646 737 ;
+C -1 ; WX 722 ; N Aacute ; B 20 0 750 936 ;
+C -1 ; WX 611 ; N ocircumflex ; B 82 -14 643 750 ;
+C -1 ; WX 556 ; N yacute ; B 42 -214 652 750 ;
+C -1 ; WX 611 ; N udieresis ; B 98 -14 658 729 ;
+C -1 ; WX 834 ; N threequarters ; B 99 -19 839 710 ;
+C -1 ; WX 556 ; N acircumflex ; B 55 -14 583 750 ;
+C -1 ; WX 722 ; N Eth ; B 62 0 777 718 ;
+C -1 ; WX 556 ; N edieresis ; B 70 -14 594 729 ;
+C -1 ; WX 611 ; N ugrave ; B 98 -14 658 750 ;
+C -1 ; WX 1000 ; N trademark ; B 179 306 1109 718 ;
+C -1 ; WX 611 ; N ograve ; B 82 -14 643 750 ;
+C -1 ; WX 556 ; N scaron ; B 63 -14 614 750 ;
+C -1 ; WX 278 ; N Idieresis ; B 64 0 494 915 ;
+C -1 ; WX 611 ; N uacute ; B 98 -14 658 750 ;
+C -1 ; WX 556 ; N agrave ; B 55 -14 583 750 ;
+C -1 ; WX 611 ; N ntilde ; B 65 0 646 737 ;
+C -1 ; WX 556 ; N aring ; B 55 -14 583 776 ;
+C -1 ; WX 500 ; N zcaron ; B 20 0 586 750 ;
+C -1 ; WX 278 ; N Icircumflex ; B 64 0 484 936 ;
+C -1 ; WX 722 ; N Ntilde ; B 69 0 807 923 ;
+C -1 ; WX 611 ; N ucircumflex ; B 98 -14 658 750 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 76 0 757 936 ;
+C -1 ; WX 278 ; N Iacute ; B 64 0 528 936 ;
+C -1 ; WX 722 ; N Ccedilla ; B 107 -228 789 737 ;
+C -1 ; WX 778 ; N Odieresis ; B 107 -19 823 915 ;
+C -1 ; WX 667 ; N Scaron ; B 81 -19 718 936 ;
+C -1 ; WX 667 ; N Edieresis ; B 76 0 757 915 ;
+C -1 ; WX 278 ; N Igrave ; B 64 0 367 936 ;
+C -1 ; WX 556 ; N adieresis ; B 55 -14 594 729 ;
+C -1 ; WX 778 ; N Ograve ; B 107 -19 823 936 ;
+C -1 ; WX 667 ; N Egrave ; B 76 0 757 936 ;
+C -1 ; WX 667 ; N Ydieresis ; B 168 0 806 915 ;
+C -1 ; WX 737 ; N registered ; B 55 -19 834 737 ;
+C -1 ; WX 778 ; N Otilde ; B 107 -19 823 923 ;
+C -1 ; WX 834 ; N onequarter ; B 132 -19 806 710 ;
+C -1 ; WX 722 ; N Ugrave ; B 116 -19 804 936 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 116 -19 804 936 ;
+C -1 ; WX 667 ; N Thorn ; B 76 0 716 718 ;
+C -1 ; WX 584 ; N divide ; B 82 -42 610 548 ;
+C -1 ; WX 722 ; N Atilde ; B 20 0 741 923 ;
+C -1 ; WX 722 ; N Uacute ; B 116 -19 804 936 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 107 -19 823 936 ;
+C -1 ; WX 584 ; N logicalnot ; B 105 108 633 419 ;
+C -1 ; WX 722 ; N Aring ; B 20 0 702 962 ;
+C -1 ; WX 278 ; N idieresis ; B 69 0 455 729 ;
+C -1 ; WX 278 ; N iacute ; B 69 0 488 750 ;
+C -1 ; WX 556 ; N aacute ; B 55 -14 627 750 ;
+C -1 ; WX 584 ; N plusminus ; B 40 0 625 506 ;
+C -1 ; WX 584 ; N multiply ; B 57 1 635 505 ;
+C -1 ; WX 722 ; N Udieresis ; B 116 -19 804 915 ;
+C -1 ; WX 584 ; N minus ; B 82 197 610 309 ;
+C -1 ; WX 333 ; N onesuperior ; B 148 283 388 710 ;
+C -1 ; WX 667 ; N Eacute ; B 76 0 757 936 ;
+C -1 ; WX 722 ; N Acircumflex ; B 20 0 706 936 ;
+C -1 ; WX 737 ; N copyright ; B 56 -19 835 737 ;
+C -1 ; WX 722 ; N Agrave ; B 20 0 702 936 ;
+C -1 ; WX 611 ; N odieresis ; B 82 -14 643 729 ;
+C -1 ; WX 611 ; N oacute ; B 82 -14 654 750 ;
+C -1 ; WX 400 ; N degree ; B 175 426 467 712 ;
+C -1 ; WX 278 ; N igrave ; B 69 0 326 750 ;
+C -1 ; WX 611 ; N mu ; B 22 -207 658 532 ;
+C -1 ; WX 778 ; N Oacute ; B 107 -19 823 936 ;
+C -1 ; WX 611 ; N eth ; B 82 -14 670 737 ;
+C -1 ; WX 722 ; N Adieresis ; B 20 0 716 915 ;
+C -1 ; WX 667 ; N Yacute ; B 168 0 806 936 ;
+C -1 ; WX 280 ; N brokenbar ; B 80 -19 353 737 ;
+C -1 ; WX 834 ; N onehalf ; B 132 -19 858 710 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 209
+
+KPX A y -30
+KPX A w -30
+KPX A v -40
+KPX A u -30
+KPX A Y -110
+KPX A W -60
+KPX A V -80
+KPX A U -50
+KPX A T -90
+KPX A Q -40
+KPX A O -40
+KPX A G -50
+KPX A C -40
+
+KPX B U -10
+KPX B A -30
+
+KPX D period -30
+KPX D comma -30
+KPX D Y -70
+KPX D W -40
+KPX D V -40
+KPX D A -40
+
+KPX F period -100
+KPX F comma -100
+KPX F a -20
+KPX F A -80
+
+KPX J u -20
+KPX J period -20
+KPX J comma -20
+KPX J A -20
+
+KPX K y -40
+KPX K u -30
+KPX K o -35
+KPX K e -15
+KPX K O -30
+
+KPX L y -30
+KPX L quoteright -140
+KPX L quotedblright -140
+KPX L Y -120
+KPX L W -80
+KPX L V -110
+KPX L T -90
+
+KPX O period -40
+KPX O comma -40
+KPX O Y -70
+KPX O X -50
+KPX O W -50
+KPX O V -50
+KPX O T -40
+KPX O A -50
+
+KPX P period -120
+KPX P o -40
+KPX P e -30
+KPX P comma -120
+KPX P a -30
+KPX P A -100
+
+KPX Q period 20
+KPX Q comma 20
+KPX Q U -10
+
+KPX R Y -50
+KPX R W -40
+KPX R V -50
+KPX R U -20
+KPX R T -20
+KPX R O -20
+
+KPX T y -60
+KPX T w -60
+KPX T u -90
+KPX T semicolon -40
+KPX T r -80
+KPX T period -80
+KPX T o -80
+KPX T hyphen -120
+KPX T e -60
+KPX T comma -80
+KPX T colon -40
+KPX T a -80
+KPX T O -40
+KPX T A -90
+
+KPX U period -30
+KPX U comma -30
+KPX U A -50
+
+KPX V u -60
+KPX V semicolon -40
+KPX V period -120
+KPX V o -90
+KPX V hyphen -80
+KPX V e -50
+KPX V comma -120
+KPX V colon -40
+KPX V a -60
+KPX V O -50
+KPX V G -50
+KPX V A -80
+
+KPX W y -20
+KPX W u -45
+KPX W semicolon -10
+KPX W period -80
+KPX W o -60
+KPX W hyphen -40
+KPX W e -35
+KPX W comma -80
+KPX W colon -10
+KPX W a -40
+KPX W O -20
+KPX W A -60
+
+KPX Y u -100
+KPX Y semicolon -50
+KPX Y period -100
+KPX Y o -100
+KPX Y e -80
+KPX Y comma -100
+KPX Y colon -50
+KPX Y a -90
+KPX Y O -70
+KPX Y A -110
+
+KPX a y -20
+KPX a w -15
+KPX a v -15
+KPX a g -10
+
+KPX b y -20
+KPX b v -20
+KPX b u -20
+KPX b l -10
+
+KPX c y -10
+KPX c l -20
+KPX c k -20
+KPX c h -10
+
+KPX colon space -40
+
+KPX comma space -40
+KPX comma quoteright -120
+KPX comma quotedblright -120
+
+KPX d y -15
+KPX d w -15
+KPX d v -15
+KPX d d -10
+
+KPX e y -15
+KPX e x -15
+KPX e w -15
+KPX e v -15
+KPX e period 20
+KPX e comma 10
+
+KPX f quoteright 30
+KPX f quotedblright 30
+KPX f period -10
+KPX f o -20
+KPX f e -10
+KPX f comma -10
+
+KPX g g -10
+KPX g e 10
+
+KPX h y -20
+
+KPX k o -15
+
+KPX l y -15
+KPX l w -15
+
+KPX m y -30
+KPX m u -20
+
+KPX n y -20
+KPX n v -40
+KPX n u -10
+
+KPX o y -20
+KPX o x -30
+KPX o w -15
+KPX o v -20
+
+KPX p y -15
+
+KPX period space -40
+KPX period quoteright -120
+KPX period quotedblright -120
+
+KPX quotedblright space -80
+
+KPX quoteleft quoteleft -46
+
+KPX quoteright v -20
+KPX quoteright space -80
+KPX quoteright s -60
+KPX quoteright r -40
+KPX quoteright quoteright -46
+KPX quoteright l -20
+KPX quoteright d -80
+
+KPX r y 10
+KPX r v 10
+KPX r t 20
+KPX r s -15
+KPX r q -20
+KPX r period -60
+KPX r o -20
+KPX r hyphen -20
+KPX r g -15
+KPX r d -20
+KPX r comma -60
+KPX r c -20
+
+KPX s w -15
+
+KPX semicolon space -40
+
+KPX space quoteleft -60
+KPX space quotedblleft -80
+KPX space Y -120
+KPX space W -80
+KPX space V -80
+KPX space T -100
+
+KPX v period -80
+KPX v o -30
+KPX v comma -80
+KPX v a -20
+
+KPX w period -40
+KPX w o -20
+KPX w comma -40
+
+KPX x e -10
+
+KPX y period -80
+KPX y o -25
+KPX y e -10
+KPX y comma -80
+KPX y a -30
+
+KPX z e 10
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 235 186 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 235 186 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 235 186 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 235 186 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 235 186 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 235 186 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 215 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 207 186 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 207 186 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 207 186 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 207 186 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 13 186 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 13 186 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 13 186 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 13 186 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 235 186 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 263 186 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 263 186 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 263 186 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 263 186 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 263 186 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 207 186 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 235 186 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 235 186 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 235 186 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 235 186 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 207 186 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 207 186 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 179 186 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 112 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 112 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 112 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 112 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 112 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 112 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 132 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 112 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 112 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 112 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 112 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -27 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -27 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -27 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -27 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 139 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 139 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 139 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 139 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 139 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 139 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 112 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 139 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 139 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 139 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 139 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 112 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 112 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 84 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvbo8an.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvbo8an.afm
new file mode 100644
index 0000000000000000000000000000000000000000..1a3800194d518afce782a07dca6fe0e900cf1799
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvbo8an.afm
@@ -0,0 +1,570 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu Mar 15 12:08:57 1990
+Comment UniqueID 28407
+Comment VMusage 7614 43068
+FontName Helvetica-Narrow-BoldOblique
+FullName Helvetica Narrow Bold Oblique
+FamilyName Helvetica
+Weight Bold
+ItalicAngle -12
+IsFixedPitch false
+FontBBox -143 -228 913 962
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 532
+Ascender 718
+Descender -207
+StartCharMetrics 228
+C 32 ; WX 228 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 273 ; N exclam ; B 77 0 325 718 ;
+C 34 ; WX 389 ; N quotedbl ; B 158 447 433 718 ;
+C 35 ; WX 456 ; N numbersign ; B 49 0 528 698 ;
+C 36 ; WX 456 ; N dollar ; B 55 -115 510 775 ;
+C 37 ; WX 729 ; N percent ; B 112 -19 739 710 ;
+C 38 ; WX 592 ; N ampersand ; B 73 -19 600 718 ;
+C 39 ; WX 228 ; N quoteright ; B 137 445 297 718 ;
+C 40 ; WX 273 ; N parenleft ; B 62 -208 385 734 ;
+C 41 ; WX 273 ; N parenright ; B -21 -208 302 734 ;
+C 42 ; WX 319 ; N asterisk ; B 120 387 394 718 ;
+C 43 ; WX 479 ; N plus ; B 67 0 500 506 ;
+C 44 ; WX 228 ; N comma ; B 23 -168 201 146 ;
+C 45 ; WX 273 ; N hyphen ; B 60 215 311 345 ;
+C 46 ; WX 228 ; N period ; B 52 0 201 146 ;
+C 47 ; WX 228 ; N slash ; B -30 -19 383 737 ;
+C 48 ; WX 456 ; N zero ; B 71 -19 506 710 ;
+C 49 ; WX 456 ; N one ; B 142 0 434 710 ;
+C 50 ; WX 456 ; N two ; B 21 0 508 710 ;
+C 51 ; WX 456 ; N three ; B 54 -19 499 710 ;
+C 52 ; WX 456 ; N four ; B 50 0 490 710 ;
+C 53 ; WX 456 ; N five ; B 53 -19 522 698 ;
+C 54 ; WX 456 ; N six ; B 70 -19 507 710 ;
+C 55 ; WX 456 ; N seven ; B 102 0 555 698 ;
+C 56 ; WX 456 ; N eight ; B 57 -19 505 710 ;
+C 57 ; WX 456 ; N nine ; B 64 -19 504 710 ;
+C 58 ; WX 273 ; N colon ; B 75 0 288 512 ;
+C 59 ; WX 273 ; N semicolon ; B 46 -168 288 512 ;
+C 60 ; WX 479 ; N less ; B 67 -8 537 514 ;
+C 61 ; WX 479 ; N equal ; B 48 87 519 419 ;
+C 62 ; WX 479 ; N greater ; B 30 -8 500 514 ;
+C 63 ; WX 501 ; N question ; B 135 0 550 727 ;
+C 64 ; WX 800 ; N at ; B 152 -19 782 737 ;
+C 65 ; WX 592 ; N A ; B 16 0 576 718 ;
+C 66 ; WX 592 ; N B ; B 62 0 626 718 ;
+C 67 ; WX 592 ; N C ; B 88 -19 647 737 ;
+C 68 ; WX 592 ; N D ; B 62 0 637 718 ;
+C 69 ; WX 547 ; N E ; B 62 0 620 718 ;
+C 70 ; WX 501 ; N F ; B 62 0 606 718 ;
+C 71 ; WX 638 ; N G ; B 89 -19 670 737 ;
+C 72 ; WX 592 ; N H ; B 58 0 659 718 ;
+C 73 ; WX 228 ; N I ; B 52 0 301 718 ;
+C 74 ; WX 456 ; N J ; B 49 -18 522 718 ;
+C 75 ; WX 592 ; N K ; B 71 0 703 718 ;
+C 76 ; WX 501 ; N L ; B 62 0 501 718 ;
+C 77 ; WX 683 ; N M ; B 57 0 752 718 ;
+C 78 ; WX 592 ; N N ; B 57 0 661 718 ;
+C 79 ; WX 638 ; N O ; B 88 -19 675 737 ;
+C 80 ; WX 547 ; N P ; B 62 0 605 718 ;
+C 81 ; WX 638 ; N Q ; B 88 -52 675 737 ;
+C 82 ; WX 592 ; N R ; B 62 0 638 718 ;
+C 83 ; WX 547 ; N S ; B 66 -19 588 737 ;
+C 84 ; WX 501 ; N T ; B 114 0 615 718 ;
+C 85 ; WX 592 ; N U ; B 96 -19 659 718 ;
+C 86 ; WX 547 ; N V ; B 141 0 656 718 ;
+C 87 ; WX 774 ; N W ; B 138 0 887 718 ;
+C 88 ; WX 547 ; N X ; B 11 0 648 718 ;
+C 89 ; WX 547 ; N Y ; B 137 0 661 718 ;
+C 90 ; WX 501 ; N Z ; B 20 0 604 718 ;
+C 91 ; WX 273 ; N bracketleft ; B 17 -196 379 722 ;
+C 92 ; WX 228 ; N backslash ; B 101 -19 252 737 ;
+C 93 ; WX 273 ; N bracketright ; B -14 -196 347 722 ;
+C 94 ; WX 479 ; N asciicircum ; B 107 323 484 698 ;
+C 95 ; WX 456 ; N underscore ; B -22 -125 443 -75 ;
+C 96 ; WX 228 ; N quoteleft ; B 136 454 296 727 ;
+C 97 ; WX 456 ; N a ; B 45 -14 478 546 ;
+C 98 ; WX 501 ; N b ; B 50 -14 529 718 ;
+C 99 ; WX 456 ; N c ; B 65 -14 491 546 ;
+C 100 ; WX 501 ; N d ; B 67 -14 577 718 ;
+C 101 ; WX 456 ; N e ; B 58 -14 486 546 ;
+C 102 ; WX 273 ; N f ; B 71 0 385 727 ; L i fi ; L l fl ;
+C 103 ; WX 501 ; N g ; B 31 -217 546 546 ;
+C 104 ; WX 501 ; N h ; B 53 0 516 718 ;
+C 105 ; WX 228 ; N i ; B 57 0 298 725 ;
+C 106 ; WX 228 ; N j ; B -35 -214 298 725 ;
+C 107 ; WX 456 ; N k ; B 57 0 549 718 ;
+C 108 ; WX 228 ; N l ; B 57 0 297 718 ;
+C 109 ; WX 729 ; N m ; B 52 0 746 546 ;
+C 110 ; WX 501 ; N n ; B 53 0 516 546 ;
+C 111 ; WX 501 ; N o ; B 67 -14 527 546 ;
+C 112 ; WX 501 ; N p ; B 15 -207 529 546 ;
+C 113 ; WX 501 ; N q ; B 66 -207 545 546 ;
+C 114 ; WX 319 ; N r ; B 52 0 401 546 ;
+C 115 ; WX 456 ; N s ; B 52 -14 479 546 ;
+C 116 ; WX 273 ; N t ; B 82 -6 346 676 ;
+C 117 ; WX 501 ; N u ; B 80 -14 540 532 ;
+C 118 ; WX 456 ; N v ; B 103 0 538 532 ;
+C 119 ; WX 638 ; N w ; B 101 0 723 532 ;
+C 120 ; WX 456 ; N x ; B 12 0 531 532 ;
+C 121 ; WX 456 ; N y ; B 34 -214 535 532 ;
+C 122 ; WX 410 ; N z ; B 16 0 478 532 ;
+C 123 ; WX 319 ; N braceleft ; B 77 -196 425 722 ;
+C 124 ; WX 230 ; N bar ; B 66 -19 289 737 ;
+C 125 ; WX 319 ; N braceright ; B -14 -196 333 722 ;
+C 126 ; WX 479 ; N asciitilde ; B 94 163 473 343 ;
+C 161 ; WX 273 ; N exclamdown ; B 41 -186 290 532 ;
+C 162 ; WX 456 ; N cent ; B 65 -118 491 628 ;
+C 163 ; WX 456 ; N sterling ; B 41 -16 520 718 ;
+C 164 ; WX 137 ; N fraction ; B -143 -19 399 710 ;
+C 165 ; WX 456 ; N yen ; B 49 0 585 698 ;
+C 166 ; WX 456 ; N florin ; B -41 -210 548 737 ;
+C 167 ; WX 456 ; N section ; B 50 -184 491 727 ;
+C 168 ; WX 456 ; N currency ; B 22 76 558 636 ;
+C 169 ; WX 195 ; N quotesingle ; B 135 447 263 718 ;
+C 170 ; WX 410 ; N quotedblleft ; B 132 454 482 727 ;
+C 171 ; WX 456 ; N guillemotleft ; B 111 76 468 484 ;
+C 172 ; WX 273 ; N guilsinglleft ; B 106 76 289 484 ;
+C 173 ; WX 273 ; N guilsinglright ; B 81 76 264 484 ;
+C 174 ; WX 501 ; N fi ; B 71 0 571 727 ;
+C 175 ; WX 501 ; N fl ; B 71 0 570 727 ;
+C 177 ; WX 456 ; N endash ; B 40 227 514 333 ;
+C 178 ; WX 456 ; N dagger ; B 97 -171 513 718 ;
+C 179 ; WX 456 ; N daggerdbl ; B 38 -171 515 718 ;
+C 180 ; WX 228 ; N periodcentered ; B 90 172 226 334 ;
+C 182 ; WX 456 ; N paragraph ; B 80 -191 564 700 ;
+C 183 ; WX 287 ; N bullet ; B 68 194 345 524 ;
+C 184 ; WX 228 ; N quotesinglbase ; B 34 -146 194 127 ;
+C 185 ; WX 410 ; N quotedblbase ; B 29 -146 380 127 ;
+C 186 ; WX 410 ; N quotedblright ; B 132 445 483 718 ;
+C 187 ; WX 456 ; N guillemotright ; B 85 76 443 484 ;
+C 188 ; WX 820 ; N ellipsis ; B 75 0 770 146 ;
+C 189 ; WX 820 ; N perthousand ; B 62 -19 851 710 ;
+C 191 ; WX 501 ; N questiondown ; B 44 -195 459 532 ;
+C 193 ; WX 273 ; N grave ; B 112 604 290 750 ;
+C 194 ; WX 273 ; N acute ; B 194 604 423 750 ;
+C 195 ; WX 273 ; N circumflex ; B 97 604 387 750 ;
+C 196 ; WX 273 ; N tilde ; B 92 610 415 737 ;
+C 197 ; WX 273 ; N macron ; B 100 604 396 678 ;
+C 198 ; WX 273 ; N breve ; B 128 604 405 750 ;
+C 199 ; WX 273 ; N dotaccent ; B 192 614 316 729 ;
+C 200 ; WX 273 ; N dieresis ; B 112 614 395 729 ;
+C 202 ; WX 273 ; N ring ; B 164 568 344 776 ;
+C 203 ; WX 273 ; N cedilla ; B -30 -228 180 0 ;
+C 205 ; WX 273 ; N hungarumlaut ; B 113 604 529 750 ;
+C 206 ; WX 273 ; N ogonek ; B 33 -228 216 0 ;
+C 207 ; WX 273 ; N caron ; B 123 604 412 750 ;
+C 208 ; WX 820 ; N emdash ; B 40 227 878 333 ;
+C 225 ; WX 820 ; N AE ; B 4 0 902 718 ;
+C 227 ; WX 303 ; N ordfeminine ; B 75 276 381 737 ;
+C 232 ; WX 501 ; N Lslash ; B 28 0 501 718 ;
+C 233 ; WX 638 ; N Oslash ; B 29 -27 733 745 ;
+C 234 ; WX 820 ; N OE ; B 81 -19 913 737 ;
+C 235 ; WX 299 ; N ordmasculine ; B 75 276 398 737 ;
+C 241 ; WX 729 ; N ae ; B 46 -14 757 546 ;
+C 245 ; WX 228 ; N dotlessi ; B 57 0 264 532 ;
+C 248 ; WX 228 ; N lslash ; B 33 0 334 718 ;
+C 249 ; WX 501 ; N oslash ; B 18 -29 575 560 ;
+C 250 ; WX 774 ; N oe ; B 67 -14 801 546 ;
+C 251 ; WX 501 ; N germandbls ; B 57 -14 539 731 ;
+C -1 ; WX 501 ; N Zcaron ; B 20 0 604 936 ;
+C -1 ; WX 456 ; N ccedilla ; B 65 -228 491 546 ;
+C -1 ; WX 456 ; N ydieresis ; B 34 -214 535 729 ;
+C -1 ; WX 456 ; N atilde ; B 45 -14 507 737 ;
+C -1 ; WX 228 ; N icircumflex ; B 57 0 364 750 ;
+C -1 ; WX 273 ; N threesuperior ; B 75 271 361 710 ;
+C -1 ; WX 456 ; N ecircumflex ; B 58 -14 486 750 ;
+C -1 ; WX 501 ; N thorn ; B 15 -208 529 718 ;
+C -1 ; WX 456 ; N egrave ; B 58 -14 486 750 ;
+C -1 ; WX 273 ; N twosuperior ; B 57 283 368 710 ;
+C -1 ; WX 456 ; N eacute ; B 58 -14 514 750 ;
+C -1 ; WX 501 ; N otilde ; B 67 -14 529 737 ;
+C -1 ; WX 592 ; N Aacute ; B 16 0 615 936 ;
+C -1 ; WX 501 ; N ocircumflex ; B 67 -14 527 750 ;
+C -1 ; WX 456 ; N yacute ; B 34 -214 535 750 ;
+C -1 ; WX 501 ; N udieresis ; B 80 -14 540 729 ;
+C -1 ; WX 684 ; N threequarters ; B 82 -19 688 710 ;
+C -1 ; WX 456 ; N acircumflex ; B 45 -14 478 750 ;
+C -1 ; WX 592 ; N Eth ; B 51 0 637 718 ;
+C -1 ; WX 456 ; N edieresis ; B 58 -14 487 729 ;
+C -1 ; WX 501 ; N ugrave ; B 80 -14 540 750 ;
+C -1 ; WX 820 ; N trademark ; B 146 306 909 718 ;
+C -1 ; WX 501 ; N ograve ; B 67 -14 527 750 ;
+C -1 ; WX 456 ; N scaron ; B 52 -14 504 750 ;
+C -1 ; WX 228 ; N Idieresis ; B 52 0 405 915 ;
+C -1 ; WX 501 ; N uacute ; B 80 -14 540 750 ;
+C -1 ; WX 456 ; N agrave ; B 45 -14 478 750 ;
+C -1 ; WX 501 ; N ntilde ; B 53 0 529 737 ;
+C -1 ; WX 456 ; N aring ; B 45 -14 478 776 ;
+C -1 ; WX 410 ; N zcaron ; B 16 0 481 750 ;
+C -1 ; WX 228 ; N Icircumflex ; B 52 0 397 936 ;
+C -1 ; WX 592 ; N Ntilde ; B 57 0 661 923 ;
+C -1 ; WX 501 ; N ucircumflex ; B 80 -14 540 750 ;
+C -1 ; WX 547 ; N Ecircumflex ; B 62 0 620 936 ;
+C -1 ; WX 228 ; N Iacute ; B 52 0 433 936 ;
+C -1 ; WX 592 ; N Ccedilla ; B 88 -228 647 737 ;
+C -1 ; WX 638 ; N Odieresis ; B 88 -19 675 915 ;
+C -1 ; WX 547 ; N Scaron ; B 66 -19 588 936 ;
+C -1 ; WX 547 ; N Edieresis ; B 62 0 620 915 ;
+C -1 ; WX 228 ; N Igrave ; B 52 0 301 936 ;
+C -1 ; WX 456 ; N adieresis ; B 45 -14 487 729 ;
+C -1 ; WX 638 ; N Ograve ; B 88 -19 675 936 ;
+C -1 ; WX 547 ; N Egrave ; B 62 0 620 936 ;
+C -1 ; WX 547 ; N Ydieresis ; B 137 0 661 915 ;
+C -1 ; WX 604 ; N registered ; B 45 -19 684 737 ;
+C -1 ; WX 638 ; N Otilde ; B 88 -19 675 923 ;
+C -1 ; WX 684 ; N onequarter ; B 108 -19 661 710 ;
+C -1 ; WX 592 ; N Ugrave ; B 96 -19 659 936 ;
+C -1 ; WX 592 ; N Ucircumflex ; B 96 -19 659 936 ;
+C -1 ; WX 547 ; N Thorn ; B 62 0 588 718 ;
+C -1 ; WX 479 ; N divide ; B 67 -42 500 548 ;
+C -1 ; WX 592 ; N Atilde ; B 16 0 608 923 ;
+C -1 ; WX 592 ; N Uacute ; B 96 -19 659 936 ;
+C -1 ; WX 638 ; N Ocircumflex ; B 88 -19 675 936 ;
+C -1 ; WX 479 ; N logicalnot ; B 86 108 519 419 ;
+C -1 ; WX 592 ; N Aring ; B 16 0 576 962 ;
+C -1 ; WX 228 ; N idieresis ; B 57 0 373 729 ;
+C -1 ; WX 228 ; N iacute ; B 57 0 400 750 ;
+C -1 ; WX 456 ; N aacute ; B 45 -14 514 750 ;
+C -1 ; WX 479 ; N plusminus ; B 33 0 512 506 ;
+C -1 ; WX 479 ; N multiply ; B 47 1 520 505 ;
+C -1 ; WX 592 ; N Udieresis ; B 96 -19 659 915 ;
+C -1 ; WX 479 ; N minus ; B 67 197 500 309 ;
+C -1 ; WX 273 ; N onesuperior ; B 121 283 318 710 ;
+C -1 ; WX 547 ; N Eacute ; B 62 0 620 936 ;
+C -1 ; WX 592 ; N Acircumflex ; B 16 0 579 936 ;
+C -1 ; WX 604 ; N copyright ; B 46 -19 685 737 ;
+C -1 ; WX 592 ; N Agrave ; B 16 0 576 936 ;
+C -1 ; WX 501 ; N odieresis ; B 67 -14 527 729 ;
+C -1 ; WX 501 ; N oacute ; B 67 -14 537 750 ;
+C -1 ; WX 328 ; N degree ; B 143 426 383 712 ;
+C -1 ; WX 228 ; N igrave ; B 57 0 268 750 ;
+C -1 ; WX 501 ; N mu ; B 18 -207 540 532 ;
+C -1 ; WX 638 ; N Oacute ; B 88 -19 675 936 ;
+C -1 ; WX 501 ; N eth ; B 67 -14 549 737 ;
+C -1 ; WX 592 ; N Adieresis ; B 16 0 588 915 ;
+C -1 ; WX 547 ; N Yacute ; B 137 0 661 936 ;
+C -1 ; WX 230 ; N brokenbar ; B 66 -19 289 737 ;
+C -1 ; WX 684 ; N onehalf ; B 108 -19 704 710 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 209
+
+KPX A y -30
+KPX A w -30
+KPX A v -40
+KPX A u -30
+KPX A Y -110
+KPX A W -60
+KPX A V -80
+KPX A U -50
+KPX A T -90
+KPX A Q -40
+KPX A O -40
+KPX A G -50
+KPX A C -40
+
+KPX B U -10
+KPX B A -30
+
+KPX D period -30
+KPX D comma -30
+KPX D Y -70
+KPX D W -40
+KPX D V -40
+KPX D A -40
+
+KPX F period -100
+KPX F comma -100
+KPX F a -20
+KPX F A -80
+
+KPX J u -20
+KPX J period -20
+KPX J comma -20
+KPX J A -20
+
+KPX K y -40
+KPX K u -30
+KPX K o -35
+KPX K e -15
+KPX K O -30
+
+KPX L y -30
+KPX L quoteright -140
+KPX L quotedblright -140
+KPX L Y -120
+KPX L W -80
+KPX L V -110
+KPX L T -90
+
+KPX O period -40
+KPX O comma -40
+KPX O Y -70
+KPX O X -50
+KPX O W -50
+KPX O V -50
+KPX O T -40
+KPX O A -50
+
+KPX P period -120
+KPX P o -40
+KPX P e -30
+KPX P comma -120
+KPX P a -30
+KPX P A -100
+
+KPX Q period 20
+KPX Q comma 20
+KPX Q U -10
+
+KPX R Y -50
+KPX R W -40
+KPX R V -50
+KPX R U -20
+KPX R T -20
+KPX R O -20
+
+KPX T y -60
+KPX T w -60
+KPX T u -90
+KPX T semicolon -40
+KPX T r -80
+KPX T period -80
+KPX T o -80
+KPX T hyphen -120
+KPX T e -60
+KPX T comma -80
+KPX T colon -40
+KPX T a -80
+KPX T O -40
+KPX T A -90
+
+KPX U period -30
+KPX U comma -30
+KPX U A -50
+
+KPX V u -60
+KPX V semicolon -40
+KPX V period -120
+KPX V o -90
+KPX V hyphen -80
+KPX V e -50
+KPX V comma -120
+KPX V colon -40
+KPX V a -60
+KPX V O -50
+KPX V G -50
+KPX V A -80
+
+KPX W y -20
+KPX W u -45
+KPX W semicolon -10
+KPX W period -80
+KPX W o -60
+KPX W hyphen -40
+KPX W e -35
+KPX W comma -80
+KPX W colon -10
+KPX W a -40
+KPX W O -20
+KPX W A -60
+
+KPX Y u -100
+KPX Y semicolon -50
+KPX Y period -100
+KPX Y o -100
+KPX Y e -80
+KPX Y comma -100
+KPX Y colon -50
+KPX Y a -90
+KPX Y O -70
+KPX Y A -110
+
+KPX a y -20
+KPX a w -15
+KPX a v -15
+KPX a g -10
+
+KPX b y -20
+KPX b v -20
+KPX b u -20
+KPX b l -10
+
+KPX c y -10
+KPX c l -20
+KPX c k -20
+KPX c h -10
+
+KPX colon space -40
+
+KPX comma space -40
+KPX comma quoteright -120
+KPX comma quotedblright -120
+
+KPX d y -15
+KPX d w -15
+KPX d v -15
+KPX d d -10
+
+KPX e y -15
+KPX e x -15
+KPX e w -15
+KPX e v -15
+KPX e period 20
+KPX e comma 10
+
+KPX f quoteright 30
+KPX f quotedblright 30
+KPX f period -10
+KPX f o -20
+KPX f e -10
+KPX f comma -10
+
+KPX g g -10
+KPX g e 10
+
+KPX h y -20
+
+KPX k o -15
+
+KPX l y -15
+KPX l w -15
+
+KPX m y -30
+KPX m u -20
+
+KPX n y -20
+KPX n v -40
+KPX n u -10
+
+KPX o y -20
+KPX o x -30
+KPX o w -15
+KPX o v -20
+
+KPX p y -15
+
+KPX period space -40
+KPX period quoteright -120
+KPX period quotedblright -120
+
+KPX quotedblright space -80
+
+KPX quoteleft quoteleft -46
+
+KPX quoteright v -20
+KPX quoteright space -80
+KPX quoteright s -60
+KPX quoteright r -40
+KPX quoteright quoteright -46
+KPX quoteright l -20
+KPX quoteright d -80
+
+KPX r y 10
+KPX r v 10
+KPX r t 20
+KPX r s -15
+KPX r q -20
+KPX r period -60
+KPX r o -20
+KPX r hyphen -20
+KPX r g -15
+KPX r d -20
+KPX r comma -60
+KPX r c -20
+
+KPX s w -15
+
+KPX semicolon space -40
+
+KPX space quoteleft -60
+KPX space quotedblleft -80
+KPX space Y -120
+KPX space W -80
+KPX space V -80
+KPX space T -100
+
+KPX v period -80
+KPX v o -30
+KPX v comma -80
+KPX v a -20
+
+KPX w period -40
+KPX w o -20
+KPX w comma -40
+
+KPX x e -10
+
+KPX y period -80
+KPX y o -25
+KPX y e -10
+KPX y comma -80
+KPX y a -30
+
+KPX z e 10
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 192 186 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 192 186 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 192 186 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 192 186 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 192 186 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 192 186 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 176 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 169 186 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 169 186 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 169 186 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 169 186 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 10 186 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 10 186 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 10 186 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 10 186 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 192 186 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 215 186 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 215 186 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 215 186 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 215 186 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 215 186 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 169 186 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 192 186 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 192 186 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 192 186 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 192 186 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 169 186 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 169 186 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 146 186 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 92 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 92 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 92 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 92 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 92 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 92 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 108 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 92 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 92 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 92 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 92 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -22 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -22 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -22 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -22 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 114 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 114 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 114 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 114 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 114 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 114 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 92 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 114 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 114 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 114 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 114 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 92 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 92 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 69 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvl8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvl8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..b02ffacbf9d9162eb450cc69b1ad5e11dd70a393
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvl8a.afm
@@ -0,0 +1,445 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1988 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date:Mon Jan 11 16:46:06 PST 1988
+FontName Helvetica-Light
+EncodingScheme AdobeStandardEncoding
+FullName Helvetica Light
+FamilyName Helvetica
+Weight Light
+ItalicAngle 0.0
+IsFixedPitch false
+UnderlinePosition -90
+UnderlineThickness 58
+Version 001.002
+Notice Copyright (c) 1985, 1987, 1988 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype Company.
+FontBBox -164 -212 1000 979
+CapHeight 720
+XHeight 518
+Descender -204
+Ascender 720
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 130 0 203 720 ;
+C 34 ; WX 278 ; N quotedbl ; B 57 494 220 720 ;
+C 35 ; WX 556 ; N numbersign ; B 27 0 530 698 ;
+C 36 ; WX 556 ; N dollar ; B 37 -95 518 766 ;
+C 37 ; WX 889 ; N percent ; B 67 -14 821 705 ;
+C 38 ; WX 667 ; N ampersand ; B 41 -19 644 720 ;
+C 39 ; WX 222 ; N quoteright ; B 80 495 153 720 ;
+C 40 ; WX 333 ; N parenleft ; B 55 -191 277 739 ;
+C 41 ; WX 333 ; N parenright ; B 56 -191 278 739 ;
+C 42 ; WX 389 ; N asterisk ; B 44 434 344 720 ;
+C 43 ; WX 660 ; N plus ; B 80 0 580 500 ;
+C 44 ; WX 278 ; N comma ; B 102 -137 175 88 ;
+C 45 ; WX 333 ; N hyphen ; B 40 229 293 291 ;
+C 46 ; WX 278 ; N period ; B 102 0 175 88 ;
+C 47 ; WX 278 ; N slash ; B -3 -90 288 739 ;
+C 48 ; WX 556 ; N zero ; B 39 -14 516 705 ;
+C 49 ; WX 556 ; N one ; B 120 0 366 705 ;
+C 50 ; WX 556 ; N two ; B 48 0 515 705 ;
+C 51 ; WX 556 ; N three ; B 34 -14 512 705 ;
+C 52 ; WX 556 ; N four ; B 36 0 520 698 ;
+C 53 ; WX 556 ; N five ; B 35 -14 506 698 ;
+C 54 ; WX 556 ; N six ; B 41 -14 514 705 ;
+C 55 ; WX 556 ; N seven ; B 59 0 508 698 ;
+C 56 ; WX 556 ; N eight ; B 44 -14 512 705 ;
+C 57 ; WX 556 ; N nine ; B 41 -14 515 705 ;
+C 58 ; WX 278 ; N colon ; B 102 0 175 492 ;
+C 59 ; WX 278 ; N semicolon ; B 102 -137 175 492 ;
+C 60 ; WX 660 ; N less ; B 80 -6 580 505 ;
+C 61 ; WX 660 ; N equal ; B 80 124 580 378 ;
+C 62 ; WX 660 ; N greater ; B 80 -6 580 505 ;
+C 63 ; WX 500 ; N question ; B 37 0 472 739 ;
+C 64 ; WX 800 ; N at ; B 40 -19 760 739 ;
+C 65 ; WX 667 ; N A ; B 15 0 651 720 ;
+C 66 ; WX 667 ; N B ; B 81 0 610 720 ;
+C 67 ; WX 722 ; N C ; B 48 -19 670 739 ;
+C 68 ; WX 722 ; N D ; B 81 0 669 720 ;
+C 69 ; WX 611 ; N E ; B 81 0 570 720 ;
+C 70 ; WX 556 ; N F ; B 74 0 538 720 ;
+C 71 ; WX 778 ; N G ; B 53 -19 695 739 ;
+C 72 ; WX 722 ; N H ; B 80 0 642 720 ;
+C 73 ; WX 278 ; N I ; B 105 0 173 720 ;
+C 74 ; WX 500 ; N J ; B 22 -19 415 720 ;
+C 75 ; WX 667 ; N K ; B 85 0 649 720 ;
+C 76 ; WX 556 ; N L ; B 81 0 535 720 ;
+C 77 ; WX 833 ; N M ; B 78 0 755 720 ;
+C 78 ; WX 722 ; N N ; B 79 0 642 720 ;
+C 79 ; WX 778 ; N O ; B 53 -19 724 739 ;
+C 80 ; WX 611 ; N P ; B 78 0 576 720 ;
+C 81 ; WX 778 ; N Q ; B 48 -52 719 739 ;
+C 82 ; WX 667 ; N R ; B 80 0 612 720 ;
+C 83 ; WX 611 ; N S ; B 43 -19 567 739 ;
+C 84 ; WX 556 ; N T ; B 16 0 540 720 ;
+C 85 ; WX 722 ; N U ; B 82 -19 640 720 ;
+C 86 ; WX 611 ; N V ; B 18 0 593 720 ;
+C 87 ; WX 889 ; N W ; B 14 0 875 720 ;
+C 88 ; WX 611 ; N X ; B 18 0 592 720 ;
+C 89 ; WX 611 ; N Y ; B 12 0 598 720 ;
+C 90 ; WX 611 ; N Z ; B 31 0 579 720 ;
+C 91 ; WX 333 ; N bracketleft ; B 91 -191 282 739 ;
+C 92 ; WX 278 ; N backslash ; B -46 0 324 739 ;
+C 93 ; WX 333 ; N bracketright ; B 51 -191 242 739 ;
+C 94 ; WX 660 ; N asciicircum ; B 73 245 586 698 ;
+C 95 ; WX 500 ; N underscore ; B 0 -119 500 -61 ;
+C 96 ; WX 222 ; N quoteleft ; B 69 495 142 720 ;
+C 97 ; WX 556 ; N a ; B 46 -14 534 532 ;
+C 98 ; WX 611 ; N b ; B 79 -14 555 720 ;
+C 99 ; WX 556 ; N c ; B 47 -14 508 532 ;
+C 100 ; WX 611 ; N d ; B 56 -14 532 720 ;
+C 101 ; WX 556 ; N e ; B 45 -14 511 532 ;
+C 102 ; WX 278 ; N f ; B 20 0 257 734 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B 56 -212 532 532 ;
+C 104 ; WX 556 ; N h ; B 72 0 483 720 ;
+C 105 ; WX 222 ; N i ; B 78 0 144 720 ;
+C 106 ; WX 222 ; N j ; B 5 -204 151 720 ;
+C 107 ; WX 500 ; N k ; B 68 0 487 720 ;
+C 108 ; WX 222 ; N l ; B 81 0 141 720 ;
+C 109 ; WX 833 ; N m ; B 64 0 768 532 ;
+C 110 ; WX 556 ; N n ; B 72 0 483 532 ;
+C 111 ; WX 556 ; N o ; B 38 -14 518 532 ;
+C 112 ; WX 611 ; N p ; B 79 -204 555 532 ;
+C 113 ; WX 611 ; N q ; B 56 -204 532 532 ;
+C 114 ; WX 333 ; N r ; B 75 0 306 532 ;
+C 115 ; WX 500 ; N s ; B 46 -14 454 532 ;
+C 116 ; WX 278 ; N t ; B 20 -14 254 662 ;
+C 117 ; WX 556 ; N u ; B 72 -14 483 518 ;
+C 118 ; WX 500 ; N v ; B 17 0 483 518 ;
+C 119 ; WX 722 ; N w ; B 15 0 707 518 ;
+C 120 ; WX 500 ; N x ; B 18 0 481 518 ;
+C 121 ; WX 500 ; N y ; B 18 -204 482 518 ;
+C 122 ; WX 500 ; N z ; B 33 0 467 518 ;
+C 123 ; WX 333 ; N braceleft ; B 45 -191 279 739 ;
+C 124 ; WX 222 ; N bar ; B 81 0 141 739 ;
+C 125 ; WX 333 ; N braceright ; B 51 -187 285 743 ;
+C 126 ; WX 660 ; N asciitilde ; B 80 174 580 339 ;
+C 161 ; WX 333 ; N exclamdown ; B 130 -187 203 532 ;
+C 162 ; WX 556 ; N cent ; B 45 -141 506 647 ;
+C 163 ; WX 556 ; N sterling ; B 25 -14 530 705 ;
+C 164 ; WX 167 ; N fraction ; B -164 -14 331 705 ;
+C 165 ; WX 556 ; N yen ; B 4 0 552 720 ;
+C 166 ; WX 556 ; N florin ; B 13 -196 539 734 ;
+C 167 ; WX 556 ; N section ; B 48 -181 508 739 ;
+C 168 ; WX 556 ; N currency ; B 27 50 529 553 ;
+C 169 ; WX 222 ; N quotesingle ; B 85 494 137 720 ;
+C 170 ; WX 389 ; N quotedblleft ; B 86 495 310 720 ;
+C 171 ; WX 556 ; N guillemotleft ; B 113 117 443 404 ;
+C 172 ; WX 389 ; N guilsinglleft ; B 121 117 267 404 ;
+C 173 ; WX 389 ; N guilsinglright ; B 122 117 268 404 ;
+C 174 ; WX 500 ; N fi ; B 13 0 435 734 ;
+C 175 ; WX 500 ; N fl ; B 13 0 432 734 ;
+C 177 ; WX 500 ; N endash ; B 0 238 500 282 ;
+C 178 ; WX 556 ; N dagger ; B 37 -166 519 720 ;
+C 179 ; WX 556 ; N daggerdbl ; B 37 -166 519 720 ;
+C 180 ; WX 278 ; N periodcentered ; B 90 301 187 398 ;
+C 182 ; WX 650 ; N paragraph ; B 66 -146 506 720 ;
+C 183 ; WX 500 ; N bullet ; B 70 180 430 540 ;
+C 184 ; WX 222 ; N quotesinglbase ; B 80 -137 153 88 ;
+C 185 ; WX 389 ; N quotedblbase ; B 79 -137 303 88 ;
+C 186 ; WX 389 ; N quotedblright ; B 79 495 303 720 ;
+C 187 ; WX 556 ; N guillemotright ; B 113 117 443 404 ;
+C 188 ; WX 1000 ; N ellipsis ; B 131 0 870 88 ;
+C 189 ; WX 1000 ; N perthousand ; B 14 -14 985 705 ;
+C 191 ; WX 500 ; N questiondown ; B 28 -207 463 532 ;
+C 193 ; WX 333 ; N grave ; B 45 574 234 713 ;
+C 194 ; WX 333 ; N acute ; B 109 574 297 713 ;
+C 195 ; WX 333 ; N circumflex ; B 24 574 318 713 ;
+C 196 ; WX 333 ; N tilde ; B 16 586 329 688 ;
+C 197 ; WX 333 ; N macron ; B 23 612 319 657 ;
+C 198 ; WX 333 ; N breve ; B 28 580 316 706 ;
+C 199 ; WX 333 ; N dotaccent ; B 134 584 199 686 ;
+C 200 ; WX 333 ; N dieresis ; B 60 584 284 686 ;
+C 202 ; WX 333 ; N ring ; B 67 578 266 777 ;
+C 203 ; WX 333 ; N cedilla ; B 54 -207 257 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 109 574 459 713 ;
+C 206 ; WX 333 ; N ogonek ; B 74 -190 228 0 ;
+C 207 ; WX 333 ; N caron ; B 24 574 318 713 ;
+C 208 ; WX 1000 ; N emdash ; B 0 238 1000 282 ;
+C 225 ; WX 1000 ; N AE ; B 5 0 960 720 ;
+C 227 ; WX 334 ; N ordfeminine ; B 8 307 325 739 ;
+C 232 ; WX 556 ; N Lslash ; B 0 0 535 720 ;
+C 233 ; WX 778 ; N Oslash ; B 42 -37 736 747 ;
+C 234 ; WX 1000 ; N OE ; B 41 -19 967 739 ;
+C 235 ; WX 334 ; N ordmasculine ; B 11 307 323 739 ;
+C 241 ; WX 889 ; N ae ; B 39 -14 847 532 ;
+C 245 ; WX 222 ; N dotlessi ; B 78 0 138 518 ;
+C 248 ; WX 222 ; N lslash ; B 10 0 212 720 ;
+C 249 ; WX 556 ; N oslash ; B 35 -23 521 541 ;
+C 250 ; WX 944 ; N oe ; B 36 -14 904 532 ;
+C 251 ; WX 500 ; N germandbls ; B 52 -14 459 734 ;
+C -1 ; WX 667 ; N Aacute ; B 15 0 651 915 ;
+C -1 ; WX 667 ; N Acircumflex ; B 15 0 651 915 ;
+C -1 ; WX 667 ; N Adieresis ; B 15 0 651 888 ;
+C -1 ; WX 667 ; N Agrave ; B 15 0 651 915 ;
+C -1 ; WX 667 ; N Aring ; B 15 0 651 979 ;
+C -1 ; WX 667 ; N Atilde ; B 15 0 651 890 ;
+C -1 ; WX 722 ; N Ccedilla ; B 48 -207 670 739 ;
+C -1 ; WX 611 ; N Eacute ; B 81 0 570 915 ;
+C -1 ; WX 611 ; N Ecircumflex ; B 81 0 570 915 ;
+C -1 ; WX 611 ; N Edieresis ; B 81 0 570 888 ;
+C -1 ; WX 611 ; N Egrave ; B 81 0 570 915 ;
+C -1 ; WX 722 ; N Eth ; B 10 0 669 720 ;
+C -1 ; WX 278 ; N Iacute ; B 62 0 250 915 ;
+C -1 ; WX 278 ; N Icircumflex ; B -23 0 271 915 ;
+C -1 ; WX 278 ; N Idieresis ; B 13 0 237 888 ;
+C -1 ; WX 278 ; N Igrave ; B 18 0 207 915 ;
+C -1 ; WX 722 ; N Ntilde ; B 79 0 642 890 ;
+C -1 ; WX 778 ; N Oacute ; B 53 -19 724 915 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 53 -19 724 915 ;
+C -1 ; WX 778 ; N Odieresis ; B 53 -19 724 888 ;
+C -1 ; WX 778 ; N Ograve ; B 53 -19 724 915 ;
+C -1 ; WX 778 ; N Otilde ; B 53 -19 724 890 ;
+C -1 ; WX 611 ; N Scaron ; B 43 -19 567 915 ;
+C -1 ; WX 611 ; N Thorn ; B 78 0 576 720 ;
+C -1 ; WX 722 ; N Uacute ; B 82 -19 640 915 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 82 -19 640 915 ;
+C -1 ; WX 722 ; N Udieresis ; B 82 -19 640 888 ;
+C -1 ; WX 722 ; N Ugrave ; B 82 -19 640 915 ;
+C -1 ; WX 611 ; N Yacute ; B 12 0 598 915 ;
+C -1 ; WX 611 ; N Ydieresis ; B 12 0 598 888 ;
+C -1 ; WX 611 ; N Zcaron ; B 31 0 579 915 ;
+C -1 ; WX 556 ; N aacute ; B 46 -14 534 713 ;
+C -1 ; WX 556 ; N acircumflex ; B 46 -14 534 713 ;
+C -1 ; WX 556 ; N adieresis ; B 46 -14 534 686 ;
+C -1 ; WX 556 ; N agrave ; B 46 -14 534 713 ;
+C -1 ; WX 556 ; N aring ; B 46 -14 534 777 ;
+C -1 ; WX 556 ; N atilde ; B 46 -14 534 688 ;
+C -1 ; WX 222 ; N brokenbar ; B 81 0 141 739 ;
+C -1 ; WX 556 ; N ccedilla ; B 47 -207 508 532 ;
+C -1 ; WX 800 ; N copyright ; B 21 -19 779 739 ;
+C -1 ; WX 400 ; N degree ; B 50 405 350 705 ;
+C -1 ; WX 660 ; N divide ; B 80 0 580 500 ;
+C -1 ; WX 556 ; N eacute ; B 45 -14 511 713 ;
+C -1 ; WX 556 ; N ecircumflex ; B 45 -14 511 713 ;
+C -1 ; WX 556 ; N edieresis ; B 45 -14 511 686 ;
+C -1 ; WX 556 ; N egrave ; B 45 -14 511 713 ;
+C -1 ; WX 556 ; N eth ; B 38 -14 518 739 ;
+C -1 ; WX 222 ; N iacute ; B 34 0 222 713 ;
+C -1 ; WX 222 ; N icircumflex ; B -51 0 243 713 ;
+C -1 ; WX 222 ; N idieresis ; B -15 0 209 686 ;
+C -1 ; WX 222 ; N igrave ; B -10 0 179 713 ;
+C -1 ; WX 660 ; N logicalnot ; B 80 112 580 378 ;
+C -1 ; WX 660 ; N minus ; B 80 220 580 280 ;
+C -1 ; WX 556 ; N mu ; B 72 -204 483 518 ;
+C -1 ; WX 660 ; N multiply ; B 83 6 578 500 ;
+C -1 ; WX 556 ; N ntilde ; B 72 0 483 688 ;
+C -1 ; WX 556 ; N oacute ; B 38 -14 518 713 ;
+C -1 ; WX 556 ; N ocircumflex ; B 38 -14 518 713 ;
+C -1 ; WX 556 ; N odieresis ; B 38 -14 518 686 ;
+C -1 ; WX 556 ; N ograve ; B 38 -14 518 713 ;
+C -1 ; WX 834 ; N onehalf ; B 40 -14 794 739 ;
+C -1 ; WX 834 ; N onequarter ; B 40 -14 794 739 ;
+C -1 ; WX 333 ; N onesuperior ; B 87 316 247 739 ;
+C -1 ; WX 556 ; N otilde ; B 38 -14 518 688 ;
+C -1 ; WX 660 ; N plusminus ; B 80 0 580 500 ;
+C -1 ; WX 800 ; N registered ; B 21 -19 779 739 ;
+C -1 ; WX 500 ; N scaron ; B 46 -14 454 713 ;
+C -1 ; WX 611 ; N thorn ; B 79 -204 555 720 ;
+C -1 ; WX 834 ; N threequarters ; B 40 -14 794 739 ;
+C -1 ; WX 333 ; N threesuperior ; B 11 308 322 739 ;
+C -1 ; WX 940 ; N trademark ; B 29 299 859 720 ;
+C -1 ; WX 333 ; N twosuperior ; B 15 316 318 739 ;
+C -1 ; WX 556 ; N uacute ; B 72 -14 483 713 ;
+C -1 ; WX 556 ; N ucircumflex ; B 72 -14 483 713 ;
+C -1 ; WX 556 ; N udieresis ; B 72 -14 483 686 ;
+C -1 ; WX 556 ; N ugrave ; B 72 -14 483 713 ;
+C -1 ; WX 500 ; N yacute ; B 18 -204 482 713 ;
+C -1 ; WX 500 ; N ydieresis ; B 18 -204 482 686 ;
+C -1 ; WX 500 ; N zcaron ; B 33 0 467 713 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 115
+
+KPX A y -18
+KPX A w -18
+KPX A v -18
+KPX A quoteright -74
+KPX A Y -74
+KPX A W -37
+KPX A V -74
+KPX A T -92
+
+KPX F period -129
+KPX F comma -129
+KPX F A -55
+
+KPX L y -37
+KPX L quoteright -74
+KPX L Y -111
+KPX L W -55
+KPX L V -92
+KPX L T -92
+
+KPX P period -129
+KPX P comma -129
+KPX P A -74
+
+KPX R y 0
+KPX R Y -37
+KPX R W -18
+KPX R V -18
+KPX R T -18
+
+KPX T y -84
+KPX T w -84
+KPX T u -92
+KPX T semicolon -111
+KPX T s -111
+KPX T r -92
+KPX T period -111
+KPX T o -111
+KPX T i 0
+KPX T hyphen -129
+KPX T e -111
+KPX T comma -111
+KPX T colon -111
+KPX T c -111
+KPX T a -111
+KPX T A -92
+
+KPX V y -18
+KPX V u -37
+KPX V semicolon -74
+KPX V r -37
+KPX V period -129
+KPX V o -55
+KPX V i -18
+KPX V hyphen -55
+KPX V e -55
+KPX V comma -129
+KPX V colon -74
+KPX V a -55
+KPX V A -74
+
+KPX W y 0
+KPX W u -18
+KPX W semicolon -18
+KPX W r -18
+KPX W period -74
+KPX W o -18
+KPX W i 0
+KPX W hyphen 0
+KPX W e -18
+KPX W comma -74
+KPX W colon -18
+KPX W a -37
+KPX W A -37
+
+KPX Y v -40
+KPX Y u -37
+KPX Y semicolon -92
+KPX Y q -92
+KPX Y period -111
+KPX Y p -37
+KPX Y o -92
+KPX Y i -20
+KPX Y hyphen -111
+KPX Y e -92
+KPX Y comma -111
+KPX Y colon -92
+KPX Y a -92
+KPX Y A -74
+
+KPX f quoteright 18
+KPX f f -18
+
+KPX quoteleft quoteleft -18
+
+KPX quoteright t -18
+KPX quoteright s -74
+KPX quoteright quoteright -18
+
+KPX r z 0
+KPX r y 18
+KPX r x 0
+KPX r w 0
+KPX r v 0
+KPX r u 0
+KPX r t 18
+KPX r r 0
+KPX r quoteright 0
+KPX r q -18
+KPX r period -92
+KPX r o -18
+KPX r n 18
+KPX r m 18
+KPX r hyphen -55
+KPX r h 0
+KPX r g 0
+KPX r f 18
+KPX r e -18
+KPX r d -18
+KPX r comma -92
+KPX r c -18
+
+KPX v period -74
+KPX v comma -74
+
+KPX w period -55
+KPX w comma -55
+
+KPX y period -92
+KPX y comma -92
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 139 202 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 83 0 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 139 202 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 83 0 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 194 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 111 0 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 139 202 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 83 0 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 139 202 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 83 0 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 194 202 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 194 202 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 194 202 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 194 202 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 111 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 111 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 111 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 111 0 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -47 202 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -47 202 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -47 202 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -27 202 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -75 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -75 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -75 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -55 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 139 202 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 139 202 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 139 202 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 139 202 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 111 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 111 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 111 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 111 0 ;
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 167 202 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 167 202 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 167 202 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 167 202 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 111 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 111 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 111 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 111 0 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 222 202 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 222 202 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 222 202 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 222 202 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 111 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 111 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 111 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 111 0 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 167 202 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 111 0 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 194 202 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 111 0 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 222 202 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 111 0 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 187 202 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 111 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvlo8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvlo8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..96612d12ff51d4663e7b27395963cf1a2a4178e7
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvlo8a.afm
@@ -0,0 +1,445 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1988 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date:Mon Jan 11 17:38:44 PST 1988
+FontName Helvetica-LightOblique
+EncodingScheme AdobeStandardEncoding
+FullName Helvetica Light Oblique
+FamilyName Helvetica
+Weight Light
+ItalicAngle -12.0
+IsFixedPitch false
+UnderlinePosition -90
+UnderlineThickness 58
+Version 001.002
+Notice Copyright (c) 1985, 1987, 1988 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype Company.
+FontBBox -167 -212 1110 979
+CapHeight 720
+XHeight 518
+Descender -204
+Ascender 720
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 130 0 356 720 ;
+C 34 ; WX 278 ; N quotedbl ; B 162 494 373 720 ;
+C 35 ; WX 556 ; N numbersign ; B 75 0 633 698 ;
+C 36 ; WX 556 ; N dollar ; B 75 -95 613 766 ;
+C 37 ; WX 889 ; N percent ; B 176 -14 860 705 ;
+C 38 ; WX 667 ; N ampersand ; B 77 -19 646 720 ;
+C 39 ; WX 222 ; N quoteright ; B 185 495 306 720 ;
+C 40 ; WX 333 ; N parenleft ; B 97 -191 434 739 ;
+C 41 ; WX 333 ; N parenright ; B 15 -191 353 739 ;
+C 42 ; WX 389 ; N asterisk ; B 172 434 472 720 ;
+C 43 ; WX 660 ; N plus ; B 127 0 640 500 ;
+C 44 ; WX 278 ; N comma ; B 73 -137 194 88 ;
+C 45 ; WX 333 ; N hyphen ; B 89 229 355 291 ;
+C 46 ; WX 278 ; N period ; B 102 0 194 88 ;
+C 47 ; WX 278 ; N slash ; B -22 -90 445 739 ;
+C 48 ; WX 556 ; N zero ; B 93 -14 609 705 ;
+C 49 ; WX 556 ; N one ; B 231 0 516 705 ;
+C 50 ; WX 556 ; N two ; B 48 0 628 705 ;
+C 51 ; WX 556 ; N three ; B 74 -14 605 705 ;
+C 52 ; WX 556 ; N four ; B 73 0 570 698 ;
+C 53 ; WX 556 ; N five ; B 71 -14 616 698 ;
+C 54 ; WX 556 ; N six ; B 94 -14 617 705 ;
+C 55 ; WX 556 ; N seven ; B 152 0 656 698 ;
+C 56 ; WX 556 ; N eight ; B 80 -14 601 705 ;
+C 57 ; WX 556 ; N nine ; B 84 -14 607 705 ;
+C 58 ; WX 278 ; N colon ; B 102 0 280 492 ;
+C 59 ; WX 278 ; N semicolon ; B 73 -137 280 492 ;
+C 60 ; WX 660 ; N less ; B 129 -6 687 505 ;
+C 61 ; WX 660 ; N equal ; B 106 124 660 378 ;
+C 62 ; WX 660 ; N greater ; B 79 -6 640 505 ;
+C 63 ; WX 500 ; N question ; B 148 0 594 739 ;
+C 64 ; WX 800 ; N at ; B 108 -19 857 739 ;
+C 65 ; WX 667 ; N A ; B 15 0 651 720 ;
+C 66 ; WX 667 ; N B ; B 81 0 697 720 ;
+C 67 ; WX 722 ; N C ; B 111 -19 771 739 ;
+C 68 ; WX 722 ; N D ; B 81 0 758 720 ;
+C 69 ; WX 611 ; N E ; B 81 0 713 720 ;
+C 70 ; WX 556 ; N F ; B 74 0 691 720 ;
+C 71 ; WX 778 ; N G ; B 116 -19 796 739 ;
+C 72 ; WX 722 ; N H ; B 80 0 795 720 ;
+C 73 ; WX 278 ; N I ; B 105 0 326 720 ;
+C 74 ; WX 500 ; N J ; B 58 -19 568 720 ;
+C 75 ; WX 667 ; N K ; B 85 0 752 720 ;
+C 76 ; WX 556 ; N L ; B 81 0 547 720 ;
+C 77 ; WX 833 ; N M ; B 78 0 908 720 ;
+C 78 ; WX 722 ; N N ; B 79 0 795 720 ;
+C 79 ; WX 778 ; N O ; B 117 -19 812 739 ;
+C 80 ; WX 611 ; N P ; B 78 0 693 720 ;
+C 81 ; WX 778 ; N Q ; B 112 -52 808 739 ;
+C 82 ; WX 667 ; N R ; B 80 0 726 720 ;
+C 83 ; WX 611 ; N S ; B 82 -19 663 739 ;
+C 84 ; WX 556 ; N T ; B 157 0 693 720 ;
+C 85 ; WX 722 ; N U ; B 129 -19 793 720 ;
+C 86 ; WX 611 ; N V ; B 171 0 746 720 ;
+C 87 ; WX 889 ; N W ; B 167 0 1028 720 ;
+C 88 ; WX 611 ; N X ; B 18 0 734 720 ;
+C 89 ; WX 611 ; N Y ; B 165 0 751 720 ;
+C 90 ; WX 611 ; N Z ; B 31 0 729 720 ;
+C 91 ; WX 333 ; N bracketleft ; B 50 -191 439 739 ;
+C 92 ; WX 278 ; N backslash ; B 111 0 324 739 ;
+C 93 ; WX 333 ; N bracketright ; B 10 -191 399 739 ;
+C 94 ; WX 660 ; N asciicircum ; B 125 245 638 698 ;
+C 95 ; WX 500 ; N underscore ; B -25 -119 487 -61 ;
+C 96 ; WX 222 ; N quoteleft ; B 174 495 295 720 ;
+C 97 ; WX 556 ; N a ; B 71 -14 555 532 ;
+C 98 ; WX 611 ; N b ; B 79 -14 619 720 ;
+C 99 ; WX 556 ; N c ; B 92 -14 576 532 ;
+C 100 ; WX 611 ; N d ; B 101 -14 685 720 ;
+C 101 ; WX 556 ; N e ; B 90 -14 575 532 ;
+C 102 ; WX 278 ; N f ; B 97 0 412 734 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B 56 -212 642 532 ;
+C 104 ; WX 556 ; N h ; B 72 0 565 720 ;
+C 105 ; WX 222 ; N i ; B 81 0 297 720 ;
+C 106 ; WX 222 ; N j ; B -38 -204 304 720 ;
+C 107 ; WX 500 ; N k ; B 68 0 574 720 ;
+C 108 ; WX 222 ; N l ; B 81 0 294 720 ;
+C 109 ; WX 833 ; N m ; B 64 0 848 532 ;
+C 110 ; WX 556 ; N n ; B 72 0 565 532 ;
+C 111 ; WX 556 ; N o ; B 84 -14 582 532 ;
+C 112 ; WX 611 ; N p ; B 36 -204 620 532 ;
+C 113 ; WX 611 ; N q ; B 102 -204 642 532 ;
+C 114 ; WX 333 ; N r ; B 75 0 419 532 ;
+C 115 ; WX 500 ; N s ; B 78 -14 519 532 ;
+C 116 ; WX 278 ; N t ; B 108 -14 360 662 ;
+C 117 ; WX 556 ; N u ; B 103 -14 593 518 ;
+C 118 ; WX 500 ; N v ; B 127 0 593 518 ;
+C 119 ; WX 722 ; N w ; B 125 0 817 518 ;
+C 120 ; WX 500 ; N x ; B 18 0 584 518 ;
+C 121 ; WX 500 ; N y ; B 26 -204 592 518 ;
+C 122 ; WX 500 ; N z ; B 33 0 564 518 ;
+C 123 ; WX 333 ; N braceleft ; B 103 -191 436 739 ;
+C 124 ; WX 222 ; N bar ; B 81 0 298 739 ;
+C 125 ; WX 333 ; N braceright ; B 12 -187 344 743 ;
+C 126 ; WX 660 ; N asciitilde ; B 127 174 645 339 ;
+C 161 ; WX 333 ; N exclamdown ; B 90 -187 316 532 ;
+C 162 ; WX 556 ; N cent ; B 90 -141 574 647 ;
+C 163 ; WX 556 ; N sterling ; B 51 -14 613 705 ;
+C 164 ; WX 167 ; N fraction ; B -167 -14 481 705 ;
+C 165 ; WX 556 ; N yen ; B 110 0 705 720 ;
+C 166 ; WX 556 ; N florin ; B -26 -196 691 734 ;
+C 167 ; WX 556 ; N section ; B 91 -181 581 739 ;
+C 168 ; WX 556 ; N currency ; B 55 50 629 553 ;
+C 169 ; WX 222 ; N quotesingle ; B 190 494 290 720 ;
+C 170 ; WX 389 ; N quotedblleft ; B 191 495 463 720 ;
+C 171 ; WX 556 ; N guillemotleft ; B 161 117 529 404 ;
+C 172 ; WX 389 ; N guilsinglleft ; B 169 117 353 404 ;
+C 173 ; WX 389 ; N guilsinglright ; B 147 117 330 404 ;
+C 174 ; WX 500 ; N fi ; B 92 0 588 734 ;
+C 175 ; WX 500 ; N fl ; B 92 0 585 734 ;
+C 177 ; WX 500 ; N endash ; B 51 238 560 282 ;
+C 178 ; WX 556 ; N dagger ; B 130 -166 623 720 ;
+C 179 ; WX 556 ; N daggerdbl ; B 49 -166 625 720 ;
+C 180 ; WX 278 ; N periodcentered ; B 163 301 262 398 ;
+C 182 ; WX 650 ; N paragraph ; B 174 -146 659 720 ;
+C 183 ; WX 500 ; N bullet ; B 142 180 510 540 ;
+C 184 ; WX 222 ; N quotesinglbase ; B 51 -137 172 88 ;
+C 185 ; WX 389 ; N quotedblbase ; B 50 -137 322 88 ;
+C 186 ; WX 389 ; N quotedblright ; B 184 495 456 720 ;
+C 187 ; WX 556 ; N guillemotright ; B 138 117 505 404 ;
+C 188 ; WX 1000 ; N ellipsis ; B 131 0 889 88 ;
+C 189 ; WX 1000 ; N perthousand ; B 83 -14 1020 705 ;
+C 191 ; WX 500 ; N questiondown ; B 19 -207 465 532 ;
+C 193 ; WX 333 ; N grave ; B 197 574 356 713 ;
+C 194 ; WX 333 ; N acute ; B 231 574 449 713 ;
+C 195 ; WX 333 ; N circumflex ; B 146 574 440 713 ;
+C 196 ; WX 333 ; N tilde ; B 141 586 475 688 ;
+C 197 ; WX 333 ; N macron ; B 153 612 459 657 ;
+C 198 ; WX 333 ; N breve ; B 177 580 466 706 ;
+C 199 ; WX 333 ; N dotaccent ; B 258 584 345 686 ;
+C 200 ; WX 333 ; N dieresis ; B 184 584 430 686 ;
+C 202 ; WX 333 ; N ring ; B 209 578 412 777 ;
+C 203 ; WX 333 ; N cedilla ; B 14 -207 233 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 231 574 611 713 ;
+C 206 ; WX 333 ; N ogonek ; B 50 -190 199 0 ;
+C 207 ; WX 333 ; N caron ; B 176 574 470 713 ;
+C 208 ; WX 1000 ; N emdash ; B 51 238 1060 282 ;
+C 225 ; WX 1000 ; N AE ; B 5 0 1101 720 ;
+C 227 ; WX 334 ; N ordfeminine ; B 73 307 423 739 ;
+C 232 ; WX 556 ; N Lslash ; B 68 0 547 720 ;
+C 233 ; WX 778 ; N Oslash ; B 41 -37 887 747 ;
+C 234 ; WX 1000 ; N OE ; B 104 -19 1110 739 ;
+C 235 ; WX 334 ; N ordmasculine ; B 76 307 450 739 ;
+C 241 ; WX 889 ; N ae ; B 63 -14 913 532 ;
+C 245 ; WX 222 ; N dotlessi ; B 78 0 248 518 ;
+C 248 ; WX 222 ; N lslash ; B 74 0 316 720 ;
+C 249 ; WX 556 ; N oslash ; B 36 -23 629 541 ;
+C 250 ; WX 944 ; N oe ; B 82 -14 970 532 ;
+C 251 ; WX 500 ; N germandbls ; B 52 -14 554 734 ;
+C -1 ; WX 667 ; N Aacute ; B 15 0 659 915 ;
+C -1 ; WX 667 ; N Acircumflex ; B 15 0 651 915 ;
+C -1 ; WX 667 ; N Adieresis ; B 15 0 651 888 ;
+C -1 ; WX 667 ; N Agrave ; B 15 0 651 915 ;
+C -1 ; WX 667 ; N Aring ; B 15 0 651 979 ;
+C -1 ; WX 667 ; N Atilde ; B 15 0 685 890 ;
+C -1 ; WX 722 ; N Ccedilla ; B 111 -207 771 739 ;
+C -1 ; WX 611 ; N Eacute ; B 81 0 713 915 ;
+C -1 ; WX 611 ; N Ecircumflex ; B 81 0 713 915 ;
+C -1 ; WX 611 ; N Edieresis ; B 81 0 713 888 ;
+C -1 ; WX 611 ; N Egrave ; B 81 0 713 915 ;
+C -1 ; WX 722 ; N Eth ; B 81 0 758 720 ;
+C -1 ; WX 278 ; N Iacute ; B 105 0 445 915 ;
+C -1 ; WX 278 ; N Icircumflex ; B 105 0 436 915 ;
+C -1 ; WX 278 ; N Idieresis ; B 105 0 426 888 ;
+C -1 ; WX 278 ; N Igrave ; B 105 0 372 915 ;
+C -1 ; WX 722 ; N Ntilde ; B 79 0 795 890 ;
+C -1 ; WX 778 ; N Oacute ; B 117 -19 812 915 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 117 -19 812 915 ;
+C -1 ; WX 778 ; N Odieresis ; B 117 -19 812 888 ;
+C -1 ; WX 778 ; N Ograve ; B 117 -19 812 915 ;
+C -1 ; WX 778 ; N Otilde ; B 117 -19 812 890 ;
+C -1 ; WX 611 ; N Scaron ; B 82 -19 663 915 ;
+C -1 ; WX 611 ; N Thorn ; B 78 0 661 720 ;
+C -1 ; WX 722 ; N Uacute ; B 129 -19 793 915 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 129 -19 793 915 ;
+C -1 ; WX 722 ; N Udieresis ; B 129 -19 793 888 ;
+C -1 ; WX 722 ; N Ugrave ; B 129 -19 793 915 ;
+C -1 ; WX 611 ; N Yacute ; B 165 0 751 915 ;
+C -1 ; WX 611 ; N Ydieresis ; B 165 0 751 888 ;
+C -1 ; WX 611 ; N Zcaron ; B 31 0 729 915 ;
+C -1 ; WX 556 ; N aacute ; B 71 -14 561 713 ;
+C -1 ; WX 556 ; N acircumflex ; B 71 -14 555 713 ;
+C -1 ; WX 556 ; N adieresis ; B 71 -14 555 686 ;
+C -1 ; WX 556 ; N agrave ; B 71 -14 555 713 ;
+C -1 ; WX 556 ; N aring ; B 71 -14 555 777 ;
+C -1 ; WX 556 ; N atilde ; B 71 -14 587 688 ;
+C -1 ; WX 222 ; N brokenbar ; B 81 0 298 739 ;
+C -1 ; WX 556 ; N ccedilla ; B 92 -207 576 532 ;
+C -1 ; WX 800 ; N copyright ; B 89 -19 864 739 ;
+C -1 ; WX 400 ; N degree ; B 165 405 471 705 ;
+C -1 ; WX 660 ; N divide ; B 127 0 640 500 ;
+C -1 ; WX 556 ; N eacute ; B 90 -14 575 713 ;
+C -1 ; WX 556 ; N ecircumflex ; B 90 -14 575 713 ;
+C -1 ; WX 556 ; N edieresis ; B 90 -14 575 686 ;
+C -1 ; WX 556 ; N egrave ; B 90 -14 575 713 ;
+C -1 ; WX 556 ; N eth ; B 84 -14 582 739 ;
+C -1 ; WX 222 ; N iacute ; B 78 0 374 713 ;
+C -1 ; WX 222 ; N icircumflex ; B 71 0 365 713 ;
+C -1 ; WX 222 ; N idieresis ; B 78 0 355 686 ;
+C -1 ; WX 222 ; N igrave ; B 78 0 301 713 ;
+C -1 ; WX 660 ; N logicalnot ; B 148 112 660 378 ;
+C -1 ; WX 660 ; N minus ; B 127 220 640 280 ;
+C -1 ; WX 556 ; N mu ; B 29 -204 593 518 ;
+C -1 ; WX 660 ; N multiply ; B 92 6 677 500 ;
+C -1 ; WX 556 ; N ntilde ; B 72 0 587 688 ;
+C -1 ; WX 556 ; N oacute ; B 84 -14 582 713 ;
+C -1 ; WX 556 ; N ocircumflex ; B 84 -14 582 713 ;
+C -1 ; WX 556 ; N odieresis ; B 84 -14 582 686 ;
+C -1 ; WX 556 ; N ograve ; B 84 -14 582 713 ;
+C -1 ; WX 834 ; N onehalf ; B 125 -14 862 739 ;
+C -1 ; WX 834 ; N onequarter ; B 165 -14 823 739 ;
+C -1 ; WX 333 ; N onesuperior ; B 221 316 404 739 ;
+C -1 ; WX 556 ; N otilde ; B 84 -14 587 688 ;
+C -1 ; WX 660 ; N plusminus ; B 80 0 650 500 ;
+C -1 ; WX 800 ; N registered ; B 89 -19 864 739 ;
+C -1 ; WX 500 ; N scaron ; B 78 -14 554 713 ;
+C -1 ; WX 611 ; N thorn ; B 36 -204 620 720 ;
+C -1 ; WX 834 ; N threequarters ; B 131 -14 853 739 ;
+C -1 ; WX 333 ; N threesuperior ; B 102 308 444 739 ;
+C -1 ; WX 940 ; N trademark ; B 174 299 1012 720 ;
+C -1 ; WX 333 ; N twosuperior ; B 82 316 453 739 ;
+C -1 ; WX 556 ; N uacute ; B 103 -14 593 713 ;
+C -1 ; WX 556 ; N ucircumflex ; B 103 -14 593 713 ;
+C -1 ; WX 556 ; N udieresis ; B 103 -14 593 686 ;
+C -1 ; WX 556 ; N ugrave ; B 103 -14 593 713 ;
+C -1 ; WX 500 ; N yacute ; B 26 -204 592 713 ;
+C -1 ; WX 500 ; N ydieresis ; B 26 -204 592 686 ;
+C -1 ; WX 500 ; N zcaron ; B 33 0 564 713 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 115
+
+KPX A y -18
+KPX A w -18
+KPX A v -18
+KPX A quoteright -74
+KPX A Y -74
+KPX A W -37
+KPX A V -74
+KPX A T -92
+
+KPX F period -129
+KPX F comma -129
+KPX F A -55
+
+KPX L y -37
+KPX L quoteright -74
+KPX L Y -111
+KPX L W -55
+KPX L V -92
+KPX L T -92
+
+KPX P period -129
+KPX P comma -129
+KPX P A -74
+
+KPX R y 0
+KPX R Y -37
+KPX R W -18
+KPX R V -18
+KPX R T -18
+
+KPX T y -84
+KPX T w -84
+KPX T u -92
+KPX T semicolon -111
+KPX T s -111
+KPX T r -92
+KPX T period -111
+KPX T o -111
+KPX T i 0
+KPX T hyphen -129
+KPX T e -111
+KPX T comma -111
+KPX T colon -111
+KPX T c -111
+KPX T a -111
+KPX T A -92
+
+KPX V y -18
+KPX V u -37
+KPX V semicolon -74
+KPX V r -37
+KPX V period -129
+KPX V o -55
+KPX V i -18
+KPX V hyphen -55
+KPX V e -55
+KPX V comma -129
+KPX V colon -74
+KPX V a -55
+KPX V A -74
+
+KPX W y 0
+KPX W u -18
+KPX W semicolon -18
+KPX W r -18
+KPX W period -74
+KPX W o -18
+KPX W i 0
+KPX W hyphen 0
+KPX W e -18
+KPX W comma -74
+KPX W colon -18
+KPX W a -37
+KPX W A -37
+
+KPX Y v -40
+KPX Y u -37
+KPX Y semicolon -92
+KPX Y q -92
+KPX Y period -111
+KPX Y p -37
+KPX Y o -92
+KPX Y i -20
+KPX Y hyphen -111
+KPX Y e -92
+KPX Y comma -111
+KPX Y colon -92
+KPX Y a -92
+KPX Y A -74
+
+KPX f quoteright 18
+KPX f f -18
+
+KPX quoteleft quoteleft -18
+
+KPX quoteright t -18
+KPX quoteright s -74
+KPX quoteright quoteright -18
+
+KPX r z 0
+KPX r y 18
+KPX r x 0
+KPX r w 0
+KPX r v 0
+KPX r u 0
+KPX r t 18
+KPX r r 0
+KPX r quoteright 0
+KPX r q -18
+KPX r period -92
+KPX r o -18
+KPX r n 18
+KPX r m 18
+KPX r hyphen -55
+KPX r h 0
+KPX r g 0
+KPX r f 18
+KPX r e -18
+KPX r d -18
+KPX r comma -92
+KPX r c -18
+
+KPX v period -74
+KPX v comma -74
+
+KPX w period -55
+KPX w comma -55
+
+KPX y period -92
+KPX y comma -92
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 139 202 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 83 0 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 139 202 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 83 0 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 194 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 111 0 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 139 202 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 83 0 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 139 202 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 83 0 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 194 202 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 194 202 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 194 202 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 194 202 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 111 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 111 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 111 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 111 0 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -47 202 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -47 202 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -47 202 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -27 202 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -75 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -75 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -75 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -55 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 139 202 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 139 202 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 139 202 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 139 202 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 111 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 111 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 111 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 111 0 ;
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 167 202 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 167 202 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 167 202 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 167 202 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 111 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 111 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 111 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 111 0 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 222 202 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 222 202 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 222 202 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 222 202 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 111 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 111 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 111 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 111 0 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 167 202 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 111 0 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 194 202 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 111 0 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 222 202 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 111 0 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 187 202 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 111 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvr8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvr8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..1eb3b448569c9790fdcf37ebef8560611c946228
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvr8a.afm
@@ -0,0 +1,612 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Thu Mar 15 08:58:00 1990
+Comment UniqueID 28352
+Comment VMusage 26389 33281
+FontName Helvetica
+FullName Helvetica
+FamilyName Helvetica
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -166 -225 1000 931
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.006
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.Helvetica is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 523
+Ascender 718
+Descender -207
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 90 0 187 718 ;
+C 34 ; WX 355 ; N quotedbl ; B 70 463 285 718 ;
+C 35 ; WX 556 ; N numbersign ; B 28 0 529 688 ;
+C 36 ; WX 556 ; N dollar ; B 32 -115 520 775 ;
+C 37 ; WX 889 ; N percent ; B 39 -19 850 703 ;
+C 38 ; WX 667 ; N ampersand ; B 44 -15 645 718 ;
+C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
+C 40 ; WX 333 ; N parenleft ; B 68 -207 299 733 ;
+C 41 ; WX 333 ; N parenright ; B 34 -207 265 733 ;
+C 42 ; WX 389 ; N asterisk ; B 39 431 349 718 ;
+C 43 ; WX 584 ; N plus ; B 39 0 545 505 ;
+C 44 ; WX 278 ; N comma ; B 87 -147 191 106 ;
+C 45 ; WX 333 ; N hyphen ; B 44 232 289 322 ;
+C 46 ; WX 278 ; N period ; B 87 0 191 106 ;
+C 47 ; WX 278 ; N slash ; B -17 -19 295 737 ;
+C 48 ; WX 556 ; N zero ; B 37 -19 519 703 ;
+C 49 ; WX 556 ; N one ; B 101 0 359 703 ;
+C 50 ; WX 556 ; N two ; B 26 0 507 703 ;
+C 51 ; WX 556 ; N three ; B 34 -19 522 703 ;
+C 52 ; WX 556 ; N four ; B 25 0 523 703 ;
+C 53 ; WX 556 ; N five ; B 32 -19 514 688 ;
+C 54 ; WX 556 ; N six ; B 38 -19 518 703 ;
+C 55 ; WX 556 ; N seven ; B 37 0 523 688 ;
+C 56 ; WX 556 ; N eight ; B 38 -19 517 703 ;
+C 57 ; WX 556 ; N nine ; B 42 -19 514 703 ;
+C 58 ; WX 278 ; N colon ; B 87 0 191 516 ;
+C 59 ; WX 278 ; N semicolon ; B 87 -147 191 516 ;
+C 60 ; WX 584 ; N less ; B 48 11 536 495 ;
+C 61 ; WX 584 ; N equal ; B 39 115 545 390 ;
+C 62 ; WX 584 ; N greater ; B 48 11 536 495 ;
+C 63 ; WX 556 ; N question ; B 56 0 492 727 ;
+C 64 ; WX 1015 ; N at ; B 147 -19 868 737 ;
+C 65 ; WX 667 ; N A ; B 14 0 654 718 ;
+C 66 ; WX 667 ; N B ; B 74 0 627 718 ;
+C 67 ; WX 722 ; N C ; B 44 -19 681 737 ;
+C 68 ; WX 722 ; N D ; B 81 0 674 718 ;
+C 69 ; WX 667 ; N E ; B 86 0 616 718 ;
+C 70 ; WX 611 ; N F ; B 86 0 583 718 ;
+C 71 ; WX 778 ; N G ; B 48 -19 704 737 ;
+C 72 ; WX 722 ; N H ; B 77 0 646 718 ;
+C 73 ; WX 278 ; N I ; B 91 0 188 718 ;
+C 74 ; WX 500 ; N J ; B 17 -19 428 718 ;
+C 75 ; WX 667 ; N K ; B 76 0 663 718 ;
+C 76 ; WX 556 ; N L ; B 76 0 537 718 ;
+C 77 ; WX 833 ; N M ; B 73 0 761 718 ;
+C 78 ; WX 722 ; N N ; B 76 0 646 718 ;
+C 79 ; WX 778 ; N O ; B 39 -19 739 737 ;
+C 80 ; WX 667 ; N P ; B 86 0 622 718 ;
+C 81 ; WX 778 ; N Q ; B 39 -56 739 737 ;
+C 82 ; WX 722 ; N R ; B 88 0 684 718 ;
+C 83 ; WX 667 ; N S ; B 49 -19 620 737 ;
+C 84 ; WX 611 ; N T ; B 14 0 597 718 ;
+C 85 ; WX 722 ; N U ; B 79 -19 644 718 ;
+C 86 ; WX 667 ; N V ; B 20 0 647 718 ;
+C 87 ; WX 944 ; N W ; B 16 0 928 718 ;
+C 88 ; WX 667 ; N X ; B 19 0 648 718 ;
+C 89 ; WX 667 ; N Y ; B 14 0 653 718 ;
+C 90 ; WX 611 ; N Z ; B 23 0 588 718 ;
+C 91 ; WX 278 ; N bracketleft ; B 63 -196 250 722 ;
+C 92 ; WX 278 ; N backslash ; B -17 -19 295 737 ;
+C 93 ; WX 278 ; N bracketright ; B 28 -196 215 722 ;
+C 94 ; WX 469 ; N asciicircum ; B -14 264 483 688 ;
+C 95 ; WX 556 ; N underscore ; B 0 -125 556 -75 ;
+C 96 ; WX 222 ; N quoteleft ; B 65 470 169 725 ;
+C 97 ; WX 556 ; N a ; B 36 -15 530 538 ;
+C 98 ; WX 556 ; N b ; B 58 -15 517 718 ;
+C 99 ; WX 500 ; N c ; B 30 -15 477 538 ;
+C 100 ; WX 556 ; N d ; B 35 -15 499 718 ;
+C 101 ; WX 556 ; N e ; B 40 -15 516 538 ;
+C 102 ; WX 278 ; N f ; B 14 0 262 728 ; L i fi ; L l fl ;
+C 103 ; WX 556 ; N g ; B 40 -220 499 538 ;
+C 104 ; WX 556 ; N h ; B 65 0 491 718 ;
+C 105 ; WX 222 ; N i ; B 67 0 155 718 ;
+C 106 ; WX 222 ; N j ; B -16 -210 155 718 ;
+C 107 ; WX 500 ; N k ; B 67 0 501 718 ;
+C 108 ; WX 222 ; N l ; B 67 0 155 718 ;
+C 109 ; WX 833 ; N m ; B 65 0 769 538 ;
+C 110 ; WX 556 ; N n ; B 65 0 491 538 ;
+C 111 ; WX 556 ; N o ; B 35 -14 521 538 ;
+C 112 ; WX 556 ; N p ; B 58 -207 517 538 ;
+C 113 ; WX 556 ; N q ; B 35 -207 494 538 ;
+C 114 ; WX 333 ; N r ; B 77 0 332 538 ;
+C 115 ; WX 500 ; N s ; B 32 -15 464 538 ;
+C 116 ; WX 278 ; N t ; B 14 -7 257 669 ;
+C 117 ; WX 556 ; N u ; B 68 -15 489 523 ;
+C 118 ; WX 500 ; N v ; B 8 0 492 523 ;
+C 119 ; WX 722 ; N w ; B 14 0 709 523 ;
+C 120 ; WX 500 ; N x ; B 11 0 490 523 ;
+C 121 ; WX 500 ; N y ; B 11 -214 489 523 ;
+C 122 ; WX 500 ; N z ; B 31 0 469 523 ;
+C 123 ; WX 334 ; N braceleft ; B 42 -196 292 722 ;
+C 124 ; WX 260 ; N bar ; B 94 -19 167 737 ;
+C 125 ; WX 334 ; N braceright ; B 42 -196 292 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 61 180 523 326 ;
+C 161 ; WX 333 ; N exclamdown ; B 118 -195 215 523 ;
+C 162 ; WX 556 ; N cent ; B 51 -115 513 623 ;
+C 163 ; WX 556 ; N sterling ; B 33 -16 539 718 ;
+C 164 ; WX 167 ; N fraction ; B -166 -19 333 703 ;
+C 165 ; WX 556 ; N yen ; B 3 0 553 688 ;
+C 166 ; WX 556 ; N florin ; B -11 -207 501 737 ;
+C 167 ; WX 556 ; N section ; B 43 -191 512 737 ;
+C 168 ; WX 556 ; N currency ; B 28 99 528 603 ;
+C 169 ; WX 191 ; N quotesingle ; B 59 463 132 718 ;
+C 170 ; WX 333 ; N quotedblleft ; B 38 470 307 725 ;
+C 171 ; WX 556 ; N guillemotleft ; B 97 108 459 446 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 88 108 245 446 ;
+C 173 ; WX 333 ; N guilsinglright ; B 88 108 245 446 ;
+C 174 ; WX 500 ; N fi ; B 14 0 434 728 ;
+C 175 ; WX 500 ; N fl ; B 14 0 432 728 ;
+C 177 ; WX 556 ; N endash ; B 0 240 556 313 ;
+C 178 ; WX 556 ; N dagger ; B 43 -159 514 718 ;
+C 179 ; WX 556 ; N daggerdbl ; B 43 -159 514 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 77 190 202 315 ;
+C 182 ; WX 537 ; N paragraph ; B 18 -173 497 718 ;
+C 183 ; WX 350 ; N bullet ; B 18 202 333 517 ;
+C 184 ; WX 222 ; N quotesinglbase ; B 53 -149 157 106 ;
+C 185 ; WX 333 ; N quotedblbase ; B 26 -149 295 106 ;
+C 186 ; WX 333 ; N quotedblright ; B 26 463 295 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 97 108 459 446 ;
+C 188 ; WX 1000 ; N ellipsis ; B 115 0 885 106 ;
+C 189 ; WX 1000 ; N perthousand ; B 7 -19 994 703 ;
+C 191 ; WX 611 ; N questiondown ; B 91 -201 527 525 ;
+C 193 ; WX 333 ; N grave ; B 14 593 211 734 ;
+C 194 ; WX 333 ; N acute ; B 122 593 319 734 ;
+C 195 ; WX 333 ; N circumflex ; B 21 593 312 734 ;
+C 196 ; WX 333 ; N tilde ; B -4 606 337 722 ;
+C 197 ; WX 333 ; N macron ; B 10 627 323 684 ;
+C 198 ; WX 333 ; N breve ; B 13 595 321 731 ;
+C 199 ; WX 333 ; N dotaccent ; B 121 604 212 706 ;
+C 200 ; WX 333 ; N dieresis ; B 40 604 293 706 ;
+C 202 ; WX 333 ; N ring ; B 75 572 259 756 ;
+C 203 ; WX 333 ; N cedilla ; B 45 -225 259 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 31 593 409 734 ;
+C 206 ; WX 333 ; N ogonek ; B 73 -225 287 0 ;
+C 207 ; WX 333 ; N caron ; B 21 593 312 734 ;
+C 208 ; WX 1000 ; N emdash ; B 0 240 1000 313 ;
+C 225 ; WX 1000 ; N AE ; B 8 0 951 718 ;
+C 227 ; WX 370 ; N ordfeminine ; B 24 304 346 737 ;
+C 232 ; WX 556 ; N Lslash ; B -20 0 537 718 ;
+C 233 ; WX 778 ; N Oslash ; B 39 -19 740 737 ;
+C 234 ; WX 1000 ; N OE ; B 36 -19 965 737 ;
+C 235 ; WX 365 ; N ordmasculine ; B 25 304 341 737 ;
+C 241 ; WX 889 ; N ae ; B 36 -15 847 538 ;
+C 245 ; WX 278 ; N dotlessi ; B 95 0 183 523 ;
+C 248 ; WX 222 ; N lslash ; B -20 0 242 718 ;
+C 249 ; WX 611 ; N oslash ; B 28 -22 537 545 ;
+C 250 ; WX 944 ; N oe ; B 35 -15 902 538 ;
+C 251 ; WX 611 ; N germandbls ; B 67 -15 571 728 ;
+C -1 ; WX 611 ; N Zcaron ; B 23 0 588 929 ;
+C -1 ; WX 500 ; N ccedilla ; B 30 -225 477 538 ;
+C -1 ; WX 500 ; N ydieresis ; B 11 -214 489 706 ;
+C -1 ; WX 556 ; N atilde ; B 36 -15 530 722 ;
+C -1 ; WX 278 ; N icircumflex ; B -6 0 285 734 ;
+C -1 ; WX 333 ; N threesuperior ; B 5 270 325 703 ;
+C -1 ; WX 556 ; N ecircumflex ; B 40 -15 516 734 ;
+C -1 ; WX 556 ; N thorn ; B 58 -207 517 718 ;
+C -1 ; WX 556 ; N egrave ; B 40 -15 516 734 ;
+C -1 ; WX 333 ; N twosuperior ; B 4 281 323 703 ;
+C -1 ; WX 556 ; N eacute ; B 40 -15 516 734 ;
+C -1 ; WX 556 ; N otilde ; B 35 -14 521 722 ;
+C -1 ; WX 667 ; N Aacute ; B 14 0 654 929 ;
+C -1 ; WX 556 ; N ocircumflex ; B 35 -14 521 734 ;
+C -1 ; WX 500 ; N yacute ; B 11 -214 489 734 ;
+C -1 ; WX 556 ; N udieresis ; B 68 -15 489 706 ;
+C -1 ; WX 834 ; N threequarters ; B 45 -19 810 703 ;
+C -1 ; WX 556 ; N acircumflex ; B 36 -15 530 734 ;
+C -1 ; WX 722 ; N Eth ; B 0 0 674 718 ;
+C -1 ; WX 556 ; N edieresis ; B 40 -15 516 706 ;
+C -1 ; WX 556 ; N ugrave ; B 68 -15 489 734 ;
+C -1 ; WX 1000 ; N trademark ; B 46 306 903 718 ;
+C -1 ; WX 556 ; N ograve ; B 35 -14 521 734 ;
+C -1 ; WX 500 ; N scaron ; B 32 -15 464 734 ;
+C -1 ; WX 278 ; N Idieresis ; B 13 0 266 901 ;
+C -1 ; WX 556 ; N uacute ; B 68 -15 489 734 ;
+C -1 ; WX 556 ; N agrave ; B 36 -15 530 734 ;
+C -1 ; WX 556 ; N ntilde ; B 65 0 491 722 ;
+C -1 ; WX 556 ; N aring ; B 36 -15 530 756 ;
+C -1 ; WX 500 ; N zcaron ; B 31 0 469 734 ;
+C -1 ; WX 278 ; N Icircumflex ; B -6 0 285 929 ;
+C -1 ; WX 722 ; N Ntilde ; B 76 0 646 917 ;
+C -1 ; WX 556 ; N ucircumflex ; B 68 -15 489 734 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 86 0 616 929 ;
+C -1 ; WX 278 ; N Iacute ; B 91 0 292 929 ;
+C -1 ; WX 722 ; N Ccedilla ; B 44 -225 681 737 ;
+C -1 ; WX 778 ; N Odieresis ; B 39 -19 739 901 ;
+C -1 ; WX 667 ; N Scaron ; B 49 -19 620 929 ;
+C -1 ; WX 667 ; N Edieresis ; B 86 0 616 901 ;
+C -1 ; WX 278 ; N Igrave ; B -13 0 188 929 ;
+C -1 ; WX 556 ; N adieresis ; B 36 -15 530 706 ;
+C -1 ; WX 778 ; N Ograve ; B 39 -19 739 929 ;
+C -1 ; WX 667 ; N Egrave ; B 86 0 616 929 ;
+C -1 ; WX 667 ; N Ydieresis ; B 14 0 653 901 ;
+C -1 ; WX 737 ; N registered ; B -14 -19 752 737 ;
+C -1 ; WX 778 ; N Otilde ; B 39 -19 739 917 ;
+C -1 ; WX 834 ; N onequarter ; B 73 -19 756 703 ;
+C -1 ; WX 722 ; N Ugrave ; B 79 -19 644 929 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 79 -19 644 929 ;
+C -1 ; WX 667 ; N Thorn ; B 86 0 622 718 ;
+C -1 ; WX 584 ; N divide ; B 39 -19 545 524 ;
+C -1 ; WX 667 ; N Atilde ; B 14 0 654 917 ;
+C -1 ; WX 722 ; N Uacute ; B 79 -19 644 929 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 39 -19 739 929 ;
+C -1 ; WX 584 ; N logicalnot ; B 39 108 545 390 ;
+C -1 ; WX 667 ; N Aring ; B 14 0 654 931 ;
+C -1 ; WX 278 ; N idieresis ; B 13 0 266 706 ;
+C -1 ; WX 278 ; N iacute ; B 95 0 292 734 ;
+C -1 ; WX 556 ; N aacute ; B 36 -15 530 734 ;
+C -1 ; WX 584 ; N plusminus ; B 39 0 545 506 ;
+C -1 ; WX 584 ; N multiply ; B 39 0 545 506 ;
+C -1 ; WX 722 ; N Udieresis ; B 79 -19 644 901 ;
+C -1 ; WX 584 ; N minus ; B 39 216 545 289 ;
+C -1 ; WX 333 ; N onesuperior ; B 43 281 222 703 ;
+C -1 ; WX 667 ; N Eacute ; B 86 0 616 929 ;
+C -1 ; WX 667 ; N Acircumflex ; B 14 0 654 929 ;
+C -1 ; WX 737 ; N copyright ; B -14 -19 752 737 ;
+C -1 ; WX 667 ; N Agrave ; B 14 0 654 929 ;
+C -1 ; WX 556 ; N odieresis ; B 35 -14 521 706 ;
+C -1 ; WX 556 ; N oacute ; B 35 -14 521 734 ;
+C -1 ; WX 400 ; N degree ; B 54 411 346 703 ;
+C -1 ; WX 278 ; N igrave ; B -13 0 184 734 ;
+C -1 ; WX 556 ; N mu ; B 68 -207 489 523 ;
+C -1 ; WX 778 ; N Oacute ; B 39 -19 739 929 ;
+C -1 ; WX 556 ; N eth ; B 35 -15 522 737 ;
+C -1 ; WX 667 ; N Adieresis ; B 14 0 654 901 ;
+C -1 ; WX 667 ; N Yacute ; B 14 0 653 929 ;
+C -1 ; WX 260 ; N brokenbar ; B 94 -19 167 737 ;
+C -1 ; WX 834 ; N onehalf ; B 43 -19 773 703 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 250
+
+KPX A y -40
+KPX A w -40
+KPX A v -40
+KPX A u -30
+KPX A Y -100
+KPX A W -50
+KPX A V -70
+KPX A U -50
+KPX A T -120
+KPX A Q -30
+KPX A O -30
+KPX A G -30
+KPX A C -30
+
+KPX B period -20
+KPX B comma -20
+KPX B U -10
+
+KPX C period -30
+KPX C comma -30
+
+KPX D period -70
+KPX D comma -70
+KPX D Y -90
+KPX D W -40
+KPX D V -70
+KPX D A -40
+
+KPX F r -45
+KPX F period -150
+KPX F o -30
+KPX F e -30
+KPX F comma -150
+KPX F a -50
+KPX F A -80
+
+KPX J u -20
+KPX J period -30
+KPX J comma -30
+KPX J a -20
+KPX J A -20
+
+KPX K y -50
+KPX K u -30
+KPX K o -40
+KPX K e -40
+KPX K O -50
+
+KPX L y -30
+KPX L quoteright -160
+KPX L quotedblright -140
+KPX L Y -140
+KPX L W -70
+KPX L V -110
+KPX L T -110
+
+KPX O period -40
+KPX O comma -40
+KPX O Y -70
+KPX O X -60
+KPX O W -30
+KPX O V -50
+KPX O T -40
+KPX O A -20
+
+KPX P period -180
+KPX P o -50
+KPX P e -50
+KPX P comma -180
+KPX P a -40
+KPX P A -120
+
+KPX Q U -10
+
+KPX R Y -50
+KPX R W -30
+KPX R V -50
+KPX R U -40
+KPX R T -30
+KPX R O -20
+
+KPX S period -20
+KPX S comma -20
+
+KPX T y -120
+KPX T w -120
+KPX T u -120
+KPX T semicolon -20
+KPX T r -120
+KPX T period -120
+KPX T o -120
+KPX T hyphen -140
+KPX T e -120
+KPX T comma -120
+KPX T colon -20
+KPX T a -120
+KPX T O -40
+KPX T A -120
+
+KPX U period -40
+KPX U comma -40
+KPX U A -40
+
+KPX V u -70
+KPX V semicolon -40
+KPX V period -125
+KPX V o -80
+KPX V hyphen -80
+KPX V e -80
+KPX V comma -125
+KPX V colon -40
+KPX V a -70
+KPX V O -40
+KPX V G -40
+KPX V A -80
+
+KPX W y -20
+KPX W u -30
+KPX W period -80
+KPX W o -30
+KPX W hyphen -40
+KPX W e -30
+KPX W comma -80
+KPX W a -40
+KPX W O -20
+KPX W A -50
+
+KPX Y u -110
+KPX Y semicolon -60
+KPX Y period -140
+KPX Y o -140
+KPX Y i -20
+KPX Y hyphen -140
+KPX Y e -140
+KPX Y comma -140
+KPX Y colon -60
+KPX Y a -140
+KPX Y O -85
+KPX Y A -110
+
+KPX a y -30
+KPX a w -20
+KPX a v -20
+
+KPX b y -20
+KPX b v -20
+KPX b u -20
+KPX b period -40
+KPX b l -20
+KPX b comma -40
+KPX b b -10
+
+KPX c k -20
+KPX c comma -15
+
+KPX colon space -50
+
+KPX comma quoteright -100
+KPX comma quotedblright -100
+
+KPX e y -20
+KPX e x -30
+KPX e w -20
+KPX e v -30
+KPX e period -15
+KPX e comma -15
+
+KPX f quoteright 50
+KPX f quotedblright 60
+KPX f period -30
+KPX f o -30
+KPX f e -30
+KPX f dotlessi -28
+KPX f comma -30
+KPX f a -30
+
+KPX g r -10
+
+KPX h y -30
+
+KPX k o -20
+KPX k e -20
+
+KPX m y -15
+KPX m u -10
+
+KPX n y -15
+KPX n v -20
+KPX n u -10
+
+KPX o y -30
+KPX o x -30
+KPX o w -15
+KPX o v -15
+KPX o period -40
+KPX o comma -40
+
+KPX oslash z -55
+KPX oslash y -70
+KPX oslash x -85
+KPX oslash w -70
+KPX oslash v -70
+KPX oslash u -55
+KPX oslash t -55
+KPX oslash s -55
+KPX oslash r -55
+KPX oslash q -55
+KPX oslash period -95
+KPX oslash p -55
+KPX oslash o -55
+KPX oslash n -55
+KPX oslash m -55
+KPX oslash l -55
+KPX oslash k -55
+KPX oslash j -55
+KPX oslash i -55
+KPX oslash h -55
+KPX oslash g -55
+KPX oslash f -55
+KPX oslash e -55
+KPX oslash d -55
+KPX oslash comma -95
+KPX oslash c -55
+KPX oslash b -55
+KPX oslash a -55
+
+KPX p y -30
+KPX p period -35
+KPX p comma -35
+
+KPX period space -60
+KPX period quoteright -100
+KPX period quotedblright -100
+
+KPX quotedblright space -40
+
+KPX quoteleft quoteleft -57
+
+KPX quoteright space -70
+KPX quoteright s -50
+KPX quoteright r -50
+KPX quoteright quoteright -57
+KPX quoteright d -50
+
+KPX r y 30
+KPX r v 30
+KPX r u 15
+KPX r t 40
+KPX r semicolon 30
+KPX r period -50
+KPX r p 30
+KPX r n 25
+KPX r m 25
+KPX r l 15
+KPX r k 15
+KPX r i 15
+KPX r comma -50
+KPX r colon 30
+KPX r a -10
+
+KPX s w -30
+KPX s period -15
+KPX s comma -15
+
+KPX semicolon space -50
+
+KPX space quoteleft -60
+KPX space quotedblleft -30
+KPX space Y -90
+KPX space W -40
+KPX space V -50
+KPX space T -50
+
+KPX v period -80
+KPX v o -25
+KPX v e -25
+KPX v comma -80
+KPX v a -25
+
+KPX w period -60
+KPX w o -10
+KPX w e -10
+KPX w comma -60
+KPX w a -15
+
+KPX x e -30
+
+KPX y period -100
+KPX y o -20
+KPX y e -20
+KPX y comma -100
+KPX y a -20
+
+KPX z o -15
+KPX z e -15
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 167 195 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 167 195 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 167 195 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 167 195 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 167 175 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 167 195 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 195 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 167 195 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 167 195 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 167 195 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 167 195 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -27 195 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -27 195 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -27 195 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -27 195 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 205 195 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 223 195 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 223 195 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 223 195 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 223 195 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 223 195 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 167 195 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 195 195 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 195 195 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 195 195 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 195 195 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 167 195 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 167 195 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 139 195 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 112 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 112 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 112 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 112 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 112 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 102 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 84 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 112 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 112 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 112 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 112 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -27 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -27 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -27 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -27 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 102 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 112 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 112 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 112 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 112 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 112 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 84 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 112 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 112 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 112 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 112 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 84 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 84 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 84 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvr8an.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvr8an.afm
new file mode 100644
index 0000000000000000000000000000000000000000..5a08aa8c99a0ad2cd9a2088d0e34c420d629e28e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvr8an.afm
@@ -0,0 +1,612 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Thu Mar 15 11:04:57 1990
+Comment UniqueID 28380
+Comment VMusage 7572 42473
+FontName Helvetica-Narrow
+FullName Helvetica Narrow
+FamilyName Helvetica
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -136 -225 820 931
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.006
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.Helvetica is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 523
+Ascender 718
+Descender -207
+StartCharMetrics 228
+C 32 ; WX 228 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 228 ; N exclam ; B 74 0 153 718 ;
+C 34 ; WX 291 ; N quotedbl ; B 57 463 234 718 ;
+C 35 ; WX 456 ; N numbersign ; B 23 0 434 688 ;
+C 36 ; WX 456 ; N dollar ; B 26 -115 426 775 ;
+C 37 ; WX 729 ; N percent ; B 32 -19 697 703 ;
+C 38 ; WX 547 ; N ampersand ; B 36 -15 529 718 ;
+C 39 ; WX 182 ; N quoteright ; B 43 463 129 718 ;
+C 40 ; WX 273 ; N parenleft ; B 56 -207 245 733 ;
+C 41 ; WX 273 ; N parenright ; B 28 -207 217 733 ;
+C 42 ; WX 319 ; N asterisk ; B 32 431 286 718 ;
+C 43 ; WX 479 ; N plus ; B 32 0 447 505 ;
+C 44 ; WX 228 ; N comma ; B 71 -147 157 106 ;
+C 45 ; WX 273 ; N hyphen ; B 36 232 237 322 ;
+C 46 ; WX 228 ; N period ; B 71 0 157 106 ;
+C 47 ; WX 228 ; N slash ; B -14 -19 242 737 ;
+C 48 ; WX 456 ; N zero ; B 30 -19 426 703 ;
+C 49 ; WX 456 ; N one ; B 83 0 294 703 ;
+C 50 ; WX 456 ; N two ; B 21 0 416 703 ;
+C 51 ; WX 456 ; N three ; B 28 -19 428 703 ;
+C 52 ; WX 456 ; N four ; B 20 0 429 703 ;
+C 53 ; WX 456 ; N five ; B 26 -19 421 688 ;
+C 54 ; WX 456 ; N six ; B 31 -19 425 703 ;
+C 55 ; WX 456 ; N seven ; B 30 0 429 688 ;
+C 56 ; WX 456 ; N eight ; B 31 -19 424 703 ;
+C 57 ; WX 456 ; N nine ; B 34 -19 421 703 ;
+C 58 ; WX 228 ; N colon ; B 71 0 157 516 ;
+C 59 ; WX 228 ; N semicolon ; B 71 -147 157 516 ;
+C 60 ; WX 479 ; N less ; B 39 11 440 495 ;
+C 61 ; WX 479 ; N equal ; B 32 115 447 390 ;
+C 62 ; WX 479 ; N greater ; B 39 11 440 495 ;
+C 63 ; WX 456 ; N question ; B 46 0 403 727 ;
+C 64 ; WX 832 ; N at ; B 121 -19 712 737 ;
+C 65 ; WX 547 ; N A ; B 11 0 536 718 ;
+C 66 ; WX 547 ; N B ; B 61 0 514 718 ;
+C 67 ; WX 592 ; N C ; B 36 -19 558 737 ;
+C 68 ; WX 592 ; N D ; B 66 0 553 718 ;
+C 69 ; WX 547 ; N E ; B 71 0 505 718 ;
+C 70 ; WX 501 ; N F ; B 71 0 478 718 ;
+C 71 ; WX 638 ; N G ; B 39 -19 577 737 ;
+C 72 ; WX 592 ; N H ; B 63 0 530 718 ;
+C 73 ; WX 228 ; N I ; B 75 0 154 718 ;
+C 74 ; WX 410 ; N J ; B 14 -19 351 718 ;
+C 75 ; WX 547 ; N K ; B 62 0 544 718 ;
+C 76 ; WX 456 ; N L ; B 62 0 440 718 ;
+C 77 ; WX 683 ; N M ; B 60 0 624 718 ;
+C 78 ; WX 592 ; N N ; B 62 0 530 718 ;
+C 79 ; WX 638 ; N O ; B 32 -19 606 737 ;
+C 80 ; WX 547 ; N P ; B 71 0 510 718 ;
+C 81 ; WX 638 ; N Q ; B 32 -56 606 737 ;
+C 82 ; WX 592 ; N R ; B 72 0 561 718 ;
+C 83 ; WX 547 ; N S ; B 40 -19 508 737 ;
+C 84 ; WX 501 ; N T ; B 11 0 490 718 ;
+C 85 ; WX 592 ; N U ; B 65 -19 528 718 ;
+C 86 ; WX 547 ; N V ; B 16 0 531 718 ;
+C 87 ; WX 774 ; N W ; B 13 0 761 718 ;
+C 88 ; WX 547 ; N X ; B 16 0 531 718 ;
+C 89 ; WX 547 ; N Y ; B 11 0 535 718 ;
+C 90 ; WX 501 ; N Z ; B 19 0 482 718 ;
+C 91 ; WX 228 ; N bracketleft ; B 52 -196 205 722 ;
+C 92 ; WX 228 ; N backslash ; B -14 -19 242 737 ;
+C 93 ; WX 228 ; N bracketright ; B 23 -196 176 722 ;
+C 94 ; WX 385 ; N asciicircum ; B -11 264 396 688 ;
+C 95 ; WX 456 ; N underscore ; B 0 -125 456 -75 ;
+C 96 ; WX 182 ; N quoteleft ; B 53 470 139 725 ;
+C 97 ; WX 456 ; N a ; B 30 -15 435 538 ;
+C 98 ; WX 456 ; N b ; B 48 -15 424 718 ;
+C 99 ; WX 410 ; N c ; B 25 -15 391 538 ;
+C 100 ; WX 456 ; N d ; B 29 -15 409 718 ;
+C 101 ; WX 456 ; N e ; B 33 -15 423 538 ;
+C 102 ; WX 228 ; N f ; B 11 0 215 728 ; L i fi ; L l fl ;
+C 103 ; WX 456 ; N g ; B 33 -220 409 538 ;
+C 104 ; WX 456 ; N h ; B 53 0 403 718 ;
+C 105 ; WX 182 ; N i ; B 55 0 127 718 ;
+C 106 ; WX 182 ; N j ; B -13 -210 127 718 ;
+C 107 ; WX 410 ; N k ; B 55 0 411 718 ;
+C 108 ; WX 182 ; N l ; B 55 0 127 718 ;
+C 109 ; WX 683 ; N m ; B 53 0 631 538 ;
+C 110 ; WX 456 ; N n ; B 53 0 403 538 ;
+C 111 ; WX 456 ; N o ; B 29 -14 427 538 ;
+C 112 ; WX 456 ; N p ; B 48 -207 424 538 ;
+C 113 ; WX 456 ; N q ; B 29 -207 405 538 ;
+C 114 ; WX 273 ; N r ; B 63 0 272 538 ;
+C 115 ; WX 410 ; N s ; B 26 -15 380 538 ;
+C 116 ; WX 228 ; N t ; B 11 -7 211 669 ;
+C 117 ; WX 456 ; N u ; B 56 -15 401 523 ;
+C 118 ; WX 410 ; N v ; B 7 0 403 523 ;
+C 119 ; WX 592 ; N w ; B 11 0 581 523 ;
+C 120 ; WX 410 ; N x ; B 9 0 402 523 ;
+C 121 ; WX 410 ; N y ; B 9 -214 401 523 ;
+C 122 ; WX 410 ; N z ; B 25 0 385 523 ;
+C 123 ; WX 274 ; N braceleft ; B 34 -196 239 722 ;
+C 124 ; WX 213 ; N bar ; B 77 -19 137 737 ;
+C 125 ; WX 274 ; N braceright ; B 34 -196 239 722 ;
+C 126 ; WX 479 ; N asciitilde ; B 50 180 429 326 ;
+C 161 ; WX 273 ; N exclamdown ; B 97 -195 176 523 ;
+C 162 ; WX 456 ; N cent ; B 42 -115 421 623 ;
+C 163 ; WX 456 ; N sterling ; B 27 -16 442 718 ;
+C 164 ; WX 137 ; N fraction ; B -136 -19 273 703 ;
+C 165 ; WX 456 ; N yen ; B 2 0 453 688 ;
+C 166 ; WX 456 ; N florin ; B -9 -207 411 737 ;
+C 167 ; WX 456 ; N section ; B 35 -191 420 737 ;
+C 168 ; WX 456 ; N currency ; B 23 99 433 603 ;
+C 169 ; WX 157 ; N quotesingle ; B 48 463 108 718 ;
+C 170 ; WX 273 ; N quotedblleft ; B 31 470 252 725 ;
+C 171 ; WX 456 ; N guillemotleft ; B 80 108 376 446 ;
+C 172 ; WX 273 ; N guilsinglleft ; B 72 108 201 446 ;
+C 173 ; WX 273 ; N guilsinglright ; B 72 108 201 446 ;
+C 174 ; WX 410 ; N fi ; B 11 0 356 728 ;
+C 175 ; WX 410 ; N fl ; B 11 0 354 728 ;
+C 177 ; WX 456 ; N endash ; B 0 240 456 313 ;
+C 178 ; WX 456 ; N dagger ; B 35 -159 421 718 ;
+C 179 ; WX 456 ; N daggerdbl ; B 35 -159 421 718 ;
+C 180 ; WX 228 ; N periodcentered ; B 63 190 166 315 ;
+C 182 ; WX 440 ; N paragraph ; B 15 -173 408 718 ;
+C 183 ; WX 287 ; N bullet ; B 15 202 273 517 ;
+C 184 ; WX 182 ; N quotesinglbase ; B 43 -149 129 106 ;
+C 185 ; WX 273 ; N quotedblbase ; B 21 -149 242 106 ;
+C 186 ; WX 273 ; N quotedblright ; B 21 463 242 718 ;
+C 187 ; WX 456 ; N guillemotright ; B 80 108 376 446 ;
+C 188 ; WX 820 ; N ellipsis ; B 94 0 726 106 ;
+C 189 ; WX 820 ; N perthousand ; B 6 -19 815 703 ;
+C 191 ; WX 501 ; N questiondown ; B 75 -201 432 525 ;
+C 193 ; WX 273 ; N grave ; B 11 593 173 734 ;
+C 194 ; WX 273 ; N acute ; B 100 593 262 734 ;
+C 195 ; WX 273 ; N circumflex ; B 17 593 256 734 ;
+C 196 ; WX 273 ; N tilde ; B -3 606 276 722 ;
+C 197 ; WX 273 ; N macron ; B 8 627 265 684 ;
+C 198 ; WX 273 ; N breve ; B 11 595 263 731 ;
+C 199 ; WX 273 ; N dotaccent ; B 99 604 174 706 ;
+C 200 ; WX 273 ; N dieresis ; B 33 604 240 706 ;
+C 202 ; WX 273 ; N ring ; B 61 572 212 756 ;
+C 203 ; WX 273 ; N cedilla ; B 37 -225 212 0 ;
+C 205 ; WX 273 ; N hungarumlaut ; B 25 593 335 734 ;
+C 206 ; WX 273 ; N ogonek ; B 60 -225 235 0 ;
+C 207 ; WX 273 ; N caron ; B 17 593 256 734 ;
+C 208 ; WX 820 ; N emdash ; B 0 240 820 313 ;
+C 225 ; WX 820 ; N AE ; B 7 0 780 718 ;
+C 227 ; WX 303 ; N ordfeminine ; B 20 304 284 737 ;
+C 232 ; WX 456 ; N Lslash ; B -16 0 440 718 ;
+C 233 ; WX 638 ; N Oslash ; B 32 -19 607 737 ;
+C 234 ; WX 820 ; N OE ; B 30 -19 791 737 ;
+C 235 ; WX 299 ; N ordmasculine ; B 20 304 280 737 ;
+C 241 ; WX 729 ; N ae ; B 30 -15 695 538 ;
+C 245 ; WX 228 ; N dotlessi ; B 78 0 150 523 ;
+C 248 ; WX 182 ; N lslash ; B -16 0 198 718 ;
+C 249 ; WX 501 ; N oslash ; B 23 -22 440 545 ;
+C 250 ; WX 774 ; N oe ; B 29 -15 740 538 ;
+C 251 ; WX 501 ; N germandbls ; B 55 -15 468 728 ;
+C -1 ; WX 501 ; N Zcaron ; B 19 0 482 929 ;
+C -1 ; WX 410 ; N ccedilla ; B 25 -225 391 538 ;
+C -1 ; WX 410 ; N ydieresis ; B 9 -214 401 706 ;
+C -1 ; WX 456 ; N atilde ; B 30 -15 435 722 ;
+C -1 ; WX 228 ; N icircumflex ; B -5 0 234 734 ;
+C -1 ; WX 273 ; N threesuperior ; B 4 270 266 703 ;
+C -1 ; WX 456 ; N ecircumflex ; B 33 -15 423 734 ;
+C -1 ; WX 456 ; N thorn ; B 48 -207 424 718 ;
+C -1 ; WX 456 ; N egrave ; B 33 -15 423 734 ;
+C -1 ; WX 273 ; N twosuperior ; B 3 281 265 703 ;
+C -1 ; WX 456 ; N eacute ; B 33 -15 423 734 ;
+C -1 ; WX 456 ; N otilde ; B 29 -14 427 722 ;
+C -1 ; WX 547 ; N Aacute ; B 11 0 536 929 ;
+C -1 ; WX 456 ; N ocircumflex ; B 29 -14 427 734 ;
+C -1 ; WX 410 ; N yacute ; B 9 -214 401 734 ;
+C -1 ; WX 456 ; N udieresis ; B 56 -15 401 706 ;
+C -1 ; WX 684 ; N threequarters ; B 37 -19 664 703 ;
+C -1 ; WX 456 ; N acircumflex ; B 30 -15 435 734 ;
+C -1 ; WX 592 ; N Eth ; B 0 0 553 718 ;
+C -1 ; WX 456 ; N edieresis ; B 33 -15 423 706 ;
+C -1 ; WX 456 ; N ugrave ; B 56 -15 401 734 ;
+C -1 ; WX 820 ; N trademark ; B 38 306 740 718 ;
+C -1 ; WX 456 ; N ograve ; B 29 -14 427 734 ;
+C -1 ; WX 410 ; N scaron ; B 26 -15 380 734 ;
+C -1 ; WX 228 ; N Idieresis ; B 11 0 218 901 ;
+C -1 ; WX 456 ; N uacute ; B 56 -15 401 734 ;
+C -1 ; WX 456 ; N agrave ; B 30 -15 435 734 ;
+C -1 ; WX 456 ; N ntilde ; B 53 0 403 722 ;
+C -1 ; WX 456 ; N aring ; B 30 -15 435 756 ;
+C -1 ; WX 410 ; N zcaron ; B 25 0 385 734 ;
+C -1 ; WX 228 ; N Icircumflex ; B -5 0 234 929 ;
+C -1 ; WX 592 ; N Ntilde ; B 62 0 530 917 ;
+C -1 ; WX 456 ; N ucircumflex ; B 56 -15 401 734 ;
+C -1 ; WX 547 ; N Ecircumflex ; B 71 0 505 929 ;
+C -1 ; WX 228 ; N Iacute ; B 75 0 239 929 ;
+C -1 ; WX 592 ; N Ccedilla ; B 36 -225 558 737 ;
+C -1 ; WX 638 ; N Odieresis ; B 32 -19 606 901 ;
+C -1 ; WX 547 ; N Scaron ; B 40 -19 508 929 ;
+C -1 ; WX 547 ; N Edieresis ; B 71 0 505 901 ;
+C -1 ; WX 228 ; N Igrave ; B -11 0 154 929 ;
+C -1 ; WX 456 ; N adieresis ; B 30 -15 435 706 ;
+C -1 ; WX 638 ; N Ograve ; B 32 -19 606 929 ;
+C -1 ; WX 547 ; N Egrave ; B 71 0 505 929 ;
+C -1 ; WX 547 ; N Ydieresis ; B 11 0 535 901 ;
+C -1 ; WX 604 ; N registered ; B -11 -19 617 737 ;
+C -1 ; WX 638 ; N Otilde ; B 32 -19 606 917 ;
+C -1 ; WX 684 ; N onequarter ; B 60 -19 620 703 ;
+C -1 ; WX 592 ; N Ugrave ; B 65 -19 528 929 ;
+C -1 ; WX 592 ; N Ucircumflex ; B 65 -19 528 929 ;
+C -1 ; WX 547 ; N Thorn ; B 71 0 510 718 ;
+C -1 ; WX 479 ; N divide ; B 32 -19 447 524 ;
+C -1 ; WX 547 ; N Atilde ; B 11 0 536 917 ;
+C -1 ; WX 592 ; N Uacute ; B 65 -19 528 929 ;
+C -1 ; WX 638 ; N Ocircumflex ; B 32 -19 606 929 ;
+C -1 ; WX 479 ; N logicalnot ; B 32 108 447 390 ;
+C -1 ; WX 547 ; N Aring ; B 11 0 536 931 ;
+C -1 ; WX 228 ; N idieresis ; B 11 0 218 706 ;
+C -1 ; WX 228 ; N iacute ; B 78 0 239 734 ;
+C -1 ; WX 456 ; N aacute ; B 30 -15 435 734 ;
+C -1 ; WX 479 ; N plusminus ; B 32 0 447 506 ;
+C -1 ; WX 479 ; N multiply ; B 32 0 447 506 ;
+C -1 ; WX 592 ; N Udieresis ; B 65 -19 528 901 ;
+C -1 ; WX 479 ; N minus ; B 32 216 447 289 ;
+C -1 ; WX 273 ; N onesuperior ; B 35 281 182 703 ;
+C -1 ; WX 547 ; N Eacute ; B 71 0 505 929 ;
+C -1 ; WX 547 ; N Acircumflex ; B 11 0 536 929 ;
+C -1 ; WX 604 ; N copyright ; B -11 -19 617 737 ;
+C -1 ; WX 547 ; N Agrave ; B 11 0 536 929 ;
+C -1 ; WX 456 ; N odieresis ; B 29 -14 427 706 ;
+C -1 ; WX 456 ; N oacute ; B 29 -14 427 734 ;
+C -1 ; WX 328 ; N degree ; B 44 411 284 703 ;
+C -1 ; WX 228 ; N igrave ; B -11 0 151 734 ;
+C -1 ; WX 456 ; N mu ; B 56 -207 401 523 ;
+C -1 ; WX 638 ; N Oacute ; B 32 -19 606 929 ;
+C -1 ; WX 456 ; N eth ; B 29 -15 428 737 ;
+C -1 ; WX 547 ; N Adieresis ; B 11 0 536 901 ;
+C -1 ; WX 547 ; N Yacute ; B 11 0 535 929 ;
+C -1 ; WX 213 ; N brokenbar ; B 77 -19 137 737 ;
+C -1 ; WX 684 ; N onehalf ; B 35 -19 634 703 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 250
+
+KPX A y -32
+KPX A w -32
+KPX A v -32
+KPX A u -24
+KPX A Y -81
+KPX A W -40
+KPX A V -56
+KPX A U -40
+KPX A T -97
+KPX A Q -24
+KPX A O -24
+KPX A G -24
+KPX A C -24
+
+KPX B period -15
+KPX B comma -15
+KPX B U -7
+
+KPX C period -24
+KPX C comma -24
+
+KPX D period -56
+KPX D comma -56
+KPX D Y -73
+KPX D W -32
+KPX D V -56
+KPX D A -32
+
+KPX F r -36
+KPX F period -122
+KPX F o -24
+KPX F e -24
+KPX F comma -122
+KPX F a -40
+KPX F A -65
+
+KPX J u -15
+KPX J period -24
+KPX J comma -24
+KPX J a -15
+KPX J A -15
+
+KPX K y -40
+KPX K u -24
+KPX K o -32
+KPX K e -32
+KPX K O -40
+
+KPX L y -24
+KPX L quoteright -130
+KPX L quotedblright -114
+KPX L Y -114
+KPX L W -56
+KPX L V -89
+KPX L T -89
+
+KPX O period -32
+KPX O comma -32
+KPX O Y -56
+KPX O X -48
+KPX O W -24
+KPX O V -40
+KPX O T -32
+KPX O A -15
+
+KPX P period -147
+KPX P o -40
+KPX P e -40
+KPX P comma -147
+KPX P a -32
+KPX P A -97
+
+KPX Q U -7
+
+KPX R Y -40
+KPX R W -24
+KPX R V -40
+KPX R U -32
+KPX R T -24
+KPX R O -15
+
+KPX S period -15
+KPX S comma -15
+
+KPX T y -97
+KPX T w -97
+KPX T u -97
+KPX T semicolon -15
+KPX T r -97
+KPX T period -97
+KPX T o -97
+KPX T hyphen -114
+KPX T e -97
+KPX T comma -97
+KPX T colon -15
+KPX T a -97
+KPX T O -32
+KPX T A -97
+
+KPX U period -32
+KPX U comma -32
+KPX U A -32
+
+KPX V u -56
+KPX V semicolon -32
+KPX V period -102
+KPX V o -65
+KPX V hyphen -65
+KPX V e -65
+KPX V comma -102
+KPX V colon -32
+KPX V a -56
+KPX V O -32
+KPX V G -32
+KPX V A -65
+
+KPX W y -15
+KPX W u -24
+KPX W period -65
+KPX W o -24
+KPX W hyphen -32
+KPX W e -24
+KPX W comma -65
+KPX W a -32
+KPX W O -15
+KPX W A -40
+
+KPX Y u -89
+KPX Y semicolon -48
+KPX Y period -114
+KPX Y o -114
+KPX Y i -15
+KPX Y hyphen -114
+KPX Y e -114
+KPX Y comma -114
+KPX Y colon -48
+KPX Y a -114
+KPX Y O -69
+KPX Y A -89
+
+KPX a y -24
+KPX a w -15
+KPX a v -15
+
+KPX b y -15
+KPX b v -15
+KPX b u -15
+KPX b period -32
+KPX b l -15
+KPX b comma -32
+KPX b b -7
+
+KPX c k -15
+KPX c comma -11
+
+KPX colon space -40
+
+KPX comma quoteright -81
+KPX comma quotedblright -81
+
+KPX e y -15
+KPX e x -24
+KPX e w -15
+KPX e v -24
+KPX e period -11
+KPX e comma -11
+
+KPX f quoteright 41
+KPX f quotedblright 49
+KPX f period -24
+KPX f o -24
+KPX f e -24
+KPX f dotlessi -22
+KPX f comma -24
+KPX f a -24
+
+KPX g r -7
+
+KPX h y -24
+
+KPX k o -15
+KPX k e -15
+
+KPX m y -11
+KPX m u -7
+
+KPX n y -11
+KPX n v -15
+KPX n u -7
+
+KPX o y -24
+KPX o x -24
+KPX o w -11
+KPX o v -11
+KPX o period -32
+KPX o comma -32
+
+KPX oslash z -44
+KPX oslash y -56
+KPX oslash x -69
+KPX oslash w -56
+KPX oslash v -56
+KPX oslash u -44
+KPX oslash t -44
+KPX oslash s -44
+KPX oslash r -44
+KPX oslash q -44
+KPX oslash period -77
+KPX oslash p -44
+KPX oslash o -44
+KPX oslash n -44
+KPX oslash m -44
+KPX oslash l -44
+KPX oslash k -44
+KPX oslash j -44
+KPX oslash i -44
+KPX oslash h -44
+KPX oslash g -44
+KPX oslash f -44
+KPX oslash e -44
+KPX oslash d -44
+KPX oslash comma -77
+KPX oslash c -44
+KPX oslash b -44
+KPX oslash a -44
+
+KPX p y -24
+KPX p period -28
+KPX p comma -28
+
+KPX period space -48
+KPX period quoteright -81
+KPX period quotedblright -81
+
+KPX quotedblright space -32
+
+KPX quoteleft quoteleft -46
+
+KPX quoteright space -56
+KPX quoteright s -40
+KPX quoteright r -40
+KPX quoteright quoteright -46
+KPX quoteright d -40
+
+KPX r y 25
+KPX r v 25
+KPX r u 12
+KPX r t 33
+KPX r semicolon 25
+KPX r period -40
+KPX r p 25
+KPX r n 21
+KPX r m 21
+KPX r l 12
+KPX r k 12
+KPX r i 12
+KPX r comma -40
+KPX r colon 25
+KPX r a -7
+
+KPX s w -24
+KPX s period -11
+KPX s comma -11
+
+KPX semicolon space -40
+
+KPX space quoteleft -48
+KPX space quotedblleft -24
+KPX space Y -73
+KPX space W -32
+KPX space V -40
+KPX space T -40
+
+KPX v period -65
+KPX v o -20
+KPX v e -20
+KPX v comma -65
+KPX v a -20
+
+KPX w period -48
+KPX w o -7
+KPX w e -7
+KPX w comma -48
+KPX w a -11
+
+KPX x e -24
+
+KPX y period -81
+KPX y o -15
+KPX y e -15
+KPX y comma -81
+KPX y a -15
+
+KPX z o -11
+KPX z e -11
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 137 195 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 137 195 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 137 195 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 137 195 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 137 175 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 137 195 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 160 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 137 195 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 137 195 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 137 195 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 137 195 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute -22 195 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -22 195 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -22 195 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave -22 195 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 168 195 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 183 195 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 183 195 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 183 195 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 183 195 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 183 195 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 137 195 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 160 195 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 160 195 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 160 195 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 160 195 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 137 195 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 137 195 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 114 195 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 92 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 92 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 92 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 92 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 92 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 84 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 69 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 92 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 92 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 92 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 92 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -22 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -22 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -22 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -22 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 84 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 92 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 92 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 92 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 92 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 92 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 69 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 92 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 92 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 92 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 92 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 69 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 69 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 69 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvro8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvro8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..3d69eb7c8e03fa4ceb2e8d6e89dc6f0ecd24ab0e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvro8a.afm
@@ -0,0 +1,612 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Thu Mar 15 10:24:18 1990
+Comment UniqueID 28362
+Comment VMusage 7572 42473
+FontName Helvetica-Oblique
+FullName Helvetica Oblique
+FamilyName Helvetica
+Weight Medium
+ItalicAngle -12
+IsFixedPitch false
+FontBBox -170 -225 1116 931
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.006
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.Helvetica is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 523
+Ascender 718
+Descender -207
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 90 0 340 718 ;
+C 34 ; WX 355 ; N quotedbl ; B 168 463 438 718 ;
+C 35 ; WX 556 ; N numbersign ; B 73 0 631 688 ;
+C 36 ; WX 556 ; N dollar ; B 69 -115 617 775 ;
+C 37 ; WX 889 ; N percent ; B 147 -19 889 703 ;
+C 38 ; WX 667 ; N ampersand ; B 77 -15 647 718 ;
+C 39 ; WX 222 ; N quoteright ; B 151 463 310 718 ;
+C 40 ; WX 333 ; N parenleft ; B 108 -207 454 733 ;
+C 41 ; WX 333 ; N parenright ; B -9 -207 337 733 ;
+C 42 ; WX 389 ; N asterisk ; B 165 431 475 718 ;
+C 43 ; WX 584 ; N plus ; B 85 0 606 505 ;
+C 44 ; WX 278 ; N comma ; B 56 -147 214 106 ;
+C 45 ; WX 333 ; N hyphen ; B 93 232 357 322 ;
+C 46 ; WX 278 ; N period ; B 87 0 214 106 ;
+C 47 ; WX 278 ; N slash ; B -21 -19 452 737 ;
+C 48 ; WX 556 ; N zero ; B 93 -19 608 703 ;
+C 49 ; WX 556 ; N one ; B 207 0 508 703 ;
+C 50 ; WX 556 ; N two ; B 26 0 617 703 ;
+C 51 ; WX 556 ; N three ; B 75 -19 610 703 ;
+C 52 ; WX 556 ; N four ; B 61 0 576 703 ;
+C 53 ; WX 556 ; N five ; B 68 -19 621 688 ;
+C 54 ; WX 556 ; N six ; B 91 -19 615 703 ;
+C 55 ; WX 556 ; N seven ; B 137 0 669 688 ;
+C 56 ; WX 556 ; N eight ; B 74 -19 607 703 ;
+C 57 ; WX 556 ; N nine ; B 82 -19 609 703 ;
+C 58 ; WX 278 ; N colon ; B 87 0 301 516 ;
+C 59 ; WX 278 ; N semicolon ; B 56 -147 301 516 ;
+C 60 ; WX 584 ; N less ; B 94 11 641 495 ;
+C 61 ; WX 584 ; N equal ; B 63 115 628 390 ;
+C 62 ; WX 584 ; N greater ; B 50 11 597 495 ;
+C 63 ; WX 556 ; N question ; B 161 0 610 727 ;
+C 64 ; WX 1015 ; N at ; B 215 -19 965 737 ;
+C 65 ; WX 667 ; N A ; B 14 0 654 718 ;
+C 66 ; WX 667 ; N B ; B 74 0 712 718 ;
+C 67 ; WX 722 ; N C ; B 108 -19 782 737 ;
+C 68 ; WX 722 ; N D ; B 81 0 764 718 ;
+C 69 ; WX 667 ; N E ; B 86 0 762 718 ;
+C 70 ; WX 611 ; N F ; B 86 0 736 718 ;
+C 71 ; WX 778 ; N G ; B 111 -19 799 737 ;
+C 72 ; WX 722 ; N H ; B 77 0 799 718 ;
+C 73 ; WX 278 ; N I ; B 91 0 341 718 ;
+C 74 ; WX 500 ; N J ; B 47 -19 581 718 ;
+C 75 ; WX 667 ; N K ; B 76 0 808 718 ;
+C 76 ; WX 556 ; N L ; B 76 0 555 718 ;
+C 77 ; WX 833 ; N M ; B 73 0 914 718 ;
+C 78 ; WX 722 ; N N ; B 76 0 799 718 ;
+C 79 ; WX 778 ; N O ; B 105 -19 826 737 ;
+C 80 ; WX 667 ; N P ; B 86 0 737 718 ;
+C 81 ; WX 778 ; N Q ; B 105 -56 826 737 ;
+C 82 ; WX 722 ; N R ; B 88 0 773 718 ;
+C 83 ; WX 667 ; N S ; B 90 -19 713 737 ;
+C 84 ; WX 611 ; N T ; B 148 0 750 718 ;
+C 85 ; WX 722 ; N U ; B 123 -19 797 718 ;
+C 86 ; WX 667 ; N V ; B 173 0 800 718 ;
+C 87 ; WX 944 ; N W ; B 169 0 1081 718 ;
+C 88 ; WX 667 ; N X ; B 19 0 790 718 ;
+C 89 ; WX 667 ; N Y ; B 167 0 806 718 ;
+C 90 ; WX 611 ; N Z ; B 23 0 741 718 ;
+C 91 ; WX 278 ; N bracketleft ; B 21 -196 403 722 ;
+C 92 ; WX 278 ; N backslash ; B 140 -19 291 737 ;
+C 93 ; WX 278 ; N bracketright ; B -14 -196 368 722 ;
+C 94 ; WX 469 ; N asciicircum ; B 42 264 539 688 ;
+C 95 ; WX 556 ; N underscore ; B -27 -125 540 -75 ;
+C 96 ; WX 222 ; N quoteleft ; B 165 470 323 725 ;
+C 97 ; WX 556 ; N a ; B 61 -15 559 538 ;
+C 98 ; WX 556 ; N b ; B 58 -15 584 718 ;
+C 99 ; WX 500 ; N c ; B 74 -15 553 538 ;
+C 100 ; WX 556 ; N d ; B 84 -15 652 718 ;
+C 101 ; WX 556 ; N e ; B 84 -15 578 538 ;
+C 102 ; WX 278 ; N f ; B 86 0 416 728 ; L i fi ; L l fl ;
+C 103 ; WX 556 ; N g ; B 42 -220 610 538 ;
+C 104 ; WX 556 ; N h ; B 65 0 573 718 ;
+C 105 ; WX 222 ; N i ; B 67 0 308 718 ;
+C 106 ; WX 222 ; N j ; B -60 -210 308 718 ;
+C 107 ; WX 500 ; N k ; B 67 0 600 718 ;
+C 108 ; WX 222 ; N l ; B 67 0 308 718 ;
+C 109 ; WX 833 ; N m ; B 65 0 852 538 ;
+C 110 ; WX 556 ; N n ; B 65 0 573 538 ;
+C 111 ; WX 556 ; N o ; B 83 -14 585 538 ;
+C 112 ; WX 556 ; N p ; B 14 -207 584 538 ;
+C 113 ; WX 556 ; N q ; B 84 -207 605 538 ;
+C 114 ; WX 333 ; N r ; B 77 0 446 538 ;
+C 115 ; WX 500 ; N s ; B 63 -15 529 538 ;
+C 116 ; WX 278 ; N t ; B 102 -7 368 669 ;
+C 117 ; WX 556 ; N u ; B 94 -15 600 523 ;
+C 118 ; WX 500 ; N v ; B 119 0 603 523 ;
+C 119 ; WX 722 ; N w ; B 125 0 820 523 ;
+C 120 ; WX 500 ; N x ; B 11 0 594 523 ;
+C 121 ; WX 500 ; N y ; B 15 -214 600 523 ;
+C 122 ; WX 500 ; N z ; B 31 0 571 523 ;
+C 123 ; WX 334 ; N braceleft ; B 92 -196 445 722 ;
+C 124 ; WX 260 ; N bar ; B 90 -19 324 737 ;
+C 125 ; WX 334 ; N braceright ; B 0 -196 354 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 111 180 580 326 ;
+C 161 ; WX 333 ; N exclamdown ; B 77 -195 326 523 ;
+C 162 ; WX 556 ; N cent ; B 95 -115 584 623 ;
+C 163 ; WX 556 ; N sterling ; B 49 -16 634 718 ;
+C 164 ; WX 167 ; N fraction ; B -170 -19 482 703 ;
+C 165 ; WX 556 ; N yen ; B 81 0 699 688 ;
+C 166 ; WX 556 ; N florin ; B -52 -207 654 737 ;
+C 167 ; WX 556 ; N section ; B 76 -191 584 737 ;
+C 168 ; WX 556 ; N currency ; B 60 99 646 603 ;
+C 169 ; WX 191 ; N quotesingle ; B 157 463 285 718 ;
+C 170 ; WX 333 ; N quotedblleft ; B 138 470 461 725 ;
+C 171 ; WX 556 ; N guillemotleft ; B 146 108 554 446 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 137 108 340 446 ;
+C 173 ; WX 333 ; N guilsinglright ; B 111 108 314 446 ;
+C 174 ; WX 500 ; N fi ; B 86 0 587 728 ;
+C 175 ; WX 500 ; N fl ; B 86 0 585 728 ;
+C 177 ; WX 556 ; N endash ; B 51 240 623 313 ;
+C 178 ; WX 556 ; N dagger ; B 135 -159 622 718 ;
+C 179 ; WX 556 ; N daggerdbl ; B 52 -159 623 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 129 190 257 315 ;
+C 182 ; WX 537 ; N paragraph ; B 126 -173 650 718 ;
+C 183 ; WX 350 ; N bullet ; B 91 202 413 517 ;
+C 184 ; WX 222 ; N quotesinglbase ; B 21 -149 180 106 ;
+C 185 ; WX 333 ; N quotedblbase ; B -6 -149 318 106 ;
+C 186 ; WX 333 ; N quotedblright ; B 124 463 448 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 120 108 528 446 ;
+C 188 ; WX 1000 ; N ellipsis ; B 115 0 908 106 ;
+C 189 ; WX 1000 ; N perthousand ; B 88 -19 1029 703 ;
+C 191 ; WX 611 ; N questiondown ; B 85 -201 534 525 ;
+C 193 ; WX 333 ; N grave ; B 170 593 337 734 ;
+C 194 ; WX 333 ; N acute ; B 248 593 475 734 ;
+C 195 ; WX 333 ; N circumflex ; B 147 593 438 734 ;
+C 196 ; WX 333 ; N tilde ; B 125 606 490 722 ;
+C 197 ; WX 333 ; N macron ; B 143 627 468 684 ;
+C 198 ; WX 333 ; N breve ; B 167 595 476 731 ;
+C 199 ; WX 333 ; N dotaccent ; B 249 604 362 706 ;
+C 200 ; WX 333 ; N dieresis ; B 168 604 443 706 ;
+C 202 ; WX 333 ; N ring ; B 214 572 402 756 ;
+C 203 ; WX 333 ; N cedilla ; B 2 -225 232 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 157 593 565 734 ;
+C 206 ; WX 333 ; N ogonek ; B 43 -225 249 0 ;
+C 207 ; WX 333 ; N caron ; B 177 593 468 734 ;
+C 208 ; WX 1000 ; N emdash ; B 51 240 1067 313 ;
+C 225 ; WX 1000 ; N AE ; B 8 0 1097 718 ;
+C 227 ; WX 370 ; N ordfeminine ; B 100 304 449 737 ;
+C 232 ; WX 556 ; N Lslash ; B 41 0 555 718 ;
+C 233 ; WX 778 ; N Oslash ; B 43 -19 890 737 ;
+C 234 ; WX 1000 ; N OE ; B 98 -19 1116 737 ;
+C 235 ; WX 365 ; N ordmasculine ; B 100 304 468 737 ;
+C 241 ; WX 889 ; N ae ; B 61 -15 909 538 ;
+C 245 ; WX 278 ; N dotlessi ; B 95 0 294 523 ;
+C 248 ; WX 222 ; N lslash ; B 41 0 347 718 ;
+C 249 ; WX 611 ; N oslash ; B 29 -22 647 545 ;
+C 250 ; WX 944 ; N oe ; B 83 -15 964 538 ;
+C 251 ; WX 611 ; N germandbls ; B 67 -15 658 728 ;
+C -1 ; WX 611 ; N Zcaron ; B 23 0 741 929 ;
+C -1 ; WX 500 ; N ccedilla ; B 74 -225 553 538 ;
+C -1 ; WX 500 ; N ydieresis ; B 15 -214 600 706 ;
+C -1 ; WX 556 ; N atilde ; B 61 -15 592 722 ;
+C -1 ; WX 278 ; N icircumflex ; B 95 0 411 734 ;
+C -1 ; WX 333 ; N threesuperior ; B 90 270 436 703 ;
+C -1 ; WX 556 ; N ecircumflex ; B 84 -15 578 734 ;
+C -1 ; WX 556 ; N thorn ; B 14 -207 584 718 ;
+C -1 ; WX 556 ; N egrave ; B 84 -15 578 734 ;
+C -1 ; WX 333 ; N twosuperior ; B 64 281 449 703 ;
+C -1 ; WX 556 ; N eacute ; B 84 -15 587 734 ;
+C -1 ; WX 556 ; N otilde ; B 83 -14 602 722 ;
+C -1 ; WX 667 ; N Aacute ; B 14 0 683 929 ;
+C -1 ; WX 556 ; N ocircumflex ; B 83 -14 585 734 ;
+C -1 ; WX 500 ; N yacute ; B 15 -214 600 734 ;
+C -1 ; WX 556 ; N udieresis ; B 94 -15 600 706 ;
+C -1 ; WX 834 ; N threequarters ; B 130 -19 861 703 ;
+C -1 ; WX 556 ; N acircumflex ; B 61 -15 559 734 ;
+C -1 ; WX 722 ; N Eth ; B 69 0 764 718 ;
+C -1 ; WX 556 ; N edieresis ; B 84 -15 578 706 ;
+C -1 ; WX 556 ; N ugrave ; B 94 -15 600 734 ;
+C -1 ; WX 1000 ; N trademark ; B 186 306 1056 718 ;
+C -1 ; WX 556 ; N ograve ; B 83 -14 585 734 ;
+C -1 ; WX 500 ; N scaron ; B 63 -15 552 734 ;
+C -1 ; WX 278 ; N Idieresis ; B 91 0 458 901 ;
+C -1 ; WX 556 ; N uacute ; B 94 -15 600 734 ;
+C -1 ; WX 556 ; N agrave ; B 61 -15 559 734 ;
+C -1 ; WX 556 ; N ntilde ; B 65 0 592 722 ;
+C -1 ; WX 556 ; N aring ; B 61 -15 559 756 ;
+C -1 ; WX 500 ; N zcaron ; B 31 0 571 734 ;
+C -1 ; WX 278 ; N Icircumflex ; B 91 0 452 929 ;
+C -1 ; WX 722 ; N Ntilde ; B 76 0 799 917 ;
+C -1 ; WX 556 ; N ucircumflex ; B 94 -15 600 734 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 86 0 762 929 ;
+C -1 ; WX 278 ; N Iacute ; B 91 0 489 929 ;
+C -1 ; WX 722 ; N Ccedilla ; B 108 -225 782 737 ;
+C -1 ; WX 778 ; N Odieresis ; B 105 -19 826 901 ;
+C -1 ; WX 667 ; N Scaron ; B 90 -19 713 929 ;
+C -1 ; WX 667 ; N Edieresis ; B 86 0 762 901 ;
+C -1 ; WX 278 ; N Igrave ; B 91 0 351 929 ;
+C -1 ; WX 556 ; N adieresis ; B 61 -15 559 706 ;
+C -1 ; WX 778 ; N Ograve ; B 105 -19 826 929 ;
+C -1 ; WX 667 ; N Egrave ; B 86 0 762 929 ;
+C -1 ; WX 667 ; N Ydieresis ; B 167 0 806 901 ;
+C -1 ; WX 737 ; N registered ; B 54 -19 837 737 ;
+C -1 ; WX 778 ; N Otilde ; B 105 -19 826 917 ;
+C -1 ; WX 834 ; N onequarter ; B 150 -19 802 703 ;
+C -1 ; WX 722 ; N Ugrave ; B 123 -19 797 929 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 123 -19 797 929 ;
+C -1 ; WX 667 ; N Thorn ; B 86 0 712 718 ;
+C -1 ; WX 584 ; N divide ; B 85 -19 606 524 ;
+C -1 ; WX 667 ; N Atilde ; B 14 0 699 917 ;
+C -1 ; WX 722 ; N Uacute ; B 123 -19 797 929 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 105 -19 826 929 ;
+C -1 ; WX 584 ; N logicalnot ; B 106 108 628 390 ;
+C -1 ; WX 667 ; N Aring ; B 14 0 654 931 ;
+C -1 ; WX 278 ; N idieresis ; B 95 0 416 706 ;
+C -1 ; WX 278 ; N iacute ; B 95 0 448 734 ;
+C -1 ; WX 556 ; N aacute ; B 61 -15 587 734 ;
+C -1 ; WX 584 ; N plusminus ; B 39 0 618 506 ;
+C -1 ; WX 584 ; N multiply ; B 50 0 642 506 ;
+C -1 ; WX 722 ; N Udieresis ; B 123 -19 797 901 ;
+C -1 ; WX 584 ; N minus ; B 85 216 606 289 ;
+C -1 ; WX 333 ; N onesuperior ; B 166 281 371 703 ;
+C -1 ; WX 667 ; N Eacute ; B 86 0 762 929 ;
+C -1 ; WX 667 ; N Acircumflex ; B 14 0 654 929 ;
+C -1 ; WX 737 ; N copyright ; B 54 -19 837 737 ;
+C -1 ; WX 667 ; N Agrave ; B 14 0 654 929 ;
+C -1 ; WX 556 ; N odieresis ; B 83 -14 585 706 ;
+C -1 ; WX 556 ; N oacute ; B 83 -14 587 734 ;
+C -1 ; WX 400 ; N degree ; B 169 411 468 703 ;
+C -1 ; WX 278 ; N igrave ; B 95 0 310 734 ;
+C -1 ; WX 556 ; N mu ; B 24 -207 600 523 ;
+C -1 ; WX 778 ; N Oacute ; B 105 -19 826 929 ;
+C -1 ; WX 556 ; N eth ; B 81 -15 617 737 ;
+C -1 ; WX 667 ; N Adieresis ; B 14 0 654 901 ;
+C -1 ; WX 667 ; N Yacute ; B 167 0 806 929 ;
+C -1 ; WX 260 ; N brokenbar ; B 90 -19 324 737 ;
+C -1 ; WX 834 ; N onehalf ; B 114 -19 839 703 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 250
+
+KPX A y -40
+KPX A w -40
+KPX A v -40
+KPX A u -30
+KPX A Y -100
+KPX A W -50
+KPX A V -70
+KPX A U -50
+KPX A T -120
+KPX A Q -30
+KPX A O -30
+KPX A G -30
+KPX A C -30
+
+KPX B period -20
+KPX B comma -20
+KPX B U -10
+
+KPX C period -30
+KPX C comma -30
+
+KPX D period -70
+KPX D comma -70
+KPX D Y -90
+KPX D W -40
+KPX D V -70
+KPX D A -40
+
+KPX F r -45
+KPX F period -150
+KPX F o -30
+KPX F e -30
+KPX F comma -150
+KPX F a -50
+KPX F A -80
+
+KPX J u -20
+KPX J period -30
+KPX J comma -30
+KPX J a -20
+KPX J A -20
+
+KPX K y -50
+KPX K u -30
+KPX K o -40
+KPX K e -40
+KPX K O -50
+
+KPX L y -30
+KPX L quoteright -160
+KPX L quotedblright -140
+KPX L Y -140
+KPX L W -70
+KPX L V -110
+KPX L T -110
+
+KPX O period -40
+KPX O comma -40
+KPX O Y -70
+KPX O X -60
+KPX O W -30
+KPX O V -50
+KPX O T -40
+KPX O A -20
+
+KPX P period -180
+KPX P o -50
+KPX P e -50
+KPX P comma -180
+KPX P a -40
+KPX P A -120
+
+KPX Q U -10
+
+KPX R Y -50
+KPX R W -30
+KPX R V -50
+KPX R U -40
+KPX R T -30
+KPX R O -20
+
+KPX S period -20
+KPX S comma -20
+
+KPX T y -120
+KPX T w -120
+KPX T u -120
+KPX T semicolon -20
+KPX T r -120
+KPX T period -120
+KPX T o -120
+KPX T hyphen -140
+KPX T e -120
+KPX T comma -120
+KPX T colon -20
+KPX T a -120
+KPX T O -40
+KPX T A -120
+
+KPX U period -40
+KPX U comma -40
+KPX U A -40
+
+KPX V u -70
+KPX V semicolon -40
+KPX V period -125
+KPX V o -80
+KPX V hyphen -80
+KPX V e -80
+KPX V comma -125
+KPX V colon -40
+KPX V a -70
+KPX V O -40
+KPX V G -40
+KPX V A -80
+
+KPX W y -20
+KPX W u -30
+KPX W period -80
+KPX W o -30
+KPX W hyphen -40
+KPX W e -30
+KPX W comma -80
+KPX W a -40
+KPX W O -20
+KPX W A -50
+
+KPX Y u -110
+KPX Y semicolon -60
+KPX Y period -140
+KPX Y o -140
+KPX Y i -20
+KPX Y hyphen -140
+KPX Y e -140
+KPX Y comma -140
+KPX Y colon -60
+KPX Y a -140
+KPX Y O -85
+KPX Y A -110
+
+KPX a y -30
+KPX a w -20
+KPX a v -20
+
+KPX b y -20
+KPX b v -20
+KPX b u -20
+KPX b period -40
+KPX b l -20
+KPX b comma -40
+KPX b b -10
+
+KPX c k -20
+KPX c comma -15
+
+KPX colon space -50
+
+KPX comma quoteright -100
+KPX comma quotedblright -100
+
+KPX e y -20
+KPX e x -30
+KPX e w -20
+KPX e v -30
+KPX e period -15
+KPX e comma -15
+
+KPX f quoteright 50
+KPX f quotedblright 60
+KPX f period -30
+KPX f o -30
+KPX f e -30
+KPX f dotlessi -28
+KPX f comma -30
+KPX f a -30
+
+KPX g r -10
+
+KPX h y -30
+
+KPX k o -20
+KPX k e -20
+
+KPX m y -15
+KPX m u -10
+
+KPX n y -15
+KPX n v -20
+KPX n u -10
+
+KPX o y -30
+KPX o x -30
+KPX o w -15
+KPX o v -15
+KPX o period -40
+KPX o comma -40
+
+KPX oslash z -55
+KPX oslash y -70
+KPX oslash x -85
+KPX oslash w -70
+KPX oslash v -70
+KPX oslash u -55
+KPX oslash t -55
+KPX oslash s -55
+KPX oslash r -55
+KPX oslash q -55
+KPX oslash period -95
+KPX oslash p -55
+KPX oslash o -55
+KPX oslash n -55
+KPX oslash m -55
+KPX oslash l -55
+KPX oslash k -55
+KPX oslash j -55
+KPX oslash i -55
+KPX oslash h -55
+KPX oslash g -55
+KPX oslash f -55
+KPX oslash e -55
+KPX oslash d -55
+KPX oslash comma -95
+KPX oslash c -55
+KPX oslash b -55
+KPX oslash a -55
+
+KPX p y -30
+KPX p period -35
+KPX p comma -35
+
+KPX period space -60
+KPX period quoteright -100
+KPX period quotedblright -100
+
+KPX quotedblright space -40
+
+KPX quoteleft quoteleft -57
+
+KPX quoteright space -70
+KPX quoteright s -50
+KPX quoteright r -50
+KPX quoteright quoteright -57
+KPX quoteright d -50
+
+KPX r y 30
+KPX r v 30
+KPX r u 15
+KPX r t 40
+KPX r semicolon 30
+KPX r period -50
+KPX r p 30
+KPX r n 25
+KPX r m 25
+KPX r l 15
+KPX r k 15
+KPX r i 15
+KPX r comma -50
+KPX r colon 30
+KPX r a -10
+
+KPX s w -30
+KPX s period -15
+KPX s comma -15
+
+KPX semicolon space -50
+
+KPX space quoteleft -60
+KPX space quotedblleft -30
+KPX space Y -90
+KPX space W -40
+KPX space V -50
+KPX space T -50
+
+KPX v period -80
+KPX v o -25
+KPX v e -25
+KPX v comma -80
+KPX v a -25
+
+KPX w period -60
+KPX w o -10
+KPX w e -10
+KPX w comma -60
+KPX w a -15
+
+KPX x e -30
+
+KPX y period -100
+KPX y o -20
+KPX y e -20
+KPX y comma -100
+KPX y a -20
+
+KPX z o -15
+KPX z e -15
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 208 195 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 208 195 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 208 195 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 208 195 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 204 175 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 208 195 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 195 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 208 195 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 208 195 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 208 195 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 208 195 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 14 195 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 14 195 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 14 195 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 14 195 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 246 195 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 264 195 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 264 195 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 264 195 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 264 195 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 264 195 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 208 195 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 236 195 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 236 195 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 236 195 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 236 195 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 208 195 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 208 195 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 180 195 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 112 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 112 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 112 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 112 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 112 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 102 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 84 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 112 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 112 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 112 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 112 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -27 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -27 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -27 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -27 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 102 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 112 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 112 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 112 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 112 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 112 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 84 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 112 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 112 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 112 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 112 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 84 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 84 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 84 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvro8an.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvro8an.afm
new file mode 100644
index 0000000000000000000000000000000000000000..f757319a4c3dbf9e6d4f67cbf339b477aef6c1f3
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/phvro8an.afm
@@ -0,0 +1,612 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Thu Mar 15 11:25:48 1990
+Comment UniqueID 28389
+Comment VMusage 7572 42473
+FontName Helvetica-Narrow-Oblique
+FullName Helvetica Narrow Oblique
+FamilyName Helvetica
+Weight Medium
+ItalicAngle -12
+IsFixedPitch false
+FontBBox -139 -225 915 931
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.006
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.Helvetica is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 523
+Ascender 718
+Descender -207
+StartCharMetrics 228
+C 32 ; WX 228 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 228 ; N exclam ; B 74 0 278 718 ;
+C 34 ; WX 291 ; N quotedbl ; B 138 463 359 718 ;
+C 35 ; WX 456 ; N numbersign ; B 60 0 517 688 ;
+C 36 ; WX 456 ; N dollar ; B 57 -115 506 775 ;
+C 37 ; WX 729 ; N percent ; B 120 -19 729 703 ;
+C 38 ; WX 547 ; N ampersand ; B 63 -15 530 718 ;
+C 39 ; WX 182 ; N quoteright ; B 124 463 254 718 ;
+C 40 ; WX 273 ; N parenleft ; B 89 -207 372 733 ;
+C 41 ; WX 273 ; N parenright ; B -7 -207 276 733 ;
+C 42 ; WX 319 ; N asterisk ; B 135 431 389 718 ;
+C 43 ; WX 479 ; N plus ; B 70 0 497 505 ;
+C 44 ; WX 228 ; N comma ; B 46 -147 175 106 ;
+C 45 ; WX 273 ; N hyphen ; B 77 232 293 322 ;
+C 46 ; WX 228 ; N period ; B 71 0 175 106 ;
+C 47 ; WX 228 ; N slash ; B -17 -19 370 737 ;
+C 48 ; WX 456 ; N zero ; B 77 -19 499 703 ;
+C 49 ; WX 456 ; N one ; B 170 0 417 703 ;
+C 50 ; WX 456 ; N two ; B 21 0 506 703 ;
+C 51 ; WX 456 ; N three ; B 61 -19 500 703 ;
+C 52 ; WX 456 ; N four ; B 50 0 472 703 ;
+C 53 ; WX 456 ; N five ; B 55 -19 509 688 ;
+C 54 ; WX 456 ; N six ; B 74 -19 504 703 ;
+C 55 ; WX 456 ; N seven ; B 112 0 549 688 ;
+C 56 ; WX 456 ; N eight ; B 60 -19 497 703 ;
+C 57 ; WX 456 ; N nine ; B 67 -19 499 703 ;
+C 58 ; WX 228 ; N colon ; B 71 0 247 516 ;
+C 59 ; WX 228 ; N semicolon ; B 46 -147 247 516 ;
+C 60 ; WX 479 ; N less ; B 77 11 526 495 ;
+C 61 ; WX 479 ; N equal ; B 52 115 515 390 ;
+C 62 ; WX 479 ; N greater ; B 41 11 490 495 ;
+C 63 ; WX 456 ; N question ; B 132 0 500 727 ;
+C 64 ; WX 832 ; N at ; B 176 -19 791 737 ;
+C 65 ; WX 547 ; N A ; B 11 0 536 718 ;
+C 66 ; WX 547 ; N B ; B 61 0 583 718 ;
+C 67 ; WX 592 ; N C ; B 88 -19 640 737 ;
+C 68 ; WX 592 ; N D ; B 66 0 626 718 ;
+C 69 ; WX 547 ; N E ; B 71 0 625 718 ;
+C 70 ; WX 501 ; N F ; B 71 0 603 718 ;
+C 71 ; WX 638 ; N G ; B 91 -19 655 737 ;
+C 72 ; WX 592 ; N H ; B 63 0 655 718 ;
+C 73 ; WX 228 ; N I ; B 75 0 279 718 ;
+C 74 ; WX 410 ; N J ; B 39 -19 476 718 ;
+C 75 ; WX 547 ; N K ; B 62 0 662 718 ;
+C 76 ; WX 456 ; N L ; B 62 0 455 718 ;
+C 77 ; WX 683 ; N M ; B 60 0 749 718 ;
+C 78 ; WX 592 ; N N ; B 62 0 655 718 ;
+C 79 ; WX 638 ; N O ; B 86 -19 677 737 ;
+C 80 ; WX 547 ; N P ; B 71 0 604 718 ;
+C 81 ; WX 638 ; N Q ; B 86 -56 677 737 ;
+C 82 ; WX 592 ; N R ; B 72 0 634 718 ;
+C 83 ; WX 547 ; N S ; B 74 -19 584 737 ;
+C 84 ; WX 501 ; N T ; B 122 0 615 718 ;
+C 85 ; WX 592 ; N U ; B 101 -19 653 718 ;
+C 86 ; WX 547 ; N V ; B 142 0 656 718 ;
+C 87 ; WX 774 ; N W ; B 138 0 886 718 ;
+C 88 ; WX 547 ; N X ; B 16 0 647 718 ;
+C 89 ; WX 547 ; N Y ; B 137 0 661 718 ;
+C 90 ; WX 501 ; N Z ; B 19 0 607 718 ;
+C 91 ; WX 228 ; N bracketleft ; B 17 -196 331 722 ;
+C 92 ; WX 228 ; N backslash ; B 115 -19 239 737 ;
+C 93 ; WX 228 ; N bracketright ; B -11 -196 302 722 ;
+C 94 ; WX 385 ; N asciicircum ; B 35 264 442 688 ;
+C 95 ; WX 456 ; N underscore ; B -22 -125 443 -75 ;
+C 96 ; WX 182 ; N quoteleft ; B 135 470 265 725 ;
+C 97 ; WX 456 ; N a ; B 50 -15 458 538 ;
+C 98 ; WX 456 ; N b ; B 48 -15 479 718 ;
+C 99 ; WX 410 ; N c ; B 61 -15 454 538 ;
+C 100 ; WX 456 ; N d ; B 69 -15 534 718 ;
+C 101 ; WX 456 ; N e ; B 69 -15 474 538 ;
+C 102 ; WX 228 ; N f ; B 71 0 341 728 ; L i fi ; L l fl ;
+C 103 ; WX 456 ; N g ; B 34 -220 500 538 ;
+C 104 ; WX 456 ; N h ; B 53 0 470 718 ;
+C 105 ; WX 182 ; N i ; B 55 0 252 718 ;
+C 106 ; WX 182 ; N j ; B -49 -210 252 718 ;
+C 107 ; WX 410 ; N k ; B 55 0 492 718 ;
+C 108 ; WX 182 ; N l ; B 55 0 252 718 ;
+C 109 ; WX 683 ; N m ; B 53 0 699 538 ;
+C 110 ; WX 456 ; N n ; B 53 0 470 538 ;
+C 111 ; WX 456 ; N o ; B 68 -14 479 538 ;
+C 112 ; WX 456 ; N p ; B 11 -207 479 538 ;
+C 113 ; WX 456 ; N q ; B 69 -207 496 538 ;
+C 114 ; WX 273 ; N r ; B 63 0 365 538 ;
+C 115 ; WX 410 ; N s ; B 52 -15 434 538 ;
+C 116 ; WX 228 ; N t ; B 84 -7 302 669 ;
+C 117 ; WX 456 ; N u ; B 77 -15 492 523 ;
+C 118 ; WX 410 ; N v ; B 98 0 495 523 ;
+C 119 ; WX 592 ; N w ; B 103 0 673 523 ;
+C 120 ; WX 410 ; N x ; B 9 0 487 523 ;
+C 121 ; WX 410 ; N y ; B 12 -214 492 523 ;
+C 122 ; WX 410 ; N z ; B 25 0 468 523 ;
+C 123 ; WX 274 ; N braceleft ; B 75 -196 365 722 ;
+C 124 ; WX 213 ; N bar ; B 74 -19 265 737 ;
+C 125 ; WX 274 ; N braceright ; B 0 -196 291 722 ;
+C 126 ; WX 479 ; N asciitilde ; B 91 180 476 326 ;
+C 161 ; WX 273 ; N exclamdown ; B 63 -195 267 523 ;
+C 162 ; WX 456 ; N cent ; B 78 -115 479 623 ;
+C 163 ; WX 456 ; N sterling ; B 40 -16 520 718 ;
+C 164 ; WX 137 ; N fraction ; B -139 -19 396 703 ;
+C 165 ; WX 456 ; N yen ; B 67 0 573 688 ;
+C 166 ; WX 456 ; N florin ; B -43 -207 537 737 ;
+C 167 ; WX 456 ; N section ; B 63 -191 479 737 ;
+C 168 ; WX 456 ; N currency ; B 49 99 530 603 ;
+C 169 ; WX 157 ; N quotesingle ; B 129 463 233 718 ;
+C 170 ; WX 273 ; N quotedblleft ; B 113 470 378 725 ;
+C 171 ; WX 456 ; N guillemotleft ; B 120 108 454 446 ;
+C 172 ; WX 273 ; N guilsinglleft ; B 112 108 279 446 ;
+C 173 ; WX 273 ; N guilsinglright ; B 91 108 257 446 ;
+C 174 ; WX 410 ; N fi ; B 71 0 481 728 ;
+C 175 ; WX 410 ; N fl ; B 71 0 479 728 ;
+C 177 ; WX 456 ; N endash ; B 42 240 510 313 ;
+C 178 ; WX 456 ; N dagger ; B 110 -159 510 718 ;
+C 179 ; WX 456 ; N daggerdbl ; B 43 -159 511 718 ;
+C 180 ; WX 228 ; N periodcentered ; B 106 190 211 315 ;
+C 182 ; WX 440 ; N paragraph ; B 103 -173 533 718 ;
+C 183 ; WX 287 ; N bullet ; B 74 202 339 517 ;
+C 184 ; WX 182 ; N quotesinglbase ; B 17 -149 147 106 ;
+C 185 ; WX 273 ; N quotedblbase ; B -5 -149 260 106 ;
+C 186 ; WX 273 ; N quotedblright ; B 102 463 367 718 ;
+C 187 ; WX 456 ; N guillemotright ; B 98 108 433 446 ;
+C 188 ; WX 820 ; N ellipsis ; B 94 0 744 106 ;
+C 189 ; WX 820 ; N perthousand ; B 72 -19 844 703 ;
+C 191 ; WX 501 ; N questiondown ; B 70 -201 438 525 ;
+C 193 ; WX 273 ; N grave ; B 139 593 276 734 ;
+C 194 ; WX 273 ; N acute ; B 203 593 390 734 ;
+C 195 ; WX 273 ; N circumflex ; B 121 593 359 734 ;
+C 196 ; WX 273 ; N tilde ; B 102 606 402 722 ;
+C 197 ; WX 273 ; N macron ; B 117 627 384 684 ;
+C 198 ; WX 273 ; N breve ; B 137 595 391 731 ;
+C 199 ; WX 273 ; N dotaccent ; B 204 604 297 706 ;
+C 200 ; WX 273 ; N dieresis ; B 138 604 363 706 ;
+C 202 ; WX 273 ; N ring ; B 175 572 330 756 ;
+C 203 ; WX 273 ; N cedilla ; B 2 -225 191 0 ;
+C 205 ; WX 273 ; N hungarumlaut ; B 129 593 463 734 ;
+C 206 ; WX 273 ; N ogonek ; B 35 -225 204 0 ;
+C 207 ; WX 273 ; N caron ; B 145 593 384 734 ;
+C 208 ; WX 820 ; N emdash ; B 42 240 875 313 ;
+C 225 ; WX 820 ; N AE ; B 7 0 899 718 ;
+C 227 ; WX 303 ; N ordfeminine ; B 82 304 368 737 ;
+C 232 ; WX 456 ; N Lslash ; B 34 0 455 718 ;
+C 233 ; WX 638 ; N Oslash ; B 35 -19 730 737 ;
+C 234 ; WX 820 ; N OE ; B 80 -19 915 737 ;
+C 235 ; WX 299 ; N ordmasculine ; B 82 304 384 737 ;
+C 241 ; WX 729 ; N ae ; B 50 -15 746 538 ;
+C 245 ; WX 228 ; N dotlessi ; B 78 0 241 523 ;
+C 248 ; WX 182 ; N lslash ; B 34 0 284 718 ;
+C 249 ; WX 501 ; N oslash ; B 24 -22 531 545 ;
+C 250 ; WX 774 ; N oe ; B 68 -15 791 538 ;
+C 251 ; WX 501 ; N germandbls ; B 55 -15 539 728 ;
+C -1 ; WX 501 ; N Zcaron ; B 19 0 607 929 ;
+C -1 ; WX 410 ; N ccedilla ; B 61 -225 454 538 ;
+C -1 ; WX 410 ; N ydieresis ; B 12 -214 492 706 ;
+C -1 ; WX 456 ; N atilde ; B 50 -15 486 722 ;
+C -1 ; WX 228 ; N icircumflex ; B 78 0 337 734 ;
+C -1 ; WX 273 ; N threesuperior ; B 74 270 358 703 ;
+C -1 ; WX 456 ; N ecircumflex ; B 69 -15 474 734 ;
+C -1 ; WX 456 ; N thorn ; B 11 -207 479 718 ;
+C -1 ; WX 456 ; N egrave ; B 69 -15 474 734 ;
+C -1 ; WX 273 ; N twosuperior ; B 52 281 368 703 ;
+C -1 ; WX 456 ; N eacute ; B 69 -15 481 734 ;
+C -1 ; WX 456 ; N otilde ; B 68 -14 494 722 ;
+C -1 ; WX 547 ; N Aacute ; B 11 0 560 929 ;
+C -1 ; WX 456 ; N ocircumflex ; B 68 -14 479 734 ;
+C -1 ; WX 410 ; N yacute ; B 12 -214 492 734 ;
+C -1 ; WX 456 ; N udieresis ; B 77 -15 492 706 ;
+C -1 ; WX 684 ; N threequarters ; B 106 -19 706 703 ;
+C -1 ; WX 456 ; N acircumflex ; B 50 -15 458 734 ;
+C -1 ; WX 592 ; N Eth ; B 57 0 626 718 ;
+C -1 ; WX 456 ; N edieresis ; B 69 -15 474 706 ;
+C -1 ; WX 456 ; N ugrave ; B 77 -15 492 734 ;
+C -1 ; WX 820 ; N trademark ; B 152 306 866 718 ;
+C -1 ; WX 456 ; N ograve ; B 68 -14 479 734 ;
+C -1 ; WX 410 ; N scaron ; B 52 -15 453 734 ;
+C -1 ; WX 228 ; N Idieresis ; B 75 0 375 901 ;
+C -1 ; WX 456 ; N uacute ; B 77 -15 492 734 ;
+C -1 ; WX 456 ; N agrave ; B 50 -15 458 734 ;
+C -1 ; WX 456 ; N ntilde ; B 53 0 486 722 ;
+C -1 ; WX 456 ; N aring ; B 50 -15 458 756 ;
+C -1 ; WX 410 ; N zcaron ; B 25 0 468 734 ;
+C -1 ; WX 228 ; N Icircumflex ; B 75 0 371 929 ;
+C -1 ; WX 592 ; N Ntilde ; B 62 0 655 917 ;
+C -1 ; WX 456 ; N ucircumflex ; B 77 -15 492 734 ;
+C -1 ; WX 547 ; N Ecircumflex ; B 71 0 625 929 ;
+C -1 ; WX 228 ; N Iacute ; B 75 0 401 929 ;
+C -1 ; WX 592 ; N Ccedilla ; B 88 -225 640 737 ;
+C -1 ; WX 638 ; N Odieresis ; B 86 -19 677 901 ;
+C -1 ; WX 547 ; N Scaron ; B 74 -19 584 929 ;
+C -1 ; WX 547 ; N Edieresis ; B 71 0 625 901 ;
+C -1 ; WX 228 ; N Igrave ; B 75 0 288 929 ;
+C -1 ; WX 456 ; N adieresis ; B 50 -15 458 706 ;
+C -1 ; WX 638 ; N Ograve ; B 86 -19 677 929 ;
+C -1 ; WX 547 ; N Egrave ; B 71 0 625 929 ;
+C -1 ; WX 547 ; N Ydieresis ; B 137 0 661 901 ;
+C -1 ; WX 604 ; N registered ; B 44 -19 687 737 ;
+C -1 ; WX 638 ; N Otilde ; B 86 -19 677 917 ;
+C -1 ; WX 684 ; N onequarter ; B 123 -19 658 703 ;
+C -1 ; WX 592 ; N Ugrave ; B 101 -19 653 929 ;
+C -1 ; WX 592 ; N Ucircumflex ; B 101 -19 653 929 ;
+C -1 ; WX 547 ; N Thorn ; B 71 0 584 718 ;
+C -1 ; WX 479 ; N divide ; B 70 -19 497 524 ;
+C -1 ; WX 547 ; N Atilde ; B 11 0 573 917 ;
+C -1 ; WX 592 ; N Uacute ; B 101 -19 653 929 ;
+C -1 ; WX 638 ; N Ocircumflex ; B 86 -19 677 929 ;
+C -1 ; WX 479 ; N logicalnot ; B 87 108 515 390 ;
+C -1 ; WX 547 ; N Aring ; B 11 0 536 931 ;
+C -1 ; WX 228 ; N idieresis ; B 78 0 341 706 ;
+C -1 ; WX 228 ; N iacute ; B 78 0 367 734 ;
+C -1 ; WX 456 ; N aacute ; B 50 -15 481 734 ;
+C -1 ; WX 479 ; N plusminus ; B 32 0 507 506 ;
+C -1 ; WX 479 ; N multiply ; B 41 0 526 506 ;
+C -1 ; WX 592 ; N Udieresis ; B 101 -19 653 901 ;
+C -1 ; WX 479 ; N minus ; B 70 216 497 289 ;
+C -1 ; WX 273 ; N onesuperior ; B 136 281 305 703 ;
+C -1 ; WX 547 ; N Eacute ; B 71 0 625 929 ;
+C -1 ; WX 547 ; N Acircumflex ; B 11 0 536 929 ;
+C -1 ; WX 604 ; N copyright ; B 44 -19 687 737 ;
+C -1 ; WX 547 ; N Agrave ; B 11 0 536 929 ;
+C -1 ; WX 456 ; N odieresis ; B 68 -14 479 706 ;
+C -1 ; WX 456 ; N oacute ; B 68 -14 481 734 ;
+C -1 ; WX 328 ; N degree ; B 138 411 384 703 ;
+C -1 ; WX 228 ; N igrave ; B 78 0 254 734 ;
+C -1 ; WX 456 ; N mu ; B 20 -207 492 523 ;
+C -1 ; WX 638 ; N Oacute ; B 86 -19 677 929 ;
+C -1 ; WX 456 ; N eth ; B 67 -15 506 737 ;
+C -1 ; WX 547 ; N Adieresis ; B 11 0 536 901 ;
+C -1 ; WX 547 ; N Yacute ; B 137 0 661 929 ;
+C -1 ; WX 213 ; N brokenbar ; B 74 -19 265 737 ;
+C -1 ; WX 684 ; N onehalf ; B 93 -19 688 703 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 250
+
+KPX A y -40
+KPX A w -40
+KPX A v -40
+KPX A u -30
+KPX A Y -100
+KPX A W -50
+KPX A V -70
+KPX A U -50
+KPX A T -120
+KPX A Q -30
+KPX A O -30
+KPX A G -30
+KPX A C -30
+
+KPX B period -20
+KPX B comma -20
+KPX B U -10
+
+KPX C period -30
+KPX C comma -30
+
+KPX D period -70
+KPX D comma -70
+KPX D Y -90
+KPX D W -40
+KPX D V -70
+KPX D A -40
+
+KPX F r -45
+KPX F period -150
+KPX F o -30
+KPX F e -30
+KPX F comma -150
+KPX F a -50
+KPX F A -80
+
+KPX J u -20
+KPX J period -30
+KPX J comma -30
+KPX J a -20
+KPX J A -20
+
+KPX K y -50
+KPX K u -30
+KPX K o -40
+KPX K e -40
+KPX K O -50
+
+KPX L y -30
+KPX L quoteright -160
+KPX L quotedblright -140
+KPX L Y -140
+KPX L W -70
+KPX L V -110
+KPX L T -110
+
+KPX O period -40
+KPX O comma -40
+KPX O Y -70
+KPX O X -60
+KPX O W -30
+KPX O V -50
+KPX O T -40
+KPX O A -20
+
+KPX P period -180
+KPX P o -50
+KPX P e -50
+KPX P comma -180
+KPX P a -40
+KPX P A -120
+
+KPX Q U -10
+
+KPX R Y -50
+KPX R W -30
+KPX R V -50
+KPX R U -40
+KPX R T -30
+KPX R O -20
+
+KPX S period -20
+KPX S comma -20
+
+KPX T y -120
+KPX T w -120
+KPX T u -120
+KPX T semicolon -20
+KPX T r -120
+KPX T period -120
+KPX T o -120
+KPX T hyphen -140
+KPX T e -120
+KPX T comma -120
+KPX T colon -20
+KPX T a -120
+KPX T O -40
+KPX T A -120
+
+KPX U period -40
+KPX U comma -40
+KPX U A -40
+
+KPX V u -70
+KPX V semicolon -40
+KPX V period -125
+KPX V o -80
+KPX V hyphen -80
+KPX V e -80
+KPX V comma -125
+KPX V colon -40
+KPX V a -70
+KPX V O -40
+KPX V G -40
+KPX V A -80
+
+KPX W y -20
+KPX W u -30
+KPX W period -80
+KPX W o -30
+KPX W hyphen -40
+KPX W e -30
+KPX W comma -80
+KPX W a -40
+KPX W O -20
+KPX W A -50
+
+KPX Y u -110
+KPX Y semicolon -60
+KPX Y period -140
+KPX Y o -140
+KPX Y i -20
+KPX Y hyphen -140
+KPX Y e -140
+KPX Y comma -140
+KPX Y colon -60
+KPX Y a -140
+KPX Y O -85
+KPX Y A -110
+
+KPX a y -30
+KPX a w -20
+KPX a v -20
+
+KPX b y -20
+KPX b v -20
+KPX b u -20
+KPX b period -40
+KPX b l -20
+KPX b comma -40
+KPX b b -10
+
+KPX c k -20
+KPX c comma -15
+
+KPX colon space -50
+
+KPX comma quoteright -100
+KPX comma quotedblright -100
+
+KPX e y -20
+KPX e x -30
+KPX e w -20
+KPX e v -30
+KPX e period -15
+KPX e comma -15
+
+KPX f quoteright 50
+KPX f quotedblright 60
+KPX f period -30
+KPX f o -30
+KPX f e -30
+KPX f dotlessi -28
+KPX f comma -30
+KPX f a -30
+
+KPX g r -10
+
+KPX h y -30
+
+KPX k o -20
+KPX k e -20
+
+KPX m y -15
+KPX m u -10
+
+KPX n y -15
+KPX n v -20
+KPX n u -10
+
+KPX o y -30
+KPX o x -30
+KPX o w -15
+KPX o v -15
+KPX o period -40
+KPX o comma -40
+
+KPX oslash z -55
+KPX oslash y -70
+KPX oslash x -85
+KPX oslash w -70
+KPX oslash v -70
+KPX oslash u -55
+KPX oslash t -55
+KPX oslash s -55
+KPX oslash r -55
+KPX oslash q -55
+KPX oslash period -95
+KPX oslash p -55
+KPX oslash o -55
+KPX oslash n -55
+KPX oslash m -55
+KPX oslash l -55
+KPX oslash k -55
+KPX oslash j -55
+KPX oslash i -55
+KPX oslash h -55
+KPX oslash g -55
+KPX oslash f -55
+KPX oslash e -55
+KPX oslash d -55
+KPX oslash comma -95
+KPX oslash c -55
+KPX oslash b -55
+KPX oslash a -55
+
+KPX p y -30
+KPX p period -35
+KPX p comma -35
+
+KPX period space -60
+KPX period quoteright -100
+KPX period quotedblright -100
+
+KPX quotedblright space -40
+
+KPX quoteleft quoteleft -57
+
+KPX quoteright space -70
+KPX quoteright s -50
+KPX quoteright r -50
+KPX quoteright quoteright -57
+KPX quoteright d -50
+
+KPX r y 30
+KPX r v 30
+KPX r u 15
+KPX r t 40
+KPX r semicolon 30
+KPX r period -50
+KPX r p 30
+KPX r n 25
+KPX r m 25
+KPX r l 15
+KPX r k 15
+KPX r i 15
+KPX r comma -50
+KPX r colon 30
+KPX r a -10
+
+KPX s w -30
+KPX s period -15
+KPX s comma -15
+
+KPX semicolon space -50
+
+KPX space quoteleft -60
+KPX space quotedblleft -30
+KPX space Y -90
+KPX space W -40
+KPX space V -50
+KPX space T -50
+
+KPX v period -80
+KPX v o -25
+KPX v e -25
+KPX v comma -80
+KPX v a -25
+
+KPX w period -60
+KPX w o -10
+KPX w e -10
+KPX w comma -60
+KPX w a -15
+
+KPX x e -30
+
+KPX y period -100
+KPX y o -20
+KPX y e -20
+KPX y comma -100
+KPX y a -20
+
+KPX z o -15
+KPX z e -15
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 171 195 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 171 195 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 171 195 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 171 195 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 167 175 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 171 195 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 160 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 171 195 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 171 195 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 171 195 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 171 195 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 12 195 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 12 195 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 12 195 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 12 195 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 202 195 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 217 195 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 217 195 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 217 195 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 217 195 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 217 195 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 171 195 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 194 195 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 194 195 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 194 195 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 194 195 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 171 195 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 171 195 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 148 195 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 92 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 92 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 92 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 92 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 92 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 84 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 69 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 92 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 92 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 92 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 92 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -22 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -22 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -22 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -22 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 84 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 92 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 92 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 92 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 92 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 92 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 69 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 92 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 92 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 92 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 92 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 69 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 69 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 69 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncb8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncb8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..ba1fed6d9a3bc1ae584d0d2916a9fdef65ae15f6
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncb8a.afm
@@ -0,0 +1,472 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1988, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue May 28 16:48:12 1991
+Comment UniqueID 35031
+Comment VMusage 30773 37665
+FontName NewCenturySchlbk-Bold
+FullName New Century Schoolbook Bold
+FamilyName New Century Schoolbook
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -165 -250 1000 988
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.009
+Notice Copyright (c) 1985, 1987, 1988, 1991 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 722
+XHeight 475
+Ascender 737
+Descender -205
+StartCharMetrics 228
+C 32 ; WX 287 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 296 ; N exclam ; B 53 -15 243 737 ;
+C 34 ; WX 333 ; N quotedbl ; B 0 378 333 737 ;
+C 35 ; WX 574 ; N numbersign ; B 36 0 538 690 ;
+C 36 ; WX 574 ; N dollar ; B 25 -141 549 810 ;
+C 37 ; WX 833 ; N percent ; B 14 -15 819 705 ;
+C 38 ; WX 852 ; N ampersand ; B 34 -15 818 737 ;
+C 39 ; WX 241 ; N quoteright ; B 22 378 220 737 ;
+C 40 ; WX 389 ; N parenleft ; B 77 -117 345 745 ;
+C 41 ; WX 389 ; N parenright ; B 44 -117 312 745 ;
+C 42 ; WX 500 ; N asterisk ; B 54 302 446 737 ;
+C 43 ; WX 606 ; N plus ; B 50 0 556 506 ;
+C 44 ; WX 278 ; N comma ; B 40 -184 238 175 ;
+C 45 ; WX 333 ; N hyphen ; B 42 174 291 302 ;
+C 46 ; WX 278 ; N period ; B 44 -15 234 175 ;
+C 47 ; WX 278 ; N slash ; B -42 -15 320 737 ;
+C 48 ; WX 574 ; N zero ; B 27 -15 547 705 ;
+C 49 ; WX 574 ; N one ; B 83 0 491 705 ;
+C 50 ; WX 574 ; N two ; B 19 0 531 705 ;
+C 51 ; WX 574 ; N three ; B 23 -15 531 705 ;
+C 52 ; WX 574 ; N four ; B 19 0 547 705 ;
+C 53 ; WX 574 ; N five ; B 32 -15 534 705 ;
+C 54 ; WX 574 ; N six ; B 27 -15 547 705 ;
+C 55 ; WX 574 ; N seven ; B 45 -15 547 705 ;
+C 56 ; WX 574 ; N eight ; B 27 -15 548 705 ;
+C 57 ; WX 574 ; N nine ; B 27 -15 547 705 ;
+C 58 ; WX 278 ; N colon ; B 44 -15 234 485 ;
+C 59 ; WX 278 ; N semicolon ; B 40 -184 238 485 ;
+C 60 ; WX 606 ; N less ; B 50 -9 556 515 ;
+C 61 ; WX 606 ; N equal ; B 50 103 556 403 ;
+C 62 ; WX 606 ; N greater ; B 50 -9 556 515 ;
+C 63 ; WX 500 ; N question ; B 23 -15 477 737 ;
+C 64 ; WX 747 ; N at ; B -2 -15 750 737 ;
+C 65 ; WX 759 ; N A ; B -19 0 778 737 ;
+C 66 ; WX 778 ; N B ; B 19 0 739 722 ;
+C 67 ; WX 778 ; N C ; B 39 -15 723 737 ;
+C 68 ; WX 833 ; N D ; B 19 0 794 722 ;
+C 69 ; WX 759 ; N E ; B 19 0 708 722 ;
+C 70 ; WX 722 ; N F ; B 19 0 697 722 ;
+C 71 ; WX 833 ; N G ; B 39 -15 818 737 ;
+C 72 ; WX 870 ; N H ; B 19 0 851 722 ;
+C 73 ; WX 444 ; N I ; B 29 0 415 722 ;
+C 74 ; WX 648 ; N J ; B 6 -15 642 722 ;
+C 75 ; WX 815 ; N K ; B 19 0 822 722 ;
+C 76 ; WX 722 ; N L ; B 19 0 703 722 ;
+C 77 ; WX 981 ; N M ; B 10 0 971 722 ;
+C 78 ; WX 833 ; N N ; B 5 -10 828 722 ;
+C 79 ; WX 833 ; N O ; B 39 -15 794 737 ;
+C 80 ; WX 759 ; N P ; B 24 0 735 722 ;
+C 81 ; WX 833 ; N Q ; B 39 -189 808 737 ;
+C 82 ; WX 815 ; N R ; B 19 -15 815 722 ;
+C 83 ; WX 667 ; N S ; B 51 -15 634 737 ;
+C 84 ; WX 722 ; N T ; B 16 0 706 722 ;
+C 85 ; WX 833 ; N U ; B 14 -15 825 722 ;
+C 86 ; WX 759 ; N V ; B -19 -10 778 722 ;
+C 87 ; WX 981 ; N W ; B 7 -10 974 722 ;
+C 88 ; WX 722 ; N X ; B -12 0 734 722 ;
+C 89 ; WX 722 ; N Y ; B -12 0 734 722 ;
+C 90 ; WX 667 ; N Z ; B 28 0 639 722 ;
+C 91 ; WX 389 ; N bracketleft ; B 84 -109 339 737 ;
+C 92 ; WX 606 ; N backslash ; B 122 -15 484 737 ;
+C 93 ; WX 389 ; N bracketright ; B 50 -109 305 737 ;
+C 94 ; WX 606 ; N asciicircum ; B 66 325 540 690 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 241 ; N quoteleft ; B 22 378 220 737 ;
+C 97 ; WX 611 ; N a ; B 40 -15 601 485 ;
+C 98 ; WX 648 ; N b ; B 4 -15 616 737 ;
+C 99 ; WX 556 ; N c ; B 32 -15 524 485 ;
+C 100 ; WX 667 ; N d ; B 32 -15 644 737 ;
+C 101 ; WX 574 ; N e ; B 32 -15 542 485 ;
+C 102 ; WX 389 ; N f ; B 11 0 461 737 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B 30 -205 623 535 ;
+C 104 ; WX 685 ; N h ; B 17 0 662 737 ;
+C 105 ; WX 370 ; N i ; B 26 0 338 737 ;
+C 106 ; WX 352 ; N j ; B -86 -205 271 737 ;
+C 107 ; WX 667 ; N k ; B 17 0 662 737 ;
+C 108 ; WX 352 ; N l ; B 17 0 329 737 ;
+C 109 ; WX 963 ; N m ; B 17 0 940 485 ;
+C 110 ; WX 685 ; N n ; B 17 0 662 485 ;
+C 111 ; WX 611 ; N o ; B 32 -15 579 485 ;
+C 112 ; WX 667 ; N p ; B 17 -205 629 485 ;
+C 113 ; WX 648 ; N q ; B 32 -205 638 485 ;
+C 114 ; WX 519 ; N r ; B 17 0 516 485 ;
+C 115 ; WX 500 ; N s ; B 48 -15 476 485 ;
+C 116 ; WX 426 ; N t ; B 21 -15 405 675 ;
+C 117 ; WX 685 ; N u ; B 17 -15 668 475 ;
+C 118 ; WX 611 ; N v ; B 12 -10 599 475 ;
+C 119 ; WX 889 ; N w ; B 16 -10 873 475 ;
+C 120 ; WX 611 ; N x ; B 12 0 599 475 ;
+C 121 ; WX 611 ; N y ; B 12 -205 599 475 ;
+C 122 ; WX 537 ; N z ; B 38 0 499 475 ;
+C 123 ; WX 389 ; N braceleft ; B 36 -109 313 737 ;
+C 124 ; WX 606 ; N bar ; B 249 -250 357 750 ;
+C 125 ; WX 389 ; N braceright ; B 76 -109 353 737 ;
+C 126 ; WX 606 ; N asciitilde ; B 72 160 534 346 ;
+C 161 ; WX 296 ; N exclamdown ; B 53 -205 243 547 ;
+C 162 ; WX 574 ; N cent ; B 32 -102 528 572 ;
+C 163 ; WX 574 ; N sterling ; B 16 -15 558 705 ;
+C 164 ; WX 167 ; N fraction ; B -165 -15 332 705 ;
+C 165 ; WX 574 ; N yen ; B -10 0 584 690 ;
+C 166 ; WX 574 ; N florin ; B 14 -205 548 737 ;
+C 167 ; WX 500 ; N section ; B 62 -86 438 737 ;
+C 168 ; WX 574 ; N currency ; B 27 84 547 605 ;
+C 169 ; WX 241 ; N quotesingle ; B 53 378 189 737 ;
+C 170 ; WX 481 ; N quotedblleft ; B 22 378 459 737 ;
+C 171 ; WX 500 ; N guillemotleft ; B 46 79 454 397 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 62 79 271 397 ;
+C 173 ; WX 333 ; N guilsinglright ; B 62 79 271 397 ;
+C 174 ; WX 685 ; N fi ; B 11 0 666 737 ;
+C 175 ; WX 685 ; N fl ; B 11 0 666 737 ;
+C 177 ; WX 500 ; N endash ; B 0 184 500 292 ;
+C 178 ; WX 500 ; N dagger ; B 39 -101 461 737 ;
+C 179 ; WX 500 ; N daggerdbl ; B 39 -89 461 737 ;
+C 180 ; WX 278 ; N periodcentered ; B 53 200 225 372 ;
+C 182 ; WX 747 ; N paragraph ; B 96 -71 631 722 ;
+C 183 ; WX 606 ; N bullet ; B 122 180 484 542 ;
+C 184 ; WX 241 ; N quotesinglbase ; B 22 -184 220 175 ;
+C 185 ; WX 481 ; N quotedblbase ; B 22 -184 459 175 ;
+C 186 ; WX 481 ; N quotedblright ; B 22 378 459 737 ;
+C 187 ; WX 500 ; N guillemotright ; B 46 79 454 397 ;
+C 188 ; WX 1000 ; N ellipsis ; B 72 -15 928 175 ;
+C 189 ; WX 1000 ; N perthousand ; B 7 -15 993 705 ;
+C 191 ; WX 500 ; N questiondown ; B 23 -205 477 547 ;
+C 193 ; WX 333 ; N grave ; B 2 547 249 737 ;
+C 194 ; WX 333 ; N acute ; B 84 547 331 737 ;
+C 195 ; WX 333 ; N circumflex ; B -10 547 344 725 ;
+C 196 ; WX 333 ; N tilde ; B -24 563 357 705 ;
+C 197 ; WX 333 ; N macron ; B -6 582 339 664 ;
+C 198 ; WX 333 ; N breve ; B 9 547 324 714 ;
+C 199 ; WX 333 ; N dotaccent ; B 95 552 237 694 ;
+C 200 ; WX 333 ; N dieresis ; B -12 552 345 694 ;
+C 202 ; WX 333 ; N ring ; B 58 545 274 761 ;
+C 203 ; WX 333 ; N cedilla ; B 17 -224 248 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B -16 547 431 737 ;
+C 206 ; WX 333 ; N ogonek ; B 168 -163 346 3 ;
+C 207 ; WX 333 ; N caron ; B -10 547 344 725 ;
+C 208 ; WX 1000 ; N emdash ; B 0 184 1000 292 ;
+C 225 ; WX 981 ; N AE ; B -29 0 963 722 ;
+C 227 ; WX 367 ; N ordfeminine ; B 1 407 393 705 ;
+C 232 ; WX 722 ; N Lslash ; B 19 0 703 722 ;
+C 233 ; WX 833 ; N Oslash ; B 39 -53 794 775 ;
+C 234 ; WX 1000 ; N OE ; B 0 0 982 722 ;
+C 235 ; WX 367 ; N ordmasculine ; B 1 407 366 705 ;
+C 241 ; WX 870 ; N ae ; B 32 -15 838 485 ;
+C 245 ; WX 370 ; N dotlessi ; B 26 0 338 475 ;
+C 248 ; WX 352 ; N lslash ; B 17 0 329 737 ;
+C 249 ; WX 611 ; N oslash ; B 32 -103 579 573 ;
+C 250 ; WX 907 ; N oe ; B 32 -15 875 485 ;
+C 251 ; WX 611 ; N germandbls ; B -2 -15 580 737 ;
+C -1 ; WX 574 ; N ecircumflex ; B 32 -15 542 725 ;
+C -1 ; WX 574 ; N edieresis ; B 32 -15 542 694 ;
+C -1 ; WX 611 ; N aacute ; B 40 -15 601 737 ;
+C -1 ; WX 747 ; N registered ; B -2 -15 750 737 ;
+C -1 ; WX 370 ; N icircumflex ; B 9 0 363 725 ;
+C -1 ; WX 685 ; N udieresis ; B 17 -15 668 694 ;
+C -1 ; WX 611 ; N ograve ; B 32 -15 579 737 ;
+C -1 ; WX 685 ; N uacute ; B 17 -15 668 737 ;
+C -1 ; WX 685 ; N ucircumflex ; B 17 -15 668 725 ;
+C -1 ; WX 759 ; N Aacute ; B -19 0 778 964 ;
+C -1 ; WX 370 ; N igrave ; B 21 0 338 737 ;
+C -1 ; WX 444 ; N Icircumflex ; B 29 0 415 952 ;
+C -1 ; WX 556 ; N ccedilla ; B 32 -224 524 485 ;
+C -1 ; WX 611 ; N adieresis ; B 40 -15 601 694 ;
+C -1 ; WX 759 ; N Ecircumflex ; B 19 0 708 952 ;
+C -1 ; WX 500 ; N scaron ; B 48 -15 476 725 ;
+C -1 ; WX 667 ; N thorn ; B 17 -205 629 737 ;
+C -1 ; WX 1000 ; N trademark ; B 6 317 982 722 ;
+C -1 ; WX 574 ; N egrave ; B 32 -15 542 737 ;
+C -1 ; WX 344 ; N threesuperior ; B -3 273 355 705 ;
+C -1 ; WX 537 ; N zcaron ; B 38 0 499 725 ;
+C -1 ; WX 611 ; N atilde ; B 40 -15 601 705 ;
+C -1 ; WX 611 ; N aring ; B 40 -15 601 761 ;
+C -1 ; WX 611 ; N ocircumflex ; B 32 -15 579 725 ;
+C -1 ; WX 759 ; N Edieresis ; B 19 0 708 921 ;
+C -1 ; WX 861 ; N threequarters ; B 15 -15 838 705 ;
+C -1 ; WX 611 ; N ydieresis ; B 12 -205 599 694 ;
+C -1 ; WX 611 ; N yacute ; B 12 -205 599 737 ;
+C -1 ; WX 370 ; N iacute ; B 26 0 350 737 ;
+C -1 ; WX 759 ; N Acircumflex ; B -19 0 778 952 ;
+C -1 ; WX 833 ; N Uacute ; B 14 -15 825 964 ;
+C -1 ; WX 574 ; N eacute ; B 32 -15 542 737 ;
+C -1 ; WX 833 ; N Ograve ; B 39 -15 794 964 ;
+C -1 ; WX 611 ; N agrave ; B 40 -15 601 737 ;
+C -1 ; WX 833 ; N Udieresis ; B 14 -15 825 921 ;
+C -1 ; WX 611 ; N acircumflex ; B 40 -15 601 725 ;
+C -1 ; WX 444 ; N Igrave ; B 29 0 415 964 ;
+C -1 ; WX 344 ; N twosuperior ; B -3 282 350 705 ;
+C -1 ; WX 833 ; N Ugrave ; B 14 -15 825 964 ;
+C -1 ; WX 861 ; N onequarter ; B 31 -15 838 705 ;
+C -1 ; WX 833 ; N Ucircumflex ; B 14 -15 825 952 ;
+C -1 ; WX 667 ; N Scaron ; B 51 -15 634 952 ;
+C -1 ; WX 444 ; N Idieresis ; B 29 0 415 921 ;
+C -1 ; WX 370 ; N idieresis ; B 7 0 364 694 ;
+C -1 ; WX 759 ; N Egrave ; B 19 0 708 964 ;
+C -1 ; WX 833 ; N Oacute ; B 39 -15 794 964 ;
+C -1 ; WX 606 ; N divide ; B 50 -40 556 546 ;
+C -1 ; WX 759 ; N Atilde ; B -19 0 778 932 ;
+C -1 ; WX 759 ; N Aring ; B -19 0 778 988 ;
+C -1 ; WX 833 ; N Odieresis ; B 39 -15 794 921 ;
+C -1 ; WX 759 ; N Adieresis ; B -19 0 778 921 ;
+C -1 ; WX 833 ; N Ntilde ; B 5 -10 828 932 ;
+C -1 ; WX 667 ; N Zcaron ; B 28 0 639 952 ;
+C -1 ; WX 759 ; N Thorn ; B 24 0 735 722 ;
+C -1 ; WX 444 ; N Iacute ; B 29 0 415 964 ;
+C -1 ; WX 606 ; N plusminus ; B 50 0 556 506 ;
+C -1 ; WX 606 ; N multiply ; B 65 15 541 491 ;
+C -1 ; WX 759 ; N Eacute ; B 19 0 708 964 ;
+C -1 ; WX 722 ; N Ydieresis ; B -12 0 734 921 ;
+C -1 ; WX 344 ; N onesuperior ; B 31 282 309 705 ;
+C -1 ; WX 685 ; N ugrave ; B 17 -15 668 737 ;
+C -1 ; WX 606 ; N logicalnot ; B 50 103 556 403 ;
+C -1 ; WX 685 ; N ntilde ; B 17 0 662 705 ;
+C -1 ; WX 833 ; N Otilde ; B 39 -15 794 932 ;
+C -1 ; WX 611 ; N otilde ; B 32 -15 579 705 ;
+C -1 ; WX 778 ; N Ccedilla ; B 39 -224 723 737 ;
+C -1 ; WX 759 ; N Agrave ; B -19 0 778 964 ;
+C -1 ; WX 861 ; N onehalf ; B 31 -15 838 705 ;
+C -1 ; WX 833 ; N Eth ; B 19 0 794 722 ;
+C -1 ; WX 400 ; N degree ; B 57 419 343 705 ;
+C -1 ; WX 722 ; N Yacute ; B -12 0 734 964 ;
+C -1 ; WX 833 ; N Ocircumflex ; B 39 -15 794 952 ;
+C -1 ; WX 611 ; N oacute ; B 32 -15 579 737 ;
+C -1 ; WX 685 ; N mu ; B 17 -205 668 475 ;
+C -1 ; WX 606 ; N minus ; B 50 199 556 307 ;
+C -1 ; WX 611 ; N eth ; B 32 -15 579 737 ;
+C -1 ; WX 611 ; N odieresis ; B 32 -15 579 694 ;
+C -1 ; WX 747 ; N copyright ; B -2 -15 750 737 ;
+C -1 ; WX 606 ; N brokenbar ; B 249 -175 357 675 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 128
+
+KPX A y -18
+KPX A w -18
+KPX A v -18
+KPX A quoteright -74
+KPX A quotedblright -74
+KPX A Y -91
+KPX A W -74
+KPX A V -74
+KPX A U -18
+KPX A T -55
+
+KPX C period -18
+KPX C comma -18
+
+KPX D period -25
+KPX D comma -25
+
+KPX F r -18
+KPX F period -125
+KPX F o -55
+KPX F i -18
+KPX F e -55
+KPX F comma -125
+KPX F a -74
+
+KPX J u -18
+KPX J period -55
+KPX J o -18
+KPX J e -18
+KPX J comma -55
+KPX J a -18
+KPX J A -18
+
+KPX K y -25
+KPX K u -18
+
+KPX L y -25
+KPX L quoteright -100
+KPX L quotedblright -100
+KPX L Y -74
+KPX L W -74
+KPX L V -100
+KPX L T -100
+
+KPX N period -18
+KPX N comma -18
+
+KPX O period -25
+KPX O comma -25
+KPX O T 10
+
+KPX P period -150
+KPX P o -55
+KPX P e -55
+KPX P comma -150
+KPX P a -55
+KPX P A -74
+
+KPX S period -18
+KPX S comma -18
+
+KPX T u -18
+KPX T r -18
+KPX T period -100
+KPX T o -74
+KPX T i -18
+KPX T hyphen -125
+KPX T e -74
+KPX T comma -100
+KPX T a -74
+KPX T O 10
+KPX T A -55
+
+KPX U period -25
+KPX U comma -25
+KPX U A -18
+
+KPX V u -55
+KPX V semicolon -37
+KPX V period -125
+KPX V o -74
+KPX V i -18
+KPX V hyphen -100
+KPX V e -74
+KPX V comma -125
+KPX V colon -37
+KPX V a -74
+KPX V A -74
+
+KPX W y -25
+KPX W u -37
+KPX W semicolon -55
+KPX W period -100
+KPX W o -74
+KPX W i -18
+KPX W hyphen -100
+KPX W e -74
+KPX W comma -100
+KPX W colon -55
+KPX W a -74
+KPX W A -74
+
+KPX Y u -55
+KPX Y semicolon -25
+KPX Y period -100
+KPX Y o -100
+KPX Y i -18
+KPX Y hyphen -125
+KPX Y e -100
+KPX Y comma -100
+KPX Y colon -25
+KPX Y a -100
+KPX Y A -91
+
+KPX colon space -18
+
+KPX comma space -18
+KPX comma quoteright -18
+KPX comma quotedblright -18
+
+KPX f quoteright 75
+KPX f quotedblright 75
+
+KPX period space -18
+KPX period quoteright -18
+KPX period quotedblright -18
+
+KPX quotedblleft A -74
+
+KPX quotedblright space -18
+
+KPX quoteleft A -74
+
+KPX quoteright s -25
+KPX quoteright d -25
+
+KPX r period -74
+KPX r comma -74
+
+KPX semicolon space -18
+
+KPX space quoteleft -18
+KPX space quotedblleft -18
+KPX space Y -18
+KPX space W -18
+KPX space V -18
+KPX space T -18
+KPX space A -18
+
+KPX v period -100
+KPX v comma -100
+
+KPX w period -100
+KPX w comma -100
+
+KPX y period -100
+KPX y comma -100
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 213 227 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 213 227 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 213 227 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 213 227 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 213 227 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 213 227 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 213 227 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 213 227 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 213 227 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 213 227 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 56 227 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 56 227 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 56 227 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 56 227 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 250 227 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 250 227 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 250 227 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 250 227 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 250 227 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 250 227 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 167 227 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 250 227 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 250 227 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 250 227 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 250 227 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 195 227 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 195 227 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 167 227 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 139 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 139 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 139 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 139 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 139 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 139 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 121 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 121 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 121 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 121 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute 19 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex 19 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis 19 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave 19 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 176 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 139 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 139 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 139 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 139 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 139 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 84 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 176 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 176 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 176 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 176 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 139 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 139 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 102 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncbi8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncbi8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..7871147e0f7bc53cf4e0bc8516807b7b10cd78cd
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncbi8a.afm
@@ -0,0 +1,602 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue May 28 16:56:07 1991
+Comment UniqueID 35034
+Comment VMusage 31030 37922
+FontName NewCenturySchlbk-BoldItalic
+FullName New Century Schoolbook Bold Italic
+FamilyName New Century Schoolbook
+Weight Bold
+ItalicAngle -16
+IsFixedPitch false
+FontBBox -205 -250 1147 991
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 722
+XHeight 477
+Ascender 737
+Descender -205
+StartCharMetrics 228
+C 32 ; WX 287 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 0 -15 333 737 ;
+C 34 ; WX 400 ; N quotedbl ; B 66 388 428 737 ;
+C 35 ; WX 574 ; N numbersign ; B 30 0 544 690 ;
+C 36 ; WX 574 ; N dollar ; B 9 -120 565 810 ;
+C 37 ; WX 889 ; N percent ; B 54 -28 835 727 ;
+C 38 ; WX 889 ; N ampersand ; B 32 -15 823 737 ;
+C 39 ; WX 259 ; N quoteright ; B 48 388 275 737 ;
+C 40 ; WX 407 ; N parenleft ; B 72 -117 454 745 ;
+C 41 ; WX 407 ; N parenright ; B -70 -117 310 745 ;
+C 42 ; WX 500 ; N asterisk ; B 58 301 498 737 ;
+C 43 ; WX 606 ; N plus ; B 50 0 556 506 ;
+C 44 ; WX 287 ; N comma ; B -57 -192 170 157 ;
+C 45 ; WX 333 ; N hyphen ; B 2 177 263 299 ;
+C 46 ; WX 287 ; N period ; B -20 -15 152 157 ;
+C 47 ; WX 278 ; N slash ; B -41 -15 320 737 ;
+C 48 ; WX 574 ; N zero ; B 21 -15 553 705 ;
+C 49 ; WX 574 ; N one ; B 25 0 489 705 ;
+C 50 ; WX 574 ; N two ; B -38 -3 538 705 ;
+C 51 ; WX 574 ; N three ; B -7 -15 536 705 ;
+C 52 ; WX 574 ; N four ; B -13 0 544 705 ;
+C 53 ; WX 574 ; N five ; B 0 -15 574 705 ;
+C 54 ; WX 574 ; N six ; B 31 -15 574 705 ;
+C 55 ; WX 574 ; N seven ; B 64 -15 593 705 ;
+C 56 ; WX 574 ; N eight ; B 0 -15 552 705 ;
+C 57 ; WX 574 ; N nine ; B 0 -15 543 705 ;
+C 58 ; WX 287 ; N colon ; B -20 -15 237 477 ;
+C 59 ; WX 287 ; N semicolon ; B -57 -192 237 477 ;
+C 60 ; WX 606 ; N less ; B 50 -9 556 515 ;
+C 61 ; WX 606 ; N equal ; B 50 103 556 403 ;
+C 62 ; WX 606 ; N greater ; B 50 -8 556 514 ;
+C 63 ; WX 481 ; N question ; B 79 -15 451 737 ;
+C 64 ; WX 747 ; N at ; B -4 -15 751 737 ;
+C 65 ; WX 741 ; N A ; B -75 0 716 737 ;
+C 66 ; WX 759 ; N B ; B -50 0 721 722 ;
+C 67 ; WX 759 ; N C ; B 37 -15 759 737 ;
+C 68 ; WX 833 ; N D ; B -47 0 796 722 ;
+C 69 ; WX 741 ; N E ; B -41 0 730 722 ;
+C 70 ; WX 704 ; N F ; B -41 0 730 722 ;
+C 71 ; WX 815 ; N G ; B 37 -15 805 737 ;
+C 72 ; WX 870 ; N H ; B -41 0 911 722 ;
+C 73 ; WX 444 ; N I ; B -41 0 485 722 ;
+C 74 ; WX 667 ; N J ; B -20 -15 708 722 ;
+C 75 ; WX 778 ; N K ; B -41 0 832 722 ;
+C 76 ; WX 704 ; N L ; B -41 0 670 722 ;
+C 77 ; WX 944 ; N M ; B -44 0 988 722 ;
+C 78 ; WX 852 ; N N ; B -61 -10 913 722 ;
+C 79 ; WX 833 ; N O ; B 37 -15 796 737 ;
+C 80 ; WX 741 ; N P ; B -41 0 730 722 ;
+C 81 ; WX 833 ; N Q ; B 37 -189 796 737 ;
+C 82 ; WX 796 ; N R ; B -41 -15 749 722 ;
+C 83 ; WX 685 ; N S ; B 1 -15 666 737 ;
+C 84 ; WX 722 ; N T ; B 41 0 759 722 ;
+C 85 ; WX 833 ; N U ; B 88 -15 900 722 ;
+C 86 ; WX 741 ; N V ; B 32 -10 802 722 ;
+C 87 ; WX 944 ; N W ; B 40 -10 1000 722 ;
+C 88 ; WX 741 ; N X ; B -82 0 801 722 ;
+C 89 ; WX 704 ; N Y ; B 13 0 775 722 ;
+C 90 ; WX 704 ; N Z ; B -33 0 711 722 ;
+C 91 ; WX 407 ; N bracketleft ; B 1 -109 464 737 ;
+C 92 ; WX 606 ; N backslash ; B 161 -15 445 737 ;
+C 93 ; WX 407 ; N bracketright ; B -101 -109 362 737 ;
+C 94 ; WX 606 ; N asciicircum ; B 66 325 540 690 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 259 ; N quoteleft ; B 47 388 274 737 ;
+C 97 ; WX 667 ; N a ; B 6 -15 636 477 ;
+C 98 ; WX 611 ; N b ; B 29 -15 557 737 ;
+C 99 ; WX 537 ; N c ; B 0 -15 482 477 ;
+C 100 ; WX 667 ; N d ; B 0 -15 660 737 ;
+C 101 ; WX 519 ; N e ; B 0 -15 479 477 ;
+C 102 ; WX 389 ; N f ; B -48 -205 550 737 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B -63 -205 604 528 ;
+C 104 ; WX 685 ; N h ; B 0 -15 639 737 ;
+C 105 ; WX 389 ; N i ; B 32 -15 345 737 ;
+C 106 ; WX 370 ; N j ; B -205 -205 347 737 ;
+C 107 ; WX 648 ; N k ; B -11 -15 578 737 ;
+C 108 ; WX 389 ; N l ; B 32 -15 375 737 ;
+C 109 ; WX 944 ; N m ; B 0 -15 909 477 ;
+C 110 ; WX 685 ; N n ; B 0 -15 639 477 ;
+C 111 ; WX 574 ; N o ; B 0 -15 530 477 ;
+C 112 ; WX 648 ; N p ; B -119 -205 590 477 ;
+C 113 ; WX 630 ; N q ; B 0 -205 587 477 ;
+C 114 ; WX 519 ; N r ; B 0 0 527 486 ;
+C 115 ; WX 481 ; N s ; B 0 -15 435 477 ;
+C 116 ; WX 407 ; N t ; B 24 -15 403 650 ;
+C 117 ; WX 685 ; N u ; B 30 -15 635 477 ;
+C 118 ; WX 556 ; N v ; B 30 -15 496 477 ;
+C 119 ; WX 833 ; N w ; B 30 -15 773 477 ;
+C 120 ; WX 574 ; N x ; B -46 -15 574 477 ;
+C 121 ; WX 519 ; N y ; B -66 -205 493 477 ;
+C 122 ; WX 519 ; N z ; B -19 -15 473 477 ;
+C 123 ; WX 407 ; N braceleft ; B 52 -109 408 737 ;
+C 124 ; WX 606 ; N bar ; B 249 -250 357 750 ;
+C 125 ; WX 407 ; N braceright ; B -25 -109 331 737 ;
+C 126 ; WX 606 ; N asciitilde ; B 72 160 534 346 ;
+C 161 ; WX 333 ; N exclamdown ; B -44 -205 289 547 ;
+C 162 ; WX 574 ; N cent ; B 30 -144 512 578 ;
+C 163 ; WX 574 ; N sterling ; B -18 -15 566 705 ;
+C 164 ; WX 167 ; N fraction ; B -166 -15 333 705 ;
+C 165 ; WX 574 ; N yen ; B 17 0 629 690 ;
+C 166 ; WX 574 ; N florin ; B -43 -205 575 737 ;
+C 167 ; WX 500 ; N section ; B -30 -146 515 737 ;
+C 168 ; WX 574 ; N currency ; B 27 84 547 605 ;
+C 169 ; WX 287 ; N quotesingle ; B 112 388 250 737 ;
+C 170 ; WX 481 ; N quotedblleft ; B 54 388 521 737 ;
+C 171 ; WX 481 ; N guillemotleft ; B -35 69 449 407 ;
+C 172 ; WX 278 ; N guilsinglleft ; B -25 69 244 407 ;
+C 173 ; WX 278 ; N guilsinglright ; B -26 69 243 407 ;
+C 174 ; WX 685 ; N fi ; B -70 -205 641 737 ;
+C 175 ; WX 685 ; N fl ; B -70 -205 671 737 ;
+C 177 ; WX 500 ; N endash ; B -47 189 479 287 ;
+C 178 ; WX 500 ; N dagger ; B 48 -146 508 737 ;
+C 179 ; WX 500 ; N daggerdbl ; B -60 -150 508 737 ;
+C 180 ; WX 287 ; N periodcentered ; B 57 200 229 372 ;
+C 182 ; WX 650 ; N paragraph ; B 25 -131 681 722 ;
+C 183 ; WX 606 ; N bullet ; B 122 180 484 542 ;
+C 184 ; WX 259 ; N quotesinglbase ; B -57 -192 170 157 ;
+C 185 ; WX 481 ; N quotedblbase ; B -57 -192 412 157 ;
+C 186 ; WX 481 ; N quotedblright ; B 43 388 510 737 ;
+C 187 ; WX 481 ; N guillemotright ; B -31 69 453 407 ;
+C 188 ; WX 1000 ; N ellipsis ; B 81 -15 919 157 ;
+C 189 ; WX 1167 ; N perthousand ; B 20 -28 1147 727 ;
+C 191 ; WX 481 ; N questiondown ; B 0 -205 372 547 ;
+C 193 ; WX 333 ; N grave ; B 74 538 294 722 ;
+C 194 ; WX 333 ; N acute ; B 123 538 372 722 ;
+C 195 ; WX 333 ; N circumflex ; B 23 533 365 705 ;
+C 196 ; WX 333 ; N tilde ; B 28 561 398 690 ;
+C 197 ; WX 333 ; N macron ; B 47 573 404 649 ;
+C 198 ; WX 333 ; N breve ; B 67 535 390 698 ;
+C 199 ; WX 333 ; N dotaccent ; B 145 546 289 690 ;
+C 200 ; WX 333 ; N dieresis ; B 33 546 393 690 ;
+C 202 ; WX 333 ; N ring ; B 111 522 335 746 ;
+C 203 ; WX 333 ; N cedilla ; B -21 -220 225 3 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 15 538 480 722 ;
+C 206 ; WX 333 ; N ogonek ; B 68 -155 246 -10 ;
+C 207 ; WX 333 ; N caron ; B 60 531 403 705 ;
+C 208 ; WX 1000 ; N emdash ; B -47 189 979 287 ;
+C 225 ; WX 889 ; N AE ; B -86 0 915 722 ;
+C 227 ; WX 412 ; N ordfeminine ; B 47 407 460 705 ;
+C 232 ; WX 704 ; N Lslash ; B -41 0 670 722 ;
+C 233 ; WX 833 ; N Oslash ; B 35 -68 798 790 ;
+C 234 ; WX 963 ; N OE ; B 29 0 989 722 ;
+C 235 ; WX 356 ; N ordmasculine ; B 42 407 394 705 ;
+C 241 ; WX 815 ; N ae ; B -18 -15 775 477 ;
+C 245 ; WX 389 ; N dotlessi ; B 32 -15 345 477 ;
+C 248 ; WX 389 ; N lslash ; B 5 -15 390 737 ;
+C 249 ; WX 574 ; N oslash ; B 0 -121 530 583 ;
+C 250 ; WX 852 ; N oe ; B -6 -15 812 477 ;
+C 251 ; WX 574 ; N germandbls ; B -91 -205 540 737 ;
+C -1 ; WX 519 ; N ecircumflex ; B 0 -15 479 705 ;
+C -1 ; WX 519 ; N edieresis ; B 0 -15 486 690 ;
+C -1 ; WX 667 ; N aacute ; B 6 -15 636 722 ;
+C -1 ; WX 747 ; N registered ; B -2 -15 750 737 ;
+C -1 ; WX 389 ; N icircumflex ; B 21 -15 363 698 ;
+C -1 ; WX 685 ; N udieresis ; B 30 -15 635 690 ;
+C -1 ; WX 574 ; N ograve ; B 0 -15 530 722 ;
+C -1 ; WX 685 ; N uacute ; B 30 -15 635 722 ;
+C -1 ; WX 685 ; N ucircumflex ; B 30 -15 635 705 ;
+C -1 ; WX 741 ; N Aacute ; B -75 0 716 947 ;
+C -1 ; WX 389 ; N igrave ; B 32 -15 345 715 ;
+C -1 ; WX 444 ; N Icircumflex ; B -41 0 485 930 ;
+C -1 ; WX 537 ; N ccedilla ; B 0 -220 482 477 ;
+C -1 ; WX 667 ; N adieresis ; B 6 -15 636 690 ;
+C -1 ; WX 741 ; N Ecircumflex ; B -41 0 730 930 ;
+C -1 ; WX 481 ; N scaron ; B 0 -15 477 705 ;
+C -1 ; WX 648 ; N thorn ; B -119 -205 590 737 ;
+C -1 ; WX 950 ; N trademark ; B 42 317 1017 722 ;
+C -1 ; WX 519 ; N egrave ; B 0 -15 479 722 ;
+C -1 ; WX 344 ; N threesuperior ; B 3 273 361 705 ;
+C -1 ; WX 519 ; N zcaron ; B -19 -15 473 695 ;
+C -1 ; WX 667 ; N atilde ; B 6 -15 636 690 ;
+C -1 ; WX 667 ; N aring ; B 6 -15 636 746 ;
+C -1 ; WX 574 ; N ocircumflex ; B 0 -15 530 705 ;
+C -1 ; WX 741 ; N Edieresis ; B -41 0 730 915 ;
+C -1 ; WX 861 ; N threequarters ; B 35 -15 789 705 ;
+C -1 ; WX 519 ; N ydieresis ; B -66 -205 493 690 ;
+C -1 ; WX 519 ; N yacute ; B -66 -205 493 722 ;
+C -1 ; WX 389 ; N iacute ; B 32 -15 370 715 ;
+C -1 ; WX 741 ; N Acircumflex ; B -75 0 716 930 ;
+C -1 ; WX 833 ; N Uacute ; B 88 -15 900 947 ;
+C -1 ; WX 519 ; N eacute ; B 0 -15 479 722 ;
+C -1 ; WX 833 ; N Ograve ; B 37 -15 796 947 ;
+C -1 ; WX 667 ; N agrave ; B 6 -15 636 722 ;
+C -1 ; WX 833 ; N Udieresis ; B 88 -15 900 915 ;
+C -1 ; WX 667 ; N acircumflex ; B 6 -15 636 705 ;
+C -1 ; WX 444 ; N Igrave ; B -41 0 485 947 ;
+C -1 ; WX 344 ; N twosuperior ; B -17 280 362 705 ;
+C -1 ; WX 833 ; N Ugrave ; B 88 -15 900 947 ;
+C -1 ; WX 861 ; N onequarter ; B 17 -15 789 705 ;
+C -1 ; WX 833 ; N Ucircumflex ; B 88 -15 900 930 ;
+C -1 ; WX 685 ; N Scaron ; B 1 -15 666 930 ;
+C -1 ; WX 444 ; N Idieresis ; B -41 0 509 915 ;
+C -1 ; WX 389 ; N idieresis ; B 31 -15 391 683 ;
+C -1 ; WX 741 ; N Egrave ; B -41 0 730 947 ;
+C -1 ; WX 833 ; N Oacute ; B 37 -15 796 947 ;
+C -1 ; WX 606 ; N divide ; B 50 -40 556 546 ;
+C -1 ; WX 741 ; N Atilde ; B -75 0 716 915 ;
+C -1 ; WX 741 ; N Aring ; B -75 0 716 991 ;
+C -1 ; WX 833 ; N Odieresis ; B 37 -15 796 915 ;
+C -1 ; WX 741 ; N Adieresis ; B -75 0 716 915 ;
+C -1 ; WX 852 ; N Ntilde ; B -61 -10 913 915 ;
+C -1 ; WX 704 ; N Zcaron ; B -33 0 711 930 ;
+C -1 ; WX 741 ; N Thorn ; B -41 0 690 722 ;
+C -1 ; WX 444 ; N Iacute ; B -41 0 488 947 ;
+C -1 ; WX 606 ; N plusminus ; B 50 0 556 506 ;
+C -1 ; WX 606 ; N multiply ; B 65 15 541 491 ;
+C -1 ; WX 741 ; N Eacute ; B -41 0 730 947 ;
+C -1 ; WX 704 ; N Ydieresis ; B 13 0 775 915 ;
+C -1 ; WX 344 ; N onesuperior ; B 19 282 326 705 ;
+C -1 ; WX 685 ; N ugrave ; B 30 -15 635 722 ;
+C -1 ; WX 606 ; N logicalnot ; B 50 103 556 403 ;
+C -1 ; WX 685 ; N ntilde ; B 0 -15 639 690 ;
+C -1 ; WX 833 ; N Otilde ; B 37 -15 796 915 ;
+C -1 ; WX 574 ; N otilde ; B 0 -15 530 690 ;
+C -1 ; WX 759 ; N Ccedilla ; B 37 -220 759 737 ;
+C -1 ; WX 741 ; N Agrave ; B -75 0 716 947 ;
+C -1 ; WX 861 ; N onehalf ; B 17 -15 798 705 ;
+C -1 ; WX 833 ; N Eth ; B -47 0 796 722 ;
+C -1 ; WX 400 ; N degree ; B 86 419 372 705 ;
+C -1 ; WX 704 ; N Yacute ; B 13 0 775 947 ;
+C -1 ; WX 833 ; N Ocircumflex ; B 37 -15 796 930 ;
+C -1 ; WX 574 ; N oacute ; B 0 -15 530 722 ;
+C -1 ; WX 685 ; N mu ; B -89 -205 635 477 ;
+C -1 ; WX 606 ; N minus ; B 50 199 556 307 ;
+C -1 ; WX 574 ; N eth ; B 0 -15 530 752 ;
+C -1 ; WX 574 ; N odieresis ; B 0 -15 530 690 ;
+C -1 ; WX 747 ; N copyright ; B -2 -15 750 737 ;
+C -1 ; WX 606 ; N brokenbar ; B 249 -175 357 675 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 239
+
+KPX A y -33
+KPX A w -25
+KPX A v -10
+KPX A u -15
+KPX A quoteright -95
+KPX A quotedblright -95
+KPX A Y -70
+KPX A W -84
+KPX A V -100
+KPX A U -32
+KPX A T 5
+KPX A Q 5
+KPX A O 5
+KPX A G 5
+KPX A C 5
+
+KPX B period 15
+KPX B comma 15
+KPX B U 15
+KPX B A -11
+
+KPX C A -5
+
+KPX D period -11
+KPX D comma -11
+KPX D Y 6
+KPX D W -11
+KPX D V -18
+
+KPX F r -27
+KPX F period -91
+KPX F o -47
+KPX F i -41
+KPX F e -41
+KPX F comma -91
+KPX F a -47
+KPX F A -79
+
+KPX J u -39
+KPX J period -74
+KPX J o -40
+KPX J e -33
+KPX J comma -74
+KPX J a -40
+KPX J A -30
+
+KPX K y -48
+KPX K u -4
+KPX K o -4
+KPX K e 18
+
+KPX L y -30
+KPX L quoteright -100
+KPX L quotedblright -100
+KPX L Y -55
+KPX L W -69
+KPX L V -97
+KPX L T -75
+
+KPX N period -49
+KPX N comma -49
+
+KPX O period -18
+KPX O comma -18
+KPX O X -18
+KPX O W -15
+KPX O V -24
+KPX O A -5
+
+KPX P period -100
+KPX P o -40
+KPX P e -33
+KPX P comma -100
+KPX P a -40
+KPX P A -80
+
+KPX R W -14
+KPX R V -24
+
+KPX S period -18
+KPX S comma -18
+
+KPX T y -30
+KPX T w -30
+KPX T u -22
+KPX T r -9
+KPX T period -55
+KPX T o -40
+KPX T i -22
+KPX T hyphen -75
+KPX T h -9
+KPX T e -33
+KPX T comma -55
+KPX T a -40
+KPX T O 11
+KPX T A -60
+
+KPX U period -25
+KPX U comma -25
+KPX U A -42
+
+KPX V u -70
+KPX V semicolon 6
+KPX V period -94
+KPX V o -71
+KPX V i -35
+KPX V hyphen -94
+KPX V e -66
+KPX V comma -94
+KPX V colon -49
+KPX V a -55
+KPX V O -19
+KPX V G -12
+KPX V A -100
+
+KPX W y -41
+KPX W u -25
+KPX W semicolon -22
+KPX W period -86
+KPX W o -33
+KPX W i -27
+KPX W hyphen -61
+KPX W h 5
+KPX W e -39
+KPX W comma -86
+KPX W colon -22
+KPX W a -33
+KPX W O -11
+KPX W A -66
+
+KPX Y u -58
+KPX Y semicolon -55
+KPX Y period -91
+KPX Y o -77
+KPX Y i -22
+KPX Y hyphen -91
+KPX Y e -71
+KPX Y comma -91
+KPX Y colon -55
+KPX Y a -77
+KPX Y A -79
+
+KPX a y -8
+KPX a w -8
+KPX a v 6
+
+KPX b y -6
+KPX b v 8
+KPX b period 6
+KPX b comma 6
+
+KPX c y -20
+KPX c period -8
+KPX c l -13
+KPX c k -8
+KPX c h -18
+KPX c comma -8
+
+KPX colon space -18
+
+KPX comma space -18
+KPX comma quoteright -18
+KPX comma quotedblright -18
+
+KPX d y -15
+KPX d w -15
+
+KPX e y -15
+KPX e x -5
+KPX e w -15
+KPX e p -11
+KPX e g -4
+KPX e b -8
+
+KPX f quoteright 105
+KPX f quotedblright 105
+KPX f period -28
+KPX f o 7
+KPX f l 7
+KPX f i 7
+KPX f e 14
+KPX f dotlessi 7
+KPX f comma -28
+KPX f a 8
+
+KPX g y -11
+KPX g r 11
+KPX g period -5
+KPX g comma -5
+
+KPX h y -20
+
+KPX i v 7
+
+KPX k y -15
+KPX k o -22
+KPX k e -16
+
+KPX l y -7
+KPX l w -7
+
+KPX m y -20
+KPX m u -11
+
+KPX n y -20
+KPX n v -7
+KPX n u -11
+
+KPX o y -11
+KPX o w -8
+KPX o v 6
+
+KPX p y -4
+KPX p period 8
+KPX p comma 8
+
+KPX period space -18
+KPX period quoteright -18
+KPX period quotedblright -18
+
+KPX quotedblleft quoteleft 20
+KPX quotedblleft A -60
+
+KPX quotedblright space -18
+
+KPX quoteleft A -80
+
+KPX quoteright v -16
+KPX quoteright t -22
+KPX quoteright s -46
+KPX quoteright r -9
+KPX quoteright l -22
+KPX quoteright d -41
+
+KPX r y -20
+KPX r v -7
+KPX r u -11
+KPX r t -11
+KPX r semicolon 9
+KPX r s -20
+KPX r quoteright 9
+KPX r period -90
+KPX r p -17
+KPX r o -11
+KPX r l -14
+KPX r k 9
+KPX r i -14
+KPX r hyphen -16
+KPX r g -11
+KPX r e -7
+KPX r d -7
+KPX r comma -90
+KPX r colon 9
+KPX r a -11
+
+KPX s period 11
+KPX s comma 11
+
+KPX semicolon space -18
+
+KPX space quotedblleft -18
+KPX space Y -18
+KPX space W -33
+KPX space V -24
+KPX space T -18
+KPX space A -22
+
+KPX v period -11
+KPX v o -6
+KPX v comma -11
+KPX v a -6
+
+KPX w period -17
+KPX w o -14
+KPX w e -8
+KPX w comma -17
+KPX w a -14
+
+KPX x e 5
+
+KPX y period -25
+KPX y o 8
+KPX y e 15
+KPX y comma -25
+KPX y a 8
+
+KPX z e 4
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 259 225 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 259 225 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 259 225 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 259 225 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 229 245 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 259 225 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 296 225 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 296 225 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 296 225 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 296 225 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 116 225 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 116 225 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 116 225 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 116 225 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 326 225 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 315 225 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 315 225 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 315 225 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 315 225 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 315 225 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 206 225 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 340 225 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 340 225 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 340 225 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 340 225 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 246 225 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 236 225 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 226 225 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 167 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 167 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 167 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 167 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 167 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 167 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 93 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 93 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 93 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 93 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -2 -7 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -2 -7 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -2 -7 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -2 -7 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 176 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 121 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 121 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 121 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 121 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 121 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 74 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 176 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 176 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 176 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 176 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 93 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 93 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 63 -10 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncr8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncr8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..b9f616cb5a203d3afef2affa08befee80088a2b5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncr8a.afm
@@ -0,0 +1,524 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue May 28 16:31:51 1991
+Comment UniqueID 35025
+Comment VMusage 30420 37312
+FontName NewCenturySchlbk-Roman
+FullName New Century Schoolbook Roman
+FamilyName New Century Schoolbook
+Weight Roman
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -195 -250 1000 965
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 722
+XHeight 464
+Ascender 737
+Descender -205
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 296 ; N exclam ; B 86 -15 210 737 ;
+C 34 ; WX 389 ; N quotedbl ; B 61 443 328 737 ;
+C 35 ; WX 556 ; N numbersign ; B 28 0 528 690 ;
+C 36 ; WX 556 ; N dollar ; B 45 -138 511 813 ;
+C 37 ; WX 833 ; N percent ; B 43 -15 790 705 ;
+C 38 ; WX 815 ; N ampersand ; B 51 -15 775 737 ;
+C 39 ; WX 204 ; N quoteright ; B 25 443 179 737 ;
+C 40 ; WX 333 ; N parenleft ; B 40 -117 279 745 ;
+C 41 ; WX 333 ; N parenright ; B 54 -117 293 745 ;
+C 42 ; WX 500 ; N asterisk ; B 57 306 443 737 ;
+C 43 ; WX 606 ; N plus ; B 50 0 556 506 ;
+C 44 ; WX 278 ; N comma ; B 62 -185 216 109 ;
+C 45 ; WX 333 ; N hyphen ; B 42 199 291 277 ;
+C 46 ; WX 278 ; N period ; B 77 -15 201 109 ;
+C 47 ; WX 278 ; N slash ; B -32 -15 310 737 ;
+C 48 ; WX 556 ; N zero ; B 42 -15 514 705 ;
+C 49 ; WX 556 ; N one ; B 100 0 496 705 ;
+C 50 ; WX 556 ; N two ; B 35 0 505 705 ;
+C 51 ; WX 556 ; N three ; B 42 -15 498 705 ;
+C 52 ; WX 556 ; N four ; B 28 0 528 705 ;
+C 53 ; WX 556 ; N five ; B 46 -15 502 705 ;
+C 54 ; WX 556 ; N six ; B 41 -15 515 705 ;
+C 55 ; WX 556 ; N seven ; B 59 -15 508 705 ;
+C 56 ; WX 556 ; N eight ; B 42 -15 514 705 ;
+C 57 ; WX 556 ; N nine ; B 41 -15 515 705 ;
+C 58 ; WX 278 ; N colon ; B 77 -15 201 474 ;
+C 59 ; WX 278 ; N semicolon ; B 62 -185 216 474 ;
+C 60 ; WX 606 ; N less ; B 50 -8 556 514 ;
+C 61 ; WX 606 ; N equal ; B 50 117 556 389 ;
+C 62 ; WX 606 ; N greater ; B 50 -8 556 514 ;
+C 63 ; WX 444 ; N question ; B 29 -15 415 737 ;
+C 64 ; WX 737 ; N at ; B -8 -15 744 737 ;
+C 65 ; WX 722 ; N A ; B -8 0 730 737 ;
+C 66 ; WX 722 ; N B ; B 29 0 669 722 ;
+C 67 ; WX 722 ; N C ; B 45 -15 668 737 ;
+C 68 ; WX 778 ; N D ; B 29 0 733 722 ;
+C 69 ; WX 722 ; N E ; B 29 0 663 722 ;
+C 70 ; WX 667 ; N F ; B 29 0 638 722 ;
+C 71 ; WX 778 ; N G ; B 45 -15 775 737 ;
+C 72 ; WX 833 ; N H ; B 29 0 804 722 ;
+C 73 ; WX 407 ; N I ; B 38 0 369 722 ;
+C 74 ; WX 556 ; N J ; B 5 -15 540 722 ;
+C 75 ; WX 778 ; N K ; B 29 0 803 722 ;
+C 76 ; WX 667 ; N L ; B 29 0 644 722 ;
+C 77 ; WX 944 ; N M ; B 29 0 915 722 ;
+C 78 ; WX 815 ; N N ; B 24 -15 791 722 ;
+C 79 ; WX 778 ; N O ; B 45 -15 733 737 ;
+C 80 ; WX 667 ; N P ; B 29 0 650 722 ;
+C 81 ; WX 778 ; N Q ; B 45 -190 748 737 ;
+C 82 ; WX 722 ; N R ; B 29 -15 713 722 ;
+C 83 ; WX 630 ; N S ; B 47 -15 583 737 ;
+C 84 ; WX 667 ; N T ; B 19 0 648 722 ;
+C 85 ; WX 815 ; N U ; B 16 -15 799 722 ;
+C 86 ; WX 722 ; N V ; B -8 -10 730 722 ;
+C 87 ; WX 981 ; N W ; B 5 -10 976 722 ;
+C 88 ; WX 704 ; N X ; B -8 0 712 722 ;
+C 89 ; WX 704 ; N Y ; B -11 0 715 722 ;
+C 90 ; WX 611 ; N Z ; B 24 0 576 722 ;
+C 91 ; WX 333 ; N bracketleft ; B 126 -109 315 737 ;
+C 92 ; WX 606 ; N backslash ; B 132 -15 474 737 ;
+C 93 ; WX 333 ; N bracketright ; B 18 -109 207 737 ;
+C 94 ; WX 606 ; N asciicircum ; B 89 325 517 690 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 204 ; N quoteleft ; B 25 443 179 737 ;
+C 97 ; WX 556 ; N a ; B 44 -15 542 479 ;
+C 98 ; WX 556 ; N b ; B 10 -15 522 737 ;
+C 99 ; WX 444 ; N c ; B 34 -15 426 479 ;
+C 100 ; WX 574 ; N d ; B 34 -15 552 737 ;
+C 101 ; WX 500 ; N e ; B 34 -15 466 479 ;
+C 102 ; WX 333 ; N f ; B 18 0 437 737 ; L i fi ; L l fl ;
+C 103 ; WX 537 ; N g ; B 23 -205 542 494 ;
+C 104 ; WX 611 ; N h ; B 7 0 592 737 ;
+C 105 ; WX 315 ; N i ; B 18 0 286 722 ;
+C 106 ; WX 296 ; N j ; B -86 -205 216 722 ;
+C 107 ; WX 593 ; N k ; B 10 0 589 737 ;
+C 108 ; WX 315 ; N l ; B 18 0 286 737 ;
+C 109 ; WX 889 ; N m ; B 26 0 863 479 ;
+C 110 ; WX 611 ; N n ; B 22 0 589 479 ;
+C 111 ; WX 500 ; N o ; B 34 -15 466 479 ;
+C 112 ; WX 574 ; N p ; B 22 -205 540 479 ;
+C 113 ; WX 556 ; N q ; B 34 -205 552 479 ;
+C 114 ; WX 444 ; N r ; B 18 0 434 479 ;
+C 115 ; WX 463 ; N s ; B 46 -15 417 479 ;
+C 116 ; WX 389 ; N t ; B 18 -15 371 666 ;
+C 117 ; WX 611 ; N u ; B 22 -15 589 464 ;
+C 118 ; WX 537 ; N v ; B -6 -10 515 464 ;
+C 119 ; WX 778 ; N w ; B 1 -10 749 464 ;
+C 120 ; WX 537 ; N x ; B 8 0 529 464 ;
+C 121 ; WX 537 ; N y ; B 4 -205 533 464 ;
+C 122 ; WX 481 ; N z ; B 42 0 439 464 ;
+C 123 ; WX 333 ; N braceleft ; B 54 -109 279 737 ;
+C 124 ; WX 606 ; N bar ; B 267 -250 339 750 ;
+C 125 ; WX 333 ; N braceright ; B 54 -109 279 737 ;
+C 126 ; WX 606 ; N asciitilde ; B 72 184 534 322 ;
+C 161 ; WX 296 ; N exclamdown ; B 86 -205 210 547 ;
+C 162 ; WX 556 ; N cent ; B 74 -141 482 584 ;
+C 163 ; WX 556 ; N sterling ; B 18 -15 538 705 ;
+C 164 ; WX 167 ; N fraction ; B -195 -15 362 705 ;
+C 165 ; WX 556 ; N yen ; B -1 0 557 690 ;
+C 166 ; WX 556 ; N florin ; B 0 -205 538 737 ;
+C 167 ; WX 500 ; N section ; B 55 -147 445 737 ;
+C 168 ; WX 556 ; N currency ; B 26 93 530 597 ;
+C 169 ; WX 204 ; N quotesingle ; B 59 443 145 737 ;
+C 170 ; WX 389 ; N quotedblleft ; B 25 443 364 737 ;
+C 171 ; WX 426 ; N guillemotleft ; B 39 78 387 398 ;
+C 172 ; WX 259 ; N guilsinglleft ; B 39 78 220 398 ;
+C 173 ; WX 259 ; N guilsinglright ; B 39 78 220 398 ;
+C 174 ; WX 611 ; N fi ; B 18 0 582 737 ;
+C 175 ; WX 611 ; N fl ; B 18 0 582 737 ;
+C 177 ; WX 556 ; N endash ; B 0 208 556 268 ;
+C 178 ; WX 500 ; N dagger ; B 42 -147 458 737 ;
+C 179 ; WX 500 ; N daggerdbl ; B 42 -149 458 737 ;
+C 180 ; WX 278 ; N periodcentered ; B 71 238 207 374 ;
+C 182 ; WX 606 ; N paragraph ; B 60 -132 546 722 ;
+C 183 ; WX 606 ; N bullet ; B 122 180 484 542 ;
+C 184 ; WX 204 ; N quotesinglbase ; B 25 -185 179 109 ;
+C 185 ; WX 389 ; N quotedblbase ; B 25 -185 364 109 ;
+C 186 ; WX 389 ; N quotedblright ; B 25 443 364 737 ;
+C 187 ; WX 426 ; N guillemotright ; B 39 78 387 398 ;
+C 188 ; WX 1000 ; N ellipsis ; B 105 -15 895 109 ;
+C 189 ; WX 1000 ; N perthousand ; B 6 -15 994 705 ;
+C 191 ; WX 444 ; N questiondown ; B 29 -205 415 547 ;
+C 193 ; WX 333 ; N grave ; B 17 528 242 699 ;
+C 194 ; WX 333 ; N acute ; B 91 528 316 699 ;
+C 195 ; WX 333 ; N circumflex ; B 10 528 323 695 ;
+C 196 ; WX 333 ; N tilde ; B 1 553 332 655 ;
+C 197 ; WX 333 ; N macron ; B 10 568 323 623 ;
+C 198 ; WX 333 ; N breve ; B 25 528 308 685 ;
+C 199 ; WX 333 ; N dotaccent ; B 116 543 218 645 ;
+C 200 ; WX 333 ; N dieresis ; B 16 543 317 645 ;
+C 202 ; WX 333 ; N ring ; B 66 522 266 722 ;
+C 203 ; WX 333 ; N cedilla ; B 29 -215 237 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B -9 528 416 699 ;
+C 206 ; WX 333 ; N ogonek ; B 68 -215 254 0 ;
+C 207 ; WX 333 ; N caron ; B 10 528 323 695 ;
+C 208 ; WX 1000 ; N emdash ; B 0 208 1000 268 ;
+C 225 ; WX 1000 ; N AE ; B 0 0 962 722 ;
+C 227 ; WX 334 ; N ordfeminine ; B -4 407 338 705 ;
+C 232 ; WX 667 ; N Lslash ; B 29 0 644 722 ;
+C 233 ; WX 778 ; N Oslash ; B 45 -56 733 778 ;
+C 234 ; WX 1000 ; N OE ; B 21 0 979 722 ;
+C 235 ; WX 300 ; N ordmasculine ; B 4 407 296 705 ;
+C 241 ; WX 796 ; N ae ; B 34 -15 762 479 ;
+C 245 ; WX 315 ; N dotlessi ; B 18 0 286 464 ;
+C 248 ; WX 315 ; N lslash ; B 18 0 286 737 ;
+C 249 ; WX 500 ; N oslash ; B 34 -97 466 561 ;
+C 250 ; WX 833 ; N oe ; B 34 -15 799 479 ;
+C 251 ; WX 574 ; N germandbls ; B 30 -15 537 737 ;
+C -1 ; WX 500 ; N ecircumflex ; B 34 -15 466 695 ;
+C -1 ; WX 500 ; N edieresis ; B 34 -15 466 645 ;
+C -1 ; WX 556 ; N aacute ; B 44 -15 542 699 ;
+C -1 ; WX 737 ; N registered ; B -8 -15 744 737 ;
+C -1 ; WX 315 ; N icircumflex ; B 1 0 314 695 ;
+C -1 ; WX 611 ; N udieresis ; B 22 -15 589 645 ;
+C -1 ; WX 500 ; N ograve ; B 34 -15 466 699 ;
+C -1 ; WX 611 ; N uacute ; B 22 -15 589 699 ;
+C -1 ; WX 611 ; N ucircumflex ; B 22 -15 589 695 ;
+C -1 ; WX 722 ; N Aacute ; B -8 0 730 937 ;
+C -1 ; WX 315 ; N igrave ; B 8 0 286 699 ;
+C -1 ; WX 407 ; N Icircumflex ; B 38 0 369 933 ;
+C -1 ; WX 444 ; N ccedilla ; B 34 -215 426 479 ;
+C -1 ; WX 556 ; N adieresis ; B 44 -15 542 645 ;
+C -1 ; WX 722 ; N Ecircumflex ; B 29 0 663 933 ;
+C -1 ; WX 463 ; N scaron ; B 46 -15 417 695 ;
+C -1 ; WX 574 ; N thorn ; B 22 -205 540 737 ;
+C -1 ; WX 1000 ; N trademark ; B 32 318 968 722 ;
+C -1 ; WX 500 ; N egrave ; B 34 -15 466 699 ;
+C -1 ; WX 333 ; N threesuperior ; B 18 273 315 705 ;
+C -1 ; WX 481 ; N zcaron ; B 42 0 439 695 ;
+C -1 ; WX 556 ; N atilde ; B 44 -15 542 655 ;
+C -1 ; WX 556 ; N aring ; B 44 -15 542 732 ;
+C -1 ; WX 500 ; N ocircumflex ; B 34 -15 466 695 ;
+C -1 ; WX 722 ; N Edieresis ; B 29 0 663 883 ;
+C -1 ; WX 834 ; N threequarters ; B 28 -15 795 705 ;
+C -1 ; WX 537 ; N ydieresis ; B 4 -205 533 645 ;
+C -1 ; WX 537 ; N yacute ; B 4 -205 533 699 ;
+C -1 ; WX 315 ; N iacute ; B 18 0 307 699 ;
+C -1 ; WX 722 ; N Acircumflex ; B -8 0 730 933 ;
+C -1 ; WX 815 ; N Uacute ; B 16 -15 799 937 ;
+C -1 ; WX 500 ; N eacute ; B 34 -15 466 699 ;
+C -1 ; WX 778 ; N Ograve ; B 45 -15 733 937 ;
+C -1 ; WX 556 ; N agrave ; B 44 -15 542 699 ;
+C -1 ; WX 815 ; N Udieresis ; B 16 -15 799 883 ;
+C -1 ; WX 556 ; N acircumflex ; B 44 -15 542 695 ;
+C -1 ; WX 407 ; N Igrave ; B 38 0 369 937 ;
+C -1 ; WX 333 ; N twosuperior ; B 14 282 319 705 ;
+C -1 ; WX 815 ; N Ugrave ; B 16 -15 799 937 ;
+C -1 ; WX 834 ; N onequarter ; B 39 -15 795 705 ;
+C -1 ; WX 815 ; N Ucircumflex ; B 16 -15 799 933 ;
+C -1 ; WX 630 ; N Scaron ; B 47 -15 583 933 ;
+C -1 ; WX 407 ; N Idieresis ; B 38 0 369 883 ;
+C -1 ; WX 315 ; N idieresis ; B 7 0 308 645 ;
+C -1 ; WX 722 ; N Egrave ; B 29 0 663 937 ;
+C -1 ; WX 778 ; N Oacute ; B 45 -15 733 937 ;
+C -1 ; WX 606 ; N divide ; B 50 -22 556 528 ;
+C -1 ; WX 722 ; N Atilde ; B -8 0 730 893 ;
+C -1 ; WX 722 ; N Aring ; B -8 0 730 965 ;
+C -1 ; WX 778 ; N Odieresis ; B 45 -15 733 883 ;
+C -1 ; WX 722 ; N Adieresis ; B -8 0 730 883 ;
+C -1 ; WX 815 ; N Ntilde ; B 24 -15 791 893 ;
+C -1 ; WX 611 ; N Zcaron ; B 24 0 576 933 ;
+C -1 ; WX 667 ; N Thorn ; B 29 0 650 722 ;
+C -1 ; WX 407 ; N Iacute ; B 38 0 369 937 ;
+C -1 ; WX 606 ; N plusminus ; B 50 0 556 506 ;
+C -1 ; WX 606 ; N multiply ; B 74 24 532 482 ;
+C -1 ; WX 722 ; N Eacute ; B 29 0 663 937 ;
+C -1 ; WX 704 ; N Ydieresis ; B -11 0 715 883 ;
+C -1 ; WX 333 ; N onesuperior ; B 39 282 294 705 ;
+C -1 ; WX 611 ; N ugrave ; B 22 -15 589 699 ;
+C -1 ; WX 606 ; N logicalnot ; B 50 108 556 389 ;
+C -1 ; WX 611 ; N ntilde ; B 22 0 589 655 ;
+C -1 ; WX 778 ; N Otilde ; B 45 -15 733 893 ;
+C -1 ; WX 500 ; N otilde ; B 34 -15 466 655 ;
+C -1 ; WX 722 ; N Ccedilla ; B 45 -215 668 737 ;
+C -1 ; WX 722 ; N Agrave ; B -8 0 730 937 ;
+C -1 ; WX 834 ; N onehalf ; B 39 -15 820 705 ;
+C -1 ; WX 778 ; N Eth ; B 29 0 733 722 ;
+C -1 ; WX 400 ; N degree ; B 57 419 343 705 ;
+C -1 ; WX 704 ; N Yacute ; B -11 0 715 937 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 45 -15 733 933 ;
+C -1 ; WX 500 ; N oacute ; B 34 -15 466 699 ;
+C -1 ; WX 611 ; N mu ; B 22 -205 589 464 ;
+C -1 ; WX 606 ; N minus ; B 50 217 556 289 ;
+C -1 ; WX 500 ; N eth ; B 34 -15 466 752 ;
+C -1 ; WX 500 ; N odieresis ; B 34 -15 466 645 ;
+C -1 ; WX 737 ; N copyright ; B -8 -15 744 737 ;
+C -1 ; WX 606 ; N brokenbar ; B 267 -175 339 675 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 169
+
+KPX A y -37
+KPX A w -25
+KPX A v -37
+KPX A quoteright -74
+KPX A quotedblright -74
+KPX A Y -75
+KPX A W -50
+KPX A V -75
+KPX A U -30
+KPX A T -18
+
+KPX B period -37
+KPX B comma -37
+KPX B A -18
+
+KPX C period -37
+KPX C comma -37
+KPX C A -18
+
+KPX D period -37
+KPX D comma -37
+KPX D Y -18
+KPX D V -18
+
+KPX F r -10
+KPX F period -125
+KPX F o -55
+KPX F i -10
+KPX F e -55
+KPX F comma -125
+KPX F a -65
+KPX F A -50
+
+KPX G period -37
+KPX G comma -37
+
+KPX J u -25
+KPX J period -74
+KPX J o -25
+KPX J e -25
+KPX J comma -74
+KPX J a -25
+KPX J A -18
+
+KPX K y -25
+KPX K o 10
+KPX K e 10
+
+KPX L y -25
+KPX L quoteright -100
+KPX L quotedblright -100
+KPX L Y -74
+KPX L W -74
+KPX L V -91
+KPX L T -75
+
+KPX N period -55
+KPX N comma -55
+
+KPX O period -37
+KPX O comma -37
+KPX O Y -18
+KPX O V -18
+KPX O T 10
+
+KPX P period -125
+KPX P o -37
+KPX P e -37
+KPX P comma -125
+KPX P a -37
+KPX P A -55
+
+KPX Q period -25
+KPX Q comma -25
+
+KPX S period -37
+KPX S comma -37
+
+KPX T semicolon -37
+KPX T period -125
+KPX T o -55
+KPX T hyphen -100
+KPX T e -55
+KPX T comma -125
+KPX T colon -37
+KPX T a -55
+KPX T O 10
+KPX T A -18
+
+KPX U period -100
+KPX U comma -100
+KPX U A -30
+
+KPX V u -75
+KPX V semicolon -75
+KPX V period -125
+KPX V o -75
+KPX V i -18
+KPX V hyphen -100
+KPX V e -75
+KPX V comma -125
+KPX V colon -75
+KPX V a -85
+KPX V O -18
+KPX V A -74
+
+KPX W y -55
+KPX W u -55
+KPX W semicolon -100
+KPX W period -125
+KPX W o -60
+KPX W i -18
+KPX W hyphen -100
+KPX W e -60
+KPX W comma -125
+KPX W colon -100
+KPX W a -75
+KPX W A -50
+
+KPX Y u -91
+KPX Y semicolon -75
+KPX Y period -100
+KPX Y o -100
+KPX Y i -18
+KPX Y hyphen -125
+KPX Y e -100
+KPX Y comma -100
+KPX Y colon -75
+KPX Y a -100
+KPX Y O -18
+KPX Y A -75
+
+KPX a y -10
+KPX a w -10
+KPX a v -10
+
+KPX b period -18
+KPX b comma -18
+
+KPX c period -18
+KPX c l -7
+KPX c k -7
+KPX c h -7
+KPX c comma -18
+
+KPX colon space -37
+
+KPX comma space -37
+KPX comma quoteright -37
+KPX comma quotedblright -37
+
+KPX e period -18
+KPX e comma -18
+
+KPX f quoteright 100
+KPX f quotedblright 100
+KPX f period -37
+KPX f comma -37
+
+KPX g period -25
+KPX g comma -25
+
+KPX o period -18
+KPX o comma -18
+
+KPX p period -18
+KPX p comma -18
+
+KPX period space -37
+KPX period quoteright -37
+KPX period quotedblright -37
+
+KPX quotedblleft A -74
+
+KPX quotedblright space -37
+
+KPX quoteleft quoteleft -25
+KPX quoteleft A -74
+
+KPX quoteright s -25
+KPX quoteright quoteright -25
+KPX quoteright d -37
+
+KPX r period -100
+KPX r hyphen -37
+KPX r comma -100
+
+KPX s period -25
+KPX s comma -25
+
+KPX semicolon space -37
+
+KPX space quoteleft -37
+KPX space quotedblleft -37
+KPX space Y -37
+KPX space W -37
+KPX space V -37
+KPX space T -37
+KPX space A -37
+
+KPX v period -125
+KPX v comma -125
+
+KPX w period -125
+KPX w comma -125
+KPX w a -18
+
+KPX y period -125
+KPX y comma -125
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 195 238 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 195 238 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 195 238 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 195 238 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 195 243 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 195 238 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 195 238 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 195 238 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 195 238 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 195 238 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 37 238 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 37 238 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 37 238 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 37 238 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 241 238 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 223 238 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 223 238 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 223 238 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 223 238 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 223 238 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 149 238 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 241 238 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 241 238 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 241 238 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 241 238 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 216 238 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 186 238 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 139 238 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 112 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 112 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 112 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 112 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 112 10 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 112 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 84 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 84 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 84 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 84 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -9 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -9 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -9 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -9 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 139 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 84 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 84 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 84 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 84 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 84 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 65 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 139 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 139 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 139 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 139 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 102 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 102 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 74 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncri8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncri8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..6dfd6a254d46823cca6baad550bb435a7467ec9d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pncri8a.afm
@@ -0,0 +1,536 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue May 28 16:40:04 1991
+Comment UniqueID 35028
+Comment VMusage 31423 38315
+FontName NewCenturySchlbk-Italic
+FullName New Century Schoolbook Italic
+FamilyName New Century Schoolbook
+Weight Medium
+ItalicAngle -16
+IsFixedPitch false
+FontBBox -166 -250 994 958
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.006
+Notice Copyright (c) 1985, 1987, 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 722
+XHeight 466
+Ascender 737
+Descender -205
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 17 -15 303 737 ;
+C 34 ; WX 400 ; N quotedbl ; B 127 463 363 737 ;
+C 35 ; WX 556 ; N numbersign ; B 28 0 528 690 ;
+C 36 ; WX 556 ; N dollar ; B 4 -142 536 808 ;
+C 37 ; WX 833 ; N percent ; B 43 -15 790 705 ;
+C 38 ; WX 852 ; N ampersand ; B 24 -15 773 737 ;
+C 39 ; WX 204 ; N quoteright ; B 39 463 229 737 ;
+C 40 ; WX 333 ; N parenleft ; B 53 -117 411 745 ;
+C 41 ; WX 333 ; N parenright ; B -93 -117 265 745 ;
+C 42 ; WX 500 ; N asterisk ; B 80 318 500 737 ;
+C 43 ; WX 606 ; N plus ; B 50 0 556 506 ;
+C 44 ; WX 278 ; N comma ; B -39 -165 151 109 ;
+C 45 ; WX 333 ; N hyphen ; B 32 202 259 274 ;
+C 46 ; WX 278 ; N period ; B 17 -15 141 109 ;
+C 47 ; WX 606 ; N slash ; B 132 -15 474 737 ;
+C 48 ; WX 556 ; N zero ; B 30 -15 526 705 ;
+C 49 ; WX 556 ; N one ; B 50 0 459 705 ;
+C 50 ; WX 556 ; N two ; B -37 0 506 705 ;
+C 51 ; WX 556 ; N three ; B -2 -15 506 705 ;
+C 52 ; WX 556 ; N four ; B -8 0 512 705 ;
+C 53 ; WX 556 ; N five ; B 4 -15 540 705 ;
+C 54 ; WX 556 ; N six ; B 36 -15 548 705 ;
+C 55 ; WX 556 ; N seven ; B 69 -15 561 705 ;
+C 56 ; WX 556 ; N eight ; B 6 -15 526 705 ;
+C 57 ; WX 556 ; N nine ; B 8 -15 520 705 ;
+C 58 ; WX 278 ; N colon ; B 17 -15 229 466 ;
+C 59 ; WX 278 ; N semicolon ; B -39 -165 229 466 ;
+C 60 ; WX 606 ; N less ; B 36 -8 542 514 ;
+C 61 ; WX 606 ; N equal ; B 50 117 556 389 ;
+C 62 ; WX 606 ; N greater ; B 64 -8 570 514 ;
+C 63 ; WX 444 ; N question ; B 102 -15 417 737 ;
+C 64 ; WX 747 ; N at ; B -2 -15 750 737 ;
+C 65 ; WX 704 ; N A ; B -87 0 668 737 ;
+C 66 ; WX 722 ; N B ; B -33 0 670 722 ;
+C 67 ; WX 722 ; N C ; B 40 -15 712 737 ;
+C 68 ; WX 778 ; N D ; B -33 0 738 722 ;
+C 69 ; WX 722 ; N E ; B -33 0 700 722 ;
+C 70 ; WX 667 ; N F ; B -33 0 700 722 ;
+C 71 ; WX 778 ; N G ; B 40 -15 763 737 ;
+C 72 ; WX 833 ; N H ; B -33 0 866 722 ;
+C 73 ; WX 407 ; N I ; B -33 0 435 722 ;
+C 74 ; WX 611 ; N J ; B -14 -15 651 722 ;
+C 75 ; WX 741 ; N K ; B -33 0 816 722 ;
+C 76 ; WX 667 ; N L ; B -33 0 627 722 ;
+C 77 ; WX 944 ; N M ; B -33 0 977 722 ;
+C 78 ; WX 815 ; N N ; B -51 -15 866 722 ;
+C 79 ; WX 778 ; N O ; B 40 -15 738 737 ;
+C 80 ; WX 667 ; N P ; B -33 0 667 722 ;
+C 81 ; WX 778 ; N Q ; B 40 -190 738 737 ;
+C 82 ; WX 741 ; N R ; B -45 -15 692 722 ;
+C 83 ; WX 667 ; N S ; B -6 -15 638 737 ;
+C 84 ; WX 685 ; N T ; B 40 0 725 722 ;
+C 85 ; WX 815 ; N U ; B 93 -15 867 722 ;
+C 86 ; WX 704 ; N V ; B 36 -10 779 722 ;
+C 87 ; WX 926 ; N W ; B 53 -10 978 722 ;
+C 88 ; WX 704 ; N X ; B -75 0 779 722 ;
+C 89 ; WX 685 ; N Y ; B 31 0 760 722 ;
+C 90 ; WX 667 ; N Z ; B -25 0 667 722 ;
+C 91 ; WX 333 ; N bracketleft ; B -55 -109 388 737 ;
+C 92 ; WX 606 ; N backslash ; B 132 -15 474 737 ;
+C 93 ; WX 333 ; N bracketright ; B -77 -109 366 737 ;
+C 94 ; WX 606 ; N asciicircum ; B 89 325 517 690 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 204 ; N quoteleft ; B 39 463 229 737 ;
+C 97 ; WX 574 ; N a ; B 2 -15 524 466 ;
+C 98 ; WX 556 ; N b ; B 32 -15 488 737 ;
+C 99 ; WX 444 ; N c ; B 2 -15 394 466 ;
+C 100 ; WX 611 ; N d ; B 2 -15 585 737 ;
+C 101 ; WX 444 ; N e ; B -6 -15 388 466 ;
+C 102 ; WX 333 ; N f ; B -68 -205 470 737 ; L i fi ; L l fl ;
+C 103 ; WX 537 ; N g ; B -79 -205 523 497 ;
+C 104 ; WX 611 ; N h ; B 14 -15 562 737 ;
+C 105 ; WX 333 ; N i ; B 29 -15 282 715 ;
+C 106 ; WX 315 ; N j ; B -166 -205 318 715 ;
+C 107 ; WX 556 ; N k ; B 0 -15 497 737 ;
+C 108 ; WX 333 ; N l ; B 14 -15 292 737 ;
+C 109 ; WX 889 ; N m ; B 14 -15 840 466 ;
+C 110 ; WX 611 ; N n ; B 14 -15 562 466 ;
+C 111 ; WX 500 ; N o ; B 2 -15 450 466 ;
+C 112 ; WX 574 ; N p ; B -101 -205 506 466 ;
+C 113 ; WX 556 ; N q ; B 2 -205 500 466 ;
+C 114 ; WX 444 ; N r ; B 10 0 434 466 ;
+C 115 ; WX 444 ; N s ; B 2 -15 394 466 ;
+C 116 ; WX 352 ; N t ; B 24 -15 328 619 ;
+C 117 ; WX 611 ; N u ; B 44 -15 556 466 ;
+C 118 ; WX 519 ; N v ; B 31 -15 447 466 ;
+C 119 ; WX 778 ; N w ; B 31 -15 706 466 ;
+C 120 ; WX 500 ; N x ; B -33 -15 471 466 ;
+C 121 ; WX 500 ; N y ; B -83 -205 450 466 ;
+C 122 ; WX 463 ; N z ; B -33 -15 416 466 ;
+C 123 ; WX 333 ; N braceleft ; B 38 -109 394 737 ;
+C 124 ; WX 606 ; N bar ; B 267 -250 339 750 ;
+C 125 ; WX 333 ; N braceright ; B -87 -109 269 737 ;
+C 126 ; WX 606 ; N asciitilde ; B 72 184 534 322 ;
+C 161 ; WX 333 ; N exclamdown ; B -22 -205 264 547 ;
+C 162 ; WX 556 ; N cent ; B 62 -144 486 580 ;
+C 163 ; WX 556 ; N sterling ; B -13 -15 544 705 ;
+C 164 ; WX 167 ; N fraction ; B -134 -15 301 705 ;
+C 165 ; WX 556 ; N yen ; B 40 0 624 690 ;
+C 166 ; WX 556 ; N florin ; B -58 -205 569 737 ;
+C 167 ; WX 500 ; N section ; B -10 -147 480 737 ;
+C 168 ; WX 556 ; N currency ; B 26 93 530 597 ;
+C 169 ; WX 278 ; N quotesingle ; B 151 463 237 737 ;
+C 170 ; WX 389 ; N quotedblleft ; B 39 463 406 737 ;
+C 171 ; WX 426 ; N guillemotleft ; B -15 74 402 402 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 40 74 259 402 ;
+C 173 ; WX 333 ; N guilsinglright ; B 40 74 259 402 ;
+C 174 ; WX 611 ; N fi ; B -68 -205 555 737 ;
+C 175 ; WX 611 ; N fl ; B -68 -205 587 737 ;
+C 177 ; WX 500 ; N endash ; B -27 208 487 268 ;
+C 178 ; WX 500 ; N dagger ; B 51 -147 506 737 ;
+C 179 ; WX 500 ; N daggerdbl ; B -54 -147 506 737 ;
+C 180 ; WX 278 ; N periodcentered ; B 71 238 207 374 ;
+C 182 ; WX 650 ; N paragraph ; B 48 -132 665 722 ;
+C 183 ; WX 606 ; N bullet ; B 122 180 484 542 ;
+C 184 ; WX 204 ; N quotesinglbase ; B -78 -165 112 109 ;
+C 185 ; WX 389 ; N quotedblbase ; B -78 -165 289 109 ;
+C 186 ; WX 389 ; N quotedblright ; B 39 463 406 737 ;
+C 187 ; WX 426 ; N guillemotright ; B -15 74 402 402 ;
+C 188 ; WX 1000 ; N ellipsis ; B 59 -15 849 109 ;
+C 189 ; WX 1000 ; N perthousand ; B 6 -15 994 705 ;
+C 191 ; WX 444 ; N questiondown ; B -3 -205 312 547 ;
+C 193 ; WX 333 ; N grave ; B 71 518 262 690 ;
+C 194 ; WX 333 ; N acute ; B 132 518 355 690 ;
+C 195 ; WX 333 ; N circumflex ; B 37 518 331 690 ;
+C 196 ; WX 333 ; N tilde ; B 52 547 383 649 ;
+C 197 ; WX 333 ; N macron ; B 52 560 363 610 ;
+C 198 ; WX 333 ; N breve ; B 69 518 370 677 ;
+C 199 ; WX 333 ; N dotaccent ; B 146 544 248 646 ;
+C 200 ; WX 333 ; N dieresis ; B 59 544 359 646 ;
+C 202 ; WX 333 ; N ring ; B 114 512 314 712 ;
+C 203 ; WX 333 ; N cedilla ; B 3 -215 215 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 32 518 455 690 ;
+C 206 ; WX 333 ; N ogonek ; B 68 -215 254 0 ;
+C 207 ; WX 333 ; N caron ; B 73 518 378 690 ;
+C 208 ; WX 1000 ; N emdash ; B -27 208 987 268 ;
+C 225 ; WX 870 ; N AE ; B -87 0 888 722 ;
+C 227 ; WX 422 ; N ordfeminine ; B 72 416 420 705 ;
+C 232 ; WX 667 ; N Lslash ; B -33 0 627 722 ;
+C 233 ; WX 778 ; N Oslash ; B 16 -68 748 780 ;
+C 234 ; WX 981 ; N OE ; B 40 0 975 722 ;
+C 235 ; WX 372 ; N ordmasculine ; B 66 416 370 705 ;
+C 241 ; WX 722 ; N ae ; B -18 -15 666 466 ;
+C 245 ; WX 333 ; N dotlessi ; B 29 -15 282 466 ;
+C 248 ; WX 333 ; N lslash ; B -25 -15 340 737 ;
+C 249 ; WX 500 ; N oslash ; B 2 -121 450 549 ;
+C 250 ; WX 778 ; N oe ; B 2 -15 722 466 ;
+C 251 ; WX 556 ; N germandbls ; B -76 -205 525 737 ;
+C -1 ; WX 444 ; N ecircumflex ; B -6 -15 388 690 ;
+C -1 ; WX 444 ; N edieresis ; B -6 -15 415 646 ;
+C -1 ; WX 574 ; N aacute ; B 2 -15 524 690 ;
+C -1 ; WX 747 ; N registered ; B -2 -15 750 737 ;
+C -1 ; WX 333 ; N icircumflex ; B 29 -15 331 690 ;
+C -1 ; WX 611 ; N udieresis ; B 44 -15 556 646 ;
+C -1 ; WX 500 ; N ograve ; B 2 -15 450 690 ;
+C -1 ; WX 611 ; N uacute ; B 44 -15 556 690 ;
+C -1 ; WX 611 ; N ucircumflex ; B 44 -15 556 690 ;
+C -1 ; WX 704 ; N Aacute ; B -87 0 668 946 ;
+C -1 ; WX 333 ; N igrave ; B 29 -15 282 690 ;
+C -1 ; WX 407 ; N Icircumflex ; B -33 0 435 946 ;
+C -1 ; WX 444 ; N ccedilla ; B 2 -215 394 466 ;
+C -1 ; WX 574 ; N adieresis ; B 2 -15 524 646 ;
+C -1 ; WX 722 ; N Ecircumflex ; B -33 0 700 946 ;
+C -1 ; WX 444 ; N scaron ; B 2 -15 434 690 ;
+C -1 ; WX 574 ; N thorn ; B -101 -205 506 737 ;
+C -1 ; WX 950 ; N trademark ; B 32 318 968 722 ;
+C -1 ; WX 444 ; N egrave ; B -6 -15 388 690 ;
+C -1 ; WX 333 ; N threesuperior ; B 22 273 359 705 ;
+C -1 ; WX 463 ; N zcaron ; B -33 -15 443 690 ;
+C -1 ; WX 574 ; N atilde ; B 2 -15 524 649 ;
+C -1 ; WX 574 ; N aring ; B 2 -15 524 712 ;
+C -1 ; WX 500 ; N ocircumflex ; B 2 -15 450 690 ;
+C -1 ; WX 722 ; N Edieresis ; B -33 0 700 902 ;
+C -1 ; WX 834 ; N threequarters ; B 22 -15 782 705 ;
+C -1 ; WX 500 ; N ydieresis ; B -83 -205 450 646 ;
+C -1 ; WX 500 ; N yacute ; B -83 -205 450 690 ;
+C -1 ; WX 333 ; N iacute ; B 29 -15 355 690 ;
+C -1 ; WX 704 ; N Acircumflex ; B -87 0 668 946 ;
+C -1 ; WX 815 ; N Uacute ; B 93 -15 867 946 ;
+C -1 ; WX 444 ; N eacute ; B -6 -15 411 690 ;
+C -1 ; WX 778 ; N Ograve ; B 40 -15 738 946 ;
+C -1 ; WX 574 ; N agrave ; B 2 -15 524 690 ;
+C -1 ; WX 815 ; N Udieresis ; B 93 -15 867 902 ;
+C -1 ; WX 574 ; N acircumflex ; B 2 -15 524 690 ;
+C -1 ; WX 407 ; N Igrave ; B -33 0 435 946 ;
+C -1 ; WX 333 ; N twosuperior ; B 0 282 359 705 ;
+C -1 ; WX 815 ; N Ugrave ; B 93 -15 867 946 ;
+C -1 ; WX 834 ; N onequarter ; B 34 -15 782 705 ;
+C -1 ; WX 815 ; N Ucircumflex ; B 93 -15 867 946 ;
+C -1 ; WX 667 ; N Scaron ; B -6 -15 638 946 ;
+C -1 ; WX 407 ; N Idieresis ; B -33 0 456 902 ;
+C -1 ; WX 333 ; N idieresis ; B 29 -15 359 646 ;
+C -1 ; WX 722 ; N Egrave ; B -33 0 700 946 ;
+C -1 ; WX 778 ; N Oacute ; B 40 -15 738 946 ;
+C -1 ; WX 606 ; N divide ; B 50 -22 556 528 ;
+C -1 ; WX 704 ; N Atilde ; B -87 0 668 905 ;
+C -1 ; WX 704 ; N Aring ; B -87 0 668 958 ;
+C -1 ; WX 778 ; N Odieresis ; B 40 -15 738 902 ;
+C -1 ; WX 704 ; N Adieresis ; B -87 0 668 902 ;
+C -1 ; WX 815 ; N Ntilde ; B -51 -15 866 905 ;
+C -1 ; WX 667 ; N Zcaron ; B -25 0 667 946 ;
+C -1 ; WX 667 ; N Thorn ; B -33 0 627 722 ;
+C -1 ; WX 407 ; N Iacute ; B -33 0 452 946 ;
+C -1 ; WX 606 ; N plusminus ; B 50 0 556 506 ;
+C -1 ; WX 606 ; N multiply ; B 74 24 532 482 ;
+C -1 ; WX 722 ; N Eacute ; B -33 0 700 946 ;
+C -1 ; WX 685 ; N Ydieresis ; B 31 0 760 902 ;
+C -1 ; WX 333 ; N onesuperior ; B 34 282 311 705 ;
+C -1 ; WX 611 ; N ugrave ; B 44 -15 556 690 ;
+C -1 ; WX 606 ; N logicalnot ; B 50 108 556 389 ;
+C -1 ; WX 611 ; N ntilde ; B 14 -15 562 649 ;
+C -1 ; WX 778 ; N Otilde ; B 40 -15 738 905 ;
+C -1 ; WX 500 ; N otilde ; B 2 -15 467 649 ;
+C -1 ; WX 722 ; N Ccedilla ; B 40 -215 712 737 ;
+C -1 ; WX 704 ; N Agrave ; B -87 0 668 946 ;
+C -1 ; WX 834 ; N onehalf ; B 34 -15 776 705 ;
+C -1 ; WX 778 ; N Eth ; B -33 0 738 722 ;
+C -1 ; WX 400 ; N degree ; B 86 419 372 705 ;
+C -1 ; WX 685 ; N Yacute ; B 31 0 760 946 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 40 -15 738 946 ;
+C -1 ; WX 500 ; N oacute ; B 2 -15 450 690 ;
+C -1 ; WX 611 ; N mu ; B -60 -205 556 466 ;
+C -1 ; WX 606 ; N minus ; B 50 217 556 289 ;
+C -1 ; WX 500 ; N eth ; B 2 -15 450 737 ;
+C -1 ; WX 500 ; N odieresis ; B 2 -15 450 646 ;
+C -1 ; WX 747 ; N copyright ; B -2 -15 750 737 ;
+C -1 ; WX 606 ; N brokenbar ; B 267 -175 339 675 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 181
+
+KPX A y -55
+KPX A w -18
+KPX A v -18
+KPX A u -18
+KPX A quoteright -125
+KPX A quotedblright -125
+KPX A Y -55
+KPX A W -74
+KPX A V -74
+KPX A U -37
+KPX A T -30
+KPX A Q -18
+KPX A O -18
+KPX A G -18
+KPX A C -18
+
+KPX B period -50
+KPX B comma -50
+
+KPX C period -50
+KPX C comma -50
+
+KPX D period -50
+KPX D comma -50
+KPX D Y -18
+KPX D W -18
+KPX D V -18
+
+KPX F r -55
+KPX F period -125
+KPX F o -55
+KPX F i -10
+KPX F e -55
+KPX F comma -125
+KPX F a -55
+KPX F A -35
+
+KPX G period -50
+KPX G comma -50
+
+KPX J u -18
+KPX J period -100
+KPX J o -37
+KPX J e -37
+KPX J comma -100
+KPX J a -37
+KPX J A -18
+
+KPX L y -50
+KPX L quoteright -125
+KPX L quotedblright -125
+KPX L Y -100
+KPX L W -100
+KPX L V -100
+KPX L T -100
+
+KPX N period -60
+KPX N comma -60
+
+KPX O period -50
+KPX O comma -50
+KPX O Y -18
+KPX O X -18
+KPX O V -18
+KPX O T 18
+
+KPX P period -125
+KPX P o -55
+KPX P e -55
+KPX P comma -125
+KPX P a -55
+KPX P A -50
+
+KPX Q period -20
+KPX Q comma -20
+
+KPX R Y -18
+KPX R W -18
+KPX R V -18
+KPX R U -18
+
+KPX S period -50
+KPX S comma -50
+
+KPX T y -50
+KPX T w -50
+KPX T u -50
+KPX T semicolon -50
+KPX T r -50
+KPX T period -100
+KPX T o -74
+KPX T i -18
+KPX T hyphen -100
+KPX T h -25
+KPX T e -74
+KPX T comma -100
+KPX T colon -50
+KPX T a -74
+KPX T O 18
+
+KPX U period -100
+KPX U comma -100
+KPX U A -18
+
+KPX V u -75
+KPX V semicolon -75
+KPX V period -100
+KPX V o -75
+KPX V i -50
+KPX V hyphen -100
+KPX V e -75
+KPX V comma -100
+KPX V colon -75
+KPX V a -75
+KPX V A -37
+
+KPX W y -55
+KPX W u -55
+KPX W semicolon -75
+KPX W period -100
+KPX W o -55
+KPX W i -20
+KPX W hyphen -75
+KPX W h -20
+KPX W e -55
+KPX W comma -100
+KPX W colon -75
+KPX W a -55
+KPX W A -55
+
+KPX Y u -100
+KPX Y semicolon -75
+KPX Y period -100
+KPX Y o -100
+KPX Y i -25
+KPX Y hyphen -100
+KPX Y e -100
+KPX Y comma -100
+KPX Y colon -75
+KPX Y a -100
+KPX Y A -55
+
+KPX b period -50
+KPX b comma -50
+KPX b b -10
+
+KPX c period -50
+KPX c k -18
+KPX c h -18
+KPX c comma -50
+
+KPX colon space -37
+
+KPX comma space -37
+KPX comma quoteright -37
+KPX comma quotedblright -37
+
+KPX e period -37
+KPX e comma -37
+
+KPX f quoteright 75
+KPX f quotedblright 75
+KPX f period -75
+KPX f o -10
+KPX f comma -75
+
+KPX g period -50
+KPX g comma -50
+
+KPX l y -10
+
+KPX o period -50
+KPX o comma -50
+
+KPX p period -50
+KPX p comma -50
+
+KPX period space -37
+KPX period quoteright -37
+KPX period quotedblright -37
+
+KPX quotedblleft A -75
+
+KPX quotedblright space -37
+
+KPX quoteleft quoteleft -37
+KPX quoteleft A -75
+
+KPX quoteright s -25
+KPX quoteright quoteright -37
+KPX quoteright d -37
+
+KPX r semicolon -25
+KPX r s -10
+KPX r period -125
+KPX r k -18
+KPX r hyphen -75
+KPX r comma -125
+KPX r colon -25
+
+KPX s period -50
+KPX s comma -50
+
+KPX semicolon space -37
+
+KPX space quoteleft -37
+KPX space quotedblleft -37
+KPX space Y -37
+KPX space W -37
+KPX space V -37
+KPX space T -37
+KPX space A -37
+
+KPX v period -75
+KPX v comma -75
+
+KPX w period -75
+KPX w comma -75
+
+KPX y period -75
+KPX y comma -75
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 246 256 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 246 256 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 231 256 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 246 256 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 216 246 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 231 256 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 255 256 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 255 256 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 255 256 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 255 256 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 97 256 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 97 256 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 97 256 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 97 256 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 301 256 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 283 256 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 283 256 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 283 256 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 283 256 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 283 256 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 227 256 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 301 256 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 301 256 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 301 256 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 301 256 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 256 256 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 236 256 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 227 256 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 121 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 121 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 121 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 121 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 121 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 121 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 56 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 56 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 56 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 56 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute 0 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex 0 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis 0 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave 0 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 139 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 84 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 84 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 84 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 84 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 84 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 56 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 139 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 139 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 139 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 139 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 84 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 84 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 65 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplb8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplb8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..de7698d29d18667ca1f9be02f58b23444bb3cc23
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplb8a.afm
@@ -0,0 +1,434 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Jul 2 22:26:30 1990
+Comment UniqueID 31793
+Comment VMusage 36031 46923
+FontName Palatino-Bold
+FullName Palatino Bold
+FamilyName Palatino
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -152 -266 1000 924
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.005
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Palatino is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 681
+XHeight 471
+Ascender 720
+Descender -258
+StartCharMetrics 228
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 63 -12 219 688 ;
+C 34 ; WX 402 ; N quotedbl ; B 22 376 380 695 ;
+C 35 ; WX 500 ; N numbersign ; B 4 0 496 673 ;
+C 36 ; WX 500 ; N dollar ; B 28 -114 472 721 ;
+C 37 ; WX 889 ; N percent ; B 61 -9 828 714 ;
+C 38 ; WX 833 ; N ampersand ; B 52 -17 813 684 ;
+C 39 ; WX 278 ; N quoteright ; B 29 405 249 695 ;
+C 40 ; WX 333 ; N parenleft ; B 65 -104 305 723 ;
+C 41 ; WX 333 ; N parenright ; B 28 -104 268 723 ;
+C 42 ; WX 444 ; N asterisk ; B 44 332 399 695 ;
+C 43 ; WX 606 ; N plus ; B 51 0 555 505 ;
+C 44 ; WX 250 ; N comma ; B -6 -166 227 141 ;
+C 45 ; WX 333 ; N hyphen ; B 16 195 317 305 ;
+C 46 ; WX 250 ; N period ; B 47 -12 203 144 ;
+C 47 ; WX 296 ; N slash ; B -9 -17 305 720 ;
+C 48 ; WX 500 ; N zero ; B 33 -17 468 660 ;
+C 49 ; WX 500 ; N one ; B 35 -3 455 670 ;
+C 50 ; WX 500 ; N two ; B 25 -3 472 660 ;
+C 51 ; WX 500 ; N three ; B 22 -17 458 660 ;
+C 52 ; WX 500 ; N four ; B 12 -3 473 672 ;
+C 53 ; WX 500 ; N five ; B 42 -17 472 656 ;
+C 54 ; WX 500 ; N six ; B 37 -17 469 660 ;
+C 55 ; WX 500 ; N seven ; B 46 -3 493 656 ;
+C 56 ; WX 500 ; N eight ; B 34 -17 467 660 ;
+C 57 ; WX 500 ; N nine ; B 31 -17 463 660 ;
+C 58 ; WX 250 ; N colon ; B 47 -12 203 454 ;
+C 59 ; WX 250 ; N semicolon ; B -6 -166 227 454 ;
+C 60 ; WX 606 ; N less ; B 49 -15 558 519 ;
+C 61 ; WX 606 ; N equal ; B 51 114 555 396 ;
+C 62 ; WX 606 ; N greater ; B 49 -15 558 519 ;
+C 63 ; WX 444 ; N question ; B 43 -12 411 687 ;
+C 64 ; WX 747 ; N at ; B 42 -12 704 681 ;
+C 65 ; WX 778 ; N A ; B 24 -3 757 686 ;
+C 66 ; WX 667 ; N B ; B 39 -3 611 681 ;
+C 67 ; WX 722 ; N C ; B 44 -17 695 695 ;
+C 68 ; WX 833 ; N D ; B 35 -3 786 681 ;
+C 69 ; WX 611 ; N E ; B 39 -4 577 681 ;
+C 70 ; WX 556 ; N F ; B 28 -3 539 681 ;
+C 71 ; WX 833 ; N G ; B 47 -17 776 695 ;
+C 72 ; WX 833 ; N H ; B 36 -3 796 681 ;
+C 73 ; WX 389 ; N I ; B 39 -3 350 681 ;
+C 74 ; WX 389 ; N J ; B -11 -213 350 681 ;
+C 75 ; WX 778 ; N K ; B 39 -3 763 681 ;
+C 76 ; WX 611 ; N L ; B 39 -4 577 681 ;
+C 77 ; WX 1000 ; N M ; B 32 -10 968 681 ;
+C 78 ; WX 833 ; N N ; B 35 -16 798 681 ;
+C 79 ; WX 833 ; N O ; B 47 -17 787 695 ;
+C 80 ; WX 611 ; N P ; B 39 -3 594 681 ;
+C 81 ; WX 833 ; N Q ; B 47 -184 787 695 ;
+C 82 ; WX 722 ; N R ; B 39 -3 708 681 ;
+C 83 ; WX 611 ; N S ; B 57 -17 559 695 ;
+C 84 ; WX 667 ; N T ; B 17 -3 650 681 ;
+C 85 ; WX 778 ; N U ; B 26 -17 760 681 ;
+C 86 ; WX 778 ; N V ; B 20 -3 763 681 ;
+C 87 ; WX 1000 ; N W ; B 17 -3 988 686 ;
+C 88 ; WX 667 ; N X ; B 17 -3 650 695 ;
+C 89 ; WX 667 ; N Y ; B 15 -3 660 695 ;
+C 90 ; WX 667 ; N Z ; B 24 -3 627 681 ;
+C 91 ; WX 333 ; N bracketleft ; B 73 -104 291 720 ;
+C 92 ; WX 606 ; N backslash ; B 72 0 534 720 ;
+C 93 ; WX 333 ; N bracketright ; B 42 -104 260 720 ;
+C 94 ; WX 606 ; N asciicircum ; B 52 275 554 678 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 29 405 249 695 ;
+C 97 ; WX 500 ; N a ; B 40 -17 478 471 ;
+C 98 ; WX 611 ; N b ; B 10 -17 556 720 ;
+C 99 ; WX 444 ; N c ; B 37 -17 414 471 ;
+C 100 ; WX 611 ; N d ; B 42 -17 577 720 ;
+C 101 ; WX 500 ; N e ; B 42 -17 461 471 ;
+C 102 ; WX 389 ; N f ; B 34 -3 381 720 ; L i fi ; L l fl ;
+C 103 ; WX 556 ; N g ; B 26 -266 535 471 ;
+C 104 ; WX 611 ; N h ; B 24 -3 587 720 ;
+C 105 ; WX 333 ; N i ; B 34 -3 298 706 ;
+C 106 ; WX 333 ; N j ; B 3 -266 241 706 ;
+C 107 ; WX 611 ; N k ; B 21 -3 597 720 ;
+C 108 ; WX 333 ; N l ; B 24 -3 296 720 ;
+C 109 ; WX 889 ; N m ; B 24 -3 864 471 ;
+C 110 ; WX 611 ; N n ; B 24 -3 587 471 ;
+C 111 ; WX 556 ; N o ; B 40 -17 517 471 ;
+C 112 ; WX 611 ; N p ; B 29 -258 567 471 ;
+C 113 ; WX 611 ; N q ; B 52 -258 589 471 ;
+C 114 ; WX 389 ; N r ; B 30 -3 389 471 ;
+C 115 ; WX 444 ; N s ; B 39 -17 405 471 ;
+C 116 ; WX 333 ; N t ; B 22 -17 324 632 ;
+C 117 ; WX 611 ; N u ; B 25 -17 583 471 ;
+C 118 ; WX 556 ; N v ; B 11 -3 545 459 ;
+C 119 ; WX 833 ; N w ; B 13 -3 820 471 ;
+C 120 ; WX 500 ; N x ; B 20 -3 483 471 ;
+C 121 ; WX 556 ; N y ; B 10 -266 546 459 ;
+C 122 ; WX 500 ; N z ; B 16 -3 464 459 ;
+C 123 ; WX 310 ; N braceleft ; B 5 -117 288 725 ;
+C 124 ; WX 606 ; N bar ; B 260 0 346 720 ;
+C 125 ; WX 310 ; N braceright ; B 22 -117 305 725 ;
+C 126 ; WX 606 ; N asciitilde ; B 51 155 555 342 ;
+C 161 ; WX 278 ; N exclamdown ; B 59 -227 215 471 ;
+C 162 ; WX 500 ; N cent ; B 73 -106 450 554 ;
+C 163 ; WX 500 ; N sterling ; B -2 -19 501 676 ;
+C 164 ; WX 167 ; N fraction ; B -152 0 320 660 ;
+C 165 ; WX 500 ; N yen ; B 17 -3 483 695 ;
+C 166 ; WX 500 ; N florin ; B 11 -242 490 703 ;
+C 167 ; WX 500 ; N section ; B 30 -217 471 695 ;
+C 168 ; WX 500 ; N currency ; B 32 96 468 533 ;
+C 169 ; WX 227 ; N quotesingle ; B 45 376 181 695 ;
+C 170 ; WX 500 ; N quotedblleft ; B 34 405 466 695 ;
+C 171 ; WX 500 ; N guillemotleft ; B 36 44 463 438 ;
+C 172 ; WX 389 ; N guilsinglleft ; B 82 44 307 438 ;
+C 173 ; WX 389 ; N guilsinglright ; B 82 44 307 438 ;
+C 174 ; WX 611 ; N fi ; B 10 -3 595 720 ;
+C 175 ; WX 611 ; N fl ; B 17 -3 593 720 ;
+C 177 ; WX 500 ; N endash ; B 0 208 500 291 ;
+C 178 ; WX 500 ; N dagger ; B 29 -6 472 682 ;
+C 179 ; WX 500 ; N daggerdbl ; B 32 -245 468 682 ;
+C 180 ; WX 250 ; N periodcentered ; B 47 179 203 335 ;
+C 182 ; WX 641 ; N paragraph ; B 19 -161 599 683 ;
+C 183 ; WX 606 ; N bullet ; B 131 172 475 516 ;
+C 184 ; WX 333 ; N quotesinglbase ; B 56 -160 276 130 ;
+C 185 ; WX 500 ; N quotedblbase ; B 34 -160 466 130 ;
+C 186 ; WX 500 ; N quotedblright ; B 34 405 466 695 ;
+C 187 ; WX 500 ; N guillemotright ; B 37 44 464 438 ;
+C 188 ; WX 1000 ; N ellipsis ; B 89 -12 911 144 ;
+C 189 ; WX 1000 ; N perthousand ; B 33 -9 982 724 ;
+C 191 ; WX 444 ; N questiondown ; B 33 -231 401 471 ;
+C 193 ; WX 333 ; N grave ; B 18 506 256 691 ;
+C 194 ; WX 333 ; N acute ; B 78 506 316 691 ;
+C 195 ; WX 333 ; N circumflex ; B -2 506 335 681 ;
+C 196 ; WX 333 ; N tilde ; B -16 535 349 661 ;
+C 197 ; WX 333 ; N macron ; B 1 538 332 609 ;
+C 198 ; WX 333 ; N breve ; B 15 506 318 669 ;
+C 199 ; WX 333 ; N dotaccent ; B 100 537 234 671 ;
+C 200 ; WX 333 ; N dieresis ; B -8 537 341 671 ;
+C 202 ; WX 333 ; N ring ; B 67 500 267 700 ;
+C 203 ; WX 333 ; N cedilla ; B 73 -225 300 -7 ;
+C 205 ; WX 333 ; N hungarumlaut ; B -56 506 390 691 ;
+C 206 ; WX 333 ; N ogonek ; B 60 -246 274 -17 ;
+C 207 ; WX 333 ; N caron ; B -2 510 335 685 ;
+C 208 ; WX 1000 ; N emdash ; B 0 208 1000 291 ;
+C 225 ; WX 1000 ; N AE ; B 12 -4 954 681 ;
+C 227 ; WX 438 ; N ordfeminine ; B 77 367 361 660 ;
+C 232 ; WX 611 ; N Lslash ; B 16 -4 577 681 ;
+C 233 ; WX 833 ; N Oslash ; B 32 -20 808 698 ;
+C 234 ; WX 1000 ; N OE ; B 43 -17 985 695 ;
+C 235 ; WX 488 ; N ordmasculine ; B 89 367 399 660 ;
+C 241 ; WX 778 ; N ae ; B 46 -17 731 471 ;
+C 245 ; WX 333 ; N dotlessi ; B 34 -3 298 471 ;
+C 248 ; WX 333 ; N lslash ; B -4 -3 334 720 ;
+C 249 ; WX 556 ; N oslash ; B 23 -18 534 471 ;
+C 250 ; WX 833 ; N oe ; B 48 -17 799 471 ;
+C 251 ; WX 611 ; N germandbls ; B 30 -17 565 720 ;
+C -1 ; WX 667 ; N Zcaron ; B 24 -3 627 909 ;
+C -1 ; WX 444 ; N ccedilla ; B 37 -225 414 471 ;
+C -1 ; WX 556 ; N ydieresis ; B 10 -266 546 691 ;
+C -1 ; WX 500 ; N atilde ; B 40 -17 478 673 ;
+C -1 ; WX 333 ; N icircumflex ; B -2 -3 335 701 ;
+C -1 ; WX 300 ; N threesuperior ; B 9 261 292 667 ;
+C -1 ; WX 500 ; N ecircumflex ; B 42 -17 461 701 ;
+C -1 ; WX 611 ; N thorn ; B 17 -258 563 720 ;
+C -1 ; WX 500 ; N egrave ; B 42 -17 461 711 ;
+C -1 ; WX 300 ; N twosuperior ; B 5 261 295 660 ;
+C -1 ; WX 500 ; N eacute ; B 42 -17 461 711 ;
+C -1 ; WX 556 ; N otilde ; B 40 -17 517 673 ;
+C -1 ; WX 778 ; N Aacute ; B 24 -3 757 915 ;
+C -1 ; WX 556 ; N ocircumflex ; B 40 -17 517 701 ;
+C -1 ; WX 556 ; N yacute ; B 10 -266 546 711 ;
+C -1 ; WX 611 ; N udieresis ; B 25 -17 583 691 ;
+C -1 ; WX 750 ; N threequarters ; B 15 -2 735 667 ;
+C -1 ; WX 500 ; N acircumflex ; B 40 -17 478 701 ;
+C -1 ; WX 833 ; N Eth ; B 10 -3 786 681 ;
+C -1 ; WX 500 ; N edieresis ; B 42 -17 461 691 ;
+C -1 ; WX 611 ; N ugrave ; B 25 -17 583 711 ;
+C -1 ; WX 998 ; N trademark ; B 38 274 961 678 ;
+C -1 ; WX 556 ; N ograve ; B 40 -17 517 711 ;
+C -1 ; WX 444 ; N scaron ; B 39 -17 405 693 ;
+C -1 ; WX 389 ; N Idieresis ; B 20 -3 369 895 ;
+C -1 ; WX 611 ; N uacute ; B 25 -17 583 711 ;
+C -1 ; WX 500 ; N agrave ; B 40 -17 478 711 ;
+C -1 ; WX 611 ; N ntilde ; B 24 -3 587 673 ;
+C -1 ; WX 500 ; N aring ; B 40 -17 478 700 ;
+C -1 ; WX 500 ; N zcaron ; B 16 -3 464 693 ;
+C -1 ; WX 389 ; N Icircumflex ; B 26 -3 363 905 ;
+C -1 ; WX 833 ; N Ntilde ; B 35 -16 798 885 ;
+C -1 ; WX 611 ; N ucircumflex ; B 25 -17 583 701 ;
+C -1 ; WX 611 ; N Ecircumflex ; B 39 -4 577 905 ;
+C -1 ; WX 389 ; N Iacute ; B 39 -3 350 915 ;
+C -1 ; WX 722 ; N Ccedilla ; B 44 -225 695 695 ;
+C -1 ; WX 833 ; N Odieresis ; B 47 -17 787 895 ;
+C -1 ; WX 611 ; N Scaron ; B 57 -17 559 909 ;
+C -1 ; WX 611 ; N Edieresis ; B 39 -4 577 895 ;
+C -1 ; WX 389 ; N Igrave ; B 39 -3 350 915 ;
+C -1 ; WX 500 ; N adieresis ; B 40 -17 478 691 ;
+C -1 ; WX 833 ; N Ograve ; B 47 -17 787 915 ;
+C -1 ; WX 611 ; N Egrave ; B 39 -4 577 915 ;
+C -1 ; WX 667 ; N Ydieresis ; B 15 -3 660 895 ;
+C -1 ; WX 747 ; N registered ; B 26 -17 720 695 ;
+C -1 ; WX 833 ; N Otilde ; B 47 -17 787 885 ;
+C -1 ; WX 750 ; N onequarter ; B 19 -2 735 665 ;
+C -1 ; WX 778 ; N Ugrave ; B 26 -17 760 915 ;
+C -1 ; WX 778 ; N Ucircumflex ; B 26 -17 760 905 ;
+C -1 ; WX 611 ; N Thorn ; B 39 -3 574 681 ;
+C -1 ; WX 606 ; N divide ; B 51 0 555 510 ;
+C -1 ; WX 778 ; N Atilde ; B 24 -3 757 885 ;
+C -1 ; WX 778 ; N Uacute ; B 26 -17 760 915 ;
+C -1 ; WX 833 ; N Ocircumflex ; B 47 -17 787 905 ;
+C -1 ; WX 606 ; N logicalnot ; B 51 114 555 396 ;
+C -1 ; WX 778 ; N Aring ; B 24 -3 757 924 ;
+C -1 ; WX 333 ; N idieresis ; B -8 -3 341 691 ;
+C -1 ; WX 333 ; N iacute ; B 34 -3 316 711 ;
+C -1 ; WX 500 ; N aacute ; B 40 -17 478 711 ;
+C -1 ; WX 606 ; N plusminus ; B 51 0 555 505 ;
+C -1 ; WX 606 ; N multiply ; B 72 21 534 483 ;
+C -1 ; WX 778 ; N Udieresis ; B 26 -17 760 895 ;
+C -1 ; WX 606 ; N minus ; B 51 212 555 298 ;
+C -1 ; WX 300 ; N onesuperior ; B 14 261 287 665 ;
+C -1 ; WX 611 ; N Eacute ; B 39 -4 577 915 ;
+C -1 ; WX 778 ; N Acircumflex ; B 24 -3 757 905 ;
+C -1 ; WX 747 ; N copyright ; B 26 -17 720 695 ;
+C -1 ; WX 778 ; N Agrave ; B 24 -3 757 915 ;
+C -1 ; WX 556 ; N odieresis ; B 40 -17 517 691 ;
+C -1 ; WX 556 ; N oacute ; B 40 -17 517 711 ;
+C -1 ; WX 400 ; N degree ; B 50 360 350 660 ;
+C -1 ; WX 333 ; N igrave ; B 18 -3 298 711 ;
+C -1 ; WX 611 ; N mu ; B 25 -225 583 471 ;
+C -1 ; WX 833 ; N Oacute ; B 47 -17 787 915 ;
+C -1 ; WX 556 ; N eth ; B 40 -17 517 720 ;
+C -1 ; WX 778 ; N Adieresis ; B 24 -3 757 895 ;
+C -1 ; WX 667 ; N Yacute ; B 15 -3 660 915 ;
+C -1 ; WX 606 ; N brokenbar ; B 260 0 346 720 ;
+C -1 ; WX 750 ; N onehalf ; B 9 -2 745 665 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 101
+
+KPX A y -70
+KPX A w -70
+KPX A v -70
+KPX A space -18
+KPX A quoteright -92
+KPX A Y -111
+KPX A W -90
+KPX A V -129
+KPX A T -92
+
+KPX F period -111
+KPX F comma -111
+KPX F A -55
+
+KPX L y -74
+KPX L space -18
+KPX L quoteright -74
+KPX L Y -92
+KPX L W -92
+KPX L V -92
+KPX L T -74
+
+KPX P period -129
+KPX P comma -129
+KPX P A -74
+
+KPX R y -30
+KPX R Y -55
+KPX R W -37
+KPX R V -74
+KPX R T -55
+
+KPX T y -90
+KPX T w -90
+KPX T u -129
+KPX T semicolon -74
+KPX T s -111
+KPX T r -111
+KPX T period -92
+KPX T o -111
+KPX T i -55
+KPX T hyphen -92
+KPX T e -111
+KPX T comma -92
+KPX T colon -74
+KPX T c -129
+KPX T a -111
+KPX T A -92
+
+KPX V y -90
+KPX V u -92
+KPX V semicolon -74
+KPX V r -111
+KPX V period -129
+KPX V o -111
+KPX V i -55
+KPX V hyphen -92
+KPX V e -111
+KPX V comma -129
+KPX V colon -74
+KPX V a -111
+KPX V A -129
+
+KPX W y -74
+KPX W u -74
+KPX W semicolon -37
+KPX W r -74
+KPX W period -37
+KPX W o -74
+KPX W i -37
+KPX W hyphen -37
+KPX W e -74
+KPX W comma -92
+KPX W colon -37
+KPX W a -74
+KPX W A -90
+
+KPX Y v -74
+KPX Y u -74
+KPX Y semicolon -55
+KPX Y q -92
+KPX Y period -74
+KPX Y p -74
+KPX Y o -74
+KPX Y i -55
+KPX Y hyphen -74
+KPX Y e -74
+KPX Y comma -74
+KPX Y colon -55
+KPX Y a -74
+KPX Y A -55
+
+KPX f quoteright 37
+KPX f f -18
+
+KPX one one -37
+
+KPX quoteleft quoteleft -55
+
+KPX quoteright t -18
+KPX quoteright space -55
+KPX quoteright s -55
+KPX quoteright quoteright -55
+
+KPX r quoteright 55
+KPX r period -55
+KPX r hyphen -18
+KPX r comma -55
+
+KPX v period -111
+KPX v comma -111
+
+KPX w period -92
+KPX w comma -92
+
+KPX y period -92
+KPX y comma -92
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 223 224 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 211 224 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 223 224 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 215 224 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 223 224 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 223 224 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 195 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 139 224 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 139 224 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 139 224 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 139 224 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 28 224 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 28 224 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 28 224 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 28 224 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 250 224 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 250 224 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 250 224 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 250 224 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 250 224 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 250 224 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 139 224 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 235 224 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 235 224 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 235 224 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 223 224 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 211 224 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 199 224 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 167 224 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 84 20 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 84 20 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 84 20 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 84 20 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 84 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 84 12 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 56 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 84 20 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 96 20 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 92 20 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 84 20 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute 0 20 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex 0 20 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis 0 20 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave 0 20 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 139 12 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 112 20 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 112 20 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 112 20 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 112 20 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 112 12 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 56 8 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 151 20 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 139 20 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 139 20 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 131 20 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 144 20 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 124 20 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 84 8 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplbi8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplbi8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..e161d049090f6293283c265d07016a1872216bd1
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplbi8a.afm
@@ -0,0 +1,441 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Jul 2 22:48:39 1990
+Comment UniqueID 31799
+Comment VMusage 37656 48548
+FontName Palatino-BoldItalic
+FullName Palatino Bold Italic
+FamilyName Palatino
+Weight Bold
+ItalicAngle -10
+IsFixedPitch false
+FontBBox -170 -271 1073 926
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.005
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Palatino is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 681
+XHeight 469
+Ascender 726
+Descender -271
+StartCharMetrics 228
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 58 -17 322 695 ;
+C 34 ; WX 500 ; N quotedbl ; B 137 467 493 720 ;
+C 35 ; WX 500 ; N numbersign ; B 4 0 496 673 ;
+C 36 ; WX 500 ; N dollar ; B 20 -108 477 737 ;
+C 37 ; WX 889 ; N percent ; B 56 -17 790 697 ;
+C 38 ; WX 833 ; N ampersand ; B 74 -17 811 695 ;
+C 39 ; WX 278 ; N quoteright ; B 76 431 302 720 ;
+C 40 ; WX 333 ; N parenleft ; B 58 -129 368 723 ;
+C 41 ; WX 333 ; N parenright ; B -12 -129 298 723 ;
+C 42 ; WX 444 ; N asterisk ; B 84 332 439 695 ;
+C 43 ; WX 606 ; N plus ; B 50 -5 556 501 ;
+C 44 ; WX 250 ; N comma ; B -33 -164 208 147 ;
+C 45 ; WX 389 ; N hyphen ; B 37 198 362 300 ;
+C 46 ; WX 250 ; N period ; B 48 -17 187 135 ;
+C 47 ; WX 315 ; N slash ; B 1 -17 315 720 ;
+C 48 ; WX 500 ; N zero ; B 42 -17 490 683 ;
+C 49 ; WX 500 ; N one ; B 41 -3 434 678 ;
+C 50 ; WX 500 ; N two ; B 1 -3 454 683 ;
+C 51 ; WX 500 ; N three ; B 8 -17 450 683 ;
+C 52 ; WX 500 ; N four ; B 3 -3 487 683 ;
+C 53 ; WX 500 ; N five ; B 14 -17 481 675 ;
+C 54 ; WX 500 ; N six ; B 39 -17 488 683 ;
+C 55 ; WX 500 ; N seven ; B 69 -3 544 674 ;
+C 56 ; WX 500 ; N eight ; B 26 -17 484 683 ;
+C 57 ; WX 500 ; N nine ; B 27 -17 491 683 ;
+C 58 ; WX 250 ; N colon ; B 38 -17 236 452 ;
+C 59 ; WX 250 ; N semicolon ; B -33 -164 247 452 ;
+C 60 ; WX 606 ; N less ; B 49 -21 558 517 ;
+C 61 ; WX 606 ; N equal ; B 51 106 555 390 ;
+C 62 ; WX 606 ; N greater ; B 48 -21 557 517 ;
+C 63 ; WX 444 ; N question ; B 91 -17 450 695 ;
+C 64 ; WX 833 ; N at ; B 82 -12 744 681 ;
+C 65 ; WX 722 ; N A ; B -35 -3 685 683 ;
+C 66 ; WX 667 ; N B ; B 8 -3 629 681 ;
+C 67 ; WX 685 ; N C ; B 69 -17 695 695 ;
+C 68 ; WX 778 ; N D ; B 0 -3 747 682 ;
+C 69 ; WX 611 ; N E ; B 11 -3 606 681 ;
+C 70 ; WX 556 ; N F ; B -6 -3 593 681 ;
+C 71 ; WX 778 ; N G ; B 72 -17 750 695 ;
+C 72 ; WX 778 ; N H ; B -12 -3 826 681 ;
+C 73 ; WX 389 ; N I ; B -1 -3 412 681 ;
+C 74 ; WX 389 ; N J ; B -29 -207 417 681 ;
+C 75 ; WX 722 ; N K ; B -10 -3 746 681 ;
+C 76 ; WX 611 ; N L ; B 26 -3 578 681 ;
+C 77 ; WX 944 ; N M ; B -23 -17 985 681 ;
+C 78 ; WX 778 ; N N ; B -2 -3 829 681 ;
+C 79 ; WX 833 ; N O ; B 76 -17 794 695 ;
+C 80 ; WX 667 ; N P ; B 11 -3 673 681 ;
+C 81 ; WX 833 ; N Q ; B 76 -222 794 695 ;
+C 82 ; WX 722 ; N R ; B 4 -3 697 681 ;
+C 83 ; WX 556 ; N S ; B 50 -17 517 695 ;
+C 84 ; WX 611 ; N T ; B 56 -3 674 681 ;
+C 85 ; WX 778 ; N U ; B 83 -17 825 681 ;
+C 86 ; WX 667 ; N V ; B 67 -3 745 681 ;
+C 87 ; WX 1000 ; N W ; B 67 -3 1073 689 ;
+C 88 ; WX 722 ; N X ; B -9 -3 772 681 ;
+C 89 ; WX 611 ; N Y ; B 54 -3 675 695 ;
+C 90 ; WX 667 ; N Z ; B 1 -3 676 681 ;
+C 91 ; WX 333 ; N bracketleft ; B 45 -102 381 723 ;
+C 92 ; WX 606 ; N backslash ; B 72 0 534 720 ;
+C 93 ; WX 333 ; N bracketright ; B -21 -102 315 723 ;
+C 94 ; WX 606 ; N asciicircum ; B 63 275 543 678 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 65 431 291 720 ;
+C 97 ; WX 556 ; N a ; B 44 -17 519 470 ;
+C 98 ; WX 537 ; N b ; B 44 -17 494 726 ;
+C 99 ; WX 444 ; N c ; B 32 -17 436 469 ;
+C 100 ; WX 556 ; N d ; B 38 -17 550 726 ;
+C 101 ; WX 444 ; N e ; B 28 -17 418 469 ;
+C 102 ; WX 333 ; N f ; B -130 -271 449 726 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B -50 -271 529 469 ;
+C 104 ; WX 556 ; N h ; B 22 -17 522 726 ;
+C 105 ; WX 333 ; N i ; B 26 -17 312 695 ;
+C 106 ; WX 333 ; N j ; B -64 -271 323 695 ;
+C 107 ; WX 556 ; N k ; B 34 -17 528 726 ;
+C 108 ; WX 333 ; N l ; B 64 -17 318 726 ;
+C 109 ; WX 833 ; N m ; B 19 -17 803 469 ;
+C 110 ; WX 556 ; N n ; B 17 -17 521 469 ;
+C 111 ; WX 556 ; N o ; B 48 -17 502 469 ;
+C 112 ; WX 556 ; N p ; B -21 -271 516 469 ;
+C 113 ; WX 537 ; N q ; B 32 -271 513 469 ;
+C 114 ; WX 389 ; N r ; B 20 -17 411 469 ;
+C 115 ; WX 444 ; N s ; B 25 -17 406 469 ;
+C 116 ; WX 389 ; N t ; B 42 -17 409 636 ;
+C 117 ; WX 556 ; N u ; B 22 -17 521 469 ;
+C 118 ; WX 556 ; N v ; B 19 -17 513 469 ;
+C 119 ; WX 833 ; N w ; B 27 -17 802 469 ;
+C 120 ; WX 500 ; N x ; B -8 -17 500 469 ;
+C 121 ; WX 556 ; N y ; B 13 -271 541 469 ;
+C 122 ; WX 500 ; N z ; B 31 -17 470 469 ;
+C 123 ; WX 333 ; N braceleft ; B 18 -105 334 720 ;
+C 124 ; WX 606 ; N bar ; B 259 0 347 720 ;
+C 125 ; WX 333 ; N braceright ; B -1 -105 315 720 ;
+C 126 ; WX 606 ; N asciitilde ; B 51 151 555 346 ;
+C 161 ; WX 333 ; N exclamdown ; B 2 -225 259 479 ;
+C 162 ; WX 500 ; N cent ; B 52 -105 456 547 ;
+C 163 ; WX 500 ; N sterling ; B 21 -5 501 683 ;
+C 164 ; WX 167 ; N fraction ; B -170 0 338 683 ;
+C 165 ; WX 500 ; N yen ; B 11 -3 538 695 ;
+C 166 ; WX 500 ; N florin ; B 8 -242 479 690 ;
+C 167 ; WX 556 ; N section ; B 47 -151 497 695 ;
+C 168 ; WX 500 ; N currency ; B 32 96 468 533 ;
+C 169 ; WX 250 ; N quotesingle ; B 127 467 293 720 ;
+C 170 ; WX 500 ; N quotedblleft ; B 65 431 511 720 ;
+C 171 ; WX 500 ; N guillemotleft ; B 35 43 458 446 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 60 43 292 446 ;
+C 173 ; WX 333 ; N guilsinglright ; B 35 40 267 443 ;
+C 174 ; WX 611 ; N fi ; B -130 -271 588 726 ;
+C 175 ; WX 611 ; N fl ; B -130 -271 631 726 ;
+C 177 ; WX 500 ; N endash ; B -12 214 512 282 ;
+C 178 ; WX 556 ; N dagger ; B 67 -3 499 685 ;
+C 179 ; WX 556 ; N daggerdbl ; B 33 -153 537 693 ;
+C 180 ; WX 250 ; N periodcentered ; B 67 172 206 324 ;
+C 182 ; WX 556 ; N paragraph ; B 14 -204 629 681 ;
+C 183 ; WX 606 ; N bullet ; B 131 172 475 516 ;
+C 184 ; WX 250 ; N quotesinglbase ; B -3 -144 220 145 ;
+C 185 ; WX 500 ; N quotedblbase ; B -18 -144 424 145 ;
+C 186 ; WX 500 ; N quotedblright ; B 73 431 519 720 ;
+C 187 ; WX 500 ; N guillemotright ; B 35 40 458 443 ;
+C 188 ; WX 1000 ; N ellipsis ; B 91 -17 896 135 ;
+C 189 ; WX 1000 ; N perthousand ; B 65 -17 912 691 ;
+C 191 ; WX 444 ; N questiondown ; B -12 -226 347 479 ;
+C 193 ; WX 333 ; N grave ; B 110 518 322 699 ;
+C 194 ; WX 333 ; N acute ; B 153 518 392 699 ;
+C 195 ; WX 333 ; N circumflex ; B 88 510 415 684 ;
+C 196 ; WX 333 ; N tilde ; B 82 535 441 654 ;
+C 197 ; WX 333 ; N macron ; B 76 538 418 608 ;
+C 198 ; WX 333 ; N breve ; B 96 518 412 680 ;
+C 199 ; WX 333 ; N dotaccent ; B 202 537 325 668 ;
+C 200 ; WX 333 ; N dieresis ; B 90 537 426 668 ;
+C 202 ; WX 556 ; N ring ; B 277 514 477 714 ;
+C 203 ; WX 333 ; N cedilla ; B 12 -218 248 5 ;
+C 205 ; WX 333 ; N hungarumlaut ; B -28 518 409 699 ;
+C 206 ; WX 333 ; N ogonek ; B 32 -206 238 -17 ;
+C 207 ; WX 333 ; N caron ; B 113 510 445 684 ;
+C 208 ; WX 1000 ; N emdash ; B -12 214 1012 282 ;
+C 225 ; WX 944 ; N AE ; B -29 -3 927 681 ;
+C 227 ; WX 333 ; N ordfeminine ; B 47 391 355 684 ;
+C 232 ; WX 611 ; N Lslash ; B 6 -3 578 681 ;
+C 233 ; WX 833 ; N Oslash ; B 57 -54 797 730 ;
+C 234 ; WX 944 ; N OE ; B 39 -17 961 695 ;
+C 235 ; WX 333 ; N ordmasculine ; B 51 391 346 683 ;
+C 241 ; WX 738 ; N ae ; B 44 -17 711 469 ;
+C 245 ; WX 333 ; N dotlessi ; B 26 -17 293 469 ;
+C 248 ; WX 333 ; N lslash ; B 13 -17 365 726 ;
+C 249 ; WX 556 ; N oslash ; B 14 -50 522 506 ;
+C 250 ; WX 778 ; N oe ; B 48 -17 755 469 ;
+C 251 ; WX 556 ; N germandbls ; B -131 -271 549 726 ;
+C -1 ; WX 667 ; N Zcaron ; B 1 -3 676 896 ;
+C -1 ; WX 444 ; N ccedilla ; B 32 -218 436 469 ;
+C -1 ; WX 556 ; N ydieresis ; B 13 -271 541 688 ;
+C -1 ; WX 556 ; N atilde ; B 44 -17 553 666 ;
+C -1 ; WX 333 ; N icircumflex ; B 26 -17 403 704 ;
+C -1 ; WX 300 ; N threesuperior ; B 23 263 310 683 ;
+C -1 ; WX 444 ; N ecircumflex ; B 28 -17 471 704 ;
+C -1 ; WX 556 ; N thorn ; B -21 -271 516 726 ;
+C -1 ; WX 444 ; N egrave ; B 28 -17 418 719 ;
+C -1 ; WX 300 ; N twosuperior ; B 26 271 321 683 ;
+C -1 ; WX 444 ; N eacute ; B 28 -17 448 719 ;
+C -1 ; WX 556 ; N otilde ; B 48 -17 553 666 ;
+C -1 ; WX 722 ; N Aacute ; B -35 -3 685 911 ;
+C -1 ; WX 556 ; N ocircumflex ; B 48 -17 515 704 ;
+C -1 ; WX 556 ; N yacute ; B 13 -271 541 719 ;
+C -1 ; WX 556 ; N udieresis ; B 22 -17 538 688 ;
+C -1 ; WX 750 ; N threequarters ; B 18 -2 732 683 ;
+C -1 ; WX 556 ; N acircumflex ; B 44 -17 527 704 ;
+C -1 ; WX 778 ; N Eth ; B 0 -3 747 682 ;
+C -1 ; WX 444 ; N edieresis ; B 28 -17 482 688 ;
+C -1 ; WX 556 ; N ugrave ; B 22 -17 521 719 ;
+C -1 ; WX 1000 ; N trademark ; B 38 274 961 678 ;
+C -1 ; WX 556 ; N ograve ; B 48 -17 502 719 ;
+C -1 ; WX 444 ; N scaron ; B 25 -17 489 692 ;
+C -1 ; WX 389 ; N Idieresis ; B -1 -3 454 880 ;
+C -1 ; WX 556 ; N uacute ; B 22 -17 521 719 ;
+C -1 ; WX 556 ; N agrave ; B 44 -17 519 719 ;
+C -1 ; WX 556 ; N ntilde ; B 17 -17 553 666 ;
+C -1 ; WX 556 ; N aring ; B 44 -17 519 714 ;
+C -1 ; WX 500 ; N zcaron ; B 31 -17 517 692 ;
+C -1 ; WX 389 ; N Icircumflex ; B -1 -3 443 896 ;
+C -1 ; WX 778 ; N Ntilde ; B -2 -3 829 866 ;
+C -1 ; WX 556 ; N ucircumflex ; B 22 -17 521 704 ;
+C -1 ; WX 611 ; N Ecircumflex ; B 11 -3 606 896 ;
+C -1 ; WX 389 ; N Iacute ; B -1 -3 420 911 ;
+C -1 ; WX 685 ; N Ccedilla ; B 69 -218 695 695 ;
+C -1 ; WX 833 ; N Odieresis ; B 76 -17 794 880 ;
+C -1 ; WX 556 ; N Scaron ; B 50 -17 557 896 ;
+C -1 ; WX 611 ; N Edieresis ; B 11 -3 606 880 ;
+C -1 ; WX 389 ; N Igrave ; B -1 -3 412 911 ;
+C -1 ; WX 556 ; N adieresis ; B 44 -17 538 688 ;
+C -1 ; WX 833 ; N Ograve ; B 76 -17 794 911 ;
+C -1 ; WX 611 ; N Egrave ; B 11 -3 606 911 ;
+C -1 ; WX 611 ; N Ydieresis ; B 54 -3 675 880 ;
+C -1 ; WX 747 ; N registered ; B 26 -17 720 695 ;
+C -1 ; WX 833 ; N Otilde ; B 76 -17 794 866 ;
+C -1 ; WX 750 ; N onequarter ; B 18 -2 732 683 ;
+C -1 ; WX 778 ; N Ugrave ; B 83 -17 825 911 ;
+C -1 ; WX 778 ; N Ucircumflex ; B 83 -17 825 896 ;
+C -1 ; WX 667 ; N Thorn ; B 11 -3 644 681 ;
+C -1 ; WX 606 ; N divide ; B 50 -5 556 501 ;
+C -1 ; WX 722 ; N Atilde ; B -35 -3 685 866 ;
+C -1 ; WX 778 ; N Uacute ; B 83 -17 825 911 ;
+C -1 ; WX 833 ; N Ocircumflex ; B 76 -17 794 896 ;
+C -1 ; WX 606 ; N logicalnot ; B 51 107 555 390 ;
+C -1 ; WX 722 ; N Aring ; B -35 -3 685 926 ;
+C -1 ; WX 333 ; N idieresis ; B 26 -17 426 688 ;
+C -1 ; WX 333 ; N iacute ; B 26 -17 392 719 ;
+C -1 ; WX 556 ; N aacute ; B 44 -17 519 719 ;
+C -1 ; WX 606 ; N plusminus ; B 50 0 556 501 ;
+C -1 ; WX 606 ; N multiply ; B 72 17 534 479 ;
+C -1 ; WX 778 ; N Udieresis ; B 83 -17 825 880 ;
+C -1 ; WX 606 ; N minus ; B 51 204 555 292 ;
+C -1 ; WX 300 ; N onesuperior ; B 41 271 298 680 ;
+C -1 ; WX 611 ; N Eacute ; B 11 -3 606 911 ;
+C -1 ; WX 722 ; N Acircumflex ; B -35 -3 685 896 ;
+C -1 ; WX 747 ; N copyright ; B 26 -17 720 695 ;
+C -1 ; WX 722 ; N Agrave ; B -35 -3 685 911 ;
+C -1 ; WX 556 ; N odieresis ; B 48 -17 538 688 ;
+C -1 ; WX 556 ; N oacute ; B 48 -17 504 719 ;
+C -1 ; WX 400 ; N degree ; B 50 383 350 683 ;
+C -1 ; WX 333 ; N igrave ; B 26 -17 322 719 ;
+C -1 ; WX 556 ; N mu ; B -15 -232 521 469 ;
+C -1 ; WX 833 ; N Oacute ; B 76 -17 794 911 ;
+C -1 ; WX 556 ; N eth ; B 48 -17 546 726 ;
+C -1 ; WX 722 ; N Adieresis ; B -35 -3 685 880 ;
+C -1 ; WX 611 ; N Yacute ; B 54 -3 675 911 ;
+C -1 ; WX 606 ; N brokenbar ; B 259 0 347 720 ;
+C -1 ; WX 750 ; N onehalf ; B 14 -2 736 683 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 108
+
+KPX A y -55
+KPX A w -37
+KPX A v -55
+KPX A space -55
+KPX A quoteright -55
+KPX A Y -74
+KPX A W -74
+KPX A V -74
+KPX A T -55
+
+KPX F space -18
+KPX F period -111
+KPX F comma -111
+KPX F A -74
+
+KPX L y -37
+KPX L space -18
+KPX L quoteright -55
+KPX L Y -74
+KPX L W -74
+KPX L V -74
+KPX L T -74
+
+KPX P space -55
+KPX P period -129
+KPX P comma -129
+KPX P A -92
+
+KPX R y -20
+KPX R Y -37
+KPX R W -55
+KPX R V -55
+KPX R T -37
+
+KPX T y -80
+KPX T w -50
+KPX T u -92
+KPX T semicolon -55
+KPX T s -92
+KPX T r -92
+KPX T period -55
+KPX T o -111
+KPX T i -74
+KPX T hyphen -92
+KPX T e -111
+KPX T comma -55
+KPX T colon -55
+KPX T c -92
+KPX T a -111
+KPX T O -18
+KPX T A -55
+
+KPX V y -50
+KPX V u -50
+KPX V semicolon -37
+KPX V r -74
+KPX V period -111
+KPX V o -74
+KPX V i -50
+KPX V hyphen -37
+KPX V e -74
+KPX V comma -111
+KPX V colon -37
+KPX V a -92
+KPX V A -74
+
+KPX W y -30
+KPX W u -30
+KPX W semicolon -18
+KPX W r -30
+KPX W period -55
+KPX W o -55
+KPX W i -30
+KPX W e -55
+KPX W comma -55
+KPX W colon -28
+KPX W a -74
+KPX W A -74
+
+KPX Y v -30
+KPX Y u -50
+KPX Y semicolon -55
+KPX Y q -92
+KPX Y period -55
+KPX Y p -74
+KPX Y o -111
+KPX Y i -54
+KPX Y hyphen -55
+KPX Y e -92
+KPX Y comma -55
+KPX Y colon -55
+KPX Y a -111
+KPX Y A -55
+
+KPX f quoteright 37
+KPX f f -37
+
+KPX one one -55
+
+KPX quoteleft quoteleft -55
+
+KPX quoteright t -18
+KPX quoteright space -37
+KPX quoteright s -37
+KPX quoteright quoteright -55
+
+KPX r quoteright 55
+KPX r q -18
+KPX r period -55
+KPX r o -18
+KPX r h -18
+KPX r g -18
+KPX r e -18
+KPX r comma -55
+KPX r c -18
+
+KPX v period -55
+KPX v comma -55
+
+KPX w period -55
+KPX w comma -55
+
+KPX y period -37
+KPX y comma -37
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 195 212 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 195 212 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 195 212 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 195 212 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 83 212 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 195 212 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 176 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 139 212 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 139 212 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 139 212 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 139 212 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 28 212 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 28 212 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 28 212 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 28 212 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 223 212 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 250 212 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 250 212 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 250 212 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 250 212 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 250 212 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 112 212 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 223 212 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 223 212 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 223 212 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 211 212 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 151 212 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 139 212 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 167 212 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 112 20 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 112 20 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 112 20 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 112 20 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 0 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 112 12 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 56 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 56 20 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 56 20 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 56 20 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 56 20 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute 0 20 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -12 20 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis 0 20 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave 0 20 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 112 12 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 112 20 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 100 20 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 112 20 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 112 20 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 112 12 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 44 8 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 112 20 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 100 20 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 112 20 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 112 20 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 112 20 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 112 20 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 72 8 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplr8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplr8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..6566b16ed78c2bfec7e0abbd7698e6bf98e3e738
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplr8a.afm
@@ -0,0 +1,445 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Jul 2 22:14:17 1990
+Comment UniqueID 31790
+Comment VMusage 36445 47337
+FontName Palatino-Roman
+FullName Palatino Roman
+FamilyName Palatino
+Weight Roman
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -166 -283 1021 927
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.005
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Palatino is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 469
+Ascender 726
+Descender -281
+StartCharMetrics 228
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 81 -5 197 694 ;
+C 34 ; WX 371 ; N quotedbl ; B 52 469 319 709 ;
+C 35 ; WX 500 ; N numbersign ; B 4 0 495 684 ;
+C 36 ; WX 500 ; N dollar ; B 30 -116 471 731 ;
+C 37 ; WX 840 ; N percent ; B 39 -20 802 709 ;
+C 38 ; WX 778 ; N ampersand ; B 43 -20 753 689 ;
+C 39 ; WX 278 ; N quoteright ; B 45 446 233 709 ;
+C 40 ; WX 333 ; N parenleft ; B 60 -215 301 726 ;
+C 41 ; WX 333 ; N parenright ; B 32 -215 273 726 ;
+C 42 ; WX 389 ; N asterisk ; B 32 342 359 689 ;
+C 43 ; WX 606 ; N plus ; B 51 7 555 512 ;
+C 44 ; WX 250 ; N comma ; B 16 -155 218 123 ;
+C 45 ; WX 333 ; N hyphen ; B 17 215 312 287 ;
+C 46 ; WX 250 ; N period ; B 67 -5 183 111 ;
+C 47 ; WX 606 ; N slash ; B 87 -119 519 726 ;
+C 48 ; WX 500 ; N zero ; B 29 -20 465 689 ;
+C 49 ; WX 500 ; N one ; B 60 -3 418 694 ;
+C 50 ; WX 500 ; N two ; B 16 -3 468 689 ;
+C 51 ; WX 500 ; N three ; B 15 -20 462 689 ;
+C 52 ; WX 500 ; N four ; B 2 -3 472 694 ;
+C 53 ; WX 500 ; N five ; B 13 -20 459 689 ;
+C 54 ; WX 500 ; N six ; B 32 -20 468 689 ;
+C 55 ; WX 500 ; N seven ; B 44 -3 497 689 ;
+C 56 ; WX 500 ; N eight ; B 30 -20 464 689 ;
+C 57 ; WX 500 ; N nine ; B 20 -20 457 689 ;
+C 58 ; WX 250 ; N colon ; B 66 -5 182 456 ;
+C 59 ; WX 250 ; N semicolon ; B 16 -153 218 456 ;
+C 60 ; WX 606 ; N less ; B 57 0 558 522 ;
+C 61 ; WX 606 ; N equal ; B 51 136 555 386 ;
+C 62 ; WX 606 ; N greater ; B 48 0 549 522 ;
+C 63 ; WX 444 ; N question ; B 43 -5 395 694 ;
+C 64 ; WX 747 ; N at ; B 24 -20 724 694 ;
+C 65 ; WX 778 ; N A ; B 15 -3 756 700 ;
+C 66 ; WX 611 ; N B ; B 26 -3 576 692 ;
+C 67 ; WX 709 ; N C ; B 22 -20 670 709 ;
+C 68 ; WX 774 ; N D ; B 22 -3 751 692 ;
+C 69 ; WX 611 ; N E ; B 22 -3 572 692 ;
+C 70 ; WX 556 ; N F ; B 22 -3 536 692 ;
+C 71 ; WX 763 ; N G ; B 22 -20 728 709 ;
+C 72 ; WX 832 ; N H ; B 22 -3 810 692 ;
+C 73 ; WX 337 ; N I ; B 22 -3 315 692 ;
+C 74 ; WX 333 ; N J ; B -15 -194 311 692 ;
+C 75 ; WX 726 ; N K ; B 22 -3 719 692 ;
+C 76 ; WX 611 ; N L ; B 22 -3 586 692 ;
+C 77 ; WX 946 ; N M ; B 16 -13 926 692 ;
+C 78 ; WX 831 ; N N ; B 17 -20 813 692 ;
+C 79 ; WX 786 ; N O ; B 22 -20 764 709 ;
+C 80 ; WX 604 ; N P ; B 22 -3 580 692 ;
+C 81 ; WX 786 ; N Q ; B 22 -176 764 709 ;
+C 82 ; WX 668 ; N R ; B 22 -3 669 692 ;
+C 83 ; WX 525 ; N S ; B 24 -20 503 709 ;
+C 84 ; WX 613 ; N T ; B 18 -3 595 692 ;
+C 85 ; WX 778 ; N U ; B 12 -20 759 692 ;
+C 86 ; WX 722 ; N V ; B 8 -9 706 692 ;
+C 87 ; WX 1000 ; N W ; B 8 -9 984 700 ;
+C 88 ; WX 667 ; N X ; B 14 -3 648 700 ;
+C 89 ; WX 667 ; N Y ; B 9 -3 654 704 ;
+C 90 ; WX 667 ; N Z ; B 15 -3 638 692 ;
+C 91 ; WX 333 ; N bracketleft ; B 79 -184 288 726 ;
+C 92 ; WX 606 ; N backslash ; B 81 0 512 726 ;
+C 93 ; WX 333 ; N bracketright ; B 45 -184 254 726 ;
+C 94 ; WX 606 ; N asciicircum ; B 51 283 554 689 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 45 446 233 709 ;
+C 97 ; WX 500 ; N a ; B 32 -12 471 469 ;
+C 98 ; WX 553 ; N b ; B -15 -12 508 726 ;
+C 99 ; WX 444 ; N c ; B 26 -20 413 469 ;
+C 100 ; WX 611 ; N d ; B 35 -12 579 726 ;
+C 101 ; WX 479 ; N e ; B 26 -20 448 469 ;
+C 102 ; WX 333 ; N f ; B 23 -3 341 728 ; L i fi ; L l fl ;
+C 103 ; WX 556 ; N g ; B 32 -283 544 469 ;
+C 104 ; WX 582 ; N h ; B 6 -3 572 726 ;
+C 105 ; WX 291 ; N i ; B 21 -3 271 687 ;
+C 106 ; WX 234 ; N j ; B -40 -283 167 688 ;
+C 107 ; WX 556 ; N k ; B 21 -12 549 726 ;
+C 108 ; WX 291 ; N l ; B 21 -3 271 726 ;
+C 109 ; WX 883 ; N m ; B 16 -3 869 469 ;
+C 110 ; WX 582 ; N n ; B 6 -3 572 469 ;
+C 111 ; WX 546 ; N o ; B 32 -20 514 469 ;
+C 112 ; WX 601 ; N p ; B 8 -281 554 469 ;
+C 113 ; WX 560 ; N q ; B 35 -281 560 469 ;
+C 114 ; WX 395 ; N r ; B 21 -3 374 469 ;
+C 115 ; WX 424 ; N s ; B 30 -20 391 469 ;
+C 116 ; WX 326 ; N t ; B 22 -12 319 621 ;
+C 117 ; WX 603 ; N u ; B 18 -12 581 469 ;
+C 118 ; WX 565 ; N v ; B 6 -7 539 459 ;
+C 119 ; WX 834 ; N w ; B 6 -7 808 469 ;
+C 120 ; WX 516 ; N x ; B 20 -3 496 469 ;
+C 121 ; WX 556 ; N y ; B 12 -283 544 459 ;
+C 122 ; WX 500 ; N z ; B 16 -3 466 462 ;
+C 123 ; WX 333 ; N braceleft ; B 58 -175 289 726 ;
+C 124 ; WX 606 ; N bar ; B 275 0 331 726 ;
+C 125 ; WX 333 ; N braceright ; B 44 -175 275 726 ;
+C 126 ; WX 606 ; N asciitilde ; B 51 176 555 347 ;
+C 161 ; WX 278 ; N exclamdown ; B 81 -225 197 469 ;
+C 162 ; WX 500 ; N cent ; B 61 -101 448 562 ;
+C 163 ; WX 500 ; N sterling ; B 12 -13 478 694 ;
+C 164 ; WX 167 ; N fraction ; B -166 0 337 689 ;
+C 165 ; WX 500 ; N yen ; B 5 -3 496 701 ;
+C 166 ; WX 500 ; N florin ; B 0 -262 473 706 ;
+C 167 ; WX 500 ; N section ; B 26 -219 465 709 ;
+C 168 ; WX 500 ; N currency ; B 30 96 470 531 ;
+C 169 ; WX 208 ; N quotesingle ; B 61 469 147 709 ;
+C 170 ; WX 500 ; N quotedblleft ; B 51 446 449 709 ;
+C 171 ; WX 500 ; N guillemotleft ; B 50 71 450 428 ;
+C 172 ; WX 331 ; N guilsinglleft ; B 66 71 265 428 ;
+C 173 ; WX 331 ; N guilsinglright ; B 66 71 265 428 ;
+C 174 ; WX 605 ; N fi ; B 23 -3 587 728 ;
+C 175 ; WX 608 ; N fl ; B 23 -3 590 728 ;
+C 177 ; WX 500 ; N endash ; B 0 219 500 277 ;
+C 178 ; WX 500 ; N dagger ; B 34 -5 466 694 ;
+C 179 ; WX 500 ; N daggerdbl ; B 34 -249 466 694 ;
+C 180 ; WX 250 ; N periodcentered ; B 67 203 183 319 ;
+C 182 ; WX 628 ; N paragraph ; B 39 -150 589 694 ;
+C 183 ; WX 606 ; N bullet ; B 131 172 475 516 ;
+C 184 ; WX 278 ; N quotesinglbase ; B 22 -153 210 110 ;
+C 185 ; WX 500 ; N quotedblbase ; B 51 -153 449 110 ;
+C 186 ; WX 500 ; N quotedblright ; B 51 446 449 709 ;
+C 187 ; WX 500 ; N guillemotright ; B 50 71 450 428 ;
+C 188 ; WX 1000 ; N ellipsis ; B 109 -5 891 111 ;
+C 189 ; WX 1144 ; N perthousand ; B 123 -20 1021 709 ;
+C 191 ; WX 444 ; N questiondown ; B 43 -231 395 469 ;
+C 193 ; WX 333 ; N grave ; B 31 506 255 677 ;
+C 194 ; WX 333 ; N acute ; B 78 506 302 677 ;
+C 195 ; WX 333 ; N circumflex ; B 11 510 323 677 ;
+C 196 ; WX 333 ; N tilde ; B 2 535 332 640 ;
+C 197 ; WX 333 ; N macron ; B 11 538 323 591 ;
+C 198 ; WX 333 ; N breve ; B 26 506 308 664 ;
+C 199 ; WX 250 ; N dotaccent ; B 75 537 175 637 ;
+C 200 ; WX 333 ; N dieresis ; B 17 537 316 637 ;
+C 202 ; WX 333 ; N ring ; B 67 496 267 696 ;
+C 203 ; WX 333 ; N cedilla ; B 96 -225 304 -10 ;
+C 205 ; WX 380 ; N hungarumlaut ; B 3 506 377 687 ;
+C 206 ; WX 313 ; N ogonek ; B 68 -165 245 -20 ;
+C 207 ; WX 333 ; N caron ; B 11 510 323 677 ;
+C 208 ; WX 1000 ; N emdash ; B 0 219 1000 277 ;
+C 225 ; WX 944 ; N AE ; B -10 -3 908 692 ;
+C 227 ; WX 333 ; N ordfeminine ; B 24 422 310 709 ;
+C 232 ; WX 611 ; N Lslash ; B 6 -3 586 692 ;
+C 233 ; WX 833 ; N Oslash ; B 30 -20 797 709 ;
+C 234 ; WX 998 ; N OE ; B 22 -20 962 709 ;
+C 235 ; WX 333 ; N ordmasculine ; B 10 416 323 709 ;
+C 241 ; WX 758 ; N ae ; B 30 -20 732 469 ;
+C 245 ; WX 287 ; N dotlessi ; B 21 -3 271 469 ;
+C 248 ; WX 291 ; N lslash ; B -14 -3 306 726 ;
+C 249 ; WX 556 ; N oslash ; B 16 -23 530 474 ;
+C 250 ; WX 827 ; N oe ; B 32 -20 800 469 ;
+C 251 ; WX 556 ; N germandbls ; B 23 -9 519 731 ;
+C -1 ; WX 667 ; N Zcaron ; B 15 -3 638 908 ;
+C -1 ; WX 444 ; N ccedilla ; B 26 -225 413 469 ;
+C -1 ; WX 556 ; N ydieresis ; B 12 -283 544 657 ;
+C -1 ; WX 500 ; N atilde ; B 32 -12 471 652 ;
+C -1 ; WX 287 ; N icircumflex ; B -12 -3 300 697 ;
+C -1 ; WX 300 ; N threesuperior ; B 1 266 299 689 ;
+C -1 ; WX 479 ; N ecircumflex ; B 26 -20 448 697 ;
+C -1 ; WX 601 ; N thorn ; B -2 -281 544 726 ;
+C -1 ; WX 479 ; N egrave ; B 26 -20 448 697 ;
+C -1 ; WX 300 ; N twosuperior ; B 0 273 301 689 ;
+C -1 ; WX 479 ; N eacute ; B 26 -20 448 697 ;
+C -1 ; WX 546 ; N otilde ; B 32 -20 514 652 ;
+C -1 ; WX 778 ; N Aacute ; B 15 -3 756 908 ;
+C -1 ; WX 546 ; N ocircumflex ; B 32 -20 514 697 ;
+C -1 ; WX 556 ; N yacute ; B 12 -283 544 697 ;
+C -1 ; WX 603 ; N udieresis ; B 18 -12 581 657 ;
+C -1 ; WX 750 ; N threequarters ; B 15 -3 735 689 ;
+C -1 ; WX 500 ; N acircumflex ; B 32 -12 471 697 ;
+C -1 ; WX 774 ; N Eth ; B 14 -3 751 692 ;
+C -1 ; WX 479 ; N edieresis ; B 26 -20 448 657 ;
+C -1 ; WX 603 ; N ugrave ; B 18 -12 581 697 ;
+C -1 ; WX 979 ; N trademark ; B 40 285 939 689 ;
+C -1 ; WX 546 ; N ograve ; B 32 -20 514 697 ;
+C -1 ; WX 424 ; N scaron ; B 30 -20 391 685 ;
+C -1 ; WX 337 ; N Idieresis ; B 19 -3 318 868 ;
+C -1 ; WX 603 ; N uacute ; B 18 -12 581 697 ;
+C -1 ; WX 500 ; N agrave ; B 32 -12 471 697 ;
+C -1 ; WX 582 ; N ntilde ; B 6 -3 572 652 ;
+C -1 ; WX 500 ; N aring ; B 32 -12 471 716 ;
+C -1 ; WX 500 ; N zcaron ; B 16 -3 466 685 ;
+C -1 ; WX 337 ; N Icircumflex ; B 13 -3 325 908 ;
+C -1 ; WX 831 ; N Ntilde ; B 17 -20 813 871 ;
+C -1 ; WX 603 ; N ucircumflex ; B 18 -12 581 697 ;
+C -1 ; WX 611 ; N Ecircumflex ; B 22 -3 572 908 ;
+C -1 ; WX 337 ; N Iacute ; B 22 -3 315 908 ;
+C -1 ; WX 709 ; N Ccedilla ; B 22 -225 670 709 ;
+C -1 ; WX 786 ; N Odieresis ; B 22 -20 764 868 ;
+C -1 ; WX 525 ; N Scaron ; B 24 -20 503 908 ;
+C -1 ; WX 611 ; N Edieresis ; B 22 -3 572 868 ;
+C -1 ; WX 337 ; N Igrave ; B 22 -3 315 908 ;
+C -1 ; WX 500 ; N adieresis ; B 32 -12 471 657 ;
+C -1 ; WX 786 ; N Ograve ; B 22 -20 764 908 ;
+C -1 ; WX 611 ; N Egrave ; B 22 -3 572 908 ;
+C -1 ; WX 667 ; N Ydieresis ; B 9 -3 654 868 ;
+C -1 ; WX 747 ; N registered ; B 11 -18 736 706 ;
+C -1 ; WX 786 ; N Otilde ; B 22 -20 764 883 ;
+C -1 ; WX 750 ; N onequarter ; B 30 -3 727 692 ;
+C -1 ; WX 778 ; N Ugrave ; B 12 -20 759 908 ;
+C -1 ; WX 778 ; N Ucircumflex ; B 12 -20 759 908 ;
+C -1 ; WX 604 ; N Thorn ; B 32 -3 574 692 ;
+C -1 ; WX 606 ; N divide ; B 51 10 555 512 ;
+C -1 ; WX 778 ; N Atilde ; B 15 -3 756 871 ;
+C -1 ; WX 778 ; N Uacute ; B 12 -20 759 908 ;
+C -1 ; WX 786 ; N Ocircumflex ; B 22 -20 764 908 ;
+C -1 ; WX 606 ; N logicalnot ; B 51 120 551 386 ;
+C -1 ; WX 778 ; N Aring ; B 15 -3 756 927 ;
+C -1 ; WX 287 ; N idieresis ; B -6 -3 293 657 ;
+C -1 ; WX 287 ; N iacute ; B 21 -3 279 697 ;
+C -1 ; WX 500 ; N aacute ; B 32 -12 471 697 ;
+C -1 ; WX 606 ; N plusminus ; B 51 0 555 512 ;
+C -1 ; WX 606 ; N multiply ; B 83 36 523 474 ;
+C -1 ; WX 778 ; N Udieresis ; B 12 -20 759 868 ;
+C -1 ; WX 606 ; N minus ; B 51 233 555 289 ;
+C -1 ; WX 300 ; N onesuperior ; B 31 273 269 692 ;
+C -1 ; WX 611 ; N Eacute ; B 22 -3 572 908 ;
+C -1 ; WX 778 ; N Acircumflex ; B 15 -3 756 908 ;
+C -1 ; WX 747 ; N copyright ; B 11 -18 736 706 ;
+C -1 ; WX 778 ; N Agrave ; B 15 -3 756 908 ;
+C -1 ; WX 546 ; N odieresis ; B 32 -20 514 657 ;
+C -1 ; WX 546 ; N oacute ; B 32 -20 514 697 ;
+C -1 ; WX 400 ; N degree ; B 50 389 350 689 ;
+C -1 ; WX 287 ; N igrave ; B 8 -3 271 697 ;
+C -1 ; WX 603 ; N mu ; B 18 -236 581 469 ;
+C -1 ; WX 786 ; N Oacute ; B 22 -20 764 908 ;
+C -1 ; WX 546 ; N eth ; B 32 -20 504 728 ;
+C -1 ; WX 778 ; N Adieresis ; B 15 -3 756 868 ;
+C -1 ; WX 667 ; N Yacute ; B 9 -3 654 908 ;
+C -1 ; WX 606 ; N brokenbar ; B 275 0 331 726 ;
+C -1 ; WX 750 ; N onehalf ; B 15 -3 735 692 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 111
+
+KPX A y -74
+KPX A w -74
+KPX A v -92
+KPX A space -55
+KPX A quoteright -74
+KPX A Y -111
+KPX A W -74
+KPX A V -111
+KPX A T -74
+
+KPX F period -92
+KPX F comma -92
+KPX F A -74
+
+KPX L y -55
+KPX L space -37
+KPX L quoteright -74
+KPX L Y -92
+KPX L W -74
+KPX L V -92
+KPX L T -74
+
+KPX P space -18
+KPX P period -129
+KPX P comma -129
+KPX P A -92
+
+KPX R y -37
+KPX R Y -37
+KPX R W -37
+KPX R V -55
+KPX R T -37
+
+KPX T y -90
+KPX T w -90
+KPX T u -90
+KPX T semicolon -55
+KPX T s -90
+KPX T r -90
+KPX T period -74
+KPX T o -92
+KPX T i -55
+KPX T hyphen -55
+KPX T e -92
+KPX T comma -74
+KPX T colon -55
+KPX T c -111
+KPX T a -92
+KPX T O -18
+KPX T A -74
+
+KPX V y -92
+KPX V u -92
+KPX V semicolon -55
+KPX V r -92
+KPX V period -129
+KPX V o -111
+KPX V i -55
+KPX V hyphen -74
+KPX V e -111
+KPX V comma -129
+KPX V colon -55
+KPX V a -92
+KPX V A -111
+
+KPX W y -50
+KPX W u -50
+KPX W semicolon -18
+KPX W r -74
+KPX W period -92
+KPX W o -92
+KPX W i -55
+KPX W hyphen -55
+KPX W e -92
+KPX W comma -92
+KPX W colon -18
+KPX W a -92
+KPX W A -92
+
+KPX Y v -90
+KPX Y u -90
+KPX Y space -18
+KPX Y semicolon -74
+KPX Y q -90
+KPX Y period -111
+KPX Y p -111
+KPX Y o -92
+KPX Y i -55
+KPX Y hyphen -92
+KPX Y e -92
+KPX Y comma -111
+KPX Y colon -74
+KPX Y a -92
+KPX Y A -92
+
+KPX f quoteright 55
+KPX f f -18
+
+KPX one one -55
+
+KPX quoteleft quoteleft -37
+
+KPX quoteright quoteright -37
+
+KPX r u -8
+KPX r quoteright 74
+KPX r q -18
+KPX r period -74
+KPX r o -18
+KPX r hyphen -18
+KPX r h -18
+KPX r g -18
+KPX r e -18
+KPX r d -18
+KPX r comma -74
+KPX r c -18
+
+KPX space Y -18
+KPX space A -37
+
+KPX v period -111
+KPX v comma -111
+
+KPX w period -92
+KPX w comma -92
+
+KPX y period -111
+KPX y comma -111
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 229 231 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 223 231 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 223 231 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 215 231 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 223 231 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 223 231 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 188 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 139 231 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 139 231 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 139 231 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 139 231 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 2 231 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 2 231 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 2 231 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 2 231 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 249 231 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 227 231 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 227 231 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 227 231 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 227 231 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 227 243 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 96 231 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 255 231 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 247 231 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 223 231 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 223 231 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 203 231 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 191 231 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 179 231 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 84 20 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 72 20 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 72 20 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 60 20 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 72 20 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 72 12 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 56 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 97 20 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 85 20 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 73 20 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 73 20 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -23 20 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -23 20 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -23 20 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -23 20 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 113 12 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 107 20 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 107 20 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 107 20 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 95 20 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 107 12 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 46 8 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 159 20 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 135 20 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 135 20 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 111 20 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 144 20 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 112 20 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 84 8 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplri8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplri8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..01bdcf0568e36ff987c3f3b4b40bf5a732a8559d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pplri8a.afm
@@ -0,0 +1,439 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Jul 2 22:37:33 1990
+Comment UniqueID 31796
+Comment VMusage 37415 48307
+FontName Palatino-Italic
+FullName Palatino Italic
+FamilyName Palatino
+Weight Medium
+ItalicAngle -10
+IsFixedPitch false
+FontBBox -170 -276 1010 918
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.005
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Palatino is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 482
+Ascender 733
+Descender -276
+StartCharMetrics 228
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 76 -8 292 733 ;
+C 34 ; WX 500 ; N quotedbl ; B 140 508 455 733 ;
+C 35 ; WX 500 ; N numbersign ; B 4 0 495 692 ;
+C 36 ; WX 500 ; N dollar ; B 15 -113 452 733 ;
+C 37 ; WX 889 ; N percent ; B 74 -7 809 710 ;
+C 38 ; WX 778 ; N ampersand ; B 47 -18 766 692 ;
+C 39 ; WX 278 ; N quoteright ; B 78 488 258 733 ;
+C 40 ; WX 333 ; N parenleft ; B 54 -106 331 733 ;
+C 41 ; WX 333 ; N parenright ; B 2 -106 279 733 ;
+C 42 ; WX 389 ; N asterisk ; B 76 368 400 706 ;
+C 43 ; WX 606 ; N plus ; B 51 0 555 504 ;
+C 44 ; WX 250 ; N comma ; B 8 -143 203 123 ;
+C 45 ; WX 333 ; N hyphen ; B 19 223 304 281 ;
+C 46 ; WX 250 ; N period ; B 53 -5 158 112 ;
+C 47 ; WX 296 ; N slash ; B -40 -119 392 733 ;
+C 48 ; WX 500 ; N zero ; B 36 -11 480 699 ;
+C 49 ; WX 500 ; N one ; B 54 -3 398 699 ;
+C 50 ; WX 500 ; N two ; B 12 -3 437 699 ;
+C 51 ; WX 500 ; N three ; B 22 -11 447 699 ;
+C 52 ; WX 500 ; N four ; B 15 -3 478 699 ;
+C 53 ; WX 500 ; N five ; B 14 -11 491 693 ;
+C 54 ; WX 500 ; N six ; B 49 -11 469 699 ;
+C 55 ; WX 500 ; N seven ; B 53 -3 502 692 ;
+C 56 ; WX 500 ; N eight ; B 36 -11 469 699 ;
+C 57 ; WX 500 ; N nine ; B 32 -11 468 699 ;
+C 58 ; WX 250 ; N colon ; B 44 -5 207 458 ;
+C 59 ; WX 250 ; N semicolon ; B -9 -146 219 456 ;
+C 60 ; WX 606 ; N less ; B 53 -6 554 516 ;
+C 61 ; WX 606 ; N equal ; B 51 126 555 378 ;
+C 62 ; WX 606 ; N greater ; B 53 -6 554 516 ;
+C 63 ; WX 500 ; N question ; B 114 -8 427 706 ;
+C 64 ; WX 747 ; N at ; B 27 -18 718 706 ;
+C 65 ; WX 722 ; N A ; B -19 -3 677 705 ;
+C 66 ; WX 611 ; N B ; B 26 -6 559 692 ;
+C 67 ; WX 667 ; N C ; B 45 -18 651 706 ;
+C 68 ; WX 778 ; N D ; B 28 -3 741 692 ;
+C 69 ; WX 611 ; N E ; B 30 -3 570 692 ;
+C 70 ; WX 556 ; N F ; B 0 -3 548 692 ;
+C 71 ; WX 722 ; N G ; B 50 -18 694 706 ;
+C 72 ; WX 778 ; N H ; B -3 -3 800 692 ;
+C 73 ; WX 333 ; N I ; B 7 -3 354 692 ;
+C 74 ; WX 333 ; N J ; B -35 -206 358 692 ;
+C 75 ; WX 667 ; N K ; B 13 -3 683 692 ;
+C 76 ; WX 556 ; N L ; B 16 -3 523 692 ;
+C 77 ; WX 944 ; N M ; B -19 -18 940 692 ;
+C 78 ; WX 778 ; N N ; B 2 -11 804 692 ;
+C 79 ; WX 778 ; N O ; B 53 -18 748 706 ;
+C 80 ; WX 611 ; N P ; B 9 -3 594 692 ;
+C 81 ; WX 778 ; N Q ; B 53 -201 748 706 ;
+C 82 ; WX 667 ; N R ; B 9 -3 639 692 ;
+C 83 ; WX 556 ; N S ; B 42 -18 506 706 ;
+C 84 ; WX 611 ; N T ; B 53 -3 635 692 ;
+C 85 ; WX 778 ; N U ; B 88 -18 798 692 ;
+C 86 ; WX 722 ; N V ; B 75 -8 754 692 ;
+C 87 ; WX 944 ; N W ; B 71 -8 980 700 ;
+C 88 ; WX 722 ; N X ; B 20 -3 734 692 ;
+C 89 ; WX 667 ; N Y ; B 52 -3 675 705 ;
+C 90 ; WX 667 ; N Z ; B 20 -3 637 692 ;
+C 91 ; WX 333 ; N bracketleft ; B 18 -100 326 733 ;
+C 92 ; WX 606 ; N backslash ; B 81 0 513 733 ;
+C 93 ; WX 333 ; N bracketright ; B 7 -100 315 733 ;
+C 94 ; WX 606 ; N asciicircum ; B 51 283 554 689 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 78 488 258 733 ;
+C 97 ; WX 444 ; N a ; B 4 -11 406 482 ;
+C 98 ; WX 463 ; N b ; B 37 -11 433 733 ;
+C 99 ; WX 407 ; N c ; B 25 -11 389 482 ;
+C 100 ; WX 500 ; N d ; B 17 -11 483 733 ;
+C 101 ; WX 389 ; N e ; B 15 -11 374 482 ;
+C 102 ; WX 278 ; N f ; B -162 -276 413 733 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B -37 -276 498 482 ;
+C 104 ; WX 500 ; N h ; B 10 -9 471 733 ;
+C 105 ; WX 278 ; N i ; B 34 -9 264 712 ;
+C 106 ; WX 278 ; N j ; B -70 -276 265 712 ;
+C 107 ; WX 444 ; N k ; B 8 -9 449 733 ;
+C 108 ; WX 278 ; N l ; B 36 -9 251 733 ;
+C 109 ; WX 778 ; N m ; B 24 -9 740 482 ;
+C 110 ; WX 556 ; N n ; B 24 -9 514 482 ;
+C 111 ; WX 444 ; N o ; B 17 -11 411 482 ;
+C 112 ; WX 500 ; N p ; B -7 -276 465 482 ;
+C 113 ; WX 463 ; N q ; B 24 -276 432 482 ;
+C 114 ; WX 389 ; N r ; B 26 -9 384 482 ;
+C 115 ; WX 389 ; N s ; B 9 -11 345 482 ;
+C 116 ; WX 333 ; N t ; B 41 -9 310 646 ;
+C 117 ; WX 556 ; N u ; B 32 -11 512 482 ;
+C 118 ; WX 500 ; N v ; B 21 -11 477 482 ;
+C 119 ; WX 722 ; N w ; B 21 -11 699 482 ;
+C 120 ; WX 500 ; N x ; B 9 -11 484 482 ;
+C 121 ; WX 500 ; N y ; B -8 -276 490 482 ;
+C 122 ; WX 444 ; N z ; B -1 -11 416 482 ;
+C 123 ; WX 333 ; N braceleft ; B 15 -100 319 733 ;
+C 124 ; WX 606 ; N bar ; B 275 0 331 733 ;
+C 125 ; WX 333 ; N braceright ; B 14 -100 318 733 ;
+C 126 ; WX 606 ; N asciitilde ; B 51 168 555 339 ;
+C 161 ; WX 333 ; N exclamdown ; B 15 -276 233 467 ;
+C 162 ; WX 500 ; N cent ; B 56 -96 418 551 ;
+C 163 ; WX 500 ; N sterling ; B 2 -18 479 708 ;
+C 164 ; WX 167 ; N fraction ; B -170 0 337 699 ;
+C 165 ; WX 500 ; N yen ; B 35 -3 512 699 ;
+C 166 ; WX 500 ; N florin ; B 5 -276 470 708 ;
+C 167 ; WX 500 ; N section ; B 14 -220 463 706 ;
+C 168 ; WX 500 ; N currency ; B 14 115 486 577 ;
+C 169 ; WX 333 ; N quotesingle ; B 140 508 288 733 ;
+C 170 ; WX 500 ; N quotedblleft ; B 98 488 475 733 ;
+C 171 ; WX 500 ; N guillemotleft ; B 57 70 437 440 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 57 70 270 440 ;
+C 173 ; WX 333 ; N guilsinglright ; B 63 70 276 440 ;
+C 174 ; WX 528 ; N fi ; B -162 -276 502 733 ;
+C 175 ; WX 545 ; N fl ; B -162 -276 520 733 ;
+C 177 ; WX 500 ; N endash ; B -10 228 510 278 ;
+C 178 ; WX 500 ; N dagger ; B 48 0 469 692 ;
+C 179 ; WX 500 ; N daggerdbl ; B 10 -162 494 692 ;
+C 180 ; WX 250 ; N periodcentered ; B 53 195 158 312 ;
+C 182 ; WX 500 ; N paragraph ; B 33 -224 611 692 ;
+C 183 ; WX 500 ; N bullet ; B 86 182 430 526 ;
+C 184 ; WX 278 ; N quotesinglbase ; B 27 -122 211 120 ;
+C 185 ; WX 500 ; N quotedblbase ; B 43 -122 424 120 ;
+C 186 ; WX 500 ; N quotedblright ; B 98 488 475 733 ;
+C 187 ; WX 500 ; N guillemotright ; B 63 70 443 440 ;
+C 188 ; WX 1000 ; N ellipsis ; B 102 -5 873 112 ;
+C 189 ; WX 1000 ; N perthousand ; B 72 -6 929 717 ;
+C 191 ; WX 500 ; N questiondown ; B 57 -246 370 467 ;
+C 193 ; WX 333 ; N grave ; B 86 518 310 687 ;
+C 194 ; WX 333 ; N acute ; B 122 518 346 687 ;
+C 195 ; WX 333 ; N circumflex ; B 56 510 350 679 ;
+C 196 ; WX 333 ; N tilde ; B 63 535 390 638 ;
+C 197 ; WX 333 ; N macron ; B 74 538 386 589 ;
+C 198 ; WX 333 ; N breve ; B 92 518 393 677 ;
+C 199 ; WX 333 ; N dotaccent ; B 175 537 283 645 ;
+C 200 ; WX 333 ; N dieresis ; B 78 537 378 637 ;
+C 202 ; WX 333 ; N ring ; B 159 508 359 708 ;
+C 203 ; WX 333 ; N cedilla ; B -9 -216 202 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 46 518 385 730 ;
+C 206 ; WX 333 ; N ogonek ; B 38 -207 196 -18 ;
+C 207 ; WX 333 ; N caron ; B 104 510 409 679 ;
+C 208 ; WX 1000 ; N emdash ; B -10 228 1010 278 ;
+C 225 ; WX 941 ; N AE ; B -4 -3 902 692 ;
+C 227 ; WX 333 ; N ordfeminine ; B 60 404 321 699 ;
+C 232 ; WX 556 ; N Lslash ; B -16 -3 523 692 ;
+C 233 ; WX 778 ; N Oslash ; B 32 -39 762 721 ;
+C 234 ; WX 1028 ; N OE ; B 56 -18 989 706 ;
+C 235 ; WX 333 ; N ordmasculine ; B 66 404 322 699 ;
+C 241 ; WX 638 ; N ae ; B 1 -11 623 482 ;
+C 245 ; WX 278 ; N dotlessi ; B 34 -9 241 482 ;
+C 248 ; WX 278 ; N lslash ; B -10 -9 302 733 ;
+C 249 ; WX 444 ; N oslash ; B -18 -24 460 510 ;
+C 250 ; WX 669 ; N oe ; B 17 -11 654 482 ;
+C 251 ; WX 500 ; N germandbls ; B -160 -276 488 733 ;
+C -1 ; WX 667 ; N Zcaron ; B 20 -3 637 907 ;
+C -1 ; WX 407 ; N ccedilla ; B 25 -216 389 482 ;
+C -1 ; WX 500 ; N ydieresis ; B -8 -276 490 657 ;
+C -1 ; WX 444 ; N atilde ; B 4 -11 446 650 ;
+C -1 ; WX 278 ; N icircumflex ; B 29 -9 323 699 ;
+C -1 ; WX 300 ; N threesuperior ; B 28 273 304 699 ;
+C -1 ; WX 389 ; N ecircumflex ; B 15 -11 398 699 ;
+C -1 ; WX 500 ; N thorn ; B -39 -276 433 733 ;
+C -1 ; WX 389 ; N egrave ; B 15 -11 374 707 ;
+C -1 ; WX 300 ; N twosuperior ; B 13 278 290 699 ;
+C -1 ; WX 389 ; N eacute ; B 15 -11 394 707 ;
+C -1 ; WX 444 ; N otilde ; B 17 -11 446 650 ;
+C -1 ; WX 722 ; N Aacute ; B -19 -3 677 897 ;
+C -1 ; WX 444 ; N ocircumflex ; B 17 -11 411 699 ;
+C -1 ; WX 500 ; N yacute ; B -8 -276 490 707 ;
+C -1 ; WX 556 ; N udieresis ; B 32 -11 512 657 ;
+C -1 ; WX 750 ; N threequarters ; B 35 -2 715 699 ;
+C -1 ; WX 444 ; N acircumflex ; B 4 -11 406 699 ;
+C -1 ; WX 778 ; N Eth ; B 19 -3 741 692 ;
+C -1 ; WX 389 ; N edieresis ; B 15 -11 406 657 ;
+C -1 ; WX 556 ; N ugrave ; B 32 -11 512 707 ;
+C -1 ; WX 1000 ; N trademark ; B 52 285 951 689 ;
+C -1 ; WX 444 ; N ograve ; B 17 -11 411 707 ;
+C -1 ; WX 389 ; N scaron ; B 9 -11 419 687 ;
+C -1 ; WX 333 ; N Idieresis ; B 7 -3 418 847 ;
+C -1 ; WX 556 ; N uacute ; B 32 -11 512 707 ;
+C -1 ; WX 444 ; N agrave ; B 4 -11 406 707 ;
+C -1 ; WX 556 ; N ntilde ; B 24 -9 514 650 ;
+C -1 ; WX 444 ; N aring ; B 4 -11 406 728 ;
+C -1 ; WX 444 ; N zcaron ; B -1 -11 447 687 ;
+C -1 ; WX 333 ; N Icircumflex ; B 7 -3 390 889 ;
+C -1 ; WX 778 ; N Ntilde ; B 2 -11 804 866 ;
+C -1 ; WX 556 ; N ucircumflex ; B 32 -11 512 699 ;
+C -1 ; WX 611 ; N Ecircumflex ; B 30 -3 570 889 ;
+C -1 ; WX 333 ; N Iacute ; B 7 -3 406 897 ;
+C -1 ; WX 667 ; N Ccedilla ; B 45 -216 651 706 ;
+C -1 ; WX 778 ; N Odieresis ; B 53 -18 748 847 ;
+C -1 ; WX 556 ; N Scaron ; B 42 -18 539 907 ;
+C -1 ; WX 611 ; N Edieresis ; B 30 -3 570 847 ;
+C -1 ; WX 333 ; N Igrave ; B 7 -3 354 897 ;
+C -1 ; WX 444 ; N adieresis ; B 4 -11 434 657 ;
+C -1 ; WX 778 ; N Ograve ; B 53 -18 748 897 ;
+C -1 ; WX 611 ; N Egrave ; B 30 -3 570 897 ;
+C -1 ; WX 667 ; N Ydieresis ; B 52 -3 675 847 ;
+C -1 ; WX 747 ; N registered ; B 11 -18 736 706 ;
+C -1 ; WX 778 ; N Otilde ; B 53 -18 748 866 ;
+C -1 ; WX 750 ; N onequarter ; B 31 -2 715 699 ;
+C -1 ; WX 778 ; N Ugrave ; B 88 -18 798 897 ;
+C -1 ; WX 778 ; N Ucircumflex ; B 88 -18 798 889 ;
+C -1 ; WX 611 ; N Thorn ; B 9 -3 570 692 ;
+C -1 ; WX 606 ; N divide ; B 51 0 555 504 ;
+C -1 ; WX 722 ; N Atilde ; B -19 -3 677 866 ;
+C -1 ; WX 778 ; N Uacute ; B 88 -18 798 897 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 53 -18 748 889 ;
+C -1 ; WX 606 ; N logicalnot ; B 51 118 555 378 ;
+C -1 ; WX 722 ; N Aring ; B -19 -3 677 918 ;
+C -1 ; WX 278 ; N idieresis ; B 34 -9 351 657 ;
+C -1 ; WX 278 ; N iacute ; B 34 -9 331 707 ;
+C -1 ; WX 444 ; N aacute ; B 4 -11 414 707 ;
+C -1 ; WX 606 ; N plusminus ; B 51 0 555 504 ;
+C -1 ; WX 606 ; N multiply ; B 83 36 523 474 ;
+C -1 ; WX 778 ; N Udieresis ; B 88 -18 798 847 ;
+C -1 ; WX 606 ; N minus ; B 51 224 555 280 ;
+C -1 ; WX 300 ; N onesuperior ; B 61 278 285 699 ;
+C -1 ; WX 611 ; N Eacute ; B 30 -3 570 897 ;
+C -1 ; WX 722 ; N Acircumflex ; B -19 -3 677 889 ;
+C -1 ; WX 747 ; N copyright ; B 11 -18 736 706 ;
+C -1 ; WX 722 ; N Agrave ; B -19 -3 677 897 ;
+C -1 ; WX 444 ; N odieresis ; B 17 -11 434 657 ;
+C -1 ; WX 444 ; N oacute ; B 17 -11 414 707 ;
+C -1 ; WX 400 ; N degree ; B 90 389 390 689 ;
+C -1 ; WX 278 ; N igrave ; B 34 -9 271 707 ;
+C -1 ; WX 556 ; N mu ; B 15 -226 512 482 ;
+C -1 ; WX 778 ; N Oacute ; B 53 -18 748 897 ;
+C -1 ; WX 444 ; N eth ; B 17 -11 478 733 ;
+C -1 ; WX 722 ; N Adieresis ; B -19 -3 677 847 ;
+C -1 ; WX 667 ; N Yacute ; B 52 -3 675 897 ;
+C -1 ; WX 606 ; N brokenbar ; B 275 0 331 733 ;
+C -1 ; WX 750 ; N onehalf ; B 31 -2 721 699 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 106
+
+KPX A y -55
+KPX A w -37
+KPX A v -37
+KPX A space -37
+KPX A quoteright -55
+KPX A Y -55
+KPX A W -55
+KPX A V -74
+KPX A T -55
+
+KPX F period -111
+KPX F comma -111
+KPX F A -111
+
+KPX L y -37
+KPX L space -18
+KPX L quoteright -37
+KPX L Y -74
+KPX L W -74
+KPX L V -74
+KPX L T -74
+
+KPX P period -129
+KPX P comma -129
+KPX P A -129
+
+KPX R y -37
+KPX R Y -55
+KPX R W -55
+KPX R V -74
+KPX R T -55
+
+KPX T y -92
+KPX T w -92
+KPX T u -111
+KPX T semicolon -74
+KPX T s -111
+KPX T r -111
+KPX T period -74
+KPX T o -111
+KPX T i -55
+KPX T hyphen -55
+KPX T e -111
+KPX T comma -74
+KPX T colon -74
+KPX T c -111
+KPX T a -111
+KPX T O -18
+KPX T A -92
+
+KPX V y -74
+KPX V u -74
+KPX V semicolon -37
+KPX V r -92
+KPX V period -129
+KPX V o -74
+KPX V i -74
+KPX V hyphen -55
+KPX V e -92
+KPX V comma -129
+KPX V colon -37
+KPX V a -74
+KPX V A -210
+
+KPX W y -20
+KPX W u -20
+KPX W semicolon -18
+KPX W r -20
+KPX W period -55
+KPX W o -20
+KPX W i -20
+KPX W hyphen -18
+KPX W e -20
+KPX W comma -55
+KPX W colon -18
+KPX W a -20
+KPX W A -92
+
+KPX Y v -74
+KPX Y u -92
+KPX Y semicolon -74
+KPX Y q -92
+KPX Y period -92
+KPX Y p -74
+KPX Y o -111
+KPX Y i -55
+KPX Y hyphen -74
+KPX Y e -111
+KPX Y comma -92
+KPX Y colon -74
+KPX Y a -92
+KPX Y A -92
+
+KPX f quoteright 55
+
+KPX one one -55
+
+KPX quoteleft quoteleft -74
+
+KPX quoteright t -37
+KPX quoteright space -55
+KPX quoteright s -55
+KPX quoteright quoteright -74
+
+KPX r quoteright 37
+KPX r q -18
+KPX r period -74
+KPX r o -18
+KPX r h -18
+KPX r g -18
+KPX r e -18
+KPX r comma -74
+KPX r c -18
+
+KPX v period -55
+KPX v comma -55
+
+KPX w period -55
+KPX w comma -55
+
+KPX y period -37
+KPX y comma -37
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 271 210 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 261 210 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 255 210 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 235 210 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 235 210 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 255 228 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 207 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 199 210 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 179 210 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 179 210 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 167 210 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 60 210 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 40 210 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 40 210 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 28 210 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 263 228 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 283 210 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 263 210 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 255 210 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 251 210 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 263 228 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 130 228 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 277 210 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 255 210 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 235 210 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 235 210 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 227 210 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 187 210 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 179 228 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 68 20 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 56 20 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 56 20 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 44 20 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 36 20 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 56 12 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 37 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 48 20 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 48 20 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 28 20 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 16 20 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -15 20 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -27 20 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -27 20 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -39 20 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 112 12 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 68 20 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 56 20 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 56 20 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 36 20 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 56 12 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 10 8 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 124 20 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 112 20 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 112 20 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 100 20 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 96 20 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 84 20 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 38 8 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/psyr.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/psyr.afm
new file mode 100644
index 0000000000000000000000000000000000000000..1cdbdae695f0a8f6b66226514b4ff039910ee69f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/psyr.afm
@@ -0,0 +1,209 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Wed Jan 17 21:48:26 1990
+Comment UniqueID 27004
+Comment VMusage 28489 37622
+FontName Symbol
+FullName Symbol
+FamilyName Symbol
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -180 -293 1090 1010
+UnderlinePosition -98
+UnderlineThickness 54
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All rights reserved.
+EncodingScheme FontSpecific
+StartCharMetrics 189
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 128 -17 240 672 ;
+C 34 ; WX 713 ; N universal ; B 31 0 681 705 ;
+C 35 ; WX 500 ; N numbersign ; B 20 -16 481 673 ;
+C 36 ; WX 549 ; N existential ; B 25 0 478 707 ;
+C 37 ; WX 833 ; N percent ; B 63 -36 771 655 ;
+C 38 ; WX 778 ; N ampersand ; B 41 -18 750 661 ;
+C 39 ; WX 439 ; N suchthat ; B 48 -17 414 500 ;
+C 40 ; WX 333 ; N parenleft ; B 53 -191 300 673 ;
+C 41 ; WX 333 ; N parenright ; B 30 -191 277 673 ;
+C 42 ; WX 500 ; N asteriskmath ; B 65 134 427 551 ;
+C 43 ; WX 549 ; N plus ; B 10 0 539 533 ;
+C 44 ; WX 250 ; N comma ; B 56 -152 194 104 ;
+C 45 ; WX 549 ; N minus ; B 11 233 535 288 ;
+C 46 ; WX 250 ; N period ; B 69 -17 181 95 ;
+C 47 ; WX 278 ; N slash ; B 0 -18 254 646 ;
+C 48 ; WX 500 ; N zero ; B 23 -17 471 685 ;
+C 49 ; WX 500 ; N one ; B 117 0 390 673 ;
+C 50 ; WX 500 ; N two ; B 25 0 475 686 ;
+C 51 ; WX 500 ; N three ; B 39 -17 435 685 ;
+C 52 ; WX 500 ; N four ; B 16 0 469 685 ;
+C 53 ; WX 500 ; N five ; B 29 -17 443 685 ;
+C 54 ; WX 500 ; N six ; B 36 -17 467 685 ;
+C 55 ; WX 500 ; N seven ; B 24 -16 448 673 ;
+C 56 ; WX 500 ; N eight ; B 54 -18 440 685 ;
+C 57 ; WX 500 ; N nine ; B 31 -18 460 685 ;
+C 58 ; WX 278 ; N colon ; B 81 -17 193 460 ;
+C 59 ; WX 278 ; N semicolon ; B 83 -152 221 460 ;
+C 60 ; WX 549 ; N less ; B 26 0 523 522 ;
+C 61 ; WX 549 ; N equal ; B 11 141 537 390 ;
+C 62 ; WX 549 ; N greater ; B 26 0 523 522 ;
+C 63 ; WX 444 ; N question ; B 70 -17 412 686 ;
+C 64 ; WX 549 ; N congruent ; B 11 0 537 475 ;
+C 65 ; WX 722 ; N Alpha ; B 4 0 684 673 ;
+C 66 ; WX 667 ; N Beta ; B 29 0 592 673 ;
+C 67 ; WX 722 ; N Chi ; B -9 0 704 673 ;
+C 68 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 69 ; WX 611 ; N Epsilon ; B 32 0 617 673 ;
+C 70 ; WX 763 ; N Phi ; B 26 0 741 673 ;
+C 71 ; WX 603 ; N Gamma ; B 24 0 609 673 ;
+C 72 ; WX 722 ; N Eta ; B 39 0 729 673 ;
+C 73 ; WX 333 ; N Iota ; B 32 0 316 673 ;
+C 74 ; WX 631 ; N theta1 ; B 18 -18 623 689 ;
+C 75 ; WX 722 ; N Kappa ; B 35 0 722 673 ;
+C 76 ; WX 686 ; N Lambda ; B 6 0 680 688 ;
+C 77 ; WX 889 ; N Mu ; B 28 0 887 673 ;
+C 78 ; WX 722 ; N Nu ; B 29 -8 720 673 ;
+C 79 ; WX 722 ; N Omicron ; B 41 -17 715 685 ;
+C 80 ; WX 768 ; N Pi ; B 25 0 745 673 ;
+C 81 ; WX 741 ; N Theta ; B 41 -17 715 685 ;
+C 82 ; WX 556 ; N Rho ; B 28 0 563 673 ;
+C 83 ; WX 592 ; N Sigma ; B 5 0 589 673 ;
+C 84 ; WX 611 ; N Tau ; B 33 0 607 673 ;
+C 85 ; WX 690 ; N Upsilon ; B -8 0 694 673 ;
+C 86 ; WX 439 ; N sigma1 ; B 40 -233 436 500 ;
+C 87 ; WX 768 ; N Omega ; B 34 0 736 688 ;
+C 88 ; WX 645 ; N Xi ; B 40 0 599 673 ;
+C 89 ; WX 795 ; N Psi ; B 15 0 781 684 ;
+C 90 ; WX 611 ; N Zeta ; B 44 0 636 673 ;
+C 91 ; WX 333 ; N bracketleft ; B 86 -155 299 674 ;
+C 92 ; WX 863 ; N therefore ; B 163 0 701 478 ;
+C 93 ; WX 333 ; N bracketright ; B 33 -155 246 674 ;
+C 94 ; WX 658 ; N perpendicular ; B 15 0 652 674 ;
+C 95 ; WX 500 ; N underscore ; B -2 -252 502 -206 ;
+C 96 ; WX 500 ; N radicalex ; B 480 881 1090 917 ;
+C 97 ; WX 631 ; N alpha ; B 41 -18 622 500 ;
+C 98 ; WX 549 ; N beta ; B 61 -223 515 741 ;
+C 99 ; WX 549 ; N chi ; B 12 -231 522 499 ;
+C 100 ; WX 494 ; N delta ; B 40 -19 481 740 ;
+C 101 ; WX 439 ; N epsilon ; B 22 -19 427 502 ;
+C 102 ; WX 521 ; N phi ; B 27 -224 490 671 ;
+C 103 ; WX 411 ; N gamma ; B 5 -225 484 499 ;
+C 104 ; WX 603 ; N eta ; B 0 -202 527 514 ;
+C 105 ; WX 329 ; N iota ; B 0 -17 301 503 ;
+C 106 ; WX 603 ; N phi1 ; B 36 -224 587 499 ;
+C 107 ; WX 549 ; N kappa ; B 33 0 558 501 ;
+C 108 ; WX 549 ; N lambda ; B 24 -17 548 739 ;
+C 109 ; WX 576 ; N mu ; B 33 -223 567 500 ;
+C 110 ; WX 521 ; N nu ; B -9 -16 475 507 ;
+C 111 ; WX 549 ; N omicron ; B 35 -19 501 499 ;
+C 112 ; WX 549 ; N pi ; B 10 -19 530 487 ;
+C 113 ; WX 521 ; N theta ; B 43 -17 485 690 ;
+C 114 ; WX 549 ; N rho ; B 50 -230 490 499 ;
+C 115 ; WX 603 ; N sigma ; B 30 -21 588 500 ;
+C 116 ; WX 439 ; N tau ; B 10 -19 418 500 ;
+C 117 ; WX 576 ; N upsilon ; B 7 -18 535 507 ;
+C 118 ; WX 713 ; N omega1 ; B 12 -18 671 583 ;
+C 119 ; WX 686 ; N omega ; B 42 -17 684 500 ;
+C 120 ; WX 493 ; N xi ; B 27 -224 469 766 ;
+C 121 ; WX 686 ; N psi ; B 12 -228 701 500 ;
+C 122 ; WX 494 ; N zeta ; B 60 -225 467 756 ;
+C 123 ; WX 480 ; N braceleft ; B 58 -183 397 673 ;
+C 124 ; WX 200 ; N bar ; B 65 -177 135 673 ;
+C 125 ; WX 480 ; N braceright ; B 79 -183 418 673 ;
+C 126 ; WX 549 ; N similar ; B 17 203 529 307 ;
+C 161 ; WX 620 ; N Upsilon1 ; B -2 0 610 685 ;
+C 162 ; WX 247 ; N minute ; B 27 459 228 735 ;
+C 163 ; WX 549 ; N lessequal ; B 29 0 526 639 ;
+C 164 ; WX 167 ; N fraction ; B -180 -12 340 677 ;
+C 165 ; WX 713 ; N infinity ; B 26 124 688 404 ;
+C 166 ; WX 500 ; N florin ; B 2 -193 494 686 ;
+C 167 ; WX 753 ; N club ; B 86 -26 660 533 ;
+C 168 ; WX 753 ; N diamond ; B 142 -36 600 550 ;
+C 169 ; WX 753 ; N heart ; B 117 -33 631 532 ;
+C 170 ; WX 753 ; N spade ; B 113 -36 629 548 ;
+C 171 ; WX 1042 ; N arrowboth ; B 24 -15 1024 511 ;
+C 172 ; WX 987 ; N arrowleft ; B 32 -15 942 511 ;
+C 173 ; WX 603 ; N arrowup ; B 45 0 571 910 ;
+C 174 ; WX 987 ; N arrowright ; B 49 -15 959 511 ;
+C 175 ; WX 603 ; N arrowdown ; B 45 -22 571 888 ;
+C 176 ; WX 400 ; N degree ; B 50 385 350 685 ;
+C 177 ; WX 549 ; N plusminus ; B 10 0 539 645 ;
+C 178 ; WX 411 ; N second ; B 20 459 413 737 ;
+C 179 ; WX 549 ; N greaterequal ; B 29 0 526 639 ;
+C 180 ; WX 549 ; N multiply ; B 17 8 533 524 ;
+C 181 ; WX 713 ; N proportional ; B 27 123 639 404 ;
+C 182 ; WX 494 ; N partialdiff ; B 26 -20 462 746 ;
+C 183 ; WX 460 ; N bullet ; B 50 113 410 473 ;
+C 184 ; WX 549 ; N divide ; B 10 71 536 456 ;
+C 185 ; WX 549 ; N notequal ; B 15 -25 540 549 ;
+C 186 ; WX 549 ; N equivalence ; B 14 82 538 443 ;
+C 187 ; WX 549 ; N approxequal ; B 14 135 527 394 ;
+C 188 ; WX 1000 ; N ellipsis ; B 111 -17 889 95 ;
+C 189 ; WX 603 ; N arrowvertex ; B 280 -120 336 1010 ;
+C 190 ; WX 1000 ; N arrowhorizex ; B -60 220 1050 276 ;
+C 191 ; WX 658 ; N carriagereturn ; B 15 -16 602 629 ;
+C 192 ; WX 823 ; N aleph ; B 175 -18 661 658 ;
+C 193 ; WX 686 ; N Ifraktur ; B 10 -53 578 740 ;
+C 194 ; WX 795 ; N Rfraktur ; B 26 -15 759 734 ;
+C 195 ; WX 987 ; N weierstrass ; B 159 -211 870 573 ;
+C 196 ; WX 768 ; N circlemultiply ; B 43 -17 733 673 ;
+C 197 ; WX 768 ; N circleplus ; B 43 -15 733 675 ;
+C 198 ; WX 823 ; N emptyset ; B 39 -24 781 719 ;
+C 199 ; WX 768 ; N intersection ; B 40 0 732 509 ;
+C 200 ; WX 768 ; N union ; B 40 -17 732 492 ;
+C 201 ; WX 713 ; N propersuperset ; B 20 0 673 470 ;
+C 202 ; WX 713 ; N reflexsuperset ; B 20 -125 673 470 ;
+C 203 ; WX 713 ; N notsubset ; B 36 -70 690 540 ;
+C 204 ; WX 713 ; N propersubset ; B 37 0 690 470 ;
+C 205 ; WX 713 ; N reflexsubset ; B 37 -125 690 470 ;
+C 206 ; WX 713 ; N element ; B 45 0 505 468 ;
+C 207 ; WX 713 ; N notelement ; B 45 -58 505 555 ;
+C 208 ; WX 768 ; N angle ; B 26 0 738 673 ;
+C 209 ; WX 713 ; N gradient ; B 36 -19 681 718 ;
+C 210 ; WX 790 ; N registerserif ; B 50 -17 740 673 ;
+C 211 ; WX 790 ; N copyrightserif ; B 51 -15 741 675 ;
+C 212 ; WX 890 ; N trademarkserif ; B 18 293 855 673 ;
+C 213 ; WX 823 ; N product ; B 25 -101 803 751 ;
+C 214 ; WX 549 ; N radical ; B 10 -38 515 917 ;
+C 215 ; WX 250 ; N dotmath ; B 69 210 169 310 ;
+C 216 ; WX 713 ; N logicalnot ; B 15 0 680 288 ;
+C 217 ; WX 603 ; N logicaland ; B 23 0 583 454 ;
+C 218 ; WX 603 ; N logicalor ; B 30 0 578 477 ;
+C 219 ; WX 1042 ; N arrowdblboth ; B 27 -20 1023 510 ;
+C 220 ; WX 987 ; N arrowdblleft ; B 30 -15 939 513 ;
+C 221 ; WX 603 ; N arrowdblup ; B 39 2 567 911 ;
+C 222 ; WX 987 ; N arrowdblright ; B 45 -20 954 508 ;
+C 223 ; WX 603 ; N arrowdbldown ; B 44 -19 572 890 ;
+C 224 ; WX 494 ; N lozenge ; B 18 0 466 745 ;
+C 225 ; WX 329 ; N angleleft ; B 25 -198 306 746 ;
+C 226 ; WX 790 ; N registersans ; B 50 -20 740 670 ;
+C 227 ; WX 790 ; N copyrightsans ; B 49 -15 739 675 ;
+C 228 ; WX 786 ; N trademarksans ; B 5 293 725 673 ;
+C 229 ; WX 713 ; N summation ; B 14 -108 695 752 ;
+C 230 ; WX 384 ; N parenlefttp ; B 40 -293 436 926 ;
+C 231 ; WX 384 ; N parenleftex ; B 40 -85 92 925 ;
+C 232 ; WX 384 ; N parenleftbt ; B 40 -293 436 926 ;
+C 233 ; WX 384 ; N bracketlefttp ; B 0 -80 341 926 ;
+C 234 ; WX 384 ; N bracketleftex ; B 0 -79 55 925 ;
+C 235 ; WX 384 ; N bracketleftbt ; B 0 -80 340 926 ;
+C 236 ; WX 494 ; N bracelefttp ; B 201 -75 439 926 ;
+C 237 ; WX 494 ; N braceleftmid ; B 14 -85 255 935 ;
+C 238 ; WX 494 ; N braceleftbt ; B 201 -70 439 926 ;
+C 239 ; WX 494 ; N braceex ; B 201 -80 255 935 ;
+C 241 ; WX 329 ; N angleright ; B 21 -198 302 746 ;
+C 242 ; WX 274 ; N integral ; B 2 -107 291 916 ;
+C 243 ; WX 686 ; N integraltp ; B 332 -83 715 921 ;
+C 244 ; WX 686 ; N integralex ; B 332 -88 415 975 ;
+C 245 ; WX 686 ; N integralbt ; B 39 -81 415 921 ;
+C 246 ; WX 384 ; N parenrighttp ; B 54 -293 450 926 ;
+C 247 ; WX 384 ; N parenrightex ; B 398 -85 450 925 ;
+C 248 ; WX 384 ; N parenrightbt ; B 54 -293 450 926 ;
+C 249 ; WX 384 ; N bracketrighttp ; B 22 -80 360 926 ;
+C 250 ; WX 384 ; N bracketrightex ; B 305 -79 360 925 ;
+C 251 ; WX 384 ; N bracketrightbt ; B 20 -80 360 926 ;
+C 252 ; WX 494 ; N bracerighttp ; B 17 -75 255 926 ;
+C 253 ; WX 494 ; N bracerightmid ; B 201 -85 442 935 ;
+C 254 ; WX 494 ; N bracerightbt ; B 17 -70 255 926 ;
+C -1 ; WX 790 ; N apple ; B 56 -3 733 808 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmb8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmb8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..55207f94d1e10c1aa6925cd1a59517b908d5c486
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmb8a.afm
@@ -0,0 +1,648 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue Mar 20 12:17:14 1990
+Comment UniqueID 28417
+Comment VMusage 30458 37350
+FontName Times-Bold
+FullName Times Bold
+FamilyName Times
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -168 -218 1000 935
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 676
+XHeight 461
+Ascender 676
+Descender -205
+StartCharMetrics 228
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 81 -13 251 691 ;
+C 34 ; WX 555 ; N quotedbl ; B 83 404 472 691 ;
+C 35 ; WX 500 ; N numbersign ; B 4 0 496 700 ;
+C 36 ; WX 500 ; N dollar ; B 29 -99 472 750 ;
+C 37 ; WX 1000 ; N percent ; B 124 -14 877 692 ;
+C 38 ; WX 833 ; N ampersand ; B 62 -16 787 691 ;
+C 39 ; WX 333 ; N quoteright ; B 79 356 263 691 ;
+C 40 ; WX 333 ; N parenleft ; B 46 -168 306 694 ;
+C 41 ; WX 333 ; N parenright ; B 27 -168 287 694 ;
+C 42 ; WX 500 ; N asterisk ; B 56 255 447 691 ;
+C 43 ; WX 570 ; N plus ; B 33 0 537 506 ;
+C 44 ; WX 250 ; N comma ; B 39 -180 223 155 ;
+C 45 ; WX 333 ; N hyphen ; B 44 171 287 287 ;
+C 46 ; WX 250 ; N period ; B 41 -13 210 156 ;
+C 47 ; WX 278 ; N slash ; B -24 -19 302 691 ;
+C 48 ; WX 500 ; N zero ; B 24 -13 476 688 ;
+C 49 ; WX 500 ; N one ; B 65 0 442 688 ;
+C 50 ; WX 500 ; N two ; B 17 0 478 688 ;
+C 51 ; WX 500 ; N three ; B 16 -14 468 688 ;
+C 52 ; WX 500 ; N four ; B 19 0 475 688 ;
+C 53 ; WX 500 ; N five ; B 22 -8 470 676 ;
+C 54 ; WX 500 ; N six ; B 28 -13 475 688 ;
+C 55 ; WX 500 ; N seven ; B 17 0 477 676 ;
+C 56 ; WX 500 ; N eight ; B 28 -13 472 688 ;
+C 57 ; WX 500 ; N nine ; B 26 -13 473 688 ;
+C 58 ; WX 333 ; N colon ; B 82 -13 251 472 ;
+C 59 ; WX 333 ; N semicolon ; B 82 -180 266 472 ;
+C 60 ; WX 570 ; N less ; B 31 -8 539 514 ;
+C 61 ; WX 570 ; N equal ; B 33 107 537 399 ;
+C 62 ; WX 570 ; N greater ; B 31 -8 539 514 ;
+C 63 ; WX 500 ; N question ; B 57 -13 445 689 ;
+C 64 ; WX 930 ; N at ; B 108 -19 822 691 ;
+C 65 ; WX 722 ; N A ; B 9 0 689 690 ;
+C 66 ; WX 667 ; N B ; B 16 0 619 676 ;
+C 67 ; WX 722 ; N C ; B 49 -19 687 691 ;
+C 68 ; WX 722 ; N D ; B 14 0 690 676 ;
+C 69 ; WX 667 ; N E ; B 16 0 641 676 ;
+C 70 ; WX 611 ; N F ; B 16 0 583 676 ;
+C 71 ; WX 778 ; N G ; B 37 -19 755 691 ;
+C 72 ; WX 778 ; N H ; B 21 0 759 676 ;
+C 73 ; WX 389 ; N I ; B 20 0 370 676 ;
+C 74 ; WX 500 ; N J ; B 3 -96 479 676 ;
+C 75 ; WX 778 ; N K ; B 30 0 769 676 ;
+C 76 ; WX 667 ; N L ; B 19 0 638 676 ;
+C 77 ; WX 944 ; N M ; B 14 0 921 676 ;
+C 78 ; WX 722 ; N N ; B 16 -18 701 676 ;
+C 79 ; WX 778 ; N O ; B 35 -19 743 691 ;
+C 80 ; WX 611 ; N P ; B 16 0 600 676 ;
+C 81 ; WX 778 ; N Q ; B 35 -176 743 691 ;
+C 82 ; WX 722 ; N R ; B 26 0 715 676 ;
+C 83 ; WX 556 ; N S ; B 35 -19 513 692 ;
+C 84 ; WX 667 ; N T ; B 31 0 636 676 ;
+C 85 ; WX 722 ; N U ; B 16 -19 701 676 ;
+C 86 ; WX 722 ; N V ; B 16 -18 701 676 ;
+C 87 ; WX 1000 ; N W ; B 19 -15 981 676 ;
+C 88 ; WX 722 ; N X ; B 16 0 699 676 ;
+C 89 ; WX 722 ; N Y ; B 15 0 699 676 ;
+C 90 ; WX 667 ; N Z ; B 28 0 634 676 ;
+C 91 ; WX 333 ; N bracketleft ; B 67 -149 301 678 ;
+C 92 ; WX 278 ; N backslash ; B -25 -19 303 691 ;
+C 93 ; WX 333 ; N bracketright ; B 32 -149 266 678 ;
+C 94 ; WX 581 ; N asciicircum ; B 73 311 509 676 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 333 ; N quoteleft ; B 70 356 254 691 ;
+C 97 ; WX 500 ; N a ; B 25 -14 488 473 ;
+C 98 ; WX 556 ; N b ; B 17 -14 521 676 ;
+C 99 ; WX 444 ; N c ; B 25 -14 430 473 ;
+C 100 ; WX 556 ; N d ; B 25 -14 534 676 ;
+C 101 ; WX 444 ; N e ; B 25 -14 426 473 ;
+C 102 ; WX 333 ; N f ; B 14 0 389 691 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B 28 -206 483 473 ;
+C 104 ; WX 556 ; N h ; B 16 0 534 676 ;
+C 105 ; WX 278 ; N i ; B 16 0 255 691 ;
+C 106 ; WX 333 ; N j ; B -57 -203 263 691 ;
+C 107 ; WX 556 ; N k ; B 22 0 543 676 ;
+C 108 ; WX 278 ; N l ; B 16 0 255 676 ;
+C 109 ; WX 833 ; N m ; B 16 0 814 473 ;
+C 110 ; WX 556 ; N n ; B 21 0 539 473 ;
+C 111 ; WX 500 ; N o ; B 25 -14 476 473 ;
+C 112 ; WX 556 ; N p ; B 19 -205 524 473 ;
+C 113 ; WX 556 ; N q ; B 34 -205 536 473 ;
+C 114 ; WX 444 ; N r ; B 29 0 434 473 ;
+C 115 ; WX 389 ; N s ; B 25 -14 361 473 ;
+C 116 ; WX 333 ; N t ; B 20 -12 332 630 ;
+C 117 ; WX 556 ; N u ; B 16 -14 537 461 ;
+C 118 ; WX 500 ; N v ; B 21 -14 485 461 ;
+C 119 ; WX 722 ; N w ; B 23 -14 707 461 ;
+C 120 ; WX 500 ; N x ; B 12 0 484 461 ;
+C 121 ; WX 500 ; N y ; B 16 -205 480 461 ;
+C 122 ; WX 444 ; N z ; B 21 0 420 461 ;
+C 123 ; WX 394 ; N braceleft ; B 22 -175 340 698 ;
+C 124 ; WX 220 ; N bar ; B 66 -19 154 691 ;
+C 125 ; WX 394 ; N braceright ; B 54 -175 372 698 ;
+C 126 ; WX 520 ; N asciitilde ; B 29 173 491 333 ;
+C 161 ; WX 333 ; N exclamdown ; B 82 -203 252 501 ;
+C 162 ; WX 500 ; N cent ; B 53 -140 458 588 ;
+C 163 ; WX 500 ; N sterling ; B 21 -14 477 684 ;
+C 164 ; WX 167 ; N fraction ; B -168 -12 329 688 ;
+C 165 ; WX 500 ; N yen ; B -64 0 547 676 ;
+C 166 ; WX 500 ; N florin ; B 0 -155 498 706 ;
+C 167 ; WX 500 ; N section ; B 57 -132 443 691 ;
+C 168 ; WX 500 ; N currency ; B -26 61 526 613 ;
+C 169 ; WX 278 ; N quotesingle ; B 75 404 204 691 ;
+C 170 ; WX 500 ; N quotedblleft ; B 32 356 486 691 ;
+C 171 ; WX 500 ; N guillemotleft ; B 23 36 473 415 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 51 36 305 415 ;
+C 173 ; WX 333 ; N guilsinglright ; B 28 36 282 415 ;
+C 174 ; WX 556 ; N fi ; B 14 0 536 691 ;
+C 175 ; WX 556 ; N fl ; B 14 0 536 691 ;
+C 177 ; WX 500 ; N endash ; B 0 181 500 271 ;
+C 178 ; WX 500 ; N dagger ; B 47 -134 453 691 ;
+C 179 ; WX 500 ; N daggerdbl ; B 45 -132 456 691 ;
+C 180 ; WX 250 ; N periodcentered ; B 41 248 210 417 ;
+C 182 ; WX 540 ; N paragraph ; B 0 -186 519 676 ;
+C 183 ; WX 350 ; N bullet ; B 35 198 315 478 ;
+C 184 ; WX 333 ; N quotesinglbase ; B 79 -180 263 155 ;
+C 185 ; WX 500 ; N quotedblbase ; B 14 -180 468 155 ;
+C 186 ; WX 500 ; N quotedblright ; B 14 356 468 691 ;
+C 187 ; WX 500 ; N guillemotright ; B 27 36 477 415 ;
+C 188 ; WX 1000 ; N ellipsis ; B 82 -13 917 156 ;
+C 189 ; WX 1000 ; N perthousand ; B 7 -29 995 706 ;
+C 191 ; WX 500 ; N questiondown ; B 55 -201 443 501 ;
+C 193 ; WX 333 ; N grave ; B 8 528 246 713 ;
+C 194 ; WX 333 ; N acute ; B 86 528 324 713 ;
+C 195 ; WX 333 ; N circumflex ; B -2 528 335 704 ;
+C 196 ; WX 333 ; N tilde ; B -16 547 349 674 ;
+C 197 ; WX 333 ; N macron ; B 1 565 331 637 ;
+C 198 ; WX 333 ; N breve ; B 15 528 318 691 ;
+C 199 ; WX 333 ; N dotaccent ; B 103 537 230 667 ;
+C 200 ; WX 333 ; N dieresis ; B -2 537 335 667 ;
+C 202 ; WX 333 ; N ring ; B 60 527 273 740 ;
+C 203 ; WX 333 ; N cedilla ; B 68 -218 294 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B -13 528 425 713 ;
+C 206 ; WX 333 ; N ogonek ; B 90 -173 319 44 ;
+C 207 ; WX 333 ; N caron ; B -2 528 335 704 ;
+C 208 ; WX 1000 ; N emdash ; B 0 181 1000 271 ;
+C 225 ; WX 1000 ; N AE ; B 4 0 951 676 ;
+C 227 ; WX 300 ; N ordfeminine ; B -1 397 301 688 ;
+C 232 ; WX 667 ; N Lslash ; B 19 0 638 676 ;
+C 233 ; WX 778 ; N Oslash ; B 35 -74 743 737 ;
+C 234 ; WX 1000 ; N OE ; B 22 -5 981 684 ;
+C 235 ; WX 330 ; N ordmasculine ; B 18 397 312 688 ;
+C 241 ; WX 722 ; N ae ; B 33 -14 693 473 ;
+C 245 ; WX 278 ; N dotlessi ; B 16 0 255 461 ;
+C 248 ; WX 278 ; N lslash ; B -22 0 303 676 ;
+C 249 ; WX 500 ; N oslash ; B 25 -92 476 549 ;
+C 250 ; WX 722 ; N oe ; B 22 -14 696 473 ;
+C 251 ; WX 556 ; N germandbls ; B 19 -12 517 691 ;
+C -1 ; WX 667 ; N Zcaron ; B 28 0 634 914 ;
+C -1 ; WX 444 ; N ccedilla ; B 25 -218 430 473 ;
+C -1 ; WX 500 ; N ydieresis ; B 16 -205 480 667 ;
+C -1 ; WX 500 ; N atilde ; B 25 -14 488 674 ;
+C -1 ; WX 278 ; N icircumflex ; B -36 0 301 704 ;
+C -1 ; WX 300 ; N threesuperior ; B 3 268 297 688 ;
+C -1 ; WX 444 ; N ecircumflex ; B 25 -14 426 704 ;
+C -1 ; WX 556 ; N thorn ; B 19 -205 524 676 ;
+C -1 ; WX 444 ; N egrave ; B 25 -14 426 713 ;
+C -1 ; WX 300 ; N twosuperior ; B 0 275 300 688 ;
+C -1 ; WX 444 ; N eacute ; B 25 -14 426 713 ;
+C -1 ; WX 500 ; N otilde ; B 25 -14 476 674 ;
+C -1 ; WX 722 ; N Aacute ; B 9 0 689 923 ;
+C -1 ; WX 500 ; N ocircumflex ; B 25 -14 476 704 ;
+C -1 ; WX 500 ; N yacute ; B 16 -205 480 713 ;
+C -1 ; WX 556 ; N udieresis ; B 16 -14 537 667 ;
+C -1 ; WX 750 ; N threequarters ; B 23 -12 733 688 ;
+C -1 ; WX 500 ; N acircumflex ; B 25 -14 488 704 ;
+C -1 ; WX 722 ; N Eth ; B 6 0 690 676 ;
+C -1 ; WX 444 ; N edieresis ; B 25 -14 426 667 ;
+C -1 ; WX 556 ; N ugrave ; B 16 -14 537 713 ;
+C -1 ; WX 1000 ; N trademark ; B 24 271 977 676 ;
+C -1 ; WX 500 ; N ograve ; B 25 -14 476 713 ;
+C -1 ; WX 389 ; N scaron ; B 25 -14 363 704 ;
+C -1 ; WX 389 ; N Idieresis ; B 20 0 370 877 ;
+C -1 ; WX 556 ; N uacute ; B 16 -14 537 713 ;
+C -1 ; WX 500 ; N agrave ; B 25 -14 488 713 ;
+C -1 ; WX 556 ; N ntilde ; B 21 0 539 674 ;
+C -1 ; WX 500 ; N aring ; B 25 -14 488 740 ;
+C -1 ; WX 444 ; N zcaron ; B 21 0 420 704 ;
+C -1 ; WX 389 ; N Icircumflex ; B 20 0 370 914 ;
+C -1 ; WX 722 ; N Ntilde ; B 16 -18 701 884 ;
+C -1 ; WX 556 ; N ucircumflex ; B 16 -14 537 704 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 16 0 641 914 ;
+C -1 ; WX 389 ; N Iacute ; B 20 0 370 923 ;
+C -1 ; WX 722 ; N Ccedilla ; B 49 -218 687 691 ;
+C -1 ; WX 778 ; N Odieresis ; B 35 -19 743 877 ;
+C -1 ; WX 556 ; N Scaron ; B 35 -19 513 914 ;
+C -1 ; WX 667 ; N Edieresis ; B 16 0 641 877 ;
+C -1 ; WX 389 ; N Igrave ; B 20 0 370 923 ;
+C -1 ; WX 500 ; N adieresis ; B 25 -14 488 667 ;
+C -1 ; WX 778 ; N Ograve ; B 35 -19 743 923 ;
+C -1 ; WX 667 ; N Egrave ; B 16 0 641 923 ;
+C -1 ; WX 722 ; N Ydieresis ; B 15 0 699 877 ;
+C -1 ; WX 747 ; N registered ; B 26 -19 721 691 ;
+C -1 ; WX 778 ; N Otilde ; B 35 -19 743 884 ;
+C -1 ; WX 750 ; N onequarter ; B 28 -12 743 688 ;
+C -1 ; WX 722 ; N Ugrave ; B 16 -19 701 923 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 16 -19 701 914 ;
+C -1 ; WX 611 ; N Thorn ; B 16 0 600 676 ;
+C -1 ; WX 570 ; N divide ; B 33 -31 537 537 ;
+C -1 ; WX 722 ; N Atilde ; B 9 0 689 884 ;
+C -1 ; WX 722 ; N Uacute ; B 16 -19 701 923 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 35 -19 743 914 ;
+C -1 ; WX 570 ; N logicalnot ; B 33 108 537 399 ;
+C -1 ; WX 722 ; N Aring ; B 9 0 689 935 ;
+C -1 ; WX 278 ; N idieresis ; B -36 0 301 667 ;
+C -1 ; WX 278 ; N iacute ; B 16 0 290 713 ;
+C -1 ; WX 500 ; N aacute ; B 25 -14 488 713 ;
+C -1 ; WX 570 ; N plusminus ; B 33 0 537 506 ;
+C -1 ; WX 570 ; N multiply ; B 48 16 522 490 ;
+C -1 ; WX 722 ; N Udieresis ; B 16 -19 701 877 ;
+C -1 ; WX 570 ; N minus ; B 33 209 537 297 ;
+C -1 ; WX 300 ; N onesuperior ; B 28 275 273 688 ;
+C -1 ; WX 667 ; N Eacute ; B 16 0 641 923 ;
+C -1 ; WX 722 ; N Acircumflex ; B 9 0 689 914 ;
+C -1 ; WX 747 ; N copyright ; B 26 -19 721 691 ;
+C -1 ; WX 722 ; N Agrave ; B 9 0 689 923 ;
+C -1 ; WX 500 ; N odieresis ; B 25 -14 476 667 ;
+C -1 ; WX 500 ; N oacute ; B 25 -14 476 713 ;
+C -1 ; WX 400 ; N degree ; B 57 402 343 688 ;
+C -1 ; WX 278 ; N igrave ; B -26 0 255 713 ;
+C -1 ; WX 556 ; N mu ; B 33 -206 536 461 ;
+C -1 ; WX 778 ; N Oacute ; B 35 -19 743 923 ;
+C -1 ; WX 500 ; N eth ; B 25 -14 476 691 ;
+C -1 ; WX 722 ; N Adieresis ; B 9 0 689 877 ;
+C -1 ; WX 722 ; N Yacute ; B 15 0 699 928 ;
+C -1 ; WX 220 ; N brokenbar ; B 66 -19 154 691 ;
+C -1 ; WX 750 ; N onehalf ; B -7 -12 775 688 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 283
+
+KPX A y -74
+KPX A w -90
+KPX A v -100
+KPX A u -50
+KPX A quoteright -74
+KPX A quotedblright 0
+KPX A p -25
+KPX A Y -100
+KPX A W -130
+KPX A V -145
+KPX A U -50
+KPX A T -95
+KPX A Q -45
+KPX A O -45
+KPX A G -55
+KPX A C -55
+
+KPX B period 0
+KPX B comma 0
+KPX B U -10
+KPX B A -30
+
+KPX D period -20
+KPX D comma 0
+KPX D Y -40
+KPX D W -40
+KPX D V -40
+KPX D A -35
+
+KPX F r 0
+KPX F period -110
+KPX F o -25
+KPX F i 0
+KPX F e -25
+KPX F comma -92
+KPX F a -25
+KPX F A -90
+
+KPX G period 0
+KPX G comma 0
+
+KPX J u -15
+KPX J period -20
+KPX J o -15
+KPX J e -15
+KPX J comma 0
+KPX J a -15
+KPX J A -30
+
+KPX K y -45
+KPX K u -15
+KPX K o -25
+KPX K e -25
+KPX K O -30
+
+KPX L y -55
+KPX L quoteright -110
+KPX L quotedblright -20
+KPX L Y -92
+KPX L W -92
+KPX L V -92
+KPX L T -92
+
+KPX N period 0
+KPX N comma 0
+KPX N A -20
+
+KPX O period 0
+KPX O comma 0
+KPX O Y -50
+KPX O X -40
+KPX O W -50
+KPX O V -50
+KPX O T -40
+KPX O A -40
+
+KPX P period -110
+KPX P o -20
+KPX P e -20
+KPX P comma -92
+KPX P a -10
+KPX P A -74
+
+KPX Q period -20
+KPX Q comma 0
+KPX Q U -10
+
+KPX R Y -35
+KPX R W -35
+KPX R V -55
+KPX R U -30
+KPX R T -40
+KPX R O -30
+
+KPX S period 0
+KPX S comma 0
+
+KPX T y -74
+KPX T w -74
+KPX T u -92
+KPX T semicolon -74
+KPX T r -74
+KPX T period -90
+KPX T o -92
+KPX T i -18
+KPX T hyphen -92
+KPX T h 0
+KPX T e -92
+KPX T comma -74
+KPX T colon -74
+KPX T a -92
+KPX T O -18
+KPX T A -90
+
+KPX U period -50
+KPX U comma -50
+KPX U A -60
+
+KPX V u -92
+KPX V semicolon -92
+KPX V period -145
+KPX V o -100
+KPX V i -37
+KPX V hyphen -74
+KPX V e -100
+KPX V comma -129
+KPX V colon -92
+KPX V a -92
+KPX V O -45
+KPX V G -30
+KPX V A -135
+
+KPX W y -60
+KPX W u -50
+KPX W semicolon -55
+KPX W period -92
+KPX W o -75
+KPX W i -18
+KPX W hyphen -37
+KPX W h 0
+KPX W e -65
+KPX W comma -92
+KPX W colon -55
+KPX W a -65
+KPX W O -10
+KPX W A -120
+
+KPX Y u -92
+KPX Y semicolon -92
+KPX Y period -92
+KPX Y o -111
+KPX Y i -37
+KPX Y hyphen -92
+KPX Y e -111
+KPX Y comma -92
+KPX Y colon -92
+KPX Y a -85
+KPX Y O -35
+KPX Y A -110
+
+KPX a y 0
+KPX a w 0
+KPX a v -25
+KPX a t 0
+KPX a p 0
+KPX a g 0
+KPX a b 0
+
+KPX b y 0
+KPX b v -15
+KPX b u -20
+KPX b period -40
+KPX b l 0
+KPX b comma 0
+KPX b b -10
+
+KPX c y 0
+KPX c period 0
+KPX c l 0
+KPX c k 0
+KPX c h 0
+KPX c comma 0
+
+KPX colon space 0
+
+KPX comma space 0
+KPX comma quoteright -55
+KPX comma quotedblright -45
+
+KPX d y 0
+KPX d w -15
+KPX d v 0
+KPX d period 0
+KPX d d 0
+KPX d comma 0
+
+KPX e y 0
+KPX e x 0
+KPX e w 0
+KPX e v -15
+KPX e period 0
+KPX e p 0
+KPX e g 0
+KPX e comma 0
+KPX e b 0
+
+KPX f quoteright 55
+KPX f quotedblright 50
+KPX f period -15
+KPX f o -25
+KPX f l 0
+KPX f i -25
+KPX f f 0
+KPX f e 0
+KPX f dotlessi -35
+KPX f comma -15
+KPX f a 0
+
+KPX g y 0
+KPX g r 0
+KPX g period -15
+KPX g o 0
+KPX g i 0
+KPX g g 0
+KPX g e 0
+KPX g comma 0
+KPX g a 0
+
+KPX h y -15
+
+KPX i v -10
+
+KPX k y -15
+KPX k o -15
+KPX k e -10
+
+KPX l y 0
+KPX l w 0
+
+KPX m y 0
+KPX m u 0
+
+KPX n y 0
+KPX n v -40
+KPX n u 0
+
+KPX o y 0
+KPX o x 0
+KPX o w -10
+KPX o v -10
+KPX o g 0
+
+KPX p y 0
+
+KPX period quoteright -55
+KPX period quotedblright -55
+
+KPX quotedblleft quoteleft 0
+KPX quotedblleft A -10
+
+KPX quotedblright space 0
+
+KPX quoteleft quoteleft -63
+KPX quoteleft A -10
+
+KPX quoteright v -20
+KPX quoteright t 0
+KPX quoteright space -74
+KPX quoteright s -37
+KPX quoteright r -20
+KPX quoteright quoteright -63
+KPX quoteright quotedblright 0
+KPX quoteright l 0
+KPX quoteright d -20
+
+KPX r y 0
+KPX r v -10
+KPX r u 0
+KPX r t 0
+KPX r s 0
+KPX r r 0
+KPX r q -18
+KPX r period -100
+KPX r p -10
+KPX r o -18
+KPX r n -15
+KPX r m 0
+KPX r l 0
+KPX r k 0
+KPX r i 0
+KPX r hyphen -37
+KPX r g -10
+KPX r e -18
+KPX r d 0
+KPX r comma -92
+KPX r c -18
+KPX r a 0
+
+KPX s w 0
+
+KPX space quoteleft 0
+KPX space quotedblleft 0
+KPX space Y -55
+KPX space W -30
+KPX space V -45
+KPX space T -30
+KPX space A -55
+
+KPX v period -70
+KPX v o -10
+KPX v e -10
+KPX v comma -55
+KPX v a -10
+
+KPX w period -70
+KPX w o -10
+KPX w h 0
+KPX w e 0
+KPX w comma -55
+KPX w a 0
+
+KPX x e 0
+
+KPX y period -70
+KPX y o -25
+KPX y e -10
+KPX y comma -55
+KPX y a 0
+
+KPX z o 0
+KPX z e 0
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 188 210 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 188 210 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 188 210 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 188 210 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 180 195 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 188 210 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 208 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 174 210 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 174 210 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 174 210 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 174 210 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 28 210 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 28 210 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 28 210 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 28 210 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 195 210 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 223 210 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 223 210 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 223 210 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 223 210 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 223 210 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 112 210 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 222 210 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 222 210 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 222 210 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 222 210 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 210 215 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 215 210 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 167 210 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 77 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 77 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 77 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 77 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 77 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 77 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 69 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 62 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 62 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 62 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 62 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -34 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -34 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -34 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -34 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 112 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 84 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 84 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 84 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 84 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 84 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 28 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 105 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 105 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 105 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 105 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 84 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 84 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 56 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmbi8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmbi8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..25ab54ea816bbcd481ebb7c085cb8aceb0ed71f2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmbi8a.afm
@@ -0,0 +1,648 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue Mar 20 13:14:55 1990
+Comment UniqueID 28425
+Comment VMusage 32721 39613
+FontName Times-BoldItalic
+FullName Times Bold Italic
+FamilyName Times
+Weight Bold
+ItalicAngle -15
+IsFixedPitch false
+FontBBox -200 -218 996 921
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.009
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 669
+XHeight 462
+Ascender 699
+Descender -205
+StartCharMetrics 228
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 389 ; N exclam ; B 67 -13 370 684 ;
+C 34 ; WX 555 ; N quotedbl ; B 136 398 536 685 ;
+C 35 ; WX 500 ; N numbersign ; B -33 0 533 700 ;
+C 36 ; WX 500 ; N dollar ; B -20 -100 497 733 ;
+C 37 ; WX 833 ; N percent ; B 39 -10 793 692 ;
+C 38 ; WX 778 ; N ampersand ; B 5 -19 699 682 ;
+C 39 ; WX 333 ; N quoteright ; B 98 369 302 685 ;
+C 40 ; WX 333 ; N parenleft ; B 28 -179 344 685 ;
+C 41 ; WX 333 ; N parenright ; B -44 -179 271 685 ;
+C 42 ; WX 500 ; N asterisk ; B 65 249 456 685 ;
+C 43 ; WX 570 ; N plus ; B 33 0 537 506 ;
+C 44 ; WX 250 ; N comma ; B -60 -182 144 134 ;
+C 45 ; WX 333 ; N hyphen ; B 2 166 271 282 ;
+C 46 ; WX 250 ; N period ; B -9 -13 139 135 ;
+C 47 ; WX 278 ; N slash ; B -64 -18 342 685 ;
+C 48 ; WX 500 ; N zero ; B 17 -14 477 683 ;
+C 49 ; WX 500 ; N one ; B 5 0 419 683 ;
+C 50 ; WX 500 ; N two ; B -27 0 446 683 ;
+C 51 ; WX 500 ; N three ; B -15 -13 450 683 ;
+C 52 ; WX 500 ; N four ; B -15 0 503 683 ;
+C 53 ; WX 500 ; N five ; B -11 -13 487 669 ;
+C 54 ; WX 500 ; N six ; B 23 -15 509 679 ;
+C 55 ; WX 500 ; N seven ; B 52 0 525 669 ;
+C 56 ; WX 500 ; N eight ; B 3 -13 476 683 ;
+C 57 ; WX 500 ; N nine ; B -12 -10 475 683 ;
+C 58 ; WX 333 ; N colon ; B 23 -13 264 459 ;
+C 59 ; WX 333 ; N semicolon ; B -25 -183 264 459 ;
+C 60 ; WX 570 ; N less ; B 31 -8 539 514 ;
+C 61 ; WX 570 ; N equal ; B 33 107 537 399 ;
+C 62 ; WX 570 ; N greater ; B 31 -8 539 514 ;
+C 63 ; WX 500 ; N question ; B 79 -13 470 684 ;
+C 64 ; WX 832 ; N at ; B 63 -18 770 685 ;
+C 65 ; WX 667 ; N A ; B -67 0 593 683 ;
+C 66 ; WX 667 ; N B ; B -24 0 624 669 ;
+C 67 ; WX 667 ; N C ; B 32 -18 677 685 ;
+C 68 ; WX 722 ; N D ; B -46 0 685 669 ;
+C 69 ; WX 667 ; N E ; B -27 0 653 669 ;
+C 70 ; WX 667 ; N F ; B -13 0 660 669 ;
+C 71 ; WX 722 ; N G ; B 21 -18 706 685 ;
+C 72 ; WX 778 ; N H ; B -24 0 799 669 ;
+C 73 ; WX 389 ; N I ; B -32 0 406 669 ;
+C 74 ; WX 500 ; N J ; B -46 -99 524 669 ;
+C 75 ; WX 667 ; N K ; B -21 0 702 669 ;
+C 76 ; WX 611 ; N L ; B -22 0 590 669 ;
+C 77 ; WX 889 ; N M ; B -29 -12 917 669 ;
+C 78 ; WX 722 ; N N ; B -27 -15 748 669 ;
+C 79 ; WX 722 ; N O ; B 27 -18 691 685 ;
+C 80 ; WX 611 ; N P ; B -27 0 613 669 ;
+C 81 ; WX 722 ; N Q ; B 27 -208 691 685 ;
+C 82 ; WX 667 ; N R ; B -29 0 623 669 ;
+C 83 ; WX 556 ; N S ; B 2 -18 526 685 ;
+C 84 ; WX 611 ; N T ; B 50 0 650 669 ;
+C 85 ; WX 722 ; N U ; B 67 -18 744 669 ;
+C 86 ; WX 667 ; N V ; B 65 -18 715 669 ;
+C 87 ; WX 889 ; N W ; B 65 -18 940 669 ;
+C 88 ; WX 667 ; N X ; B -24 0 694 669 ;
+C 89 ; WX 611 ; N Y ; B 73 0 659 669 ;
+C 90 ; WX 611 ; N Z ; B -11 0 590 669 ;
+C 91 ; WX 333 ; N bracketleft ; B -37 -159 362 674 ;
+C 92 ; WX 278 ; N backslash ; B -1 -18 279 685 ;
+C 93 ; WX 333 ; N bracketright ; B -56 -157 343 674 ;
+C 94 ; WX 570 ; N asciicircum ; B 67 304 503 669 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 333 ; N quoteleft ; B 128 369 332 685 ;
+C 97 ; WX 500 ; N a ; B -21 -14 455 462 ;
+C 98 ; WX 500 ; N b ; B -14 -13 444 699 ;
+C 99 ; WX 444 ; N c ; B -5 -13 392 462 ;
+C 100 ; WX 500 ; N d ; B -21 -13 517 699 ;
+C 101 ; WX 444 ; N e ; B 5 -13 398 462 ;
+C 102 ; WX 333 ; N f ; B -169 -205 446 698 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B -52 -203 478 462 ;
+C 104 ; WX 556 ; N h ; B -13 -9 498 699 ;
+C 105 ; WX 278 ; N i ; B 2 -9 263 684 ;
+C 106 ; WX 278 ; N j ; B -189 -207 279 684 ;
+C 107 ; WX 500 ; N k ; B -23 -8 483 699 ;
+C 108 ; WX 278 ; N l ; B 2 -9 290 699 ;
+C 109 ; WX 778 ; N m ; B -14 -9 722 462 ;
+C 110 ; WX 556 ; N n ; B -6 -9 493 462 ;
+C 111 ; WX 500 ; N o ; B -3 -13 441 462 ;
+C 112 ; WX 500 ; N p ; B -120 -205 446 462 ;
+C 113 ; WX 500 ; N q ; B 1 -205 471 462 ;
+C 114 ; WX 389 ; N r ; B -21 0 389 462 ;
+C 115 ; WX 389 ; N s ; B -19 -13 333 462 ;
+C 116 ; WX 278 ; N t ; B -11 -9 281 594 ;
+C 117 ; WX 556 ; N u ; B 15 -9 492 462 ;
+C 118 ; WX 444 ; N v ; B 16 -13 401 462 ;
+C 119 ; WX 667 ; N w ; B 16 -13 614 462 ;
+C 120 ; WX 500 ; N x ; B -46 -13 469 462 ;
+C 121 ; WX 444 ; N y ; B -94 -205 392 462 ;
+C 122 ; WX 389 ; N z ; B -43 -78 368 449 ;
+C 123 ; WX 348 ; N braceleft ; B 5 -187 436 686 ;
+C 124 ; WX 220 ; N bar ; B 66 -18 154 685 ;
+C 125 ; WX 348 ; N braceright ; B -129 -187 302 686 ;
+C 126 ; WX 570 ; N asciitilde ; B 54 173 516 333 ;
+C 161 ; WX 389 ; N exclamdown ; B 19 -205 322 492 ;
+C 162 ; WX 500 ; N cent ; B 42 -143 439 576 ;
+C 163 ; WX 500 ; N sterling ; B -32 -12 510 683 ;
+C 164 ; WX 167 ; N fraction ; B -169 -14 324 683 ;
+C 165 ; WX 500 ; N yen ; B 33 0 628 669 ;
+C 166 ; WX 500 ; N florin ; B -87 -156 537 707 ;
+C 167 ; WX 500 ; N section ; B 36 -143 459 685 ;
+C 168 ; WX 500 ; N currency ; B -26 34 526 586 ;
+C 169 ; WX 278 ; N quotesingle ; B 128 398 268 685 ;
+C 170 ; WX 500 ; N quotedblleft ; B 53 369 513 685 ;
+C 171 ; WX 500 ; N guillemotleft ; B 12 32 468 415 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 32 32 303 415 ;
+C 173 ; WX 333 ; N guilsinglright ; B 10 32 281 415 ;
+C 174 ; WX 556 ; N fi ; B -188 -205 514 703 ;
+C 175 ; WX 556 ; N fl ; B -186 -205 553 704 ;
+C 177 ; WX 500 ; N endash ; B -40 178 477 269 ;
+C 178 ; WX 500 ; N dagger ; B 91 -145 494 685 ;
+C 179 ; WX 500 ; N daggerdbl ; B 10 -139 493 685 ;
+C 180 ; WX 250 ; N periodcentered ; B 51 257 199 405 ;
+C 182 ; WX 500 ; N paragraph ; B -57 -193 562 669 ;
+C 183 ; WX 350 ; N bullet ; B 0 175 350 525 ;
+C 184 ; WX 333 ; N quotesinglbase ; B -5 -182 199 134 ;
+C 185 ; WX 500 ; N quotedblbase ; B -57 -182 403 134 ;
+C 186 ; WX 500 ; N quotedblright ; B 53 369 513 685 ;
+C 187 ; WX 500 ; N guillemotright ; B 12 32 468 415 ;
+C 188 ; WX 1000 ; N ellipsis ; B 40 -13 852 135 ;
+C 189 ; WX 1000 ; N perthousand ; B 7 -29 996 706 ;
+C 191 ; WX 500 ; N questiondown ; B 30 -205 421 492 ;
+C 193 ; WX 333 ; N grave ; B 85 516 297 697 ;
+C 194 ; WX 333 ; N acute ; B 139 516 379 697 ;
+C 195 ; WX 333 ; N circumflex ; B 40 516 367 690 ;
+C 196 ; WX 333 ; N tilde ; B 48 536 407 655 ;
+C 197 ; WX 333 ; N macron ; B 51 553 393 623 ;
+C 198 ; WX 333 ; N breve ; B 71 516 387 678 ;
+C 199 ; WX 333 ; N dotaccent ; B 163 525 293 655 ;
+C 200 ; WX 333 ; N dieresis ; B 55 525 397 655 ;
+C 202 ; WX 333 ; N ring ; B 127 516 340 729 ;
+C 203 ; WX 333 ; N cedilla ; B -80 -218 156 5 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 69 516 498 697 ;
+C 206 ; WX 333 ; N ogonek ; B -40 -173 189 44 ;
+C 207 ; WX 333 ; N caron ; B 79 516 411 690 ;
+C 208 ; WX 1000 ; N emdash ; B -40 178 977 269 ;
+C 225 ; WX 944 ; N AE ; B -64 0 918 669 ;
+C 227 ; WX 266 ; N ordfeminine ; B 16 399 330 685 ;
+C 232 ; WX 611 ; N Lslash ; B -22 0 590 669 ;
+C 233 ; WX 722 ; N Oslash ; B 27 -125 691 764 ;
+C 234 ; WX 944 ; N OE ; B 23 -8 946 677 ;
+C 235 ; WX 300 ; N ordmasculine ; B 56 400 347 685 ;
+C 241 ; WX 722 ; N ae ; B -5 -13 673 462 ;
+C 245 ; WX 278 ; N dotlessi ; B 2 -9 238 462 ;
+C 248 ; WX 278 ; N lslash ; B -13 -9 301 699 ;
+C 249 ; WX 500 ; N oslash ; B -3 -119 441 560 ;
+C 250 ; WX 722 ; N oe ; B 6 -13 674 462 ;
+C 251 ; WX 500 ; N germandbls ; B -200 -200 473 705 ;
+C -1 ; WX 611 ; N Zcaron ; B -11 0 590 897 ;
+C -1 ; WX 444 ; N ccedilla ; B -24 -218 392 462 ;
+C -1 ; WX 444 ; N ydieresis ; B -94 -205 438 655 ;
+C -1 ; WX 500 ; N atilde ; B -21 -14 491 655 ;
+C -1 ; WX 278 ; N icircumflex ; B -2 -9 325 690 ;
+C -1 ; WX 300 ; N threesuperior ; B 17 265 321 683 ;
+C -1 ; WX 444 ; N ecircumflex ; B 5 -13 423 690 ;
+C -1 ; WX 500 ; N thorn ; B -120 -205 446 699 ;
+C -1 ; WX 444 ; N egrave ; B 5 -13 398 697 ;
+C -1 ; WX 300 ; N twosuperior ; B 2 274 313 683 ;
+C -1 ; WX 444 ; N eacute ; B 5 -13 435 697 ;
+C -1 ; WX 500 ; N otilde ; B -3 -13 491 655 ;
+C -1 ; WX 667 ; N Aacute ; B -67 0 593 904 ;
+C -1 ; WX 500 ; N ocircumflex ; B -3 -13 451 690 ;
+C -1 ; WX 444 ; N yacute ; B -94 -205 435 697 ;
+C -1 ; WX 556 ; N udieresis ; B 15 -9 494 655 ;
+C -1 ; WX 750 ; N threequarters ; B 7 -14 726 683 ;
+C -1 ; WX 500 ; N acircumflex ; B -21 -14 455 690 ;
+C -1 ; WX 722 ; N Eth ; B -31 0 700 669 ;
+C -1 ; WX 444 ; N edieresis ; B 5 -13 443 655 ;
+C -1 ; WX 556 ; N ugrave ; B 15 -9 492 697 ;
+C -1 ; WX 1000 ; N trademark ; B 32 263 968 669 ;
+C -1 ; WX 500 ; N ograve ; B -3 -13 441 697 ;
+C -1 ; WX 389 ; N scaron ; B -19 -13 439 690 ;
+C -1 ; WX 389 ; N Idieresis ; B -32 0 445 862 ;
+C -1 ; WX 556 ; N uacute ; B 15 -9 492 697 ;
+C -1 ; WX 500 ; N agrave ; B -21 -14 455 697 ;
+C -1 ; WX 556 ; N ntilde ; B -6 -9 504 655 ;
+C -1 ; WX 500 ; N aring ; B -21 -14 455 729 ;
+C -1 ; WX 389 ; N zcaron ; B -43 -78 424 690 ;
+C -1 ; WX 389 ; N Icircumflex ; B -32 0 420 897 ;
+C -1 ; WX 722 ; N Ntilde ; B -27 -15 748 862 ;
+C -1 ; WX 556 ; N ucircumflex ; B 15 -9 492 690 ;
+C -1 ; WX 667 ; N Ecircumflex ; B -27 0 653 897 ;
+C -1 ; WX 389 ; N Iacute ; B -32 0 412 904 ;
+C -1 ; WX 667 ; N Ccedilla ; B 32 -218 677 685 ;
+C -1 ; WX 722 ; N Odieresis ; B 27 -18 691 862 ;
+C -1 ; WX 556 ; N Scaron ; B 2 -18 526 897 ;
+C -1 ; WX 667 ; N Edieresis ; B -27 0 653 862 ;
+C -1 ; WX 389 ; N Igrave ; B -32 0 406 904 ;
+C -1 ; WX 500 ; N adieresis ; B -21 -14 471 655 ;
+C -1 ; WX 722 ; N Ograve ; B 27 -18 691 904 ;
+C -1 ; WX 667 ; N Egrave ; B -27 0 653 904 ;
+C -1 ; WX 611 ; N Ydieresis ; B 73 0 659 862 ;
+C -1 ; WX 747 ; N registered ; B 30 -18 718 685 ;
+C -1 ; WX 722 ; N Otilde ; B 27 -18 691 862 ;
+C -1 ; WX 750 ; N onequarter ; B 7 -14 721 683 ;
+C -1 ; WX 722 ; N Ugrave ; B 67 -18 744 904 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 67 -18 744 897 ;
+C -1 ; WX 611 ; N Thorn ; B -27 0 573 669 ;
+C -1 ; WX 570 ; N divide ; B 33 -29 537 535 ;
+C -1 ; WX 667 ; N Atilde ; B -67 0 593 862 ;
+C -1 ; WX 722 ; N Uacute ; B 67 -18 744 904 ;
+C -1 ; WX 722 ; N Ocircumflex ; B 27 -18 691 897 ;
+C -1 ; WX 606 ; N logicalnot ; B 51 108 555 399 ;
+C -1 ; WX 667 ; N Aring ; B -67 0 593 921 ;
+C -1 ; WX 278 ; N idieresis ; B 2 -9 360 655 ;
+C -1 ; WX 278 ; N iacute ; B 2 -9 352 697 ;
+C -1 ; WX 500 ; N aacute ; B -21 -14 463 697 ;
+C -1 ; WX 570 ; N plusminus ; B 33 0 537 506 ;
+C -1 ; WX 570 ; N multiply ; B 48 16 522 490 ;
+C -1 ; WX 722 ; N Udieresis ; B 67 -18 744 862 ;
+C -1 ; WX 606 ; N minus ; B 51 209 555 297 ;
+C -1 ; WX 300 ; N onesuperior ; B 30 274 301 683 ;
+C -1 ; WX 667 ; N Eacute ; B -27 0 653 904 ;
+C -1 ; WX 667 ; N Acircumflex ; B -67 0 593 897 ;
+C -1 ; WX 747 ; N copyright ; B 30 -18 718 685 ;
+C -1 ; WX 667 ; N Agrave ; B -67 0 593 904 ;
+C -1 ; WX 500 ; N odieresis ; B -3 -13 466 655 ;
+C -1 ; WX 500 ; N oacute ; B -3 -13 463 697 ;
+C -1 ; WX 400 ; N degree ; B 83 397 369 683 ;
+C -1 ; WX 278 ; N igrave ; B 2 -9 260 697 ;
+C -1 ; WX 576 ; N mu ; B -60 -207 516 449 ;
+C -1 ; WX 722 ; N Oacute ; B 27 -18 691 904 ;
+C -1 ; WX 500 ; N eth ; B -3 -13 454 699 ;
+C -1 ; WX 667 ; N Adieresis ; B -67 0 593 862 ;
+C -1 ; WX 611 ; N Yacute ; B 73 0 659 904 ;
+C -1 ; WX 220 ; N brokenbar ; B 66 -18 154 685 ;
+C -1 ; WX 750 ; N onehalf ; B -9 -14 723 683 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 283
+
+KPX A y -74
+KPX A w -74
+KPX A v -74
+KPX A u -30
+KPX A quoteright -74
+KPX A quotedblright 0
+KPX A p 0
+KPX A Y -70
+KPX A W -100
+KPX A V -95
+KPX A U -50
+KPX A T -55
+KPX A Q -55
+KPX A O -50
+KPX A G -60
+KPX A C -65
+
+KPX B period 0
+KPX B comma 0
+KPX B U -10
+KPX B A -25
+
+KPX D period 0
+KPX D comma 0
+KPX D Y -50
+KPX D W -40
+KPX D V -50
+KPX D A -25
+
+KPX F r -50
+KPX F period -129
+KPX F o -70
+KPX F i -40
+KPX F e -100
+KPX F comma -129
+KPX F a -95
+KPX F A -100
+
+KPX G period 0
+KPX G comma 0
+
+KPX J u -40
+KPX J period -10
+KPX J o -40
+KPX J e -40
+KPX J comma -10
+KPX J a -40
+KPX J A -25
+
+KPX K y -20
+KPX K u -20
+KPX K o -25
+KPX K e -25
+KPX K O -30
+
+KPX L y -37
+KPX L quoteright -55
+KPX L quotedblright 0
+KPX L Y -37
+KPX L W -37
+KPX L V -37
+KPX L T -18
+
+KPX N period 0
+KPX N comma 0
+KPX N A -30
+
+KPX O period 0
+KPX O comma 0
+KPX O Y -50
+KPX O X -40
+KPX O W -50
+KPX O V -50
+KPX O T -40
+KPX O A -40
+
+KPX P period -129
+KPX P o -55
+KPX P e -50
+KPX P comma -129
+KPX P a -40
+KPX P A -85
+
+KPX Q period 0
+KPX Q comma 0
+KPX Q U -10
+
+KPX R Y -18
+KPX R W -18
+KPX R V -18
+KPX R U -40
+KPX R T -30
+KPX R O -40
+
+KPX S period 0
+KPX S comma 0
+
+KPX T y -37
+KPX T w -37
+KPX T u -37
+KPX T semicolon -74
+KPX T r -37
+KPX T period -92
+KPX T o -95
+KPX T i -37
+KPX T hyphen -92
+KPX T h 0
+KPX T e -92
+KPX T comma -92
+KPX T colon -74
+KPX T a -92
+KPX T O -18
+KPX T A -55
+
+KPX U period 0
+KPX U comma 0
+KPX U A -45
+
+KPX V u -55
+KPX V semicolon -74
+KPX V period -129
+KPX V o -111
+KPX V i -55
+KPX V hyphen -70
+KPX V e -111
+KPX V comma -129
+KPX V colon -74
+KPX V a -111
+KPX V O -30
+KPX V G -10
+KPX V A -85
+
+KPX W y -55
+KPX W u -55
+KPX W semicolon -55
+KPX W period -74
+KPX W o -80
+KPX W i -37
+KPX W hyphen -50
+KPX W h 0
+KPX W e -90
+KPX W comma -74
+KPX W colon -55
+KPX W a -85
+KPX W O -15
+KPX W A -74
+
+KPX Y u -92
+KPX Y semicolon -92
+KPX Y period -74
+KPX Y o -111
+KPX Y i -55
+KPX Y hyphen -92
+KPX Y e -111
+KPX Y comma -92
+KPX Y colon -92
+KPX Y a -92
+KPX Y O -25
+KPX Y A -74
+
+KPX a y 0
+KPX a w 0
+KPX a v 0
+KPX a t 0
+KPX a p 0
+KPX a g 0
+KPX a b 0
+
+KPX b y 0
+KPX b v 0
+KPX b u -20
+KPX b period -40
+KPX b l 0
+KPX b comma 0
+KPX b b -10
+
+KPX c y 0
+KPX c period 0
+KPX c l 0
+KPX c k -10
+KPX c h -10
+KPX c comma 0
+
+KPX colon space 0
+
+KPX comma space 0
+KPX comma quoteright -95
+KPX comma quotedblright -95
+
+KPX d y 0
+KPX d w 0
+KPX d v 0
+KPX d period 0
+KPX d d 0
+KPX d comma 0
+
+KPX e y 0
+KPX e x 0
+KPX e w 0
+KPX e v 0
+KPX e period 0
+KPX e p 0
+KPX e g 0
+KPX e comma 0
+KPX e b -10
+
+KPX f quoteright 55
+KPX f quotedblright 0
+KPX f period -10
+KPX f o -10
+KPX f l 0
+KPX f i 0
+KPX f f -18
+KPX f e -10
+KPX f dotlessi -30
+KPX f comma -10
+KPX f a 0
+
+KPX g y 0
+KPX g r 0
+KPX g period 0
+KPX g o 0
+KPX g i 0
+KPX g g 0
+KPX g e 0
+KPX g comma 0
+KPX g a 0
+
+KPX h y 0
+
+KPX i v 0
+
+KPX k y 0
+KPX k o -10
+KPX k e -30
+
+KPX l y 0
+KPX l w 0
+
+KPX m y 0
+KPX m u 0
+
+KPX n y 0
+KPX n v -40
+KPX n u 0
+
+KPX o y -10
+KPX o x -10
+KPX o w -25
+KPX o v -15
+KPX o g 0
+
+KPX p y 0
+
+KPX period quoteright -95
+KPX period quotedblright -95
+
+KPX quotedblleft quoteleft 0
+KPX quotedblleft A 0
+
+KPX quotedblright space 0
+
+KPX quoteleft quoteleft -74
+KPX quoteleft A 0
+
+KPX quoteright v -15
+KPX quoteright t -37
+KPX quoteright space -74
+KPX quoteright s -74
+KPX quoteright r -15
+KPX quoteright quoteright -74
+KPX quoteright quotedblright 0
+KPX quoteright l 0
+KPX quoteright d -15
+
+KPX r y 0
+KPX r v 0
+KPX r u 0
+KPX r t 0
+KPX r s 0
+KPX r r 0
+KPX r q 0
+KPX r period -65
+KPX r p 0
+KPX r o 0
+KPX r n 0
+KPX r m 0
+KPX r l 0
+KPX r k 0
+KPX r i 0
+KPX r hyphen 0
+KPX r g 0
+KPX r e 0
+KPX r d 0
+KPX r comma -65
+KPX r c 0
+KPX r a 0
+
+KPX s w 0
+
+KPX space quoteleft 0
+KPX space quotedblleft 0
+KPX space Y -70
+KPX space W -70
+KPX space V -70
+KPX space T 0
+KPX space A -37
+
+KPX v period -37
+KPX v o -15
+KPX v e -15
+KPX v comma -37
+KPX v a 0
+
+KPX w period -37
+KPX w o -15
+KPX w h 0
+KPX w e -10
+KPX w comma -37
+KPX w a -10
+
+KPX x e -10
+
+KPX y period -37
+KPX y o 0
+KPX y e 0
+KPX y comma -37
+KPX y a 0
+
+KPX z o 0
+KPX z e 0
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 172 207 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 187 207 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 167 207 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 172 207 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 157 192 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 167 207 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 167 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 172 207 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 187 207 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 187 207 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 172 207 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 33 207 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 53 207 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 48 207 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 33 207 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 210 207 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 200 207 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 230 207 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 215 207 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 200 207 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 215 207 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 112 207 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 210 207 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 230 207 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 230 207 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 200 207 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 154 207 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 169 207 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 139 207 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 84 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 84 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 74 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 74 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 84 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 84 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 56 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 56 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 56 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 46 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 46 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -27 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -42 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -37 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -37 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 97 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 84 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 84 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 69 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 74 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 84 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 28 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 112 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 112 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 97 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 102 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 56 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 41 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 13 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmr8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmr8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..e5092b5c850763ffba89dfa3c83d826c6e254a14
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmr8a.afm
@@ -0,0 +1,648 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue Mar 20 12:15:44 1990
+Comment UniqueID 28416
+Comment VMusage 30487 37379
+FontName Times-Roman
+FullName Times Roman
+FamilyName Times
+Weight Roman
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -168 -218 1000 898
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 662
+XHeight 450
+Ascender 683
+Descender -217
+StartCharMetrics 228
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 130 -9 238 676 ;
+C 34 ; WX 408 ; N quotedbl ; B 77 431 331 676 ;
+C 35 ; WX 500 ; N numbersign ; B 5 0 496 662 ;
+C 36 ; WX 500 ; N dollar ; B 44 -87 457 727 ;
+C 37 ; WX 833 ; N percent ; B 61 -13 772 676 ;
+C 38 ; WX 778 ; N ampersand ; B 42 -13 750 676 ;
+C 39 ; WX 333 ; N quoteright ; B 79 433 218 676 ;
+C 40 ; WX 333 ; N parenleft ; B 48 -177 304 676 ;
+C 41 ; WX 333 ; N parenright ; B 29 -177 285 676 ;
+C 42 ; WX 500 ; N asterisk ; B 69 265 432 676 ;
+C 43 ; WX 564 ; N plus ; B 30 0 534 506 ;
+C 44 ; WX 250 ; N comma ; B 56 -141 195 102 ;
+C 45 ; WX 333 ; N hyphen ; B 39 194 285 257 ;
+C 46 ; WX 250 ; N period ; B 70 -11 181 100 ;
+C 47 ; WX 278 ; N slash ; B -9 -14 287 676 ;
+C 48 ; WX 500 ; N zero ; B 24 -14 476 676 ;
+C 49 ; WX 500 ; N one ; B 111 0 394 676 ;
+C 50 ; WX 500 ; N two ; B 30 0 475 676 ;
+C 51 ; WX 500 ; N three ; B 43 -14 431 676 ;
+C 52 ; WX 500 ; N four ; B 12 0 472 676 ;
+C 53 ; WX 500 ; N five ; B 32 -14 438 688 ;
+C 54 ; WX 500 ; N six ; B 34 -14 468 684 ;
+C 55 ; WX 500 ; N seven ; B 20 -8 449 662 ;
+C 56 ; WX 500 ; N eight ; B 56 -14 445 676 ;
+C 57 ; WX 500 ; N nine ; B 30 -22 459 676 ;
+C 58 ; WX 278 ; N colon ; B 81 -11 192 459 ;
+C 59 ; WX 278 ; N semicolon ; B 80 -141 219 459 ;
+C 60 ; WX 564 ; N less ; B 28 -8 536 514 ;
+C 61 ; WX 564 ; N equal ; B 30 120 534 386 ;
+C 62 ; WX 564 ; N greater ; B 28 -8 536 514 ;
+C 63 ; WX 444 ; N question ; B 68 -8 414 676 ;
+C 64 ; WX 921 ; N at ; B 116 -14 809 676 ;
+C 65 ; WX 722 ; N A ; B 15 0 706 674 ;
+C 66 ; WX 667 ; N B ; B 17 0 593 662 ;
+C 67 ; WX 667 ; N C ; B 28 -14 633 676 ;
+C 68 ; WX 722 ; N D ; B 16 0 685 662 ;
+C 69 ; WX 611 ; N E ; B 12 0 597 662 ;
+C 70 ; WX 556 ; N F ; B 12 0 546 662 ;
+C 71 ; WX 722 ; N G ; B 32 -14 709 676 ;
+C 72 ; WX 722 ; N H ; B 19 0 702 662 ;
+C 73 ; WX 333 ; N I ; B 18 0 315 662 ;
+C 74 ; WX 389 ; N J ; B 10 -14 370 662 ;
+C 75 ; WX 722 ; N K ; B 34 0 723 662 ;
+C 76 ; WX 611 ; N L ; B 12 0 598 662 ;
+C 77 ; WX 889 ; N M ; B 12 0 863 662 ;
+C 78 ; WX 722 ; N N ; B 12 -11 707 662 ;
+C 79 ; WX 722 ; N O ; B 34 -14 688 676 ;
+C 80 ; WX 556 ; N P ; B 16 0 542 662 ;
+C 81 ; WX 722 ; N Q ; B 34 -178 701 676 ;
+C 82 ; WX 667 ; N R ; B 17 0 659 662 ;
+C 83 ; WX 556 ; N S ; B 42 -14 491 676 ;
+C 84 ; WX 611 ; N T ; B 17 0 593 662 ;
+C 85 ; WX 722 ; N U ; B 14 -14 705 662 ;
+C 86 ; WX 722 ; N V ; B 16 -11 697 662 ;
+C 87 ; WX 944 ; N W ; B 5 -11 932 662 ;
+C 88 ; WX 722 ; N X ; B 10 0 704 662 ;
+C 89 ; WX 722 ; N Y ; B 22 0 703 662 ;
+C 90 ; WX 611 ; N Z ; B 9 0 597 662 ;
+C 91 ; WX 333 ; N bracketleft ; B 88 -156 299 662 ;
+C 92 ; WX 278 ; N backslash ; B -9 -14 287 676 ;
+C 93 ; WX 333 ; N bracketright ; B 34 -156 245 662 ;
+C 94 ; WX 469 ; N asciicircum ; B 24 297 446 662 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 333 ; N quoteleft ; B 115 433 254 676 ;
+C 97 ; WX 444 ; N a ; B 37 -10 442 460 ;
+C 98 ; WX 500 ; N b ; B 3 -10 468 683 ;
+C 99 ; WX 444 ; N c ; B 25 -10 412 460 ;
+C 100 ; WX 500 ; N d ; B 27 -10 491 683 ;
+C 101 ; WX 444 ; N e ; B 25 -10 424 460 ;
+C 102 ; WX 333 ; N f ; B 20 0 383 683 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B 28 -218 470 460 ;
+C 104 ; WX 500 ; N h ; B 9 0 487 683 ;
+C 105 ; WX 278 ; N i ; B 16 0 253 683 ;
+C 106 ; WX 278 ; N j ; B -70 -218 194 683 ;
+C 107 ; WX 500 ; N k ; B 7 0 505 683 ;
+C 108 ; WX 278 ; N l ; B 19 0 257 683 ;
+C 109 ; WX 778 ; N m ; B 16 0 775 460 ;
+C 110 ; WX 500 ; N n ; B 16 0 485 460 ;
+C 111 ; WX 500 ; N o ; B 29 -10 470 460 ;
+C 112 ; WX 500 ; N p ; B 5 -217 470 460 ;
+C 113 ; WX 500 ; N q ; B 24 -217 488 460 ;
+C 114 ; WX 333 ; N r ; B 5 0 335 460 ;
+C 115 ; WX 389 ; N s ; B 51 -10 348 460 ;
+C 116 ; WX 278 ; N t ; B 13 -10 279 579 ;
+C 117 ; WX 500 ; N u ; B 9 -10 479 450 ;
+C 118 ; WX 500 ; N v ; B 19 -14 477 450 ;
+C 119 ; WX 722 ; N w ; B 21 -14 694 450 ;
+C 120 ; WX 500 ; N x ; B 17 0 479 450 ;
+C 121 ; WX 500 ; N y ; B 14 -218 475 450 ;
+C 122 ; WX 444 ; N z ; B 27 0 418 450 ;
+C 123 ; WX 480 ; N braceleft ; B 100 -181 350 680 ;
+C 124 ; WX 200 ; N bar ; B 67 -14 133 676 ;
+C 125 ; WX 480 ; N braceright ; B 130 -181 380 680 ;
+C 126 ; WX 541 ; N asciitilde ; B 40 183 502 323 ;
+C 161 ; WX 333 ; N exclamdown ; B 97 -218 205 467 ;
+C 162 ; WX 500 ; N cent ; B 53 -138 448 579 ;
+C 163 ; WX 500 ; N sterling ; B 12 -8 490 676 ;
+C 164 ; WX 167 ; N fraction ; B -168 -14 331 676 ;
+C 165 ; WX 500 ; N yen ; B -53 0 512 662 ;
+C 166 ; WX 500 ; N florin ; B 7 -189 490 676 ;
+C 167 ; WX 500 ; N section ; B 70 -148 426 676 ;
+C 168 ; WX 500 ; N currency ; B -22 58 522 602 ;
+C 169 ; WX 180 ; N quotesingle ; B 48 431 133 676 ;
+C 170 ; WX 444 ; N quotedblleft ; B 43 433 414 676 ;
+C 171 ; WX 500 ; N guillemotleft ; B 42 33 456 416 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 63 33 285 416 ;
+C 173 ; WX 333 ; N guilsinglright ; B 48 33 270 416 ;
+C 174 ; WX 556 ; N fi ; B 31 0 521 683 ;
+C 175 ; WX 556 ; N fl ; B 32 0 521 683 ;
+C 177 ; WX 500 ; N endash ; B 0 201 500 250 ;
+C 178 ; WX 500 ; N dagger ; B 59 -149 442 676 ;
+C 179 ; WX 500 ; N daggerdbl ; B 58 -153 442 676 ;
+C 180 ; WX 250 ; N periodcentered ; B 70 199 181 310 ;
+C 182 ; WX 453 ; N paragraph ; B -22 -154 450 662 ;
+C 183 ; WX 350 ; N bullet ; B 40 196 310 466 ;
+C 184 ; WX 333 ; N quotesinglbase ; B 79 -141 218 102 ;
+C 185 ; WX 444 ; N quotedblbase ; B 45 -141 416 102 ;
+C 186 ; WX 444 ; N quotedblright ; B 30 433 401 676 ;
+C 187 ; WX 500 ; N guillemotright ; B 44 33 458 416 ;
+C 188 ; WX 1000 ; N ellipsis ; B 111 -11 888 100 ;
+C 189 ; WX 1000 ; N perthousand ; B 7 -19 994 706 ;
+C 191 ; WX 444 ; N questiondown ; B 30 -218 376 466 ;
+C 193 ; WX 333 ; N grave ; B 19 507 242 678 ;
+C 194 ; WX 333 ; N acute ; B 93 507 317 678 ;
+C 195 ; WX 333 ; N circumflex ; B 11 507 322 674 ;
+C 196 ; WX 333 ; N tilde ; B 1 532 331 638 ;
+C 197 ; WX 333 ; N macron ; B 11 547 322 601 ;
+C 198 ; WX 333 ; N breve ; B 26 507 307 664 ;
+C 199 ; WX 333 ; N dotaccent ; B 118 523 216 623 ;
+C 200 ; WX 333 ; N dieresis ; B 18 523 315 623 ;
+C 202 ; WX 333 ; N ring ; B 67 512 266 711 ;
+C 203 ; WX 333 ; N cedilla ; B 52 -215 261 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B -3 507 377 678 ;
+C 206 ; WX 333 ; N ogonek ; B 64 -165 249 0 ;
+C 207 ; WX 333 ; N caron ; B 11 507 322 674 ;
+C 208 ; WX 1000 ; N emdash ; B 0 201 1000 250 ;
+C 225 ; WX 889 ; N AE ; B 0 0 863 662 ;
+C 227 ; WX 276 ; N ordfeminine ; B 4 394 270 676 ;
+C 232 ; WX 611 ; N Lslash ; B 12 0 598 662 ;
+C 233 ; WX 722 ; N Oslash ; B 34 -80 688 734 ;
+C 234 ; WX 889 ; N OE ; B 30 -6 885 668 ;
+C 235 ; WX 310 ; N ordmasculine ; B 6 394 304 676 ;
+C 241 ; WX 667 ; N ae ; B 38 -10 632 460 ;
+C 245 ; WX 278 ; N dotlessi ; B 16 0 253 460 ;
+C 248 ; WX 278 ; N lslash ; B 19 0 259 683 ;
+C 249 ; WX 500 ; N oslash ; B 29 -112 470 551 ;
+C 250 ; WX 722 ; N oe ; B 30 -10 690 460 ;
+C 251 ; WX 500 ; N germandbls ; B 12 -9 468 683 ;
+C -1 ; WX 611 ; N Zcaron ; B 9 0 597 886 ;
+C -1 ; WX 444 ; N ccedilla ; B 25 -215 412 460 ;
+C -1 ; WX 500 ; N ydieresis ; B 14 -218 475 623 ;
+C -1 ; WX 444 ; N atilde ; B 37 -10 442 638 ;
+C -1 ; WX 278 ; N icircumflex ; B -16 0 295 674 ;
+C -1 ; WX 300 ; N threesuperior ; B 15 262 291 676 ;
+C -1 ; WX 444 ; N ecircumflex ; B 25 -10 424 674 ;
+C -1 ; WX 500 ; N thorn ; B 5 -217 470 683 ;
+C -1 ; WX 444 ; N egrave ; B 25 -10 424 678 ;
+C -1 ; WX 300 ; N twosuperior ; B 1 270 296 676 ;
+C -1 ; WX 444 ; N eacute ; B 25 -10 424 678 ;
+C -1 ; WX 500 ; N otilde ; B 29 -10 470 638 ;
+C -1 ; WX 722 ; N Aacute ; B 15 0 706 890 ;
+C -1 ; WX 500 ; N ocircumflex ; B 29 -10 470 674 ;
+C -1 ; WX 500 ; N yacute ; B 14 -218 475 678 ;
+C -1 ; WX 500 ; N udieresis ; B 9 -10 479 623 ;
+C -1 ; WX 750 ; N threequarters ; B 15 -14 718 676 ;
+C -1 ; WX 444 ; N acircumflex ; B 37 -10 442 674 ;
+C -1 ; WX 722 ; N Eth ; B 16 0 685 662 ;
+C -1 ; WX 444 ; N edieresis ; B 25 -10 424 623 ;
+C -1 ; WX 500 ; N ugrave ; B 9 -10 479 678 ;
+C -1 ; WX 980 ; N trademark ; B 30 256 957 662 ;
+C -1 ; WX 500 ; N ograve ; B 29 -10 470 678 ;
+C -1 ; WX 389 ; N scaron ; B 39 -10 350 674 ;
+C -1 ; WX 333 ; N Idieresis ; B 18 0 315 835 ;
+C -1 ; WX 500 ; N uacute ; B 9 -10 479 678 ;
+C -1 ; WX 444 ; N agrave ; B 37 -10 442 678 ;
+C -1 ; WX 500 ; N ntilde ; B 16 0 485 638 ;
+C -1 ; WX 444 ; N aring ; B 37 -10 442 711 ;
+C -1 ; WX 444 ; N zcaron ; B 27 0 418 674 ;
+C -1 ; WX 333 ; N Icircumflex ; B 11 0 322 886 ;
+C -1 ; WX 722 ; N Ntilde ; B 12 -11 707 850 ;
+C -1 ; WX 500 ; N ucircumflex ; B 9 -10 479 674 ;
+C -1 ; WX 611 ; N Ecircumflex ; B 12 0 597 886 ;
+C -1 ; WX 333 ; N Iacute ; B 18 0 317 890 ;
+C -1 ; WX 667 ; N Ccedilla ; B 28 -215 633 676 ;
+C -1 ; WX 722 ; N Odieresis ; B 34 -14 688 835 ;
+C -1 ; WX 556 ; N Scaron ; B 42 -14 491 886 ;
+C -1 ; WX 611 ; N Edieresis ; B 12 0 597 835 ;
+C -1 ; WX 333 ; N Igrave ; B 18 0 315 890 ;
+C -1 ; WX 444 ; N adieresis ; B 37 -10 442 623 ;
+C -1 ; WX 722 ; N Ograve ; B 34 -14 688 890 ;
+C -1 ; WX 611 ; N Egrave ; B 12 0 597 890 ;
+C -1 ; WX 722 ; N Ydieresis ; B 22 0 703 835 ;
+C -1 ; WX 760 ; N registered ; B 38 -14 722 676 ;
+C -1 ; WX 722 ; N Otilde ; B 34 -14 688 850 ;
+C -1 ; WX 750 ; N onequarter ; B 37 -14 718 676 ;
+C -1 ; WX 722 ; N Ugrave ; B 14 -14 705 890 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 14 -14 705 886 ;
+C -1 ; WX 556 ; N Thorn ; B 16 0 542 662 ;
+C -1 ; WX 564 ; N divide ; B 30 -10 534 516 ;
+C -1 ; WX 722 ; N Atilde ; B 15 0 706 850 ;
+C -1 ; WX 722 ; N Uacute ; B 14 -14 705 890 ;
+C -1 ; WX 722 ; N Ocircumflex ; B 34 -14 688 886 ;
+C -1 ; WX 564 ; N logicalnot ; B 30 108 534 386 ;
+C -1 ; WX 722 ; N Aring ; B 15 0 706 898 ;
+C -1 ; WX 278 ; N idieresis ; B -9 0 288 623 ;
+C -1 ; WX 278 ; N iacute ; B 16 0 290 678 ;
+C -1 ; WX 444 ; N aacute ; B 37 -10 442 678 ;
+C -1 ; WX 564 ; N plusminus ; B 30 0 534 506 ;
+C -1 ; WX 564 ; N multiply ; B 38 8 527 497 ;
+C -1 ; WX 722 ; N Udieresis ; B 14 -14 705 835 ;
+C -1 ; WX 564 ; N minus ; B 30 220 534 286 ;
+C -1 ; WX 300 ; N onesuperior ; B 57 270 248 676 ;
+C -1 ; WX 611 ; N Eacute ; B 12 0 597 890 ;
+C -1 ; WX 722 ; N Acircumflex ; B 15 0 706 886 ;
+C -1 ; WX 760 ; N copyright ; B 38 -14 722 676 ;
+C -1 ; WX 722 ; N Agrave ; B 15 0 706 890 ;
+C -1 ; WX 500 ; N odieresis ; B 29 -10 470 623 ;
+C -1 ; WX 500 ; N oacute ; B 29 -10 470 678 ;
+C -1 ; WX 400 ; N degree ; B 57 390 343 676 ;
+C -1 ; WX 278 ; N igrave ; B -8 0 253 678 ;
+C -1 ; WX 500 ; N mu ; B 36 -218 512 450 ;
+C -1 ; WX 722 ; N Oacute ; B 34 -14 688 890 ;
+C -1 ; WX 500 ; N eth ; B 29 -10 471 686 ;
+C -1 ; WX 722 ; N Adieresis ; B 15 0 706 835 ;
+C -1 ; WX 722 ; N Yacute ; B 22 0 703 890 ;
+C -1 ; WX 200 ; N brokenbar ; B 67 -14 133 676 ;
+C -1 ; WX 750 ; N onehalf ; B 31 -14 746 676 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 283
+
+KPX A y -92
+KPX A w -92
+KPX A v -74
+KPX A u 0
+KPX A quoteright -111
+KPX A quotedblright 0
+KPX A p 0
+KPX A Y -105
+KPX A W -90
+KPX A V -135
+KPX A U -55
+KPX A T -111
+KPX A Q -55
+KPX A O -55
+KPX A G -40
+KPX A C -40
+
+KPX B period 0
+KPX B comma 0
+KPX B U -10
+KPX B A -35
+
+KPX D period 0
+KPX D comma 0
+KPX D Y -55
+KPX D W -30
+KPX D V -40
+KPX D A -40
+
+KPX F r 0
+KPX F period -80
+KPX F o -15
+KPX F i 0
+KPX F e 0
+KPX F comma -80
+KPX F a -15
+KPX F A -74
+
+KPX G period 0
+KPX G comma 0
+
+KPX J u 0
+KPX J period 0
+KPX J o 0
+KPX J e 0
+KPX J comma 0
+KPX J a 0
+KPX J A -60
+
+KPX K y -25
+KPX K u -15
+KPX K o -35
+KPX K e -25
+KPX K O -30
+
+KPX L y -55
+KPX L quoteright -92
+KPX L quotedblright 0
+KPX L Y -100
+KPX L W -74
+KPX L V -100
+KPX L T -92
+
+KPX N period 0
+KPX N comma 0
+KPX N A -35
+
+KPX O period 0
+KPX O comma 0
+KPX O Y -50
+KPX O X -40
+KPX O W -35
+KPX O V -50
+KPX O T -40
+KPX O A -35
+
+KPX P period -111
+KPX P o 0
+KPX P e 0
+KPX P comma -111
+KPX P a -15
+KPX P A -92
+
+KPX Q period 0
+KPX Q comma 0
+KPX Q U -10
+
+KPX R Y -65
+KPX R W -55
+KPX R V -80
+KPX R U -40
+KPX R T -60
+KPX R O -40
+
+KPX S period 0
+KPX S comma 0
+
+KPX T y -80
+KPX T w -80
+KPX T u -45
+KPX T semicolon -55
+KPX T r -35
+KPX T period -74
+KPX T o -80
+KPX T i -35
+KPX T hyphen -92
+KPX T h 0
+KPX T e -70
+KPX T comma -74
+KPX T colon -50
+KPX T a -80
+KPX T O -18
+KPX T A -93
+
+KPX U period 0
+KPX U comma 0
+KPX U A -40
+
+KPX V u -75
+KPX V semicolon -74
+KPX V period -129
+KPX V o -129
+KPX V i -60
+KPX V hyphen -100
+KPX V e -111
+KPX V comma -129
+KPX V colon -74
+KPX V a -111
+KPX V O -40
+KPX V G -15
+KPX V A -135
+
+KPX W y -73
+KPX W u -50
+KPX W semicolon -37
+KPX W period -92
+KPX W o -80
+KPX W i -40
+KPX W hyphen -65
+KPX W h 0
+KPX W e -80
+KPX W comma -92
+KPX W colon -37
+KPX W a -80
+KPX W O -10
+KPX W A -120
+
+KPX Y u -111
+KPX Y semicolon -92
+KPX Y period -129
+KPX Y o -110
+KPX Y i -55
+KPX Y hyphen -111
+KPX Y e -100
+KPX Y comma -129
+KPX Y colon -92
+KPX Y a -100
+KPX Y O -30
+KPX Y A -120
+
+KPX a y 0
+KPX a w -15
+KPX a v -20
+KPX a t 0
+KPX a p 0
+KPX a g 0
+KPX a b 0
+
+KPX b y 0
+KPX b v -15
+KPX b u -20
+KPX b period -40
+KPX b l 0
+KPX b comma 0
+KPX b b 0
+
+KPX c y -15
+KPX c period 0
+KPX c l 0
+KPX c k 0
+KPX c h 0
+KPX c comma 0
+
+KPX colon space 0
+
+KPX comma space 0
+KPX comma quoteright -70
+KPX comma quotedblright -70
+
+KPX d y 0
+KPX d w 0
+KPX d v 0
+KPX d period 0
+KPX d d 0
+KPX d comma 0
+
+KPX e y -15
+KPX e x -15
+KPX e w -25
+KPX e v -25
+KPX e period 0
+KPX e p 0
+KPX e g -15
+KPX e comma 0
+KPX e b 0
+
+KPX f quoteright 55
+KPX f quotedblright 0
+KPX f period 0
+KPX f o 0
+KPX f l 0
+KPX f i -20
+KPX f f -25
+KPX f e 0
+KPX f dotlessi -50
+KPX f comma 0
+KPX f a -10
+
+KPX g y 0
+KPX g r 0
+KPX g period 0
+KPX g o 0
+KPX g i 0
+KPX g g 0
+KPX g e 0
+KPX g comma 0
+KPX g a -5
+
+KPX h y -5
+
+KPX i v -25
+
+KPX k y -15
+KPX k o -10
+KPX k e -10
+
+KPX l y 0
+KPX l w -10
+
+KPX m y 0
+KPX m u 0
+
+KPX n y -15
+KPX n v -40
+KPX n u 0
+
+KPX o y -10
+KPX o x 0
+KPX o w -25
+KPX o v -15
+KPX o g 0
+
+KPX p y -10
+
+KPX period quoteright -70
+KPX period quotedblright -70
+
+KPX quotedblleft quoteleft 0
+KPX quotedblleft A -80
+
+KPX quotedblright space 0
+
+KPX quoteleft quoteleft -74
+KPX quoteleft A -80
+
+KPX quoteright v -50
+KPX quoteright t -18
+KPX quoteright space -74
+KPX quoteright s -55
+KPX quoteright r -50
+KPX quoteright quoteright -74
+KPX quoteright quotedblright 0
+KPX quoteright l -10
+KPX quoteright d -50
+
+KPX r y 0
+KPX r v 0
+KPX r u 0
+KPX r t 0
+KPX r s 0
+KPX r r 0
+KPX r q 0
+KPX r period -55
+KPX r p 0
+KPX r o 0
+KPX r n 0
+KPX r m 0
+KPX r l 0
+KPX r k 0
+KPX r i 0
+KPX r hyphen -20
+KPX r g -18
+KPX r e 0
+KPX r d 0
+KPX r comma -40
+KPX r c 0
+KPX r a 0
+
+KPX s w 0
+
+KPX space quoteleft 0
+KPX space quotedblleft 0
+KPX space Y -90
+KPX space W -30
+KPX space V -50
+KPX space T -18
+KPX space A -55
+
+KPX v period -65
+KPX v o -20
+KPX v e -15
+KPX v comma -65
+KPX v a -25
+
+KPX w period -65
+KPX w o -10
+KPX w h 0
+KPX w e 0
+KPX w comma -65
+KPX w a -10
+
+KPX x e -15
+
+KPX y period -65
+KPX y o 0
+KPX y e 0
+KPX y comma -65
+KPX y a 0
+
+KPX z o 0
+KPX z e 0
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 195 212 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 195 212 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 195 212 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 195 212 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 185 187 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 195 212 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 167 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 139 212 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 139 212 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 139 212 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 139 212 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 0 212 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 0 212 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 0 212 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 0 212 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 195 212 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 195 212 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 195 212 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 195 212 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 195 212 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 195 212 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 112 212 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 195 212 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 195 212 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 195 212 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 195 212 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 195 212 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 195 212 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 139 212 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 56 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 56 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 56 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 56 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 56 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 56 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 56 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 56 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 56 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 56 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 56 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -27 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -27 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -27 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -27 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 84 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 84 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 84 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 84 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 84 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 84 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 28 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 84 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 84 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 84 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 84 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 84 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 84 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 56 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmri8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmri8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..6d7a003ba83426f6d006a893b5236c088aa7eab5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/ptmri8a.afm
@@ -0,0 +1,648 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Tue Mar 20 13:14:56 1990
+Comment UniqueID 28427
+Comment VMusage 32912 39804
+FontName Times-Italic
+FullName Times Italic
+FamilyName Times
+Weight Medium
+ItalicAngle -15.5
+IsFixedPitch false
+FontBBox -169 -217 1010 883
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 653
+XHeight 441
+Ascender 683
+Descender -205
+StartCharMetrics 228
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 39 -11 302 667 ;
+C 34 ; WX 420 ; N quotedbl ; B 144 421 432 666 ;
+C 35 ; WX 500 ; N numbersign ; B 2 0 540 676 ;
+C 36 ; WX 500 ; N dollar ; B 31 -89 497 731 ;
+C 37 ; WX 833 ; N percent ; B 79 -13 790 676 ;
+C 38 ; WX 778 ; N ampersand ; B 76 -18 723 666 ;
+C 39 ; WX 333 ; N quoteright ; B 151 436 290 666 ;
+C 40 ; WX 333 ; N parenleft ; B 42 -181 315 669 ;
+C 41 ; WX 333 ; N parenright ; B 16 -180 289 669 ;
+C 42 ; WX 500 ; N asterisk ; B 128 255 492 666 ;
+C 43 ; WX 675 ; N plus ; B 86 0 590 506 ;
+C 44 ; WX 250 ; N comma ; B -4 -129 135 101 ;
+C 45 ; WX 333 ; N hyphen ; B 49 192 282 255 ;
+C 46 ; WX 250 ; N period ; B 27 -11 138 100 ;
+C 47 ; WX 278 ; N slash ; B -65 -18 386 666 ;
+C 48 ; WX 500 ; N zero ; B 32 -7 497 676 ;
+C 49 ; WX 500 ; N one ; B 49 0 409 676 ;
+C 50 ; WX 500 ; N two ; B 12 0 452 676 ;
+C 51 ; WX 500 ; N three ; B 15 -7 465 676 ;
+C 52 ; WX 500 ; N four ; B 1 0 479 676 ;
+C 53 ; WX 500 ; N five ; B 15 -7 491 666 ;
+C 54 ; WX 500 ; N six ; B 30 -7 521 686 ;
+C 55 ; WX 500 ; N seven ; B 75 -8 537 666 ;
+C 56 ; WX 500 ; N eight ; B 30 -7 493 676 ;
+C 57 ; WX 500 ; N nine ; B 23 -17 492 676 ;
+C 58 ; WX 333 ; N colon ; B 50 -11 261 441 ;
+C 59 ; WX 333 ; N semicolon ; B 27 -129 261 441 ;
+C 60 ; WX 675 ; N less ; B 84 -8 592 514 ;
+C 61 ; WX 675 ; N equal ; B 86 120 590 386 ;
+C 62 ; WX 675 ; N greater ; B 84 -8 592 514 ;
+C 63 ; WX 500 ; N question ; B 132 -12 472 664 ;
+C 64 ; WX 920 ; N at ; B 118 -18 806 666 ;
+C 65 ; WX 611 ; N A ; B -51 0 564 668 ;
+C 66 ; WX 611 ; N B ; B -8 0 588 653 ;
+C 67 ; WX 667 ; N C ; B 66 -18 689 666 ;
+C 68 ; WX 722 ; N D ; B -8 0 700 653 ;
+C 69 ; WX 611 ; N E ; B -1 0 634 653 ;
+C 70 ; WX 611 ; N F ; B 8 0 645 653 ;
+C 71 ; WX 722 ; N G ; B 52 -18 722 666 ;
+C 72 ; WX 722 ; N H ; B -8 0 767 653 ;
+C 73 ; WX 333 ; N I ; B -8 0 384 653 ;
+C 74 ; WX 444 ; N J ; B -6 -18 491 653 ;
+C 75 ; WX 667 ; N K ; B 7 0 722 653 ;
+C 76 ; WX 556 ; N L ; B -8 0 559 653 ;
+C 77 ; WX 833 ; N M ; B -18 0 873 653 ;
+C 78 ; WX 667 ; N N ; B -20 -15 727 653 ;
+C 79 ; WX 722 ; N O ; B 60 -18 699 666 ;
+C 80 ; WX 611 ; N P ; B 0 0 605 653 ;
+C 81 ; WX 722 ; N Q ; B 59 -182 699 666 ;
+C 82 ; WX 611 ; N R ; B -13 0 588 653 ;
+C 83 ; WX 500 ; N S ; B 17 -18 508 667 ;
+C 84 ; WX 556 ; N T ; B 59 0 633 653 ;
+C 85 ; WX 722 ; N U ; B 102 -18 765 653 ;
+C 86 ; WX 611 ; N V ; B 76 -18 688 653 ;
+C 87 ; WX 833 ; N W ; B 71 -18 906 653 ;
+C 88 ; WX 611 ; N X ; B -29 0 655 653 ;
+C 89 ; WX 556 ; N Y ; B 78 0 633 653 ;
+C 90 ; WX 556 ; N Z ; B -6 0 606 653 ;
+C 91 ; WX 389 ; N bracketleft ; B 21 -153 391 663 ;
+C 92 ; WX 278 ; N backslash ; B -41 -18 319 666 ;
+C 93 ; WX 389 ; N bracketright ; B 12 -153 382 663 ;
+C 94 ; WX 422 ; N asciicircum ; B 0 301 422 666 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 333 ; N quoteleft ; B 171 436 310 666 ;
+C 97 ; WX 500 ; N a ; B 17 -11 476 441 ;
+C 98 ; WX 500 ; N b ; B 23 -11 473 683 ;
+C 99 ; WX 444 ; N c ; B 30 -11 425 441 ;
+C 100 ; WX 500 ; N d ; B 15 -13 527 683 ;
+C 101 ; WX 444 ; N e ; B 31 -11 412 441 ;
+C 102 ; WX 278 ; N f ; B -147 -207 424 678 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B 8 -206 472 441 ;
+C 104 ; WX 500 ; N h ; B 19 -9 478 683 ;
+C 105 ; WX 278 ; N i ; B 49 -11 264 654 ;
+C 106 ; WX 278 ; N j ; B -124 -207 276 654 ;
+C 107 ; WX 444 ; N k ; B 14 -11 461 683 ;
+C 108 ; WX 278 ; N l ; B 41 -11 279 683 ;
+C 109 ; WX 722 ; N m ; B 12 -9 704 441 ;
+C 110 ; WX 500 ; N n ; B 14 -9 474 441 ;
+C 111 ; WX 500 ; N o ; B 27 -11 468 441 ;
+C 112 ; WX 500 ; N p ; B -75 -205 469 441 ;
+C 113 ; WX 500 ; N q ; B 25 -209 483 441 ;
+C 114 ; WX 389 ; N r ; B 45 0 412 441 ;
+C 115 ; WX 389 ; N s ; B 16 -13 366 442 ;
+C 116 ; WX 278 ; N t ; B 37 -11 296 546 ;
+C 117 ; WX 500 ; N u ; B 42 -11 475 441 ;
+C 118 ; WX 444 ; N v ; B 21 -18 426 441 ;
+C 119 ; WX 667 ; N w ; B 16 -18 648 441 ;
+C 120 ; WX 444 ; N x ; B -27 -11 447 441 ;
+C 121 ; WX 444 ; N y ; B -24 -206 426 441 ;
+C 122 ; WX 389 ; N z ; B -2 -81 380 428 ;
+C 123 ; WX 400 ; N braceleft ; B 51 -177 407 687 ;
+C 124 ; WX 275 ; N bar ; B 105 -18 171 666 ;
+C 125 ; WX 400 ; N braceright ; B -7 -177 349 687 ;
+C 126 ; WX 541 ; N asciitilde ; B 40 183 502 323 ;
+C 161 ; WX 389 ; N exclamdown ; B 59 -205 322 473 ;
+C 162 ; WX 500 ; N cent ; B 77 -143 472 560 ;
+C 163 ; WX 500 ; N sterling ; B 10 -6 517 670 ;
+C 164 ; WX 167 ; N fraction ; B -169 -10 337 676 ;
+C 165 ; WX 500 ; N yen ; B 27 0 603 653 ;
+C 166 ; WX 500 ; N florin ; B 25 -182 507 682 ;
+C 167 ; WX 500 ; N section ; B 53 -162 461 666 ;
+C 168 ; WX 500 ; N currency ; B -22 53 522 597 ;
+C 169 ; WX 214 ; N quotesingle ; B 132 421 241 666 ;
+C 170 ; WX 556 ; N quotedblleft ; B 166 436 514 666 ;
+C 171 ; WX 500 ; N guillemotleft ; B 53 37 445 403 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 51 37 281 403 ;
+C 173 ; WX 333 ; N guilsinglright ; B 52 37 282 403 ;
+C 174 ; WX 500 ; N fi ; B -141 -207 481 681 ;
+C 175 ; WX 500 ; N fl ; B -141 -204 518 682 ;
+C 177 ; WX 500 ; N endash ; B -6 197 505 243 ;
+C 178 ; WX 500 ; N dagger ; B 101 -159 488 666 ;
+C 179 ; WX 500 ; N daggerdbl ; B 22 -143 491 666 ;
+C 180 ; WX 250 ; N periodcentered ; B 70 199 181 310 ;
+C 182 ; WX 523 ; N paragraph ; B 55 -123 616 653 ;
+C 183 ; WX 350 ; N bullet ; B 40 191 310 461 ;
+C 184 ; WX 333 ; N quotesinglbase ; B 44 -129 183 101 ;
+C 185 ; WX 556 ; N quotedblbase ; B 57 -129 405 101 ;
+C 186 ; WX 556 ; N quotedblright ; B 151 436 499 666 ;
+C 187 ; WX 500 ; N guillemotright ; B 55 37 447 403 ;
+C 188 ; WX 889 ; N ellipsis ; B 57 -11 762 100 ;
+C 189 ; WX 1000 ; N perthousand ; B 25 -19 1010 706 ;
+C 191 ; WX 500 ; N questiondown ; B 28 -205 368 471 ;
+C 193 ; WX 333 ; N grave ; B 121 492 311 664 ;
+C 194 ; WX 333 ; N acute ; B 180 494 403 664 ;
+C 195 ; WX 333 ; N circumflex ; B 91 492 385 661 ;
+C 196 ; WX 333 ; N tilde ; B 100 517 427 624 ;
+C 197 ; WX 333 ; N macron ; B 99 532 411 583 ;
+C 198 ; WX 333 ; N breve ; B 117 492 418 650 ;
+C 199 ; WX 333 ; N dotaccent ; B 207 508 305 606 ;
+C 200 ; WX 333 ; N dieresis ; B 107 508 405 606 ;
+C 202 ; WX 333 ; N ring ; B 155 492 355 691 ;
+C 203 ; WX 333 ; N cedilla ; B -30 -217 182 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 93 494 486 664 ;
+C 206 ; WX 333 ; N ogonek ; B -20 -169 200 40 ;
+C 207 ; WX 333 ; N caron ; B 121 492 426 661 ;
+C 208 ; WX 889 ; N emdash ; B -6 197 894 243 ;
+C 225 ; WX 889 ; N AE ; B -27 0 911 653 ;
+C 227 ; WX 276 ; N ordfeminine ; B 42 406 352 676 ;
+C 232 ; WX 556 ; N Lslash ; B -8 0 559 653 ;
+C 233 ; WX 722 ; N Oslash ; B 60 -105 699 722 ;
+C 234 ; WX 944 ; N OE ; B 49 -8 964 666 ;
+C 235 ; WX 310 ; N ordmasculine ; B 67 406 362 676 ;
+C 241 ; WX 667 ; N ae ; B 23 -11 640 441 ;
+C 245 ; WX 278 ; N dotlessi ; B 49 -11 235 441 ;
+C 248 ; WX 278 ; N lslash ; B 37 -11 307 683 ;
+C 249 ; WX 500 ; N oslash ; B 28 -135 469 554 ;
+C 250 ; WX 667 ; N oe ; B 20 -12 646 441 ;
+C 251 ; WX 500 ; N germandbls ; B -168 -207 493 679 ;
+C -1 ; WX 556 ; N Zcaron ; B -6 0 606 873 ;
+C -1 ; WX 444 ; N ccedilla ; B 26 -217 425 441 ;
+C -1 ; WX 444 ; N ydieresis ; B -24 -206 441 606 ;
+C -1 ; WX 500 ; N atilde ; B 17 -11 511 624 ;
+C -1 ; WX 278 ; N icircumflex ; B 34 -11 328 661 ;
+C -1 ; WX 300 ; N threesuperior ; B 43 268 339 676 ;
+C -1 ; WX 444 ; N ecircumflex ; B 31 -11 441 661 ;
+C -1 ; WX 500 ; N thorn ; B -75 -205 469 683 ;
+C -1 ; WX 444 ; N egrave ; B 31 -11 412 664 ;
+C -1 ; WX 300 ; N twosuperior ; B 33 271 324 676 ;
+C -1 ; WX 444 ; N eacute ; B 31 -11 459 664 ;
+C -1 ; WX 500 ; N otilde ; B 27 -11 496 624 ;
+C -1 ; WX 611 ; N Aacute ; B -51 0 564 876 ;
+C -1 ; WX 500 ; N ocircumflex ; B 27 -11 468 661 ;
+C -1 ; WX 444 ; N yacute ; B -24 -206 459 664 ;
+C -1 ; WX 500 ; N udieresis ; B 42 -11 479 606 ;
+C -1 ; WX 750 ; N threequarters ; B 23 -10 736 676 ;
+C -1 ; WX 500 ; N acircumflex ; B 17 -11 476 661 ;
+C -1 ; WX 722 ; N Eth ; B -8 0 700 653 ;
+C -1 ; WX 444 ; N edieresis ; B 31 -11 451 606 ;
+C -1 ; WX 500 ; N ugrave ; B 42 -11 475 664 ;
+C -1 ; WX 980 ; N trademark ; B 30 247 957 653 ;
+C -1 ; WX 500 ; N ograve ; B 27 -11 468 664 ;
+C -1 ; WX 389 ; N scaron ; B 16 -13 454 661 ;
+C -1 ; WX 333 ; N Idieresis ; B -8 0 435 818 ;
+C -1 ; WX 500 ; N uacute ; B 42 -11 477 664 ;
+C -1 ; WX 500 ; N agrave ; B 17 -11 476 664 ;
+C -1 ; WX 500 ; N ntilde ; B 14 -9 476 624 ;
+C -1 ; WX 500 ; N aring ; B 17 -11 476 691 ;
+C -1 ; WX 389 ; N zcaron ; B -2 -81 434 661 ;
+C -1 ; WX 333 ; N Icircumflex ; B -8 0 425 873 ;
+C -1 ; WX 667 ; N Ntilde ; B -20 -15 727 836 ;
+C -1 ; WX 500 ; N ucircumflex ; B 42 -11 475 661 ;
+C -1 ; WX 611 ; N Ecircumflex ; B -1 0 634 873 ;
+C -1 ; WX 333 ; N Iacute ; B -8 0 413 876 ;
+C -1 ; WX 667 ; N Ccedilla ; B 66 -217 689 666 ;
+C -1 ; WX 722 ; N Odieresis ; B 60 -18 699 818 ;
+C -1 ; WX 500 ; N Scaron ; B 17 -18 520 873 ;
+C -1 ; WX 611 ; N Edieresis ; B -1 0 634 818 ;
+C -1 ; WX 333 ; N Igrave ; B -8 0 384 876 ;
+C -1 ; WX 500 ; N adieresis ; B 17 -11 489 606 ;
+C -1 ; WX 722 ; N Ograve ; B 60 -18 699 876 ;
+C -1 ; WX 611 ; N Egrave ; B -1 0 634 876 ;
+C -1 ; WX 556 ; N Ydieresis ; B 78 0 633 818 ;
+C -1 ; WX 760 ; N registered ; B 41 -18 719 666 ;
+C -1 ; WX 722 ; N Otilde ; B 60 -18 699 836 ;
+C -1 ; WX 750 ; N onequarter ; B 33 -10 736 676 ;
+C -1 ; WX 722 ; N Ugrave ; B 102 -18 765 876 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 102 -18 765 873 ;
+C -1 ; WX 611 ; N Thorn ; B 0 0 569 653 ;
+C -1 ; WX 675 ; N divide ; B 86 -11 590 517 ;
+C -1 ; WX 611 ; N Atilde ; B -51 0 566 836 ;
+C -1 ; WX 722 ; N Uacute ; B 102 -18 765 876 ;
+C -1 ; WX 722 ; N Ocircumflex ; B 60 -18 699 873 ;
+C -1 ; WX 675 ; N logicalnot ; B 86 108 590 386 ;
+C -1 ; WX 611 ; N Aring ; B -51 0 564 883 ;
+C -1 ; WX 278 ; N idieresis ; B 49 -11 353 606 ;
+C -1 ; WX 278 ; N iacute ; B 49 -11 356 664 ;
+C -1 ; WX 500 ; N aacute ; B 17 -11 487 664 ;
+C -1 ; WX 675 ; N plusminus ; B 86 0 590 506 ;
+C -1 ; WX 675 ; N multiply ; B 93 8 582 497 ;
+C -1 ; WX 722 ; N Udieresis ; B 102 -18 765 818 ;
+C -1 ; WX 675 ; N minus ; B 86 220 590 286 ;
+C -1 ; WX 300 ; N onesuperior ; B 43 271 284 676 ;
+C -1 ; WX 611 ; N Eacute ; B -1 0 634 876 ;
+C -1 ; WX 611 ; N Acircumflex ; B -51 0 564 873 ;
+C -1 ; WX 760 ; N copyright ; B 41 -18 719 666 ;
+C -1 ; WX 611 ; N Agrave ; B -51 0 564 876 ;
+C -1 ; WX 500 ; N odieresis ; B 27 -11 489 606 ;
+C -1 ; WX 500 ; N oacute ; B 27 -11 487 664 ;
+C -1 ; WX 400 ; N degree ; B 101 390 387 676 ;
+C -1 ; WX 278 ; N igrave ; B 49 -11 284 664 ;
+C -1 ; WX 500 ; N mu ; B -30 -209 497 428 ;
+C -1 ; WX 722 ; N Oacute ; B 60 -18 699 876 ;
+C -1 ; WX 500 ; N eth ; B 27 -11 482 683 ;
+C -1 ; WX 611 ; N Adieresis ; B -51 0 564 818 ;
+C -1 ; WX 556 ; N Yacute ; B 78 0 633 876 ;
+C -1 ; WX 275 ; N brokenbar ; B 105 -18 171 666 ;
+C -1 ; WX 750 ; N onehalf ; B 34 -10 749 676 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 283
+
+KPX A y -55
+KPX A w -55
+KPX A v -55
+KPX A u -20
+KPX A quoteright -37
+KPX A quotedblright 0
+KPX A p 0
+KPX A Y -55
+KPX A W -95
+KPX A V -105
+KPX A U -50
+KPX A T -37
+KPX A Q -40
+KPX A O -40
+KPX A G -35
+KPX A C -30
+
+KPX B period 0
+KPX B comma 0
+KPX B U -10
+KPX B A -25
+
+KPX D period 0
+KPX D comma 0
+KPX D Y -40
+KPX D W -40
+KPX D V -40
+KPX D A -35
+
+KPX F r -55
+KPX F period -135
+KPX F o -105
+KPX F i -45
+KPX F e -75
+KPX F comma -135
+KPX F a -75
+KPX F A -115
+
+KPX G period 0
+KPX G comma 0
+
+KPX J u -35
+KPX J period -25
+KPX J o -25
+KPX J e -25
+KPX J comma -25
+KPX J a -35
+KPX J A -40
+
+KPX K y -40
+KPX K u -40
+KPX K o -40
+KPX K e -35
+KPX K O -50
+
+KPX L y -30
+KPX L quoteright -37
+KPX L quotedblright 0
+KPX L Y -20
+KPX L W -55
+KPX L V -55
+KPX L T -20
+
+KPX N period 0
+KPX N comma 0
+KPX N A -27
+
+KPX O period 0
+KPX O comma 0
+KPX O Y -50
+KPX O X -40
+KPX O W -50
+KPX O V -50
+KPX O T -40
+KPX O A -55
+
+KPX P period -135
+KPX P o -80
+KPX P e -80
+KPX P comma -135
+KPX P a -80
+KPX P A -90
+
+KPX Q period 0
+KPX Q comma 0
+KPX Q U -10
+
+KPX R Y -18
+KPX R W -18
+KPX R V -18
+KPX R U -40
+KPX R T 0
+KPX R O -40
+
+KPX S period 0
+KPX S comma 0
+
+KPX T y -74
+KPX T w -74
+KPX T u -55
+KPX T semicolon -65
+KPX T r -55
+KPX T period -74
+KPX T o -92
+KPX T i -55
+KPX T hyphen -74
+KPX T h 0
+KPX T e -92
+KPX T comma -74
+KPX T colon -55
+KPX T a -92
+KPX T O -18
+KPX T A -50
+
+KPX U period -25
+KPX U comma -25
+KPX U A -40
+
+KPX V u -74
+KPX V semicolon -74
+KPX V period -129
+KPX V o -111
+KPX V i -74
+KPX V hyphen -55
+KPX V e -111
+KPX V comma -129
+KPX V colon -65
+KPX V a -111
+KPX V O -30
+KPX V G 0
+KPX V A -60
+
+KPX W y -70
+KPX W u -55
+KPX W semicolon -65
+KPX W period -92
+KPX W o -92
+KPX W i -55
+KPX W hyphen -37
+KPX W h 0
+KPX W e -92
+KPX W comma -92
+KPX W colon -65
+KPX W a -92
+KPX W O -25
+KPX W A -60
+
+KPX Y u -92
+KPX Y semicolon -65
+KPX Y period -92
+KPX Y o -92
+KPX Y i -74
+KPX Y hyphen -74
+KPX Y e -92
+KPX Y comma -92
+KPX Y colon -65
+KPX Y a -92
+KPX Y O -15
+KPX Y A -50
+
+KPX a y 0
+KPX a w 0
+KPX a v 0
+KPX a t 0
+KPX a p 0
+KPX a g -10
+KPX a b 0
+
+KPX b y 0
+KPX b v 0
+KPX b u -20
+KPX b period -40
+KPX b l 0
+KPX b comma 0
+KPX b b 0
+
+KPX c y 0
+KPX c period 0
+KPX c l 0
+KPX c k -20
+KPX c h -15
+KPX c comma 0
+
+KPX colon space 0
+
+KPX comma space 0
+KPX comma quoteright -140
+KPX comma quotedblright -140
+
+KPX d y 0
+KPX d w 0
+KPX d v 0
+KPX d period 0
+KPX d d 0
+KPX d comma 0
+
+KPX e y -30
+KPX e x -20
+KPX e w -15
+KPX e v -15
+KPX e period -15
+KPX e p 0
+KPX e g -40
+KPX e comma -10
+KPX e b 0
+
+KPX f quoteright 92
+KPX f quotedblright 0
+KPX f period -15
+KPX f o 0
+KPX f l 0
+KPX f i -20
+KPX f f -18
+KPX f e 0
+KPX f dotlessi -60
+KPX f comma -10
+KPX f a 0
+
+KPX g y 0
+KPX g r 0
+KPX g period -15
+KPX g o 0
+KPX g i 0
+KPX g g -10
+KPX g e -10
+KPX g comma -10
+KPX g a 0
+
+KPX h y 0
+
+KPX i v 0
+
+KPX k y -10
+KPX k o -10
+KPX k e -10
+
+KPX l y 0
+KPX l w 0
+
+KPX m y 0
+KPX m u 0
+
+KPX n y 0
+KPX n v -40
+KPX n u 0
+
+KPX o y 0
+KPX o x 0
+KPX o w 0
+KPX o v -10
+KPX o g -10
+
+KPX p y 0
+
+KPX period quoteright -140
+KPX period quotedblright -140
+
+KPX quotedblleft quoteleft 0
+KPX quotedblleft A 0
+
+KPX quotedblright space 0
+
+KPX quoteleft quoteleft -111
+KPX quoteleft A 0
+
+KPX quoteright v -10
+KPX quoteright t -30
+KPX quoteright space -111
+KPX quoteright s -40
+KPX quoteright r -25
+KPX quoteright quoteright -111
+KPX quoteright quotedblright 0
+KPX quoteright l 0
+KPX quoteright d -25
+
+KPX r y 0
+KPX r v 0
+KPX r u 0
+KPX r t 0
+KPX r s -10
+KPX r r 0
+KPX r q -37
+KPX r period -111
+KPX r p 0
+KPX r o -45
+KPX r n 0
+KPX r m 0
+KPX r l 0
+KPX r k 0
+KPX r i 0
+KPX r hyphen -20
+KPX r g -37
+KPX r e -37
+KPX r d -37
+KPX r comma -111
+KPX r c -37
+KPX r a -15
+
+KPX s w 0
+
+KPX space quoteleft 0
+KPX space quotedblleft 0
+KPX space Y -75
+KPX space W -40
+KPX space V -35
+KPX space T -18
+KPX space A -18
+
+KPX v period -74
+KPX v o 0
+KPX v e 0
+KPX v comma -74
+KPX v a 0
+
+KPX w period -74
+KPX w o 0
+KPX w h 0
+KPX w e 0
+KPX w comma -74
+KPX w a 0
+
+KPX x e 0
+
+KPX y period -55
+KPX y o 0
+KPX y e 0
+KPX y comma -55
+KPX y a 0
+
+KPX z o 0
+KPX z e 0
+EndKernPairs
+EndKernData
+StartComposites 58
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 139 212 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 144 212 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 139 212 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 149 212 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 129 192 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 139 212 ;
+CC Ccedilla 2 ; PCC C 0 0 ; PCC cedilla 167 0 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 149 212 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 169 212 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 159 212 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 149 212 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 10 212 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 40 212 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 30 212 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 10 212 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 177 212 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 195 212 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 230 212 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 230 212 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 205 212 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 215 212 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 94 212 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 195 212 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 215 212 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 225 212 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 215 212 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 132 212 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 142 212 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 112 212 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 84 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 84 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 84 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 84 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 84 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 84 0 ;
+CC ccedilla 2 ; PCC c 0 0 ; PCC cedilla 56 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 56 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 56 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 46 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 56 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -47 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -57 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -52 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -27 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 49 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 84 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 74 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 84 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 84 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde 69 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron 28 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 74 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 74 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 74 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 84 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 56 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 36 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 8 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putb8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putb8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..2eaa540d6348c955f4edc7c48e50f3e9b1067c43
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putb8a.afm
@@ -0,0 +1,1005 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Fri Jan 17 15:08:52 1992
+Comment UniqueID 37705
+Comment VMusage 33078 39970
+FontName Utopia-Bold
+FullName Utopia Bold
+FamilyName Utopia
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -155 -250 1249 916
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.002
+Notice Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 490
+Ascender 742
+Descender -230
+StartCharMetrics 228
+C 32 ; WX 210 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 47 -12 231 707 ;
+C 34 ; WX 473 ; N quotedbl ; B 71 407 402 707 ;
+C 35 ; WX 560 ; N numbersign ; B 14 0 547 668 ;
+C 36 ; WX 560 ; N dollar ; B 38 -104 524 748 ;
+C 37 ; WX 887 ; N percent ; B 40 -31 847 701 ;
+C 38 ; WX 748 ; N ampersand ; B 45 -12 734 680 ;
+C 39 ; WX 252 ; N quoteright ; B 40 387 212 707 ;
+C 40 ; WX 365 ; N parenleft ; B 99 -135 344 699 ;
+C 41 ; WX 365 ; N parenright ; B 21 -135 266 699 ;
+C 42 ; WX 442 ; N asterisk ; B 40 315 402 707 ;
+C 43 ; WX 600 ; N plus ; B 58 0 542 490 ;
+C 44 ; WX 280 ; N comma ; B 40 -167 226 180 ;
+C 45 ; WX 392 ; N hyphen ; B 65 203 328 298 ;
+C 46 ; WX 280 ; N period ; B 48 -12 232 174 ;
+C 47 ; WX 378 ; N slash ; B 34 -15 344 707 ;
+C 48 ; WX 560 ; N zero ; B 31 -12 530 680 ;
+C 49 ; WX 560 ; N one ; B 102 0 459 680 ;
+C 50 ; WX 560 ; N two ; B 30 0 539 680 ;
+C 51 ; WX 560 ; N three ; B 27 -12 519 680 ;
+C 52 ; WX 560 ; N four ; B 19 0 533 668 ;
+C 53 ; WX 560 ; N five ; B 43 -12 519 668 ;
+C 54 ; WX 560 ; N six ; B 30 -12 537 680 ;
+C 55 ; WX 560 ; N seven ; B 34 -12 530 668 ;
+C 56 ; WX 560 ; N eight ; B 27 -12 533 680 ;
+C 57 ; WX 560 ; N nine ; B 34 -12 523 680 ;
+C 58 ; WX 280 ; N colon ; B 48 -12 232 490 ;
+C 59 ; WX 280 ; N semicolon ; B 40 -167 232 490 ;
+C 60 ; WX 600 ; N less ; B 61 5 539 493 ;
+C 61 ; WX 600 ; N equal ; B 58 103 542 397 ;
+C 62 ; WX 600 ; N greater ; B 61 5 539 493 ;
+C 63 ; WX 456 ; N question ; B 20 -12 433 707 ;
+C 64 ; WX 833 ; N at ; B 45 -15 797 707 ;
+C 65 ; WX 644 ; N A ; B -28 0 663 692 ;
+C 66 ; WX 683 ; N B ; B 33 0 645 692 ;
+C 67 ; WX 689 ; N C ; B 42 -15 654 707 ;
+C 68 ; WX 777 ; N D ; B 33 0 735 692 ;
+C 69 ; WX 629 ; N E ; B 33 0 604 692 ;
+C 70 ; WX 593 ; N F ; B 37 0 568 692 ;
+C 71 ; WX 726 ; N G ; B 42 -15 709 707 ;
+C 72 ; WX 807 ; N H ; B 33 0 774 692 ;
+C 73 ; WX 384 ; N I ; B 33 0 351 692 ;
+C 74 ; WX 386 ; N J ; B 6 -114 361 692 ;
+C 75 ; WX 707 ; N K ; B 33 -6 719 692 ;
+C 76 ; WX 585 ; N L ; B 33 0 584 692 ;
+C 77 ; WX 918 ; N M ; B 23 0 885 692 ;
+C 78 ; WX 739 ; N N ; B 25 0 719 692 ;
+C 79 ; WX 768 ; N O ; B 42 -15 726 707 ;
+C 80 ; WX 650 ; N P ; B 33 0 623 692 ;
+C 81 ; WX 768 ; N Q ; B 42 -193 726 707 ;
+C 82 ; WX 684 ; N R ; B 33 0 686 692 ;
+C 83 ; WX 561 ; N S ; B 42 -15 533 707 ;
+C 84 ; WX 624 ; N T ; B 15 0 609 692 ;
+C 85 ; WX 786 ; N U ; B 29 -15 757 692 ;
+C 86 ; WX 645 ; N V ; B -16 0 679 692 ;
+C 87 ; WX 933 ; N W ; B -10 0 960 692 ;
+C 88 ; WX 634 ; N X ; B -19 0 671 692 ;
+C 89 ; WX 617 ; N Y ; B -12 0 655 692 ;
+C 90 ; WX 614 ; N Z ; B 0 0 606 692 ;
+C 91 ; WX 335 ; N bracketleft ; B 123 -128 308 692 ;
+C 92 ; WX 379 ; N backslash ; B 34 -15 345 707 ;
+C 93 ; WX 335 ; N bracketright ; B 27 -128 212 692 ;
+C 94 ; WX 600 ; N asciicircum ; B 56 215 544 668 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 252 ; N quoteleft ; B 40 399 212 719 ;
+C 97 ; WX 544 ; N a ; B 41 -12 561 502 ;
+C 98 ; WX 605 ; N b ; B 15 -12 571 742 ;
+C 99 ; WX 494 ; N c ; B 34 -12 484 502 ;
+C 100 ; WX 605 ; N d ; B 34 -12 596 742 ;
+C 101 ; WX 519 ; N e ; B 34 -12 505 502 ;
+C 102 ; WX 342 ; N f ; B 27 0 421 742 ; L i fi ; L l fl ;
+C 103 ; WX 533 ; N g ; B 25 -242 546 512 ;
+C 104 ; WX 631 ; N h ; B 19 0 622 742 ;
+C 105 ; WX 316 ; N i ; B 26 0 307 720 ;
+C 106 ; WX 316 ; N j ; B -12 -232 260 720 ;
+C 107 ; WX 582 ; N k ; B 19 0 595 742 ;
+C 108 ; WX 309 ; N l ; B 19 0 300 742 ;
+C 109 ; WX 948 ; N m ; B 26 0 939 502 ;
+C 110 ; WX 638 ; N n ; B 26 0 629 502 ;
+C 111 ; WX 585 ; N o ; B 34 -12 551 502 ;
+C 112 ; WX 615 ; N p ; B 19 -230 581 502 ;
+C 113 ; WX 597 ; N q ; B 34 -230 596 502 ;
+C 114 ; WX 440 ; N r ; B 26 0 442 502 ;
+C 115 ; WX 446 ; N s ; B 38 -12 425 502 ;
+C 116 ; WX 370 ; N t ; B 32 -12 373 616 ;
+C 117 ; WX 629 ; N u ; B 23 -12 620 502 ;
+C 118 ; WX 520 ; N v ; B -8 0 546 490 ;
+C 119 ; WX 774 ; N w ; B -10 0 802 490 ;
+C 120 ; WX 522 ; N x ; B -15 0 550 490 ;
+C 121 ; WX 524 ; N y ; B -12 -242 557 490 ;
+C 122 ; WX 483 ; N z ; B -1 0 480 490 ;
+C 123 ; WX 365 ; N braceleft ; B 74 -128 325 692 ;
+C 124 ; WX 284 ; N bar ; B 94 -250 190 750 ;
+C 125 ; WX 365 ; N braceright ; B 40 -128 291 692 ;
+C 126 ; WX 600 ; N asciitilde ; B 50 158 551 339 ;
+C 161 ; WX 278 ; N exclamdown ; B 47 -217 231 502 ;
+C 162 ; WX 560 ; N cent ; B 39 -15 546 678 ;
+C 163 ; WX 560 ; N sterling ; B 21 0 555 679 ;
+C 164 ; WX 100 ; N fraction ; B -155 -27 255 695 ;
+C 165 ; WX 560 ; N yen ; B 3 0 562 668 ;
+C 166 ; WX 560 ; N florin ; B -40 -135 562 691 ;
+C 167 ; WX 566 ; N section ; B 35 -115 531 707 ;
+C 168 ; WX 560 ; N currency ; B 21 73 539 596 ;
+C 169 ; WX 252 ; N quotesingle ; B 57 407 196 707 ;
+C 170 ; WX 473 ; N quotedblleft ; B 40 399 433 719 ;
+C 171 ; WX 487 ; N guillemotleft ; B 40 37 452 464 ;
+C 172 ; WX 287 ; N guilsinglleft ; B 40 37 252 464 ;
+C 173 ; WX 287 ; N guilsinglright ; B 35 37 247 464 ;
+C 174 ; WX 639 ; N fi ; B 27 0 630 742 ;
+C 175 ; WX 639 ; N fl ; B 27 0 630 742 ;
+C 177 ; WX 500 ; N endash ; B 0 209 500 292 ;
+C 178 ; WX 510 ; N dagger ; B 35 -125 475 707 ;
+C 179 ; WX 486 ; N daggerdbl ; B 35 -119 451 707 ;
+C 180 ; WX 280 ; N periodcentered ; B 48 156 232 342 ;
+C 182 ; WX 552 ; N paragraph ; B 35 -101 527 692 ;
+C 183 ; WX 455 ; N bullet ; B 50 174 405 529 ;
+C 184 ; WX 252 ; N quotesinglbase ; B 40 -153 212 167 ;
+C 185 ; WX 473 ; N quotedblbase ; B 40 -153 433 167 ;
+C 186 ; WX 473 ; N quotedblright ; B 40 387 433 707 ;
+C 187 ; WX 487 ; N guillemotright ; B 35 37 447 464 ;
+C 188 ; WX 1000 ; N ellipsis ; B 75 -12 925 174 ;
+C 189 ; WX 1289 ; N perthousand ; B 40 -31 1249 701 ;
+C 191 ; WX 456 ; N questiondown ; B 23 -217 436 502 ;
+C 193 ; WX 430 ; N grave ; B 40 511 312 740 ;
+C 194 ; WX 430 ; N acute ; B 119 511 391 740 ;
+C 195 ; WX 430 ; N circumflex ; B 28 520 402 747 ;
+C 196 ; WX 430 ; N tilde ; B 2 553 427 706 ;
+C 197 ; WX 430 ; N macron ; B 60 587 371 674 ;
+C 198 ; WX 430 ; N breve ; B 56 556 375 716 ;
+C 199 ; WX 430 ; N dotaccent ; B 136 561 294 710 ;
+C 200 ; WX 430 ; N dieresis ; B 16 561 414 710 ;
+C 202 ; WX 430 ; N ring ; B 96 540 334 762 ;
+C 203 ; WX 430 ; N cedilla ; B 136 -246 335 0 ;
+C 205 ; WX 430 ; N hungarumlaut ; B 64 521 446 751 ;
+C 206 ; WX 430 ; N ogonek ; B 105 -246 325 0 ;
+C 207 ; WX 430 ; N caron ; B 28 520 402 747 ;
+C 208 ; WX 1000 ; N emdash ; B 0 209 1000 292 ;
+C 225 ; WX 879 ; N AE ; B -77 0 854 692 ;
+C 227 ; WX 405 ; N ordfeminine ; B 28 265 395 590 ;
+C 232 ; WX 591 ; N Lslash ; B 30 0 590 692 ;
+C 233 ; WX 768 ; N Oslash ; B 42 -61 726 747 ;
+C 234 ; WX 1049 ; N OE ; B 42 0 1024 692 ;
+C 235 ; WX 427 ; N ordmasculine ; B 28 265 399 590 ;
+C 241 ; WX 806 ; N ae ; B 41 -12 792 502 ;
+C 245 ; WX 316 ; N dotlessi ; B 26 0 307 502 ;
+C 248 ; WX 321 ; N lslash ; B 16 0 332 742 ;
+C 249 ; WX 585 ; N oslash ; B 34 -51 551 535 ;
+C 250 ; WX 866 ; N oe ; B 34 -12 852 502 ;
+C 251 ; WX 662 ; N germandbls ; B 29 -12 647 742 ;
+C -1 ; WX 402 ; N onesuperior ; B 71 272 324 680 ;
+C -1 ; WX 600 ; N minus ; B 58 210 542 290 ;
+C -1 ; WX 396 ; N degree ; B 35 360 361 680 ;
+C -1 ; WX 585 ; N oacute ; B 34 -12 551 755 ;
+C -1 ; WX 768 ; N Odieresis ; B 42 -15 726 881 ;
+C -1 ; WX 585 ; N odieresis ; B 34 -12 551 710 ;
+C -1 ; WX 629 ; N Eacute ; B 33 0 604 904 ;
+C -1 ; WX 629 ; N ucircumflex ; B 23 -12 620 747 ;
+C -1 ; WX 900 ; N onequarter ; B 73 -27 814 695 ;
+C -1 ; WX 600 ; N logicalnot ; B 58 95 542 397 ;
+C -1 ; WX 629 ; N Ecircumflex ; B 33 0 604 905 ;
+C -1 ; WX 900 ; N onehalf ; B 53 -27 849 695 ;
+C -1 ; WX 768 ; N Otilde ; B 42 -15 726 876 ;
+C -1 ; WX 629 ; N uacute ; B 23 -12 620 740 ;
+C -1 ; WX 519 ; N eacute ; B 34 -12 505 755 ;
+C -1 ; WX 316 ; N iacute ; B 26 0 369 740 ;
+C -1 ; WX 629 ; N Egrave ; B 33 0 604 904 ;
+C -1 ; WX 316 ; N icircumflex ; B -28 0 346 747 ;
+C -1 ; WX 629 ; N mu ; B 23 -242 620 502 ;
+C -1 ; WX 284 ; N brokenbar ; B 94 -175 190 675 ;
+C -1 ; WX 609 ; N thorn ; B 13 -230 575 722 ;
+C -1 ; WX 644 ; N Aring ; B -28 0 663 872 ;
+C -1 ; WX 524 ; N yacute ; B -12 -242 557 740 ;
+C -1 ; WX 617 ; N Ydieresis ; B -12 0 655 881 ;
+C -1 ; WX 1090 ; N trademark ; B 38 277 1028 692 ;
+C -1 ; WX 800 ; N registered ; B 36 -15 764 707 ;
+C -1 ; WX 585 ; N ocircumflex ; B 34 -12 551 747 ;
+C -1 ; WX 644 ; N Agrave ; B -28 0 663 904 ;
+C -1 ; WX 561 ; N Scaron ; B 42 -15 533 916 ;
+C -1 ; WX 786 ; N Ugrave ; B 29 -15 757 904 ;
+C -1 ; WX 629 ; N Edieresis ; B 33 0 604 881 ;
+C -1 ; WX 786 ; N Uacute ; B 29 -15 757 904 ;
+C -1 ; WX 585 ; N otilde ; B 34 -12 551 706 ;
+C -1 ; WX 638 ; N ntilde ; B 26 0 629 706 ;
+C -1 ; WX 524 ; N ydieresis ; B -12 -242 557 710 ;
+C -1 ; WX 644 ; N Aacute ; B -28 0 663 904 ;
+C -1 ; WX 585 ; N eth ; B 34 -12 551 742 ;
+C -1 ; WX 544 ; N acircumflex ; B 41 -12 561 747 ;
+C -1 ; WX 544 ; N aring ; B 41 -12 561 762 ;
+C -1 ; WX 768 ; N Ograve ; B 42 -15 726 904 ;
+C -1 ; WX 494 ; N ccedilla ; B 34 -246 484 502 ;
+C -1 ; WX 600 ; N multiply ; B 75 20 525 476 ;
+C -1 ; WX 600 ; N divide ; B 58 6 542 494 ;
+C -1 ; WX 402 ; N twosuperior ; B 29 272 382 680 ;
+C -1 ; WX 739 ; N Ntilde ; B 25 0 719 876 ;
+C -1 ; WX 629 ; N ugrave ; B 23 -12 620 740 ;
+C -1 ; WX 786 ; N Ucircumflex ; B 29 -15 757 905 ;
+C -1 ; WX 644 ; N Atilde ; B -28 0 663 876 ;
+C -1 ; WX 483 ; N zcaron ; B -1 0 480 747 ;
+C -1 ; WX 316 ; N idieresis ; B -37 0 361 710 ;
+C -1 ; WX 644 ; N Acircumflex ; B -28 0 663 905 ;
+C -1 ; WX 384 ; N Icircumflex ; B 4 0 380 905 ;
+C -1 ; WX 617 ; N Yacute ; B -12 0 655 904 ;
+C -1 ; WX 768 ; N Oacute ; B 42 -15 726 904 ;
+C -1 ; WX 644 ; N Adieresis ; B -28 0 663 881 ;
+C -1 ; WX 614 ; N Zcaron ; B 0 0 606 916 ;
+C -1 ; WX 544 ; N agrave ; B 41 -12 561 755 ;
+C -1 ; WX 402 ; N threesuperior ; B 30 265 368 680 ;
+C -1 ; WX 585 ; N ograve ; B 34 -12 551 755 ;
+C -1 ; WX 900 ; N threequarters ; B 40 -27 842 695 ;
+C -1 ; WX 783 ; N Eth ; B 35 0 741 692 ;
+C -1 ; WX 600 ; N plusminus ; B 58 0 542 549 ;
+C -1 ; WX 629 ; N udieresis ; B 23 -12 620 710 ;
+C -1 ; WX 519 ; N edieresis ; B 34 -12 505 710 ;
+C -1 ; WX 544 ; N aacute ; B 41 -12 561 755 ;
+C -1 ; WX 316 ; N igrave ; B -47 0 307 740 ;
+C -1 ; WX 384 ; N Idieresis ; B -13 0 397 881 ;
+C -1 ; WX 544 ; N adieresis ; B 41 -12 561 710 ;
+C -1 ; WX 384 ; N Iacute ; B 33 0 423 904 ;
+C -1 ; WX 800 ; N copyright ; B 36 -15 764 707 ;
+C -1 ; WX 384 ; N Igrave ; B -31 0 351 904 ;
+C -1 ; WX 689 ; N Ccedilla ; B 42 -246 654 707 ;
+C -1 ; WX 446 ; N scaron ; B 38 -12 425 747 ;
+C -1 ; WX 519 ; N egrave ; B 34 -12 505 755 ;
+C -1 ; WX 768 ; N Ocircumflex ; B 42 -15 726 905 ;
+C -1 ; WX 640 ; N Thorn ; B 33 0 622 692 ;
+C -1 ; WX 544 ; N atilde ; B 41 -12 561 706 ;
+C -1 ; WX 786 ; N Udieresis ; B 29 -15 757 881 ;
+C -1 ; WX 519 ; N ecircumflex ; B 34 -12 505 747 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 685
+
+KPX A z 25
+KPX A y -40
+KPX A w -42
+KPX A v -48
+KPX A u -18
+KPX A t -12
+KPX A s 6
+KPX A quoteright -110
+KPX A quotedblright -80
+KPX A q -6
+KPX A p -18
+KPX A o -12
+KPX A e -6
+KPX A d -12
+KPX A c -12
+KPX A b -12
+KPX A a -6
+KPX A Y -64
+KPX A X -18
+KPX A W -54
+KPX A V -70
+KPX A U -40
+KPX A T -58
+KPX A Q -18
+KPX A O -18
+KPX A G -18
+KPX A C -18
+
+KPX B y -18
+KPX B u -12
+KPX B r -12
+KPX B o -6
+KPX B l -15
+KPX B k -15
+KPX B i -12
+KPX B h -15
+KPX B e -6
+KPX B b -10
+KPX B a -12
+KPX B W -20
+KPX B V -20
+KPX B U -25
+KPX B T -20
+
+KPX C z -5
+KPX C y -24
+KPX C u -18
+KPX C r -6
+KPX C o -12
+KPX C e -12
+KPX C a -16
+KPX C Q -6
+KPX C O -6
+KPX C G -6
+KPX C C -6
+
+KPX D u -12
+KPX D r -12
+KPX D period -40
+KPX D o -5
+KPX D i -12
+KPX D h -18
+KPX D e -5
+KPX D comma -40
+KPX D a -15
+KPX D Y -60
+KPX D W -40
+KPX D V -40
+
+KPX E y -30
+KPX E w -24
+KPX E v -24
+KPX E u -12
+KPX E t -18
+KPX E s -12
+KPX E r -4
+KPX E q -6
+KPX E period 10
+KPX E p -18
+KPX E o -6
+KPX E n -4
+KPX E m -4
+KPX E j -6
+KPX E i -6
+KPX E g -6
+KPX E e -6
+KPX E d -6
+KPX E comma 10
+KPX E c -6
+KPX E b -5
+KPX E a -4
+KPX E Y -6
+KPX E W -6
+KPX E V -6
+
+KPX F y -18
+KPX F u -12
+KPX F r -36
+KPX F quoteright 20
+KPX F quotedblright 20
+KPX F period -150
+KPX F o -36
+KPX F l -12
+KPX F i -22
+KPX F e -36
+KPX F comma -150
+KPX F a -48
+KPX F A -60
+
+KPX G y -12
+KPX G u -12
+KPX G r -18
+KPX G quotedblright -20
+KPX G n -18
+KPX G l -6
+KPX G i -12
+KPX G h -12
+KPX G a -12
+
+KPX H y -24
+KPX H u -26
+KPX H o -30
+KPX H i -18
+KPX H e -30
+KPX H a -25
+
+KPX I z -6
+KPX I y -6
+KPX I x -6
+KPX I w -18
+KPX I v -24
+KPX I u -26
+KPX I t -24
+KPX I s -18
+KPX I r -12
+KPX I p -26
+KPX I o -30
+KPX I n -18
+KPX I m -18
+KPX I l -6
+KPX I k -6
+KPX I h -6
+KPX I g -6
+KPX I f -6
+KPX I e -30
+KPX I d -30
+KPX I c -30
+KPX I b -6
+KPX I a -24
+
+KPX J y -20
+KPX J u -36
+KPX J o -35
+KPX J i -20
+KPX J e -35
+KPX J bracketright 15
+KPX J braceright 15
+KPX J a -36
+
+KPX K y -70
+KPX K w -60
+KPX K v -80
+KPX K u -42
+KPX K o -30
+KPX K l 10
+KPX K i 6
+KPX K h 10
+KPX K e -18
+KPX K a -6
+KPX K Q -36
+KPX K O -36
+KPX K G -36
+KPX K C -36
+KPX K A 20
+
+KPX L y -52
+KPX L w -58
+KPX L u -12
+KPX L quoteright -130
+KPX L quotedblright -130
+KPX L l 6
+KPX L j -6
+KPX L Y -70
+KPX L W -78
+KPX L V -95
+KPX L U -32
+KPX L T -80
+KPX L Q -12
+KPX L O -12
+KPX L G -12
+KPX L C -12
+KPX L A 30
+
+KPX M y -24
+KPX M u -36
+KPX M o -30
+KPX M n -6
+KPX M j -12
+KPX M i -12
+KPX M e -30
+KPX M d -30
+KPX M c -30
+KPX M a -25
+
+KPX N y -24
+KPX N u -30
+KPX N o -30
+KPX N i -24
+KPX N e -30
+KPX N a -30
+
+KPX O z -6
+KPX O u -6
+KPX O t -6
+KPX O s -6
+KPX O r -10
+KPX O q -6
+KPX O period -40
+KPX O p -10
+KPX O o -6
+KPX O n -10
+KPX O m -10
+KPX O l -15
+KPX O k -15
+KPX O i -6
+KPX O h -15
+KPX O g -6
+KPX O e -6
+KPX O d -6
+KPX O comma -40
+KPX O c -6
+KPX O b -15
+KPX O a -12
+KPX O Y -50
+KPX O X -40
+KPX O W -35
+KPX O V -35
+KPX O T -40
+KPX O A -30
+
+KPX P y 10
+KPX P u -18
+KPX P t -6
+KPX P s -30
+KPX P r -12
+KPX P quoteright 20
+KPX P quotedblright 20
+KPX P period -200
+KPX P o -36
+KPX P n -12
+KPX P l -15
+KPX P i -6
+KPX P hyphen -30
+KPX P h -15
+KPX P e -36
+KPX P comma -200
+KPX P a -36
+KPX P I -20
+KPX P H -20
+KPX P E -20
+KPX P A -85
+
+KPX Q u -6
+KPX Q a -18
+KPX Q Y -50
+KPX Q X -40
+KPX Q W -35
+KPX Q V -35
+KPX Q U -25
+KPX Q T -40
+KPX Q A -30
+
+KPX R y -20
+KPX R u -12
+KPX R t -25
+KPX R quoteright -10
+KPX R quotedblright -10
+KPX R o -12
+KPX R e -18
+KPX R a -6
+KPX R Y -32
+KPX R X 20
+KPX R W -18
+KPX R V -26
+KPX R U -30
+KPX R T -20
+KPX R Q -10
+KPX R O -10
+KPX R G -10
+KPX R C -10
+
+KPX S y -35
+KPX S w -30
+KPX S v -40
+KPX S u -24
+KPX S t -24
+KPX S r -10
+KPX S quoteright -15
+KPX S quotedblright -15
+KPX S p -24
+KPX S n -24
+KPX S m -24
+KPX S l -18
+KPX S k -24
+KPX S j -30
+KPX S i -12
+KPX S h -12
+KPX S a -18
+
+KPX T z -64
+KPX T y -74
+KPX T w -72
+KPX T u -74
+KPX T semicolon -50
+KPX T s -82
+KPX T r -74
+KPX T quoteright 24
+KPX T quotedblright 24
+KPX T period -95
+KPX T parenright 40
+KPX T o -90
+KPX T m -72
+KPX T i -28
+KPX T hyphen -110
+KPX T endash -40
+KPX T emdash -60
+KPX T e -80
+KPX T comma -95
+KPX T bracketright 40
+KPX T braceright 30
+KPX T a -90
+KPX T Y 12
+KPX T X 10
+KPX T W 15
+KPX T V 6
+KPX T T 30
+KPX T S -12
+KPX T Q -25
+KPX T O -25
+KPX T G -25
+KPX T C -25
+KPX T A -52
+
+KPX U z -35
+KPX U y -30
+KPX U x -30
+KPX U v -30
+KPX U t -36
+KPX U s -45
+KPX U r -50
+KPX U p -50
+KPX U n -50
+KPX U m -50
+KPX U l -12
+KPX U k -12
+KPX U i -22
+KPX U h -6
+KPX U g -40
+KPX U f -20
+KPX U d -40
+KPX U c -40
+KPX U b -12
+KPX U a -50
+KPX U A -50
+
+KPX V y -36
+KPX V u -50
+KPX V semicolon -45
+KPX V r -75
+KPX V quoteright 50
+KPX V quotedblright 36
+KPX V period -135
+KPX V parenright 80
+KPX V o -70
+KPX V i 20
+KPX V hyphen -90
+KPX V emdash -20
+KPX V e -70
+KPX V comma -135
+KPX V colon -45
+KPX V bracketright 80
+KPX V braceright 80
+KPX V a -70
+KPX V Q -20
+KPX V O -20
+KPX V G -20
+KPX V C -20
+KPX V A -60
+
+KPX W y -50
+KPX W u -46
+KPX W t -30
+KPX W semicolon -40
+KPX W r -50
+KPX W quoteright 40
+KPX W quotedblright 24
+KPX W period -100
+KPX W parenright 80
+KPX W o -60
+KPX W m -50
+KPX W i 5
+KPX W hyphen -70
+KPX W h 20
+KPX W e -60
+KPX W d -60
+KPX W comma -100
+KPX W colon -40
+KPX W bracketright 80
+KPX W braceright 70
+KPX W a -75
+KPX W T 30
+KPX W Q -20
+KPX W O -20
+KPX W G -20
+KPX W C -20
+KPX W A -58
+
+KPX X y -40
+KPX X u -24
+KPX X quoteright 15
+KPX X e -6
+KPX X a -6
+KPX X Q -24
+KPX X O -30
+KPX X G -30
+KPX X C -30
+KPX X A 20
+
+KPX Y v -50
+KPX Y u -65
+KPX Y t -46
+KPX Y semicolon -37
+KPX Y quoteright 50
+KPX Y quotedblright 36
+KPX Y q -100
+KPX Y period -90
+KPX Y parenright 60
+KPX Y o -90
+KPX Y l 25
+KPX Y i 15
+KPX Y hyphen -100
+KPX Y endash -30
+KPX Y emdash -50
+KPX Y e -90
+KPX Y d -90
+KPX Y comma -90
+KPX Y colon -60
+KPX Y bracketright 80
+KPX Y braceright 64
+KPX Y a -80
+KPX Y Y 12
+KPX Y X 12
+KPX Y W 12
+KPX Y V 12
+KPX Y T 30
+KPX Y Q -40
+KPX Y O -40
+KPX Y G -40
+KPX Y C -40
+KPX Y A -55
+
+KPX Z y -36
+KPX Z w -36
+KPX Z u -6
+KPX Z o -12
+KPX Z i -12
+KPX Z e -6
+KPX Z a -6
+KPX Z Q -18
+KPX Z O -18
+KPX Z G -18
+KPX Z C -18
+KPX Z A 25
+
+KPX a quoteright -45
+KPX a quotedblright -40
+
+KPX b y -15
+KPX b w -20
+KPX b v -20
+KPX b quoteright -45
+KPX b quotedblright -40
+KPX b period -10
+KPX b comma -10
+
+KPX braceleft Y 64
+KPX braceleft W 64
+KPX braceleft V 64
+KPX braceleft T 25
+KPX braceleft J 50
+
+KPX bracketleft Y 64
+KPX bracketleft W 64
+KPX bracketleft V 64
+KPX bracketleft T 35
+KPX bracketleft J 60
+
+KPX c quoteright -5
+
+KPX colon space -20
+
+KPX comma space -40
+KPX comma quoteright -100
+KPX comma quotedblright -100
+
+KPX d quoteright -24
+KPX d quotedblright -24
+
+KPX e z -4
+KPX e quoteright -25
+KPX e quotedblright -20
+
+KPX f quotesingle 70
+KPX f quoteright 68
+KPX f quotedblright 68
+KPX f period -10
+KPX f parenright 110
+KPX f comma -20
+KPX f bracketright 100
+KPX f braceright 80
+
+KPX g y 20
+KPX g p 20
+KPX g f 20
+KPX g comma 10
+
+KPX h quoteright -60
+KPX h quotedblright -60
+
+KPX i quoteright -20
+KPX i quotedblright -20
+
+KPX j quoteright -20
+KPX j quotedblright -20
+KPX j period -10
+KPX j comma -10
+
+KPX k quoteright -30
+KPX k quotedblright -30
+
+KPX l quoteright -24
+KPX l quotedblright -24
+
+KPX m quoteright -60
+KPX m quotedblright -60
+
+KPX n quoteright -60
+KPX n quotedblright -60
+
+KPX o z -12
+KPX o y -25
+KPX o x -18
+KPX o w -30
+KPX o v -30
+KPX o quoteright -45
+KPX o quotedblright -40
+KPX o period -10
+KPX o comma -10
+
+KPX p z -10
+KPX p y -15
+KPX p w -15
+KPX p quoteright -45
+KPX p quotedblright -60
+KPX p period -10
+KPX p comma -10
+
+KPX parenleft Y 64
+KPX parenleft W 64
+KPX parenleft V 64
+KPX parenleft T 50
+KPX parenleft J 50
+
+KPX period space -40
+KPX period quoteright -100
+KPX period quotedblright -100
+
+KPX q quoteright -50
+KPX q quotedblright -50
+KPX q period -10
+KPX q comma -10
+
+KPX quotedblleft z -26
+KPX quotedblleft w 10
+KPX quotedblleft u -40
+KPX quotedblleft t -40
+KPX quotedblleft s -32
+KPX quotedblleft r -40
+KPX quotedblleft q -70
+KPX quotedblleft p -40
+KPX quotedblleft o -70
+KPX quotedblleft n -40
+KPX quotedblleft m -40
+KPX quotedblleft g -50
+KPX quotedblleft f -30
+KPX quotedblleft e -70
+KPX quotedblleft d -70
+KPX quotedblleft c -70
+KPX quotedblleft a -60
+KPX quotedblleft Y 30
+KPX quotedblleft X 20
+KPX quotedblleft W 40
+KPX quotedblleft V 40
+KPX quotedblleft T 18
+KPX quotedblleft J -24
+KPX quotedblleft A -122
+
+KPX quotedblright space -40
+KPX quotedblright period -100
+KPX quotedblright comma -100
+
+KPX quoteleft z -26
+KPX quoteleft y -5
+KPX quoteleft x -5
+KPX quoteleft w 5
+KPX quoteleft v -5
+KPX quoteleft u -25
+KPX quoteleft t -25
+KPX quoteleft s -40
+KPX quoteleft r -40
+KPX quoteleft quoteleft -30
+KPX quoteleft q -70
+KPX quoteleft p -40
+KPX quoteleft o -70
+KPX quoteleft n -40
+KPX quoteleft m -40
+KPX quoteleft g -50
+KPX quoteleft f -10
+KPX quoteleft e -70
+KPX quoteleft d -70
+KPX quoteleft c -70
+KPX quoteleft a -60
+KPX quoteleft Y 35
+KPX quoteleft X 30
+KPX quoteleft W 35
+KPX quoteleft V 35
+KPX quoteleft T 35
+KPX quoteleft J -24
+KPX quoteleft A -122
+
+KPX quoteright v -20
+KPX quoteright t -50
+KPX quoteright space -40
+KPX quoteright s -70
+KPX quoteright r -42
+KPX quoteright quoteright -30
+KPX quoteright period -100
+KPX quoteright m -42
+KPX quoteright l -6
+KPX quoteright d -100
+KPX quoteright comma -100
+
+KPX r z 20
+KPX r y 18
+KPX r x 12
+KPX r w 30
+KPX r v 30
+KPX r u 8
+KPX r t 8
+KPX r semicolon 20
+KPX r quoteright -20
+KPX r quotedblright -10
+KPX r q -6
+KPX r period -60
+KPX r o -6
+KPX r n 8
+KPX r m 8
+KPX r l -10
+KPX r k -10
+KPX r i 8
+KPX r hyphen -60
+KPX r h -10
+KPX r g 5
+KPX r f 8
+KPX r emdash -20
+KPX r e -20
+KPX r d -20
+KPX r comma -80
+KPX r colon 20
+KPX r c -20
+
+KPX s quoteright -40
+KPX s quotedblright -40
+
+KPX semicolon space -20
+
+KPX space quotesinglbase -100
+KPX space quoteleft -40
+KPX space quotedblleft -40
+KPX space quotedblbase -100
+KPX space Y -60
+KPX space W -60
+KPX space V -60
+KPX space T -40
+
+KPX t period 15
+KPX t comma 10
+
+KPX u quoteright -60
+KPX u quotedblright -60
+
+KPX v semicolon 20
+KPX v quoteright 5
+KPX v quotedblright 10
+KPX v q -15
+KPX v period -75
+KPX v o -15
+KPX v e -15
+KPX v d -15
+KPX v comma -90
+KPX v colon 20
+KPX v c -15
+KPX v a -15
+
+KPX w semicolon 20
+KPX w quoteright 15
+KPX w quotedblright 20
+KPX w q -10
+KPX w period -60
+KPX w o -10
+KPX w e -10
+KPX w d -10
+KPX w comma -68
+KPX w colon 20
+KPX w c -10
+
+KPX x quoteright -25
+KPX x quotedblright -20
+KPX x q -6
+KPX x o -6
+KPX x e -12
+KPX x d -12
+KPX x c -12
+
+KPX y semicolon 20
+KPX y quoteright 5
+KPX y quotedblright 10
+KPX y period -72
+KPX y hyphen -20
+KPX y comma -72
+KPX y colon 20
+
+KPX z quoteright -20
+KPX z quotedblright -20
+KPX z o -6
+KPX z e -6
+KPX z d -6
+KPX z c -6
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putbi8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putbi8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..5e8384852c5a58dc0afe2c496c37649e3495e085
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putbi8a.afm
@@ -0,0 +1,1017 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Fri Jan 17 15:47:44 1992
+Comment UniqueID 37716
+Comment VMusage 34427 41319
+FontName Utopia-BoldItalic
+FullName Utopia Bold Italic
+FamilyName Utopia
+Weight Bold
+ItalicAngle -13
+IsFixedPitch false
+FontBBox -176 -250 1262 916
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.002
+Notice Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 502
+Ascender 742
+Descender -242
+StartCharMetrics 228
+C 32 ; WX 210 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 285 ; N exclam ; B 35 -12 336 707 ;
+C 34 ; WX 455 ; N quotedbl ; B 142 407 496 707 ;
+C 35 ; WX 560 ; N numbersign ; B 37 0 606 668 ;
+C 36 ; WX 560 ; N dollar ; B 32 -104 588 748 ;
+C 37 ; WX 896 ; N percent ; B 106 -31 861 702 ;
+C 38 ; WX 752 ; N ampersand ; B 62 -12 736 680 ;
+C 39 ; WX 246 ; N quoteright ; B 95 387 294 707 ;
+C 40 ; WX 350 ; N parenleft ; B 87 -135 438 699 ;
+C 41 ; WX 350 ; N parenright ; B -32 -135 319 699 ;
+C 42 ; WX 500 ; N asterisk ; B 121 315 528 707 ;
+C 43 ; WX 600 ; N plus ; B 83 0 567 490 ;
+C 44 ; WX 280 ; N comma ; B -9 -167 207 180 ;
+C 45 ; WX 392 ; N hyphen ; B 71 203 354 298 ;
+C 46 ; WX 280 ; N period ; B 32 -12 212 166 ;
+C 47 ; WX 260 ; N slash ; B -16 -15 370 707 ;
+C 48 ; WX 560 ; N zero ; B 57 -12 583 680 ;
+C 49 ; WX 560 ; N one ; B 72 0 470 680 ;
+C 50 ; WX 560 ; N two ; B 4 0 578 680 ;
+C 51 ; WX 560 ; N three ; B 21 -12 567 680 ;
+C 52 ; WX 560 ; N four ; B 28 0 557 668 ;
+C 53 ; WX 560 ; N five ; B 23 -12 593 668 ;
+C 54 ; WX 560 ; N six ; B 56 -12 586 680 ;
+C 55 ; WX 560 ; N seven ; B 112 -12 632 668 ;
+C 56 ; WX 560 ; N eight ; B 37 -12 584 680 ;
+C 57 ; WX 560 ; N nine ; B 48 -12 570 680 ;
+C 58 ; WX 280 ; N colon ; B 32 -12 280 490 ;
+C 59 ; WX 280 ; N semicolon ; B -9 -167 280 490 ;
+C 60 ; WX 600 ; N less ; B 66 5 544 495 ;
+C 61 ; WX 600 ; N equal ; B 83 103 567 397 ;
+C 62 ; WX 600 ; N greater ; B 86 5 564 495 ;
+C 63 ; WX 454 ; N question ; B 115 -12 515 707 ;
+C 64 ; WX 828 ; N at ; B 90 -15 842 707 ;
+C 65 ; WX 634 ; N A ; B -59 0 639 692 ;
+C 66 ; WX 680 ; N B ; B 5 0 689 692 ;
+C 67 ; WX 672 ; N C ; B 76 -15 742 707 ;
+C 68 ; WX 774 ; N D ; B 5 0 784 692 ;
+C 69 ; WX 622 ; N E ; B 5 0 687 692 ;
+C 70 ; WX 585 ; N F ; B 5 0 683 692 ;
+C 71 ; WX 726 ; N G ; B 76 -15 756 707 ;
+C 72 ; WX 800 ; N H ; B 5 0 880 692 ;
+C 73 ; WX 386 ; N I ; B 5 0 466 692 ;
+C 74 ; WX 388 ; N J ; B -50 -114 477 692 ;
+C 75 ; WX 688 ; N K ; B 5 -6 823 692 ;
+C 76 ; WX 586 ; N L ; B 5 0 591 692 ;
+C 77 ; WX 921 ; N M ; B 0 0 998 692 ;
+C 78 ; WX 741 ; N N ; B -5 0 838 692 ;
+C 79 ; WX 761 ; N O ; B 78 -15 768 707 ;
+C 80 ; WX 660 ; N P ; B 5 0 694 692 ;
+C 81 ; WX 761 ; N Q ; B 78 -193 768 707 ;
+C 82 ; WX 681 ; N R ; B 5 0 696 692 ;
+C 83 ; WX 551 ; N S ; B 31 -15 570 707 ;
+C 84 ; WX 616 ; N T ; B 91 0 722 692 ;
+C 85 ; WX 776 ; N U ; B 115 -15 867 692 ;
+C 86 ; WX 630 ; N V ; B 92 0 783 692 ;
+C 87 ; WX 920 ; N W ; B 80 0 1062 692 ;
+C 88 ; WX 630 ; N X ; B -56 0 744 692 ;
+C 89 ; WX 622 ; N Y ; B 92 0 765 692 ;
+C 90 ; WX 618 ; N Z ; B -30 0 714 692 ;
+C 91 ; WX 350 ; N bracketleft ; B 56 -128 428 692 ;
+C 92 ; WX 460 ; N backslash ; B 114 -15 425 707 ;
+C 93 ; WX 350 ; N bracketright ; B -22 -128 350 692 ;
+C 94 ; WX 600 ; N asciicircum ; B 79 215 567 668 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 246 ; N quoteleft ; B 114 399 313 719 ;
+C 97 ; WX 596 ; N a ; B 26 -12 612 502 ;
+C 98 ; WX 586 ; N b ; B 34 -12 592 742 ;
+C 99 ; WX 456 ; N c ; B 38 -12 498 502 ;
+C 100 ; WX 609 ; N d ; B 29 -12 651 742 ;
+C 101 ; WX 476 ; N e ; B 38 -12 497 502 ;
+C 102 ; WX 348 ; N f ; B -129 -242 553 742 ; L i fi ; L l fl ;
+C 103 ; WX 522 ; N g ; B -14 -242 609 512 ;
+C 104 ; WX 629 ; N h ; B 44 -12 631 742 ;
+C 105 ; WX 339 ; N i ; B 66 -12 357 720 ;
+C 106 ; WX 333 ; N j ; B -120 -242 364 720 ;
+C 107 ; WX 570 ; N k ; B 39 -12 604 742 ;
+C 108 ; WX 327 ; N l ; B 62 -12 360 742 ;
+C 109 ; WX 914 ; N m ; B 46 -12 917 502 ;
+C 110 ; WX 635 ; N n ; B 45 -12 639 502 ;
+C 111 ; WX 562 ; N o ; B 42 -12 556 502 ;
+C 112 ; WX 606 ; N p ; B 0 -242 613 502 ;
+C 113 ; WX 584 ; N q ; B 29 -242 604 513 ;
+C 114 ; WX 440 ; N r ; B 51 -12 497 502 ;
+C 115 ; WX 417 ; N s ; B 10 -12 432 502 ;
+C 116 ; WX 359 ; N t ; B 68 -12 428 641 ;
+C 117 ; WX 634 ; N u ; B 71 -12 643 502 ;
+C 118 ; WX 518 ; N v ; B 68 -12 547 502 ;
+C 119 ; WX 795 ; N w ; B 70 -12 826 502 ;
+C 120 ; WX 516 ; N x ; B -26 -12 546 502 ;
+C 121 ; WX 489 ; N y ; B -49 -242 532 502 ;
+C 122 ; WX 466 ; N z ; B -17 -12 506 490 ;
+C 123 ; WX 340 ; N braceleft ; B 90 -128 439 692 ;
+C 124 ; WX 265 ; N bar ; B 117 -250 221 750 ;
+C 125 ; WX 340 ; N braceright ; B -42 -128 307 692 ;
+C 126 ; WX 600 ; N asciitilde ; B 70 157 571 338 ;
+C 161 ; WX 285 ; N exclamdown ; B -13 -217 288 502 ;
+C 162 ; WX 560 ; N cent ; B 80 -21 611 668 ;
+C 163 ; WX 560 ; N sterling ; B -4 0 583 679 ;
+C 164 ; WX 100 ; N fraction ; B -176 -27 370 695 ;
+C 165 ; WX 560 ; N yen ; B 65 0 676 668 ;
+C 166 ; WX 560 ; N florin ; B -16 -135 635 691 ;
+C 167 ; WX 568 ; N section ; B 64 -115 559 707 ;
+C 168 ; WX 560 ; N currency ; B 60 73 578 596 ;
+C 169 ; WX 246 ; N quotesingle ; B 134 376 285 707 ;
+C 170 ; WX 455 ; N quotedblleft ; B 114 399 522 719 ;
+C 171 ; WX 560 ; N guillemotleft ; B 90 37 533 464 ;
+C 172 ; WX 360 ; N guilsinglleft ; B 90 37 333 464 ;
+C 173 ; WX 360 ; N guilsinglright ; B 58 37 301 464 ;
+C 174 ; WX 651 ; N fi ; B -129 -242 655 742 ;
+C 175 ; WX 652 ; N fl ; B -129 -242 685 742 ;
+C 177 ; WX 500 ; N endash ; B 12 209 531 292 ;
+C 178 ; WX 514 ; N dagger ; B 101 -125 545 707 ;
+C 179 ; WX 490 ; N daggerdbl ; B 32 -119 528 707 ;
+C 180 ; WX 280 ; N periodcentered ; B 67 161 247 339 ;
+C 182 ; WX 580 ; N paragraph ; B 110 -101 653 692 ;
+C 183 ; WX 465 ; N bullet ; B 99 174 454 529 ;
+C 184 ; WX 246 ; N quotesinglbase ; B -17 -153 182 167 ;
+C 185 ; WX 455 ; N quotedblbase ; B -17 -153 391 167 ;
+C 186 ; WX 455 ; N quotedblright ; B 95 387 503 707 ;
+C 187 ; WX 560 ; N guillemotright ; B 58 37 502 464 ;
+C 188 ; WX 1000 ; N ellipsis ; B 85 -12 931 166 ;
+C 189 ; WX 1297 ; N perthousand ; B 106 -31 1262 702 ;
+C 191 ; WX 454 ; N questiondown ; B -10 -217 391 502 ;
+C 193 ; WX 400 ; N grave ; B 109 511 381 740 ;
+C 194 ; WX 400 ; N acute ; B 186 511 458 740 ;
+C 195 ; WX 400 ; N circumflex ; B 93 520 471 747 ;
+C 196 ; WX 400 ; N tilde ; B 94 549 502 697 ;
+C 197 ; WX 400 ; N macron ; B 133 592 459 664 ;
+C 198 ; WX 400 ; N breve ; B 146 556 469 714 ;
+C 199 ; WX 402 ; N dotaccent ; B 220 561 378 710 ;
+C 200 ; WX 400 ; N dieresis ; B 106 561 504 710 ;
+C 202 ; WX 400 ; N ring ; B 166 529 423 762 ;
+C 203 ; WX 400 ; N cedilla ; B 85 -246 292 0 ;
+C 205 ; WX 400 ; N hungarumlaut ; B 158 546 482 750 ;
+C 206 ; WX 350 ; N ogonek ; B 38 -246 253 0 ;
+C 207 ; WX 400 ; N caron ; B 130 520 508 747 ;
+C 208 ; WX 1000 ; N emdash ; B 12 209 1031 292 ;
+C 225 ; WX 890 ; N AE ; B -107 0 958 692 ;
+C 227 ; WX 444 ; N ordfeminine ; B 62 265 482 590 ;
+C 232 ; WX 592 ; N Lslash ; B 11 0 597 692 ;
+C 233 ; WX 761 ; N Oslash ; B 77 -51 769 734 ;
+C 234 ; WX 1016 ; N OE ; B 76 0 1084 692 ;
+C 235 ; WX 412 ; N ordmasculine ; B 86 265 446 590 ;
+C 241 ; WX 789 ; N ae ; B 26 -12 810 509 ;
+C 245 ; WX 339 ; N dotlessi ; B 66 -12 343 502 ;
+C 248 ; WX 339 ; N lslash ; B 18 -12 420 742 ;
+C 249 ; WX 562 ; N oslash ; B 42 -69 556 549 ;
+C 250 ; WX 811 ; N oe ; B 42 -12 832 502 ;
+C 251 ; WX 628 ; N germandbls ; B -129 -242 692 742 ;
+C -1 ; WX 402 ; N onesuperior ; B 84 272 361 680 ;
+C -1 ; WX 600 ; N minus ; B 83 210 567 290 ;
+C -1 ; WX 375 ; N degree ; B 93 360 425 680 ;
+C -1 ; WX 562 ; N oacute ; B 42 -12 572 755 ;
+C -1 ; WX 761 ; N Odieresis ; B 78 -15 768 881 ;
+C -1 ; WX 562 ; N odieresis ; B 42 -12 585 710 ;
+C -1 ; WX 622 ; N Eacute ; B 5 0 687 904 ;
+C -1 ; WX 634 ; N ucircumflex ; B 71 -12 643 747 ;
+C -1 ; WX 940 ; N onequarter ; B 104 -27 849 695 ;
+C -1 ; WX 600 ; N logicalnot ; B 83 95 567 397 ;
+C -1 ; WX 622 ; N Ecircumflex ; B 5 0 687 905 ;
+C -1 ; WX 940 ; N onehalf ; B 90 -27 898 695 ;
+C -1 ; WX 761 ; N Otilde ; B 78 -15 768 876 ;
+C -1 ; WX 634 ; N uacute ; B 71 -12 643 740 ;
+C -1 ; WX 476 ; N eacute ; B 38 -12 545 755 ;
+C -1 ; WX 339 ; N iacute ; B 66 -12 438 740 ;
+C -1 ; WX 622 ; N Egrave ; B 5 0 687 904 ;
+C -1 ; WX 339 ; N icircumflex ; B 38 -12 416 747 ;
+C -1 ; WX 634 ; N mu ; B -3 -230 643 502 ;
+C -1 ; WX 265 ; N brokenbar ; B 117 -175 221 675 ;
+C -1 ; WX 600 ; N thorn ; B -6 -242 607 700 ;
+C -1 ; WX 634 ; N Aring ; B -59 0 639 879 ;
+C -1 ; WX 489 ; N yacute ; B -49 -242 553 740 ;
+C -1 ; WX 622 ; N Ydieresis ; B 92 0 765 881 ;
+C -1 ; WX 1100 ; N trademark ; B 103 277 1093 692 ;
+C -1 ; WX 824 ; N registered ; B 91 -15 819 707 ;
+C -1 ; WX 562 ; N ocircumflex ; B 42 -12 556 747 ;
+C -1 ; WX 634 ; N Agrave ; B -59 0 639 904 ;
+C -1 ; WX 551 ; N Scaron ; B 31 -15 612 916 ;
+C -1 ; WX 776 ; N Ugrave ; B 115 -15 867 904 ;
+C -1 ; WX 622 ; N Edieresis ; B 5 0 687 881 ;
+C -1 ; WX 776 ; N Uacute ; B 115 -15 867 904 ;
+C -1 ; WX 562 ; N otilde ; B 42 -12 583 697 ;
+C -1 ; WX 635 ; N ntilde ; B 45 -12 639 697 ;
+C -1 ; WX 489 ; N ydieresis ; B -49 -242 532 710 ;
+C -1 ; WX 634 ; N Aacute ; B -59 0 678 904 ;
+C -1 ; WX 562 ; N eth ; B 42 -12 558 742 ;
+C -1 ; WX 596 ; N acircumflex ; B 26 -12 612 747 ;
+C -1 ; WX 596 ; N aring ; B 26 -12 612 762 ;
+C -1 ; WX 761 ; N Ograve ; B 78 -15 768 904 ;
+C -1 ; WX 456 ; N ccedilla ; B 38 -246 498 502 ;
+C -1 ; WX 600 ; N multiply ; B 110 22 560 478 ;
+C -1 ; WX 600 ; N divide ; B 63 7 547 493 ;
+C -1 ; WX 402 ; N twosuperior ; B 29 272 423 680 ;
+C -1 ; WX 741 ; N Ntilde ; B -5 0 838 876 ;
+C -1 ; WX 634 ; N ugrave ; B 71 -12 643 740 ;
+C -1 ; WX 776 ; N Ucircumflex ; B 115 -15 867 905 ;
+C -1 ; WX 634 ; N Atilde ; B -59 0 662 876 ;
+C -1 ; WX 466 ; N zcaron ; B -17 -12 526 747 ;
+C -1 ; WX 339 ; N idieresis ; B 46 -12 444 710 ;
+C -1 ; WX 634 ; N Acircumflex ; B -59 0 639 905 ;
+C -1 ; WX 386 ; N Icircumflex ; B 5 0 506 905 ;
+C -1 ; WX 622 ; N Yacute ; B 92 0 765 904 ;
+C -1 ; WX 761 ; N Oacute ; B 78 -15 768 904 ;
+C -1 ; WX 634 ; N Adieresis ; B -59 0 652 881 ;
+C -1 ; WX 618 ; N Zcaron ; B -30 0 714 916 ;
+C -1 ; WX 596 ; N agrave ; B 26 -12 612 755 ;
+C -1 ; WX 402 ; N threesuperior ; B 59 265 421 680 ;
+C -1 ; WX 562 ; N ograve ; B 42 -12 556 755 ;
+C -1 ; WX 940 ; N threequarters ; B 95 -27 876 695 ;
+C -1 ; WX 780 ; N Eth ; B 11 0 790 692 ;
+C -1 ; WX 600 ; N plusminus ; B 83 0 567 549 ;
+C -1 ; WX 634 ; N udieresis ; B 71 -12 643 710 ;
+C -1 ; WX 476 ; N edieresis ; B 38 -12 542 710 ;
+C -1 ; WX 596 ; N aacute ; B 26 -12 621 755 ;
+C -1 ; WX 339 ; N igrave ; B 39 -12 343 740 ;
+C -1 ; WX 386 ; N Idieresis ; B 5 0 533 881 ;
+C -1 ; WX 596 ; N adieresis ; B 26 -12 612 710 ;
+C -1 ; WX 386 ; N Iacute ; B 5 0 549 904 ;
+C -1 ; WX 824 ; N copyright ; B 91 -15 819 707 ;
+C -1 ; WX 386 ; N Igrave ; B 5 0 466 904 ;
+C -1 ; WX 672 ; N Ccedilla ; B 76 -246 742 707 ;
+C -1 ; WX 417 ; N scaron ; B 10 -12 522 747 ;
+C -1 ; WX 476 ; N egrave ; B 38 -12 497 755 ;
+C -1 ; WX 761 ; N Ocircumflex ; B 78 -15 768 905 ;
+C -1 ; WX 629 ; N Thorn ; B 5 0 660 692 ;
+C -1 ; WX 596 ; N atilde ; B 26 -12 612 697 ;
+C -1 ; WX 776 ; N Udieresis ; B 115 -15 867 881 ;
+C -1 ; WX 476 ; N ecircumflex ; B 38 -12 524 747 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 697
+
+KPX A z 18
+KPX A y -40
+KPX A x 16
+KPX A w -30
+KPX A v -30
+KPX A u -18
+KPX A t -6
+KPX A s 6
+KPX A r -6
+KPX A quoteright -92
+KPX A quotedblright -92
+KPX A p -6
+KPX A o -18
+KPX A n -12
+KPX A m -12
+KPX A l -18
+KPX A h -6
+KPX A d 4
+KPX A c -6
+KPX A b -6
+KPX A a 10
+KPX A Y -56
+KPX A X -8
+KPX A W -46
+KPX A V -75
+KPX A U -50
+KPX A T -60
+KPX A Q -30
+KPX A O -30
+KPX A G -30
+KPX A C -30
+
+KPX B y -6
+KPX B u -12
+KPX B r -6
+KPX B quoteright -20
+KPX B quotedblright -32
+KPX B o 6
+KPX B l -20
+KPX B k -10
+KPX B i -12
+KPX B h -15
+KPX B e 4
+KPX B a 10
+KPX B W -30
+KPX B V -45
+KPX B U -30
+KPX B T -20
+
+KPX C z -6
+KPX C y -18
+KPX C u -12
+KPX C r -12
+KPX C quoteright 12
+KPX C quotedblright 20
+KPX C i -6
+KPX C e -6
+KPX C a -6
+KPX C Q -12
+KPX C O -12
+KPX C G -12
+KPX C C -12
+
+KPX D y 18
+KPX D quoteright -20
+KPX D quotedblright -20
+KPX D period -20
+KPX D o 6
+KPX D h -15
+KPX D e 6
+KPX D comma -20
+KPX D a 6
+KPX D Y -80
+KPX D W -40
+KPX D V -65
+
+KPX E z -6
+KPX E y -24
+KPX E x 15
+KPX E w -30
+KPX E v -18
+KPX E u -24
+KPX E t -18
+KPX E s -6
+KPX E r -6
+KPX E quoteright 10
+KPX E q 10
+KPX E period 15
+KPX E p -12
+KPX E n -12
+KPX E m -12
+KPX E l -6
+KPX E j -6
+KPX E i -12
+KPX E g -12
+KPX E d 10
+KPX E comma 15
+KPX E a 10
+
+KPX F y -12
+KPX F u -24
+KPX F r -12
+KPX F quoteright 40
+KPX F quotedblright 35
+KPX F period -120
+KPX F o -24
+KPX F i -6
+KPX F e -24
+KPX F comma -110
+KPX F a -30
+KPX F A -45
+
+KPX G y -25
+KPX G u -22
+KPX G r -22
+KPX G quoteright -30
+KPX G quotedblright -30
+KPX G n -22
+KPX G l -24
+KPX G i -12
+KPX G h -18
+KPX G e 5
+
+KPX H y -18
+KPX H u -30
+KPX H o -25
+KPX H i -25
+KPX H e -25
+KPX H a -25
+
+KPX I z -20
+KPX I y -6
+KPX I x -6
+KPX I w -30
+KPX I v -30
+KPX I u -30
+KPX I t -18
+KPX I s -18
+KPX I r -12
+KPX I p -18
+KPX I o -25
+KPX I n -18
+KPX I m -18
+KPX I l -6
+KPX I k -6
+KPX I j -20
+KPX I i -10
+KPX I g -24
+KPX I f -6
+KPX I e -25
+KPX I d -15
+KPX I c -25
+KPX I b -6
+KPX I a -15
+
+KPX J y -12
+KPX J u -32
+KPX J quoteright 6
+KPX J quotedblright 6
+KPX J o -36
+KPX J i -30
+KPX J e -30
+KPX J braceright 15
+KPX J a -36
+
+KPX K y -70
+KPX K w -36
+KPX K v -30
+KPX K u -30
+KPX K r -24
+KPX K quoteright 36
+KPX K quotedblright 36
+KPX K o -30
+KPX K n -24
+KPX K l 10
+KPX K i -12
+KPX K h 15
+KPX K e -30
+KPX K a -12
+KPX K Q -50
+KPX K O -50
+KPX K G -50
+KPX K C -50
+KPX K A 15
+
+KPX L y -70
+KPX L w -30
+KPX L u -18
+KPX L quoteright -110
+KPX L quotedblright -110
+KPX L l -16
+KPX L j -18
+KPX L i -18
+KPX L Y -80
+KPX L W -78
+KPX L V -110
+KPX L U -42
+KPX L T -100
+KPX L Q -48
+KPX L O -48
+KPX L G -48
+KPX L C -48
+KPX L A 40
+
+KPX M y -18
+KPX M u -24
+KPX M quoteright 6
+KPX M quotedblright 6
+KPX M o -25
+KPX M n -20
+KPX M j -35
+KPX M i -20
+KPX M e -25
+KPX M d -20
+KPX M c -25
+KPX M a -20
+
+KPX N y -18
+KPX N u -24
+KPX N o -18
+KPX N i -12
+KPX N e -16
+KPX N a -22
+
+KPX O z -6
+KPX O y 12
+KPX O u -6
+KPX O t -6
+KPX O s -6
+KPX O r -6
+KPX O quoteright -20
+KPX O quotedblright -20
+KPX O q 6
+KPX O period -10
+KPX O p -6
+KPX O n -6
+KPX O m -6
+KPX O l -15
+KPX O k -10
+KPX O j -6
+KPX O h -10
+KPX O g -6
+KPX O e 6
+KPX O d 6
+KPX O comma -10
+KPX O a 6
+KPX O Y -70
+KPX O X -30
+KPX O W -35
+KPX O V -50
+KPX O T -42
+KPX O A -8
+
+KPX P y 6
+KPX P u -18
+KPX P t -6
+KPX P s -24
+KPX P r -6
+KPX P quoteright -12
+KPX P period -170
+KPX P o -24
+KPX P n -12
+KPX P l -20
+KPX P h -20
+KPX P e -24
+KPX P comma -170
+KPX P a -40
+KPX P I -45
+KPX P H -45
+KPX P E -45
+KPX P A -70
+
+KPX Q u -6
+KPX Q quoteright -20
+KPX Q quotedblright -38
+KPX Q a -6
+KPX Q Y -70
+KPX Q X -12
+KPX Q W -35
+KPX Q V -50
+KPX Q U -30
+KPX Q T -36
+KPX Q A -18
+
+KPX R y -6
+KPX R u -12
+KPX R quoteright -22
+KPX R quotedblright -22
+KPX R o -20
+KPX R e -12
+KPX R Y -45
+KPX R X 15
+KPX R W -25
+KPX R V -35
+KPX R U -40
+KPX R T -18
+KPX R Q -8
+KPX R O -8
+KPX R G -8
+KPX R C -8
+KPX R A 15
+
+KPX S y -30
+KPX S w -30
+KPX S v -20
+KPX S u -18
+KPX S t -18
+KPX S r -20
+KPX S quoteright -38
+KPX S quotedblright -50
+KPX S p -18
+KPX S n -24
+KPX S m -24
+KPX S l -20
+KPX S k -18
+KPX S j -25
+KPX S i -20
+KPX S h -12
+KPX S e -6
+
+KPX T z -48
+KPX T y -52
+KPX T w -54
+KPX T u -54
+KPX T semicolon -6
+KPX T s -60
+KPX T r -54
+KPX T quoteright 36
+KPX T quotedblright 36
+KPX T period -70
+KPX T parenright 25
+KPX T o -78
+KPX T m -54
+KPX T i -22
+KPX T hyphen -100
+KPX T h 6
+KPX T endash -40
+KPX T emdash -40
+KPX T e -78
+KPX T comma -90
+KPX T bracketright 20
+KPX T braceright 30
+KPX T a -78
+KPX T Y 12
+KPX T X 18
+KPX T W 30
+KPX T V 20
+KPX T T 40
+KPX T Q -6
+KPX T O -6
+KPX T G -6
+KPX T C -6
+KPX T A -40
+
+KPX U z -18
+KPX U x -30
+KPX U v -20
+KPX U t -24
+KPX U s -40
+KPX U r -30
+KPX U p -30
+KPX U n -30
+KPX U m -30
+KPX U l -12
+KPX U k -12
+KPX U i -24
+KPX U h -6
+KPX U g -30
+KPX U f -10
+KPX U d -30
+KPX U c -30
+KPX U b -6
+KPX U a -30
+KPX U A -40
+
+KPX V y -34
+KPX V u -42
+KPX V semicolon -45
+KPX V r -55
+KPX V quoteright 46
+KPX V quotedblright 60
+KPX V period -110
+KPX V parenright 64
+KPX V o -55
+KPX V i 15
+KPX V hyphen -60
+KPX V endash -20
+KPX V emdash -20
+KPX V e -55
+KPX V comma -110
+KPX V colon -18
+KPX V bracketright 64
+KPX V braceright 64
+KPX V a -80
+KPX V T 12
+KPX V A -70
+
+KPX W y -36
+KPX W u -30
+KPX W t -10
+KPX W semicolon -12
+KPX W r -30
+KPX W quoteright 42
+KPX W quotedblright 55
+KPX W period -80
+KPX W parenright 55
+KPX W o -55
+KPX W m -30
+KPX W i 5
+KPX W hyphen -40
+KPX W h 16
+KPX W e -55
+KPX W d -60
+KPX W comma -80
+KPX W colon -12
+KPX W bracketright 64
+KPX W braceright 64
+KPX W a -60
+KPX W T 30
+KPX W Q -5
+KPX W O -5
+KPX W G -5
+KPX W C -5
+KPX W A -45
+
+KPX X y -40
+KPX X u -30
+KPX X r -6
+KPX X quoteright 24
+KPX X quotedblright 40
+KPX X i -6
+KPX X e -18
+KPX X a -6
+KPX X Y -6
+KPX X W -6
+KPX X Q -45
+KPX X O -45
+KPX X G -45
+KPX X C -45
+
+KPX Y v -60
+KPX Y u -70
+KPX Y t -32
+KPX Y semicolon -20
+KPX Y quoteright 56
+KPX Y quotedblright 70
+KPX Y q -100
+KPX Y period -80
+KPX Y parenright 5
+KPX Y o -95
+KPX Y l 15
+KPX Y i 15
+KPX Y hyphen -110
+KPX Y endash -40
+KPX Y emdash -40
+KPX Y e -95
+KPX Y d -85
+KPX Y comma -80
+KPX Y colon -20
+KPX Y bracketright 64
+KPX Y braceright 64
+KPX Y a -85
+KPX Y Y 12
+KPX Y X 12
+KPX Y W 12
+KPX Y V 6
+KPX Y T 30
+KPX Y Q -25
+KPX Y O -25
+KPX Y G -25
+KPX Y C -25
+KPX Y A -40
+
+KPX Z y -36
+KPX Z w -36
+KPX Z u -12
+KPX Z quoteright 18
+KPX Z quotedblright 18
+KPX Z o -6
+KPX Z i -12
+KPX Z e -6
+KPX Z a -6
+KPX Z Q -20
+KPX Z O -20
+KPX Z G -20
+KPX Z C -20
+KPX Z A 30
+
+KPX a quoteright -54
+KPX a quotedblright -54
+
+KPX b y -6
+KPX b w -5
+KPX b v -5
+KPX b quoteright -30
+KPX b quotedblright -30
+KPX b period -15
+KPX b comma -15
+
+KPX braceleft Y 64
+KPX braceleft W 64
+KPX braceleft V 64
+KPX braceleft T 40
+KPX braceleft J 60
+
+KPX bracketleft Y 60
+KPX bracketleft W 64
+KPX bracketleft V 64
+KPX bracketleft T 35
+KPX bracketleft J 30
+
+KPX c quoteright 5
+KPX c quotedblright 5
+
+KPX colon space -30
+
+KPX comma space -40
+KPX comma quoteright -100
+KPX comma quotedblright -100
+
+KPX d quoteright -12
+KPX d quotedblright -12
+KPX d period 15
+KPX d comma 15
+
+KPX e y 6
+KPX e x -10
+KPX e w -10
+KPX e v -10
+KPX e quoteright -25
+KPX e quotedblright -25
+
+KPX f quoteright 120
+KPX f quotedblright 120
+KPX f period -30
+KPX f parenright 100
+KPX f comma -30
+KPX f bracketright 110
+KPX f braceright 110
+
+KPX g y 50
+KPX g quotedblright -20
+KPX g p 30
+KPX g f 42
+KPX g comma 20
+
+KPX h quoteright -78
+KPX h quotedblright -78
+
+KPX i quoteright -20
+KPX i quotedblright -20
+
+KPX j quoteright -20
+KPX j quotedblright -20
+KPX j period -20
+KPX j comma -20
+
+KPX k quoteright -38
+KPX k quotedblright -38
+
+KPX l quoteright -12
+KPX l quotedblright -12
+
+KPX m quoteright -78
+KPX m quotedblright -78
+
+KPX n quoteright -88
+KPX n quotedblright -88
+
+KPX o y -12
+KPX o x -20
+KPX o w -25
+KPX o v -25
+KPX o quoteright -50
+KPX o quotedblright -50
+KPX o period -10
+KPX o comma -10
+
+KPX p w -6
+KPX p quoteright -30
+KPX p quotedblright -52
+KPX p period -15
+KPX p comma -15
+
+KPX parenleft Y 64
+KPX parenleft W 64
+KPX parenleft V 64
+KPX parenleft T 30
+KPX parenleft J 50
+
+KPX period space -40
+KPX period quoteright -100
+KPX period quotedblright -100
+
+KPX q quoteright -40
+KPX q quotedblright -40
+KPX q period -10
+KPX q comma -5
+
+KPX quotedblleft z -30
+KPX quotedblleft x -60
+KPX quotedblleft w -12
+KPX quotedblleft v -12
+KPX quotedblleft u -12
+KPX quotedblleft t 5
+KPX quotedblleft s -30
+KPX quotedblleft r -12
+KPX quotedblleft q -50
+KPX quotedblleft p -12
+KPX quotedblleft o -30
+KPX quotedblleft n -12
+KPX quotedblleft m -12
+KPX quotedblleft l 10
+KPX quotedblleft k 10
+KPX quotedblleft h 10
+KPX quotedblleft g -30
+KPX quotedblleft e -30
+KPX quotedblleft d -50
+KPX quotedblleft c -30
+KPX quotedblleft b 24
+KPX quotedblleft a -50
+KPX quotedblleft Y 30
+KPX quotedblleft X 45
+KPX quotedblleft W 55
+KPX quotedblleft V 40
+KPX quotedblleft T 36
+KPX quotedblleft A -100
+
+KPX quotedblright space -50
+KPX quotedblright period -200
+KPX quotedblright comma -200
+
+KPX quoteleft z -30
+KPX quoteleft y 30
+KPX quoteleft x -10
+KPX quoteleft w -12
+KPX quoteleft u -12
+KPX quoteleft t -30
+KPX quoteleft s -30
+KPX quoteleft r -12
+KPX quoteleft q -30
+KPX quoteleft p -12
+KPX quoteleft o -30
+KPX quoteleft n -12
+KPX quoteleft m -12
+KPX quoteleft l 10
+KPX quoteleft k 10
+KPX quoteleft h 10
+KPX quoteleft g -30
+KPX quoteleft e -30
+KPX quoteleft d -30
+KPX quoteleft c -30
+KPX quoteleft b 24
+KPX quoteleft a -30
+KPX quoteleft Y 12
+KPX quoteleft X 46
+KPX quoteleft W 46
+KPX quoteleft V 28
+KPX quoteleft T 36
+KPX quoteleft A -100
+
+KPX quoteright v -20
+KPX quoteright space -50
+KPX quoteright s -45
+KPX quoteright r -12
+KPX quoteright period -140
+KPX quoteright m -12
+KPX quoteright l -12
+KPX quoteright d -65
+KPX quoteright comma -140
+
+KPX r z 20
+KPX r y 18
+KPX r x 12
+KPX r w 6
+KPX r v 6
+KPX r t 8
+KPX r semicolon 20
+KPX r quoteright -6
+KPX r quotedblright -6
+KPX r q -24
+KPX r period -100
+KPX r o -6
+KPX r l -12
+KPX r k -12
+KPX r hyphen -40
+KPX r h -10
+KPX r f 8
+KPX r endash -20
+KPX r e -26
+KPX r d -25
+KPX r comma -100
+KPX r colon 20
+KPX r c -12
+KPX r a -25
+
+KPX s quoteright -25
+KPX s quotedblright -30
+
+KPX semicolon space -30
+
+KPX space quotesinglbase -60
+KPX space quoteleft -60
+KPX space quotedblleft -60
+KPX space quotedblbase -60
+KPX space Y -70
+KPX space W -50
+KPX space V -70
+KPX space T -50
+KPX space A -50
+
+KPX t quoteright 15
+KPX t quotedblright 15
+KPX t period 15
+KPX t comma 15
+
+KPX u quoteright -65
+KPX u quotedblright -78
+KPX u period 20
+KPX u comma 20
+
+KPX v quoteright -10
+KPX v quotedblright -10
+KPX v q -6
+KPX v period -62
+KPX v o -6
+KPX v e -6
+KPX v d -6
+KPX v comma -62
+KPX v c -6
+KPX v a -6
+
+KPX w quoteright -10
+KPX w quotedblright -10
+KPX w period -40
+KPX w comma -50
+
+KPX x y 12
+KPX x w -6
+KPX x quoteright -30
+KPX x quotedblright -30
+KPX x q -6
+KPX x o -6
+KPX x e -6
+KPX x d -6
+KPX x c -6
+
+KPX y quoteright -10
+KPX y quotedblright -10
+KPX y q -10
+KPX y period -56
+KPX y d -10
+KPX y comma -56
+
+KPX z quoteright -40
+KPX z quotedblright -40
+KPX z o -6
+KPX z e -6
+KPX z d -6
+KPX z c -6
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putr8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putr8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..be1bb781ad14e1a0bbfed60d4d6d1aad1405b737
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putr8a.afm
@@ -0,0 +1,1029 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Fri Jan 17 13:38:17 1992
+Comment UniqueID 37674
+Comment VMusage 32991 39883
+FontName Utopia-Regular
+FullName Utopia Regular
+FamilyName Utopia
+Weight Regular
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -158 -250 1158 890
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.002
+Notice Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 490
+Ascender 742
+Descender -230
+StartCharMetrics 228
+C 32 ; WX 225 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 242 ; N exclam ; B 58 -12 184 707 ;
+C 34 ; WX 458 ; N quotedbl ; B 101 464 358 742 ;
+C 35 ; WX 530 ; N numbersign ; B 11 0 519 668 ;
+C 36 ; WX 530 ; N dollar ; B 44 -102 487 743 ;
+C 37 ; WX 838 ; N percent ; B 50 -25 788 700 ;
+C 38 ; WX 706 ; N ampersand ; B 46 -12 692 680 ;
+C 39 ; WX 278 ; N quoteright ; B 72 472 207 742 ;
+C 40 ; WX 350 ; N parenleft ; B 105 -128 325 692 ;
+C 41 ; WX 350 ; N parenright ; B 25 -128 245 692 ;
+C 42 ; WX 412 ; N asterisk ; B 50 356 363 707 ;
+C 43 ; WX 570 ; N plus ; B 43 0 527 490 ;
+C 44 ; WX 265 ; N comma ; B 51 -141 193 141 ;
+C 45 ; WX 392 ; N hyphen ; B 74 216 319 286 ;
+C 46 ; WX 265 ; N period ; B 70 -12 196 116 ;
+C 47 ; WX 460 ; N slash ; B 92 -15 369 707 ;
+C 48 ; WX 530 ; N zero ; B 41 -12 489 680 ;
+C 49 ; WX 530 ; N one ; B 109 0 437 680 ;
+C 50 ; WX 530 ; N two ; B 27 0 485 680 ;
+C 51 ; WX 530 ; N three ; B 27 -12 473 680 ;
+C 52 ; WX 530 ; N four ; B 19 0 493 668 ;
+C 53 ; WX 530 ; N five ; B 40 -12 480 668 ;
+C 54 ; WX 530 ; N six ; B 44 -12 499 680 ;
+C 55 ; WX 530 ; N seven ; B 41 -12 497 668 ;
+C 56 ; WX 530 ; N eight ; B 42 -12 488 680 ;
+C 57 ; WX 530 ; N nine ; B 36 -12 477 680 ;
+C 58 ; WX 265 ; N colon ; B 70 -12 196 490 ;
+C 59 ; WX 265 ; N semicolon ; B 51 -141 196 490 ;
+C 60 ; WX 570 ; N less ; B 46 1 524 499 ;
+C 61 ; WX 570 ; N equal ; B 43 111 527 389 ;
+C 62 ; WX 570 ; N greater ; B 46 1 524 499 ;
+C 63 ; WX 389 ; N question ; B 29 -12 359 707 ;
+C 64 ; WX 793 ; N at ; B 46 -15 755 707 ;
+C 65 ; WX 635 ; N A ; B -29 0 650 692 ;
+C 66 ; WX 646 ; N B ; B 35 0 595 692 ;
+C 67 ; WX 684 ; N C ; B 48 -15 649 707 ;
+C 68 ; WX 779 ; N D ; B 35 0 731 692 ;
+C 69 ; WX 606 ; N E ; B 35 0 577 692 ;
+C 70 ; WX 580 ; N F ; B 35 0 543 692 ;
+C 71 ; WX 734 ; N G ; B 48 -15 725 707 ;
+C 72 ; WX 798 ; N H ; B 35 0 763 692 ;
+C 73 ; WX 349 ; N I ; B 35 0 314 692 ;
+C 74 ; WX 350 ; N J ; B 0 -114 323 692 ;
+C 75 ; WX 658 ; N K ; B 35 -5 671 692 ;
+C 76 ; WX 568 ; N L ; B 35 0 566 692 ;
+C 77 ; WX 944 ; N M ; B 33 0 909 692 ;
+C 78 ; WX 780 ; N N ; B 34 0 753 692 ;
+C 79 ; WX 762 ; N O ; B 48 -15 714 707 ;
+C 80 ; WX 600 ; N P ; B 35 0 574 692 ;
+C 81 ; WX 762 ; N Q ; B 48 -193 714 707 ;
+C 82 ; WX 644 ; N R ; B 35 0 638 692 ;
+C 83 ; WX 541 ; N S ; B 50 -15 504 707 ;
+C 84 ; WX 621 ; N T ; B 22 0 599 692 ;
+C 85 ; WX 791 ; N U ; B 29 -15 762 692 ;
+C 86 ; WX 634 ; N V ; B -18 0 678 692 ;
+C 87 ; WX 940 ; N W ; B -13 0 977 692 ;
+C 88 ; WX 624 ; N X ; B -19 0 657 692 ;
+C 89 ; WX 588 ; N Y ; B -12 0 632 692 ;
+C 90 ; WX 610 ; N Z ; B 9 0 594 692 ;
+C 91 ; WX 330 ; N bracketleft ; B 133 -128 292 692 ;
+C 92 ; WX 460 ; N backslash ; B 91 -15 369 707 ;
+C 93 ; WX 330 ; N bracketright ; B 38 -128 197 692 ;
+C 94 ; WX 570 ; N asciicircum ; B 56 228 514 668 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 72 478 207 748 ;
+C 97 ; WX 523 ; N a ; B 49 -12 525 502 ;
+C 98 ; WX 598 ; N b ; B 20 -12 549 742 ;
+C 99 ; WX 496 ; N c ; B 49 -12 473 502 ;
+C 100 ; WX 598 ; N d ; B 49 -12 583 742 ;
+C 101 ; WX 514 ; N e ; B 49 -12 481 502 ;
+C 102 ; WX 319 ; N f ; B 30 0 389 742 ; L i fi ; L l fl ;
+C 103 ; WX 520 ; N g ; B 42 -242 525 512 ;
+C 104 ; WX 607 ; N h ; B 21 0 592 742 ;
+C 105 ; WX 291 ; N i ; B 32 0 276 715 ;
+C 106 ; WX 280 ; N j ; B -33 -242 214 715 ;
+C 107 ; WX 524 ; N k ; B 20 -5 538 742 ;
+C 108 ; WX 279 ; N l ; B 20 0 264 742 ;
+C 109 ; WX 923 ; N m ; B 32 0 908 502 ;
+C 110 ; WX 619 ; N n ; B 32 0 604 502 ;
+C 111 ; WX 577 ; N o ; B 49 -12 528 502 ;
+C 112 ; WX 608 ; N p ; B 25 -230 559 502 ;
+C 113 ; WX 591 ; N q ; B 49 -230 583 502 ;
+C 114 ; WX 389 ; N r ; B 32 0 386 502 ;
+C 115 ; WX 436 ; N s ; B 47 -12 400 502 ;
+C 116 ; WX 344 ; N t ; B 31 -12 342 616 ;
+C 117 ; WX 606 ; N u ; B 26 -12 591 502 ;
+C 118 ; WX 504 ; N v ; B 1 0 529 490 ;
+C 119 ; WX 768 ; N w ; B -2 0 792 490 ;
+C 120 ; WX 486 ; N x ; B 1 0 509 490 ;
+C 121 ; WX 506 ; N y ; B -5 -242 528 490 ;
+C 122 ; WX 480 ; N z ; B 19 0 462 490 ;
+C 123 ; WX 340 ; N braceleft ; B 79 -128 298 692 ;
+C 124 ; WX 228 ; N bar ; B 80 -250 148 750 ;
+C 125 ; WX 340 ; N braceright ; B 42 -128 261 692 ;
+C 126 ; WX 570 ; N asciitilde ; B 73 175 497 317 ;
+C 161 ; WX 242 ; N exclamdown ; B 58 -217 184 502 ;
+C 162 ; WX 530 ; N cent ; B 37 -10 487 675 ;
+C 163 ; WX 530 ; N sterling ; B 27 0 510 680 ;
+C 164 ; WX 150 ; N fraction ; B -158 -27 308 695 ;
+C 165 ; WX 530 ; N yen ; B -2 0 525 668 ;
+C 166 ; WX 530 ; N florin ; B -2 -135 522 691 ;
+C 167 ; WX 554 ; N section ; B 46 -115 507 707 ;
+C 168 ; WX 530 ; N currency ; B 25 90 505 578 ;
+C 169 ; WX 278 ; N quotesingle ; B 93 464 185 742 ;
+C 170 ; WX 458 ; N quotedblleft ; B 72 478 387 748 ;
+C 171 ; WX 442 ; N guillemotleft ; B 41 41 401 435 ;
+C 172 ; WX 257 ; N guilsinglleft ; B 41 41 216 435 ;
+C 173 ; WX 257 ; N guilsinglright ; B 41 41 216 435 ;
+C 174 ; WX 610 ; N fi ; B 30 0 595 742 ;
+C 175 ; WX 610 ; N fl ; B 30 0 595 742 ;
+C 177 ; WX 500 ; N endash ; B 0 221 500 279 ;
+C 178 ; WX 504 ; N dagger ; B 45 -125 459 717 ;
+C 179 ; WX 488 ; N daggerdbl ; B 45 -119 443 717 ;
+C 180 ; WX 265 ; N periodcentered ; B 70 188 196 316 ;
+C 182 ; WX 555 ; N paragraph ; B 64 -101 529 692 ;
+C 183 ; WX 409 ; N bullet ; B 45 192 364 512 ;
+C 184 ; WX 278 ; N quotesinglbase ; B 72 -125 207 145 ;
+C 185 ; WX 458 ; N quotedblbase ; B 72 -125 387 145 ;
+C 186 ; WX 458 ; N quotedblright ; B 72 472 387 742 ;
+C 187 ; WX 442 ; N guillemotright ; B 41 41 401 435 ;
+C 188 ; WX 1000 ; N ellipsis ; B 104 -12 896 116 ;
+C 189 ; WX 1208 ; N perthousand ; B 50 -25 1158 700 ;
+C 191 ; WX 389 ; N questiondown ; B 30 -217 360 502 ;
+C 193 ; WX 400 ; N grave ; B 49 542 271 723 ;
+C 194 ; WX 400 ; N acute ; B 129 542 351 723 ;
+C 195 ; WX 400 ; N circumflex ; B 47 541 353 720 ;
+C 196 ; WX 400 ; N tilde ; B 22 563 377 682 ;
+C 197 ; WX 400 ; N macron ; B 56 597 344 656 ;
+C 198 ; WX 400 ; N breve ; B 63 568 337 704 ;
+C 199 ; WX 400 ; N dotaccent ; B 140 570 260 683 ;
+C 200 ; WX 400 ; N dieresis ; B 36 570 364 683 ;
+C 202 ; WX 400 ; N ring ; B 92 550 308 752 ;
+C 203 ; WX 400 ; N cedilla ; B 163 -230 329 0 ;
+C 205 ; WX 400 ; N hungarumlaut ; B 101 546 380 750 ;
+C 206 ; WX 400 ; N ogonek ; B 103 -230 295 0 ;
+C 207 ; WX 400 ; N caron ; B 47 541 353 720 ;
+C 208 ; WX 1000 ; N emdash ; B 0 221 1000 279 ;
+C 225 ; WX 876 ; N AE ; B -63 0 847 692 ;
+C 227 ; WX 390 ; N ordfeminine ; B 40 265 364 590 ;
+C 232 ; WX 574 ; N Lslash ; B 36 0 572 692 ;
+C 233 ; WX 762 ; N Oslash ; B 48 -53 714 739 ;
+C 234 ; WX 1025 ; N OE ; B 48 0 996 692 ;
+C 235 ; WX 398 ; N ordmasculine ; B 35 265 363 590 ;
+C 241 ; WX 797 ; N ae ; B 49 -12 764 502 ;
+C 245 ; WX 291 ; N dotlessi ; B 32 0 276 502 ;
+C 248 ; WX 294 ; N lslash ; B 14 0 293 742 ;
+C 249 ; WX 577 ; N oslash ; B 49 -41 528 532 ;
+C 250 ; WX 882 ; N oe ; B 49 -12 849 502 ;
+C 251 ; WX 601 ; N germandbls ; B 22 -12 573 742 ;
+C -1 ; WX 380 ; N onesuperior ; B 81 272 307 680 ;
+C -1 ; WX 570 ; N minus ; B 43 221 527 279 ;
+C -1 ; WX 350 ; N degree ; B 37 404 313 680 ;
+C -1 ; WX 577 ; N oacute ; B 49 -12 528 723 ;
+C -1 ; WX 762 ; N Odieresis ; B 48 -15 714 841 ;
+C -1 ; WX 577 ; N odieresis ; B 49 -12 528 683 ;
+C -1 ; WX 606 ; N Eacute ; B 35 0 577 890 ;
+C -1 ; WX 606 ; N ucircumflex ; B 26 -12 591 720 ;
+C -1 ; WX 860 ; N onequarter ; B 65 -27 795 695 ;
+C -1 ; WX 570 ; N logicalnot ; B 43 102 527 389 ;
+C -1 ; WX 606 ; N Ecircumflex ; B 35 0 577 876 ;
+C -1 ; WX 860 ; N onehalf ; B 58 -27 807 695 ;
+C -1 ; WX 762 ; N Otilde ; B 48 -15 714 842 ;
+C -1 ; WX 606 ; N uacute ; B 26 -12 591 723 ;
+C -1 ; WX 514 ; N eacute ; B 49 -12 481 723 ;
+C -1 ; WX 291 ; N iacute ; B 32 0 317 723 ;
+C -1 ; WX 606 ; N Egrave ; B 35 0 577 890 ;
+C -1 ; WX 291 ; N icircumflex ; B -3 0 304 720 ;
+C -1 ; WX 606 ; N mu ; B 26 -246 591 502 ;
+C -1 ; WX 228 ; N brokenbar ; B 80 -175 148 675 ;
+C -1 ; WX 606 ; N thorn ; B 23 -230 557 722 ;
+C -1 ; WX 627 ; N Aring ; B -32 0 647 861 ;
+C -1 ; WX 506 ; N yacute ; B -5 -242 528 723 ;
+C -1 ; WX 588 ; N Ydieresis ; B -12 0 632 841 ;
+C -1 ; WX 1100 ; N trademark ; B 45 277 1048 692 ;
+C -1 ; WX 818 ; N registered ; B 45 -15 773 707 ;
+C -1 ; WX 577 ; N ocircumflex ; B 49 -12 528 720 ;
+C -1 ; WX 635 ; N Agrave ; B -29 0 650 890 ;
+C -1 ; WX 541 ; N Scaron ; B 50 -15 504 882 ;
+C -1 ; WX 791 ; N Ugrave ; B 29 -15 762 890 ;
+C -1 ; WX 606 ; N Edieresis ; B 35 0 577 841 ;
+C -1 ; WX 791 ; N Uacute ; B 29 -15 762 890 ;
+C -1 ; WX 577 ; N otilde ; B 49 -12 528 682 ;
+C -1 ; WX 619 ; N ntilde ; B 32 0 604 682 ;
+C -1 ; WX 506 ; N ydieresis ; B -5 -242 528 683 ;
+C -1 ; WX 635 ; N Aacute ; B -29 0 650 890 ;
+C -1 ; WX 577 ; N eth ; B 49 -12 528 742 ;
+C -1 ; WX 523 ; N acircumflex ; B 49 -12 525 720 ;
+C -1 ; WX 523 ; N aring ; B 49 -12 525 752 ;
+C -1 ; WX 762 ; N Ograve ; B 48 -15 714 890 ;
+C -1 ; WX 496 ; N ccedilla ; B 49 -230 473 502 ;
+C -1 ; WX 570 ; N multiply ; B 63 22 507 478 ;
+C -1 ; WX 570 ; N divide ; B 43 26 527 474 ;
+C -1 ; WX 380 ; N twosuperior ; B 32 272 348 680 ;
+C -1 ; WX 780 ; N Ntilde ; B 34 0 753 842 ;
+C -1 ; WX 606 ; N ugrave ; B 26 -12 591 723 ;
+C -1 ; WX 791 ; N Ucircumflex ; B 29 -15 762 876 ;
+C -1 ; WX 635 ; N Atilde ; B -29 0 650 842 ;
+C -1 ; WX 480 ; N zcaron ; B 19 0 462 720 ;
+C -1 ; WX 291 ; N idieresis ; B -19 0 310 683 ;
+C -1 ; WX 635 ; N Acircumflex ; B -29 0 650 876 ;
+C -1 ; WX 349 ; N Icircumflex ; B 22 0 328 876 ;
+C -1 ; WX 588 ; N Yacute ; B -12 0 632 890 ;
+C -1 ; WX 762 ; N Oacute ; B 48 -15 714 890 ;
+C -1 ; WX 635 ; N Adieresis ; B -29 0 650 841 ;
+C -1 ; WX 610 ; N Zcaron ; B 9 0 594 882 ;
+C -1 ; WX 523 ; N agrave ; B 49 -12 525 723 ;
+C -1 ; WX 380 ; N threesuperior ; B 36 265 339 680 ;
+C -1 ; WX 577 ; N ograve ; B 49 -12 528 723 ;
+C -1 ; WX 860 ; N threequarters ; B 50 -27 808 695 ;
+C -1 ; WX 785 ; N Eth ; B 20 0 737 692 ;
+C -1 ; WX 570 ; N plusminus ; B 43 0 527 556 ;
+C -1 ; WX 606 ; N udieresis ; B 26 -12 591 683 ;
+C -1 ; WX 514 ; N edieresis ; B 49 -12 481 683 ;
+C -1 ; WX 523 ; N aacute ; B 49 -12 525 723 ;
+C -1 ; WX 291 ; N igrave ; B -35 0 276 723 ;
+C -1 ; WX 349 ; N Idieresis ; B 13 0 337 841 ;
+C -1 ; WX 523 ; N adieresis ; B 49 -12 525 683 ;
+C -1 ; WX 349 ; N Iacute ; B 35 0 371 890 ;
+C -1 ; WX 818 ; N copyright ; B 45 -15 773 707 ;
+C -1 ; WX 349 ; N Igrave ; B -17 0 314 890 ;
+C -1 ; WX 680 ; N Ccedilla ; B 48 -230 649 707 ;
+C -1 ; WX 436 ; N scaron ; B 47 -12 400 720 ;
+C -1 ; WX 514 ; N egrave ; B 49 -12 481 723 ;
+C -1 ; WX 762 ; N Ocircumflex ; B 48 -15 714 876 ;
+C -1 ; WX 593 ; N Thorn ; B 35 0 556 692 ;
+C -1 ; WX 523 ; N atilde ; B 49 -12 525 682 ;
+C -1 ; WX 791 ; N Udieresis ; B 29 -15 762 841 ;
+C -1 ; WX 514 ; N ecircumflex ; B 49 -12 481 720 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 712
+
+KPX A z 6
+KPX A y -50
+KPX A w -45
+KPX A v -60
+KPX A u -25
+KPX A t -12
+KPX A quoteright -120
+KPX A quotedblright -120
+KPX A q -6
+KPX A p -18
+KPX A o -12
+KPX A e -6
+KPX A d -12
+KPX A c -12
+KPX A b -12
+KPX A Y -70
+KPX A X -6
+KPX A W -58
+KPX A V -72
+KPX A U -50
+KPX A T -70
+KPX A Q -24
+KPX A O -24
+KPX A G -24
+KPX A C -24
+
+KPX B y -18
+KPX B u -12
+KPX B r -12
+KPX B period -30
+KPX B o -6
+KPX B l -12
+KPX B i -12
+KPX B h -12
+KPX B e -6
+KPX B comma -20
+KPX B a -12
+KPX B W -25
+KPX B V -20
+KPX B U -20
+KPX B T -20
+
+KPX C z -18
+KPX C y -24
+KPX C u -18
+KPX C r -6
+KPX C o -12
+KPX C e -12
+KPX C a -12
+KPX C Q -6
+KPX C O -6
+KPX C G -6
+KPX C C -6
+
+KPX D y 6
+KPX D u -12
+KPX D r -12
+KPX D quoteright -20
+KPX D quotedblright -20
+KPX D period -60
+KPX D i -6
+KPX D h -12
+KPX D e -6
+KPX D comma -50
+KPX D a -6
+KPX D Y -45
+KPX D W -35
+KPX D V -35
+
+KPX E z -6
+KPX E y -30
+KPX E x -6
+KPX E w -24
+KPX E v -24
+KPX E u -12
+KPX E t -18
+KPX E r -4
+KPX E q -6
+KPX E p -18
+KPX E o -6
+KPX E n -4
+KPX E m -4
+KPX E l 5
+KPX E k 5
+KPX E j -6
+KPX E i -6
+KPX E g -6
+KPX E f -12
+KPX E e -6
+KPX E d -6
+KPX E c -6
+KPX E b -12
+KPX E Y -6
+KPX E W -6
+KPX E V -6
+
+KPX F y -18
+KPX F u -12
+KPX F r -20
+KPX F period -180
+KPX F o -36
+KPX F l -12
+KPX F i -10
+KPX F endash 20
+KPX F e -36
+KPX F comma -180
+KPX F a -48
+KPX F A -60
+
+KPX G y -18
+KPX G u -12
+KPX G r -5
+KPX G o 5
+KPX G n -5
+KPX G l -6
+KPX G i -12
+KPX G h -12
+KPX G e 5
+KPX G a -12
+
+KPX H y -24
+KPX H u -26
+KPX H o -30
+KPX H i -18
+KPX H e -30
+KPX H a -24
+
+KPX I z -6
+KPX I y -6
+KPX I x -6
+KPX I w -18
+KPX I v -24
+KPX I u -26
+KPX I t -24
+KPX I s -18
+KPX I r -12
+KPX I p -26
+KPX I o -30
+KPX I n -18
+KPX I m -18
+KPX I l -6
+KPX I k -6
+KPX I h -6
+KPX I g -10
+KPX I f -6
+KPX I e -30
+KPX I d -30
+KPX I c -30
+KPX I b -6
+KPX I a -24
+
+KPX J y -12
+KPX J u -36
+KPX J o -30
+KPX J i -20
+KPX J e -30
+KPX J bracketright 20
+KPX J braceright 20
+KPX J a -36
+
+KPX K y -60
+KPX K w -70
+KPX K v -70
+KPX K u -42
+KPX K o -30
+KPX K i 6
+KPX K e -24
+KPX K a -12
+KPX K Q -42
+KPX K O -42
+KPX K G -42
+KPX K C -42
+
+KPX L y -52
+KPX L w -58
+KPX L u -12
+KPX L quoteright -130
+KPX L quotedblright -50
+KPX L l 6
+KPX L j -6
+KPX L Y -70
+KPX L W -90
+KPX L V -100
+KPX L U -24
+KPX L T -100
+KPX L Q -18
+KPX L O -10
+KPX L G -18
+KPX L C -18
+KPX L A 12
+
+KPX M y -24
+KPX M u -36
+KPX M o -30
+KPX M n -6
+KPX M j -12
+KPX M i -12
+KPX M e -30
+KPX M d -30
+KPX M c -30
+KPX M a -12
+
+KPX N y -24
+KPX N u -30
+KPX N o -30
+KPX N i -24
+KPX N e -30
+KPX N a -30
+
+KPX O z -6
+KPX O u -6
+KPX O t -6
+KPX O s -6
+KPX O q -6
+KPX O period -60
+KPX O p -6
+KPX O o -6
+KPX O n -5
+KPX O m -5
+KPX O l -6
+KPX O k -6
+KPX O i -5
+KPX O h -12
+KPX O g -6
+KPX O e -6
+KPX O d -6
+KPX O comma -50
+KPX O c -6
+KPX O a -12
+KPX O Y -55
+KPX O X -24
+KPX O W -30
+KPX O V -18
+KPX O T -30
+KPX O A -18
+
+KPX P u -12
+KPX P t -6
+KPX P s -24
+KPX P r -12
+KPX P period -200
+KPX P o -30
+KPX P n -12
+KPX P l -6
+KPX P hyphen -40
+KPX P h -6
+KPX P e -30
+KPX P comma -200
+KPX P a -36
+KPX P I -6
+KPX P H -12
+KPX P E -6
+KPX P A -55
+
+KPX Q u -6
+KPX Q a -18
+KPX Q Y -30
+KPX Q X -24
+KPX Q W -24
+KPX Q V -18
+KPX Q U -30
+KPX Q T -24
+KPX Q A -18
+
+KPX R y -20
+KPX R u -12
+KPX R quoteright -20
+KPX R quotedblright -20
+KPX R o -20
+KPX R hyphen -30
+KPX R e -20
+KPX R d -20
+KPX R a -12
+KPX R Y -45
+KPX R W -24
+KPX R V -32
+KPX R U -30
+KPX R T -32
+KPX R Q -24
+KPX R O -24
+KPX R G -24
+KPX R C -24
+
+KPX S y -25
+KPX S w -30
+KPX S v -30
+KPX S u -24
+KPX S t -24
+KPX S r -20
+KPX S quoteright -10
+KPX S quotedblright -10
+KPX S q -5
+KPX S p -24
+KPX S o -12
+KPX S n -20
+KPX S m -20
+KPX S l -18
+KPX S k -24
+KPX S j -12
+KPX S i -20
+KPX S h -12
+KPX S e -12
+KPX S a -18
+
+KPX T z -64
+KPX T y -84
+KPX T w -100
+KPX T u -82
+KPX T semicolon -56
+KPX T s -82
+KPX T r -82
+KPX T quoteright 24
+KPX T period -110
+KPX T parenright 54
+KPX T o -100
+KPX T m -82
+KPX T i -34
+KPX T hyphen -100
+KPX T endash -50
+KPX T emdash -50
+KPX T e -100
+KPX T comma -110
+KPX T colon -50
+KPX T bracketright 54
+KPX T braceright 54
+KPX T a -100
+KPX T Y 12
+KPX T X 18
+KPX T W 6
+KPX T V 6
+KPX T T 12
+KPX T S -12
+KPX T Q -18
+KPX T O -18
+KPX T G -18
+KPX T C -18
+KPX T A -65
+
+KPX U z -30
+KPX U y -20
+KPX U x -30
+KPX U v -20
+KPX U t -36
+KPX U s -40
+KPX U r -40
+KPX U p -42
+KPX U n -40
+KPX U m -40
+KPX U l -12
+KPX U k -12
+KPX U i -28
+KPX U h -6
+KPX U g -50
+KPX U f -12
+KPX U d -45
+KPX U c -45
+KPX U b -12
+KPX U a -40
+KPX U A -40
+
+KPX V y -36
+KPX V u -40
+KPX V semicolon -45
+KPX V r -70
+KPX V quoteright 36
+KPX V quotedblright 20
+KPX V period -140
+KPX V parenright 85
+KPX V o -70
+KPX V i 6
+KPX V hyphen -60
+KPX V endash -20
+KPX V emdash -20
+KPX V e -70
+KPX V comma -140
+KPX V colon -45
+KPX V bracketright 64
+KPX V braceright 64
+KPX V a -60
+KPX V T 6
+KPX V Q -12
+KPX V O -12
+KPX V G -12
+KPX V C -12
+KPX V A -60
+
+KPX W y -50
+KPX W u -46
+KPX W semicolon -40
+KPX W r -45
+KPX W quoteright 36
+KPX W quotedblright 20
+KPX W period -110
+KPX W parenright 85
+KPX W o -65
+KPX W m -45
+KPX W i -10
+KPX W hyphen -40
+KPX W e -65
+KPX W d -65
+KPX W comma -100
+KPX W colon -40
+KPX W bracketright 64
+KPX W braceright 64
+KPX W a -60
+KPX W T 18
+KPX W Q -6
+KPX W O -6
+KPX W G -6
+KPX W C -6
+KPX W A -48
+
+KPX X y -18
+KPX X u -24
+KPX X quoteright 15
+KPX X e -6
+KPX X a -6
+KPX X Q -24
+KPX X O -30
+KPX X G -30
+KPX X C -30
+KPX X A 6
+
+KPX Y v -50
+KPX Y u -54
+KPX Y t -46
+KPX Y semicolon -37
+KPX Y quoteright 36
+KPX Y quotedblright 20
+KPX Y q -100
+KPX Y period -90
+KPX Y parenright 60
+KPX Y o -90
+KPX Y l 10
+KPX Y hyphen -50
+KPX Y emdash -20
+KPX Y e -90
+KPX Y d -90
+KPX Y comma -90
+KPX Y colon -50
+KPX Y bracketright 64
+KPX Y braceright 64
+KPX Y a -68
+KPX Y Y 12
+KPX Y X 12
+KPX Y W 12
+KPX Y V 12
+KPX Y T 12
+KPX Y Q -18
+KPX Y O -18
+KPX Y G -18
+KPX Y C -18
+KPX Y A -32
+
+KPX Z y -36
+KPX Z w -36
+KPX Z u -6
+KPX Z o -12
+KPX Z i -12
+KPX Z e -6
+KPX Z a -6
+KPX Z Q -20
+KPX Z O -20
+KPX Z G -30
+KPX Z C -20
+KPX Z A 20
+
+KPX a quoteright -70
+KPX a quotedblright -80
+
+KPX b y -25
+KPX b w -30
+KPX b v -35
+KPX b quoteright -70
+KPX b quotedblright -70
+KPX b period -40
+KPX b comma -40
+
+KPX braceleft Y 64
+KPX braceleft W 64
+KPX braceleft V 64
+KPX braceleft T 54
+KPX braceleft J 80
+
+KPX bracketleft Y 64
+KPX bracketleft W 64
+KPX bracketleft V 64
+KPX bracketleft T 54
+KPX bracketleft J 80
+
+KPX c quoteright -28
+KPX c quotedblright -28
+KPX c period -10
+
+KPX comma quoteright -50
+KPX comma quotedblright -50
+
+KPX d quoteright -24
+KPX d quotedblright -24
+
+KPX e z -4
+KPX e quoteright -60
+KPX e quotedblright -60
+KPX e period -20
+KPX e comma -20
+
+KPX f quotesingle 30
+KPX f quoteright 65
+KPX f quotedblright 56
+KPX f quotedbl 30
+KPX f parenright 100
+KPX f bracketright 100
+KPX f braceright 100
+
+KPX g quoteright -18
+KPX g quotedblright -10
+
+KPX h quoteright -80
+KPX h quotedblright -80
+
+KPX j quoteright -20
+KPX j quotedblright -20
+KPX j period -30
+KPX j comma -30
+
+KPX k quoteright -40
+KPX k quotedblright -40
+
+KPX l quoteright -10
+KPX l quotedblright -10
+
+KPX m quoteright -80
+KPX m quotedblright -80
+
+KPX n quoteright -80
+KPX n quotedblright -80
+
+KPX o z -12
+KPX o y -30
+KPX o x -18
+KPX o w -30
+KPX o v -30
+KPX o quoteright -70
+KPX o quotedblright -70
+KPX o period -40
+KPX o comma -40
+
+KPX p z -20
+KPX p y -25
+KPX p w -30
+KPX p quoteright -70
+KPX p quotedblright -70
+KPX p period -40
+KPX p comma -40
+
+KPX parenleft Y 64
+KPX parenleft W 64
+KPX parenleft V 64
+KPX parenleft T 64
+KPX parenleft J 80
+
+KPX period quoteright -50
+KPX period quotedblright -50
+
+KPX q quoteright -50
+KPX q quotedblright -50
+KPX q period -20
+KPX q comma -10
+
+KPX quotedblleft z -60
+KPX quotedblleft y -30
+KPX quotedblleft x -40
+KPX quotedblleft w -20
+KPX quotedblleft v -20
+KPX quotedblleft u -40
+KPX quotedblleft t -40
+KPX quotedblleft s -50
+KPX quotedblleft r -50
+KPX quotedblleft q -80
+KPX quotedblleft p -50
+KPX quotedblleft o -80
+KPX quotedblleft n -50
+KPX quotedblleft m -50
+KPX quotedblleft g -70
+KPX quotedblleft f -50
+KPX quotedblleft e -80
+KPX quotedblleft d -80
+KPX quotedblleft c -80
+KPX quotedblleft a -70
+KPX quotedblleft Z -20
+KPX quotedblleft Y 12
+KPX quotedblleft W 18
+KPX quotedblleft V 18
+KPX quotedblleft U -20
+KPX quotedblleft T 10
+KPX quotedblleft S -20
+KPX quotedblleft R -20
+KPX quotedblleft Q -20
+KPX quotedblleft P -20
+KPX quotedblleft O -30
+KPX quotedblleft N -20
+KPX quotedblleft M -20
+KPX quotedblleft L -20
+KPX quotedblleft K -20
+KPX quotedblleft J -40
+KPX quotedblleft I -20
+KPX quotedblleft H -20
+KPX quotedblleft G -30
+KPX quotedblleft F -20
+KPX quotedblleft E -20
+KPX quotedblleft D -20
+KPX quotedblleft C -30
+KPX quotedblleft B -20
+KPX quotedblleft A -130
+
+KPX quotedblright period -130
+KPX quotedblright comma -130
+
+KPX quoteleft z -40
+KPX quoteleft y -35
+KPX quoteleft x -30
+KPX quoteleft w -20
+KPX quoteleft v -20
+KPX quoteleft u -50
+KPX quoteleft t -40
+KPX quoteleft s -45
+KPX quoteleft r -50
+KPX quoteleft quoteleft -72
+KPX quoteleft q -70
+KPX quoteleft p -50
+KPX quoteleft o -70
+KPX quoteleft n -50
+KPX quoteleft m -50
+KPX quoteleft g -65
+KPX quoteleft f -40
+KPX quoteleft e -70
+KPX quoteleft d -70
+KPX quoteleft c -70
+KPX quoteleft a -60
+KPX quoteleft Z -20
+KPX quoteleft Y 18
+KPX quoteleft X 12
+KPX quoteleft W 18
+KPX quoteleft V 18
+KPX quoteleft U -20
+KPX quoteleft T 10
+KPX quoteleft R -20
+KPX quoteleft Q -20
+KPX quoteleft P -20
+KPX quoteleft O -30
+KPX quoteleft N -20
+KPX quoteleft M -20
+KPX quoteleft L -20
+KPX quoteleft K -20
+KPX quoteleft J -40
+KPX quoteleft I -20
+KPX quoteleft H -20
+KPX quoteleft G -40
+KPX quoteleft F -20
+KPX quoteleft E -20
+KPX quoteleft D -20
+KPX quoteleft C -30
+KPX quoteleft B -20
+KPX quoteleft A -130
+
+KPX quoteright v -40
+KPX quoteright t -75
+KPX quoteright s -110
+KPX quoteright r -70
+KPX quoteright quoteright -72
+KPX quoteright period -130
+KPX quoteright m -70
+KPX quoteright l -6
+KPX quoteright d -120
+KPX quoteright comma -130
+
+KPX r z 10
+KPX r y 18
+KPX r x 12
+KPX r w 18
+KPX r v 18
+KPX r u 8
+KPX r t 8
+KPX r semicolon 10
+KPX r quoteright -20
+KPX r quotedblright -20
+KPX r q -6
+KPX r period -60
+KPX r o -6
+KPX r n 8
+KPX r m 8
+KPX r k -6
+KPX r i 8
+KPX r hyphen -20
+KPX r h 6
+KPX r g -6
+KPX r f 8
+KPX r e -20
+KPX r d -20
+KPX r comma -60
+KPX r colon 10
+KPX r c -20
+KPX r a -10
+
+KPX s quoteright -40
+KPX s quotedblright -40
+KPX s period -20
+KPX s comma -10
+
+KPX space quotesinglbase -60
+KPX space quoteleft -40
+KPX space quotedblleft -40
+KPX space quotedblbase -60
+KPX space Y -60
+KPX space W -60
+KPX space V -60
+KPX space T -36
+
+KPX t quoteright -18
+KPX t quotedblright -18
+
+KPX u quoteright -30
+KPX u quotedblright -30
+
+KPX v semicolon 10
+KPX v quoteright 20
+KPX v quotedblright 20
+KPX v q -10
+KPX v period -90
+KPX v o -5
+KPX v e -5
+KPX v d -10
+KPX v comma -90
+KPX v colon 10
+KPX v c -6
+KPX v a -6
+
+KPX w semicolon 10
+KPX w quoteright 20
+KPX w quotedblright 20
+KPX w q -6
+KPX w period -80
+KPX w e -6
+KPX w d -6
+KPX w comma -75
+KPX w colon 10
+KPX w c -6
+
+KPX x quoteright -10
+KPX x quotedblright -20
+KPX x q -6
+KPX x o -6
+KPX x d -12
+KPX x c -12
+
+KPX y semicolon 10
+KPX y q -6
+KPX y period -95
+KPX y o -6
+KPX y hyphen -30
+KPX y e -6
+KPX y d -6
+KPX y comma -85
+KPX y colon 10
+KPX y c -6
+
+KPX z quoteright -20
+KPX z quotedblright -30
+KPX z o -6
+KPX z e -6
+KPX z d -6
+KPX z c -6
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putri8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putri8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..b3dd45b553bb72a47ff31ebdc766ef0bfc5a4370
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/putri8a.afm
@@ -0,0 +1,1008 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Fri Jan 17 13:15:45 1992
+Comment UniqueID 37666
+Comment VMusage 34143 41035
+FontName Utopia-Italic
+FullName Utopia Italic
+FamilyName Utopia
+Weight Regular
+ItalicAngle -13
+IsFixedPitch false
+FontBBox -201 -250 1170 890
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.002
+Notice Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 502
+Ascender 742
+Descender -242
+StartCharMetrics 228
+C 32 ; WX 225 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 240 ; N exclam ; B 34 -12 290 707 ;
+C 34 ; WX 402 ; N quotedbl ; B 171 469 454 742 ;
+C 35 ; WX 530 ; N numbersign ; B 54 0 585 668 ;
+C 36 ; WX 530 ; N dollar ; B 31 -109 551 743 ;
+C 37 ; WX 826 ; N percent ; B 98 -25 795 702 ;
+C 38 ; WX 725 ; N ampersand ; B 60 -12 703 680 ;
+C 39 ; WX 216 ; N quoteright ; B 112 482 265 742 ;
+C 40 ; WX 350 ; N parenleft ; B 106 -128 458 692 ;
+C 41 ; WX 350 ; N parenright ; B -46 -128 306 692 ;
+C 42 ; WX 412 ; N asterisk ; B 106 356 458 707 ;
+C 43 ; WX 570 ; N plus ; B 58 0 542 490 ;
+C 44 ; WX 265 ; N comma ; B 11 -134 173 142 ;
+C 45 ; WX 392 ; N hyphen ; B 82 216 341 286 ;
+C 46 ; WX 265 ; N period ; B 47 -12 169 113 ;
+C 47 ; WX 270 ; N slash ; B 0 -15 341 707 ;
+C 48 ; WX 530 ; N zero ; B 60 -12 541 680 ;
+C 49 ; WX 530 ; N one ; B 74 0 429 680 ;
+C 50 ; WX 530 ; N two ; B -2 0 538 680 ;
+C 51 ; WX 530 ; N three ; B 19 -12 524 680 ;
+C 52 ; WX 530 ; N four ; B 32 0 509 668 ;
+C 53 ; WX 530 ; N five ; B 24 -12 550 668 ;
+C 54 ; WX 530 ; N six ; B 56 -12 551 680 ;
+C 55 ; WX 530 ; N seven ; B 130 -12 600 668 ;
+C 56 ; WX 530 ; N eight ; B 46 -12 535 680 ;
+C 57 ; WX 530 ; N nine ; B 51 -12 536 680 ;
+C 58 ; WX 265 ; N colon ; B 47 -12 248 490 ;
+C 59 ; WX 265 ; N semicolon ; B 11 -134 248 490 ;
+C 60 ; WX 570 ; N less ; B 51 1 529 497 ;
+C 61 ; WX 570 ; N equal ; B 58 111 542 389 ;
+C 62 ; WX 570 ; N greater ; B 51 1 529 497 ;
+C 63 ; WX 425 ; N question ; B 115 -12 456 707 ;
+C 64 ; WX 794 ; N at ; B 88 -15 797 707 ;
+C 65 ; WX 624 ; N A ; B -58 0 623 692 ;
+C 66 ; WX 632 ; N B ; B 3 0 636 692 ;
+C 67 ; WX 661 ; N C ; B 79 -15 723 707 ;
+C 68 ; WX 763 ; N D ; B 5 0 767 692 ;
+C 69 ; WX 596 ; N E ; B 3 0 657 692 ;
+C 70 ; WX 571 ; N F ; B 3 0 660 692 ;
+C 71 ; WX 709 ; N G ; B 79 -15 737 707 ;
+C 72 ; WX 775 ; N H ; B 5 0 857 692 ;
+C 73 ; WX 345 ; N I ; B 5 0 428 692 ;
+C 74 ; WX 352 ; N J ; B -78 -119 436 692 ;
+C 75 ; WX 650 ; N K ; B 5 -5 786 692 ;
+C 76 ; WX 565 ; N L ; B 5 0 568 692 ;
+C 77 ; WX 920 ; N M ; B -4 0 1002 692 ;
+C 78 ; WX 763 ; N N ; B -4 0 855 692 ;
+C 79 ; WX 753 ; N O ; B 79 -15 754 707 ;
+C 80 ; WX 614 ; N P ; B 5 0 646 692 ;
+C 81 ; WX 753 ; N Q ; B 79 -203 754 707 ;
+C 82 ; WX 640 ; N R ; B 5 0 642 692 ;
+C 83 ; WX 533 ; N S ; B 34 -15 542 707 ;
+C 84 ; WX 606 ; N T ; B 102 0 708 692 ;
+C 85 ; WX 794 ; N U ; B 131 -15 880 692 ;
+C 86 ; WX 637 ; N V ; B 96 0 786 692 ;
+C 87 ; WX 946 ; N W ; B 86 0 1075 692 ;
+C 88 ; WX 632 ; N X ; B -36 0 735 692 ;
+C 89 ; WX 591 ; N Y ; B 96 0 744 692 ;
+C 90 ; WX 622 ; N Z ; B -20 0 703 692 ;
+C 91 ; WX 330 ; N bracketleft ; B 69 -128 414 692 ;
+C 92 ; WX 390 ; N backslash ; B 89 -15 371 707 ;
+C 93 ; WX 330 ; N bracketright ; B -21 -128 324 692 ;
+C 94 ; WX 570 ; N asciicircum ; B 83 228 547 668 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 216 ; N quoteleft ; B 130 488 283 748 ;
+C 97 ; WX 561 ; N a ; B 31 -12 563 502 ;
+C 98 ; WX 559 ; N b ; B 47 -12 557 742 ;
+C 99 ; WX 441 ; N c ; B 46 -12 465 502 ;
+C 100 ; WX 587 ; N d ; B 37 -12 612 742 ;
+C 101 ; WX 453 ; N e ; B 45 -12 471 502 ;
+C 102 ; WX 315 ; N f ; B -107 -242 504 742 ; L i fi ; L l fl ;
+C 103 ; WX 499 ; N g ; B -5 -242 573 512 ;
+C 104 ; WX 607 ; N h ; B 57 -12 588 742 ;
+C 105 ; WX 317 ; N i ; B 79 -12 328 715 ;
+C 106 ; WX 309 ; N j ; B -95 -242 330 715 ;
+C 107 ; WX 545 ; N k ; B 57 -12 567 742 ;
+C 108 ; WX 306 ; N l ; B 76 -12 331 742 ;
+C 109 ; WX 912 ; N m ; B 63 -12 894 502 ;
+C 110 ; WX 618 ; N n ; B 63 -12 600 502 ;
+C 111 ; WX 537 ; N o ; B 49 -12 522 502 ;
+C 112 ; WX 590 ; N p ; B 22 -242 586 502 ;
+C 113 ; WX 559 ; N q ; B 38 -242 567 525 ;
+C 114 ; WX 402 ; N r ; B 69 -12 448 502 ;
+C 115 ; WX 389 ; N s ; B 19 -12 397 502 ;
+C 116 ; WX 341 ; N t ; B 84 -12 404 616 ;
+C 117 ; WX 618 ; N u ; B 89 -12 609 502 ;
+C 118 ; WX 510 ; N v ; B 84 -12 528 502 ;
+C 119 ; WX 785 ; N w ; B 87 -12 808 502 ;
+C 120 ; WX 516 ; N x ; B -4 -12 531 502 ;
+C 121 ; WX 468 ; N y ; B -40 -242 505 502 ;
+C 122 ; WX 468 ; N z ; B 4 -12 483 490 ;
+C 123 ; WX 340 ; N braceleft ; B 100 -128 423 692 ;
+C 124 ; WX 270 ; N bar ; B 130 -250 198 750 ;
+C 125 ; WX 340 ; N braceright ; B -20 -128 302 692 ;
+C 126 ; WX 570 ; N asciitilde ; B 98 176 522 318 ;
+C 161 ; WX 240 ; N exclamdown ; B -18 -217 238 502 ;
+C 162 ; WX 530 ; N cent ; B 94 -21 563 669 ;
+C 163 ; WX 530 ; N sterling ; B 9 0 549 680 ;
+C 164 ; WX 100 ; N fraction ; B -201 -24 369 698 ;
+C 165 ; WX 530 ; N yen ; B 72 0 645 668 ;
+C 166 ; WX 530 ; N florin ; B 4 -135 588 691 ;
+C 167 ; WX 530 ; N section ; B 55 -115 533 707 ;
+C 168 ; WX 530 ; N currency ; B 56 90 536 578 ;
+C 169 ; WX 216 ; N quotesingle ; B 161 469 274 742 ;
+C 170 ; WX 402 ; N quotedblleft ; B 134 488 473 748 ;
+C 171 ; WX 462 ; N guillemotleft ; B 79 41 470 435 ;
+C 172 ; WX 277 ; N guilsinglleft ; B 71 41 267 435 ;
+C 173 ; WX 277 ; N guilsinglright ; B 44 41 240 435 ;
+C 174 ; WX 607 ; N fi ; B -107 -242 589 742 ;
+C 175 ; WX 603 ; N fl ; B -107 -242 628 742 ;
+C 177 ; WX 500 ; N endash ; B 12 221 524 279 ;
+C 178 ; WX 500 ; N dagger ; B 101 -125 519 717 ;
+C 179 ; WX 490 ; N daggerdbl ; B 39 -119 509 717 ;
+C 180 ; WX 265 ; N periodcentered ; B 89 187 211 312 ;
+C 182 ; WX 560 ; N paragraph ; B 109 -101 637 692 ;
+C 183 ; WX 500 ; N bullet ; B 110 192 429 512 ;
+C 184 ; WX 216 ; N quotesinglbase ; B -7 -109 146 151 ;
+C 185 ; WX 402 ; N quotedblbase ; B -7 -109 332 151 ;
+C 186 ; WX 402 ; N quotedblright ; B 107 484 446 744 ;
+C 187 ; WX 462 ; N guillemotright ; B 29 41 420 435 ;
+C 188 ; WX 1000 ; N ellipsis ; B 85 -12 873 113 ;
+C 189 ; WX 1200 ; N perthousand ; B 98 -25 1170 702 ;
+C 191 ; WX 425 ; N questiondown ; B 3 -217 344 502 ;
+C 193 ; WX 400 ; N grave ; B 146 542 368 723 ;
+C 194 ; WX 400 ; N acute ; B 214 542 436 723 ;
+C 195 ; WX 400 ; N circumflex ; B 187 546 484 720 ;
+C 196 ; WX 400 ; N tilde ; B 137 563 492 682 ;
+C 197 ; WX 400 ; N macron ; B 193 597 489 656 ;
+C 198 ; WX 400 ; N breve ; B 227 568 501 698 ;
+C 199 ; WX 402 ; N dotaccent ; B 252 570 359 680 ;
+C 200 ; WX 400 ; N dieresis ; B 172 572 487 682 ;
+C 202 ; WX 400 ; N ring ; B 186 550 402 752 ;
+C 203 ; WX 400 ; N cedilla ; B 62 -230 241 0 ;
+C 205 ; WX 400 ; N hungarumlaut ; B 176 546 455 750 ;
+C 206 ; WX 350 ; N ogonek ; B 68 -219 248 0 ;
+C 207 ; WX 400 ; N caron ; B 213 557 510 731 ;
+C 208 ; WX 1000 ; N emdash ; B 12 221 1024 279 ;
+C 225 ; WX 880 ; N AE ; B -88 0 941 692 ;
+C 227 ; WX 425 ; N ordfeminine ; B 77 265 460 590 ;
+C 232 ; WX 571 ; N Lslash ; B 11 0 574 692 ;
+C 233 ; WX 753 ; N Oslash ; B 79 -45 754 736 ;
+C 234 ; WX 1020 ; N OE ; B 79 0 1081 692 ;
+C 235 ; WX 389 ; N ordmasculine ; B 86 265 420 590 ;
+C 241 ; WX 779 ; N ae ; B 34 -12 797 514 ;
+C 245 ; WX 317 ; N dotlessi ; B 79 -12 299 502 ;
+C 248 ; WX 318 ; N lslash ; B 45 -12 376 742 ;
+C 249 ; WX 537 ; N oslash ; B 49 -39 522 529 ;
+C 250 ; WX 806 ; N oe ; B 49 -12 824 502 ;
+C 251 ; WX 577 ; N germandbls ; B -107 -242 630 742 ;
+C -1 ; WX 370 ; N onesuperior ; B 90 272 326 680 ;
+C -1 ; WX 570 ; N minus ; B 58 221 542 279 ;
+C -1 ; WX 400 ; N degree ; B 152 404 428 680 ;
+C -1 ; WX 537 ; N oacute ; B 49 -12 530 723 ;
+C -1 ; WX 753 ; N Odieresis ; B 79 -15 754 848 ;
+C -1 ; WX 537 ; N odieresis ; B 49 -12 532 682 ;
+C -1 ; WX 596 ; N Eacute ; B 3 0 657 890 ;
+C -1 ; WX 618 ; N ucircumflex ; B 89 -12 609 720 ;
+C -1 ; WX 890 ; N onequarter ; B 97 -24 805 698 ;
+C -1 ; WX 570 ; N logicalnot ; B 58 102 542 389 ;
+C -1 ; WX 596 ; N Ecircumflex ; B 3 0 657 876 ;
+C -1 ; WX 890 ; N onehalf ; B 71 -24 812 698 ;
+C -1 ; WX 753 ; N Otilde ; B 79 -15 754 842 ;
+C -1 ; WX 618 ; N uacute ; B 89 -12 609 723 ;
+C -1 ; WX 453 ; N eacute ; B 45 -12 508 723 ;
+C -1 ; WX 317 ; N iacute ; B 79 -12 398 723 ;
+C -1 ; WX 596 ; N Egrave ; B 3 0 657 890 ;
+C -1 ; WX 317 ; N icircumflex ; B 79 -12 383 720 ;
+C -1 ; WX 618 ; N mu ; B 11 -232 609 502 ;
+C -1 ; WX 270 ; N brokenbar ; B 130 -175 198 675 ;
+C -1 ; WX 584 ; N thorn ; B 16 -242 580 700 ;
+C -1 ; WX 624 ; N Aring ; B -58 0 623 861 ;
+C -1 ; WX 468 ; N yacute ; B -40 -242 505 723 ;
+C -1 ; WX 591 ; N Ydieresis ; B 96 0 744 848 ;
+C -1 ; WX 1100 ; N trademark ; B 91 277 1094 692 ;
+C -1 ; WX 836 ; N registered ; B 91 -15 819 707 ;
+C -1 ; WX 537 ; N ocircumflex ; B 49 -12 522 720 ;
+C -1 ; WX 624 ; N Agrave ; B -58 0 623 890 ;
+C -1 ; WX 533 ; N Scaron ; B 34 -15 561 888 ;
+C -1 ; WX 794 ; N Ugrave ; B 131 -15 880 890 ;
+C -1 ; WX 596 ; N Edieresis ; B 3 0 657 848 ;
+C -1 ; WX 794 ; N Uacute ; B 131 -15 880 890 ;
+C -1 ; WX 537 ; N otilde ; B 49 -12 525 682 ;
+C -1 ; WX 618 ; N ntilde ; B 63 -12 600 682 ;
+C -1 ; WX 468 ; N ydieresis ; B -40 -242 513 682 ;
+C -1 ; WX 624 ; N Aacute ; B -58 0 642 890 ;
+C -1 ; WX 537 ; N eth ; B 47 -12 521 742 ;
+C -1 ; WX 561 ; N acircumflex ; B 31 -12 563 720 ;
+C -1 ; WX 561 ; N aring ; B 31 -12 563 752 ;
+C -1 ; WX 753 ; N Ograve ; B 79 -15 754 890 ;
+C -1 ; WX 441 ; N ccedilla ; B 46 -230 465 502 ;
+C -1 ; WX 570 ; N multiply ; B 88 22 532 478 ;
+C -1 ; WX 570 ; N divide ; B 58 25 542 475 ;
+C -1 ; WX 370 ; N twosuperior ; B 35 272 399 680 ;
+C -1 ; WX 763 ; N Ntilde ; B -4 0 855 842 ;
+C -1 ; WX 618 ; N ugrave ; B 89 -12 609 723 ;
+C -1 ; WX 794 ; N Ucircumflex ; B 131 -15 880 876 ;
+C -1 ; WX 624 ; N Atilde ; B -58 0 623 842 ;
+C -1 ; WX 468 ; N zcaron ; B 4 -12 484 731 ;
+C -1 ; WX 317 ; N idieresis ; B 79 -12 398 682 ;
+C -1 ; WX 624 ; N Acircumflex ; B -58 0 623 876 ;
+C -1 ; WX 345 ; N Icircumflex ; B 5 0 453 876 ;
+C -1 ; WX 591 ; N Yacute ; B 96 0 744 890 ;
+C -1 ; WX 753 ; N Oacute ; B 79 -15 754 890 ;
+C -1 ; WX 624 ; N Adieresis ; B -58 0 623 848 ;
+C -1 ; WX 622 ; N Zcaron ; B -20 0 703 888 ;
+C -1 ; WX 561 ; N agrave ; B 31 -12 563 723 ;
+C -1 ; WX 370 ; N threesuperior ; B 59 265 389 680 ;
+C -1 ; WX 537 ; N ograve ; B 49 -12 522 723 ;
+C -1 ; WX 890 ; N threequarters ; B 105 -24 816 698 ;
+C -1 ; WX 770 ; N Eth ; B 12 0 774 692 ;
+C -1 ; WX 570 ; N plusminus ; B 58 0 542 556 ;
+C -1 ; WX 618 ; N udieresis ; B 89 -12 609 682 ;
+C -1 ; WX 453 ; N edieresis ; B 45 -12 490 682 ;
+C -1 ; WX 561 ; N aacute ; B 31 -12 571 723 ;
+C -1 ; WX 317 ; N igrave ; B 55 -12 299 723 ;
+C -1 ; WX 345 ; N Idieresis ; B 5 0 461 848 ;
+C -1 ; WX 561 ; N adieresis ; B 31 -12 563 682 ;
+C -1 ; WX 345 ; N Iacute ; B 5 0 506 890 ;
+C -1 ; WX 836 ; N copyright ; B 91 -15 819 707 ;
+C -1 ; WX 345 ; N Igrave ; B 5 0 428 890 ;
+C -1 ; WX 661 ; N Ccedilla ; B 79 -230 723 707 ;
+C -1 ; WX 389 ; N scaron ; B 19 -12 457 731 ;
+C -1 ; WX 453 ; N egrave ; B 45 -12 471 723 ;
+C -1 ; WX 753 ; N Ocircumflex ; B 79 -15 754 876 ;
+C -1 ; WX 604 ; N Thorn ; B 5 0 616 692 ;
+C -1 ; WX 561 ; N atilde ; B 31 -12 563 682 ;
+C -1 ; WX 794 ; N Udieresis ; B 131 -15 880 848 ;
+C -1 ; WX 453 ; N ecircumflex ; B 45 -12 475 720 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 690
+
+KPX A y -20
+KPX A x 10
+KPX A w -30
+KPX A v -30
+KPX A u -10
+KPX A t -6
+KPX A s 15
+KPX A r -12
+KPX A quoteright -110
+KPX A quotedblright -110
+KPX A q 10
+KPX A p -12
+KPX A o -10
+KPX A n -18
+KPX A m -18
+KPX A l -18
+KPX A j 6
+KPX A h -6
+KPX A d 10
+KPX A c -6
+KPX A b -6
+KPX A a 12
+KPX A Y -76
+KPX A X -8
+KPX A W -80
+KPX A V -90
+KPX A U -60
+KPX A T -72
+KPX A Q -30
+KPX A O -30
+KPX A G -30
+KPX A C -30
+
+KPX B y -6
+KPX B u -20
+KPX B r -15
+KPX B quoteright -40
+KPX B quotedblright -30
+KPX B o 6
+KPX B l -20
+KPX B k -15
+KPX B i -12
+KPX B h -15
+KPX B e 6
+KPX B a 12
+KPX B W -20
+KPX B V -50
+KPX B U -50
+KPX B T -20
+
+KPX C z -6
+KPX C y -18
+KPX C u -18
+KPX C quotedblright 20
+KPX C i -5
+KPX C e -6
+KPX C a -6
+
+KPX D y 18
+KPX D u -10
+KPX D quoteright -40
+KPX D quotedblright -50
+KPX D period -30
+KPX D o 6
+KPX D i 6
+KPX D h -25
+KPX D e 6
+KPX D comma -20
+KPX D a 6
+KPX D Y -70
+KPX D W -50
+KPX D V -60
+
+KPX E z -6
+KPX E y -18
+KPX E x 5
+KPX E w -20
+KPX E v -18
+KPX E u -24
+KPX E t -18
+KPX E s 5
+KPX E r -6
+KPX E quoteright 10
+KPX E quotedblright 10
+KPX E q 10
+KPX E period 10
+KPX E p -12
+KPX E o -6
+KPX E n -12
+KPX E m -12
+KPX E l -12
+KPX E k -10
+KPX E j -6
+KPX E i -12
+KPX E g -12
+KPX E e 5
+KPX E d 10
+KPX E comma 10
+KPX E b -6
+
+KPX F y -12
+KPX F u -30
+KPX F r -18
+KPX F quoteright 15
+KPX F quotedblright 35
+KPX F period -180
+KPX F o -30
+KPX F l -6
+KPX F i -12
+KPX F e -30
+KPX F comma -170
+KPX F a -30
+KPX F A -45
+
+KPX G y -16
+KPX G u -22
+KPX G r -22
+KPX G quoteright -20
+KPX G quotedblright -20
+KPX G o 10
+KPX G n -22
+KPX G l -24
+KPX G i -12
+KPX G h -18
+KPX G e 10
+KPX G a 5
+
+KPX H y -18
+KPX H u -30
+KPX H quoteright 10
+KPX H quotedblright 10
+KPX H o -12
+KPX H i -12
+KPX H e -12
+KPX H a -12
+
+KPX I z -20
+KPX I y -6
+KPX I x -6
+KPX I w -30
+KPX I v -30
+KPX I u -30
+KPX I t -18
+KPX I s -18
+KPX I r -12
+KPX I quoteright 10
+KPX I quotedblright 10
+KPX I p -18
+KPX I o -12
+KPX I n -18
+KPX I m -18
+KPX I l -6
+KPX I k -6
+KPX I g -12
+KPX I f -6
+KPX I d -6
+KPX I c -12
+KPX I b -6
+KPX I a -6
+
+KPX J y -12
+KPX J u -36
+KPX J quoteright 6
+KPX J quotedblright 15
+KPX J o -36
+KPX J i -30
+KPX J e -36
+KPX J braceright 10
+KPX J a -36
+
+KPX K y -40
+KPX K w -30
+KPX K v -20
+KPX K u -24
+KPX K r -12
+KPX K quoteright 25
+KPX K quotedblright 40
+KPX K o -24
+KPX K n -18
+KPX K i -6
+KPX K h 6
+KPX K e -12
+KPX K a -6
+KPX K Q -24
+KPX K O -24
+KPX K G -24
+KPX K C -24
+
+KPX L y -55
+KPX L w -30
+KPX L u -18
+KPX L quoteright -110
+KPX L quotedblright -110
+KPX L l -16
+KPX L j -18
+KPX L i -18
+KPX L a 10
+KPX L Y -80
+KPX L W -90
+KPX L V -110
+KPX L U -42
+KPX L T -80
+KPX L Q -48
+KPX L O -48
+KPX L G -48
+KPX L C -48
+KPX L A 30
+
+KPX M y -18
+KPX M u -24
+KPX M quoteright 6
+KPX M quotedblright 15
+KPX M o -25
+KPX M n -12
+KPX M j -18
+KPX M i -12
+KPX M e -20
+KPX M d -10
+KPX M c -20
+KPX M a -6
+
+KPX N y -18
+KPX N u -24
+KPX N quoteright 10
+KPX N quotedblright 10
+KPX N o -25
+KPX N i -12
+KPX N e -20
+KPX N a -22
+
+KPX O z -6
+KPX O y 12
+KPX O w -10
+KPX O v -10
+KPX O u -6
+KPX O t -6
+KPX O s -6
+KPX O r -6
+KPX O quoteright -40
+KPX O quotedblright -40
+KPX O q 5
+KPX O period -20
+KPX O p -6
+KPX O n -6
+KPX O m -6
+KPX O l -20
+KPX O k -10
+KPX O j -6
+KPX O h -10
+KPX O g -6
+KPX O e 5
+KPX O d 6
+KPX O comma -10
+KPX O c 5
+KPX O b -6
+KPX O a 5
+KPX O Y -75
+KPX O X -30
+KPX O W -40
+KPX O V -60
+KPX O T -48
+KPX O A -18
+
+KPX P y 6
+KPX P u -18
+KPX P t -6
+KPX P s -24
+KPX P r -6
+KPX P period -220
+KPX P o -24
+KPX P n -12
+KPX P l -25
+KPX P h -15
+KPX P e -24
+KPX P comma -220
+KPX P a -24
+KPX P I -30
+KPX P H -30
+KPX P E -30
+KPX P A -75
+
+KPX Q u -6
+KPX Q quoteright -40
+KPX Q quotedblright -50
+KPX Q a -6
+KPX Q Y -70
+KPX Q X -12
+KPX Q W -35
+KPX Q V -60
+KPX Q U -35
+KPX Q T -36
+KPX Q A -18
+
+KPX R y -14
+KPX R u -12
+KPX R quoteright -30
+KPX R quotedblright -20
+KPX R o -12
+KPX R hyphen -20
+KPX R e -12
+KPX R Y -50
+KPX R W -30
+KPX R V -40
+KPX R U -40
+KPX R T -30
+KPX R Q -10
+KPX R O -10
+KPX R G -10
+KPX R C -10
+KPX R A -6
+
+KPX S y -30
+KPX S w -30
+KPX S v -30
+KPX S u -18
+KPX S t -30
+KPX S r -20
+KPX S quoteright -38
+KPX S quotedblright -30
+KPX S p -18
+KPX S n -24
+KPX S m -24
+KPX S l -30
+KPX S k -24
+KPX S j -25
+KPX S i -30
+KPX S h -30
+KPX S e -6
+
+KPX T z -70
+KPX T y -60
+KPX T w -64
+KPX T u -74
+KPX T semicolon -36
+KPX T s -72
+KPX T r -64
+KPX T quoteright 45
+KPX T quotedblright 50
+KPX T period -100
+KPX T parenright 54
+KPX T o -90
+KPX T m -64
+KPX T i -34
+KPX T hyphen -100
+KPX T endash -60
+KPX T emdash -60
+KPX T e -90
+KPX T comma -110
+KPX T colon -10
+KPX T bracketright 45
+KPX T braceright 54
+KPX T a -90
+KPX T Y 12
+KPX T X 18
+KPX T W 6
+KPX T T 18
+KPX T Q -12
+KPX T O -12
+KPX T G -12
+KPX T C -12
+KPX T A -56
+
+KPX U z -30
+KPX U x -40
+KPX U t -24
+KPX U s -30
+KPX U r -30
+KPX U quoteright 10
+KPX U quotedblright 10
+KPX U p -40
+KPX U n -45
+KPX U m -45
+KPX U l -12
+KPX U k -12
+KPX U i -24
+KPX U h -6
+KPX U g -30
+KPX U d -40
+KPX U c -35
+KPX U b -6
+KPX U a -40
+KPX U A -45
+
+KPX V y -46
+KPX V u -42
+KPX V semicolon -35
+KPX V r -50
+KPX V quoteright 75
+KPX V quotedblright 70
+KPX V period -130
+KPX V parenright 64
+KPX V o -62
+KPX V i -10
+KPX V hyphen -60
+KPX V endash -20
+KPX V emdash -20
+KPX V e -52
+KPX V comma -120
+KPX V colon -18
+KPX V bracketright 64
+KPX V braceright 64
+KPX V a -60
+KPX V T 6
+KPX V A -70
+
+KPX W y -42
+KPX W u -56
+KPX W t -20
+KPX W semicolon -28
+KPX W r -40
+KPX W quoteright 55
+KPX W quotedblright 60
+KPX W period -108
+KPX W parenright 64
+KPX W o -60
+KPX W m -35
+KPX W i -10
+KPX W hyphen -40
+KPX W endash -2
+KPX W emdash -10
+KPX W e -54
+KPX W d -50
+KPX W comma -108
+KPX W colon -28
+KPX W bracketright 55
+KPX W braceright 64
+KPX W a -60
+KPX W T 12
+KPX W Q -10
+KPX W O -10
+KPX W G -10
+KPX W C -10
+KPX W A -58
+
+KPX X y -35
+KPX X u -30
+KPX X r -6
+KPX X quoteright 35
+KPX X quotedblright 15
+KPX X i -6
+KPX X e -10
+KPX X a 5
+KPX X Y -6
+KPX X W -6
+KPX X Q -30
+KPX X O -30
+KPX X G -30
+KPX X C -30
+KPX X A -18
+
+KPX Y v -50
+KPX Y u -58
+KPX Y t -32
+KPX Y semicolon -36
+KPX Y quoteright 65
+KPX Y quotedblright 70
+KPX Y q -100
+KPX Y period -90
+KPX Y parenright 60
+KPX Y o -72
+KPX Y l 10
+KPX Y hyphen -95
+KPX Y endash -20
+KPX Y emdash -20
+KPX Y e -72
+KPX Y d -80
+KPX Y comma -80
+KPX Y colon -36
+KPX Y bracketright 64
+KPX Y braceright 75
+KPX Y a -82
+KPX Y Y 12
+KPX Y X 12
+KPX Y W 12
+KPX Y V 6
+KPX Y T 25
+KPX Y Q -5
+KPX Y O -5
+KPX Y G -5
+KPX Y C -5
+KPX Y A -36
+
+KPX Z y -36
+KPX Z w -36
+KPX Z u -12
+KPX Z quoteright 10
+KPX Z quotedblright 10
+KPX Z o -6
+KPX Z i -12
+KPX Z e -6
+KPX Z a -6
+KPX Z Q -30
+KPX Z O -30
+KPX Z G -30
+KPX Z C -30
+KPX Z A 12
+
+KPX a quoteright -40
+KPX a quotedblright -40
+
+KPX b y -6
+KPX b w -15
+KPX b v -15
+KPX b quoteright -50
+KPX b quotedblright -50
+KPX b period -40
+KPX b comma -30
+
+KPX braceleft Y 64
+KPX braceleft W 64
+KPX braceleft V 64
+KPX braceleft T 54
+KPX braceleft J 80
+
+KPX bracketleft Y 64
+KPX bracketleft W 64
+KPX bracketleft V 64
+KPX bracketleft T 54
+KPX bracketleft J 80
+
+KPX c quoteright -20
+KPX c quotedblright -20
+
+KPX colon space -30
+
+KPX comma space -40
+KPX comma quoteright -80
+KPX comma quotedblright -80
+
+KPX d quoteright -12
+KPX d quotedblright -12
+
+KPX e x -10
+KPX e w -10
+KPX e quoteright -30
+KPX e quotedblright -30
+
+KPX f quoteright 110
+KPX f quotedblright 110
+KPX f period -20
+KPX f parenright 100
+KPX f comma -20
+KPX f bracketright 90
+KPX f braceright 90
+
+KPX g y 30
+KPX g p 12
+KPX g f 42
+
+KPX h quoteright -80
+KPX h quotedblright -80
+
+KPX j quoteright -20
+KPX j quotedblright -20
+KPX j period -35
+KPX j comma -20
+
+KPX k quoteright -30
+KPX k quotedblright -50
+
+KPX m quoteright -80
+KPX m quotedblright -80
+
+KPX n quoteright -80
+KPX n quotedblright -80
+
+KPX o z -10
+KPX o y -20
+KPX o x -20
+KPX o w -30
+KPX o v -35
+KPX o quoteright -60
+KPX o quotedblright -50
+KPX o period -30
+KPX o comma -20
+
+KPX p z -10
+KPX p w -15
+KPX p quoteright -50
+KPX p quotedblright -70
+KPX p period -30
+KPX p comma -20
+
+KPX parenleft Y 75
+KPX parenleft W 75
+KPX parenleft V 75
+KPX parenleft T 64
+KPX parenleft J 80
+
+KPX period space -40
+KPX period quoteright -80
+KPX period quotedblright -80
+
+KPX q quoteright -20
+KPX q quotedblright -30
+KPX q period -20
+KPX q comma -10
+
+KPX quotedblleft z -30
+KPX quotedblleft x -40
+KPX quotedblleft w -12
+KPX quotedblleft v -12
+KPX quotedblleft u -12
+KPX quotedblleft t -12
+KPX quotedblleft s -30
+KPX quotedblleft r -12
+KPX quotedblleft q -40
+KPX quotedblleft p -12
+KPX quotedblleft o -30
+KPX quotedblleft n -12
+KPX quotedblleft m -12
+KPX quotedblleft l 10
+KPX quotedblleft k 10
+KPX quotedblleft h 10
+KPX quotedblleft g -30
+KPX quotedblleft e -40
+KPX quotedblleft d -40
+KPX quotedblleft c -40
+KPX quotedblleft b 24
+KPX quotedblleft a -60
+KPX quotedblleft Y 12
+KPX quotedblleft X 28
+KPX quotedblleft W 28
+KPX quotedblleft V 28
+KPX quotedblleft T 36
+KPX quotedblleft A -90
+
+KPX quotedblright space -40
+KPX quotedblright period -100
+KPX quotedblright comma -100
+
+KPX quoteleft z -30
+KPX quoteleft y -10
+KPX quoteleft x -40
+KPX quoteleft w -12
+KPX quoteleft v -12
+KPX quoteleft u -12
+KPX quoteleft t -12
+KPX quoteleft s -30
+KPX quoteleft r -12
+KPX quoteleft quoteleft -18
+KPX quoteleft q -30
+KPX quoteleft p -12
+KPX quoteleft o -30
+KPX quoteleft n -12
+KPX quoteleft m -12
+KPX quoteleft l 10
+KPX quoteleft k 10
+KPX quoteleft h 10
+KPX quoteleft g -30
+KPX quoteleft e -30
+KPX quoteleft d -30
+KPX quoteleft c -30
+KPX quoteleft b 24
+KPX quoteleft a -45
+KPX quoteleft Y 12
+KPX quoteleft X 28
+KPX quoteleft W 28
+KPX quoteleft V 28
+KPX quoteleft T 36
+KPX quoteleft A -90
+
+KPX quoteright v -35
+KPX quoteright t -35
+KPX quoteright space -40
+KPX quoteright s -55
+KPX quoteright r -25
+KPX quoteright quoteright -18
+KPX quoteright period -100
+KPX quoteright m -25
+KPX quoteright l -12
+KPX quoteright d -70
+KPX quoteright comma -100
+
+KPX r y 18
+KPX r w 6
+KPX r v 6
+KPX r t 8
+KPX r quotedblright -15
+KPX r q -24
+KPX r period -120
+KPX r o -6
+KPX r l -20
+KPX r k -20
+KPX r hyphen -30
+KPX r h -20
+KPX r f 8
+KPX r emdash -20
+KPX r e -26
+KPX r d -26
+KPX r comma -110
+KPX r c -12
+KPX r a -20
+
+KPX s quoteright -40
+KPX s quotedblright -45
+
+KPX semicolon space -30
+
+KPX space quotesinglbase -30
+KPX space quoteleft -40
+KPX space quotedblleft -40
+KPX space quotedblbase -30
+KPX space Y -70
+KPX space W -70
+KPX space V -70
+
+KPX t quoteright 10
+KPX t quotedblright -10
+
+KPX u quoteright -55
+KPX u quotedblright -50
+
+KPX v quoteright -20
+KPX v quotedblright -30
+KPX v q -6
+KPX v period -70
+KPX v o -6
+KPX v e -6
+KPX v d -6
+KPX v comma -70
+KPX v c -6
+KPX v a -6
+
+KPX w quoteright -20
+KPX w quotedblright -30
+KPX w period -62
+KPX w comma -62
+
+KPX x y 12
+KPX x w -6
+KPX x quoteright -40
+KPX x quotedblright -50
+KPX x q -6
+KPX x o -6
+KPX x e -6
+KPX x d -6
+KPX x c -6
+
+KPX y quoteright -10
+KPX y quotedblright -20
+KPX y period -70
+KPX y emdash 40
+KPX y comma -60
+
+KPX z quoteright -40
+KPX z quotedblright -50
+KPX z o -6
+KPX z e -6
+KPX z d -6
+KPX z c -6
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pzcmi8a.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pzcmi8a.afm
new file mode 100644
index 0000000000000000000000000000000000000000..6efb57ab586f61707cc01e74c37e09217d9c3cbd
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pzcmi8a.afm
@@ -0,0 +1,480 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Fri Dec 28 16:35:46 1990
+Comment UniqueID 33936
+Comment VMusage 34559 41451
+FontName ZapfChancery-MediumItalic
+FullName ITC Zapf Chancery Medium Italic
+FamilyName ITC Zapf Chancery
+Weight Medium
+ItalicAngle -14
+IsFixedPitch false
+FontBBox -181 -314 1065 831
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.007
+Notice Copyright (c) 1985, 1987, 1989, 1990 Adobe Systems Incorporated. All Rights Reserved.ITC Zapf Chancery is a registered trademark of International Typeface Corporation.
+EncodingScheme AdobeStandardEncoding
+CapHeight 708
+XHeight 438
+Ascender 714
+Descender -314
+StartCharMetrics 228
+C 32 ; WX 220 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 280 ; N exclam ; B 119 -14 353 610 ;
+C 34 ; WX 220 ; N quotedbl ; B 120 343 333 610 ;
+C 35 ; WX 440 ; N numbersign ; B 83 0 521 594 ;
+C 36 ; WX 440 ; N dollar ; B 60 -144 508 709 ;
+C 37 ; WX 680 ; N percent ; B 132 -160 710 700 ;
+C 38 ; WX 780 ; N ampersand ; B 126 -16 915 610 ;
+C 39 ; WX 240 ; N quoteright ; B 168 343 338 610 ;
+C 40 ; WX 260 ; N parenleft ; B 96 -216 411 664 ;
+C 41 ; WX 220 ; N parenright ; B -13 -216 302 664 ;
+C 42 ; WX 420 ; N asterisk ; B 139 263 479 610 ;
+C 43 ; WX 520 ; N plus ; B 117 0 543 426 ;
+C 44 ; WX 220 ; N comma ; B 25 -140 213 148 ;
+C 45 ; WX 280 ; N hyphen ; B 69 190 334 248 ;
+C 46 ; WX 220 ; N period ; B 102 -14 228 128 ;
+C 47 ; WX 340 ; N slash ; B 74 -16 458 610 ;
+C 48 ; WX 440 ; N zero ; B 79 -16 538 610 ;
+C 49 ; WX 440 ; N one ; B 41 0 428 610 ;
+C 50 ; WX 440 ; N two ; B 17 -16 485 610 ;
+C 51 ; WX 440 ; N three ; B 1 -16 485 610 ;
+C 52 ; WX 440 ; N four ; B 77 -35 499 610 ;
+C 53 ; WX 440 ; N five ; B 60 -16 595 679 ;
+C 54 ; WX 440 ; N six ; B 90 -16 556 610 ;
+C 55 ; WX 440 ; N seven ; B 157 -33 561 645 ;
+C 56 ; WX 440 ; N eight ; B 65 -16 529 610 ;
+C 57 ; WX 440 ; N nine ; B 32 -16 517 610 ;
+C 58 ; WX 260 ; N colon ; B 98 -14 296 438 ;
+C 59 ; WX 240 ; N semicolon ; B 29 -140 299 438 ;
+C 60 ; WX 520 ; N less ; B 139 0 527 468 ;
+C 61 ; WX 520 ; N equal ; B 117 86 543 340 ;
+C 62 ; WX 520 ; N greater ; B 139 0 527 468 ;
+C 63 ; WX 380 ; N question ; B 150 -14 455 610 ;
+C 64 ; WX 700 ; N at ; B 127 -16 753 610 ;
+C 65 ; WX 620 ; N A ; B 13 -16 697 632 ;
+C 66 ; WX 600 ; N B ; B 85 -6 674 640 ;
+C 67 ; WX 520 ; N C ; B 93 -16 631 610 ;
+C 68 ; WX 700 ; N D ; B 86 -6 768 640 ;
+C 69 ; WX 620 ; N E ; B 91 -12 709 618 ;
+C 70 ; WX 580 ; N F ; B 120 -118 793 629 ;
+C 71 ; WX 620 ; N G ; B 148 -242 709 610 ;
+C 72 ; WX 680 ; N H ; B 18 -16 878 708 ;
+C 73 ; WX 380 ; N I ; B 99 0 504 594 ;
+C 74 ; WX 400 ; N J ; B -14 -147 538 594 ;
+C 75 ; WX 660 ; N K ; B 53 -153 844 610 ;
+C 76 ; WX 580 ; N L ; B 53 -16 657 610 ;
+C 77 ; WX 840 ; N M ; B 58 -16 1020 722 ;
+C 78 ; WX 700 ; N N ; B 85 -168 915 708 ;
+C 79 ; WX 600 ; N O ; B 94 -16 660 610 ;
+C 80 ; WX 540 ; N P ; B 42 0 658 628 ;
+C 81 ; WX 600 ; N Q ; B 84 -177 775 610 ;
+C 82 ; WX 600 ; N R ; B 58 -168 805 640 ;
+C 83 ; WX 460 ; N S ; B 45 -81 558 610 ;
+C 84 ; WX 500 ; N T ; B 63 0 744 667 ;
+C 85 ; WX 740 ; N U ; B 126 -16 792 617 ;
+C 86 ; WX 640 ; N V ; B 124 -16 810 714 ;
+C 87 ; WX 880 ; N W ; B 94 -16 1046 723 ;
+C 88 ; WX 560 ; N X ; B -30 -16 699 610 ;
+C 89 ; WX 560 ; N Y ; B 41 -168 774 647 ;
+C 90 ; WX 620 ; N Z ; B 42 -19 669 624 ;
+C 91 ; WX 240 ; N bracketleft ; B -13 -207 405 655 ;
+C 92 ; WX 480 ; N backslash ; B 140 -16 524 610 ;
+C 93 ; WX 320 ; N bracketright ; B -27 -207 391 655 ;
+C 94 ; WX 520 ; N asciicircum ; B 132 239 532 594 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 240 ; N quoteleft ; B 169 343 339 610 ;
+C 97 ; WX 420 ; N a ; B 92 -15 485 438 ;
+C 98 ; WX 420 ; N b ; B 82 -23 492 714 ;
+C 99 ; WX 340 ; N c ; B 87 -14 406 438 ;
+C 100 ; WX 440 ; N d ; B 102 -14 651 714 ;
+C 101 ; WX 340 ; N e ; B 87 -14 403 438 ;
+C 102 ; WX 320 ; N f ; B -119 -314 547 714 ; L i fi ; L l fl ;
+C 103 ; WX 400 ; N g ; B -108 -314 503 438 ;
+C 104 ; WX 440 ; N h ; B 55 -14 524 714 ;
+C 105 ; WX 240 ; N i ; B 100 -14 341 635 ;
+C 106 ; WX 220 ; N j ; B -112 -314 332 635 ;
+C 107 ; WX 440 ; N k ; B 87 -184 628 714 ;
+C 108 ; WX 240 ; N l ; B 102 -14 480 714 ;
+C 109 ; WX 620 ; N m ; B 86 -14 704 438 ;
+C 110 ; WX 460 ; N n ; B 101 -14 544 438 ;
+C 111 ; WX 400 ; N o ; B 87 -14 449 438 ;
+C 112 ; WX 440 ; N p ; B -23 -314 484 432 ;
+C 113 ; WX 400 ; N q ; B 87 -300 490 510 ;
+C 114 ; WX 300 ; N r ; B 101 -14 424 438 ;
+C 115 ; WX 320 ; N s ; B 46 -14 403 438 ;
+C 116 ; WX 320 ; N t ; B 106 -14 426 539 ;
+C 117 ; WX 460 ; N u ; B 102 -14 528 438 ;
+C 118 ; WX 440 ; N v ; B 87 -14 533 488 ;
+C 119 ; WX 680 ; N w ; B 87 -14 782 488 ;
+C 120 ; WX 420 ; N x ; B 70 -195 589 438 ;
+C 121 ; WX 400 ; N y ; B -24 -314 483 438 ;
+C 122 ; WX 440 ; N z ; B 26 -14 508 445 ;
+C 123 ; WX 240 ; N braceleft ; B 55 -207 383 655 ;
+C 124 ; WX 520 ; N bar ; B 320 -16 378 714 ;
+C 125 ; WX 240 ; N braceright ; B -10 -207 318 655 ;
+C 126 ; WX 520 ; N asciitilde ; B 123 186 539 320 ;
+C 161 ; WX 280 ; N exclamdown ; B 72 -186 306 438 ;
+C 162 ; WX 440 ; N cent ; B 122 -134 476 543 ;
+C 163 ; WX 440 ; N sterling ; B -16 -52 506 610 ;
+C 164 ; WX 60 ; N fraction ; B -181 -16 320 610 ;
+C 165 ; WX 440 ; N yen ; B -1 -168 613 647 ;
+C 166 ; WX 440 ; N florin ; B -64 -314 582 610 ;
+C 167 ; WX 420 ; N section ; B 53 -215 514 610 ;
+C 168 ; WX 440 ; N currency ; B 50 85 474 509 ;
+C 169 ; WX 160 ; N quotesingle ; B 145 343 215 610 ;
+C 170 ; WX 340 ; N quotedblleft ; B 169 343 464 610 ;
+C 171 ; WX 340 ; N guillemotleft ; B 98 24 356 414 ;
+C 172 ; WX 240 ; N guilsinglleft ; B 98 24 258 414 ;
+C 173 ; WX 260 ; N guilsinglright ; B 106 24 266 414 ;
+C 174 ; WX 520 ; N fi ; B -124 -314 605 714 ;
+C 175 ; WX 520 ; N fl ; B -124 -314 670 714 ;
+C 177 ; WX 500 ; N endash ; B 51 199 565 239 ;
+C 178 ; WX 460 ; N dagger ; B 138 -37 568 610 ;
+C 179 ; WX 480 ; N daggerdbl ; B 138 -59 533 610 ;
+C 180 ; WX 220 ; N periodcentered ; B 139 208 241 310 ;
+C 182 ; WX 500 ; N paragraph ; B 105 -199 638 594 ;
+C 183 ; WX 600 ; N bullet ; B 228 149 524 445 ;
+C 184 ; WX 180 ; N quotesinglbase ; B 21 -121 191 146 ;
+C 185 ; WX 280 ; N quotedblbase ; B -14 -121 281 146 ;
+C 186 ; WX 360 ; N quotedblright ; B 158 343 453 610 ;
+C 187 ; WX 380 ; N guillemotright ; B 117 24 375 414 ;
+C 188 ; WX 1000 ; N ellipsis ; B 124 -14 916 128 ;
+C 189 ; WX 960 ; N perthousand ; B 112 -160 1005 700 ;
+C 191 ; WX 400 ; N questiondown ; B 82 -186 387 438 ;
+C 193 ; WX 220 ; N grave ; B 193 492 339 659 ;
+C 194 ; WX 300 ; N acute ; B 265 492 422 659 ;
+C 195 ; WX 340 ; N circumflex ; B 223 482 443 649 ;
+C 196 ; WX 440 ; N tilde ; B 243 543 522 619 ;
+C 197 ; WX 440 ; N macron ; B 222 544 465 578 ;
+C 198 ; WX 440 ; N breve ; B 253 522 501 631 ;
+C 199 ; WX 220 ; N dotaccent ; B 236 522 328 610 ;
+C 200 ; WX 360 ; N dieresis ; B 243 522 469 610 ;
+C 202 ; WX 300 ; N ring ; B 240 483 416 659 ;
+C 203 ; WX 300 ; N cedilla ; B 12 -191 184 6 ;
+C 205 ; WX 400 ; N hungarumlaut ; B 208 492 495 659 ;
+C 206 ; WX 280 ; N ogonek ; B 38 -191 233 6 ;
+C 207 ; WX 340 ; N caron ; B 254 492 474 659 ;
+C 208 ; WX 1000 ; N emdash ; B 51 199 1065 239 ;
+C 225 ; WX 740 ; N AE ; B -21 -16 799 594 ;
+C 227 ; WX 260 ; N ordfeminine ; B 111 338 386 610 ;
+C 232 ; WX 580 ; N Lslash ; B 49 -16 657 610 ;
+C 233 ; WX 660 ; N Oslash ; B 83 -78 751 672 ;
+C 234 ; WX 820 ; N OE ; B 63 -16 909 610 ;
+C 235 ; WX 260 ; N ordmasculine ; B 128 339 373 610 ;
+C 241 ; WX 540 ; N ae ; B 67 -14 624 468 ;
+C 245 ; WX 240 ; N dotlessi ; B 100 -14 306 438 ;
+C 248 ; WX 300 ; N lslash ; B 121 -14 515 714 ;
+C 249 ; WX 440 ; N oslash ; B 46 -64 540 488 ;
+C 250 ; WX 560 ; N oe ; B 78 -14 628 438 ;
+C 251 ; WX 420 ; N germandbls ; B -127 -314 542 714 ;
+C -1 ; WX 340 ; N ecircumflex ; B 87 -14 433 649 ;
+C -1 ; WX 340 ; N edieresis ; B 87 -14 449 610 ;
+C -1 ; WX 420 ; N aacute ; B 92 -15 492 659 ;
+C -1 ; WX 740 ; N registered ; B 137 -16 763 610 ;
+C -1 ; WX 240 ; N icircumflex ; B 100 -14 363 649 ;
+C -1 ; WX 460 ; N udieresis ; B 102 -14 528 610 ;
+C -1 ; WX 400 ; N ograve ; B 87 -14 449 659 ;
+C -1 ; WX 460 ; N uacute ; B 102 -14 528 659 ;
+C -1 ; WX 460 ; N ucircumflex ; B 102 -14 528 649 ;
+C -1 ; WX 620 ; N Aacute ; B 13 -16 702 821 ;
+C -1 ; WX 240 ; N igrave ; B 100 -14 306 659 ;
+C -1 ; WX 380 ; N Icircumflex ; B 99 0 504 821 ;
+C -1 ; WX 340 ; N ccedilla ; B 62 -191 406 438 ;
+C -1 ; WX 420 ; N adieresis ; B 92 -15 485 610 ;
+C -1 ; WX 620 ; N Ecircumflex ; B 91 -12 709 821 ;
+C -1 ; WX 320 ; N scaron ; B 46 -14 464 659 ;
+C -1 ; WX 440 ; N thorn ; B -38 -314 505 714 ;
+C -1 ; WX 1000 ; N trademark ; B 127 187 1046 594 ;
+C -1 ; WX 340 ; N egrave ; B 87 -14 403 659 ;
+C -1 ; WX 264 ; N threesuperior ; B 59 234 348 610 ;
+C -1 ; WX 440 ; N zcaron ; B 26 -14 514 659 ;
+C -1 ; WX 420 ; N atilde ; B 92 -15 522 619 ;
+C -1 ; WX 420 ; N aring ; B 92 -15 485 659 ;
+C -1 ; WX 400 ; N ocircumflex ; B 87 -14 453 649 ;
+C -1 ; WX 620 ; N Edieresis ; B 91 -12 709 762 ;
+C -1 ; WX 660 ; N threequarters ; B 39 -16 706 610 ;
+C -1 ; WX 400 ; N ydieresis ; B -24 -314 483 610 ;
+C -1 ; WX 400 ; N yacute ; B -24 -314 483 659 ;
+C -1 ; WX 240 ; N iacute ; B 100 -14 392 659 ;
+C -1 ; WX 620 ; N Acircumflex ; B 13 -16 697 821 ;
+C -1 ; WX 740 ; N Uacute ; B 126 -16 792 821 ;
+C -1 ; WX 340 ; N eacute ; B 87 -14 462 659 ;
+C -1 ; WX 600 ; N Ograve ; B 94 -16 660 821 ;
+C -1 ; WX 420 ; N agrave ; B 92 -15 485 659 ;
+C -1 ; WX 740 ; N Udieresis ; B 126 -16 792 762 ;
+C -1 ; WX 420 ; N acircumflex ; B 92 -15 485 649 ;
+C -1 ; WX 380 ; N Igrave ; B 99 0 504 821 ;
+C -1 ; WX 264 ; N twosuperior ; B 72 234 354 610 ;
+C -1 ; WX 740 ; N Ugrave ; B 126 -16 792 821 ;
+C -1 ; WX 660 ; N onequarter ; B 56 -16 702 610 ;
+C -1 ; WX 740 ; N Ucircumflex ; B 126 -16 792 821 ;
+C -1 ; WX 460 ; N Scaron ; B 45 -81 594 831 ;
+C -1 ; WX 380 ; N Idieresis ; B 99 0 519 762 ;
+C -1 ; WX 240 ; N idieresis ; B 100 -14 369 610 ;
+C -1 ; WX 620 ; N Egrave ; B 91 -12 709 821 ;
+C -1 ; WX 600 ; N Oacute ; B 94 -16 660 821 ;
+C -1 ; WX 520 ; N divide ; B 117 -14 543 440 ;
+C -1 ; WX 620 ; N Atilde ; B 13 -16 702 771 ;
+C -1 ; WX 620 ; N Aring ; B 13 -16 697 831 ;
+C -1 ; WX 600 ; N Odieresis ; B 94 -16 660 762 ;
+C -1 ; WX 620 ; N Adieresis ; B 13 -16 709 762 ;
+C -1 ; WX 700 ; N Ntilde ; B 85 -168 915 761 ;
+C -1 ; WX 620 ; N Zcaron ; B 42 -19 669 831 ;
+C -1 ; WX 540 ; N Thorn ; B 52 0 647 623 ;
+C -1 ; WX 380 ; N Iacute ; B 99 0 532 821 ;
+C -1 ; WX 520 ; N plusminus ; B 117 0 543 436 ;
+C -1 ; WX 520 ; N multiply ; B 133 16 527 410 ;
+C -1 ; WX 620 ; N Eacute ; B 91 -12 709 821 ;
+C -1 ; WX 560 ; N Ydieresis ; B 41 -168 774 762 ;
+C -1 ; WX 264 ; N onesuperior ; B 83 244 311 610 ;
+C -1 ; WX 460 ; N ugrave ; B 102 -14 528 659 ;
+C -1 ; WX 520 ; N logicalnot ; B 117 86 543 340 ;
+C -1 ; WX 460 ; N ntilde ; B 101 -14 544 619 ;
+C -1 ; WX 600 ; N Otilde ; B 94 -16 660 761 ;
+C -1 ; WX 400 ; N otilde ; B 87 -14 502 619 ;
+C -1 ; WX 520 ; N Ccedilla ; B 93 -191 631 610 ;
+C -1 ; WX 620 ; N Agrave ; B 13 -16 697 821 ;
+C -1 ; WX 660 ; N onehalf ; B 56 -16 702 610 ;
+C -1 ; WX 700 ; N Eth ; B 86 -6 768 640 ;
+C -1 ; WX 400 ; N degree ; B 171 324 457 610 ;
+C -1 ; WX 560 ; N Yacute ; B 41 -168 774 821 ;
+C -1 ; WX 600 ; N Ocircumflex ; B 94 -16 660 821 ;
+C -1 ; WX 400 ; N oacute ; B 87 -14 482 659 ;
+C -1 ; WX 460 ; N mu ; B 7 -314 523 438 ;
+C -1 ; WX 520 ; N minus ; B 117 184 543 242 ;
+C -1 ; WX 400 ; N eth ; B 87 -14 522 714 ;
+C -1 ; WX 400 ; N odieresis ; B 87 -14 479 610 ;
+C -1 ; WX 740 ; N copyright ; B 137 -16 763 610 ;
+C -1 ; WX 520 ; N brokenbar ; B 320 -16 378 714 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 131
+
+KPX A quoteright -40
+KPX A quotedblright -40
+KPX A U -10
+KPX A T 10
+KPX A Q 10
+KPX A O 10
+KPX A G -30
+KPX A C 20
+
+KPX D period -30
+KPX D comma -20
+KPX D Y 10
+KPX D A -10
+
+KPX F period -40
+KPX F i 10
+KPX F comma -30
+
+KPX G period -20
+KPX G comma -10
+
+KPX J period -20
+KPX J comma -10
+
+KPX K u -20
+KPX K o -20
+KPX K e -20
+
+KPX L y -10
+KPX L quoteright -25
+KPX L quotedblright -25
+KPX L W -10
+KPX L V -20
+
+KPX O period -20
+KPX O comma -10
+KPX O Y 10
+KPX O T 20
+KPX O A -20
+
+KPX P period -50
+KPX P o -10
+KPX P e -10
+KPX P comma -40
+KPX P a -20
+KPX P A -10
+
+KPX Q U -10
+
+KPX R Y 10
+KPX R W 10
+KPX R T 20
+
+KPX T o -20
+KPX T i 20
+KPX T hyphen -20
+KPX T h 20
+KPX T e -20
+KPX T a -20
+KPX T O 30
+KPX T A 10
+
+KPX V period -100
+KPX V o -20
+KPX V e -20
+KPX V comma -90
+KPX V a -20
+KPX V O 10
+KPX V G -20
+
+KPX W period -50
+KPX W o -20
+KPX W i 10
+KPX W h 10
+KPX W e -20
+KPX W comma -40
+KPX W a -20
+KPX W O 10
+
+KPX Y u -20
+KPX Y period -50
+KPX Y o -50
+KPX Y i 10
+KPX Y e -40
+KPX Y comma -40
+KPX Y a -60
+
+KPX b period -30
+KPX b l -20
+KPX b comma -20
+KPX b b -20
+
+KPX c k -10
+
+KPX comma quoteright -70
+KPX comma quotedblright -70
+
+KPX d w -20
+KPX d v -10
+KPX d d -40
+
+KPX e y 10
+
+KPX f quoteright 30
+KPX f quotedblright 30
+KPX f period -50
+KPX f f -50
+KPX f e -10
+KPX f comma -40
+KPX f a -20
+
+KPX g y 10
+KPX g period -30
+KPX g i 10
+KPX g e 10
+KPX g comma -20
+KPX g a 10
+
+KPX k y 10
+KPX k o -10
+KPX k e -20
+
+KPX m y 10
+KPX m u 10
+
+KPX n y 20
+
+KPX o period -30
+KPX o comma -20
+
+KPX p period -30
+KPX p p -10
+KPX p comma -20
+
+KPX period quoteright -80
+KPX period quotedblright -80
+
+KPX quotedblleft quoteleft 20
+KPX quotedblleft A 10
+
+KPX quoteleft quoteleft -115
+KPX quoteleft A 10
+
+KPX quoteright v 30
+KPX quoteright t 20
+KPX quoteright s -25
+KPX quoteright r 30
+KPX quoteright quoteright -115
+KPX quoteright quotedblright 20
+KPX quoteright l 20
+
+KPX r period -50
+KPX r i 10
+KPX r comma -40
+
+KPX s period -20
+KPX s comma -10
+
+KPX v period -30
+KPX v comma -20
+
+KPX w period -30
+KPX w o 10
+KPX w h 20
+KPX w comma -20
+EndKernPairs
+EndKernData
+StartComposites 56
+CC Aacute 2 ; PCC A 0 0 ; PCC acute 280 162 ;
+CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 240 172 ;
+CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 240 152 ;
+CC Agrave 2 ; PCC A 0 0 ; PCC grave 250 162 ;
+CC Aring 2 ; PCC A 0 0 ; PCC ring 260 172 ;
+CC Atilde 2 ; PCC A 0 0 ; PCC tilde 180 152 ;
+CC Eacute 2 ; PCC E 0 0 ; PCC acute 230 162 ;
+CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex 180 172 ;
+CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 170 152 ;
+CC Egrave 2 ; PCC E 0 0 ; PCC grave 220 162 ;
+CC Iacute 2 ; PCC I 0 0 ; PCC acute 110 162 ;
+CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex 60 172 ;
+CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis 50 152 ;
+CC Igrave 2 ; PCC I 0 0 ; PCC grave 100 162 ;
+CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 210 142 ;
+CC Oacute 2 ; PCC O 0 0 ; PCC acute 160 162 ;
+CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 130 172 ;
+CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 120 152 ;
+CC Ograve 2 ; PCC O 0 0 ; PCC grave 150 162 ;
+CC Otilde 2 ; PCC O 0 0 ; PCC tilde 90 142 ;
+CC Scaron 2 ; PCC S 0 0 ; PCC caron 120 172 ;
+CC Uacute 2 ; PCC U 0 0 ; PCC acute 310 162 ;
+CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 260 172 ;
+CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 260 152 ;
+CC Ugrave 2 ; PCC U 0 0 ; PCC grave 270 162 ;
+CC Yacute 2 ; PCC Y 0 0 ; PCC acute 220 162 ;
+CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 170 152 ;
+CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 130 172 ;
+CC aacute 2 ; PCC a 0 0 ; PCC acute 70 0 ;
+CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 20 0 ;
+CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 10 0 ;
+CC agrave 2 ; PCC a 0 0 ; PCC grave 80 0 ;
+CC aring 2 ; PCC a 0 0 ; PCC ring 60 0 ;
+CC atilde 2 ; PCC a 0 0 ; PCC tilde 0 0 ;
+CC eacute 2 ; PCC e 0 0 ; PCC acute 40 0 ;
+CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex -10 0 ;
+CC edieresis 2 ; PCC e 0 0 ; PCC dieresis -20 0 ;
+CC egrave 2 ; PCC e 0 0 ; PCC grave 30 0 ;
+CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -30 0 ;
+CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -80 0 ;
+CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -100 0 ;
+CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -40 0 ;
+CC ntilde 2 ; PCC n 0 0 ; PCC tilde 10 0 ;
+CC oacute 2 ; PCC o 0 0 ; PCC acute 60 0 ;
+CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 10 0 ;
+CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 10 0 ;
+CC ograve 2 ; PCC o 0 0 ; PCC grave 60 0 ;
+CC otilde 2 ; PCC o 0 0 ; PCC tilde -20 0 ;
+CC scaron 2 ; PCC s 0 0 ; PCC caron -10 0 ;
+CC uacute 2 ; PCC u 0 0 ; PCC acute 70 0 ;
+CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 30 0 ;
+CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 20 0 ;
+CC ugrave 2 ; PCC u 0 0 ; PCC grave 50 0 ;
+CC yacute 2 ; PCC y 0 0 ; PCC acute 60 0 ;
+CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 0 0 ;
+CC zcaron 2 ; PCC z 0 0 ; PCC caron 40 0 ;
+EndComposites
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pzdr.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pzdr.afm
new file mode 100644
index 0000000000000000000000000000000000000000..6b98e8d35f1e257f797d51f51b60fd4f11562e06
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pzdr.afm
@@ -0,0 +1,222 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1985, 1987, 1988, 1989 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Fri Dec 1 12:57:42 1989
+Comment UniqueID 26200
+Comment VMusage 39281 49041
+FontName ZapfDingbats
+FullName ITC Zapf Dingbats
+FamilyName ITC Zapf Dingbats
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -1 -143 981 820
+UnderlinePosition -98
+UnderlineThickness 54
+Version 001.004
+Notice Copyright (c) 1985, 1987, 1988, 1989 Adobe Systems Incorporated. All rights reserved.ITC Zapf Dingbats is a registered trademark of International Typeface Corporation.
+EncodingScheme FontSpecific
+StartCharMetrics 202
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 974 ; N a1 ; B 35 72 939 621 ;
+C 34 ; WX 961 ; N a2 ; B 35 81 927 611 ;
+C 35 ; WX 974 ; N a202 ; B 35 72 939 621 ;
+C 36 ; WX 980 ; N a3 ; B 35 0 945 692 ;
+C 37 ; WX 719 ; N a4 ; B 34 139 685 566 ;
+C 38 ; WX 789 ; N a5 ; B 35 -14 755 705 ;
+C 39 ; WX 790 ; N a119 ; B 35 -14 755 705 ;
+C 40 ; WX 791 ; N a118 ; B 35 -13 761 705 ;
+C 41 ; WX 690 ; N a117 ; B 35 138 655 553 ;
+C 42 ; WX 960 ; N a11 ; B 35 123 925 568 ;
+C 43 ; WX 939 ; N a12 ; B 35 134 904 559 ;
+C 44 ; WX 549 ; N a13 ; B 29 -11 516 705 ;
+C 45 ; WX 855 ; N a14 ; B 34 59 820 632 ;
+C 46 ; WX 911 ; N a15 ; B 35 50 876 642 ;
+C 47 ; WX 933 ; N a16 ; B 35 139 899 550 ;
+C 48 ; WX 911 ; N a105 ; B 35 50 876 642 ;
+C 49 ; WX 945 ; N a17 ; B 35 139 909 553 ;
+C 50 ; WX 974 ; N a18 ; B 35 104 938 587 ;
+C 51 ; WX 755 ; N a19 ; B 34 -13 721 705 ;
+C 52 ; WX 846 ; N a20 ; B 36 -14 811 705 ;
+C 53 ; WX 762 ; N a21 ; B 35 0 727 692 ;
+C 54 ; WX 761 ; N a22 ; B 35 0 727 692 ;
+C 55 ; WX 571 ; N a23 ; B -1 -68 571 661 ;
+C 56 ; WX 677 ; N a24 ; B 36 -13 642 705 ;
+C 57 ; WX 763 ; N a25 ; B 35 0 728 692 ;
+C 58 ; WX 760 ; N a26 ; B 35 0 726 692 ;
+C 59 ; WX 759 ; N a27 ; B 35 0 725 692 ;
+C 60 ; WX 754 ; N a28 ; B 35 0 720 692 ;
+C 61 ; WX 494 ; N a6 ; B 35 0 460 692 ;
+C 62 ; WX 552 ; N a7 ; B 35 0 517 692 ;
+C 63 ; WX 537 ; N a8 ; B 35 0 503 692 ;
+C 64 ; WX 577 ; N a9 ; B 35 96 542 596 ;
+C 65 ; WX 692 ; N a10 ; B 35 -14 657 705 ;
+C 66 ; WX 786 ; N a29 ; B 35 -14 751 705 ;
+C 67 ; WX 788 ; N a30 ; B 35 -14 752 705 ;
+C 68 ; WX 788 ; N a31 ; B 35 -14 753 705 ;
+C 69 ; WX 790 ; N a32 ; B 35 -14 756 705 ;
+C 70 ; WX 793 ; N a33 ; B 35 -13 759 705 ;
+C 71 ; WX 794 ; N a34 ; B 35 -13 759 705 ;
+C 72 ; WX 816 ; N a35 ; B 35 -14 782 705 ;
+C 73 ; WX 823 ; N a36 ; B 35 -14 787 705 ;
+C 74 ; WX 789 ; N a37 ; B 35 -14 754 705 ;
+C 75 ; WX 841 ; N a38 ; B 35 -14 807 705 ;
+C 76 ; WX 823 ; N a39 ; B 35 -14 789 705 ;
+C 77 ; WX 833 ; N a40 ; B 35 -14 798 705 ;
+C 78 ; WX 816 ; N a41 ; B 35 -13 782 705 ;
+C 79 ; WX 831 ; N a42 ; B 35 -14 796 705 ;
+C 80 ; WX 923 ; N a43 ; B 35 -14 888 705 ;
+C 81 ; WX 744 ; N a44 ; B 35 0 710 692 ;
+C 82 ; WX 723 ; N a45 ; B 35 0 688 692 ;
+C 83 ; WX 749 ; N a46 ; B 35 0 714 692 ;
+C 84 ; WX 790 ; N a47 ; B 34 -14 756 705 ;
+C 85 ; WX 792 ; N a48 ; B 35 -14 758 705 ;
+C 86 ; WX 695 ; N a49 ; B 35 -14 661 706 ;
+C 87 ; WX 776 ; N a50 ; B 35 -6 741 699 ;
+C 88 ; WX 768 ; N a51 ; B 35 -7 734 699 ;
+C 89 ; WX 792 ; N a52 ; B 35 -14 757 705 ;
+C 90 ; WX 759 ; N a53 ; B 35 0 725 692 ;
+C 91 ; WX 707 ; N a54 ; B 35 -13 672 704 ;
+C 92 ; WX 708 ; N a55 ; B 35 -14 672 705 ;
+C 93 ; WX 682 ; N a56 ; B 35 -14 647 705 ;
+C 94 ; WX 701 ; N a57 ; B 35 -14 666 705 ;
+C 95 ; WX 826 ; N a58 ; B 35 -14 791 705 ;
+C 96 ; WX 815 ; N a59 ; B 35 -14 780 705 ;
+C 97 ; WX 789 ; N a60 ; B 35 -14 754 705 ;
+C 98 ; WX 789 ; N a61 ; B 35 -14 754 705 ;
+C 99 ; WX 707 ; N a62 ; B 34 -14 673 705 ;
+C 100 ; WX 687 ; N a63 ; B 36 0 651 692 ;
+C 101 ; WX 696 ; N a64 ; B 35 0 661 691 ;
+C 102 ; WX 689 ; N a65 ; B 35 0 655 692 ;
+C 103 ; WX 786 ; N a66 ; B 34 -14 751 705 ;
+C 104 ; WX 787 ; N a67 ; B 35 -14 752 705 ;
+C 105 ; WX 713 ; N a68 ; B 35 -14 678 705 ;
+C 106 ; WX 791 ; N a69 ; B 35 -14 756 705 ;
+C 107 ; WX 785 ; N a70 ; B 36 -14 751 705 ;
+C 108 ; WX 791 ; N a71 ; B 35 -14 757 705 ;
+C 109 ; WX 873 ; N a72 ; B 35 -14 838 705 ;
+C 110 ; WX 761 ; N a73 ; B 35 0 726 692 ;
+C 111 ; WX 762 ; N a74 ; B 35 0 727 692 ;
+C 112 ; WX 762 ; N a203 ; B 35 0 727 692 ;
+C 113 ; WX 759 ; N a75 ; B 35 0 725 692 ;
+C 114 ; WX 759 ; N a204 ; B 35 0 725 692 ;
+C 115 ; WX 892 ; N a76 ; B 35 0 858 705 ;
+C 116 ; WX 892 ; N a77 ; B 35 -14 858 692 ;
+C 117 ; WX 788 ; N a78 ; B 35 -14 754 705 ;
+C 118 ; WX 784 ; N a79 ; B 35 -14 749 705 ;
+C 119 ; WX 438 ; N a81 ; B 35 -14 403 705 ;
+C 120 ; WX 138 ; N a82 ; B 35 0 104 692 ;
+C 121 ; WX 277 ; N a83 ; B 35 0 242 692 ;
+C 122 ; WX 415 ; N a84 ; B 35 0 380 692 ;
+C 123 ; WX 392 ; N a97 ; B 35 263 357 705 ;
+C 124 ; WX 392 ; N a98 ; B 34 263 357 705 ;
+C 125 ; WX 668 ; N a99 ; B 35 263 633 705 ;
+C 126 ; WX 668 ; N a100 ; B 36 263 634 705 ;
+C 161 ; WX 732 ; N a101 ; B 35 -143 697 806 ;
+C 162 ; WX 544 ; N a102 ; B 56 -14 488 706 ;
+C 163 ; WX 544 ; N a103 ; B 34 -14 508 705 ;
+C 164 ; WX 910 ; N a104 ; B 35 40 875 651 ;
+C 165 ; WX 667 ; N a106 ; B 35 -14 633 705 ;
+C 166 ; WX 760 ; N a107 ; B 35 -14 726 705 ;
+C 167 ; WX 760 ; N a108 ; B 0 121 758 569 ;
+C 168 ; WX 776 ; N a112 ; B 35 0 741 705 ;
+C 169 ; WX 595 ; N a111 ; B 34 -14 560 705 ;
+C 170 ; WX 694 ; N a110 ; B 35 -14 659 705 ;
+C 171 ; WX 626 ; N a109 ; B 34 0 591 705 ;
+C 172 ; WX 788 ; N a120 ; B 35 -14 754 705 ;
+C 173 ; WX 788 ; N a121 ; B 35 -14 754 705 ;
+C 174 ; WX 788 ; N a122 ; B 35 -14 754 705 ;
+C 175 ; WX 788 ; N a123 ; B 35 -14 754 705 ;
+C 176 ; WX 788 ; N a124 ; B 35 -14 754 705 ;
+C 177 ; WX 788 ; N a125 ; B 35 -14 754 705 ;
+C 178 ; WX 788 ; N a126 ; B 35 -14 754 705 ;
+C 179 ; WX 788 ; N a127 ; B 35 -14 754 705 ;
+C 180 ; WX 788 ; N a128 ; B 35 -14 754 705 ;
+C 181 ; WX 788 ; N a129 ; B 35 -14 754 705 ;
+C 182 ; WX 788 ; N a130 ; B 35 -14 754 705 ;
+C 183 ; WX 788 ; N a131 ; B 35 -14 754 705 ;
+C 184 ; WX 788 ; N a132 ; B 35 -14 754 705 ;
+C 185 ; WX 788 ; N a133 ; B 35 -14 754 705 ;
+C 186 ; WX 788 ; N a134 ; B 35 -14 754 705 ;
+C 187 ; WX 788 ; N a135 ; B 35 -14 754 705 ;
+C 188 ; WX 788 ; N a136 ; B 35 -14 754 705 ;
+C 189 ; WX 788 ; N a137 ; B 35 -14 754 705 ;
+C 190 ; WX 788 ; N a138 ; B 35 -14 754 705 ;
+C 191 ; WX 788 ; N a139 ; B 35 -14 754 705 ;
+C 192 ; WX 788 ; N a140 ; B 35 -14 754 705 ;
+C 193 ; WX 788 ; N a141 ; B 35 -14 754 705 ;
+C 194 ; WX 788 ; N a142 ; B 35 -14 754 705 ;
+C 195 ; WX 788 ; N a143 ; B 35 -14 754 705 ;
+C 196 ; WX 788 ; N a144 ; B 35 -14 754 705 ;
+C 197 ; WX 788 ; N a145 ; B 35 -14 754 705 ;
+C 198 ; WX 788 ; N a146 ; B 35 -14 754 705 ;
+C 199 ; WX 788 ; N a147 ; B 35 -14 754 705 ;
+C 200 ; WX 788 ; N a148 ; B 35 -14 754 705 ;
+C 201 ; WX 788 ; N a149 ; B 35 -14 754 705 ;
+C 202 ; WX 788 ; N a150 ; B 35 -14 754 705 ;
+C 203 ; WX 788 ; N a151 ; B 35 -14 754 705 ;
+C 204 ; WX 788 ; N a152 ; B 35 -14 754 705 ;
+C 205 ; WX 788 ; N a153 ; B 35 -14 754 705 ;
+C 206 ; WX 788 ; N a154 ; B 35 -14 754 705 ;
+C 207 ; WX 788 ; N a155 ; B 35 -14 754 705 ;
+C 208 ; WX 788 ; N a156 ; B 35 -14 754 705 ;
+C 209 ; WX 788 ; N a157 ; B 35 -14 754 705 ;
+C 210 ; WX 788 ; N a158 ; B 35 -14 754 705 ;
+C 211 ; WX 788 ; N a159 ; B 35 -14 754 705 ;
+C 212 ; WX 894 ; N a160 ; B 35 58 860 634 ;
+C 213 ; WX 838 ; N a161 ; B 35 152 803 540 ;
+C 214 ; WX 1016 ; N a163 ; B 34 152 981 540 ;
+C 215 ; WX 458 ; N a164 ; B 35 -127 422 820 ;
+C 216 ; WX 748 ; N a196 ; B 35 94 698 597 ;
+C 217 ; WX 924 ; N a165 ; B 35 140 890 552 ;
+C 218 ; WX 748 ; N a192 ; B 35 94 698 597 ;
+C 219 ; WX 918 ; N a166 ; B 35 166 884 526 ;
+C 220 ; WX 927 ; N a167 ; B 35 32 892 660 ;
+C 221 ; WX 928 ; N a168 ; B 35 129 891 562 ;
+C 222 ; WX 928 ; N a169 ; B 35 128 893 563 ;
+C 223 ; WX 834 ; N a170 ; B 35 155 799 537 ;
+C 224 ; WX 873 ; N a171 ; B 35 93 838 599 ;
+C 225 ; WX 828 ; N a172 ; B 35 104 791 588 ;
+C 226 ; WX 924 ; N a173 ; B 35 98 889 594 ;
+C 227 ; WX 924 ; N a162 ; B 35 98 889 594 ;
+C 228 ; WX 917 ; N a174 ; B 35 0 882 692 ;
+C 229 ; WX 930 ; N a175 ; B 35 84 896 608 ;
+C 230 ; WX 931 ; N a176 ; B 35 84 896 608 ;
+C 231 ; WX 463 ; N a177 ; B 35 -99 429 791 ;
+C 232 ; WX 883 ; N a178 ; B 35 71 848 623 ;
+C 233 ; WX 836 ; N a179 ; B 35 44 802 648 ;
+C 234 ; WX 836 ; N a193 ; B 35 44 802 648 ;
+C 235 ; WX 867 ; N a180 ; B 35 101 832 591 ;
+C 236 ; WX 867 ; N a199 ; B 35 101 832 591 ;
+C 237 ; WX 696 ; N a181 ; B 35 44 661 648 ;
+C 238 ; WX 696 ; N a200 ; B 35 44 661 648 ;
+C 239 ; WX 874 ; N a182 ; B 35 77 840 619 ;
+C 241 ; WX 874 ; N a201 ; B 35 73 840 615 ;
+C 242 ; WX 760 ; N a183 ; B 35 0 725 692 ;
+C 243 ; WX 946 ; N a184 ; B 35 160 911 533 ;
+C 244 ; WX 771 ; N a197 ; B 34 37 736 655 ;
+C 245 ; WX 865 ; N a185 ; B 35 207 830 481 ;
+C 246 ; WX 771 ; N a194 ; B 34 37 736 655 ;
+C 247 ; WX 888 ; N a198 ; B 34 -19 853 712 ;
+C 248 ; WX 967 ; N a186 ; B 35 124 932 568 ;
+C 249 ; WX 888 ; N a195 ; B 34 -19 853 712 ;
+C 250 ; WX 831 ; N a187 ; B 35 113 796 579 ;
+C 251 ; WX 873 ; N a188 ; B 36 118 838 578 ;
+C 252 ; WX 927 ; N a189 ; B 35 150 891 542 ;
+C 253 ; WX 970 ; N a190 ; B 35 76 931 616 ;
+C 254 ; WX 918 ; N a191 ; B 34 99 884 593 ;
+C -1 ; WX 410 ; N a86 ; B 35 0 375 692 ;
+C -1 ; WX 509 ; N a85 ; B 35 0 475 692 ;
+C -1 ; WX 334 ; N a95 ; B 35 0 299 692 ;
+C -1 ; WX 509 ; N a205 ; B 35 0 475 692 ;
+C -1 ; WX 390 ; N a89 ; B 35 -14 356 705 ;
+C -1 ; WX 234 ; N a87 ; B 35 -14 199 705 ;
+C -1 ; WX 276 ; N a91 ; B 35 0 242 692 ;
+C -1 ; WX 390 ; N a90 ; B 35 -14 355 705 ;
+C -1 ; WX 410 ; N a206 ; B 35 0 375 692 ;
+C -1 ; WX 317 ; N a94 ; B 35 0 283 692 ;
+C -1 ; WX 317 ; N a93 ; B 35 0 283 692 ;
+C -1 ; WX 276 ; N a92 ; B 35 0 242 692 ;
+C -1 ; WX 334 ; N a96 ; B 35 0 299 692 ;
+C -1 ; WX 234 ; N a88 ; B 35 -14 199 705 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Bold.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Bold.afm
new file mode 100644
index 0000000000000000000000000000000000000000..eb80542b11fa911243728a2ee962fa430668c64b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Bold.afm
@@ -0,0 +1,342 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Jun 23 16:28:00 1997
+Comment UniqueID 43048
+Comment VMusage 41139 52164
+FontName Courier-Bold
+FullName Courier Bold
+FamilyName Courier
+Weight Bold
+ItalicAngle 0
+IsFixedPitch true
+CharacterSet ExtendedRoman
+FontBBox -113 -250 749 801
+UnderlinePosition -100
+UnderlineThickness 50
+Version 003.000
+Notice Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 562
+XHeight 439
+Ascender 629
+Descender -157
+StdHW 84
+StdVW 106
+StartCharMetrics 315
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 202 -15 398 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 135 277 465 562 ;
+C 35 ; WX 600 ; N numbersign ; B 56 -45 544 651 ;
+C 36 ; WX 600 ; N dollar ; B 82 -126 519 666 ;
+C 37 ; WX 600 ; N percent ; B 5 -15 595 616 ;
+C 38 ; WX 600 ; N ampersand ; B 36 -15 546 543 ;
+C 39 ; WX 600 ; N quoteright ; B 171 277 423 562 ;
+C 40 ; WX 600 ; N parenleft ; B 219 -102 461 616 ;
+C 41 ; WX 600 ; N parenright ; B 139 -102 381 616 ;
+C 42 ; WX 600 ; N asterisk ; B 91 219 509 601 ;
+C 43 ; WX 600 ; N plus ; B 71 39 529 478 ;
+C 44 ; WX 600 ; N comma ; B 123 -111 393 174 ;
+C 45 ; WX 600 ; N hyphen ; B 100 203 500 313 ;
+C 46 ; WX 600 ; N period ; B 192 -15 408 171 ;
+C 47 ; WX 600 ; N slash ; B 98 -77 502 626 ;
+C 48 ; WX 600 ; N zero ; B 87 -15 513 616 ;
+C 49 ; WX 600 ; N one ; B 81 0 539 616 ;
+C 50 ; WX 600 ; N two ; B 61 0 499 616 ;
+C 51 ; WX 600 ; N three ; B 63 -15 501 616 ;
+C 52 ; WX 600 ; N four ; B 53 0 507 616 ;
+C 53 ; WX 600 ; N five ; B 70 -15 521 601 ;
+C 54 ; WX 600 ; N six ; B 90 -15 521 616 ;
+C 55 ; WX 600 ; N seven ; B 55 0 494 601 ;
+C 56 ; WX 600 ; N eight ; B 83 -15 517 616 ;
+C 57 ; WX 600 ; N nine ; B 79 -15 510 616 ;
+C 58 ; WX 600 ; N colon ; B 191 -15 407 425 ;
+C 59 ; WX 600 ; N semicolon ; B 123 -111 408 425 ;
+C 60 ; WX 600 ; N less ; B 66 15 523 501 ;
+C 61 ; WX 600 ; N equal ; B 71 118 529 398 ;
+C 62 ; WX 600 ; N greater ; B 77 15 534 501 ;
+C 63 ; WX 600 ; N question ; B 98 -14 501 580 ;
+C 64 ; WX 600 ; N at ; B 16 -15 584 616 ;
+C 65 ; WX 600 ; N A ; B -9 0 609 562 ;
+C 66 ; WX 600 ; N B ; B 30 0 573 562 ;
+C 67 ; WX 600 ; N C ; B 22 -18 560 580 ;
+C 68 ; WX 600 ; N D ; B 30 0 594 562 ;
+C 69 ; WX 600 ; N E ; B 25 0 560 562 ;
+C 70 ; WX 600 ; N F ; B 39 0 570 562 ;
+C 71 ; WX 600 ; N G ; B 22 -18 594 580 ;
+C 72 ; WX 600 ; N H ; B 20 0 580 562 ;
+C 73 ; WX 600 ; N I ; B 77 0 523 562 ;
+C 74 ; WX 600 ; N J ; B 37 -18 601 562 ;
+C 75 ; WX 600 ; N K ; B 21 0 599 562 ;
+C 76 ; WX 600 ; N L ; B 39 0 578 562 ;
+C 77 ; WX 600 ; N M ; B -2 0 602 562 ;
+C 78 ; WX 600 ; N N ; B 8 -12 610 562 ;
+C 79 ; WX 600 ; N O ; B 22 -18 578 580 ;
+C 80 ; WX 600 ; N P ; B 48 0 559 562 ;
+C 81 ; WX 600 ; N Q ; B 32 -138 578 580 ;
+C 82 ; WX 600 ; N R ; B 24 0 599 562 ;
+C 83 ; WX 600 ; N S ; B 47 -22 553 582 ;
+C 84 ; WX 600 ; N T ; B 21 0 579 562 ;
+C 85 ; WX 600 ; N U ; B 4 -18 596 562 ;
+C 86 ; WX 600 ; N V ; B -13 0 613 562 ;
+C 87 ; WX 600 ; N W ; B -18 0 618 562 ;
+C 88 ; WX 600 ; N X ; B 12 0 588 562 ;
+C 89 ; WX 600 ; N Y ; B 12 0 589 562 ;
+C 90 ; WX 600 ; N Z ; B 62 0 539 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 245 -102 475 616 ;
+C 92 ; WX 600 ; N backslash ; B 99 -77 503 626 ;
+C 93 ; WX 600 ; N bracketright ; B 125 -102 355 616 ;
+C 94 ; WX 600 ; N asciicircum ; B 108 250 492 616 ;
+C 95 ; WX 600 ; N underscore ; B 0 -125 600 -75 ;
+C 96 ; WX 600 ; N quoteleft ; B 178 277 428 562 ;
+C 97 ; WX 600 ; N a ; B 35 -15 570 454 ;
+C 98 ; WX 600 ; N b ; B 0 -15 584 626 ;
+C 99 ; WX 600 ; N c ; B 40 -15 545 459 ;
+C 100 ; WX 600 ; N d ; B 20 -15 591 626 ;
+C 101 ; WX 600 ; N e ; B 40 -15 563 454 ;
+C 102 ; WX 600 ; N f ; B 83 0 547 626 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 30 -146 580 454 ;
+C 104 ; WX 600 ; N h ; B 5 0 592 626 ;
+C 105 ; WX 600 ; N i ; B 77 0 523 658 ;
+C 106 ; WX 600 ; N j ; B 63 -146 440 658 ;
+C 107 ; WX 600 ; N k ; B 20 0 585 626 ;
+C 108 ; WX 600 ; N l ; B 77 0 523 626 ;
+C 109 ; WX 600 ; N m ; B -22 0 626 454 ;
+C 110 ; WX 600 ; N n ; B 18 0 592 454 ;
+C 111 ; WX 600 ; N o ; B 30 -15 570 454 ;
+C 112 ; WX 600 ; N p ; B -1 -142 570 454 ;
+C 113 ; WX 600 ; N q ; B 20 -142 591 454 ;
+C 114 ; WX 600 ; N r ; B 47 0 580 454 ;
+C 115 ; WX 600 ; N s ; B 68 -17 535 459 ;
+C 116 ; WX 600 ; N t ; B 47 -15 532 562 ;
+C 117 ; WX 600 ; N u ; B -1 -15 569 439 ;
+C 118 ; WX 600 ; N v ; B -1 0 601 439 ;
+C 119 ; WX 600 ; N w ; B -18 0 618 439 ;
+C 120 ; WX 600 ; N x ; B 6 0 594 439 ;
+C 121 ; WX 600 ; N y ; B -4 -142 601 439 ;
+C 122 ; WX 600 ; N z ; B 81 0 520 439 ;
+C 123 ; WX 600 ; N braceleft ; B 160 -102 464 616 ;
+C 124 ; WX 600 ; N bar ; B 255 -250 345 750 ;
+C 125 ; WX 600 ; N braceright ; B 136 -102 440 616 ;
+C 126 ; WX 600 ; N asciitilde ; B 71 153 530 356 ;
+C 161 ; WX 600 ; N exclamdown ; B 202 -146 398 449 ;
+C 162 ; WX 600 ; N cent ; B 66 -49 518 614 ;
+C 163 ; WX 600 ; N sterling ; B 72 -28 558 611 ;
+C 164 ; WX 600 ; N fraction ; B 25 -60 576 661 ;
+C 165 ; WX 600 ; N yen ; B 10 0 590 562 ;
+C 166 ; WX 600 ; N florin ; B -30 -131 572 616 ;
+C 167 ; WX 600 ; N section ; B 83 -70 517 580 ;
+C 168 ; WX 600 ; N currency ; B 54 49 546 517 ;
+C 169 ; WX 600 ; N quotesingle ; B 227 277 373 562 ;
+C 170 ; WX 600 ; N quotedblleft ; B 71 277 535 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 8 70 553 446 ;
+C 172 ; WX 600 ; N guilsinglleft ; B 141 70 459 446 ;
+C 173 ; WX 600 ; N guilsinglright ; B 141 70 459 446 ;
+C 174 ; WX 600 ; N fi ; B 12 0 593 626 ;
+C 175 ; WX 600 ; N fl ; B 12 0 593 626 ;
+C 177 ; WX 600 ; N endash ; B 65 203 535 313 ;
+C 178 ; WX 600 ; N dagger ; B 106 -70 494 580 ;
+C 179 ; WX 600 ; N daggerdbl ; B 106 -70 494 580 ;
+C 180 ; WX 600 ; N periodcentered ; B 196 165 404 351 ;
+C 182 ; WX 600 ; N paragraph ; B 6 -70 576 580 ;
+C 183 ; WX 600 ; N bullet ; B 140 132 460 430 ;
+C 184 ; WX 600 ; N quotesinglbase ; B 175 -142 427 143 ;
+C 185 ; WX 600 ; N quotedblbase ; B 65 -142 529 143 ;
+C 186 ; WX 600 ; N quotedblright ; B 61 277 525 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 47 70 592 446 ;
+C 188 ; WX 600 ; N ellipsis ; B 26 -15 574 116 ;
+C 189 ; WX 600 ; N perthousand ; B -113 -15 713 616 ;
+C 191 ; WX 600 ; N questiondown ; B 99 -146 502 449 ;
+C 193 ; WX 600 ; N grave ; B 132 508 395 661 ;
+C 194 ; WX 600 ; N acute ; B 205 508 468 661 ;
+C 195 ; WX 600 ; N circumflex ; B 103 483 497 657 ;
+C 196 ; WX 600 ; N tilde ; B 89 493 512 636 ;
+C 197 ; WX 600 ; N macron ; B 88 505 512 585 ;
+C 198 ; WX 600 ; N breve ; B 83 468 517 631 ;
+C 199 ; WX 600 ; N dotaccent ; B 230 498 370 638 ;
+C 200 ; WX 600 ; N dieresis ; B 128 498 472 638 ;
+C 202 ; WX 600 ; N ring ; B 198 481 402 678 ;
+C 203 ; WX 600 ; N cedilla ; B 205 -206 387 0 ;
+C 205 ; WX 600 ; N hungarumlaut ; B 68 488 588 661 ;
+C 206 ; WX 600 ; N ogonek ; B 169 -199 400 0 ;
+C 207 ; WX 600 ; N caron ; B 103 493 497 667 ;
+C 208 ; WX 600 ; N emdash ; B -10 203 610 313 ;
+C 225 ; WX 600 ; N AE ; B -29 0 602 562 ;
+C 227 ; WX 600 ; N ordfeminine ; B 147 196 453 580 ;
+C 232 ; WX 600 ; N Lslash ; B 39 0 578 562 ;
+C 233 ; WX 600 ; N Oslash ; B 22 -22 578 584 ;
+C 234 ; WX 600 ; N OE ; B -25 0 595 562 ;
+C 235 ; WX 600 ; N ordmasculine ; B 147 196 453 580 ;
+C 241 ; WX 600 ; N ae ; B -4 -15 601 454 ;
+C 245 ; WX 600 ; N dotlessi ; B 77 0 523 439 ;
+C 248 ; WX 600 ; N lslash ; B 77 0 523 626 ;
+C 249 ; WX 600 ; N oslash ; B 30 -24 570 463 ;
+C 250 ; WX 600 ; N oe ; B -18 -15 611 454 ;
+C 251 ; WX 600 ; N germandbls ; B 22 -15 596 626 ;
+C -1 ; WX 600 ; N Idieresis ; B 77 0 523 761 ;
+C -1 ; WX 600 ; N eacute ; B 40 -15 563 661 ;
+C -1 ; WX 600 ; N abreve ; B 35 -15 570 661 ;
+C -1 ; WX 600 ; N uhungarumlaut ; B -1 -15 628 661 ;
+C -1 ; WX 600 ; N ecaron ; B 40 -15 563 667 ;
+C -1 ; WX 600 ; N Ydieresis ; B 12 0 589 761 ;
+C -1 ; WX 600 ; N divide ; B 71 16 529 500 ;
+C -1 ; WX 600 ; N Yacute ; B 12 0 589 784 ;
+C -1 ; WX 600 ; N Acircumflex ; B -9 0 609 780 ;
+C -1 ; WX 600 ; N aacute ; B 35 -15 570 661 ;
+C -1 ; WX 600 ; N Ucircumflex ; B 4 -18 596 780 ;
+C -1 ; WX 600 ; N yacute ; B -4 -142 601 661 ;
+C -1 ; WX 600 ; N scommaaccent ; B 68 -250 535 459 ;
+C -1 ; WX 600 ; N ecircumflex ; B 40 -15 563 657 ;
+C -1 ; WX 600 ; N Uring ; B 4 -18 596 801 ;
+C -1 ; WX 600 ; N Udieresis ; B 4 -18 596 761 ;
+C -1 ; WX 600 ; N aogonek ; B 35 -199 586 454 ;
+C -1 ; WX 600 ; N Uacute ; B 4 -18 596 784 ;
+C -1 ; WX 600 ; N uogonek ; B -1 -199 585 439 ;
+C -1 ; WX 600 ; N Edieresis ; B 25 0 560 761 ;
+C -1 ; WX 600 ; N Dcroat ; B 30 0 594 562 ;
+C -1 ; WX 600 ; N commaaccent ; B 205 -250 397 -57 ;
+C -1 ; WX 600 ; N copyright ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N Emacron ; B 25 0 560 708 ;
+C -1 ; WX 600 ; N ccaron ; B 40 -15 545 667 ;
+C -1 ; WX 600 ; N aring ; B 35 -15 570 678 ;
+C -1 ; WX 600 ; N Ncommaaccent ; B 8 -250 610 562 ;
+C -1 ; WX 600 ; N lacute ; B 77 0 523 801 ;
+C -1 ; WX 600 ; N agrave ; B 35 -15 570 661 ;
+C -1 ; WX 600 ; N Tcommaaccent ; B 21 -250 579 562 ;
+C -1 ; WX 600 ; N Cacute ; B 22 -18 560 784 ;
+C -1 ; WX 600 ; N atilde ; B 35 -15 570 636 ;
+C -1 ; WX 600 ; N Edotaccent ; B 25 0 560 761 ;
+C -1 ; WX 600 ; N scaron ; B 68 -17 535 667 ;
+C -1 ; WX 600 ; N scedilla ; B 68 -206 535 459 ;
+C -1 ; WX 600 ; N iacute ; B 77 0 523 661 ;
+C -1 ; WX 600 ; N lozenge ; B 66 0 534 740 ;
+C -1 ; WX 600 ; N Rcaron ; B 24 0 599 790 ;
+C -1 ; WX 600 ; N Gcommaaccent ; B 22 -250 594 580 ;
+C -1 ; WX 600 ; N ucircumflex ; B -1 -15 569 657 ;
+C -1 ; WX 600 ; N acircumflex ; B 35 -15 570 657 ;
+C -1 ; WX 600 ; N Amacron ; B -9 0 609 708 ;
+C -1 ; WX 600 ; N rcaron ; B 47 0 580 667 ;
+C -1 ; WX 600 ; N ccedilla ; B 40 -206 545 459 ;
+C -1 ; WX 600 ; N Zdotaccent ; B 62 0 539 761 ;
+C -1 ; WX 600 ; N Thorn ; B 48 0 557 562 ;
+C -1 ; WX 600 ; N Omacron ; B 22 -18 578 708 ;
+C -1 ; WX 600 ; N Racute ; B 24 0 599 784 ;
+C -1 ; WX 600 ; N Sacute ; B 47 -22 553 784 ;
+C -1 ; WX 600 ; N dcaron ; B 20 -15 727 626 ;
+C -1 ; WX 600 ; N Umacron ; B 4 -18 596 708 ;
+C -1 ; WX 600 ; N uring ; B -1 -15 569 678 ;
+C -1 ; WX 600 ; N threesuperior ; B 138 222 433 616 ;
+C -1 ; WX 600 ; N Ograve ; B 22 -18 578 784 ;
+C -1 ; WX 600 ; N Agrave ; B -9 0 609 784 ;
+C -1 ; WX 600 ; N Abreve ; B -9 0 609 784 ;
+C -1 ; WX 600 ; N multiply ; B 81 39 520 478 ;
+C -1 ; WX 600 ; N uacute ; B -1 -15 569 661 ;
+C -1 ; WX 600 ; N Tcaron ; B 21 0 579 790 ;
+C -1 ; WX 600 ; N partialdiff ; B 63 -38 537 728 ;
+C -1 ; WX 600 ; N ydieresis ; B -4 -142 601 638 ;
+C -1 ; WX 600 ; N Nacute ; B 8 -12 610 784 ;
+C -1 ; WX 600 ; N icircumflex ; B 73 0 523 657 ;
+C -1 ; WX 600 ; N Ecircumflex ; B 25 0 560 780 ;
+C -1 ; WX 600 ; N adieresis ; B 35 -15 570 638 ;
+C -1 ; WX 600 ; N edieresis ; B 40 -15 563 638 ;
+C -1 ; WX 600 ; N cacute ; B 40 -15 545 661 ;
+C -1 ; WX 600 ; N nacute ; B 18 0 592 661 ;
+C -1 ; WX 600 ; N umacron ; B -1 -15 569 585 ;
+C -1 ; WX 600 ; N Ncaron ; B 8 -12 610 790 ;
+C -1 ; WX 600 ; N Iacute ; B 77 0 523 784 ;
+C -1 ; WX 600 ; N plusminus ; B 71 24 529 515 ;
+C -1 ; WX 600 ; N brokenbar ; B 255 -175 345 675 ;
+C -1 ; WX 600 ; N registered ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N Gbreve ; B 22 -18 594 784 ;
+C -1 ; WX 600 ; N Idotaccent ; B 77 0 523 761 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 586 706 ;
+C -1 ; WX 600 ; N Egrave ; B 25 0 560 784 ;
+C -1 ; WX 600 ; N racute ; B 47 0 580 661 ;
+C -1 ; WX 600 ; N omacron ; B 30 -15 570 585 ;
+C -1 ; WX 600 ; N Zacute ; B 62 0 539 784 ;
+C -1 ; WX 600 ; N Zcaron ; B 62 0 539 790 ;
+C -1 ; WX 600 ; N greaterequal ; B 26 0 523 696 ;
+C -1 ; WX 600 ; N Eth ; B 30 0 594 562 ;
+C -1 ; WX 600 ; N Ccedilla ; B 22 -206 560 580 ;
+C -1 ; WX 600 ; N lcommaaccent ; B 77 -250 523 626 ;
+C -1 ; WX 600 ; N tcaron ; B 47 -15 532 703 ;
+C -1 ; WX 600 ; N eogonek ; B 40 -199 563 454 ;
+C -1 ; WX 600 ; N Uogonek ; B 4 -199 596 562 ;
+C -1 ; WX 600 ; N Aacute ; B -9 0 609 784 ;
+C -1 ; WX 600 ; N Adieresis ; B -9 0 609 761 ;
+C -1 ; WX 600 ; N egrave ; B 40 -15 563 661 ;
+C -1 ; WX 600 ; N zacute ; B 81 0 520 661 ;
+C -1 ; WX 600 ; N iogonek ; B 77 -199 523 658 ;
+C -1 ; WX 600 ; N Oacute ; B 22 -18 578 784 ;
+C -1 ; WX 600 ; N oacute ; B 30 -15 570 661 ;
+C -1 ; WX 600 ; N amacron ; B 35 -15 570 585 ;
+C -1 ; WX 600 ; N sacute ; B 68 -17 535 661 ;
+C -1 ; WX 600 ; N idieresis ; B 77 0 523 618 ;
+C -1 ; WX 600 ; N Ocircumflex ; B 22 -18 578 780 ;
+C -1 ; WX 600 ; N Ugrave ; B 4 -18 596 784 ;
+C -1 ; WX 600 ; N Delta ; B 6 0 594 688 ;
+C -1 ; WX 600 ; N thorn ; B -14 -142 570 626 ;
+C -1 ; WX 600 ; N twosuperior ; B 143 230 436 616 ;
+C -1 ; WX 600 ; N Odieresis ; B 22 -18 578 761 ;
+C -1 ; WX 600 ; N mu ; B -1 -142 569 439 ;
+C -1 ; WX 600 ; N igrave ; B 77 0 523 661 ;
+C -1 ; WX 600 ; N ohungarumlaut ; B 30 -15 668 661 ;
+C -1 ; WX 600 ; N Eogonek ; B 25 -199 576 562 ;
+C -1 ; WX 600 ; N dcroat ; B 20 -15 591 626 ;
+C -1 ; WX 600 ; N threequarters ; B -47 -60 648 661 ;
+C -1 ; WX 600 ; N Scedilla ; B 47 -206 553 582 ;
+C -1 ; WX 600 ; N lcaron ; B 77 0 597 626 ;
+C -1 ; WX 600 ; N Kcommaaccent ; B 21 -250 599 562 ;
+C -1 ; WX 600 ; N Lacute ; B 39 0 578 784 ;
+C -1 ; WX 600 ; N trademark ; B -9 230 749 562 ;
+C -1 ; WX 600 ; N edotaccent ; B 40 -15 563 638 ;
+C -1 ; WX 600 ; N Igrave ; B 77 0 523 784 ;
+C -1 ; WX 600 ; N Imacron ; B 77 0 523 708 ;
+C -1 ; WX 600 ; N Lcaron ; B 39 0 637 562 ;
+C -1 ; WX 600 ; N onehalf ; B -47 -60 648 661 ;
+C -1 ; WX 600 ; N lessequal ; B 26 0 523 696 ;
+C -1 ; WX 600 ; N ocircumflex ; B 30 -15 570 657 ;
+C -1 ; WX 600 ; N ntilde ; B 18 0 592 636 ;
+C -1 ; WX 600 ; N Uhungarumlaut ; B 4 -18 638 784 ;
+C -1 ; WX 600 ; N Eacute ; B 25 0 560 784 ;
+C -1 ; WX 600 ; N emacron ; B 40 -15 563 585 ;
+C -1 ; WX 600 ; N gbreve ; B 30 -146 580 661 ;
+C -1 ; WX 600 ; N onequarter ; B -56 -60 656 661 ;
+C -1 ; WX 600 ; N Scaron ; B 47 -22 553 790 ;
+C -1 ; WX 600 ; N Scommaaccent ; B 47 -250 553 582 ;
+C -1 ; WX 600 ; N Ohungarumlaut ; B 22 -18 628 784 ;
+C -1 ; WX 600 ; N degree ; B 86 243 474 616 ;
+C -1 ; WX 600 ; N ograve ; B 30 -15 570 661 ;
+C -1 ; WX 600 ; N Ccaron ; B 22 -18 560 790 ;
+C -1 ; WX 600 ; N ugrave ; B -1 -15 569 661 ;
+C -1 ; WX 600 ; N radical ; B -19 -104 473 778 ;
+C -1 ; WX 600 ; N Dcaron ; B 30 0 594 790 ;
+C -1 ; WX 600 ; N rcommaaccent ; B 47 -250 580 454 ;
+C -1 ; WX 600 ; N Ntilde ; B 8 -12 610 759 ;
+C -1 ; WX 600 ; N otilde ; B 30 -15 570 636 ;
+C -1 ; WX 600 ; N Rcommaaccent ; B 24 -250 599 562 ;
+C -1 ; WX 600 ; N Lcommaaccent ; B 39 -250 578 562 ;
+C -1 ; WX 600 ; N Atilde ; B -9 0 609 759 ;
+C -1 ; WX 600 ; N Aogonek ; B -9 -199 625 562 ;
+C -1 ; WX 600 ; N Aring ; B -9 0 609 801 ;
+C -1 ; WX 600 ; N Otilde ; B 22 -18 578 759 ;
+C -1 ; WX 600 ; N zdotaccent ; B 81 0 520 638 ;
+C -1 ; WX 600 ; N Ecaron ; B 25 0 560 790 ;
+C -1 ; WX 600 ; N Iogonek ; B 77 -199 523 562 ;
+C -1 ; WX 600 ; N kcommaaccent ; B 20 -250 585 626 ;
+C -1 ; WX 600 ; N minus ; B 71 203 529 313 ;
+C -1 ; WX 600 ; N Icircumflex ; B 77 0 523 780 ;
+C -1 ; WX 600 ; N ncaron ; B 18 0 592 667 ;
+C -1 ; WX 600 ; N tcommaaccent ; B 47 -250 532 562 ;
+C -1 ; WX 600 ; N logicalnot ; B 71 103 529 413 ;
+C -1 ; WX 600 ; N odieresis ; B 30 -15 570 638 ;
+C -1 ; WX 600 ; N udieresis ; B -1 -15 569 638 ;
+C -1 ; WX 600 ; N notequal ; B 12 -47 537 563 ;
+C -1 ; WX 600 ; N gcommaaccent ; B 30 -146 580 714 ;
+C -1 ; WX 600 ; N eth ; B 58 -27 543 626 ;
+C -1 ; WX 600 ; N zcaron ; B 81 0 520 667 ;
+C -1 ; WX 600 ; N ncommaaccent ; B 18 -250 592 454 ;
+C -1 ; WX 600 ; N onesuperior ; B 153 230 447 616 ;
+C -1 ; WX 600 ; N imacron ; B 77 0 523 585 ;
+C -1 ; WX 600 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier-BoldOblique.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier-BoldOblique.afm
new file mode 100644
index 0000000000000000000000000000000000000000..29d3b8b10ec4807eca647057669f027e93943527
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier-BoldOblique.afm
@@ -0,0 +1,342 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Jun 23 16:28:46 1997
+Comment UniqueID 43049
+Comment VMusage 17529 79244
+FontName Courier-BoldOblique
+FullName Courier Bold Oblique
+FamilyName Courier
+Weight Bold
+ItalicAngle -12
+IsFixedPitch true
+CharacterSet ExtendedRoman
+FontBBox -57 -250 869 801
+UnderlinePosition -100
+UnderlineThickness 50
+Version 003.000
+Notice Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 562
+XHeight 439
+Ascender 629
+Descender -157
+StdHW 84
+StdVW 106
+StartCharMetrics 315
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 215 -15 495 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 211 277 585 562 ;
+C 35 ; WX 600 ; N numbersign ; B 88 -45 641 651 ;
+C 36 ; WX 600 ; N dollar ; B 87 -126 630 666 ;
+C 37 ; WX 600 ; N percent ; B 101 -15 625 616 ;
+C 38 ; WX 600 ; N ampersand ; B 61 -15 595 543 ;
+C 39 ; WX 600 ; N quoteright ; B 229 277 543 562 ;
+C 40 ; WX 600 ; N parenleft ; B 265 -102 592 616 ;
+C 41 ; WX 600 ; N parenright ; B 117 -102 444 616 ;
+C 42 ; WX 600 ; N asterisk ; B 179 219 598 601 ;
+C 43 ; WX 600 ; N plus ; B 114 39 596 478 ;
+C 44 ; WX 600 ; N comma ; B 99 -111 430 174 ;
+C 45 ; WX 600 ; N hyphen ; B 143 203 567 313 ;
+C 46 ; WX 600 ; N period ; B 206 -15 427 171 ;
+C 47 ; WX 600 ; N slash ; B 90 -77 626 626 ;
+C 48 ; WX 600 ; N zero ; B 135 -15 593 616 ;
+C 49 ; WX 600 ; N one ; B 93 0 562 616 ;
+C 50 ; WX 600 ; N two ; B 61 0 594 616 ;
+C 51 ; WX 600 ; N three ; B 71 -15 571 616 ;
+C 52 ; WX 600 ; N four ; B 81 0 559 616 ;
+C 53 ; WX 600 ; N five ; B 77 -15 621 601 ;
+C 54 ; WX 600 ; N six ; B 135 -15 652 616 ;
+C 55 ; WX 600 ; N seven ; B 147 0 622 601 ;
+C 56 ; WX 600 ; N eight ; B 115 -15 604 616 ;
+C 57 ; WX 600 ; N nine ; B 75 -15 592 616 ;
+C 58 ; WX 600 ; N colon ; B 205 -15 480 425 ;
+C 59 ; WX 600 ; N semicolon ; B 99 -111 481 425 ;
+C 60 ; WX 600 ; N less ; B 120 15 613 501 ;
+C 61 ; WX 600 ; N equal ; B 96 118 614 398 ;
+C 62 ; WX 600 ; N greater ; B 97 15 589 501 ;
+C 63 ; WX 600 ; N question ; B 183 -14 592 580 ;
+C 64 ; WX 600 ; N at ; B 65 -15 642 616 ;
+C 65 ; WX 600 ; N A ; B -9 0 632 562 ;
+C 66 ; WX 600 ; N B ; B 30 0 630 562 ;
+C 67 ; WX 600 ; N C ; B 74 -18 675 580 ;
+C 68 ; WX 600 ; N D ; B 30 0 664 562 ;
+C 69 ; WX 600 ; N E ; B 25 0 670 562 ;
+C 70 ; WX 600 ; N F ; B 39 0 684 562 ;
+C 71 ; WX 600 ; N G ; B 74 -18 675 580 ;
+C 72 ; WX 600 ; N H ; B 20 0 700 562 ;
+C 73 ; WX 600 ; N I ; B 77 0 643 562 ;
+C 74 ; WX 600 ; N J ; B 58 -18 721 562 ;
+C 75 ; WX 600 ; N K ; B 21 0 692 562 ;
+C 76 ; WX 600 ; N L ; B 39 0 636 562 ;
+C 77 ; WX 600 ; N M ; B -2 0 722 562 ;
+C 78 ; WX 600 ; N N ; B 8 -12 730 562 ;
+C 79 ; WX 600 ; N O ; B 74 -18 645 580 ;
+C 80 ; WX 600 ; N P ; B 48 0 643 562 ;
+C 81 ; WX 600 ; N Q ; B 83 -138 636 580 ;
+C 82 ; WX 600 ; N R ; B 24 0 617 562 ;
+C 83 ; WX 600 ; N S ; B 54 -22 673 582 ;
+C 84 ; WX 600 ; N T ; B 86 0 679 562 ;
+C 85 ; WX 600 ; N U ; B 101 -18 716 562 ;
+C 86 ; WX 600 ; N V ; B 84 0 733 562 ;
+C 87 ; WX 600 ; N W ; B 79 0 738 562 ;
+C 88 ; WX 600 ; N X ; B 12 0 690 562 ;
+C 89 ; WX 600 ; N Y ; B 109 0 709 562 ;
+C 90 ; WX 600 ; N Z ; B 62 0 637 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 223 -102 606 616 ;
+C 92 ; WX 600 ; N backslash ; B 222 -77 496 626 ;
+C 93 ; WX 600 ; N bracketright ; B 103 -102 486 616 ;
+C 94 ; WX 600 ; N asciicircum ; B 171 250 556 616 ;
+C 95 ; WX 600 ; N underscore ; B -27 -125 585 -75 ;
+C 96 ; WX 600 ; N quoteleft ; B 297 277 487 562 ;
+C 97 ; WX 600 ; N a ; B 61 -15 593 454 ;
+C 98 ; WX 600 ; N b ; B 13 -15 636 626 ;
+C 99 ; WX 600 ; N c ; B 81 -15 631 459 ;
+C 100 ; WX 600 ; N d ; B 60 -15 645 626 ;
+C 101 ; WX 600 ; N e ; B 81 -15 605 454 ;
+C 102 ; WX 600 ; N f ; B 83 0 677 626 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 40 -146 674 454 ;
+C 104 ; WX 600 ; N h ; B 18 0 615 626 ;
+C 105 ; WX 600 ; N i ; B 77 0 546 658 ;
+C 106 ; WX 600 ; N j ; B 36 -146 580 658 ;
+C 107 ; WX 600 ; N k ; B 33 0 643 626 ;
+C 108 ; WX 600 ; N l ; B 77 0 546 626 ;
+C 109 ; WX 600 ; N m ; B -22 0 649 454 ;
+C 110 ; WX 600 ; N n ; B 18 0 615 454 ;
+C 111 ; WX 600 ; N o ; B 71 -15 622 454 ;
+C 112 ; WX 600 ; N p ; B -32 -142 622 454 ;
+C 113 ; WX 600 ; N q ; B 60 -142 685 454 ;
+C 114 ; WX 600 ; N r ; B 47 0 655 454 ;
+C 115 ; WX 600 ; N s ; B 66 -17 608 459 ;
+C 116 ; WX 600 ; N t ; B 118 -15 567 562 ;
+C 117 ; WX 600 ; N u ; B 70 -15 592 439 ;
+C 118 ; WX 600 ; N v ; B 70 0 695 439 ;
+C 119 ; WX 600 ; N w ; B 53 0 712 439 ;
+C 120 ; WX 600 ; N x ; B 6 0 671 439 ;
+C 121 ; WX 600 ; N y ; B -21 -142 695 439 ;
+C 122 ; WX 600 ; N z ; B 81 0 614 439 ;
+C 123 ; WX 600 ; N braceleft ; B 203 -102 595 616 ;
+C 124 ; WX 600 ; N bar ; B 201 -250 505 750 ;
+C 125 ; WX 600 ; N braceright ; B 114 -102 506 616 ;
+C 126 ; WX 600 ; N asciitilde ; B 120 153 590 356 ;
+C 161 ; WX 600 ; N exclamdown ; B 196 -146 477 449 ;
+C 162 ; WX 600 ; N cent ; B 121 -49 605 614 ;
+C 163 ; WX 600 ; N sterling ; B 106 -28 650 611 ;
+C 164 ; WX 600 ; N fraction ; B 22 -60 708 661 ;
+C 165 ; WX 600 ; N yen ; B 98 0 710 562 ;
+C 166 ; WX 600 ; N florin ; B -57 -131 702 616 ;
+C 167 ; WX 600 ; N section ; B 74 -70 620 580 ;
+C 168 ; WX 600 ; N currency ; B 77 49 644 517 ;
+C 169 ; WX 600 ; N quotesingle ; B 303 277 493 562 ;
+C 170 ; WX 600 ; N quotedblleft ; B 190 277 594 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 62 70 639 446 ;
+C 172 ; WX 600 ; N guilsinglleft ; B 195 70 545 446 ;
+C 173 ; WX 600 ; N guilsinglright ; B 165 70 514 446 ;
+C 174 ; WX 600 ; N fi ; B 12 0 644 626 ;
+C 175 ; WX 600 ; N fl ; B 12 0 644 626 ;
+C 177 ; WX 600 ; N endash ; B 108 203 602 313 ;
+C 178 ; WX 600 ; N dagger ; B 175 -70 586 580 ;
+C 179 ; WX 600 ; N daggerdbl ; B 121 -70 587 580 ;
+C 180 ; WX 600 ; N periodcentered ; B 248 165 461 351 ;
+C 182 ; WX 600 ; N paragraph ; B 61 -70 700 580 ;
+C 183 ; WX 600 ; N bullet ; B 196 132 523 430 ;
+C 184 ; WX 600 ; N quotesinglbase ; B 144 -142 458 143 ;
+C 185 ; WX 600 ; N quotedblbase ; B 34 -142 560 143 ;
+C 186 ; WX 600 ; N quotedblright ; B 119 277 645 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 71 70 647 446 ;
+C 188 ; WX 600 ; N ellipsis ; B 35 -15 587 116 ;
+C 189 ; WX 600 ; N perthousand ; B -45 -15 743 616 ;
+C 191 ; WX 600 ; N questiondown ; B 100 -146 509 449 ;
+C 193 ; WX 600 ; N grave ; B 272 508 503 661 ;
+C 194 ; WX 600 ; N acute ; B 312 508 609 661 ;
+C 195 ; WX 600 ; N circumflex ; B 212 483 607 657 ;
+C 196 ; WX 600 ; N tilde ; B 199 493 643 636 ;
+C 197 ; WX 600 ; N macron ; B 195 505 637 585 ;
+C 198 ; WX 600 ; N breve ; B 217 468 652 631 ;
+C 199 ; WX 600 ; N dotaccent ; B 348 498 493 638 ;
+C 200 ; WX 600 ; N dieresis ; B 246 498 595 638 ;
+C 202 ; WX 600 ; N ring ; B 319 481 528 678 ;
+C 203 ; WX 600 ; N cedilla ; B 168 -206 368 0 ;
+C 205 ; WX 600 ; N hungarumlaut ; B 171 488 729 661 ;
+C 206 ; WX 600 ; N ogonek ; B 143 -199 367 0 ;
+C 207 ; WX 600 ; N caron ; B 238 493 633 667 ;
+C 208 ; WX 600 ; N emdash ; B 33 203 677 313 ;
+C 225 ; WX 600 ; N AE ; B -29 0 708 562 ;
+C 227 ; WX 600 ; N ordfeminine ; B 188 196 526 580 ;
+C 232 ; WX 600 ; N Lslash ; B 39 0 636 562 ;
+C 233 ; WX 600 ; N Oslash ; B 48 -22 673 584 ;
+C 234 ; WX 600 ; N OE ; B 26 0 701 562 ;
+C 235 ; WX 600 ; N ordmasculine ; B 188 196 543 580 ;
+C 241 ; WX 600 ; N ae ; B 21 -15 652 454 ;
+C 245 ; WX 600 ; N dotlessi ; B 77 0 546 439 ;
+C 248 ; WX 600 ; N lslash ; B 77 0 587 626 ;
+C 249 ; WX 600 ; N oslash ; B 54 -24 638 463 ;
+C 250 ; WX 600 ; N oe ; B 18 -15 662 454 ;
+C 251 ; WX 600 ; N germandbls ; B 22 -15 629 626 ;
+C -1 ; WX 600 ; N Idieresis ; B 77 0 643 761 ;
+C -1 ; WX 600 ; N eacute ; B 81 -15 609 661 ;
+C -1 ; WX 600 ; N abreve ; B 61 -15 658 661 ;
+C -1 ; WX 600 ; N uhungarumlaut ; B 70 -15 769 661 ;
+C -1 ; WX 600 ; N ecaron ; B 81 -15 633 667 ;
+C -1 ; WX 600 ; N Ydieresis ; B 109 0 709 761 ;
+C -1 ; WX 600 ; N divide ; B 114 16 596 500 ;
+C -1 ; WX 600 ; N Yacute ; B 109 0 709 784 ;
+C -1 ; WX 600 ; N Acircumflex ; B -9 0 632 780 ;
+C -1 ; WX 600 ; N aacute ; B 61 -15 609 661 ;
+C -1 ; WX 600 ; N Ucircumflex ; B 101 -18 716 780 ;
+C -1 ; WX 600 ; N yacute ; B -21 -142 695 661 ;
+C -1 ; WX 600 ; N scommaaccent ; B 66 -250 608 459 ;
+C -1 ; WX 600 ; N ecircumflex ; B 81 -15 607 657 ;
+C -1 ; WX 600 ; N Uring ; B 101 -18 716 801 ;
+C -1 ; WX 600 ; N Udieresis ; B 101 -18 716 761 ;
+C -1 ; WX 600 ; N aogonek ; B 61 -199 593 454 ;
+C -1 ; WX 600 ; N Uacute ; B 101 -18 716 784 ;
+C -1 ; WX 600 ; N uogonek ; B 70 -199 592 439 ;
+C -1 ; WX 600 ; N Edieresis ; B 25 0 670 761 ;
+C -1 ; WX 600 ; N Dcroat ; B 30 0 664 562 ;
+C -1 ; WX 600 ; N commaaccent ; B 151 -250 385 -57 ;
+C -1 ; WX 600 ; N copyright ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N Emacron ; B 25 0 670 708 ;
+C -1 ; WX 600 ; N ccaron ; B 81 -15 633 667 ;
+C -1 ; WX 600 ; N aring ; B 61 -15 593 678 ;
+C -1 ; WX 600 ; N Ncommaaccent ; B 8 -250 730 562 ;
+C -1 ; WX 600 ; N lacute ; B 77 0 639 801 ;
+C -1 ; WX 600 ; N agrave ; B 61 -15 593 661 ;
+C -1 ; WX 600 ; N Tcommaaccent ; B 86 -250 679 562 ;
+C -1 ; WX 600 ; N Cacute ; B 74 -18 675 784 ;
+C -1 ; WX 600 ; N atilde ; B 61 -15 643 636 ;
+C -1 ; WX 600 ; N Edotaccent ; B 25 0 670 761 ;
+C -1 ; WX 600 ; N scaron ; B 66 -17 633 667 ;
+C -1 ; WX 600 ; N scedilla ; B 66 -206 608 459 ;
+C -1 ; WX 600 ; N iacute ; B 77 0 609 661 ;
+C -1 ; WX 600 ; N lozenge ; B 145 0 614 740 ;
+C -1 ; WX 600 ; N Rcaron ; B 24 0 659 790 ;
+C -1 ; WX 600 ; N Gcommaaccent ; B 74 -250 675 580 ;
+C -1 ; WX 600 ; N ucircumflex ; B 70 -15 597 657 ;
+C -1 ; WX 600 ; N acircumflex ; B 61 -15 607 657 ;
+C -1 ; WX 600 ; N Amacron ; B -9 0 633 708 ;
+C -1 ; WX 600 ; N rcaron ; B 47 0 655 667 ;
+C -1 ; WX 600 ; N ccedilla ; B 81 -206 631 459 ;
+C -1 ; WX 600 ; N Zdotaccent ; B 62 0 637 761 ;
+C -1 ; WX 600 ; N Thorn ; B 48 0 620 562 ;
+C -1 ; WX 600 ; N Omacron ; B 74 -18 663 708 ;
+C -1 ; WX 600 ; N Racute ; B 24 0 665 784 ;
+C -1 ; WX 600 ; N Sacute ; B 54 -22 673 784 ;
+C -1 ; WX 600 ; N dcaron ; B 60 -15 861 626 ;
+C -1 ; WX 600 ; N Umacron ; B 101 -18 716 708 ;
+C -1 ; WX 600 ; N uring ; B 70 -15 592 678 ;
+C -1 ; WX 600 ; N threesuperior ; B 193 222 526 616 ;
+C -1 ; WX 600 ; N Ograve ; B 74 -18 645 784 ;
+C -1 ; WX 600 ; N Agrave ; B -9 0 632 784 ;
+C -1 ; WX 600 ; N Abreve ; B -9 0 684 784 ;
+C -1 ; WX 600 ; N multiply ; B 104 39 606 478 ;
+C -1 ; WX 600 ; N uacute ; B 70 -15 599 661 ;
+C -1 ; WX 600 ; N Tcaron ; B 86 0 679 790 ;
+C -1 ; WX 600 ; N partialdiff ; B 91 -38 627 728 ;
+C -1 ; WX 600 ; N ydieresis ; B -21 -142 695 638 ;
+C -1 ; WX 600 ; N Nacute ; B 8 -12 730 784 ;
+C -1 ; WX 600 ; N icircumflex ; B 77 0 577 657 ;
+C -1 ; WX 600 ; N Ecircumflex ; B 25 0 670 780 ;
+C -1 ; WX 600 ; N adieresis ; B 61 -15 595 638 ;
+C -1 ; WX 600 ; N edieresis ; B 81 -15 605 638 ;
+C -1 ; WX 600 ; N cacute ; B 81 -15 649 661 ;
+C -1 ; WX 600 ; N nacute ; B 18 0 639 661 ;
+C -1 ; WX 600 ; N umacron ; B 70 -15 637 585 ;
+C -1 ; WX 600 ; N Ncaron ; B 8 -12 730 790 ;
+C -1 ; WX 600 ; N Iacute ; B 77 0 643 784 ;
+C -1 ; WX 600 ; N plusminus ; B 76 24 614 515 ;
+C -1 ; WX 600 ; N brokenbar ; B 217 -175 489 675 ;
+C -1 ; WX 600 ; N registered ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N Gbreve ; B 74 -18 684 784 ;
+C -1 ; WX 600 ; N Idotaccent ; B 77 0 643 761 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 672 706 ;
+C -1 ; WX 600 ; N Egrave ; B 25 0 670 784 ;
+C -1 ; WX 600 ; N racute ; B 47 0 655 661 ;
+C -1 ; WX 600 ; N omacron ; B 71 -15 637 585 ;
+C -1 ; WX 600 ; N Zacute ; B 62 0 665 784 ;
+C -1 ; WX 600 ; N Zcaron ; B 62 0 659 790 ;
+C -1 ; WX 600 ; N greaterequal ; B 26 0 627 696 ;
+C -1 ; WX 600 ; N Eth ; B 30 0 664 562 ;
+C -1 ; WX 600 ; N Ccedilla ; B 74 -206 675 580 ;
+C -1 ; WX 600 ; N lcommaaccent ; B 77 -250 546 626 ;
+C -1 ; WX 600 ; N tcaron ; B 118 -15 627 703 ;
+C -1 ; WX 600 ; N eogonek ; B 81 -199 605 454 ;
+C -1 ; WX 600 ; N Uogonek ; B 101 -199 716 562 ;
+C -1 ; WX 600 ; N Aacute ; B -9 0 655 784 ;
+C -1 ; WX 600 ; N Adieresis ; B -9 0 632 761 ;
+C -1 ; WX 600 ; N egrave ; B 81 -15 605 661 ;
+C -1 ; WX 600 ; N zacute ; B 81 0 614 661 ;
+C -1 ; WX 600 ; N iogonek ; B 77 -199 546 658 ;
+C -1 ; WX 600 ; N Oacute ; B 74 -18 645 784 ;
+C -1 ; WX 600 ; N oacute ; B 71 -15 649 661 ;
+C -1 ; WX 600 ; N amacron ; B 61 -15 637 585 ;
+C -1 ; WX 600 ; N sacute ; B 66 -17 609 661 ;
+C -1 ; WX 600 ; N idieresis ; B 77 0 561 618 ;
+C -1 ; WX 600 ; N Ocircumflex ; B 74 -18 645 780 ;
+C -1 ; WX 600 ; N Ugrave ; B 101 -18 716 784 ;
+C -1 ; WX 600 ; N Delta ; B 6 0 594 688 ;
+C -1 ; WX 600 ; N thorn ; B -32 -142 622 626 ;
+C -1 ; WX 600 ; N twosuperior ; B 191 230 542 616 ;
+C -1 ; WX 600 ; N Odieresis ; B 74 -18 645 761 ;
+C -1 ; WX 600 ; N mu ; B 49 -142 592 439 ;
+C -1 ; WX 600 ; N igrave ; B 77 0 546 661 ;
+C -1 ; WX 600 ; N ohungarumlaut ; B 71 -15 809 661 ;
+C -1 ; WX 600 ; N Eogonek ; B 25 -199 670 562 ;
+C -1 ; WX 600 ; N dcroat ; B 60 -15 712 626 ;
+C -1 ; WX 600 ; N threequarters ; B 8 -60 699 661 ;
+C -1 ; WX 600 ; N Scedilla ; B 54 -206 673 582 ;
+C -1 ; WX 600 ; N lcaron ; B 77 0 731 626 ;
+C -1 ; WX 600 ; N Kcommaaccent ; B 21 -250 692 562 ;
+C -1 ; WX 600 ; N Lacute ; B 39 0 636 784 ;
+C -1 ; WX 600 ; N trademark ; B 86 230 869 562 ;
+C -1 ; WX 600 ; N edotaccent ; B 81 -15 605 638 ;
+C -1 ; WX 600 ; N Igrave ; B 77 0 643 784 ;
+C -1 ; WX 600 ; N Imacron ; B 77 0 663 708 ;
+C -1 ; WX 600 ; N Lcaron ; B 39 0 757 562 ;
+C -1 ; WX 600 ; N onehalf ; B 22 -60 716 661 ;
+C -1 ; WX 600 ; N lessequal ; B 26 0 671 696 ;
+C -1 ; WX 600 ; N ocircumflex ; B 71 -15 622 657 ;
+C -1 ; WX 600 ; N ntilde ; B 18 0 643 636 ;
+C -1 ; WX 600 ; N Uhungarumlaut ; B 101 -18 805 784 ;
+C -1 ; WX 600 ; N Eacute ; B 25 0 670 784 ;
+C -1 ; WX 600 ; N emacron ; B 81 -15 637 585 ;
+C -1 ; WX 600 ; N gbreve ; B 40 -146 674 661 ;
+C -1 ; WX 600 ; N onequarter ; B 13 -60 707 661 ;
+C -1 ; WX 600 ; N Scaron ; B 54 -22 689 790 ;
+C -1 ; WX 600 ; N Scommaaccent ; B 54 -250 673 582 ;
+C -1 ; WX 600 ; N Ohungarumlaut ; B 74 -18 795 784 ;
+C -1 ; WX 600 ; N degree ; B 173 243 570 616 ;
+C -1 ; WX 600 ; N ograve ; B 71 -15 622 661 ;
+C -1 ; WX 600 ; N Ccaron ; B 74 -18 689 790 ;
+C -1 ; WX 600 ; N ugrave ; B 70 -15 592 661 ;
+C -1 ; WX 600 ; N radical ; B 67 -104 635 778 ;
+C -1 ; WX 600 ; N Dcaron ; B 30 0 664 790 ;
+C -1 ; WX 600 ; N rcommaaccent ; B 47 -250 655 454 ;
+C -1 ; WX 600 ; N Ntilde ; B 8 -12 730 759 ;
+C -1 ; WX 600 ; N otilde ; B 71 -15 643 636 ;
+C -1 ; WX 600 ; N Rcommaaccent ; B 24 -250 617 562 ;
+C -1 ; WX 600 ; N Lcommaaccent ; B 39 -250 636 562 ;
+C -1 ; WX 600 ; N Atilde ; B -9 0 669 759 ;
+C -1 ; WX 600 ; N Aogonek ; B -9 -199 632 562 ;
+C -1 ; WX 600 ; N Aring ; B -9 0 632 801 ;
+C -1 ; WX 600 ; N Otilde ; B 74 -18 669 759 ;
+C -1 ; WX 600 ; N zdotaccent ; B 81 0 614 638 ;
+C -1 ; WX 600 ; N Ecaron ; B 25 0 670 790 ;
+C -1 ; WX 600 ; N Iogonek ; B 77 -199 643 562 ;
+C -1 ; WX 600 ; N kcommaaccent ; B 33 -250 643 626 ;
+C -1 ; WX 600 ; N minus ; B 114 203 596 313 ;
+C -1 ; WX 600 ; N Icircumflex ; B 77 0 643 780 ;
+C -1 ; WX 600 ; N ncaron ; B 18 0 633 667 ;
+C -1 ; WX 600 ; N tcommaaccent ; B 118 -250 567 562 ;
+C -1 ; WX 600 ; N logicalnot ; B 135 103 617 413 ;
+C -1 ; WX 600 ; N odieresis ; B 71 -15 622 638 ;
+C -1 ; WX 600 ; N udieresis ; B 70 -15 595 638 ;
+C -1 ; WX 600 ; N notequal ; B 30 -47 626 563 ;
+C -1 ; WX 600 ; N gcommaaccent ; B 40 -146 674 714 ;
+C -1 ; WX 600 ; N eth ; B 93 -27 661 626 ;
+C -1 ; WX 600 ; N zcaron ; B 81 0 643 667 ;
+C -1 ; WX 600 ; N ncommaaccent ; B 18 -250 615 454 ;
+C -1 ; WX 600 ; N onesuperior ; B 212 230 514 616 ;
+C -1 ; WX 600 ; N imacron ; B 77 0 575 585 ;
+C -1 ; WX 600 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Oblique.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Oblique.afm
new file mode 100644
index 0000000000000000000000000000000000000000..3dc163f771a7a5bfd4978f6cb09b3f9eb29c55c6
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Oblique.afm
@@ -0,0 +1,342 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 17:37:52 1997
+Comment UniqueID 43051
+Comment VMusage 16248 75829
+FontName Courier-Oblique
+FullName Courier Oblique
+FamilyName Courier
+Weight Medium
+ItalicAngle -12
+IsFixedPitch true
+CharacterSet ExtendedRoman
+FontBBox -27 -250 849 805
+UnderlinePosition -100
+UnderlineThickness 50
+Version 003.000
+Notice Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 562
+XHeight 426
+Ascender 629
+Descender -157
+StdHW 51
+StdVW 51
+StartCharMetrics 315
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 243 -15 464 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 273 328 532 562 ;
+C 35 ; WX 600 ; N numbersign ; B 133 -32 596 639 ;
+C 36 ; WX 600 ; N dollar ; B 108 -126 596 662 ;
+C 37 ; WX 600 ; N percent ; B 134 -15 599 622 ;
+C 38 ; WX 600 ; N ampersand ; B 87 -15 580 543 ;
+C 39 ; WX 600 ; N quoteright ; B 283 328 495 562 ;
+C 40 ; WX 600 ; N parenleft ; B 313 -108 572 622 ;
+C 41 ; WX 600 ; N parenright ; B 137 -108 396 622 ;
+C 42 ; WX 600 ; N asterisk ; B 212 257 580 607 ;
+C 43 ; WX 600 ; N plus ; B 129 44 580 470 ;
+C 44 ; WX 600 ; N comma ; B 157 -112 370 122 ;
+C 45 ; WX 600 ; N hyphen ; B 152 231 558 285 ;
+C 46 ; WX 600 ; N period ; B 238 -15 382 109 ;
+C 47 ; WX 600 ; N slash ; B 112 -80 604 629 ;
+C 48 ; WX 600 ; N zero ; B 154 -15 575 622 ;
+C 49 ; WX 600 ; N one ; B 98 0 515 622 ;
+C 50 ; WX 600 ; N two ; B 70 0 568 622 ;
+C 51 ; WX 600 ; N three ; B 82 -15 538 622 ;
+C 52 ; WX 600 ; N four ; B 108 0 541 622 ;
+C 53 ; WX 600 ; N five ; B 99 -15 589 607 ;
+C 54 ; WX 600 ; N six ; B 155 -15 629 622 ;
+C 55 ; WX 600 ; N seven ; B 182 0 612 607 ;
+C 56 ; WX 600 ; N eight ; B 132 -15 588 622 ;
+C 57 ; WX 600 ; N nine ; B 93 -15 574 622 ;
+C 58 ; WX 600 ; N colon ; B 238 -15 441 385 ;
+C 59 ; WX 600 ; N semicolon ; B 157 -112 441 385 ;
+C 60 ; WX 600 ; N less ; B 96 42 610 472 ;
+C 61 ; WX 600 ; N equal ; B 109 138 600 376 ;
+C 62 ; WX 600 ; N greater ; B 85 42 599 472 ;
+C 63 ; WX 600 ; N question ; B 222 -15 583 572 ;
+C 64 ; WX 600 ; N at ; B 127 -15 582 622 ;
+C 65 ; WX 600 ; N A ; B 3 0 607 562 ;
+C 66 ; WX 600 ; N B ; B 43 0 616 562 ;
+C 67 ; WX 600 ; N C ; B 93 -18 655 580 ;
+C 68 ; WX 600 ; N D ; B 43 0 645 562 ;
+C 69 ; WX 600 ; N E ; B 53 0 660 562 ;
+C 70 ; WX 600 ; N F ; B 53 0 660 562 ;
+C 71 ; WX 600 ; N G ; B 83 -18 645 580 ;
+C 72 ; WX 600 ; N H ; B 32 0 687 562 ;
+C 73 ; WX 600 ; N I ; B 96 0 623 562 ;
+C 74 ; WX 600 ; N J ; B 52 -18 685 562 ;
+C 75 ; WX 600 ; N K ; B 38 0 671 562 ;
+C 76 ; WX 600 ; N L ; B 47 0 607 562 ;
+C 77 ; WX 600 ; N M ; B 4 0 715 562 ;
+C 78 ; WX 600 ; N N ; B 7 -13 712 562 ;
+C 79 ; WX 600 ; N O ; B 94 -18 625 580 ;
+C 80 ; WX 600 ; N P ; B 79 0 644 562 ;
+C 81 ; WX 600 ; N Q ; B 95 -138 625 580 ;
+C 82 ; WX 600 ; N R ; B 38 0 598 562 ;
+C 83 ; WX 600 ; N S ; B 76 -20 650 580 ;
+C 84 ; WX 600 ; N T ; B 108 0 665 562 ;
+C 85 ; WX 600 ; N U ; B 125 -18 702 562 ;
+C 86 ; WX 600 ; N V ; B 105 -13 723 562 ;
+C 87 ; WX 600 ; N W ; B 106 -13 722 562 ;
+C 88 ; WX 600 ; N X ; B 23 0 675 562 ;
+C 89 ; WX 600 ; N Y ; B 133 0 695 562 ;
+C 90 ; WX 600 ; N Z ; B 86 0 610 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 246 -108 574 622 ;
+C 92 ; WX 600 ; N backslash ; B 249 -80 468 629 ;
+C 93 ; WX 600 ; N bracketright ; B 135 -108 463 622 ;
+C 94 ; WX 600 ; N asciicircum ; B 175 354 587 622 ;
+C 95 ; WX 600 ; N underscore ; B -27 -125 584 -75 ;
+C 96 ; WX 600 ; N quoteleft ; B 343 328 457 562 ;
+C 97 ; WX 600 ; N a ; B 76 -15 569 441 ;
+C 98 ; WX 600 ; N b ; B 29 -15 625 629 ;
+C 99 ; WX 600 ; N c ; B 106 -15 608 441 ;
+C 100 ; WX 600 ; N d ; B 85 -15 640 629 ;
+C 101 ; WX 600 ; N e ; B 106 -15 598 441 ;
+C 102 ; WX 600 ; N f ; B 114 0 662 629 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 61 -157 657 441 ;
+C 104 ; WX 600 ; N h ; B 33 0 592 629 ;
+C 105 ; WX 600 ; N i ; B 95 0 515 657 ;
+C 106 ; WX 600 ; N j ; B 52 -157 550 657 ;
+C 107 ; WX 600 ; N k ; B 58 0 633 629 ;
+C 108 ; WX 600 ; N l ; B 95 0 515 629 ;
+C 109 ; WX 600 ; N m ; B -5 0 615 441 ;
+C 110 ; WX 600 ; N n ; B 26 0 585 441 ;
+C 111 ; WX 600 ; N o ; B 102 -15 588 441 ;
+C 112 ; WX 600 ; N p ; B -24 -157 605 441 ;
+C 113 ; WX 600 ; N q ; B 85 -157 682 441 ;
+C 114 ; WX 600 ; N r ; B 60 0 636 441 ;
+C 115 ; WX 600 ; N s ; B 78 -15 584 441 ;
+C 116 ; WX 600 ; N t ; B 167 -15 561 561 ;
+C 117 ; WX 600 ; N u ; B 101 -15 572 426 ;
+C 118 ; WX 600 ; N v ; B 90 -10 681 426 ;
+C 119 ; WX 600 ; N w ; B 76 -10 695 426 ;
+C 120 ; WX 600 ; N x ; B 20 0 655 426 ;
+C 121 ; WX 600 ; N y ; B -4 -157 683 426 ;
+C 122 ; WX 600 ; N z ; B 99 0 593 426 ;
+C 123 ; WX 600 ; N braceleft ; B 233 -108 569 622 ;
+C 124 ; WX 600 ; N bar ; B 222 -250 485 750 ;
+C 125 ; WX 600 ; N braceright ; B 140 -108 477 622 ;
+C 126 ; WX 600 ; N asciitilde ; B 116 197 600 320 ;
+C 161 ; WX 600 ; N exclamdown ; B 225 -157 445 430 ;
+C 162 ; WX 600 ; N cent ; B 151 -49 588 614 ;
+C 163 ; WX 600 ; N sterling ; B 124 -21 621 611 ;
+C 164 ; WX 600 ; N fraction ; B 84 -57 646 665 ;
+C 165 ; WX 600 ; N yen ; B 120 0 693 562 ;
+C 166 ; WX 600 ; N florin ; B -26 -143 671 622 ;
+C 167 ; WX 600 ; N section ; B 104 -78 590 580 ;
+C 168 ; WX 600 ; N currency ; B 94 58 628 506 ;
+C 169 ; WX 600 ; N quotesingle ; B 345 328 460 562 ;
+C 170 ; WX 600 ; N quotedblleft ; B 262 328 541 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 92 70 652 446 ;
+C 172 ; WX 600 ; N guilsinglleft ; B 204 70 540 446 ;
+C 173 ; WX 600 ; N guilsinglright ; B 170 70 506 446 ;
+C 174 ; WX 600 ; N fi ; B 3 0 619 629 ;
+C 175 ; WX 600 ; N fl ; B 3 0 619 629 ;
+C 177 ; WX 600 ; N endash ; B 124 231 586 285 ;
+C 178 ; WX 600 ; N dagger ; B 217 -78 546 580 ;
+C 179 ; WX 600 ; N daggerdbl ; B 163 -78 546 580 ;
+C 180 ; WX 600 ; N periodcentered ; B 275 189 434 327 ;
+C 182 ; WX 600 ; N paragraph ; B 100 -78 630 562 ;
+C 183 ; WX 600 ; N bullet ; B 224 130 485 383 ;
+C 184 ; WX 600 ; N quotesinglbase ; B 185 -134 397 100 ;
+C 185 ; WX 600 ; N quotedblbase ; B 115 -134 478 100 ;
+C 186 ; WX 600 ; N quotedblright ; B 213 328 576 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 58 70 618 446 ;
+C 188 ; WX 600 ; N ellipsis ; B 46 -15 575 111 ;
+C 189 ; WX 600 ; N perthousand ; B 59 -15 627 622 ;
+C 191 ; WX 600 ; N questiondown ; B 105 -157 466 430 ;
+C 193 ; WX 600 ; N grave ; B 294 497 484 672 ;
+C 194 ; WX 600 ; N acute ; B 348 497 612 672 ;
+C 195 ; WX 600 ; N circumflex ; B 229 477 581 654 ;
+C 196 ; WX 600 ; N tilde ; B 212 489 629 606 ;
+C 197 ; WX 600 ; N macron ; B 232 525 600 565 ;
+C 198 ; WX 600 ; N breve ; B 279 501 576 609 ;
+C 199 ; WX 600 ; N dotaccent ; B 373 537 478 640 ;
+C 200 ; WX 600 ; N dieresis ; B 272 537 579 640 ;
+C 202 ; WX 600 ; N ring ; B 332 463 500 627 ;
+C 203 ; WX 600 ; N cedilla ; B 197 -151 344 10 ;
+C 205 ; WX 600 ; N hungarumlaut ; B 239 497 683 672 ;
+C 206 ; WX 600 ; N ogonek ; B 189 -172 377 4 ;
+C 207 ; WX 600 ; N caron ; B 262 492 614 669 ;
+C 208 ; WX 600 ; N emdash ; B 49 231 661 285 ;
+C 225 ; WX 600 ; N AE ; B 3 0 655 562 ;
+C 227 ; WX 600 ; N ordfeminine ; B 209 249 512 580 ;
+C 232 ; WX 600 ; N Lslash ; B 47 0 607 562 ;
+C 233 ; WX 600 ; N Oslash ; B 94 -80 625 629 ;
+C 234 ; WX 600 ; N OE ; B 59 0 672 562 ;
+C 235 ; WX 600 ; N ordmasculine ; B 210 249 535 580 ;
+C 241 ; WX 600 ; N ae ; B 41 -15 626 441 ;
+C 245 ; WX 600 ; N dotlessi ; B 95 0 515 426 ;
+C 248 ; WX 600 ; N lslash ; B 95 0 587 629 ;
+C 249 ; WX 600 ; N oslash ; B 102 -80 588 506 ;
+C 250 ; WX 600 ; N oe ; B 54 -15 615 441 ;
+C 251 ; WX 600 ; N germandbls ; B 48 -15 617 629 ;
+C -1 ; WX 600 ; N Idieresis ; B 96 0 623 753 ;
+C -1 ; WX 600 ; N eacute ; B 106 -15 612 672 ;
+C -1 ; WX 600 ; N abreve ; B 76 -15 576 609 ;
+C -1 ; WX 600 ; N uhungarumlaut ; B 101 -15 723 672 ;
+C -1 ; WX 600 ; N ecaron ; B 106 -15 614 669 ;
+C -1 ; WX 600 ; N Ydieresis ; B 133 0 695 753 ;
+C -1 ; WX 600 ; N divide ; B 136 48 573 467 ;
+C -1 ; WX 600 ; N Yacute ; B 133 0 695 805 ;
+C -1 ; WX 600 ; N Acircumflex ; B 3 0 607 787 ;
+C -1 ; WX 600 ; N aacute ; B 76 -15 612 672 ;
+C -1 ; WX 600 ; N Ucircumflex ; B 125 -18 702 787 ;
+C -1 ; WX 600 ; N yacute ; B -4 -157 683 672 ;
+C -1 ; WX 600 ; N scommaaccent ; B 78 -250 584 441 ;
+C -1 ; WX 600 ; N ecircumflex ; B 106 -15 598 654 ;
+C -1 ; WX 600 ; N Uring ; B 125 -18 702 760 ;
+C -1 ; WX 600 ; N Udieresis ; B 125 -18 702 753 ;
+C -1 ; WX 600 ; N aogonek ; B 76 -172 569 441 ;
+C -1 ; WX 600 ; N Uacute ; B 125 -18 702 805 ;
+C -1 ; WX 600 ; N uogonek ; B 101 -172 572 426 ;
+C -1 ; WX 600 ; N Edieresis ; B 53 0 660 753 ;
+C -1 ; WX 600 ; N Dcroat ; B 43 0 645 562 ;
+C -1 ; WX 600 ; N commaaccent ; B 145 -250 323 -58 ;
+C -1 ; WX 600 ; N copyright ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N Emacron ; B 53 0 660 698 ;
+C -1 ; WX 600 ; N ccaron ; B 106 -15 614 669 ;
+C -1 ; WX 600 ; N aring ; B 76 -15 569 627 ;
+C -1 ; WX 600 ; N Ncommaaccent ; B 7 -250 712 562 ;
+C -1 ; WX 600 ; N lacute ; B 95 0 640 805 ;
+C -1 ; WX 600 ; N agrave ; B 76 -15 569 672 ;
+C -1 ; WX 600 ; N Tcommaaccent ; B 108 -250 665 562 ;
+C -1 ; WX 600 ; N Cacute ; B 93 -18 655 805 ;
+C -1 ; WX 600 ; N atilde ; B 76 -15 629 606 ;
+C -1 ; WX 600 ; N Edotaccent ; B 53 0 660 753 ;
+C -1 ; WX 600 ; N scaron ; B 78 -15 614 669 ;
+C -1 ; WX 600 ; N scedilla ; B 78 -151 584 441 ;
+C -1 ; WX 600 ; N iacute ; B 95 0 612 672 ;
+C -1 ; WX 600 ; N lozenge ; B 94 0 519 706 ;
+C -1 ; WX 600 ; N Rcaron ; B 38 0 642 802 ;
+C -1 ; WX 600 ; N Gcommaaccent ; B 83 -250 645 580 ;
+C -1 ; WX 600 ; N ucircumflex ; B 101 -15 572 654 ;
+C -1 ; WX 600 ; N acircumflex ; B 76 -15 581 654 ;
+C -1 ; WX 600 ; N Amacron ; B 3 0 607 698 ;
+C -1 ; WX 600 ; N rcaron ; B 60 0 636 669 ;
+C -1 ; WX 600 ; N ccedilla ; B 106 -151 614 441 ;
+C -1 ; WX 600 ; N Zdotaccent ; B 86 0 610 753 ;
+C -1 ; WX 600 ; N Thorn ; B 79 0 606 562 ;
+C -1 ; WX 600 ; N Omacron ; B 94 -18 628 698 ;
+C -1 ; WX 600 ; N Racute ; B 38 0 670 805 ;
+C -1 ; WX 600 ; N Sacute ; B 76 -20 650 805 ;
+C -1 ; WX 600 ; N dcaron ; B 85 -15 849 629 ;
+C -1 ; WX 600 ; N Umacron ; B 125 -18 702 698 ;
+C -1 ; WX 600 ; N uring ; B 101 -15 572 627 ;
+C -1 ; WX 600 ; N threesuperior ; B 213 240 501 622 ;
+C -1 ; WX 600 ; N Ograve ; B 94 -18 625 805 ;
+C -1 ; WX 600 ; N Agrave ; B 3 0 607 805 ;
+C -1 ; WX 600 ; N Abreve ; B 3 0 607 732 ;
+C -1 ; WX 600 ; N multiply ; B 103 43 607 470 ;
+C -1 ; WX 600 ; N uacute ; B 101 -15 602 672 ;
+C -1 ; WX 600 ; N Tcaron ; B 108 0 665 802 ;
+C -1 ; WX 600 ; N partialdiff ; B 45 -38 546 710 ;
+C -1 ; WX 600 ; N ydieresis ; B -4 -157 683 620 ;
+C -1 ; WX 600 ; N Nacute ; B 7 -13 712 805 ;
+C -1 ; WX 600 ; N icircumflex ; B 95 0 551 654 ;
+C -1 ; WX 600 ; N Ecircumflex ; B 53 0 660 787 ;
+C -1 ; WX 600 ; N adieresis ; B 76 -15 575 620 ;
+C -1 ; WX 600 ; N edieresis ; B 106 -15 598 620 ;
+C -1 ; WX 600 ; N cacute ; B 106 -15 612 672 ;
+C -1 ; WX 600 ; N nacute ; B 26 0 602 672 ;
+C -1 ; WX 600 ; N umacron ; B 101 -15 600 565 ;
+C -1 ; WX 600 ; N Ncaron ; B 7 -13 712 802 ;
+C -1 ; WX 600 ; N Iacute ; B 96 0 640 805 ;
+C -1 ; WX 600 ; N plusminus ; B 96 44 594 558 ;
+C -1 ; WX 600 ; N brokenbar ; B 238 -175 469 675 ;
+C -1 ; WX 600 ; N registered ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N Gbreve ; B 83 -18 645 732 ;
+C -1 ; WX 600 ; N Idotaccent ; B 96 0 623 753 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 670 706 ;
+C -1 ; WX 600 ; N Egrave ; B 53 0 660 805 ;
+C -1 ; WX 600 ; N racute ; B 60 0 636 672 ;
+C -1 ; WX 600 ; N omacron ; B 102 -15 600 565 ;
+C -1 ; WX 600 ; N Zacute ; B 86 0 670 805 ;
+C -1 ; WX 600 ; N Zcaron ; B 86 0 642 802 ;
+C -1 ; WX 600 ; N greaterequal ; B 98 0 594 710 ;
+C -1 ; WX 600 ; N Eth ; B 43 0 645 562 ;
+C -1 ; WX 600 ; N Ccedilla ; B 93 -151 658 580 ;
+C -1 ; WX 600 ; N lcommaaccent ; B 95 -250 515 629 ;
+C -1 ; WX 600 ; N tcaron ; B 167 -15 587 717 ;
+C -1 ; WX 600 ; N eogonek ; B 106 -172 598 441 ;
+C -1 ; WX 600 ; N Uogonek ; B 124 -172 702 562 ;
+C -1 ; WX 600 ; N Aacute ; B 3 0 660 805 ;
+C -1 ; WX 600 ; N Adieresis ; B 3 0 607 753 ;
+C -1 ; WX 600 ; N egrave ; B 106 -15 598 672 ;
+C -1 ; WX 600 ; N zacute ; B 99 0 612 672 ;
+C -1 ; WX 600 ; N iogonek ; B 95 -172 515 657 ;
+C -1 ; WX 600 ; N Oacute ; B 94 -18 640 805 ;
+C -1 ; WX 600 ; N oacute ; B 102 -15 612 672 ;
+C -1 ; WX 600 ; N amacron ; B 76 -15 600 565 ;
+C -1 ; WX 600 ; N sacute ; B 78 -15 612 672 ;
+C -1 ; WX 600 ; N idieresis ; B 95 0 545 620 ;
+C -1 ; WX 600 ; N Ocircumflex ; B 94 -18 625 787 ;
+C -1 ; WX 600 ; N Ugrave ; B 125 -18 702 805 ;
+C -1 ; WX 600 ; N Delta ; B 6 0 598 688 ;
+C -1 ; WX 600 ; N thorn ; B -24 -157 605 629 ;
+C -1 ; WX 600 ; N twosuperior ; B 230 249 535 622 ;
+C -1 ; WX 600 ; N Odieresis ; B 94 -18 625 753 ;
+C -1 ; WX 600 ; N mu ; B 72 -157 572 426 ;
+C -1 ; WX 600 ; N igrave ; B 95 0 515 672 ;
+C -1 ; WX 600 ; N ohungarumlaut ; B 102 -15 723 672 ;
+C -1 ; WX 600 ; N Eogonek ; B 53 -172 660 562 ;
+C -1 ; WX 600 ; N dcroat ; B 85 -15 704 629 ;
+C -1 ; WX 600 ; N threequarters ; B 73 -56 659 666 ;
+C -1 ; WX 600 ; N Scedilla ; B 76 -151 650 580 ;
+C -1 ; WX 600 ; N lcaron ; B 95 0 667 629 ;
+C -1 ; WX 600 ; N Kcommaaccent ; B 38 -250 671 562 ;
+C -1 ; WX 600 ; N Lacute ; B 47 0 607 805 ;
+C -1 ; WX 600 ; N trademark ; B 75 263 742 562 ;
+C -1 ; WX 600 ; N edotaccent ; B 106 -15 598 620 ;
+C -1 ; WX 600 ; N Igrave ; B 96 0 623 805 ;
+C -1 ; WX 600 ; N Imacron ; B 96 0 628 698 ;
+C -1 ; WX 600 ; N Lcaron ; B 47 0 632 562 ;
+C -1 ; WX 600 ; N onehalf ; B 65 -57 669 665 ;
+C -1 ; WX 600 ; N lessequal ; B 98 0 645 710 ;
+C -1 ; WX 600 ; N ocircumflex ; B 102 -15 588 654 ;
+C -1 ; WX 600 ; N ntilde ; B 26 0 629 606 ;
+C -1 ; WX 600 ; N Uhungarumlaut ; B 125 -18 761 805 ;
+C -1 ; WX 600 ; N Eacute ; B 53 0 670 805 ;
+C -1 ; WX 600 ; N emacron ; B 106 -15 600 565 ;
+C -1 ; WX 600 ; N gbreve ; B 61 -157 657 609 ;
+C -1 ; WX 600 ; N onequarter ; B 65 -57 674 665 ;
+C -1 ; WX 600 ; N Scaron ; B 76 -20 672 802 ;
+C -1 ; WX 600 ; N Scommaaccent ; B 76 -250 650 580 ;
+C -1 ; WX 600 ; N Ohungarumlaut ; B 94 -18 751 805 ;
+C -1 ; WX 600 ; N degree ; B 214 269 576 622 ;
+C -1 ; WX 600 ; N ograve ; B 102 -15 588 672 ;
+C -1 ; WX 600 ; N Ccaron ; B 93 -18 672 802 ;
+C -1 ; WX 600 ; N ugrave ; B 101 -15 572 672 ;
+C -1 ; WX 600 ; N radical ; B 85 -15 765 792 ;
+C -1 ; WX 600 ; N Dcaron ; B 43 0 645 802 ;
+C -1 ; WX 600 ; N rcommaaccent ; B 60 -250 636 441 ;
+C -1 ; WX 600 ; N Ntilde ; B 7 -13 712 729 ;
+C -1 ; WX 600 ; N otilde ; B 102 -15 629 606 ;
+C -1 ; WX 600 ; N Rcommaaccent ; B 38 -250 598 562 ;
+C -1 ; WX 600 ; N Lcommaaccent ; B 47 -250 607 562 ;
+C -1 ; WX 600 ; N Atilde ; B 3 0 655 729 ;
+C -1 ; WX 600 ; N Aogonek ; B 3 -172 607 562 ;
+C -1 ; WX 600 ; N Aring ; B 3 0 607 750 ;
+C -1 ; WX 600 ; N Otilde ; B 94 -18 655 729 ;
+C -1 ; WX 600 ; N zdotaccent ; B 99 0 593 620 ;
+C -1 ; WX 600 ; N Ecaron ; B 53 0 660 802 ;
+C -1 ; WX 600 ; N Iogonek ; B 96 -172 623 562 ;
+C -1 ; WX 600 ; N kcommaaccent ; B 58 -250 633 629 ;
+C -1 ; WX 600 ; N minus ; B 129 232 580 283 ;
+C -1 ; WX 600 ; N Icircumflex ; B 96 0 623 787 ;
+C -1 ; WX 600 ; N ncaron ; B 26 0 614 669 ;
+C -1 ; WX 600 ; N tcommaaccent ; B 165 -250 561 561 ;
+C -1 ; WX 600 ; N logicalnot ; B 155 108 591 369 ;
+C -1 ; WX 600 ; N odieresis ; B 102 -15 588 620 ;
+C -1 ; WX 600 ; N udieresis ; B 101 -15 575 620 ;
+C -1 ; WX 600 ; N notequal ; B 43 -16 621 529 ;
+C -1 ; WX 600 ; N gcommaaccent ; B 61 -157 657 708 ;
+C -1 ; WX 600 ; N eth ; B 102 -15 639 629 ;
+C -1 ; WX 600 ; N zcaron ; B 99 0 624 669 ;
+C -1 ; WX 600 ; N ncommaaccent ; B 26 -250 585 441 ;
+C -1 ; WX 600 ; N onesuperior ; B 231 249 491 622 ;
+C -1 ; WX 600 ; N imacron ; B 95 0 543 565 ;
+C -1 ; WX 600 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier.afm
new file mode 100644
index 0000000000000000000000000000000000000000..2f7be81d583f0f95cda1c73f5604d0ee5425019c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Courier.afm
@@ -0,0 +1,342 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 17:27:09 1997
+Comment UniqueID 43050
+Comment VMusage 39754 50779
+FontName Courier
+FullName Courier
+FamilyName Courier
+Weight Medium
+ItalicAngle 0
+IsFixedPitch true
+CharacterSet ExtendedRoman
+FontBBox -23 -250 715 805
+UnderlinePosition -100
+UnderlineThickness 50
+Version 003.000
+Notice Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme AdobeStandardEncoding
+CapHeight 562
+XHeight 426
+Ascender 629
+Descender -157
+StdHW 51
+StdVW 51
+StartCharMetrics 315
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 236 -15 364 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 187 328 413 562 ;
+C 35 ; WX 600 ; N numbersign ; B 93 -32 507 639 ;
+C 36 ; WX 600 ; N dollar ; B 105 -126 496 662 ;
+C 37 ; WX 600 ; N percent ; B 81 -15 518 622 ;
+C 38 ; WX 600 ; N ampersand ; B 63 -15 538 543 ;
+C 39 ; WX 600 ; N quoteright ; B 213 328 376 562 ;
+C 40 ; WX 600 ; N parenleft ; B 269 -108 440 622 ;
+C 41 ; WX 600 ; N parenright ; B 160 -108 331 622 ;
+C 42 ; WX 600 ; N asterisk ; B 116 257 484 607 ;
+C 43 ; WX 600 ; N plus ; B 80 44 520 470 ;
+C 44 ; WX 600 ; N comma ; B 181 -112 344 122 ;
+C 45 ; WX 600 ; N hyphen ; B 103 231 497 285 ;
+C 46 ; WX 600 ; N period ; B 229 -15 371 109 ;
+C 47 ; WX 600 ; N slash ; B 125 -80 475 629 ;
+C 48 ; WX 600 ; N zero ; B 106 -15 494 622 ;
+C 49 ; WX 600 ; N one ; B 96 0 505 622 ;
+C 50 ; WX 600 ; N two ; B 70 0 471 622 ;
+C 51 ; WX 600 ; N three ; B 75 -15 466 622 ;
+C 52 ; WX 600 ; N four ; B 78 0 500 622 ;
+C 53 ; WX 600 ; N five ; B 92 -15 497 607 ;
+C 54 ; WX 600 ; N six ; B 111 -15 497 622 ;
+C 55 ; WX 600 ; N seven ; B 82 0 483 607 ;
+C 56 ; WX 600 ; N eight ; B 102 -15 498 622 ;
+C 57 ; WX 600 ; N nine ; B 96 -15 489 622 ;
+C 58 ; WX 600 ; N colon ; B 229 -15 371 385 ;
+C 59 ; WX 600 ; N semicolon ; B 181 -112 371 385 ;
+C 60 ; WX 600 ; N less ; B 41 42 519 472 ;
+C 61 ; WX 600 ; N equal ; B 80 138 520 376 ;
+C 62 ; WX 600 ; N greater ; B 66 42 544 472 ;
+C 63 ; WX 600 ; N question ; B 129 -15 492 572 ;
+C 64 ; WX 600 ; N at ; B 77 -15 533 622 ;
+C 65 ; WX 600 ; N A ; B 3 0 597 562 ;
+C 66 ; WX 600 ; N B ; B 43 0 559 562 ;
+C 67 ; WX 600 ; N C ; B 41 -18 540 580 ;
+C 68 ; WX 600 ; N D ; B 43 0 574 562 ;
+C 69 ; WX 600 ; N E ; B 53 0 550 562 ;
+C 70 ; WX 600 ; N F ; B 53 0 545 562 ;
+C 71 ; WX 600 ; N G ; B 31 -18 575 580 ;
+C 72 ; WX 600 ; N H ; B 32 0 568 562 ;
+C 73 ; WX 600 ; N I ; B 96 0 504 562 ;
+C 74 ; WX 600 ; N J ; B 34 -18 566 562 ;
+C 75 ; WX 600 ; N K ; B 38 0 582 562 ;
+C 76 ; WX 600 ; N L ; B 47 0 554 562 ;
+C 77 ; WX 600 ; N M ; B 4 0 596 562 ;
+C 78 ; WX 600 ; N N ; B 7 -13 593 562 ;
+C 79 ; WX 600 ; N O ; B 43 -18 557 580 ;
+C 80 ; WX 600 ; N P ; B 79 0 558 562 ;
+C 81 ; WX 600 ; N Q ; B 43 -138 557 580 ;
+C 82 ; WX 600 ; N R ; B 38 0 588 562 ;
+C 83 ; WX 600 ; N S ; B 72 -20 529 580 ;
+C 84 ; WX 600 ; N T ; B 38 0 563 562 ;
+C 85 ; WX 600 ; N U ; B 17 -18 583 562 ;
+C 86 ; WX 600 ; N V ; B -4 -13 604 562 ;
+C 87 ; WX 600 ; N W ; B -3 -13 603 562 ;
+C 88 ; WX 600 ; N X ; B 23 0 577 562 ;
+C 89 ; WX 600 ; N Y ; B 24 0 576 562 ;
+C 90 ; WX 600 ; N Z ; B 86 0 514 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 269 -108 442 622 ;
+C 92 ; WX 600 ; N backslash ; B 118 -80 482 629 ;
+C 93 ; WX 600 ; N bracketright ; B 158 -108 331 622 ;
+C 94 ; WX 600 ; N asciicircum ; B 94 354 506 622 ;
+C 95 ; WX 600 ; N underscore ; B 0 -125 600 -75 ;
+C 96 ; WX 600 ; N quoteleft ; B 224 328 387 562 ;
+C 97 ; WX 600 ; N a ; B 53 -15 559 441 ;
+C 98 ; WX 600 ; N b ; B 14 -15 575 629 ;
+C 99 ; WX 600 ; N c ; B 66 -15 529 441 ;
+C 100 ; WX 600 ; N d ; B 45 -15 591 629 ;
+C 101 ; WX 600 ; N e ; B 66 -15 548 441 ;
+C 102 ; WX 600 ; N f ; B 114 0 531 629 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 45 -157 566 441 ;
+C 104 ; WX 600 ; N h ; B 18 0 582 629 ;
+C 105 ; WX 600 ; N i ; B 95 0 505 657 ;
+C 106 ; WX 600 ; N j ; B 82 -157 410 657 ;
+C 107 ; WX 600 ; N k ; B 43 0 580 629 ;
+C 108 ; WX 600 ; N l ; B 95 0 505 629 ;
+C 109 ; WX 600 ; N m ; B -5 0 605 441 ;
+C 110 ; WX 600 ; N n ; B 26 0 575 441 ;
+C 111 ; WX 600 ; N o ; B 62 -15 538 441 ;
+C 112 ; WX 600 ; N p ; B 9 -157 555 441 ;
+C 113 ; WX 600 ; N q ; B 45 -157 591 441 ;
+C 114 ; WX 600 ; N r ; B 60 0 559 441 ;
+C 115 ; WX 600 ; N s ; B 80 -15 513 441 ;
+C 116 ; WX 600 ; N t ; B 87 -15 530 561 ;
+C 117 ; WX 600 ; N u ; B 21 -15 562 426 ;
+C 118 ; WX 600 ; N v ; B 10 -10 590 426 ;
+C 119 ; WX 600 ; N w ; B -4 -10 604 426 ;
+C 120 ; WX 600 ; N x ; B 20 0 580 426 ;
+C 121 ; WX 600 ; N y ; B 7 -157 592 426 ;
+C 122 ; WX 600 ; N z ; B 99 0 502 426 ;
+C 123 ; WX 600 ; N braceleft ; B 182 -108 437 622 ;
+C 124 ; WX 600 ; N bar ; B 275 -250 326 750 ;
+C 125 ; WX 600 ; N braceright ; B 163 -108 418 622 ;
+C 126 ; WX 600 ; N asciitilde ; B 63 197 540 320 ;
+C 161 ; WX 600 ; N exclamdown ; B 236 -157 364 430 ;
+C 162 ; WX 600 ; N cent ; B 96 -49 500 614 ;
+C 163 ; WX 600 ; N sterling ; B 84 -21 521 611 ;
+C 164 ; WX 600 ; N fraction ; B 92 -57 509 665 ;
+C 165 ; WX 600 ; N yen ; B 26 0 574 562 ;
+C 166 ; WX 600 ; N florin ; B 4 -143 539 622 ;
+C 167 ; WX 600 ; N section ; B 113 -78 488 580 ;
+C 168 ; WX 600 ; N currency ; B 73 58 527 506 ;
+C 169 ; WX 600 ; N quotesingle ; B 259 328 341 562 ;
+C 170 ; WX 600 ; N quotedblleft ; B 143 328 471 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 37 70 563 446 ;
+C 172 ; WX 600 ; N guilsinglleft ; B 149 70 451 446 ;
+C 173 ; WX 600 ; N guilsinglright ; B 149 70 451 446 ;
+C 174 ; WX 600 ; N fi ; B 3 0 597 629 ;
+C 175 ; WX 600 ; N fl ; B 3 0 597 629 ;
+C 177 ; WX 600 ; N endash ; B 75 231 525 285 ;
+C 178 ; WX 600 ; N dagger ; B 141 -78 459 580 ;
+C 179 ; WX 600 ; N daggerdbl ; B 141 -78 459 580 ;
+C 180 ; WX 600 ; N periodcentered ; B 222 189 378 327 ;
+C 182 ; WX 600 ; N paragraph ; B 50 -78 511 562 ;
+C 183 ; WX 600 ; N bullet ; B 172 130 428 383 ;
+C 184 ; WX 600 ; N quotesinglbase ; B 213 -134 376 100 ;
+C 185 ; WX 600 ; N quotedblbase ; B 143 -134 457 100 ;
+C 186 ; WX 600 ; N quotedblright ; B 143 328 457 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 37 70 563 446 ;
+C 188 ; WX 600 ; N ellipsis ; B 37 -15 563 111 ;
+C 189 ; WX 600 ; N perthousand ; B 3 -15 600 622 ;
+C 191 ; WX 600 ; N questiondown ; B 108 -157 471 430 ;
+C 193 ; WX 600 ; N grave ; B 151 497 378 672 ;
+C 194 ; WX 600 ; N acute ; B 242 497 469 672 ;
+C 195 ; WX 600 ; N circumflex ; B 124 477 476 654 ;
+C 196 ; WX 600 ; N tilde ; B 105 489 503 606 ;
+C 197 ; WX 600 ; N macron ; B 120 525 480 565 ;
+C 198 ; WX 600 ; N breve ; B 153 501 447 609 ;
+C 199 ; WX 600 ; N dotaccent ; B 249 537 352 640 ;
+C 200 ; WX 600 ; N dieresis ; B 148 537 453 640 ;
+C 202 ; WX 600 ; N ring ; B 218 463 382 627 ;
+C 203 ; WX 600 ; N cedilla ; B 224 -151 362 10 ;
+C 205 ; WX 600 ; N hungarumlaut ; B 133 497 540 672 ;
+C 206 ; WX 600 ; N ogonek ; B 211 -172 407 4 ;
+C 207 ; WX 600 ; N caron ; B 124 492 476 669 ;
+C 208 ; WX 600 ; N emdash ; B 0 231 600 285 ;
+C 225 ; WX 600 ; N AE ; B 3 0 550 562 ;
+C 227 ; WX 600 ; N ordfeminine ; B 156 249 442 580 ;
+C 232 ; WX 600 ; N Lslash ; B 47 0 554 562 ;
+C 233 ; WX 600 ; N Oslash ; B 43 -80 557 629 ;
+C 234 ; WX 600 ; N OE ; B 7 0 567 562 ;
+C 235 ; WX 600 ; N ordmasculine ; B 157 249 443 580 ;
+C 241 ; WX 600 ; N ae ; B 19 -15 570 441 ;
+C 245 ; WX 600 ; N dotlessi ; B 95 0 505 426 ;
+C 248 ; WX 600 ; N lslash ; B 95 0 505 629 ;
+C 249 ; WX 600 ; N oslash ; B 62 -80 538 506 ;
+C 250 ; WX 600 ; N oe ; B 19 -15 559 441 ;
+C 251 ; WX 600 ; N germandbls ; B 48 -15 588 629 ;
+C -1 ; WX 600 ; N Idieresis ; B 96 0 504 753 ;
+C -1 ; WX 600 ; N eacute ; B 66 -15 548 672 ;
+C -1 ; WX 600 ; N abreve ; B 53 -15 559 609 ;
+C -1 ; WX 600 ; N uhungarumlaut ; B 21 -15 580 672 ;
+C -1 ; WX 600 ; N ecaron ; B 66 -15 548 669 ;
+C -1 ; WX 600 ; N Ydieresis ; B 24 0 576 753 ;
+C -1 ; WX 600 ; N divide ; B 87 48 513 467 ;
+C -1 ; WX 600 ; N Yacute ; B 24 0 576 805 ;
+C -1 ; WX 600 ; N Acircumflex ; B 3 0 597 787 ;
+C -1 ; WX 600 ; N aacute ; B 53 -15 559 672 ;
+C -1 ; WX 600 ; N Ucircumflex ; B 17 -18 583 787 ;
+C -1 ; WX 600 ; N yacute ; B 7 -157 592 672 ;
+C -1 ; WX 600 ; N scommaaccent ; B 80 -250 513 441 ;
+C -1 ; WX 600 ; N ecircumflex ; B 66 -15 548 654 ;
+C -1 ; WX 600 ; N Uring ; B 17 -18 583 760 ;
+C -1 ; WX 600 ; N Udieresis ; B 17 -18 583 753 ;
+C -1 ; WX 600 ; N aogonek ; B 53 -172 587 441 ;
+C -1 ; WX 600 ; N Uacute ; B 17 -18 583 805 ;
+C -1 ; WX 600 ; N uogonek ; B 21 -172 590 426 ;
+C -1 ; WX 600 ; N Edieresis ; B 53 0 550 753 ;
+C -1 ; WX 600 ; N Dcroat ; B 30 0 574 562 ;
+C -1 ; WX 600 ; N commaaccent ; B 198 -250 335 -58 ;
+C -1 ; WX 600 ; N copyright ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N Emacron ; B 53 0 550 698 ;
+C -1 ; WX 600 ; N ccaron ; B 66 -15 529 669 ;
+C -1 ; WX 600 ; N aring ; B 53 -15 559 627 ;
+C -1 ; WX 600 ; N Ncommaaccent ; B 7 -250 593 562 ;
+C -1 ; WX 600 ; N lacute ; B 95 0 505 805 ;
+C -1 ; WX 600 ; N agrave ; B 53 -15 559 672 ;
+C -1 ; WX 600 ; N Tcommaaccent ; B 38 -250 563 562 ;
+C -1 ; WX 600 ; N Cacute ; B 41 -18 540 805 ;
+C -1 ; WX 600 ; N atilde ; B 53 -15 559 606 ;
+C -1 ; WX 600 ; N Edotaccent ; B 53 0 550 753 ;
+C -1 ; WX 600 ; N scaron ; B 80 -15 513 669 ;
+C -1 ; WX 600 ; N scedilla ; B 80 -151 513 441 ;
+C -1 ; WX 600 ; N iacute ; B 95 0 505 672 ;
+C -1 ; WX 600 ; N lozenge ; B 18 0 443 706 ;
+C -1 ; WX 600 ; N Rcaron ; B 38 0 588 802 ;
+C -1 ; WX 600 ; N Gcommaaccent ; B 31 -250 575 580 ;
+C -1 ; WX 600 ; N ucircumflex ; B 21 -15 562 654 ;
+C -1 ; WX 600 ; N acircumflex ; B 53 -15 559 654 ;
+C -1 ; WX 600 ; N Amacron ; B 3 0 597 698 ;
+C -1 ; WX 600 ; N rcaron ; B 60 0 559 669 ;
+C -1 ; WX 600 ; N ccedilla ; B 66 -151 529 441 ;
+C -1 ; WX 600 ; N Zdotaccent ; B 86 0 514 753 ;
+C -1 ; WX 600 ; N Thorn ; B 79 0 538 562 ;
+C -1 ; WX 600 ; N Omacron ; B 43 -18 557 698 ;
+C -1 ; WX 600 ; N Racute ; B 38 0 588 805 ;
+C -1 ; WX 600 ; N Sacute ; B 72 -20 529 805 ;
+C -1 ; WX 600 ; N dcaron ; B 45 -15 715 629 ;
+C -1 ; WX 600 ; N Umacron ; B 17 -18 583 698 ;
+C -1 ; WX 600 ; N uring ; B 21 -15 562 627 ;
+C -1 ; WX 600 ; N threesuperior ; B 155 240 406 622 ;
+C -1 ; WX 600 ; N Ograve ; B 43 -18 557 805 ;
+C -1 ; WX 600 ; N Agrave ; B 3 0 597 805 ;
+C -1 ; WX 600 ; N Abreve ; B 3 0 597 732 ;
+C -1 ; WX 600 ; N multiply ; B 87 43 515 470 ;
+C -1 ; WX 600 ; N uacute ; B 21 -15 562 672 ;
+C -1 ; WX 600 ; N Tcaron ; B 38 0 563 802 ;
+C -1 ; WX 600 ; N partialdiff ; B 17 -38 459 710 ;
+C -1 ; WX 600 ; N ydieresis ; B 7 -157 592 620 ;
+C -1 ; WX 600 ; N Nacute ; B 7 -13 593 805 ;
+C -1 ; WX 600 ; N icircumflex ; B 94 0 505 654 ;
+C -1 ; WX 600 ; N Ecircumflex ; B 53 0 550 787 ;
+C -1 ; WX 600 ; N adieresis ; B 53 -15 559 620 ;
+C -1 ; WX 600 ; N edieresis ; B 66 -15 548 620 ;
+C -1 ; WX 600 ; N cacute ; B 66 -15 529 672 ;
+C -1 ; WX 600 ; N nacute ; B 26 0 575 672 ;
+C -1 ; WX 600 ; N umacron ; B 21 -15 562 565 ;
+C -1 ; WX 600 ; N Ncaron ; B 7 -13 593 802 ;
+C -1 ; WX 600 ; N Iacute ; B 96 0 504 805 ;
+C -1 ; WX 600 ; N plusminus ; B 87 44 513 558 ;
+C -1 ; WX 600 ; N brokenbar ; B 275 -175 326 675 ;
+C -1 ; WX 600 ; N registered ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N Gbreve ; B 31 -18 575 732 ;
+C -1 ; WX 600 ; N Idotaccent ; B 96 0 504 753 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 585 706 ;
+C -1 ; WX 600 ; N Egrave ; B 53 0 550 805 ;
+C -1 ; WX 600 ; N racute ; B 60 0 559 672 ;
+C -1 ; WX 600 ; N omacron ; B 62 -15 538 565 ;
+C -1 ; WX 600 ; N Zacute ; B 86 0 514 805 ;
+C -1 ; WX 600 ; N Zcaron ; B 86 0 514 802 ;
+C -1 ; WX 600 ; N greaterequal ; B 98 0 502 710 ;
+C -1 ; WX 600 ; N Eth ; B 30 0 574 562 ;
+C -1 ; WX 600 ; N Ccedilla ; B 41 -151 540 580 ;
+C -1 ; WX 600 ; N lcommaaccent ; B 95 -250 505 629 ;
+C -1 ; WX 600 ; N tcaron ; B 87 -15 530 717 ;
+C -1 ; WX 600 ; N eogonek ; B 66 -172 548 441 ;
+C -1 ; WX 600 ; N Uogonek ; B 17 -172 583 562 ;
+C -1 ; WX 600 ; N Aacute ; B 3 0 597 805 ;
+C -1 ; WX 600 ; N Adieresis ; B 3 0 597 753 ;
+C -1 ; WX 600 ; N egrave ; B 66 -15 548 672 ;
+C -1 ; WX 600 ; N zacute ; B 99 0 502 672 ;
+C -1 ; WX 600 ; N iogonek ; B 95 -172 505 657 ;
+C -1 ; WX 600 ; N Oacute ; B 43 -18 557 805 ;
+C -1 ; WX 600 ; N oacute ; B 62 -15 538 672 ;
+C -1 ; WX 600 ; N amacron ; B 53 -15 559 565 ;
+C -1 ; WX 600 ; N sacute ; B 80 -15 513 672 ;
+C -1 ; WX 600 ; N idieresis ; B 95 0 505 620 ;
+C -1 ; WX 600 ; N Ocircumflex ; B 43 -18 557 787 ;
+C -1 ; WX 600 ; N Ugrave ; B 17 -18 583 805 ;
+C -1 ; WX 600 ; N Delta ; B 6 0 598 688 ;
+C -1 ; WX 600 ; N thorn ; B -6 -157 555 629 ;
+C -1 ; WX 600 ; N twosuperior ; B 177 249 424 622 ;
+C -1 ; WX 600 ; N Odieresis ; B 43 -18 557 753 ;
+C -1 ; WX 600 ; N mu ; B 21 -157 562 426 ;
+C -1 ; WX 600 ; N igrave ; B 95 0 505 672 ;
+C -1 ; WX 600 ; N ohungarumlaut ; B 62 -15 580 672 ;
+C -1 ; WX 600 ; N Eogonek ; B 53 -172 561 562 ;
+C -1 ; WX 600 ; N dcroat ; B 45 -15 591 629 ;
+C -1 ; WX 600 ; N threequarters ; B 8 -56 593 666 ;
+C -1 ; WX 600 ; N Scedilla ; B 72 -151 529 580 ;
+C -1 ; WX 600 ; N lcaron ; B 95 0 533 629 ;
+C -1 ; WX 600 ; N Kcommaaccent ; B 38 -250 582 562 ;
+C -1 ; WX 600 ; N Lacute ; B 47 0 554 805 ;
+C -1 ; WX 600 ; N trademark ; B -23 263 623 562 ;
+C -1 ; WX 600 ; N edotaccent ; B 66 -15 548 620 ;
+C -1 ; WX 600 ; N Igrave ; B 96 0 504 805 ;
+C -1 ; WX 600 ; N Imacron ; B 96 0 504 698 ;
+C -1 ; WX 600 ; N Lcaron ; B 47 0 554 562 ;
+C -1 ; WX 600 ; N onehalf ; B 0 -57 611 665 ;
+C -1 ; WX 600 ; N lessequal ; B 98 0 502 710 ;
+C -1 ; WX 600 ; N ocircumflex ; B 62 -15 538 654 ;
+C -1 ; WX 600 ; N ntilde ; B 26 0 575 606 ;
+C -1 ; WX 600 ; N Uhungarumlaut ; B 17 -18 590 805 ;
+C -1 ; WX 600 ; N Eacute ; B 53 0 550 805 ;
+C -1 ; WX 600 ; N emacron ; B 66 -15 548 565 ;
+C -1 ; WX 600 ; N gbreve ; B 45 -157 566 609 ;
+C -1 ; WX 600 ; N onequarter ; B 0 -57 600 665 ;
+C -1 ; WX 600 ; N Scaron ; B 72 -20 529 802 ;
+C -1 ; WX 600 ; N Scommaaccent ; B 72 -250 529 580 ;
+C -1 ; WX 600 ; N Ohungarumlaut ; B 43 -18 580 805 ;
+C -1 ; WX 600 ; N degree ; B 123 269 477 622 ;
+C -1 ; WX 600 ; N ograve ; B 62 -15 538 672 ;
+C -1 ; WX 600 ; N Ccaron ; B 41 -18 540 802 ;
+C -1 ; WX 600 ; N ugrave ; B 21 -15 562 672 ;
+C -1 ; WX 600 ; N radical ; B 3 -15 597 792 ;
+C -1 ; WX 600 ; N Dcaron ; B 43 0 574 802 ;
+C -1 ; WX 600 ; N rcommaaccent ; B 60 -250 559 441 ;
+C -1 ; WX 600 ; N Ntilde ; B 7 -13 593 729 ;
+C -1 ; WX 600 ; N otilde ; B 62 -15 538 606 ;
+C -1 ; WX 600 ; N Rcommaaccent ; B 38 -250 588 562 ;
+C -1 ; WX 600 ; N Lcommaaccent ; B 47 -250 554 562 ;
+C -1 ; WX 600 ; N Atilde ; B 3 0 597 729 ;
+C -1 ; WX 600 ; N Aogonek ; B 3 -172 608 562 ;
+C -1 ; WX 600 ; N Aring ; B 3 0 597 750 ;
+C -1 ; WX 600 ; N Otilde ; B 43 -18 557 729 ;
+C -1 ; WX 600 ; N zdotaccent ; B 99 0 502 620 ;
+C -1 ; WX 600 ; N Ecaron ; B 53 0 550 802 ;
+C -1 ; WX 600 ; N Iogonek ; B 96 -172 504 562 ;
+C -1 ; WX 600 ; N kcommaaccent ; B 43 -250 580 629 ;
+C -1 ; WX 600 ; N minus ; B 80 232 520 283 ;
+C -1 ; WX 600 ; N Icircumflex ; B 96 0 504 787 ;
+C -1 ; WX 600 ; N ncaron ; B 26 0 575 669 ;
+C -1 ; WX 600 ; N tcommaaccent ; B 87 -250 530 561 ;
+C -1 ; WX 600 ; N logicalnot ; B 87 108 513 369 ;
+C -1 ; WX 600 ; N odieresis ; B 62 -15 538 620 ;
+C -1 ; WX 600 ; N udieresis ; B 21 -15 562 620 ;
+C -1 ; WX 600 ; N notequal ; B 15 -16 540 529 ;
+C -1 ; WX 600 ; N gcommaaccent ; B 45 -157 566 708 ;
+C -1 ; WX 600 ; N eth ; B 62 -15 538 629 ;
+C -1 ; WX 600 ; N zcaron ; B 99 0 502 669 ;
+C -1 ; WX 600 ; N ncommaaccent ; B 26 -250 575 441 ;
+C -1 ; WX 600 ; N onesuperior ; B 172 249 428 622 ;
+C -1 ; WX 600 ; N imacron ; B 95 0 505 565 ;
+C -1 ; WX 600 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Bold.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Bold.afm
new file mode 100644
index 0000000000000000000000000000000000000000..837c594e0e80214c2c9c520b2aa9ae1ce3f18b33
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Bold.afm
@@ -0,0 +1,2827 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:43:52 1997
+Comment UniqueID 43052
+Comment VMusage 37169 48194
+FontName Helvetica-Bold
+FullName Helvetica Bold
+FamilyName Helvetica
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -170 -228 1003 962
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 532
+Ascender 718
+Descender -207
+StdHW 118
+StdVW 140
+StartCharMetrics 315
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 90 0 244 718 ;
+C 34 ; WX 474 ; N quotedbl ; B 98 447 376 718 ;
+C 35 ; WX 556 ; N numbersign ; B 18 0 538 698 ;
+C 36 ; WX 556 ; N dollar ; B 30 -115 523 775 ;
+C 37 ; WX 889 ; N percent ; B 28 -19 861 710 ;
+C 38 ; WX 722 ; N ampersand ; B 54 -19 701 718 ;
+C 39 ; WX 278 ; N quoteright ; B 69 445 209 718 ;
+C 40 ; WX 333 ; N parenleft ; B 35 -208 314 734 ;
+C 41 ; WX 333 ; N parenright ; B 19 -208 298 734 ;
+C 42 ; WX 389 ; N asterisk ; B 27 387 362 718 ;
+C 43 ; WX 584 ; N plus ; B 40 0 544 506 ;
+C 44 ; WX 278 ; N comma ; B 64 -168 214 146 ;
+C 45 ; WX 333 ; N hyphen ; B 27 215 306 345 ;
+C 46 ; WX 278 ; N period ; B 64 0 214 146 ;
+C 47 ; WX 278 ; N slash ; B -33 -19 311 737 ;
+C 48 ; WX 556 ; N zero ; B 32 -19 524 710 ;
+C 49 ; WX 556 ; N one ; B 69 0 378 710 ;
+C 50 ; WX 556 ; N two ; B 26 0 511 710 ;
+C 51 ; WX 556 ; N three ; B 27 -19 516 710 ;
+C 52 ; WX 556 ; N four ; B 27 0 526 710 ;
+C 53 ; WX 556 ; N five ; B 27 -19 516 698 ;
+C 54 ; WX 556 ; N six ; B 31 -19 520 710 ;
+C 55 ; WX 556 ; N seven ; B 25 0 528 698 ;
+C 56 ; WX 556 ; N eight ; B 32 -19 524 710 ;
+C 57 ; WX 556 ; N nine ; B 30 -19 522 710 ;
+C 58 ; WX 333 ; N colon ; B 92 0 242 512 ;
+C 59 ; WX 333 ; N semicolon ; B 92 -168 242 512 ;
+C 60 ; WX 584 ; N less ; B 38 -8 546 514 ;
+C 61 ; WX 584 ; N equal ; B 40 87 544 419 ;
+C 62 ; WX 584 ; N greater ; B 38 -8 546 514 ;
+C 63 ; WX 611 ; N question ; B 60 0 556 727 ;
+C 64 ; WX 975 ; N at ; B 118 -19 856 737 ;
+C 65 ; WX 722 ; N A ; B 20 0 702 718 ;
+C 66 ; WX 722 ; N B ; B 76 0 669 718 ;
+C 67 ; WX 722 ; N C ; B 44 -19 684 737 ;
+C 68 ; WX 722 ; N D ; B 76 0 685 718 ;
+C 69 ; WX 667 ; N E ; B 76 0 621 718 ;
+C 70 ; WX 611 ; N F ; B 76 0 587 718 ;
+C 71 ; WX 778 ; N G ; B 44 -19 713 737 ;
+C 72 ; WX 722 ; N H ; B 71 0 651 718 ;
+C 73 ; WX 278 ; N I ; B 64 0 214 718 ;
+C 74 ; WX 556 ; N J ; B 22 -18 484 718 ;
+C 75 ; WX 722 ; N K ; B 87 0 722 718 ;
+C 76 ; WX 611 ; N L ; B 76 0 583 718 ;
+C 77 ; WX 833 ; N M ; B 69 0 765 718 ;
+C 78 ; WX 722 ; N N ; B 69 0 654 718 ;
+C 79 ; WX 778 ; N O ; B 44 -19 734 737 ;
+C 80 ; WX 667 ; N P ; B 76 0 627 718 ;
+C 81 ; WX 778 ; N Q ; B 44 -52 737 737 ;
+C 82 ; WX 722 ; N R ; B 76 0 677 718 ;
+C 83 ; WX 667 ; N S ; B 39 -19 629 737 ;
+C 84 ; WX 611 ; N T ; B 14 0 598 718 ;
+C 85 ; WX 722 ; N U ; B 72 -19 651 718 ;
+C 86 ; WX 667 ; N V ; B 19 0 648 718 ;
+C 87 ; WX 944 ; N W ; B 16 0 929 718 ;
+C 88 ; WX 667 ; N X ; B 14 0 653 718 ;
+C 89 ; WX 667 ; N Y ; B 15 0 653 718 ;
+C 90 ; WX 611 ; N Z ; B 25 0 586 718 ;
+C 91 ; WX 333 ; N bracketleft ; B 63 -196 309 722 ;
+C 92 ; WX 278 ; N backslash ; B -33 -19 311 737 ;
+C 93 ; WX 333 ; N bracketright ; B 24 -196 270 722 ;
+C 94 ; WX 584 ; N asciicircum ; B 62 323 522 698 ;
+C 95 ; WX 556 ; N underscore ; B 0 -125 556 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 69 454 209 727 ;
+C 97 ; WX 556 ; N a ; B 29 -14 527 546 ;
+C 98 ; WX 611 ; N b ; B 61 -14 578 718 ;
+C 99 ; WX 556 ; N c ; B 34 -14 524 546 ;
+C 100 ; WX 611 ; N d ; B 34 -14 551 718 ;
+C 101 ; WX 556 ; N e ; B 23 -14 528 546 ;
+C 102 ; WX 333 ; N f ; B 10 0 318 727 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B 40 -217 553 546 ;
+C 104 ; WX 611 ; N h ; B 65 0 546 718 ;
+C 105 ; WX 278 ; N i ; B 69 0 209 725 ;
+C 106 ; WX 278 ; N j ; B 3 -214 209 725 ;
+C 107 ; WX 556 ; N k ; B 69 0 562 718 ;
+C 108 ; WX 278 ; N l ; B 69 0 209 718 ;
+C 109 ; WX 889 ; N m ; B 64 0 826 546 ;
+C 110 ; WX 611 ; N n ; B 65 0 546 546 ;
+C 111 ; WX 611 ; N o ; B 34 -14 578 546 ;
+C 112 ; WX 611 ; N p ; B 62 -207 578 546 ;
+C 113 ; WX 611 ; N q ; B 34 -207 552 546 ;
+C 114 ; WX 389 ; N r ; B 64 0 373 546 ;
+C 115 ; WX 556 ; N s ; B 30 -14 519 546 ;
+C 116 ; WX 333 ; N t ; B 10 -6 309 676 ;
+C 117 ; WX 611 ; N u ; B 66 -14 545 532 ;
+C 118 ; WX 556 ; N v ; B 13 0 543 532 ;
+C 119 ; WX 778 ; N w ; B 10 0 769 532 ;
+C 120 ; WX 556 ; N x ; B 15 0 541 532 ;
+C 121 ; WX 556 ; N y ; B 10 -214 539 532 ;
+C 122 ; WX 500 ; N z ; B 20 0 480 532 ;
+C 123 ; WX 389 ; N braceleft ; B 48 -196 365 722 ;
+C 124 ; WX 280 ; N bar ; B 84 -225 196 775 ;
+C 125 ; WX 389 ; N braceright ; B 24 -196 341 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 61 163 523 343 ;
+C 161 ; WX 333 ; N exclamdown ; B 90 -186 244 532 ;
+C 162 ; WX 556 ; N cent ; B 34 -118 524 628 ;
+C 163 ; WX 556 ; N sterling ; B 28 -16 541 718 ;
+C 164 ; WX 167 ; N fraction ; B -170 -19 336 710 ;
+C 165 ; WX 556 ; N yen ; B -9 0 565 698 ;
+C 166 ; WX 556 ; N florin ; B -10 -210 516 737 ;
+C 167 ; WX 556 ; N section ; B 34 -184 522 727 ;
+C 168 ; WX 556 ; N currency ; B -3 76 559 636 ;
+C 169 ; WX 238 ; N quotesingle ; B 70 447 168 718 ;
+C 170 ; WX 500 ; N quotedblleft ; B 64 454 436 727 ;
+C 171 ; WX 556 ; N guillemotleft ; B 88 76 468 484 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 83 76 250 484 ;
+C 173 ; WX 333 ; N guilsinglright ; B 83 76 250 484 ;
+C 174 ; WX 611 ; N fi ; B 10 0 542 727 ;
+C 175 ; WX 611 ; N fl ; B 10 0 542 727 ;
+C 177 ; WX 556 ; N endash ; B 0 227 556 333 ;
+C 178 ; WX 556 ; N dagger ; B 36 -171 520 718 ;
+C 179 ; WX 556 ; N daggerdbl ; B 36 -171 520 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 58 172 220 334 ;
+C 182 ; WX 556 ; N paragraph ; B -8 -191 539 700 ;
+C 183 ; WX 350 ; N bullet ; B 10 194 340 524 ;
+C 184 ; WX 278 ; N quotesinglbase ; B 69 -146 209 127 ;
+C 185 ; WX 500 ; N quotedblbase ; B 64 -146 436 127 ;
+C 186 ; WX 500 ; N quotedblright ; B 64 445 436 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 88 76 468 484 ;
+C 188 ; WX 1000 ; N ellipsis ; B 92 0 908 146 ;
+C 189 ; WX 1000 ; N perthousand ; B -3 -19 1003 710 ;
+C 191 ; WX 611 ; N questiondown ; B 55 -195 551 532 ;
+C 193 ; WX 333 ; N grave ; B -23 604 225 750 ;
+C 194 ; WX 333 ; N acute ; B 108 604 356 750 ;
+C 195 ; WX 333 ; N circumflex ; B -10 604 343 750 ;
+C 196 ; WX 333 ; N tilde ; B -17 610 350 737 ;
+C 197 ; WX 333 ; N macron ; B -6 604 339 678 ;
+C 198 ; WX 333 ; N breve ; B -2 604 335 750 ;
+C 199 ; WX 333 ; N dotaccent ; B 104 614 230 729 ;
+C 200 ; WX 333 ; N dieresis ; B 6 614 327 729 ;
+C 202 ; WX 333 ; N ring ; B 59 568 275 776 ;
+C 203 ; WX 333 ; N cedilla ; B 6 -228 245 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 9 604 486 750 ;
+C 206 ; WX 333 ; N ogonek ; B 71 -228 304 0 ;
+C 207 ; WX 333 ; N caron ; B -10 604 343 750 ;
+C 208 ; WX 1000 ; N emdash ; B 0 227 1000 333 ;
+C 225 ; WX 1000 ; N AE ; B 5 0 954 718 ;
+C 227 ; WX 370 ; N ordfeminine ; B 22 401 347 737 ;
+C 232 ; WX 611 ; N Lslash ; B -20 0 583 718 ;
+C 233 ; WX 778 ; N Oslash ; B 33 -27 744 745 ;
+C 234 ; WX 1000 ; N OE ; B 37 -19 961 737 ;
+C 235 ; WX 365 ; N ordmasculine ; B 6 401 360 737 ;
+C 241 ; WX 889 ; N ae ; B 29 -14 858 546 ;
+C 245 ; WX 278 ; N dotlessi ; B 69 0 209 532 ;
+C 248 ; WX 278 ; N lslash ; B -18 0 296 718 ;
+C 249 ; WX 611 ; N oslash ; B 22 -29 589 560 ;
+C 250 ; WX 944 ; N oe ; B 34 -14 912 546 ;
+C 251 ; WX 611 ; N germandbls ; B 69 -14 579 731 ;
+C -1 ; WX 278 ; N Idieresis ; B -21 0 300 915 ;
+C -1 ; WX 556 ; N eacute ; B 23 -14 528 750 ;
+C -1 ; WX 556 ; N abreve ; B 29 -14 527 750 ;
+C -1 ; WX 611 ; N uhungarumlaut ; B 66 -14 625 750 ;
+C -1 ; WX 556 ; N ecaron ; B 23 -14 528 750 ;
+C -1 ; WX 667 ; N Ydieresis ; B 15 0 653 915 ;
+C -1 ; WX 584 ; N divide ; B 40 -42 544 548 ;
+C -1 ; WX 667 ; N Yacute ; B 15 0 653 936 ;
+C -1 ; WX 722 ; N Acircumflex ; B 20 0 702 936 ;
+C -1 ; WX 556 ; N aacute ; B 29 -14 527 750 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 72 -19 651 936 ;
+C -1 ; WX 556 ; N yacute ; B 10 -214 539 750 ;
+C -1 ; WX 556 ; N scommaaccent ; B 30 -228 519 546 ;
+C -1 ; WX 556 ; N ecircumflex ; B 23 -14 528 750 ;
+C -1 ; WX 722 ; N Uring ; B 72 -19 651 962 ;
+C -1 ; WX 722 ; N Udieresis ; B 72 -19 651 915 ;
+C -1 ; WX 556 ; N aogonek ; B 29 -224 545 546 ;
+C -1 ; WX 722 ; N Uacute ; B 72 -19 651 936 ;
+C -1 ; WX 611 ; N uogonek ; B 66 -228 545 532 ;
+C -1 ; WX 667 ; N Edieresis ; B 76 0 621 915 ;
+C -1 ; WX 722 ; N Dcroat ; B -5 0 685 718 ;
+C -1 ; WX 250 ; N commaaccent ; B 64 -228 199 -50 ;
+C -1 ; WX 737 ; N copyright ; B -11 -19 749 737 ;
+C -1 ; WX 667 ; N Emacron ; B 76 0 621 864 ;
+C -1 ; WX 556 ; N ccaron ; B 34 -14 524 750 ;
+C -1 ; WX 556 ; N aring ; B 29 -14 527 776 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 69 -228 654 718 ;
+C -1 ; WX 278 ; N lacute ; B 69 0 329 936 ;
+C -1 ; WX 556 ; N agrave ; B 29 -14 527 750 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 14 -228 598 718 ;
+C -1 ; WX 722 ; N Cacute ; B 44 -19 684 936 ;
+C -1 ; WX 556 ; N atilde ; B 29 -14 527 737 ;
+C -1 ; WX 667 ; N Edotaccent ; B 76 0 621 915 ;
+C -1 ; WX 556 ; N scaron ; B 30 -14 519 750 ;
+C -1 ; WX 556 ; N scedilla ; B 30 -228 519 546 ;
+C -1 ; WX 278 ; N iacute ; B 69 0 329 750 ;
+C -1 ; WX 494 ; N lozenge ; B 10 0 484 745 ;
+C -1 ; WX 722 ; N Rcaron ; B 76 0 677 936 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 44 -228 713 737 ;
+C -1 ; WX 611 ; N ucircumflex ; B 66 -14 545 750 ;
+C -1 ; WX 556 ; N acircumflex ; B 29 -14 527 750 ;
+C -1 ; WX 722 ; N Amacron ; B 20 0 702 864 ;
+C -1 ; WX 389 ; N rcaron ; B 18 0 373 750 ;
+C -1 ; WX 556 ; N ccedilla ; B 34 -228 524 546 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 25 0 586 915 ;
+C -1 ; WX 667 ; N Thorn ; B 76 0 627 718 ;
+C -1 ; WX 778 ; N Omacron ; B 44 -19 734 864 ;
+C -1 ; WX 722 ; N Racute ; B 76 0 677 936 ;
+C -1 ; WX 667 ; N Sacute ; B 39 -19 629 936 ;
+C -1 ; WX 743 ; N dcaron ; B 34 -14 750 718 ;
+C -1 ; WX 722 ; N Umacron ; B 72 -19 651 864 ;
+C -1 ; WX 611 ; N uring ; B 66 -14 545 776 ;
+C -1 ; WX 333 ; N threesuperior ; B 8 271 326 710 ;
+C -1 ; WX 778 ; N Ograve ; B 44 -19 734 936 ;
+C -1 ; WX 722 ; N Agrave ; B 20 0 702 936 ;
+C -1 ; WX 722 ; N Abreve ; B 20 0 702 936 ;
+C -1 ; WX 584 ; N multiply ; B 40 1 545 505 ;
+C -1 ; WX 611 ; N uacute ; B 66 -14 545 750 ;
+C -1 ; WX 611 ; N Tcaron ; B 14 0 598 936 ;
+C -1 ; WX 494 ; N partialdiff ; B 11 -21 494 750 ;
+C -1 ; WX 556 ; N ydieresis ; B 10 -214 539 729 ;
+C -1 ; WX 722 ; N Nacute ; B 69 0 654 936 ;
+C -1 ; WX 278 ; N icircumflex ; B -37 0 316 750 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 76 0 621 936 ;
+C -1 ; WX 556 ; N adieresis ; B 29 -14 527 729 ;
+C -1 ; WX 556 ; N edieresis ; B 23 -14 528 729 ;
+C -1 ; WX 556 ; N cacute ; B 34 -14 524 750 ;
+C -1 ; WX 611 ; N nacute ; B 65 0 546 750 ;
+C -1 ; WX 611 ; N umacron ; B 66 -14 545 678 ;
+C -1 ; WX 722 ; N Ncaron ; B 69 0 654 936 ;
+C -1 ; WX 278 ; N Iacute ; B 64 0 329 936 ;
+C -1 ; WX 584 ; N plusminus ; B 40 0 544 506 ;
+C -1 ; WX 280 ; N brokenbar ; B 84 -150 196 700 ;
+C -1 ; WX 737 ; N registered ; B -11 -19 748 737 ;
+C -1 ; WX 778 ; N Gbreve ; B 44 -19 713 936 ;
+C -1 ; WX 278 ; N Idotaccent ; B 64 0 214 915 ;
+C -1 ; WX 600 ; N summation ; B 14 -10 585 706 ;
+C -1 ; WX 667 ; N Egrave ; B 76 0 621 936 ;
+C -1 ; WX 389 ; N racute ; B 64 0 384 750 ;
+C -1 ; WX 611 ; N omacron ; B 34 -14 578 678 ;
+C -1 ; WX 611 ; N Zacute ; B 25 0 586 936 ;
+C -1 ; WX 611 ; N Zcaron ; B 25 0 586 936 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 704 ;
+C -1 ; WX 722 ; N Eth ; B -5 0 685 718 ;
+C -1 ; WX 722 ; N Ccedilla ; B 44 -228 684 737 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 69 -228 213 718 ;
+C -1 ; WX 389 ; N tcaron ; B 10 -6 421 878 ;
+C -1 ; WX 556 ; N eogonek ; B 23 -228 528 546 ;
+C -1 ; WX 722 ; N Uogonek ; B 72 -228 651 718 ;
+C -1 ; WX 722 ; N Aacute ; B 20 0 702 936 ;
+C -1 ; WX 722 ; N Adieresis ; B 20 0 702 915 ;
+C -1 ; WX 556 ; N egrave ; B 23 -14 528 750 ;
+C -1 ; WX 500 ; N zacute ; B 20 0 480 750 ;
+C -1 ; WX 278 ; N iogonek ; B 16 -224 249 725 ;
+C -1 ; WX 778 ; N Oacute ; B 44 -19 734 936 ;
+C -1 ; WX 611 ; N oacute ; B 34 -14 578 750 ;
+C -1 ; WX 556 ; N amacron ; B 29 -14 527 678 ;
+C -1 ; WX 556 ; N sacute ; B 30 -14 519 750 ;
+C -1 ; WX 278 ; N idieresis ; B -21 0 300 729 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 44 -19 734 936 ;
+C -1 ; WX 722 ; N Ugrave ; B 72 -19 651 936 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C -1 ; WX 611 ; N thorn ; B 62 -208 578 718 ;
+C -1 ; WX 333 ; N twosuperior ; B 9 283 324 710 ;
+C -1 ; WX 778 ; N Odieresis ; B 44 -19 734 915 ;
+C -1 ; WX 611 ; N mu ; B 66 -207 545 532 ;
+C -1 ; WX 278 ; N igrave ; B -50 0 209 750 ;
+C -1 ; WX 611 ; N ohungarumlaut ; B 34 -14 625 750 ;
+C -1 ; WX 667 ; N Eogonek ; B 76 -224 639 718 ;
+C -1 ; WX 611 ; N dcroat ; B 34 -14 650 718 ;
+C -1 ; WX 834 ; N threequarters ; B 16 -19 799 710 ;
+C -1 ; WX 667 ; N Scedilla ; B 39 -228 629 737 ;
+C -1 ; WX 400 ; N lcaron ; B 69 0 408 718 ;
+C -1 ; WX 722 ; N Kcommaaccent ; B 87 -228 722 718 ;
+C -1 ; WX 611 ; N Lacute ; B 76 0 583 936 ;
+C -1 ; WX 1000 ; N trademark ; B 44 306 956 718 ;
+C -1 ; WX 556 ; N edotaccent ; B 23 -14 528 729 ;
+C -1 ; WX 278 ; N Igrave ; B -50 0 214 936 ;
+C -1 ; WX 278 ; N Imacron ; B -33 0 312 864 ;
+C -1 ; WX 611 ; N Lcaron ; B 76 0 583 718 ;
+C -1 ; WX 834 ; N onehalf ; B 26 -19 794 710 ;
+C -1 ; WX 549 ; N lessequal ; B 29 0 526 704 ;
+C -1 ; WX 611 ; N ocircumflex ; B 34 -14 578 750 ;
+C -1 ; WX 611 ; N ntilde ; B 65 0 546 737 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 72 -19 681 936 ;
+C -1 ; WX 667 ; N Eacute ; B 76 0 621 936 ;
+C -1 ; WX 556 ; N emacron ; B 23 -14 528 678 ;
+C -1 ; WX 611 ; N gbreve ; B 40 -217 553 750 ;
+C -1 ; WX 834 ; N onequarter ; B 26 -19 766 710 ;
+C -1 ; WX 667 ; N Scaron ; B 39 -19 629 936 ;
+C -1 ; WX 667 ; N Scommaaccent ; B 39 -228 629 737 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 44 -19 734 936 ;
+C -1 ; WX 400 ; N degree ; B 57 426 343 712 ;
+C -1 ; WX 611 ; N ograve ; B 34 -14 578 750 ;
+C -1 ; WX 722 ; N Ccaron ; B 44 -19 684 936 ;
+C -1 ; WX 611 ; N ugrave ; B 66 -14 545 750 ;
+C -1 ; WX 549 ; N radical ; B 10 -46 512 850 ;
+C -1 ; WX 722 ; N Dcaron ; B 76 0 685 936 ;
+C -1 ; WX 389 ; N rcommaaccent ; B 64 -228 373 546 ;
+C -1 ; WX 722 ; N Ntilde ; B 69 0 654 923 ;
+C -1 ; WX 611 ; N otilde ; B 34 -14 578 737 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 76 -228 677 718 ;
+C -1 ; WX 611 ; N Lcommaaccent ; B 76 -228 583 718 ;
+C -1 ; WX 722 ; N Atilde ; B 20 0 702 923 ;
+C -1 ; WX 722 ; N Aogonek ; B 20 -224 742 718 ;
+C -1 ; WX 722 ; N Aring ; B 20 0 702 962 ;
+C -1 ; WX 778 ; N Otilde ; B 44 -19 734 923 ;
+C -1 ; WX 500 ; N zdotaccent ; B 20 0 480 729 ;
+C -1 ; WX 667 ; N Ecaron ; B 76 0 621 936 ;
+C -1 ; WX 278 ; N Iogonek ; B -11 -228 222 718 ;
+C -1 ; WX 556 ; N kcommaaccent ; B 69 -228 562 718 ;
+C -1 ; WX 584 ; N minus ; B 40 197 544 309 ;
+C -1 ; WX 278 ; N Icircumflex ; B -37 0 316 936 ;
+C -1 ; WX 611 ; N ncaron ; B 65 0 546 750 ;
+C -1 ; WX 333 ; N tcommaaccent ; B 10 -228 309 676 ;
+C -1 ; WX 584 ; N logicalnot ; B 40 108 544 419 ;
+C -1 ; WX 611 ; N odieresis ; B 34 -14 578 729 ;
+C -1 ; WX 611 ; N udieresis ; B 66 -14 545 729 ;
+C -1 ; WX 549 ; N notequal ; B 15 -49 540 570 ;
+C -1 ; WX 611 ; N gcommaaccent ; B 40 -217 553 850 ;
+C -1 ; WX 611 ; N eth ; B 34 -14 578 737 ;
+C -1 ; WX 500 ; N zcaron ; B 20 0 480 750 ;
+C -1 ; WX 611 ; N ncommaaccent ; B 65 -228 546 546 ;
+C -1 ; WX 333 ; N onesuperior ; B 26 283 237 710 ;
+C -1 ; WX 278 ; N imacron ; B -8 0 285 678 ;
+C -1 ; WX 556 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2481
+KPX A C -40
+KPX A Cacute -40
+KPX A Ccaron -40
+KPX A Ccedilla -40
+KPX A G -50
+KPX A Gbreve -50
+KPX A Gcommaaccent -50
+KPX A O -40
+KPX A Oacute -40
+KPX A Ocircumflex -40
+KPX A Odieresis -40
+KPX A Ograve -40
+KPX A Ohungarumlaut -40
+KPX A Omacron -40
+KPX A Oslash -40
+KPX A Otilde -40
+KPX A Q -40
+KPX A T -90
+KPX A Tcaron -90
+KPX A Tcommaaccent -90
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -80
+KPX A W -60
+KPX A Y -110
+KPX A Yacute -110
+KPX A Ydieresis -110
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -40
+KPX A w -30
+KPX A y -30
+KPX A yacute -30
+KPX A ydieresis -30
+KPX Aacute C -40
+KPX Aacute Cacute -40
+KPX Aacute Ccaron -40
+KPX Aacute Ccedilla -40
+KPX Aacute G -50
+KPX Aacute Gbreve -50
+KPX Aacute Gcommaaccent -50
+KPX Aacute O -40
+KPX Aacute Oacute -40
+KPX Aacute Ocircumflex -40
+KPX Aacute Odieresis -40
+KPX Aacute Ograve -40
+KPX Aacute Ohungarumlaut -40
+KPX Aacute Omacron -40
+KPX Aacute Oslash -40
+KPX Aacute Otilde -40
+KPX Aacute Q -40
+KPX Aacute T -90
+KPX Aacute Tcaron -90
+KPX Aacute Tcommaaccent -90
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -80
+KPX Aacute W -60
+KPX Aacute Y -110
+KPX Aacute Yacute -110
+KPX Aacute Ydieresis -110
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -40
+KPX Aacute w -30
+KPX Aacute y -30
+KPX Aacute yacute -30
+KPX Aacute ydieresis -30
+KPX Abreve C -40
+KPX Abreve Cacute -40
+KPX Abreve Ccaron -40
+KPX Abreve Ccedilla -40
+KPX Abreve G -50
+KPX Abreve Gbreve -50
+KPX Abreve Gcommaaccent -50
+KPX Abreve O -40
+KPX Abreve Oacute -40
+KPX Abreve Ocircumflex -40
+KPX Abreve Odieresis -40
+KPX Abreve Ograve -40
+KPX Abreve Ohungarumlaut -40
+KPX Abreve Omacron -40
+KPX Abreve Oslash -40
+KPX Abreve Otilde -40
+KPX Abreve Q -40
+KPX Abreve T -90
+KPX Abreve Tcaron -90
+KPX Abreve Tcommaaccent -90
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -80
+KPX Abreve W -60
+KPX Abreve Y -110
+KPX Abreve Yacute -110
+KPX Abreve Ydieresis -110
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -40
+KPX Abreve w -30
+KPX Abreve y -30
+KPX Abreve yacute -30
+KPX Abreve ydieresis -30
+KPX Acircumflex C -40
+KPX Acircumflex Cacute -40
+KPX Acircumflex Ccaron -40
+KPX Acircumflex Ccedilla -40
+KPX Acircumflex G -50
+KPX Acircumflex Gbreve -50
+KPX Acircumflex Gcommaaccent -50
+KPX Acircumflex O -40
+KPX Acircumflex Oacute -40
+KPX Acircumflex Ocircumflex -40
+KPX Acircumflex Odieresis -40
+KPX Acircumflex Ograve -40
+KPX Acircumflex Ohungarumlaut -40
+KPX Acircumflex Omacron -40
+KPX Acircumflex Oslash -40
+KPX Acircumflex Otilde -40
+KPX Acircumflex Q -40
+KPX Acircumflex T -90
+KPX Acircumflex Tcaron -90
+KPX Acircumflex Tcommaaccent -90
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -80
+KPX Acircumflex W -60
+KPX Acircumflex Y -110
+KPX Acircumflex Yacute -110
+KPX Acircumflex Ydieresis -110
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -40
+KPX Acircumflex w -30
+KPX Acircumflex y -30
+KPX Acircumflex yacute -30
+KPX Acircumflex ydieresis -30
+KPX Adieresis C -40
+KPX Adieresis Cacute -40
+KPX Adieresis Ccaron -40
+KPX Adieresis Ccedilla -40
+KPX Adieresis G -50
+KPX Adieresis Gbreve -50
+KPX Adieresis Gcommaaccent -50
+KPX Adieresis O -40
+KPX Adieresis Oacute -40
+KPX Adieresis Ocircumflex -40
+KPX Adieresis Odieresis -40
+KPX Adieresis Ograve -40
+KPX Adieresis Ohungarumlaut -40
+KPX Adieresis Omacron -40
+KPX Adieresis Oslash -40
+KPX Adieresis Otilde -40
+KPX Adieresis Q -40
+KPX Adieresis T -90
+KPX Adieresis Tcaron -90
+KPX Adieresis Tcommaaccent -90
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -80
+KPX Adieresis W -60
+KPX Adieresis Y -110
+KPX Adieresis Yacute -110
+KPX Adieresis Ydieresis -110
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -40
+KPX Adieresis w -30
+KPX Adieresis y -30
+KPX Adieresis yacute -30
+KPX Adieresis ydieresis -30
+KPX Agrave C -40
+KPX Agrave Cacute -40
+KPX Agrave Ccaron -40
+KPX Agrave Ccedilla -40
+KPX Agrave G -50
+KPX Agrave Gbreve -50
+KPX Agrave Gcommaaccent -50
+KPX Agrave O -40
+KPX Agrave Oacute -40
+KPX Agrave Ocircumflex -40
+KPX Agrave Odieresis -40
+KPX Agrave Ograve -40
+KPX Agrave Ohungarumlaut -40
+KPX Agrave Omacron -40
+KPX Agrave Oslash -40
+KPX Agrave Otilde -40
+KPX Agrave Q -40
+KPX Agrave T -90
+KPX Agrave Tcaron -90
+KPX Agrave Tcommaaccent -90
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -80
+KPX Agrave W -60
+KPX Agrave Y -110
+KPX Agrave Yacute -110
+KPX Agrave Ydieresis -110
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -40
+KPX Agrave w -30
+KPX Agrave y -30
+KPX Agrave yacute -30
+KPX Agrave ydieresis -30
+KPX Amacron C -40
+KPX Amacron Cacute -40
+KPX Amacron Ccaron -40
+KPX Amacron Ccedilla -40
+KPX Amacron G -50
+KPX Amacron Gbreve -50
+KPX Amacron Gcommaaccent -50
+KPX Amacron O -40
+KPX Amacron Oacute -40
+KPX Amacron Ocircumflex -40
+KPX Amacron Odieresis -40
+KPX Amacron Ograve -40
+KPX Amacron Ohungarumlaut -40
+KPX Amacron Omacron -40
+KPX Amacron Oslash -40
+KPX Amacron Otilde -40
+KPX Amacron Q -40
+KPX Amacron T -90
+KPX Amacron Tcaron -90
+KPX Amacron Tcommaaccent -90
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -80
+KPX Amacron W -60
+KPX Amacron Y -110
+KPX Amacron Yacute -110
+KPX Amacron Ydieresis -110
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -40
+KPX Amacron w -30
+KPX Amacron y -30
+KPX Amacron yacute -30
+KPX Amacron ydieresis -30
+KPX Aogonek C -40
+KPX Aogonek Cacute -40
+KPX Aogonek Ccaron -40
+KPX Aogonek Ccedilla -40
+KPX Aogonek G -50
+KPX Aogonek Gbreve -50
+KPX Aogonek Gcommaaccent -50
+KPX Aogonek O -40
+KPX Aogonek Oacute -40
+KPX Aogonek Ocircumflex -40
+KPX Aogonek Odieresis -40
+KPX Aogonek Ograve -40
+KPX Aogonek Ohungarumlaut -40
+KPX Aogonek Omacron -40
+KPX Aogonek Oslash -40
+KPX Aogonek Otilde -40
+KPX Aogonek Q -40
+KPX Aogonek T -90
+KPX Aogonek Tcaron -90
+KPX Aogonek Tcommaaccent -90
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -80
+KPX Aogonek W -60
+KPX Aogonek Y -110
+KPX Aogonek Yacute -110
+KPX Aogonek Ydieresis -110
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -40
+KPX Aogonek w -30
+KPX Aogonek y -30
+KPX Aogonek yacute -30
+KPX Aogonek ydieresis -30
+KPX Aring C -40
+KPX Aring Cacute -40
+KPX Aring Ccaron -40
+KPX Aring Ccedilla -40
+KPX Aring G -50
+KPX Aring Gbreve -50
+KPX Aring Gcommaaccent -50
+KPX Aring O -40
+KPX Aring Oacute -40
+KPX Aring Ocircumflex -40
+KPX Aring Odieresis -40
+KPX Aring Ograve -40
+KPX Aring Ohungarumlaut -40
+KPX Aring Omacron -40
+KPX Aring Oslash -40
+KPX Aring Otilde -40
+KPX Aring Q -40
+KPX Aring T -90
+KPX Aring Tcaron -90
+KPX Aring Tcommaaccent -90
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -80
+KPX Aring W -60
+KPX Aring Y -110
+KPX Aring Yacute -110
+KPX Aring Ydieresis -110
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -40
+KPX Aring w -30
+KPX Aring y -30
+KPX Aring yacute -30
+KPX Aring ydieresis -30
+KPX Atilde C -40
+KPX Atilde Cacute -40
+KPX Atilde Ccaron -40
+KPX Atilde Ccedilla -40
+KPX Atilde G -50
+KPX Atilde Gbreve -50
+KPX Atilde Gcommaaccent -50
+KPX Atilde O -40
+KPX Atilde Oacute -40
+KPX Atilde Ocircumflex -40
+KPX Atilde Odieresis -40
+KPX Atilde Ograve -40
+KPX Atilde Ohungarumlaut -40
+KPX Atilde Omacron -40
+KPX Atilde Oslash -40
+KPX Atilde Otilde -40
+KPX Atilde Q -40
+KPX Atilde T -90
+KPX Atilde Tcaron -90
+KPX Atilde Tcommaaccent -90
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -80
+KPX Atilde W -60
+KPX Atilde Y -110
+KPX Atilde Yacute -110
+KPX Atilde Ydieresis -110
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -40
+KPX Atilde w -30
+KPX Atilde y -30
+KPX Atilde yacute -30
+KPX Atilde ydieresis -30
+KPX B A -30
+KPX B Aacute -30
+KPX B Abreve -30
+KPX B Acircumflex -30
+KPX B Adieresis -30
+KPX B Agrave -30
+KPX B Amacron -30
+KPX B Aogonek -30
+KPX B Aring -30
+KPX B Atilde -30
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -40
+KPX D W -40
+KPX D Y -70
+KPX D Yacute -70
+KPX D Ydieresis -70
+KPX D comma -30
+KPX D period -30
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -40
+KPX Dcaron W -40
+KPX Dcaron Y -70
+KPX Dcaron Yacute -70
+KPX Dcaron Ydieresis -70
+KPX Dcaron comma -30
+KPX Dcaron period -30
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -40
+KPX Dcroat W -40
+KPX Dcroat Y -70
+KPX Dcroat Yacute -70
+KPX Dcroat Ydieresis -70
+KPX Dcroat comma -30
+KPX Dcroat period -30
+KPX F A -80
+KPX F Aacute -80
+KPX F Abreve -80
+KPX F Acircumflex -80
+KPX F Adieresis -80
+KPX F Agrave -80
+KPX F Amacron -80
+KPX F Aogonek -80
+KPX F Aring -80
+KPX F Atilde -80
+KPX F a -20
+KPX F aacute -20
+KPX F abreve -20
+KPX F acircumflex -20
+KPX F adieresis -20
+KPX F agrave -20
+KPX F amacron -20
+KPX F aogonek -20
+KPX F aring -20
+KPX F atilde -20
+KPX F comma -100
+KPX F period -100
+KPX J A -20
+KPX J Aacute -20
+KPX J Abreve -20
+KPX J Acircumflex -20
+KPX J Adieresis -20
+KPX J Agrave -20
+KPX J Amacron -20
+KPX J Aogonek -20
+KPX J Aring -20
+KPX J Atilde -20
+KPX J comma -20
+KPX J period -20
+KPX J u -20
+KPX J uacute -20
+KPX J ucircumflex -20
+KPX J udieresis -20
+KPX J ugrave -20
+KPX J uhungarumlaut -20
+KPX J umacron -20
+KPX J uogonek -20
+KPX J uring -20
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -15
+KPX K eacute -15
+KPX K ecaron -15
+KPX K ecircumflex -15
+KPX K edieresis -15
+KPX K edotaccent -15
+KPX K egrave -15
+KPX K emacron -15
+KPX K eogonek -15
+KPX K o -35
+KPX K oacute -35
+KPX K ocircumflex -35
+KPX K odieresis -35
+KPX K ograve -35
+KPX K ohungarumlaut -35
+KPX K omacron -35
+KPX K oslash -35
+KPX K otilde -35
+KPX K u -30
+KPX K uacute -30
+KPX K ucircumflex -30
+KPX K udieresis -30
+KPX K ugrave -30
+KPX K uhungarumlaut -30
+KPX K umacron -30
+KPX K uogonek -30
+KPX K uring -30
+KPX K y -40
+KPX K yacute -40
+KPX K ydieresis -40
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -15
+KPX Kcommaaccent eacute -15
+KPX Kcommaaccent ecaron -15
+KPX Kcommaaccent ecircumflex -15
+KPX Kcommaaccent edieresis -15
+KPX Kcommaaccent edotaccent -15
+KPX Kcommaaccent egrave -15
+KPX Kcommaaccent emacron -15
+KPX Kcommaaccent eogonek -15
+KPX Kcommaaccent o -35
+KPX Kcommaaccent oacute -35
+KPX Kcommaaccent ocircumflex -35
+KPX Kcommaaccent odieresis -35
+KPX Kcommaaccent ograve -35
+KPX Kcommaaccent ohungarumlaut -35
+KPX Kcommaaccent omacron -35
+KPX Kcommaaccent oslash -35
+KPX Kcommaaccent otilde -35
+KPX Kcommaaccent u -30
+KPX Kcommaaccent uacute -30
+KPX Kcommaaccent ucircumflex -30
+KPX Kcommaaccent udieresis -30
+KPX Kcommaaccent ugrave -30
+KPX Kcommaaccent uhungarumlaut -30
+KPX Kcommaaccent umacron -30
+KPX Kcommaaccent uogonek -30
+KPX Kcommaaccent uring -30
+KPX Kcommaaccent y -40
+KPX Kcommaaccent yacute -40
+KPX Kcommaaccent ydieresis -40
+KPX L T -90
+KPX L Tcaron -90
+KPX L Tcommaaccent -90
+KPX L V -110
+KPX L W -80
+KPX L Y -120
+KPX L Yacute -120
+KPX L Ydieresis -120
+KPX L quotedblright -140
+KPX L quoteright -140
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -90
+KPX Lacute Tcaron -90
+KPX Lacute Tcommaaccent -90
+KPX Lacute V -110
+KPX Lacute W -80
+KPX Lacute Y -120
+KPX Lacute Yacute -120
+KPX Lacute Ydieresis -120
+KPX Lacute quotedblright -140
+KPX Lacute quoteright -140
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcommaaccent T -90
+KPX Lcommaaccent Tcaron -90
+KPX Lcommaaccent Tcommaaccent -90
+KPX Lcommaaccent V -110
+KPX Lcommaaccent W -80
+KPX Lcommaaccent Y -120
+KPX Lcommaaccent Yacute -120
+KPX Lcommaaccent Ydieresis -120
+KPX Lcommaaccent quotedblright -140
+KPX Lcommaaccent quoteright -140
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -90
+KPX Lslash Tcaron -90
+KPX Lslash Tcommaaccent -90
+KPX Lslash V -110
+KPX Lslash W -80
+KPX Lslash Y -120
+KPX Lslash Yacute -120
+KPX Lslash Ydieresis -120
+KPX Lslash quotedblright -140
+KPX Lslash quoteright -140
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX O A -50
+KPX O Aacute -50
+KPX O Abreve -50
+KPX O Acircumflex -50
+KPX O Adieresis -50
+KPX O Agrave -50
+KPX O Amacron -50
+KPX O Aogonek -50
+KPX O Aring -50
+KPX O Atilde -50
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -50
+KPX O Y -70
+KPX O Yacute -70
+KPX O Ydieresis -70
+KPX O comma -40
+KPX O period -40
+KPX Oacute A -50
+KPX Oacute Aacute -50
+KPX Oacute Abreve -50
+KPX Oacute Acircumflex -50
+KPX Oacute Adieresis -50
+KPX Oacute Agrave -50
+KPX Oacute Amacron -50
+KPX Oacute Aogonek -50
+KPX Oacute Aring -50
+KPX Oacute Atilde -50
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -50
+KPX Oacute Y -70
+KPX Oacute Yacute -70
+KPX Oacute Ydieresis -70
+KPX Oacute comma -40
+KPX Oacute period -40
+KPX Ocircumflex A -50
+KPX Ocircumflex Aacute -50
+KPX Ocircumflex Abreve -50
+KPX Ocircumflex Acircumflex -50
+KPX Ocircumflex Adieresis -50
+KPX Ocircumflex Agrave -50
+KPX Ocircumflex Amacron -50
+KPX Ocircumflex Aogonek -50
+KPX Ocircumflex Aring -50
+KPX Ocircumflex Atilde -50
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -50
+KPX Ocircumflex Y -70
+KPX Ocircumflex Yacute -70
+KPX Ocircumflex Ydieresis -70
+KPX Ocircumflex comma -40
+KPX Ocircumflex period -40
+KPX Odieresis A -50
+KPX Odieresis Aacute -50
+KPX Odieresis Abreve -50
+KPX Odieresis Acircumflex -50
+KPX Odieresis Adieresis -50
+KPX Odieresis Agrave -50
+KPX Odieresis Amacron -50
+KPX Odieresis Aogonek -50
+KPX Odieresis Aring -50
+KPX Odieresis Atilde -50
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -50
+KPX Odieresis Y -70
+KPX Odieresis Yacute -70
+KPX Odieresis Ydieresis -70
+KPX Odieresis comma -40
+KPX Odieresis period -40
+KPX Ograve A -50
+KPX Ograve Aacute -50
+KPX Ograve Abreve -50
+KPX Ograve Acircumflex -50
+KPX Ograve Adieresis -50
+KPX Ograve Agrave -50
+KPX Ograve Amacron -50
+KPX Ograve Aogonek -50
+KPX Ograve Aring -50
+KPX Ograve Atilde -50
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -50
+KPX Ograve Y -70
+KPX Ograve Yacute -70
+KPX Ograve Ydieresis -70
+KPX Ograve comma -40
+KPX Ograve period -40
+KPX Ohungarumlaut A -50
+KPX Ohungarumlaut Aacute -50
+KPX Ohungarumlaut Abreve -50
+KPX Ohungarumlaut Acircumflex -50
+KPX Ohungarumlaut Adieresis -50
+KPX Ohungarumlaut Agrave -50
+KPX Ohungarumlaut Amacron -50
+KPX Ohungarumlaut Aogonek -50
+KPX Ohungarumlaut Aring -50
+KPX Ohungarumlaut Atilde -50
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -50
+KPX Ohungarumlaut Y -70
+KPX Ohungarumlaut Yacute -70
+KPX Ohungarumlaut Ydieresis -70
+KPX Ohungarumlaut comma -40
+KPX Ohungarumlaut period -40
+KPX Omacron A -50
+KPX Omacron Aacute -50
+KPX Omacron Abreve -50
+KPX Omacron Acircumflex -50
+KPX Omacron Adieresis -50
+KPX Omacron Agrave -50
+KPX Omacron Amacron -50
+KPX Omacron Aogonek -50
+KPX Omacron Aring -50
+KPX Omacron Atilde -50
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -50
+KPX Omacron Y -70
+KPX Omacron Yacute -70
+KPX Omacron Ydieresis -70
+KPX Omacron comma -40
+KPX Omacron period -40
+KPX Oslash A -50
+KPX Oslash Aacute -50
+KPX Oslash Abreve -50
+KPX Oslash Acircumflex -50
+KPX Oslash Adieresis -50
+KPX Oslash Agrave -50
+KPX Oslash Amacron -50
+KPX Oslash Aogonek -50
+KPX Oslash Aring -50
+KPX Oslash Atilde -50
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -50
+KPX Oslash Y -70
+KPX Oslash Yacute -70
+KPX Oslash Ydieresis -70
+KPX Oslash comma -40
+KPX Oslash period -40
+KPX Otilde A -50
+KPX Otilde Aacute -50
+KPX Otilde Abreve -50
+KPX Otilde Acircumflex -50
+KPX Otilde Adieresis -50
+KPX Otilde Agrave -50
+KPX Otilde Amacron -50
+KPX Otilde Aogonek -50
+KPX Otilde Aring -50
+KPX Otilde Atilde -50
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -50
+KPX Otilde Y -70
+KPX Otilde Yacute -70
+KPX Otilde Ydieresis -70
+KPX Otilde comma -40
+KPX Otilde period -40
+KPX P A -100
+KPX P Aacute -100
+KPX P Abreve -100
+KPX P Acircumflex -100
+KPX P Adieresis -100
+KPX P Agrave -100
+KPX P Amacron -100
+KPX P Aogonek -100
+KPX P Aring -100
+KPX P Atilde -100
+KPX P a -30
+KPX P aacute -30
+KPX P abreve -30
+KPX P acircumflex -30
+KPX P adieresis -30
+KPX P agrave -30
+KPX P amacron -30
+KPX P aogonek -30
+KPX P aring -30
+KPX P atilde -30
+KPX P comma -120
+KPX P e -30
+KPX P eacute -30
+KPX P ecaron -30
+KPX P ecircumflex -30
+KPX P edieresis -30
+KPX P edotaccent -30
+KPX P egrave -30
+KPX P emacron -30
+KPX P eogonek -30
+KPX P o -40
+KPX P oacute -40
+KPX P ocircumflex -40
+KPX P odieresis -40
+KPX P ograve -40
+KPX P ohungarumlaut -40
+KPX P omacron -40
+KPX P oslash -40
+KPX P otilde -40
+KPX P period -120
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX Q comma 20
+KPX Q period 20
+KPX R O -20
+KPX R Oacute -20
+KPX R Ocircumflex -20
+KPX R Odieresis -20
+KPX R Ograve -20
+KPX R Ohungarumlaut -20
+KPX R Omacron -20
+KPX R Oslash -20
+KPX R Otilde -20
+KPX R T -20
+KPX R Tcaron -20
+KPX R Tcommaaccent -20
+KPX R U -20
+KPX R Uacute -20
+KPX R Ucircumflex -20
+KPX R Udieresis -20
+KPX R Ugrave -20
+KPX R Uhungarumlaut -20
+KPX R Umacron -20
+KPX R Uogonek -20
+KPX R Uring -20
+KPX R V -50
+KPX R W -40
+KPX R Y -50
+KPX R Yacute -50
+KPX R Ydieresis -50
+KPX Racute O -20
+KPX Racute Oacute -20
+KPX Racute Ocircumflex -20
+KPX Racute Odieresis -20
+KPX Racute Ograve -20
+KPX Racute Ohungarumlaut -20
+KPX Racute Omacron -20
+KPX Racute Oslash -20
+KPX Racute Otilde -20
+KPX Racute T -20
+KPX Racute Tcaron -20
+KPX Racute Tcommaaccent -20
+KPX Racute U -20
+KPX Racute Uacute -20
+KPX Racute Ucircumflex -20
+KPX Racute Udieresis -20
+KPX Racute Ugrave -20
+KPX Racute Uhungarumlaut -20
+KPX Racute Umacron -20
+KPX Racute Uogonek -20
+KPX Racute Uring -20
+KPX Racute V -50
+KPX Racute W -40
+KPX Racute Y -50
+KPX Racute Yacute -50
+KPX Racute Ydieresis -50
+KPX Rcaron O -20
+KPX Rcaron Oacute -20
+KPX Rcaron Ocircumflex -20
+KPX Rcaron Odieresis -20
+KPX Rcaron Ograve -20
+KPX Rcaron Ohungarumlaut -20
+KPX Rcaron Omacron -20
+KPX Rcaron Oslash -20
+KPX Rcaron Otilde -20
+KPX Rcaron T -20
+KPX Rcaron Tcaron -20
+KPX Rcaron Tcommaaccent -20
+KPX Rcaron U -20
+KPX Rcaron Uacute -20
+KPX Rcaron Ucircumflex -20
+KPX Rcaron Udieresis -20
+KPX Rcaron Ugrave -20
+KPX Rcaron Uhungarumlaut -20
+KPX Rcaron Umacron -20
+KPX Rcaron Uogonek -20
+KPX Rcaron Uring -20
+KPX Rcaron V -50
+KPX Rcaron W -40
+KPX Rcaron Y -50
+KPX Rcaron Yacute -50
+KPX Rcaron Ydieresis -50
+KPX Rcommaaccent O -20
+KPX Rcommaaccent Oacute -20
+KPX Rcommaaccent Ocircumflex -20
+KPX Rcommaaccent Odieresis -20
+KPX Rcommaaccent Ograve -20
+KPX Rcommaaccent Ohungarumlaut -20
+KPX Rcommaaccent Omacron -20
+KPX Rcommaaccent Oslash -20
+KPX Rcommaaccent Otilde -20
+KPX Rcommaaccent T -20
+KPX Rcommaaccent Tcaron -20
+KPX Rcommaaccent Tcommaaccent -20
+KPX Rcommaaccent U -20
+KPX Rcommaaccent Uacute -20
+KPX Rcommaaccent Ucircumflex -20
+KPX Rcommaaccent Udieresis -20
+KPX Rcommaaccent Ugrave -20
+KPX Rcommaaccent Uhungarumlaut -20
+KPX Rcommaaccent Umacron -20
+KPX Rcommaaccent Uogonek -20
+KPX Rcommaaccent Uring -20
+KPX Rcommaaccent V -50
+KPX Rcommaaccent W -40
+KPX Rcommaaccent Y -50
+KPX Rcommaaccent Yacute -50
+KPX Rcommaaccent Ydieresis -50
+KPX T A -90
+KPX T Aacute -90
+KPX T Abreve -90
+KPX T Acircumflex -90
+KPX T Adieresis -90
+KPX T Agrave -90
+KPX T Amacron -90
+KPX T Aogonek -90
+KPX T Aring -90
+KPX T Atilde -90
+KPX T O -40
+KPX T Oacute -40
+KPX T Ocircumflex -40
+KPX T Odieresis -40
+KPX T Ograve -40
+KPX T Ohungarumlaut -40
+KPX T Omacron -40
+KPX T Oslash -40
+KPX T Otilde -40
+KPX T a -80
+KPX T aacute -80
+KPX T abreve -80
+KPX T acircumflex -80
+KPX T adieresis -80
+KPX T agrave -80
+KPX T amacron -80
+KPX T aogonek -80
+KPX T aring -80
+KPX T atilde -80
+KPX T colon -40
+KPX T comma -80
+KPX T e -60
+KPX T eacute -60
+KPX T ecaron -60
+KPX T ecircumflex -60
+KPX T edieresis -60
+KPX T edotaccent -60
+KPX T egrave -60
+KPX T emacron -60
+KPX T eogonek -60
+KPX T hyphen -120
+KPX T o -80
+KPX T oacute -80
+KPX T ocircumflex -80
+KPX T odieresis -80
+KPX T ograve -80
+KPX T ohungarumlaut -80
+KPX T omacron -80
+KPX T oslash -80
+KPX T otilde -80
+KPX T period -80
+KPX T r -80
+KPX T racute -80
+KPX T rcommaaccent -80
+KPX T semicolon -40
+KPX T u -90
+KPX T uacute -90
+KPX T ucircumflex -90
+KPX T udieresis -90
+KPX T ugrave -90
+KPX T uhungarumlaut -90
+KPX T umacron -90
+KPX T uogonek -90
+KPX T uring -90
+KPX T w -60
+KPX T y -60
+KPX T yacute -60
+KPX T ydieresis -60
+KPX Tcaron A -90
+KPX Tcaron Aacute -90
+KPX Tcaron Abreve -90
+KPX Tcaron Acircumflex -90
+KPX Tcaron Adieresis -90
+KPX Tcaron Agrave -90
+KPX Tcaron Amacron -90
+KPX Tcaron Aogonek -90
+KPX Tcaron Aring -90
+KPX Tcaron Atilde -90
+KPX Tcaron O -40
+KPX Tcaron Oacute -40
+KPX Tcaron Ocircumflex -40
+KPX Tcaron Odieresis -40
+KPX Tcaron Ograve -40
+KPX Tcaron Ohungarumlaut -40
+KPX Tcaron Omacron -40
+KPX Tcaron Oslash -40
+KPX Tcaron Otilde -40
+KPX Tcaron a -80
+KPX Tcaron aacute -80
+KPX Tcaron abreve -80
+KPX Tcaron acircumflex -80
+KPX Tcaron adieresis -80
+KPX Tcaron agrave -80
+KPX Tcaron amacron -80
+KPX Tcaron aogonek -80
+KPX Tcaron aring -80
+KPX Tcaron atilde -80
+KPX Tcaron colon -40
+KPX Tcaron comma -80
+KPX Tcaron e -60
+KPX Tcaron eacute -60
+KPX Tcaron ecaron -60
+KPX Tcaron ecircumflex -60
+KPX Tcaron edieresis -60
+KPX Tcaron edotaccent -60
+KPX Tcaron egrave -60
+KPX Tcaron emacron -60
+KPX Tcaron eogonek -60
+KPX Tcaron hyphen -120
+KPX Tcaron o -80
+KPX Tcaron oacute -80
+KPX Tcaron ocircumflex -80
+KPX Tcaron odieresis -80
+KPX Tcaron ograve -80
+KPX Tcaron ohungarumlaut -80
+KPX Tcaron omacron -80
+KPX Tcaron oslash -80
+KPX Tcaron otilde -80
+KPX Tcaron period -80
+KPX Tcaron r -80
+KPX Tcaron racute -80
+KPX Tcaron rcommaaccent -80
+KPX Tcaron semicolon -40
+KPX Tcaron u -90
+KPX Tcaron uacute -90
+KPX Tcaron ucircumflex -90
+KPX Tcaron udieresis -90
+KPX Tcaron ugrave -90
+KPX Tcaron uhungarumlaut -90
+KPX Tcaron umacron -90
+KPX Tcaron uogonek -90
+KPX Tcaron uring -90
+KPX Tcaron w -60
+KPX Tcaron y -60
+KPX Tcaron yacute -60
+KPX Tcaron ydieresis -60
+KPX Tcommaaccent A -90
+KPX Tcommaaccent Aacute -90
+KPX Tcommaaccent Abreve -90
+KPX Tcommaaccent Acircumflex -90
+KPX Tcommaaccent Adieresis -90
+KPX Tcommaaccent Agrave -90
+KPX Tcommaaccent Amacron -90
+KPX Tcommaaccent Aogonek -90
+KPX Tcommaaccent Aring -90
+KPX Tcommaaccent Atilde -90
+KPX Tcommaaccent O -40
+KPX Tcommaaccent Oacute -40
+KPX Tcommaaccent Ocircumflex -40
+KPX Tcommaaccent Odieresis -40
+KPX Tcommaaccent Ograve -40
+KPX Tcommaaccent Ohungarumlaut -40
+KPX Tcommaaccent Omacron -40
+KPX Tcommaaccent Oslash -40
+KPX Tcommaaccent Otilde -40
+KPX Tcommaaccent a -80
+KPX Tcommaaccent aacute -80
+KPX Tcommaaccent abreve -80
+KPX Tcommaaccent acircumflex -80
+KPX Tcommaaccent adieresis -80
+KPX Tcommaaccent agrave -80
+KPX Tcommaaccent amacron -80
+KPX Tcommaaccent aogonek -80
+KPX Tcommaaccent aring -80
+KPX Tcommaaccent atilde -80
+KPX Tcommaaccent colon -40
+KPX Tcommaaccent comma -80
+KPX Tcommaaccent e -60
+KPX Tcommaaccent eacute -60
+KPX Tcommaaccent ecaron -60
+KPX Tcommaaccent ecircumflex -60
+KPX Tcommaaccent edieresis -60
+KPX Tcommaaccent edotaccent -60
+KPX Tcommaaccent egrave -60
+KPX Tcommaaccent emacron -60
+KPX Tcommaaccent eogonek -60
+KPX Tcommaaccent hyphen -120
+KPX Tcommaaccent o -80
+KPX Tcommaaccent oacute -80
+KPX Tcommaaccent ocircumflex -80
+KPX Tcommaaccent odieresis -80
+KPX Tcommaaccent ograve -80
+KPX Tcommaaccent ohungarumlaut -80
+KPX Tcommaaccent omacron -80
+KPX Tcommaaccent oslash -80
+KPX Tcommaaccent otilde -80
+KPX Tcommaaccent period -80
+KPX Tcommaaccent r -80
+KPX Tcommaaccent racute -80
+KPX Tcommaaccent rcommaaccent -80
+KPX Tcommaaccent semicolon -40
+KPX Tcommaaccent u -90
+KPX Tcommaaccent uacute -90
+KPX Tcommaaccent ucircumflex -90
+KPX Tcommaaccent udieresis -90
+KPX Tcommaaccent ugrave -90
+KPX Tcommaaccent uhungarumlaut -90
+KPX Tcommaaccent umacron -90
+KPX Tcommaaccent uogonek -90
+KPX Tcommaaccent uring -90
+KPX Tcommaaccent w -60
+KPX Tcommaaccent y -60
+KPX Tcommaaccent yacute -60
+KPX Tcommaaccent ydieresis -60
+KPX U A -50
+KPX U Aacute -50
+KPX U Abreve -50
+KPX U Acircumflex -50
+KPX U Adieresis -50
+KPX U Agrave -50
+KPX U Amacron -50
+KPX U Aogonek -50
+KPX U Aring -50
+KPX U Atilde -50
+KPX U comma -30
+KPX U period -30
+KPX Uacute A -50
+KPX Uacute Aacute -50
+KPX Uacute Abreve -50
+KPX Uacute Acircumflex -50
+KPX Uacute Adieresis -50
+KPX Uacute Agrave -50
+KPX Uacute Amacron -50
+KPX Uacute Aogonek -50
+KPX Uacute Aring -50
+KPX Uacute Atilde -50
+KPX Uacute comma -30
+KPX Uacute period -30
+KPX Ucircumflex A -50
+KPX Ucircumflex Aacute -50
+KPX Ucircumflex Abreve -50
+KPX Ucircumflex Acircumflex -50
+KPX Ucircumflex Adieresis -50
+KPX Ucircumflex Agrave -50
+KPX Ucircumflex Amacron -50
+KPX Ucircumflex Aogonek -50
+KPX Ucircumflex Aring -50
+KPX Ucircumflex Atilde -50
+KPX Ucircumflex comma -30
+KPX Ucircumflex period -30
+KPX Udieresis A -50
+KPX Udieresis Aacute -50
+KPX Udieresis Abreve -50
+KPX Udieresis Acircumflex -50
+KPX Udieresis Adieresis -50
+KPX Udieresis Agrave -50
+KPX Udieresis Amacron -50
+KPX Udieresis Aogonek -50
+KPX Udieresis Aring -50
+KPX Udieresis Atilde -50
+KPX Udieresis comma -30
+KPX Udieresis period -30
+KPX Ugrave A -50
+KPX Ugrave Aacute -50
+KPX Ugrave Abreve -50
+KPX Ugrave Acircumflex -50
+KPX Ugrave Adieresis -50
+KPX Ugrave Agrave -50
+KPX Ugrave Amacron -50
+KPX Ugrave Aogonek -50
+KPX Ugrave Aring -50
+KPX Ugrave Atilde -50
+KPX Ugrave comma -30
+KPX Ugrave period -30
+KPX Uhungarumlaut A -50
+KPX Uhungarumlaut Aacute -50
+KPX Uhungarumlaut Abreve -50
+KPX Uhungarumlaut Acircumflex -50
+KPX Uhungarumlaut Adieresis -50
+KPX Uhungarumlaut Agrave -50
+KPX Uhungarumlaut Amacron -50
+KPX Uhungarumlaut Aogonek -50
+KPX Uhungarumlaut Aring -50
+KPX Uhungarumlaut Atilde -50
+KPX Uhungarumlaut comma -30
+KPX Uhungarumlaut period -30
+KPX Umacron A -50
+KPX Umacron Aacute -50
+KPX Umacron Abreve -50
+KPX Umacron Acircumflex -50
+KPX Umacron Adieresis -50
+KPX Umacron Agrave -50
+KPX Umacron Amacron -50
+KPX Umacron Aogonek -50
+KPX Umacron Aring -50
+KPX Umacron Atilde -50
+KPX Umacron comma -30
+KPX Umacron period -30
+KPX Uogonek A -50
+KPX Uogonek Aacute -50
+KPX Uogonek Abreve -50
+KPX Uogonek Acircumflex -50
+KPX Uogonek Adieresis -50
+KPX Uogonek Agrave -50
+KPX Uogonek Amacron -50
+KPX Uogonek Aogonek -50
+KPX Uogonek Aring -50
+KPX Uogonek Atilde -50
+KPX Uogonek comma -30
+KPX Uogonek period -30
+KPX Uring A -50
+KPX Uring Aacute -50
+KPX Uring Abreve -50
+KPX Uring Acircumflex -50
+KPX Uring Adieresis -50
+KPX Uring Agrave -50
+KPX Uring Amacron -50
+KPX Uring Aogonek -50
+KPX Uring Aring -50
+KPX Uring Atilde -50
+KPX Uring comma -30
+KPX Uring period -30
+KPX V A -80
+KPX V Aacute -80
+KPX V Abreve -80
+KPX V Acircumflex -80
+KPX V Adieresis -80
+KPX V Agrave -80
+KPX V Amacron -80
+KPX V Aogonek -80
+KPX V Aring -80
+KPX V Atilde -80
+KPX V G -50
+KPX V Gbreve -50
+KPX V Gcommaaccent -50
+KPX V O -50
+KPX V Oacute -50
+KPX V Ocircumflex -50
+KPX V Odieresis -50
+KPX V Ograve -50
+KPX V Ohungarumlaut -50
+KPX V Omacron -50
+KPX V Oslash -50
+KPX V Otilde -50
+KPX V a -60
+KPX V aacute -60
+KPX V abreve -60
+KPX V acircumflex -60
+KPX V adieresis -60
+KPX V agrave -60
+KPX V amacron -60
+KPX V aogonek -60
+KPX V aring -60
+KPX V atilde -60
+KPX V colon -40
+KPX V comma -120
+KPX V e -50
+KPX V eacute -50
+KPX V ecaron -50
+KPX V ecircumflex -50
+KPX V edieresis -50
+KPX V edotaccent -50
+KPX V egrave -50
+KPX V emacron -50
+KPX V eogonek -50
+KPX V hyphen -80
+KPX V o -90
+KPX V oacute -90
+KPX V ocircumflex -90
+KPX V odieresis -90
+KPX V ograve -90
+KPX V ohungarumlaut -90
+KPX V omacron -90
+KPX V oslash -90
+KPX V otilde -90
+KPX V period -120
+KPX V semicolon -40
+KPX V u -60
+KPX V uacute -60
+KPX V ucircumflex -60
+KPX V udieresis -60
+KPX V ugrave -60
+KPX V uhungarumlaut -60
+KPX V umacron -60
+KPX V uogonek -60
+KPX V uring -60
+KPX W A -60
+KPX W Aacute -60
+KPX W Abreve -60
+KPX W Acircumflex -60
+KPX W Adieresis -60
+KPX W Agrave -60
+KPX W Amacron -60
+KPX W Aogonek -60
+KPX W Aring -60
+KPX W Atilde -60
+KPX W O -20
+KPX W Oacute -20
+KPX W Ocircumflex -20
+KPX W Odieresis -20
+KPX W Ograve -20
+KPX W Ohungarumlaut -20
+KPX W Omacron -20
+KPX W Oslash -20
+KPX W Otilde -20
+KPX W a -40
+KPX W aacute -40
+KPX W abreve -40
+KPX W acircumflex -40
+KPX W adieresis -40
+KPX W agrave -40
+KPX W amacron -40
+KPX W aogonek -40
+KPX W aring -40
+KPX W atilde -40
+KPX W colon -10
+KPX W comma -80
+KPX W e -35
+KPX W eacute -35
+KPX W ecaron -35
+KPX W ecircumflex -35
+KPX W edieresis -35
+KPX W edotaccent -35
+KPX W egrave -35
+KPX W emacron -35
+KPX W eogonek -35
+KPX W hyphen -40
+KPX W o -60
+KPX W oacute -60
+KPX W ocircumflex -60
+KPX W odieresis -60
+KPX W ograve -60
+KPX W ohungarumlaut -60
+KPX W omacron -60
+KPX W oslash -60
+KPX W otilde -60
+KPX W period -80
+KPX W semicolon -10
+KPX W u -45
+KPX W uacute -45
+KPX W ucircumflex -45
+KPX W udieresis -45
+KPX W ugrave -45
+KPX W uhungarumlaut -45
+KPX W umacron -45
+KPX W uogonek -45
+KPX W uring -45
+KPX W y -20
+KPX W yacute -20
+KPX W ydieresis -20
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -70
+KPX Y Oacute -70
+KPX Y Ocircumflex -70
+KPX Y Odieresis -70
+KPX Y Ograve -70
+KPX Y Ohungarumlaut -70
+KPX Y Omacron -70
+KPX Y Oslash -70
+KPX Y Otilde -70
+KPX Y a -90
+KPX Y aacute -90
+KPX Y abreve -90
+KPX Y acircumflex -90
+KPX Y adieresis -90
+KPX Y agrave -90
+KPX Y amacron -90
+KPX Y aogonek -90
+KPX Y aring -90
+KPX Y atilde -90
+KPX Y colon -50
+KPX Y comma -100
+KPX Y e -80
+KPX Y eacute -80
+KPX Y ecaron -80
+KPX Y ecircumflex -80
+KPX Y edieresis -80
+KPX Y edotaccent -80
+KPX Y egrave -80
+KPX Y emacron -80
+KPX Y eogonek -80
+KPX Y o -100
+KPX Y oacute -100
+KPX Y ocircumflex -100
+KPX Y odieresis -100
+KPX Y ograve -100
+KPX Y ohungarumlaut -100
+KPX Y omacron -100
+KPX Y oslash -100
+KPX Y otilde -100
+KPX Y period -100
+KPX Y semicolon -50
+KPX Y u -100
+KPX Y uacute -100
+KPX Y ucircumflex -100
+KPX Y udieresis -100
+KPX Y ugrave -100
+KPX Y uhungarumlaut -100
+KPX Y umacron -100
+KPX Y uogonek -100
+KPX Y uring -100
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -70
+KPX Yacute Oacute -70
+KPX Yacute Ocircumflex -70
+KPX Yacute Odieresis -70
+KPX Yacute Ograve -70
+KPX Yacute Ohungarumlaut -70
+KPX Yacute Omacron -70
+KPX Yacute Oslash -70
+KPX Yacute Otilde -70
+KPX Yacute a -90
+KPX Yacute aacute -90
+KPX Yacute abreve -90
+KPX Yacute acircumflex -90
+KPX Yacute adieresis -90
+KPX Yacute agrave -90
+KPX Yacute amacron -90
+KPX Yacute aogonek -90
+KPX Yacute aring -90
+KPX Yacute atilde -90
+KPX Yacute colon -50
+KPX Yacute comma -100
+KPX Yacute e -80
+KPX Yacute eacute -80
+KPX Yacute ecaron -80
+KPX Yacute ecircumflex -80
+KPX Yacute edieresis -80
+KPX Yacute edotaccent -80
+KPX Yacute egrave -80
+KPX Yacute emacron -80
+KPX Yacute eogonek -80
+KPX Yacute o -100
+KPX Yacute oacute -100
+KPX Yacute ocircumflex -100
+KPX Yacute odieresis -100
+KPX Yacute ograve -100
+KPX Yacute ohungarumlaut -100
+KPX Yacute omacron -100
+KPX Yacute oslash -100
+KPX Yacute otilde -100
+KPX Yacute period -100
+KPX Yacute semicolon -50
+KPX Yacute u -100
+KPX Yacute uacute -100
+KPX Yacute ucircumflex -100
+KPX Yacute udieresis -100
+KPX Yacute ugrave -100
+KPX Yacute uhungarumlaut -100
+KPX Yacute umacron -100
+KPX Yacute uogonek -100
+KPX Yacute uring -100
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -70
+KPX Ydieresis Oacute -70
+KPX Ydieresis Ocircumflex -70
+KPX Ydieresis Odieresis -70
+KPX Ydieresis Ograve -70
+KPX Ydieresis Ohungarumlaut -70
+KPX Ydieresis Omacron -70
+KPX Ydieresis Oslash -70
+KPX Ydieresis Otilde -70
+KPX Ydieresis a -90
+KPX Ydieresis aacute -90
+KPX Ydieresis abreve -90
+KPX Ydieresis acircumflex -90
+KPX Ydieresis adieresis -90
+KPX Ydieresis agrave -90
+KPX Ydieresis amacron -90
+KPX Ydieresis aogonek -90
+KPX Ydieresis aring -90
+KPX Ydieresis atilde -90
+KPX Ydieresis colon -50
+KPX Ydieresis comma -100
+KPX Ydieresis e -80
+KPX Ydieresis eacute -80
+KPX Ydieresis ecaron -80
+KPX Ydieresis ecircumflex -80
+KPX Ydieresis edieresis -80
+KPX Ydieresis edotaccent -80
+KPX Ydieresis egrave -80
+KPX Ydieresis emacron -80
+KPX Ydieresis eogonek -80
+KPX Ydieresis o -100
+KPX Ydieresis oacute -100
+KPX Ydieresis ocircumflex -100
+KPX Ydieresis odieresis -100
+KPX Ydieresis ograve -100
+KPX Ydieresis ohungarumlaut -100
+KPX Ydieresis omacron -100
+KPX Ydieresis oslash -100
+KPX Ydieresis otilde -100
+KPX Ydieresis period -100
+KPX Ydieresis semicolon -50
+KPX Ydieresis u -100
+KPX Ydieresis uacute -100
+KPX Ydieresis ucircumflex -100
+KPX Ydieresis udieresis -100
+KPX Ydieresis ugrave -100
+KPX Ydieresis uhungarumlaut -100
+KPX Ydieresis umacron -100
+KPX Ydieresis uogonek -100
+KPX Ydieresis uring -100
+KPX a g -10
+KPX a gbreve -10
+KPX a gcommaaccent -10
+KPX a v -15
+KPX a w -15
+KPX a y -20
+KPX a yacute -20
+KPX a ydieresis -20
+KPX aacute g -10
+KPX aacute gbreve -10
+KPX aacute gcommaaccent -10
+KPX aacute v -15
+KPX aacute w -15
+KPX aacute y -20
+KPX aacute yacute -20
+KPX aacute ydieresis -20
+KPX abreve g -10
+KPX abreve gbreve -10
+KPX abreve gcommaaccent -10
+KPX abreve v -15
+KPX abreve w -15
+KPX abreve y -20
+KPX abreve yacute -20
+KPX abreve ydieresis -20
+KPX acircumflex g -10
+KPX acircumflex gbreve -10
+KPX acircumflex gcommaaccent -10
+KPX acircumflex v -15
+KPX acircumflex w -15
+KPX acircumflex y -20
+KPX acircumflex yacute -20
+KPX acircumflex ydieresis -20
+KPX adieresis g -10
+KPX adieresis gbreve -10
+KPX adieresis gcommaaccent -10
+KPX adieresis v -15
+KPX adieresis w -15
+KPX adieresis y -20
+KPX adieresis yacute -20
+KPX adieresis ydieresis -20
+KPX agrave g -10
+KPX agrave gbreve -10
+KPX agrave gcommaaccent -10
+KPX agrave v -15
+KPX agrave w -15
+KPX agrave y -20
+KPX agrave yacute -20
+KPX agrave ydieresis -20
+KPX amacron g -10
+KPX amacron gbreve -10
+KPX amacron gcommaaccent -10
+KPX amacron v -15
+KPX amacron w -15
+KPX amacron y -20
+KPX amacron yacute -20
+KPX amacron ydieresis -20
+KPX aogonek g -10
+KPX aogonek gbreve -10
+KPX aogonek gcommaaccent -10
+KPX aogonek v -15
+KPX aogonek w -15
+KPX aogonek y -20
+KPX aogonek yacute -20
+KPX aogonek ydieresis -20
+KPX aring g -10
+KPX aring gbreve -10
+KPX aring gcommaaccent -10
+KPX aring v -15
+KPX aring w -15
+KPX aring y -20
+KPX aring yacute -20
+KPX aring ydieresis -20
+KPX atilde g -10
+KPX atilde gbreve -10
+KPX atilde gcommaaccent -10
+KPX atilde v -15
+KPX atilde w -15
+KPX atilde y -20
+KPX atilde yacute -20
+KPX atilde ydieresis -20
+KPX b l -10
+KPX b lacute -10
+KPX b lcommaaccent -10
+KPX b lslash -10
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -20
+KPX b y -20
+KPX b yacute -20
+KPX b ydieresis -20
+KPX c h -10
+KPX c k -20
+KPX c kcommaaccent -20
+KPX c l -20
+KPX c lacute -20
+KPX c lcommaaccent -20
+KPX c lslash -20
+KPX c y -10
+KPX c yacute -10
+KPX c ydieresis -10
+KPX cacute h -10
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX cacute l -20
+KPX cacute lacute -20
+KPX cacute lcommaaccent -20
+KPX cacute lslash -20
+KPX cacute y -10
+KPX cacute yacute -10
+KPX cacute ydieresis -10
+KPX ccaron h -10
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccaron l -20
+KPX ccaron lacute -20
+KPX ccaron lcommaaccent -20
+KPX ccaron lslash -20
+KPX ccaron y -10
+KPX ccaron yacute -10
+KPX ccaron ydieresis -10
+KPX ccedilla h -10
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX ccedilla l -20
+KPX ccedilla lacute -20
+KPX ccedilla lcommaaccent -20
+KPX ccedilla lslash -20
+KPX ccedilla y -10
+KPX ccedilla yacute -10
+KPX ccedilla ydieresis -10
+KPX colon space -40
+KPX comma quotedblright -120
+KPX comma quoteright -120
+KPX comma space -40
+KPX d d -10
+KPX d dcroat -10
+KPX d v -15
+KPX d w -15
+KPX d y -15
+KPX d yacute -15
+KPX d ydieresis -15
+KPX dcroat d -10
+KPX dcroat dcroat -10
+KPX dcroat v -15
+KPX dcroat w -15
+KPX dcroat y -15
+KPX dcroat yacute -15
+KPX dcroat ydieresis -15
+KPX e comma 10
+KPX e period 20
+KPX e v -15
+KPX e w -15
+KPX e x -15
+KPX e y -15
+KPX e yacute -15
+KPX e ydieresis -15
+KPX eacute comma 10
+KPX eacute period 20
+KPX eacute v -15
+KPX eacute w -15
+KPX eacute x -15
+KPX eacute y -15
+KPX eacute yacute -15
+KPX eacute ydieresis -15
+KPX ecaron comma 10
+KPX ecaron period 20
+KPX ecaron v -15
+KPX ecaron w -15
+KPX ecaron x -15
+KPX ecaron y -15
+KPX ecaron yacute -15
+KPX ecaron ydieresis -15
+KPX ecircumflex comma 10
+KPX ecircumflex period 20
+KPX ecircumflex v -15
+KPX ecircumflex w -15
+KPX ecircumflex x -15
+KPX ecircumflex y -15
+KPX ecircumflex yacute -15
+KPX ecircumflex ydieresis -15
+KPX edieresis comma 10
+KPX edieresis period 20
+KPX edieresis v -15
+KPX edieresis w -15
+KPX edieresis x -15
+KPX edieresis y -15
+KPX edieresis yacute -15
+KPX edieresis ydieresis -15
+KPX edotaccent comma 10
+KPX edotaccent period 20
+KPX edotaccent v -15
+KPX edotaccent w -15
+KPX edotaccent x -15
+KPX edotaccent y -15
+KPX edotaccent yacute -15
+KPX edotaccent ydieresis -15
+KPX egrave comma 10
+KPX egrave period 20
+KPX egrave v -15
+KPX egrave w -15
+KPX egrave x -15
+KPX egrave y -15
+KPX egrave yacute -15
+KPX egrave ydieresis -15
+KPX emacron comma 10
+KPX emacron period 20
+KPX emacron v -15
+KPX emacron w -15
+KPX emacron x -15
+KPX emacron y -15
+KPX emacron yacute -15
+KPX emacron ydieresis -15
+KPX eogonek comma 10
+KPX eogonek period 20
+KPX eogonek v -15
+KPX eogonek w -15
+KPX eogonek x -15
+KPX eogonek y -15
+KPX eogonek yacute -15
+KPX eogonek ydieresis -15
+KPX f comma -10
+KPX f e -10
+KPX f eacute -10
+KPX f ecaron -10
+KPX f ecircumflex -10
+KPX f edieresis -10
+KPX f edotaccent -10
+KPX f egrave -10
+KPX f emacron -10
+KPX f eogonek -10
+KPX f o -20
+KPX f oacute -20
+KPX f ocircumflex -20
+KPX f odieresis -20
+KPX f ograve -20
+KPX f ohungarumlaut -20
+KPX f omacron -20
+KPX f oslash -20
+KPX f otilde -20
+KPX f period -10
+KPX f quotedblright 30
+KPX f quoteright 30
+KPX g e 10
+KPX g eacute 10
+KPX g ecaron 10
+KPX g ecircumflex 10
+KPX g edieresis 10
+KPX g edotaccent 10
+KPX g egrave 10
+KPX g emacron 10
+KPX g eogonek 10
+KPX g g -10
+KPX g gbreve -10
+KPX g gcommaaccent -10
+KPX gbreve e 10
+KPX gbreve eacute 10
+KPX gbreve ecaron 10
+KPX gbreve ecircumflex 10
+KPX gbreve edieresis 10
+KPX gbreve edotaccent 10
+KPX gbreve egrave 10
+KPX gbreve emacron 10
+KPX gbreve eogonek 10
+KPX gbreve g -10
+KPX gbreve gbreve -10
+KPX gbreve gcommaaccent -10
+KPX gcommaaccent e 10
+KPX gcommaaccent eacute 10
+KPX gcommaaccent ecaron 10
+KPX gcommaaccent ecircumflex 10
+KPX gcommaaccent edieresis 10
+KPX gcommaaccent edotaccent 10
+KPX gcommaaccent egrave 10
+KPX gcommaaccent emacron 10
+KPX gcommaaccent eogonek 10
+KPX gcommaaccent g -10
+KPX gcommaaccent gbreve -10
+KPX gcommaaccent gcommaaccent -10
+KPX h y -20
+KPX h yacute -20
+KPX h ydieresis -20
+KPX k o -15
+KPX k oacute -15
+KPX k ocircumflex -15
+KPX k odieresis -15
+KPX k ograve -15
+KPX k ohungarumlaut -15
+KPX k omacron -15
+KPX k oslash -15
+KPX k otilde -15
+KPX kcommaaccent o -15
+KPX kcommaaccent oacute -15
+KPX kcommaaccent ocircumflex -15
+KPX kcommaaccent odieresis -15
+KPX kcommaaccent ograve -15
+KPX kcommaaccent ohungarumlaut -15
+KPX kcommaaccent omacron -15
+KPX kcommaaccent oslash -15
+KPX kcommaaccent otilde -15
+KPX l w -15
+KPX l y -15
+KPX l yacute -15
+KPX l ydieresis -15
+KPX lacute w -15
+KPX lacute y -15
+KPX lacute yacute -15
+KPX lacute ydieresis -15
+KPX lcommaaccent w -15
+KPX lcommaaccent y -15
+KPX lcommaaccent yacute -15
+KPX lcommaaccent ydieresis -15
+KPX lslash w -15
+KPX lslash y -15
+KPX lslash yacute -15
+KPX lslash ydieresis -15
+KPX m u -20
+KPX m uacute -20
+KPX m ucircumflex -20
+KPX m udieresis -20
+KPX m ugrave -20
+KPX m uhungarumlaut -20
+KPX m umacron -20
+KPX m uogonek -20
+KPX m uring -20
+KPX m y -30
+KPX m yacute -30
+KPX m ydieresis -30
+KPX n u -10
+KPX n uacute -10
+KPX n ucircumflex -10
+KPX n udieresis -10
+KPX n ugrave -10
+KPX n uhungarumlaut -10
+KPX n umacron -10
+KPX n uogonek -10
+KPX n uring -10
+KPX n v -40
+KPX n y -20
+KPX n yacute -20
+KPX n ydieresis -20
+KPX nacute u -10
+KPX nacute uacute -10
+KPX nacute ucircumflex -10
+KPX nacute udieresis -10
+KPX nacute ugrave -10
+KPX nacute uhungarumlaut -10
+KPX nacute umacron -10
+KPX nacute uogonek -10
+KPX nacute uring -10
+KPX nacute v -40
+KPX nacute y -20
+KPX nacute yacute -20
+KPX nacute ydieresis -20
+KPX ncaron u -10
+KPX ncaron uacute -10
+KPX ncaron ucircumflex -10
+KPX ncaron udieresis -10
+KPX ncaron ugrave -10
+KPX ncaron uhungarumlaut -10
+KPX ncaron umacron -10
+KPX ncaron uogonek -10
+KPX ncaron uring -10
+KPX ncaron v -40
+KPX ncaron y -20
+KPX ncaron yacute -20
+KPX ncaron ydieresis -20
+KPX ncommaaccent u -10
+KPX ncommaaccent uacute -10
+KPX ncommaaccent ucircumflex -10
+KPX ncommaaccent udieresis -10
+KPX ncommaaccent ugrave -10
+KPX ncommaaccent uhungarumlaut -10
+KPX ncommaaccent umacron -10
+KPX ncommaaccent uogonek -10
+KPX ncommaaccent uring -10
+KPX ncommaaccent v -40
+KPX ncommaaccent y -20
+KPX ncommaaccent yacute -20
+KPX ncommaaccent ydieresis -20
+KPX ntilde u -10
+KPX ntilde uacute -10
+KPX ntilde ucircumflex -10
+KPX ntilde udieresis -10
+KPX ntilde ugrave -10
+KPX ntilde uhungarumlaut -10
+KPX ntilde umacron -10
+KPX ntilde uogonek -10
+KPX ntilde uring -10
+KPX ntilde v -40
+KPX ntilde y -20
+KPX ntilde yacute -20
+KPX ntilde ydieresis -20
+KPX o v -20
+KPX o w -15
+KPX o x -30
+KPX o y -20
+KPX o yacute -20
+KPX o ydieresis -20
+KPX oacute v -20
+KPX oacute w -15
+KPX oacute x -30
+KPX oacute y -20
+KPX oacute yacute -20
+KPX oacute ydieresis -20
+KPX ocircumflex v -20
+KPX ocircumflex w -15
+KPX ocircumflex x -30
+KPX ocircumflex y -20
+KPX ocircumflex yacute -20
+KPX ocircumflex ydieresis -20
+KPX odieresis v -20
+KPX odieresis w -15
+KPX odieresis x -30
+KPX odieresis y -20
+KPX odieresis yacute -20
+KPX odieresis ydieresis -20
+KPX ograve v -20
+KPX ograve w -15
+KPX ograve x -30
+KPX ograve y -20
+KPX ograve yacute -20
+KPX ograve ydieresis -20
+KPX ohungarumlaut v -20
+KPX ohungarumlaut w -15
+KPX ohungarumlaut x -30
+KPX ohungarumlaut y -20
+KPX ohungarumlaut yacute -20
+KPX ohungarumlaut ydieresis -20
+KPX omacron v -20
+KPX omacron w -15
+KPX omacron x -30
+KPX omacron y -20
+KPX omacron yacute -20
+KPX omacron ydieresis -20
+KPX oslash v -20
+KPX oslash w -15
+KPX oslash x -30
+KPX oslash y -20
+KPX oslash yacute -20
+KPX oslash ydieresis -20
+KPX otilde v -20
+KPX otilde w -15
+KPX otilde x -30
+KPX otilde y -20
+KPX otilde yacute -20
+KPX otilde ydieresis -20
+KPX p y -15
+KPX p yacute -15
+KPX p ydieresis -15
+KPX period quotedblright -120
+KPX period quoteright -120
+KPX period space -40
+KPX quotedblright space -80
+KPX quoteleft quoteleft -46
+KPX quoteright d -80
+KPX quoteright dcroat -80
+KPX quoteright l -20
+KPX quoteright lacute -20
+KPX quoteright lcommaaccent -20
+KPX quoteright lslash -20
+KPX quoteright quoteright -46
+KPX quoteright r -40
+KPX quoteright racute -40
+KPX quoteright rcaron -40
+KPX quoteright rcommaaccent -40
+KPX quoteright s -60
+KPX quoteright sacute -60
+KPX quoteright scaron -60
+KPX quoteright scedilla -60
+KPX quoteright scommaaccent -60
+KPX quoteright space -80
+KPX quoteright v -20
+KPX r c -20
+KPX r cacute -20
+KPX r ccaron -20
+KPX r ccedilla -20
+KPX r comma -60
+KPX r d -20
+KPX r dcroat -20
+KPX r g -15
+KPX r gbreve -15
+KPX r gcommaaccent -15
+KPX r hyphen -20
+KPX r o -20
+KPX r oacute -20
+KPX r ocircumflex -20
+KPX r odieresis -20
+KPX r ograve -20
+KPX r ohungarumlaut -20
+KPX r omacron -20
+KPX r oslash -20
+KPX r otilde -20
+KPX r period -60
+KPX r q -20
+KPX r s -15
+KPX r sacute -15
+KPX r scaron -15
+KPX r scedilla -15
+KPX r scommaaccent -15
+KPX r t 20
+KPX r tcommaaccent 20
+KPX r v 10
+KPX r y 10
+KPX r yacute 10
+KPX r ydieresis 10
+KPX racute c -20
+KPX racute cacute -20
+KPX racute ccaron -20
+KPX racute ccedilla -20
+KPX racute comma -60
+KPX racute d -20
+KPX racute dcroat -20
+KPX racute g -15
+KPX racute gbreve -15
+KPX racute gcommaaccent -15
+KPX racute hyphen -20
+KPX racute o -20
+KPX racute oacute -20
+KPX racute ocircumflex -20
+KPX racute odieresis -20
+KPX racute ograve -20
+KPX racute ohungarumlaut -20
+KPX racute omacron -20
+KPX racute oslash -20
+KPX racute otilde -20
+KPX racute period -60
+KPX racute q -20
+KPX racute s -15
+KPX racute sacute -15
+KPX racute scaron -15
+KPX racute scedilla -15
+KPX racute scommaaccent -15
+KPX racute t 20
+KPX racute tcommaaccent 20
+KPX racute v 10
+KPX racute y 10
+KPX racute yacute 10
+KPX racute ydieresis 10
+KPX rcaron c -20
+KPX rcaron cacute -20
+KPX rcaron ccaron -20
+KPX rcaron ccedilla -20
+KPX rcaron comma -60
+KPX rcaron d -20
+KPX rcaron dcroat -20
+KPX rcaron g -15
+KPX rcaron gbreve -15
+KPX rcaron gcommaaccent -15
+KPX rcaron hyphen -20
+KPX rcaron o -20
+KPX rcaron oacute -20
+KPX rcaron ocircumflex -20
+KPX rcaron odieresis -20
+KPX rcaron ograve -20
+KPX rcaron ohungarumlaut -20
+KPX rcaron omacron -20
+KPX rcaron oslash -20
+KPX rcaron otilde -20
+KPX rcaron period -60
+KPX rcaron q -20
+KPX rcaron s -15
+KPX rcaron sacute -15
+KPX rcaron scaron -15
+KPX rcaron scedilla -15
+KPX rcaron scommaaccent -15
+KPX rcaron t 20
+KPX rcaron tcommaaccent 20
+KPX rcaron v 10
+KPX rcaron y 10
+KPX rcaron yacute 10
+KPX rcaron ydieresis 10
+KPX rcommaaccent c -20
+KPX rcommaaccent cacute -20
+KPX rcommaaccent ccaron -20
+KPX rcommaaccent ccedilla -20
+KPX rcommaaccent comma -60
+KPX rcommaaccent d -20
+KPX rcommaaccent dcroat -20
+KPX rcommaaccent g -15
+KPX rcommaaccent gbreve -15
+KPX rcommaaccent gcommaaccent -15
+KPX rcommaaccent hyphen -20
+KPX rcommaaccent o -20
+KPX rcommaaccent oacute -20
+KPX rcommaaccent ocircumflex -20
+KPX rcommaaccent odieresis -20
+KPX rcommaaccent ograve -20
+KPX rcommaaccent ohungarumlaut -20
+KPX rcommaaccent omacron -20
+KPX rcommaaccent oslash -20
+KPX rcommaaccent otilde -20
+KPX rcommaaccent period -60
+KPX rcommaaccent q -20
+KPX rcommaaccent s -15
+KPX rcommaaccent sacute -15
+KPX rcommaaccent scaron -15
+KPX rcommaaccent scedilla -15
+KPX rcommaaccent scommaaccent -15
+KPX rcommaaccent t 20
+KPX rcommaaccent tcommaaccent 20
+KPX rcommaaccent v 10
+KPX rcommaaccent y 10
+KPX rcommaaccent yacute 10
+KPX rcommaaccent ydieresis 10
+KPX s w -15
+KPX sacute w -15
+KPX scaron w -15
+KPX scedilla w -15
+KPX scommaaccent w -15
+KPX semicolon space -40
+KPX space T -100
+KPX space Tcaron -100
+KPX space Tcommaaccent -100
+KPX space V -80
+KPX space W -80
+KPX space Y -120
+KPX space Yacute -120
+KPX space Ydieresis -120
+KPX space quotedblleft -80
+KPX space quoteleft -60
+KPX v a -20
+KPX v aacute -20
+KPX v abreve -20
+KPX v acircumflex -20
+KPX v adieresis -20
+KPX v agrave -20
+KPX v amacron -20
+KPX v aogonek -20
+KPX v aring -20
+KPX v atilde -20
+KPX v comma -80
+KPX v o -30
+KPX v oacute -30
+KPX v ocircumflex -30
+KPX v odieresis -30
+KPX v ograve -30
+KPX v ohungarumlaut -30
+KPX v omacron -30
+KPX v oslash -30
+KPX v otilde -30
+KPX v period -80
+KPX w comma -40
+KPX w o -20
+KPX w oacute -20
+KPX w ocircumflex -20
+KPX w odieresis -20
+KPX w ograve -20
+KPX w ohungarumlaut -20
+KPX w omacron -20
+KPX w oslash -20
+KPX w otilde -20
+KPX w period -40
+KPX x e -10
+KPX x eacute -10
+KPX x ecaron -10
+KPX x ecircumflex -10
+KPX x edieresis -10
+KPX x edotaccent -10
+KPX x egrave -10
+KPX x emacron -10
+KPX x eogonek -10
+KPX y a -30
+KPX y aacute -30
+KPX y abreve -30
+KPX y acircumflex -30
+KPX y adieresis -30
+KPX y agrave -30
+KPX y amacron -30
+KPX y aogonek -30
+KPX y aring -30
+KPX y atilde -30
+KPX y comma -80
+KPX y e -10
+KPX y eacute -10
+KPX y ecaron -10
+KPX y ecircumflex -10
+KPX y edieresis -10
+KPX y edotaccent -10
+KPX y egrave -10
+KPX y emacron -10
+KPX y eogonek -10
+KPX y o -25
+KPX y oacute -25
+KPX y ocircumflex -25
+KPX y odieresis -25
+KPX y ograve -25
+KPX y ohungarumlaut -25
+KPX y omacron -25
+KPX y oslash -25
+KPX y otilde -25
+KPX y period -80
+KPX yacute a -30
+KPX yacute aacute -30
+KPX yacute abreve -30
+KPX yacute acircumflex -30
+KPX yacute adieresis -30
+KPX yacute agrave -30
+KPX yacute amacron -30
+KPX yacute aogonek -30
+KPX yacute aring -30
+KPX yacute atilde -30
+KPX yacute comma -80
+KPX yacute e -10
+KPX yacute eacute -10
+KPX yacute ecaron -10
+KPX yacute ecircumflex -10
+KPX yacute edieresis -10
+KPX yacute edotaccent -10
+KPX yacute egrave -10
+KPX yacute emacron -10
+KPX yacute eogonek -10
+KPX yacute o -25
+KPX yacute oacute -25
+KPX yacute ocircumflex -25
+KPX yacute odieresis -25
+KPX yacute ograve -25
+KPX yacute ohungarumlaut -25
+KPX yacute omacron -25
+KPX yacute oslash -25
+KPX yacute otilde -25
+KPX yacute period -80
+KPX ydieresis a -30
+KPX ydieresis aacute -30
+KPX ydieresis abreve -30
+KPX ydieresis acircumflex -30
+KPX ydieresis adieresis -30
+KPX ydieresis agrave -30
+KPX ydieresis amacron -30
+KPX ydieresis aogonek -30
+KPX ydieresis aring -30
+KPX ydieresis atilde -30
+KPX ydieresis comma -80
+KPX ydieresis e -10
+KPX ydieresis eacute -10
+KPX ydieresis ecaron -10
+KPX ydieresis ecircumflex -10
+KPX ydieresis edieresis -10
+KPX ydieresis edotaccent -10
+KPX ydieresis egrave -10
+KPX ydieresis emacron -10
+KPX ydieresis eogonek -10
+KPX ydieresis o -25
+KPX ydieresis oacute -25
+KPX ydieresis ocircumflex -25
+KPX ydieresis odieresis -25
+KPX ydieresis ograve -25
+KPX ydieresis ohungarumlaut -25
+KPX ydieresis omacron -25
+KPX ydieresis oslash -25
+KPX ydieresis otilde -25
+KPX ydieresis period -80
+KPX z e 10
+KPX z eacute 10
+KPX z ecaron 10
+KPX z ecircumflex 10
+KPX z edieresis 10
+KPX z edotaccent 10
+KPX z egrave 10
+KPX z emacron 10
+KPX z eogonek 10
+KPX zacute e 10
+KPX zacute eacute 10
+KPX zacute ecaron 10
+KPX zacute ecircumflex 10
+KPX zacute edieresis 10
+KPX zacute edotaccent 10
+KPX zacute egrave 10
+KPX zacute emacron 10
+KPX zacute eogonek 10
+KPX zcaron e 10
+KPX zcaron eacute 10
+KPX zcaron ecaron 10
+KPX zcaron ecircumflex 10
+KPX zcaron edieresis 10
+KPX zcaron edotaccent 10
+KPX zcaron egrave 10
+KPX zcaron emacron 10
+KPX zcaron eogonek 10
+KPX zdotaccent e 10
+KPX zdotaccent eacute 10
+KPX zdotaccent ecaron 10
+KPX zdotaccent ecircumflex 10
+KPX zdotaccent edieresis 10
+KPX zdotaccent edotaccent 10
+KPX zdotaccent egrave 10
+KPX zdotaccent emacron 10
+KPX zdotaccent eogonek 10
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-BoldOblique.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-BoldOblique.afm
new file mode 100644
index 0000000000000000000000000000000000000000..1715b210467ce82625be36f6cbf20eae54cb67e0
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-BoldOblique.afm
@@ -0,0 +1,2827 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:45:12 1997
+Comment UniqueID 43053
+Comment VMusage 14482 68586
+FontName Helvetica-BoldOblique
+FullName Helvetica Bold Oblique
+FamilyName Helvetica
+Weight Bold
+ItalicAngle -12
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -174 -228 1114 962
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 532
+Ascender 718
+Descender -207
+StdHW 118
+StdVW 140
+StartCharMetrics 315
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 94 0 397 718 ;
+C 34 ; WX 474 ; N quotedbl ; B 193 447 529 718 ;
+C 35 ; WX 556 ; N numbersign ; B 60 0 644 698 ;
+C 36 ; WX 556 ; N dollar ; B 67 -115 622 775 ;
+C 37 ; WX 889 ; N percent ; B 136 -19 901 710 ;
+C 38 ; WX 722 ; N ampersand ; B 89 -19 732 718 ;
+C 39 ; WX 278 ; N quoteright ; B 167 445 362 718 ;
+C 40 ; WX 333 ; N parenleft ; B 76 -208 470 734 ;
+C 41 ; WX 333 ; N parenright ; B -25 -208 369 734 ;
+C 42 ; WX 389 ; N asterisk ; B 146 387 481 718 ;
+C 43 ; WX 584 ; N plus ; B 82 0 610 506 ;
+C 44 ; WX 278 ; N comma ; B 28 -168 245 146 ;
+C 45 ; WX 333 ; N hyphen ; B 73 215 379 345 ;
+C 46 ; WX 278 ; N period ; B 64 0 245 146 ;
+C 47 ; WX 278 ; N slash ; B -37 -19 468 737 ;
+C 48 ; WX 556 ; N zero ; B 86 -19 617 710 ;
+C 49 ; WX 556 ; N one ; B 173 0 529 710 ;
+C 50 ; WX 556 ; N two ; B 26 0 619 710 ;
+C 51 ; WX 556 ; N three ; B 65 -19 608 710 ;
+C 52 ; WX 556 ; N four ; B 60 0 598 710 ;
+C 53 ; WX 556 ; N five ; B 64 -19 636 698 ;
+C 54 ; WX 556 ; N six ; B 85 -19 619 710 ;
+C 55 ; WX 556 ; N seven ; B 125 0 676 698 ;
+C 56 ; WX 556 ; N eight ; B 69 -19 616 710 ;
+C 57 ; WX 556 ; N nine ; B 78 -19 615 710 ;
+C 58 ; WX 333 ; N colon ; B 92 0 351 512 ;
+C 59 ; WX 333 ; N semicolon ; B 56 -168 351 512 ;
+C 60 ; WX 584 ; N less ; B 82 -8 655 514 ;
+C 61 ; WX 584 ; N equal ; B 58 87 633 419 ;
+C 62 ; WX 584 ; N greater ; B 36 -8 609 514 ;
+C 63 ; WX 611 ; N question ; B 165 0 671 727 ;
+C 64 ; WX 975 ; N at ; B 186 -19 954 737 ;
+C 65 ; WX 722 ; N A ; B 20 0 702 718 ;
+C 66 ; WX 722 ; N B ; B 76 0 764 718 ;
+C 67 ; WX 722 ; N C ; B 107 -19 789 737 ;
+C 68 ; WX 722 ; N D ; B 76 0 777 718 ;
+C 69 ; WX 667 ; N E ; B 76 0 757 718 ;
+C 70 ; WX 611 ; N F ; B 76 0 740 718 ;
+C 71 ; WX 778 ; N G ; B 108 -19 817 737 ;
+C 72 ; WX 722 ; N H ; B 71 0 804 718 ;
+C 73 ; WX 278 ; N I ; B 64 0 367 718 ;
+C 74 ; WX 556 ; N J ; B 60 -18 637 718 ;
+C 75 ; WX 722 ; N K ; B 87 0 858 718 ;
+C 76 ; WX 611 ; N L ; B 76 0 611 718 ;
+C 77 ; WX 833 ; N M ; B 69 0 918 718 ;
+C 78 ; WX 722 ; N N ; B 69 0 807 718 ;
+C 79 ; WX 778 ; N O ; B 107 -19 823 737 ;
+C 80 ; WX 667 ; N P ; B 76 0 738 718 ;
+C 81 ; WX 778 ; N Q ; B 107 -52 823 737 ;
+C 82 ; WX 722 ; N R ; B 76 0 778 718 ;
+C 83 ; WX 667 ; N S ; B 81 -19 718 737 ;
+C 84 ; WX 611 ; N T ; B 140 0 751 718 ;
+C 85 ; WX 722 ; N U ; B 116 -19 804 718 ;
+C 86 ; WX 667 ; N V ; B 172 0 801 718 ;
+C 87 ; WX 944 ; N W ; B 169 0 1082 718 ;
+C 88 ; WX 667 ; N X ; B 14 0 791 718 ;
+C 89 ; WX 667 ; N Y ; B 168 0 806 718 ;
+C 90 ; WX 611 ; N Z ; B 25 0 737 718 ;
+C 91 ; WX 333 ; N bracketleft ; B 21 -196 462 722 ;
+C 92 ; WX 278 ; N backslash ; B 124 -19 307 737 ;
+C 93 ; WX 333 ; N bracketright ; B -18 -196 423 722 ;
+C 94 ; WX 584 ; N asciicircum ; B 131 323 591 698 ;
+C 95 ; WX 556 ; N underscore ; B -27 -125 540 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 165 454 361 727 ;
+C 97 ; WX 556 ; N a ; B 55 -14 583 546 ;
+C 98 ; WX 611 ; N b ; B 61 -14 645 718 ;
+C 99 ; WX 556 ; N c ; B 79 -14 599 546 ;
+C 100 ; WX 611 ; N d ; B 82 -14 704 718 ;
+C 101 ; WX 556 ; N e ; B 70 -14 593 546 ;
+C 102 ; WX 333 ; N f ; B 87 0 469 727 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B 38 -217 666 546 ;
+C 104 ; WX 611 ; N h ; B 65 0 629 718 ;
+C 105 ; WX 278 ; N i ; B 69 0 363 725 ;
+C 106 ; WX 278 ; N j ; B -42 -214 363 725 ;
+C 107 ; WX 556 ; N k ; B 69 0 670 718 ;
+C 108 ; WX 278 ; N l ; B 69 0 362 718 ;
+C 109 ; WX 889 ; N m ; B 64 0 909 546 ;
+C 110 ; WX 611 ; N n ; B 65 0 629 546 ;
+C 111 ; WX 611 ; N o ; B 82 -14 643 546 ;
+C 112 ; WX 611 ; N p ; B 18 -207 645 546 ;
+C 113 ; WX 611 ; N q ; B 80 -207 665 546 ;
+C 114 ; WX 389 ; N r ; B 64 0 489 546 ;
+C 115 ; WX 556 ; N s ; B 63 -14 584 546 ;
+C 116 ; WX 333 ; N t ; B 100 -6 422 676 ;
+C 117 ; WX 611 ; N u ; B 98 -14 658 532 ;
+C 118 ; WX 556 ; N v ; B 126 0 656 532 ;
+C 119 ; WX 778 ; N w ; B 123 0 882 532 ;
+C 120 ; WX 556 ; N x ; B 15 0 648 532 ;
+C 121 ; WX 556 ; N y ; B 42 -214 652 532 ;
+C 122 ; WX 500 ; N z ; B 20 0 583 532 ;
+C 123 ; WX 389 ; N braceleft ; B 94 -196 518 722 ;
+C 124 ; WX 280 ; N bar ; B 36 -225 361 775 ;
+C 125 ; WX 389 ; N braceright ; B -18 -196 407 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 115 163 577 343 ;
+C 161 ; WX 333 ; N exclamdown ; B 50 -186 353 532 ;
+C 162 ; WX 556 ; N cent ; B 79 -118 599 628 ;
+C 163 ; WX 556 ; N sterling ; B 50 -16 635 718 ;
+C 164 ; WX 167 ; N fraction ; B -174 -19 487 710 ;
+C 165 ; WX 556 ; N yen ; B 60 0 713 698 ;
+C 166 ; WX 556 ; N florin ; B -50 -210 669 737 ;
+C 167 ; WX 556 ; N section ; B 61 -184 598 727 ;
+C 168 ; WX 556 ; N currency ; B 27 76 680 636 ;
+C 169 ; WX 238 ; N quotesingle ; B 165 447 321 718 ;
+C 170 ; WX 500 ; N quotedblleft ; B 160 454 588 727 ;
+C 171 ; WX 556 ; N guillemotleft ; B 135 76 571 484 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 130 76 353 484 ;
+C 173 ; WX 333 ; N guilsinglright ; B 99 76 322 484 ;
+C 174 ; WX 611 ; N fi ; B 87 0 696 727 ;
+C 175 ; WX 611 ; N fl ; B 87 0 695 727 ;
+C 177 ; WX 556 ; N endash ; B 48 227 627 333 ;
+C 178 ; WX 556 ; N dagger ; B 118 -171 626 718 ;
+C 179 ; WX 556 ; N daggerdbl ; B 46 -171 628 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 110 172 276 334 ;
+C 182 ; WX 556 ; N paragraph ; B 98 -191 688 700 ;
+C 183 ; WX 350 ; N bullet ; B 83 194 420 524 ;
+C 184 ; WX 278 ; N quotesinglbase ; B 41 -146 236 127 ;
+C 185 ; WX 500 ; N quotedblbase ; B 36 -146 463 127 ;
+C 186 ; WX 500 ; N quotedblright ; B 162 445 589 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 104 76 540 484 ;
+C 188 ; WX 1000 ; N ellipsis ; B 92 0 939 146 ;
+C 189 ; WX 1000 ; N perthousand ; B 76 -19 1038 710 ;
+C 191 ; WX 611 ; N questiondown ; B 53 -195 559 532 ;
+C 193 ; WX 333 ; N grave ; B 136 604 353 750 ;
+C 194 ; WX 333 ; N acute ; B 236 604 515 750 ;
+C 195 ; WX 333 ; N circumflex ; B 118 604 471 750 ;
+C 196 ; WX 333 ; N tilde ; B 113 610 507 737 ;
+C 197 ; WX 333 ; N macron ; B 122 604 483 678 ;
+C 198 ; WX 333 ; N breve ; B 156 604 494 750 ;
+C 199 ; WX 333 ; N dotaccent ; B 235 614 385 729 ;
+C 200 ; WX 333 ; N dieresis ; B 137 614 482 729 ;
+C 202 ; WX 333 ; N ring ; B 200 568 420 776 ;
+C 203 ; WX 333 ; N cedilla ; B -37 -228 220 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 137 604 645 750 ;
+C 206 ; WX 333 ; N ogonek ; B 41 -228 264 0 ;
+C 207 ; WX 333 ; N caron ; B 149 604 502 750 ;
+C 208 ; WX 1000 ; N emdash ; B 48 227 1071 333 ;
+C 225 ; WX 1000 ; N AE ; B 5 0 1100 718 ;
+C 227 ; WX 370 ; N ordfeminine ; B 125 401 465 737 ;
+C 232 ; WX 611 ; N Lslash ; B 34 0 611 718 ;
+C 233 ; WX 778 ; N Oslash ; B 35 -27 894 745 ;
+C 234 ; WX 1000 ; N OE ; B 99 -19 1114 737 ;
+C 235 ; WX 365 ; N ordmasculine ; B 123 401 485 737 ;
+C 241 ; WX 889 ; N ae ; B 56 -14 923 546 ;
+C 245 ; WX 278 ; N dotlessi ; B 69 0 322 532 ;
+C 248 ; WX 278 ; N lslash ; B 40 0 407 718 ;
+C 249 ; WX 611 ; N oslash ; B 22 -29 701 560 ;
+C 250 ; WX 944 ; N oe ; B 82 -14 977 546 ;
+C 251 ; WX 611 ; N germandbls ; B 69 -14 657 731 ;
+C -1 ; WX 278 ; N Idieresis ; B 64 0 494 915 ;
+C -1 ; WX 556 ; N eacute ; B 70 -14 627 750 ;
+C -1 ; WX 556 ; N abreve ; B 55 -14 606 750 ;
+C -1 ; WX 611 ; N uhungarumlaut ; B 98 -14 784 750 ;
+C -1 ; WX 556 ; N ecaron ; B 70 -14 614 750 ;
+C -1 ; WX 667 ; N Ydieresis ; B 168 0 806 915 ;
+C -1 ; WX 584 ; N divide ; B 82 -42 610 548 ;
+C -1 ; WX 667 ; N Yacute ; B 168 0 806 936 ;
+C -1 ; WX 722 ; N Acircumflex ; B 20 0 706 936 ;
+C -1 ; WX 556 ; N aacute ; B 55 -14 627 750 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 116 -19 804 936 ;
+C -1 ; WX 556 ; N yacute ; B 42 -214 652 750 ;
+C -1 ; WX 556 ; N scommaaccent ; B 63 -228 584 546 ;
+C -1 ; WX 556 ; N ecircumflex ; B 70 -14 593 750 ;
+C -1 ; WX 722 ; N Uring ; B 116 -19 804 962 ;
+C -1 ; WX 722 ; N Udieresis ; B 116 -19 804 915 ;
+C -1 ; WX 556 ; N aogonek ; B 55 -224 583 546 ;
+C -1 ; WX 722 ; N Uacute ; B 116 -19 804 936 ;
+C -1 ; WX 611 ; N uogonek ; B 98 -228 658 532 ;
+C -1 ; WX 667 ; N Edieresis ; B 76 0 757 915 ;
+C -1 ; WX 722 ; N Dcroat ; B 62 0 777 718 ;
+C -1 ; WX 250 ; N commaaccent ; B 16 -228 188 -50 ;
+C -1 ; WX 737 ; N copyright ; B 56 -19 835 737 ;
+C -1 ; WX 667 ; N Emacron ; B 76 0 757 864 ;
+C -1 ; WX 556 ; N ccaron ; B 79 -14 614 750 ;
+C -1 ; WX 556 ; N aring ; B 55 -14 583 776 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 69 -228 807 718 ;
+C -1 ; WX 278 ; N lacute ; B 69 0 528 936 ;
+C -1 ; WX 556 ; N agrave ; B 55 -14 583 750 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 140 -228 751 718 ;
+C -1 ; WX 722 ; N Cacute ; B 107 -19 789 936 ;
+C -1 ; WX 556 ; N atilde ; B 55 -14 619 737 ;
+C -1 ; WX 667 ; N Edotaccent ; B 76 0 757 915 ;
+C -1 ; WX 556 ; N scaron ; B 63 -14 614 750 ;
+C -1 ; WX 556 ; N scedilla ; B 63 -228 584 546 ;
+C -1 ; WX 278 ; N iacute ; B 69 0 488 750 ;
+C -1 ; WX 494 ; N lozenge ; B 90 0 564 745 ;
+C -1 ; WX 722 ; N Rcaron ; B 76 0 778 936 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 108 -228 817 737 ;
+C -1 ; WX 611 ; N ucircumflex ; B 98 -14 658 750 ;
+C -1 ; WX 556 ; N acircumflex ; B 55 -14 583 750 ;
+C -1 ; WX 722 ; N Amacron ; B 20 0 718 864 ;
+C -1 ; WX 389 ; N rcaron ; B 64 0 530 750 ;
+C -1 ; WX 556 ; N ccedilla ; B 79 -228 599 546 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 25 0 737 915 ;
+C -1 ; WX 667 ; N Thorn ; B 76 0 716 718 ;
+C -1 ; WX 778 ; N Omacron ; B 107 -19 823 864 ;
+C -1 ; WX 722 ; N Racute ; B 76 0 778 936 ;
+C -1 ; WX 667 ; N Sacute ; B 81 -19 722 936 ;
+C -1 ; WX 743 ; N dcaron ; B 82 -14 903 718 ;
+C -1 ; WX 722 ; N Umacron ; B 116 -19 804 864 ;
+C -1 ; WX 611 ; N uring ; B 98 -14 658 776 ;
+C -1 ; WX 333 ; N threesuperior ; B 91 271 441 710 ;
+C -1 ; WX 778 ; N Ograve ; B 107 -19 823 936 ;
+C -1 ; WX 722 ; N Agrave ; B 20 0 702 936 ;
+C -1 ; WX 722 ; N Abreve ; B 20 0 729 936 ;
+C -1 ; WX 584 ; N multiply ; B 57 1 635 505 ;
+C -1 ; WX 611 ; N uacute ; B 98 -14 658 750 ;
+C -1 ; WX 611 ; N Tcaron ; B 140 0 751 936 ;
+C -1 ; WX 494 ; N partialdiff ; B 43 -21 585 750 ;
+C -1 ; WX 556 ; N ydieresis ; B 42 -214 652 729 ;
+C -1 ; WX 722 ; N Nacute ; B 69 0 807 936 ;
+C -1 ; WX 278 ; N icircumflex ; B 69 0 444 750 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 76 0 757 936 ;
+C -1 ; WX 556 ; N adieresis ; B 55 -14 594 729 ;
+C -1 ; WX 556 ; N edieresis ; B 70 -14 594 729 ;
+C -1 ; WX 556 ; N cacute ; B 79 -14 627 750 ;
+C -1 ; WX 611 ; N nacute ; B 65 0 654 750 ;
+C -1 ; WX 611 ; N umacron ; B 98 -14 658 678 ;
+C -1 ; WX 722 ; N Ncaron ; B 69 0 807 936 ;
+C -1 ; WX 278 ; N Iacute ; B 64 0 528 936 ;
+C -1 ; WX 584 ; N plusminus ; B 40 0 625 506 ;
+C -1 ; WX 280 ; N brokenbar ; B 52 -150 345 700 ;
+C -1 ; WX 737 ; N registered ; B 55 -19 834 737 ;
+C -1 ; WX 778 ; N Gbreve ; B 108 -19 817 936 ;
+C -1 ; WX 278 ; N Idotaccent ; B 64 0 397 915 ;
+C -1 ; WX 600 ; N summation ; B 14 -10 670 706 ;
+C -1 ; WX 667 ; N Egrave ; B 76 0 757 936 ;
+C -1 ; WX 389 ; N racute ; B 64 0 543 750 ;
+C -1 ; WX 611 ; N omacron ; B 82 -14 643 678 ;
+C -1 ; WX 611 ; N Zacute ; B 25 0 737 936 ;
+C -1 ; WX 611 ; N Zcaron ; B 25 0 737 936 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 629 704 ;
+C -1 ; WX 722 ; N Eth ; B 62 0 777 718 ;
+C -1 ; WX 722 ; N Ccedilla ; B 107 -228 789 737 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 30 -228 362 718 ;
+C -1 ; WX 389 ; N tcaron ; B 100 -6 608 878 ;
+C -1 ; WX 556 ; N eogonek ; B 70 -228 593 546 ;
+C -1 ; WX 722 ; N Uogonek ; B 116 -228 804 718 ;
+C -1 ; WX 722 ; N Aacute ; B 20 0 750 936 ;
+C -1 ; WX 722 ; N Adieresis ; B 20 0 716 915 ;
+C -1 ; WX 556 ; N egrave ; B 70 -14 593 750 ;
+C -1 ; WX 500 ; N zacute ; B 20 0 599 750 ;
+C -1 ; WX 278 ; N iogonek ; B -14 -224 363 725 ;
+C -1 ; WX 778 ; N Oacute ; B 107 -19 823 936 ;
+C -1 ; WX 611 ; N oacute ; B 82 -14 654 750 ;
+C -1 ; WX 556 ; N amacron ; B 55 -14 595 678 ;
+C -1 ; WX 556 ; N sacute ; B 63 -14 627 750 ;
+C -1 ; WX 278 ; N idieresis ; B 69 0 455 729 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 107 -19 823 936 ;
+C -1 ; WX 722 ; N Ugrave ; B 116 -19 804 936 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C -1 ; WX 611 ; N thorn ; B 18 -208 645 718 ;
+C -1 ; WX 333 ; N twosuperior ; B 69 283 449 710 ;
+C -1 ; WX 778 ; N Odieresis ; B 107 -19 823 915 ;
+C -1 ; WX 611 ; N mu ; B 22 -207 658 532 ;
+C -1 ; WX 278 ; N igrave ; B 69 0 326 750 ;
+C -1 ; WX 611 ; N ohungarumlaut ; B 82 -14 784 750 ;
+C -1 ; WX 667 ; N Eogonek ; B 76 -224 757 718 ;
+C -1 ; WX 611 ; N dcroat ; B 82 -14 789 718 ;
+C -1 ; WX 834 ; N threequarters ; B 99 -19 839 710 ;
+C -1 ; WX 667 ; N Scedilla ; B 81 -228 718 737 ;
+C -1 ; WX 400 ; N lcaron ; B 69 0 561 718 ;
+C -1 ; WX 722 ; N Kcommaaccent ; B 87 -228 858 718 ;
+C -1 ; WX 611 ; N Lacute ; B 76 0 611 936 ;
+C -1 ; WX 1000 ; N trademark ; B 179 306 1109 718 ;
+C -1 ; WX 556 ; N edotaccent ; B 70 -14 593 729 ;
+C -1 ; WX 278 ; N Igrave ; B 64 0 367 936 ;
+C -1 ; WX 278 ; N Imacron ; B 64 0 496 864 ;
+C -1 ; WX 611 ; N Lcaron ; B 76 0 643 718 ;
+C -1 ; WX 834 ; N onehalf ; B 132 -19 858 710 ;
+C -1 ; WX 549 ; N lessequal ; B 29 0 676 704 ;
+C -1 ; WX 611 ; N ocircumflex ; B 82 -14 643 750 ;
+C -1 ; WX 611 ; N ntilde ; B 65 0 646 737 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 116 -19 880 936 ;
+C -1 ; WX 667 ; N Eacute ; B 76 0 757 936 ;
+C -1 ; WX 556 ; N emacron ; B 70 -14 595 678 ;
+C -1 ; WX 611 ; N gbreve ; B 38 -217 666 750 ;
+C -1 ; WX 834 ; N onequarter ; B 132 -19 806 710 ;
+C -1 ; WX 667 ; N Scaron ; B 81 -19 718 936 ;
+C -1 ; WX 667 ; N Scommaaccent ; B 81 -228 718 737 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 107 -19 908 936 ;
+C -1 ; WX 400 ; N degree ; B 175 426 467 712 ;
+C -1 ; WX 611 ; N ograve ; B 82 -14 643 750 ;
+C -1 ; WX 722 ; N Ccaron ; B 107 -19 789 936 ;
+C -1 ; WX 611 ; N ugrave ; B 98 -14 658 750 ;
+C -1 ; WX 549 ; N radical ; B 112 -46 689 850 ;
+C -1 ; WX 722 ; N Dcaron ; B 76 0 777 936 ;
+C -1 ; WX 389 ; N rcommaaccent ; B 26 -228 489 546 ;
+C -1 ; WX 722 ; N Ntilde ; B 69 0 807 923 ;
+C -1 ; WX 611 ; N otilde ; B 82 -14 646 737 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 76 -228 778 718 ;
+C -1 ; WX 611 ; N Lcommaaccent ; B 76 -228 611 718 ;
+C -1 ; WX 722 ; N Atilde ; B 20 0 741 923 ;
+C -1 ; WX 722 ; N Aogonek ; B 20 -224 702 718 ;
+C -1 ; WX 722 ; N Aring ; B 20 0 702 962 ;
+C -1 ; WX 778 ; N Otilde ; B 107 -19 823 923 ;
+C -1 ; WX 500 ; N zdotaccent ; B 20 0 583 729 ;
+C -1 ; WX 667 ; N Ecaron ; B 76 0 757 936 ;
+C -1 ; WX 278 ; N Iogonek ; B -41 -228 367 718 ;
+C -1 ; WX 556 ; N kcommaaccent ; B 69 -228 670 718 ;
+C -1 ; WX 584 ; N minus ; B 82 197 610 309 ;
+C -1 ; WX 278 ; N Icircumflex ; B 64 0 484 936 ;
+C -1 ; WX 611 ; N ncaron ; B 65 0 641 750 ;
+C -1 ; WX 333 ; N tcommaaccent ; B 58 -228 422 676 ;
+C -1 ; WX 584 ; N logicalnot ; B 105 108 633 419 ;
+C -1 ; WX 611 ; N odieresis ; B 82 -14 643 729 ;
+C -1 ; WX 611 ; N udieresis ; B 98 -14 658 729 ;
+C -1 ; WX 549 ; N notequal ; B 32 -49 630 570 ;
+C -1 ; WX 611 ; N gcommaaccent ; B 38 -217 666 850 ;
+C -1 ; WX 611 ; N eth ; B 82 -14 670 737 ;
+C -1 ; WX 500 ; N zcaron ; B 20 0 586 750 ;
+C -1 ; WX 611 ; N ncommaaccent ; B 65 -228 629 546 ;
+C -1 ; WX 333 ; N onesuperior ; B 148 283 388 710 ;
+C -1 ; WX 278 ; N imacron ; B 69 0 429 678 ;
+C -1 ; WX 556 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2481
+KPX A C -40
+KPX A Cacute -40
+KPX A Ccaron -40
+KPX A Ccedilla -40
+KPX A G -50
+KPX A Gbreve -50
+KPX A Gcommaaccent -50
+KPX A O -40
+KPX A Oacute -40
+KPX A Ocircumflex -40
+KPX A Odieresis -40
+KPX A Ograve -40
+KPX A Ohungarumlaut -40
+KPX A Omacron -40
+KPX A Oslash -40
+KPX A Otilde -40
+KPX A Q -40
+KPX A T -90
+KPX A Tcaron -90
+KPX A Tcommaaccent -90
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -80
+KPX A W -60
+KPX A Y -110
+KPX A Yacute -110
+KPX A Ydieresis -110
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -40
+KPX A w -30
+KPX A y -30
+KPX A yacute -30
+KPX A ydieresis -30
+KPX Aacute C -40
+KPX Aacute Cacute -40
+KPX Aacute Ccaron -40
+KPX Aacute Ccedilla -40
+KPX Aacute G -50
+KPX Aacute Gbreve -50
+KPX Aacute Gcommaaccent -50
+KPX Aacute O -40
+KPX Aacute Oacute -40
+KPX Aacute Ocircumflex -40
+KPX Aacute Odieresis -40
+KPX Aacute Ograve -40
+KPX Aacute Ohungarumlaut -40
+KPX Aacute Omacron -40
+KPX Aacute Oslash -40
+KPX Aacute Otilde -40
+KPX Aacute Q -40
+KPX Aacute T -90
+KPX Aacute Tcaron -90
+KPX Aacute Tcommaaccent -90
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -80
+KPX Aacute W -60
+KPX Aacute Y -110
+KPX Aacute Yacute -110
+KPX Aacute Ydieresis -110
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -40
+KPX Aacute w -30
+KPX Aacute y -30
+KPX Aacute yacute -30
+KPX Aacute ydieresis -30
+KPX Abreve C -40
+KPX Abreve Cacute -40
+KPX Abreve Ccaron -40
+KPX Abreve Ccedilla -40
+KPX Abreve G -50
+KPX Abreve Gbreve -50
+KPX Abreve Gcommaaccent -50
+KPX Abreve O -40
+KPX Abreve Oacute -40
+KPX Abreve Ocircumflex -40
+KPX Abreve Odieresis -40
+KPX Abreve Ograve -40
+KPX Abreve Ohungarumlaut -40
+KPX Abreve Omacron -40
+KPX Abreve Oslash -40
+KPX Abreve Otilde -40
+KPX Abreve Q -40
+KPX Abreve T -90
+KPX Abreve Tcaron -90
+KPX Abreve Tcommaaccent -90
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -80
+KPX Abreve W -60
+KPX Abreve Y -110
+KPX Abreve Yacute -110
+KPX Abreve Ydieresis -110
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -40
+KPX Abreve w -30
+KPX Abreve y -30
+KPX Abreve yacute -30
+KPX Abreve ydieresis -30
+KPX Acircumflex C -40
+KPX Acircumflex Cacute -40
+KPX Acircumflex Ccaron -40
+KPX Acircumflex Ccedilla -40
+KPX Acircumflex G -50
+KPX Acircumflex Gbreve -50
+KPX Acircumflex Gcommaaccent -50
+KPX Acircumflex O -40
+KPX Acircumflex Oacute -40
+KPX Acircumflex Ocircumflex -40
+KPX Acircumflex Odieresis -40
+KPX Acircumflex Ograve -40
+KPX Acircumflex Ohungarumlaut -40
+KPX Acircumflex Omacron -40
+KPX Acircumflex Oslash -40
+KPX Acircumflex Otilde -40
+KPX Acircumflex Q -40
+KPX Acircumflex T -90
+KPX Acircumflex Tcaron -90
+KPX Acircumflex Tcommaaccent -90
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -80
+KPX Acircumflex W -60
+KPX Acircumflex Y -110
+KPX Acircumflex Yacute -110
+KPX Acircumflex Ydieresis -110
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -40
+KPX Acircumflex w -30
+KPX Acircumflex y -30
+KPX Acircumflex yacute -30
+KPX Acircumflex ydieresis -30
+KPX Adieresis C -40
+KPX Adieresis Cacute -40
+KPX Adieresis Ccaron -40
+KPX Adieresis Ccedilla -40
+KPX Adieresis G -50
+KPX Adieresis Gbreve -50
+KPX Adieresis Gcommaaccent -50
+KPX Adieresis O -40
+KPX Adieresis Oacute -40
+KPX Adieresis Ocircumflex -40
+KPX Adieresis Odieresis -40
+KPX Adieresis Ograve -40
+KPX Adieresis Ohungarumlaut -40
+KPX Adieresis Omacron -40
+KPX Adieresis Oslash -40
+KPX Adieresis Otilde -40
+KPX Adieresis Q -40
+KPX Adieresis T -90
+KPX Adieresis Tcaron -90
+KPX Adieresis Tcommaaccent -90
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -80
+KPX Adieresis W -60
+KPX Adieresis Y -110
+KPX Adieresis Yacute -110
+KPX Adieresis Ydieresis -110
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -40
+KPX Adieresis w -30
+KPX Adieresis y -30
+KPX Adieresis yacute -30
+KPX Adieresis ydieresis -30
+KPX Agrave C -40
+KPX Agrave Cacute -40
+KPX Agrave Ccaron -40
+KPX Agrave Ccedilla -40
+KPX Agrave G -50
+KPX Agrave Gbreve -50
+KPX Agrave Gcommaaccent -50
+KPX Agrave O -40
+KPX Agrave Oacute -40
+KPX Agrave Ocircumflex -40
+KPX Agrave Odieresis -40
+KPX Agrave Ograve -40
+KPX Agrave Ohungarumlaut -40
+KPX Agrave Omacron -40
+KPX Agrave Oslash -40
+KPX Agrave Otilde -40
+KPX Agrave Q -40
+KPX Agrave T -90
+KPX Agrave Tcaron -90
+KPX Agrave Tcommaaccent -90
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -80
+KPX Agrave W -60
+KPX Agrave Y -110
+KPX Agrave Yacute -110
+KPX Agrave Ydieresis -110
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -40
+KPX Agrave w -30
+KPX Agrave y -30
+KPX Agrave yacute -30
+KPX Agrave ydieresis -30
+KPX Amacron C -40
+KPX Amacron Cacute -40
+KPX Amacron Ccaron -40
+KPX Amacron Ccedilla -40
+KPX Amacron G -50
+KPX Amacron Gbreve -50
+KPX Amacron Gcommaaccent -50
+KPX Amacron O -40
+KPX Amacron Oacute -40
+KPX Amacron Ocircumflex -40
+KPX Amacron Odieresis -40
+KPX Amacron Ograve -40
+KPX Amacron Ohungarumlaut -40
+KPX Amacron Omacron -40
+KPX Amacron Oslash -40
+KPX Amacron Otilde -40
+KPX Amacron Q -40
+KPX Amacron T -90
+KPX Amacron Tcaron -90
+KPX Amacron Tcommaaccent -90
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -80
+KPX Amacron W -60
+KPX Amacron Y -110
+KPX Amacron Yacute -110
+KPX Amacron Ydieresis -110
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -40
+KPX Amacron w -30
+KPX Amacron y -30
+KPX Amacron yacute -30
+KPX Amacron ydieresis -30
+KPX Aogonek C -40
+KPX Aogonek Cacute -40
+KPX Aogonek Ccaron -40
+KPX Aogonek Ccedilla -40
+KPX Aogonek G -50
+KPX Aogonek Gbreve -50
+KPX Aogonek Gcommaaccent -50
+KPX Aogonek O -40
+KPX Aogonek Oacute -40
+KPX Aogonek Ocircumflex -40
+KPX Aogonek Odieresis -40
+KPX Aogonek Ograve -40
+KPX Aogonek Ohungarumlaut -40
+KPX Aogonek Omacron -40
+KPX Aogonek Oslash -40
+KPX Aogonek Otilde -40
+KPX Aogonek Q -40
+KPX Aogonek T -90
+KPX Aogonek Tcaron -90
+KPX Aogonek Tcommaaccent -90
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -80
+KPX Aogonek W -60
+KPX Aogonek Y -110
+KPX Aogonek Yacute -110
+KPX Aogonek Ydieresis -110
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -40
+KPX Aogonek w -30
+KPX Aogonek y -30
+KPX Aogonek yacute -30
+KPX Aogonek ydieresis -30
+KPX Aring C -40
+KPX Aring Cacute -40
+KPX Aring Ccaron -40
+KPX Aring Ccedilla -40
+KPX Aring G -50
+KPX Aring Gbreve -50
+KPX Aring Gcommaaccent -50
+KPX Aring O -40
+KPX Aring Oacute -40
+KPX Aring Ocircumflex -40
+KPX Aring Odieresis -40
+KPX Aring Ograve -40
+KPX Aring Ohungarumlaut -40
+KPX Aring Omacron -40
+KPX Aring Oslash -40
+KPX Aring Otilde -40
+KPX Aring Q -40
+KPX Aring T -90
+KPX Aring Tcaron -90
+KPX Aring Tcommaaccent -90
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -80
+KPX Aring W -60
+KPX Aring Y -110
+KPX Aring Yacute -110
+KPX Aring Ydieresis -110
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -40
+KPX Aring w -30
+KPX Aring y -30
+KPX Aring yacute -30
+KPX Aring ydieresis -30
+KPX Atilde C -40
+KPX Atilde Cacute -40
+KPX Atilde Ccaron -40
+KPX Atilde Ccedilla -40
+KPX Atilde G -50
+KPX Atilde Gbreve -50
+KPX Atilde Gcommaaccent -50
+KPX Atilde O -40
+KPX Atilde Oacute -40
+KPX Atilde Ocircumflex -40
+KPX Atilde Odieresis -40
+KPX Atilde Ograve -40
+KPX Atilde Ohungarumlaut -40
+KPX Atilde Omacron -40
+KPX Atilde Oslash -40
+KPX Atilde Otilde -40
+KPX Atilde Q -40
+KPX Atilde T -90
+KPX Atilde Tcaron -90
+KPX Atilde Tcommaaccent -90
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -80
+KPX Atilde W -60
+KPX Atilde Y -110
+KPX Atilde Yacute -110
+KPX Atilde Ydieresis -110
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -40
+KPX Atilde w -30
+KPX Atilde y -30
+KPX Atilde yacute -30
+KPX Atilde ydieresis -30
+KPX B A -30
+KPX B Aacute -30
+KPX B Abreve -30
+KPX B Acircumflex -30
+KPX B Adieresis -30
+KPX B Agrave -30
+KPX B Amacron -30
+KPX B Aogonek -30
+KPX B Aring -30
+KPX B Atilde -30
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -40
+KPX D W -40
+KPX D Y -70
+KPX D Yacute -70
+KPX D Ydieresis -70
+KPX D comma -30
+KPX D period -30
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -40
+KPX Dcaron W -40
+KPX Dcaron Y -70
+KPX Dcaron Yacute -70
+KPX Dcaron Ydieresis -70
+KPX Dcaron comma -30
+KPX Dcaron period -30
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -40
+KPX Dcroat W -40
+KPX Dcroat Y -70
+KPX Dcroat Yacute -70
+KPX Dcroat Ydieresis -70
+KPX Dcroat comma -30
+KPX Dcroat period -30
+KPX F A -80
+KPX F Aacute -80
+KPX F Abreve -80
+KPX F Acircumflex -80
+KPX F Adieresis -80
+KPX F Agrave -80
+KPX F Amacron -80
+KPX F Aogonek -80
+KPX F Aring -80
+KPX F Atilde -80
+KPX F a -20
+KPX F aacute -20
+KPX F abreve -20
+KPX F acircumflex -20
+KPX F adieresis -20
+KPX F agrave -20
+KPX F amacron -20
+KPX F aogonek -20
+KPX F aring -20
+KPX F atilde -20
+KPX F comma -100
+KPX F period -100
+KPX J A -20
+KPX J Aacute -20
+KPX J Abreve -20
+KPX J Acircumflex -20
+KPX J Adieresis -20
+KPX J Agrave -20
+KPX J Amacron -20
+KPX J Aogonek -20
+KPX J Aring -20
+KPX J Atilde -20
+KPX J comma -20
+KPX J period -20
+KPX J u -20
+KPX J uacute -20
+KPX J ucircumflex -20
+KPX J udieresis -20
+KPX J ugrave -20
+KPX J uhungarumlaut -20
+KPX J umacron -20
+KPX J uogonek -20
+KPX J uring -20
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -15
+KPX K eacute -15
+KPX K ecaron -15
+KPX K ecircumflex -15
+KPX K edieresis -15
+KPX K edotaccent -15
+KPX K egrave -15
+KPX K emacron -15
+KPX K eogonek -15
+KPX K o -35
+KPX K oacute -35
+KPX K ocircumflex -35
+KPX K odieresis -35
+KPX K ograve -35
+KPX K ohungarumlaut -35
+KPX K omacron -35
+KPX K oslash -35
+KPX K otilde -35
+KPX K u -30
+KPX K uacute -30
+KPX K ucircumflex -30
+KPX K udieresis -30
+KPX K ugrave -30
+KPX K uhungarumlaut -30
+KPX K umacron -30
+KPX K uogonek -30
+KPX K uring -30
+KPX K y -40
+KPX K yacute -40
+KPX K ydieresis -40
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -15
+KPX Kcommaaccent eacute -15
+KPX Kcommaaccent ecaron -15
+KPX Kcommaaccent ecircumflex -15
+KPX Kcommaaccent edieresis -15
+KPX Kcommaaccent edotaccent -15
+KPX Kcommaaccent egrave -15
+KPX Kcommaaccent emacron -15
+KPX Kcommaaccent eogonek -15
+KPX Kcommaaccent o -35
+KPX Kcommaaccent oacute -35
+KPX Kcommaaccent ocircumflex -35
+KPX Kcommaaccent odieresis -35
+KPX Kcommaaccent ograve -35
+KPX Kcommaaccent ohungarumlaut -35
+KPX Kcommaaccent omacron -35
+KPX Kcommaaccent oslash -35
+KPX Kcommaaccent otilde -35
+KPX Kcommaaccent u -30
+KPX Kcommaaccent uacute -30
+KPX Kcommaaccent ucircumflex -30
+KPX Kcommaaccent udieresis -30
+KPX Kcommaaccent ugrave -30
+KPX Kcommaaccent uhungarumlaut -30
+KPX Kcommaaccent umacron -30
+KPX Kcommaaccent uogonek -30
+KPX Kcommaaccent uring -30
+KPX Kcommaaccent y -40
+KPX Kcommaaccent yacute -40
+KPX Kcommaaccent ydieresis -40
+KPX L T -90
+KPX L Tcaron -90
+KPX L Tcommaaccent -90
+KPX L V -110
+KPX L W -80
+KPX L Y -120
+KPX L Yacute -120
+KPX L Ydieresis -120
+KPX L quotedblright -140
+KPX L quoteright -140
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -90
+KPX Lacute Tcaron -90
+KPX Lacute Tcommaaccent -90
+KPX Lacute V -110
+KPX Lacute W -80
+KPX Lacute Y -120
+KPX Lacute Yacute -120
+KPX Lacute Ydieresis -120
+KPX Lacute quotedblright -140
+KPX Lacute quoteright -140
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcommaaccent T -90
+KPX Lcommaaccent Tcaron -90
+KPX Lcommaaccent Tcommaaccent -90
+KPX Lcommaaccent V -110
+KPX Lcommaaccent W -80
+KPX Lcommaaccent Y -120
+KPX Lcommaaccent Yacute -120
+KPX Lcommaaccent Ydieresis -120
+KPX Lcommaaccent quotedblright -140
+KPX Lcommaaccent quoteright -140
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -90
+KPX Lslash Tcaron -90
+KPX Lslash Tcommaaccent -90
+KPX Lslash V -110
+KPX Lslash W -80
+KPX Lslash Y -120
+KPX Lslash Yacute -120
+KPX Lslash Ydieresis -120
+KPX Lslash quotedblright -140
+KPX Lslash quoteright -140
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX O A -50
+KPX O Aacute -50
+KPX O Abreve -50
+KPX O Acircumflex -50
+KPX O Adieresis -50
+KPX O Agrave -50
+KPX O Amacron -50
+KPX O Aogonek -50
+KPX O Aring -50
+KPX O Atilde -50
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -50
+KPX O Y -70
+KPX O Yacute -70
+KPX O Ydieresis -70
+KPX O comma -40
+KPX O period -40
+KPX Oacute A -50
+KPX Oacute Aacute -50
+KPX Oacute Abreve -50
+KPX Oacute Acircumflex -50
+KPX Oacute Adieresis -50
+KPX Oacute Agrave -50
+KPX Oacute Amacron -50
+KPX Oacute Aogonek -50
+KPX Oacute Aring -50
+KPX Oacute Atilde -50
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -50
+KPX Oacute Y -70
+KPX Oacute Yacute -70
+KPX Oacute Ydieresis -70
+KPX Oacute comma -40
+KPX Oacute period -40
+KPX Ocircumflex A -50
+KPX Ocircumflex Aacute -50
+KPX Ocircumflex Abreve -50
+KPX Ocircumflex Acircumflex -50
+KPX Ocircumflex Adieresis -50
+KPX Ocircumflex Agrave -50
+KPX Ocircumflex Amacron -50
+KPX Ocircumflex Aogonek -50
+KPX Ocircumflex Aring -50
+KPX Ocircumflex Atilde -50
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -50
+KPX Ocircumflex Y -70
+KPX Ocircumflex Yacute -70
+KPX Ocircumflex Ydieresis -70
+KPX Ocircumflex comma -40
+KPX Ocircumflex period -40
+KPX Odieresis A -50
+KPX Odieresis Aacute -50
+KPX Odieresis Abreve -50
+KPX Odieresis Acircumflex -50
+KPX Odieresis Adieresis -50
+KPX Odieresis Agrave -50
+KPX Odieresis Amacron -50
+KPX Odieresis Aogonek -50
+KPX Odieresis Aring -50
+KPX Odieresis Atilde -50
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -50
+KPX Odieresis Y -70
+KPX Odieresis Yacute -70
+KPX Odieresis Ydieresis -70
+KPX Odieresis comma -40
+KPX Odieresis period -40
+KPX Ograve A -50
+KPX Ograve Aacute -50
+KPX Ograve Abreve -50
+KPX Ograve Acircumflex -50
+KPX Ograve Adieresis -50
+KPX Ograve Agrave -50
+KPX Ograve Amacron -50
+KPX Ograve Aogonek -50
+KPX Ograve Aring -50
+KPX Ograve Atilde -50
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -50
+KPX Ograve Y -70
+KPX Ograve Yacute -70
+KPX Ograve Ydieresis -70
+KPX Ograve comma -40
+KPX Ograve period -40
+KPX Ohungarumlaut A -50
+KPX Ohungarumlaut Aacute -50
+KPX Ohungarumlaut Abreve -50
+KPX Ohungarumlaut Acircumflex -50
+KPX Ohungarumlaut Adieresis -50
+KPX Ohungarumlaut Agrave -50
+KPX Ohungarumlaut Amacron -50
+KPX Ohungarumlaut Aogonek -50
+KPX Ohungarumlaut Aring -50
+KPX Ohungarumlaut Atilde -50
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -50
+KPX Ohungarumlaut Y -70
+KPX Ohungarumlaut Yacute -70
+KPX Ohungarumlaut Ydieresis -70
+KPX Ohungarumlaut comma -40
+KPX Ohungarumlaut period -40
+KPX Omacron A -50
+KPX Omacron Aacute -50
+KPX Omacron Abreve -50
+KPX Omacron Acircumflex -50
+KPX Omacron Adieresis -50
+KPX Omacron Agrave -50
+KPX Omacron Amacron -50
+KPX Omacron Aogonek -50
+KPX Omacron Aring -50
+KPX Omacron Atilde -50
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -50
+KPX Omacron Y -70
+KPX Omacron Yacute -70
+KPX Omacron Ydieresis -70
+KPX Omacron comma -40
+KPX Omacron period -40
+KPX Oslash A -50
+KPX Oslash Aacute -50
+KPX Oslash Abreve -50
+KPX Oslash Acircumflex -50
+KPX Oslash Adieresis -50
+KPX Oslash Agrave -50
+KPX Oslash Amacron -50
+KPX Oslash Aogonek -50
+KPX Oslash Aring -50
+KPX Oslash Atilde -50
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -50
+KPX Oslash Y -70
+KPX Oslash Yacute -70
+KPX Oslash Ydieresis -70
+KPX Oslash comma -40
+KPX Oslash period -40
+KPX Otilde A -50
+KPX Otilde Aacute -50
+KPX Otilde Abreve -50
+KPX Otilde Acircumflex -50
+KPX Otilde Adieresis -50
+KPX Otilde Agrave -50
+KPX Otilde Amacron -50
+KPX Otilde Aogonek -50
+KPX Otilde Aring -50
+KPX Otilde Atilde -50
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -50
+KPX Otilde Y -70
+KPX Otilde Yacute -70
+KPX Otilde Ydieresis -70
+KPX Otilde comma -40
+KPX Otilde period -40
+KPX P A -100
+KPX P Aacute -100
+KPX P Abreve -100
+KPX P Acircumflex -100
+KPX P Adieresis -100
+KPX P Agrave -100
+KPX P Amacron -100
+KPX P Aogonek -100
+KPX P Aring -100
+KPX P Atilde -100
+KPX P a -30
+KPX P aacute -30
+KPX P abreve -30
+KPX P acircumflex -30
+KPX P adieresis -30
+KPX P agrave -30
+KPX P amacron -30
+KPX P aogonek -30
+KPX P aring -30
+KPX P atilde -30
+KPX P comma -120
+KPX P e -30
+KPX P eacute -30
+KPX P ecaron -30
+KPX P ecircumflex -30
+KPX P edieresis -30
+KPX P edotaccent -30
+KPX P egrave -30
+KPX P emacron -30
+KPX P eogonek -30
+KPX P o -40
+KPX P oacute -40
+KPX P ocircumflex -40
+KPX P odieresis -40
+KPX P ograve -40
+KPX P ohungarumlaut -40
+KPX P omacron -40
+KPX P oslash -40
+KPX P otilde -40
+KPX P period -120
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX Q comma 20
+KPX Q period 20
+KPX R O -20
+KPX R Oacute -20
+KPX R Ocircumflex -20
+KPX R Odieresis -20
+KPX R Ograve -20
+KPX R Ohungarumlaut -20
+KPX R Omacron -20
+KPX R Oslash -20
+KPX R Otilde -20
+KPX R T -20
+KPX R Tcaron -20
+KPX R Tcommaaccent -20
+KPX R U -20
+KPX R Uacute -20
+KPX R Ucircumflex -20
+KPX R Udieresis -20
+KPX R Ugrave -20
+KPX R Uhungarumlaut -20
+KPX R Umacron -20
+KPX R Uogonek -20
+KPX R Uring -20
+KPX R V -50
+KPX R W -40
+KPX R Y -50
+KPX R Yacute -50
+KPX R Ydieresis -50
+KPX Racute O -20
+KPX Racute Oacute -20
+KPX Racute Ocircumflex -20
+KPX Racute Odieresis -20
+KPX Racute Ograve -20
+KPX Racute Ohungarumlaut -20
+KPX Racute Omacron -20
+KPX Racute Oslash -20
+KPX Racute Otilde -20
+KPX Racute T -20
+KPX Racute Tcaron -20
+KPX Racute Tcommaaccent -20
+KPX Racute U -20
+KPX Racute Uacute -20
+KPX Racute Ucircumflex -20
+KPX Racute Udieresis -20
+KPX Racute Ugrave -20
+KPX Racute Uhungarumlaut -20
+KPX Racute Umacron -20
+KPX Racute Uogonek -20
+KPX Racute Uring -20
+KPX Racute V -50
+KPX Racute W -40
+KPX Racute Y -50
+KPX Racute Yacute -50
+KPX Racute Ydieresis -50
+KPX Rcaron O -20
+KPX Rcaron Oacute -20
+KPX Rcaron Ocircumflex -20
+KPX Rcaron Odieresis -20
+KPX Rcaron Ograve -20
+KPX Rcaron Ohungarumlaut -20
+KPX Rcaron Omacron -20
+KPX Rcaron Oslash -20
+KPX Rcaron Otilde -20
+KPX Rcaron T -20
+KPX Rcaron Tcaron -20
+KPX Rcaron Tcommaaccent -20
+KPX Rcaron U -20
+KPX Rcaron Uacute -20
+KPX Rcaron Ucircumflex -20
+KPX Rcaron Udieresis -20
+KPX Rcaron Ugrave -20
+KPX Rcaron Uhungarumlaut -20
+KPX Rcaron Umacron -20
+KPX Rcaron Uogonek -20
+KPX Rcaron Uring -20
+KPX Rcaron V -50
+KPX Rcaron W -40
+KPX Rcaron Y -50
+KPX Rcaron Yacute -50
+KPX Rcaron Ydieresis -50
+KPX Rcommaaccent O -20
+KPX Rcommaaccent Oacute -20
+KPX Rcommaaccent Ocircumflex -20
+KPX Rcommaaccent Odieresis -20
+KPX Rcommaaccent Ograve -20
+KPX Rcommaaccent Ohungarumlaut -20
+KPX Rcommaaccent Omacron -20
+KPX Rcommaaccent Oslash -20
+KPX Rcommaaccent Otilde -20
+KPX Rcommaaccent T -20
+KPX Rcommaaccent Tcaron -20
+KPX Rcommaaccent Tcommaaccent -20
+KPX Rcommaaccent U -20
+KPX Rcommaaccent Uacute -20
+KPX Rcommaaccent Ucircumflex -20
+KPX Rcommaaccent Udieresis -20
+KPX Rcommaaccent Ugrave -20
+KPX Rcommaaccent Uhungarumlaut -20
+KPX Rcommaaccent Umacron -20
+KPX Rcommaaccent Uogonek -20
+KPX Rcommaaccent Uring -20
+KPX Rcommaaccent V -50
+KPX Rcommaaccent W -40
+KPX Rcommaaccent Y -50
+KPX Rcommaaccent Yacute -50
+KPX Rcommaaccent Ydieresis -50
+KPX T A -90
+KPX T Aacute -90
+KPX T Abreve -90
+KPX T Acircumflex -90
+KPX T Adieresis -90
+KPX T Agrave -90
+KPX T Amacron -90
+KPX T Aogonek -90
+KPX T Aring -90
+KPX T Atilde -90
+KPX T O -40
+KPX T Oacute -40
+KPX T Ocircumflex -40
+KPX T Odieresis -40
+KPX T Ograve -40
+KPX T Ohungarumlaut -40
+KPX T Omacron -40
+KPX T Oslash -40
+KPX T Otilde -40
+KPX T a -80
+KPX T aacute -80
+KPX T abreve -80
+KPX T acircumflex -80
+KPX T adieresis -80
+KPX T agrave -80
+KPX T amacron -80
+KPX T aogonek -80
+KPX T aring -80
+KPX T atilde -80
+KPX T colon -40
+KPX T comma -80
+KPX T e -60
+KPX T eacute -60
+KPX T ecaron -60
+KPX T ecircumflex -60
+KPX T edieresis -60
+KPX T edotaccent -60
+KPX T egrave -60
+KPX T emacron -60
+KPX T eogonek -60
+KPX T hyphen -120
+KPX T o -80
+KPX T oacute -80
+KPX T ocircumflex -80
+KPX T odieresis -80
+KPX T ograve -80
+KPX T ohungarumlaut -80
+KPX T omacron -80
+KPX T oslash -80
+KPX T otilde -80
+KPX T period -80
+KPX T r -80
+KPX T racute -80
+KPX T rcommaaccent -80
+KPX T semicolon -40
+KPX T u -90
+KPX T uacute -90
+KPX T ucircumflex -90
+KPX T udieresis -90
+KPX T ugrave -90
+KPX T uhungarumlaut -90
+KPX T umacron -90
+KPX T uogonek -90
+KPX T uring -90
+KPX T w -60
+KPX T y -60
+KPX T yacute -60
+KPX T ydieresis -60
+KPX Tcaron A -90
+KPX Tcaron Aacute -90
+KPX Tcaron Abreve -90
+KPX Tcaron Acircumflex -90
+KPX Tcaron Adieresis -90
+KPX Tcaron Agrave -90
+KPX Tcaron Amacron -90
+KPX Tcaron Aogonek -90
+KPX Tcaron Aring -90
+KPX Tcaron Atilde -90
+KPX Tcaron O -40
+KPX Tcaron Oacute -40
+KPX Tcaron Ocircumflex -40
+KPX Tcaron Odieresis -40
+KPX Tcaron Ograve -40
+KPX Tcaron Ohungarumlaut -40
+KPX Tcaron Omacron -40
+KPX Tcaron Oslash -40
+KPX Tcaron Otilde -40
+KPX Tcaron a -80
+KPX Tcaron aacute -80
+KPX Tcaron abreve -80
+KPX Tcaron acircumflex -80
+KPX Tcaron adieresis -80
+KPX Tcaron agrave -80
+KPX Tcaron amacron -80
+KPX Tcaron aogonek -80
+KPX Tcaron aring -80
+KPX Tcaron atilde -80
+KPX Tcaron colon -40
+KPX Tcaron comma -80
+KPX Tcaron e -60
+KPX Tcaron eacute -60
+KPX Tcaron ecaron -60
+KPX Tcaron ecircumflex -60
+KPX Tcaron edieresis -60
+KPX Tcaron edotaccent -60
+KPX Tcaron egrave -60
+KPX Tcaron emacron -60
+KPX Tcaron eogonek -60
+KPX Tcaron hyphen -120
+KPX Tcaron o -80
+KPX Tcaron oacute -80
+KPX Tcaron ocircumflex -80
+KPX Tcaron odieresis -80
+KPX Tcaron ograve -80
+KPX Tcaron ohungarumlaut -80
+KPX Tcaron omacron -80
+KPX Tcaron oslash -80
+KPX Tcaron otilde -80
+KPX Tcaron period -80
+KPX Tcaron r -80
+KPX Tcaron racute -80
+KPX Tcaron rcommaaccent -80
+KPX Tcaron semicolon -40
+KPX Tcaron u -90
+KPX Tcaron uacute -90
+KPX Tcaron ucircumflex -90
+KPX Tcaron udieresis -90
+KPX Tcaron ugrave -90
+KPX Tcaron uhungarumlaut -90
+KPX Tcaron umacron -90
+KPX Tcaron uogonek -90
+KPX Tcaron uring -90
+KPX Tcaron w -60
+KPX Tcaron y -60
+KPX Tcaron yacute -60
+KPX Tcaron ydieresis -60
+KPX Tcommaaccent A -90
+KPX Tcommaaccent Aacute -90
+KPX Tcommaaccent Abreve -90
+KPX Tcommaaccent Acircumflex -90
+KPX Tcommaaccent Adieresis -90
+KPX Tcommaaccent Agrave -90
+KPX Tcommaaccent Amacron -90
+KPX Tcommaaccent Aogonek -90
+KPX Tcommaaccent Aring -90
+KPX Tcommaaccent Atilde -90
+KPX Tcommaaccent O -40
+KPX Tcommaaccent Oacute -40
+KPX Tcommaaccent Ocircumflex -40
+KPX Tcommaaccent Odieresis -40
+KPX Tcommaaccent Ograve -40
+KPX Tcommaaccent Ohungarumlaut -40
+KPX Tcommaaccent Omacron -40
+KPX Tcommaaccent Oslash -40
+KPX Tcommaaccent Otilde -40
+KPX Tcommaaccent a -80
+KPX Tcommaaccent aacute -80
+KPX Tcommaaccent abreve -80
+KPX Tcommaaccent acircumflex -80
+KPX Tcommaaccent adieresis -80
+KPX Tcommaaccent agrave -80
+KPX Tcommaaccent amacron -80
+KPX Tcommaaccent aogonek -80
+KPX Tcommaaccent aring -80
+KPX Tcommaaccent atilde -80
+KPX Tcommaaccent colon -40
+KPX Tcommaaccent comma -80
+KPX Tcommaaccent e -60
+KPX Tcommaaccent eacute -60
+KPX Tcommaaccent ecaron -60
+KPX Tcommaaccent ecircumflex -60
+KPX Tcommaaccent edieresis -60
+KPX Tcommaaccent edotaccent -60
+KPX Tcommaaccent egrave -60
+KPX Tcommaaccent emacron -60
+KPX Tcommaaccent eogonek -60
+KPX Tcommaaccent hyphen -120
+KPX Tcommaaccent o -80
+KPX Tcommaaccent oacute -80
+KPX Tcommaaccent ocircumflex -80
+KPX Tcommaaccent odieresis -80
+KPX Tcommaaccent ograve -80
+KPX Tcommaaccent ohungarumlaut -80
+KPX Tcommaaccent omacron -80
+KPX Tcommaaccent oslash -80
+KPX Tcommaaccent otilde -80
+KPX Tcommaaccent period -80
+KPX Tcommaaccent r -80
+KPX Tcommaaccent racute -80
+KPX Tcommaaccent rcommaaccent -80
+KPX Tcommaaccent semicolon -40
+KPX Tcommaaccent u -90
+KPX Tcommaaccent uacute -90
+KPX Tcommaaccent ucircumflex -90
+KPX Tcommaaccent udieresis -90
+KPX Tcommaaccent ugrave -90
+KPX Tcommaaccent uhungarumlaut -90
+KPX Tcommaaccent umacron -90
+KPX Tcommaaccent uogonek -90
+KPX Tcommaaccent uring -90
+KPX Tcommaaccent w -60
+KPX Tcommaaccent y -60
+KPX Tcommaaccent yacute -60
+KPX Tcommaaccent ydieresis -60
+KPX U A -50
+KPX U Aacute -50
+KPX U Abreve -50
+KPX U Acircumflex -50
+KPX U Adieresis -50
+KPX U Agrave -50
+KPX U Amacron -50
+KPX U Aogonek -50
+KPX U Aring -50
+KPX U Atilde -50
+KPX U comma -30
+KPX U period -30
+KPX Uacute A -50
+KPX Uacute Aacute -50
+KPX Uacute Abreve -50
+KPX Uacute Acircumflex -50
+KPX Uacute Adieresis -50
+KPX Uacute Agrave -50
+KPX Uacute Amacron -50
+KPX Uacute Aogonek -50
+KPX Uacute Aring -50
+KPX Uacute Atilde -50
+KPX Uacute comma -30
+KPX Uacute period -30
+KPX Ucircumflex A -50
+KPX Ucircumflex Aacute -50
+KPX Ucircumflex Abreve -50
+KPX Ucircumflex Acircumflex -50
+KPX Ucircumflex Adieresis -50
+KPX Ucircumflex Agrave -50
+KPX Ucircumflex Amacron -50
+KPX Ucircumflex Aogonek -50
+KPX Ucircumflex Aring -50
+KPX Ucircumflex Atilde -50
+KPX Ucircumflex comma -30
+KPX Ucircumflex period -30
+KPX Udieresis A -50
+KPX Udieresis Aacute -50
+KPX Udieresis Abreve -50
+KPX Udieresis Acircumflex -50
+KPX Udieresis Adieresis -50
+KPX Udieresis Agrave -50
+KPX Udieresis Amacron -50
+KPX Udieresis Aogonek -50
+KPX Udieresis Aring -50
+KPX Udieresis Atilde -50
+KPX Udieresis comma -30
+KPX Udieresis period -30
+KPX Ugrave A -50
+KPX Ugrave Aacute -50
+KPX Ugrave Abreve -50
+KPX Ugrave Acircumflex -50
+KPX Ugrave Adieresis -50
+KPX Ugrave Agrave -50
+KPX Ugrave Amacron -50
+KPX Ugrave Aogonek -50
+KPX Ugrave Aring -50
+KPX Ugrave Atilde -50
+KPX Ugrave comma -30
+KPX Ugrave period -30
+KPX Uhungarumlaut A -50
+KPX Uhungarumlaut Aacute -50
+KPX Uhungarumlaut Abreve -50
+KPX Uhungarumlaut Acircumflex -50
+KPX Uhungarumlaut Adieresis -50
+KPX Uhungarumlaut Agrave -50
+KPX Uhungarumlaut Amacron -50
+KPX Uhungarumlaut Aogonek -50
+KPX Uhungarumlaut Aring -50
+KPX Uhungarumlaut Atilde -50
+KPX Uhungarumlaut comma -30
+KPX Uhungarumlaut period -30
+KPX Umacron A -50
+KPX Umacron Aacute -50
+KPX Umacron Abreve -50
+KPX Umacron Acircumflex -50
+KPX Umacron Adieresis -50
+KPX Umacron Agrave -50
+KPX Umacron Amacron -50
+KPX Umacron Aogonek -50
+KPX Umacron Aring -50
+KPX Umacron Atilde -50
+KPX Umacron comma -30
+KPX Umacron period -30
+KPX Uogonek A -50
+KPX Uogonek Aacute -50
+KPX Uogonek Abreve -50
+KPX Uogonek Acircumflex -50
+KPX Uogonek Adieresis -50
+KPX Uogonek Agrave -50
+KPX Uogonek Amacron -50
+KPX Uogonek Aogonek -50
+KPX Uogonek Aring -50
+KPX Uogonek Atilde -50
+KPX Uogonek comma -30
+KPX Uogonek period -30
+KPX Uring A -50
+KPX Uring Aacute -50
+KPX Uring Abreve -50
+KPX Uring Acircumflex -50
+KPX Uring Adieresis -50
+KPX Uring Agrave -50
+KPX Uring Amacron -50
+KPX Uring Aogonek -50
+KPX Uring Aring -50
+KPX Uring Atilde -50
+KPX Uring comma -30
+KPX Uring period -30
+KPX V A -80
+KPX V Aacute -80
+KPX V Abreve -80
+KPX V Acircumflex -80
+KPX V Adieresis -80
+KPX V Agrave -80
+KPX V Amacron -80
+KPX V Aogonek -80
+KPX V Aring -80
+KPX V Atilde -80
+KPX V G -50
+KPX V Gbreve -50
+KPX V Gcommaaccent -50
+KPX V O -50
+KPX V Oacute -50
+KPX V Ocircumflex -50
+KPX V Odieresis -50
+KPX V Ograve -50
+KPX V Ohungarumlaut -50
+KPX V Omacron -50
+KPX V Oslash -50
+KPX V Otilde -50
+KPX V a -60
+KPX V aacute -60
+KPX V abreve -60
+KPX V acircumflex -60
+KPX V adieresis -60
+KPX V agrave -60
+KPX V amacron -60
+KPX V aogonek -60
+KPX V aring -60
+KPX V atilde -60
+KPX V colon -40
+KPX V comma -120
+KPX V e -50
+KPX V eacute -50
+KPX V ecaron -50
+KPX V ecircumflex -50
+KPX V edieresis -50
+KPX V edotaccent -50
+KPX V egrave -50
+KPX V emacron -50
+KPX V eogonek -50
+KPX V hyphen -80
+KPX V o -90
+KPX V oacute -90
+KPX V ocircumflex -90
+KPX V odieresis -90
+KPX V ograve -90
+KPX V ohungarumlaut -90
+KPX V omacron -90
+KPX V oslash -90
+KPX V otilde -90
+KPX V period -120
+KPX V semicolon -40
+KPX V u -60
+KPX V uacute -60
+KPX V ucircumflex -60
+KPX V udieresis -60
+KPX V ugrave -60
+KPX V uhungarumlaut -60
+KPX V umacron -60
+KPX V uogonek -60
+KPX V uring -60
+KPX W A -60
+KPX W Aacute -60
+KPX W Abreve -60
+KPX W Acircumflex -60
+KPX W Adieresis -60
+KPX W Agrave -60
+KPX W Amacron -60
+KPX W Aogonek -60
+KPX W Aring -60
+KPX W Atilde -60
+KPX W O -20
+KPX W Oacute -20
+KPX W Ocircumflex -20
+KPX W Odieresis -20
+KPX W Ograve -20
+KPX W Ohungarumlaut -20
+KPX W Omacron -20
+KPX W Oslash -20
+KPX W Otilde -20
+KPX W a -40
+KPX W aacute -40
+KPX W abreve -40
+KPX W acircumflex -40
+KPX W adieresis -40
+KPX W agrave -40
+KPX W amacron -40
+KPX W aogonek -40
+KPX W aring -40
+KPX W atilde -40
+KPX W colon -10
+KPX W comma -80
+KPX W e -35
+KPX W eacute -35
+KPX W ecaron -35
+KPX W ecircumflex -35
+KPX W edieresis -35
+KPX W edotaccent -35
+KPX W egrave -35
+KPX W emacron -35
+KPX W eogonek -35
+KPX W hyphen -40
+KPX W o -60
+KPX W oacute -60
+KPX W ocircumflex -60
+KPX W odieresis -60
+KPX W ograve -60
+KPX W ohungarumlaut -60
+KPX W omacron -60
+KPX W oslash -60
+KPX W otilde -60
+KPX W period -80
+KPX W semicolon -10
+KPX W u -45
+KPX W uacute -45
+KPX W ucircumflex -45
+KPX W udieresis -45
+KPX W ugrave -45
+KPX W uhungarumlaut -45
+KPX W umacron -45
+KPX W uogonek -45
+KPX W uring -45
+KPX W y -20
+KPX W yacute -20
+KPX W ydieresis -20
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -70
+KPX Y Oacute -70
+KPX Y Ocircumflex -70
+KPX Y Odieresis -70
+KPX Y Ograve -70
+KPX Y Ohungarumlaut -70
+KPX Y Omacron -70
+KPX Y Oslash -70
+KPX Y Otilde -70
+KPX Y a -90
+KPX Y aacute -90
+KPX Y abreve -90
+KPX Y acircumflex -90
+KPX Y adieresis -90
+KPX Y agrave -90
+KPX Y amacron -90
+KPX Y aogonek -90
+KPX Y aring -90
+KPX Y atilde -90
+KPX Y colon -50
+KPX Y comma -100
+KPX Y e -80
+KPX Y eacute -80
+KPX Y ecaron -80
+KPX Y ecircumflex -80
+KPX Y edieresis -80
+KPX Y edotaccent -80
+KPX Y egrave -80
+KPX Y emacron -80
+KPX Y eogonek -80
+KPX Y o -100
+KPX Y oacute -100
+KPX Y ocircumflex -100
+KPX Y odieresis -100
+KPX Y ograve -100
+KPX Y ohungarumlaut -100
+KPX Y omacron -100
+KPX Y oslash -100
+KPX Y otilde -100
+KPX Y period -100
+KPX Y semicolon -50
+KPX Y u -100
+KPX Y uacute -100
+KPX Y ucircumflex -100
+KPX Y udieresis -100
+KPX Y ugrave -100
+KPX Y uhungarumlaut -100
+KPX Y umacron -100
+KPX Y uogonek -100
+KPX Y uring -100
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -70
+KPX Yacute Oacute -70
+KPX Yacute Ocircumflex -70
+KPX Yacute Odieresis -70
+KPX Yacute Ograve -70
+KPX Yacute Ohungarumlaut -70
+KPX Yacute Omacron -70
+KPX Yacute Oslash -70
+KPX Yacute Otilde -70
+KPX Yacute a -90
+KPX Yacute aacute -90
+KPX Yacute abreve -90
+KPX Yacute acircumflex -90
+KPX Yacute adieresis -90
+KPX Yacute agrave -90
+KPX Yacute amacron -90
+KPX Yacute aogonek -90
+KPX Yacute aring -90
+KPX Yacute atilde -90
+KPX Yacute colon -50
+KPX Yacute comma -100
+KPX Yacute e -80
+KPX Yacute eacute -80
+KPX Yacute ecaron -80
+KPX Yacute ecircumflex -80
+KPX Yacute edieresis -80
+KPX Yacute edotaccent -80
+KPX Yacute egrave -80
+KPX Yacute emacron -80
+KPX Yacute eogonek -80
+KPX Yacute o -100
+KPX Yacute oacute -100
+KPX Yacute ocircumflex -100
+KPX Yacute odieresis -100
+KPX Yacute ograve -100
+KPX Yacute ohungarumlaut -100
+KPX Yacute omacron -100
+KPX Yacute oslash -100
+KPX Yacute otilde -100
+KPX Yacute period -100
+KPX Yacute semicolon -50
+KPX Yacute u -100
+KPX Yacute uacute -100
+KPX Yacute ucircumflex -100
+KPX Yacute udieresis -100
+KPX Yacute ugrave -100
+KPX Yacute uhungarumlaut -100
+KPX Yacute umacron -100
+KPX Yacute uogonek -100
+KPX Yacute uring -100
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -70
+KPX Ydieresis Oacute -70
+KPX Ydieresis Ocircumflex -70
+KPX Ydieresis Odieresis -70
+KPX Ydieresis Ograve -70
+KPX Ydieresis Ohungarumlaut -70
+KPX Ydieresis Omacron -70
+KPX Ydieresis Oslash -70
+KPX Ydieresis Otilde -70
+KPX Ydieresis a -90
+KPX Ydieresis aacute -90
+KPX Ydieresis abreve -90
+KPX Ydieresis acircumflex -90
+KPX Ydieresis adieresis -90
+KPX Ydieresis agrave -90
+KPX Ydieresis amacron -90
+KPX Ydieresis aogonek -90
+KPX Ydieresis aring -90
+KPX Ydieresis atilde -90
+KPX Ydieresis colon -50
+KPX Ydieresis comma -100
+KPX Ydieresis e -80
+KPX Ydieresis eacute -80
+KPX Ydieresis ecaron -80
+KPX Ydieresis ecircumflex -80
+KPX Ydieresis edieresis -80
+KPX Ydieresis edotaccent -80
+KPX Ydieresis egrave -80
+KPX Ydieresis emacron -80
+KPX Ydieresis eogonek -80
+KPX Ydieresis o -100
+KPX Ydieresis oacute -100
+KPX Ydieresis ocircumflex -100
+KPX Ydieresis odieresis -100
+KPX Ydieresis ograve -100
+KPX Ydieresis ohungarumlaut -100
+KPX Ydieresis omacron -100
+KPX Ydieresis oslash -100
+KPX Ydieresis otilde -100
+KPX Ydieresis period -100
+KPX Ydieresis semicolon -50
+KPX Ydieresis u -100
+KPX Ydieresis uacute -100
+KPX Ydieresis ucircumflex -100
+KPX Ydieresis udieresis -100
+KPX Ydieresis ugrave -100
+KPX Ydieresis uhungarumlaut -100
+KPX Ydieresis umacron -100
+KPX Ydieresis uogonek -100
+KPX Ydieresis uring -100
+KPX a g -10
+KPX a gbreve -10
+KPX a gcommaaccent -10
+KPX a v -15
+KPX a w -15
+KPX a y -20
+KPX a yacute -20
+KPX a ydieresis -20
+KPX aacute g -10
+KPX aacute gbreve -10
+KPX aacute gcommaaccent -10
+KPX aacute v -15
+KPX aacute w -15
+KPX aacute y -20
+KPX aacute yacute -20
+KPX aacute ydieresis -20
+KPX abreve g -10
+KPX abreve gbreve -10
+KPX abreve gcommaaccent -10
+KPX abreve v -15
+KPX abreve w -15
+KPX abreve y -20
+KPX abreve yacute -20
+KPX abreve ydieresis -20
+KPX acircumflex g -10
+KPX acircumflex gbreve -10
+KPX acircumflex gcommaaccent -10
+KPX acircumflex v -15
+KPX acircumflex w -15
+KPX acircumflex y -20
+KPX acircumflex yacute -20
+KPX acircumflex ydieresis -20
+KPX adieresis g -10
+KPX adieresis gbreve -10
+KPX adieresis gcommaaccent -10
+KPX adieresis v -15
+KPX adieresis w -15
+KPX adieresis y -20
+KPX adieresis yacute -20
+KPX adieresis ydieresis -20
+KPX agrave g -10
+KPX agrave gbreve -10
+KPX agrave gcommaaccent -10
+KPX agrave v -15
+KPX agrave w -15
+KPX agrave y -20
+KPX agrave yacute -20
+KPX agrave ydieresis -20
+KPX amacron g -10
+KPX amacron gbreve -10
+KPX amacron gcommaaccent -10
+KPX amacron v -15
+KPX amacron w -15
+KPX amacron y -20
+KPX amacron yacute -20
+KPX amacron ydieresis -20
+KPX aogonek g -10
+KPX aogonek gbreve -10
+KPX aogonek gcommaaccent -10
+KPX aogonek v -15
+KPX aogonek w -15
+KPX aogonek y -20
+KPX aogonek yacute -20
+KPX aogonek ydieresis -20
+KPX aring g -10
+KPX aring gbreve -10
+KPX aring gcommaaccent -10
+KPX aring v -15
+KPX aring w -15
+KPX aring y -20
+KPX aring yacute -20
+KPX aring ydieresis -20
+KPX atilde g -10
+KPX atilde gbreve -10
+KPX atilde gcommaaccent -10
+KPX atilde v -15
+KPX atilde w -15
+KPX atilde y -20
+KPX atilde yacute -20
+KPX atilde ydieresis -20
+KPX b l -10
+KPX b lacute -10
+KPX b lcommaaccent -10
+KPX b lslash -10
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -20
+KPX b y -20
+KPX b yacute -20
+KPX b ydieresis -20
+KPX c h -10
+KPX c k -20
+KPX c kcommaaccent -20
+KPX c l -20
+KPX c lacute -20
+KPX c lcommaaccent -20
+KPX c lslash -20
+KPX c y -10
+KPX c yacute -10
+KPX c ydieresis -10
+KPX cacute h -10
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX cacute l -20
+KPX cacute lacute -20
+KPX cacute lcommaaccent -20
+KPX cacute lslash -20
+KPX cacute y -10
+KPX cacute yacute -10
+KPX cacute ydieresis -10
+KPX ccaron h -10
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccaron l -20
+KPX ccaron lacute -20
+KPX ccaron lcommaaccent -20
+KPX ccaron lslash -20
+KPX ccaron y -10
+KPX ccaron yacute -10
+KPX ccaron ydieresis -10
+KPX ccedilla h -10
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX ccedilla l -20
+KPX ccedilla lacute -20
+KPX ccedilla lcommaaccent -20
+KPX ccedilla lslash -20
+KPX ccedilla y -10
+KPX ccedilla yacute -10
+KPX ccedilla ydieresis -10
+KPX colon space -40
+KPX comma quotedblright -120
+KPX comma quoteright -120
+KPX comma space -40
+KPX d d -10
+KPX d dcroat -10
+KPX d v -15
+KPX d w -15
+KPX d y -15
+KPX d yacute -15
+KPX d ydieresis -15
+KPX dcroat d -10
+KPX dcroat dcroat -10
+KPX dcroat v -15
+KPX dcroat w -15
+KPX dcroat y -15
+KPX dcroat yacute -15
+KPX dcroat ydieresis -15
+KPX e comma 10
+KPX e period 20
+KPX e v -15
+KPX e w -15
+KPX e x -15
+KPX e y -15
+KPX e yacute -15
+KPX e ydieresis -15
+KPX eacute comma 10
+KPX eacute period 20
+KPX eacute v -15
+KPX eacute w -15
+KPX eacute x -15
+KPX eacute y -15
+KPX eacute yacute -15
+KPX eacute ydieresis -15
+KPX ecaron comma 10
+KPX ecaron period 20
+KPX ecaron v -15
+KPX ecaron w -15
+KPX ecaron x -15
+KPX ecaron y -15
+KPX ecaron yacute -15
+KPX ecaron ydieresis -15
+KPX ecircumflex comma 10
+KPX ecircumflex period 20
+KPX ecircumflex v -15
+KPX ecircumflex w -15
+KPX ecircumflex x -15
+KPX ecircumflex y -15
+KPX ecircumflex yacute -15
+KPX ecircumflex ydieresis -15
+KPX edieresis comma 10
+KPX edieresis period 20
+KPX edieresis v -15
+KPX edieresis w -15
+KPX edieresis x -15
+KPX edieresis y -15
+KPX edieresis yacute -15
+KPX edieresis ydieresis -15
+KPX edotaccent comma 10
+KPX edotaccent period 20
+KPX edotaccent v -15
+KPX edotaccent w -15
+KPX edotaccent x -15
+KPX edotaccent y -15
+KPX edotaccent yacute -15
+KPX edotaccent ydieresis -15
+KPX egrave comma 10
+KPX egrave period 20
+KPX egrave v -15
+KPX egrave w -15
+KPX egrave x -15
+KPX egrave y -15
+KPX egrave yacute -15
+KPX egrave ydieresis -15
+KPX emacron comma 10
+KPX emacron period 20
+KPX emacron v -15
+KPX emacron w -15
+KPX emacron x -15
+KPX emacron y -15
+KPX emacron yacute -15
+KPX emacron ydieresis -15
+KPX eogonek comma 10
+KPX eogonek period 20
+KPX eogonek v -15
+KPX eogonek w -15
+KPX eogonek x -15
+KPX eogonek y -15
+KPX eogonek yacute -15
+KPX eogonek ydieresis -15
+KPX f comma -10
+KPX f e -10
+KPX f eacute -10
+KPX f ecaron -10
+KPX f ecircumflex -10
+KPX f edieresis -10
+KPX f edotaccent -10
+KPX f egrave -10
+KPX f emacron -10
+KPX f eogonek -10
+KPX f o -20
+KPX f oacute -20
+KPX f ocircumflex -20
+KPX f odieresis -20
+KPX f ograve -20
+KPX f ohungarumlaut -20
+KPX f omacron -20
+KPX f oslash -20
+KPX f otilde -20
+KPX f period -10
+KPX f quotedblright 30
+KPX f quoteright 30
+KPX g e 10
+KPX g eacute 10
+KPX g ecaron 10
+KPX g ecircumflex 10
+KPX g edieresis 10
+KPX g edotaccent 10
+KPX g egrave 10
+KPX g emacron 10
+KPX g eogonek 10
+KPX g g -10
+KPX g gbreve -10
+KPX g gcommaaccent -10
+KPX gbreve e 10
+KPX gbreve eacute 10
+KPX gbreve ecaron 10
+KPX gbreve ecircumflex 10
+KPX gbreve edieresis 10
+KPX gbreve edotaccent 10
+KPX gbreve egrave 10
+KPX gbreve emacron 10
+KPX gbreve eogonek 10
+KPX gbreve g -10
+KPX gbreve gbreve -10
+KPX gbreve gcommaaccent -10
+KPX gcommaaccent e 10
+KPX gcommaaccent eacute 10
+KPX gcommaaccent ecaron 10
+KPX gcommaaccent ecircumflex 10
+KPX gcommaaccent edieresis 10
+KPX gcommaaccent edotaccent 10
+KPX gcommaaccent egrave 10
+KPX gcommaaccent emacron 10
+KPX gcommaaccent eogonek 10
+KPX gcommaaccent g -10
+KPX gcommaaccent gbreve -10
+KPX gcommaaccent gcommaaccent -10
+KPX h y -20
+KPX h yacute -20
+KPX h ydieresis -20
+KPX k o -15
+KPX k oacute -15
+KPX k ocircumflex -15
+KPX k odieresis -15
+KPX k ograve -15
+KPX k ohungarumlaut -15
+KPX k omacron -15
+KPX k oslash -15
+KPX k otilde -15
+KPX kcommaaccent o -15
+KPX kcommaaccent oacute -15
+KPX kcommaaccent ocircumflex -15
+KPX kcommaaccent odieresis -15
+KPX kcommaaccent ograve -15
+KPX kcommaaccent ohungarumlaut -15
+KPX kcommaaccent omacron -15
+KPX kcommaaccent oslash -15
+KPX kcommaaccent otilde -15
+KPX l w -15
+KPX l y -15
+KPX l yacute -15
+KPX l ydieresis -15
+KPX lacute w -15
+KPX lacute y -15
+KPX lacute yacute -15
+KPX lacute ydieresis -15
+KPX lcommaaccent w -15
+KPX lcommaaccent y -15
+KPX lcommaaccent yacute -15
+KPX lcommaaccent ydieresis -15
+KPX lslash w -15
+KPX lslash y -15
+KPX lslash yacute -15
+KPX lslash ydieresis -15
+KPX m u -20
+KPX m uacute -20
+KPX m ucircumflex -20
+KPX m udieresis -20
+KPX m ugrave -20
+KPX m uhungarumlaut -20
+KPX m umacron -20
+KPX m uogonek -20
+KPX m uring -20
+KPX m y -30
+KPX m yacute -30
+KPX m ydieresis -30
+KPX n u -10
+KPX n uacute -10
+KPX n ucircumflex -10
+KPX n udieresis -10
+KPX n ugrave -10
+KPX n uhungarumlaut -10
+KPX n umacron -10
+KPX n uogonek -10
+KPX n uring -10
+KPX n v -40
+KPX n y -20
+KPX n yacute -20
+KPX n ydieresis -20
+KPX nacute u -10
+KPX nacute uacute -10
+KPX nacute ucircumflex -10
+KPX nacute udieresis -10
+KPX nacute ugrave -10
+KPX nacute uhungarumlaut -10
+KPX nacute umacron -10
+KPX nacute uogonek -10
+KPX nacute uring -10
+KPX nacute v -40
+KPX nacute y -20
+KPX nacute yacute -20
+KPX nacute ydieresis -20
+KPX ncaron u -10
+KPX ncaron uacute -10
+KPX ncaron ucircumflex -10
+KPX ncaron udieresis -10
+KPX ncaron ugrave -10
+KPX ncaron uhungarumlaut -10
+KPX ncaron umacron -10
+KPX ncaron uogonek -10
+KPX ncaron uring -10
+KPX ncaron v -40
+KPX ncaron y -20
+KPX ncaron yacute -20
+KPX ncaron ydieresis -20
+KPX ncommaaccent u -10
+KPX ncommaaccent uacute -10
+KPX ncommaaccent ucircumflex -10
+KPX ncommaaccent udieresis -10
+KPX ncommaaccent ugrave -10
+KPX ncommaaccent uhungarumlaut -10
+KPX ncommaaccent umacron -10
+KPX ncommaaccent uogonek -10
+KPX ncommaaccent uring -10
+KPX ncommaaccent v -40
+KPX ncommaaccent y -20
+KPX ncommaaccent yacute -20
+KPX ncommaaccent ydieresis -20
+KPX ntilde u -10
+KPX ntilde uacute -10
+KPX ntilde ucircumflex -10
+KPX ntilde udieresis -10
+KPX ntilde ugrave -10
+KPX ntilde uhungarumlaut -10
+KPX ntilde umacron -10
+KPX ntilde uogonek -10
+KPX ntilde uring -10
+KPX ntilde v -40
+KPX ntilde y -20
+KPX ntilde yacute -20
+KPX ntilde ydieresis -20
+KPX o v -20
+KPX o w -15
+KPX o x -30
+KPX o y -20
+KPX o yacute -20
+KPX o ydieresis -20
+KPX oacute v -20
+KPX oacute w -15
+KPX oacute x -30
+KPX oacute y -20
+KPX oacute yacute -20
+KPX oacute ydieresis -20
+KPX ocircumflex v -20
+KPX ocircumflex w -15
+KPX ocircumflex x -30
+KPX ocircumflex y -20
+KPX ocircumflex yacute -20
+KPX ocircumflex ydieresis -20
+KPX odieresis v -20
+KPX odieresis w -15
+KPX odieresis x -30
+KPX odieresis y -20
+KPX odieresis yacute -20
+KPX odieresis ydieresis -20
+KPX ograve v -20
+KPX ograve w -15
+KPX ograve x -30
+KPX ograve y -20
+KPX ograve yacute -20
+KPX ograve ydieresis -20
+KPX ohungarumlaut v -20
+KPX ohungarumlaut w -15
+KPX ohungarumlaut x -30
+KPX ohungarumlaut y -20
+KPX ohungarumlaut yacute -20
+KPX ohungarumlaut ydieresis -20
+KPX omacron v -20
+KPX omacron w -15
+KPX omacron x -30
+KPX omacron y -20
+KPX omacron yacute -20
+KPX omacron ydieresis -20
+KPX oslash v -20
+KPX oslash w -15
+KPX oslash x -30
+KPX oslash y -20
+KPX oslash yacute -20
+KPX oslash ydieresis -20
+KPX otilde v -20
+KPX otilde w -15
+KPX otilde x -30
+KPX otilde y -20
+KPX otilde yacute -20
+KPX otilde ydieresis -20
+KPX p y -15
+KPX p yacute -15
+KPX p ydieresis -15
+KPX period quotedblright -120
+KPX period quoteright -120
+KPX period space -40
+KPX quotedblright space -80
+KPX quoteleft quoteleft -46
+KPX quoteright d -80
+KPX quoteright dcroat -80
+KPX quoteright l -20
+KPX quoteright lacute -20
+KPX quoteright lcommaaccent -20
+KPX quoteright lslash -20
+KPX quoteright quoteright -46
+KPX quoteright r -40
+KPX quoteright racute -40
+KPX quoteright rcaron -40
+KPX quoteright rcommaaccent -40
+KPX quoteright s -60
+KPX quoteright sacute -60
+KPX quoteright scaron -60
+KPX quoteright scedilla -60
+KPX quoteright scommaaccent -60
+KPX quoteright space -80
+KPX quoteright v -20
+KPX r c -20
+KPX r cacute -20
+KPX r ccaron -20
+KPX r ccedilla -20
+KPX r comma -60
+KPX r d -20
+KPX r dcroat -20
+KPX r g -15
+KPX r gbreve -15
+KPX r gcommaaccent -15
+KPX r hyphen -20
+KPX r o -20
+KPX r oacute -20
+KPX r ocircumflex -20
+KPX r odieresis -20
+KPX r ograve -20
+KPX r ohungarumlaut -20
+KPX r omacron -20
+KPX r oslash -20
+KPX r otilde -20
+KPX r period -60
+KPX r q -20
+KPX r s -15
+KPX r sacute -15
+KPX r scaron -15
+KPX r scedilla -15
+KPX r scommaaccent -15
+KPX r t 20
+KPX r tcommaaccent 20
+KPX r v 10
+KPX r y 10
+KPX r yacute 10
+KPX r ydieresis 10
+KPX racute c -20
+KPX racute cacute -20
+KPX racute ccaron -20
+KPX racute ccedilla -20
+KPX racute comma -60
+KPX racute d -20
+KPX racute dcroat -20
+KPX racute g -15
+KPX racute gbreve -15
+KPX racute gcommaaccent -15
+KPX racute hyphen -20
+KPX racute o -20
+KPX racute oacute -20
+KPX racute ocircumflex -20
+KPX racute odieresis -20
+KPX racute ograve -20
+KPX racute ohungarumlaut -20
+KPX racute omacron -20
+KPX racute oslash -20
+KPX racute otilde -20
+KPX racute period -60
+KPX racute q -20
+KPX racute s -15
+KPX racute sacute -15
+KPX racute scaron -15
+KPX racute scedilla -15
+KPX racute scommaaccent -15
+KPX racute t 20
+KPX racute tcommaaccent 20
+KPX racute v 10
+KPX racute y 10
+KPX racute yacute 10
+KPX racute ydieresis 10
+KPX rcaron c -20
+KPX rcaron cacute -20
+KPX rcaron ccaron -20
+KPX rcaron ccedilla -20
+KPX rcaron comma -60
+KPX rcaron d -20
+KPX rcaron dcroat -20
+KPX rcaron g -15
+KPX rcaron gbreve -15
+KPX rcaron gcommaaccent -15
+KPX rcaron hyphen -20
+KPX rcaron o -20
+KPX rcaron oacute -20
+KPX rcaron ocircumflex -20
+KPX rcaron odieresis -20
+KPX rcaron ograve -20
+KPX rcaron ohungarumlaut -20
+KPX rcaron omacron -20
+KPX rcaron oslash -20
+KPX rcaron otilde -20
+KPX rcaron period -60
+KPX rcaron q -20
+KPX rcaron s -15
+KPX rcaron sacute -15
+KPX rcaron scaron -15
+KPX rcaron scedilla -15
+KPX rcaron scommaaccent -15
+KPX rcaron t 20
+KPX rcaron tcommaaccent 20
+KPX rcaron v 10
+KPX rcaron y 10
+KPX rcaron yacute 10
+KPX rcaron ydieresis 10
+KPX rcommaaccent c -20
+KPX rcommaaccent cacute -20
+KPX rcommaaccent ccaron -20
+KPX rcommaaccent ccedilla -20
+KPX rcommaaccent comma -60
+KPX rcommaaccent d -20
+KPX rcommaaccent dcroat -20
+KPX rcommaaccent g -15
+KPX rcommaaccent gbreve -15
+KPX rcommaaccent gcommaaccent -15
+KPX rcommaaccent hyphen -20
+KPX rcommaaccent o -20
+KPX rcommaaccent oacute -20
+KPX rcommaaccent ocircumflex -20
+KPX rcommaaccent odieresis -20
+KPX rcommaaccent ograve -20
+KPX rcommaaccent ohungarumlaut -20
+KPX rcommaaccent omacron -20
+KPX rcommaaccent oslash -20
+KPX rcommaaccent otilde -20
+KPX rcommaaccent period -60
+KPX rcommaaccent q -20
+KPX rcommaaccent s -15
+KPX rcommaaccent sacute -15
+KPX rcommaaccent scaron -15
+KPX rcommaaccent scedilla -15
+KPX rcommaaccent scommaaccent -15
+KPX rcommaaccent t 20
+KPX rcommaaccent tcommaaccent 20
+KPX rcommaaccent v 10
+KPX rcommaaccent y 10
+KPX rcommaaccent yacute 10
+KPX rcommaaccent ydieresis 10
+KPX s w -15
+KPX sacute w -15
+KPX scaron w -15
+KPX scedilla w -15
+KPX scommaaccent w -15
+KPX semicolon space -40
+KPX space T -100
+KPX space Tcaron -100
+KPX space Tcommaaccent -100
+KPX space V -80
+KPX space W -80
+KPX space Y -120
+KPX space Yacute -120
+KPX space Ydieresis -120
+KPX space quotedblleft -80
+KPX space quoteleft -60
+KPX v a -20
+KPX v aacute -20
+KPX v abreve -20
+KPX v acircumflex -20
+KPX v adieresis -20
+KPX v agrave -20
+KPX v amacron -20
+KPX v aogonek -20
+KPX v aring -20
+KPX v atilde -20
+KPX v comma -80
+KPX v o -30
+KPX v oacute -30
+KPX v ocircumflex -30
+KPX v odieresis -30
+KPX v ograve -30
+KPX v ohungarumlaut -30
+KPX v omacron -30
+KPX v oslash -30
+KPX v otilde -30
+KPX v period -80
+KPX w comma -40
+KPX w o -20
+KPX w oacute -20
+KPX w ocircumflex -20
+KPX w odieresis -20
+KPX w ograve -20
+KPX w ohungarumlaut -20
+KPX w omacron -20
+KPX w oslash -20
+KPX w otilde -20
+KPX w period -40
+KPX x e -10
+KPX x eacute -10
+KPX x ecaron -10
+KPX x ecircumflex -10
+KPX x edieresis -10
+KPX x edotaccent -10
+KPX x egrave -10
+KPX x emacron -10
+KPX x eogonek -10
+KPX y a -30
+KPX y aacute -30
+KPX y abreve -30
+KPX y acircumflex -30
+KPX y adieresis -30
+KPX y agrave -30
+KPX y amacron -30
+KPX y aogonek -30
+KPX y aring -30
+KPX y atilde -30
+KPX y comma -80
+KPX y e -10
+KPX y eacute -10
+KPX y ecaron -10
+KPX y ecircumflex -10
+KPX y edieresis -10
+KPX y edotaccent -10
+KPX y egrave -10
+KPX y emacron -10
+KPX y eogonek -10
+KPX y o -25
+KPX y oacute -25
+KPX y ocircumflex -25
+KPX y odieresis -25
+KPX y ograve -25
+KPX y ohungarumlaut -25
+KPX y omacron -25
+KPX y oslash -25
+KPX y otilde -25
+KPX y period -80
+KPX yacute a -30
+KPX yacute aacute -30
+KPX yacute abreve -30
+KPX yacute acircumflex -30
+KPX yacute adieresis -30
+KPX yacute agrave -30
+KPX yacute amacron -30
+KPX yacute aogonek -30
+KPX yacute aring -30
+KPX yacute atilde -30
+KPX yacute comma -80
+KPX yacute e -10
+KPX yacute eacute -10
+KPX yacute ecaron -10
+KPX yacute ecircumflex -10
+KPX yacute edieresis -10
+KPX yacute edotaccent -10
+KPX yacute egrave -10
+KPX yacute emacron -10
+KPX yacute eogonek -10
+KPX yacute o -25
+KPX yacute oacute -25
+KPX yacute ocircumflex -25
+KPX yacute odieresis -25
+KPX yacute ograve -25
+KPX yacute ohungarumlaut -25
+KPX yacute omacron -25
+KPX yacute oslash -25
+KPX yacute otilde -25
+KPX yacute period -80
+KPX ydieresis a -30
+KPX ydieresis aacute -30
+KPX ydieresis abreve -30
+KPX ydieresis acircumflex -30
+KPX ydieresis adieresis -30
+KPX ydieresis agrave -30
+KPX ydieresis amacron -30
+KPX ydieresis aogonek -30
+KPX ydieresis aring -30
+KPX ydieresis atilde -30
+KPX ydieresis comma -80
+KPX ydieresis e -10
+KPX ydieresis eacute -10
+KPX ydieresis ecaron -10
+KPX ydieresis ecircumflex -10
+KPX ydieresis edieresis -10
+KPX ydieresis edotaccent -10
+KPX ydieresis egrave -10
+KPX ydieresis emacron -10
+KPX ydieresis eogonek -10
+KPX ydieresis o -25
+KPX ydieresis oacute -25
+KPX ydieresis ocircumflex -25
+KPX ydieresis odieresis -25
+KPX ydieresis ograve -25
+KPX ydieresis ohungarumlaut -25
+KPX ydieresis omacron -25
+KPX ydieresis oslash -25
+KPX ydieresis otilde -25
+KPX ydieresis period -80
+KPX z e 10
+KPX z eacute 10
+KPX z ecaron 10
+KPX z ecircumflex 10
+KPX z edieresis 10
+KPX z edotaccent 10
+KPX z egrave 10
+KPX z emacron 10
+KPX z eogonek 10
+KPX zacute e 10
+KPX zacute eacute 10
+KPX zacute ecaron 10
+KPX zacute ecircumflex 10
+KPX zacute edieresis 10
+KPX zacute edotaccent 10
+KPX zacute egrave 10
+KPX zacute emacron 10
+KPX zacute eogonek 10
+KPX zcaron e 10
+KPX zcaron eacute 10
+KPX zcaron ecaron 10
+KPX zcaron ecircumflex 10
+KPX zcaron edieresis 10
+KPX zcaron edotaccent 10
+KPX zcaron egrave 10
+KPX zcaron emacron 10
+KPX zcaron eogonek 10
+KPX zdotaccent e 10
+KPX zdotaccent eacute 10
+KPX zdotaccent ecaron 10
+KPX zdotaccent ecircumflex 10
+KPX zdotaccent edieresis 10
+KPX zdotaccent edotaccent 10
+KPX zdotaccent egrave 10
+KPX zdotaccent emacron 10
+KPX zdotaccent eogonek 10
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Oblique.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Oblique.afm
new file mode 100644
index 0000000000000000000000000000000000000000..7a7af0017fe740ac245c5dc6d5d1f4744e89597b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Oblique.afm
@@ -0,0 +1,3051 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:44:31 1997
+Comment UniqueID 43055
+Comment VMusage 14960 69346
+FontName Helvetica-Oblique
+FullName Helvetica Oblique
+FamilyName Helvetica
+Weight Medium
+ItalicAngle -12
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -170 -225 1116 931
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 523
+Ascender 718
+Descender -207
+StdHW 76
+StdVW 88
+StartCharMetrics 315
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 90 0 340 718 ;
+C 34 ; WX 355 ; N quotedbl ; B 168 463 438 718 ;
+C 35 ; WX 556 ; N numbersign ; B 73 0 631 688 ;
+C 36 ; WX 556 ; N dollar ; B 69 -115 617 775 ;
+C 37 ; WX 889 ; N percent ; B 147 -19 889 703 ;
+C 38 ; WX 667 ; N ampersand ; B 77 -15 647 718 ;
+C 39 ; WX 222 ; N quoteright ; B 151 463 310 718 ;
+C 40 ; WX 333 ; N parenleft ; B 108 -207 454 733 ;
+C 41 ; WX 333 ; N parenright ; B -9 -207 337 733 ;
+C 42 ; WX 389 ; N asterisk ; B 165 431 475 718 ;
+C 43 ; WX 584 ; N plus ; B 85 0 606 505 ;
+C 44 ; WX 278 ; N comma ; B 56 -147 214 106 ;
+C 45 ; WX 333 ; N hyphen ; B 93 232 357 322 ;
+C 46 ; WX 278 ; N period ; B 87 0 214 106 ;
+C 47 ; WX 278 ; N slash ; B -21 -19 452 737 ;
+C 48 ; WX 556 ; N zero ; B 93 -19 608 703 ;
+C 49 ; WX 556 ; N one ; B 207 0 508 703 ;
+C 50 ; WX 556 ; N two ; B 26 0 617 703 ;
+C 51 ; WX 556 ; N three ; B 75 -19 610 703 ;
+C 52 ; WX 556 ; N four ; B 61 0 576 703 ;
+C 53 ; WX 556 ; N five ; B 68 -19 621 688 ;
+C 54 ; WX 556 ; N six ; B 91 -19 615 703 ;
+C 55 ; WX 556 ; N seven ; B 137 0 669 688 ;
+C 56 ; WX 556 ; N eight ; B 74 -19 607 703 ;
+C 57 ; WX 556 ; N nine ; B 82 -19 609 703 ;
+C 58 ; WX 278 ; N colon ; B 87 0 301 516 ;
+C 59 ; WX 278 ; N semicolon ; B 56 -147 301 516 ;
+C 60 ; WX 584 ; N less ; B 94 11 641 495 ;
+C 61 ; WX 584 ; N equal ; B 63 115 628 390 ;
+C 62 ; WX 584 ; N greater ; B 50 11 597 495 ;
+C 63 ; WX 556 ; N question ; B 161 0 610 727 ;
+C 64 ; WX 1015 ; N at ; B 215 -19 965 737 ;
+C 65 ; WX 667 ; N A ; B 14 0 654 718 ;
+C 66 ; WX 667 ; N B ; B 74 0 712 718 ;
+C 67 ; WX 722 ; N C ; B 108 -19 782 737 ;
+C 68 ; WX 722 ; N D ; B 81 0 764 718 ;
+C 69 ; WX 667 ; N E ; B 86 0 762 718 ;
+C 70 ; WX 611 ; N F ; B 86 0 736 718 ;
+C 71 ; WX 778 ; N G ; B 111 -19 799 737 ;
+C 72 ; WX 722 ; N H ; B 77 0 799 718 ;
+C 73 ; WX 278 ; N I ; B 91 0 341 718 ;
+C 74 ; WX 500 ; N J ; B 47 -19 581 718 ;
+C 75 ; WX 667 ; N K ; B 76 0 808 718 ;
+C 76 ; WX 556 ; N L ; B 76 0 555 718 ;
+C 77 ; WX 833 ; N M ; B 73 0 914 718 ;
+C 78 ; WX 722 ; N N ; B 76 0 799 718 ;
+C 79 ; WX 778 ; N O ; B 105 -19 826 737 ;
+C 80 ; WX 667 ; N P ; B 86 0 737 718 ;
+C 81 ; WX 778 ; N Q ; B 105 -56 826 737 ;
+C 82 ; WX 722 ; N R ; B 88 0 773 718 ;
+C 83 ; WX 667 ; N S ; B 90 -19 713 737 ;
+C 84 ; WX 611 ; N T ; B 148 0 750 718 ;
+C 85 ; WX 722 ; N U ; B 123 -19 797 718 ;
+C 86 ; WX 667 ; N V ; B 173 0 800 718 ;
+C 87 ; WX 944 ; N W ; B 169 0 1081 718 ;
+C 88 ; WX 667 ; N X ; B 19 0 790 718 ;
+C 89 ; WX 667 ; N Y ; B 167 0 806 718 ;
+C 90 ; WX 611 ; N Z ; B 23 0 741 718 ;
+C 91 ; WX 278 ; N bracketleft ; B 21 -196 403 722 ;
+C 92 ; WX 278 ; N backslash ; B 140 -19 291 737 ;
+C 93 ; WX 278 ; N bracketright ; B -14 -196 368 722 ;
+C 94 ; WX 469 ; N asciicircum ; B 42 264 539 688 ;
+C 95 ; WX 556 ; N underscore ; B -27 -125 540 -75 ;
+C 96 ; WX 222 ; N quoteleft ; B 165 470 323 725 ;
+C 97 ; WX 556 ; N a ; B 61 -15 559 538 ;
+C 98 ; WX 556 ; N b ; B 58 -15 584 718 ;
+C 99 ; WX 500 ; N c ; B 74 -15 553 538 ;
+C 100 ; WX 556 ; N d ; B 84 -15 652 718 ;
+C 101 ; WX 556 ; N e ; B 84 -15 578 538 ;
+C 102 ; WX 278 ; N f ; B 86 0 416 728 ; L i fi ; L l fl ;
+C 103 ; WX 556 ; N g ; B 42 -220 610 538 ;
+C 104 ; WX 556 ; N h ; B 65 0 573 718 ;
+C 105 ; WX 222 ; N i ; B 67 0 308 718 ;
+C 106 ; WX 222 ; N j ; B -60 -210 308 718 ;
+C 107 ; WX 500 ; N k ; B 67 0 600 718 ;
+C 108 ; WX 222 ; N l ; B 67 0 308 718 ;
+C 109 ; WX 833 ; N m ; B 65 0 852 538 ;
+C 110 ; WX 556 ; N n ; B 65 0 573 538 ;
+C 111 ; WX 556 ; N o ; B 83 -14 585 538 ;
+C 112 ; WX 556 ; N p ; B 14 -207 584 538 ;
+C 113 ; WX 556 ; N q ; B 84 -207 605 538 ;
+C 114 ; WX 333 ; N r ; B 77 0 446 538 ;
+C 115 ; WX 500 ; N s ; B 63 -15 529 538 ;
+C 116 ; WX 278 ; N t ; B 102 -7 368 669 ;
+C 117 ; WX 556 ; N u ; B 94 -15 600 523 ;
+C 118 ; WX 500 ; N v ; B 119 0 603 523 ;
+C 119 ; WX 722 ; N w ; B 125 0 820 523 ;
+C 120 ; WX 500 ; N x ; B 11 0 594 523 ;
+C 121 ; WX 500 ; N y ; B 15 -214 600 523 ;
+C 122 ; WX 500 ; N z ; B 31 0 571 523 ;
+C 123 ; WX 334 ; N braceleft ; B 92 -196 445 722 ;
+C 124 ; WX 260 ; N bar ; B 46 -225 332 775 ;
+C 125 ; WX 334 ; N braceright ; B 0 -196 354 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 111 180 580 326 ;
+C 161 ; WX 333 ; N exclamdown ; B 77 -195 326 523 ;
+C 162 ; WX 556 ; N cent ; B 95 -115 584 623 ;
+C 163 ; WX 556 ; N sterling ; B 49 -16 634 718 ;
+C 164 ; WX 167 ; N fraction ; B -170 -19 482 703 ;
+C 165 ; WX 556 ; N yen ; B 81 0 699 688 ;
+C 166 ; WX 556 ; N florin ; B -52 -207 654 737 ;
+C 167 ; WX 556 ; N section ; B 76 -191 584 737 ;
+C 168 ; WX 556 ; N currency ; B 60 99 646 603 ;
+C 169 ; WX 191 ; N quotesingle ; B 157 463 285 718 ;
+C 170 ; WX 333 ; N quotedblleft ; B 138 470 461 725 ;
+C 171 ; WX 556 ; N guillemotleft ; B 146 108 554 446 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 137 108 340 446 ;
+C 173 ; WX 333 ; N guilsinglright ; B 111 108 314 446 ;
+C 174 ; WX 500 ; N fi ; B 86 0 587 728 ;
+C 175 ; WX 500 ; N fl ; B 86 0 585 728 ;
+C 177 ; WX 556 ; N endash ; B 51 240 623 313 ;
+C 178 ; WX 556 ; N dagger ; B 135 -159 622 718 ;
+C 179 ; WX 556 ; N daggerdbl ; B 52 -159 623 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 129 190 257 315 ;
+C 182 ; WX 537 ; N paragraph ; B 126 -173 650 718 ;
+C 183 ; WX 350 ; N bullet ; B 91 202 413 517 ;
+C 184 ; WX 222 ; N quotesinglbase ; B 21 -149 180 106 ;
+C 185 ; WX 333 ; N quotedblbase ; B -6 -149 318 106 ;
+C 186 ; WX 333 ; N quotedblright ; B 124 463 448 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 120 108 528 446 ;
+C 188 ; WX 1000 ; N ellipsis ; B 115 0 908 106 ;
+C 189 ; WX 1000 ; N perthousand ; B 88 -19 1029 703 ;
+C 191 ; WX 611 ; N questiondown ; B 85 -201 534 525 ;
+C 193 ; WX 333 ; N grave ; B 170 593 337 734 ;
+C 194 ; WX 333 ; N acute ; B 248 593 475 734 ;
+C 195 ; WX 333 ; N circumflex ; B 147 593 438 734 ;
+C 196 ; WX 333 ; N tilde ; B 125 606 490 722 ;
+C 197 ; WX 333 ; N macron ; B 143 627 468 684 ;
+C 198 ; WX 333 ; N breve ; B 167 595 476 731 ;
+C 199 ; WX 333 ; N dotaccent ; B 249 604 362 706 ;
+C 200 ; WX 333 ; N dieresis ; B 168 604 443 706 ;
+C 202 ; WX 333 ; N ring ; B 214 572 402 756 ;
+C 203 ; WX 333 ; N cedilla ; B 2 -225 232 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 157 593 565 734 ;
+C 206 ; WX 333 ; N ogonek ; B 43 -225 249 0 ;
+C 207 ; WX 333 ; N caron ; B 177 593 468 734 ;
+C 208 ; WX 1000 ; N emdash ; B 51 240 1067 313 ;
+C 225 ; WX 1000 ; N AE ; B 8 0 1097 718 ;
+C 227 ; WX 370 ; N ordfeminine ; B 127 405 449 737 ;
+C 232 ; WX 556 ; N Lslash ; B 41 0 555 718 ;
+C 233 ; WX 778 ; N Oslash ; B 43 -19 890 737 ;
+C 234 ; WX 1000 ; N OE ; B 98 -19 1116 737 ;
+C 235 ; WX 365 ; N ordmasculine ; B 141 405 468 737 ;
+C 241 ; WX 889 ; N ae ; B 61 -15 909 538 ;
+C 245 ; WX 278 ; N dotlessi ; B 95 0 294 523 ;
+C 248 ; WX 222 ; N lslash ; B 41 0 347 718 ;
+C 249 ; WX 611 ; N oslash ; B 29 -22 647 545 ;
+C 250 ; WX 944 ; N oe ; B 83 -15 964 538 ;
+C 251 ; WX 611 ; N germandbls ; B 67 -15 658 728 ;
+C -1 ; WX 278 ; N Idieresis ; B 91 0 458 901 ;
+C -1 ; WX 556 ; N eacute ; B 84 -15 587 734 ;
+C -1 ; WX 556 ; N abreve ; B 61 -15 578 731 ;
+C -1 ; WX 556 ; N uhungarumlaut ; B 94 -15 677 734 ;
+C -1 ; WX 556 ; N ecaron ; B 84 -15 580 734 ;
+C -1 ; WX 667 ; N Ydieresis ; B 167 0 806 901 ;
+C -1 ; WX 584 ; N divide ; B 85 -19 606 524 ;
+C -1 ; WX 667 ; N Yacute ; B 167 0 806 929 ;
+C -1 ; WX 667 ; N Acircumflex ; B 14 0 654 929 ;
+C -1 ; WX 556 ; N aacute ; B 61 -15 587 734 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 123 -19 797 929 ;
+C -1 ; WX 500 ; N yacute ; B 15 -214 600 734 ;
+C -1 ; WX 500 ; N scommaaccent ; B 63 -225 529 538 ;
+C -1 ; WX 556 ; N ecircumflex ; B 84 -15 578 734 ;
+C -1 ; WX 722 ; N Uring ; B 123 -19 797 931 ;
+C -1 ; WX 722 ; N Udieresis ; B 123 -19 797 901 ;
+C -1 ; WX 556 ; N aogonek ; B 61 -220 559 538 ;
+C -1 ; WX 722 ; N Uacute ; B 123 -19 797 929 ;
+C -1 ; WX 556 ; N uogonek ; B 94 -225 600 523 ;
+C -1 ; WX 667 ; N Edieresis ; B 86 0 762 901 ;
+C -1 ; WX 722 ; N Dcroat ; B 69 0 764 718 ;
+C -1 ; WX 250 ; N commaaccent ; B 39 -225 172 -40 ;
+C -1 ; WX 737 ; N copyright ; B 54 -19 837 737 ;
+C -1 ; WX 667 ; N Emacron ; B 86 0 762 879 ;
+C -1 ; WX 500 ; N ccaron ; B 74 -15 553 734 ;
+C -1 ; WX 556 ; N aring ; B 61 -15 559 756 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 76 -225 799 718 ;
+C -1 ; WX 222 ; N lacute ; B 67 0 461 929 ;
+C -1 ; WX 556 ; N agrave ; B 61 -15 559 734 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 148 -225 750 718 ;
+C -1 ; WX 722 ; N Cacute ; B 108 -19 782 929 ;
+C -1 ; WX 556 ; N atilde ; B 61 -15 592 722 ;
+C -1 ; WX 667 ; N Edotaccent ; B 86 0 762 901 ;
+C -1 ; WX 500 ; N scaron ; B 63 -15 552 734 ;
+C -1 ; WX 500 ; N scedilla ; B 63 -225 529 538 ;
+C -1 ; WX 278 ; N iacute ; B 95 0 448 734 ;
+C -1 ; WX 471 ; N lozenge ; B 88 0 540 728 ;
+C -1 ; WX 722 ; N Rcaron ; B 88 0 773 929 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 111 -225 799 737 ;
+C -1 ; WX 556 ; N ucircumflex ; B 94 -15 600 734 ;
+C -1 ; WX 556 ; N acircumflex ; B 61 -15 559 734 ;
+C -1 ; WX 667 ; N Amacron ; B 14 0 677 879 ;
+C -1 ; WX 333 ; N rcaron ; B 77 0 508 734 ;
+C -1 ; WX 500 ; N ccedilla ; B 74 -225 553 538 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 23 0 741 901 ;
+C -1 ; WX 667 ; N Thorn ; B 86 0 712 718 ;
+C -1 ; WX 778 ; N Omacron ; B 105 -19 826 879 ;
+C -1 ; WX 722 ; N Racute ; B 88 0 773 929 ;
+C -1 ; WX 667 ; N Sacute ; B 90 -19 713 929 ;
+C -1 ; WX 643 ; N dcaron ; B 84 -15 808 718 ;
+C -1 ; WX 722 ; N Umacron ; B 123 -19 797 879 ;
+C -1 ; WX 556 ; N uring ; B 94 -15 600 756 ;
+C -1 ; WX 333 ; N threesuperior ; B 90 270 436 703 ;
+C -1 ; WX 778 ; N Ograve ; B 105 -19 826 929 ;
+C -1 ; WX 667 ; N Agrave ; B 14 0 654 929 ;
+C -1 ; WX 667 ; N Abreve ; B 14 0 685 926 ;
+C -1 ; WX 584 ; N multiply ; B 50 0 642 506 ;
+C -1 ; WX 556 ; N uacute ; B 94 -15 600 734 ;
+C -1 ; WX 611 ; N Tcaron ; B 148 0 750 929 ;
+C -1 ; WX 476 ; N partialdiff ; B 41 -38 550 714 ;
+C -1 ; WX 500 ; N ydieresis ; B 15 -214 600 706 ;
+C -1 ; WX 722 ; N Nacute ; B 76 0 799 929 ;
+C -1 ; WX 278 ; N icircumflex ; B 95 0 411 734 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 86 0 762 929 ;
+C -1 ; WX 556 ; N adieresis ; B 61 -15 559 706 ;
+C -1 ; WX 556 ; N edieresis ; B 84 -15 578 706 ;
+C -1 ; WX 500 ; N cacute ; B 74 -15 559 734 ;
+C -1 ; WX 556 ; N nacute ; B 65 0 587 734 ;
+C -1 ; WX 556 ; N umacron ; B 94 -15 600 684 ;
+C -1 ; WX 722 ; N Ncaron ; B 76 0 799 929 ;
+C -1 ; WX 278 ; N Iacute ; B 91 0 489 929 ;
+C -1 ; WX 584 ; N plusminus ; B 39 0 618 506 ;
+C -1 ; WX 260 ; N brokenbar ; B 62 -150 316 700 ;
+C -1 ; WX 737 ; N registered ; B 54 -19 837 737 ;
+C -1 ; WX 778 ; N Gbreve ; B 111 -19 799 926 ;
+C -1 ; WX 278 ; N Idotaccent ; B 91 0 377 901 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 671 706 ;
+C -1 ; WX 667 ; N Egrave ; B 86 0 762 929 ;
+C -1 ; WX 333 ; N racute ; B 77 0 475 734 ;
+C -1 ; WX 556 ; N omacron ; B 83 -14 585 684 ;
+C -1 ; WX 611 ; N Zacute ; B 23 0 741 929 ;
+C -1 ; WX 611 ; N Zcaron ; B 23 0 741 929 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 620 674 ;
+C -1 ; WX 722 ; N Eth ; B 69 0 764 718 ;
+C -1 ; WX 722 ; N Ccedilla ; B 108 -225 782 737 ;
+C -1 ; WX 222 ; N lcommaaccent ; B 25 -225 308 718 ;
+C -1 ; WX 317 ; N tcaron ; B 102 -7 501 808 ;
+C -1 ; WX 556 ; N eogonek ; B 84 -225 578 538 ;
+C -1 ; WX 722 ; N Uogonek ; B 123 -225 797 718 ;
+C -1 ; WX 667 ; N Aacute ; B 14 0 683 929 ;
+C -1 ; WX 667 ; N Adieresis ; B 14 0 654 901 ;
+C -1 ; WX 556 ; N egrave ; B 84 -15 578 734 ;
+C -1 ; WX 500 ; N zacute ; B 31 0 571 734 ;
+C -1 ; WX 222 ; N iogonek ; B -61 -225 308 718 ;
+C -1 ; WX 778 ; N Oacute ; B 105 -19 826 929 ;
+C -1 ; WX 556 ; N oacute ; B 83 -14 587 734 ;
+C -1 ; WX 556 ; N amacron ; B 61 -15 580 684 ;
+C -1 ; WX 500 ; N sacute ; B 63 -15 559 734 ;
+C -1 ; WX 278 ; N idieresis ; B 95 0 416 706 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 105 -19 826 929 ;
+C -1 ; WX 722 ; N Ugrave ; B 123 -19 797 929 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C -1 ; WX 556 ; N thorn ; B 14 -207 584 718 ;
+C -1 ; WX 333 ; N twosuperior ; B 64 281 449 703 ;
+C -1 ; WX 778 ; N Odieresis ; B 105 -19 826 901 ;
+C -1 ; WX 556 ; N mu ; B 24 -207 600 523 ;
+C -1 ; WX 278 ; N igrave ; B 95 0 310 734 ;
+C -1 ; WX 556 ; N ohungarumlaut ; B 83 -14 677 734 ;
+C -1 ; WX 667 ; N Eogonek ; B 86 -220 762 718 ;
+C -1 ; WX 556 ; N dcroat ; B 84 -15 689 718 ;
+C -1 ; WX 834 ; N threequarters ; B 130 -19 861 703 ;
+C -1 ; WX 667 ; N Scedilla ; B 90 -225 713 737 ;
+C -1 ; WX 299 ; N lcaron ; B 67 0 464 718 ;
+C -1 ; WX 667 ; N Kcommaaccent ; B 76 -225 808 718 ;
+C -1 ; WX 556 ; N Lacute ; B 76 0 555 929 ;
+C -1 ; WX 1000 ; N trademark ; B 186 306 1056 718 ;
+C -1 ; WX 556 ; N edotaccent ; B 84 -15 578 706 ;
+C -1 ; WX 278 ; N Igrave ; B 91 0 351 929 ;
+C -1 ; WX 278 ; N Imacron ; B 91 0 483 879 ;
+C -1 ; WX 556 ; N Lcaron ; B 76 0 570 718 ;
+C -1 ; WX 834 ; N onehalf ; B 114 -19 839 703 ;
+C -1 ; WX 549 ; N lessequal ; B 26 0 666 674 ;
+C -1 ; WX 556 ; N ocircumflex ; B 83 -14 585 734 ;
+C -1 ; WX 556 ; N ntilde ; B 65 0 592 722 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 123 -19 801 929 ;
+C -1 ; WX 667 ; N Eacute ; B 86 0 762 929 ;
+C -1 ; WX 556 ; N emacron ; B 84 -15 580 684 ;
+C -1 ; WX 556 ; N gbreve ; B 42 -220 610 731 ;
+C -1 ; WX 834 ; N onequarter ; B 150 -19 802 703 ;
+C -1 ; WX 667 ; N Scaron ; B 90 -19 713 929 ;
+C -1 ; WX 667 ; N Scommaaccent ; B 90 -225 713 737 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 105 -19 829 929 ;
+C -1 ; WX 400 ; N degree ; B 169 411 468 703 ;
+C -1 ; WX 556 ; N ograve ; B 83 -14 585 734 ;
+C -1 ; WX 722 ; N Ccaron ; B 108 -19 782 929 ;
+C -1 ; WX 556 ; N ugrave ; B 94 -15 600 734 ;
+C -1 ; WX 453 ; N radical ; B 79 -80 617 762 ;
+C -1 ; WX 722 ; N Dcaron ; B 81 0 764 929 ;
+C -1 ; WX 333 ; N rcommaaccent ; B 30 -225 446 538 ;
+C -1 ; WX 722 ; N Ntilde ; B 76 0 799 917 ;
+C -1 ; WX 556 ; N otilde ; B 83 -14 602 722 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 88 -225 773 718 ;
+C -1 ; WX 556 ; N Lcommaaccent ; B 76 -225 555 718 ;
+C -1 ; WX 667 ; N Atilde ; B 14 0 699 917 ;
+C -1 ; WX 667 ; N Aogonek ; B 14 -225 654 718 ;
+C -1 ; WX 667 ; N Aring ; B 14 0 654 931 ;
+C -1 ; WX 778 ; N Otilde ; B 105 -19 826 917 ;
+C -1 ; WX 500 ; N zdotaccent ; B 31 0 571 706 ;
+C -1 ; WX 667 ; N Ecaron ; B 86 0 762 929 ;
+C -1 ; WX 278 ; N Iogonek ; B -33 -225 341 718 ;
+C -1 ; WX 500 ; N kcommaaccent ; B 67 -225 600 718 ;
+C -1 ; WX 584 ; N minus ; B 85 216 606 289 ;
+C -1 ; WX 278 ; N Icircumflex ; B 91 0 452 929 ;
+C -1 ; WX 556 ; N ncaron ; B 65 0 580 734 ;
+C -1 ; WX 278 ; N tcommaaccent ; B 63 -225 368 669 ;
+C -1 ; WX 584 ; N logicalnot ; B 106 108 628 390 ;
+C -1 ; WX 556 ; N odieresis ; B 83 -14 585 706 ;
+C -1 ; WX 556 ; N udieresis ; B 94 -15 600 706 ;
+C -1 ; WX 549 ; N notequal ; B 34 -35 623 551 ;
+C -1 ; WX 556 ; N gcommaaccent ; B 42 -220 610 822 ;
+C -1 ; WX 556 ; N eth ; B 81 -15 617 737 ;
+C -1 ; WX 500 ; N zcaron ; B 31 0 571 734 ;
+C -1 ; WX 556 ; N ncommaaccent ; B 65 -225 573 538 ;
+C -1 ; WX 333 ; N onesuperior ; B 166 281 371 703 ;
+C -1 ; WX 278 ; N imacron ; B 95 0 417 684 ;
+C -1 ; WX 556 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2705
+KPX A C -30
+KPX A Cacute -30
+KPX A Ccaron -30
+KPX A Ccedilla -30
+KPX A G -30
+KPX A Gbreve -30
+KPX A Gcommaaccent -30
+KPX A O -30
+KPX A Oacute -30
+KPX A Ocircumflex -30
+KPX A Odieresis -30
+KPX A Ograve -30
+KPX A Ohungarumlaut -30
+KPX A Omacron -30
+KPX A Oslash -30
+KPX A Otilde -30
+KPX A Q -30
+KPX A T -120
+KPX A Tcaron -120
+KPX A Tcommaaccent -120
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -70
+KPX A W -50
+KPX A Y -100
+KPX A Yacute -100
+KPX A Ydieresis -100
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -40
+KPX A w -40
+KPX A y -40
+KPX A yacute -40
+KPX A ydieresis -40
+KPX Aacute C -30
+KPX Aacute Cacute -30
+KPX Aacute Ccaron -30
+KPX Aacute Ccedilla -30
+KPX Aacute G -30
+KPX Aacute Gbreve -30
+KPX Aacute Gcommaaccent -30
+KPX Aacute O -30
+KPX Aacute Oacute -30
+KPX Aacute Ocircumflex -30
+KPX Aacute Odieresis -30
+KPX Aacute Ograve -30
+KPX Aacute Ohungarumlaut -30
+KPX Aacute Omacron -30
+KPX Aacute Oslash -30
+KPX Aacute Otilde -30
+KPX Aacute Q -30
+KPX Aacute T -120
+KPX Aacute Tcaron -120
+KPX Aacute Tcommaaccent -120
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -70
+KPX Aacute W -50
+KPX Aacute Y -100
+KPX Aacute Yacute -100
+KPX Aacute Ydieresis -100
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -40
+KPX Aacute w -40
+KPX Aacute y -40
+KPX Aacute yacute -40
+KPX Aacute ydieresis -40
+KPX Abreve C -30
+KPX Abreve Cacute -30
+KPX Abreve Ccaron -30
+KPX Abreve Ccedilla -30
+KPX Abreve G -30
+KPX Abreve Gbreve -30
+KPX Abreve Gcommaaccent -30
+KPX Abreve O -30
+KPX Abreve Oacute -30
+KPX Abreve Ocircumflex -30
+KPX Abreve Odieresis -30
+KPX Abreve Ograve -30
+KPX Abreve Ohungarumlaut -30
+KPX Abreve Omacron -30
+KPX Abreve Oslash -30
+KPX Abreve Otilde -30
+KPX Abreve Q -30
+KPX Abreve T -120
+KPX Abreve Tcaron -120
+KPX Abreve Tcommaaccent -120
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -70
+KPX Abreve W -50
+KPX Abreve Y -100
+KPX Abreve Yacute -100
+KPX Abreve Ydieresis -100
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -40
+KPX Abreve w -40
+KPX Abreve y -40
+KPX Abreve yacute -40
+KPX Abreve ydieresis -40
+KPX Acircumflex C -30
+KPX Acircumflex Cacute -30
+KPX Acircumflex Ccaron -30
+KPX Acircumflex Ccedilla -30
+KPX Acircumflex G -30
+KPX Acircumflex Gbreve -30
+KPX Acircumflex Gcommaaccent -30
+KPX Acircumflex O -30
+KPX Acircumflex Oacute -30
+KPX Acircumflex Ocircumflex -30
+KPX Acircumflex Odieresis -30
+KPX Acircumflex Ograve -30
+KPX Acircumflex Ohungarumlaut -30
+KPX Acircumflex Omacron -30
+KPX Acircumflex Oslash -30
+KPX Acircumflex Otilde -30
+KPX Acircumflex Q -30
+KPX Acircumflex T -120
+KPX Acircumflex Tcaron -120
+KPX Acircumflex Tcommaaccent -120
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -70
+KPX Acircumflex W -50
+KPX Acircumflex Y -100
+KPX Acircumflex Yacute -100
+KPX Acircumflex Ydieresis -100
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -40
+KPX Acircumflex w -40
+KPX Acircumflex y -40
+KPX Acircumflex yacute -40
+KPX Acircumflex ydieresis -40
+KPX Adieresis C -30
+KPX Adieresis Cacute -30
+KPX Adieresis Ccaron -30
+KPX Adieresis Ccedilla -30
+KPX Adieresis G -30
+KPX Adieresis Gbreve -30
+KPX Adieresis Gcommaaccent -30
+KPX Adieresis O -30
+KPX Adieresis Oacute -30
+KPX Adieresis Ocircumflex -30
+KPX Adieresis Odieresis -30
+KPX Adieresis Ograve -30
+KPX Adieresis Ohungarumlaut -30
+KPX Adieresis Omacron -30
+KPX Adieresis Oslash -30
+KPX Adieresis Otilde -30
+KPX Adieresis Q -30
+KPX Adieresis T -120
+KPX Adieresis Tcaron -120
+KPX Adieresis Tcommaaccent -120
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -70
+KPX Adieresis W -50
+KPX Adieresis Y -100
+KPX Adieresis Yacute -100
+KPX Adieresis Ydieresis -100
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -40
+KPX Adieresis w -40
+KPX Adieresis y -40
+KPX Adieresis yacute -40
+KPX Adieresis ydieresis -40
+KPX Agrave C -30
+KPX Agrave Cacute -30
+KPX Agrave Ccaron -30
+KPX Agrave Ccedilla -30
+KPX Agrave G -30
+KPX Agrave Gbreve -30
+KPX Agrave Gcommaaccent -30
+KPX Agrave O -30
+KPX Agrave Oacute -30
+KPX Agrave Ocircumflex -30
+KPX Agrave Odieresis -30
+KPX Agrave Ograve -30
+KPX Agrave Ohungarumlaut -30
+KPX Agrave Omacron -30
+KPX Agrave Oslash -30
+KPX Agrave Otilde -30
+KPX Agrave Q -30
+KPX Agrave T -120
+KPX Agrave Tcaron -120
+KPX Agrave Tcommaaccent -120
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -70
+KPX Agrave W -50
+KPX Agrave Y -100
+KPX Agrave Yacute -100
+KPX Agrave Ydieresis -100
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -40
+KPX Agrave w -40
+KPX Agrave y -40
+KPX Agrave yacute -40
+KPX Agrave ydieresis -40
+KPX Amacron C -30
+KPX Amacron Cacute -30
+KPX Amacron Ccaron -30
+KPX Amacron Ccedilla -30
+KPX Amacron G -30
+KPX Amacron Gbreve -30
+KPX Amacron Gcommaaccent -30
+KPX Amacron O -30
+KPX Amacron Oacute -30
+KPX Amacron Ocircumflex -30
+KPX Amacron Odieresis -30
+KPX Amacron Ograve -30
+KPX Amacron Ohungarumlaut -30
+KPX Amacron Omacron -30
+KPX Amacron Oslash -30
+KPX Amacron Otilde -30
+KPX Amacron Q -30
+KPX Amacron T -120
+KPX Amacron Tcaron -120
+KPX Amacron Tcommaaccent -120
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -70
+KPX Amacron W -50
+KPX Amacron Y -100
+KPX Amacron Yacute -100
+KPX Amacron Ydieresis -100
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -40
+KPX Amacron w -40
+KPX Amacron y -40
+KPX Amacron yacute -40
+KPX Amacron ydieresis -40
+KPX Aogonek C -30
+KPX Aogonek Cacute -30
+KPX Aogonek Ccaron -30
+KPX Aogonek Ccedilla -30
+KPX Aogonek G -30
+KPX Aogonek Gbreve -30
+KPX Aogonek Gcommaaccent -30
+KPX Aogonek O -30
+KPX Aogonek Oacute -30
+KPX Aogonek Ocircumflex -30
+KPX Aogonek Odieresis -30
+KPX Aogonek Ograve -30
+KPX Aogonek Ohungarumlaut -30
+KPX Aogonek Omacron -30
+KPX Aogonek Oslash -30
+KPX Aogonek Otilde -30
+KPX Aogonek Q -30
+KPX Aogonek T -120
+KPX Aogonek Tcaron -120
+KPX Aogonek Tcommaaccent -120
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -70
+KPX Aogonek W -50
+KPX Aogonek Y -100
+KPX Aogonek Yacute -100
+KPX Aogonek Ydieresis -100
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -40
+KPX Aogonek w -40
+KPX Aogonek y -40
+KPX Aogonek yacute -40
+KPX Aogonek ydieresis -40
+KPX Aring C -30
+KPX Aring Cacute -30
+KPX Aring Ccaron -30
+KPX Aring Ccedilla -30
+KPX Aring G -30
+KPX Aring Gbreve -30
+KPX Aring Gcommaaccent -30
+KPX Aring O -30
+KPX Aring Oacute -30
+KPX Aring Ocircumflex -30
+KPX Aring Odieresis -30
+KPX Aring Ograve -30
+KPX Aring Ohungarumlaut -30
+KPX Aring Omacron -30
+KPX Aring Oslash -30
+KPX Aring Otilde -30
+KPX Aring Q -30
+KPX Aring T -120
+KPX Aring Tcaron -120
+KPX Aring Tcommaaccent -120
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -70
+KPX Aring W -50
+KPX Aring Y -100
+KPX Aring Yacute -100
+KPX Aring Ydieresis -100
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -40
+KPX Aring w -40
+KPX Aring y -40
+KPX Aring yacute -40
+KPX Aring ydieresis -40
+KPX Atilde C -30
+KPX Atilde Cacute -30
+KPX Atilde Ccaron -30
+KPX Atilde Ccedilla -30
+KPX Atilde G -30
+KPX Atilde Gbreve -30
+KPX Atilde Gcommaaccent -30
+KPX Atilde O -30
+KPX Atilde Oacute -30
+KPX Atilde Ocircumflex -30
+KPX Atilde Odieresis -30
+KPX Atilde Ograve -30
+KPX Atilde Ohungarumlaut -30
+KPX Atilde Omacron -30
+KPX Atilde Oslash -30
+KPX Atilde Otilde -30
+KPX Atilde Q -30
+KPX Atilde T -120
+KPX Atilde Tcaron -120
+KPX Atilde Tcommaaccent -120
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -70
+KPX Atilde W -50
+KPX Atilde Y -100
+KPX Atilde Yacute -100
+KPX Atilde Ydieresis -100
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -40
+KPX Atilde w -40
+KPX Atilde y -40
+KPX Atilde yacute -40
+KPX Atilde ydieresis -40
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX B comma -20
+KPX B period -20
+KPX C comma -30
+KPX C period -30
+KPX Cacute comma -30
+KPX Cacute period -30
+KPX Ccaron comma -30
+KPX Ccaron period -30
+KPX Ccedilla comma -30
+KPX Ccedilla period -30
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -70
+KPX D W -40
+KPX D Y -90
+KPX D Yacute -90
+KPX D Ydieresis -90
+KPX D comma -70
+KPX D period -70
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -70
+KPX Dcaron W -40
+KPX Dcaron Y -90
+KPX Dcaron Yacute -90
+KPX Dcaron Ydieresis -90
+KPX Dcaron comma -70
+KPX Dcaron period -70
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -70
+KPX Dcroat W -40
+KPX Dcroat Y -90
+KPX Dcroat Yacute -90
+KPX Dcroat Ydieresis -90
+KPX Dcroat comma -70
+KPX Dcroat period -70
+KPX F A -80
+KPX F Aacute -80
+KPX F Abreve -80
+KPX F Acircumflex -80
+KPX F Adieresis -80
+KPX F Agrave -80
+KPX F Amacron -80
+KPX F Aogonek -80
+KPX F Aring -80
+KPX F Atilde -80
+KPX F a -50
+KPX F aacute -50
+KPX F abreve -50
+KPX F acircumflex -50
+KPX F adieresis -50
+KPX F agrave -50
+KPX F amacron -50
+KPX F aogonek -50
+KPX F aring -50
+KPX F atilde -50
+KPX F comma -150
+KPX F e -30
+KPX F eacute -30
+KPX F ecaron -30
+KPX F ecircumflex -30
+KPX F edieresis -30
+KPX F edotaccent -30
+KPX F egrave -30
+KPX F emacron -30
+KPX F eogonek -30
+KPX F o -30
+KPX F oacute -30
+KPX F ocircumflex -30
+KPX F odieresis -30
+KPX F ograve -30
+KPX F ohungarumlaut -30
+KPX F omacron -30
+KPX F oslash -30
+KPX F otilde -30
+KPX F period -150
+KPX F r -45
+KPX F racute -45
+KPX F rcaron -45
+KPX F rcommaaccent -45
+KPX J A -20
+KPX J Aacute -20
+KPX J Abreve -20
+KPX J Acircumflex -20
+KPX J Adieresis -20
+KPX J Agrave -20
+KPX J Amacron -20
+KPX J Aogonek -20
+KPX J Aring -20
+KPX J Atilde -20
+KPX J a -20
+KPX J aacute -20
+KPX J abreve -20
+KPX J acircumflex -20
+KPX J adieresis -20
+KPX J agrave -20
+KPX J amacron -20
+KPX J aogonek -20
+KPX J aring -20
+KPX J atilde -20
+KPX J comma -30
+KPX J period -30
+KPX J u -20
+KPX J uacute -20
+KPX J ucircumflex -20
+KPX J udieresis -20
+KPX J ugrave -20
+KPX J uhungarumlaut -20
+KPX J umacron -20
+KPX J uogonek -20
+KPX J uring -20
+KPX K O -50
+KPX K Oacute -50
+KPX K Ocircumflex -50
+KPX K Odieresis -50
+KPX K Ograve -50
+KPX K Ohungarumlaut -50
+KPX K Omacron -50
+KPX K Oslash -50
+KPX K Otilde -50
+KPX K e -40
+KPX K eacute -40
+KPX K ecaron -40
+KPX K ecircumflex -40
+KPX K edieresis -40
+KPX K edotaccent -40
+KPX K egrave -40
+KPX K emacron -40
+KPX K eogonek -40
+KPX K o -40
+KPX K oacute -40
+KPX K ocircumflex -40
+KPX K odieresis -40
+KPX K ograve -40
+KPX K ohungarumlaut -40
+KPX K omacron -40
+KPX K oslash -40
+KPX K otilde -40
+KPX K u -30
+KPX K uacute -30
+KPX K ucircumflex -30
+KPX K udieresis -30
+KPX K ugrave -30
+KPX K uhungarumlaut -30
+KPX K umacron -30
+KPX K uogonek -30
+KPX K uring -30
+KPX K y -50
+KPX K yacute -50
+KPX K ydieresis -50
+KPX Kcommaaccent O -50
+KPX Kcommaaccent Oacute -50
+KPX Kcommaaccent Ocircumflex -50
+KPX Kcommaaccent Odieresis -50
+KPX Kcommaaccent Ograve -50
+KPX Kcommaaccent Ohungarumlaut -50
+KPX Kcommaaccent Omacron -50
+KPX Kcommaaccent Oslash -50
+KPX Kcommaaccent Otilde -50
+KPX Kcommaaccent e -40
+KPX Kcommaaccent eacute -40
+KPX Kcommaaccent ecaron -40
+KPX Kcommaaccent ecircumflex -40
+KPX Kcommaaccent edieresis -40
+KPX Kcommaaccent edotaccent -40
+KPX Kcommaaccent egrave -40
+KPX Kcommaaccent emacron -40
+KPX Kcommaaccent eogonek -40
+KPX Kcommaaccent o -40
+KPX Kcommaaccent oacute -40
+KPX Kcommaaccent ocircumflex -40
+KPX Kcommaaccent odieresis -40
+KPX Kcommaaccent ograve -40
+KPX Kcommaaccent ohungarumlaut -40
+KPX Kcommaaccent omacron -40
+KPX Kcommaaccent oslash -40
+KPX Kcommaaccent otilde -40
+KPX Kcommaaccent u -30
+KPX Kcommaaccent uacute -30
+KPX Kcommaaccent ucircumflex -30
+KPX Kcommaaccent udieresis -30
+KPX Kcommaaccent ugrave -30
+KPX Kcommaaccent uhungarumlaut -30
+KPX Kcommaaccent umacron -30
+KPX Kcommaaccent uogonek -30
+KPX Kcommaaccent uring -30
+KPX Kcommaaccent y -50
+KPX Kcommaaccent yacute -50
+KPX Kcommaaccent ydieresis -50
+KPX L T -110
+KPX L Tcaron -110
+KPX L Tcommaaccent -110
+KPX L V -110
+KPX L W -70
+KPX L Y -140
+KPX L Yacute -140
+KPX L Ydieresis -140
+KPX L quotedblright -140
+KPX L quoteright -160
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -110
+KPX Lacute Tcaron -110
+KPX Lacute Tcommaaccent -110
+KPX Lacute V -110
+KPX Lacute W -70
+KPX Lacute Y -140
+KPX Lacute Yacute -140
+KPX Lacute Ydieresis -140
+KPX Lacute quotedblright -140
+KPX Lacute quoteright -160
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcaron T -110
+KPX Lcaron Tcaron -110
+KPX Lcaron Tcommaaccent -110
+KPX Lcaron V -110
+KPX Lcaron W -70
+KPX Lcaron Y -140
+KPX Lcaron Yacute -140
+KPX Lcaron Ydieresis -140
+KPX Lcaron quotedblright -140
+KPX Lcaron quoteright -160
+KPX Lcaron y -30
+KPX Lcaron yacute -30
+KPX Lcaron ydieresis -30
+KPX Lcommaaccent T -110
+KPX Lcommaaccent Tcaron -110
+KPX Lcommaaccent Tcommaaccent -110
+KPX Lcommaaccent V -110
+KPX Lcommaaccent W -70
+KPX Lcommaaccent Y -140
+KPX Lcommaaccent Yacute -140
+KPX Lcommaaccent Ydieresis -140
+KPX Lcommaaccent quotedblright -140
+KPX Lcommaaccent quoteright -160
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -110
+KPX Lslash Tcaron -110
+KPX Lslash Tcommaaccent -110
+KPX Lslash V -110
+KPX Lslash W -70
+KPX Lslash Y -140
+KPX Lslash Yacute -140
+KPX Lslash Ydieresis -140
+KPX Lslash quotedblright -140
+KPX Lslash quoteright -160
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX O A -20
+KPX O Aacute -20
+KPX O Abreve -20
+KPX O Acircumflex -20
+KPX O Adieresis -20
+KPX O Agrave -20
+KPX O Amacron -20
+KPX O Aogonek -20
+KPX O Aring -20
+KPX O Atilde -20
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -30
+KPX O X -60
+KPX O Y -70
+KPX O Yacute -70
+KPX O Ydieresis -70
+KPX O comma -40
+KPX O period -40
+KPX Oacute A -20
+KPX Oacute Aacute -20
+KPX Oacute Abreve -20
+KPX Oacute Acircumflex -20
+KPX Oacute Adieresis -20
+KPX Oacute Agrave -20
+KPX Oacute Amacron -20
+KPX Oacute Aogonek -20
+KPX Oacute Aring -20
+KPX Oacute Atilde -20
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -30
+KPX Oacute X -60
+KPX Oacute Y -70
+KPX Oacute Yacute -70
+KPX Oacute Ydieresis -70
+KPX Oacute comma -40
+KPX Oacute period -40
+KPX Ocircumflex A -20
+KPX Ocircumflex Aacute -20
+KPX Ocircumflex Abreve -20
+KPX Ocircumflex Acircumflex -20
+KPX Ocircumflex Adieresis -20
+KPX Ocircumflex Agrave -20
+KPX Ocircumflex Amacron -20
+KPX Ocircumflex Aogonek -20
+KPX Ocircumflex Aring -20
+KPX Ocircumflex Atilde -20
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -30
+KPX Ocircumflex X -60
+KPX Ocircumflex Y -70
+KPX Ocircumflex Yacute -70
+KPX Ocircumflex Ydieresis -70
+KPX Ocircumflex comma -40
+KPX Ocircumflex period -40
+KPX Odieresis A -20
+KPX Odieresis Aacute -20
+KPX Odieresis Abreve -20
+KPX Odieresis Acircumflex -20
+KPX Odieresis Adieresis -20
+KPX Odieresis Agrave -20
+KPX Odieresis Amacron -20
+KPX Odieresis Aogonek -20
+KPX Odieresis Aring -20
+KPX Odieresis Atilde -20
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -30
+KPX Odieresis X -60
+KPX Odieresis Y -70
+KPX Odieresis Yacute -70
+KPX Odieresis Ydieresis -70
+KPX Odieresis comma -40
+KPX Odieresis period -40
+KPX Ograve A -20
+KPX Ograve Aacute -20
+KPX Ograve Abreve -20
+KPX Ograve Acircumflex -20
+KPX Ograve Adieresis -20
+KPX Ograve Agrave -20
+KPX Ograve Amacron -20
+KPX Ograve Aogonek -20
+KPX Ograve Aring -20
+KPX Ograve Atilde -20
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -30
+KPX Ograve X -60
+KPX Ograve Y -70
+KPX Ograve Yacute -70
+KPX Ograve Ydieresis -70
+KPX Ograve comma -40
+KPX Ograve period -40
+KPX Ohungarumlaut A -20
+KPX Ohungarumlaut Aacute -20
+KPX Ohungarumlaut Abreve -20
+KPX Ohungarumlaut Acircumflex -20
+KPX Ohungarumlaut Adieresis -20
+KPX Ohungarumlaut Agrave -20
+KPX Ohungarumlaut Amacron -20
+KPX Ohungarumlaut Aogonek -20
+KPX Ohungarumlaut Aring -20
+KPX Ohungarumlaut Atilde -20
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -30
+KPX Ohungarumlaut X -60
+KPX Ohungarumlaut Y -70
+KPX Ohungarumlaut Yacute -70
+KPX Ohungarumlaut Ydieresis -70
+KPX Ohungarumlaut comma -40
+KPX Ohungarumlaut period -40
+KPX Omacron A -20
+KPX Omacron Aacute -20
+KPX Omacron Abreve -20
+KPX Omacron Acircumflex -20
+KPX Omacron Adieresis -20
+KPX Omacron Agrave -20
+KPX Omacron Amacron -20
+KPX Omacron Aogonek -20
+KPX Omacron Aring -20
+KPX Omacron Atilde -20
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -30
+KPX Omacron X -60
+KPX Omacron Y -70
+KPX Omacron Yacute -70
+KPX Omacron Ydieresis -70
+KPX Omacron comma -40
+KPX Omacron period -40
+KPX Oslash A -20
+KPX Oslash Aacute -20
+KPX Oslash Abreve -20
+KPX Oslash Acircumflex -20
+KPX Oslash Adieresis -20
+KPX Oslash Agrave -20
+KPX Oslash Amacron -20
+KPX Oslash Aogonek -20
+KPX Oslash Aring -20
+KPX Oslash Atilde -20
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -30
+KPX Oslash X -60
+KPX Oslash Y -70
+KPX Oslash Yacute -70
+KPX Oslash Ydieresis -70
+KPX Oslash comma -40
+KPX Oslash period -40
+KPX Otilde A -20
+KPX Otilde Aacute -20
+KPX Otilde Abreve -20
+KPX Otilde Acircumflex -20
+KPX Otilde Adieresis -20
+KPX Otilde Agrave -20
+KPX Otilde Amacron -20
+KPX Otilde Aogonek -20
+KPX Otilde Aring -20
+KPX Otilde Atilde -20
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -30
+KPX Otilde X -60
+KPX Otilde Y -70
+KPX Otilde Yacute -70
+KPX Otilde Ydieresis -70
+KPX Otilde comma -40
+KPX Otilde period -40
+KPX P A -120
+KPX P Aacute -120
+KPX P Abreve -120
+KPX P Acircumflex -120
+KPX P Adieresis -120
+KPX P Agrave -120
+KPX P Amacron -120
+KPX P Aogonek -120
+KPX P Aring -120
+KPX P Atilde -120
+KPX P a -40
+KPX P aacute -40
+KPX P abreve -40
+KPX P acircumflex -40
+KPX P adieresis -40
+KPX P agrave -40
+KPX P amacron -40
+KPX P aogonek -40
+KPX P aring -40
+KPX P atilde -40
+KPX P comma -180
+KPX P e -50
+KPX P eacute -50
+KPX P ecaron -50
+KPX P ecircumflex -50
+KPX P edieresis -50
+KPX P edotaccent -50
+KPX P egrave -50
+KPX P emacron -50
+KPX P eogonek -50
+KPX P o -50
+KPX P oacute -50
+KPX P ocircumflex -50
+KPX P odieresis -50
+KPX P ograve -50
+KPX P ohungarumlaut -50
+KPX P omacron -50
+KPX P oslash -50
+KPX P otilde -50
+KPX P period -180
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -20
+KPX R Oacute -20
+KPX R Ocircumflex -20
+KPX R Odieresis -20
+KPX R Ograve -20
+KPX R Ohungarumlaut -20
+KPX R Omacron -20
+KPX R Oslash -20
+KPX R Otilde -20
+KPX R T -30
+KPX R Tcaron -30
+KPX R Tcommaaccent -30
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -50
+KPX R W -30
+KPX R Y -50
+KPX R Yacute -50
+KPX R Ydieresis -50
+KPX Racute O -20
+KPX Racute Oacute -20
+KPX Racute Ocircumflex -20
+KPX Racute Odieresis -20
+KPX Racute Ograve -20
+KPX Racute Ohungarumlaut -20
+KPX Racute Omacron -20
+KPX Racute Oslash -20
+KPX Racute Otilde -20
+KPX Racute T -30
+KPX Racute Tcaron -30
+KPX Racute Tcommaaccent -30
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -50
+KPX Racute W -30
+KPX Racute Y -50
+KPX Racute Yacute -50
+KPX Racute Ydieresis -50
+KPX Rcaron O -20
+KPX Rcaron Oacute -20
+KPX Rcaron Ocircumflex -20
+KPX Rcaron Odieresis -20
+KPX Rcaron Ograve -20
+KPX Rcaron Ohungarumlaut -20
+KPX Rcaron Omacron -20
+KPX Rcaron Oslash -20
+KPX Rcaron Otilde -20
+KPX Rcaron T -30
+KPX Rcaron Tcaron -30
+KPX Rcaron Tcommaaccent -30
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -50
+KPX Rcaron W -30
+KPX Rcaron Y -50
+KPX Rcaron Yacute -50
+KPX Rcaron Ydieresis -50
+KPX Rcommaaccent O -20
+KPX Rcommaaccent Oacute -20
+KPX Rcommaaccent Ocircumflex -20
+KPX Rcommaaccent Odieresis -20
+KPX Rcommaaccent Ograve -20
+KPX Rcommaaccent Ohungarumlaut -20
+KPX Rcommaaccent Omacron -20
+KPX Rcommaaccent Oslash -20
+KPX Rcommaaccent Otilde -20
+KPX Rcommaaccent T -30
+KPX Rcommaaccent Tcaron -30
+KPX Rcommaaccent Tcommaaccent -30
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -50
+KPX Rcommaaccent W -30
+KPX Rcommaaccent Y -50
+KPX Rcommaaccent Yacute -50
+KPX Rcommaaccent Ydieresis -50
+KPX S comma -20
+KPX S period -20
+KPX Sacute comma -20
+KPX Sacute period -20
+KPX Scaron comma -20
+KPX Scaron period -20
+KPX Scedilla comma -20
+KPX Scedilla period -20
+KPX Scommaaccent comma -20
+KPX Scommaaccent period -20
+KPX T A -120
+KPX T Aacute -120
+KPX T Abreve -120
+KPX T Acircumflex -120
+KPX T Adieresis -120
+KPX T Agrave -120
+KPX T Amacron -120
+KPX T Aogonek -120
+KPX T Aring -120
+KPX T Atilde -120
+KPX T O -40
+KPX T Oacute -40
+KPX T Ocircumflex -40
+KPX T Odieresis -40
+KPX T Ograve -40
+KPX T Ohungarumlaut -40
+KPX T Omacron -40
+KPX T Oslash -40
+KPX T Otilde -40
+KPX T a -120
+KPX T aacute -120
+KPX T abreve -60
+KPX T acircumflex -120
+KPX T adieresis -120
+KPX T agrave -120
+KPX T amacron -60
+KPX T aogonek -120
+KPX T aring -120
+KPX T atilde -60
+KPX T colon -20
+KPX T comma -120
+KPX T e -120
+KPX T eacute -120
+KPX T ecaron -120
+KPX T ecircumflex -120
+KPX T edieresis -120
+KPX T edotaccent -120
+KPX T egrave -60
+KPX T emacron -60
+KPX T eogonek -120
+KPX T hyphen -140
+KPX T o -120
+KPX T oacute -120
+KPX T ocircumflex -120
+KPX T odieresis -120
+KPX T ograve -120
+KPX T ohungarumlaut -120
+KPX T omacron -60
+KPX T oslash -120
+KPX T otilde -60
+KPX T period -120
+KPX T r -120
+KPX T racute -120
+KPX T rcaron -120
+KPX T rcommaaccent -120
+KPX T semicolon -20
+KPX T u -120
+KPX T uacute -120
+KPX T ucircumflex -120
+KPX T udieresis -120
+KPX T ugrave -120
+KPX T uhungarumlaut -120
+KPX T umacron -60
+KPX T uogonek -120
+KPX T uring -120
+KPX T w -120
+KPX T y -120
+KPX T yacute -120
+KPX T ydieresis -60
+KPX Tcaron A -120
+KPX Tcaron Aacute -120
+KPX Tcaron Abreve -120
+KPX Tcaron Acircumflex -120
+KPX Tcaron Adieresis -120
+KPX Tcaron Agrave -120
+KPX Tcaron Amacron -120
+KPX Tcaron Aogonek -120
+KPX Tcaron Aring -120
+KPX Tcaron Atilde -120
+KPX Tcaron O -40
+KPX Tcaron Oacute -40
+KPX Tcaron Ocircumflex -40
+KPX Tcaron Odieresis -40
+KPX Tcaron Ograve -40
+KPX Tcaron Ohungarumlaut -40
+KPX Tcaron Omacron -40
+KPX Tcaron Oslash -40
+KPX Tcaron Otilde -40
+KPX Tcaron a -120
+KPX Tcaron aacute -120
+KPX Tcaron abreve -60
+KPX Tcaron acircumflex -120
+KPX Tcaron adieresis -120
+KPX Tcaron agrave -120
+KPX Tcaron amacron -60
+KPX Tcaron aogonek -120
+KPX Tcaron aring -120
+KPX Tcaron atilde -60
+KPX Tcaron colon -20
+KPX Tcaron comma -120
+KPX Tcaron e -120
+KPX Tcaron eacute -120
+KPX Tcaron ecaron -120
+KPX Tcaron ecircumflex -120
+KPX Tcaron edieresis -120
+KPX Tcaron edotaccent -120
+KPX Tcaron egrave -60
+KPX Tcaron emacron -60
+KPX Tcaron eogonek -120
+KPX Tcaron hyphen -140
+KPX Tcaron o -120
+KPX Tcaron oacute -120
+KPX Tcaron ocircumflex -120
+KPX Tcaron odieresis -120
+KPX Tcaron ograve -120
+KPX Tcaron ohungarumlaut -120
+KPX Tcaron omacron -60
+KPX Tcaron oslash -120
+KPX Tcaron otilde -60
+KPX Tcaron period -120
+KPX Tcaron r -120
+KPX Tcaron racute -120
+KPX Tcaron rcaron -120
+KPX Tcaron rcommaaccent -120
+KPX Tcaron semicolon -20
+KPX Tcaron u -120
+KPX Tcaron uacute -120
+KPX Tcaron ucircumflex -120
+KPX Tcaron udieresis -120
+KPX Tcaron ugrave -120
+KPX Tcaron uhungarumlaut -120
+KPX Tcaron umacron -60
+KPX Tcaron uogonek -120
+KPX Tcaron uring -120
+KPX Tcaron w -120
+KPX Tcaron y -120
+KPX Tcaron yacute -120
+KPX Tcaron ydieresis -60
+KPX Tcommaaccent A -120
+KPX Tcommaaccent Aacute -120
+KPX Tcommaaccent Abreve -120
+KPX Tcommaaccent Acircumflex -120
+KPX Tcommaaccent Adieresis -120
+KPX Tcommaaccent Agrave -120
+KPX Tcommaaccent Amacron -120
+KPX Tcommaaccent Aogonek -120
+KPX Tcommaaccent Aring -120
+KPX Tcommaaccent Atilde -120
+KPX Tcommaaccent O -40
+KPX Tcommaaccent Oacute -40
+KPX Tcommaaccent Ocircumflex -40
+KPX Tcommaaccent Odieresis -40
+KPX Tcommaaccent Ograve -40
+KPX Tcommaaccent Ohungarumlaut -40
+KPX Tcommaaccent Omacron -40
+KPX Tcommaaccent Oslash -40
+KPX Tcommaaccent Otilde -40
+KPX Tcommaaccent a -120
+KPX Tcommaaccent aacute -120
+KPX Tcommaaccent abreve -60
+KPX Tcommaaccent acircumflex -120
+KPX Tcommaaccent adieresis -120
+KPX Tcommaaccent agrave -120
+KPX Tcommaaccent amacron -60
+KPX Tcommaaccent aogonek -120
+KPX Tcommaaccent aring -120
+KPX Tcommaaccent atilde -60
+KPX Tcommaaccent colon -20
+KPX Tcommaaccent comma -120
+KPX Tcommaaccent e -120
+KPX Tcommaaccent eacute -120
+KPX Tcommaaccent ecaron -120
+KPX Tcommaaccent ecircumflex -120
+KPX Tcommaaccent edieresis -120
+KPX Tcommaaccent edotaccent -120
+KPX Tcommaaccent egrave -60
+KPX Tcommaaccent emacron -60
+KPX Tcommaaccent eogonek -120
+KPX Tcommaaccent hyphen -140
+KPX Tcommaaccent o -120
+KPX Tcommaaccent oacute -120
+KPX Tcommaaccent ocircumflex -120
+KPX Tcommaaccent odieresis -120
+KPX Tcommaaccent ograve -120
+KPX Tcommaaccent ohungarumlaut -120
+KPX Tcommaaccent omacron -60
+KPX Tcommaaccent oslash -120
+KPX Tcommaaccent otilde -60
+KPX Tcommaaccent period -120
+KPX Tcommaaccent r -120
+KPX Tcommaaccent racute -120
+KPX Tcommaaccent rcaron -120
+KPX Tcommaaccent rcommaaccent -120
+KPX Tcommaaccent semicolon -20
+KPX Tcommaaccent u -120
+KPX Tcommaaccent uacute -120
+KPX Tcommaaccent ucircumflex -120
+KPX Tcommaaccent udieresis -120
+KPX Tcommaaccent ugrave -120
+KPX Tcommaaccent uhungarumlaut -120
+KPX Tcommaaccent umacron -60
+KPX Tcommaaccent uogonek -120
+KPX Tcommaaccent uring -120
+KPX Tcommaaccent w -120
+KPX Tcommaaccent y -120
+KPX Tcommaaccent yacute -120
+KPX Tcommaaccent ydieresis -60
+KPX U A -40
+KPX U Aacute -40
+KPX U Abreve -40
+KPX U Acircumflex -40
+KPX U Adieresis -40
+KPX U Agrave -40
+KPX U Amacron -40
+KPX U Aogonek -40
+KPX U Aring -40
+KPX U Atilde -40
+KPX U comma -40
+KPX U period -40
+KPX Uacute A -40
+KPX Uacute Aacute -40
+KPX Uacute Abreve -40
+KPX Uacute Acircumflex -40
+KPX Uacute Adieresis -40
+KPX Uacute Agrave -40
+KPX Uacute Amacron -40
+KPX Uacute Aogonek -40
+KPX Uacute Aring -40
+KPX Uacute Atilde -40
+KPX Uacute comma -40
+KPX Uacute period -40
+KPX Ucircumflex A -40
+KPX Ucircumflex Aacute -40
+KPX Ucircumflex Abreve -40
+KPX Ucircumflex Acircumflex -40
+KPX Ucircumflex Adieresis -40
+KPX Ucircumflex Agrave -40
+KPX Ucircumflex Amacron -40
+KPX Ucircumflex Aogonek -40
+KPX Ucircumflex Aring -40
+KPX Ucircumflex Atilde -40
+KPX Ucircumflex comma -40
+KPX Ucircumflex period -40
+KPX Udieresis A -40
+KPX Udieresis Aacute -40
+KPX Udieresis Abreve -40
+KPX Udieresis Acircumflex -40
+KPX Udieresis Adieresis -40
+KPX Udieresis Agrave -40
+KPX Udieresis Amacron -40
+KPX Udieresis Aogonek -40
+KPX Udieresis Aring -40
+KPX Udieresis Atilde -40
+KPX Udieresis comma -40
+KPX Udieresis period -40
+KPX Ugrave A -40
+KPX Ugrave Aacute -40
+KPX Ugrave Abreve -40
+KPX Ugrave Acircumflex -40
+KPX Ugrave Adieresis -40
+KPX Ugrave Agrave -40
+KPX Ugrave Amacron -40
+KPX Ugrave Aogonek -40
+KPX Ugrave Aring -40
+KPX Ugrave Atilde -40
+KPX Ugrave comma -40
+KPX Ugrave period -40
+KPX Uhungarumlaut A -40
+KPX Uhungarumlaut Aacute -40
+KPX Uhungarumlaut Abreve -40
+KPX Uhungarumlaut Acircumflex -40
+KPX Uhungarumlaut Adieresis -40
+KPX Uhungarumlaut Agrave -40
+KPX Uhungarumlaut Amacron -40
+KPX Uhungarumlaut Aogonek -40
+KPX Uhungarumlaut Aring -40
+KPX Uhungarumlaut Atilde -40
+KPX Uhungarumlaut comma -40
+KPX Uhungarumlaut period -40
+KPX Umacron A -40
+KPX Umacron Aacute -40
+KPX Umacron Abreve -40
+KPX Umacron Acircumflex -40
+KPX Umacron Adieresis -40
+KPX Umacron Agrave -40
+KPX Umacron Amacron -40
+KPX Umacron Aogonek -40
+KPX Umacron Aring -40
+KPX Umacron Atilde -40
+KPX Umacron comma -40
+KPX Umacron period -40
+KPX Uogonek A -40
+KPX Uogonek Aacute -40
+KPX Uogonek Abreve -40
+KPX Uogonek Acircumflex -40
+KPX Uogonek Adieresis -40
+KPX Uogonek Agrave -40
+KPX Uogonek Amacron -40
+KPX Uogonek Aogonek -40
+KPX Uogonek Aring -40
+KPX Uogonek Atilde -40
+KPX Uogonek comma -40
+KPX Uogonek period -40
+KPX Uring A -40
+KPX Uring Aacute -40
+KPX Uring Abreve -40
+KPX Uring Acircumflex -40
+KPX Uring Adieresis -40
+KPX Uring Agrave -40
+KPX Uring Amacron -40
+KPX Uring Aogonek -40
+KPX Uring Aring -40
+KPX Uring Atilde -40
+KPX Uring comma -40
+KPX Uring period -40
+KPX V A -80
+KPX V Aacute -80
+KPX V Abreve -80
+KPX V Acircumflex -80
+KPX V Adieresis -80
+KPX V Agrave -80
+KPX V Amacron -80
+KPX V Aogonek -80
+KPX V Aring -80
+KPX V Atilde -80
+KPX V G -40
+KPX V Gbreve -40
+KPX V Gcommaaccent -40
+KPX V O -40
+KPX V Oacute -40
+KPX V Ocircumflex -40
+KPX V Odieresis -40
+KPX V Ograve -40
+KPX V Ohungarumlaut -40
+KPX V Omacron -40
+KPX V Oslash -40
+KPX V Otilde -40
+KPX V a -70
+KPX V aacute -70
+KPX V abreve -70
+KPX V acircumflex -70
+KPX V adieresis -70
+KPX V agrave -70
+KPX V amacron -70
+KPX V aogonek -70
+KPX V aring -70
+KPX V atilde -70
+KPX V colon -40
+KPX V comma -125
+KPX V e -80
+KPX V eacute -80
+KPX V ecaron -80
+KPX V ecircumflex -80
+KPX V edieresis -80
+KPX V edotaccent -80
+KPX V egrave -80
+KPX V emacron -80
+KPX V eogonek -80
+KPX V hyphen -80
+KPX V o -80
+KPX V oacute -80
+KPX V ocircumflex -80
+KPX V odieresis -80
+KPX V ograve -80
+KPX V ohungarumlaut -80
+KPX V omacron -80
+KPX V oslash -80
+KPX V otilde -80
+KPX V period -125
+KPX V semicolon -40
+KPX V u -70
+KPX V uacute -70
+KPX V ucircumflex -70
+KPX V udieresis -70
+KPX V ugrave -70
+KPX V uhungarumlaut -70
+KPX V umacron -70
+KPX V uogonek -70
+KPX V uring -70
+KPX W A -50
+KPX W Aacute -50
+KPX W Abreve -50
+KPX W Acircumflex -50
+KPX W Adieresis -50
+KPX W Agrave -50
+KPX W Amacron -50
+KPX W Aogonek -50
+KPX W Aring -50
+KPX W Atilde -50
+KPX W O -20
+KPX W Oacute -20
+KPX W Ocircumflex -20
+KPX W Odieresis -20
+KPX W Ograve -20
+KPX W Ohungarumlaut -20
+KPX W Omacron -20
+KPX W Oslash -20
+KPX W Otilde -20
+KPX W a -40
+KPX W aacute -40
+KPX W abreve -40
+KPX W acircumflex -40
+KPX W adieresis -40
+KPX W agrave -40
+KPX W amacron -40
+KPX W aogonek -40
+KPX W aring -40
+KPX W atilde -40
+KPX W comma -80
+KPX W e -30
+KPX W eacute -30
+KPX W ecaron -30
+KPX W ecircumflex -30
+KPX W edieresis -30
+KPX W edotaccent -30
+KPX W egrave -30
+KPX W emacron -30
+KPX W eogonek -30
+KPX W hyphen -40
+KPX W o -30
+KPX W oacute -30
+KPX W ocircumflex -30
+KPX W odieresis -30
+KPX W ograve -30
+KPX W ohungarumlaut -30
+KPX W omacron -30
+KPX W oslash -30
+KPX W otilde -30
+KPX W period -80
+KPX W u -30
+KPX W uacute -30
+KPX W ucircumflex -30
+KPX W udieresis -30
+KPX W ugrave -30
+KPX W uhungarumlaut -30
+KPX W umacron -30
+KPX W uogonek -30
+KPX W uring -30
+KPX W y -20
+KPX W yacute -20
+KPX W ydieresis -20
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -85
+KPX Y Oacute -85
+KPX Y Ocircumflex -85
+KPX Y Odieresis -85
+KPX Y Ograve -85
+KPX Y Ohungarumlaut -85
+KPX Y Omacron -85
+KPX Y Oslash -85
+KPX Y Otilde -85
+KPX Y a -140
+KPX Y aacute -140
+KPX Y abreve -70
+KPX Y acircumflex -140
+KPX Y adieresis -140
+KPX Y agrave -140
+KPX Y amacron -70
+KPX Y aogonek -140
+KPX Y aring -140
+KPX Y atilde -140
+KPX Y colon -60
+KPX Y comma -140
+KPX Y e -140
+KPX Y eacute -140
+KPX Y ecaron -140
+KPX Y ecircumflex -140
+KPX Y edieresis -140
+KPX Y edotaccent -140
+KPX Y egrave -140
+KPX Y emacron -70
+KPX Y eogonek -140
+KPX Y hyphen -140
+KPX Y i -20
+KPX Y iacute -20
+KPX Y iogonek -20
+KPX Y o -140
+KPX Y oacute -140
+KPX Y ocircumflex -140
+KPX Y odieresis -140
+KPX Y ograve -140
+KPX Y ohungarumlaut -140
+KPX Y omacron -140
+KPX Y oslash -140
+KPX Y otilde -140
+KPX Y period -140
+KPX Y semicolon -60
+KPX Y u -110
+KPX Y uacute -110
+KPX Y ucircumflex -110
+KPX Y udieresis -110
+KPX Y ugrave -110
+KPX Y uhungarumlaut -110
+KPX Y umacron -110
+KPX Y uogonek -110
+KPX Y uring -110
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -85
+KPX Yacute Oacute -85
+KPX Yacute Ocircumflex -85
+KPX Yacute Odieresis -85
+KPX Yacute Ograve -85
+KPX Yacute Ohungarumlaut -85
+KPX Yacute Omacron -85
+KPX Yacute Oslash -85
+KPX Yacute Otilde -85
+KPX Yacute a -140
+KPX Yacute aacute -140
+KPX Yacute abreve -70
+KPX Yacute acircumflex -140
+KPX Yacute adieresis -140
+KPX Yacute agrave -140
+KPX Yacute amacron -70
+KPX Yacute aogonek -140
+KPX Yacute aring -140
+KPX Yacute atilde -70
+KPX Yacute colon -60
+KPX Yacute comma -140
+KPX Yacute e -140
+KPX Yacute eacute -140
+KPX Yacute ecaron -140
+KPX Yacute ecircumflex -140
+KPX Yacute edieresis -140
+KPX Yacute edotaccent -140
+KPX Yacute egrave -140
+KPX Yacute emacron -70
+KPX Yacute eogonek -140
+KPX Yacute hyphen -140
+KPX Yacute i -20
+KPX Yacute iacute -20
+KPX Yacute iogonek -20
+KPX Yacute o -140
+KPX Yacute oacute -140
+KPX Yacute ocircumflex -140
+KPX Yacute odieresis -140
+KPX Yacute ograve -140
+KPX Yacute ohungarumlaut -140
+KPX Yacute omacron -70
+KPX Yacute oslash -140
+KPX Yacute otilde -140
+KPX Yacute period -140
+KPX Yacute semicolon -60
+KPX Yacute u -110
+KPX Yacute uacute -110
+KPX Yacute ucircumflex -110
+KPX Yacute udieresis -110
+KPX Yacute ugrave -110
+KPX Yacute uhungarumlaut -110
+KPX Yacute umacron -110
+KPX Yacute uogonek -110
+KPX Yacute uring -110
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -85
+KPX Ydieresis Oacute -85
+KPX Ydieresis Ocircumflex -85
+KPX Ydieresis Odieresis -85
+KPX Ydieresis Ograve -85
+KPX Ydieresis Ohungarumlaut -85
+KPX Ydieresis Omacron -85
+KPX Ydieresis Oslash -85
+KPX Ydieresis Otilde -85
+KPX Ydieresis a -140
+KPX Ydieresis aacute -140
+KPX Ydieresis abreve -70
+KPX Ydieresis acircumflex -140
+KPX Ydieresis adieresis -140
+KPX Ydieresis agrave -140
+KPX Ydieresis amacron -70
+KPX Ydieresis aogonek -140
+KPX Ydieresis aring -140
+KPX Ydieresis atilde -70
+KPX Ydieresis colon -60
+KPX Ydieresis comma -140
+KPX Ydieresis e -140
+KPX Ydieresis eacute -140
+KPX Ydieresis ecaron -140
+KPX Ydieresis ecircumflex -140
+KPX Ydieresis edieresis -140
+KPX Ydieresis edotaccent -140
+KPX Ydieresis egrave -140
+KPX Ydieresis emacron -70
+KPX Ydieresis eogonek -140
+KPX Ydieresis hyphen -140
+KPX Ydieresis i -20
+KPX Ydieresis iacute -20
+KPX Ydieresis iogonek -20
+KPX Ydieresis o -140
+KPX Ydieresis oacute -140
+KPX Ydieresis ocircumflex -140
+KPX Ydieresis odieresis -140
+KPX Ydieresis ograve -140
+KPX Ydieresis ohungarumlaut -140
+KPX Ydieresis omacron -140
+KPX Ydieresis oslash -140
+KPX Ydieresis otilde -140
+KPX Ydieresis period -140
+KPX Ydieresis semicolon -60
+KPX Ydieresis u -110
+KPX Ydieresis uacute -110
+KPX Ydieresis ucircumflex -110
+KPX Ydieresis udieresis -110
+KPX Ydieresis ugrave -110
+KPX Ydieresis uhungarumlaut -110
+KPX Ydieresis umacron -110
+KPX Ydieresis uogonek -110
+KPX Ydieresis uring -110
+KPX a v -20
+KPX a w -20
+KPX a y -30
+KPX a yacute -30
+KPX a ydieresis -30
+KPX aacute v -20
+KPX aacute w -20
+KPX aacute y -30
+KPX aacute yacute -30
+KPX aacute ydieresis -30
+KPX abreve v -20
+KPX abreve w -20
+KPX abreve y -30
+KPX abreve yacute -30
+KPX abreve ydieresis -30
+KPX acircumflex v -20
+KPX acircumflex w -20
+KPX acircumflex y -30
+KPX acircumflex yacute -30
+KPX acircumflex ydieresis -30
+KPX adieresis v -20
+KPX adieresis w -20
+KPX adieresis y -30
+KPX adieresis yacute -30
+KPX adieresis ydieresis -30
+KPX agrave v -20
+KPX agrave w -20
+KPX agrave y -30
+KPX agrave yacute -30
+KPX agrave ydieresis -30
+KPX amacron v -20
+KPX amacron w -20
+KPX amacron y -30
+KPX amacron yacute -30
+KPX amacron ydieresis -30
+KPX aogonek v -20
+KPX aogonek w -20
+KPX aogonek y -30
+KPX aogonek yacute -30
+KPX aogonek ydieresis -30
+KPX aring v -20
+KPX aring w -20
+KPX aring y -30
+KPX aring yacute -30
+KPX aring ydieresis -30
+KPX atilde v -20
+KPX atilde w -20
+KPX atilde y -30
+KPX atilde yacute -30
+KPX atilde ydieresis -30
+KPX b b -10
+KPX b comma -40
+KPX b l -20
+KPX b lacute -20
+KPX b lcommaaccent -20
+KPX b lslash -20
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -20
+KPX b y -20
+KPX b yacute -20
+KPX b ydieresis -20
+KPX c comma -15
+KPX c k -20
+KPX c kcommaaccent -20
+KPX cacute comma -15
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX ccaron comma -15
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccedilla comma -15
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX colon space -50
+KPX comma quotedblright -100
+KPX comma quoteright -100
+KPX e comma -15
+KPX e period -15
+KPX e v -30
+KPX e w -20
+KPX e x -30
+KPX e y -20
+KPX e yacute -20
+KPX e ydieresis -20
+KPX eacute comma -15
+KPX eacute period -15
+KPX eacute v -30
+KPX eacute w -20
+KPX eacute x -30
+KPX eacute y -20
+KPX eacute yacute -20
+KPX eacute ydieresis -20
+KPX ecaron comma -15
+KPX ecaron period -15
+KPX ecaron v -30
+KPX ecaron w -20
+KPX ecaron x -30
+KPX ecaron y -20
+KPX ecaron yacute -20
+KPX ecaron ydieresis -20
+KPX ecircumflex comma -15
+KPX ecircumflex period -15
+KPX ecircumflex v -30
+KPX ecircumflex w -20
+KPX ecircumflex x -30
+KPX ecircumflex y -20
+KPX ecircumflex yacute -20
+KPX ecircumflex ydieresis -20
+KPX edieresis comma -15
+KPX edieresis period -15
+KPX edieresis v -30
+KPX edieresis w -20
+KPX edieresis x -30
+KPX edieresis y -20
+KPX edieresis yacute -20
+KPX edieresis ydieresis -20
+KPX edotaccent comma -15
+KPX edotaccent period -15
+KPX edotaccent v -30
+KPX edotaccent w -20
+KPX edotaccent x -30
+KPX edotaccent y -20
+KPX edotaccent yacute -20
+KPX edotaccent ydieresis -20
+KPX egrave comma -15
+KPX egrave period -15
+KPX egrave v -30
+KPX egrave w -20
+KPX egrave x -30
+KPX egrave y -20
+KPX egrave yacute -20
+KPX egrave ydieresis -20
+KPX emacron comma -15
+KPX emacron period -15
+KPX emacron v -30
+KPX emacron w -20
+KPX emacron x -30
+KPX emacron y -20
+KPX emacron yacute -20
+KPX emacron ydieresis -20
+KPX eogonek comma -15
+KPX eogonek period -15
+KPX eogonek v -30
+KPX eogonek w -20
+KPX eogonek x -30
+KPX eogonek y -20
+KPX eogonek yacute -20
+KPX eogonek ydieresis -20
+KPX f a -30
+KPX f aacute -30
+KPX f abreve -30
+KPX f acircumflex -30
+KPX f adieresis -30
+KPX f agrave -30
+KPX f amacron -30
+KPX f aogonek -30
+KPX f aring -30
+KPX f atilde -30
+KPX f comma -30
+KPX f dotlessi -28
+KPX f e -30
+KPX f eacute -30
+KPX f ecaron -30
+KPX f ecircumflex -30
+KPX f edieresis -30
+KPX f edotaccent -30
+KPX f egrave -30
+KPX f emacron -30
+KPX f eogonek -30
+KPX f o -30
+KPX f oacute -30
+KPX f ocircumflex -30
+KPX f odieresis -30
+KPX f ograve -30
+KPX f ohungarumlaut -30
+KPX f omacron -30
+KPX f oslash -30
+KPX f otilde -30
+KPX f period -30
+KPX f quotedblright 60
+KPX f quoteright 50
+KPX g r -10
+KPX g racute -10
+KPX g rcaron -10
+KPX g rcommaaccent -10
+KPX gbreve r -10
+KPX gbreve racute -10
+KPX gbreve rcaron -10
+KPX gbreve rcommaaccent -10
+KPX gcommaaccent r -10
+KPX gcommaaccent racute -10
+KPX gcommaaccent rcaron -10
+KPX gcommaaccent rcommaaccent -10
+KPX h y -30
+KPX h yacute -30
+KPX h ydieresis -30
+KPX k e -20
+KPX k eacute -20
+KPX k ecaron -20
+KPX k ecircumflex -20
+KPX k edieresis -20
+KPX k edotaccent -20
+KPX k egrave -20
+KPX k emacron -20
+KPX k eogonek -20
+KPX k o -20
+KPX k oacute -20
+KPX k ocircumflex -20
+KPX k odieresis -20
+KPX k ograve -20
+KPX k ohungarumlaut -20
+KPX k omacron -20
+KPX k oslash -20
+KPX k otilde -20
+KPX kcommaaccent e -20
+KPX kcommaaccent eacute -20
+KPX kcommaaccent ecaron -20
+KPX kcommaaccent ecircumflex -20
+KPX kcommaaccent edieresis -20
+KPX kcommaaccent edotaccent -20
+KPX kcommaaccent egrave -20
+KPX kcommaaccent emacron -20
+KPX kcommaaccent eogonek -20
+KPX kcommaaccent o -20
+KPX kcommaaccent oacute -20
+KPX kcommaaccent ocircumflex -20
+KPX kcommaaccent odieresis -20
+KPX kcommaaccent ograve -20
+KPX kcommaaccent ohungarumlaut -20
+KPX kcommaaccent omacron -20
+KPX kcommaaccent oslash -20
+KPX kcommaaccent otilde -20
+KPX m u -10
+KPX m uacute -10
+KPX m ucircumflex -10
+KPX m udieresis -10
+KPX m ugrave -10
+KPX m uhungarumlaut -10
+KPX m umacron -10
+KPX m uogonek -10
+KPX m uring -10
+KPX m y -15
+KPX m yacute -15
+KPX m ydieresis -15
+KPX n u -10
+KPX n uacute -10
+KPX n ucircumflex -10
+KPX n udieresis -10
+KPX n ugrave -10
+KPX n uhungarumlaut -10
+KPX n umacron -10
+KPX n uogonek -10
+KPX n uring -10
+KPX n v -20
+KPX n y -15
+KPX n yacute -15
+KPX n ydieresis -15
+KPX nacute u -10
+KPX nacute uacute -10
+KPX nacute ucircumflex -10
+KPX nacute udieresis -10
+KPX nacute ugrave -10
+KPX nacute uhungarumlaut -10
+KPX nacute umacron -10
+KPX nacute uogonek -10
+KPX nacute uring -10
+KPX nacute v -20
+KPX nacute y -15
+KPX nacute yacute -15
+KPX nacute ydieresis -15
+KPX ncaron u -10
+KPX ncaron uacute -10
+KPX ncaron ucircumflex -10
+KPX ncaron udieresis -10
+KPX ncaron ugrave -10
+KPX ncaron uhungarumlaut -10
+KPX ncaron umacron -10
+KPX ncaron uogonek -10
+KPX ncaron uring -10
+KPX ncaron v -20
+KPX ncaron y -15
+KPX ncaron yacute -15
+KPX ncaron ydieresis -15
+KPX ncommaaccent u -10
+KPX ncommaaccent uacute -10
+KPX ncommaaccent ucircumflex -10
+KPX ncommaaccent udieresis -10
+KPX ncommaaccent ugrave -10
+KPX ncommaaccent uhungarumlaut -10
+KPX ncommaaccent umacron -10
+KPX ncommaaccent uogonek -10
+KPX ncommaaccent uring -10
+KPX ncommaaccent v -20
+KPX ncommaaccent y -15
+KPX ncommaaccent yacute -15
+KPX ncommaaccent ydieresis -15
+KPX ntilde u -10
+KPX ntilde uacute -10
+KPX ntilde ucircumflex -10
+KPX ntilde udieresis -10
+KPX ntilde ugrave -10
+KPX ntilde uhungarumlaut -10
+KPX ntilde umacron -10
+KPX ntilde uogonek -10
+KPX ntilde uring -10
+KPX ntilde v -20
+KPX ntilde y -15
+KPX ntilde yacute -15
+KPX ntilde ydieresis -15
+KPX o comma -40
+KPX o period -40
+KPX o v -15
+KPX o w -15
+KPX o x -30
+KPX o y -30
+KPX o yacute -30
+KPX o ydieresis -30
+KPX oacute comma -40
+KPX oacute period -40
+KPX oacute v -15
+KPX oacute w -15
+KPX oacute x -30
+KPX oacute y -30
+KPX oacute yacute -30
+KPX oacute ydieresis -30
+KPX ocircumflex comma -40
+KPX ocircumflex period -40
+KPX ocircumflex v -15
+KPX ocircumflex w -15
+KPX ocircumflex x -30
+KPX ocircumflex y -30
+KPX ocircumflex yacute -30
+KPX ocircumflex ydieresis -30
+KPX odieresis comma -40
+KPX odieresis period -40
+KPX odieresis v -15
+KPX odieresis w -15
+KPX odieresis x -30
+KPX odieresis y -30
+KPX odieresis yacute -30
+KPX odieresis ydieresis -30
+KPX ograve comma -40
+KPX ograve period -40
+KPX ograve v -15
+KPX ograve w -15
+KPX ograve x -30
+KPX ograve y -30
+KPX ograve yacute -30
+KPX ograve ydieresis -30
+KPX ohungarumlaut comma -40
+KPX ohungarumlaut period -40
+KPX ohungarumlaut v -15
+KPX ohungarumlaut w -15
+KPX ohungarumlaut x -30
+KPX ohungarumlaut y -30
+KPX ohungarumlaut yacute -30
+KPX ohungarumlaut ydieresis -30
+KPX omacron comma -40
+KPX omacron period -40
+KPX omacron v -15
+KPX omacron w -15
+KPX omacron x -30
+KPX omacron y -30
+KPX omacron yacute -30
+KPX omacron ydieresis -30
+KPX oslash a -55
+KPX oslash aacute -55
+KPX oslash abreve -55
+KPX oslash acircumflex -55
+KPX oslash adieresis -55
+KPX oslash agrave -55
+KPX oslash amacron -55
+KPX oslash aogonek -55
+KPX oslash aring -55
+KPX oslash atilde -55
+KPX oslash b -55
+KPX oslash c -55
+KPX oslash cacute -55
+KPX oslash ccaron -55
+KPX oslash ccedilla -55
+KPX oslash comma -95
+KPX oslash d -55
+KPX oslash dcroat -55
+KPX oslash e -55
+KPX oslash eacute -55
+KPX oslash ecaron -55
+KPX oslash ecircumflex -55
+KPX oslash edieresis -55
+KPX oslash edotaccent -55
+KPX oslash egrave -55
+KPX oslash emacron -55
+KPX oslash eogonek -55
+KPX oslash f -55
+KPX oslash g -55
+KPX oslash gbreve -55
+KPX oslash gcommaaccent -55
+KPX oslash h -55
+KPX oslash i -55
+KPX oslash iacute -55
+KPX oslash icircumflex -55
+KPX oslash idieresis -55
+KPX oslash igrave -55
+KPX oslash imacron -55
+KPX oslash iogonek -55
+KPX oslash j -55
+KPX oslash k -55
+KPX oslash kcommaaccent -55
+KPX oslash l -55
+KPX oslash lacute -55
+KPX oslash lcommaaccent -55
+KPX oslash lslash -55
+KPX oslash m -55
+KPX oslash n -55
+KPX oslash nacute -55
+KPX oslash ncaron -55
+KPX oslash ncommaaccent -55
+KPX oslash ntilde -55
+KPX oslash o -55
+KPX oslash oacute -55
+KPX oslash ocircumflex -55
+KPX oslash odieresis -55
+KPX oslash ograve -55
+KPX oslash ohungarumlaut -55
+KPX oslash omacron -55
+KPX oslash oslash -55
+KPX oslash otilde -55
+KPX oslash p -55
+KPX oslash period -95
+KPX oslash q -55
+KPX oslash r -55
+KPX oslash racute -55
+KPX oslash rcaron -55
+KPX oslash rcommaaccent -55
+KPX oslash s -55
+KPX oslash sacute -55
+KPX oslash scaron -55
+KPX oslash scedilla -55
+KPX oslash scommaaccent -55
+KPX oslash t -55
+KPX oslash tcommaaccent -55
+KPX oslash u -55
+KPX oslash uacute -55
+KPX oslash ucircumflex -55
+KPX oslash udieresis -55
+KPX oslash ugrave -55
+KPX oslash uhungarumlaut -55
+KPX oslash umacron -55
+KPX oslash uogonek -55
+KPX oslash uring -55
+KPX oslash v -70
+KPX oslash w -70
+KPX oslash x -85
+KPX oslash y -70
+KPX oslash yacute -70
+KPX oslash ydieresis -70
+KPX oslash z -55
+KPX oslash zacute -55
+KPX oslash zcaron -55
+KPX oslash zdotaccent -55
+KPX otilde comma -40
+KPX otilde period -40
+KPX otilde v -15
+KPX otilde w -15
+KPX otilde x -30
+KPX otilde y -30
+KPX otilde yacute -30
+KPX otilde ydieresis -30
+KPX p comma -35
+KPX p period -35
+KPX p y -30
+KPX p yacute -30
+KPX p ydieresis -30
+KPX period quotedblright -100
+KPX period quoteright -100
+KPX period space -60
+KPX quotedblright space -40
+KPX quoteleft quoteleft -57
+KPX quoteright d -50
+KPX quoteright dcroat -50
+KPX quoteright quoteright -57
+KPX quoteright r -50
+KPX quoteright racute -50
+KPX quoteright rcaron -50
+KPX quoteright rcommaaccent -50
+KPX quoteright s -50
+KPX quoteright sacute -50
+KPX quoteright scaron -50
+KPX quoteright scedilla -50
+KPX quoteright scommaaccent -50
+KPX quoteright space -70
+KPX r a -10
+KPX r aacute -10
+KPX r abreve -10
+KPX r acircumflex -10
+KPX r adieresis -10
+KPX r agrave -10
+KPX r amacron -10
+KPX r aogonek -10
+KPX r aring -10
+KPX r atilde -10
+KPX r colon 30
+KPX r comma -50
+KPX r i 15
+KPX r iacute 15
+KPX r icircumflex 15
+KPX r idieresis 15
+KPX r igrave 15
+KPX r imacron 15
+KPX r iogonek 15
+KPX r k 15
+KPX r kcommaaccent 15
+KPX r l 15
+KPX r lacute 15
+KPX r lcommaaccent 15
+KPX r lslash 15
+KPX r m 25
+KPX r n 25
+KPX r nacute 25
+KPX r ncaron 25
+KPX r ncommaaccent 25
+KPX r ntilde 25
+KPX r p 30
+KPX r period -50
+KPX r semicolon 30
+KPX r t 40
+KPX r tcommaaccent 40
+KPX r u 15
+KPX r uacute 15
+KPX r ucircumflex 15
+KPX r udieresis 15
+KPX r ugrave 15
+KPX r uhungarumlaut 15
+KPX r umacron 15
+KPX r uogonek 15
+KPX r uring 15
+KPX r v 30
+KPX r y 30
+KPX r yacute 30
+KPX r ydieresis 30
+KPX racute a -10
+KPX racute aacute -10
+KPX racute abreve -10
+KPX racute acircumflex -10
+KPX racute adieresis -10
+KPX racute agrave -10
+KPX racute amacron -10
+KPX racute aogonek -10
+KPX racute aring -10
+KPX racute atilde -10
+KPX racute colon 30
+KPX racute comma -50
+KPX racute i 15
+KPX racute iacute 15
+KPX racute icircumflex 15
+KPX racute idieresis 15
+KPX racute igrave 15
+KPX racute imacron 15
+KPX racute iogonek 15
+KPX racute k 15
+KPX racute kcommaaccent 15
+KPX racute l 15
+KPX racute lacute 15
+KPX racute lcommaaccent 15
+KPX racute lslash 15
+KPX racute m 25
+KPX racute n 25
+KPX racute nacute 25
+KPX racute ncaron 25
+KPX racute ncommaaccent 25
+KPX racute ntilde 25
+KPX racute p 30
+KPX racute period -50
+KPX racute semicolon 30
+KPX racute t 40
+KPX racute tcommaaccent 40
+KPX racute u 15
+KPX racute uacute 15
+KPX racute ucircumflex 15
+KPX racute udieresis 15
+KPX racute ugrave 15
+KPX racute uhungarumlaut 15
+KPX racute umacron 15
+KPX racute uogonek 15
+KPX racute uring 15
+KPX racute v 30
+KPX racute y 30
+KPX racute yacute 30
+KPX racute ydieresis 30
+KPX rcaron a -10
+KPX rcaron aacute -10
+KPX rcaron abreve -10
+KPX rcaron acircumflex -10
+KPX rcaron adieresis -10
+KPX rcaron agrave -10
+KPX rcaron amacron -10
+KPX rcaron aogonek -10
+KPX rcaron aring -10
+KPX rcaron atilde -10
+KPX rcaron colon 30
+KPX rcaron comma -50
+KPX rcaron i 15
+KPX rcaron iacute 15
+KPX rcaron icircumflex 15
+KPX rcaron idieresis 15
+KPX rcaron igrave 15
+KPX rcaron imacron 15
+KPX rcaron iogonek 15
+KPX rcaron k 15
+KPX rcaron kcommaaccent 15
+KPX rcaron l 15
+KPX rcaron lacute 15
+KPX rcaron lcommaaccent 15
+KPX rcaron lslash 15
+KPX rcaron m 25
+KPX rcaron n 25
+KPX rcaron nacute 25
+KPX rcaron ncaron 25
+KPX rcaron ncommaaccent 25
+KPX rcaron ntilde 25
+KPX rcaron p 30
+KPX rcaron period -50
+KPX rcaron semicolon 30
+KPX rcaron t 40
+KPX rcaron tcommaaccent 40
+KPX rcaron u 15
+KPX rcaron uacute 15
+KPX rcaron ucircumflex 15
+KPX rcaron udieresis 15
+KPX rcaron ugrave 15
+KPX rcaron uhungarumlaut 15
+KPX rcaron umacron 15
+KPX rcaron uogonek 15
+KPX rcaron uring 15
+KPX rcaron v 30
+KPX rcaron y 30
+KPX rcaron yacute 30
+KPX rcaron ydieresis 30
+KPX rcommaaccent a -10
+KPX rcommaaccent aacute -10
+KPX rcommaaccent abreve -10
+KPX rcommaaccent acircumflex -10
+KPX rcommaaccent adieresis -10
+KPX rcommaaccent agrave -10
+KPX rcommaaccent amacron -10
+KPX rcommaaccent aogonek -10
+KPX rcommaaccent aring -10
+KPX rcommaaccent atilde -10
+KPX rcommaaccent colon 30
+KPX rcommaaccent comma -50
+KPX rcommaaccent i 15
+KPX rcommaaccent iacute 15
+KPX rcommaaccent icircumflex 15
+KPX rcommaaccent idieresis 15
+KPX rcommaaccent igrave 15
+KPX rcommaaccent imacron 15
+KPX rcommaaccent iogonek 15
+KPX rcommaaccent k 15
+KPX rcommaaccent kcommaaccent 15
+KPX rcommaaccent l 15
+KPX rcommaaccent lacute 15
+KPX rcommaaccent lcommaaccent 15
+KPX rcommaaccent lslash 15
+KPX rcommaaccent m 25
+KPX rcommaaccent n 25
+KPX rcommaaccent nacute 25
+KPX rcommaaccent ncaron 25
+KPX rcommaaccent ncommaaccent 25
+KPX rcommaaccent ntilde 25
+KPX rcommaaccent p 30
+KPX rcommaaccent period -50
+KPX rcommaaccent semicolon 30
+KPX rcommaaccent t 40
+KPX rcommaaccent tcommaaccent 40
+KPX rcommaaccent u 15
+KPX rcommaaccent uacute 15
+KPX rcommaaccent ucircumflex 15
+KPX rcommaaccent udieresis 15
+KPX rcommaaccent ugrave 15
+KPX rcommaaccent uhungarumlaut 15
+KPX rcommaaccent umacron 15
+KPX rcommaaccent uogonek 15
+KPX rcommaaccent uring 15
+KPX rcommaaccent v 30
+KPX rcommaaccent y 30
+KPX rcommaaccent yacute 30
+KPX rcommaaccent ydieresis 30
+KPX s comma -15
+KPX s period -15
+KPX s w -30
+KPX sacute comma -15
+KPX sacute period -15
+KPX sacute w -30
+KPX scaron comma -15
+KPX scaron period -15
+KPX scaron w -30
+KPX scedilla comma -15
+KPX scedilla period -15
+KPX scedilla w -30
+KPX scommaaccent comma -15
+KPX scommaaccent period -15
+KPX scommaaccent w -30
+KPX semicolon space -50
+KPX space T -50
+KPX space Tcaron -50
+KPX space Tcommaaccent -50
+KPX space V -50
+KPX space W -40
+KPX space Y -90
+KPX space Yacute -90
+KPX space Ydieresis -90
+KPX space quotedblleft -30
+KPX space quoteleft -60
+KPX v a -25
+KPX v aacute -25
+KPX v abreve -25
+KPX v acircumflex -25
+KPX v adieresis -25
+KPX v agrave -25
+KPX v amacron -25
+KPX v aogonek -25
+KPX v aring -25
+KPX v atilde -25
+KPX v comma -80
+KPX v e -25
+KPX v eacute -25
+KPX v ecaron -25
+KPX v ecircumflex -25
+KPX v edieresis -25
+KPX v edotaccent -25
+KPX v egrave -25
+KPX v emacron -25
+KPX v eogonek -25
+KPX v o -25
+KPX v oacute -25
+KPX v ocircumflex -25
+KPX v odieresis -25
+KPX v ograve -25
+KPX v ohungarumlaut -25
+KPX v omacron -25
+KPX v oslash -25
+KPX v otilde -25
+KPX v period -80
+KPX w a -15
+KPX w aacute -15
+KPX w abreve -15
+KPX w acircumflex -15
+KPX w adieresis -15
+KPX w agrave -15
+KPX w amacron -15
+KPX w aogonek -15
+KPX w aring -15
+KPX w atilde -15
+KPX w comma -60
+KPX w e -10
+KPX w eacute -10
+KPX w ecaron -10
+KPX w ecircumflex -10
+KPX w edieresis -10
+KPX w edotaccent -10
+KPX w egrave -10
+KPX w emacron -10
+KPX w eogonek -10
+KPX w o -10
+KPX w oacute -10
+KPX w ocircumflex -10
+KPX w odieresis -10
+KPX w ograve -10
+KPX w ohungarumlaut -10
+KPX w omacron -10
+KPX w oslash -10
+KPX w otilde -10
+KPX w period -60
+KPX x e -30
+KPX x eacute -30
+KPX x ecaron -30
+KPX x ecircumflex -30
+KPX x edieresis -30
+KPX x edotaccent -30
+KPX x egrave -30
+KPX x emacron -30
+KPX x eogonek -30
+KPX y a -20
+KPX y aacute -20
+KPX y abreve -20
+KPX y acircumflex -20
+KPX y adieresis -20
+KPX y agrave -20
+KPX y amacron -20
+KPX y aogonek -20
+KPX y aring -20
+KPX y atilde -20
+KPX y comma -100
+KPX y e -20
+KPX y eacute -20
+KPX y ecaron -20
+KPX y ecircumflex -20
+KPX y edieresis -20
+KPX y edotaccent -20
+KPX y egrave -20
+KPX y emacron -20
+KPX y eogonek -20
+KPX y o -20
+KPX y oacute -20
+KPX y ocircumflex -20
+KPX y odieresis -20
+KPX y ograve -20
+KPX y ohungarumlaut -20
+KPX y omacron -20
+KPX y oslash -20
+KPX y otilde -20
+KPX y period -100
+KPX yacute a -20
+KPX yacute aacute -20
+KPX yacute abreve -20
+KPX yacute acircumflex -20
+KPX yacute adieresis -20
+KPX yacute agrave -20
+KPX yacute amacron -20
+KPX yacute aogonek -20
+KPX yacute aring -20
+KPX yacute atilde -20
+KPX yacute comma -100
+KPX yacute e -20
+KPX yacute eacute -20
+KPX yacute ecaron -20
+KPX yacute ecircumflex -20
+KPX yacute edieresis -20
+KPX yacute edotaccent -20
+KPX yacute egrave -20
+KPX yacute emacron -20
+KPX yacute eogonek -20
+KPX yacute o -20
+KPX yacute oacute -20
+KPX yacute ocircumflex -20
+KPX yacute odieresis -20
+KPX yacute ograve -20
+KPX yacute ohungarumlaut -20
+KPX yacute omacron -20
+KPX yacute oslash -20
+KPX yacute otilde -20
+KPX yacute period -100
+KPX ydieresis a -20
+KPX ydieresis aacute -20
+KPX ydieresis abreve -20
+KPX ydieresis acircumflex -20
+KPX ydieresis adieresis -20
+KPX ydieresis agrave -20
+KPX ydieresis amacron -20
+KPX ydieresis aogonek -20
+KPX ydieresis aring -20
+KPX ydieresis atilde -20
+KPX ydieresis comma -100
+KPX ydieresis e -20
+KPX ydieresis eacute -20
+KPX ydieresis ecaron -20
+KPX ydieresis ecircumflex -20
+KPX ydieresis edieresis -20
+KPX ydieresis edotaccent -20
+KPX ydieresis egrave -20
+KPX ydieresis emacron -20
+KPX ydieresis eogonek -20
+KPX ydieresis o -20
+KPX ydieresis oacute -20
+KPX ydieresis ocircumflex -20
+KPX ydieresis odieresis -20
+KPX ydieresis ograve -20
+KPX ydieresis ohungarumlaut -20
+KPX ydieresis omacron -20
+KPX ydieresis oslash -20
+KPX ydieresis otilde -20
+KPX ydieresis period -100
+KPX z e -15
+KPX z eacute -15
+KPX z ecaron -15
+KPX z ecircumflex -15
+KPX z edieresis -15
+KPX z edotaccent -15
+KPX z egrave -15
+KPX z emacron -15
+KPX z eogonek -15
+KPX z o -15
+KPX z oacute -15
+KPX z ocircumflex -15
+KPX z odieresis -15
+KPX z ograve -15
+KPX z ohungarumlaut -15
+KPX z omacron -15
+KPX z oslash -15
+KPX z otilde -15
+KPX zacute e -15
+KPX zacute eacute -15
+KPX zacute ecaron -15
+KPX zacute ecircumflex -15
+KPX zacute edieresis -15
+KPX zacute edotaccent -15
+KPX zacute egrave -15
+KPX zacute emacron -15
+KPX zacute eogonek -15
+KPX zacute o -15
+KPX zacute oacute -15
+KPX zacute ocircumflex -15
+KPX zacute odieresis -15
+KPX zacute ograve -15
+KPX zacute ohungarumlaut -15
+KPX zacute omacron -15
+KPX zacute oslash -15
+KPX zacute otilde -15
+KPX zcaron e -15
+KPX zcaron eacute -15
+KPX zcaron ecaron -15
+KPX zcaron ecircumflex -15
+KPX zcaron edieresis -15
+KPX zcaron edotaccent -15
+KPX zcaron egrave -15
+KPX zcaron emacron -15
+KPX zcaron eogonek -15
+KPX zcaron o -15
+KPX zcaron oacute -15
+KPX zcaron ocircumflex -15
+KPX zcaron odieresis -15
+KPX zcaron ograve -15
+KPX zcaron ohungarumlaut -15
+KPX zcaron omacron -15
+KPX zcaron oslash -15
+KPX zcaron otilde -15
+KPX zdotaccent e -15
+KPX zdotaccent eacute -15
+KPX zdotaccent ecaron -15
+KPX zdotaccent ecircumflex -15
+KPX zdotaccent edieresis -15
+KPX zdotaccent edotaccent -15
+KPX zdotaccent egrave -15
+KPX zdotaccent emacron -15
+KPX zdotaccent eogonek -15
+KPX zdotaccent o -15
+KPX zdotaccent oacute -15
+KPX zdotaccent ocircumflex -15
+KPX zdotaccent odieresis -15
+KPX zdotaccent ograve -15
+KPX zdotaccent ohungarumlaut -15
+KPX zdotaccent omacron -15
+KPX zdotaccent oslash -15
+KPX zdotaccent otilde -15
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica.afm
new file mode 100644
index 0000000000000000000000000000000000000000..bd32af54dec5563db4033ed2c8bb4a26f1eaeecf
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica.afm
@@ -0,0 +1,3051 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:38:23 1997
+Comment UniqueID 43054
+Comment VMusage 37069 48094
+FontName Helvetica
+FullName Helvetica
+FamilyName Helvetica
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -166 -225 1000 931
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 718
+XHeight 523
+Ascender 718
+Descender -207
+StdHW 76
+StdVW 88
+StartCharMetrics 315
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 90 0 187 718 ;
+C 34 ; WX 355 ; N quotedbl ; B 70 463 285 718 ;
+C 35 ; WX 556 ; N numbersign ; B 28 0 529 688 ;
+C 36 ; WX 556 ; N dollar ; B 32 -115 520 775 ;
+C 37 ; WX 889 ; N percent ; B 39 -19 850 703 ;
+C 38 ; WX 667 ; N ampersand ; B 44 -15 645 718 ;
+C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
+C 40 ; WX 333 ; N parenleft ; B 68 -207 299 733 ;
+C 41 ; WX 333 ; N parenright ; B 34 -207 265 733 ;
+C 42 ; WX 389 ; N asterisk ; B 39 431 349 718 ;
+C 43 ; WX 584 ; N plus ; B 39 0 545 505 ;
+C 44 ; WX 278 ; N comma ; B 87 -147 191 106 ;
+C 45 ; WX 333 ; N hyphen ; B 44 232 289 322 ;
+C 46 ; WX 278 ; N period ; B 87 0 191 106 ;
+C 47 ; WX 278 ; N slash ; B -17 -19 295 737 ;
+C 48 ; WX 556 ; N zero ; B 37 -19 519 703 ;
+C 49 ; WX 556 ; N one ; B 101 0 359 703 ;
+C 50 ; WX 556 ; N two ; B 26 0 507 703 ;
+C 51 ; WX 556 ; N three ; B 34 -19 522 703 ;
+C 52 ; WX 556 ; N four ; B 25 0 523 703 ;
+C 53 ; WX 556 ; N five ; B 32 -19 514 688 ;
+C 54 ; WX 556 ; N six ; B 38 -19 518 703 ;
+C 55 ; WX 556 ; N seven ; B 37 0 523 688 ;
+C 56 ; WX 556 ; N eight ; B 38 -19 517 703 ;
+C 57 ; WX 556 ; N nine ; B 42 -19 514 703 ;
+C 58 ; WX 278 ; N colon ; B 87 0 191 516 ;
+C 59 ; WX 278 ; N semicolon ; B 87 -147 191 516 ;
+C 60 ; WX 584 ; N less ; B 48 11 536 495 ;
+C 61 ; WX 584 ; N equal ; B 39 115 545 390 ;
+C 62 ; WX 584 ; N greater ; B 48 11 536 495 ;
+C 63 ; WX 556 ; N question ; B 56 0 492 727 ;
+C 64 ; WX 1015 ; N at ; B 147 -19 868 737 ;
+C 65 ; WX 667 ; N A ; B 14 0 654 718 ;
+C 66 ; WX 667 ; N B ; B 74 0 627 718 ;
+C 67 ; WX 722 ; N C ; B 44 -19 681 737 ;
+C 68 ; WX 722 ; N D ; B 81 0 674 718 ;
+C 69 ; WX 667 ; N E ; B 86 0 616 718 ;
+C 70 ; WX 611 ; N F ; B 86 0 583 718 ;
+C 71 ; WX 778 ; N G ; B 48 -19 704 737 ;
+C 72 ; WX 722 ; N H ; B 77 0 646 718 ;
+C 73 ; WX 278 ; N I ; B 91 0 188 718 ;
+C 74 ; WX 500 ; N J ; B 17 -19 428 718 ;
+C 75 ; WX 667 ; N K ; B 76 0 663 718 ;
+C 76 ; WX 556 ; N L ; B 76 0 537 718 ;
+C 77 ; WX 833 ; N M ; B 73 0 761 718 ;
+C 78 ; WX 722 ; N N ; B 76 0 646 718 ;
+C 79 ; WX 778 ; N O ; B 39 -19 739 737 ;
+C 80 ; WX 667 ; N P ; B 86 0 622 718 ;
+C 81 ; WX 778 ; N Q ; B 39 -56 739 737 ;
+C 82 ; WX 722 ; N R ; B 88 0 684 718 ;
+C 83 ; WX 667 ; N S ; B 49 -19 620 737 ;
+C 84 ; WX 611 ; N T ; B 14 0 597 718 ;
+C 85 ; WX 722 ; N U ; B 79 -19 644 718 ;
+C 86 ; WX 667 ; N V ; B 20 0 647 718 ;
+C 87 ; WX 944 ; N W ; B 16 0 928 718 ;
+C 88 ; WX 667 ; N X ; B 19 0 648 718 ;
+C 89 ; WX 667 ; N Y ; B 14 0 653 718 ;
+C 90 ; WX 611 ; N Z ; B 23 0 588 718 ;
+C 91 ; WX 278 ; N bracketleft ; B 63 -196 250 722 ;
+C 92 ; WX 278 ; N backslash ; B -17 -19 295 737 ;
+C 93 ; WX 278 ; N bracketright ; B 28 -196 215 722 ;
+C 94 ; WX 469 ; N asciicircum ; B -14 264 483 688 ;
+C 95 ; WX 556 ; N underscore ; B 0 -125 556 -75 ;
+C 96 ; WX 222 ; N quoteleft ; B 65 470 169 725 ;
+C 97 ; WX 556 ; N a ; B 36 -15 530 538 ;
+C 98 ; WX 556 ; N b ; B 58 -15 517 718 ;
+C 99 ; WX 500 ; N c ; B 30 -15 477 538 ;
+C 100 ; WX 556 ; N d ; B 35 -15 499 718 ;
+C 101 ; WX 556 ; N e ; B 40 -15 516 538 ;
+C 102 ; WX 278 ; N f ; B 14 0 262 728 ; L i fi ; L l fl ;
+C 103 ; WX 556 ; N g ; B 40 -220 499 538 ;
+C 104 ; WX 556 ; N h ; B 65 0 491 718 ;
+C 105 ; WX 222 ; N i ; B 67 0 155 718 ;
+C 106 ; WX 222 ; N j ; B -16 -210 155 718 ;
+C 107 ; WX 500 ; N k ; B 67 0 501 718 ;
+C 108 ; WX 222 ; N l ; B 67 0 155 718 ;
+C 109 ; WX 833 ; N m ; B 65 0 769 538 ;
+C 110 ; WX 556 ; N n ; B 65 0 491 538 ;
+C 111 ; WX 556 ; N o ; B 35 -14 521 538 ;
+C 112 ; WX 556 ; N p ; B 58 -207 517 538 ;
+C 113 ; WX 556 ; N q ; B 35 -207 494 538 ;
+C 114 ; WX 333 ; N r ; B 77 0 332 538 ;
+C 115 ; WX 500 ; N s ; B 32 -15 464 538 ;
+C 116 ; WX 278 ; N t ; B 14 -7 257 669 ;
+C 117 ; WX 556 ; N u ; B 68 -15 489 523 ;
+C 118 ; WX 500 ; N v ; B 8 0 492 523 ;
+C 119 ; WX 722 ; N w ; B 14 0 709 523 ;
+C 120 ; WX 500 ; N x ; B 11 0 490 523 ;
+C 121 ; WX 500 ; N y ; B 11 -214 489 523 ;
+C 122 ; WX 500 ; N z ; B 31 0 469 523 ;
+C 123 ; WX 334 ; N braceleft ; B 42 -196 292 722 ;
+C 124 ; WX 260 ; N bar ; B 94 -225 167 775 ;
+C 125 ; WX 334 ; N braceright ; B 42 -196 292 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 61 180 523 326 ;
+C 161 ; WX 333 ; N exclamdown ; B 118 -195 215 523 ;
+C 162 ; WX 556 ; N cent ; B 51 -115 513 623 ;
+C 163 ; WX 556 ; N sterling ; B 33 -16 539 718 ;
+C 164 ; WX 167 ; N fraction ; B -166 -19 333 703 ;
+C 165 ; WX 556 ; N yen ; B 3 0 553 688 ;
+C 166 ; WX 556 ; N florin ; B -11 -207 501 737 ;
+C 167 ; WX 556 ; N section ; B 43 -191 512 737 ;
+C 168 ; WX 556 ; N currency ; B 28 99 528 603 ;
+C 169 ; WX 191 ; N quotesingle ; B 59 463 132 718 ;
+C 170 ; WX 333 ; N quotedblleft ; B 38 470 307 725 ;
+C 171 ; WX 556 ; N guillemotleft ; B 97 108 459 446 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 88 108 245 446 ;
+C 173 ; WX 333 ; N guilsinglright ; B 88 108 245 446 ;
+C 174 ; WX 500 ; N fi ; B 14 0 434 728 ;
+C 175 ; WX 500 ; N fl ; B 14 0 432 728 ;
+C 177 ; WX 556 ; N endash ; B 0 240 556 313 ;
+C 178 ; WX 556 ; N dagger ; B 43 -159 514 718 ;
+C 179 ; WX 556 ; N daggerdbl ; B 43 -159 514 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 77 190 202 315 ;
+C 182 ; WX 537 ; N paragraph ; B 18 -173 497 718 ;
+C 183 ; WX 350 ; N bullet ; B 18 202 333 517 ;
+C 184 ; WX 222 ; N quotesinglbase ; B 53 -149 157 106 ;
+C 185 ; WX 333 ; N quotedblbase ; B 26 -149 295 106 ;
+C 186 ; WX 333 ; N quotedblright ; B 26 463 295 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 97 108 459 446 ;
+C 188 ; WX 1000 ; N ellipsis ; B 115 0 885 106 ;
+C 189 ; WX 1000 ; N perthousand ; B 7 -19 994 703 ;
+C 191 ; WX 611 ; N questiondown ; B 91 -201 527 525 ;
+C 193 ; WX 333 ; N grave ; B 14 593 211 734 ;
+C 194 ; WX 333 ; N acute ; B 122 593 319 734 ;
+C 195 ; WX 333 ; N circumflex ; B 21 593 312 734 ;
+C 196 ; WX 333 ; N tilde ; B -4 606 337 722 ;
+C 197 ; WX 333 ; N macron ; B 10 627 323 684 ;
+C 198 ; WX 333 ; N breve ; B 13 595 321 731 ;
+C 199 ; WX 333 ; N dotaccent ; B 121 604 212 706 ;
+C 200 ; WX 333 ; N dieresis ; B 40 604 293 706 ;
+C 202 ; WX 333 ; N ring ; B 75 572 259 756 ;
+C 203 ; WX 333 ; N cedilla ; B 45 -225 259 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 31 593 409 734 ;
+C 206 ; WX 333 ; N ogonek ; B 73 -225 287 0 ;
+C 207 ; WX 333 ; N caron ; B 21 593 312 734 ;
+C 208 ; WX 1000 ; N emdash ; B 0 240 1000 313 ;
+C 225 ; WX 1000 ; N AE ; B 8 0 951 718 ;
+C 227 ; WX 370 ; N ordfeminine ; B 24 405 346 737 ;
+C 232 ; WX 556 ; N Lslash ; B -20 0 537 718 ;
+C 233 ; WX 778 ; N Oslash ; B 39 -19 740 737 ;
+C 234 ; WX 1000 ; N OE ; B 36 -19 965 737 ;
+C 235 ; WX 365 ; N ordmasculine ; B 25 405 341 737 ;
+C 241 ; WX 889 ; N ae ; B 36 -15 847 538 ;
+C 245 ; WX 278 ; N dotlessi ; B 95 0 183 523 ;
+C 248 ; WX 222 ; N lslash ; B -20 0 242 718 ;
+C 249 ; WX 611 ; N oslash ; B 28 -22 537 545 ;
+C 250 ; WX 944 ; N oe ; B 35 -15 902 538 ;
+C 251 ; WX 611 ; N germandbls ; B 67 -15 571 728 ;
+C -1 ; WX 278 ; N Idieresis ; B 13 0 266 901 ;
+C -1 ; WX 556 ; N eacute ; B 40 -15 516 734 ;
+C -1 ; WX 556 ; N abreve ; B 36 -15 530 731 ;
+C -1 ; WX 556 ; N uhungarumlaut ; B 68 -15 521 734 ;
+C -1 ; WX 556 ; N ecaron ; B 40 -15 516 734 ;
+C -1 ; WX 667 ; N Ydieresis ; B 14 0 653 901 ;
+C -1 ; WX 584 ; N divide ; B 39 -19 545 524 ;
+C -1 ; WX 667 ; N Yacute ; B 14 0 653 929 ;
+C -1 ; WX 667 ; N Acircumflex ; B 14 0 654 929 ;
+C -1 ; WX 556 ; N aacute ; B 36 -15 530 734 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 79 -19 644 929 ;
+C -1 ; WX 500 ; N yacute ; B 11 -214 489 734 ;
+C -1 ; WX 500 ; N scommaaccent ; B 32 -225 464 538 ;
+C -1 ; WX 556 ; N ecircumflex ; B 40 -15 516 734 ;
+C -1 ; WX 722 ; N Uring ; B 79 -19 644 931 ;
+C -1 ; WX 722 ; N Udieresis ; B 79 -19 644 901 ;
+C -1 ; WX 556 ; N aogonek ; B 36 -220 547 538 ;
+C -1 ; WX 722 ; N Uacute ; B 79 -19 644 929 ;
+C -1 ; WX 556 ; N uogonek ; B 68 -225 519 523 ;
+C -1 ; WX 667 ; N Edieresis ; B 86 0 616 901 ;
+C -1 ; WX 722 ; N Dcroat ; B 0 0 674 718 ;
+C -1 ; WX 250 ; N commaaccent ; B 87 -225 181 -40 ;
+C -1 ; WX 737 ; N copyright ; B -14 -19 752 737 ;
+C -1 ; WX 667 ; N Emacron ; B 86 0 616 879 ;
+C -1 ; WX 500 ; N ccaron ; B 30 -15 477 734 ;
+C -1 ; WX 556 ; N aring ; B 36 -15 530 756 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 76 -225 646 718 ;
+C -1 ; WX 222 ; N lacute ; B 67 0 264 929 ;
+C -1 ; WX 556 ; N agrave ; B 36 -15 530 734 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 14 -225 597 718 ;
+C -1 ; WX 722 ; N Cacute ; B 44 -19 681 929 ;
+C -1 ; WX 556 ; N atilde ; B 36 -15 530 722 ;
+C -1 ; WX 667 ; N Edotaccent ; B 86 0 616 901 ;
+C -1 ; WX 500 ; N scaron ; B 32 -15 464 734 ;
+C -1 ; WX 500 ; N scedilla ; B 32 -225 464 538 ;
+C -1 ; WX 278 ; N iacute ; B 95 0 292 734 ;
+C -1 ; WX 471 ; N lozenge ; B 10 0 462 728 ;
+C -1 ; WX 722 ; N Rcaron ; B 88 0 684 929 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 48 -225 704 737 ;
+C -1 ; WX 556 ; N ucircumflex ; B 68 -15 489 734 ;
+C -1 ; WX 556 ; N acircumflex ; B 36 -15 530 734 ;
+C -1 ; WX 667 ; N Amacron ; B 14 0 654 879 ;
+C -1 ; WX 333 ; N rcaron ; B 61 0 352 734 ;
+C -1 ; WX 500 ; N ccedilla ; B 30 -225 477 538 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 23 0 588 901 ;
+C -1 ; WX 667 ; N Thorn ; B 86 0 622 718 ;
+C -1 ; WX 778 ; N Omacron ; B 39 -19 739 879 ;
+C -1 ; WX 722 ; N Racute ; B 88 0 684 929 ;
+C -1 ; WX 667 ; N Sacute ; B 49 -19 620 929 ;
+C -1 ; WX 643 ; N dcaron ; B 35 -15 655 718 ;
+C -1 ; WX 722 ; N Umacron ; B 79 -19 644 879 ;
+C -1 ; WX 556 ; N uring ; B 68 -15 489 756 ;
+C -1 ; WX 333 ; N threesuperior ; B 5 270 325 703 ;
+C -1 ; WX 778 ; N Ograve ; B 39 -19 739 929 ;
+C -1 ; WX 667 ; N Agrave ; B 14 0 654 929 ;
+C -1 ; WX 667 ; N Abreve ; B 14 0 654 926 ;
+C -1 ; WX 584 ; N multiply ; B 39 0 545 506 ;
+C -1 ; WX 556 ; N uacute ; B 68 -15 489 734 ;
+C -1 ; WX 611 ; N Tcaron ; B 14 0 597 929 ;
+C -1 ; WX 476 ; N partialdiff ; B 13 -38 463 714 ;
+C -1 ; WX 500 ; N ydieresis ; B 11 -214 489 706 ;
+C -1 ; WX 722 ; N Nacute ; B 76 0 646 929 ;
+C -1 ; WX 278 ; N icircumflex ; B -6 0 285 734 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 86 0 616 929 ;
+C -1 ; WX 556 ; N adieresis ; B 36 -15 530 706 ;
+C -1 ; WX 556 ; N edieresis ; B 40 -15 516 706 ;
+C -1 ; WX 500 ; N cacute ; B 30 -15 477 734 ;
+C -1 ; WX 556 ; N nacute ; B 65 0 491 734 ;
+C -1 ; WX 556 ; N umacron ; B 68 -15 489 684 ;
+C -1 ; WX 722 ; N Ncaron ; B 76 0 646 929 ;
+C -1 ; WX 278 ; N Iacute ; B 91 0 292 929 ;
+C -1 ; WX 584 ; N plusminus ; B 39 0 545 506 ;
+C -1 ; WX 260 ; N brokenbar ; B 94 -150 167 700 ;
+C -1 ; WX 737 ; N registered ; B -14 -19 752 737 ;
+C -1 ; WX 778 ; N Gbreve ; B 48 -19 704 926 ;
+C -1 ; WX 278 ; N Idotaccent ; B 91 0 188 901 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 586 706 ;
+C -1 ; WX 667 ; N Egrave ; B 86 0 616 929 ;
+C -1 ; WX 333 ; N racute ; B 77 0 332 734 ;
+C -1 ; WX 556 ; N omacron ; B 35 -14 521 684 ;
+C -1 ; WX 611 ; N Zacute ; B 23 0 588 929 ;
+C -1 ; WX 611 ; N Zcaron ; B 23 0 588 929 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 674 ;
+C -1 ; WX 722 ; N Eth ; B 0 0 674 718 ;
+C -1 ; WX 722 ; N Ccedilla ; B 44 -225 681 737 ;
+C -1 ; WX 222 ; N lcommaaccent ; B 67 -225 167 718 ;
+C -1 ; WX 317 ; N tcaron ; B 14 -7 329 808 ;
+C -1 ; WX 556 ; N eogonek ; B 40 -225 516 538 ;
+C -1 ; WX 722 ; N Uogonek ; B 79 -225 644 718 ;
+C -1 ; WX 667 ; N Aacute ; B 14 0 654 929 ;
+C -1 ; WX 667 ; N Adieresis ; B 14 0 654 901 ;
+C -1 ; WX 556 ; N egrave ; B 40 -15 516 734 ;
+C -1 ; WX 500 ; N zacute ; B 31 0 469 734 ;
+C -1 ; WX 222 ; N iogonek ; B -31 -225 183 718 ;
+C -1 ; WX 778 ; N Oacute ; B 39 -19 739 929 ;
+C -1 ; WX 556 ; N oacute ; B 35 -14 521 734 ;
+C -1 ; WX 556 ; N amacron ; B 36 -15 530 684 ;
+C -1 ; WX 500 ; N sacute ; B 32 -15 464 734 ;
+C -1 ; WX 278 ; N idieresis ; B 13 0 266 706 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 39 -19 739 929 ;
+C -1 ; WX 722 ; N Ugrave ; B 79 -19 644 929 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C -1 ; WX 556 ; N thorn ; B 58 -207 517 718 ;
+C -1 ; WX 333 ; N twosuperior ; B 4 281 323 703 ;
+C -1 ; WX 778 ; N Odieresis ; B 39 -19 739 901 ;
+C -1 ; WX 556 ; N mu ; B 68 -207 489 523 ;
+C -1 ; WX 278 ; N igrave ; B -13 0 184 734 ;
+C -1 ; WX 556 ; N ohungarumlaut ; B 35 -14 521 734 ;
+C -1 ; WX 667 ; N Eogonek ; B 86 -220 633 718 ;
+C -1 ; WX 556 ; N dcroat ; B 35 -15 550 718 ;
+C -1 ; WX 834 ; N threequarters ; B 45 -19 810 703 ;
+C -1 ; WX 667 ; N Scedilla ; B 49 -225 620 737 ;
+C -1 ; WX 299 ; N lcaron ; B 67 0 311 718 ;
+C -1 ; WX 667 ; N Kcommaaccent ; B 76 -225 663 718 ;
+C -1 ; WX 556 ; N Lacute ; B 76 0 537 929 ;
+C -1 ; WX 1000 ; N trademark ; B 46 306 903 718 ;
+C -1 ; WX 556 ; N edotaccent ; B 40 -15 516 706 ;
+C -1 ; WX 278 ; N Igrave ; B -13 0 188 929 ;
+C -1 ; WX 278 ; N Imacron ; B -17 0 296 879 ;
+C -1 ; WX 556 ; N Lcaron ; B 76 0 537 718 ;
+C -1 ; WX 834 ; N onehalf ; B 43 -19 773 703 ;
+C -1 ; WX 549 ; N lessequal ; B 26 0 523 674 ;
+C -1 ; WX 556 ; N ocircumflex ; B 35 -14 521 734 ;
+C -1 ; WX 556 ; N ntilde ; B 65 0 491 722 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 79 -19 644 929 ;
+C -1 ; WX 667 ; N Eacute ; B 86 0 616 929 ;
+C -1 ; WX 556 ; N emacron ; B 40 -15 516 684 ;
+C -1 ; WX 556 ; N gbreve ; B 40 -220 499 731 ;
+C -1 ; WX 834 ; N onequarter ; B 73 -19 756 703 ;
+C -1 ; WX 667 ; N Scaron ; B 49 -19 620 929 ;
+C -1 ; WX 667 ; N Scommaaccent ; B 49 -225 620 737 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 39 -19 739 929 ;
+C -1 ; WX 400 ; N degree ; B 54 411 346 703 ;
+C -1 ; WX 556 ; N ograve ; B 35 -14 521 734 ;
+C -1 ; WX 722 ; N Ccaron ; B 44 -19 681 929 ;
+C -1 ; WX 556 ; N ugrave ; B 68 -15 489 734 ;
+C -1 ; WX 453 ; N radical ; B -4 -80 458 762 ;
+C -1 ; WX 722 ; N Dcaron ; B 81 0 674 929 ;
+C -1 ; WX 333 ; N rcommaaccent ; B 77 -225 332 538 ;
+C -1 ; WX 722 ; N Ntilde ; B 76 0 646 917 ;
+C -1 ; WX 556 ; N otilde ; B 35 -14 521 722 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 88 -225 684 718 ;
+C -1 ; WX 556 ; N Lcommaaccent ; B 76 -225 537 718 ;
+C -1 ; WX 667 ; N Atilde ; B 14 0 654 917 ;
+C -1 ; WX 667 ; N Aogonek ; B 14 -225 654 718 ;
+C -1 ; WX 667 ; N Aring ; B 14 0 654 931 ;
+C -1 ; WX 778 ; N Otilde ; B 39 -19 739 917 ;
+C -1 ; WX 500 ; N zdotaccent ; B 31 0 469 706 ;
+C -1 ; WX 667 ; N Ecaron ; B 86 0 616 929 ;
+C -1 ; WX 278 ; N Iogonek ; B -3 -225 211 718 ;
+C -1 ; WX 500 ; N kcommaaccent ; B 67 -225 501 718 ;
+C -1 ; WX 584 ; N minus ; B 39 216 545 289 ;
+C -1 ; WX 278 ; N Icircumflex ; B -6 0 285 929 ;
+C -1 ; WX 556 ; N ncaron ; B 65 0 491 734 ;
+C -1 ; WX 278 ; N tcommaaccent ; B 14 -225 257 669 ;
+C -1 ; WX 584 ; N logicalnot ; B 39 108 545 390 ;
+C -1 ; WX 556 ; N odieresis ; B 35 -14 521 706 ;
+C -1 ; WX 556 ; N udieresis ; B 68 -15 489 706 ;
+C -1 ; WX 549 ; N notequal ; B 12 -35 537 551 ;
+C -1 ; WX 556 ; N gcommaaccent ; B 40 -220 499 822 ;
+C -1 ; WX 556 ; N eth ; B 35 -15 522 737 ;
+C -1 ; WX 500 ; N zcaron ; B 31 0 469 734 ;
+C -1 ; WX 556 ; N ncommaaccent ; B 65 -225 491 538 ;
+C -1 ; WX 333 ; N onesuperior ; B 43 281 222 703 ;
+C -1 ; WX 278 ; N imacron ; B 5 0 272 684 ;
+C -1 ; WX 556 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2705
+KPX A C -30
+KPX A Cacute -30
+KPX A Ccaron -30
+KPX A Ccedilla -30
+KPX A G -30
+KPX A Gbreve -30
+KPX A Gcommaaccent -30
+KPX A O -30
+KPX A Oacute -30
+KPX A Ocircumflex -30
+KPX A Odieresis -30
+KPX A Ograve -30
+KPX A Ohungarumlaut -30
+KPX A Omacron -30
+KPX A Oslash -30
+KPX A Otilde -30
+KPX A Q -30
+KPX A T -120
+KPX A Tcaron -120
+KPX A Tcommaaccent -120
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -70
+KPX A W -50
+KPX A Y -100
+KPX A Yacute -100
+KPX A Ydieresis -100
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -40
+KPX A w -40
+KPX A y -40
+KPX A yacute -40
+KPX A ydieresis -40
+KPX Aacute C -30
+KPX Aacute Cacute -30
+KPX Aacute Ccaron -30
+KPX Aacute Ccedilla -30
+KPX Aacute G -30
+KPX Aacute Gbreve -30
+KPX Aacute Gcommaaccent -30
+KPX Aacute O -30
+KPX Aacute Oacute -30
+KPX Aacute Ocircumflex -30
+KPX Aacute Odieresis -30
+KPX Aacute Ograve -30
+KPX Aacute Ohungarumlaut -30
+KPX Aacute Omacron -30
+KPX Aacute Oslash -30
+KPX Aacute Otilde -30
+KPX Aacute Q -30
+KPX Aacute T -120
+KPX Aacute Tcaron -120
+KPX Aacute Tcommaaccent -120
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -70
+KPX Aacute W -50
+KPX Aacute Y -100
+KPX Aacute Yacute -100
+KPX Aacute Ydieresis -100
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -40
+KPX Aacute w -40
+KPX Aacute y -40
+KPX Aacute yacute -40
+KPX Aacute ydieresis -40
+KPX Abreve C -30
+KPX Abreve Cacute -30
+KPX Abreve Ccaron -30
+KPX Abreve Ccedilla -30
+KPX Abreve G -30
+KPX Abreve Gbreve -30
+KPX Abreve Gcommaaccent -30
+KPX Abreve O -30
+KPX Abreve Oacute -30
+KPX Abreve Ocircumflex -30
+KPX Abreve Odieresis -30
+KPX Abreve Ograve -30
+KPX Abreve Ohungarumlaut -30
+KPX Abreve Omacron -30
+KPX Abreve Oslash -30
+KPX Abreve Otilde -30
+KPX Abreve Q -30
+KPX Abreve T -120
+KPX Abreve Tcaron -120
+KPX Abreve Tcommaaccent -120
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -70
+KPX Abreve W -50
+KPX Abreve Y -100
+KPX Abreve Yacute -100
+KPX Abreve Ydieresis -100
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -40
+KPX Abreve w -40
+KPX Abreve y -40
+KPX Abreve yacute -40
+KPX Abreve ydieresis -40
+KPX Acircumflex C -30
+KPX Acircumflex Cacute -30
+KPX Acircumflex Ccaron -30
+KPX Acircumflex Ccedilla -30
+KPX Acircumflex G -30
+KPX Acircumflex Gbreve -30
+KPX Acircumflex Gcommaaccent -30
+KPX Acircumflex O -30
+KPX Acircumflex Oacute -30
+KPX Acircumflex Ocircumflex -30
+KPX Acircumflex Odieresis -30
+KPX Acircumflex Ograve -30
+KPX Acircumflex Ohungarumlaut -30
+KPX Acircumflex Omacron -30
+KPX Acircumflex Oslash -30
+KPX Acircumflex Otilde -30
+KPX Acircumflex Q -30
+KPX Acircumflex T -120
+KPX Acircumflex Tcaron -120
+KPX Acircumflex Tcommaaccent -120
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -70
+KPX Acircumflex W -50
+KPX Acircumflex Y -100
+KPX Acircumflex Yacute -100
+KPX Acircumflex Ydieresis -100
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -40
+KPX Acircumflex w -40
+KPX Acircumflex y -40
+KPX Acircumflex yacute -40
+KPX Acircumflex ydieresis -40
+KPX Adieresis C -30
+KPX Adieresis Cacute -30
+KPX Adieresis Ccaron -30
+KPX Adieresis Ccedilla -30
+KPX Adieresis G -30
+KPX Adieresis Gbreve -30
+KPX Adieresis Gcommaaccent -30
+KPX Adieresis O -30
+KPX Adieresis Oacute -30
+KPX Adieresis Ocircumflex -30
+KPX Adieresis Odieresis -30
+KPX Adieresis Ograve -30
+KPX Adieresis Ohungarumlaut -30
+KPX Adieresis Omacron -30
+KPX Adieresis Oslash -30
+KPX Adieresis Otilde -30
+KPX Adieresis Q -30
+KPX Adieresis T -120
+KPX Adieresis Tcaron -120
+KPX Adieresis Tcommaaccent -120
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -70
+KPX Adieresis W -50
+KPX Adieresis Y -100
+KPX Adieresis Yacute -100
+KPX Adieresis Ydieresis -100
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -40
+KPX Adieresis w -40
+KPX Adieresis y -40
+KPX Adieresis yacute -40
+KPX Adieresis ydieresis -40
+KPX Agrave C -30
+KPX Agrave Cacute -30
+KPX Agrave Ccaron -30
+KPX Agrave Ccedilla -30
+KPX Agrave G -30
+KPX Agrave Gbreve -30
+KPX Agrave Gcommaaccent -30
+KPX Agrave O -30
+KPX Agrave Oacute -30
+KPX Agrave Ocircumflex -30
+KPX Agrave Odieresis -30
+KPX Agrave Ograve -30
+KPX Agrave Ohungarumlaut -30
+KPX Agrave Omacron -30
+KPX Agrave Oslash -30
+KPX Agrave Otilde -30
+KPX Agrave Q -30
+KPX Agrave T -120
+KPX Agrave Tcaron -120
+KPX Agrave Tcommaaccent -120
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -70
+KPX Agrave W -50
+KPX Agrave Y -100
+KPX Agrave Yacute -100
+KPX Agrave Ydieresis -100
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -40
+KPX Agrave w -40
+KPX Agrave y -40
+KPX Agrave yacute -40
+KPX Agrave ydieresis -40
+KPX Amacron C -30
+KPX Amacron Cacute -30
+KPX Amacron Ccaron -30
+KPX Amacron Ccedilla -30
+KPX Amacron G -30
+KPX Amacron Gbreve -30
+KPX Amacron Gcommaaccent -30
+KPX Amacron O -30
+KPX Amacron Oacute -30
+KPX Amacron Ocircumflex -30
+KPX Amacron Odieresis -30
+KPX Amacron Ograve -30
+KPX Amacron Ohungarumlaut -30
+KPX Amacron Omacron -30
+KPX Amacron Oslash -30
+KPX Amacron Otilde -30
+KPX Amacron Q -30
+KPX Amacron T -120
+KPX Amacron Tcaron -120
+KPX Amacron Tcommaaccent -120
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -70
+KPX Amacron W -50
+KPX Amacron Y -100
+KPX Amacron Yacute -100
+KPX Amacron Ydieresis -100
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -40
+KPX Amacron w -40
+KPX Amacron y -40
+KPX Amacron yacute -40
+KPX Amacron ydieresis -40
+KPX Aogonek C -30
+KPX Aogonek Cacute -30
+KPX Aogonek Ccaron -30
+KPX Aogonek Ccedilla -30
+KPX Aogonek G -30
+KPX Aogonek Gbreve -30
+KPX Aogonek Gcommaaccent -30
+KPX Aogonek O -30
+KPX Aogonek Oacute -30
+KPX Aogonek Ocircumflex -30
+KPX Aogonek Odieresis -30
+KPX Aogonek Ograve -30
+KPX Aogonek Ohungarumlaut -30
+KPX Aogonek Omacron -30
+KPX Aogonek Oslash -30
+KPX Aogonek Otilde -30
+KPX Aogonek Q -30
+KPX Aogonek T -120
+KPX Aogonek Tcaron -120
+KPX Aogonek Tcommaaccent -120
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -70
+KPX Aogonek W -50
+KPX Aogonek Y -100
+KPX Aogonek Yacute -100
+KPX Aogonek Ydieresis -100
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -40
+KPX Aogonek w -40
+KPX Aogonek y -40
+KPX Aogonek yacute -40
+KPX Aogonek ydieresis -40
+KPX Aring C -30
+KPX Aring Cacute -30
+KPX Aring Ccaron -30
+KPX Aring Ccedilla -30
+KPX Aring G -30
+KPX Aring Gbreve -30
+KPX Aring Gcommaaccent -30
+KPX Aring O -30
+KPX Aring Oacute -30
+KPX Aring Ocircumflex -30
+KPX Aring Odieresis -30
+KPX Aring Ograve -30
+KPX Aring Ohungarumlaut -30
+KPX Aring Omacron -30
+KPX Aring Oslash -30
+KPX Aring Otilde -30
+KPX Aring Q -30
+KPX Aring T -120
+KPX Aring Tcaron -120
+KPX Aring Tcommaaccent -120
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -70
+KPX Aring W -50
+KPX Aring Y -100
+KPX Aring Yacute -100
+KPX Aring Ydieresis -100
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -40
+KPX Aring w -40
+KPX Aring y -40
+KPX Aring yacute -40
+KPX Aring ydieresis -40
+KPX Atilde C -30
+KPX Atilde Cacute -30
+KPX Atilde Ccaron -30
+KPX Atilde Ccedilla -30
+KPX Atilde G -30
+KPX Atilde Gbreve -30
+KPX Atilde Gcommaaccent -30
+KPX Atilde O -30
+KPX Atilde Oacute -30
+KPX Atilde Ocircumflex -30
+KPX Atilde Odieresis -30
+KPX Atilde Ograve -30
+KPX Atilde Ohungarumlaut -30
+KPX Atilde Omacron -30
+KPX Atilde Oslash -30
+KPX Atilde Otilde -30
+KPX Atilde Q -30
+KPX Atilde T -120
+KPX Atilde Tcaron -120
+KPX Atilde Tcommaaccent -120
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -70
+KPX Atilde W -50
+KPX Atilde Y -100
+KPX Atilde Yacute -100
+KPX Atilde Ydieresis -100
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -40
+KPX Atilde w -40
+KPX Atilde y -40
+KPX Atilde yacute -40
+KPX Atilde ydieresis -40
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX B comma -20
+KPX B period -20
+KPX C comma -30
+KPX C period -30
+KPX Cacute comma -30
+KPX Cacute period -30
+KPX Ccaron comma -30
+KPX Ccaron period -30
+KPX Ccedilla comma -30
+KPX Ccedilla period -30
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -70
+KPX D W -40
+KPX D Y -90
+KPX D Yacute -90
+KPX D Ydieresis -90
+KPX D comma -70
+KPX D period -70
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -70
+KPX Dcaron W -40
+KPX Dcaron Y -90
+KPX Dcaron Yacute -90
+KPX Dcaron Ydieresis -90
+KPX Dcaron comma -70
+KPX Dcaron period -70
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -70
+KPX Dcroat W -40
+KPX Dcroat Y -90
+KPX Dcroat Yacute -90
+KPX Dcroat Ydieresis -90
+KPX Dcroat comma -70
+KPX Dcroat period -70
+KPX F A -80
+KPX F Aacute -80
+KPX F Abreve -80
+KPX F Acircumflex -80
+KPX F Adieresis -80
+KPX F Agrave -80
+KPX F Amacron -80
+KPX F Aogonek -80
+KPX F Aring -80
+KPX F Atilde -80
+KPX F a -50
+KPX F aacute -50
+KPX F abreve -50
+KPX F acircumflex -50
+KPX F adieresis -50
+KPX F agrave -50
+KPX F amacron -50
+KPX F aogonek -50
+KPX F aring -50
+KPX F atilde -50
+KPX F comma -150
+KPX F e -30
+KPX F eacute -30
+KPX F ecaron -30
+KPX F ecircumflex -30
+KPX F edieresis -30
+KPX F edotaccent -30
+KPX F egrave -30
+KPX F emacron -30
+KPX F eogonek -30
+KPX F o -30
+KPX F oacute -30
+KPX F ocircumflex -30
+KPX F odieresis -30
+KPX F ograve -30
+KPX F ohungarumlaut -30
+KPX F omacron -30
+KPX F oslash -30
+KPX F otilde -30
+KPX F period -150
+KPX F r -45
+KPX F racute -45
+KPX F rcaron -45
+KPX F rcommaaccent -45
+KPX J A -20
+KPX J Aacute -20
+KPX J Abreve -20
+KPX J Acircumflex -20
+KPX J Adieresis -20
+KPX J Agrave -20
+KPX J Amacron -20
+KPX J Aogonek -20
+KPX J Aring -20
+KPX J Atilde -20
+KPX J a -20
+KPX J aacute -20
+KPX J abreve -20
+KPX J acircumflex -20
+KPX J adieresis -20
+KPX J agrave -20
+KPX J amacron -20
+KPX J aogonek -20
+KPX J aring -20
+KPX J atilde -20
+KPX J comma -30
+KPX J period -30
+KPX J u -20
+KPX J uacute -20
+KPX J ucircumflex -20
+KPX J udieresis -20
+KPX J ugrave -20
+KPX J uhungarumlaut -20
+KPX J umacron -20
+KPX J uogonek -20
+KPX J uring -20
+KPX K O -50
+KPX K Oacute -50
+KPX K Ocircumflex -50
+KPX K Odieresis -50
+KPX K Ograve -50
+KPX K Ohungarumlaut -50
+KPX K Omacron -50
+KPX K Oslash -50
+KPX K Otilde -50
+KPX K e -40
+KPX K eacute -40
+KPX K ecaron -40
+KPX K ecircumflex -40
+KPX K edieresis -40
+KPX K edotaccent -40
+KPX K egrave -40
+KPX K emacron -40
+KPX K eogonek -40
+KPX K o -40
+KPX K oacute -40
+KPX K ocircumflex -40
+KPX K odieresis -40
+KPX K ograve -40
+KPX K ohungarumlaut -40
+KPX K omacron -40
+KPX K oslash -40
+KPX K otilde -40
+KPX K u -30
+KPX K uacute -30
+KPX K ucircumflex -30
+KPX K udieresis -30
+KPX K ugrave -30
+KPX K uhungarumlaut -30
+KPX K umacron -30
+KPX K uogonek -30
+KPX K uring -30
+KPX K y -50
+KPX K yacute -50
+KPX K ydieresis -50
+KPX Kcommaaccent O -50
+KPX Kcommaaccent Oacute -50
+KPX Kcommaaccent Ocircumflex -50
+KPX Kcommaaccent Odieresis -50
+KPX Kcommaaccent Ograve -50
+KPX Kcommaaccent Ohungarumlaut -50
+KPX Kcommaaccent Omacron -50
+KPX Kcommaaccent Oslash -50
+KPX Kcommaaccent Otilde -50
+KPX Kcommaaccent e -40
+KPX Kcommaaccent eacute -40
+KPX Kcommaaccent ecaron -40
+KPX Kcommaaccent ecircumflex -40
+KPX Kcommaaccent edieresis -40
+KPX Kcommaaccent edotaccent -40
+KPX Kcommaaccent egrave -40
+KPX Kcommaaccent emacron -40
+KPX Kcommaaccent eogonek -40
+KPX Kcommaaccent o -40
+KPX Kcommaaccent oacute -40
+KPX Kcommaaccent ocircumflex -40
+KPX Kcommaaccent odieresis -40
+KPX Kcommaaccent ograve -40
+KPX Kcommaaccent ohungarumlaut -40
+KPX Kcommaaccent omacron -40
+KPX Kcommaaccent oslash -40
+KPX Kcommaaccent otilde -40
+KPX Kcommaaccent u -30
+KPX Kcommaaccent uacute -30
+KPX Kcommaaccent ucircumflex -30
+KPX Kcommaaccent udieresis -30
+KPX Kcommaaccent ugrave -30
+KPX Kcommaaccent uhungarumlaut -30
+KPX Kcommaaccent umacron -30
+KPX Kcommaaccent uogonek -30
+KPX Kcommaaccent uring -30
+KPX Kcommaaccent y -50
+KPX Kcommaaccent yacute -50
+KPX Kcommaaccent ydieresis -50
+KPX L T -110
+KPX L Tcaron -110
+KPX L Tcommaaccent -110
+KPX L V -110
+KPX L W -70
+KPX L Y -140
+KPX L Yacute -140
+KPX L Ydieresis -140
+KPX L quotedblright -140
+KPX L quoteright -160
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -110
+KPX Lacute Tcaron -110
+KPX Lacute Tcommaaccent -110
+KPX Lacute V -110
+KPX Lacute W -70
+KPX Lacute Y -140
+KPX Lacute Yacute -140
+KPX Lacute Ydieresis -140
+KPX Lacute quotedblright -140
+KPX Lacute quoteright -160
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcaron T -110
+KPX Lcaron Tcaron -110
+KPX Lcaron Tcommaaccent -110
+KPX Lcaron V -110
+KPX Lcaron W -70
+KPX Lcaron Y -140
+KPX Lcaron Yacute -140
+KPX Lcaron Ydieresis -140
+KPX Lcaron quotedblright -140
+KPX Lcaron quoteright -160
+KPX Lcaron y -30
+KPX Lcaron yacute -30
+KPX Lcaron ydieresis -30
+KPX Lcommaaccent T -110
+KPX Lcommaaccent Tcaron -110
+KPX Lcommaaccent Tcommaaccent -110
+KPX Lcommaaccent V -110
+KPX Lcommaaccent W -70
+KPX Lcommaaccent Y -140
+KPX Lcommaaccent Yacute -140
+KPX Lcommaaccent Ydieresis -140
+KPX Lcommaaccent quotedblright -140
+KPX Lcommaaccent quoteright -160
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -110
+KPX Lslash Tcaron -110
+KPX Lslash Tcommaaccent -110
+KPX Lslash V -110
+KPX Lslash W -70
+KPX Lslash Y -140
+KPX Lslash Yacute -140
+KPX Lslash Ydieresis -140
+KPX Lslash quotedblright -140
+KPX Lslash quoteright -160
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX O A -20
+KPX O Aacute -20
+KPX O Abreve -20
+KPX O Acircumflex -20
+KPX O Adieresis -20
+KPX O Agrave -20
+KPX O Amacron -20
+KPX O Aogonek -20
+KPX O Aring -20
+KPX O Atilde -20
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -30
+KPX O X -60
+KPX O Y -70
+KPX O Yacute -70
+KPX O Ydieresis -70
+KPX O comma -40
+KPX O period -40
+KPX Oacute A -20
+KPX Oacute Aacute -20
+KPX Oacute Abreve -20
+KPX Oacute Acircumflex -20
+KPX Oacute Adieresis -20
+KPX Oacute Agrave -20
+KPX Oacute Amacron -20
+KPX Oacute Aogonek -20
+KPX Oacute Aring -20
+KPX Oacute Atilde -20
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -30
+KPX Oacute X -60
+KPX Oacute Y -70
+KPX Oacute Yacute -70
+KPX Oacute Ydieresis -70
+KPX Oacute comma -40
+KPX Oacute period -40
+KPX Ocircumflex A -20
+KPX Ocircumflex Aacute -20
+KPX Ocircumflex Abreve -20
+KPX Ocircumflex Acircumflex -20
+KPX Ocircumflex Adieresis -20
+KPX Ocircumflex Agrave -20
+KPX Ocircumflex Amacron -20
+KPX Ocircumflex Aogonek -20
+KPX Ocircumflex Aring -20
+KPX Ocircumflex Atilde -20
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -30
+KPX Ocircumflex X -60
+KPX Ocircumflex Y -70
+KPX Ocircumflex Yacute -70
+KPX Ocircumflex Ydieresis -70
+KPX Ocircumflex comma -40
+KPX Ocircumflex period -40
+KPX Odieresis A -20
+KPX Odieresis Aacute -20
+KPX Odieresis Abreve -20
+KPX Odieresis Acircumflex -20
+KPX Odieresis Adieresis -20
+KPX Odieresis Agrave -20
+KPX Odieresis Amacron -20
+KPX Odieresis Aogonek -20
+KPX Odieresis Aring -20
+KPX Odieresis Atilde -20
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -30
+KPX Odieresis X -60
+KPX Odieresis Y -70
+KPX Odieresis Yacute -70
+KPX Odieresis Ydieresis -70
+KPX Odieresis comma -40
+KPX Odieresis period -40
+KPX Ograve A -20
+KPX Ograve Aacute -20
+KPX Ograve Abreve -20
+KPX Ograve Acircumflex -20
+KPX Ograve Adieresis -20
+KPX Ograve Agrave -20
+KPX Ograve Amacron -20
+KPX Ograve Aogonek -20
+KPX Ograve Aring -20
+KPX Ograve Atilde -20
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -30
+KPX Ograve X -60
+KPX Ograve Y -70
+KPX Ograve Yacute -70
+KPX Ograve Ydieresis -70
+KPX Ograve comma -40
+KPX Ograve period -40
+KPX Ohungarumlaut A -20
+KPX Ohungarumlaut Aacute -20
+KPX Ohungarumlaut Abreve -20
+KPX Ohungarumlaut Acircumflex -20
+KPX Ohungarumlaut Adieresis -20
+KPX Ohungarumlaut Agrave -20
+KPX Ohungarumlaut Amacron -20
+KPX Ohungarumlaut Aogonek -20
+KPX Ohungarumlaut Aring -20
+KPX Ohungarumlaut Atilde -20
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -30
+KPX Ohungarumlaut X -60
+KPX Ohungarumlaut Y -70
+KPX Ohungarumlaut Yacute -70
+KPX Ohungarumlaut Ydieresis -70
+KPX Ohungarumlaut comma -40
+KPX Ohungarumlaut period -40
+KPX Omacron A -20
+KPX Omacron Aacute -20
+KPX Omacron Abreve -20
+KPX Omacron Acircumflex -20
+KPX Omacron Adieresis -20
+KPX Omacron Agrave -20
+KPX Omacron Amacron -20
+KPX Omacron Aogonek -20
+KPX Omacron Aring -20
+KPX Omacron Atilde -20
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -30
+KPX Omacron X -60
+KPX Omacron Y -70
+KPX Omacron Yacute -70
+KPX Omacron Ydieresis -70
+KPX Omacron comma -40
+KPX Omacron period -40
+KPX Oslash A -20
+KPX Oslash Aacute -20
+KPX Oslash Abreve -20
+KPX Oslash Acircumflex -20
+KPX Oslash Adieresis -20
+KPX Oslash Agrave -20
+KPX Oslash Amacron -20
+KPX Oslash Aogonek -20
+KPX Oslash Aring -20
+KPX Oslash Atilde -20
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -30
+KPX Oslash X -60
+KPX Oslash Y -70
+KPX Oslash Yacute -70
+KPX Oslash Ydieresis -70
+KPX Oslash comma -40
+KPX Oslash period -40
+KPX Otilde A -20
+KPX Otilde Aacute -20
+KPX Otilde Abreve -20
+KPX Otilde Acircumflex -20
+KPX Otilde Adieresis -20
+KPX Otilde Agrave -20
+KPX Otilde Amacron -20
+KPX Otilde Aogonek -20
+KPX Otilde Aring -20
+KPX Otilde Atilde -20
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -30
+KPX Otilde X -60
+KPX Otilde Y -70
+KPX Otilde Yacute -70
+KPX Otilde Ydieresis -70
+KPX Otilde comma -40
+KPX Otilde period -40
+KPX P A -120
+KPX P Aacute -120
+KPX P Abreve -120
+KPX P Acircumflex -120
+KPX P Adieresis -120
+KPX P Agrave -120
+KPX P Amacron -120
+KPX P Aogonek -120
+KPX P Aring -120
+KPX P Atilde -120
+KPX P a -40
+KPX P aacute -40
+KPX P abreve -40
+KPX P acircumflex -40
+KPX P adieresis -40
+KPX P agrave -40
+KPX P amacron -40
+KPX P aogonek -40
+KPX P aring -40
+KPX P atilde -40
+KPX P comma -180
+KPX P e -50
+KPX P eacute -50
+KPX P ecaron -50
+KPX P ecircumflex -50
+KPX P edieresis -50
+KPX P edotaccent -50
+KPX P egrave -50
+KPX P emacron -50
+KPX P eogonek -50
+KPX P o -50
+KPX P oacute -50
+KPX P ocircumflex -50
+KPX P odieresis -50
+KPX P ograve -50
+KPX P ohungarumlaut -50
+KPX P omacron -50
+KPX P oslash -50
+KPX P otilde -50
+KPX P period -180
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -20
+KPX R Oacute -20
+KPX R Ocircumflex -20
+KPX R Odieresis -20
+KPX R Ograve -20
+KPX R Ohungarumlaut -20
+KPX R Omacron -20
+KPX R Oslash -20
+KPX R Otilde -20
+KPX R T -30
+KPX R Tcaron -30
+KPX R Tcommaaccent -30
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -50
+KPX R W -30
+KPX R Y -50
+KPX R Yacute -50
+KPX R Ydieresis -50
+KPX Racute O -20
+KPX Racute Oacute -20
+KPX Racute Ocircumflex -20
+KPX Racute Odieresis -20
+KPX Racute Ograve -20
+KPX Racute Ohungarumlaut -20
+KPX Racute Omacron -20
+KPX Racute Oslash -20
+KPX Racute Otilde -20
+KPX Racute T -30
+KPX Racute Tcaron -30
+KPX Racute Tcommaaccent -30
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -50
+KPX Racute W -30
+KPX Racute Y -50
+KPX Racute Yacute -50
+KPX Racute Ydieresis -50
+KPX Rcaron O -20
+KPX Rcaron Oacute -20
+KPX Rcaron Ocircumflex -20
+KPX Rcaron Odieresis -20
+KPX Rcaron Ograve -20
+KPX Rcaron Ohungarumlaut -20
+KPX Rcaron Omacron -20
+KPX Rcaron Oslash -20
+KPX Rcaron Otilde -20
+KPX Rcaron T -30
+KPX Rcaron Tcaron -30
+KPX Rcaron Tcommaaccent -30
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -50
+KPX Rcaron W -30
+KPX Rcaron Y -50
+KPX Rcaron Yacute -50
+KPX Rcaron Ydieresis -50
+KPX Rcommaaccent O -20
+KPX Rcommaaccent Oacute -20
+KPX Rcommaaccent Ocircumflex -20
+KPX Rcommaaccent Odieresis -20
+KPX Rcommaaccent Ograve -20
+KPX Rcommaaccent Ohungarumlaut -20
+KPX Rcommaaccent Omacron -20
+KPX Rcommaaccent Oslash -20
+KPX Rcommaaccent Otilde -20
+KPX Rcommaaccent T -30
+KPX Rcommaaccent Tcaron -30
+KPX Rcommaaccent Tcommaaccent -30
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -50
+KPX Rcommaaccent W -30
+KPX Rcommaaccent Y -50
+KPX Rcommaaccent Yacute -50
+KPX Rcommaaccent Ydieresis -50
+KPX S comma -20
+KPX S period -20
+KPX Sacute comma -20
+KPX Sacute period -20
+KPX Scaron comma -20
+KPX Scaron period -20
+KPX Scedilla comma -20
+KPX Scedilla period -20
+KPX Scommaaccent comma -20
+KPX Scommaaccent period -20
+KPX T A -120
+KPX T Aacute -120
+KPX T Abreve -120
+KPX T Acircumflex -120
+KPX T Adieresis -120
+KPX T Agrave -120
+KPX T Amacron -120
+KPX T Aogonek -120
+KPX T Aring -120
+KPX T Atilde -120
+KPX T O -40
+KPX T Oacute -40
+KPX T Ocircumflex -40
+KPX T Odieresis -40
+KPX T Ograve -40
+KPX T Ohungarumlaut -40
+KPX T Omacron -40
+KPX T Oslash -40
+KPX T Otilde -40
+KPX T a -120
+KPX T aacute -120
+KPX T abreve -60
+KPX T acircumflex -120
+KPX T adieresis -120
+KPX T agrave -120
+KPX T amacron -60
+KPX T aogonek -120
+KPX T aring -120
+KPX T atilde -60
+KPX T colon -20
+KPX T comma -120
+KPX T e -120
+KPX T eacute -120
+KPX T ecaron -120
+KPX T ecircumflex -120
+KPX T edieresis -120
+KPX T edotaccent -120
+KPX T egrave -60
+KPX T emacron -60
+KPX T eogonek -120
+KPX T hyphen -140
+KPX T o -120
+KPX T oacute -120
+KPX T ocircumflex -120
+KPX T odieresis -120
+KPX T ograve -120
+KPX T ohungarumlaut -120
+KPX T omacron -60
+KPX T oslash -120
+KPX T otilde -60
+KPX T period -120
+KPX T r -120
+KPX T racute -120
+KPX T rcaron -120
+KPX T rcommaaccent -120
+KPX T semicolon -20
+KPX T u -120
+KPX T uacute -120
+KPX T ucircumflex -120
+KPX T udieresis -120
+KPX T ugrave -120
+KPX T uhungarumlaut -120
+KPX T umacron -60
+KPX T uogonek -120
+KPX T uring -120
+KPX T w -120
+KPX T y -120
+KPX T yacute -120
+KPX T ydieresis -60
+KPX Tcaron A -120
+KPX Tcaron Aacute -120
+KPX Tcaron Abreve -120
+KPX Tcaron Acircumflex -120
+KPX Tcaron Adieresis -120
+KPX Tcaron Agrave -120
+KPX Tcaron Amacron -120
+KPX Tcaron Aogonek -120
+KPX Tcaron Aring -120
+KPX Tcaron Atilde -120
+KPX Tcaron O -40
+KPX Tcaron Oacute -40
+KPX Tcaron Ocircumflex -40
+KPX Tcaron Odieresis -40
+KPX Tcaron Ograve -40
+KPX Tcaron Ohungarumlaut -40
+KPX Tcaron Omacron -40
+KPX Tcaron Oslash -40
+KPX Tcaron Otilde -40
+KPX Tcaron a -120
+KPX Tcaron aacute -120
+KPX Tcaron abreve -60
+KPX Tcaron acircumflex -120
+KPX Tcaron adieresis -120
+KPX Tcaron agrave -120
+KPX Tcaron amacron -60
+KPX Tcaron aogonek -120
+KPX Tcaron aring -120
+KPX Tcaron atilde -60
+KPX Tcaron colon -20
+KPX Tcaron comma -120
+KPX Tcaron e -120
+KPX Tcaron eacute -120
+KPX Tcaron ecaron -120
+KPX Tcaron ecircumflex -120
+KPX Tcaron edieresis -120
+KPX Tcaron edotaccent -120
+KPX Tcaron egrave -60
+KPX Tcaron emacron -60
+KPX Tcaron eogonek -120
+KPX Tcaron hyphen -140
+KPX Tcaron o -120
+KPX Tcaron oacute -120
+KPX Tcaron ocircumflex -120
+KPX Tcaron odieresis -120
+KPX Tcaron ograve -120
+KPX Tcaron ohungarumlaut -120
+KPX Tcaron omacron -60
+KPX Tcaron oslash -120
+KPX Tcaron otilde -60
+KPX Tcaron period -120
+KPX Tcaron r -120
+KPX Tcaron racute -120
+KPX Tcaron rcaron -120
+KPX Tcaron rcommaaccent -120
+KPX Tcaron semicolon -20
+KPX Tcaron u -120
+KPX Tcaron uacute -120
+KPX Tcaron ucircumflex -120
+KPX Tcaron udieresis -120
+KPX Tcaron ugrave -120
+KPX Tcaron uhungarumlaut -120
+KPX Tcaron umacron -60
+KPX Tcaron uogonek -120
+KPX Tcaron uring -120
+KPX Tcaron w -120
+KPX Tcaron y -120
+KPX Tcaron yacute -120
+KPX Tcaron ydieresis -60
+KPX Tcommaaccent A -120
+KPX Tcommaaccent Aacute -120
+KPX Tcommaaccent Abreve -120
+KPX Tcommaaccent Acircumflex -120
+KPX Tcommaaccent Adieresis -120
+KPX Tcommaaccent Agrave -120
+KPX Tcommaaccent Amacron -120
+KPX Tcommaaccent Aogonek -120
+KPX Tcommaaccent Aring -120
+KPX Tcommaaccent Atilde -120
+KPX Tcommaaccent O -40
+KPX Tcommaaccent Oacute -40
+KPX Tcommaaccent Ocircumflex -40
+KPX Tcommaaccent Odieresis -40
+KPX Tcommaaccent Ograve -40
+KPX Tcommaaccent Ohungarumlaut -40
+KPX Tcommaaccent Omacron -40
+KPX Tcommaaccent Oslash -40
+KPX Tcommaaccent Otilde -40
+KPX Tcommaaccent a -120
+KPX Tcommaaccent aacute -120
+KPX Tcommaaccent abreve -60
+KPX Tcommaaccent acircumflex -120
+KPX Tcommaaccent adieresis -120
+KPX Tcommaaccent agrave -120
+KPX Tcommaaccent amacron -60
+KPX Tcommaaccent aogonek -120
+KPX Tcommaaccent aring -120
+KPX Tcommaaccent atilde -60
+KPX Tcommaaccent colon -20
+KPX Tcommaaccent comma -120
+KPX Tcommaaccent e -120
+KPX Tcommaaccent eacute -120
+KPX Tcommaaccent ecaron -120
+KPX Tcommaaccent ecircumflex -120
+KPX Tcommaaccent edieresis -120
+KPX Tcommaaccent edotaccent -120
+KPX Tcommaaccent egrave -60
+KPX Tcommaaccent emacron -60
+KPX Tcommaaccent eogonek -120
+KPX Tcommaaccent hyphen -140
+KPX Tcommaaccent o -120
+KPX Tcommaaccent oacute -120
+KPX Tcommaaccent ocircumflex -120
+KPX Tcommaaccent odieresis -120
+KPX Tcommaaccent ograve -120
+KPX Tcommaaccent ohungarumlaut -120
+KPX Tcommaaccent omacron -60
+KPX Tcommaaccent oslash -120
+KPX Tcommaaccent otilde -60
+KPX Tcommaaccent period -120
+KPX Tcommaaccent r -120
+KPX Tcommaaccent racute -120
+KPX Tcommaaccent rcaron -120
+KPX Tcommaaccent rcommaaccent -120
+KPX Tcommaaccent semicolon -20
+KPX Tcommaaccent u -120
+KPX Tcommaaccent uacute -120
+KPX Tcommaaccent ucircumflex -120
+KPX Tcommaaccent udieresis -120
+KPX Tcommaaccent ugrave -120
+KPX Tcommaaccent uhungarumlaut -120
+KPX Tcommaaccent umacron -60
+KPX Tcommaaccent uogonek -120
+KPX Tcommaaccent uring -120
+KPX Tcommaaccent w -120
+KPX Tcommaaccent y -120
+KPX Tcommaaccent yacute -120
+KPX Tcommaaccent ydieresis -60
+KPX U A -40
+KPX U Aacute -40
+KPX U Abreve -40
+KPX U Acircumflex -40
+KPX U Adieresis -40
+KPX U Agrave -40
+KPX U Amacron -40
+KPX U Aogonek -40
+KPX U Aring -40
+KPX U Atilde -40
+KPX U comma -40
+KPX U period -40
+KPX Uacute A -40
+KPX Uacute Aacute -40
+KPX Uacute Abreve -40
+KPX Uacute Acircumflex -40
+KPX Uacute Adieresis -40
+KPX Uacute Agrave -40
+KPX Uacute Amacron -40
+KPX Uacute Aogonek -40
+KPX Uacute Aring -40
+KPX Uacute Atilde -40
+KPX Uacute comma -40
+KPX Uacute period -40
+KPX Ucircumflex A -40
+KPX Ucircumflex Aacute -40
+KPX Ucircumflex Abreve -40
+KPX Ucircumflex Acircumflex -40
+KPX Ucircumflex Adieresis -40
+KPX Ucircumflex Agrave -40
+KPX Ucircumflex Amacron -40
+KPX Ucircumflex Aogonek -40
+KPX Ucircumflex Aring -40
+KPX Ucircumflex Atilde -40
+KPX Ucircumflex comma -40
+KPX Ucircumflex period -40
+KPX Udieresis A -40
+KPX Udieresis Aacute -40
+KPX Udieresis Abreve -40
+KPX Udieresis Acircumflex -40
+KPX Udieresis Adieresis -40
+KPX Udieresis Agrave -40
+KPX Udieresis Amacron -40
+KPX Udieresis Aogonek -40
+KPX Udieresis Aring -40
+KPX Udieresis Atilde -40
+KPX Udieresis comma -40
+KPX Udieresis period -40
+KPX Ugrave A -40
+KPX Ugrave Aacute -40
+KPX Ugrave Abreve -40
+KPX Ugrave Acircumflex -40
+KPX Ugrave Adieresis -40
+KPX Ugrave Agrave -40
+KPX Ugrave Amacron -40
+KPX Ugrave Aogonek -40
+KPX Ugrave Aring -40
+KPX Ugrave Atilde -40
+KPX Ugrave comma -40
+KPX Ugrave period -40
+KPX Uhungarumlaut A -40
+KPX Uhungarumlaut Aacute -40
+KPX Uhungarumlaut Abreve -40
+KPX Uhungarumlaut Acircumflex -40
+KPX Uhungarumlaut Adieresis -40
+KPX Uhungarumlaut Agrave -40
+KPX Uhungarumlaut Amacron -40
+KPX Uhungarumlaut Aogonek -40
+KPX Uhungarumlaut Aring -40
+KPX Uhungarumlaut Atilde -40
+KPX Uhungarumlaut comma -40
+KPX Uhungarumlaut period -40
+KPX Umacron A -40
+KPX Umacron Aacute -40
+KPX Umacron Abreve -40
+KPX Umacron Acircumflex -40
+KPX Umacron Adieresis -40
+KPX Umacron Agrave -40
+KPX Umacron Amacron -40
+KPX Umacron Aogonek -40
+KPX Umacron Aring -40
+KPX Umacron Atilde -40
+KPX Umacron comma -40
+KPX Umacron period -40
+KPX Uogonek A -40
+KPX Uogonek Aacute -40
+KPX Uogonek Abreve -40
+KPX Uogonek Acircumflex -40
+KPX Uogonek Adieresis -40
+KPX Uogonek Agrave -40
+KPX Uogonek Amacron -40
+KPX Uogonek Aogonek -40
+KPX Uogonek Aring -40
+KPX Uogonek Atilde -40
+KPX Uogonek comma -40
+KPX Uogonek period -40
+KPX Uring A -40
+KPX Uring Aacute -40
+KPX Uring Abreve -40
+KPX Uring Acircumflex -40
+KPX Uring Adieresis -40
+KPX Uring Agrave -40
+KPX Uring Amacron -40
+KPX Uring Aogonek -40
+KPX Uring Aring -40
+KPX Uring Atilde -40
+KPX Uring comma -40
+KPX Uring period -40
+KPX V A -80
+KPX V Aacute -80
+KPX V Abreve -80
+KPX V Acircumflex -80
+KPX V Adieresis -80
+KPX V Agrave -80
+KPX V Amacron -80
+KPX V Aogonek -80
+KPX V Aring -80
+KPX V Atilde -80
+KPX V G -40
+KPX V Gbreve -40
+KPX V Gcommaaccent -40
+KPX V O -40
+KPX V Oacute -40
+KPX V Ocircumflex -40
+KPX V Odieresis -40
+KPX V Ograve -40
+KPX V Ohungarumlaut -40
+KPX V Omacron -40
+KPX V Oslash -40
+KPX V Otilde -40
+KPX V a -70
+KPX V aacute -70
+KPX V abreve -70
+KPX V acircumflex -70
+KPX V adieresis -70
+KPX V agrave -70
+KPX V amacron -70
+KPX V aogonek -70
+KPX V aring -70
+KPX V atilde -70
+KPX V colon -40
+KPX V comma -125
+KPX V e -80
+KPX V eacute -80
+KPX V ecaron -80
+KPX V ecircumflex -80
+KPX V edieresis -80
+KPX V edotaccent -80
+KPX V egrave -80
+KPX V emacron -80
+KPX V eogonek -80
+KPX V hyphen -80
+KPX V o -80
+KPX V oacute -80
+KPX V ocircumflex -80
+KPX V odieresis -80
+KPX V ograve -80
+KPX V ohungarumlaut -80
+KPX V omacron -80
+KPX V oslash -80
+KPX V otilde -80
+KPX V period -125
+KPX V semicolon -40
+KPX V u -70
+KPX V uacute -70
+KPX V ucircumflex -70
+KPX V udieresis -70
+KPX V ugrave -70
+KPX V uhungarumlaut -70
+KPX V umacron -70
+KPX V uogonek -70
+KPX V uring -70
+KPX W A -50
+KPX W Aacute -50
+KPX W Abreve -50
+KPX W Acircumflex -50
+KPX W Adieresis -50
+KPX W Agrave -50
+KPX W Amacron -50
+KPX W Aogonek -50
+KPX W Aring -50
+KPX W Atilde -50
+KPX W O -20
+KPX W Oacute -20
+KPX W Ocircumflex -20
+KPX W Odieresis -20
+KPX W Ograve -20
+KPX W Ohungarumlaut -20
+KPX W Omacron -20
+KPX W Oslash -20
+KPX W Otilde -20
+KPX W a -40
+KPX W aacute -40
+KPX W abreve -40
+KPX W acircumflex -40
+KPX W adieresis -40
+KPX W agrave -40
+KPX W amacron -40
+KPX W aogonek -40
+KPX W aring -40
+KPX W atilde -40
+KPX W comma -80
+KPX W e -30
+KPX W eacute -30
+KPX W ecaron -30
+KPX W ecircumflex -30
+KPX W edieresis -30
+KPX W edotaccent -30
+KPX W egrave -30
+KPX W emacron -30
+KPX W eogonek -30
+KPX W hyphen -40
+KPX W o -30
+KPX W oacute -30
+KPX W ocircumflex -30
+KPX W odieresis -30
+KPX W ograve -30
+KPX W ohungarumlaut -30
+KPX W omacron -30
+KPX W oslash -30
+KPX W otilde -30
+KPX W period -80
+KPX W u -30
+KPX W uacute -30
+KPX W ucircumflex -30
+KPX W udieresis -30
+KPX W ugrave -30
+KPX W uhungarumlaut -30
+KPX W umacron -30
+KPX W uogonek -30
+KPX W uring -30
+KPX W y -20
+KPX W yacute -20
+KPX W ydieresis -20
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -85
+KPX Y Oacute -85
+KPX Y Ocircumflex -85
+KPX Y Odieresis -85
+KPX Y Ograve -85
+KPX Y Ohungarumlaut -85
+KPX Y Omacron -85
+KPX Y Oslash -85
+KPX Y Otilde -85
+KPX Y a -140
+KPX Y aacute -140
+KPX Y abreve -70
+KPX Y acircumflex -140
+KPX Y adieresis -140
+KPX Y agrave -140
+KPX Y amacron -70
+KPX Y aogonek -140
+KPX Y aring -140
+KPX Y atilde -140
+KPX Y colon -60
+KPX Y comma -140
+KPX Y e -140
+KPX Y eacute -140
+KPX Y ecaron -140
+KPX Y ecircumflex -140
+KPX Y edieresis -140
+KPX Y edotaccent -140
+KPX Y egrave -140
+KPX Y emacron -70
+KPX Y eogonek -140
+KPX Y hyphen -140
+KPX Y i -20
+KPX Y iacute -20
+KPX Y iogonek -20
+KPX Y o -140
+KPX Y oacute -140
+KPX Y ocircumflex -140
+KPX Y odieresis -140
+KPX Y ograve -140
+KPX Y ohungarumlaut -140
+KPX Y omacron -140
+KPX Y oslash -140
+KPX Y otilde -140
+KPX Y period -140
+KPX Y semicolon -60
+KPX Y u -110
+KPX Y uacute -110
+KPX Y ucircumflex -110
+KPX Y udieresis -110
+KPX Y ugrave -110
+KPX Y uhungarumlaut -110
+KPX Y umacron -110
+KPX Y uogonek -110
+KPX Y uring -110
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -85
+KPX Yacute Oacute -85
+KPX Yacute Ocircumflex -85
+KPX Yacute Odieresis -85
+KPX Yacute Ograve -85
+KPX Yacute Ohungarumlaut -85
+KPX Yacute Omacron -85
+KPX Yacute Oslash -85
+KPX Yacute Otilde -85
+KPX Yacute a -140
+KPX Yacute aacute -140
+KPX Yacute abreve -70
+KPX Yacute acircumflex -140
+KPX Yacute adieresis -140
+KPX Yacute agrave -140
+KPX Yacute amacron -70
+KPX Yacute aogonek -140
+KPX Yacute aring -140
+KPX Yacute atilde -70
+KPX Yacute colon -60
+KPX Yacute comma -140
+KPX Yacute e -140
+KPX Yacute eacute -140
+KPX Yacute ecaron -140
+KPX Yacute ecircumflex -140
+KPX Yacute edieresis -140
+KPX Yacute edotaccent -140
+KPX Yacute egrave -140
+KPX Yacute emacron -70
+KPX Yacute eogonek -140
+KPX Yacute hyphen -140
+KPX Yacute i -20
+KPX Yacute iacute -20
+KPX Yacute iogonek -20
+KPX Yacute o -140
+KPX Yacute oacute -140
+KPX Yacute ocircumflex -140
+KPX Yacute odieresis -140
+KPX Yacute ograve -140
+KPX Yacute ohungarumlaut -140
+KPX Yacute omacron -70
+KPX Yacute oslash -140
+KPX Yacute otilde -140
+KPX Yacute period -140
+KPX Yacute semicolon -60
+KPX Yacute u -110
+KPX Yacute uacute -110
+KPX Yacute ucircumflex -110
+KPX Yacute udieresis -110
+KPX Yacute ugrave -110
+KPX Yacute uhungarumlaut -110
+KPX Yacute umacron -110
+KPX Yacute uogonek -110
+KPX Yacute uring -110
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -85
+KPX Ydieresis Oacute -85
+KPX Ydieresis Ocircumflex -85
+KPX Ydieresis Odieresis -85
+KPX Ydieresis Ograve -85
+KPX Ydieresis Ohungarumlaut -85
+KPX Ydieresis Omacron -85
+KPX Ydieresis Oslash -85
+KPX Ydieresis Otilde -85
+KPX Ydieresis a -140
+KPX Ydieresis aacute -140
+KPX Ydieresis abreve -70
+KPX Ydieresis acircumflex -140
+KPX Ydieresis adieresis -140
+KPX Ydieresis agrave -140
+KPX Ydieresis amacron -70
+KPX Ydieresis aogonek -140
+KPX Ydieresis aring -140
+KPX Ydieresis atilde -70
+KPX Ydieresis colon -60
+KPX Ydieresis comma -140
+KPX Ydieresis e -140
+KPX Ydieresis eacute -140
+KPX Ydieresis ecaron -140
+KPX Ydieresis ecircumflex -140
+KPX Ydieresis edieresis -140
+KPX Ydieresis edotaccent -140
+KPX Ydieresis egrave -140
+KPX Ydieresis emacron -70
+KPX Ydieresis eogonek -140
+KPX Ydieresis hyphen -140
+KPX Ydieresis i -20
+KPX Ydieresis iacute -20
+KPX Ydieresis iogonek -20
+KPX Ydieresis o -140
+KPX Ydieresis oacute -140
+KPX Ydieresis ocircumflex -140
+KPX Ydieresis odieresis -140
+KPX Ydieresis ograve -140
+KPX Ydieresis ohungarumlaut -140
+KPX Ydieresis omacron -140
+KPX Ydieresis oslash -140
+KPX Ydieresis otilde -140
+KPX Ydieresis period -140
+KPX Ydieresis semicolon -60
+KPX Ydieresis u -110
+KPX Ydieresis uacute -110
+KPX Ydieresis ucircumflex -110
+KPX Ydieresis udieresis -110
+KPX Ydieresis ugrave -110
+KPX Ydieresis uhungarumlaut -110
+KPX Ydieresis umacron -110
+KPX Ydieresis uogonek -110
+KPX Ydieresis uring -110
+KPX a v -20
+KPX a w -20
+KPX a y -30
+KPX a yacute -30
+KPX a ydieresis -30
+KPX aacute v -20
+KPX aacute w -20
+KPX aacute y -30
+KPX aacute yacute -30
+KPX aacute ydieresis -30
+KPX abreve v -20
+KPX abreve w -20
+KPX abreve y -30
+KPX abreve yacute -30
+KPX abreve ydieresis -30
+KPX acircumflex v -20
+KPX acircumflex w -20
+KPX acircumflex y -30
+KPX acircumflex yacute -30
+KPX acircumflex ydieresis -30
+KPX adieresis v -20
+KPX adieresis w -20
+KPX adieresis y -30
+KPX adieresis yacute -30
+KPX adieresis ydieresis -30
+KPX agrave v -20
+KPX agrave w -20
+KPX agrave y -30
+KPX agrave yacute -30
+KPX agrave ydieresis -30
+KPX amacron v -20
+KPX amacron w -20
+KPX amacron y -30
+KPX amacron yacute -30
+KPX amacron ydieresis -30
+KPX aogonek v -20
+KPX aogonek w -20
+KPX aogonek y -30
+KPX aogonek yacute -30
+KPX aogonek ydieresis -30
+KPX aring v -20
+KPX aring w -20
+KPX aring y -30
+KPX aring yacute -30
+KPX aring ydieresis -30
+KPX atilde v -20
+KPX atilde w -20
+KPX atilde y -30
+KPX atilde yacute -30
+KPX atilde ydieresis -30
+KPX b b -10
+KPX b comma -40
+KPX b l -20
+KPX b lacute -20
+KPX b lcommaaccent -20
+KPX b lslash -20
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -20
+KPX b y -20
+KPX b yacute -20
+KPX b ydieresis -20
+KPX c comma -15
+KPX c k -20
+KPX c kcommaaccent -20
+KPX cacute comma -15
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX ccaron comma -15
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccedilla comma -15
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX colon space -50
+KPX comma quotedblright -100
+KPX comma quoteright -100
+KPX e comma -15
+KPX e period -15
+KPX e v -30
+KPX e w -20
+KPX e x -30
+KPX e y -20
+KPX e yacute -20
+KPX e ydieresis -20
+KPX eacute comma -15
+KPX eacute period -15
+KPX eacute v -30
+KPX eacute w -20
+KPX eacute x -30
+KPX eacute y -20
+KPX eacute yacute -20
+KPX eacute ydieresis -20
+KPX ecaron comma -15
+KPX ecaron period -15
+KPX ecaron v -30
+KPX ecaron w -20
+KPX ecaron x -30
+KPX ecaron y -20
+KPX ecaron yacute -20
+KPX ecaron ydieresis -20
+KPX ecircumflex comma -15
+KPX ecircumflex period -15
+KPX ecircumflex v -30
+KPX ecircumflex w -20
+KPX ecircumflex x -30
+KPX ecircumflex y -20
+KPX ecircumflex yacute -20
+KPX ecircumflex ydieresis -20
+KPX edieresis comma -15
+KPX edieresis period -15
+KPX edieresis v -30
+KPX edieresis w -20
+KPX edieresis x -30
+KPX edieresis y -20
+KPX edieresis yacute -20
+KPX edieresis ydieresis -20
+KPX edotaccent comma -15
+KPX edotaccent period -15
+KPX edotaccent v -30
+KPX edotaccent w -20
+KPX edotaccent x -30
+KPX edotaccent y -20
+KPX edotaccent yacute -20
+KPX edotaccent ydieresis -20
+KPX egrave comma -15
+KPX egrave period -15
+KPX egrave v -30
+KPX egrave w -20
+KPX egrave x -30
+KPX egrave y -20
+KPX egrave yacute -20
+KPX egrave ydieresis -20
+KPX emacron comma -15
+KPX emacron period -15
+KPX emacron v -30
+KPX emacron w -20
+KPX emacron x -30
+KPX emacron y -20
+KPX emacron yacute -20
+KPX emacron ydieresis -20
+KPX eogonek comma -15
+KPX eogonek period -15
+KPX eogonek v -30
+KPX eogonek w -20
+KPX eogonek x -30
+KPX eogonek y -20
+KPX eogonek yacute -20
+KPX eogonek ydieresis -20
+KPX f a -30
+KPX f aacute -30
+KPX f abreve -30
+KPX f acircumflex -30
+KPX f adieresis -30
+KPX f agrave -30
+KPX f amacron -30
+KPX f aogonek -30
+KPX f aring -30
+KPX f atilde -30
+KPX f comma -30
+KPX f dotlessi -28
+KPX f e -30
+KPX f eacute -30
+KPX f ecaron -30
+KPX f ecircumflex -30
+KPX f edieresis -30
+KPX f edotaccent -30
+KPX f egrave -30
+KPX f emacron -30
+KPX f eogonek -30
+KPX f o -30
+KPX f oacute -30
+KPX f ocircumflex -30
+KPX f odieresis -30
+KPX f ograve -30
+KPX f ohungarumlaut -30
+KPX f omacron -30
+KPX f oslash -30
+KPX f otilde -30
+KPX f period -30
+KPX f quotedblright 60
+KPX f quoteright 50
+KPX g r -10
+KPX g racute -10
+KPX g rcaron -10
+KPX g rcommaaccent -10
+KPX gbreve r -10
+KPX gbreve racute -10
+KPX gbreve rcaron -10
+KPX gbreve rcommaaccent -10
+KPX gcommaaccent r -10
+KPX gcommaaccent racute -10
+KPX gcommaaccent rcaron -10
+KPX gcommaaccent rcommaaccent -10
+KPX h y -30
+KPX h yacute -30
+KPX h ydieresis -30
+KPX k e -20
+KPX k eacute -20
+KPX k ecaron -20
+KPX k ecircumflex -20
+KPX k edieresis -20
+KPX k edotaccent -20
+KPX k egrave -20
+KPX k emacron -20
+KPX k eogonek -20
+KPX k o -20
+KPX k oacute -20
+KPX k ocircumflex -20
+KPX k odieresis -20
+KPX k ograve -20
+KPX k ohungarumlaut -20
+KPX k omacron -20
+KPX k oslash -20
+KPX k otilde -20
+KPX kcommaaccent e -20
+KPX kcommaaccent eacute -20
+KPX kcommaaccent ecaron -20
+KPX kcommaaccent ecircumflex -20
+KPX kcommaaccent edieresis -20
+KPX kcommaaccent edotaccent -20
+KPX kcommaaccent egrave -20
+KPX kcommaaccent emacron -20
+KPX kcommaaccent eogonek -20
+KPX kcommaaccent o -20
+KPX kcommaaccent oacute -20
+KPX kcommaaccent ocircumflex -20
+KPX kcommaaccent odieresis -20
+KPX kcommaaccent ograve -20
+KPX kcommaaccent ohungarumlaut -20
+KPX kcommaaccent omacron -20
+KPX kcommaaccent oslash -20
+KPX kcommaaccent otilde -20
+KPX m u -10
+KPX m uacute -10
+KPX m ucircumflex -10
+KPX m udieresis -10
+KPX m ugrave -10
+KPX m uhungarumlaut -10
+KPX m umacron -10
+KPX m uogonek -10
+KPX m uring -10
+KPX m y -15
+KPX m yacute -15
+KPX m ydieresis -15
+KPX n u -10
+KPX n uacute -10
+KPX n ucircumflex -10
+KPX n udieresis -10
+KPX n ugrave -10
+KPX n uhungarumlaut -10
+KPX n umacron -10
+KPX n uogonek -10
+KPX n uring -10
+KPX n v -20
+KPX n y -15
+KPX n yacute -15
+KPX n ydieresis -15
+KPX nacute u -10
+KPX nacute uacute -10
+KPX nacute ucircumflex -10
+KPX nacute udieresis -10
+KPX nacute ugrave -10
+KPX nacute uhungarumlaut -10
+KPX nacute umacron -10
+KPX nacute uogonek -10
+KPX nacute uring -10
+KPX nacute v -20
+KPX nacute y -15
+KPX nacute yacute -15
+KPX nacute ydieresis -15
+KPX ncaron u -10
+KPX ncaron uacute -10
+KPX ncaron ucircumflex -10
+KPX ncaron udieresis -10
+KPX ncaron ugrave -10
+KPX ncaron uhungarumlaut -10
+KPX ncaron umacron -10
+KPX ncaron uogonek -10
+KPX ncaron uring -10
+KPX ncaron v -20
+KPX ncaron y -15
+KPX ncaron yacute -15
+KPX ncaron ydieresis -15
+KPX ncommaaccent u -10
+KPX ncommaaccent uacute -10
+KPX ncommaaccent ucircumflex -10
+KPX ncommaaccent udieresis -10
+KPX ncommaaccent ugrave -10
+KPX ncommaaccent uhungarumlaut -10
+KPX ncommaaccent umacron -10
+KPX ncommaaccent uogonek -10
+KPX ncommaaccent uring -10
+KPX ncommaaccent v -20
+KPX ncommaaccent y -15
+KPX ncommaaccent yacute -15
+KPX ncommaaccent ydieresis -15
+KPX ntilde u -10
+KPX ntilde uacute -10
+KPX ntilde ucircumflex -10
+KPX ntilde udieresis -10
+KPX ntilde ugrave -10
+KPX ntilde uhungarumlaut -10
+KPX ntilde umacron -10
+KPX ntilde uogonek -10
+KPX ntilde uring -10
+KPX ntilde v -20
+KPX ntilde y -15
+KPX ntilde yacute -15
+KPX ntilde ydieresis -15
+KPX o comma -40
+KPX o period -40
+KPX o v -15
+KPX o w -15
+KPX o x -30
+KPX o y -30
+KPX o yacute -30
+KPX o ydieresis -30
+KPX oacute comma -40
+KPX oacute period -40
+KPX oacute v -15
+KPX oacute w -15
+KPX oacute x -30
+KPX oacute y -30
+KPX oacute yacute -30
+KPX oacute ydieresis -30
+KPX ocircumflex comma -40
+KPX ocircumflex period -40
+KPX ocircumflex v -15
+KPX ocircumflex w -15
+KPX ocircumflex x -30
+KPX ocircumflex y -30
+KPX ocircumflex yacute -30
+KPX ocircumflex ydieresis -30
+KPX odieresis comma -40
+KPX odieresis period -40
+KPX odieresis v -15
+KPX odieresis w -15
+KPX odieresis x -30
+KPX odieresis y -30
+KPX odieresis yacute -30
+KPX odieresis ydieresis -30
+KPX ograve comma -40
+KPX ograve period -40
+KPX ograve v -15
+KPX ograve w -15
+KPX ograve x -30
+KPX ograve y -30
+KPX ograve yacute -30
+KPX ograve ydieresis -30
+KPX ohungarumlaut comma -40
+KPX ohungarumlaut period -40
+KPX ohungarumlaut v -15
+KPX ohungarumlaut w -15
+KPX ohungarumlaut x -30
+KPX ohungarumlaut y -30
+KPX ohungarumlaut yacute -30
+KPX ohungarumlaut ydieresis -30
+KPX omacron comma -40
+KPX omacron period -40
+KPX omacron v -15
+KPX omacron w -15
+KPX omacron x -30
+KPX omacron y -30
+KPX omacron yacute -30
+KPX omacron ydieresis -30
+KPX oslash a -55
+KPX oslash aacute -55
+KPX oslash abreve -55
+KPX oslash acircumflex -55
+KPX oslash adieresis -55
+KPX oslash agrave -55
+KPX oslash amacron -55
+KPX oslash aogonek -55
+KPX oslash aring -55
+KPX oslash atilde -55
+KPX oslash b -55
+KPX oslash c -55
+KPX oslash cacute -55
+KPX oslash ccaron -55
+KPX oslash ccedilla -55
+KPX oslash comma -95
+KPX oslash d -55
+KPX oslash dcroat -55
+KPX oslash e -55
+KPX oslash eacute -55
+KPX oslash ecaron -55
+KPX oslash ecircumflex -55
+KPX oslash edieresis -55
+KPX oslash edotaccent -55
+KPX oslash egrave -55
+KPX oslash emacron -55
+KPX oslash eogonek -55
+KPX oslash f -55
+KPX oslash g -55
+KPX oslash gbreve -55
+KPX oslash gcommaaccent -55
+KPX oslash h -55
+KPX oslash i -55
+KPX oslash iacute -55
+KPX oslash icircumflex -55
+KPX oslash idieresis -55
+KPX oslash igrave -55
+KPX oslash imacron -55
+KPX oslash iogonek -55
+KPX oslash j -55
+KPX oslash k -55
+KPX oslash kcommaaccent -55
+KPX oslash l -55
+KPX oslash lacute -55
+KPX oslash lcommaaccent -55
+KPX oslash lslash -55
+KPX oslash m -55
+KPX oslash n -55
+KPX oslash nacute -55
+KPX oslash ncaron -55
+KPX oslash ncommaaccent -55
+KPX oslash ntilde -55
+KPX oslash o -55
+KPX oslash oacute -55
+KPX oslash ocircumflex -55
+KPX oslash odieresis -55
+KPX oslash ograve -55
+KPX oslash ohungarumlaut -55
+KPX oslash omacron -55
+KPX oslash oslash -55
+KPX oslash otilde -55
+KPX oslash p -55
+KPX oslash period -95
+KPX oslash q -55
+KPX oslash r -55
+KPX oslash racute -55
+KPX oslash rcaron -55
+KPX oslash rcommaaccent -55
+KPX oslash s -55
+KPX oslash sacute -55
+KPX oslash scaron -55
+KPX oslash scedilla -55
+KPX oslash scommaaccent -55
+KPX oslash t -55
+KPX oslash tcommaaccent -55
+KPX oslash u -55
+KPX oslash uacute -55
+KPX oslash ucircumflex -55
+KPX oslash udieresis -55
+KPX oslash ugrave -55
+KPX oslash uhungarumlaut -55
+KPX oslash umacron -55
+KPX oslash uogonek -55
+KPX oslash uring -55
+KPX oslash v -70
+KPX oslash w -70
+KPX oslash x -85
+KPX oslash y -70
+KPX oslash yacute -70
+KPX oslash ydieresis -70
+KPX oslash z -55
+KPX oslash zacute -55
+KPX oslash zcaron -55
+KPX oslash zdotaccent -55
+KPX otilde comma -40
+KPX otilde period -40
+KPX otilde v -15
+KPX otilde w -15
+KPX otilde x -30
+KPX otilde y -30
+KPX otilde yacute -30
+KPX otilde ydieresis -30
+KPX p comma -35
+KPX p period -35
+KPX p y -30
+KPX p yacute -30
+KPX p ydieresis -30
+KPX period quotedblright -100
+KPX period quoteright -100
+KPX period space -60
+KPX quotedblright space -40
+KPX quoteleft quoteleft -57
+KPX quoteright d -50
+KPX quoteright dcroat -50
+KPX quoteright quoteright -57
+KPX quoteright r -50
+KPX quoteright racute -50
+KPX quoteright rcaron -50
+KPX quoteright rcommaaccent -50
+KPX quoteright s -50
+KPX quoteright sacute -50
+KPX quoteright scaron -50
+KPX quoteright scedilla -50
+KPX quoteright scommaaccent -50
+KPX quoteright space -70
+KPX r a -10
+KPX r aacute -10
+KPX r abreve -10
+KPX r acircumflex -10
+KPX r adieresis -10
+KPX r agrave -10
+KPX r amacron -10
+KPX r aogonek -10
+KPX r aring -10
+KPX r atilde -10
+KPX r colon 30
+KPX r comma -50
+KPX r i 15
+KPX r iacute 15
+KPX r icircumflex 15
+KPX r idieresis 15
+KPX r igrave 15
+KPX r imacron 15
+KPX r iogonek 15
+KPX r k 15
+KPX r kcommaaccent 15
+KPX r l 15
+KPX r lacute 15
+KPX r lcommaaccent 15
+KPX r lslash 15
+KPX r m 25
+KPX r n 25
+KPX r nacute 25
+KPX r ncaron 25
+KPX r ncommaaccent 25
+KPX r ntilde 25
+KPX r p 30
+KPX r period -50
+KPX r semicolon 30
+KPX r t 40
+KPX r tcommaaccent 40
+KPX r u 15
+KPX r uacute 15
+KPX r ucircumflex 15
+KPX r udieresis 15
+KPX r ugrave 15
+KPX r uhungarumlaut 15
+KPX r umacron 15
+KPX r uogonek 15
+KPX r uring 15
+KPX r v 30
+KPX r y 30
+KPX r yacute 30
+KPX r ydieresis 30
+KPX racute a -10
+KPX racute aacute -10
+KPX racute abreve -10
+KPX racute acircumflex -10
+KPX racute adieresis -10
+KPX racute agrave -10
+KPX racute amacron -10
+KPX racute aogonek -10
+KPX racute aring -10
+KPX racute atilde -10
+KPX racute colon 30
+KPX racute comma -50
+KPX racute i 15
+KPX racute iacute 15
+KPX racute icircumflex 15
+KPX racute idieresis 15
+KPX racute igrave 15
+KPX racute imacron 15
+KPX racute iogonek 15
+KPX racute k 15
+KPX racute kcommaaccent 15
+KPX racute l 15
+KPX racute lacute 15
+KPX racute lcommaaccent 15
+KPX racute lslash 15
+KPX racute m 25
+KPX racute n 25
+KPX racute nacute 25
+KPX racute ncaron 25
+KPX racute ncommaaccent 25
+KPX racute ntilde 25
+KPX racute p 30
+KPX racute period -50
+KPX racute semicolon 30
+KPX racute t 40
+KPX racute tcommaaccent 40
+KPX racute u 15
+KPX racute uacute 15
+KPX racute ucircumflex 15
+KPX racute udieresis 15
+KPX racute ugrave 15
+KPX racute uhungarumlaut 15
+KPX racute umacron 15
+KPX racute uogonek 15
+KPX racute uring 15
+KPX racute v 30
+KPX racute y 30
+KPX racute yacute 30
+KPX racute ydieresis 30
+KPX rcaron a -10
+KPX rcaron aacute -10
+KPX rcaron abreve -10
+KPX rcaron acircumflex -10
+KPX rcaron adieresis -10
+KPX rcaron agrave -10
+KPX rcaron amacron -10
+KPX rcaron aogonek -10
+KPX rcaron aring -10
+KPX rcaron atilde -10
+KPX rcaron colon 30
+KPX rcaron comma -50
+KPX rcaron i 15
+KPX rcaron iacute 15
+KPX rcaron icircumflex 15
+KPX rcaron idieresis 15
+KPX rcaron igrave 15
+KPX rcaron imacron 15
+KPX rcaron iogonek 15
+KPX rcaron k 15
+KPX rcaron kcommaaccent 15
+KPX rcaron l 15
+KPX rcaron lacute 15
+KPX rcaron lcommaaccent 15
+KPX rcaron lslash 15
+KPX rcaron m 25
+KPX rcaron n 25
+KPX rcaron nacute 25
+KPX rcaron ncaron 25
+KPX rcaron ncommaaccent 25
+KPX rcaron ntilde 25
+KPX rcaron p 30
+KPX rcaron period -50
+KPX rcaron semicolon 30
+KPX rcaron t 40
+KPX rcaron tcommaaccent 40
+KPX rcaron u 15
+KPX rcaron uacute 15
+KPX rcaron ucircumflex 15
+KPX rcaron udieresis 15
+KPX rcaron ugrave 15
+KPX rcaron uhungarumlaut 15
+KPX rcaron umacron 15
+KPX rcaron uogonek 15
+KPX rcaron uring 15
+KPX rcaron v 30
+KPX rcaron y 30
+KPX rcaron yacute 30
+KPX rcaron ydieresis 30
+KPX rcommaaccent a -10
+KPX rcommaaccent aacute -10
+KPX rcommaaccent abreve -10
+KPX rcommaaccent acircumflex -10
+KPX rcommaaccent adieresis -10
+KPX rcommaaccent agrave -10
+KPX rcommaaccent amacron -10
+KPX rcommaaccent aogonek -10
+KPX rcommaaccent aring -10
+KPX rcommaaccent atilde -10
+KPX rcommaaccent colon 30
+KPX rcommaaccent comma -50
+KPX rcommaaccent i 15
+KPX rcommaaccent iacute 15
+KPX rcommaaccent icircumflex 15
+KPX rcommaaccent idieresis 15
+KPX rcommaaccent igrave 15
+KPX rcommaaccent imacron 15
+KPX rcommaaccent iogonek 15
+KPX rcommaaccent k 15
+KPX rcommaaccent kcommaaccent 15
+KPX rcommaaccent l 15
+KPX rcommaaccent lacute 15
+KPX rcommaaccent lcommaaccent 15
+KPX rcommaaccent lslash 15
+KPX rcommaaccent m 25
+KPX rcommaaccent n 25
+KPX rcommaaccent nacute 25
+KPX rcommaaccent ncaron 25
+KPX rcommaaccent ncommaaccent 25
+KPX rcommaaccent ntilde 25
+KPX rcommaaccent p 30
+KPX rcommaaccent period -50
+KPX rcommaaccent semicolon 30
+KPX rcommaaccent t 40
+KPX rcommaaccent tcommaaccent 40
+KPX rcommaaccent u 15
+KPX rcommaaccent uacute 15
+KPX rcommaaccent ucircumflex 15
+KPX rcommaaccent udieresis 15
+KPX rcommaaccent ugrave 15
+KPX rcommaaccent uhungarumlaut 15
+KPX rcommaaccent umacron 15
+KPX rcommaaccent uogonek 15
+KPX rcommaaccent uring 15
+KPX rcommaaccent v 30
+KPX rcommaaccent y 30
+KPX rcommaaccent yacute 30
+KPX rcommaaccent ydieresis 30
+KPX s comma -15
+KPX s period -15
+KPX s w -30
+KPX sacute comma -15
+KPX sacute period -15
+KPX sacute w -30
+KPX scaron comma -15
+KPX scaron period -15
+KPX scaron w -30
+KPX scedilla comma -15
+KPX scedilla period -15
+KPX scedilla w -30
+KPX scommaaccent comma -15
+KPX scommaaccent period -15
+KPX scommaaccent w -30
+KPX semicolon space -50
+KPX space T -50
+KPX space Tcaron -50
+KPX space Tcommaaccent -50
+KPX space V -50
+KPX space W -40
+KPX space Y -90
+KPX space Yacute -90
+KPX space Ydieresis -90
+KPX space quotedblleft -30
+KPX space quoteleft -60
+KPX v a -25
+KPX v aacute -25
+KPX v abreve -25
+KPX v acircumflex -25
+KPX v adieresis -25
+KPX v agrave -25
+KPX v amacron -25
+KPX v aogonek -25
+KPX v aring -25
+KPX v atilde -25
+KPX v comma -80
+KPX v e -25
+KPX v eacute -25
+KPX v ecaron -25
+KPX v ecircumflex -25
+KPX v edieresis -25
+KPX v edotaccent -25
+KPX v egrave -25
+KPX v emacron -25
+KPX v eogonek -25
+KPX v o -25
+KPX v oacute -25
+KPX v ocircumflex -25
+KPX v odieresis -25
+KPX v ograve -25
+KPX v ohungarumlaut -25
+KPX v omacron -25
+KPX v oslash -25
+KPX v otilde -25
+KPX v period -80
+KPX w a -15
+KPX w aacute -15
+KPX w abreve -15
+KPX w acircumflex -15
+KPX w adieresis -15
+KPX w agrave -15
+KPX w amacron -15
+KPX w aogonek -15
+KPX w aring -15
+KPX w atilde -15
+KPX w comma -60
+KPX w e -10
+KPX w eacute -10
+KPX w ecaron -10
+KPX w ecircumflex -10
+KPX w edieresis -10
+KPX w edotaccent -10
+KPX w egrave -10
+KPX w emacron -10
+KPX w eogonek -10
+KPX w o -10
+KPX w oacute -10
+KPX w ocircumflex -10
+KPX w odieresis -10
+KPX w ograve -10
+KPX w ohungarumlaut -10
+KPX w omacron -10
+KPX w oslash -10
+KPX w otilde -10
+KPX w period -60
+KPX x e -30
+KPX x eacute -30
+KPX x ecaron -30
+KPX x ecircumflex -30
+KPX x edieresis -30
+KPX x edotaccent -30
+KPX x egrave -30
+KPX x emacron -30
+KPX x eogonek -30
+KPX y a -20
+KPX y aacute -20
+KPX y abreve -20
+KPX y acircumflex -20
+KPX y adieresis -20
+KPX y agrave -20
+KPX y amacron -20
+KPX y aogonek -20
+KPX y aring -20
+KPX y atilde -20
+KPX y comma -100
+KPX y e -20
+KPX y eacute -20
+KPX y ecaron -20
+KPX y ecircumflex -20
+KPX y edieresis -20
+KPX y edotaccent -20
+KPX y egrave -20
+KPX y emacron -20
+KPX y eogonek -20
+KPX y o -20
+KPX y oacute -20
+KPX y ocircumflex -20
+KPX y odieresis -20
+KPX y ograve -20
+KPX y ohungarumlaut -20
+KPX y omacron -20
+KPX y oslash -20
+KPX y otilde -20
+KPX y period -100
+KPX yacute a -20
+KPX yacute aacute -20
+KPX yacute abreve -20
+KPX yacute acircumflex -20
+KPX yacute adieresis -20
+KPX yacute agrave -20
+KPX yacute amacron -20
+KPX yacute aogonek -20
+KPX yacute aring -20
+KPX yacute atilde -20
+KPX yacute comma -100
+KPX yacute e -20
+KPX yacute eacute -20
+KPX yacute ecaron -20
+KPX yacute ecircumflex -20
+KPX yacute edieresis -20
+KPX yacute edotaccent -20
+KPX yacute egrave -20
+KPX yacute emacron -20
+KPX yacute eogonek -20
+KPX yacute o -20
+KPX yacute oacute -20
+KPX yacute ocircumflex -20
+KPX yacute odieresis -20
+KPX yacute ograve -20
+KPX yacute ohungarumlaut -20
+KPX yacute omacron -20
+KPX yacute oslash -20
+KPX yacute otilde -20
+KPX yacute period -100
+KPX ydieresis a -20
+KPX ydieresis aacute -20
+KPX ydieresis abreve -20
+KPX ydieresis acircumflex -20
+KPX ydieresis adieresis -20
+KPX ydieresis agrave -20
+KPX ydieresis amacron -20
+KPX ydieresis aogonek -20
+KPX ydieresis aring -20
+KPX ydieresis atilde -20
+KPX ydieresis comma -100
+KPX ydieresis e -20
+KPX ydieresis eacute -20
+KPX ydieresis ecaron -20
+KPX ydieresis ecircumflex -20
+KPX ydieresis edieresis -20
+KPX ydieresis edotaccent -20
+KPX ydieresis egrave -20
+KPX ydieresis emacron -20
+KPX ydieresis eogonek -20
+KPX ydieresis o -20
+KPX ydieresis oacute -20
+KPX ydieresis ocircumflex -20
+KPX ydieresis odieresis -20
+KPX ydieresis ograve -20
+KPX ydieresis ohungarumlaut -20
+KPX ydieresis omacron -20
+KPX ydieresis oslash -20
+KPX ydieresis otilde -20
+KPX ydieresis period -100
+KPX z e -15
+KPX z eacute -15
+KPX z ecaron -15
+KPX z ecircumflex -15
+KPX z edieresis -15
+KPX z edotaccent -15
+KPX z egrave -15
+KPX z emacron -15
+KPX z eogonek -15
+KPX z o -15
+KPX z oacute -15
+KPX z ocircumflex -15
+KPX z odieresis -15
+KPX z ograve -15
+KPX z ohungarumlaut -15
+KPX z omacron -15
+KPX z oslash -15
+KPX z otilde -15
+KPX zacute e -15
+KPX zacute eacute -15
+KPX zacute ecaron -15
+KPX zacute ecircumflex -15
+KPX zacute edieresis -15
+KPX zacute edotaccent -15
+KPX zacute egrave -15
+KPX zacute emacron -15
+KPX zacute eogonek -15
+KPX zacute o -15
+KPX zacute oacute -15
+KPX zacute ocircumflex -15
+KPX zacute odieresis -15
+KPX zacute ograve -15
+KPX zacute ohungarumlaut -15
+KPX zacute omacron -15
+KPX zacute oslash -15
+KPX zacute otilde -15
+KPX zcaron e -15
+KPX zcaron eacute -15
+KPX zcaron ecaron -15
+KPX zcaron ecircumflex -15
+KPX zcaron edieresis -15
+KPX zcaron edotaccent -15
+KPX zcaron egrave -15
+KPX zcaron emacron -15
+KPX zcaron eogonek -15
+KPX zcaron o -15
+KPX zcaron oacute -15
+KPX zcaron ocircumflex -15
+KPX zcaron odieresis -15
+KPX zcaron ograve -15
+KPX zcaron ohungarumlaut -15
+KPX zcaron omacron -15
+KPX zcaron oslash -15
+KPX zcaron otilde -15
+KPX zdotaccent e -15
+KPX zdotaccent eacute -15
+KPX zdotaccent ecaron -15
+KPX zdotaccent ecircumflex -15
+KPX zdotaccent edieresis -15
+KPX zdotaccent edotaccent -15
+KPX zdotaccent egrave -15
+KPX zdotaccent emacron -15
+KPX zdotaccent eogonek -15
+KPX zdotaccent o -15
+KPX zdotaccent oacute -15
+KPX zdotaccent ocircumflex -15
+KPX zdotaccent odieresis -15
+KPX zdotaccent ograve -15
+KPX zdotaccent ohungarumlaut -15
+KPX zdotaccent omacron -15
+KPX zdotaccent oslash -15
+KPX zdotaccent otilde -15
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Symbol.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Symbol.afm
new file mode 100644
index 0000000000000000000000000000000000000000..6a5386a9190eda3d7e9d4b49cfa7a6dd971c9e70
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Symbol.afm
@@ -0,0 +1,213 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Thu May 1 15:12:25 1997
+Comment UniqueID 43064
+Comment VMusage 30820 39997
+FontName Symbol
+FullName Symbol
+FamilyName Symbol
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet Special
+FontBBox -180 -293 1090 1010
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.008
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All rights reserved.
+EncodingScheme FontSpecific
+StdHW 92
+StdVW 85
+StartCharMetrics 190
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 128 -17 240 672 ;
+C 34 ; WX 713 ; N universal ; B 31 0 681 705 ;
+C 35 ; WX 500 ; N numbersign ; B 20 -16 481 673 ;
+C 36 ; WX 549 ; N existential ; B 25 0 478 707 ;
+C 37 ; WX 833 ; N percent ; B 63 -36 771 655 ;
+C 38 ; WX 778 ; N ampersand ; B 41 -18 750 661 ;
+C 39 ; WX 439 ; N suchthat ; B 48 -17 414 500 ;
+C 40 ; WX 333 ; N parenleft ; B 53 -191 300 673 ;
+C 41 ; WX 333 ; N parenright ; B 30 -191 277 673 ;
+C 42 ; WX 500 ; N asteriskmath ; B 65 134 427 551 ;
+C 43 ; WX 549 ; N plus ; B 10 0 539 533 ;
+C 44 ; WX 250 ; N comma ; B 56 -152 194 104 ;
+C 45 ; WX 549 ; N minus ; B 11 233 535 288 ;
+C 46 ; WX 250 ; N period ; B 69 -17 181 95 ;
+C 47 ; WX 278 ; N slash ; B 0 -18 254 646 ;
+C 48 ; WX 500 ; N zero ; B 24 -14 476 685 ;
+C 49 ; WX 500 ; N one ; B 117 0 390 673 ;
+C 50 ; WX 500 ; N two ; B 25 0 475 685 ;
+C 51 ; WX 500 ; N three ; B 43 -14 435 685 ;
+C 52 ; WX 500 ; N four ; B 15 0 469 685 ;
+C 53 ; WX 500 ; N five ; B 32 -14 445 690 ;
+C 54 ; WX 500 ; N six ; B 34 -14 468 685 ;
+C 55 ; WX 500 ; N seven ; B 24 -16 448 673 ;
+C 56 ; WX 500 ; N eight ; B 56 -14 445 685 ;
+C 57 ; WX 500 ; N nine ; B 30 -18 459 685 ;
+C 58 ; WX 278 ; N colon ; B 81 -17 193 460 ;
+C 59 ; WX 278 ; N semicolon ; B 83 -152 221 460 ;
+C 60 ; WX 549 ; N less ; B 26 0 523 522 ;
+C 61 ; WX 549 ; N equal ; B 11 141 537 390 ;
+C 62 ; WX 549 ; N greater ; B 26 0 523 522 ;
+C 63 ; WX 444 ; N question ; B 70 -17 412 686 ;
+C 64 ; WX 549 ; N congruent ; B 11 0 537 475 ;
+C 65 ; WX 722 ; N Alpha ; B 4 0 684 673 ;
+C 66 ; WX 667 ; N Beta ; B 29 0 592 673 ;
+C 67 ; WX 722 ; N Chi ; B -9 0 704 673 ;
+C 68 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 69 ; WX 611 ; N Epsilon ; B 32 0 617 673 ;
+C 70 ; WX 763 ; N Phi ; B 26 0 741 673 ;
+C 71 ; WX 603 ; N Gamma ; B 24 0 609 673 ;
+C 72 ; WX 722 ; N Eta ; B 39 0 729 673 ;
+C 73 ; WX 333 ; N Iota ; B 32 0 316 673 ;
+C 74 ; WX 631 ; N theta1 ; B 18 -18 623 689 ;
+C 75 ; WX 722 ; N Kappa ; B 35 0 722 673 ;
+C 76 ; WX 686 ; N Lambda ; B 6 0 680 688 ;
+C 77 ; WX 889 ; N Mu ; B 28 0 887 673 ;
+C 78 ; WX 722 ; N Nu ; B 29 -8 720 673 ;
+C 79 ; WX 722 ; N Omicron ; B 41 -17 715 685 ;
+C 80 ; WX 768 ; N Pi ; B 25 0 745 673 ;
+C 81 ; WX 741 ; N Theta ; B 41 -17 715 685 ;
+C 82 ; WX 556 ; N Rho ; B 28 0 563 673 ;
+C 83 ; WX 592 ; N Sigma ; B 5 0 589 673 ;
+C 84 ; WX 611 ; N Tau ; B 33 0 607 673 ;
+C 85 ; WX 690 ; N Upsilon ; B -8 0 694 673 ;
+C 86 ; WX 439 ; N sigma1 ; B 40 -233 436 500 ;
+C 87 ; WX 768 ; N Omega ; B 34 0 736 688 ;
+C 88 ; WX 645 ; N Xi ; B 40 0 599 673 ;
+C 89 ; WX 795 ; N Psi ; B 15 0 781 684 ;
+C 90 ; WX 611 ; N Zeta ; B 44 0 636 673 ;
+C 91 ; WX 333 ; N bracketleft ; B 86 -155 299 674 ;
+C 92 ; WX 863 ; N therefore ; B 163 0 701 487 ;
+C 93 ; WX 333 ; N bracketright ; B 33 -155 246 674 ;
+C 94 ; WX 658 ; N perpendicular ; B 15 0 652 674 ;
+C 95 ; WX 500 ; N underscore ; B -2 -125 502 -75 ;
+C 96 ; WX 500 ; N radicalex ; B 480 881 1090 917 ;
+C 97 ; WX 631 ; N alpha ; B 41 -18 622 500 ;
+C 98 ; WX 549 ; N beta ; B 61 -223 515 741 ;
+C 99 ; WX 549 ; N chi ; B 12 -231 522 499 ;
+C 100 ; WX 494 ; N delta ; B 40 -19 481 740 ;
+C 101 ; WX 439 ; N epsilon ; B 22 -19 427 502 ;
+C 102 ; WX 521 ; N phi ; B 28 -224 492 673 ;
+C 103 ; WX 411 ; N gamma ; B 5 -225 484 499 ;
+C 104 ; WX 603 ; N eta ; B 0 -202 527 514 ;
+C 105 ; WX 329 ; N iota ; B 0 -17 301 503 ;
+C 106 ; WX 603 ; N phi1 ; B 36 -224 587 499 ;
+C 107 ; WX 549 ; N kappa ; B 33 0 558 501 ;
+C 108 ; WX 549 ; N lambda ; B 24 -17 548 739 ;
+C 109 ; WX 576 ; N mu ; B 33 -223 567 500 ;
+C 110 ; WX 521 ; N nu ; B -9 -16 475 507 ;
+C 111 ; WX 549 ; N omicron ; B 35 -19 501 499 ;
+C 112 ; WX 549 ; N pi ; B 10 -19 530 487 ;
+C 113 ; WX 521 ; N theta ; B 43 -17 485 690 ;
+C 114 ; WX 549 ; N rho ; B 50 -230 490 499 ;
+C 115 ; WX 603 ; N sigma ; B 30 -21 588 500 ;
+C 116 ; WX 439 ; N tau ; B 10 -19 418 500 ;
+C 117 ; WX 576 ; N upsilon ; B 7 -18 535 507 ;
+C 118 ; WX 713 ; N omega1 ; B 12 -18 671 583 ;
+C 119 ; WX 686 ; N omega ; B 42 -17 684 500 ;
+C 120 ; WX 493 ; N xi ; B 27 -224 469 766 ;
+C 121 ; WX 686 ; N psi ; B 12 -228 701 500 ;
+C 122 ; WX 494 ; N zeta ; B 60 -225 467 756 ;
+C 123 ; WX 480 ; N braceleft ; B 58 -183 397 673 ;
+C 124 ; WX 200 ; N bar ; B 65 -293 135 707 ;
+C 125 ; WX 480 ; N braceright ; B 79 -183 418 673 ;
+C 126 ; WX 549 ; N similar ; B 17 203 529 307 ;
+C 160 ; WX 750 ; N Euro ; B 20 -12 714 685 ;
+C 161 ; WX 620 ; N Upsilon1 ; B -2 0 610 685 ;
+C 162 ; WX 247 ; N minute ; B 27 459 228 735 ;
+C 163 ; WX 549 ; N lessequal ; B 29 0 526 639 ;
+C 164 ; WX 167 ; N fraction ; B -180 -12 340 677 ;
+C 165 ; WX 713 ; N infinity ; B 26 124 688 404 ;
+C 166 ; WX 500 ; N florin ; B 2 -193 494 686 ;
+C 167 ; WX 753 ; N club ; B 86 -26 660 533 ;
+C 168 ; WX 753 ; N diamond ; B 142 -36 600 550 ;
+C 169 ; WX 753 ; N heart ; B 117 -33 631 532 ;
+C 170 ; WX 753 ; N spade ; B 113 -36 629 548 ;
+C 171 ; WX 1042 ; N arrowboth ; B 24 -15 1024 511 ;
+C 172 ; WX 987 ; N arrowleft ; B 32 -15 942 511 ;
+C 173 ; WX 603 ; N arrowup ; B 45 0 571 910 ;
+C 174 ; WX 987 ; N arrowright ; B 49 -15 959 511 ;
+C 175 ; WX 603 ; N arrowdown ; B 45 -22 571 888 ;
+C 176 ; WX 400 ; N degree ; B 50 385 350 685 ;
+C 177 ; WX 549 ; N plusminus ; B 10 0 539 645 ;
+C 178 ; WX 411 ; N second ; B 20 459 413 737 ;
+C 179 ; WX 549 ; N greaterequal ; B 29 0 526 639 ;
+C 180 ; WX 549 ; N multiply ; B 17 8 533 524 ;
+C 181 ; WX 713 ; N proportional ; B 27 123 639 404 ;
+C 182 ; WX 494 ; N partialdiff ; B 26 -20 462 746 ;
+C 183 ; WX 460 ; N bullet ; B 50 113 410 473 ;
+C 184 ; WX 549 ; N divide ; B 10 71 536 456 ;
+C 185 ; WX 549 ; N notequal ; B 15 -25 540 549 ;
+C 186 ; WX 549 ; N equivalence ; B 14 82 538 443 ;
+C 187 ; WX 549 ; N approxequal ; B 14 135 527 394 ;
+C 188 ; WX 1000 ; N ellipsis ; B 111 -17 889 95 ;
+C 189 ; WX 603 ; N arrowvertex ; B 280 -120 336 1010 ;
+C 190 ; WX 1000 ; N arrowhorizex ; B -60 220 1050 276 ;
+C 191 ; WX 658 ; N carriagereturn ; B 15 -16 602 629 ;
+C 192 ; WX 823 ; N aleph ; B 175 -18 661 658 ;
+C 193 ; WX 686 ; N Ifraktur ; B 10 -53 578 740 ;
+C 194 ; WX 795 ; N Rfraktur ; B 26 -15 759 734 ;
+C 195 ; WX 987 ; N weierstrass ; B 159 -211 870 573 ;
+C 196 ; WX 768 ; N circlemultiply ; B 43 -17 733 673 ;
+C 197 ; WX 768 ; N circleplus ; B 43 -15 733 675 ;
+C 198 ; WX 823 ; N emptyset ; B 39 -24 781 719 ;
+C 199 ; WX 768 ; N intersection ; B 40 0 732 509 ;
+C 200 ; WX 768 ; N union ; B 40 -17 732 492 ;
+C 201 ; WX 713 ; N propersuperset ; B 20 0 673 470 ;
+C 202 ; WX 713 ; N reflexsuperset ; B 20 -125 673 470 ;
+C 203 ; WX 713 ; N notsubset ; B 36 -70 690 540 ;
+C 204 ; WX 713 ; N propersubset ; B 37 0 690 470 ;
+C 205 ; WX 713 ; N reflexsubset ; B 37 -125 690 470 ;
+C 206 ; WX 713 ; N element ; B 45 0 505 468 ;
+C 207 ; WX 713 ; N notelement ; B 45 -58 505 555 ;
+C 208 ; WX 768 ; N angle ; B 26 0 738 673 ;
+C 209 ; WX 713 ; N gradient ; B 36 -19 681 718 ;
+C 210 ; WX 790 ; N registerserif ; B 50 -17 740 673 ;
+C 211 ; WX 790 ; N copyrightserif ; B 51 -15 741 675 ;
+C 212 ; WX 890 ; N trademarkserif ; B 18 293 855 673 ;
+C 213 ; WX 823 ; N product ; B 25 -101 803 751 ;
+C 214 ; WX 549 ; N radical ; B 10 -38 515 917 ;
+C 215 ; WX 250 ; N dotmath ; B 69 210 169 310 ;
+C 216 ; WX 713 ; N logicalnot ; B 15 0 680 288 ;
+C 217 ; WX 603 ; N logicaland ; B 23 0 583 454 ;
+C 218 ; WX 603 ; N logicalor ; B 30 0 578 477 ;
+C 219 ; WX 1042 ; N arrowdblboth ; B 27 -20 1023 510 ;
+C 220 ; WX 987 ; N arrowdblleft ; B 30 -15 939 513 ;
+C 221 ; WX 603 ; N arrowdblup ; B 39 2 567 911 ;
+C 222 ; WX 987 ; N arrowdblright ; B 45 -20 954 508 ;
+C 223 ; WX 603 ; N arrowdbldown ; B 44 -19 572 890 ;
+C 224 ; WX 494 ; N lozenge ; B 18 0 466 745 ;
+C 225 ; WX 329 ; N angleleft ; B 25 -198 306 746 ;
+C 226 ; WX 790 ; N registersans ; B 50 -20 740 670 ;
+C 227 ; WX 790 ; N copyrightsans ; B 49 -15 739 675 ;
+C 228 ; WX 786 ; N trademarksans ; B 5 293 725 673 ;
+C 229 ; WX 713 ; N summation ; B 14 -108 695 752 ;
+C 230 ; WX 384 ; N parenlefttp ; B 24 -293 436 926 ;
+C 231 ; WX 384 ; N parenleftex ; B 24 -85 108 925 ;
+C 232 ; WX 384 ; N parenleftbt ; B 24 -293 436 926 ;
+C 233 ; WX 384 ; N bracketlefttp ; B 0 -80 349 926 ;
+C 234 ; WX 384 ; N bracketleftex ; B 0 -79 77 925 ;
+C 235 ; WX 384 ; N bracketleftbt ; B 0 -80 349 926 ;
+C 236 ; WX 494 ; N bracelefttp ; B 209 -85 445 925 ;
+C 237 ; WX 494 ; N braceleftmid ; B 20 -85 284 935 ;
+C 238 ; WX 494 ; N braceleftbt ; B 209 -75 445 935 ;
+C 239 ; WX 494 ; N braceex ; B 209 -85 284 935 ;
+C 241 ; WX 329 ; N angleright ; B 21 -198 302 746 ;
+C 242 ; WX 274 ; N integral ; B 2 -107 291 916 ;
+C 243 ; WX 686 ; N integraltp ; B 308 -88 675 920 ;
+C 244 ; WX 686 ; N integralex ; B 308 -88 378 975 ;
+C 245 ; WX 686 ; N integralbt ; B 11 -87 378 921 ;
+C 246 ; WX 384 ; N parenrighttp ; B 54 -293 466 926 ;
+C 247 ; WX 384 ; N parenrightex ; B 382 -85 466 925 ;
+C 248 ; WX 384 ; N parenrightbt ; B 54 -293 466 926 ;
+C 249 ; WX 384 ; N bracketrighttp ; B 22 -80 371 926 ;
+C 250 ; WX 384 ; N bracketrightex ; B 294 -79 371 925 ;
+C 251 ; WX 384 ; N bracketrightbt ; B 22 -80 371 926 ;
+C 252 ; WX 494 ; N bracerighttp ; B 48 -85 284 925 ;
+C 253 ; WX 494 ; N bracerightmid ; B 209 -85 473 935 ;
+C 254 ; WX 494 ; N bracerightbt ; B 48 -75 284 935 ;
+C -1 ; WX 790 ; N apple ; B 56 -3 733 808 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-Bold.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-Bold.afm
new file mode 100644
index 0000000000000000000000000000000000000000..559ebaeb6f099c547502c0e5186df745e36774a7
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-Bold.afm
@@ -0,0 +1,2588 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:52:56 1997
+Comment UniqueID 43065
+Comment VMusage 41636 52661
+FontName Times-Bold
+FullName Times Bold
+FamilyName Times
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -168 -218 1000 935
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 676
+XHeight 461
+Ascender 683
+Descender -217
+StdHW 44
+StdVW 139
+StartCharMetrics 315
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 81 -13 251 691 ;
+C 34 ; WX 555 ; N quotedbl ; B 83 404 472 691 ;
+C 35 ; WX 500 ; N numbersign ; B 4 0 496 700 ;
+C 36 ; WX 500 ; N dollar ; B 29 -99 472 750 ;
+C 37 ; WX 1000 ; N percent ; B 124 -14 877 692 ;
+C 38 ; WX 833 ; N ampersand ; B 62 -16 787 691 ;
+C 39 ; WX 333 ; N quoteright ; B 79 356 263 691 ;
+C 40 ; WX 333 ; N parenleft ; B 46 -168 306 694 ;
+C 41 ; WX 333 ; N parenright ; B 27 -168 287 694 ;
+C 42 ; WX 500 ; N asterisk ; B 56 255 447 691 ;
+C 43 ; WX 570 ; N plus ; B 33 0 537 506 ;
+C 44 ; WX 250 ; N comma ; B 39 -180 223 155 ;
+C 45 ; WX 333 ; N hyphen ; B 44 171 287 287 ;
+C 46 ; WX 250 ; N period ; B 41 -13 210 156 ;
+C 47 ; WX 278 ; N slash ; B -24 -19 302 691 ;
+C 48 ; WX 500 ; N zero ; B 24 -13 476 688 ;
+C 49 ; WX 500 ; N one ; B 65 0 442 688 ;
+C 50 ; WX 500 ; N two ; B 17 0 478 688 ;
+C 51 ; WX 500 ; N three ; B 16 -14 468 688 ;
+C 52 ; WX 500 ; N four ; B 19 0 475 688 ;
+C 53 ; WX 500 ; N five ; B 22 -8 470 676 ;
+C 54 ; WX 500 ; N six ; B 28 -13 475 688 ;
+C 55 ; WX 500 ; N seven ; B 17 0 477 676 ;
+C 56 ; WX 500 ; N eight ; B 28 -13 472 688 ;
+C 57 ; WX 500 ; N nine ; B 26 -13 473 688 ;
+C 58 ; WX 333 ; N colon ; B 82 -13 251 472 ;
+C 59 ; WX 333 ; N semicolon ; B 82 -180 266 472 ;
+C 60 ; WX 570 ; N less ; B 31 -8 539 514 ;
+C 61 ; WX 570 ; N equal ; B 33 107 537 399 ;
+C 62 ; WX 570 ; N greater ; B 31 -8 539 514 ;
+C 63 ; WX 500 ; N question ; B 57 -13 445 689 ;
+C 64 ; WX 930 ; N at ; B 108 -19 822 691 ;
+C 65 ; WX 722 ; N A ; B 9 0 689 690 ;
+C 66 ; WX 667 ; N B ; B 16 0 619 676 ;
+C 67 ; WX 722 ; N C ; B 49 -19 687 691 ;
+C 68 ; WX 722 ; N D ; B 14 0 690 676 ;
+C 69 ; WX 667 ; N E ; B 16 0 641 676 ;
+C 70 ; WX 611 ; N F ; B 16 0 583 676 ;
+C 71 ; WX 778 ; N G ; B 37 -19 755 691 ;
+C 72 ; WX 778 ; N H ; B 21 0 759 676 ;
+C 73 ; WX 389 ; N I ; B 20 0 370 676 ;
+C 74 ; WX 500 ; N J ; B 3 -96 479 676 ;
+C 75 ; WX 778 ; N K ; B 30 0 769 676 ;
+C 76 ; WX 667 ; N L ; B 19 0 638 676 ;
+C 77 ; WX 944 ; N M ; B 14 0 921 676 ;
+C 78 ; WX 722 ; N N ; B 16 -18 701 676 ;
+C 79 ; WX 778 ; N O ; B 35 -19 743 691 ;
+C 80 ; WX 611 ; N P ; B 16 0 600 676 ;
+C 81 ; WX 778 ; N Q ; B 35 -176 743 691 ;
+C 82 ; WX 722 ; N R ; B 26 0 715 676 ;
+C 83 ; WX 556 ; N S ; B 35 -19 513 692 ;
+C 84 ; WX 667 ; N T ; B 31 0 636 676 ;
+C 85 ; WX 722 ; N U ; B 16 -19 701 676 ;
+C 86 ; WX 722 ; N V ; B 16 -18 701 676 ;
+C 87 ; WX 1000 ; N W ; B 19 -15 981 676 ;
+C 88 ; WX 722 ; N X ; B 16 0 699 676 ;
+C 89 ; WX 722 ; N Y ; B 15 0 699 676 ;
+C 90 ; WX 667 ; N Z ; B 28 0 634 676 ;
+C 91 ; WX 333 ; N bracketleft ; B 67 -149 301 678 ;
+C 92 ; WX 278 ; N backslash ; B -25 -19 303 691 ;
+C 93 ; WX 333 ; N bracketright ; B 32 -149 266 678 ;
+C 94 ; WX 581 ; N asciicircum ; B 73 311 509 676 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 333 ; N quoteleft ; B 70 356 254 691 ;
+C 97 ; WX 500 ; N a ; B 25 -14 488 473 ;
+C 98 ; WX 556 ; N b ; B 17 -14 521 676 ;
+C 99 ; WX 444 ; N c ; B 25 -14 430 473 ;
+C 100 ; WX 556 ; N d ; B 25 -14 534 676 ;
+C 101 ; WX 444 ; N e ; B 25 -14 426 473 ;
+C 102 ; WX 333 ; N f ; B 14 0 389 691 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B 28 -206 483 473 ;
+C 104 ; WX 556 ; N h ; B 16 0 534 676 ;
+C 105 ; WX 278 ; N i ; B 16 0 255 691 ;
+C 106 ; WX 333 ; N j ; B -57 -203 263 691 ;
+C 107 ; WX 556 ; N k ; B 22 0 543 676 ;
+C 108 ; WX 278 ; N l ; B 16 0 255 676 ;
+C 109 ; WX 833 ; N m ; B 16 0 814 473 ;
+C 110 ; WX 556 ; N n ; B 21 0 539 473 ;
+C 111 ; WX 500 ; N o ; B 25 -14 476 473 ;
+C 112 ; WX 556 ; N p ; B 19 -205 524 473 ;
+C 113 ; WX 556 ; N q ; B 34 -205 536 473 ;
+C 114 ; WX 444 ; N r ; B 29 0 434 473 ;
+C 115 ; WX 389 ; N s ; B 25 -14 361 473 ;
+C 116 ; WX 333 ; N t ; B 20 -12 332 630 ;
+C 117 ; WX 556 ; N u ; B 16 -14 537 461 ;
+C 118 ; WX 500 ; N v ; B 21 -14 485 461 ;
+C 119 ; WX 722 ; N w ; B 23 -14 707 461 ;
+C 120 ; WX 500 ; N x ; B 12 0 484 461 ;
+C 121 ; WX 500 ; N y ; B 16 -205 480 461 ;
+C 122 ; WX 444 ; N z ; B 21 0 420 461 ;
+C 123 ; WX 394 ; N braceleft ; B 22 -175 340 698 ;
+C 124 ; WX 220 ; N bar ; B 66 -218 154 782 ;
+C 125 ; WX 394 ; N braceright ; B 54 -175 372 698 ;
+C 126 ; WX 520 ; N asciitilde ; B 29 173 491 333 ;
+C 161 ; WX 333 ; N exclamdown ; B 82 -203 252 501 ;
+C 162 ; WX 500 ; N cent ; B 53 -140 458 588 ;
+C 163 ; WX 500 ; N sterling ; B 21 -14 477 684 ;
+C 164 ; WX 167 ; N fraction ; B -168 -12 329 688 ;
+C 165 ; WX 500 ; N yen ; B -64 0 547 676 ;
+C 166 ; WX 500 ; N florin ; B 0 -155 498 706 ;
+C 167 ; WX 500 ; N section ; B 57 -132 443 691 ;
+C 168 ; WX 500 ; N currency ; B -26 61 526 613 ;
+C 169 ; WX 278 ; N quotesingle ; B 75 404 204 691 ;
+C 170 ; WX 500 ; N quotedblleft ; B 32 356 486 691 ;
+C 171 ; WX 500 ; N guillemotleft ; B 23 36 473 415 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 51 36 305 415 ;
+C 173 ; WX 333 ; N guilsinglright ; B 28 36 282 415 ;
+C 174 ; WX 556 ; N fi ; B 14 0 536 691 ;
+C 175 ; WX 556 ; N fl ; B 14 0 536 691 ;
+C 177 ; WX 500 ; N endash ; B 0 181 500 271 ;
+C 178 ; WX 500 ; N dagger ; B 47 -134 453 691 ;
+C 179 ; WX 500 ; N daggerdbl ; B 45 -132 456 691 ;
+C 180 ; WX 250 ; N periodcentered ; B 41 248 210 417 ;
+C 182 ; WX 540 ; N paragraph ; B 0 -186 519 676 ;
+C 183 ; WX 350 ; N bullet ; B 35 198 315 478 ;
+C 184 ; WX 333 ; N quotesinglbase ; B 79 -180 263 155 ;
+C 185 ; WX 500 ; N quotedblbase ; B 14 -180 468 155 ;
+C 186 ; WX 500 ; N quotedblright ; B 14 356 468 691 ;
+C 187 ; WX 500 ; N guillemotright ; B 27 36 477 415 ;
+C 188 ; WX 1000 ; N ellipsis ; B 82 -13 917 156 ;
+C 189 ; WX 1000 ; N perthousand ; B 7 -29 995 706 ;
+C 191 ; WX 500 ; N questiondown ; B 55 -201 443 501 ;
+C 193 ; WX 333 ; N grave ; B 8 528 246 713 ;
+C 194 ; WX 333 ; N acute ; B 86 528 324 713 ;
+C 195 ; WX 333 ; N circumflex ; B -2 528 335 704 ;
+C 196 ; WX 333 ; N tilde ; B -16 547 349 674 ;
+C 197 ; WX 333 ; N macron ; B 1 565 331 637 ;
+C 198 ; WX 333 ; N breve ; B 15 528 318 691 ;
+C 199 ; WX 333 ; N dotaccent ; B 103 536 258 691 ;
+C 200 ; WX 333 ; N dieresis ; B -2 537 335 667 ;
+C 202 ; WX 333 ; N ring ; B 60 527 273 740 ;
+C 203 ; WX 333 ; N cedilla ; B 68 -218 294 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B -13 528 425 713 ;
+C 206 ; WX 333 ; N ogonek ; B 90 -193 319 24 ;
+C 207 ; WX 333 ; N caron ; B -2 528 335 704 ;
+C 208 ; WX 1000 ; N emdash ; B 0 181 1000 271 ;
+C 225 ; WX 1000 ; N AE ; B 4 0 951 676 ;
+C 227 ; WX 300 ; N ordfeminine ; B -1 397 301 688 ;
+C 232 ; WX 667 ; N Lslash ; B 19 0 638 676 ;
+C 233 ; WX 778 ; N Oslash ; B 35 -74 743 737 ;
+C 234 ; WX 1000 ; N OE ; B 22 -5 981 684 ;
+C 235 ; WX 330 ; N ordmasculine ; B 18 397 312 688 ;
+C 241 ; WX 722 ; N ae ; B 33 -14 693 473 ;
+C 245 ; WX 278 ; N dotlessi ; B 16 0 255 461 ;
+C 248 ; WX 278 ; N lslash ; B -22 0 303 676 ;
+C 249 ; WX 500 ; N oslash ; B 25 -92 476 549 ;
+C 250 ; WX 722 ; N oe ; B 22 -14 696 473 ;
+C 251 ; WX 556 ; N germandbls ; B 19 -12 517 691 ;
+C -1 ; WX 389 ; N Idieresis ; B 20 0 370 877 ;
+C -1 ; WX 444 ; N eacute ; B 25 -14 426 713 ;
+C -1 ; WX 500 ; N abreve ; B 25 -14 488 691 ;
+C -1 ; WX 556 ; N uhungarumlaut ; B 16 -14 557 713 ;
+C -1 ; WX 444 ; N ecaron ; B 25 -14 426 704 ;
+C -1 ; WX 722 ; N Ydieresis ; B 15 0 699 877 ;
+C -1 ; WX 570 ; N divide ; B 33 -31 537 537 ;
+C -1 ; WX 722 ; N Yacute ; B 15 0 699 923 ;
+C -1 ; WX 722 ; N Acircumflex ; B 9 0 689 914 ;
+C -1 ; WX 500 ; N aacute ; B 25 -14 488 713 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 16 -19 701 914 ;
+C -1 ; WX 500 ; N yacute ; B 16 -205 480 713 ;
+C -1 ; WX 389 ; N scommaaccent ; B 25 -218 361 473 ;
+C -1 ; WX 444 ; N ecircumflex ; B 25 -14 426 704 ;
+C -1 ; WX 722 ; N Uring ; B 16 -19 701 935 ;
+C -1 ; WX 722 ; N Udieresis ; B 16 -19 701 877 ;
+C -1 ; WX 500 ; N aogonek ; B 25 -193 504 473 ;
+C -1 ; WX 722 ; N Uacute ; B 16 -19 701 923 ;
+C -1 ; WX 556 ; N uogonek ; B 16 -193 539 461 ;
+C -1 ; WX 667 ; N Edieresis ; B 16 0 641 877 ;
+C -1 ; WX 722 ; N Dcroat ; B 6 0 690 676 ;
+C -1 ; WX 250 ; N commaaccent ; B 47 -218 203 -50 ;
+C -1 ; WX 747 ; N copyright ; B 26 -19 721 691 ;
+C -1 ; WX 667 ; N Emacron ; B 16 0 641 847 ;
+C -1 ; WX 444 ; N ccaron ; B 25 -14 430 704 ;
+C -1 ; WX 500 ; N aring ; B 25 -14 488 740 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 16 -188 701 676 ;
+C -1 ; WX 278 ; N lacute ; B 16 0 297 923 ;
+C -1 ; WX 500 ; N agrave ; B 25 -14 488 713 ;
+C -1 ; WX 667 ; N Tcommaaccent ; B 31 -218 636 676 ;
+C -1 ; WX 722 ; N Cacute ; B 49 -19 687 923 ;
+C -1 ; WX 500 ; N atilde ; B 25 -14 488 674 ;
+C -1 ; WX 667 ; N Edotaccent ; B 16 0 641 901 ;
+C -1 ; WX 389 ; N scaron ; B 25 -14 363 704 ;
+C -1 ; WX 389 ; N scedilla ; B 25 -218 361 473 ;
+C -1 ; WX 278 ; N iacute ; B 16 0 289 713 ;
+C -1 ; WX 494 ; N lozenge ; B 10 0 484 745 ;
+C -1 ; WX 722 ; N Rcaron ; B 26 0 715 914 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 37 -218 755 691 ;
+C -1 ; WX 556 ; N ucircumflex ; B 16 -14 537 704 ;
+C -1 ; WX 500 ; N acircumflex ; B 25 -14 488 704 ;
+C -1 ; WX 722 ; N Amacron ; B 9 0 689 847 ;
+C -1 ; WX 444 ; N rcaron ; B 29 0 434 704 ;
+C -1 ; WX 444 ; N ccedilla ; B 25 -218 430 473 ;
+C -1 ; WX 667 ; N Zdotaccent ; B 28 0 634 901 ;
+C -1 ; WX 611 ; N Thorn ; B 16 0 600 676 ;
+C -1 ; WX 778 ; N Omacron ; B 35 -19 743 847 ;
+C -1 ; WX 722 ; N Racute ; B 26 0 715 923 ;
+C -1 ; WX 556 ; N Sacute ; B 35 -19 513 923 ;
+C -1 ; WX 672 ; N dcaron ; B 25 -14 681 682 ;
+C -1 ; WX 722 ; N Umacron ; B 16 -19 701 847 ;
+C -1 ; WX 556 ; N uring ; B 16 -14 537 740 ;
+C -1 ; WX 300 ; N threesuperior ; B 3 268 297 688 ;
+C -1 ; WX 778 ; N Ograve ; B 35 -19 743 923 ;
+C -1 ; WX 722 ; N Agrave ; B 9 0 689 923 ;
+C -1 ; WX 722 ; N Abreve ; B 9 0 689 901 ;
+C -1 ; WX 570 ; N multiply ; B 48 16 522 490 ;
+C -1 ; WX 556 ; N uacute ; B 16 -14 537 713 ;
+C -1 ; WX 667 ; N Tcaron ; B 31 0 636 914 ;
+C -1 ; WX 494 ; N partialdiff ; B 11 -21 494 750 ;
+C -1 ; WX 500 ; N ydieresis ; B 16 -205 480 667 ;
+C -1 ; WX 722 ; N Nacute ; B 16 -18 701 923 ;
+C -1 ; WX 278 ; N icircumflex ; B -37 0 300 704 ;
+C -1 ; WX 667 ; N Ecircumflex ; B 16 0 641 914 ;
+C -1 ; WX 500 ; N adieresis ; B 25 -14 488 667 ;
+C -1 ; WX 444 ; N edieresis ; B 25 -14 426 667 ;
+C -1 ; WX 444 ; N cacute ; B 25 -14 430 713 ;
+C -1 ; WX 556 ; N nacute ; B 21 0 539 713 ;
+C -1 ; WX 556 ; N umacron ; B 16 -14 537 637 ;
+C -1 ; WX 722 ; N Ncaron ; B 16 -18 701 914 ;
+C -1 ; WX 389 ; N Iacute ; B 20 0 370 923 ;
+C -1 ; WX 570 ; N plusminus ; B 33 0 537 506 ;
+C -1 ; WX 220 ; N brokenbar ; B 66 -143 154 707 ;
+C -1 ; WX 747 ; N registered ; B 26 -19 721 691 ;
+C -1 ; WX 778 ; N Gbreve ; B 37 -19 755 901 ;
+C -1 ; WX 389 ; N Idotaccent ; B 20 0 370 901 ;
+C -1 ; WX 600 ; N summation ; B 14 -10 585 706 ;
+C -1 ; WX 667 ; N Egrave ; B 16 0 641 923 ;
+C -1 ; WX 444 ; N racute ; B 29 0 434 713 ;
+C -1 ; WX 500 ; N omacron ; B 25 -14 476 637 ;
+C -1 ; WX 667 ; N Zacute ; B 28 0 634 923 ;
+C -1 ; WX 667 ; N Zcaron ; B 28 0 634 914 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 704 ;
+C -1 ; WX 722 ; N Eth ; B 6 0 690 676 ;
+C -1 ; WX 722 ; N Ccedilla ; B 49 -218 687 691 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 16 -218 255 676 ;
+C -1 ; WX 416 ; N tcaron ; B 20 -12 425 815 ;
+C -1 ; WX 444 ; N eogonek ; B 25 -193 426 473 ;
+C -1 ; WX 722 ; N Uogonek ; B 16 -193 701 676 ;
+C -1 ; WX 722 ; N Aacute ; B 9 0 689 923 ;
+C -1 ; WX 722 ; N Adieresis ; B 9 0 689 877 ;
+C -1 ; WX 444 ; N egrave ; B 25 -14 426 713 ;
+C -1 ; WX 444 ; N zacute ; B 21 0 420 713 ;
+C -1 ; WX 278 ; N iogonek ; B 16 -193 274 691 ;
+C -1 ; WX 778 ; N Oacute ; B 35 -19 743 923 ;
+C -1 ; WX 500 ; N oacute ; B 25 -14 476 713 ;
+C -1 ; WX 500 ; N amacron ; B 25 -14 488 637 ;
+C -1 ; WX 389 ; N sacute ; B 25 -14 361 713 ;
+C -1 ; WX 278 ; N idieresis ; B -37 0 300 667 ;
+C -1 ; WX 778 ; N Ocircumflex ; B 35 -19 743 914 ;
+C -1 ; WX 722 ; N Ugrave ; B 16 -19 701 923 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C -1 ; WX 556 ; N thorn ; B 19 -205 524 676 ;
+C -1 ; WX 300 ; N twosuperior ; B 0 275 300 688 ;
+C -1 ; WX 778 ; N Odieresis ; B 35 -19 743 877 ;
+C -1 ; WX 556 ; N mu ; B 33 -206 536 461 ;
+C -1 ; WX 278 ; N igrave ; B -27 0 255 713 ;
+C -1 ; WX 500 ; N ohungarumlaut ; B 25 -14 529 713 ;
+C -1 ; WX 667 ; N Eogonek ; B 16 -193 644 676 ;
+C -1 ; WX 556 ; N dcroat ; B 25 -14 534 676 ;
+C -1 ; WX 750 ; N threequarters ; B 23 -12 733 688 ;
+C -1 ; WX 556 ; N Scedilla ; B 35 -218 513 692 ;
+C -1 ; WX 394 ; N lcaron ; B 16 0 412 682 ;
+C -1 ; WX 778 ; N Kcommaaccent ; B 30 -218 769 676 ;
+C -1 ; WX 667 ; N Lacute ; B 19 0 638 923 ;
+C -1 ; WX 1000 ; N trademark ; B 24 271 977 676 ;
+C -1 ; WX 444 ; N edotaccent ; B 25 -14 426 691 ;
+C -1 ; WX 389 ; N Igrave ; B 20 0 370 923 ;
+C -1 ; WX 389 ; N Imacron ; B 20 0 370 847 ;
+C -1 ; WX 667 ; N Lcaron ; B 19 0 652 682 ;
+C -1 ; WX 750 ; N onehalf ; B -7 -12 775 688 ;
+C -1 ; WX 549 ; N lessequal ; B 29 0 526 704 ;
+C -1 ; WX 500 ; N ocircumflex ; B 25 -14 476 704 ;
+C -1 ; WX 556 ; N ntilde ; B 21 0 539 674 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 16 -19 701 923 ;
+C -1 ; WX 667 ; N Eacute ; B 16 0 641 923 ;
+C -1 ; WX 444 ; N emacron ; B 25 -14 426 637 ;
+C -1 ; WX 500 ; N gbreve ; B 28 -206 483 691 ;
+C -1 ; WX 750 ; N onequarter ; B 28 -12 743 688 ;
+C -1 ; WX 556 ; N Scaron ; B 35 -19 513 914 ;
+C -1 ; WX 556 ; N Scommaaccent ; B 35 -218 513 692 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 35 -19 743 923 ;
+C -1 ; WX 400 ; N degree ; B 57 402 343 688 ;
+C -1 ; WX 500 ; N ograve ; B 25 -14 476 713 ;
+C -1 ; WX 722 ; N Ccaron ; B 49 -19 687 914 ;
+C -1 ; WX 556 ; N ugrave ; B 16 -14 537 713 ;
+C -1 ; WX 549 ; N radical ; B 10 -46 512 850 ;
+C -1 ; WX 722 ; N Dcaron ; B 14 0 690 914 ;
+C -1 ; WX 444 ; N rcommaaccent ; B 29 -218 434 473 ;
+C -1 ; WX 722 ; N Ntilde ; B 16 -18 701 884 ;
+C -1 ; WX 500 ; N otilde ; B 25 -14 476 674 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 26 -218 715 676 ;
+C -1 ; WX 667 ; N Lcommaaccent ; B 19 -218 638 676 ;
+C -1 ; WX 722 ; N Atilde ; B 9 0 689 884 ;
+C -1 ; WX 722 ; N Aogonek ; B 9 -193 699 690 ;
+C -1 ; WX 722 ; N Aring ; B 9 0 689 935 ;
+C -1 ; WX 778 ; N Otilde ; B 35 -19 743 884 ;
+C -1 ; WX 444 ; N zdotaccent ; B 21 0 420 691 ;
+C -1 ; WX 667 ; N Ecaron ; B 16 0 641 914 ;
+C -1 ; WX 389 ; N Iogonek ; B 20 -193 370 676 ;
+C -1 ; WX 556 ; N kcommaaccent ; B 22 -218 543 676 ;
+C -1 ; WX 570 ; N minus ; B 33 209 537 297 ;
+C -1 ; WX 389 ; N Icircumflex ; B 20 0 370 914 ;
+C -1 ; WX 556 ; N ncaron ; B 21 0 539 704 ;
+C -1 ; WX 333 ; N tcommaaccent ; B 20 -218 332 630 ;
+C -1 ; WX 570 ; N logicalnot ; B 33 108 537 399 ;
+C -1 ; WX 500 ; N odieresis ; B 25 -14 476 667 ;
+C -1 ; WX 556 ; N udieresis ; B 16 -14 537 667 ;
+C -1 ; WX 549 ; N notequal ; B 15 -49 540 570 ;
+C -1 ; WX 500 ; N gcommaaccent ; B 28 -206 483 829 ;
+C -1 ; WX 500 ; N eth ; B 25 -14 476 691 ;
+C -1 ; WX 444 ; N zcaron ; B 21 0 420 704 ;
+C -1 ; WX 556 ; N ncommaaccent ; B 21 -218 539 473 ;
+C -1 ; WX 300 ; N onesuperior ; B 28 275 273 688 ;
+C -1 ; WX 278 ; N imacron ; B -8 0 272 637 ;
+C -1 ; WX 500 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2242
+KPX A C -55
+KPX A Cacute -55
+KPX A Ccaron -55
+KPX A Ccedilla -55
+KPX A G -55
+KPX A Gbreve -55
+KPX A Gcommaaccent -55
+KPX A O -45
+KPX A Oacute -45
+KPX A Ocircumflex -45
+KPX A Odieresis -45
+KPX A Ograve -45
+KPX A Ohungarumlaut -45
+KPX A Omacron -45
+KPX A Oslash -45
+KPX A Otilde -45
+KPX A Q -45
+KPX A T -95
+KPX A Tcaron -95
+KPX A Tcommaaccent -95
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -145
+KPX A W -130
+KPX A Y -100
+KPX A Yacute -100
+KPX A Ydieresis -100
+KPX A p -25
+KPX A quoteright -74
+KPX A u -50
+KPX A uacute -50
+KPX A ucircumflex -50
+KPX A udieresis -50
+KPX A ugrave -50
+KPX A uhungarumlaut -50
+KPX A umacron -50
+KPX A uogonek -50
+KPX A uring -50
+KPX A v -100
+KPX A w -90
+KPX A y -74
+KPX A yacute -74
+KPX A ydieresis -74
+KPX Aacute C -55
+KPX Aacute Cacute -55
+KPX Aacute Ccaron -55
+KPX Aacute Ccedilla -55
+KPX Aacute G -55
+KPX Aacute Gbreve -55
+KPX Aacute Gcommaaccent -55
+KPX Aacute O -45
+KPX Aacute Oacute -45
+KPX Aacute Ocircumflex -45
+KPX Aacute Odieresis -45
+KPX Aacute Ograve -45
+KPX Aacute Ohungarumlaut -45
+KPX Aacute Omacron -45
+KPX Aacute Oslash -45
+KPX Aacute Otilde -45
+KPX Aacute Q -45
+KPX Aacute T -95
+KPX Aacute Tcaron -95
+KPX Aacute Tcommaaccent -95
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -145
+KPX Aacute W -130
+KPX Aacute Y -100
+KPX Aacute Yacute -100
+KPX Aacute Ydieresis -100
+KPX Aacute p -25
+KPX Aacute quoteright -74
+KPX Aacute u -50
+KPX Aacute uacute -50
+KPX Aacute ucircumflex -50
+KPX Aacute udieresis -50
+KPX Aacute ugrave -50
+KPX Aacute uhungarumlaut -50
+KPX Aacute umacron -50
+KPX Aacute uogonek -50
+KPX Aacute uring -50
+KPX Aacute v -100
+KPX Aacute w -90
+KPX Aacute y -74
+KPX Aacute yacute -74
+KPX Aacute ydieresis -74
+KPX Abreve C -55
+KPX Abreve Cacute -55
+KPX Abreve Ccaron -55
+KPX Abreve Ccedilla -55
+KPX Abreve G -55
+KPX Abreve Gbreve -55
+KPX Abreve Gcommaaccent -55
+KPX Abreve O -45
+KPX Abreve Oacute -45
+KPX Abreve Ocircumflex -45
+KPX Abreve Odieresis -45
+KPX Abreve Ograve -45
+KPX Abreve Ohungarumlaut -45
+KPX Abreve Omacron -45
+KPX Abreve Oslash -45
+KPX Abreve Otilde -45
+KPX Abreve Q -45
+KPX Abreve T -95
+KPX Abreve Tcaron -95
+KPX Abreve Tcommaaccent -95
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -145
+KPX Abreve W -130
+KPX Abreve Y -100
+KPX Abreve Yacute -100
+KPX Abreve Ydieresis -100
+KPX Abreve p -25
+KPX Abreve quoteright -74
+KPX Abreve u -50
+KPX Abreve uacute -50
+KPX Abreve ucircumflex -50
+KPX Abreve udieresis -50
+KPX Abreve ugrave -50
+KPX Abreve uhungarumlaut -50
+KPX Abreve umacron -50
+KPX Abreve uogonek -50
+KPX Abreve uring -50
+KPX Abreve v -100
+KPX Abreve w -90
+KPX Abreve y -74
+KPX Abreve yacute -74
+KPX Abreve ydieresis -74
+KPX Acircumflex C -55
+KPX Acircumflex Cacute -55
+KPX Acircumflex Ccaron -55
+KPX Acircumflex Ccedilla -55
+KPX Acircumflex G -55
+KPX Acircumflex Gbreve -55
+KPX Acircumflex Gcommaaccent -55
+KPX Acircumflex O -45
+KPX Acircumflex Oacute -45
+KPX Acircumflex Ocircumflex -45
+KPX Acircumflex Odieresis -45
+KPX Acircumflex Ograve -45
+KPX Acircumflex Ohungarumlaut -45
+KPX Acircumflex Omacron -45
+KPX Acircumflex Oslash -45
+KPX Acircumflex Otilde -45
+KPX Acircumflex Q -45
+KPX Acircumflex T -95
+KPX Acircumflex Tcaron -95
+KPX Acircumflex Tcommaaccent -95
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -145
+KPX Acircumflex W -130
+KPX Acircumflex Y -100
+KPX Acircumflex Yacute -100
+KPX Acircumflex Ydieresis -100
+KPX Acircumflex p -25
+KPX Acircumflex quoteright -74
+KPX Acircumflex u -50
+KPX Acircumflex uacute -50
+KPX Acircumflex ucircumflex -50
+KPX Acircumflex udieresis -50
+KPX Acircumflex ugrave -50
+KPX Acircumflex uhungarumlaut -50
+KPX Acircumflex umacron -50
+KPX Acircumflex uogonek -50
+KPX Acircumflex uring -50
+KPX Acircumflex v -100
+KPX Acircumflex w -90
+KPX Acircumflex y -74
+KPX Acircumflex yacute -74
+KPX Acircumflex ydieresis -74
+KPX Adieresis C -55
+KPX Adieresis Cacute -55
+KPX Adieresis Ccaron -55
+KPX Adieresis Ccedilla -55
+KPX Adieresis G -55
+KPX Adieresis Gbreve -55
+KPX Adieresis Gcommaaccent -55
+KPX Adieresis O -45
+KPX Adieresis Oacute -45
+KPX Adieresis Ocircumflex -45
+KPX Adieresis Odieresis -45
+KPX Adieresis Ograve -45
+KPX Adieresis Ohungarumlaut -45
+KPX Adieresis Omacron -45
+KPX Adieresis Oslash -45
+KPX Adieresis Otilde -45
+KPX Adieresis Q -45
+KPX Adieresis T -95
+KPX Adieresis Tcaron -95
+KPX Adieresis Tcommaaccent -95
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -145
+KPX Adieresis W -130
+KPX Adieresis Y -100
+KPX Adieresis Yacute -100
+KPX Adieresis Ydieresis -100
+KPX Adieresis p -25
+KPX Adieresis quoteright -74
+KPX Adieresis u -50
+KPX Adieresis uacute -50
+KPX Adieresis ucircumflex -50
+KPX Adieresis udieresis -50
+KPX Adieresis ugrave -50
+KPX Adieresis uhungarumlaut -50
+KPX Adieresis umacron -50
+KPX Adieresis uogonek -50
+KPX Adieresis uring -50
+KPX Adieresis v -100
+KPX Adieresis w -90
+KPX Adieresis y -74
+KPX Adieresis yacute -74
+KPX Adieresis ydieresis -74
+KPX Agrave C -55
+KPX Agrave Cacute -55
+KPX Agrave Ccaron -55
+KPX Agrave Ccedilla -55
+KPX Agrave G -55
+KPX Agrave Gbreve -55
+KPX Agrave Gcommaaccent -55
+KPX Agrave O -45
+KPX Agrave Oacute -45
+KPX Agrave Ocircumflex -45
+KPX Agrave Odieresis -45
+KPX Agrave Ograve -45
+KPX Agrave Ohungarumlaut -45
+KPX Agrave Omacron -45
+KPX Agrave Oslash -45
+KPX Agrave Otilde -45
+KPX Agrave Q -45
+KPX Agrave T -95
+KPX Agrave Tcaron -95
+KPX Agrave Tcommaaccent -95
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -145
+KPX Agrave W -130
+KPX Agrave Y -100
+KPX Agrave Yacute -100
+KPX Agrave Ydieresis -100
+KPX Agrave p -25
+KPX Agrave quoteright -74
+KPX Agrave u -50
+KPX Agrave uacute -50
+KPX Agrave ucircumflex -50
+KPX Agrave udieresis -50
+KPX Agrave ugrave -50
+KPX Agrave uhungarumlaut -50
+KPX Agrave umacron -50
+KPX Agrave uogonek -50
+KPX Agrave uring -50
+KPX Agrave v -100
+KPX Agrave w -90
+KPX Agrave y -74
+KPX Agrave yacute -74
+KPX Agrave ydieresis -74
+KPX Amacron C -55
+KPX Amacron Cacute -55
+KPX Amacron Ccaron -55
+KPX Amacron Ccedilla -55
+KPX Amacron G -55
+KPX Amacron Gbreve -55
+KPX Amacron Gcommaaccent -55
+KPX Amacron O -45
+KPX Amacron Oacute -45
+KPX Amacron Ocircumflex -45
+KPX Amacron Odieresis -45
+KPX Amacron Ograve -45
+KPX Amacron Ohungarumlaut -45
+KPX Amacron Omacron -45
+KPX Amacron Oslash -45
+KPX Amacron Otilde -45
+KPX Amacron Q -45
+KPX Amacron T -95
+KPX Amacron Tcaron -95
+KPX Amacron Tcommaaccent -95
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -145
+KPX Amacron W -130
+KPX Amacron Y -100
+KPX Amacron Yacute -100
+KPX Amacron Ydieresis -100
+KPX Amacron p -25
+KPX Amacron quoteright -74
+KPX Amacron u -50
+KPX Amacron uacute -50
+KPX Amacron ucircumflex -50
+KPX Amacron udieresis -50
+KPX Amacron ugrave -50
+KPX Amacron uhungarumlaut -50
+KPX Amacron umacron -50
+KPX Amacron uogonek -50
+KPX Amacron uring -50
+KPX Amacron v -100
+KPX Amacron w -90
+KPX Amacron y -74
+KPX Amacron yacute -74
+KPX Amacron ydieresis -74
+KPX Aogonek C -55
+KPX Aogonek Cacute -55
+KPX Aogonek Ccaron -55
+KPX Aogonek Ccedilla -55
+KPX Aogonek G -55
+KPX Aogonek Gbreve -55
+KPX Aogonek Gcommaaccent -55
+KPX Aogonek O -45
+KPX Aogonek Oacute -45
+KPX Aogonek Ocircumflex -45
+KPX Aogonek Odieresis -45
+KPX Aogonek Ograve -45
+KPX Aogonek Ohungarumlaut -45
+KPX Aogonek Omacron -45
+KPX Aogonek Oslash -45
+KPX Aogonek Otilde -45
+KPX Aogonek Q -45
+KPX Aogonek T -95
+KPX Aogonek Tcaron -95
+KPX Aogonek Tcommaaccent -95
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -145
+KPX Aogonek W -130
+KPX Aogonek Y -100
+KPX Aogonek Yacute -100
+KPX Aogonek Ydieresis -100
+KPX Aogonek p -25
+KPX Aogonek quoteright -74
+KPX Aogonek u -50
+KPX Aogonek uacute -50
+KPX Aogonek ucircumflex -50
+KPX Aogonek udieresis -50
+KPX Aogonek ugrave -50
+KPX Aogonek uhungarumlaut -50
+KPX Aogonek umacron -50
+KPX Aogonek uogonek -50
+KPX Aogonek uring -50
+KPX Aogonek v -100
+KPX Aogonek w -90
+KPX Aogonek y -34
+KPX Aogonek yacute -34
+KPX Aogonek ydieresis -34
+KPX Aring C -55
+KPX Aring Cacute -55
+KPX Aring Ccaron -55
+KPX Aring Ccedilla -55
+KPX Aring G -55
+KPX Aring Gbreve -55
+KPX Aring Gcommaaccent -55
+KPX Aring O -45
+KPX Aring Oacute -45
+KPX Aring Ocircumflex -45
+KPX Aring Odieresis -45
+KPX Aring Ograve -45
+KPX Aring Ohungarumlaut -45
+KPX Aring Omacron -45
+KPX Aring Oslash -45
+KPX Aring Otilde -45
+KPX Aring Q -45
+KPX Aring T -95
+KPX Aring Tcaron -95
+KPX Aring Tcommaaccent -95
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -145
+KPX Aring W -130
+KPX Aring Y -100
+KPX Aring Yacute -100
+KPX Aring Ydieresis -100
+KPX Aring p -25
+KPX Aring quoteright -74
+KPX Aring u -50
+KPX Aring uacute -50
+KPX Aring ucircumflex -50
+KPX Aring udieresis -50
+KPX Aring ugrave -50
+KPX Aring uhungarumlaut -50
+KPX Aring umacron -50
+KPX Aring uogonek -50
+KPX Aring uring -50
+KPX Aring v -100
+KPX Aring w -90
+KPX Aring y -74
+KPX Aring yacute -74
+KPX Aring ydieresis -74
+KPX Atilde C -55
+KPX Atilde Cacute -55
+KPX Atilde Ccaron -55
+KPX Atilde Ccedilla -55
+KPX Atilde G -55
+KPX Atilde Gbreve -55
+KPX Atilde Gcommaaccent -55
+KPX Atilde O -45
+KPX Atilde Oacute -45
+KPX Atilde Ocircumflex -45
+KPX Atilde Odieresis -45
+KPX Atilde Ograve -45
+KPX Atilde Ohungarumlaut -45
+KPX Atilde Omacron -45
+KPX Atilde Oslash -45
+KPX Atilde Otilde -45
+KPX Atilde Q -45
+KPX Atilde T -95
+KPX Atilde Tcaron -95
+KPX Atilde Tcommaaccent -95
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -145
+KPX Atilde W -130
+KPX Atilde Y -100
+KPX Atilde Yacute -100
+KPX Atilde Ydieresis -100
+KPX Atilde p -25
+KPX Atilde quoteright -74
+KPX Atilde u -50
+KPX Atilde uacute -50
+KPX Atilde ucircumflex -50
+KPX Atilde udieresis -50
+KPX Atilde ugrave -50
+KPX Atilde uhungarumlaut -50
+KPX Atilde umacron -50
+KPX Atilde uogonek -50
+KPX Atilde uring -50
+KPX Atilde v -100
+KPX Atilde w -90
+KPX Atilde y -74
+KPX Atilde yacute -74
+KPX Atilde ydieresis -74
+KPX B A -30
+KPX B Aacute -30
+KPX B Abreve -30
+KPX B Acircumflex -30
+KPX B Adieresis -30
+KPX B Agrave -30
+KPX B Amacron -30
+KPX B Aogonek -30
+KPX B Aring -30
+KPX B Atilde -30
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -35
+KPX D Aacute -35
+KPX D Abreve -35
+KPX D Acircumflex -35
+KPX D Adieresis -35
+KPX D Agrave -35
+KPX D Amacron -35
+KPX D Aogonek -35
+KPX D Aring -35
+KPX D Atilde -35
+KPX D V -40
+KPX D W -40
+KPX D Y -40
+KPX D Yacute -40
+KPX D Ydieresis -40
+KPX D period -20
+KPX Dcaron A -35
+KPX Dcaron Aacute -35
+KPX Dcaron Abreve -35
+KPX Dcaron Acircumflex -35
+KPX Dcaron Adieresis -35
+KPX Dcaron Agrave -35
+KPX Dcaron Amacron -35
+KPX Dcaron Aogonek -35
+KPX Dcaron Aring -35
+KPX Dcaron Atilde -35
+KPX Dcaron V -40
+KPX Dcaron W -40
+KPX Dcaron Y -40
+KPX Dcaron Yacute -40
+KPX Dcaron Ydieresis -40
+KPX Dcaron period -20
+KPX Dcroat A -35
+KPX Dcroat Aacute -35
+KPX Dcroat Abreve -35
+KPX Dcroat Acircumflex -35
+KPX Dcroat Adieresis -35
+KPX Dcroat Agrave -35
+KPX Dcroat Amacron -35
+KPX Dcroat Aogonek -35
+KPX Dcroat Aring -35
+KPX Dcroat Atilde -35
+KPX Dcroat V -40
+KPX Dcroat W -40
+KPX Dcroat Y -40
+KPX Dcroat Yacute -40
+KPX Dcroat Ydieresis -40
+KPX Dcroat period -20
+KPX F A -90
+KPX F Aacute -90
+KPX F Abreve -90
+KPX F Acircumflex -90
+KPX F Adieresis -90
+KPX F Agrave -90
+KPX F Amacron -90
+KPX F Aogonek -90
+KPX F Aring -90
+KPX F Atilde -90
+KPX F a -25
+KPX F aacute -25
+KPX F abreve -25
+KPX F acircumflex -25
+KPX F adieresis -25
+KPX F agrave -25
+KPX F amacron -25
+KPX F aogonek -25
+KPX F aring -25
+KPX F atilde -25
+KPX F comma -92
+KPX F e -25
+KPX F eacute -25
+KPX F ecaron -25
+KPX F ecircumflex -25
+KPX F edieresis -25
+KPX F edotaccent -25
+KPX F egrave -25
+KPX F emacron -25
+KPX F eogonek -25
+KPX F o -25
+KPX F oacute -25
+KPX F ocircumflex -25
+KPX F odieresis -25
+KPX F ograve -25
+KPX F ohungarumlaut -25
+KPX F omacron -25
+KPX F oslash -25
+KPX F otilde -25
+KPX F period -110
+KPX J A -30
+KPX J Aacute -30
+KPX J Abreve -30
+KPX J Acircumflex -30
+KPX J Adieresis -30
+KPX J Agrave -30
+KPX J Amacron -30
+KPX J Aogonek -30
+KPX J Aring -30
+KPX J Atilde -30
+KPX J a -15
+KPX J aacute -15
+KPX J abreve -15
+KPX J acircumflex -15
+KPX J adieresis -15
+KPX J agrave -15
+KPX J amacron -15
+KPX J aogonek -15
+KPX J aring -15
+KPX J atilde -15
+KPX J e -15
+KPX J eacute -15
+KPX J ecaron -15
+KPX J ecircumflex -15
+KPX J edieresis -15
+KPX J edotaccent -15
+KPX J egrave -15
+KPX J emacron -15
+KPX J eogonek -15
+KPX J o -15
+KPX J oacute -15
+KPX J ocircumflex -15
+KPX J odieresis -15
+KPX J ograve -15
+KPX J ohungarumlaut -15
+KPX J omacron -15
+KPX J oslash -15
+KPX J otilde -15
+KPX J period -20
+KPX J u -15
+KPX J uacute -15
+KPX J ucircumflex -15
+KPX J udieresis -15
+KPX J ugrave -15
+KPX J uhungarumlaut -15
+KPX J umacron -15
+KPX J uogonek -15
+KPX J uring -15
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -25
+KPX K eacute -25
+KPX K ecaron -25
+KPX K ecircumflex -25
+KPX K edieresis -25
+KPX K edotaccent -25
+KPX K egrave -25
+KPX K emacron -25
+KPX K eogonek -25
+KPX K o -25
+KPX K oacute -25
+KPX K ocircumflex -25
+KPX K odieresis -25
+KPX K ograve -25
+KPX K ohungarumlaut -25
+KPX K omacron -25
+KPX K oslash -25
+KPX K otilde -25
+KPX K u -15
+KPX K uacute -15
+KPX K ucircumflex -15
+KPX K udieresis -15
+KPX K ugrave -15
+KPX K uhungarumlaut -15
+KPX K umacron -15
+KPX K uogonek -15
+KPX K uring -15
+KPX K y -45
+KPX K yacute -45
+KPX K ydieresis -45
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -25
+KPX Kcommaaccent eacute -25
+KPX Kcommaaccent ecaron -25
+KPX Kcommaaccent ecircumflex -25
+KPX Kcommaaccent edieresis -25
+KPX Kcommaaccent edotaccent -25
+KPX Kcommaaccent egrave -25
+KPX Kcommaaccent emacron -25
+KPX Kcommaaccent eogonek -25
+KPX Kcommaaccent o -25
+KPX Kcommaaccent oacute -25
+KPX Kcommaaccent ocircumflex -25
+KPX Kcommaaccent odieresis -25
+KPX Kcommaaccent ograve -25
+KPX Kcommaaccent ohungarumlaut -25
+KPX Kcommaaccent omacron -25
+KPX Kcommaaccent oslash -25
+KPX Kcommaaccent otilde -25
+KPX Kcommaaccent u -15
+KPX Kcommaaccent uacute -15
+KPX Kcommaaccent ucircumflex -15
+KPX Kcommaaccent udieresis -15
+KPX Kcommaaccent ugrave -15
+KPX Kcommaaccent uhungarumlaut -15
+KPX Kcommaaccent umacron -15
+KPX Kcommaaccent uogonek -15
+KPX Kcommaaccent uring -15
+KPX Kcommaaccent y -45
+KPX Kcommaaccent yacute -45
+KPX Kcommaaccent ydieresis -45
+KPX L T -92
+KPX L Tcaron -92
+KPX L Tcommaaccent -92
+KPX L V -92
+KPX L W -92
+KPX L Y -92
+KPX L Yacute -92
+KPX L Ydieresis -92
+KPX L quotedblright -20
+KPX L quoteright -110
+KPX L y -55
+KPX L yacute -55
+KPX L ydieresis -55
+KPX Lacute T -92
+KPX Lacute Tcaron -92
+KPX Lacute Tcommaaccent -92
+KPX Lacute V -92
+KPX Lacute W -92
+KPX Lacute Y -92
+KPX Lacute Yacute -92
+KPX Lacute Ydieresis -92
+KPX Lacute quotedblright -20
+KPX Lacute quoteright -110
+KPX Lacute y -55
+KPX Lacute yacute -55
+KPX Lacute ydieresis -55
+KPX Lcommaaccent T -92
+KPX Lcommaaccent Tcaron -92
+KPX Lcommaaccent Tcommaaccent -92
+KPX Lcommaaccent V -92
+KPX Lcommaaccent W -92
+KPX Lcommaaccent Y -92
+KPX Lcommaaccent Yacute -92
+KPX Lcommaaccent Ydieresis -92
+KPX Lcommaaccent quotedblright -20
+KPX Lcommaaccent quoteright -110
+KPX Lcommaaccent y -55
+KPX Lcommaaccent yacute -55
+KPX Lcommaaccent ydieresis -55
+KPX Lslash T -92
+KPX Lslash Tcaron -92
+KPX Lslash Tcommaaccent -92
+KPX Lslash V -92
+KPX Lslash W -92
+KPX Lslash Y -92
+KPX Lslash Yacute -92
+KPX Lslash Ydieresis -92
+KPX Lslash quotedblright -20
+KPX Lslash quoteright -110
+KPX Lslash y -55
+KPX Lslash yacute -55
+KPX Lslash ydieresis -55
+KPX N A -20
+KPX N Aacute -20
+KPX N Abreve -20
+KPX N Acircumflex -20
+KPX N Adieresis -20
+KPX N Agrave -20
+KPX N Amacron -20
+KPX N Aogonek -20
+KPX N Aring -20
+KPX N Atilde -20
+KPX Nacute A -20
+KPX Nacute Aacute -20
+KPX Nacute Abreve -20
+KPX Nacute Acircumflex -20
+KPX Nacute Adieresis -20
+KPX Nacute Agrave -20
+KPX Nacute Amacron -20
+KPX Nacute Aogonek -20
+KPX Nacute Aring -20
+KPX Nacute Atilde -20
+KPX Ncaron A -20
+KPX Ncaron Aacute -20
+KPX Ncaron Abreve -20
+KPX Ncaron Acircumflex -20
+KPX Ncaron Adieresis -20
+KPX Ncaron Agrave -20
+KPX Ncaron Amacron -20
+KPX Ncaron Aogonek -20
+KPX Ncaron Aring -20
+KPX Ncaron Atilde -20
+KPX Ncommaaccent A -20
+KPX Ncommaaccent Aacute -20
+KPX Ncommaaccent Abreve -20
+KPX Ncommaaccent Acircumflex -20
+KPX Ncommaaccent Adieresis -20
+KPX Ncommaaccent Agrave -20
+KPX Ncommaaccent Amacron -20
+KPX Ncommaaccent Aogonek -20
+KPX Ncommaaccent Aring -20
+KPX Ncommaaccent Atilde -20
+KPX Ntilde A -20
+KPX Ntilde Aacute -20
+KPX Ntilde Abreve -20
+KPX Ntilde Acircumflex -20
+KPX Ntilde Adieresis -20
+KPX Ntilde Agrave -20
+KPX Ntilde Amacron -20
+KPX Ntilde Aogonek -20
+KPX Ntilde Aring -20
+KPX Ntilde Atilde -20
+KPX O A -40
+KPX O Aacute -40
+KPX O Abreve -40
+KPX O Acircumflex -40
+KPX O Adieresis -40
+KPX O Agrave -40
+KPX O Amacron -40
+KPX O Aogonek -40
+KPX O Aring -40
+KPX O Atilde -40
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -40
+KPX O Y -50
+KPX O Yacute -50
+KPX O Ydieresis -50
+KPX Oacute A -40
+KPX Oacute Aacute -40
+KPX Oacute Abreve -40
+KPX Oacute Acircumflex -40
+KPX Oacute Adieresis -40
+KPX Oacute Agrave -40
+KPX Oacute Amacron -40
+KPX Oacute Aogonek -40
+KPX Oacute Aring -40
+KPX Oacute Atilde -40
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -40
+KPX Oacute Y -50
+KPX Oacute Yacute -50
+KPX Oacute Ydieresis -50
+KPX Ocircumflex A -40
+KPX Ocircumflex Aacute -40
+KPX Ocircumflex Abreve -40
+KPX Ocircumflex Acircumflex -40
+KPX Ocircumflex Adieresis -40
+KPX Ocircumflex Agrave -40
+KPX Ocircumflex Amacron -40
+KPX Ocircumflex Aogonek -40
+KPX Ocircumflex Aring -40
+KPX Ocircumflex Atilde -40
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -40
+KPX Ocircumflex Y -50
+KPX Ocircumflex Yacute -50
+KPX Ocircumflex Ydieresis -50
+KPX Odieresis A -40
+KPX Odieresis Aacute -40
+KPX Odieresis Abreve -40
+KPX Odieresis Acircumflex -40
+KPX Odieresis Adieresis -40
+KPX Odieresis Agrave -40
+KPX Odieresis Amacron -40
+KPX Odieresis Aogonek -40
+KPX Odieresis Aring -40
+KPX Odieresis Atilde -40
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -40
+KPX Odieresis Y -50
+KPX Odieresis Yacute -50
+KPX Odieresis Ydieresis -50
+KPX Ograve A -40
+KPX Ograve Aacute -40
+KPX Ograve Abreve -40
+KPX Ograve Acircumflex -40
+KPX Ograve Adieresis -40
+KPX Ograve Agrave -40
+KPX Ograve Amacron -40
+KPX Ograve Aogonek -40
+KPX Ograve Aring -40
+KPX Ograve Atilde -40
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -40
+KPX Ograve Y -50
+KPX Ograve Yacute -50
+KPX Ograve Ydieresis -50
+KPX Ohungarumlaut A -40
+KPX Ohungarumlaut Aacute -40
+KPX Ohungarumlaut Abreve -40
+KPX Ohungarumlaut Acircumflex -40
+KPX Ohungarumlaut Adieresis -40
+KPX Ohungarumlaut Agrave -40
+KPX Ohungarumlaut Amacron -40
+KPX Ohungarumlaut Aogonek -40
+KPX Ohungarumlaut Aring -40
+KPX Ohungarumlaut Atilde -40
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -40
+KPX Ohungarumlaut Y -50
+KPX Ohungarumlaut Yacute -50
+KPX Ohungarumlaut Ydieresis -50
+KPX Omacron A -40
+KPX Omacron Aacute -40
+KPX Omacron Abreve -40
+KPX Omacron Acircumflex -40
+KPX Omacron Adieresis -40
+KPX Omacron Agrave -40
+KPX Omacron Amacron -40
+KPX Omacron Aogonek -40
+KPX Omacron Aring -40
+KPX Omacron Atilde -40
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -40
+KPX Omacron Y -50
+KPX Omacron Yacute -50
+KPX Omacron Ydieresis -50
+KPX Oslash A -40
+KPX Oslash Aacute -40
+KPX Oslash Abreve -40
+KPX Oslash Acircumflex -40
+KPX Oslash Adieresis -40
+KPX Oslash Agrave -40
+KPX Oslash Amacron -40
+KPX Oslash Aogonek -40
+KPX Oslash Aring -40
+KPX Oslash Atilde -40
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -40
+KPX Oslash Y -50
+KPX Oslash Yacute -50
+KPX Oslash Ydieresis -50
+KPX Otilde A -40
+KPX Otilde Aacute -40
+KPX Otilde Abreve -40
+KPX Otilde Acircumflex -40
+KPX Otilde Adieresis -40
+KPX Otilde Agrave -40
+KPX Otilde Amacron -40
+KPX Otilde Aogonek -40
+KPX Otilde Aring -40
+KPX Otilde Atilde -40
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -40
+KPX Otilde Y -50
+KPX Otilde Yacute -50
+KPX Otilde Ydieresis -50
+KPX P A -74
+KPX P Aacute -74
+KPX P Abreve -74
+KPX P Acircumflex -74
+KPX P Adieresis -74
+KPX P Agrave -74
+KPX P Amacron -74
+KPX P Aogonek -74
+KPX P Aring -74
+KPX P Atilde -74
+KPX P a -10
+KPX P aacute -10
+KPX P abreve -10
+KPX P acircumflex -10
+KPX P adieresis -10
+KPX P agrave -10
+KPX P amacron -10
+KPX P aogonek -10
+KPX P aring -10
+KPX P atilde -10
+KPX P comma -92
+KPX P e -20
+KPX P eacute -20
+KPX P ecaron -20
+KPX P ecircumflex -20
+KPX P edieresis -20
+KPX P edotaccent -20
+KPX P egrave -20
+KPX P emacron -20
+KPX P eogonek -20
+KPX P o -20
+KPX P oacute -20
+KPX P ocircumflex -20
+KPX P odieresis -20
+KPX P ograve -20
+KPX P ohungarumlaut -20
+KPX P omacron -20
+KPX P oslash -20
+KPX P otilde -20
+KPX P period -110
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX Q period -20
+KPX R O -30
+KPX R Oacute -30
+KPX R Ocircumflex -30
+KPX R Odieresis -30
+KPX R Ograve -30
+KPX R Ohungarumlaut -30
+KPX R Omacron -30
+KPX R Oslash -30
+KPX R Otilde -30
+KPX R T -40
+KPX R Tcaron -40
+KPX R Tcommaaccent -40
+KPX R U -30
+KPX R Uacute -30
+KPX R Ucircumflex -30
+KPX R Udieresis -30
+KPX R Ugrave -30
+KPX R Uhungarumlaut -30
+KPX R Umacron -30
+KPX R Uogonek -30
+KPX R Uring -30
+KPX R V -55
+KPX R W -35
+KPX R Y -35
+KPX R Yacute -35
+KPX R Ydieresis -35
+KPX Racute O -30
+KPX Racute Oacute -30
+KPX Racute Ocircumflex -30
+KPX Racute Odieresis -30
+KPX Racute Ograve -30
+KPX Racute Ohungarumlaut -30
+KPX Racute Omacron -30
+KPX Racute Oslash -30
+KPX Racute Otilde -30
+KPX Racute T -40
+KPX Racute Tcaron -40
+KPX Racute Tcommaaccent -40
+KPX Racute U -30
+KPX Racute Uacute -30
+KPX Racute Ucircumflex -30
+KPX Racute Udieresis -30
+KPX Racute Ugrave -30
+KPX Racute Uhungarumlaut -30
+KPX Racute Umacron -30
+KPX Racute Uogonek -30
+KPX Racute Uring -30
+KPX Racute V -55
+KPX Racute W -35
+KPX Racute Y -35
+KPX Racute Yacute -35
+KPX Racute Ydieresis -35
+KPX Rcaron O -30
+KPX Rcaron Oacute -30
+KPX Rcaron Ocircumflex -30
+KPX Rcaron Odieresis -30
+KPX Rcaron Ograve -30
+KPX Rcaron Ohungarumlaut -30
+KPX Rcaron Omacron -30
+KPX Rcaron Oslash -30
+KPX Rcaron Otilde -30
+KPX Rcaron T -40
+KPX Rcaron Tcaron -40
+KPX Rcaron Tcommaaccent -40
+KPX Rcaron U -30
+KPX Rcaron Uacute -30
+KPX Rcaron Ucircumflex -30
+KPX Rcaron Udieresis -30
+KPX Rcaron Ugrave -30
+KPX Rcaron Uhungarumlaut -30
+KPX Rcaron Umacron -30
+KPX Rcaron Uogonek -30
+KPX Rcaron Uring -30
+KPX Rcaron V -55
+KPX Rcaron W -35
+KPX Rcaron Y -35
+KPX Rcaron Yacute -35
+KPX Rcaron Ydieresis -35
+KPX Rcommaaccent O -30
+KPX Rcommaaccent Oacute -30
+KPX Rcommaaccent Ocircumflex -30
+KPX Rcommaaccent Odieresis -30
+KPX Rcommaaccent Ograve -30
+KPX Rcommaaccent Ohungarumlaut -30
+KPX Rcommaaccent Omacron -30
+KPX Rcommaaccent Oslash -30
+KPX Rcommaaccent Otilde -30
+KPX Rcommaaccent T -40
+KPX Rcommaaccent Tcaron -40
+KPX Rcommaaccent Tcommaaccent -40
+KPX Rcommaaccent U -30
+KPX Rcommaaccent Uacute -30
+KPX Rcommaaccent Ucircumflex -30
+KPX Rcommaaccent Udieresis -30
+KPX Rcommaaccent Ugrave -30
+KPX Rcommaaccent Uhungarumlaut -30
+KPX Rcommaaccent Umacron -30
+KPX Rcommaaccent Uogonek -30
+KPX Rcommaaccent Uring -30
+KPX Rcommaaccent V -55
+KPX Rcommaaccent W -35
+KPX Rcommaaccent Y -35
+KPX Rcommaaccent Yacute -35
+KPX Rcommaaccent Ydieresis -35
+KPX T A -90
+KPX T Aacute -90
+KPX T Abreve -90
+KPX T Acircumflex -90
+KPX T Adieresis -90
+KPX T Agrave -90
+KPX T Amacron -90
+KPX T Aogonek -90
+KPX T Aring -90
+KPX T Atilde -90
+KPX T O -18
+KPX T Oacute -18
+KPX T Ocircumflex -18
+KPX T Odieresis -18
+KPX T Ograve -18
+KPX T Ohungarumlaut -18
+KPX T Omacron -18
+KPX T Oslash -18
+KPX T Otilde -18
+KPX T a -92
+KPX T aacute -92
+KPX T abreve -52
+KPX T acircumflex -52
+KPX T adieresis -52
+KPX T agrave -52
+KPX T amacron -52
+KPX T aogonek -92
+KPX T aring -92
+KPX T atilde -52
+KPX T colon -74
+KPX T comma -74
+KPX T e -92
+KPX T eacute -92
+KPX T ecaron -92
+KPX T ecircumflex -92
+KPX T edieresis -52
+KPX T edotaccent -92
+KPX T egrave -52
+KPX T emacron -52
+KPX T eogonek -92
+KPX T hyphen -92
+KPX T i -18
+KPX T iacute -18
+KPX T iogonek -18
+KPX T o -92
+KPX T oacute -92
+KPX T ocircumflex -92
+KPX T odieresis -92
+KPX T ograve -92
+KPX T ohungarumlaut -92
+KPX T omacron -92
+KPX T oslash -92
+KPX T otilde -92
+KPX T period -90
+KPX T r -74
+KPX T racute -74
+KPX T rcaron -74
+KPX T rcommaaccent -74
+KPX T semicolon -74
+KPX T u -92
+KPX T uacute -92
+KPX T ucircumflex -92
+KPX T udieresis -92
+KPX T ugrave -92
+KPX T uhungarumlaut -92
+KPX T umacron -92
+KPX T uogonek -92
+KPX T uring -92
+KPX T w -74
+KPX T y -34
+KPX T yacute -34
+KPX T ydieresis -34
+KPX Tcaron A -90
+KPX Tcaron Aacute -90
+KPX Tcaron Abreve -90
+KPX Tcaron Acircumflex -90
+KPX Tcaron Adieresis -90
+KPX Tcaron Agrave -90
+KPX Tcaron Amacron -90
+KPX Tcaron Aogonek -90
+KPX Tcaron Aring -90
+KPX Tcaron Atilde -90
+KPX Tcaron O -18
+KPX Tcaron Oacute -18
+KPX Tcaron Ocircumflex -18
+KPX Tcaron Odieresis -18
+KPX Tcaron Ograve -18
+KPX Tcaron Ohungarumlaut -18
+KPX Tcaron Omacron -18
+KPX Tcaron Oslash -18
+KPX Tcaron Otilde -18
+KPX Tcaron a -92
+KPX Tcaron aacute -92
+KPX Tcaron abreve -52
+KPX Tcaron acircumflex -52
+KPX Tcaron adieresis -52
+KPX Tcaron agrave -52
+KPX Tcaron amacron -52
+KPX Tcaron aogonek -92
+KPX Tcaron aring -92
+KPX Tcaron atilde -52
+KPX Tcaron colon -74
+KPX Tcaron comma -74
+KPX Tcaron e -92
+KPX Tcaron eacute -92
+KPX Tcaron ecaron -92
+KPX Tcaron ecircumflex -92
+KPX Tcaron edieresis -52
+KPX Tcaron edotaccent -92
+KPX Tcaron egrave -52
+KPX Tcaron emacron -52
+KPX Tcaron eogonek -92
+KPX Tcaron hyphen -92
+KPX Tcaron i -18
+KPX Tcaron iacute -18
+KPX Tcaron iogonek -18
+KPX Tcaron o -92
+KPX Tcaron oacute -92
+KPX Tcaron ocircumflex -92
+KPX Tcaron odieresis -92
+KPX Tcaron ograve -92
+KPX Tcaron ohungarumlaut -92
+KPX Tcaron omacron -92
+KPX Tcaron oslash -92
+KPX Tcaron otilde -92
+KPX Tcaron period -90
+KPX Tcaron r -74
+KPX Tcaron racute -74
+KPX Tcaron rcaron -74
+KPX Tcaron rcommaaccent -74
+KPX Tcaron semicolon -74
+KPX Tcaron u -92
+KPX Tcaron uacute -92
+KPX Tcaron ucircumflex -92
+KPX Tcaron udieresis -92
+KPX Tcaron ugrave -92
+KPX Tcaron uhungarumlaut -92
+KPX Tcaron umacron -92
+KPX Tcaron uogonek -92
+KPX Tcaron uring -92
+KPX Tcaron w -74
+KPX Tcaron y -34
+KPX Tcaron yacute -34
+KPX Tcaron ydieresis -34
+KPX Tcommaaccent A -90
+KPX Tcommaaccent Aacute -90
+KPX Tcommaaccent Abreve -90
+KPX Tcommaaccent Acircumflex -90
+KPX Tcommaaccent Adieresis -90
+KPX Tcommaaccent Agrave -90
+KPX Tcommaaccent Amacron -90
+KPX Tcommaaccent Aogonek -90
+KPX Tcommaaccent Aring -90
+KPX Tcommaaccent Atilde -90
+KPX Tcommaaccent O -18
+KPX Tcommaaccent Oacute -18
+KPX Tcommaaccent Ocircumflex -18
+KPX Tcommaaccent Odieresis -18
+KPX Tcommaaccent Ograve -18
+KPX Tcommaaccent Ohungarumlaut -18
+KPX Tcommaaccent Omacron -18
+KPX Tcommaaccent Oslash -18
+KPX Tcommaaccent Otilde -18
+KPX Tcommaaccent a -92
+KPX Tcommaaccent aacute -92
+KPX Tcommaaccent abreve -52
+KPX Tcommaaccent acircumflex -52
+KPX Tcommaaccent adieresis -52
+KPX Tcommaaccent agrave -52
+KPX Tcommaaccent amacron -52
+KPX Tcommaaccent aogonek -92
+KPX Tcommaaccent aring -92
+KPX Tcommaaccent atilde -52
+KPX Tcommaaccent colon -74
+KPX Tcommaaccent comma -74
+KPX Tcommaaccent e -92
+KPX Tcommaaccent eacute -92
+KPX Tcommaaccent ecaron -92
+KPX Tcommaaccent ecircumflex -92
+KPX Tcommaaccent edieresis -52
+KPX Tcommaaccent edotaccent -92
+KPX Tcommaaccent egrave -52
+KPX Tcommaaccent emacron -52
+KPX Tcommaaccent eogonek -92
+KPX Tcommaaccent hyphen -92
+KPX Tcommaaccent i -18
+KPX Tcommaaccent iacute -18
+KPX Tcommaaccent iogonek -18
+KPX Tcommaaccent o -92
+KPX Tcommaaccent oacute -92
+KPX Tcommaaccent ocircumflex -92
+KPX Tcommaaccent odieresis -92
+KPX Tcommaaccent ograve -92
+KPX Tcommaaccent ohungarumlaut -92
+KPX Tcommaaccent omacron -92
+KPX Tcommaaccent oslash -92
+KPX Tcommaaccent otilde -92
+KPX Tcommaaccent period -90
+KPX Tcommaaccent r -74
+KPX Tcommaaccent racute -74
+KPX Tcommaaccent rcaron -74
+KPX Tcommaaccent rcommaaccent -74
+KPX Tcommaaccent semicolon -74
+KPX Tcommaaccent u -92
+KPX Tcommaaccent uacute -92
+KPX Tcommaaccent ucircumflex -92
+KPX Tcommaaccent udieresis -92
+KPX Tcommaaccent ugrave -92
+KPX Tcommaaccent uhungarumlaut -92
+KPX Tcommaaccent umacron -92
+KPX Tcommaaccent uogonek -92
+KPX Tcommaaccent uring -92
+KPX Tcommaaccent w -74
+KPX Tcommaaccent y -34
+KPX Tcommaaccent yacute -34
+KPX Tcommaaccent ydieresis -34
+KPX U A -60
+KPX U Aacute -60
+KPX U Abreve -60
+KPX U Acircumflex -60
+KPX U Adieresis -60
+KPX U Agrave -60
+KPX U Amacron -60
+KPX U Aogonek -60
+KPX U Aring -60
+KPX U Atilde -60
+KPX U comma -50
+KPX U period -50
+KPX Uacute A -60
+KPX Uacute Aacute -60
+KPX Uacute Abreve -60
+KPX Uacute Acircumflex -60
+KPX Uacute Adieresis -60
+KPX Uacute Agrave -60
+KPX Uacute Amacron -60
+KPX Uacute Aogonek -60
+KPX Uacute Aring -60
+KPX Uacute Atilde -60
+KPX Uacute comma -50
+KPX Uacute period -50
+KPX Ucircumflex A -60
+KPX Ucircumflex Aacute -60
+KPX Ucircumflex Abreve -60
+KPX Ucircumflex Acircumflex -60
+KPX Ucircumflex Adieresis -60
+KPX Ucircumflex Agrave -60
+KPX Ucircumflex Amacron -60
+KPX Ucircumflex Aogonek -60
+KPX Ucircumflex Aring -60
+KPX Ucircumflex Atilde -60
+KPX Ucircumflex comma -50
+KPX Ucircumflex period -50
+KPX Udieresis A -60
+KPX Udieresis Aacute -60
+KPX Udieresis Abreve -60
+KPX Udieresis Acircumflex -60
+KPX Udieresis Adieresis -60
+KPX Udieresis Agrave -60
+KPX Udieresis Amacron -60
+KPX Udieresis Aogonek -60
+KPX Udieresis Aring -60
+KPX Udieresis Atilde -60
+KPX Udieresis comma -50
+KPX Udieresis period -50
+KPX Ugrave A -60
+KPX Ugrave Aacute -60
+KPX Ugrave Abreve -60
+KPX Ugrave Acircumflex -60
+KPX Ugrave Adieresis -60
+KPX Ugrave Agrave -60
+KPX Ugrave Amacron -60
+KPX Ugrave Aogonek -60
+KPX Ugrave Aring -60
+KPX Ugrave Atilde -60
+KPX Ugrave comma -50
+KPX Ugrave period -50
+KPX Uhungarumlaut A -60
+KPX Uhungarumlaut Aacute -60
+KPX Uhungarumlaut Abreve -60
+KPX Uhungarumlaut Acircumflex -60
+KPX Uhungarumlaut Adieresis -60
+KPX Uhungarumlaut Agrave -60
+KPX Uhungarumlaut Amacron -60
+KPX Uhungarumlaut Aogonek -60
+KPX Uhungarumlaut Aring -60
+KPX Uhungarumlaut Atilde -60
+KPX Uhungarumlaut comma -50
+KPX Uhungarumlaut period -50
+KPX Umacron A -60
+KPX Umacron Aacute -60
+KPX Umacron Abreve -60
+KPX Umacron Acircumflex -60
+KPX Umacron Adieresis -60
+KPX Umacron Agrave -60
+KPX Umacron Amacron -60
+KPX Umacron Aogonek -60
+KPX Umacron Aring -60
+KPX Umacron Atilde -60
+KPX Umacron comma -50
+KPX Umacron period -50
+KPX Uogonek A -60
+KPX Uogonek Aacute -60
+KPX Uogonek Abreve -60
+KPX Uogonek Acircumflex -60
+KPX Uogonek Adieresis -60
+KPX Uogonek Agrave -60
+KPX Uogonek Amacron -60
+KPX Uogonek Aogonek -60
+KPX Uogonek Aring -60
+KPX Uogonek Atilde -60
+KPX Uogonek comma -50
+KPX Uogonek period -50
+KPX Uring A -60
+KPX Uring Aacute -60
+KPX Uring Abreve -60
+KPX Uring Acircumflex -60
+KPX Uring Adieresis -60
+KPX Uring Agrave -60
+KPX Uring Amacron -60
+KPX Uring Aogonek -60
+KPX Uring Aring -60
+KPX Uring Atilde -60
+KPX Uring comma -50
+KPX Uring period -50
+KPX V A -135
+KPX V Aacute -135
+KPX V Abreve -135
+KPX V Acircumflex -135
+KPX V Adieresis -135
+KPX V Agrave -135
+KPX V Amacron -135
+KPX V Aogonek -135
+KPX V Aring -135
+KPX V Atilde -135
+KPX V G -30
+KPX V Gbreve -30
+KPX V Gcommaaccent -30
+KPX V O -45
+KPX V Oacute -45
+KPX V Ocircumflex -45
+KPX V Odieresis -45
+KPX V Ograve -45
+KPX V Ohungarumlaut -45
+KPX V Omacron -45
+KPX V Oslash -45
+KPX V Otilde -45
+KPX V a -92
+KPX V aacute -92
+KPX V abreve -92
+KPX V acircumflex -92
+KPX V adieresis -92
+KPX V agrave -92
+KPX V amacron -92
+KPX V aogonek -92
+KPX V aring -92
+KPX V atilde -92
+KPX V colon -92
+KPX V comma -129
+KPX V e -100
+KPX V eacute -100
+KPX V ecaron -100
+KPX V ecircumflex -100
+KPX V edieresis -100
+KPX V edotaccent -100
+KPX V egrave -100
+KPX V emacron -100
+KPX V eogonek -100
+KPX V hyphen -74
+KPX V i -37
+KPX V iacute -37
+KPX V icircumflex -37
+KPX V idieresis -37
+KPX V igrave -37
+KPX V imacron -37
+KPX V iogonek -37
+KPX V o -100
+KPX V oacute -100
+KPX V ocircumflex -100
+KPX V odieresis -100
+KPX V ograve -100
+KPX V ohungarumlaut -100
+KPX V omacron -100
+KPX V oslash -100
+KPX V otilde -100
+KPX V period -145
+KPX V semicolon -92
+KPX V u -92
+KPX V uacute -92
+KPX V ucircumflex -92
+KPX V udieresis -92
+KPX V ugrave -92
+KPX V uhungarumlaut -92
+KPX V umacron -92
+KPX V uogonek -92
+KPX V uring -92
+KPX W A -120
+KPX W Aacute -120
+KPX W Abreve -120
+KPX W Acircumflex -120
+KPX W Adieresis -120
+KPX W Agrave -120
+KPX W Amacron -120
+KPX W Aogonek -120
+KPX W Aring -120
+KPX W Atilde -120
+KPX W O -10
+KPX W Oacute -10
+KPX W Ocircumflex -10
+KPX W Odieresis -10
+KPX W Ograve -10
+KPX W Ohungarumlaut -10
+KPX W Omacron -10
+KPX W Oslash -10
+KPX W Otilde -10
+KPX W a -65
+KPX W aacute -65
+KPX W abreve -65
+KPX W acircumflex -65
+KPX W adieresis -65
+KPX W agrave -65
+KPX W amacron -65
+KPX W aogonek -65
+KPX W aring -65
+KPX W atilde -65
+KPX W colon -55
+KPX W comma -92
+KPX W e -65
+KPX W eacute -65
+KPX W ecaron -65
+KPX W ecircumflex -65
+KPX W edieresis -65
+KPX W edotaccent -65
+KPX W egrave -65
+KPX W emacron -65
+KPX W eogonek -65
+KPX W hyphen -37
+KPX W i -18
+KPX W iacute -18
+KPX W iogonek -18
+KPX W o -75
+KPX W oacute -75
+KPX W ocircumflex -75
+KPX W odieresis -75
+KPX W ograve -75
+KPX W ohungarumlaut -75
+KPX W omacron -75
+KPX W oslash -75
+KPX W otilde -75
+KPX W period -92
+KPX W semicolon -55
+KPX W u -50
+KPX W uacute -50
+KPX W ucircumflex -50
+KPX W udieresis -50
+KPX W ugrave -50
+KPX W uhungarumlaut -50
+KPX W umacron -50
+KPX W uogonek -50
+KPX W uring -50
+KPX W y -60
+KPX W yacute -60
+KPX W ydieresis -60
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -35
+KPX Y Oacute -35
+KPX Y Ocircumflex -35
+KPX Y Odieresis -35
+KPX Y Ograve -35
+KPX Y Ohungarumlaut -35
+KPX Y Omacron -35
+KPX Y Oslash -35
+KPX Y Otilde -35
+KPX Y a -85
+KPX Y aacute -85
+KPX Y abreve -85
+KPX Y acircumflex -85
+KPX Y adieresis -85
+KPX Y agrave -85
+KPX Y amacron -85
+KPX Y aogonek -85
+KPX Y aring -85
+KPX Y atilde -85
+KPX Y colon -92
+KPX Y comma -92
+KPX Y e -111
+KPX Y eacute -111
+KPX Y ecaron -111
+KPX Y ecircumflex -111
+KPX Y edieresis -71
+KPX Y edotaccent -111
+KPX Y egrave -71
+KPX Y emacron -71
+KPX Y eogonek -111
+KPX Y hyphen -92
+KPX Y i -37
+KPX Y iacute -37
+KPX Y iogonek -37
+KPX Y o -111
+KPX Y oacute -111
+KPX Y ocircumflex -111
+KPX Y odieresis -111
+KPX Y ograve -111
+KPX Y ohungarumlaut -111
+KPX Y omacron -111
+KPX Y oslash -111
+KPX Y otilde -111
+KPX Y period -92
+KPX Y semicolon -92
+KPX Y u -92
+KPX Y uacute -92
+KPX Y ucircumflex -92
+KPX Y udieresis -92
+KPX Y ugrave -92
+KPX Y uhungarumlaut -92
+KPX Y umacron -92
+KPX Y uogonek -92
+KPX Y uring -92
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -35
+KPX Yacute Oacute -35
+KPX Yacute Ocircumflex -35
+KPX Yacute Odieresis -35
+KPX Yacute Ograve -35
+KPX Yacute Ohungarumlaut -35
+KPX Yacute Omacron -35
+KPX Yacute Oslash -35
+KPX Yacute Otilde -35
+KPX Yacute a -85
+KPX Yacute aacute -85
+KPX Yacute abreve -85
+KPX Yacute acircumflex -85
+KPX Yacute adieresis -85
+KPX Yacute agrave -85
+KPX Yacute amacron -85
+KPX Yacute aogonek -85
+KPX Yacute aring -85
+KPX Yacute atilde -85
+KPX Yacute colon -92
+KPX Yacute comma -92
+KPX Yacute e -111
+KPX Yacute eacute -111
+KPX Yacute ecaron -111
+KPX Yacute ecircumflex -111
+KPX Yacute edieresis -71
+KPX Yacute edotaccent -111
+KPX Yacute egrave -71
+KPX Yacute emacron -71
+KPX Yacute eogonek -111
+KPX Yacute hyphen -92
+KPX Yacute i -37
+KPX Yacute iacute -37
+KPX Yacute iogonek -37
+KPX Yacute o -111
+KPX Yacute oacute -111
+KPX Yacute ocircumflex -111
+KPX Yacute odieresis -111
+KPX Yacute ograve -111
+KPX Yacute ohungarumlaut -111
+KPX Yacute omacron -111
+KPX Yacute oslash -111
+KPX Yacute otilde -111
+KPX Yacute period -92
+KPX Yacute semicolon -92
+KPX Yacute u -92
+KPX Yacute uacute -92
+KPX Yacute ucircumflex -92
+KPX Yacute udieresis -92
+KPX Yacute ugrave -92
+KPX Yacute uhungarumlaut -92
+KPX Yacute umacron -92
+KPX Yacute uogonek -92
+KPX Yacute uring -92
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -35
+KPX Ydieresis Oacute -35
+KPX Ydieresis Ocircumflex -35
+KPX Ydieresis Odieresis -35
+KPX Ydieresis Ograve -35
+KPX Ydieresis Ohungarumlaut -35
+KPX Ydieresis Omacron -35
+KPX Ydieresis Oslash -35
+KPX Ydieresis Otilde -35
+KPX Ydieresis a -85
+KPX Ydieresis aacute -85
+KPX Ydieresis abreve -85
+KPX Ydieresis acircumflex -85
+KPX Ydieresis adieresis -85
+KPX Ydieresis agrave -85
+KPX Ydieresis amacron -85
+KPX Ydieresis aogonek -85
+KPX Ydieresis aring -85
+KPX Ydieresis atilde -85
+KPX Ydieresis colon -92
+KPX Ydieresis comma -92
+KPX Ydieresis e -111
+KPX Ydieresis eacute -111
+KPX Ydieresis ecaron -111
+KPX Ydieresis ecircumflex -111
+KPX Ydieresis edieresis -71
+KPX Ydieresis edotaccent -111
+KPX Ydieresis egrave -71
+KPX Ydieresis emacron -71
+KPX Ydieresis eogonek -111
+KPX Ydieresis hyphen -92
+KPX Ydieresis i -37
+KPX Ydieresis iacute -37
+KPX Ydieresis iogonek -37
+KPX Ydieresis o -111
+KPX Ydieresis oacute -111
+KPX Ydieresis ocircumflex -111
+KPX Ydieresis odieresis -111
+KPX Ydieresis ograve -111
+KPX Ydieresis ohungarumlaut -111
+KPX Ydieresis omacron -111
+KPX Ydieresis oslash -111
+KPX Ydieresis otilde -111
+KPX Ydieresis period -92
+KPX Ydieresis semicolon -92
+KPX Ydieresis u -92
+KPX Ydieresis uacute -92
+KPX Ydieresis ucircumflex -92
+KPX Ydieresis udieresis -92
+KPX Ydieresis ugrave -92
+KPX Ydieresis uhungarumlaut -92
+KPX Ydieresis umacron -92
+KPX Ydieresis uogonek -92
+KPX Ydieresis uring -92
+KPX a v -25
+KPX aacute v -25
+KPX abreve v -25
+KPX acircumflex v -25
+KPX adieresis v -25
+KPX agrave v -25
+KPX amacron v -25
+KPX aogonek v -25
+KPX aring v -25
+KPX atilde v -25
+KPX b b -10
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -15
+KPX comma quotedblright -45
+KPX comma quoteright -55
+KPX d w -15
+KPX dcroat w -15
+KPX e v -15
+KPX eacute v -15
+KPX ecaron v -15
+KPX ecircumflex v -15
+KPX edieresis v -15
+KPX edotaccent v -15
+KPX egrave v -15
+KPX emacron v -15
+KPX eogonek v -15
+KPX f comma -15
+KPX f dotlessi -35
+KPX f i -25
+KPX f o -25
+KPX f oacute -25
+KPX f ocircumflex -25
+KPX f odieresis -25
+KPX f ograve -25
+KPX f ohungarumlaut -25
+KPX f omacron -25
+KPX f oslash -25
+KPX f otilde -25
+KPX f period -15
+KPX f quotedblright 50
+KPX f quoteright 55
+KPX g period -15
+KPX gbreve period -15
+KPX gcommaaccent period -15
+KPX h y -15
+KPX h yacute -15
+KPX h ydieresis -15
+KPX i v -10
+KPX iacute v -10
+KPX icircumflex v -10
+KPX idieresis v -10
+KPX igrave v -10
+KPX imacron v -10
+KPX iogonek v -10
+KPX k e -10
+KPX k eacute -10
+KPX k ecaron -10
+KPX k ecircumflex -10
+KPX k edieresis -10
+KPX k edotaccent -10
+KPX k egrave -10
+KPX k emacron -10
+KPX k eogonek -10
+KPX k o -15
+KPX k oacute -15
+KPX k ocircumflex -15
+KPX k odieresis -15
+KPX k ograve -15
+KPX k ohungarumlaut -15
+KPX k omacron -15
+KPX k oslash -15
+KPX k otilde -15
+KPX k y -15
+KPX k yacute -15
+KPX k ydieresis -15
+KPX kcommaaccent e -10
+KPX kcommaaccent eacute -10
+KPX kcommaaccent ecaron -10
+KPX kcommaaccent ecircumflex -10
+KPX kcommaaccent edieresis -10
+KPX kcommaaccent edotaccent -10
+KPX kcommaaccent egrave -10
+KPX kcommaaccent emacron -10
+KPX kcommaaccent eogonek -10
+KPX kcommaaccent o -15
+KPX kcommaaccent oacute -15
+KPX kcommaaccent ocircumflex -15
+KPX kcommaaccent odieresis -15
+KPX kcommaaccent ograve -15
+KPX kcommaaccent ohungarumlaut -15
+KPX kcommaaccent omacron -15
+KPX kcommaaccent oslash -15
+KPX kcommaaccent otilde -15
+KPX kcommaaccent y -15
+KPX kcommaaccent yacute -15
+KPX kcommaaccent ydieresis -15
+KPX n v -40
+KPX nacute v -40
+KPX ncaron v -40
+KPX ncommaaccent v -40
+KPX ntilde v -40
+KPX o v -10
+KPX o w -10
+KPX oacute v -10
+KPX oacute w -10
+KPX ocircumflex v -10
+KPX ocircumflex w -10
+KPX odieresis v -10
+KPX odieresis w -10
+KPX ograve v -10
+KPX ograve w -10
+KPX ohungarumlaut v -10
+KPX ohungarumlaut w -10
+KPX omacron v -10
+KPX omacron w -10
+KPX oslash v -10
+KPX oslash w -10
+KPX otilde v -10
+KPX otilde w -10
+KPX period quotedblright -55
+KPX period quoteright -55
+KPX quotedblleft A -10
+KPX quotedblleft Aacute -10
+KPX quotedblleft Abreve -10
+KPX quotedblleft Acircumflex -10
+KPX quotedblleft Adieresis -10
+KPX quotedblleft Agrave -10
+KPX quotedblleft Amacron -10
+KPX quotedblleft Aogonek -10
+KPX quotedblleft Aring -10
+KPX quotedblleft Atilde -10
+KPX quoteleft A -10
+KPX quoteleft Aacute -10
+KPX quoteleft Abreve -10
+KPX quoteleft Acircumflex -10
+KPX quoteleft Adieresis -10
+KPX quoteleft Agrave -10
+KPX quoteleft Amacron -10
+KPX quoteleft Aogonek -10
+KPX quoteleft Aring -10
+KPX quoteleft Atilde -10
+KPX quoteleft quoteleft -63
+KPX quoteright d -20
+KPX quoteright dcroat -20
+KPX quoteright quoteright -63
+KPX quoteright r -20
+KPX quoteright racute -20
+KPX quoteright rcaron -20
+KPX quoteright rcommaaccent -20
+KPX quoteright s -37
+KPX quoteright sacute -37
+KPX quoteright scaron -37
+KPX quoteright scedilla -37
+KPX quoteright scommaaccent -37
+KPX quoteright space -74
+KPX quoteright v -20
+KPX r c -18
+KPX r cacute -18
+KPX r ccaron -18
+KPX r ccedilla -18
+KPX r comma -92
+KPX r e -18
+KPX r eacute -18
+KPX r ecaron -18
+KPX r ecircumflex -18
+KPX r edieresis -18
+KPX r edotaccent -18
+KPX r egrave -18
+KPX r emacron -18
+KPX r eogonek -18
+KPX r g -10
+KPX r gbreve -10
+KPX r gcommaaccent -10
+KPX r hyphen -37
+KPX r n -15
+KPX r nacute -15
+KPX r ncaron -15
+KPX r ncommaaccent -15
+KPX r ntilde -15
+KPX r o -18
+KPX r oacute -18
+KPX r ocircumflex -18
+KPX r odieresis -18
+KPX r ograve -18
+KPX r ohungarumlaut -18
+KPX r omacron -18
+KPX r oslash -18
+KPX r otilde -18
+KPX r p -10
+KPX r period -100
+KPX r q -18
+KPX r v -10
+KPX racute c -18
+KPX racute cacute -18
+KPX racute ccaron -18
+KPX racute ccedilla -18
+KPX racute comma -92
+KPX racute e -18
+KPX racute eacute -18
+KPX racute ecaron -18
+KPX racute ecircumflex -18
+KPX racute edieresis -18
+KPX racute edotaccent -18
+KPX racute egrave -18
+KPX racute emacron -18
+KPX racute eogonek -18
+KPX racute g -10
+KPX racute gbreve -10
+KPX racute gcommaaccent -10
+KPX racute hyphen -37
+KPX racute n -15
+KPX racute nacute -15
+KPX racute ncaron -15
+KPX racute ncommaaccent -15
+KPX racute ntilde -15
+KPX racute o -18
+KPX racute oacute -18
+KPX racute ocircumflex -18
+KPX racute odieresis -18
+KPX racute ograve -18
+KPX racute ohungarumlaut -18
+KPX racute omacron -18
+KPX racute oslash -18
+KPX racute otilde -18
+KPX racute p -10
+KPX racute period -100
+KPX racute q -18
+KPX racute v -10
+KPX rcaron c -18
+KPX rcaron cacute -18
+KPX rcaron ccaron -18
+KPX rcaron ccedilla -18
+KPX rcaron comma -92
+KPX rcaron e -18
+KPX rcaron eacute -18
+KPX rcaron ecaron -18
+KPX rcaron ecircumflex -18
+KPX rcaron edieresis -18
+KPX rcaron edotaccent -18
+KPX rcaron egrave -18
+KPX rcaron emacron -18
+KPX rcaron eogonek -18
+KPX rcaron g -10
+KPX rcaron gbreve -10
+KPX rcaron gcommaaccent -10
+KPX rcaron hyphen -37
+KPX rcaron n -15
+KPX rcaron nacute -15
+KPX rcaron ncaron -15
+KPX rcaron ncommaaccent -15
+KPX rcaron ntilde -15
+KPX rcaron o -18
+KPX rcaron oacute -18
+KPX rcaron ocircumflex -18
+KPX rcaron odieresis -18
+KPX rcaron ograve -18
+KPX rcaron ohungarumlaut -18
+KPX rcaron omacron -18
+KPX rcaron oslash -18
+KPX rcaron otilde -18
+KPX rcaron p -10
+KPX rcaron period -100
+KPX rcaron q -18
+KPX rcaron v -10
+KPX rcommaaccent c -18
+KPX rcommaaccent cacute -18
+KPX rcommaaccent ccaron -18
+KPX rcommaaccent ccedilla -18
+KPX rcommaaccent comma -92
+KPX rcommaaccent e -18
+KPX rcommaaccent eacute -18
+KPX rcommaaccent ecaron -18
+KPX rcommaaccent ecircumflex -18
+KPX rcommaaccent edieresis -18
+KPX rcommaaccent edotaccent -18
+KPX rcommaaccent egrave -18
+KPX rcommaaccent emacron -18
+KPX rcommaaccent eogonek -18
+KPX rcommaaccent g -10
+KPX rcommaaccent gbreve -10
+KPX rcommaaccent gcommaaccent -10
+KPX rcommaaccent hyphen -37
+KPX rcommaaccent n -15
+KPX rcommaaccent nacute -15
+KPX rcommaaccent ncaron -15
+KPX rcommaaccent ncommaaccent -15
+KPX rcommaaccent ntilde -15
+KPX rcommaaccent o -18
+KPX rcommaaccent oacute -18
+KPX rcommaaccent ocircumflex -18
+KPX rcommaaccent odieresis -18
+KPX rcommaaccent ograve -18
+KPX rcommaaccent ohungarumlaut -18
+KPX rcommaaccent omacron -18
+KPX rcommaaccent oslash -18
+KPX rcommaaccent otilde -18
+KPX rcommaaccent p -10
+KPX rcommaaccent period -100
+KPX rcommaaccent q -18
+KPX rcommaaccent v -10
+KPX space A -55
+KPX space Aacute -55
+KPX space Abreve -55
+KPX space Acircumflex -55
+KPX space Adieresis -55
+KPX space Agrave -55
+KPX space Amacron -55
+KPX space Aogonek -55
+KPX space Aring -55
+KPX space Atilde -55
+KPX space T -30
+KPX space Tcaron -30
+KPX space Tcommaaccent -30
+KPX space V -45
+KPX space W -30
+KPX space Y -55
+KPX space Yacute -55
+KPX space Ydieresis -55
+KPX v a -10
+KPX v aacute -10
+KPX v abreve -10
+KPX v acircumflex -10
+KPX v adieresis -10
+KPX v agrave -10
+KPX v amacron -10
+KPX v aogonek -10
+KPX v aring -10
+KPX v atilde -10
+KPX v comma -55
+KPX v e -10
+KPX v eacute -10
+KPX v ecaron -10
+KPX v ecircumflex -10
+KPX v edieresis -10
+KPX v edotaccent -10
+KPX v egrave -10
+KPX v emacron -10
+KPX v eogonek -10
+KPX v o -10
+KPX v oacute -10
+KPX v ocircumflex -10
+KPX v odieresis -10
+KPX v ograve -10
+KPX v ohungarumlaut -10
+KPX v omacron -10
+KPX v oslash -10
+KPX v otilde -10
+KPX v period -70
+KPX w comma -55
+KPX w o -10
+KPX w oacute -10
+KPX w ocircumflex -10
+KPX w odieresis -10
+KPX w ograve -10
+KPX w ohungarumlaut -10
+KPX w omacron -10
+KPX w oslash -10
+KPX w otilde -10
+KPX w period -70
+KPX y comma -55
+KPX y e -10
+KPX y eacute -10
+KPX y ecaron -10
+KPX y ecircumflex -10
+KPX y edieresis -10
+KPX y edotaccent -10
+KPX y egrave -10
+KPX y emacron -10
+KPX y eogonek -10
+KPX y o -25
+KPX y oacute -25
+KPX y ocircumflex -25
+KPX y odieresis -25
+KPX y ograve -25
+KPX y ohungarumlaut -25
+KPX y omacron -25
+KPX y oslash -25
+KPX y otilde -25
+KPX y period -70
+KPX yacute comma -55
+KPX yacute e -10
+KPX yacute eacute -10
+KPX yacute ecaron -10
+KPX yacute ecircumflex -10
+KPX yacute edieresis -10
+KPX yacute edotaccent -10
+KPX yacute egrave -10
+KPX yacute emacron -10
+KPX yacute eogonek -10
+KPX yacute o -25
+KPX yacute oacute -25
+KPX yacute ocircumflex -25
+KPX yacute odieresis -25
+KPX yacute ograve -25
+KPX yacute ohungarumlaut -25
+KPX yacute omacron -25
+KPX yacute oslash -25
+KPX yacute otilde -25
+KPX yacute period -70
+KPX ydieresis comma -55
+KPX ydieresis e -10
+KPX ydieresis eacute -10
+KPX ydieresis ecaron -10
+KPX ydieresis ecircumflex -10
+KPX ydieresis edieresis -10
+KPX ydieresis edotaccent -10
+KPX ydieresis egrave -10
+KPX ydieresis emacron -10
+KPX ydieresis eogonek -10
+KPX ydieresis o -25
+KPX ydieresis oacute -25
+KPX ydieresis ocircumflex -25
+KPX ydieresis odieresis -25
+KPX ydieresis ograve -25
+KPX ydieresis ohungarumlaut -25
+KPX ydieresis omacron -25
+KPX ydieresis oslash -25
+KPX ydieresis otilde -25
+KPX ydieresis period -70
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-BoldItalic.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-BoldItalic.afm
new file mode 100644
index 0000000000000000000000000000000000000000..2301dfd232647c35540f590315823c18f9633670
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-BoldItalic.afm
@@ -0,0 +1,2384 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 13:04:06 1997
+Comment UniqueID 43066
+Comment VMusage 45874 56899
+FontName Times-BoldItalic
+FullName Times Bold Italic
+FamilyName Times
+Weight Bold
+ItalicAngle -15
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -200 -218 996 921
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 669
+XHeight 462
+Ascender 683
+Descender -217
+StdHW 42
+StdVW 121
+StartCharMetrics 315
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 389 ; N exclam ; B 67 -13 370 684 ;
+C 34 ; WX 555 ; N quotedbl ; B 136 398 536 685 ;
+C 35 ; WX 500 ; N numbersign ; B -33 0 533 700 ;
+C 36 ; WX 500 ; N dollar ; B -20 -100 497 733 ;
+C 37 ; WX 833 ; N percent ; B 39 -10 793 692 ;
+C 38 ; WX 778 ; N ampersand ; B 5 -19 699 682 ;
+C 39 ; WX 333 ; N quoteright ; B 98 369 302 685 ;
+C 40 ; WX 333 ; N parenleft ; B 28 -179 344 685 ;
+C 41 ; WX 333 ; N parenright ; B -44 -179 271 685 ;
+C 42 ; WX 500 ; N asterisk ; B 65 249 456 685 ;
+C 43 ; WX 570 ; N plus ; B 33 0 537 506 ;
+C 44 ; WX 250 ; N comma ; B -60 -182 144 134 ;
+C 45 ; WX 333 ; N hyphen ; B 2 166 271 282 ;
+C 46 ; WX 250 ; N period ; B -9 -13 139 135 ;
+C 47 ; WX 278 ; N slash ; B -64 -18 342 685 ;
+C 48 ; WX 500 ; N zero ; B 17 -14 477 683 ;
+C 49 ; WX 500 ; N one ; B 5 0 419 683 ;
+C 50 ; WX 500 ; N two ; B -27 0 446 683 ;
+C 51 ; WX 500 ; N three ; B -15 -13 450 683 ;
+C 52 ; WX 500 ; N four ; B -15 0 503 683 ;
+C 53 ; WX 500 ; N five ; B -11 -13 487 669 ;
+C 54 ; WX 500 ; N six ; B 23 -15 509 679 ;
+C 55 ; WX 500 ; N seven ; B 52 0 525 669 ;
+C 56 ; WX 500 ; N eight ; B 3 -13 476 683 ;
+C 57 ; WX 500 ; N nine ; B -12 -10 475 683 ;
+C 58 ; WX 333 ; N colon ; B 23 -13 264 459 ;
+C 59 ; WX 333 ; N semicolon ; B -25 -183 264 459 ;
+C 60 ; WX 570 ; N less ; B 31 -8 539 514 ;
+C 61 ; WX 570 ; N equal ; B 33 107 537 399 ;
+C 62 ; WX 570 ; N greater ; B 31 -8 539 514 ;
+C 63 ; WX 500 ; N question ; B 79 -13 470 684 ;
+C 64 ; WX 832 ; N at ; B 63 -18 770 685 ;
+C 65 ; WX 667 ; N A ; B -67 0 593 683 ;
+C 66 ; WX 667 ; N B ; B -24 0 624 669 ;
+C 67 ; WX 667 ; N C ; B 32 -18 677 685 ;
+C 68 ; WX 722 ; N D ; B -46 0 685 669 ;
+C 69 ; WX 667 ; N E ; B -27 0 653 669 ;
+C 70 ; WX 667 ; N F ; B -13 0 660 669 ;
+C 71 ; WX 722 ; N G ; B 21 -18 706 685 ;
+C 72 ; WX 778 ; N H ; B -24 0 799 669 ;
+C 73 ; WX 389 ; N I ; B -32 0 406 669 ;
+C 74 ; WX 500 ; N J ; B -46 -99 524 669 ;
+C 75 ; WX 667 ; N K ; B -21 0 702 669 ;
+C 76 ; WX 611 ; N L ; B -22 0 590 669 ;
+C 77 ; WX 889 ; N M ; B -29 -12 917 669 ;
+C 78 ; WX 722 ; N N ; B -27 -15 748 669 ;
+C 79 ; WX 722 ; N O ; B 27 -18 691 685 ;
+C 80 ; WX 611 ; N P ; B -27 0 613 669 ;
+C 81 ; WX 722 ; N Q ; B 27 -208 691 685 ;
+C 82 ; WX 667 ; N R ; B -29 0 623 669 ;
+C 83 ; WX 556 ; N S ; B 2 -18 526 685 ;
+C 84 ; WX 611 ; N T ; B 50 0 650 669 ;
+C 85 ; WX 722 ; N U ; B 67 -18 744 669 ;
+C 86 ; WX 667 ; N V ; B 65 -18 715 669 ;
+C 87 ; WX 889 ; N W ; B 65 -18 940 669 ;
+C 88 ; WX 667 ; N X ; B -24 0 694 669 ;
+C 89 ; WX 611 ; N Y ; B 73 0 659 669 ;
+C 90 ; WX 611 ; N Z ; B -11 0 590 669 ;
+C 91 ; WX 333 ; N bracketleft ; B -37 -159 362 674 ;
+C 92 ; WX 278 ; N backslash ; B -1 -18 279 685 ;
+C 93 ; WX 333 ; N bracketright ; B -56 -157 343 674 ;
+C 94 ; WX 570 ; N asciicircum ; B 67 304 503 669 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 333 ; N quoteleft ; B 128 369 332 685 ;
+C 97 ; WX 500 ; N a ; B -21 -14 455 462 ;
+C 98 ; WX 500 ; N b ; B -14 -13 444 699 ;
+C 99 ; WX 444 ; N c ; B -5 -13 392 462 ;
+C 100 ; WX 500 ; N d ; B -21 -13 517 699 ;
+C 101 ; WX 444 ; N e ; B 5 -13 398 462 ;
+C 102 ; WX 333 ; N f ; B -169 -205 446 698 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B -52 -203 478 462 ;
+C 104 ; WX 556 ; N h ; B -13 -9 498 699 ;
+C 105 ; WX 278 ; N i ; B 2 -9 263 684 ;
+C 106 ; WX 278 ; N j ; B -189 -207 279 684 ;
+C 107 ; WX 500 ; N k ; B -23 -8 483 699 ;
+C 108 ; WX 278 ; N l ; B 2 -9 290 699 ;
+C 109 ; WX 778 ; N m ; B -14 -9 722 462 ;
+C 110 ; WX 556 ; N n ; B -6 -9 493 462 ;
+C 111 ; WX 500 ; N o ; B -3 -13 441 462 ;
+C 112 ; WX 500 ; N p ; B -120 -205 446 462 ;
+C 113 ; WX 500 ; N q ; B 1 -205 471 462 ;
+C 114 ; WX 389 ; N r ; B -21 0 389 462 ;
+C 115 ; WX 389 ; N s ; B -19 -13 333 462 ;
+C 116 ; WX 278 ; N t ; B -11 -9 281 594 ;
+C 117 ; WX 556 ; N u ; B 15 -9 492 462 ;
+C 118 ; WX 444 ; N v ; B 16 -13 401 462 ;
+C 119 ; WX 667 ; N w ; B 16 -13 614 462 ;
+C 120 ; WX 500 ; N x ; B -46 -13 469 462 ;
+C 121 ; WX 444 ; N y ; B -94 -205 392 462 ;
+C 122 ; WX 389 ; N z ; B -43 -78 368 449 ;
+C 123 ; WX 348 ; N braceleft ; B 5 -187 436 686 ;
+C 124 ; WX 220 ; N bar ; B 66 -218 154 782 ;
+C 125 ; WX 348 ; N braceright ; B -129 -187 302 686 ;
+C 126 ; WX 570 ; N asciitilde ; B 54 173 516 333 ;
+C 161 ; WX 389 ; N exclamdown ; B 19 -205 322 492 ;
+C 162 ; WX 500 ; N cent ; B 42 -143 439 576 ;
+C 163 ; WX 500 ; N sterling ; B -32 -12 510 683 ;
+C 164 ; WX 167 ; N fraction ; B -169 -14 324 683 ;
+C 165 ; WX 500 ; N yen ; B 33 0 628 669 ;
+C 166 ; WX 500 ; N florin ; B -87 -156 537 707 ;
+C 167 ; WX 500 ; N section ; B 36 -143 459 685 ;
+C 168 ; WX 500 ; N currency ; B -26 34 526 586 ;
+C 169 ; WX 278 ; N quotesingle ; B 128 398 268 685 ;
+C 170 ; WX 500 ; N quotedblleft ; B 53 369 513 685 ;
+C 171 ; WX 500 ; N guillemotleft ; B 12 32 468 415 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 32 32 303 415 ;
+C 173 ; WX 333 ; N guilsinglright ; B 10 32 281 415 ;
+C 174 ; WX 556 ; N fi ; B -188 -205 514 703 ;
+C 175 ; WX 556 ; N fl ; B -186 -205 553 704 ;
+C 177 ; WX 500 ; N endash ; B -40 178 477 269 ;
+C 178 ; WX 500 ; N dagger ; B 91 -145 494 685 ;
+C 179 ; WX 500 ; N daggerdbl ; B 10 -139 493 685 ;
+C 180 ; WX 250 ; N periodcentered ; B 51 257 199 405 ;
+C 182 ; WX 500 ; N paragraph ; B -57 -193 562 669 ;
+C 183 ; WX 350 ; N bullet ; B 0 175 350 525 ;
+C 184 ; WX 333 ; N quotesinglbase ; B -5 -182 199 134 ;
+C 185 ; WX 500 ; N quotedblbase ; B -57 -182 403 134 ;
+C 186 ; WX 500 ; N quotedblright ; B 53 369 513 685 ;
+C 187 ; WX 500 ; N guillemotright ; B 12 32 468 415 ;
+C 188 ; WX 1000 ; N ellipsis ; B 40 -13 852 135 ;
+C 189 ; WX 1000 ; N perthousand ; B 7 -29 996 706 ;
+C 191 ; WX 500 ; N questiondown ; B 30 -205 421 492 ;
+C 193 ; WX 333 ; N grave ; B 85 516 297 697 ;
+C 194 ; WX 333 ; N acute ; B 139 516 379 697 ;
+C 195 ; WX 333 ; N circumflex ; B 40 516 367 690 ;
+C 196 ; WX 333 ; N tilde ; B 48 536 407 655 ;
+C 197 ; WX 333 ; N macron ; B 51 553 393 623 ;
+C 198 ; WX 333 ; N breve ; B 71 516 387 678 ;
+C 199 ; WX 333 ; N dotaccent ; B 163 550 298 684 ;
+C 200 ; WX 333 ; N dieresis ; B 55 550 402 684 ;
+C 202 ; WX 333 ; N ring ; B 127 516 340 729 ;
+C 203 ; WX 333 ; N cedilla ; B -80 -218 156 5 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 69 516 498 697 ;
+C 206 ; WX 333 ; N ogonek ; B 15 -183 244 34 ;
+C 207 ; WX 333 ; N caron ; B 79 516 411 690 ;
+C 208 ; WX 1000 ; N emdash ; B -40 178 977 269 ;
+C 225 ; WX 944 ; N AE ; B -64 0 918 669 ;
+C 227 ; WX 266 ; N ordfeminine ; B 16 399 330 685 ;
+C 232 ; WX 611 ; N Lslash ; B -22 0 590 669 ;
+C 233 ; WX 722 ; N Oslash ; B 27 -125 691 764 ;
+C 234 ; WX 944 ; N OE ; B 23 -8 946 677 ;
+C 235 ; WX 300 ; N ordmasculine ; B 56 400 347 685 ;
+C 241 ; WX 722 ; N ae ; B -5 -13 673 462 ;
+C 245 ; WX 278 ; N dotlessi ; B 2 -9 238 462 ;
+C 248 ; WX 278 ; N lslash ; B -7 -9 307 699 ;
+C 249 ; WX 500 ; N oslash ; B -3 -119 441 560 ;
+C 250 ; WX 722 ; N oe ; B 6 -13 674 462 ;
+C 251 ; WX 500 ; N germandbls ; B -200 -200 473 705 ;
+C -1 ; WX 389 ; N Idieresis ; B -32 0 450 862 ;
+C -1 ; WX 444 ; N eacute ; B 5 -13 435 697 ;
+C -1 ; WX 500 ; N abreve ; B -21 -14 471 678 ;
+C -1 ; WX 556 ; N uhungarumlaut ; B 15 -9 610 697 ;
+C -1 ; WX 444 ; N ecaron ; B 5 -13 467 690 ;
+C -1 ; WX 611 ; N Ydieresis ; B 73 0 659 862 ;
+C -1 ; WX 570 ; N divide ; B 33 -29 537 535 ;
+C -1 ; WX 611 ; N Yacute ; B 73 0 659 904 ;
+C -1 ; WX 667 ; N Acircumflex ; B -67 0 593 897 ;
+C -1 ; WX 500 ; N aacute ; B -21 -14 463 697 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 67 -18 744 897 ;
+C -1 ; WX 444 ; N yacute ; B -94 -205 435 697 ;
+C -1 ; WX 389 ; N scommaaccent ; B -19 -218 333 462 ;
+C -1 ; WX 444 ; N ecircumflex ; B 5 -13 423 690 ;
+C -1 ; WX 722 ; N Uring ; B 67 -18 744 921 ;
+C -1 ; WX 722 ; N Udieresis ; B 67 -18 744 862 ;
+C -1 ; WX 500 ; N aogonek ; B -21 -183 455 462 ;
+C -1 ; WX 722 ; N Uacute ; B 67 -18 744 904 ;
+C -1 ; WX 556 ; N uogonek ; B 15 -183 492 462 ;
+C -1 ; WX 667 ; N Edieresis ; B -27 0 653 862 ;
+C -1 ; WX 722 ; N Dcroat ; B -31 0 700 669 ;
+C -1 ; WX 250 ; N commaaccent ; B -36 -218 131 -50 ;
+C -1 ; WX 747 ; N copyright ; B 30 -18 718 685 ;
+C -1 ; WX 667 ; N Emacron ; B -27 0 653 830 ;
+C -1 ; WX 444 ; N ccaron ; B -5 -13 467 690 ;
+C -1 ; WX 500 ; N aring ; B -21 -14 455 729 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B -27 -218 748 669 ;
+C -1 ; WX 278 ; N lacute ; B 2 -9 392 904 ;
+C -1 ; WX 500 ; N agrave ; B -21 -14 455 697 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 50 -218 650 669 ;
+C -1 ; WX 667 ; N Cacute ; B 32 -18 677 904 ;
+C -1 ; WX 500 ; N atilde ; B -21 -14 491 655 ;
+C -1 ; WX 667 ; N Edotaccent ; B -27 0 653 862 ;
+C -1 ; WX 389 ; N scaron ; B -19 -13 424 690 ;
+C -1 ; WX 389 ; N scedilla ; B -19 -218 333 462 ;
+C -1 ; WX 278 ; N iacute ; B 2 -9 352 697 ;
+C -1 ; WX 494 ; N lozenge ; B 10 0 484 745 ;
+C -1 ; WX 667 ; N Rcaron ; B -29 0 623 897 ;
+C -1 ; WX 722 ; N Gcommaaccent ; B 21 -218 706 685 ;
+C -1 ; WX 556 ; N ucircumflex ; B 15 -9 492 690 ;
+C -1 ; WX 500 ; N acircumflex ; B -21 -14 455 690 ;
+C -1 ; WX 667 ; N Amacron ; B -67 0 593 830 ;
+C -1 ; WX 389 ; N rcaron ; B -21 0 424 690 ;
+C -1 ; WX 444 ; N ccedilla ; B -5 -218 392 462 ;
+C -1 ; WX 611 ; N Zdotaccent ; B -11 0 590 862 ;
+C -1 ; WX 611 ; N Thorn ; B -27 0 573 669 ;
+C -1 ; WX 722 ; N Omacron ; B 27 -18 691 830 ;
+C -1 ; WX 667 ; N Racute ; B -29 0 623 904 ;
+C -1 ; WX 556 ; N Sacute ; B 2 -18 531 904 ;
+C -1 ; WX 608 ; N dcaron ; B -21 -13 675 708 ;
+C -1 ; WX 722 ; N Umacron ; B 67 -18 744 830 ;
+C -1 ; WX 556 ; N uring ; B 15 -9 492 729 ;
+C -1 ; WX 300 ; N threesuperior ; B 17 265 321 683 ;
+C -1 ; WX 722 ; N Ograve ; B 27 -18 691 904 ;
+C -1 ; WX 667 ; N Agrave ; B -67 0 593 904 ;
+C -1 ; WX 667 ; N Abreve ; B -67 0 593 885 ;
+C -1 ; WX 570 ; N multiply ; B 48 16 522 490 ;
+C -1 ; WX 556 ; N uacute ; B 15 -9 492 697 ;
+C -1 ; WX 611 ; N Tcaron ; B 50 0 650 897 ;
+C -1 ; WX 494 ; N partialdiff ; B 11 -21 494 750 ;
+C -1 ; WX 444 ; N ydieresis ; B -94 -205 443 655 ;
+C -1 ; WX 722 ; N Nacute ; B -27 -15 748 904 ;
+C -1 ; WX 278 ; N icircumflex ; B -3 -9 324 690 ;
+C -1 ; WX 667 ; N Ecircumflex ; B -27 0 653 897 ;
+C -1 ; WX 500 ; N adieresis ; B -21 -14 476 655 ;
+C -1 ; WX 444 ; N edieresis ; B 5 -13 448 655 ;
+C -1 ; WX 444 ; N cacute ; B -5 -13 435 697 ;
+C -1 ; WX 556 ; N nacute ; B -6 -9 493 697 ;
+C -1 ; WX 556 ; N umacron ; B 15 -9 492 623 ;
+C -1 ; WX 722 ; N Ncaron ; B -27 -15 748 897 ;
+C -1 ; WX 389 ; N Iacute ; B -32 0 432 904 ;
+C -1 ; WX 570 ; N plusminus ; B 33 0 537 506 ;
+C -1 ; WX 220 ; N brokenbar ; B 66 -143 154 707 ;
+C -1 ; WX 747 ; N registered ; B 30 -18 718 685 ;
+C -1 ; WX 722 ; N Gbreve ; B 21 -18 706 885 ;
+C -1 ; WX 389 ; N Idotaccent ; B -32 0 406 862 ;
+C -1 ; WX 600 ; N summation ; B 14 -10 585 706 ;
+C -1 ; WX 667 ; N Egrave ; B -27 0 653 904 ;
+C -1 ; WX 389 ; N racute ; B -21 0 407 697 ;
+C -1 ; WX 500 ; N omacron ; B -3 -13 462 623 ;
+C -1 ; WX 611 ; N Zacute ; B -11 0 590 904 ;
+C -1 ; WX 611 ; N Zcaron ; B -11 0 590 897 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 704 ;
+C -1 ; WX 722 ; N Eth ; B -31 0 700 669 ;
+C -1 ; WX 667 ; N Ccedilla ; B 32 -218 677 685 ;
+C -1 ; WX 278 ; N lcommaaccent ; B -42 -218 290 699 ;
+C -1 ; WX 366 ; N tcaron ; B -11 -9 434 754 ;
+C -1 ; WX 444 ; N eogonek ; B 5 -183 398 462 ;
+C -1 ; WX 722 ; N Uogonek ; B 67 -183 744 669 ;
+C -1 ; WX 667 ; N Aacute ; B -67 0 593 904 ;
+C -1 ; WX 667 ; N Adieresis ; B -67 0 593 862 ;
+C -1 ; WX 444 ; N egrave ; B 5 -13 398 697 ;
+C -1 ; WX 389 ; N zacute ; B -43 -78 407 697 ;
+C -1 ; WX 278 ; N iogonek ; B -20 -183 263 684 ;
+C -1 ; WX 722 ; N Oacute ; B 27 -18 691 904 ;
+C -1 ; WX 500 ; N oacute ; B -3 -13 463 697 ;
+C -1 ; WX 500 ; N amacron ; B -21 -14 467 623 ;
+C -1 ; WX 389 ; N sacute ; B -19 -13 407 697 ;
+C -1 ; WX 278 ; N idieresis ; B 2 -9 364 655 ;
+C -1 ; WX 722 ; N Ocircumflex ; B 27 -18 691 897 ;
+C -1 ; WX 722 ; N Ugrave ; B 67 -18 744 904 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C -1 ; WX 500 ; N thorn ; B -120 -205 446 699 ;
+C -1 ; WX 300 ; N twosuperior ; B 2 274 313 683 ;
+C -1 ; WX 722 ; N Odieresis ; B 27 -18 691 862 ;
+C -1 ; WX 576 ; N mu ; B -60 -207 516 449 ;
+C -1 ; WX 278 ; N igrave ; B 2 -9 259 697 ;
+C -1 ; WX 500 ; N ohungarumlaut ; B -3 -13 582 697 ;
+C -1 ; WX 667 ; N Eogonek ; B -27 -183 653 669 ;
+C -1 ; WX 500 ; N dcroat ; B -21 -13 552 699 ;
+C -1 ; WX 750 ; N threequarters ; B 7 -14 726 683 ;
+C -1 ; WX 556 ; N Scedilla ; B 2 -218 526 685 ;
+C -1 ; WX 382 ; N lcaron ; B 2 -9 448 708 ;
+C -1 ; WX 667 ; N Kcommaaccent ; B -21 -218 702 669 ;
+C -1 ; WX 611 ; N Lacute ; B -22 0 590 904 ;
+C -1 ; WX 1000 ; N trademark ; B 32 263 968 669 ;
+C -1 ; WX 444 ; N edotaccent ; B 5 -13 398 655 ;
+C -1 ; WX 389 ; N Igrave ; B -32 0 406 904 ;
+C -1 ; WX 389 ; N Imacron ; B -32 0 461 830 ;
+C -1 ; WX 611 ; N Lcaron ; B -22 0 671 718 ;
+C -1 ; WX 750 ; N onehalf ; B -9 -14 723 683 ;
+C -1 ; WX 549 ; N lessequal ; B 29 0 526 704 ;
+C -1 ; WX 500 ; N ocircumflex ; B -3 -13 451 690 ;
+C -1 ; WX 556 ; N ntilde ; B -6 -9 504 655 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 67 -18 744 904 ;
+C -1 ; WX 667 ; N Eacute ; B -27 0 653 904 ;
+C -1 ; WX 444 ; N emacron ; B 5 -13 439 623 ;
+C -1 ; WX 500 ; N gbreve ; B -52 -203 478 678 ;
+C -1 ; WX 750 ; N onequarter ; B 7 -14 721 683 ;
+C -1 ; WX 556 ; N Scaron ; B 2 -18 553 897 ;
+C -1 ; WX 556 ; N Scommaaccent ; B 2 -218 526 685 ;
+C -1 ; WX 722 ; N Ohungarumlaut ; B 27 -18 723 904 ;
+C -1 ; WX 400 ; N degree ; B 83 397 369 683 ;
+C -1 ; WX 500 ; N ograve ; B -3 -13 441 697 ;
+C -1 ; WX 667 ; N Ccaron ; B 32 -18 677 897 ;
+C -1 ; WX 556 ; N ugrave ; B 15 -9 492 697 ;
+C -1 ; WX 549 ; N radical ; B 10 -46 512 850 ;
+C -1 ; WX 722 ; N Dcaron ; B -46 0 685 897 ;
+C -1 ; WX 389 ; N rcommaaccent ; B -67 -218 389 462 ;
+C -1 ; WX 722 ; N Ntilde ; B -27 -15 748 862 ;
+C -1 ; WX 500 ; N otilde ; B -3 -13 491 655 ;
+C -1 ; WX 667 ; N Rcommaaccent ; B -29 -218 623 669 ;
+C -1 ; WX 611 ; N Lcommaaccent ; B -22 -218 590 669 ;
+C -1 ; WX 667 ; N Atilde ; B -67 0 593 862 ;
+C -1 ; WX 667 ; N Aogonek ; B -67 -183 604 683 ;
+C -1 ; WX 667 ; N Aring ; B -67 0 593 921 ;
+C -1 ; WX 722 ; N Otilde ; B 27 -18 691 862 ;
+C -1 ; WX 389 ; N zdotaccent ; B -43 -78 368 655 ;
+C -1 ; WX 667 ; N Ecaron ; B -27 0 653 897 ;
+C -1 ; WX 389 ; N Iogonek ; B -32 -183 406 669 ;
+C -1 ; WX 500 ; N kcommaaccent ; B -23 -218 483 699 ;
+C -1 ; WX 606 ; N minus ; B 51 209 555 297 ;
+C -1 ; WX 389 ; N Icircumflex ; B -32 0 450 897 ;
+C -1 ; WX 556 ; N ncaron ; B -6 -9 523 690 ;
+C -1 ; WX 278 ; N tcommaaccent ; B -62 -218 281 594 ;
+C -1 ; WX 606 ; N logicalnot ; B 51 108 555 399 ;
+C -1 ; WX 500 ; N odieresis ; B -3 -13 471 655 ;
+C -1 ; WX 556 ; N udieresis ; B 15 -9 499 655 ;
+C -1 ; WX 549 ; N notequal ; B 15 -49 540 570 ;
+C -1 ; WX 500 ; N gcommaaccent ; B -52 -203 478 767 ;
+C -1 ; WX 500 ; N eth ; B -3 -13 454 699 ;
+C -1 ; WX 389 ; N zcaron ; B -43 -78 424 690 ;
+C -1 ; WX 556 ; N ncommaaccent ; B -6 -218 493 462 ;
+C -1 ; WX 300 ; N onesuperior ; B 30 274 301 683 ;
+C -1 ; WX 278 ; N imacron ; B 2 -9 294 623 ;
+C -1 ; WX 500 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2038
+KPX A C -65
+KPX A Cacute -65
+KPX A Ccaron -65
+KPX A Ccedilla -65
+KPX A G -60
+KPX A Gbreve -60
+KPX A Gcommaaccent -60
+KPX A O -50
+KPX A Oacute -50
+KPX A Ocircumflex -50
+KPX A Odieresis -50
+KPX A Ograve -50
+KPX A Ohungarumlaut -50
+KPX A Omacron -50
+KPX A Oslash -50
+KPX A Otilde -50
+KPX A Q -55
+KPX A T -55
+KPX A Tcaron -55
+KPX A Tcommaaccent -55
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -95
+KPX A W -100
+KPX A Y -70
+KPX A Yacute -70
+KPX A Ydieresis -70
+KPX A quoteright -74
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -74
+KPX A w -74
+KPX A y -74
+KPX A yacute -74
+KPX A ydieresis -74
+KPX Aacute C -65
+KPX Aacute Cacute -65
+KPX Aacute Ccaron -65
+KPX Aacute Ccedilla -65
+KPX Aacute G -60
+KPX Aacute Gbreve -60
+KPX Aacute Gcommaaccent -60
+KPX Aacute O -50
+KPX Aacute Oacute -50
+KPX Aacute Ocircumflex -50
+KPX Aacute Odieresis -50
+KPX Aacute Ograve -50
+KPX Aacute Ohungarumlaut -50
+KPX Aacute Omacron -50
+KPX Aacute Oslash -50
+KPX Aacute Otilde -50
+KPX Aacute Q -55
+KPX Aacute T -55
+KPX Aacute Tcaron -55
+KPX Aacute Tcommaaccent -55
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -95
+KPX Aacute W -100
+KPX Aacute Y -70
+KPX Aacute Yacute -70
+KPX Aacute Ydieresis -70
+KPX Aacute quoteright -74
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -74
+KPX Aacute w -74
+KPX Aacute y -74
+KPX Aacute yacute -74
+KPX Aacute ydieresis -74
+KPX Abreve C -65
+KPX Abreve Cacute -65
+KPX Abreve Ccaron -65
+KPX Abreve Ccedilla -65
+KPX Abreve G -60
+KPX Abreve Gbreve -60
+KPX Abreve Gcommaaccent -60
+KPX Abreve O -50
+KPX Abreve Oacute -50
+KPX Abreve Ocircumflex -50
+KPX Abreve Odieresis -50
+KPX Abreve Ograve -50
+KPX Abreve Ohungarumlaut -50
+KPX Abreve Omacron -50
+KPX Abreve Oslash -50
+KPX Abreve Otilde -50
+KPX Abreve Q -55
+KPX Abreve T -55
+KPX Abreve Tcaron -55
+KPX Abreve Tcommaaccent -55
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -95
+KPX Abreve W -100
+KPX Abreve Y -70
+KPX Abreve Yacute -70
+KPX Abreve Ydieresis -70
+KPX Abreve quoteright -74
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -74
+KPX Abreve w -74
+KPX Abreve y -74
+KPX Abreve yacute -74
+KPX Abreve ydieresis -74
+KPX Acircumflex C -65
+KPX Acircumflex Cacute -65
+KPX Acircumflex Ccaron -65
+KPX Acircumflex Ccedilla -65
+KPX Acircumflex G -60
+KPX Acircumflex Gbreve -60
+KPX Acircumflex Gcommaaccent -60
+KPX Acircumflex O -50
+KPX Acircumflex Oacute -50
+KPX Acircumflex Ocircumflex -50
+KPX Acircumflex Odieresis -50
+KPX Acircumflex Ograve -50
+KPX Acircumflex Ohungarumlaut -50
+KPX Acircumflex Omacron -50
+KPX Acircumflex Oslash -50
+KPX Acircumflex Otilde -50
+KPX Acircumflex Q -55
+KPX Acircumflex T -55
+KPX Acircumflex Tcaron -55
+KPX Acircumflex Tcommaaccent -55
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -95
+KPX Acircumflex W -100
+KPX Acircumflex Y -70
+KPX Acircumflex Yacute -70
+KPX Acircumflex Ydieresis -70
+KPX Acircumflex quoteright -74
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -74
+KPX Acircumflex w -74
+KPX Acircumflex y -74
+KPX Acircumflex yacute -74
+KPX Acircumflex ydieresis -74
+KPX Adieresis C -65
+KPX Adieresis Cacute -65
+KPX Adieresis Ccaron -65
+KPX Adieresis Ccedilla -65
+KPX Adieresis G -60
+KPX Adieresis Gbreve -60
+KPX Adieresis Gcommaaccent -60
+KPX Adieresis O -50
+KPX Adieresis Oacute -50
+KPX Adieresis Ocircumflex -50
+KPX Adieresis Odieresis -50
+KPX Adieresis Ograve -50
+KPX Adieresis Ohungarumlaut -50
+KPX Adieresis Omacron -50
+KPX Adieresis Oslash -50
+KPX Adieresis Otilde -50
+KPX Adieresis Q -55
+KPX Adieresis T -55
+KPX Adieresis Tcaron -55
+KPX Adieresis Tcommaaccent -55
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -95
+KPX Adieresis W -100
+KPX Adieresis Y -70
+KPX Adieresis Yacute -70
+KPX Adieresis Ydieresis -70
+KPX Adieresis quoteright -74
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -74
+KPX Adieresis w -74
+KPX Adieresis y -74
+KPX Adieresis yacute -74
+KPX Adieresis ydieresis -74
+KPX Agrave C -65
+KPX Agrave Cacute -65
+KPX Agrave Ccaron -65
+KPX Agrave Ccedilla -65
+KPX Agrave G -60
+KPX Agrave Gbreve -60
+KPX Agrave Gcommaaccent -60
+KPX Agrave O -50
+KPX Agrave Oacute -50
+KPX Agrave Ocircumflex -50
+KPX Agrave Odieresis -50
+KPX Agrave Ograve -50
+KPX Agrave Ohungarumlaut -50
+KPX Agrave Omacron -50
+KPX Agrave Oslash -50
+KPX Agrave Otilde -50
+KPX Agrave Q -55
+KPX Agrave T -55
+KPX Agrave Tcaron -55
+KPX Agrave Tcommaaccent -55
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -95
+KPX Agrave W -100
+KPX Agrave Y -70
+KPX Agrave Yacute -70
+KPX Agrave Ydieresis -70
+KPX Agrave quoteright -74
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -74
+KPX Agrave w -74
+KPX Agrave y -74
+KPX Agrave yacute -74
+KPX Agrave ydieresis -74
+KPX Amacron C -65
+KPX Amacron Cacute -65
+KPX Amacron Ccaron -65
+KPX Amacron Ccedilla -65
+KPX Amacron G -60
+KPX Amacron Gbreve -60
+KPX Amacron Gcommaaccent -60
+KPX Amacron O -50
+KPX Amacron Oacute -50
+KPX Amacron Ocircumflex -50
+KPX Amacron Odieresis -50
+KPX Amacron Ograve -50
+KPX Amacron Ohungarumlaut -50
+KPX Amacron Omacron -50
+KPX Amacron Oslash -50
+KPX Amacron Otilde -50
+KPX Amacron Q -55
+KPX Amacron T -55
+KPX Amacron Tcaron -55
+KPX Amacron Tcommaaccent -55
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -95
+KPX Amacron W -100
+KPX Amacron Y -70
+KPX Amacron Yacute -70
+KPX Amacron Ydieresis -70
+KPX Amacron quoteright -74
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -74
+KPX Amacron w -74
+KPX Amacron y -74
+KPX Amacron yacute -74
+KPX Amacron ydieresis -74
+KPX Aogonek C -65
+KPX Aogonek Cacute -65
+KPX Aogonek Ccaron -65
+KPX Aogonek Ccedilla -65
+KPX Aogonek G -60
+KPX Aogonek Gbreve -60
+KPX Aogonek Gcommaaccent -60
+KPX Aogonek O -50
+KPX Aogonek Oacute -50
+KPX Aogonek Ocircumflex -50
+KPX Aogonek Odieresis -50
+KPX Aogonek Ograve -50
+KPX Aogonek Ohungarumlaut -50
+KPX Aogonek Omacron -50
+KPX Aogonek Oslash -50
+KPX Aogonek Otilde -50
+KPX Aogonek Q -55
+KPX Aogonek T -55
+KPX Aogonek Tcaron -55
+KPX Aogonek Tcommaaccent -55
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -95
+KPX Aogonek W -100
+KPX Aogonek Y -70
+KPX Aogonek Yacute -70
+KPX Aogonek Ydieresis -70
+KPX Aogonek quoteright -74
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -74
+KPX Aogonek w -74
+KPX Aogonek y -34
+KPX Aogonek yacute -34
+KPX Aogonek ydieresis -34
+KPX Aring C -65
+KPX Aring Cacute -65
+KPX Aring Ccaron -65
+KPX Aring Ccedilla -65
+KPX Aring G -60
+KPX Aring Gbreve -60
+KPX Aring Gcommaaccent -60
+KPX Aring O -50
+KPX Aring Oacute -50
+KPX Aring Ocircumflex -50
+KPX Aring Odieresis -50
+KPX Aring Ograve -50
+KPX Aring Ohungarumlaut -50
+KPX Aring Omacron -50
+KPX Aring Oslash -50
+KPX Aring Otilde -50
+KPX Aring Q -55
+KPX Aring T -55
+KPX Aring Tcaron -55
+KPX Aring Tcommaaccent -55
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -95
+KPX Aring W -100
+KPX Aring Y -70
+KPX Aring Yacute -70
+KPX Aring Ydieresis -70
+KPX Aring quoteright -74
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -74
+KPX Aring w -74
+KPX Aring y -74
+KPX Aring yacute -74
+KPX Aring ydieresis -74
+KPX Atilde C -65
+KPX Atilde Cacute -65
+KPX Atilde Ccaron -65
+KPX Atilde Ccedilla -65
+KPX Atilde G -60
+KPX Atilde Gbreve -60
+KPX Atilde Gcommaaccent -60
+KPX Atilde O -50
+KPX Atilde Oacute -50
+KPX Atilde Ocircumflex -50
+KPX Atilde Odieresis -50
+KPX Atilde Ograve -50
+KPX Atilde Ohungarumlaut -50
+KPX Atilde Omacron -50
+KPX Atilde Oslash -50
+KPX Atilde Otilde -50
+KPX Atilde Q -55
+KPX Atilde T -55
+KPX Atilde Tcaron -55
+KPX Atilde Tcommaaccent -55
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -95
+KPX Atilde W -100
+KPX Atilde Y -70
+KPX Atilde Yacute -70
+KPX Atilde Ydieresis -70
+KPX Atilde quoteright -74
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -74
+KPX Atilde w -74
+KPX Atilde y -74
+KPX Atilde yacute -74
+KPX Atilde ydieresis -74
+KPX B A -25
+KPX B Aacute -25
+KPX B Abreve -25
+KPX B Acircumflex -25
+KPX B Adieresis -25
+KPX B Agrave -25
+KPX B Amacron -25
+KPX B Aogonek -25
+KPX B Aring -25
+KPX B Atilde -25
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -25
+KPX D Aacute -25
+KPX D Abreve -25
+KPX D Acircumflex -25
+KPX D Adieresis -25
+KPX D Agrave -25
+KPX D Amacron -25
+KPX D Aogonek -25
+KPX D Aring -25
+KPX D Atilde -25
+KPX D V -50
+KPX D W -40
+KPX D Y -50
+KPX D Yacute -50
+KPX D Ydieresis -50
+KPX Dcaron A -25
+KPX Dcaron Aacute -25
+KPX Dcaron Abreve -25
+KPX Dcaron Acircumflex -25
+KPX Dcaron Adieresis -25
+KPX Dcaron Agrave -25
+KPX Dcaron Amacron -25
+KPX Dcaron Aogonek -25
+KPX Dcaron Aring -25
+KPX Dcaron Atilde -25
+KPX Dcaron V -50
+KPX Dcaron W -40
+KPX Dcaron Y -50
+KPX Dcaron Yacute -50
+KPX Dcaron Ydieresis -50
+KPX Dcroat A -25
+KPX Dcroat Aacute -25
+KPX Dcroat Abreve -25
+KPX Dcroat Acircumflex -25
+KPX Dcroat Adieresis -25
+KPX Dcroat Agrave -25
+KPX Dcroat Amacron -25
+KPX Dcroat Aogonek -25
+KPX Dcroat Aring -25
+KPX Dcroat Atilde -25
+KPX Dcroat V -50
+KPX Dcroat W -40
+KPX Dcroat Y -50
+KPX Dcroat Yacute -50
+KPX Dcroat Ydieresis -50
+KPX F A -100
+KPX F Aacute -100
+KPX F Abreve -100
+KPX F Acircumflex -100
+KPX F Adieresis -100
+KPX F Agrave -100
+KPX F Amacron -100
+KPX F Aogonek -100
+KPX F Aring -100
+KPX F Atilde -100
+KPX F a -95
+KPX F aacute -95
+KPX F abreve -95
+KPX F acircumflex -95
+KPX F adieresis -95
+KPX F agrave -95
+KPX F amacron -95
+KPX F aogonek -95
+KPX F aring -95
+KPX F atilde -95
+KPX F comma -129
+KPX F e -100
+KPX F eacute -100
+KPX F ecaron -100
+KPX F ecircumflex -100
+KPX F edieresis -100
+KPX F edotaccent -100
+KPX F egrave -100
+KPX F emacron -100
+KPX F eogonek -100
+KPX F i -40
+KPX F iacute -40
+KPX F icircumflex -40
+KPX F idieresis -40
+KPX F igrave -40
+KPX F imacron -40
+KPX F iogonek -40
+KPX F o -70
+KPX F oacute -70
+KPX F ocircumflex -70
+KPX F odieresis -70
+KPX F ograve -70
+KPX F ohungarumlaut -70
+KPX F omacron -70
+KPX F oslash -70
+KPX F otilde -70
+KPX F period -129
+KPX F r -50
+KPX F racute -50
+KPX F rcaron -50
+KPX F rcommaaccent -50
+KPX J A -25
+KPX J Aacute -25
+KPX J Abreve -25
+KPX J Acircumflex -25
+KPX J Adieresis -25
+KPX J Agrave -25
+KPX J Amacron -25
+KPX J Aogonek -25
+KPX J Aring -25
+KPX J Atilde -25
+KPX J a -40
+KPX J aacute -40
+KPX J abreve -40
+KPX J acircumflex -40
+KPX J adieresis -40
+KPX J agrave -40
+KPX J amacron -40
+KPX J aogonek -40
+KPX J aring -40
+KPX J atilde -40
+KPX J comma -10
+KPX J e -40
+KPX J eacute -40
+KPX J ecaron -40
+KPX J ecircumflex -40
+KPX J edieresis -40
+KPX J edotaccent -40
+KPX J egrave -40
+KPX J emacron -40
+KPX J eogonek -40
+KPX J o -40
+KPX J oacute -40
+KPX J ocircumflex -40
+KPX J odieresis -40
+KPX J ograve -40
+KPX J ohungarumlaut -40
+KPX J omacron -40
+KPX J oslash -40
+KPX J otilde -40
+KPX J period -10
+KPX J u -40
+KPX J uacute -40
+KPX J ucircumflex -40
+KPX J udieresis -40
+KPX J ugrave -40
+KPX J uhungarumlaut -40
+KPX J umacron -40
+KPX J uogonek -40
+KPX J uring -40
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -25
+KPX K eacute -25
+KPX K ecaron -25
+KPX K ecircumflex -25
+KPX K edieresis -25
+KPX K edotaccent -25
+KPX K egrave -25
+KPX K emacron -25
+KPX K eogonek -25
+KPX K o -25
+KPX K oacute -25
+KPX K ocircumflex -25
+KPX K odieresis -25
+KPX K ograve -25
+KPX K ohungarumlaut -25
+KPX K omacron -25
+KPX K oslash -25
+KPX K otilde -25
+KPX K u -20
+KPX K uacute -20
+KPX K ucircumflex -20
+KPX K udieresis -20
+KPX K ugrave -20
+KPX K uhungarumlaut -20
+KPX K umacron -20
+KPX K uogonek -20
+KPX K uring -20
+KPX K y -20
+KPX K yacute -20
+KPX K ydieresis -20
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -25
+KPX Kcommaaccent eacute -25
+KPX Kcommaaccent ecaron -25
+KPX Kcommaaccent ecircumflex -25
+KPX Kcommaaccent edieresis -25
+KPX Kcommaaccent edotaccent -25
+KPX Kcommaaccent egrave -25
+KPX Kcommaaccent emacron -25
+KPX Kcommaaccent eogonek -25
+KPX Kcommaaccent o -25
+KPX Kcommaaccent oacute -25
+KPX Kcommaaccent ocircumflex -25
+KPX Kcommaaccent odieresis -25
+KPX Kcommaaccent ograve -25
+KPX Kcommaaccent ohungarumlaut -25
+KPX Kcommaaccent omacron -25
+KPX Kcommaaccent oslash -25
+KPX Kcommaaccent otilde -25
+KPX Kcommaaccent u -20
+KPX Kcommaaccent uacute -20
+KPX Kcommaaccent ucircumflex -20
+KPX Kcommaaccent udieresis -20
+KPX Kcommaaccent ugrave -20
+KPX Kcommaaccent uhungarumlaut -20
+KPX Kcommaaccent umacron -20
+KPX Kcommaaccent uogonek -20
+KPX Kcommaaccent uring -20
+KPX Kcommaaccent y -20
+KPX Kcommaaccent yacute -20
+KPX Kcommaaccent ydieresis -20
+KPX L T -18
+KPX L Tcaron -18
+KPX L Tcommaaccent -18
+KPX L V -37
+KPX L W -37
+KPX L Y -37
+KPX L Yacute -37
+KPX L Ydieresis -37
+KPX L quoteright -55
+KPX L y -37
+KPX L yacute -37
+KPX L ydieresis -37
+KPX Lacute T -18
+KPX Lacute Tcaron -18
+KPX Lacute Tcommaaccent -18
+KPX Lacute V -37
+KPX Lacute W -37
+KPX Lacute Y -37
+KPX Lacute Yacute -37
+KPX Lacute Ydieresis -37
+KPX Lacute quoteright -55
+KPX Lacute y -37
+KPX Lacute yacute -37
+KPX Lacute ydieresis -37
+KPX Lcommaaccent T -18
+KPX Lcommaaccent Tcaron -18
+KPX Lcommaaccent Tcommaaccent -18
+KPX Lcommaaccent V -37
+KPX Lcommaaccent W -37
+KPX Lcommaaccent Y -37
+KPX Lcommaaccent Yacute -37
+KPX Lcommaaccent Ydieresis -37
+KPX Lcommaaccent quoteright -55
+KPX Lcommaaccent y -37
+KPX Lcommaaccent yacute -37
+KPX Lcommaaccent ydieresis -37
+KPX Lslash T -18
+KPX Lslash Tcaron -18
+KPX Lslash Tcommaaccent -18
+KPX Lslash V -37
+KPX Lslash W -37
+KPX Lslash Y -37
+KPX Lslash Yacute -37
+KPX Lslash Ydieresis -37
+KPX Lslash quoteright -55
+KPX Lslash y -37
+KPX Lslash yacute -37
+KPX Lslash ydieresis -37
+KPX N A -30
+KPX N Aacute -30
+KPX N Abreve -30
+KPX N Acircumflex -30
+KPX N Adieresis -30
+KPX N Agrave -30
+KPX N Amacron -30
+KPX N Aogonek -30
+KPX N Aring -30
+KPX N Atilde -30
+KPX Nacute A -30
+KPX Nacute Aacute -30
+KPX Nacute Abreve -30
+KPX Nacute Acircumflex -30
+KPX Nacute Adieresis -30
+KPX Nacute Agrave -30
+KPX Nacute Amacron -30
+KPX Nacute Aogonek -30
+KPX Nacute Aring -30
+KPX Nacute Atilde -30
+KPX Ncaron A -30
+KPX Ncaron Aacute -30
+KPX Ncaron Abreve -30
+KPX Ncaron Acircumflex -30
+KPX Ncaron Adieresis -30
+KPX Ncaron Agrave -30
+KPX Ncaron Amacron -30
+KPX Ncaron Aogonek -30
+KPX Ncaron Aring -30
+KPX Ncaron Atilde -30
+KPX Ncommaaccent A -30
+KPX Ncommaaccent Aacute -30
+KPX Ncommaaccent Abreve -30
+KPX Ncommaaccent Acircumflex -30
+KPX Ncommaaccent Adieresis -30
+KPX Ncommaaccent Agrave -30
+KPX Ncommaaccent Amacron -30
+KPX Ncommaaccent Aogonek -30
+KPX Ncommaaccent Aring -30
+KPX Ncommaaccent Atilde -30
+KPX Ntilde A -30
+KPX Ntilde Aacute -30
+KPX Ntilde Abreve -30
+KPX Ntilde Acircumflex -30
+KPX Ntilde Adieresis -30
+KPX Ntilde Agrave -30
+KPX Ntilde Amacron -30
+KPX Ntilde Aogonek -30
+KPX Ntilde Aring -30
+KPX Ntilde Atilde -30
+KPX O A -40
+KPX O Aacute -40
+KPX O Abreve -40
+KPX O Acircumflex -40
+KPX O Adieresis -40
+KPX O Agrave -40
+KPX O Amacron -40
+KPX O Aogonek -40
+KPX O Aring -40
+KPX O Atilde -40
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -40
+KPX O Y -50
+KPX O Yacute -50
+KPX O Ydieresis -50
+KPX Oacute A -40
+KPX Oacute Aacute -40
+KPX Oacute Abreve -40
+KPX Oacute Acircumflex -40
+KPX Oacute Adieresis -40
+KPX Oacute Agrave -40
+KPX Oacute Amacron -40
+KPX Oacute Aogonek -40
+KPX Oacute Aring -40
+KPX Oacute Atilde -40
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -40
+KPX Oacute Y -50
+KPX Oacute Yacute -50
+KPX Oacute Ydieresis -50
+KPX Ocircumflex A -40
+KPX Ocircumflex Aacute -40
+KPX Ocircumflex Abreve -40
+KPX Ocircumflex Acircumflex -40
+KPX Ocircumflex Adieresis -40
+KPX Ocircumflex Agrave -40
+KPX Ocircumflex Amacron -40
+KPX Ocircumflex Aogonek -40
+KPX Ocircumflex Aring -40
+KPX Ocircumflex Atilde -40
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -40
+KPX Ocircumflex Y -50
+KPX Ocircumflex Yacute -50
+KPX Ocircumflex Ydieresis -50
+KPX Odieresis A -40
+KPX Odieresis Aacute -40
+KPX Odieresis Abreve -40
+KPX Odieresis Acircumflex -40
+KPX Odieresis Adieresis -40
+KPX Odieresis Agrave -40
+KPX Odieresis Amacron -40
+KPX Odieresis Aogonek -40
+KPX Odieresis Aring -40
+KPX Odieresis Atilde -40
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -40
+KPX Odieresis Y -50
+KPX Odieresis Yacute -50
+KPX Odieresis Ydieresis -50
+KPX Ograve A -40
+KPX Ograve Aacute -40
+KPX Ograve Abreve -40
+KPX Ograve Acircumflex -40
+KPX Ograve Adieresis -40
+KPX Ograve Agrave -40
+KPX Ograve Amacron -40
+KPX Ograve Aogonek -40
+KPX Ograve Aring -40
+KPX Ograve Atilde -40
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -40
+KPX Ograve Y -50
+KPX Ograve Yacute -50
+KPX Ograve Ydieresis -50
+KPX Ohungarumlaut A -40
+KPX Ohungarumlaut Aacute -40
+KPX Ohungarumlaut Abreve -40
+KPX Ohungarumlaut Acircumflex -40
+KPX Ohungarumlaut Adieresis -40
+KPX Ohungarumlaut Agrave -40
+KPX Ohungarumlaut Amacron -40
+KPX Ohungarumlaut Aogonek -40
+KPX Ohungarumlaut Aring -40
+KPX Ohungarumlaut Atilde -40
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -40
+KPX Ohungarumlaut Y -50
+KPX Ohungarumlaut Yacute -50
+KPX Ohungarumlaut Ydieresis -50
+KPX Omacron A -40
+KPX Omacron Aacute -40
+KPX Omacron Abreve -40
+KPX Omacron Acircumflex -40
+KPX Omacron Adieresis -40
+KPX Omacron Agrave -40
+KPX Omacron Amacron -40
+KPX Omacron Aogonek -40
+KPX Omacron Aring -40
+KPX Omacron Atilde -40
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -40
+KPX Omacron Y -50
+KPX Omacron Yacute -50
+KPX Omacron Ydieresis -50
+KPX Oslash A -40
+KPX Oslash Aacute -40
+KPX Oslash Abreve -40
+KPX Oslash Acircumflex -40
+KPX Oslash Adieresis -40
+KPX Oslash Agrave -40
+KPX Oslash Amacron -40
+KPX Oslash Aogonek -40
+KPX Oslash Aring -40
+KPX Oslash Atilde -40
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -40
+KPX Oslash Y -50
+KPX Oslash Yacute -50
+KPX Oslash Ydieresis -50
+KPX Otilde A -40
+KPX Otilde Aacute -40
+KPX Otilde Abreve -40
+KPX Otilde Acircumflex -40
+KPX Otilde Adieresis -40
+KPX Otilde Agrave -40
+KPX Otilde Amacron -40
+KPX Otilde Aogonek -40
+KPX Otilde Aring -40
+KPX Otilde Atilde -40
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -40
+KPX Otilde Y -50
+KPX Otilde Yacute -50
+KPX Otilde Ydieresis -50
+KPX P A -85
+KPX P Aacute -85
+KPX P Abreve -85
+KPX P Acircumflex -85
+KPX P Adieresis -85
+KPX P Agrave -85
+KPX P Amacron -85
+KPX P Aogonek -85
+KPX P Aring -85
+KPX P Atilde -85
+KPX P a -40
+KPX P aacute -40
+KPX P abreve -40
+KPX P acircumflex -40
+KPX P adieresis -40
+KPX P agrave -40
+KPX P amacron -40
+KPX P aogonek -40
+KPX P aring -40
+KPX P atilde -40
+KPX P comma -129
+KPX P e -50
+KPX P eacute -50
+KPX P ecaron -50
+KPX P ecircumflex -50
+KPX P edieresis -50
+KPX P edotaccent -50
+KPX P egrave -50
+KPX P emacron -50
+KPX P eogonek -50
+KPX P o -55
+KPX P oacute -55
+KPX P ocircumflex -55
+KPX P odieresis -55
+KPX P ograve -55
+KPX P ohungarumlaut -55
+KPX P omacron -55
+KPX P oslash -55
+KPX P otilde -55
+KPX P period -129
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -40
+KPX R Oacute -40
+KPX R Ocircumflex -40
+KPX R Odieresis -40
+KPX R Ograve -40
+KPX R Ohungarumlaut -40
+KPX R Omacron -40
+KPX R Oslash -40
+KPX R Otilde -40
+KPX R T -30
+KPX R Tcaron -30
+KPX R Tcommaaccent -30
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -18
+KPX R W -18
+KPX R Y -18
+KPX R Yacute -18
+KPX R Ydieresis -18
+KPX Racute O -40
+KPX Racute Oacute -40
+KPX Racute Ocircumflex -40
+KPX Racute Odieresis -40
+KPX Racute Ograve -40
+KPX Racute Ohungarumlaut -40
+KPX Racute Omacron -40
+KPX Racute Oslash -40
+KPX Racute Otilde -40
+KPX Racute T -30
+KPX Racute Tcaron -30
+KPX Racute Tcommaaccent -30
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -18
+KPX Racute W -18
+KPX Racute Y -18
+KPX Racute Yacute -18
+KPX Racute Ydieresis -18
+KPX Rcaron O -40
+KPX Rcaron Oacute -40
+KPX Rcaron Ocircumflex -40
+KPX Rcaron Odieresis -40
+KPX Rcaron Ograve -40
+KPX Rcaron Ohungarumlaut -40
+KPX Rcaron Omacron -40
+KPX Rcaron Oslash -40
+KPX Rcaron Otilde -40
+KPX Rcaron T -30
+KPX Rcaron Tcaron -30
+KPX Rcaron Tcommaaccent -30
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -18
+KPX Rcaron W -18
+KPX Rcaron Y -18
+KPX Rcaron Yacute -18
+KPX Rcaron Ydieresis -18
+KPX Rcommaaccent O -40
+KPX Rcommaaccent Oacute -40
+KPX Rcommaaccent Ocircumflex -40
+KPX Rcommaaccent Odieresis -40
+KPX Rcommaaccent Ograve -40
+KPX Rcommaaccent Ohungarumlaut -40
+KPX Rcommaaccent Omacron -40
+KPX Rcommaaccent Oslash -40
+KPX Rcommaaccent Otilde -40
+KPX Rcommaaccent T -30
+KPX Rcommaaccent Tcaron -30
+KPX Rcommaaccent Tcommaaccent -30
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -18
+KPX Rcommaaccent W -18
+KPX Rcommaaccent Y -18
+KPX Rcommaaccent Yacute -18
+KPX Rcommaaccent Ydieresis -18
+KPX T A -55
+KPX T Aacute -55
+KPX T Abreve -55
+KPX T Acircumflex -55
+KPX T Adieresis -55
+KPX T Agrave -55
+KPX T Amacron -55
+KPX T Aogonek -55
+KPX T Aring -55
+KPX T Atilde -55
+KPX T O -18
+KPX T Oacute -18
+KPX T Ocircumflex -18
+KPX T Odieresis -18
+KPX T Ograve -18
+KPX T Ohungarumlaut -18
+KPX T Omacron -18
+KPX T Oslash -18
+KPX T Otilde -18
+KPX T a -92
+KPX T aacute -92
+KPX T abreve -92
+KPX T acircumflex -92
+KPX T adieresis -92
+KPX T agrave -92
+KPX T amacron -92
+KPX T aogonek -92
+KPX T aring -92
+KPX T atilde -92
+KPX T colon -74
+KPX T comma -92
+KPX T e -92
+KPX T eacute -92
+KPX T ecaron -92
+KPX T ecircumflex -92
+KPX T edieresis -52
+KPX T edotaccent -92
+KPX T egrave -52
+KPX T emacron -52
+KPX T eogonek -92
+KPX T hyphen -92
+KPX T i -37
+KPX T iacute -37
+KPX T iogonek -37
+KPX T o -95
+KPX T oacute -95
+KPX T ocircumflex -95
+KPX T odieresis -95
+KPX T ograve -95
+KPX T ohungarumlaut -95
+KPX T omacron -95
+KPX T oslash -95
+KPX T otilde -95
+KPX T period -92
+KPX T r -37
+KPX T racute -37
+KPX T rcaron -37
+KPX T rcommaaccent -37
+KPX T semicolon -74
+KPX T u -37
+KPX T uacute -37
+KPX T ucircumflex -37
+KPX T udieresis -37
+KPX T ugrave -37
+KPX T uhungarumlaut -37
+KPX T umacron -37
+KPX T uogonek -37
+KPX T uring -37
+KPX T w -37
+KPX T y -37
+KPX T yacute -37
+KPX T ydieresis -37
+KPX Tcaron A -55
+KPX Tcaron Aacute -55
+KPX Tcaron Abreve -55
+KPX Tcaron Acircumflex -55
+KPX Tcaron Adieresis -55
+KPX Tcaron Agrave -55
+KPX Tcaron Amacron -55
+KPX Tcaron Aogonek -55
+KPX Tcaron Aring -55
+KPX Tcaron Atilde -55
+KPX Tcaron O -18
+KPX Tcaron Oacute -18
+KPX Tcaron Ocircumflex -18
+KPX Tcaron Odieresis -18
+KPX Tcaron Ograve -18
+KPX Tcaron Ohungarumlaut -18
+KPX Tcaron Omacron -18
+KPX Tcaron Oslash -18
+KPX Tcaron Otilde -18
+KPX Tcaron a -92
+KPX Tcaron aacute -92
+KPX Tcaron abreve -92
+KPX Tcaron acircumflex -92
+KPX Tcaron adieresis -92
+KPX Tcaron agrave -92
+KPX Tcaron amacron -92
+KPX Tcaron aogonek -92
+KPX Tcaron aring -92
+KPX Tcaron atilde -92
+KPX Tcaron colon -74
+KPX Tcaron comma -92
+KPX Tcaron e -92
+KPX Tcaron eacute -92
+KPX Tcaron ecaron -92
+KPX Tcaron ecircumflex -92
+KPX Tcaron edieresis -52
+KPX Tcaron edotaccent -92
+KPX Tcaron egrave -52
+KPX Tcaron emacron -52
+KPX Tcaron eogonek -92
+KPX Tcaron hyphen -92
+KPX Tcaron i -37
+KPX Tcaron iacute -37
+KPX Tcaron iogonek -37
+KPX Tcaron o -95
+KPX Tcaron oacute -95
+KPX Tcaron ocircumflex -95
+KPX Tcaron odieresis -95
+KPX Tcaron ograve -95
+KPX Tcaron ohungarumlaut -95
+KPX Tcaron omacron -95
+KPX Tcaron oslash -95
+KPX Tcaron otilde -95
+KPX Tcaron period -92
+KPX Tcaron r -37
+KPX Tcaron racute -37
+KPX Tcaron rcaron -37
+KPX Tcaron rcommaaccent -37
+KPX Tcaron semicolon -74
+KPX Tcaron u -37
+KPX Tcaron uacute -37
+KPX Tcaron ucircumflex -37
+KPX Tcaron udieresis -37
+KPX Tcaron ugrave -37
+KPX Tcaron uhungarumlaut -37
+KPX Tcaron umacron -37
+KPX Tcaron uogonek -37
+KPX Tcaron uring -37
+KPX Tcaron w -37
+KPX Tcaron y -37
+KPX Tcaron yacute -37
+KPX Tcaron ydieresis -37
+KPX Tcommaaccent A -55
+KPX Tcommaaccent Aacute -55
+KPX Tcommaaccent Abreve -55
+KPX Tcommaaccent Acircumflex -55
+KPX Tcommaaccent Adieresis -55
+KPX Tcommaaccent Agrave -55
+KPX Tcommaaccent Amacron -55
+KPX Tcommaaccent Aogonek -55
+KPX Tcommaaccent Aring -55
+KPX Tcommaaccent Atilde -55
+KPX Tcommaaccent O -18
+KPX Tcommaaccent Oacute -18
+KPX Tcommaaccent Ocircumflex -18
+KPX Tcommaaccent Odieresis -18
+KPX Tcommaaccent Ograve -18
+KPX Tcommaaccent Ohungarumlaut -18
+KPX Tcommaaccent Omacron -18
+KPX Tcommaaccent Oslash -18
+KPX Tcommaaccent Otilde -18
+KPX Tcommaaccent a -92
+KPX Tcommaaccent aacute -92
+KPX Tcommaaccent abreve -92
+KPX Tcommaaccent acircumflex -92
+KPX Tcommaaccent adieresis -92
+KPX Tcommaaccent agrave -92
+KPX Tcommaaccent amacron -92
+KPX Tcommaaccent aogonek -92
+KPX Tcommaaccent aring -92
+KPX Tcommaaccent atilde -92
+KPX Tcommaaccent colon -74
+KPX Tcommaaccent comma -92
+KPX Tcommaaccent e -92
+KPX Tcommaaccent eacute -92
+KPX Tcommaaccent ecaron -92
+KPX Tcommaaccent ecircumflex -92
+KPX Tcommaaccent edieresis -52
+KPX Tcommaaccent edotaccent -92
+KPX Tcommaaccent egrave -52
+KPX Tcommaaccent emacron -52
+KPX Tcommaaccent eogonek -92
+KPX Tcommaaccent hyphen -92
+KPX Tcommaaccent i -37
+KPX Tcommaaccent iacute -37
+KPX Tcommaaccent iogonek -37
+KPX Tcommaaccent o -95
+KPX Tcommaaccent oacute -95
+KPX Tcommaaccent ocircumflex -95
+KPX Tcommaaccent odieresis -95
+KPX Tcommaaccent ograve -95
+KPX Tcommaaccent ohungarumlaut -95
+KPX Tcommaaccent omacron -95
+KPX Tcommaaccent oslash -95
+KPX Tcommaaccent otilde -95
+KPX Tcommaaccent period -92
+KPX Tcommaaccent r -37
+KPX Tcommaaccent racute -37
+KPX Tcommaaccent rcaron -37
+KPX Tcommaaccent rcommaaccent -37
+KPX Tcommaaccent semicolon -74
+KPX Tcommaaccent u -37
+KPX Tcommaaccent uacute -37
+KPX Tcommaaccent ucircumflex -37
+KPX Tcommaaccent udieresis -37
+KPX Tcommaaccent ugrave -37
+KPX Tcommaaccent uhungarumlaut -37
+KPX Tcommaaccent umacron -37
+KPX Tcommaaccent uogonek -37
+KPX Tcommaaccent uring -37
+KPX Tcommaaccent w -37
+KPX Tcommaaccent y -37
+KPX Tcommaaccent yacute -37
+KPX Tcommaaccent ydieresis -37
+KPX U A -45
+KPX U Aacute -45
+KPX U Abreve -45
+KPX U Acircumflex -45
+KPX U Adieresis -45
+KPX U Agrave -45
+KPX U Amacron -45
+KPX U Aogonek -45
+KPX U Aring -45
+KPX U Atilde -45
+KPX Uacute A -45
+KPX Uacute Aacute -45
+KPX Uacute Abreve -45
+KPX Uacute Acircumflex -45
+KPX Uacute Adieresis -45
+KPX Uacute Agrave -45
+KPX Uacute Amacron -45
+KPX Uacute Aogonek -45
+KPX Uacute Aring -45
+KPX Uacute Atilde -45
+KPX Ucircumflex A -45
+KPX Ucircumflex Aacute -45
+KPX Ucircumflex Abreve -45
+KPX Ucircumflex Acircumflex -45
+KPX Ucircumflex Adieresis -45
+KPX Ucircumflex Agrave -45
+KPX Ucircumflex Amacron -45
+KPX Ucircumflex Aogonek -45
+KPX Ucircumflex Aring -45
+KPX Ucircumflex Atilde -45
+KPX Udieresis A -45
+KPX Udieresis Aacute -45
+KPX Udieresis Abreve -45
+KPX Udieresis Acircumflex -45
+KPX Udieresis Adieresis -45
+KPX Udieresis Agrave -45
+KPX Udieresis Amacron -45
+KPX Udieresis Aogonek -45
+KPX Udieresis Aring -45
+KPX Udieresis Atilde -45
+KPX Ugrave A -45
+KPX Ugrave Aacute -45
+KPX Ugrave Abreve -45
+KPX Ugrave Acircumflex -45
+KPX Ugrave Adieresis -45
+KPX Ugrave Agrave -45
+KPX Ugrave Amacron -45
+KPX Ugrave Aogonek -45
+KPX Ugrave Aring -45
+KPX Ugrave Atilde -45
+KPX Uhungarumlaut A -45
+KPX Uhungarumlaut Aacute -45
+KPX Uhungarumlaut Abreve -45
+KPX Uhungarumlaut Acircumflex -45
+KPX Uhungarumlaut Adieresis -45
+KPX Uhungarumlaut Agrave -45
+KPX Uhungarumlaut Amacron -45
+KPX Uhungarumlaut Aogonek -45
+KPX Uhungarumlaut Aring -45
+KPX Uhungarumlaut Atilde -45
+KPX Umacron A -45
+KPX Umacron Aacute -45
+KPX Umacron Abreve -45
+KPX Umacron Acircumflex -45
+KPX Umacron Adieresis -45
+KPX Umacron Agrave -45
+KPX Umacron Amacron -45
+KPX Umacron Aogonek -45
+KPX Umacron Aring -45
+KPX Umacron Atilde -45
+KPX Uogonek A -45
+KPX Uogonek Aacute -45
+KPX Uogonek Abreve -45
+KPX Uogonek Acircumflex -45
+KPX Uogonek Adieresis -45
+KPX Uogonek Agrave -45
+KPX Uogonek Amacron -45
+KPX Uogonek Aogonek -45
+KPX Uogonek Aring -45
+KPX Uogonek Atilde -45
+KPX Uring A -45
+KPX Uring Aacute -45
+KPX Uring Abreve -45
+KPX Uring Acircumflex -45
+KPX Uring Adieresis -45
+KPX Uring Agrave -45
+KPX Uring Amacron -45
+KPX Uring Aogonek -45
+KPX Uring Aring -45
+KPX Uring Atilde -45
+KPX V A -85
+KPX V Aacute -85
+KPX V Abreve -85
+KPX V Acircumflex -85
+KPX V Adieresis -85
+KPX V Agrave -85
+KPX V Amacron -85
+KPX V Aogonek -85
+KPX V Aring -85
+KPX V Atilde -85
+KPX V G -10
+KPX V Gbreve -10
+KPX V Gcommaaccent -10
+KPX V O -30
+KPX V Oacute -30
+KPX V Ocircumflex -30
+KPX V Odieresis -30
+KPX V Ograve -30
+KPX V Ohungarumlaut -30
+KPX V Omacron -30
+KPX V Oslash -30
+KPX V Otilde -30
+KPX V a -111
+KPX V aacute -111
+KPX V abreve -111
+KPX V acircumflex -111
+KPX V adieresis -111
+KPX V agrave -111
+KPX V amacron -111
+KPX V aogonek -111
+KPX V aring -111
+KPX V atilde -111
+KPX V colon -74
+KPX V comma -129
+KPX V e -111
+KPX V eacute -111
+KPX V ecaron -111
+KPX V ecircumflex -111
+KPX V edieresis -71
+KPX V edotaccent -111
+KPX V egrave -71
+KPX V emacron -71
+KPX V eogonek -111
+KPX V hyphen -70
+KPX V i -55
+KPX V iacute -55
+KPX V iogonek -55
+KPX V o -111
+KPX V oacute -111
+KPX V ocircumflex -111
+KPX V odieresis -111
+KPX V ograve -111
+KPX V ohungarumlaut -111
+KPX V omacron -111
+KPX V oslash -111
+KPX V otilde -111
+KPX V period -129
+KPX V semicolon -74
+KPX V u -55
+KPX V uacute -55
+KPX V ucircumflex -55
+KPX V udieresis -55
+KPX V ugrave -55
+KPX V uhungarumlaut -55
+KPX V umacron -55
+KPX V uogonek -55
+KPX V uring -55
+KPX W A -74
+KPX W Aacute -74
+KPX W Abreve -74
+KPX W Acircumflex -74
+KPX W Adieresis -74
+KPX W Agrave -74
+KPX W Amacron -74
+KPX W Aogonek -74
+KPX W Aring -74
+KPX W Atilde -74
+KPX W O -15
+KPX W Oacute -15
+KPX W Ocircumflex -15
+KPX W Odieresis -15
+KPX W Ograve -15
+KPX W Ohungarumlaut -15
+KPX W Omacron -15
+KPX W Oslash -15
+KPX W Otilde -15
+KPX W a -85
+KPX W aacute -85
+KPX W abreve -85
+KPX W acircumflex -85
+KPX W adieresis -85
+KPX W agrave -85
+KPX W amacron -85
+KPX W aogonek -85
+KPX W aring -85
+KPX W atilde -85
+KPX W colon -55
+KPX W comma -74
+KPX W e -90
+KPX W eacute -90
+KPX W ecaron -90
+KPX W ecircumflex -90
+KPX W edieresis -50
+KPX W edotaccent -90
+KPX W egrave -50
+KPX W emacron -50
+KPX W eogonek -90
+KPX W hyphen -50
+KPX W i -37
+KPX W iacute -37
+KPX W iogonek -37
+KPX W o -80
+KPX W oacute -80
+KPX W ocircumflex -80
+KPX W odieresis -80
+KPX W ograve -80
+KPX W ohungarumlaut -80
+KPX W omacron -80
+KPX W oslash -80
+KPX W otilde -80
+KPX W period -74
+KPX W semicolon -55
+KPX W u -55
+KPX W uacute -55
+KPX W ucircumflex -55
+KPX W udieresis -55
+KPX W ugrave -55
+KPX W uhungarumlaut -55
+KPX W umacron -55
+KPX W uogonek -55
+KPX W uring -55
+KPX W y -55
+KPX W yacute -55
+KPX W ydieresis -55
+KPX Y A -74
+KPX Y Aacute -74
+KPX Y Abreve -74
+KPX Y Acircumflex -74
+KPX Y Adieresis -74
+KPX Y Agrave -74
+KPX Y Amacron -74
+KPX Y Aogonek -74
+KPX Y Aring -74
+KPX Y Atilde -74
+KPX Y O -25
+KPX Y Oacute -25
+KPX Y Ocircumflex -25
+KPX Y Odieresis -25
+KPX Y Ograve -25
+KPX Y Ohungarumlaut -25
+KPX Y Omacron -25
+KPX Y Oslash -25
+KPX Y Otilde -25
+KPX Y a -92
+KPX Y aacute -92
+KPX Y abreve -92
+KPX Y acircumflex -92
+KPX Y adieresis -92
+KPX Y agrave -92
+KPX Y amacron -92
+KPX Y aogonek -92
+KPX Y aring -92
+KPX Y atilde -92
+KPX Y colon -92
+KPX Y comma -92
+KPX Y e -111
+KPX Y eacute -111
+KPX Y ecaron -111
+KPX Y ecircumflex -71
+KPX Y edieresis -71
+KPX Y edotaccent -111
+KPX Y egrave -71
+KPX Y emacron -71
+KPX Y eogonek -111
+KPX Y hyphen -92
+KPX Y i -55
+KPX Y iacute -55
+KPX Y iogonek -55
+KPX Y o -111
+KPX Y oacute -111
+KPX Y ocircumflex -111
+KPX Y odieresis -111
+KPX Y ograve -111
+KPX Y ohungarumlaut -111
+KPX Y omacron -111
+KPX Y oslash -111
+KPX Y otilde -111
+KPX Y period -74
+KPX Y semicolon -92
+KPX Y u -92
+KPX Y uacute -92
+KPX Y ucircumflex -92
+KPX Y udieresis -92
+KPX Y ugrave -92
+KPX Y uhungarumlaut -92
+KPX Y umacron -92
+KPX Y uogonek -92
+KPX Y uring -92
+KPX Yacute A -74
+KPX Yacute Aacute -74
+KPX Yacute Abreve -74
+KPX Yacute Acircumflex -74
+KPX Yacute Adieresis -74
+KPX Yacute Agrave -74
+KPX Yacute Amacron -74
+KPX Yacute Aogonek -74
+KPX Yacute Aring -74
+KPX Yacute Atilde -74
+KPX Yacute O -25
+KPX Yacute Oacute -25
+KPX Yacute Ocircumflex -25
+KPX Yacute Odieresis -25
+KPX Yacute Ograve -25
+KPX Yacute Ohungarumlaut -25
+KPX Yacute Omacron -25
+KPX Yacute Oslash -25
+KPX Yacute Otilde -25
+KPX Yacute a -92
+KPX Yacute aacute -92
+KPX Yacute abreve -92
+KPX Yacute acircumflex -92
+KPX Yacute adieresis -92
+KPX Yacute agrave -92
+KPX Yacute amacron -92
+KPX Yacute aogonek -92
+KPX Yacute aring -92
+KPX Yacute atilde -92
+KPX Yacute colon -92
+KPX Yacute comma -92
+KPX Yacute e -111
+KPX Yacute eacute -111
+KPX Yacute ecaron -111
+KPX Yacute ecircumflex -71
+KPX Yacute edieresis -71
+KPX Yacute edotaccent -111
+KPX Yacute egrave -71
+KPX Yacute emacron -71
+KPX Yacute eogonek -111
+KPX Yacute hyphen -92
+KPX Yacute i -55
+KPX Yacute iacute -55
+KPX Yacute iogonek -55
+KPX Yacute o -111
+KPX Yacute oacute -111
+KPX Yacute ocircumflex -111
+KPX Yacute odieresis -111
+KPX Yacute ograve -111
+KPX Yacute ohungarumlaut -111
+KPX Yacute omacron -111
+KPX Yacute oslash -111
+KPX Yacute otilde -111
+KPX Yacute period -74
+KPX Yacute semicolon -92
+KPX Yacute u -92
+KPX Yacute uacute -92
+KPX Yacute ucircumflex -92
+KPX Yacute udieresis -92
+KPX Yacute ugrave -92
+KPX Yacute uhungarumlaut -92
+KPX Yacute umacron -92
+KPX Yacute uogonek -92
+KPX Yacute uring -92
+KPX Ydieresis A -74
+KPX Ydieresis Aacute -74
+KPX Ydieresis Abreve -74
+KPX Ydieresis Acircumflex -74
+KPX Ydieresis Adieresis -74
+KPX Ydieresis Agrave -74
+KPX Ydieresis Amacron -74
+KPX Ydieresis Aogonek -74
+KPX Ydieresis Aring -74
+KPX Ydieresis Atilde -74
+KPX Ydieresis O -25
+KPX Ydieresis Oacute -25
+KPX Ydieresis Ocircumflex -25
+KPX Ydieresis Odieresis -25
+KPX Ydieresis Ograve -25
+KPX Ydieresis Ohungarumlaut -25
+KPX Ydieresis Omacron -25
+KPX Ydieresis Oslash -25
+KPX Ydieresis Otilde -25
+KPX Ydieresis a -92
+KPX Ydieresis aacute -92
+KPX Ydieresis abreve -92
+KPX Ydieresis acircumflex -92
+KPX Ydieresis adieresis -92
+KPX Ydieresis agrave -92
+KPX Ydieresis amacron -92
+KPX Ydieresis aogonek -92
+KPX Ydieresis aring -92
+KPX Ydieresis atilde -92
+KPX Ydieresis colon -92
+KPX Ydieresis comma -92
+KPX Ydieresis e -111
+KPX Ydieresis eacute -111
+KPX Ydieresis ecaron -111
+KPX Ydieresis ecircumflex -71
+KPX Ydieresis edieresis -71
+KPX Ydieresis edotaccent -111
+KPX Ydieresis egrave -71
+KPX Ydieresis emacron -71
+KPX Ydieresis eogonek -111
+KPX Ydieresis hyphen -92
+KPX Ydieresis i -55
+KPX Ydieresis iacute -55
+KPX Ydieresis iogonek -55
+KPX Ydieresis o -111
+KPX Ydieresis oacute -111
+KPX Ydieresis ocircumflex -111
+KPX Ydieresis odieresis -111
+KPX Ydieresis ograve -111
+KPX Ydieresis ohungarumlaut -111
+KPX Ydieresis omacron -111
+KPX Ydieresis oslash -111
+KPX Ydieresis otilde -111
+KPX Ydieresis period -74
+KPX Ydieresis semicolon -92
+KPX Ydieresis u -92
+KPX Ydieresis uacute -92
+KPX Ydieresis ucircumflex -92
+KPX Ydieresis udieresis -92
+KPX Ydieresis ugrave -92
+KPX Ydieresis uhungarumlaut -92
+KPX Ydieresis umacron -92
+KPX Ydieresis uogonek -92
+KPX Ydieresis uring -92
+KPX b b -10
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX c h -10
+KPX c k -10
+KPX c kcommaaccent -10
+KPX cacute h -10
+KPX cacute k -10
+KPX cacute kcommaaccent -10
+KPX ccaron h -10
+KPX ccaron k -10
+KPX ccaron kcommaaccent -10
+KPX ccedilla h -10
+KPX ccedilla k -10
+KPX ccedilla kcommaaccent -10
+KPX comma quotedblright -95
+KPX comma quoteright -95
+KPX e b -10
+KPX eacute b -10
+KPX ecaron b -10
+KPX ecircumflex b -10
+KPX edieresis b -10
+KPX edotaccent b -10
+KPX egrave b -10
+KPX emacron b -10
+KPX eogonek b -10
+KPX f comma -10
+KPX f dotlessi -30
+KPX f e -10
+KPX f eacute -10
+KPX f edotaccent -10
+KPX f eogonek -10
+KPX f f -18
+KPX f o -10
+KPX f oacute -10
+KPX f ocircumflex -10
+KPX f ograve -10
+KPX f ohungarumlaut -10
+KPX f oslash -10
+KPX f otilde -10
+KPX f period -10
+KPX f quoteright 55
+KPX k e -30
+KPX k eacute -30
+KPX k ecaron -30
+KPX k ecircumflex -30
+KPX k edieresis -30
+KPX k edotaccent -30
+KPX k egrave -30
+KPX k emacron -30
+KPX k eogonek -30
+KPX k o -10
+KPX k oacute -10
+KPX k ocircumflex -10
+KPX k odieresis -10
+KPX k ograve -10
+KPX k ohungarumlaut -10
+KPX k omacron -10
+KPX k oslash -10
+KPX k otilde -10
+KPX kcommaaccent e -30
+KPX kcommaaccent eacute -30
+KPX kcommaaccent ecaron -30
+KPX kcommaaccent ecircumflex -30
+KPX kcommaaccent edieresis -30
+KPX kcommaaccent edotaccent -30
+KPX kcommaaccent egrave -30
+KPX kcommaaccent emacron -30
+KPX kcommaaccent eogonek -30
+KPX kcommaaccent o -10
+KPX kcommaaccent oacute -10
+KPX kcommaaccent ocircumflex -10
+KPX kcommaaccent odieresis -10
+KPX kcommaaccent ograve -10
+KPX kcommaaccent ohungarumlaut -10
+KPX kcommaaccent omacron -10
+KPX kcommaaccent oslash -10
+KPX kcommaaccent otilde -10
+KPX n v -40
+KPX nacute v -40
+KPX ncaron v -40
+KPX ncommaaccent v -40
+KPX ntilde v -40
+KPX o v -15
+KPX o w -25
+KPX o x -10
+KPX o y -10
+KPX o yacute -10
+KPX o ydieresis -10
+KPX oacute v -15
+KPX oacute w -25
+KPX oacute x -10
+KPX oacute y -10
+KPX oacute yacute -10
+KPX oacute ydieresis -10
+KPX ocircumflex v -15
+KPX ocircumflex w -25
+KPX ocircumflex x -10
+KPX ocircumflex y -10
+KPX ocircumflex yacute -10
+KPX ocircumflex ydieresis -10
+KPX odieresis v -15
+KPX odieresis w -25
+KPX odieresis x -10
+KPX odieresis y -10
+KPX odieresis yacute -10
+KPX odieresis ydieresis -10
+KPX ograve v -15
+KPX ograve w -25
+KPX ograve x -10
+KPX ograve y -10
+KPX ograve yacute -10
+KPX ograve ydieresis -10
+KPX ohungarumlaut v -15
+KPX ohungarumlaut w -25
+KPX ohungarumlaut x -10
+KPX ohungarumlaut y -10
+KPX ohungarumlaut yacute -10
+KPX ohungarumlaut ydieresis -10
+KPX omacron v -15
+KPX omacron w -25
+KPX omacron x -10
+KPX omacron y -10
+KPX omacron yacute -10
+KPX omacron ydieresis -10
+KPX oslash v -15
+KPX oslash w -25
+KPX oslash x -10
+KPX oslash y -10
+KPX oslash yacute -10
+KPX oslash ydieresis -10
+KPX otilde v -15
+KPX otilde w -25
+KPX otilde x -10
+KPX otilde y -10
+KPX otilde yacute -10
+KPX otilde ydieresis -10
+KPX period quotedblright -95
+KPX period quoteright -95
+KPX quoteleft quoteleft -74
+KPX quoteright d -15
+KPX quoteright dcroat -15
+KPX quoteright quoteright -74
+KPX quoteright r -15
+KPX quoteright racute -15
+KPX quoteright rcaron -15
+KPX quoteright rcommaaccent -15
+KPX quoteright s -74
+KPX quoteright sacute -74
+KPX quoteright scaron -74
+KPX quoteright scedilla -74
+KPX quoteright scommaaccent -74
+KPX quoteright space -74
+KPX quoteright t -37
+KPX quoteright tcommaaccent -37
+KPX quoteright v -15
+KPX r comma -65
+KPX r period -65
+KPX racute comma -65
+KPX racute period -65
+KPX rcaron comma -65
+KPX rcaron period -65
+KPX rcommaaccent comma -65
+KPX rcommaaccent period -65
+KPX space A -37
+KPX space Aacute -37
+KPX space Abreve -37
+KPX space Acircumflex -37
+KPX space Adieresis -37
+KPX space Agrave -37
+KPX space Amacron -37
+KPX space Aogonek -37
+KPX space Aring -37
+KPX space Atilde -37
+KPX space V -70
+KPX space W -70
+KPX space Y -70
+KPX space Yacute -70
+KPX space Ydieresis -70
+KPX v comma -37
+KPX v e -15
+KPX v eacute -15
+KPX v ecaron -15
+KPX v ecircumflex -15
+KPX v edieresis -15
+KPX v edotaccent -15
+KPX v egrave -15
+KPX v emacron -15
+KPX v eogonek -15
+KPX v o -15
+KPX v oacute -15
+KPX v ocircumflex -15
+KPX v odieresis -15
+KPX v ograve -15
+KPX v ohungarumlaut -15
+KPX v omacron -15
+KPX v oslash -15
+KPX v otilde -15
+KPX v period -37
+KPX w a -10
+KPX w aacute -10
+KPX w abreve -10
+KPX w acircumflex -10
+KPX w adieresis -10
+KPX w agrave -10
+KPX w amacron -10
+KPX w aogonek -10
+KPX w aring -10
+KPX w atilde -10
+KPX w comma -37
+KPX w e -10
+KPX w eacute -10
+KPX w ecaron -10
+KPX w ecircumflex -10
+KPX w edieresis -10
+KPX w edotaccent -10
+KPX w egrave -10
+KPX w emacron -10
+KPX w eogonek -10
+KPX w o -15
+KPX w oacute -15
+KPX w ocircumflex -15
+KPX w odieresis -15
+KPX w ograve -15
+KPX w ohungarumlaut -15
+KPX w omacron -15
+KPX w oslash -15
+KPX w otilde -15
+KPX w period -37
+KPX x e -10
+KPX x eacute -10
+KPX x ecaron -10
+KPX x ecircumflex -10
+KPX x edieresis -10
+KPX x edotaccent -10
+KPX x egrave -10
+KPX x emacron -10
+KPX x eogonek -10
+KPX y comma -37
+KPX y period -37
+KPX yacute comma -37
+KPX yacute period -37
+KPX ydieresis comma -37
+KPX ydieresis period -37
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-Italic.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-Italic.afm
new file mode 100644
index 0000000000000000000000000000000000000000..b0eaee40fc6ab16103375431fea06c22f648f471
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-Italic.afm
@@ -0,0 +1,2667 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:56:55 1997
+Comment UniqueID 43067
+Comment VMusage 47727 58752
+FontName Times-Italic
+FullName Times Italic
+FamilyName Times
+Weight Medium
+ItalicAngle -15.5
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -169 -217 1010 883
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 653
+XHeight 441
+Ascender 683
+Descender -217
+StdHW 32
+StdVW 76
+StartCharMetrics 315
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 39 -11 302 667 ;
+C 34 ; WX 420 ; N quotedbl ; B 144 421 432 666 ;
+C 35 ; WX 500 ; N numbersign ; B 2 0 540 676 ;
+C 36 ; WX 500 ; N dollar ; B 31 -89 497 731 ;
+C 37 ; WX 833 ; N percent ; B 79 -13 790 676 ;
+C 38 ; WX 778 ; N ampersand ; B 76 -18 723 666 ;
+C 39 ; WX 333 ; N quoteright ; B 151 436 290 666 ;
+C 40 ; WX 333 ; N parenleft ; B 42 -181 315 669 ;
+C 41 ; WX 333 ; N parenright ; B 16 -180 289 669 ;
+C 42 ; WX 500 ; N asterisk ; B 128 255 492 666 ;
+C 43 ; WX 675 ; N plus ; B 86 0 590 506 ;
+C 44 ; WX 250 ; N comma ; B -4 -129 135 101 ;
+C 45 ; WX 333 ; N hyphen ; B 49 192 282 255 ;
+C 46 ; WX 250 ; N period ; B 27 -11 138 100 ;
+C 47 ; WX 278 ; N slash ; B -65 -18 386 666 ;
+C 48 ; WX 500 ; N zero ; B 32 -7 497 676 ;
+C 49 ; WX 500 ; N one ; B 49 0 409 676 ;
+C 50 ; WX 500 ; N two ; B 12 0 452 676 ;
+C 51 ; WX 500 ; N three ; B 15 -7 465 676 ;
+C 52 ; WX 500 ; N four ; B 1 0 479 676 ;
+C 53 ; WX 500 ; N five ; B 15 -7 491 666 ;
+C 54 ; WX 500 ; N six ; B 30 -7 521 686 ;
+C 55 ; WX 500 ; N seven ; B 75 -8 537 666 ;
+C 56 ; WX 500 ; N eight ; B 30 -7 493 676 ;
+C 57 ; WX 500 ; N nine ; B 23 -17 492 676 ;
+C 58 ; WX 333 ; N colon ; B 50 -11 261 441 ;
+C 59 ; WX 333 ; N semicolon ; B 27 -129 261 441 ;
+C 60 ; WX 675 ; N less ; B 84 -8 592 514 ;
+C 61 ; WX 675 ; N equal ; B 86 120 590 386 ;
+C 62 ; WX 675 ; N greater ; B 84 -8 592 514 ;
+C 63 ; WX 500 ; N question ; B 132 -12 472 664 ;
+C 64 ; WX 920 ; N at ; B 118 -18 806 666 ;
+C 65 ; WX 611 ; N A ; B -51 0 564 668 ;
+C 66 ; WX 611 ; N B ; B -8 0 588 653 ;
+C 67 ; WX 667 ; N C ; B 66 -18 689 666 ;
+C 68 ; WX 722 ; N D ; B -8 0 700 653 ;
+C 69 ; WX 611 ; N E ; B -1 0 634 653 ;
+C 70 ; WX 611 ; N F ; B 8 0 645 653 ;
+C 71 ; WX 722 ; N G ; B 52 -18 722 666 ;
+C 72 ; WX 722 ; N H ; B -8 0 767 653 ;
+C 73 ; WX 333 ; N I ; B -8 0 384 653 ;
+C 74 ; WX 444 ; N J ; B -6 -18 491 653 ;
+C 75 ; WX 667 ; N K ; B 7 0 722 653 ;
+C 76 ; WX 556 ; N L ; B -8 0 559 653 ;
+C 77 ; WX 833 ; N M ; B -18 0 873 653 ;
+C 78 ; WX 667 ; N N ; B -20 -15 727 653 ;
+C 79 ; WX 722 ; N O ; B 60 -18 699 666 ;
+C 80 ; WX 611 ; N P ; B 0 0 605 653 ;
+C 81 ; WX 722 ; N Q ; B 59 -182 699 666 ;
+C 82 ; WX 611 ; N R ; B -13 0 588 653 ;
+C 83 ; WX 500 ; N S ; B 17 -18 508 667 ;
+C 84 ; WX 556 ; N T ; B 59 0 633 653 ;
+C 85 ; WX 722 ; N U ; B 102 -18 765 653 ;
+C 86 ; WX 611 ; N V ; B 76 -18 688 653 ;
+C 87 ; WX 833 ; N W ; B 71 -18 906 653 ;
+C 88 ; WX 611 ; N X ; B -29 0 655 653 ;
+C 89 ; WX 556 ; N Y ; B 78 0 633 653 ;
+C 90 ; WX 556 ; N Z ; B -6 0 606 653 ;
+C 91 ; WX 389 ; N bracketleft ; B 21 -153 391 663 ;
+C 92 ; WX 278 ; N backslash ; B -41 -18 319 666 ;
+C 93 ; WX 389 ; N bracketright ; B 12 -153 382 663 ;
+C 94 ; WX 422 ; N asciicircum ; B 0 301 422 666 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 333 ; N quoteleft ; B 171 436 310 666 ;
+C 97 ; WX 500 ; N a ; B 17 -11 476 441 ;
+C 98 ; WX 500 ; N b ; B 23 -11 473 683 ;
+C 99 ; WX 444 ; N c ; B 30 -11 425 441 ;
+C 100 ; WX 500 ; N d ; B 15 -13 527 683 ;
+C 101 ; WX 444 ; N e ; B 31 -11 412 441 ;
+C 102 ; WX 278 ; N f ; B -147 -207 424 678 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B 8 -206 472 441 ;
+C 104 ; WX 500 ; N h ; B 19 -9 478 683 ;
+C 105 ; WX 278 ; N i ; B 49 -11 264 654 ;
+C 106 ; WX 278 ; N j ; B -124 -207 276 654 ;
+C 107 ; WX 444 ; N k ; B 14 -11 461 683 ;
+C 108 ; WX 278 ; N l ; B 41 -11 279 683 ;
+C 109 ; WX 722 ; N m ; B 12 -9 704 441 ;
+C 110 ; WX 500 ; N n ; B 14 -9 474 441 ;
+C 111 ; WX 500 ; N o ; B 27 -11 468 441 ;
+C 112 ; WX 500 ; N p ; B -75 -205 469 441 ;
+C 113 ; WX 500 ; N q ; B 25 -209 483 441 ;
+C 114 ; WX 389 ; N r ; B 45 0 412 441 ;
+C 115 ; WX 389 ; N s ; B 16 -13 366 442 ;
+C 116 ; WX 278 ; N t ; B 37 -11 296 546 ;
+C 117 ; WX 500 ; N u ; B 42 -11 475 441 ;
+C 118 ; WX 444 ; N v ; B 21 -18 426 441 ;
+C 119 ; WX 667 ; N w ; B 16 -18 648 441 ;
+C 120 ; WX 444 ; N x ; B -27 -11 447 441 ;
+C 121 ; WX 444 ; N y ; B -24 -206 426 441 ;
+C 122 ; WX 389 ; N z ; B -2 -81 380 428 ;
+C 123 ; WX 400 ; N braceleft ; B 51 -177 407 687 ;
+C 124 ; WX 275 ; N bar ; B 105 -217 171 783 ;
+C 125 ; WX 400 ; N braceright ; B -7 -177 349 687 ;
+C 126 ; WX 541 ; N asciitilde ; B 40 183 502 323 ;
+C 161 ; WX 389 ; N exclamdown ; B 59 -205 322 473 ;
+C 162 ; WX 500 ; N cent ; B 77 -143 472 560 ;
+C 163 ; WX 500 ; N sterling ; B 10 -6 517 670 ;
+C 164 ; WX 167 ; N fraction ; B -169 -10 337 676 ;
+C 165 ; WX 500 ; N yen ; B 27 0 603 653 ;
+C 166 ; WX 500 ; N florin ; B 25 -182 507 682 ;
+C 167 ; WX 500 ; N section ; B 53 -162 461 666 ;
+C 168 ; WX 500 ; N currency ; B -22 53 522 597 ;
+C 169 ; WX 214 ; N quotesingle ; B 132 421 241 666 ;
+C 170 ; WX 556 ; N quotedblleft ; B 166 436 514 666 ;
+C 171 ; WX 500 ; N guillemotleft ; B 53 37 445 403 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 51 37 281 403 ;
+C 173 ; WX 333 ; N guilsinglright ; B 52 37 282 403 ;
+C 174 ; WX 500 ; N fi ; B -141 -207 481 681 ;
+C 175 ; WX 500 ; N fl ; B -141 -204 518 682 ;
+C 177 ; WX 500 ; N endash ; B -6 197 505 243 ;
+C 178 ; WX 500 ; N dagger ; B 101 -159 488 666 ;
+C 179 ; WX 500 ; N daggerdbl ; B 22 -143 491 666 ;
+C 180 ; WX 250 ; N periodcentered ; B 70 199 181 310 ;
+C 182 ; WX 523 ; N paragraph ; B 55 -123 616 653 ;
+C 183 ; WX 350 ; N bullet ; B 40 191 310 461 ;
+C 184 ; WX 333 ; N quotesinglbase ; B 44 -129 183 101 ;
+C 185 ; WX 556 ; N quotedblbase ; B 57 -129 405 101 ;
+C 186 ; WX 556 ; N quotedblright ; B 151 436 499 666 ;
+C 187 ; WX 500 ; N guillemotright ; B 55 37 447 403 ;
+C 188 ; WX 889 ; N ellipsis ; B 57 -11 762 100 ;
+C 189 ; WX 1000 ; N perthousand ; B 25 -19 1010 706 ;
+C 191 ; WX 500 ; N questiondown ; B 28 -205 368 471 ;
+C 193 ; WX 333 ; N grave ; B 121 492 311 664 ;
+C 194 ; WX 333 ; N acute ; B 180 494 403 664 ;
+C 195 ; WX 333 ; N circumflex ; B 91 492 385 661 ;
+C 196 ; WX 333 ; N tilde ; B 100 517 427 624 ;
+C 197 ; WX 333 ; N macron ; B 99 532 411 583 ;
+C 198 ; WX 333 ; N breve ; B 117 492 418 650 ;
+C 199 ; WX 333 ; N dotaccent ; B 207 548 305 646 ;
+C 200 ; WX 333 ; N dieresis ; B 107 548 405 646 ;
+C 202 ; WX 333 ; N ring ; B 155 492 355 691 ;
+C 203 ; WX 333 ; N cedilla ; B -30 -217 182 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B 93 494 486 664 ;
+C 206 ; WX 333 ; N ogonek ; B 20 -169 203 40 ;
+C 207 ; WX 333 ; N caron ; B 121 492 426 661 ;
+C 208 ; WX 889 ; N emdash ; B -6 197 894 243 ;
+C 225 ; WX 889 ; N AE ; B -27 0 911 653 ;
+C 227 ; WX 276 ; N ordfeminine ; B 42 406 352 676 ;
+C 232 ; WX 556 ; N Lslash ; B -8 0 559 653 ;
+C 233 ; WX 722 ; N Oslash ; B 60 -105 699 722 ;
+C 234 ; WX 944 ; N OE ; B 49 -8 964 666 ;
+C 235 ; WX 310 ; N ordmasculine ; B 67 406 362 676 ;
+C 241 ; WX 667 ; N ae ; B 23 -11 640 441 ;
+C 245 ; WX 278 ; N dotlessi ; B 49 -11 235 441 ;
+C 248 ; WX 278 ; N lslash ; B 41 -11 312 683 ;
+C 249 ; WX 500 ; N oslash ; B 28 -135 469 554 ;
+C 250 ; WX 667 ; N oe ; B 20 -12 646 441 ;
+C 251 ; WX 500 ; N germandbls ; B -168 -207 493 679 ;
+C -1 ; WX 333 ; N Idieresis ; B -8 0 435 818 ;
+C -1 ; WX 444 ; N eacute ; B 31 -11 459 664 ;
+C -1 ; WX 500 ; N abreve ; B 17 -11 502 650 ;
+C -1 ; WX 500 ; N uhungarumlaut ; B 42 -11 580 664 ;
+C -1 ; WX 444 ; N ecaron ; B 31 -11 482 661 ;
+C -1 ; WX 556 ; N Ydieresis ; B 78 0 633 818 ;
+C -1 ; WX 675 ; N divide ; B 86 -11 590 517 ;
+C -1 ; WX 556 ; N Yacute ; B 78 0 633 876 ;
+C -1 ; WX 611 ; N Acircumflex ; B -51 0 564 873 ;
+C -1 ; WX 500 ; N aacute ; B 17 -11 487 664 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 102 -18 765 873 ;
+C -1 ; WX 444 ; N yacute ; B -24 -206 459 664 ;
+C -1 ; WX 389 ; N scommaaccent ; B 16 -217 366 442 ;
+C -1 ; WX 444 ; N ecircumflex ; B 31 -11 441 661 ;
+C -1 ; WX 722 ; N Uring ; B 102 -18 765 883 ;
+C -1 ; WX 722 ; N Udieresis ; B 102 -18 765 818 ;
+C -1 ; WX 500 ; N aogonek ; B 17 -169 476 441 ;
+C -1 ; WX 722 ; N Uacute ; B 102 -18 765 876 ;
+C -1 ; WX 500 ; N uogonek ; B 42 -169 477 441 ;
+C -1 ; WX 611 ; N Edieresis ; B -1 0 634 818 ;
+C -1 ; WX 722 ; N Dcroat ; B -8 0 700 653 ;
+C -1 ; WX 250 ; N commaaccent ; B 8 -217 133 -50 ;
+C -1 ; WX 760 ; N copyright ; B 41 -18 719 666 ;
+C -1 ; WX 611 ; N Emacron ; B -1 0 634 795 ;
+C -1 ; WX 444 ; N ccaron ; B 30 -11 482 661 ;
+C -1 ; WX 500 ; N aring ; B 17 -11 476 691 ;
+C -1 ; WX 667 ; N Ncommaaccent ; B -20 -187 727 653 ;
+C -1 ; WX 278 ; N lacute ; B 41 -11 395 876 ;
+C -1 ; WX 500 ; N agrave ; B 17 -11 476 664 ;
+C -1 ; WX 556 ; N Tcommaaccent ; B 59 -217 633 653 ;
+C -1 ; WX 667 ; N Cacute ; B 66 -18 690 876 ;
+C -1 ; WX 500 ; N atilde ; B 17 -11 511 624 ;
+C -1 ; WX 611 ; N Edotaccent ; B -1 0 634 818 ;
+C -1 ; WX 389 ; N scaron ; B 16 -13 454 661 ;
+C -1 ; WX 389 ; N scedilla ; B 16 -217 366 442 ;
+C -1 ; WX 278 ; N iacute ; B 49 -11 355 664 ;
+C -1 ; WX 471 ; N lozenge ; B 13 0 459 724 ;
+C -1 ; WX 611 ; N Rcaron ; B -13 0 588 873 ;
+C -1 ; WX 722 ; N Gcommaaccent ; B 52 -217 722 666 ;
+C -1 ; WX 500 ; N ucircumflex ; B 42 -11 475 661 ;
+C -1 ; WX 500 ; N acircumflex ; B 17 -11 476 661 ;
+C -1 ; WX 611 ; N Amacron ; B -51 0 564 795 ;
+C -1 ; WX 389 ; N rcaron ; B 45 0 434 661 ;
+C -1 ; WX 444 ; N ccedilla ; B 30 -217 425 441 ;
+C -1 ; WX 556 ; N Zdotaccent ; B -6 0 606 818 ;
+C -1 ; WX 611 ; N Thorn ; B 0 0 569 653 ;
+C -1 ; WX 722 ; N Omacron ; B 60 -18 699 795 ;
+C -1 ; WX 611 ; N Racute ; B -13 0 588 876 ;
+C -1 ; WX 500 ; N Sacute ; B 17 -18 508 876 ;
+C -1 ; WX 544 ; N dcaron ; B 15 -13 658 683 ;
+C -1 ; WX 722 ; N Umacron ; B 102 -18 765 795 ;
+C -1 ; WX 500 ; N uring ; B 42 -11 475 691 ;
+C -1 ; WX 300 ; N threesuperior ; B 43 268 339 676 ;
+C -1 ; WX 722 ; N Ograve ; B 60 -18 699 876 ;
+C -1 ; WX 611 ; N Agrave ; B -51 0 564 876 ;
+C -1 ; WX 611 ; N Abreve ; B -51 0 564 862 ;
+C -1 ; WX 675 ; N multiply ; B 93 8 582 497 ;
+C -1 ; WX 500 ; N uacute ; B 42 -11 477 664 ;
+C -1 ; WX 556 ; N Tcaron ; B 59 0 633 873 ;
+C -1 ; WX 476 ; N partialdiff ; B 17 -38 459 710 ;
+C -1 ; WX 444 ; N ydieresis ; B -24 -206 441 606 ;
+C -1 ; WX 667 ; N Nacute ; B -20 -15 727 876 ;
+C -1 ; WX 278 ; N icircumflex ; B 33 -11 327 661 ;
+C -1 ; WX 611 ; N Ecircumflex ; B -1 0 634 873 ;
+C -1 ; WX 500 ; N adieresis ; B 17 -11 489 606 ;
+C -1 ; WX 444 ; N edieresis ; B 31 -11 451 606 ;
+C -1 ; WX 444 ; N cacute ; B 30 -11 459 664 ;
+C -1 ; WX 500 ; N nacute ; B 14 -9 477 664 ;
+C -1 ; WX 500 ; N umacron ; B 42 -11 485 583 ;
+C -1 ; WX 667 ; N Ncaron ; B -20 -15 727 873 ;
+C -1 ; WX 333 ; N Iacute ; B -8 0 433 876 ;
+C -1 ; WX 675 ; N plusminus ; B 86 0 590 506 ;
+C -1 ; WX 275 ; N brokenbar ; B 105 -142 171 708 ;
+C -1 ; WX 760 ; N registered ; B 41 -18 719 666 ;
+C -1 ; WX 722 ; N Gbreve ; B 52 -18 722 862 ;
+C -1 ; WX 333 ; N Idotaccent ; B -8 0 384 818 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 585 706 ;
+C -1 ; WX 611 ; N Egrave ; B -1 0 634 876 ;
+C -1 ; WX 389 ; N racute ; B 45 0 431 664 ;
+C -1 ; WX 500 ; N omacron ; B 27 -11 495 583 ;
+C -1 ; WX 556 ; N Zacute ; B -6 0 606 876 ;
+C -1 ; WX 556 ; N Zcaron ; B -6 0 606 873 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 658 ;
+C -1 ; WX 722 ; N Eth ; B -8 0 700 653 ;
+C -1 ; WX 667 ; N Ccedilla ; B 66 -217 689 666 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 22 -217 279 683 ;
+C -1 ; WX 300 ; N tcaron ; B 37 -11 407 681 ;
+C -1 ; WX 444 ; N eogonek ; B 31 -169 412 441 ;
+C -1 ; WX 722 ; N Uogonek ; B 102 -184 765 653 ;
+C -1 ; WX 611 ; N Aacute ; B -51 0 564 876 ;
+C -1 ; WX 611 ; N Adieresis ; B -51 0 564 818 ;
+C -1 ; WX 444 ; N egrave ; B 31 -11 412 664 ;
+C -1 ; WX 389 ; N zacute ; B -2 -81 431 664 ;
+C -1 ; WX 278 ; N iogonek ; B 49 -169 264 654 ;
+C -1 ; WX 722 ; N Oacute ; B 60 -18 699 876 ;
+C -1 ; WX 500 ; N oacute ; B 27 -11 487 664 ;
+C -1 ; WX 500 ; N amacron ; B 17 -11 495 583 ;
+C -1 ; WX 389 ; N sacute ; B 16 -13 431 664 ;
+C -1 ; WX 278 ; N idieresis ; B 49 -11 352 606 ;
+C -1 ; WX 722 ; N Ocircumflex ; B 60 -18 699 873 ;
+C -1 ; WX 722 ; N Ugrave ; B 102 -18 765 876 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C -1 ; WX 500 ; N thorn ; B -75 -205 469 683 ;
+C -1 ; WX 300 ; N twosuperior ; B 33 271 324 676 ;
+C -1 ; WX 722 ; N Odieresis ; B 60 -18 699 818 ;
+C -1 ; WX 500 ; N mu ; B -30 -209 497 428 ;
+C -1 ; WX 278 ; N igrave ; B 49 -11 284 664 ;
+C -1 ; WX 500 ; N ohungarumlaut ; B 27 -11 590 664 ;
+C -1 ; WX 611 ; N Eogonek ; B -1 -169 634 653 ;
+C -1 ; WX 500 ; N dcroat ; B 15 -13 572 683 ;
+C -1 ; WX 750 ; N threequarters ; B 23 -10 736 676 ;
+C -1 ; WX 500 ; N Scedilla ; B 17 -217 508 667 ;
+C -1 ; WX 300 ; N lcaron ; B 41 -11 407 683 ;
+C -1 ; WX 667 ; N Kcommaaccent ; B 7 -217 722 653 ;
+C -1 ; WX 556 ; N Lacute ; B -8 0 559 876 ;
+C -1 ; WX 980 ; N trademark ; B 30 247 957 653 ;
+C -1 ; WX 444 ; N edotaccent ; B 31 -11 412 606 ;
+C -1 ; WX 333 ; N Igrave ; B -8 0 384 876 ;
+C -1 ; WX 333 ; N Imacron ; B -8 0 441 795 ;
+C -1 ; WX 611 ; N Lcaron ; B -8 0 586 653 ;
+C -1 ; WX 750 ; N onehalf ; B 34 -10 749 676 ;
+C -1 ; WX 549 ; N lessequal ; B 26 0 523 658 ;
+C -1 ; WX 500 ; N ocircumflex ; B 27 -11 468 661 ;
+C -1 ; WX 500 ; N ntilde ; B 14 -9 476 624 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 102 -18 765 876 ;
+C -1 ; WX 611 ; N Eacute ; B -1 0 634 876 ;
+C -1 ; WX 444 ; N emacron ; B 31 -11 457 583 ;
+C -1 ; WX 500 ; N gbreve ; B 8 -206 487 650 ;
+C -1 ; WX 750 ; N onequarter ; B 33 -10 736 676 ;
+C -1 ; WX 500 ; N Scaron ; B 17 -18 520 873 ;
+C -1 ; WX 500 ; N Scommaaccent ; B 17 -217 508 667 ;
+C -1 ; WX 722 ; N Ohungarumlaut ; B 60 -18 699 876 ;
+C -1 ; WX 400 ; N degree ; B 101 390 387 676 ;
+C -1 ; WX 500 ; N ograve ; B 27 -11 468 664 ;
+C -1 ; WX 667 ; N Ccaron ; B 66 -18 689 873 ;
+C -1 ; WX 500 ; N ugrave ; B 42 -11 475 664 ;
+C -1 ; WX 453 ; N radical ; B 2 -60 452 768 ;
+C -1 ; WX 722 ; N Dcaron ; B -8 0 700 873 ;
+C -1 ; WX 389 ; N rcommaaccent ; B -3 -217 412 441 ;
+C -1 ; WX 667 ; N Ntilde ; B -20 -15 727 836 ;
+C -1 ; WX 500 ; N otilde ; B 27 -11 496 624 ;
+C -1 ; WX 611 ; N Rcommaaccent ; B -13 -187 588 653 ;
+C -1 ; WX 556 ; N Lcommaaccent ; B -8 -217 559 653 ;
+C -1 ; WX 611 ; N Atilde ; B -51 0 566 836 ;
+C -1 ; WX 611 ; N Aogonek ; B -51 -169 566 668 ;
+C -1 ; WX 611 ; N Aring ; B -51 0 564 883 ;
+C -1 ; WX 722 ; N Otilde ; B 60 -18 699 836 ;
+C -1 ; WX 389 ; N zdotaccent ; B -2 -81 380 606 ;
+C -1 ; WX 611 ; N Ecaron ; B -1 0 634 873 ;
+C -1 ; WX 333 ; N Iogonek ; B -8 -169 384 653 ;
+C -1 ; WX 444 ; N kcommaaccent ; B 14 -187 461 683 ;
+C -1 ; WX 675 ; N minus ; B 86 220 590 286 ;
+C -1 ; WX 333 ; N Icircumflex ; B -8 0 425 873 ;
+C -1 ; WX 500 ; N ncaron ; B 14 -9 510 661 ;
+C -1 ; WX 278 ; N tcommaaccent ; B 2 -217 296 546 ;
+C -1 ; WX 675 ; N logicalnot ; B 86 108 590 386 ;
+C -1 ; WX 500 ; N odieresis ; B 27 -11 489 606 ;
+C -1 ; WX 500 ; N udieresis ; B 42 -11 479 606 ;
+C -1 ; WX 549 ; N notequal ; B 12 -29 537 541 ;
+C -1 ; WX 500 ; N gcommaaccent ; B 8 -206 472 706 ;
+C -1 ; WX 500 ; N eth ; B 27 -11 482 683 ;
+C -1 ; WX 389 ; N zcaron ; B -2 -81 434 661 ;
+C -1 ; WX 500 ; N ncommaaccent ; B 14 -187 474 441 ;
+C -1 ; WX 300 ; N onesuperior ; B 43 271 284 676 ;
+C -1 ; WX 278 ; N imacron ; B 46 -11 311 583 ;
+C -1 ; WX 500 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2321
+KPX A C -30
+KPX A Cacute -30
+KPX A Ccaron -30
+KPX A Ccedilla -30
+KPX A G -35
+KPX A Gbreve -35
+KPX A Gcommaaccent -35
+KPX A O -40
+KPX A Oacute -40
+KPX A Ocircumflex -40
+KPX A Odieresis -40
+KPX A Ograve -40
+KPX A Ohungarumlaut -40
+KPX A Omacron -40
+KPX A Oslash -40
+KPX A Otilde -40
+KPX A Q -40
+KPX A T -37
+KPX A Tcaron -37
+KPX A Tcommaaccent -37
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -105
+KPX A W -95
+KPX A Y -55
+KPX A Yacute -55
+KPX A Ydieresis -55
+KPX A quoteright -37
+KPX A u -20
+KPX A uacute -20
+KPX A ucircumflex -20
+KPX A udieresis -20
+KPX A ugrave -20
+KPX A uhungarumlaut -20
+KPX A umacron -20
+KPX A uogonek -20
+KPX A uring -20
+KPX A v -55
+KPX A w -55
+KPX A y -55
+KPX A yacute -55
+KPX A ydieresis -55
+KPX Aacute C -30
+KPX Aacute Cacute -30
+KPX Aacute Ccaron -30
+KPX Aacute Ccedilla -30
+KPX Aacute G -35
+KPX Aacute Gbreve -35
+KPX Aacute Gcommaaccent -35
+KPX Aacute O -40
+KPX Aacute Oacute -40
+KPX Aacute Ocircumflex -40
+KPX Aacute Odieresis -40
+KPX Aacute Ograve -40
+KPX Aacute Ohungarumlaut -40
+KPX Aacute Omacron -40
+KPX Aacute Oslash -40
+KPX Aacute Otilde -40
+KPX Aacute Q -40
+KPX Aacute T -37
+KPX Aacute Tcaron -37
+KPX Aacute Tcommaaccent -37
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -105
+KPX Aacute W -95
+KPX Aacute Y -55
+KPX Aacute Yacute -55
+KPX Aacute Ydieresis -55
+KPX Aacute quoteright -37
+KPX Aacute u -20
+KPX Aacute uacute -20
+KPX Aacute ucircumflex -20
+KPX Aacute udieresis -20
+KPX Aacute ugrave -20
+KPX Aacute uhungarumlaut -20
+KPX Aacute umacron -20
+KPX Aacute uogonek -20
+KPX Aacute uring -20
+KPX Aacute v -55
+KPX Aacute w -55
+KPX Aacute y -55
+KPX Aacute yacute -55
+KPX Aacute ydieresis -55
+KPX Abreve C -30
+KPX Abreve Cacute -30
+KPX Abreve Ccaron -30
+KPX Abreve Ccedilla -30
+KPX Abreve G -35
+KPX Abreve Gbreve -35
+KPX Abreve Gcommaaccent -35
+KPX Abreve O -40
+KPX Abreve Oacute -40
+KPX Abreve Ocircumflex -40
+KPX Abreve Odieresis -40
+KPX Abreve Ograve -40
+KPX Abreve Ohungarumlaut -40
+KPX Abreve Omacron -40
+KPX Abreve Oslash -40
+KPX Abreve Otilde -40
+KPX Abreve Q -40
+KPX Abreve T -37
+KPX Abreve Tcaron -37
+KPX Abreve Tcommaaccent -37
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -105
+KPX Abreve W -95
+KPX Abreve Y -55
+KPX Abreve Yacute -55
+KPX Abreve Ydieresis -55
+KPX Abreve quoteright -37
+KPX Abreve u -20
+KPX Abreve uacute -20
+KPX Abreve ucircumflex -20
+KPX Abreve udieresis -20
+KPX Abreve ugrave -20
+KPX Abreve uhungarumlaut -20
+KPX Abreve umacron -20
+KPX Abreve uogonek -20
+KPX Abreve uring -20
+KPX Abreve v -55
+KPX Abreve w -55
+KPX Abreve y -55
+KPX Abreve yacute -55
+KPX Abreve ydieresis -55
+KPX Acircumflex C -30
+KPX Acircumflex Cacute -30
+KPX Acircumflex Ccaron -30
+KPX Acircumflex Ccedilla -30
+KPX Acircumflex G -35
+KPX Acircumflex Gbreve -35
+KPX Acircumflex Gcommaaccent -35
+KPX Acircumflex O -40
+KPX Acircumflex Oacute -40
+KPX Acircumflex Ocircumflex -40
+KPX Acircumflex Odieresis -40
+KPX Acircumflex Ograve -40
+KPX Acircumflex Ohungarumlaut -40
+KPX Acircumflex Omacron -40
+KPX Acircumflex Oslash -40
+KPX Acircumflex Otilde -40
+KPX Acircumflex Q -40
+KPX Acircumflex T -37
+KPX Acircumflex Tcaron -37
+KPX Acircumflex Tcommaaccent -37
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -105
+KPX Acircumflex W -95
+KPX Acircumflex Y -55
+KPX Acircumflex Yacute -55
+KPX Acircumflex Ydieresis -55
+KPX Acircumflex quoteright -37
+KPX Acircumflex u -20
+KPX Acircumflex uacute -20
+KPX Acircumflex ucircumflex -20
+KPX Acircumflex udieresis -20
+KPX Acircumflex ugrave -20
+KPX Acircumflex uhungarumlaut -20
+KPX Acircumflex umacron -20
+KPX Acircumflex uogonek -20
+KPX Acircumflex uring -20
+KPX Acircumflex v -55
+KPX Acircumflex w -55
+KPX Acircumflex y -55
+KPX Acircumflex yacute -55
+KPX Acircumflex ydieresis -55
+KPX Adieresis C -30
+KPX Adieresis Cacute -30
+KPX Adieresis Ccaron -30
+KPX Adieresis Ccedilla -30
+KPX Adieresis G -35
+KPX Adieresis Gbreve -35
+KPX Adieresis Gcommaaccent -35
+KPX Adieresis O -40
+KPX Adieresis Oacute -40
+KPX Adieresis Ocircumflex -40
+KPX Adieresis Odieresis -40
+KPX Adieresis Ograve -40
+KPX Adieresis Ohungarumlaut -40
+KPX Adieresis Omacron -40
+KPX Adieresis Oslash -40
+KPX Adieresis Otilde -40
+KPX Adieresis Q -40
+KPX Adieresis T -37
+KPX Adieresis Tcaron -37
+KPX Adieresis Tcommaaccent -37
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -105
+KPX Adieresis W -95
+KPX Adieresis Y -55
+KPX Adieresis Yacute -55
+KPX Adieresis Ydieresis -55
+KPX Adieresis quoteright -37
+KPX Adieresis u -20
+KPX Adieresis uacute -20
+KPX Adieresis ucircumflex -20
+KPX Adieresis udieresis -20
+KPX Adieresis ugrave -20
+KPX Adieresis uhungarumlaut -20
+KPX Adieresis umacron -20
+KPX Adieresis uogonek -20
+KPX Adieresis uring -20
+KPX Adieresis v -55
+KPX Adieresis w -55
+KPX Adieresis y -55
+KPX Adieresis yacute -55
+KPX Adieresis ydieresis -55
+KPX Agrave C -30
+KPX Agrave Cacute -30
+KPX Agrave Ccaron -30
+KPX Agrave Ccedilla -30
+KPX Agrave G -35
+KPX Agrave Gbreve -35
+KPX Agrave Gcommaaccent -35
+KPX Agrave O -40
+KPX Agrave Oacute -40
+KPX Agrave Ocircumflex -40
+KPX Agrave Odieresis -40
+KPX Agrave Ograve -40
+KPX Agrave Ohungarumlaut -40
+KPX Agrave Omacron -40
+KPX Agrave Oslash -40
+KPX Agrave Otilde -40
+KPX Agrave Q -40
+KPX Agrave T -37
+KPX Agrave Tcaron -37
+KPX Agrave Tcommaaccent -37
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -105
+KPX Agrave W -95
+KPX Agrave Y -55
+KPX Agrave Yacute -55
+KPX Agrave Ydieresis -55
+KPX Agrave quoteright -37
+KPX Agrave u -20
+KPX Agrave uacute -20
+KPX Agrave ucircumflex -20
+KPX Agrave udieresis -20
+KPX Agrave ugrave -20
+KPX Agrave uhungarumlaut -20
+KPX Agrave umacron -20
+KPX Agrave uogonek -20
+KPX Agrave uring -20
+KPX Agrave v -55
+KPX Agrave w -55
+KPX Agrave y -55
+KPX Agrave yacute -55
+KPX Agrave ydieresis -55
+KPX Amacron C -30
+KPX Amacron Cacute -30
+KPX Amacron Ccaron -30
+KPX Amacron Ccedilla -30
+KPX Amacron G -35
+KPX Amacron Gbreve -35
+KPX Amacron Gcommaaccent -35
+KPX Amacron O -40
+KPX Amacron Oacute -40
+KPX Amacron Ocircumflex -40
+KPX Amacron Odieresis -40
+KPX Amacron Ograve -40
+KPX Amacron Ohungarumlaut -40
+KPX Amacron Omacron -40
+KPX Amacron Oslash -40
+KPX Amacron Otilde -40
+KPX Amacron Q -40
+KPX Amacron T -37
+KPX Amacron Tcaron -37
+KPX Amacron Tcommaaccent -37
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -105
+KPX Amacron W -95
+KPX Amacron Y -55
+KPX Amacron Yacute -55
+KPX Amacron Ydieresis -55
+KPX Amacron quoteright -37
+KPX Amacron u -20
+KPX Amacron uacute -20
+KPX Amacron ucircumflex -20
+KPX Amacron udieresis -20
+KPX Amacron ugrave -20
+KPX Amacron uhungarumlaut -20
+KPX Amacron umacron -20
+KPX Amacron uogonek -20
+KPX Amacron uring -20
+KPX Amacron v -55
+KPX Amacron w -55
+KPX Amacron y -55
+KPX Amacron yacute -55
+KPX Amacron ydieresis -55
+KPX Aogonek C -30
+KPX Aogonek Cacute -30
+KPX Aogonek Ccaron -30
+KPX Aogonek Ccedilla -30
+KPX Aogonek G -35
+KPX Aogonek Gbreve -35
+KPX Aogonek Gcommaaccent -35
+KPX Aogonek O -40
+KPX Aogonek Oacute -40
+KPX Aogonek Ocircumflex -40
+KPX Aogonek Odieresis -40
+KPX Aogonek Ograve -40
+KPX Aogonek Ohungarumlaut -40
+KPX Aogonek Omacron -40
+KPX Aogonek Oslash -40
+KPX Aogonek Otilde -40
+KPX Aogonek Q -40
+KPX Aogonek T -37
+KPX Aogonek Tcaron -37
+KPX Aogonek Tcommaaccent -37
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -105
+KPX Aogonek W -95
+KPX Aogonek Y -55
+KPX Aogonek Yacute -55
+KPX Aogonek Ydieresis -55
+KPX Aogonek quoteright -37
+KPX Aogonek u -20
+KPX Aogonek uacute -20
+KPX Aogonek ucircumflex -20
+KPX Aogonek udieresis -20
+KPX Aogonek ugrave -20
+KPX Aogonek uhungarumlaut -20
+KPX Aogonek umacron -20
+KPX Aogonek uogonek -20
+KPX Aogonek uring -20
+KPX Aogonek v -55
+KPX Aogonek w -55
+KPX Aogonek y -55
+KPX Aogonek yacute -55
+KPX Aogonek ydieresis -55
+KPX Aring C -30
+KPX Aring Cacute -30
+KPX Aring Ccaron -30
+KPX Aring Ccedilla -30
+KPX Aring G -35
+KPX Aring Gbreve -35
+KPX Aring Gcommaaccent -35
+KPX Aring O -40
+KPX Aring Oacute -40
+KPX Aring Ocircumflex -40
+KPX Aring Odieresis -40
+KPX Aring Ograve -40
+KPX Aring Ohungarumlaut -40
+KPX Aring Omacron -40
+KPX Aring Oslash -40
+KPX Aring Otilde -40
+KPX Aring Q -40
+KPX Aring T -37
+KPX Aring Tcaron -37
+KPX Aring Tcommaaccent -37
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -105
+KPX Aring W -95
+KPX Aring Y -55
+KPX Aring Yacute -55
+KPX Aring Ydieresis -55
+KPX Aring quoteright -37
+KPX Aring u -20
+KPX Aring uacute -20
+KPX Aring ucircumflex -20
+KPX Aring udieresis -20
+KPX Aring ugrave -20
+KPX Aring uhungarumlaut -20
+KPX Aring umacron -20
+KPX Aring uogonek -20
+KPX Aring uring -20
+KPX Aring v -55
+KPX Aring w -55
+KPX Aring y -55
+KPX Aring yacute -55
+KPX Aring ydieresis -55
+KPX Atilde C -30
+KPX Atilde Cacute -30
+KPX Atilde Ccaron -30
+KPX Atilde Ccedilla -30
+KPX Atilde G -35
+KPX Atilde Gbreve -35
+KPX Atilde Gcommaaccent -35
+KPX Atilde O -40
+KPX Atilde Oacute -40
+KPX Atilde Ocircumflex -40
+KPX Atilde Odieresis -40
+KPX Atilde Ograve -40
+KPX Atilde Ohungarumlaut -40
+KPX Atilde Omacron -40
+KPX Atilde Oslash -40
+KPX Atilde Otilde -40
+KPX Atilde Q -40
+KPX Atilde T -37
+KPX Atilde Tcaron -37
+KPX Atilde Tcommaaccent -37
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -105
+KPX Atilde W -95
+KPX Atilde Y -55
+KPX Atilde Yacute -55
+KPX Atilde Ydieresis -55
+KPX Atilde quoteright -37
+KPX Atilde u -20
+KPX Atilde uacute -20
+KPX Atilde ucircumflex -20
+KPX Atilde udieresis -20
+KPX Atilde ugrave -20
+KPX Atilde uhungarumlaut -20
+KPX Atilde umacron -20
+KPX Atilde uogonek -20
+KPX Atilde uring -20
+KPX Atilde v -55
+KPX Atilde w -55
+KPX Atilde y -55
+KPX Atilde yacute -55
+KPX Atilde ydieresis -55
+KPX B A -25
+KPX B Aacute -25
+KPX B Abreve -25
+KPX B Acircumflex -25
+KPX B Adieresis -25
+KPX B Agrave -25
+KPX B Amacron -25
+KPX B Aogonek -25
+KPX B Aring -25
+KPX B Atilde -25
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -35
+KPX D Aacute -35
+KPX D Abreve -35
+KPX D Acircumflex -35
+KPX D Adieresis -35
+KPX D Agrave -35
+KPX D Amacron -35
+KPX D Aogonek -35
+KPX D Aring -35
+KPX D Atilde -35
+KPX D V -40
+KPX D W -40
+KPX D Y -40
+KPX D Yacute -40
+KPX D Ydieresis -40
+KPX Dcaron A -35
+KPX Dcaron Aacute -35
+KPX Dcaron Abreve -35
+KPX Dcaron Acircumflex -35
+KPX Dcaron Adieresis -35
+KPX Dcaron Agrave -35
+KPX Dcaron Amacron -35
+KPX Dcaron Aogonek -35
+KPX Dcaron Aring -35
+KPX Dcaron Atilde -35
+KPX Dcaron V -40
+KPX Dcaron W -40
+KPX Dcaron Y -40
+KPX Dcaron Yacute -40
+KPX Dcaron Ydieresis -40
+KPX Dcroat A -35
+KPX Dcroat Aacute -35
+KPX Dcroat Abreve -35
+KPX Dcroat Acircumflex -35
+KPX Dcroat Adieresis -35
+KPX Dcroat Agrave -35
+KPX Dcroat Amacron -35
+KPX Dcroat Aogonek -35
+KPX Dcroat Aring -35
+KPX Dcroat Atilde -35
+KPX Dcroat V -40
+KPX Dcroat W -40
+KPX Dcroat Y -40
+KPX Dcroat Yacute -40
+KPX Dcroat Ydieresis -40
+KPX F A -115
+KPX F Aacute -115
+KPX F Abreve -115
+KPX F Acircumflex -115
+KPX F Adieresis -115
+KPX F Agrave -115
+KPX F Amacron -115
+KPX F Aogonek -115
+KPX F Aring -115
+KPX F Atilde -115
+KPX F a -75
+KPX F aacute -75
+KPX F abreve -75
+KPX F acircumflex -75
+KPX F adieresis -75
+KPX F agrave -75
+KPX F amacron -75
+KPX F aogonek -75
+KPX F aring -75
+KPX F atilde -75
+KPX F comma -135
+KPX F e -75
+KPX F eacute -75
+KPX F ecaron -75
+KPX F ecircumflex -75
+KPX F edieresis -75
+KPX F edotaccent -75
+KPX F egrave -75
+KPX F emacron -75
+KPX F eogonek -75
+KPX F i -45
+KPX F iacute -45
+KPX F icircumflex -45
+KPX F idieresis -45
+KPX F igrave -45
+KPX F imacron -45
+KPX F iogonek -45
+KPX F o -105
+KPX F oacute -105
+KPX F ocircumflex -105
+KPX F odieresis -105
+KPX F ograve -105
+KPX F ohungarumlaut -105
+KPX F omacron -105
+KPX F oslash -105
+KPX F otilde -105
+KPX F period -135
+KPX F r -55
+KPX F racute -55
+KPX F rcaron -55
+KPX F rcommaaccent -55
+KPX J A -40
+KPX J Aacute -40
+KPX J Abreve -40
+KPX J Acircumflex -40
+KPX J Adieresis -40
+KPX J Agrave -40
+KPX J Amacron -40
+KPX J Aogonek -40
+KPX J Aring -40
+KPX J Atilde -40
+KPX J a -35
+KPX J aacute -35
+KPX J abreve -35
+KPX J acircumflex -35
+KPX J adieresis -35
+KPX J agrave -35
+KPX J amacron -35
+KPX J aogonek -35
+KPX J aring -35
+KPX J atilde -35
+KPX J comma -25
+KPX J e -25
+KPX J eacute -25
+KPX J ecaron -25
+KPX J ecircumflex -25
+KPX J edieresis -25
+KPX J edotaccent -25
+KPX J egrave -25
+KPX J emacron -25
+KPX J eogonek -25
+KPX J o -25
+KPX J oacute -25
+KPX J ocircumflex -25
+KPX J odieresis -25
+KPX J ograve -25
+KPX J ohungarumlaut -25
+KPX J omacron -25
+KPX J oslash -25
+KPX J otilde -25
+KPX J period -25
+KPX J u -35
+KPX J uacute -35
+KPX J ucircumflex -35
+KPX J udieresis -35
+KPX J ugrave -35
+KPX J uhungarumlaut -35
+KPX J umacron -35
+KPX J uogonek -35
+KPX J uring -35
+KPX K O -50
+KPX K Oacute -50
+KPX K Ocircumflex -50
+KPX K Odieresis -50
+KPX K Ograve -50
+KPX K Ohungarumlaut -50
+KPX K Omacron -50
+KPX K Oslash -50
+KPX K Otilde -50
+KPX K e -35
+KPX K eacute -35
+KPX K ecaron -35
+KPX K ecircumflex -35
+KPX K edieresis -35
+KPX K edotaccent -35
+KPX K egrave -35
+KPX K emacron -35
+KPX K eogonek -35
+KPX K o -40
+KPX K oacute -40
+KPX K ocircumflex -40
+KPX K odieresis -40
+KPX K ograve -40
+KPX K ohungarumlaut -40
+KPX K omacron -40
+KPX K oslash -40
+KPX K otilde -40
+KPX K u -40
+KPX K uacute -40
+KPX K ucircumflex -40
+KPX K udieresis -40
+KPX K ugrave -40
+KPX K uhungarumlaut -40
+KPX K umacron -40
+KPX K uogonek -40
+KPX K uring -40
+KPX K y -40
+KPX K yacute -40
+KPX K ydieresis -40
+KPX Kcommaaccent O -50
+KPX Kcommaaccent Oacute -50
+KPX Kcommaaccent Ocircumflex -50
+KPX Kcommaaccent Odieresis -50
+KPX Kcommaaccent Ograve -50
+KPX Kcommaaccent Ohungarumlaut -50
+KPX Kcommaaccent Omacron -50
+KPX Kcommaaccent Oslash -50
+KPX Kcommaaccent Otilde -50
+KPX Kcommaaccent e -35
+KPX Kcommaaccent eacute -35
+KPX Kcommaaccent ecaron -35
+KPX Kcommaaccent ecircumflex -35
+KPX Kcommaaccent edieresis -35
+KPX Kcommaaccent edotaccent -35
+KPX Kcommaaccent egrave -35
+KPX Kcommaaccent emacron -35
+KPX Kcommaaccent eogonek -35
+KPX Kcommaaccent o -40
+KPX Kcommaaccent oacute -40
+KPX Kcommaaccent ocircumflex -40
+KPX Kcommaaccent odieresis -40
+KPX Kcommaaccent ograve -40
+KPX Kcommaaccent ohungarumlaut -40
+KPX Kcommaaccent omacron -40
+KPX Kcommaaccent oslash -40
+KPX Kcommaaccent otilde -40
+KPX Kcommaaccent u -40
+KPX Kcommaaccent uacute -40
+KPX Kcommaaccent ucircumflex -40
+KPX Kcommaaccent udieresis -40
+KPX Kcommaaccent ugrave -40
+KPX Kcommaaccent uhungarumlaut -40
+KPX Kcommaaccent umacron -40
+KPX Kcommaaccent uogonek -40
+KPX Kcommaaccent uring -40
+KPX Kcommaaccent y -40
+KPX Kcommaaccent yacute -40
+KPX Kcommaaccent ydieresis -40
+KPX L T -20
+KPX L Tcaron -20
+KPX L Tcommaaccent -20
+KPX L V -55
+KPX L W -55
+KPX L Y -20
+KPX L Yacute -20
+KPX L Ydieresis -20
+KPX L quoteright -37
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -20
+KPX Lacute Tcaron -20
+KPX Lacute Tcommaaccent -20
+KPX Lacute V -55
+KPX Lacute W -55
+KPX Lacute Y -20
+KPX Lacute Yacute -20
+KPX Lacute Ydieresis -20
+KPX Lacute quoteright -37
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcommaaccent T -20
+KPX Lcommaaccent Tcaron -20
+KPX Lcommaaccent Tcommaaccent -20
+KPX Lcommaaccent V -55
+KPX Lcommaaccent W -55
+KPX Lcommaaccent Y -20
+KPX Lcommaaccent Yacute -20
+KPX Lcommaaccent Ydieresis -20
+KPX Lcommaaccent quoteright -37
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -20
+KPX Lslash Tcaron -20
+KPX Lslash Tcommaaccent -20
+KPX Lslash V -55
+KPX Lslash W -55
+KPX Lslash Y -20
+KPX Lslash Yacute -20
+KPX Lslash Ydieresis -20
+KPX Lslash quoteright -37
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX N A -27
+KPX N Aacute -27
+KPX N Abreve -27
+KPX N Acircumflex -27
+KPX N Adieresis -27
+KPX N Agrave -27
+KPX N Amacron -27
+KPX N Aogonek -27
+KPX N Aring -27
+KPX N Atilde -27
+KPX Nacute A -27
+KPX Nacute Aacute -27
+KPX Nacute Abreve -27
+KPX Nacute Acircumflex -27
+KPX Nacute Adieresis -27
+KPX Nacute Agrave -27
+KPX Nacute Amacron -27
+KPX Nacute Aogonek -27
+KPX Nacute Aring -27
+KPX Nacute Atilde -27
+KPX Ncaron A -27
+KPX Ncaron Aacute -27
+KPX Ncaron Abreve -27
+KPX Ncaron Acircumflex -27
+KPX Ncaron Adieresis -27
+KPX Ncaron Agrave -27
+KPX Ncaron Amacron -27
+KPX Ncaron Aogonek -27
+KPX Ncaron Aring -27
+KPX Ncaron Atilde -27
+KPX Ncommaaccent A -27
+KPX Ncommaaccent Aacute -27
+KPX Ncommaaccent Abreve -27
+KPX Ncommaaccent Acircumflex -27
+KPX Ncommaaccent Adieresis -27
+KPX Ncommaaccent Agrave -27
+KPX Ncommaaccent Amacron -27
+KPX Ncommaaccent Aogonek -27
+KPX Ncommaaccent Aring -27
+KPX Ncommaaccent Atilde -27
+KPX Ntilde A -27
+KPX Ntilde Aacute -27
+KPX Ntilde Abreve -27
+KPX Ntilde Acircumflex -27
+KPX Ntilde Adieresis -27
+KPX Ntilde Agrave -27
+KPX Ntilde Amacron -27
+KPX Ntilde Aogonek -27
+KPX Ntilde Aring -27
+KPX Ntilde Atilde -27
+KPX O A -55
+KPX O Aacute -55
+KPX O Abreve -55
+KPX O Acircumflex -55
+KPX O Adieresis -55
+KPX O Agrave -55
+KPX O Amacron -55
+KPX O Aogonek -55
+KPX O Aring -55
+KPX O Atilde -55
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -40
+KPX O Y -50
+KPX O Yacute -50
+KPX O Ydieresis -50
+KPX Oacute A -55
+KPX Oacute Aacute -55
+KPX Oacute Abreve -55
+KPX Oacute Acircumflex -55
+KPX Oacute Adieresis -55
+KPX Oacute Agrave -55
+KPX Oacute Amacron -55
+KPX Oacute Aogonek -55
+KPX Oacute Aring -55
+KPX Oacute Atilde -55
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -40
+KPX Oacute Y -50
+KPX Oacute Yacute -50
+KPX Oacute Ydieresis -50
+KPX Ocircumflex A -55
+KPX Ocircumflex Aacute -55
+KPX Ocircumflex Abreve -55
+KPX Ocircumflex Acircumflex -55
+KPX Ocircumflex Adieresis -55
+KPX Ocircumflex Agrave -55
+KPX Ocircumflex Amacron -55
+KPX Ocircumflex Aogonek -55
+KPX Ocircumflex Aring -55
+KPX Ocircumflex Atilde -55
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -40
+KPX Ocircumflex Y -50
+KPX Ocircumflex Yacute -50
+KPX Ocircumflex Ydieresis -50
+KPX Odieresis A -55
+KPX Odieresis Aacute -55
+KPX Odieresis Abreve -55
+KPX Odieresis Acircumflex -55
+KPX Odieresis Adieresis -55
+KPX Odieresis Agrave -55
+KPX Odieresis Amacron -55
+KPX Odieresis Aogonek -55
+KPX Odieresis Aring -55
+KPX Odieresis Atilde -55
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -40
+KPX Odieresis Y -50
+KPX Odieresis Yacute -50
+KPX Odieresis Ydieresis -50
+KPX Ograve A -55
+KPX Ograve Aacute -55
+KPX Ograve Abreve -55
+KPX Ograve Acircumflex -55
+KPX Ograve Adieresis -55
+KPX Ograve Agrave -55
+KPX Ograve Amacron -55
+KPX Ograve Aogonek -55
+KPX Ograve Aring -55
+KPX Ograve Atilde -55
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -40
+KPX Ograve Y -50
+KPX Ograve Yacute -50
+KPX Ograve Ydieresis -50
+KPX Ohungarumlaut A -55
+KPX Ohungarumlaut Aacute -55
+KPX Ohungarumlaut Abreve -55
+KPX Ohungarumlaut Acircumflex -55
+KPX Ohungarumlaut Adieresis -55
+KPX Ohungarumlaut Agrave -55
+KPX Ohungarumlaut Amacron -55
+KPX Ohungarumlaut Aogonek -55
+KPX Ohungarumlaut Aring -55
+KPX Ohungarumlaut Atilde -55
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -40
+KPX Ohungarumlaut Y -50
+KPX Ohungarumlaut Yacute -50
+KPX Ohungarumlaut Ydieresis -50
+KPX Omacron A -55
+KPX Omacron Aacute -55
+KPX Omacron Abreve -55
+KPX Omacron Acircumflex -55
+KPX Omacron Adieresis -55
+KPX Omacron Agrave -55
+KPX Omacron Amacron -55
+KPX Omacron Aogonek -55
+KPX Omacron Aring -55
+KPX Omacron Atilde -55
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -40
+KPX Omacron Y -50
+KPX Omacron Yacute -50
+KPX Omacron Ydieresis -50
+KPX Oslash A -55
+KPX Oslash Aacute -55
+KPX Oslash Abreve -55
+KPX Oslash Acircumflex -55
+KPX Oslash Adieresis -55
+KPX Oslash Agrave -55
+KPX Oslash Amacron -55
+KPX Oslash Aogonek -55
+KPX Oslash Aring -55
+KPX Oslash Atilde -55
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -40
+KPX Oslash Y -50
+KPX Oslash Yacute -50
+KPX Oslash Ydieresis -50
+KPX Otilde A -55
+KPX Otilde Aacute -55
+KPX Otilde Abreve -55
+KPX Otilde Acircumflex -55
+KPX Otilde Adieresis -55
+KPX Otilde Agrave -55
+KPX Otilde Amacron -55
+KPX Otilde Aogonek -55
+KPX Otilde Aring -55
+KPX Otilde Atilde -55
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -40
+KPX Otilde Y -50
+KPX Otilde Yacute -50
+KPX Otilde Ydieresis -50
+KPX P A -90
+KPX P Aacute -90
+KPX P Abreve -90
+KPX P Acircumflex -90
+KPX P Adieresis -90
+KPX P Agrave -90
+KPX P Amacron -90
+KPX P Aogonek -90
+KPX P Aring -90
+KPX P Atilde -90
+KPX P a -80
+KPX P aacute -80
+KPX P abreve -80
+KPX P acircumflex -80
+KPX P adieresis -80
+KPX P agrave -80
+KPX P amacron -80
+KPX P aogonek -80
+KPX P aring -80
+KPX P atilde -80
+KPX P comma -135
+KPX P e -80
+KPX P eacute -80
+KPX P ecaron -80
+KPX P ecircumflex -80
+KPX P edieresis -80
+KPX P edotaccent -80
+KPX P egrave -80
+KPX P emacron -80
+KPX P eogonek -80
+KPX P o -80
+KPX P oacute -80
+KPX P ocircumflex -80
+KPX P odieresis -80
+KPX P ograve -80
+KPX P ohungarumlaut -80
+KPX P omacron -80
+KPX P oslash -80
+KPX P otilde -80
+KPX P period -135
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -40
+KPX R Oacute -40
+KPX R Ocircumflex -40
+KPX R Odieresis -40
+KPX R Ograve -40
+KPX R Ohungarumlaut -40
+KPX R Omacron -40
+KPX R Oslash -40
+KPX R Otilde -40
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -18
+KPX R W -18
+KPX R Y -18
+KPX R Yacute -18
+KPX R Ydieresis -18
+KPX Racute O -40
+KPX Racute Oacute -40
+KPX Racute Ocircumflex -40
+KPX Racute Odieresis -40
+KPX Racute Ograve -40
+KPX Racute Ohungarumlaut -40
+KPX Racute Omacron -40
+KPX Racute Oslash -40
+KPX Racute Otilde -40
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -18
+KPX Racute W -18
+KPX Racute Y -18
+KPX Racute Yacute -18
+KPX Racute Ydieresis -18
+KPX Rcaron O -40
+KPX Rcaron Oacute -40
+KPX Rcaron Ocircumflex -40
+KPX Rcaron Odieresis -40
+KPX Rcaron Ograve -40
+KPX Rcaron Ohungarumlaut -40
+KPX Rcaron Omacron -40
+KPX Rcaron Oslash -40
+KPX Rcaron Otilde -40
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -18
+KPX Rcaron W -18
+KPX Rcaron Y -18
+KPX Rcaron Yacute -18
+KPX Rcaron Ydieresis -18
+KPX Rcommaaccent O -40
+KPX Rcommaaccent Oacute -40
+KPX Rcommaaccent Ocircumflex -40
+KPX Rcommaaccent Odieresis -40
+KPX Rcommaaccent Ograve -40
+KPX Rcommaaccent Ohungarumlaut -40
+KPX Rcommaaccent Omacron -40
+KPX Rcommaaccent Oslash -40
+KPX Rcommaaccent Otilde -40
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -18
+KPX Rcommaaccent W -18
+KPX Rcommaaccent Y -18
+KPX Rcommaaccent Yacute -18
+KPX Rcommaaccent Ydieresis -18
+KPX T A -50
+KPX T Aacute -50
+KPX T Abreve -50
+KPX T Acircumflex -50
+KPX T Adieresis -50
+KPX T Agrave -50
+KPX T Amacron -50
+KPX T Aogonek -50
+KPX T Aring -50
+KPX T Atilde -50
+KPX T O -18
+KPX T Oacute -18
+KPX T Ocircumflex -18
+KPX T Odieresis -18
+KPX T Ograve -18
+KPX T Ohungarumlaut -18
+KPX T Omacron -18
+KPX T Oslash -18
+KPX T Otilde -18
+KPX T a -92
+KPX T aacute -92
+KPX T abreve -92
+KPX T acircumflex -92
+KPX T adieresis -92
+KPX T agrave -92
+KPX T amacron -92
+KPX T aogonek -92
+KPX T aring -92
+KPX T atilde -92
+KPX T colon -55
+KPX T comma -74
+KPX T e -92
+KPX T eacute -92
+KPX T ecaron -92
+KPX T ecircumflex -52
+KPX T edieresis -52
+KPX T edotaccent -92
+KPX T egrave -52
+KPX T emacron -52
+KPX T eogonek -92
+KPX T hyphen -74
+KPX T i -55
+KPX T iacute -55
+KPX T iogonek -55
+KPX T o -92
+KPX T oacute -92
+KPX T ocircumflex -92
+KPX T odieresis -92
+KPX T ograve -92
+KPX T ohungarumlaut -92
+KPX T omacron -92
+KPX T oslash -92
+KPX T otilde -92
+KPX T period -74
+KPX T r -55
+KPX T racute -55
+KPX T rcaron -55
+KPX T rcommaaccent -55
+KPX T semicolon -65
+KPX T u -55
+KPX T uacute -55
+KPX T ucircumflex -55
+KPX T udieresis -55
+KPX T ugrave -55
+KPX T uhungarumlaut -55
+KPX T umacron -55
+KPX T uogonek -55
+KPX T uring -55
+KPX T w -74
+KPX T y -74
+KPX T yacute -74
+KPX T ydieresis -34
+KPX Tcaron A -50
+KPX Tcaron Aacute -50
+KPX Tcaron Abreve -50
+KPX Tcaron Acircumflex -50
+KPX Tcaron Adieresis -50
+KPX Tcaron Agrave -50
+KPX Tcaron Amacron -50
+KPX Tcaron Aogonek -50
+KPX Tcaron Aring -50
+KPX Tcaron Atilde -50
+KPX Tcaron O -18
+KPX Tcaron Oacute -18
+KPX Tcaron Ocircumflex -18
+KPX Tcaron Odieresis -18
+KPX Tcaron Ograve -18
+KPX Tcaron Ohungarumlaut -18
+KPX Tcaron Omacron -18
+KPX Tcaron Oslash -18
+KPX Tcaron Otilde -18
+KPX Tcaron a -92
+KPX Tcaron aacute -92
+KPX Tcaron abreve -92
+KPX Tcaron acircumflex -92
+KPX Tcaron adieresis -92
+KPX Tcaron agrave -92
+KPX Tcaron amacron -92
+KPX Tcaron aogonek -92
+KPX Tcaron aring -92
+KPX Tcaron atilde -92
+KPX Tcaron colon -55
+KPX Tcaron comma -74
+KPX Tcaron e -92
+KPX Tcaron eacute -92
+KPX Tcaron ecaron -92
+KPX Tcaron ecircumflex -52
+KPX Tcaron edieresis -52
+KPX Tcaron edotaccent -92
+KPX Tcaron egrave -52
+KPX Tcaron emacron -52
+KPX Tcaron eogonek -92
+KPX Tcaron hyphen -74
+KPX Tcaron i -55
+KPX Tcaron iacute -55
+KPX Tcaron iogonek -55
+KPX Tcaron o -92
+KPX Tcaron oacute -92
+KPX Tcaron ocircumflex -92
+KPX Tcaron odieresis -92
+KPX Tcaron ograve -92
+KPX Tcaron ohungarumlaut -92
+KPX Tcaron omacron -92
+KPX Tcaron oslash -92
+KPX Tcaron otilde -92
+KPX Tcaron period -74
+KPX Tcaron r -55
+KPX Tcaron racute -55
+KPX Tcaron rcaron -55
+KPX Tcaron rcommaaccent -55
+KPX Tcaron semicolon -65
+KPX Tcaron u -55
+KPX Tcaron uacute -55
+KPX Tcaron ucircumflex -55
+KPX Tcaron udieresis -55
+KPX Tcaron ugrave -55
+KPX Tcaron uhungarumlaut -55
+KPX Tcaron umacron -55
+KPX Tcaron uogonek -55
+KPX Tcaron uring -55
+KPX Tcaron w -74
+KPX Tcaron y -74
+KPX Tcaron yacute -74
+KPX Tcaron ydieresis -34
+KPX Tcommaaccent A -50
+KPX Tcommaaccent Aacute -50
+KPX Tcommaaccent Abreve -50
+KPX Tcommaaccent Acircumflex -50
+KPX Tcommaaccent Adieresis -50
+KPX Tcommaaccent Agrave -50
+KPX Tcommaaccent Amacron -50
+KPX Tcommaaccent Aogonek -50
+KPX Tcommaaccent Aring -50
+KPX Tcommaaccent Atilde -50
+KPX Tcommaaccent O -18
+KPX Tcommaaccent Oacute -18
+KPX Tcommaaccent Ocircumflex -18
+KPX Tcommaaccent Odieresis -18
+KPX Tcommaaccent Ograve -18
+KPX Tcommaaccent Ohungarumlaut -18
+KPX Tcommaaccent Omacron -18
+KPX Tcommaaccent Oslash -18
+KPX Tcommaaccent Otilde -18
+KPX Tcommaaccent a -92
+KPX Tcommaaccent aacute -92
+KPX Tcommaaccent abreve -92
+KPX Tcommaaccent acircumflex -92
+KPX Tcommaaccent adieresis -92
+KPX Tcommaaccent agrave -92
+KPX Tcommaaccent amacron -92
+KPX Tcommaaccent aogonek -92
+KPX Tcommaaccent aring -92
+KPX Tcommaaccent atilde -92
+KPX Tcommaaccent colon -55
+KPX Tcommaaccent comma -74
+KPX Tcommaaccent e -92
+KPX Tcommaaccent eacute -92
+KPX Tcommaaccent ecaron -92
+KPX Tcommaaccent ecircumflex -52
+KPX Tcommaaccent edieresis -52
+KPX Tcommaaccent edotaccent -92
+KPX Tcommaaccent egrave -52
+KPX Tcommaaccent emacron -52
+KPX Tcommaaccent eogonek -92
+KPX Tcommaaccent hyphen -74
+KPX Tcommaaccent i -55
+KPX Tcommaaccent iacute -55
+KPX Tcommaaccent iogonek -55
+KPX Tcommaaccent o -92
+KPX Tcommaaccent oacute -92
+KPX Tcommaaccent ocircumflex -92
+KPX Tcommaaccent odieresis -92
+KPX Tcommaaccent ograve -92
+KPX Tcommaaccent ohungarumlaut -92
+KPX Tcommaaccent omacron -92
+KPX Tcommaaccent oslash -92
+KPX Tcommaaccent otilde -92
+KPX Tcommaaccent period -74
+KPX Tcommaaccent r -55
+KPX Tcommaaccent racute -55
+KPX Tcommaaccent rcaron -55
+KPX Tcommaaccent rcommaaccent -55
+KPX Tcommaaccent semicolon -65
+KPX Tcommaaccent u -55
+KPX Tcommaaccent uacute -55
+KPX Tcommaaccent ucircumflex -55
+KPX Tcommaaccent udieresis -55
+KPX Tcommaaccent ugrave -55
+KPX Tcommaaccent uhungarumlaut -55
+KPX Tcommaaccent umacron -55
+KPX Tcommaaccent uogonek -55
+KPX Tcommaaccent uring -55
+KPX Tcommaaccent w -74
+KPX Tcommaaccent y -74
+KPX Tcommaaccent yacute -74
+KPX Tcommaaccent ydieresis -34
+KPX U A -40
+KPX U Aacute -40
+KPX U Abreve -40
+KPX U Acircumflex -40
+KPX U Adieresis -40
+KPX U Agrave -40
+KPX U Amacron -40
+KPX U Aogonek -40
+KPX U Aring -40
+KPX U Atilde -40
+KPX U comma -25
+KPX U period -25
+KPX Uacute A -40
+KPX Uacute Aacute -40
+KPX Uacute Abreve -40
+KPX Uacute Acircumflex -40
+KPX Uacute Adieresis -40
+KPX Uacute Agrave -40
+KPX Uacute Amacron -40
+KPX Uacute Aogonek -40
+KPX Uacute Aring -40
+KPX Uacute Atilde -40
+KPX Uacute comma -25
+KPX Uacute period -25
+KPX Ucircumflex A -40
+KPX Ucircumflex Aacute -40
+KPX Ucircumflex Abreve -40
+KPX Ucircumflex Acircumflex -40
+KPX Ucircumflex Adieresis -40
+KPX Ucircumflex Agrave -40
+KPX Ucircumflex Amacron -40
+KPX Ucircumflex Aogonek -40
+KPX Ucircumflex Aring -40
+KPX Ucircumflex Atilde -40
+KPX Ucircumflex comma -25
+KPX Ucircumflex period -25
+KPX Udieresis A -40
+KPX Udieresis Aacute -40
+KPX Udieresis Abreve -40
+KPX Udieresis Acircumflex -40
+KPX Udieresis Adieresis -40
+KPX Udieresis Agrave -40
+KPX Udieresis Amacron -40
+KPX Udieresis Aogonek -40
+KPX Udieresis Aring -40
+KPX Udieresis Atilde -40
+KPX Udieresis comma -25
+KPX Udieresis period -25
+KPX Ugrave A -40
+KPX Ugrave Aacute -40
+KPX Ugrave Abreve -40
+KPX Ugrave Acircumflex -40
+KPX Ugrave Adieresis -40
+KPX Ugrave Agrave -40
+KPX Ugrave Amacron -40
+KPX Ugrave Aogonek -40
+KPX Ugrave Aring -40
+KPX Ugrave Atilde -40
+KPX Ugrave comma -25
+KPX Ugrave period -25
+KPX Uhungarumlaut A -40
+KPX Uhungarumlaut Aacute -40
+KPX Uhungarumlaut Abreve -40
+KPX Uhungarumlaut Acircumflex -40
+KPX Uhungarumlaut Adieresis -40
+KPX Uhungarumlaut Agrave -40
+KPX Uhungarumlaut Amacron -40
+KPX Uhungarumlaut Aogonek -40
+KPX Uhungarumlaut Aring -40
+KPX Uhungarumlaut Atilde -40
+KPX Uhungarumlaut comma -25
+KPX Uhungarumlaut period -25
+KPX Umacron A -40
+KPX Umacron Aacute -40
+KPX Umacron Abreve -40
+KPX Umacron Acircumflex -40
+KPX Umacron Adieresis -40
+KPX Umacron Agrave -40
+KPX Umacron Amacron -40
+KPX Umacron Aogonek -40
+KPX Umacron Aring -40
+KPX Umacron Atilde -40
+KPX Umacron comma -25
+KPX Umacron period -25
+KPX Uogonek A -40
+KPX Uogonek Aacute -40
+KPX Uogonek Abreve -40
+KPX Uogonek Acircumflex -40
+KPX Uogonek Adieresis -40
+KPX Uogonek Agrave -40
+KPX Uogonek Amacron -40
+KPX Uogonek Aogonek -40
+KPX Uogonek Aring -40
+KPX Uogonek Atilde -40
+KPX Uogonek comma -25
+KPX Uogonek period -25
+KPX Uring A -40
+KPX Uring Aacute -40
+KPX Uring Abreve -40
+KPX Uring Acircumflex -40
+KPX Uring Adieresis -40
+KPX Uring Agrave -40
+KPX Uring Amacron -40
+KPX Uring Aogonek -40
+KPX Uring Aring -40
+KPX Uring Atilde -40
+KPX Uring comma -25
+KPX Uring period -25
+KPX V A -60
+KPX V Aacute -60
+KPX V Abreve -60
+KPX V Acircumflex -60
+KPX V Adieresis -60
+KPX V Agrave -60
+KPX V Amacron -60
+KPX V Aogonek -60
+KPX V Aring -60
+KPX V Atilde -60
+KPX V O -30
+KPX V Oacute -30
+KPX V Ocircumflex -30
+KPX V Odieresis -30
+KPX V Ograve -30
+KPX V Ohungarumlaut -30
+KPX V Omacron -30
+KPX V Oslash -30
+KPX V Otilde -30
+KPX V a -111
+KPX V aacute -111
+KPX V abreve -111
+KPX V acircumflex -111
+KPX V adieresis -111
+KPX V agrave -111
+KPX V amacron -111
+KPX V aogonek -111
+KPX V aring -111
+KPX V atilde -111
+KPX V colon -65
+KPX V comma -129
+KPX V e -111
+KPX V eacute -111
+KPX V ecaron -111
+KPX V ecircumflex -111
+KPX V edieresis -71
+KPX V edotaccent -111
+KPX V egrave -71
+KPX V emacron -71
+KPX V eogonek -111
+KPX V hyphen -55
+KPX V i -74
+KPX V iacute -74
+KPX V icircumflex -34
+KPX V idieresis -34
+KPX V igrave -34
+KPX V imacron -34
+KPX V iogonek -74
+KPX V o -111
+KPX V oacute -111
+KPX V ocircumflex -111
+KPX V odieresis -111
+KPX V ograve -111
+KPX V ohungarumlaut -111
+KPX V omacron -111
+KPX V oslash -111
+KPX V otilde -111
+KPX V period -129
+KPX V semicolon -74
+KPX V u -74
+KPX V uacute -74
+KPX V ucircumflex -74
+KPX V udieresis -74
+KPX V ugrave -74
+KPX V uhungarumlaut -74
+KPX V umacron -74
+KPX V uogonek -74
+KPX V uring -74
+KPX W A -60
+KPX W Aacute -60
+KPX W Abreve -60
+KPX W Acircumflex -60
+KPX W Adieresis -60
+KPX W Agrave -60
+KPX W Amacron -60
+KPX W Aogonek -60
+KPX W Aring -60
+KPX W Atilde -60
+KPX W O -25
+KPX W Oacute -25
+KPX W Ocircumflex -25
+KPX W Odieresis -25
+KPX W Ograve -25
+KPX W Ohungarumlaut -25
+KPX W Omacron -25
+KPX W Oslash -25
+KPX W Otilde -25
+KPX W a -92
+KPX W aacute -92
+KPX W abreve -92
+KPX W acircumflex -92
+KPX W adieresis -92
+KPX W agrave -92
+KPX W amacron -92
+KPX W aogonek -92
+KPX W aring -92
+KPX W atilde -92
+KPX W colon -65
+KPX W comma -92
+KPX W e -92
+KPX W eacute -92
+KPX W ecaron -92
+KPX W ecircumflex -92
+KPX W edieresis -52
+KPX W edotaccent -92
+KPX W egrave -52
+KPX W emacron -52
+KPX W eogonek -92
+KPX W hyphen -37
+KPX W i -55
+KPX W iacute -55
+KPX W iogonek -55
+KPX W o -92
+KPX W oacute -92
+KPX W ocircumflex -92
+KPX W odieresis -92
+KPX W ograve -92
+KPX W ohungarumlaut -92
+KPX W omacron -92
+KPX W oslash -92
+KPX W otilde -92
+KPX W period -92
+KPX W semicolon -65
+KPX W u -55
+KPX W uacute -55
+KPX W ucircumflex -55
+KPX W udieresis -55
+KPX W ugrave -55
+KPX W uhungarumlaut -55
+KPX W umacron -55
+KPX W uogonek -55
+KPX W uring -55
+KPX W y -70
+KPX W yacute -70
+KPX W ydieresis -70
+KPX Y A -50
+KPX Y Aacute -50
+KPX Y Abreve -50
+KPX Y Acircumflex -50
+KPX Y Adieresis -50
+KPX Y Agrave -50
+KPX Y Amacron -50
+KPX Y Aogonek -50
+KPX Y Aring -50
+KPX Y Atilde -50
+KPX Y O -15
+KPX Y Oacute -15
+KPX Y Ocircumflex -15
+KPX Y Odieresis -15
+KPX Y Ograve -15
+KPX Y Ohungarumlaut -15
+KPX Y Omacron -15
+KPX Y Oslash -15
+KPX Y Otilde -15
+KPX Y a -92
+KPX Y aacute -92
+KPX Y abreve -92
+KPX Y acircumflex -92
+KPX Y adieresis -92
+KPX Y agrave -92
+KPX Y amacron -92
+KPX Y aogonek -92
+KPX Y aring -92
+KPX Y atilde -92
+KPX Y colon -65
+KPX Y comma -92
+KPX Y e -92
+KPX Y eacute -92
+KPX Y ecaron -92
+KPX Y ecircumflex -92
+KPX Y edieresis -52
+KPX Y edotaccent -92
+KPX Y egrave -52
+KPX Y emacron -52
+KPX Y eogonek -92
+KPX Y hyphen -74
+KPX Y i -74
+KPX Y iacute -74
+KPX Y icircumflex -34
+KPX Y idieresis -34
+KPX Y igrave -34
+KPX Y imacron -34
+KPX Y iogonek -74
+KPX Y o -92
+KPX Y oacute -92
+KPX Y ocircumflex -92
+KPX Y odieresis -92
+KPX Y ograve -92
+KPX Y ohungarumlaut -92
+KPX Y omacron -92
+KPX Y oslash -92
+KPX Y otilde -92
+KPX Y period -92
+KPX Y semicolon -65
+KPX Y u -92
+KPX Y uacute -92
+KPX Y ucircumflex -92
+KPX Y udieresis -92
+KPX Y ugrave -92
+KPX Y uhungarumlaut -92
+KPX Y umacron -92
+KPX Y uogonek -92
+KPX Y uring -92
+KPX Yacute A -50
+KPX Yacute Aacute -50
+KPX Yacute Abreve -50
+KPX Yacute Acircumflex -50
+KPX Yacute Adieresis -50
+KPX Yacute Agrave -50
+KPX Yacute Amacron -50
+KPX Yacute Aogonek -50
+KPX Yacute Aring -50
+KPX Yacute Atilde -50
+KPX Yacute O -15
+KPX Yacute Oacute -15
+KPX Yacute Ocircumflex -15
+KPX Yacute Odieresis -15
+KPX Yacute Ograve -15
+KPX Yacute Ohungarumlaut -15
+KPX Yacute Omacron -15
+KPX Yacute Oslash -15
+KPX Yacute Otilde -15
+KPX Yacute a -92
+KPX Yacute aacute -92
+KPX Yacute abreve -92
+KPX Yacute acircumflex -92
+KPX Yacute adieresis -92
+KPX Yacute agrave -92
+KPX Yacute amacron -92
+KPX Yacute aogonek -92
+KPX Yacute aring -92
+KPX Yacute atilde -92
+KPX Yacute colon -65
+KPX Yacute comma -92
+KPX Yacute e -92
+KPX Yacute eacute -92
+KPX Yacute ecaron -92
+KPX Yacute ecircumflex -92
+KPX Yacute edieresis -52
+KPX Yacute edotaccent -92
+KPX Yacute egrave -52
+KPX Yacute emacron -52
+KPX Yacute eogonek -92
+KPX Yacute hyphen -74
+KPX Yacute i -74
+KPX Yacute iacute -74
+KPX Yacute icircumflex -34
+KPX Yacute idieresis -34
+KPX Yacute igrave -34
+KPX Yacute imacron -34
+KPX Yacute iogonek -74
+KPX Yacute o -92
+KPX Yacute oacute -92
+KPX Yacute ocircumflex -92
+KPX Yacute odieresis -92
+KPX Yacute ograve -92
+KPX Yacute ohungarumlaut -92
+KPX Yacute omacron -92
+KPX Yacute oslash -92
+KPX Yacute otilde -92
+KPX Yacute period -92
+KPX Yacute semicolon -65
+KPX Yacute u -92
+KPX Yacute uacute -92
+KPX Yacute ucircumflex -92
+KPX Yacute udieresis -92
+KPX Yacute ugrave -92
+KPX Yacute uhungarumlaut -92
+KPX Yacute umacron -92
+KPX Yacute uogonek -92
+KPX Yacute uring -92
+KPX Ydieresis A -50
+KPX Ydieresis Aacute -50
+KPX Ydieresis Abreve -50
+KPX Ydieresis Acircumflex -50
+KPX Ydieresis Adieresis -50
+KPX Ydieresis Agrave -50
+KPX Ydieresis Amacron -50
+KPX Ydieresis Aogonek -50
+KPX Ydieresis Aring -50
+KPX Ydieresis Atilde -50
+KPX Ydieresis O -15
+KPX Ydieresis Oacute -15
+KPX Ydieresis Ocircumflex -15
+KPX Ydieresis Odieresis -15
+KPX Ydieresis Ograve -15
+KPX Ydieresis Ohungarumlaut -15
+KPX Ydieresis Omacron -15
+KPX Ydieresis Oslash -15
+KPX Ydieresis Otilde -15
+KPX Ydieresis a -92
+KPX Ydieresis aacute -92
+KPX Ydieresis abreve -92
+KPX Ydieresis acircumflex -92
+KPX Ydieresis adieresis -92
+KPX Ydieresis agrave -92
+KPX Ydieresis amacron -92
+KPX Ydieresis aogonek -92
+KPX Ydieresis aring -92
+KPX Ydieresis atilde -92
+KPX Ydieresis colon -65
+KPX Ydieresis comma -92
+KPX Ydieresis e -92
+KPX Ydieresis eacute -92
+KPX Ydieresis ecaron -92
+KPX Ydieresis ecircumflex -92
+KPX Ydieresis edieresis -52
+KPX Ydieresis edotaccent -92
+KPX Ydieresis egrave -52
+KPX Ydieresis emacron -52
+KPX Ydieresis eogonek -92
+KPX Ydieresis hyphen -74
+KPX Ydieresis i -74
+KPX Ydieresis iacute -74
+KPX Ydieresis icircumflex -34
+KPX Ydieresis idieresis -34
+KPX Ydieresis igrave -34
+KPX Ydieresis imacron -34
+KPX Ydieresis iogonek -74
+KPX Ydieresis o -92
+KPX Ydieresis oacute -92
+KPX Ydieresis ocircumflex -92
+KPX Ydieresis odieresis -92
+KPX Ydieresis ograve -92
+KPX Ydieresis ohungarumlaut -92
+KPX Ydieresis omacron -92
+KPX Ydieresis oslash -92
+KPX Ydieresis otilde -92
+KPX Ydieresis period -92
+KPX Ydieresis semicolon -65
+KPX Ydieresis u -92
+KPX Ydieresis uacute -92
+KPX Ydieresis ucircumflex -92
+KPX Ydieresis udieresis -92
+KPX Ydieresis ugrave -92
+KPX Ydieresis uhungarumlaut -92
+KPX Ydieresis umacron -92
+KPX Ydieresis uogonek -92
+KPX Ydieresis uring -92
+KPX a g -10
+KPX a gbreve -10
+KPX a gcommaaccent -10
+KPX aacute g -10
+KPX aacute gbreve -10
+KPX aacute gcommaaccent -10
+KPX abreve g -10
+KPX abreve gbreve -10
+KPX abreve gcommaaccent -10
+KPX acircumflex g -10
+KPX acircumflex gbreve -10
+KPX acircumflex gcommaaccent -10
+KPX adieresis g -10
+KPX adieresis gbreve -10
+KPX adieresis gcommaaccent -10
+KPX agrave g -10
+KPX agrave gbreve -10
+KPX agrave gcommaaccent -10
+KPX amacron g -10
+KPX amacron gbreve -10
+KPX amacron gcommaaccent -10
+KPX aogonek g -10
+KPX aogonek gbreve -10
+KPX aogonek gcommaaccent -10
+KPX aring g -10
+KPX aring gbreve -10
+KPX aring gcommaaccent -10
+KPX atilde g -10
+KPX atilde gbreve -10
+KPX atilde gcommaaccent -10
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX c h -15
+KPX c k -20
+KPX c kcommaaccent -20
+KPX cacute h -15
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX ccaron h -15
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccedilla h -15
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX comma quotedblright -140
+KPX comma quoteright -140
+KPX e comma -10
+KPX e g -40
+KPX e gbreve -40
+KPX e gcommaaccent -40
+KPX e period -15
+KPX e v -15
+KPX e w -15
+KPX e x -20
+KPX e y -30
+KPX e yacute -30
+KPX e ydieresis -30
+KPX eacute comma -10
+KPX eacute g -40
+KPX eacute gbreve -40
+KPX eacute gcommaaccent -40
+KPX eacute period -15
+KPX eacute v -15
+KPX eacute w -15
+KPX eacute x -20
+KPX eacute y -30
+KPX eacute yacute -30
+KPX eacute ydieresis -30
+KPX ecaron comma -10
+KPX ecaron g -40
+KPX ecaron gbreve -40
+KPX ecaron gcommaaccent -40
+KPX ecaron period -15
+KPX ecaron v -15
+KPX ecaron w -15
+KPX ecaron x -20
+KPX ecaron y -30
+KPX ecaron yacute -30
+KPX ecaron ydieresis -30
+KPX ecircumflex comma -10
+KPX ecircumflex g -40
+KPX ecircumflex gbreve -40
+KPX ecircumflex gcommaaccent -40
+KPX ecircumflex period -15
+KPX ecircumflex v -15
+KPX ecircumflex w -15
+KPX ecircumflex x -20
+KPX ecircumflex y -30
+KPX ecircumflex yacute -30
+KPX ecircumflex ydieresis -30
+KPX edieresis comma -10
+KPX edieresis g -40
+KPX edieresis gbreve -40
+KPX edieresis gcommaaccent -40
+KPX edieresis period -15
+KPX edieresis v -15
+KPX edieresis w -15
+KPX edieresis x -20
+KPX edieresis y -30
+KPX edieresis yacute -30
+KPX edieresis ydieresis -30
+KPX edotaccent comma -10
+KPX edotaccent g -40
+KPX edotaccent gbreve -40
+KPX edotaccent gcommaaccent -40
+KPX edotaccent period -15
+KPX edotaccent v -15
+KPX edotaccent w -15
+KPX edotaccent x -20
+KPX edotaccent y -30
+KPX edotaccent yacute -30
+KPX edotaccent ydieresis -30
+KPX egrave comma -10
+KPX egrave g -40
+KPX egrave gbreve -40
+KPX egrave gcommaaccent -40
+KPX egrave period -15
+KPX egrave v -15
+KPX egrave w -15
+KPX egrave x -20
+KPX egrave y -30
+KPX egrave yacute -30
+KPX egrave ydieresis -30
+KPX emacron comma -10
+KPX emacron g -40
+KPX emacron gbreve -40
+KPX emacron gcommaaccent -40
+KPX emacron period -15
+KPX emacron v -15
+KPX emacron w -15
+KPX emacron x -20
+KPX emacron y -30
+KPX emacron yacute -30
+KPX emacron ydieresis -30
+KPX eogonek comma -10
+KPX eogonek g -40
+KPX eogonek gbreve -40
+KPX eogonek gcommaaccent -40
+KPX eogonek period -15
+KPX eogonek v -15
+KPX eogonek w -15
+KPX eogonek x -20
+KPX eogonek y -30
+KPX eogonek yacute -30
+KPX eogonek ydieresis -30
+KPX f comma -10
+KPX f dotlessi -60
+KPX f f -18
+KPX f i -20
+KPX f iogonek -20
+KPX f period -15
+KPX f quoteright 92
+KPX g comma -10
+KPX g e -10
+KPX g eacute -10
+KPX g ecaron -10
+KPX g ecircumflex -10
+KPX g edieresis -10
+KPX g edotaccent -10
+KPX g egrave -10
+KPX g emacron -10
+KPX g eogonek -10
+KPX g g -10
+KPX g gbreve -10
+KPX g gcommaaccent -10
+KPX g period -15
+KPX gbreve comma -10
+KPX gbreve e -10
+KPX gbreve eacute -10
+KPX gbreve ecaron -10
+KPX gbreve ecircumflex -10
+KPX gbreve edieresis -10
+KPX gbreve edotaccent -10
+KPX gbreve egrave -10
+KPX gbreve emacron -10
+KPX gbreve eogonek -10
+KPX gbreve g -10
+KPX gbreve gbreve -10
+KPX gbreve gcommaaccent -10
+KPX gbreve period -15
+KPX gcommaaccent comma -10
+KPX gcommaaccent e -10
+KPX gcommaaccent eacute -10
+KPX gcommaaccent ecaron -10
+KPX gcommaaccent ecircumflex -10
+KPX gcommaaccent edieresis -10
+KPX gcommaaccent edotaccent -10
+KPX gcommaaccent egrave -10
+KPX gcommaaccent emacron -10
+KPX gcommaaccent eogonek -10
+KPX gcommaaccent g -10
+KPX gcommaaccent gbreve -10
+KPX gcommaaccent gcommaaccent -10
+KPX gcommaaccent period -15
+KPX k e -10
+KPX k eacute -10
+KPX k ecaron -10
+KPX k ecircumflex -10
+KPX k edieresis -10
+KPX k edotaccent -10
+KPX k egrave -10
+KPX k emacron -10
+KPX k eogonek -10
+KPX k o -10
+KPX k oacute -10
+KPX k ocircumflex -10
+KPX k odieresis -10
+KPX k ograve -10
+KPX k ohungarumlaut -10
+KPX k omacron -10
+KPX k oslash -10
+KPX k otilde -10
+KPX k y -10
+KPX k yacute -10
+KPX k ydieresis -10
+KPX kcommaaccent e -10
+KPX kcommaaccent eacute -10
+KPX kcommaaccent ecaron -10
+KPX kcommaaccent ecircumflex -10
+KPX kcommaaccent edieresis -10
+KPX kcommaaccent edotaccent -10
+KPX kcommaaccent egrave -10
+KPX kcommaaccent emacron -10
+KPX kcommaaccent eogonek -10
+KPX kcommaaccent o -10
+KPX kcommaaccent oacute -10
+KPX kcommaaccent ocircumflex -10
+KPX kcommaaccent odieresis -10
+KPX kcommaaccent ograve -10
+KPX kcommaaccent ohungarumlaut -10
+KPX kcommaaccent omacron -10
+KPX kcommaaccent oslash -10
+KPX kcommaaccent otilde -10
+KPX kcommaaccent y -10
+KPX kcommaaccent yacute -10
+KPX kcommaaccent ydieresis -10
+KPX n v -40
+KPX nacute v -40
+KPX ncaron v -40
+KPX ncommaaccent v -40
+KPX ntilde v -40
+KPX o g -10
+KPX o gbreve -10
+KPX o gcommaaccent -10
+KPX o v -10
+KPX oacute g -10
+KPX oacute gbreve -10
+KPX oacute gcommaaccent -10
+KPX oacute v -10
+KPX ocircumflex g -10
+KPX ocircumflex gbreve -10
+KPX ocircumflex gcommaaccent -10
+KPX ocircumflex v -10
+KPX odieresis g -10
+KPX odieresis gbreve -10
+KPX odieresis gcommaaccent -10
+KPX odieresis v -10
+KPX ograve g -10
+KPX ograve gbreve -10
+KPX ograve gcommaaccent -10
+KPX ograve v -10
+KPX ohungarumlaut g -10
+KPX ohungarumlaut gbreve -10
+KPX ohungarumlaut gcommaaccent -10
+KPX ohungarumlaut v -10
+KPX omacron g -10
+KPX omacron gbreve -10
+KPX omacron gcommaaccent -10
+KPX omacron v -10
+KPX oslash g -10
+KPX oslash gbreve -10
+KPX oslash gcommaaccent -10
+KPX oslash v -10
+KPX otilde g -10
+KPX otilde gbreve -10
+KPX otilde gcommaaccent -10
+KPX otilde v -10
+KPX period quotedblright -140
+KPX period quoteright -140
+KPX quoteleft quoteleft -111
+KPX quoteright d -25
+KPX quoteright dcroat -25
+KPX quoteright quoteright -111
+KPX quoteright r -25
+KPX quoteright racute -25
+KPX quoteright rcaron -25
+KPX quoteright rcommaaccent -25
+KPX quoteright s -40
+KPX quoteright sacute -40
+KPX quoteright scaron -40
+KPX quoteright scedilla -40
+KPX quoteright scommaaccent -40
+KPX quoteright space -111
+KPX quoteright t -30
+KPX quoteright tcommaaccent -30
+KPX quoteright v -10
+KPX r a -15
+KPX r aacute -15
+KPX r abreve -15
+KPX r acircumflex -15
+KPX r adieresis -15
+KPX r agrave -15
+KPX r amacron -15
+KPX r aogonek -15
+KPX r aring -15
+KPX r atilde -15
+KPX r c -37
+KPX r cacute -37
+KPX r ccaron -37
+KPX r ccedilla -37
+KPX r comma -111
+KPX r d -37
+KPX r dcroat -37
+KPX r e -37
+KPX r eacute -37
+KPX r ecaron -37
+KPX r ecircumflex -37
+KPX r edieresis -37
+KPX r edotaccent -37
+KPX r egrave -37
+KPX r emacron -37
+KPX r eogonek -37
+KPX r g -37
+KPX r gbreve -37
+KPX r gcommaaccent -37
+KPX r hyphen -20
+KPX r o -45
+KPX r oacute -45
+KPX r ocircumflex -45
+KPX r odieresis -45
+KPX r ograve -45
+KPX r ohungarumlaut -45
+KPX r omacron -45
+KPX r oslash -45
+KPX r otilde -45
+KPX r period -111
+KPX r q -37
+KPX r s -10
+KPX r sacute -10
+KPX r scaron -10
+KPX r scedilla -10
+KPX r scommaaccent -10
+KPX racute a -15
+KPX racute aacute -15
+KPX racute abreve -15
+KPX racute acircumflex -15
+KPX racute adieresis -15
+KPX racute agrave -15
+KPX racute amacron -15
+KPX racute aogonek -15
+KPX racute aring -15
+KPX racute atilde -15
+KPX racute c -37
+KPX racute cacute -37
+KPX racute ccaron -37
+KPX racute ccedilla -37
+KPX racute comma -111
+KPX racute d -37
+KPX racute dcroat -37
+KPX racute e -37
+KPX racute eacute -37
+KPX racute ecaron -37
+KPX racute ecircumflex -37
+KPX racute edieresis -37
+KPX racute edotaccent -37
+KPX racute egrave -37
+KPX racute emacron -37
+KPX racute eogonek -37
+KPX racute g -37
+KPX racute gbreve -37
+KPX racute gcommaaccent -37
+KPX racute hyphen -20
+KPX racute o -45
+KPX racute oacute -45
+KPX racute ocircumflex -45
+KPX racute odieresis -45
+KPX racute ograve -45
+KPX racute ohungarumlaut -45
+KPX racute omacron -45
+KPX racute oslash -45
+KPX racute otilde -45
+KPX racute period -111
+KPX racute q -37
+KPX racute s -10
+KPX racute sacute -10
+KPX racute scaron -10
+KPX racute scedilla -10
+KPX racute scommaaccent -10
+KPX rcaron a -15
+KPX rcaron aacute -15
+KPX rcaron abreve -15
+KPX rcaron acircumflex -15
+KPX rcaron adieresis -15
+KPX rcaron agrave -15
+KPX rcaron amacron -15
+KPX rcaron aogonek -15
+KPX rcaron aring -15
+KPX rcaron atilde -15
+KPX rcaron c -37
+KPX rcaron cacute -37
+KPX rcaron ccaron -37
+KPX rcaron ccedilla -37
+KPX rcaron comma -111
+KPX rcaron d -37
+KPX rcaron dcroat -37
+KPX rcaron e -37
+KPX rcaron eacute -37
+KPX rcaron ecaron -37
+KPX rcaron ecircumflex -37
+KPX rcaron edieresis -37
+KPX rcaron edotaccent -37
+KPX rcaron egrave -37
+KPX rcaron emacron -37
+KPX rcaron eogonek -37
+KPX rcaron g -37
+KPX rcaron gbreve -37
+KPX rcaron gcommaaccent -37
+KPX rcaron hyphen -20
+KPX rcaron o -45
+KPX rcaron oacute -45
+KPX rcaron ocircumflex -45
+KPX rcaron odieresis -45
+KPX rcaron ograve -45
+KPX rcaron ohungarumlaut -45
+KPX rcaron omacron -45
+KPX rcaron oslash -45
+KPX rcaron otilde -45
+KPX rcaron period -111
+KPX rcaron q -37
+KPX rcaron s -10
+KPX rcaron sacute -10
+KPX rcaron scaron -10
+KPX rcaron scedilla -10
+KPX rcaron scommaaccent -10
+KPX rcommaaccent a -15
+KPX rcommaaccent aacute -15
+KPX rcommaaccent abreve -15
+KPX rcommaaccent acircumflex -15
+KPX rcommaaccent adieresis -15
+KPX rcommaaccent agrave -15
+KPX rcommaaccent amacron -15
+KPX rcommaaccent aogonek -15
+KPX rcommaaccent aring -15
+KPX rcommaaccent atilde -15
+KPX rcommaaccent c -37
+KPX rcommaaccent cacute -37
+KPX rcommaaccent ccaron -37
+KPX rcommaaccent ccedilla -37
+KPX rcommaaccent comma -111
+KPX rcommaaccent d -37
+KPX rcommaaccent dcroat -37
+KPX rcommaaccent e -37
+KPX rcommaaccent eacute -37
+KPX rcommaaccent ecaron -37
+KPX rcommaaccent ecircumflex -37
+KPX rcommaaccent edieresis -37
+KPX rcommaaccent edotaccent -37
+KPX rcommaaccent egrave -37
+KPX rcommaaccent emacron -37
+KPX rcommaaccent eogonek -37
+KPX rcommaaccent g -37
+KPX rcommaaccent gbreve -37
+KPX rcommaaccent gcommaaccent -37
+KPX rcommaaccent hyphen -20
+KPX rcommaaccent o -45
+KPX rcommaaccent oacute -45
+KPX rcommaaccent ocircumflex -45
+KPX rcommaaccent odieresis -45
+KPX rcommaaccent ograve -45
+KPX rcommaaccent ohungarumlaut -45
+KPX rcommaaccent omacron -45
+KPX rcommaaccent oslash -45
+KPX rcommaaccent otilde -45
+KPX rcommaaccent period -111
+KPX rcommaaccent q -37
+KPX rcommaaccent s -10
+KPX rcommaaccent sacute -10
+KPX rcommaaccent scaron -10
+KPX rcommaaccent scedilla -10
+KPX rcommaaccent scommaaccent -10
+KPX space A -18
+KPX space Aacute -18
+KPX space Abreve -18
+KPX space Acircumflex -18
+KPX space Adieresis -18
+KPX space Agrave -18
+KPX space Amacron -18
+KPX space Aogonek -18
+KPX space Aring -18
+KPX space Atilde -18
+KPX space T -18
+KPX space Tcaron -18
+KPX space Tcommaaccent -18
+KPX space V -35
+KPX space W -40
+KPX space Y -75
+KPX space Yacute -75
+KPX space Ydieresis -75
+KPX v comma -74
+KPX v period -74
+KPX w comma -74
+KPX w period -74
+KPX y comma -55
+KPX y period -55
+KPX yacute comma -55
+KPX yacute period -55
+KPX ydieresis comma -55
+KPX ydieresis period -55
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-Roman.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-Roman.afm
new file mode 100644
index 0000000000000000000000000000000000000000..a0953f2802cbde4ca95c212f21195b55c0bc5a42
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/Times-Roman.afm
@@ -0,0 +1,2419 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:49:17 1997
+Comment UniqueID 43068
+Comment VMusage 43909 54934
+FontName Times-Roman
+FullName Times Roman
+FamilyName Times
+Weight Roman
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -168 -218 1000 898
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme AdobeStandardEncoding
+CapHeight 662
+XHeight 450
+Ascender 683
+Descender -217
+StdHW 28
+StdVW 84
+StartCharMetrics 315
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 130 -9 238 676 ;
+C 34 ; WX 408 ; N quotedbl ; B 77 431 331 676 ;
+C 35 ; WX 500 ; N numbersign ; B 5 0 496 662 ;
+C 36 ; WX 500 ; N dollar ; B 44 -87 457 727 ;
+C 37 ; WX 833 ; N percent ; B 61 -13 772 676 ;
+C 38 ; WX 778 ; N ampersand ; B 42 -13 750 676 ;
+C 39 ; WX 333 ; N quoteright ; B 79 433 218 676 ;
+C 40 ; WX 333 ; N parenleft ; B 48 -177 304 676 ;
+C 41 ; WX 333 ; N parenright ; B 29 -177 285 676 ;
+C 42 ; WX 500 ; N asterisk ; B 69 265 432 676 ;
+C 43 ; WX 564 ; N plus ; B 30 0 534 506 ;
+C 44 ; WX 250 ; N comma ; B 56 -141 195 102 ;
+C 45 ; WX 333 ; N hyphen ; B 39 194 285 257 ;
+C 46 ; WX 250 ; N period ; B 70 -11 181 100 ;
+C 47 ; WX 278 ; N slash ; B -9 -14 287 676 ;
+C 48 ; WX 500 ; N zero ; B 24 -14 476 676 ;
+C 49 ; WX 500 ; N one ; B 111 0 394 676 ;
+C 50 ; WX 500 ; N two ; B 30 0 475 676 ;
+C 51 ; WX 500 ; N three ; B 43 -14 431 676 ;
+C 52 ; WX 500 ; N four ; B 12 0 472 676 ;
+C 53 ; WX 500 ; N five ; B 32 -14 438 688 ;
+C 54 ; WX 500 ; N six ; B 34 -14 468 684 ;
+C 55 ; WX 500 ; N seven ; B 20 -8 449 662 ;
+C 56 ; WX 500 ; N eight ; B 56 -14 445 676 ;
+C 57 ; WX 500 ; N nine ; B 30 -22 459 676 ;
+C 58 ; WX 278 ; N colon ; B 81 -11 192 459 ;
+C 59 ; WX 278 ; N semicolon ; B 80 -141 219 459 ;
+C 60 ; WX 564 ; N less ; B 28 -8 536 514 ;
+C 61 ; WX 564 ; N equal ; B 30 120 534 386 ;
+C 62 ; WX 564 ; N greater ; B 28 -8 536 514 ;
+C 63 ; WX 444 ; N question ; B 68 -8 414 676 ;
+C 64 ; WX 921 ; N at ; B 116 -14 809 676 ;
+C 65 ; WX 722 ; N A ; B 15 0 706 674 ;
+C 66 ; WX 667 ; N B ; B 17 0 593 662 ;
+C 67 ; WX 667 ; N C ; B 28 -14 633 676 ;
+C 68 ; WX 722 ; N D ; B 16 0 685 662 ;
+C 69 ; WX 611 ; N E ; B 12 0 597 662 ;
+C 70 ; WX 556 ; N F ; B 12 0 546 662 ;
+C 71 ; WX 722 ; N G ; B 32 -14 709 676 ;
+C 72 ; WX 722 ; N H ; B 19 0 702 662 ;
+C 73 ; WX 333 ; N I ; B 18 0 315 662 ;
+C 74 ; WX 389 ; N J ; B 10 -14 370 662 ;
+C 75 ; WX 722 ; N K ; B 34 0 723 662 ;
+C 76 ; WX 611 ; N L ; B 12 0 598 662 ;
+C 77 ; WX 889 ; N M ; B 12 0 863 662 ;
+C 78 ; WX 722 ; N N ; B 12 -11 707 662 ;
+C 79 ; WX 722 ; N O ; B 34 -14 688 676 ;
+C 80 ; WX 556 ; N P ; B 16 0 542 662 ;
+C 81 ; WX 722 ; N Q ; B 34 -178 701 676 ;
+C 82 ; WX 667 ; N R ; B 17 0 659 662 ;
+C 83 ; WX 556 ; N S ; B 42 -14 491 676 ;
+C 84 ; WX 611 ; N T ; B 17 0 593 662 ;
+C 85 ; WX 722 ; N U ; B 14 -14 705 662 ;
+C 86 ; WX 722 ; N V ; B 16 -11 697 662 ;
+C 87 ; WX 944 ; N W ; B 5 -11 932 662 ;
+C 88 ; WX 722 ; N X ; B 10 0 704 662 ;
+C 89 ; WX 722 ; N Y ; B 22 0 703 662 ;
+C 90 ; WX 611 ; N Z ; B 9 0 597 662 ;
+C 91 ; WX 333 ; N bracketleft ; B 88 -156 299 662 ;
+C 92 ; WX 278 ; N backslash ; B -9 -14 287 676 ;
+C 93 ; WX 333 ; N bracketright ; B 34 -156 245 662 ;
+C 94 ; WX 469 ; N asciicircum ; B 24 297 446 662 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 333 ; N quoteleft ; B 115 433 254 676 ;
+C 97 ; WX 444 ; N a ; B 37 -10 442 460 ;
+C 98 ; WX 500 ; N b ; B 3 -10 468 683 ;
+C 99 ; WX 444 ; N c ; B 25 -10 412 460 ;
+C 100 ; WX 500 ; N d ; B 27 -10 491 683 ;
+C 101 ; WX 444 ; N e ; B 25 -10 424 460 ;
+C 102 ; WX 333 ; N f ; B 20 0 383 683 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B 28 -218 470 460 ;
+C 104 ; WX 500 ; N h ; B 9 0 487 683 ;
+C 105 ; WX 278 ; N i ; B 16 0 253 683 ;
+C 106 ; WX 278 ; N j ; B -70 -218 194 683 ;
+C 107 ; WX 500 ; N k ; B 7 0 505 683 ;
+C 108 ; WX 278 ; N l ; B 19 0 257 683 ;
+C 109 ; WX 778 ; N m ; B 16 0 775 460 ;
+C 110 ; WX 500 ; N n ; B 16 0 485 460 ;
+C 111 ; WX 500 ; N o ; B 29 -10 470 460 ;
+C 112 ; WX 500 ; N p ; B 5 -217 470 460 ;
+C 113 ; WX 500 ; N q ; B 24 -217 488 460 ;
+C 114 ; WX 333 ; N r ; B 5 0 335 460 ;
+C 115 ; WX 389 ; N s ; B 51 -10 348 460 ;
+C 116 ; WX 278 ; N t ; B 13 -10 279 579 ;
+C 117 ; WX 500 ; N u ; B 9 -10 479 450 ;
+C 118 ; WX 500 ; N v ; B 19 -14 477 450 ;
+C 119 ; WX 722 ; N w ; B 21 -14 694 450 ;
+C 120 ; WX 500 ; N x ; B 17 0 479 450 ;
+C 121 ; WX 500 ; N y ; B 14 -218 475 450 ;
+C 122 ; WX 444 ; N z ; B 27 0 418 450 ;
+C 123 ; WX 480 ; N braceleft ; B 100 -181 350 680 ;
+C 124 ; WX 200 ; N bar ; B 67 -218 133 782 ;
+C 125 ; WX 480 ; N braceright ; B 130 -181 380 680 ;
+C 126 ; WX 541 ; N asciitilde ; B 40 183 502 323 ;
+C 161 ; WX 333 ; N exclamdown ; B 97 -218 205 467 ;
+C 162 ; WX 500 ; N cent ; B 53 -138 448 579 ;
+C 163 ; WX 500 ; N sterling ; B 12 -8 490 676 ;
+C 164 ; WX 167 ; N fraction ; B -168 -14 331 676 ;
+C 165 ; WX 500 ; N yen ; B -53 0 512 662 ;
+C 166 ; WX 500 ; N florin ; B 7 -189 490 676 ;
+C 167 ; WX 500 ; N section ; B 70 -148 426 676 ;
+C 168 ; WX 500 ; N currency ; B -22 58 522 602 ;
+C 169 ; WX 180 ; N quotesingle ; B 48 431 133 676 ;
+C 170 ; WX 444 ; N quotedblleft ; B 43 433 414 676 ;
+C 171 ; WX 500 ; N guillemotleft ; B 42 33 456 416 ;
+C 172 ; WX 333 ; N guilsinglleft ; B 63 33 285 416 ;
+C 173 ; WX 333 ; N guilsinglright ; B 48 33 270 416 ;
+C 174 ; WX 556 ; N fi ; B 31 0 521 683 ;
+C 175 ; WX 556 ; N fl ; B 32 0 521 683 ;
+C 177 ; WX 500 ; N endash ; B 0 201 500 250 ;
+C 178 ; WX 500 ; N dagger ; B 59 -149 442 676 ;
+C 179 ; WX 500 ; N daggerdbl ; B 58 -153 442 676 ;
+C 180 ; WX 250 ; N periodcentered ; B 70 199 181 310 ;
+C 182 ; WX 453 ; N paragraph ; B -22 -154 450 662 ;
+C 183 ; WX 350 ; N bullet ; B 40 196 310 466 ;
+C 184 ; WX 333 ; N quotesinglbase ; B 79 -141 218 102 ;
+C 185 ; WX 444 ; N quotedblbase ; B 45 -141 416 102 ;
+C 186 ; WX 444 ; N quotedblright ; B 30 433 401 676 ;
+C 187 ; WX 500 ; N guillemotright ; B 44 33 458 416 ;
+C 188 ; WX 1000 ; N ellipsis ; B 111 -11 888 100 ;
+C 189 ; WX 1000 ; N perthousand ; B 7 -19 994 706 ;
+C 191 ; WX 444 ; N questiondown ; B 30 -218 376 466 ;
+C 193 ; WX 333 ; N grave ; B 19 507 242 678 ;
+C 194 ; WX 333 ; N acute ; B 93 507 317 678 ;
+C 195 ; WX 333 ; N circumflex ; B 11 507 322 674 ;
+C 196 ; WX 333 ; N tilde ; B 1 532 331 638 ;
+C 197 ; WX 333 ; N macron ; B 11 547 322 601 ;
+C 198 ; WX 333 ; N breve ; B 26 507 307 664 ;
+C 199 ; WX 333 ; N dotaccent ; B 118 581 216 681 ;
+C 200 ; WX 333 ; N dieresis ; B 18 581 315 681 ;
+C 202 ; WX 333 ; N ring ; B 67 512 266 711 ;
+C 203 ; WX 333 ; N cedilla ; B 52 -215 261 0 ;
+C 205 ; WX 333 ; N hungarumlaut ; B -3 507 377 678 ;
+C 206 ; WX 333 ; N ogonek ; B 62 -165 243 0 ;
+C 207 ; WX 333 ; N caron ; B 11 507 322 674 ;
+C 208 ; WX 1000 ; N emdash ; B 0 201 1000 250 ;
+C 225 ; WX 889 ; N AE ; B 0 0 863 662 ;
+C 227 ; WX 276 ; N ordfeminine ; B 4 394 270 676 ;
+C 232 ; WX 611 ; N Lslash ; B 12 0 598 662 ;
+C 233 ; WX 722 ; N Oslash ; B 34 -80 688 734 ;
+C 234 ; WX 889 ; N OE ; B 30 -6 885 668 ;
+C 235 ; WX 310 ; N ordmasculine ; B 6 394 304 676 ;
+C 241 ; WX 667 ; N ae ; B 38 -10 632 460 ;
+C 245 ; WX 278 ; N dotlessi ; B 16 0 253 460 ;
+C 248 ; WX 278 ; N lslash ; B 19 0 259 683 ;
+C 249 ; WX 500 ; N oslash ; B 29 -112 470 551 ;
+C 250 ; WX 722 ; N oe ; B 30 -10 690 460 ;
+C 251 ; WX 500 ; N germandbls ; B 12 -9 468 683 ;
+C -1 ; WX 333 ; N Idieresis ; B 18 0 315 835 ;
+C -1 ; WX 444 ; N eacute ; B 25 -10 424 678 ;
+C -1 ; WX 444 ; N abreve ; B 37 -10 442 664 ;
+C -1 ; WX 500 ; N uhungarumlaut ; B 9 -10 501 678 ;
+C -1 ; WX 444 ; N ecaron ; B 25 -10 424 674 ;
+C -1 ; WX 722 ; N Ydieresis ; B 22 0 703 835 ;
+C -1 ; WX 564 ; N divide ; B 30 -10 534 516 ;
+C -1 ; WX 722 ; N Yacute ; B 22 0 703 890 ;
+C -1 ; WX 722 ; N Acircumflex ; B 15 0 706 886 ;
+C -1 ; WX 444 ; N aacute ; B 37 -10 442 678 ;
+C -1 ; WX 722 ; N Ucircumflex ; B 14 -14 705 886 ;
+C -1 ; WX 500 ; N yacute ; B 14 -218 475 678 ;
+C -1 ; WX 389 ; N scommaaccent ; B 51 -218 348 460 ;
+C -1 ; WX 444 ; N ecircumflex ; B 25 -10 424 674 ;
+C -1 ; WX 722 ; N Uring ; B 14 -14 705 898 ;
+C -1 ; WX 722 ; N Udieresis ; B 14 -14 705 835 ;
+C -1 ; WX 444 ; N aogonek ; B 37 -165 469 460 ;
+C -1 ; WX 722 ; N Uacute ; B 14 -14 705 890 ;
+C -1 ; WX 500 ; N uogonek ; B 9 -155 487 450 ;
+C -1 ; WX 611 ; N Edieresis ; B 12 0 597 835 ;
+C -1 ; WX 722 ; N Dcroat ; B 16 0 685 662 ;
+C -1 ; WX 250 ; N commaaccent ; B 59 -218 184 -50 ;
+C -1 ; WX 760 ; N copyright ; B 38 -14 722 676 ;
+C -1 ; WX 611 ; N Emacron ; B 12 0 597 813 ;
+C -1 ; WX 444 ; N ccaron ; B 25 -10 412 674 ;
+C -1 ; WX 444 ; N aring ; B 37 -10 442 711 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 12 -198 707 662 ;
+C -1 ; WX 278 ; N lacute ; B 19 0 290 890 ;
+C -1 ; WX 444 ; N agrave ; B 37 -10 442 678 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 17 -218 593 662 ;
+C -1 ; WX 667 ; N Cacute ; B 28 -14 633 890 ;
+C -1 ; WX 444 ; N atilde ; B 37 -10 442 638 ;
+C -1 ; WX 611 ; N Edotaccent ; B 12 0 597 835 ;
+C -1 ; WX 389 ; N scaron ; B 39 -10 350 674 ;
+C -1 ; WX 389 ; N scedilla ; B 51 -215 348 460 ;
+C -1 ; WX 278 ; N iacute ; B 16 0 290 678 ;
+C -1 ; WX 471 ; N lozenge ; B 13 0 459 724 ;
+C -1 ; WX 667 ; N Rcaron ; B 17 0 659 886 ;
+C -1 ; WX 722 ; N Gcommaaccent ; B 32 -218 709 676 ;
+C -1 ; WX 500 ; N ucircumflex ; B 9 -10 479 674 ;
+C -1 ; WX 444 ; N acircumflex ; B 37 -10 442 674 ;
+C -1 ; WX 722 ; N Amacron ; B 15 0 706 813 ;
+C -1 ; WX 333 ; N rcaron ; B 5 0 335 674 ;
+C -1 ; WX 444 ; N ccedilla ; B 25 -215 412 460 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 9 0 597 835 ;
+C -1 ; WX 556 ; N Thorn ; B 16 0 542 662 ;
+C -1 ; WX 722 ; N Omacron ; B 34 -14 688 813 ;
+C -1 ; WX 667 ; N Racute ; B 17 0 659 890 ;
+C -1 ; WX 556 ; N Sacute ; B 42 -14 491 890 ;
+C -1 ; WX 588 ; N dcaron ; B 27 -10 589 695 ;
+C -1 ; WX 722 ; N Umacron ; B 14 -14 705 813 ;
+C -1 ; WX 500 ; N uring ; B 9 -10 479 711 ;
+C -1 ; WX 300 ; N threesuperior ; B 15 262 291 676 ;
+C -1 ; WX 722 ; N Ograve ; B 34 -14 688 890 ;
+C -1 ; WX 722 ; N Agrave ; B 15 0 706 890 ;
+C -1 ; WX 722 ; N Abreve ; B 15 0 706 876 ;
+C -1 ; WX 564 ; N multiply ; B 38 8 527 497 ;
+C -1 ; WX 500 ; N uacute ; B 9 -10 479 678 ;
+C -1 ; WX 611 ; N Tcaron ; B 17 0 593 886 ;
+C -1 ; WX 476 ; N partialdiff ; B 17 -38 459 710 ;
+C -1 ; WX 500 ; N ydieresis ; B 14 -218 475 623 ;
+C -1 ; WX 722 ; N Nacute ; B 12 -11 707 890 ;
+C -1 ; WX 278 ; N icircumflex ; B -16 0 295 674 ;
+C -1 ; WX 611 ; N Ecircumflex ; B 12 0 597 886 ;
+C -1 ; WX 444 ; N adieresis ; B 37 -10 442 623 ;
+C -1 ; WX 444 ; N edieresis ; B 25 -10 424 623 ;
+C -1 ; WX 444 ; N cacute ; B 25 -10 413 678 ;
+C -1 ; WX 500 ; N nacute ; B 16 0 485 678 ;
+C -1 ; WX 500 ; N umacron ; B 9 -10 479 601 ;
+C -1 ; WX 722 ; N Ncaron ; B 12 -11 707 886 ;
+C -1 ; WX 333 ; N Iacute ; B 18 0 317 890 ;
+C -1 ; WX 564 ; N plusminus ; B 30 0 534 506 ;
+C -1 ; WX 200 ; N brokenbar ; B 67 -143 133 707 ;
+C -1 ; WX 760 ; N registered ; B 38 -14 722 676 ;
+C -1 ; WX 722 ; N Gbreve ; B 32 -14 709 876 ;
+C -1 ; WX 333 ; N Idotaccent ; B 18 0 315 835 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 585 706 ;
+C -1 ; WX 611 ; N Egrave ; B 12 0 597 890 ;
+C -1 ; WX 333 ; N racute ; B 5 0 335 678 ;
+C -1 ; WX 500 ; N omacron ; B 29 -10 470 601 ;
+C -1 ; WX 611 ; N Zacute ; B 9 0 597 890 ;
+C -1 ; WX 611 ; N Zcaron ; B 9 0 597 886 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 666 ;
+C -1 ; WX 722 ; N Eth ; B 16 0 685 662 ;
+C -1 ; WX 667 ; N Ccedilla ; B 28 -215 633 676 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 19 -218 257 683 ;
+C -1 ; WX 326 ; N tcaron ; B 13 -10 318 722 ;
+C -1 ; WX 444 ; N eogonek ; B 25 -165 424 460 ;
+C -1 ; WX 722 ; N Uogonek ; B 14 -165 705 662 ;
+C -1 ; WX 722 ; N Aacute ; B 15 0 706 890 ;
+C -1 ; WX 722 ; N Adieresis ; B 15 0 706 835 ;
+C -1 ; WX 444 ; N egrave ; B 25 -10 424 678 ;
+C -1 ; WX 444 ; N zacute ; B 27 0 418 678 ;
+C -1 ; WX 278 ; N iogonek ; B 16 -165 265 683 ;
+C -1 ; WX 722 ; N Oacute ; B 34 -14 688 890 ;
+C -1 ; WX 500 ; N oacute ; B 29 -10 470 678 ;
+C -1 ; WX 444 ; N amacron ; B 37 -10 442 601 ;
+C -1 ; WX 389 ; N sacute ; B 51 -10 348 678 ;
+C -1 ; WX 278 ; N idieresis ; B -9 0 288 623 ;
+C -1 ; WX 722 ; N Ocircumflex ; B 34 -14 688 886 ;
+C -1 ; WX 722 ; N Ugrave ; B 14 -14 705 890 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C -1 ; WX 500 ; N thorn ; B 5 -217 470 683 ;
+C -1 ; WX 300 ; N twosuperior ; B 1 270 296 676 ;
+C -1 ; WX 722 ; N Odieresis ; B 34 -14 688 835 ;
+C -1 ; WX 500 ; N mu ; B 36 -218 512 450 ;
+C -1 ; WX 278 ; N igrave ; B -8 0 253 678 ;
+C -1 ; WX 500 ; N ohungarumlaut ; B 29 -10 491 678 ;
+C -1 ; WX 611 ; N Eogonek ; B 12 -165 597 662 ;
+C -1 ; WX 500 ; N dcroat ; B 27 -10 500 683 ;
+C -1 ; WX 750 ; N threequarters ; B 15 -14 718 676 ;
+C -1 ; WX 556 ; N Scedilla ; B 42 -215 491 676 ;
+C -1 ; WX 344 ; N lcaron ; B 19 0 347 695 ;
+C -1 ; WX 722 ; N Kcommaaccent ; B 34 -198 723 662 ;
+C -1 ; WX 611 ; N Lacute ; B 12 0 598 890 ;
+C -1 ; WX 980 ; N trademark ; B 30 256 957 662 ;
+C -1 ; WX 444 ; N edotaccent ; B 25 -10 424 623 ;
+C -1 ; WX 333 ; N Igrave ; B 18 0 315 890 ;
+C -1 ; WX 333 ; N Imacron ; B 11 0 322 813 ;
+C -1 ; WX 611 ; N Lcaron ; B 12 0 598 676 ;
+C -1 ; WX 750 ; N onehalf ; B 31 -14 746 676 ;
+C -1 ; WX 549 ; N lessequal ; B 26 0 523 666 ;
+C -1 ; WX 500 ; N ocircumflex ; B 29 -10 470 674 ;
+C -1 ; WX 500 ; N ntilde ; B 16 0 485 638 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 14 -14 705 890 ;
+C -1 ; WX 611 ; N Eacute ; B 12 0 597 890 ;
+C -1 ; WX 444 ; N emacron ; B 25 -10 424 601 ;
+C -1 ; WX 500 ; N gbreve ; B 28 -218 470 664 ;
+C -1 ; WX 750 ; N onequarter ; B 37 -14 718 676 ;
+C -1 ; WX 556 ; N Scaron ; B 42 -14 491 886 ;
+C -1 ; WX 556 ; N Scommaaccent ; B 42 -218 491 676 ;
+C -1 ; WX 722 ; N Ohungarumlaut ; B 34 -14 688 890 ;
+C -1 ; WX 400 ; N degree ; B 57 390 343 676 ;
+C -1 ; WX 500 ; N ograve ; B 29 -10 470 678 ;
+C -1 ; WX 667 ; N Ccaron ; B 28 -14 633 886 ;
+C -1 ; WX 500 ; N ugrave ; B 9 -10 479 678 ;
+C -1 ; WX 453 ; N radical ; B 2 -60 452 768 ;
+C -1 ; WX 722 ; N Dcaron ; B 16 0 685 886 ;
+C -1 ; WX 333 ; N rcommaaccent ; B 5 -218 335 460 ;
+C -1 ; WX 722 ; N Ntilde ; B 12 -11 707 850 ;
+C -1 ; WX 500 ; N otilde ; B 29 -10 470 638 ;
+C -1 ; WX 667 ; N Rcommaaccent ; B 17 -198 659 662 ;
+C -1 ; WX 611 ; N Lcommaaccent ; B 12 -218 598 662 ;
+C -1 ; WX 722 ; N Atilde ; B 15 0 706 850 ;
+C -1 ; WX 722 ; N Aogonek ; B 15 -165 738 674 ;
+C -1 ; WX 722 ; N Aring ; B 15 0 706 898 ;
+C -1 ; WX 722 ; N Otilde ; B 34 -14 688 850 ;
+C -1 ; WX 444 ; N zdotaccent ; B 27 0 418 623 ;
+C -1 ; WX 611 ; N Ecaron ; B 12 0 597 886 ;
+C -1 ; WX 333 ; N Iogonek ; B 18 -165 315 662 ;
+C -1 ; WX 500 ; N kcommaaccent ; B 7 -218 505 683 ;
+C -1 ; WX 564 ; N minus ; B 30 220 534 286 ;
+C -1 ; WX 333 ; N Icircumflex ; B 11 0 322 886 ;
+C -1 ; WX 500 ; N ncaron ; B 16 0 485 674 ;
+C -1 ; WX 278 ; N tcommaaccent ; B 13 -218 279 579 ;
+C -1 ; WX 564 ; N logicalnot ; B 30 108 534 386 ;
+C -1 ; WX 500 ; N odieresis ; B 29 -10 470 623 ;
+C -1 ; WX 500 ; N udieresis ; B 9 -10 479 623 ;
+C -1 ; WX 549 ; N notequal ; B 12 -31 537 547 ;
+C -1 ; WX 500 ; N gcommaaccent ; B 28 -218 470 749 ;
+C -1 ; WX 500 ; N eth ; B 29 -10 471 686 ;
+C -1 ; WX 444 ; N zcaron ; B 27 0 418 674 ;
+C -1 ; WX 500 ; N ncommaaccent ; B 16 -218 485 460 ;
+C -1 ; WX 300 ; N onesuperior ; B 57 270 248 676 ;
+C -1 ; WX 278 ; N imacron ; B 6 0 271 601 ;
+C -1 ; WX 500 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2073
+KPX A C -40
+KPX A Cacute -40
+KPX A Ccaron -40
+KPX A Ccedilla -40
+KPX A G -40
+KPX A Gbreve -40
+KPX A Gcommaaccent -40
+KPX A O -55
+KPX A Oacute -55
+KPX A Ocircumflex -55
+KPX A Odieresis -55
+KPX A Ograve -55
+KPX A Ohungarumlaut -55
+KPX A Omacron -55
+KPX A Oslash -55
+KPX A Otilde -55
+KPX A Q -55
+KPX A T -111
+KPX A Tcaron -111
+KPX A Tcommaaccent -111
+KPX A U -55
+KPX A Uacute -55
+KPX A Ucircumflex -55
+KPX A Udieresis -55
+KPX A Ugrave -55
+KPX A Uhungarumlaut -55
+KPX A Umacron -55
+KPX A Uogonek -55
+KPX A Uring -55
+KPX A V -135
+KPX A W -90
+KPX A Y -105
+KPX A Yacute -105
+KPX A Ydieresis -105
+KPX A quoteright -111
+KPX A v -74
+KPX A w -92
+KPX A y -92
+KPX A yacute -92
+KPX A ydieresis -92
+KPX Aacute C -40
+KPX Aacute Cacute -40
+KPX Aacute Ccaron -40
+KPX Aacute Ccedilla -40
+KPX Aacute G -40
+KPX Aacute Gbreve -40
+KPX Aacute Gcommaaccent -40
+KPX Aacute O -55
+KPX Aacute Oacute -55
+KPX Aacute Ocircumflex -55
+KPX Aacute Odieresis -55
+KPX Aacute Ograve -55
+KPX Aacute Ohungarumlaut -55
+KPX Aacute Omacron -55
+KPX Aacute Oslash -55
+KPX Aacute Otilde -55
+KPX Aacute Q -55
+KPX Aacute T -111
+KPX Aacute Tcaron -111
+KPX Aacute Tcommaaccent -111
+KPX Aacute U -55
+KPX Aacute Uacute -55
+KPX Aacute Ucircumflex -55
+KPX Aacute Udieresis -55
+KPX Aacute Ugrave -55
+KPX Aacute Uhungarumlaut -55
+KPX Aacute Umacron -55
+KPX Aacute Uogonek -55
+KPX Aacute Uring -55
+KPX Aacute V -135
+KPX Aacute W -90
+KPX Aacute Y -105
+KPX Aacute Yacute -105
+KPX Aacute Ydieresis -105
+KPX Aacute quoteright -111
+KPX Aacute v -74
+KPX Aacute w -92
+KPX Aacute y -92
+KPX Aacute yacute -92
+KPX Aacute ydieresis -92
+KPX Abreve C -40
+KPX Abreve Cacute -40
+KPX Abreve Ccaron -40
+KPX Abreve Ccedilla -40
+KPX Abreve G -40
+KPX Abreve Gbreve -40
+KPX Abreve Gcommaaccent -40
+KPX Abreve O -55
+KPX Abreve Oacute -55
+KPX Abreve Ocircumflex -55
+KPX Abreve Odieresis -55
+KPX Abreve Ograve -55
+KPX Abreve Ohungarumlaut -55
+KPX Abreve Omacron -55
+KPX Abreve Oslash -55
+KPX Abreve Otilde -55
+KPX Abreve Q -55
+KPX Abreve T -111
+KPX Abreve Tcaron -111
+KPX Abreve Tcommaaccent -111
+KPX Abreve U -55
+KPX Abreve Uacute -55
+KPX Abreve Ucircumflex -55
+KPX Abreve Udieresis -55
+KPX Abreve Ugrave -55
+KPX Abreve Uhungarumlaut -55
+KPX Abreve Umacron -55
+KPX Abreve Uogonek -55
+KPX Abreve Uring -55
+KPX Abreve V -135
+KPX Abreve W -90
+KPX Abreve Y -105
+KPX Abreve Yacute -105
+KPX Abreve Ydieresis -105
+KPX Abreve quoteright -111
+KPX Abreve v -74
+KPX Abreve w -92
+KPX Abreve y -92
+KPX Abreve yacute -92
+KPX Abreve ydieresis -92
+KPX Acircumflex C -40
+KPX Acircumflex Cacute -40
+KPX Acircumflex Ccaron -40
+KPX Acircumflex Ccedilla -40
+KPX Acircumflex G -40
+KPX Acircumflex Gbreve -40
+KPX Acircumflex Gcommaaccent -40
+KPX Acircumflex O -55
+KPX Acircumflex Oacute -55
+KPX Acircumflex Ocircumflex -55
+KPX Acircumflex Odieresis -55
+KPX Acircumflex Ograve -55
+KPX Acircumflex Ohungarumlaut -55
+KPX Acircumflex Omacron -55
+KPX Acircumflex Oslash -55
+KPX Acircumflex Otilde -55
+KPX Acircumflex Q -55
+KPX Acircumflex T -111
+KPX Acircumflex Tcaron -111
+KPX Acircumflex Tcommaaccent -111
+KPX Acircumflex U -55
+KPX Acircumflex Uacute -55
+KPX Acircumflex Ucircumflex -55
+KPX Acircumflex Udieresis -55
+KPX Acircumflex Ugrave -55
+KPX Acircumflex Uhungarumlaut -55
+KPX Acircumflex Umacron -55
+KPX Acircumflex Uogonek -55
+KPX Acircumflex Uring -55
+KPX Acircumflex V -135
+KPX Acircumflex W -90
+KPX Acircumflex Y -105
+KPX Acircumflex Yacute -105
+KPX Acircumflex Ydieresis -105
+KPX Acircumflex quoteright -111
+KPX Acircumflex v -74
+KPX Acircumflex w -92
+KPX Acircumflex y -92
+KPX Acircumflex yacute -92
+KPX Acircumflex ydieresis -92
+KPX Adieresis C -40
+KPX Adieresis Cacute -40
+KPX Adieresis Ccaron -40
+KPX Adieresis Ccedilla -40
+KPX Adieresis G -40
+KPX Adieresis Gbreve -40
+KPX Adieresis Gcommaaccent -40
+KPX Adieresis O -55
+KPX Adieresis Oacute -55
+KPX Adieresis Ocircumflex -55
+KPX Adieresis Odieresis -55
+KPX Adieresis Ograve -55
+KPX Adieresis Ohungarumlaut -55
+KPX Adieresis Omacron -55
+KPX Adieresis Oslash -55
+KPX Adieresis Otilde -55
+KPX Adieresis Q -55
+KPX Adieresis T -111
+KPX Adieresis Tcaron -111
+KPX Adieresis Tcommaaccent -111
+KPX Adieresis U -55
+KPX Adieresis Uacute -55
+KPX Adieresis Ucircumflex -55
+KPX Adieresis Udieresis -55
+KPX Adieresis Ugrave -55
+KPX Adieresis Uhungarumlaut -55
+KPX Adieresis Umacron -55
+KPX Adieresis Uogonek -55
+KPX Adieresis Uring -55
+KPX Adieresis V -135
+KPX Adieresis W -90
+KPX Adieresis Y -105
+KPX Adieresis Yacute -105
+KPX Adieresis Ydieresis -105
+KPX Adieresis quoteright -111
+KPX Adieresis v -74
+KPX Adieresis w -92
+KPX Adieresis y -92
+KPX Adieresis yacute -92
+KPX Adieresis ydieresis -92
+KPX Agrave C -40
+KPX Agrave Cacute -40
+KPX Agrave Ccaron -40
+KPX Agrave Ccedilla -40
+KPX Agrave G -40
+KPX Agrave Gbreve -40
+KPX Agrave Gcommaaccent -40
+KPX Agrave O -55
+KPX Agrave Oacute -55
+KPX Agrave Ocircumflex -55
+KPX Agrave Odieresis -55
+KPX Agrave Ograve -55
+KPX Agrave Ohungarumlaut -55
+KPX Agrave Omacron -55
+KPX Agrave Oslash -55
+KPX Agrave Otilde -55
+KPX Agrave Q -55
+KPX Agrave T -111
+KPX Agrave Tcaron -111
+KPX Agrave Tcommaaccent -111
+KPX Agrave U -55
+KPX Agrave Uacute -55
+KPX Agrave Ucircumflex -55
+KPX Agrave Udieresis -55
+KPX Agrave Ugrave -55
+KPX Agrave Uhungarumlaut -55
+KPX Agrave Umacron -55
+KPX Agrave Uogonek -55
+KPX Agrave Uring -55
+KPX Agrave V -135
+KPX Agrave W -90
+KPX Agrave Y -105
+KPX Agrave Yacute -105
+KPX Agrave Ydieresis -105
+KPX Agrave quoteright -111
+KPX Agrave v -74
+KPX Agrave w -92
+KPX Agrave y -92
+KPX Agrave yacute -92
+KPX Agrave ydieresis -92
+KPX Amacron C -40
+KPX Amacron Cacute -40
+KPX Amacron Ccaron -40
+KPX Amacron Ccedilla -40
+KPX Amacron G -40
+KPX Amacron Gbreve -40
+KPX Amacron Gcommaaccent -40
+KPX Amacron O -55
+KPX Amacron Oacute -55
+KPX Amacron Ocircumflex -55
+KPX Amacron Odieresis -55
+KPX Amacron Ograve -55
+KPX Amacron Ohungarumlaut -55
+KPX Amacron Omacron -55
+KPX Amacron Oslash -55
+KPX Amacron Otilde -55
+KPX Amacron Q -55
+KPX Amacron T -111
+KPX Amacron Tcaron -111
+KPX Amacron Tcommaaccent -111
+KPX Amacron U -55
+KPX Amacron Uacute -55
+KPX Amacron Ucircumflex -55
+KPX Amacron Udieresis -55
+KPX Amacron Ugrave -55
+KPX Amacron Uhungarumlaut -55
+KPX Amacron Umacron -55
+KPX Amacron Uogonek -55
+KPX Amacron Uring -55
+KPX Amacron V -135
+KPX Amacron W -90
+KPX Amacron Y -105
+KPX Amacron Yacute -105
+KPX Amacron Ydieresis -105
+KPX Amacron quoteright -111
+KPX Amacron v -74
+KPX Amacron w -92
+KPX Amacron y -92
+KPX Amacron yacute -92
+KPX Amacron ydieresis -92
+KPX Aogonek C -40
+KPX Aogonek Cacute -40
+KPX Aogonek Ccaron -40
+KPX Aogonek Ccedilla -40
+KPX Aogonek G -40
+KPX Aogonek Gbreve -40
+KPX Aogonek Gcommaaccent -40
+KPX Aogonek O -55
+KPX Aogonek Oacute -55
+KPX Aogonek Ocircumflex -55
+KPX Aogonek Odieresis -55
+KPX Aogonek Ograve -55
+KPX Aogonek Ohungarumlaut -55
+KPX Aogonek Omacron -55
+KPX Aogonek Oslash -55
+KPX Aogonek Otilde -55
+KPX Aogonek Q -55
+KPX Aogonek T -111
+KPX Aogonek Tcaron -111
+KPX Aogonek Tcommaaccent -111
+KPX Aogonek U -55
+KPX Aogonek Uacute -55
+KPX Aogonek Ucircumflex -55
+KPX Aogonek Udieresis -55
+KPX Aogonek Ugrave -55
+KPX Aogonek Uhungarumlaut -55
+KPX Aogonek Umacron -55
+KPX Aogonek Uogonek -55
+KPX Aogonek Uring -55
+KPX Aogonek V -135
+KPX Aogonek W -90
+KPX Aogonek Y -105
+KPX Aogonek Yacute -105
+KPX Aogonek Ydieresis -105
+KPX Aogonek quoteright -111
+KPX Aogonek v -74
+KPX Aogonek w -52
+KPX Aogonek y -52
+KPX Aogonek yacute -52
+KPX Aogonek ydieresis -52
+KPX Aring C -40
+KPX Aring Cacute -40
+KPX Aring Ccaron -40
+KPX Aring Ccedilla -40
+KPX Aring G -40
+KPX Aring Gbreve -40
+KPX Aring Gcommaaccent -40
+KPX Aring O -55
+KPX Aring Oacute -55
+KPX Aring Ocircumflex -55
+KPX Aring Odieresis -55
+KPX Aring Ograve -55
+KPX Aring Ohungarumlaut -55
+KPX Aring Omacron -55
+KPX Aring Oslash -55
+KPX Aring Otilde -55
+KPX Aring Q -55
+KPX Aring T -111
+KPX Aring Tcaron -111
+KPX Aring Tcommaaccent -111
+KPX Aring U -55
+KPX Aring Uacute -55
+KPX Aring Ucircumflex -55
+KPX Aring Udieresis -55
+KPX Aring Ugrave -55
+KPX Aring Uhungarumlaut -55
+KPX Aring Umacron -55
+KPX Aring Uogonek -55
+KPX Aring Uring -55
+KPX Aring V -135
+KPX Aring W -90
+KPX Aring Y -105
+KPX Aring Yacute -105
+KPX Aring Ydieresis -105
+KPX Aring quoteright -111
+KPX Aring v -74
+KPX Aring w -92
+KPX Aring y -92
+KPX Aring yacute -92
+KPX Aring ydieresis -92
+KPX Atilde C -40
+KPX Atilde Cacute -40
+KPX Atilde Ccaron -40
+KPX Atilde Ccedilla -40
+KPX Atilde G -40
+KPX Atilde Gbreve -40
+KPX Atilde Gcommaaccent -40
+KPX Atilde O -55
+KPX Atilde Oacute -55
+KPX Atilde Ocircumflex -55
+KPX Atilde Odieresis -55
+KPX Atilde Ograve -55
+KPX Atilde Ohungarumlaut -55
+KPX Atilde Omacron -55
+KPX Atilde Oslash -55
+KPX Atilde Otilde -55
+KPX Atilde Q -55
+KPX Atilde T -111
+KPX Atilde Tcaron -111
+KPX Atilde Tcommaaccent -111
+KPX Atilde U -55
+KPX Atilde Uacute -55
+KPX Atilde Ucircumflex -55
+KPX Atilde Udieresis -55
+KPX Atilde Ugrave -55
+KPX Atilde Uhungarumlaut -55
+KPX Atilde Umacron -55
+KPX Atilde Uogonek -55
+KPX Atilde Uring -55
+KPX Atilde V -135
+KPX Atilde W -90
+KPX Atilde Y -105
+KPX Atilde Yacute -105
+KPX Atilde Ydieresis -105
+KPX Atilde quoteright -111
+KPX Atilde v -74
+KPX Atilde w -92
+KPX Atilde y -92
+KPX Atilde yacute -92
+KPX Atilde ydieresis -92
+KPX B A -35
+KPX B Aacute -35
+KPX B Abreve -35
+KPX B Acircumflex -35
+KPX B Adieresis -35
+KPX B Agrave -35
+KPX B Amacron -35
+KPX B Aogonek -35
+KPX B Aring -35
+KPX B Atilde -35
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -40
+KPX D W -30
+KPX D Y -55
+KPX D Yacute -55
+KPX D Ydieresis -55
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -40
+KPX Dcaron W -30
+KPX Dcaron Y -55
+KPX Dcaron Yacute -55
+KPX Dcaron Ydieresis -55
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -40
+KPX Dcroat W -30
+KPX Dcroat Y -55
+KPX Dcroat Yacute -55
+KPX Dcroat Ydieresis -55
+KPX F A -74
+KPX F Aacute -74
+KPX F Abreve -74
+KPX F Acircumflex -74
+KPX F Adieresis -74
+KPX F Agrave -74
+KPX F Amacron -74
+KPX F Aogonek -74
+KPX F Aring -74
+KPX F Atilde -74
+KPX F a -15
+KPX F aacute -15
+KPX F abreve -15
+KPX F acircumflex -15
+KPX F adieresis -15
+KPX F agrave -15
+KPX F amacron -15
+KPX F aogonek -15
+KPX F aring -15
+KPX F atilde -15
+KPX F comma -80
+KPX F o -15
+KPX F oacute -15
+KPX F ocircumflex -15
+KPX F odieresis -15
+KPX F ograve -15
+KPX F ohungarumlaut -15
+KPX F omacron -15
+KPX F oslash -15
+KPX F otilde -15
+KPX F period -80
+KPX J A -60
+KPX J Aacute -60
+KPX J Abreve -60
+KPX J Acircumflex -60
+KPX J Adieresis -60
+KPX J Agrave -60
+KPX J Amacron -60
+KPX J Aogonek -60
+KPX J Aring -60
+KPX J Atilde -60
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -25
+KPX K eacute -25
+KPX K ecaron -25
+KPX K ecircumflex -25
+KPX K edieresis -25
+KPX K edotaccent -25
+KPX K egrave -25
+KPX K emacron -25
+KPX K eogonek -25
+KPX K o -35
+KPX K oacute -35
+KPX K ocircumflex -35
+KPX K odieresis -35
+KPX K ograve -35
+KPX K ohungarumlaut -35
+KPX K omacron -35
+KPX K oslash -35
+KPX K otilde -35
+KPX K u -15
+KPX K uacute -15
+KPX K ucircumflex -15
+KPX K udieresis -15
+KPX K ugrave -15
+KPX K uhungarumlaut -15
+KPX K umacron -15
+KPX K uogonek -15
+KPX K uring -15
+KPX K y -25
+KPX K yacute -25
+KPX K ydieresis -25
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -25
+KPX Kcommaaccent eacute -25
+KPX Kcommaaccent ecaron -25
+KPX Kcommaaccent ecircumflex -25
+KPX Kcommaaccent edieresis -25
+KPX Kcommaaccent edotaccent -25
+KPX Kcommaaccent egrave -25
+KPX Kcommaaccent emacron -25
+KPX Kcommaaccent eogonek -25
+KPX Kcommaaccent o -35
+KPX Kcommaaccent oacute -35
+KPX Kcommaaccent ocircumflex -35
+KPX Kcommaaccent odieresis -35
+KPX Kcommaaccent ograve -35
+KPX Kcommaaccent ohungarumlaut -35
+KPX Kcommaaccent omacron -35
+KPX Kcommaaccent oslash -35
+KPX Kcommaaccent otilde -35
+KPX Kcommaaccent u -15
+KPX Kcommaaccent uacute -15
+KPX Kcommaaccent ucircumflex -15
+KPX Kcommaaccent udieresis -15
+KPX Kcommaaccent ugrave -15
+KPX Kcommaaccent uhungarumlaut -15
+KPX Kcommaaccent umacron -15
+KPX Kcommaaccent uogonek -15
+KPX Kcommaaccent uring -15
+KPX Kcommaaccent y -25
+KPX Kcommaaccent yacute -25
+KPX Kcommaaccent ydieresis -25
+KPX L T -92
+KPX L Tcaron -92
+KPX L Tcommaaccent -92
+KPX L V -100
+KPX L W -74
+KPX L Y -100
+KPX L Yacute -100
+KPX L Ydieresis -100
+KPX L quoteright -92
+KPX L y -55
+KPX L yacute -55
+KPX L ydieresis -55
+KPX Lacute T -92
+KPX Lacute Tcaron -92
+KPX Lacute Tcommaaccent -92
+KPX Lacute V -100
+KPX Lacute W -74
+KPX Lacute Y -100
+KPX Lacute Yacute -100
+KPX Lacute Ydieresis -100
+KPX Lacute quoteright -92
+KPX Lacute y -55
+KPX Lacute yacute -55
+KPX Lacute ydieresis -55
+KPX Lcaron quoteright -92
+KPX Lcaron y -55
+KPX Lcaron yacute -55
+KPX Lcaron ydieresis -55
+KPX Lcommaaccent T -92
+KPX Lcommaaccent Tcaron -92
+KPX Lcommaaccent Tcommaaccent -92
+KPX Lcommaaccent V -100
+KPX Lcommaaccent W -74
+KPX Lcommaaccent Y -100
+KPX Lcommaaccent Yacute -100
+KPX Lcommaaccent Ydieresis -100
+KPX Lcommaaccent quoteright -92
+KPX Lcommaaccent y -55
+KPX Lcommaaccent yacute -55
+KPX Lcommaaccent ydieresis -55
+KPX Lslash T -92
+KPX Lslash Tcaron -92
+KPX Lslash Tcommaaccent -92
+KPX Lslash V -100
+KPX Lslash W -74
+KPX Lslash Y -100
+KPX Lslash Yacute -100
+KPX Lslash Ydieresis -100
+KPX Lslash quoteright -92
+KPX Lslash y -55
+KPX Lslash yacute -55
+KPX Lslash ydieresis -55
+KPX N A -35
+KPX N Aacute -35
+KPX N Abreve -35
+KPX N Acircumflex -35
+KPX N Adieresis -35
+KPX N Agrave -35
+KPX N Amacron -35
+KPX N Aogonek -35
+KPX N Aring -35
+KPX N Atilde -35
+KPX Nacute A -35
+KPX Nacute Aacute -35
+KPX Nacute Abreve -35
+KPX Nacute Acircumflex -35
+KPX Nacute Adieresis -35
+KPX Nacute Agrave -35
+KPX Nacute Amacron -35
+KPX Nacute Aogonek -35
+KPX Nacute Aring -35
+KPX Nacute Atilde -35
+KPX Ncaron A -35
+KPX Ncaron Aacute -35
+KPX Ncaron Abreve -35
+KPX Ncaron Acircumflex -35
+KPX Ncaron Adieresis -35
+KPX Ncaron Agrave -35
+KPX Ncaron Amacron -35
+KPX Ncaron Aogonek -35
+KPX Ncaron Aring -35
+KPX Ncaron Atilde -35
+KPX Ncommaaccent A -35
+KPX Ncommaaccent Aacute -35
+KPX Ncommaaccent Abreve -35
+KPX Ncommaaccent Acircumflex -35
+KPX Ncommaaccent Adieresis -35
+KPX Ncommaaccent Agrave -35
+KPX Ncommaaccent Amacron -35
+KPX Ncommaaccent Aogonek -35
+KPX Ncommaaccent Aring -35
+KPX Ncommaaccent Atilde -35
+KPX Ntilde A -35
+KPX Ntilde Aacute -35
+KPX Ntilde Abreve -35
+KPX Ntilde Acircumflex -35
+KPX Ntilde Adieresis -35
+KPX Ntilde Agrave -35
+KPX Ntilde Amacron -35
+KPX Ntilde Aogonek -35
+KPX Ntilde Aring -35
+KPX Ntilde Atilde -35
+KPX O A -35
+KPX O Aacute -35
+KPX O Abreve -35
+KPX O Acircumflex -35
+KPX O Adieresis -35
+KPX O Agrave -35
+KPX O Amacron -35
+KPX O Aogonek -35
+KPX O Aring -35
+KPX O Atilde -35
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -35
+KPX O X -40
+KPX O Y -50
+KPX O Yacute -50
+KPX O Ydieresis -50
+KPX Oacute A -35
+KPX Oacute Aacute -35
+KPX Oacute Abreve -35
+KPX Oacute Acircumflex -35
+KPX Oacute Adieresis -35
+KPX Oacute Agrave -35
+KPX Oacute Amacron -35
+KPX Oacute Aogonek -35
+KPX Oacute Aring -35
+KPX Oacute Atilde -35
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -35
+KPX Oacute X -40
+KPX Oacute Y -50
+KPX Oacute Yacute -50
+KPX Oacute Ydieresis -50
+KPX Ocircumflex A -35
+KPX Ocircumflex Aacute -35
+KPX Ocircumflex Abreve -35
+KPX Ocircumflex Acircumflex -35
+KPX Ocircumflex Adieresis -35
+KPX Ocircumflex Agrave -35
+KPX Ocircumflex Amacron -35
+KPX Ocircumflex Aogonek -35
+KPX Ocircumflex Aring -35
+KPX Ocircumflex Atilde -35
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -35
+KPX Ocircumflex X -40
+KPX Ocircumflex Y -50
+KPX Ocircumflex Yacute -50
+KPX Ocircumflex Ydieresis -50
+KPX Odieresis A -35
+KPX Odieresis Aacute -35
+KPX Odieresis Abreve -35
+KPX Odieresis Acircumflex -35
+KPX Odieresis Adieresis -35
+KPX Odieresis Agrave -35
+KPX Odieresis Amacron -35
+KPX Odieresis Aogonek -35
+KPX Odieresis Aring -35
+KPX Odieresis Atilde -35
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -35
+KPX Odieresis X -40
+KPX Odieresis Y -50
+KPX Odieresis Yacute -50
+KPX Odieresis Ydieresis -50
+KPX Ograve A -35
+KPX Ograve Aacute -35
+KPX Ograve Abreve -35
+KPX Ograve Acircumflex -35
+KPX Ograve Adieresis -35
+KPX Ograve Agrave -35
+KPX Ograve Amacron -35
+KPX Ograve Aogonek -35
+KPX Ograve Aring -35
+KPX Ograve Atilde -35
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -35
+KPX Ograve X -40
+KPX Ograve Y -50
+KPX Ograve Yacute -50
+KPX Ograve Ydieresis -50
+KPX Ohungarumlaut A -35
+KPX Ohungarumlaut Aacute -35
+KPX Ohungarumlaut Abreve -35
+KPX Ohungarumlaut Acircumflex -35
+KPX Ohungarumlaut Adieresis -35
+KPX Ohungarumlaut Agrave -35
+KPX Ohungarumlaut Amacron -35
+KPX Ohungarumlaut Aogonek -35
+KPX Ohungarumlaut Aring -35
+KPX Ohungarumlaut Atilde -35
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -35
+KPX Ohungarumlaut X -40
+KPX Ohungarumlaut Y -50
+KPX Ohungarumlaut Yacute -50
+KPX Ohungarumlaut Ydieresis -50
+KPX Omacron A -35
+KPX Omacron Aacute -35
+KPX Omacron Abreve -35
+KPX Omacron Acircumflex -35
+KPX Omacron Adieresis -35
+KPX Omacron Agrave -35
+KPX Omacron Amacron -35
+KPX Omacron Aogonek -35
+KPX Omacron Aring -35
+KPX Omacron Atilde -35
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -35
+KPX Omacron X -40
+KPX Omacron Y -50
+KPX Omacron Yacute -50
+KPX Omacron Ydieresis -50
+KPX Oslash A -35
+KPX Oslash Aacute -35
+KPX Oslash Abreve -35
+KPX Oslash Acircumflex -35
+KPX Oslash Adieresis -35
+KPX Oslash Agrave -35
+KPX Oslash Amacron -35
+KPX Oslash Aogonek -35
+KPX Oslash Aring -35
+KPX Oslash Atilde -35
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -35
+KPX Oslash X -40
+KPX Oslash Y -50
+KPX Oslash Yacute -50
+KPX Oslash Ydieresis -50
+KPX Otilde A -35
+KPX Otilde Aacute -35
+KPX Otilde Abreve -35
+KPX Otilde Acircumflex -35
+KPX Otilde Adieresis -35
+KPX Otilde Agrave -35
+KPX Otilde Amacron -35
+KPX Otilde Aogonek -35
+KPX Otilde Aring -35
+KPX Otilde Atilde -35
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -35
+KPX Otilde X -40
+KPX Otilde Y -50
+KPX Otilde Yacute -50
+KPX Otilde Ydieresis -50
+KPX P A -92
+KPX P Aacute -92
+KPX P Abreve -92
+KPX P Acircumflex -92
+KPX P Adieresis -92
+KPX P Agrave -92
+KPX P Amacron -92
+KPX P Aogonek -92
+KPX P Aring -92
+KPX P Atilde -92
+KPX P a -15
+KPX P aacute -15
+KPX P abreve -15
+KPX P acircumflex -15
+KPX P adieresis -15
+KPX P agrave -15
+KPX P amacron -15
+KPX P aogonek -15
+KPX P aring -15
+KPX P atilde -15
+KPX P comma -111
+KPX P period -111
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -40
+KPX R Oacute -40
+KPX R Ocircumflex -40
+KPX R Odieresis -40
+KPX R Ograve -40
+KPX R Ohungarumlaut -40
+KPX R Omacron -40
+KPX R Oslash -40
+KPX R Otilde -40
+KPX R T -60
+KPX R Tcaron -60
+KPX R Tcommaaccent -60
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -80
+KPX R W -55
+KPX R Y -65
+KPX R Yacute -65
+KPX R Ydieresis -65
+KPX Racute O -40
+KPX Racute Oacute -40
+KPX Racute Ocircumflex -40
+KPX Racute Odieresis -40
+KPX Racute Ograve -40
+KPX Racute Ohungarumlaut -40
+KPX Racute Omacron -40
+KPX Racute Oslash -40
+KPX Racute Otilde -40
+KPX Racute T -60
+KPX Racute Tcaron -60
+KPX Racute Tcommaaccent -60
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -80
+KPX Racute W -55
+KPX Racute Y -65
+KPX Racute Yacute -65
+KPX Racute Ydieresis -65
+KPX Rcaron O -40
+KPX Rcaron Oacute -40
+KPX Rcaron Ocircumflex -40
+KPX Rcaron Odieresis -40
+KPX Rcaron Ograve -40
+KPX Rcaron Ohungarumlaut -40
+KPX Rcaron Omacron -40
+KPX Rcaron Oslash -40
+KPX Rcaron Otilde -40
+KPX Rcaron T -60
+KPX Rcaron Tcaron -60
+KPX Rcaron Tcommaaccent -60
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -80
+KPX Rcaron W -55
+KPX Rcaron Y -65
+KPX Rcaron Yacute -65
+KPX Rcaron Ydieresis -65
+KPX Rcommaaccent O -40
+KPX Rcommaaccent Oacute -40
+KPX Rcommaaccent Ocircumflex -40
+KPX Rcommaaccent Odieresis -40
+KPX Rcommaaccent Ograve -40
+KPX Rcommaaccent Ohungarumlaut -40
+KPX Rcommaaccent Omacron -40
+KPX Rcommaaccent Oslash -40
+KPX Rcommaaccent Otilde -40
+KPX Rcommaaccent T -60
+KPX Rcommaaccent Tcaron -60
+KPX Rcommaaccent Tcommaaccent -60
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -80
+KPX Rcommaaccent W -55
+KPX Rcommaaccent Y -65
+KPX Rcommaaccent Yacute -65
+KPX Rcommaaccent Ydieresis -65
+KPX T A -93
+KPX T Aacute -93
+KPX T Abreve -93
+KPX T Acircumflex -93
+KPX T Adieresis -93
+KPX T Agrave -93
+KPX T Amacron -93
+KPX T Aogonek -93
+KPX T Aring -93
+KPX T Atilde -93
+KPX T O -18
+KPX T Oacute -18
+KPX T Ocircumflex -18
+KPX T Odieresis -18
+KPX T Ograve -18
+KPX T Ohungarumlaut -18
+KPX T Omacron -18
+KPX T Oslash -18
+KPX T Otilde -18
+KPX T a -80
+KPX T aacute -80
+KPX T abreve -80
+KPX T acircumflex -80
+KPX T adieresis -40
+KPX T agrave -40
+KPX T amacron -40
+KPX T aogonek -80
+KPX T aring -80
+KPX T atilde -40
+KPX T colon -50
+KPX T comma -74
+KPX T e -70
+KPX T eacute -70
+KPX T ecaron -70
+KPX T ecircumflex -70
+KPX T edieresis -30
+KPX T edotaccent -70
+KPX T egrave -70
+KPX T emacron -30
+KPX T eogonek -70
+KPX T hyphen -92
+KPX T i -35
+KPX T iacute -35
+KPX T iogonek -35
+KPX T o -80
+KPX T oacute -80
+KPX T ocircumflex -80
+KPX T odieresis -80
+KPX T ograve -80
+KPX T ohungarumlaut -80
+KPX T omacron -80
+KPX T oslash -80
+KPX T otilde -80
+KPX T period -74
+KPX T r -35
+KPX T racute -35
+KPX T rcaron -35
+KPX T rcommaaccent -35
+KPX T semicolon -55
+KPX T u -45
+KPX T uacute -45
+KPX T ucircumflex -45
+KPX T udieresis -45
+KPX T ugrave -45
+KPX T uhungarumlaut -45
+KPX T umacron -45
+KPX T uogonek -45
+KPX T uring -45
+KPX T w -80
+KPX T y -80
+KPX T yacute -80
+KPX T ydieresis -80
+KPX Tcaron A -93
+KPX Tcaron Aacute -93
+KPX Tcaron Abreve -93
+KPX Tcaron Acircumflex -93
+KPX Tcaron Adieresis -93
+KPX Tcaron Agrave -93
+KPX Tcaron Amacron -93
+KPX Tcaron Aogonek -93
+KPX Tcaron Aring -93
+KPX Tcaron Atilde -93
+KPX Tcaron O -18
+KPX Tcaron Oacute -18
+KPX Tcaron Ocircumflex -18
+KPX Tcaron Odieresis -18
+KPX Tcaron Ograve -18
+KPX Tcaron Ohungarumlaut -18
+KPX Tcaron Omacron -18
+KPX Tcaron Oslash -18
+KPX Tcaron Otilde -18
+KPX Tcaron a -80
+KPX Tcaron aacute -80
+KPX Tcaron abreve -80
+KPX Tcaron acircumflex -80
+KPX Tcaron adieresis -40
+KPX Tcaron agrave -40
+KPX Tcaron amacron -40
+KPX Tcaron aogonek -80
+KPX Tcaron aring -80
+KPX Tcaron atilde -40
+KPX Tcaron colon -50
+KPX Tcaron comma -74
+KPX Tcaron e -70
+KPX Tcaron eacute -70
+KPX Tcaron ecaron -70
+KPX Tcaron ecircumflex -30
+KPX Tcaron edieresis -30
+KPX Tcaron edotaccent -70
+KPX Tcaron egrave -70
+KPX Tcaron emacron -30
+KPX Tcaron eogonek -70
+KPX Tcaron hyphen -92
+KPX Tcaron i -35
+KPX Tcaron iacute -35
+KPX Tcaron iogonek -35
+KPX Tcaron o -80
+KPX Tcaron oacute -80
+KPX Tcaron ocircumflex -80
+KPX Tcaron odieresis -80
+KPX Tcaron ograve -80
+KPX Tcaron ohungarumlaut -80
+KPX Tcaron omacron -80
+KPX Tcaron oslash -80
+KPX Tcaron otilde -80
+KPX Tcaron period -74
+KPX Tcaron r -35
+KPX Tcaron racute -35
+KPX Tcaron rcaron -35
+KPX Tcaron rcommaaccent -35
+KPX Tcaron semicolon -55
+KPX Tcaron u -45
+KPX Tcaron uacute -45
+KPX Tcaron ucircumflex -45
+KPX Tcaron udieresis -45
+KPX Tcaron ugrave -45
+KPX Tcaron uhungarumlaut -45
+KPX Tcaron umacron -45
+KPX Tcaron uogonek -45
+KPX Tcaron uring -45
+KPX Tcaron w -80
+KPX Tcaron y -80
+KPX Tcaron yacute -80
+KPX Tcaron ydieresis -80
+KPX Tcommaaccent A -93
+KPX Tcommaaccent Aacute -93
+KPX Tcommaaccent Abreve -93
+KPX Tcommaaccent Acircumflex -93
+KPX Tcommaaccent Adieresis -93
+KPX Tcommaaccent Agrave -93
+KPX Tcommaaccent Amacron -93
+KPX Tcommaaccent Aogonek -93
+KPX Tcommaaccent Aring -93
+KPX Tcommaaccent Atilde -93
+KPX Tcommaaccent O -18
+KPX Tcommaaccent Oacute -18
+KPX Tcommaaccent Ocircumflex -18
+KPX Tcommaaccent Odieresis -18
+KPX Tcommaaccent Ograve -18
+KPX Tcommaaccent Ohungarumlaut -18
+KPX Tcommaaccent Omacron -18
+KPX Tcommaaccent Oslash -18
+KPX Tcommaaccent Otilde -18
+KPX Tcommaaccent a -80
+KPX Tcommaaccent aacute -80
+KPX Tcommaaccent abreve -80
+KPX Tcommaaccent acircumflex -80
+KPX Tcommaaccent adieresis -40
+KPX Tcommaaccent agrave -40
+KPX Tcommaaccent amacron -40
+KPX Tcommaaccent aogonek -80
+KPX Tcommaaccent aring -80
+KPX Tcommaaccent atilde -40
+KPX Tcommaaccent colon -50
+KPX Tcommaaccent comma -74
+KPX Tcommaaccent e -70
+KPX Tcommaaccent eacute -70
+KPX Tcommaaccent ecaron -70
+KPX Tcommaaccent ecircumflex -30
+KPX Tcommaaccent edieresis -30
+KPX Tcommaaccent edotaccent -70
+KPX Tcommaaccent egrave -30
+KPX Tcommaaccent emacron -70
+KPX Tcommaaccent eogonek -70
+KPX Tcommaaccent hyphen -92
+KPX Tcommaaccent i -35
+KPX Tcommaaccent iacute -35
+KPX Tcommaaccent iogonek -35
+KPX Tcommaaccent o -80
+KPX Tcommaaccent oacute -80
+KPX Tcommaaccent ocircumflex -80
+KPX Tcommaaccent odieresis -80
+KPX Tcommaaccent ograve -80
+KPX Tcommaaccent ohungarumlaut -80
+KPX Tcommaaccent omacron -80
+KPX Tcommaaccent oslash -80
+KPX Tcommaaccent otilde -80
+KPX Tcommaaccent period -74
+KPX Tcommaaccent r -35
+KPX Tcommaaccent racute -35
+KPX Tcommaaccent rcaron -35
+KPX Tcommaaccent rcommaaccent -35
+KPX Tcommaaccent semicolon -55
+KPX Tcommaaccent u -45
+KPX Tcommaaccent uacute -45
+KPX Tcommaaccent ucircumflex -45
+KPX Tcommaaccent udieresis -45
+KPX Tcommaaccent ugrave -45
+KPX Tcommaaccent uhungarumlaut -45
+KPX Tcommaaccent umacron -45
+KPX Tcommaaccent uogonek -45
+KPX Tcommaaccent uring -45
+KPX Tcommaaccent w -80
+KPX Tcommaaccent y -80
+KPX Tcommaaccent yacute -80
+KPX Tcommaaccent ydieresis -80
+KPX U A -40
+KPX U Aacute -40
+KPX U Abreve -40
+KPX U Acircumflex -40
+KPX U Adieresis -40
+KPX U Agrave -40
+KPX U Amacron -40
+KPX U Aogonek -40
+KPX U Aring -40
+KPX U Atilde -40
+KPX Uacute A -40
+KPX Uacute Aacute -40
+KPX Uacute Abreve -40
+KPX Uacute Acircumflex -40
+KPX Uacute Adieresis -40
+KPX Uacute Agrave -40
+KPX Uacute Amacron -40
+KPX Uacute Aogonek -40
+KPX Uacute Aring -40
+KPX Uacute Atilde -40
+KPX Ucircumflex A -40
+KPX Ucircumflex Aacute -40
+KPX Ucircumflex Abreve -40
+KPX Ucircumflex Acircumflex -40
+KPX Ucircumflex Adieresis -40
+KPX Ucircumflex Agrave -40
+KPX Ucircumflex Amacron -40
+KPX Ucircumflex Aogonek -40
+KPX Ucircumflex Aring -40
+KPX Ucircumflex Atilde -40
+KPX Udieresis A -40
+KPX Udieresis Aacute -40
+KPX Udieresis Abreve -40
+KPX Udieresis Acircumflex -40
+KPX Udieresis Adieresis -40
+KPX Udieresis Agrave -40
+KPX Udieresis Amacron -40
+KPX Udieresis Aogonek -40
+KPX Udieresis Aring -40
+KPX Udieresis Atilde -40
+KPX Ugrave A -40
+KPX Ugrave Aacute -40
+KPX Ugrave Abreve -40
+KPX Ugrave Acircumflex -40
+KPX Ugrave Adieresis -40
+KPX Ugrave Agrave -40
+KPX Ugrave Amacron -40
+KPX Ugrave Aogonek -40
+KPX Ugrave Aring -40
+KPX Ugrave Atilde -40
+KPX Uhungarumlaut A -40
+KPX Uhungarumlaut Aacute -40
+KPX Uhungarumlaut Abreve -40
+KPX Uhungarumlaut Acircumflex -40
+KPX Uhungarumlaut Adieresis -40
+KPX Uhungarumlaut Agrave -40
+KPX Uhungarumlaut Amacron -40
+KPX Uhungarumlaut Aogonek -40
+KPX Uhungarumlaut Aring -40
+KPX Uhungarumlaut Atilde -40
+KPX Umacron A -40
+KPX Umacron Aacute -40
+KPX Umacron Abreve -40
+KPX Umacron Acircumflex -40
+KPX Umacron Adieresis -40
+KPX Umacron Agrave -40
+KPX Umacron Amacron -40
+KPX Umacron Aogonek -40
+KPX Umacron Aring -40
+KPX Umacron Atilde -40
+KPX Uogonek A -40
+KPX Uogonek Aacute -40
+KPX Uogonek Abreve -40
+KPX Uogonek Acircumflex -40
+KPX Uogonek Adieresis -40
+KPX Uogonek Agrave -40
+KPX Uogonek Amacron -40
+KPX Uogonek Aogonek -40
+KPX Uogonek Aring -40
+KPX Uogonek Atilde -40
+KPX Uring A -40
+KPX Uring Aacute -40
+KPX Uring Abreve -40
+KPX Uring Acircumflex -40
+KPX Uring Adieresis -40
+KPX Uring Agrave -40
+KPX Uring Amacron -40
+KPX Uring Aogonek -40
+KPX Uring Aring -40
+KPX Uring Atilde -40
+KPX V A -135
+KPX V Aacute -135
+KPX V Abreve -135
+KPX V Acircumflex -135
+KPX V Adieresis -135
+KPX V Agrave -135
+KPX V Amacron -135
+KPX V Aogonek -135
+KPX V Aring -135
+KPX V Atilde -135
+KPX V G -15
+KPX V Gbreve -15
+KPX V Gcommaaccent -15
+KPX V O -40
+KPX V Oacute -40
+KPX V Ocircumflex -40
+KPX V Odieresis -40
+KPX V Ograve -40
+KPX V Ohungarumlaut -40
+KPX V Omacron -40
+KPX V Oslash -40
+KPX V Otilde -40
+KPX V a -111
+KPX V aacute -111
+KPX V abreve -111
+KPX V acircumflex -71
+KPX V adieresis -71
+KPX V agrave -71
+KPX V amacron -71
+KPX V aogonek -111
+KPX V aring -111
+KPX V atilde -71
+KPX V colon -74
+KPX V comma -129
+KPX V e -111
+KPX V eacute -111
+KPX V ecaron -71
+KPX V ecircumflex -71
+KPX V edieresis -71
+KPX V edotaccent -111
+KPX V egrave -71
+KPX V emacron -71
+KPX V eogonek -111
+KPX V hyphen -100
+KPX V i -60
+KPX V iacute -60
+KPX V icircumflex -20
+KPX V idieresis -20
+KPX V igrave -20
+KPX V imacron -20
+KPX V iogonek -60
+KPX V o -129
+KPX V oacute -129
+KPX V ocircumflex -129
+KPX V odieresis -89
+KPX V ograve -89
+KPX V ohungarumlaut -129
+KPX V omacron -89
+KPX V oslash -129
+KPX V otilde -89
+KPX V period -129
+KPX V semicolon -74
+KPX V u -75
+KPX V uacute -75
+KPX V ucircumflex -75
+KPX V udieresis -75
+KPX V ugrave -75
+KPX V uhungarumlaut -75
+KPX V umacron -75
+KPX V uogonek -75
+KPX V uring -75
+KPX W A -120
+KPX W Aacute -120
+KPX W Abreve -120
+KPX W Acircumflex -120
+KPX W Adieresis -120
+KPX W Agrave -120
+KPX W Amacron -120
+KPX W Aogonek -120
+KPX W Aring -120
+KPX W Atilde -120
+KPX W O -10
+KPX W Oacute -10
+KPX W Ocircumflex -10
+KPX W Odieresis -10
+KPX W Ograve -10
+KPX W Ohungarumlaut -10
+KPX W Omacron -10
+KPX W Oslash -10
+KPX W Otilde -10
+KPX W a -80
+KPX W aacute -80
+KPX W abreve -80
+KPX W acircumflex -80
+KPX W adieresis -80
+KPX W agrave -80
+KPX W amacron -80
+KPX W aogonek -80
+KPX W aring -80
+KPX W atilde -80
+KPX W colon -37
+KPX W comma -92
+KPX W e -80
+KPX W eacute -80
+KPX W ecaron -80
+KPX W ecircumflex -80
+KPX W edieresis -40
+KPX W edotaccent -80
+KPX W egrave -40
+KPX W emacron -40
+KPX W eogonek -80
+KPX W hyphen -65
+KPX W i -40
+KPX W iacute -40
+KPX W iogonek -40
+KPX W o -80
+KPX W oacute -80
+KPX W ocircumflex -80
+KPX W odieresis -80
+KPX W ograve -80
+KPX W ohungarumlaut -80
+KPX W omacron -80
+KPX W oslash -80
+KPX W otilde -80
+KPX W period -92
+KPX W semicolon -37
+KPX W u -50
+KPX W uacute -50
+KPX W ucircumflex -50
+KPX W udieresis -50
+KPX W ugrave -50
+KPX W uhungarumlaut -50
+KPX W umacron -50
+KPX W uogonek -50
+KPX W uring -50
+KPX W y -73
+KPX W yacute -73
+KPX W ydieresis -73
+KPX Y A -120
+KPX Y Aacute -120
+KPX Y Abreve -120
+KPX Y Acircumflex -120
+KPX Y Adieresis -120
+KPX Y Agrave -120
+KPX Y Amacron -120
+KPX Y Aogonek -120
+KPX Y Aring -120
+KPX Y Atilde -120
+KPX Y O -30
+KPX Y Oacute -30
+KPX Y Ocircumflex -30
+KPX Y Odieresis -30
+KPX Y Ograve -30
+KPX Y Ohungarumlaut -30
+KPX Y Omacron -30
+KPX Y Oslash -30
+KPX Y Otilde -30
+KPX Y a -100
+KPX Y aacute -100
+KPX Y abreve -100
+KPX Y acircumflex -100
+KPX Y adieresis -60
+KPX Y agrave -60
+KPX Y amacron -60
+KPX Y aogonek -100
+KPX Y aring -100
+KPX Y atilde -60
+KPX Y colon -92
+KPX Y comma -129
+KPX Y e -100
+KPX Y eacute -100
+KPX Y ecaron -100
+KPX Y ecircumflex -100
+KPX Y edieresis -60
+KPX Y edotaccent -100
+KPX Y egrave -60
+KPX Y emacron -60
+KPX Y eogonek -100
+KPX Y hyphen -111
+KPX Y i -55
+KPX Y iacute -55
+KPX Y iogonek -55
+KPX Y o -110
+KPX Y oacute -110
+KPX Y ocircumflex -110
+KPX Y odieresis -70
+KPX Y ograve -70
+KPX Y ohungarumlaut -110
+KPX Y omacron -70
+KPX Y oslash -110
+KPX Y otilde -70
+KPX Y period -129
+KPX Y semicolon -92
+KPX Y u -111
+KPX Y uacute -111
+KPX Y ucircumflex -111
+KPX Y udieresis -71
+KPX Y ugrave -71
+KPX Y uhungarumlaut -111
+KPX Y umacron -71
+KPX Y uogonek -111
+KPX Y uring -111
+KPX Yacute A -120
+KPX Yacute Aacute -120
+KPX Yacute Abreve -120
+KPX Yacute Acircumflex -120
+KPX Yacute Adieresis -120
+KPX Yacute Agrave -120
+KPX Yacute Amacron -120
+KPX Yacute Aogonek -120
+KPX Yacute Aring -120
+KPX Yacute Atilde -120
+KPX Yacute O -30
+KPX Yacute Oacute -30
+KPX Yacute Ocircumflex -30
+KPX Yacute Odieresis -30
+KPX Yacute Ograve -30
+KPX Yacute Ohungarumlaut -30
+KPX Yacute Omacron -30
+KPX Yacute Oslash -30
+KPX Yacute Otilde -30
+KPX Yacute a -100
+KPX Yacute aacute -100
+KPX Yacute abreve -100
+KPX Yacute acircumflex -100
+KPX Yacute adieresis -60
+KPX Yacute agrave -60
+KPX Yacute amacron -60
+KPX Yacute aogonek -100
+KPX Yacute aring -100
+KPX Yacute atilde -60
+KPX Yacute colon -92
+KPX Yacute comma -129
+KPX Yacute e -100
+KPX Yacute eacute -100
+KPX Yacute ecaron -100
+KPX Yacute ecircumflex -100
+KPX Yacute edieresis -60
+KPX Yacute edotaccent -100
+KPX Yacute egrave -60
+KPX Yacute emacron -60
+KPX Yacute eogonek -100
+KPX Yacute hyphen -111
+KPX Yacute i -55
+KPX Yacute iacute -55
+KPX Yacute iogonek -55
+KPX Yacute o -110
+KPX Yacute oacute -110
+KPX Yacute ocircumflex -110
+KPX Yacute odieresis -70
+KPX Yacute ograve -70
+KPX Yacute ohungarumlaut -110
+KPX Yacute omacron -70
+KPX Yacute oslash -110
+KPX Yacute otilde -70
+KPX Yacute period -129
+KPX Yacute semicolon -92
+KPX Yacute u -111
+KPX Yacute uacute -111
+KPX Yacute ucircumflex -111
+KPX Yacute udieresis -71
+KPX Yacute ugrave -71
+KPX Yacute uhungarumlaut -111
+KPX Yacute umacron -71
+KPX Yacute uogonek -111
+KPX Yacute uring -111
+KPX Ydieresis A -120
+KPX Ydieresis Aacute -120
+KPX Ydieresis Abreve -120
+KPX Ydieresis Acircumflex -120
+KPX Ydieresis Adieresis -120
+KPX Ydieresis Agrave -120
+KPX Ydieresis Amacron -120
+KPX Ydieresis Aogonek -120
+KPX Ydieresis Aring -120
+KPX Ydieresis Atilde -120
+KPX Ydieresis O -30
+KPX Ydieresis Oacute -30
+KPX Ydieresis Ocircumflex -30
+KPX Ydieresis Odieresis -30
+KPX Ydieresis Ograve -30
+KPX Ydieresis Ohungarumlaut -30
+KPX Ydieresis Omacron -30
+KPX Ydieresis Oslash -30
+KPX Ydieresis Otilde -30
+KPX Ydieresis a -100
+KPX Ydieresis aacute -100
+KPX Ydieresis abreve -100
+KPX Ydieresis acircumflex -100
+KPX Ydieresis adieresis -60
+KPX Ydieresis agrave -60
+KPX Ydieresis amacron -60
+KPX Ydieresis aogonek -100
+KPX Ydieresis aring -100
+KPX Ydieresis atilde -100
+KPX Ydieresis colon -92
+KPX Ydieresis comma -129
+KPX Ydieresis e -100
+KPX Ydieresis eacute -100
+KPX Ydieresis ecaron -100
+KPX Ydieresis ecircumflex -100
+KPX Ydieresis edieresis -60
+KPX Ydieresis edotaccent -100
+KPX Ydieresis egrave -60
+KPX Ydieresis emacron -60
+KPX Ydieresis eogonek -100
+KPX Ydieresis hyphen -111
+KPX Ydieresis i -55
+KPX Ydieresis iacute -55
+KPX Ydieresis iogonek -55
+KPX Ydieresis o -110
+KPX Ydieresis oacute -110
+KPX Ydieresis ocircumflex -110
+KPX Ydieresis odieresis -70
+KPX Ydieresis ograve -70
+KPX Ydieresis ohungarumlaut -110
+KPX Ydieresis omacron -70
+KPX Ydieresis oslash -110
+KPX Ydieresis otilde -70
+KPX Ydieresis period -129
+KPX Ydieresis semicolon -92
+KPX Ydieresis u -111
+KPX Ydieresis uacute -111
+KPX Ydieresis ucircumflex -111
+KPX Ydieresis udieresis -71
+KPX Ydieresis ugrave -71
+KPX Ydieresis uhungarumlaut -111
+KPX Ydieresis umacron -71
+KPX Ydieresis uogonek -111
+KPX Ydieresis uring -111
+KPX a v -20
+KPX a w -15
+KPX aacute v -20
+KPX aacute w -15
+KPX abreve v -20
+KPX abreve w -15
+KPX acircumflex v -20
+KPX acircumflex w -15
+KPX adieresis v -20
+KPX adieresis w -15
+KPX agrave v -20
+KPX agrave w -15
+KPX amacron v -20
+KPX amacron w -15
+KPX aogonek v -20
+KPX aogonek w -15
+KPX aring v -20
+KPX aring w -15
+KPX atilde v -20
+KPX atilde w -15
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -15
+KPX c y -15
+KPX c yacute -15
+KPX c ydieresis -15
+KPX cacute y -15
+KPX cacute yacute -15
+KPX cacute ydieresis -15
+KPX ccaron y -15
+KPX ccaron yacute -15
+KPX ccaron ydieresis -15
+KPX ccedilla y -15
+KPX ccedilla yacute -15
+KPX ccedilla ydieresis -15
+KPX comma quotedblright -70
+KPX comma quoteright -70
+KPX e g -15
+KPX e gbreve -15
+KPX e gcommaaccent -15
+KPX e v -25
+KPX e w -25
+KPX e x -15
+KPX e y -15
+KPX e yacute -15
+KPX e ydieresis -15
+KPX eacute g -15
+KPX eacute gbreve -15
+KPX eacute gcommaaccent -15
+KPX eacute v -25
+KPX eacute w -25
+KPX eacute x -15
+KPX eacute y -15
+KPX eacute yacute -15
+KPX eacute ydieresis -15
+KPX ecaron g -15
+KPX ecaron gbreve -15
+KPX ecaron gcommaaccent -15
+KPX ecaron v -25
+KPX ecaron w -25
+KPX ecaron x -15
+KPX ecaron y -15
+KPX ecaron yacute -15
+KPX ecaron ydieresis -15
+KPX ecircumflex g -15
+KPX ecircumflex gbreve -15
+KPX ecircumflex gcommaaccent -15
+KPX ecircumflex v -25
+KPX ecircumflex w -25
+KPX ecircumflex x -15
+KPX ecircumflex y -15
+KPX ecircumflex yacute -15
+KPX ecircumflex ydieresis -15
+KPX edieresis g -15
+KPX edieresis gbreve -15
+KPX edieresis gcommaaccent -15
+KPX edieresis v -25
+KPX edieresis w -25
+KPX edieresis x -15
+KPX edieresis y -15
+KPX edieresis yacute -15
+KPX edieresis ydieresis -15
+KPX edotaccent g -15
+KPX edotaccent gbreve -15
+KPX edotaccent gcommaaccent -15
+KPX edotaccent v -25
+KPX edotaccent w -25
+KPX edotaccent x -15
+KPX edotaccent y -15
+KPX edotaccent yacute -15
+KPX edotaccent ydieresis -15
+KPX egrave g -15
+KPX egrave gbreve -15
+KPX egrave gcommaaccent -15
+KPX egrave v -25
+KPX egrave w -25
+KPX egrave x -15
+KPX egrave y -15
+KPX egrave yacute -15
+KPX egrave ydieresis -15
+KPX emacron g -15
+KPX emacron gbreve -15
+KPX emacron gcommaaccent -15
+KPX emacron v -25
+KPX emacron w -25
+KPX emacron x -15
+KPX emacron y -15
+KPX emacron yacute -15
+KPX emacron ydieresis -15
+KPX eogonek g -15
+KPX eogonek gbreve -15
+KPX eogonek gcommaaccent -15
+KPX eogonek v -25
+KPX eogonek w -25
+KPX eogonek x -15
+KPX eogonek y -15
+KPX eogonek yacute -15
+KPX eogonek ydieresis -15
+KPX f a -10
+KPX f aacute -10
+KPX f abreve -10
+KPX f acircumflex -10
+KPX f adieresis -10
+KPX f agrave -10
+KPX f amacron -10
+KPX f aogonek -10
+KPX f aring -10
+KPX f atilde -10
+KPX f dotlessi -50
+KPX f f -25
+KPX f i -20
+KPX f iacute -20
+KPX f quoteright 55
+KPX g a -5
+KPX g aacute -5
+KPX g abreve -5
+KPX g acircumflex -5
+KPX g adieresis -5
+KPX g agrave -5
+KPX g amacron -5
+KPX g aogonek -5
+KPX g aring -5
+KPX g atilde -5
+KPX gbreve a -5
+KPX gbreve aacute -5
+KPX gbreve abreve -5
+KPX gbreve acircumflex -5
+KPX gbreve adieresis -5
+KPX gbreve agrave -5
+KPX gbreve amacron -5
+KPX gbreve aogonek -5
+KPX gbreve aring -5
+KPX gbreve atilde -5
+KPX gcommaaccent a -5
+KPX gcommaaccent aacute -5
+KPX gcommaaccent abreve -5
+KPX gcommaaccent acircumflex -5
+KPX gcommaaccent adieresis -5
+KPX gcommaaccent agrave -5
+KPX gcommaaccent amacron -5
+KPX gcommaaccent aogonek -5
+KPX gcommaaccent aring -5
+KPX gcommaaccent atilde -5
+KPX h y -5
+KPX h yacute -5
+KPX h ydieresis -5
+KPX i v -25
+KPX iacute v -25
+KPX icircumflex v -25
+KPX idieresis v -25
+KPX igrave v -25
+KPX imacron v -25
+KPX iogonek v -25
+KPX k e -10
+KPX k eacute -10
+KPX k ecaron -10
+KPX k ecircumflex -10
+KPX k edieresis -10
+KPX k edotaccent -10
+KPX k egrave -10
+KPX k emacron -10
+KPX k eogonek -10
+KPX k o -10
+KPX k oacute -10
+KPX k ocircumflex -10
+KPX k odieresis -10
+KPX k ograve -10
+KPX k ohungarumlaut -10
+KPX k omacron -10
+KPX k oslash -10
+KPX k otilde -10
+KPX k y -15
+KPX k yacute -15
+KPX k ydieresis -15
+KPX kcommaaccent e -10
+KPX kcommaaccent eacute -10
+KPX kcommaaccent ecaron -10
+KPX kcommaaccent ecircumflex -10
+KPX kcommaaccent edieresis -10
+KPX kcommaaccent edotaccent -10
+KPX kcommaaccent egrave -10
+KPX kcommaaccent emacron -10
+KPX kcommaaccent eogonek -10
+KPX kcommaaccent o -10
+KPX kcommaaccent oacute -10
+KPX kcommaaccent ocircumflex -10
+KPX kcommaaccent odieresis -10
+KPX kcommaaccent ograve -10
+KPX kcommaaccent ohungarumlaut -10
+KPX kcommaaccent omacron -10
+KPX kcommaaccent oslash -10
+KPX kcommaaccent otilde -10
+KPX kcommaaccent y -15
+KPX kcommaaccent yacute -15
+KPX kcommaaccent ydieresis -15
+KPX l w -10
+KPX lacute w -10
+KPX lcommaaccent w -10
+KPX lslash w -10
+KPX n v -40
+KPX n y -15
+KPX n yacute -15
+KPX n ydieresis -15
+KPX nacute v -40
+KPX nacute y -15
+KPX nacute yacute -15
+KPX nacute ydieresis -15
+KPX ncaron v -40
+KPX ncaron y -15
+KPX ncaron yacute -15
+KPX ncaron ydieresis -15
+KPX ncommaaccent v -40
+KPX ncommaaccent y -15
+KPX ncommaaccent yacute -15
+KPX ncommaaccent ydieresis -15
+KPX ntilde v -40
+KPX ntilde y -15
+KPX ntilde yacute -15
+KPX ntilde ydieresis -15
+KPX o v -15
+KPX o w -25
+KPX o y -10
+KPX o yacute -10
+KPX o ydieresis -10
+KPX oacute v -15
+KPX oacute w -25
+KPX oacute y -10
+KPX oacute yacute -10
+KPX oacute ydieresis -10
+KPX ocircumflex v -15
+KPX ocircumflex w -25
+KPX ocircumflex y -10
+KPX ocircumflex yacute -10
+KPX ocircumflex ydieresis -10
+KPX odieresis v -15
+KPX odieresis w -25
+KPX odieresis y -10
+KPX odieresis yacute -10
+KPX odieresis ydieresis -10
+KPX ograve v -15
+KPX ograve w -25
+KPX ograve y -10
+KPX ograve yacute -10
+KPX ograve ydieresis -10
+KPX ohungarumlaut v -15
+KPX ohungarumlaut w -25
+KPX ohungarumlaut y -10
+KPX ohungarumlaut yacute -10
+KPX ohungarumlaut ydieresis -10
+KPX omacron v -15
+KPX omacron w -25
+KPX omacron y -10
+KPX omacron yacute -10
+KPX omacron ydieresis -10
+KPX oslash v -15
+KPX oslash w -25
+KPX oslash y -10
+KPX oslash yacute -10
+KPX oslash ydieresis -10
+KPX otilde v -15
+KPX otilde w -25
+KPX otilde y -10
+KPX otilde yacute -10
+KPX otilde ydieresis -10
+KPX p y -10
+KPX p yacute -10
+KPX p ydieresis -10
+KPX period quotedblright -70
+KPX period quoteright -70
+KPX quotedblleft A -80
+KPX quotedblleft Aacute -80
+KPX quotedblleft Abreve -80
+KPX quotedblleft Acircumflex -80
+KPX quotedblleft Adieresis -80
+KPX quotedblleft Agrave -80
+KPX quotedblleft Amacron -80
+KPX quotedblleft Aogonek -80
+KPX quotedblleft Aring -80
+KPX quotedblleft Atilde -80
+KPX quoteleft A -80
+KPX quoteleft Aacute -80
+KPX quoteleft Abreve -80
+KPX quoteleft Acircumflex -80
+KPX quoteleft Adieresis -80
+KPX quoteleft Agrave -80
+KPX quoteleft Amacron -80
+KPX quoteleft Aogonek -80
+KPX quoteleft Aring -80
+KPX quoteleft Atilde -80
+KPX quoteleft quoteleft -74
+KPX quoteright d -50
+KPX quoteright dcroat -50
+KPX quoteright l -10
+KPX quoteright lacute -10
+KPX quoteright lcommaaccent -10
+KPX quoteright lslash -10
+KPX quoteright quoteright -74
+KPX quoteright r -50
+KPX quoteright racute -50
+KPX quoteright rcaron -50
+KPX quoteright rcommaaccent -50
+KPX quoteright s -55
+KPX quoteright sacute -55
+KPX quoteright scaron -55
+KPX quoteright scedilla -55
+KPX quoteright scommaaccent -55
+KPX quoteright space -74
+KPX quoteright t -18
+KPX quoteright tcommaaccent -18
+KPX quoteright v -50
+KPX r comma -40
+KPX r g -18
+KPX r gbreve -18
+KPX r gcommaaccent -18
+KPX r hyphen -20
+KPX r period -55
+KPX racute comma -40
+KPX racute g -18
+KPX racute gbreve -18
+KPX racute gcommaaccent -18
+KPX racute hyphen -20
+KPX racute period -55
+KPX rcaron comma -40
+KPX rcaron g -18
+KPX rcaron gbreve -18
+KPX rcaron gcommaaccent -18
+KPX rcaron hyphen -20
+KPX rcaron period -55
+KPX rcommaaccent comma -40
+KPX rcommaaccent g -18
+KPX rcommaaccent gbreve -18
+KPX rcommaaccent gcommaaccent -18
+KPX rcommaaccent hyphen -20
+KPX rcommaaccent period -55
+KPX space A -55
+KPX space Aacute -55
+KPX space Abreve -55
+KPX space Acircumflex -55
+KPX space Adieresis -55
+KPX space Agrave -55
+KPX space Amacron -55
+KPX space Aogonek -55
+KPX space Aring -55
+KPX space Atilde -55
+KPX space T -18
+KPX space Tcaron -18
+KPX space Tcommaaccent -18
+KPX space V -50
+KPX space W -30
+KPX space Y -90
+KPX space Yacute -90
+KPX space Ydieresis -90
+KPX v a -25
+KPX v aacute -25
+KPX v abreve -25
+KPX v acircumflex -25
+KPX v adieresis -25
+KPX v agrave -25
+KPX v amacron -25
+KPX v aogonek -25
+KPX v aring -25
+KPX v atilde -25
+KPX v comma -65
+KPX v e -15
+KPX v eacute -15
+KPX v ecaron -15
+KPX v ecircumflex -15
+KPX v edieresis -15
+KPX v edotaccent -15
+KPX v egrave -15
+KPX v emacron -15
+KPX v eogonek -15
+KPX v o -20
+KPX v oacute -20
+KPX v ocircumflex -20
+KPX v odieresis -20
+KPX v ograve -20
+KPX v ohungarumlaut -20
+KPX v omacron -20
+KPX v oslash -20
+KPX v otilde -20
+KPX v period -65
+KPX w a -10
+KPX w aacute -10
+KPX w abreve -10
+KPX w acircumflex -10
+KPX w adieresis -10
+KPX w agrave -10
+KPX w amacron -10
+KPX w aogonek -10
+KPX w aring -10
+KPX w atilde -10
+KPX w comma -65
+KPX w o -10
+KPX w oacute -10
+KPX w ocircumflex -10
+KPX w odieresis -10
+KPX w ograve -10
+KPX w ohungarumlaut -10
+KPX w omacron -10
+KPX w oslash -10
+KPX w otilde -10
+KPX w period -65
+KPX x e -15
+KPX x eacute -15
+KPX x ecaron -15
+KPX x ecircumflex -15
+KPX x edieresis -15
+KPX x edotaccent -15
+KPX x egrave -15
+KPX x emacron -15
+KPX x eogonek -15
+KPX y comma -65
+KPX y period -65
+KPX yacute comma -65
+KPX yacute period -65
+KPX ydieresis comma -65
+KPX ydieresis period -65
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/ZapfDingbats.afm b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/ZapfDingbats.afm
new file mode 100644
index 0000000000000000000000000000000000000000..b2745053e4086c1a5ea7b74e460e62526b64009a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/ZapfDingbats.afm
@@ -0,0 +1,225 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 15:14:13 1997
+Comment UniqueID 43082
+Comment VMusage 45775 55535
+FontName ZapfDingbats
+FullName ITC Zapf Dingbats
+FamilyName ZapfDingbats
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet Special
+FontBBox -1 -143 981 820
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. All Rights Reserved.ITC Zapf Dingbats is a registered trademark of International Typeface Corporation.
+EncodingScheme FontSpecific
+StdHW 28
+StdVW 90
+StartCharMetrics 202
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 974 ; N a1 ; B 35 72 939 621 ;
+C 34 ; WX 961 ; N a2 ; B 35 81 927 611 ;
+C 35 ; WX 974 ; N a202 ; B 35 72 939 621 ;
+C 36 ; WX 980 ; N a3 ; B 35 0 945 692 ;
+C 37 ; WX 719 ; N a4 ; B 34 139 685 566 ;
+C 38 ; WX 789 ; N a5 ; B 35 -14 755 705 ;
+C 39 ; WX 790 ; N a119 ; B 35 -14 755 705 ;
+C 40 ; WX 791 ; N a118 ; B 35 -13 761 705 ;
+C 41 ; WX 690 ; N a117 ; B 34 138 655 553 ;
+C 42 ; WX 960 ; N a11 ; B 35 123 925 568 ;
+C 43 ; WX 939 ; N a12 ; B 35 134 904 559 ;
+C 44 ; WX 549 ; N a13 ; B 29 -11 516 705 ;
+C 45 ; WX 855 ; N a14 ; B 34 59 820 632 ;
+C 46 ; WX 911 ; N a15 ; B 35 50 876 642 ;
+C 47 ; WX 933 ; N a16 ; B 35 139 899 550 ;
+C 48 ; WX 911 ; N a105 ; B 35 50 876 642 ;
+C 49 ; WX 945 ; N a17 ; B 35 139 909 553 ;
+C 50 ; WX 974 ; N a18 ; B 35 104 938 587 ;
+C 51 ; WX 755 ; N a19 ; B 34 -13 721 705 ;
+C 52 ; WX 846 ; N a20 ; B 36 -14 811 705 ;
+C 53 ; WX 762 ; N a21 ; B 35 0 727 692 ;
+C 54 ; WX 761 ; N a22 ; B 35 0 727 692 ;
+C 55 ; WX 571 ; N a23 ; B -1 -68 571 661 ;
+C 56 ; WX 677 ; N a24 ; B 36 -13 642 705 ;
+C 57 ; WX 763 ; N a25 ; B 35 0 728 692 ;
+C 58 ; WX 760 ; N a26 ; B 35 0 726 692 ;
+C 59 ; WX 759 ; N a27 ; B 35 0 725 692 ;
+C 60 ; WX 754 ; N a28 ; B 35 0 720 692 ;
+C 61 ; WX 494 ; N a6 ; B 35 0 460 692 ;
+C 62 ; WX 552 ; N a7 ; B 35 0 517 692 ;
+C 63 ; WX 537 ; N a8 ; B 35 0 503 692 ;
+C 64 ; WX 577 ; N a9 ; B 35 96 542 596 ;
+C 65 ; WX 692 ; N a10 ; B 35 -14 657 705 ;
+C 66 ; WX 786 ; N a29 ; B 35 -14 751 705 ;
+C 67 ; WX 788 ; N a30 ; B 35 -14 752 705 ;
+C 68 ; WX 788 ; N a31 ; B 35 -14 753 705 ;
+C 69 ; WX 790 ; N a32 ; B 35 -14 756 705 ;
+C 70 ; WX 793 ; N a33 ; B 35 -13 759 705 ;
+C 71 ; WX 794 ; N a34 ; B 35 -13 759 705 ;
+C 72 ; WX 816 ; N a35 ; B 35 -14 782 705 ;
+C 73 ; WX 823 ; N a36 ; B 35 -14 787 705 ;
+C 74 ; WX 789 ; N a37 ; B 35 -14 754 705 ;
+C 75 ; WX 841 ; N a38 ; B 35 -14 807 705 ;
+C 76 ; WX 823 ; N a39 ; B 35 -14 789 705 ;
+C 77 ; WX 833 ; N a40 ; B 35 -14 798 705 ;
+C 78 ; WX 816 ; N a41 ; B 35 -13 782 705 ;
+C 79 ; WX 831 ; N a42 ; B 35 -14 796 705 ;
+C 80 ; WX 923 ; N a43 ; B 35 -14 888 705 ;
+C 81 ; WX 744 ; N a44 ; B 35 0 710 692 ;
+C 82 ; WX 723 ; N a45 ; B 35 0 688 692 ;
+C 83 ; WX 749 ; N a46 ; B 35 0 714 692 ;
+C 84 ; WX 790 ; N a47 ; B 34 -14 756 705 ;
+C 85 ; WX 792 ; N a48 ; B 35 -14 758 705 ;
+C 86 ; WX 695 ; N a49 ; B 35 -14 661 706 ;
+C 87 ; WX 776 ; N a50 ; B 35 -6 741 699 ;
+C 88 ; WX 768 ; N a51 ; B 35 -7 734 699 ;
+C 89 ; WX 792 ; N a52 ; B 35 -14 757 705 ;
+C 90 ; WX 759 ; N a53 ; B 35 0 725 692 ;
+C 91 ; WX 707 ; N a54 ; B 35 -13 672 704 ;
+C 92 ; WX 708 ; N a55 ; B 35 -14 672 705 ;
+C 93 ; WX 682 ; N a56 ; B 35 -14 647 705 ;
+C 94 ; WX 701 ; N a57 ; B 35 -14 666 705 ;
+C 95 ; WX 826 ; N a58 ; B 35 -14 791 705 ;
+C 96 ; WX 815 ; N a59 ; B 35 -14 780 705 ;
+C 97 ; WX 789 ; N a60 ; B 35 -14 754 705 ;
+C 98 ; WX 789 ; N a61 ; B 35 -14 754 705 ;
+C 99 ; WX 707 ; N a62 ; B 34 -14 673 705 ;
+C 100 ; WX 687 ; N a63 ; B 36 0 651 692 ;
+C 101 ; WX 696 ; N a64 ; B 35 0 661 691 ;
+C 102 ; WX 689 ; N a65 ; B 35 0 655 692 ;
+C 103 ; WX 786 ; N a66 ; B 34 -14 751 705 ;
+C 104 ; WX 787 ; N a67 ; B 35 -14 752 705 ;
+C 105 ; WX 713 ; N a68 ; B 35 -14 678 705 ;
+C 106 ; WX 791 ; N a69 ; B 35 -14 756 705 ;
+C 107 ; WX 785 ; N a70 ; B 36 -14 751 705 ;
+C 108 ; WX 791 ; N a71 ; B 35 -14 757 705 ;
+C 109 ; WX 873 ; N a72 ; B 35 -14 838 705 ;
+C 110 ; WX 761 ; N a73 ; B 35 0 726 692 ;
+C 111 ; WX 762 ; N a74 ; B 35 0 727 692 ;
+C 112 ; WX 762 ; N a203 ; B 35 0 727 692 ;
+C 113 ; WX 759 ; N a75 ; B 35 0 725 692 ;
+C 114 ; WX 759 ; N a204 ; B 35 0 725 692 ;
+C 115 ; WX 892 ; N a76 ; B 35 0 858 705 ;
+C 116 ; WX 892 ; N a77 ; B 35 -14 858 692 ;
+C 117 ; WX 788 ; N a78 ; B 35 -14 754 705 ;
+C 118 ; WX 784 ; N a79 ; B 35 -14 749 705 ;
+C 119 ; WX 438 ; N a81 ; B 35 -14 403 705 ;
+C 120 ; WX 138 ; N a82 ; B 35 0 104 692 ;
+C 121 ; WX 277 ; N a83 ; B 35 0 242 692 ;
+C 122 ; WX 415 ; N a84 ; B 35 0 380 692 ;
+C 123 ; WX 392 ; N a97 ; B 35 263 357 705 ;
+C 124 ; WX 392 ; N a98 ; B 34 263 357 705 ;
+C 125 ; WX 668 ; N a99 ; B 35 263 633 705 ;
+C 126 ; WX 668 ; N a100 ; B 36 263 634 705 ;
+C 128 ; WX 390 ; N a89 ; B 35 -14 356 705 ;
+C 129 ; WX 390 ; N a90 ; B 35 -14 355 705 ;
+C 130 ; WX 317 ; N a93 ; B 35 0 283 692 ;
+C 131 ; WX 317 ; N a94 ; B 35 0 283 692 ;
+C 132 ; WX 276 ; N a91 ; B 35 0 242 692 ;
+C 133 ; WX 276 ; N a92 ; B 35 0 242 692 ;
+C 134 ; WX 509 ; N a205 ; B 35 0 475 692 ;
+C 135 ; WX 509 ; N a85 ; B 35 0 475 692 ;
+C 136 ; WX 410 ; N a206 ; B 35 0 375 692 ;
+C 137 ; WX 410 ; N a86 ; B 35 0 375 692 ;
+C 138 ; WX 234 ; N a87 ; B 35 -14 199 705 ;
+C 139 ; WX 234 ; N a88 ; B 35 -14 199 705 ;
+C 140 ; WX 334 ; N a95 ; B 35 0 299 692 ;
+C 141 ; WX 334 ; N a96 ; B 35 0 299 692 ;
+C 161 ; WX 732 ; N a101 ; B 35 -143 697 806 ;
+C 162 ; WX 544 ; N a102 ; B 56 -14 488 706 ;
+C 163 ; WX 544 ; N a103 ; B 34 -14 508 705 ;
+C 164 ; WX 910 ; N a104 ; B 35 40 875 651 ;
+C 165 ; WX 667 ; N a106 ; B 35 -14 633 705 ;
+C 166 ; WX 760 ; N a107 ; B 35 -14 726 705 ;
+C 167 ; WX 760 ; N a108 ; B 0 121 758 569 ;
+C 168 ; WX 776 ; N a112 ; B 35 0 741 705 ;
+C 169 ; WX 595 ; N a111 ; B 34 -14 560 705 ;
+C 170 ; WX 694 ; N a110 ; B 35 -14 659 705 ;
+C 171 ; WX 626 ; N a109 ; B 34 0 591 705 ;
+C 172 ; WX 788 ; N a120 ; B 35 -14 754 705 ;
+C 173 ; WX 788 ; N a121 ; B 35 -14 754 705 ;
+C 174 ; WX 788 ; N a122 ; B 35 -14 754 705 ;
+C 175 ; WX 788 ; N a123 ; B 35 -14 754 705 ;
+C 176 ; WX 788 ; N a124 ; B 35 -14 754 705 ;
+C 177 ; WX 788 ; N a125 ; B 35 -14 754 705 ;
+C 178 ; WX 788 ; N a126 ; B 35 -14 754 705 ;
+C 179 ; WX 788 ; N a127 ; B 35 -14 754 705 ;
+C 180 ; WX 788 ; N a128 ; B 35 -14 754 705 ;
+C 181 ; WX 788 ; N a129 ; B 35 -14 754 705 ;
+C 182 ; WX 788 ; N a130 ; B 35 -14 754 705 ;
+C 183 ; WX 788 ; N a131 ; B 35 -14 754 705 ;
+C 184 ; WX 788 ; N a132 ; B 35 -14 754 705 ;
+C 185 ; WX 788 ; N a133 ; B 35 -14 754 705 ;
+C 186 ; WX 788 ; N a134 ; B 35 -14 754 705 ;
+C 187 ; WX 788 ; N a135 ; B 35 -14 754 705 ;
+C 188 ; WX 788 ; N a136 ; B 35 -14 754 705 ;
+C 189 ; WX 788 ; N a137 ; B 35 -14 754 705 ;
+C 190 ; WX 788 ; N a138 ; B 35 -14 754 705 ;
+C 191 ; WX 788 ; N a139 ; B 35 -14 754 705 ;
+C 192 ; WX 788 ; N a140 ; B 35 -14 754 705 ;
+C 193 ; WX 788 ; N a141 ; B 35 -14 754 705 ;
+C 194 ; WX 788 ; N a142 ; B 35 -14 754 705 ;
+C 195 ; WX 788 ; N a143 ; B 35 -14 754 705 ;
+C 196 ; WX 788 ; N a144 ; B 35 -14 754 705 ;
+C 197 ; WX 788 ; N a145 ; B 35 -14 754 705 ;
+C 198 ; WX 788 ; N a146 ; B 35 -14 754 705 ;
+C 199 ; WX 788 ; N a147 ; B 35 -14 754 705 ;
+C 200 ; WX 788 ; N a148 ; B 35 -14 754 705 ;
+C 201 ; WX 788 ; N a149 ; B 35 -14 754 705 ;
+C 202 ; WX 788 ; N a150 ; B 35 -14 754 705 ;
+C 203 ; WX 788 ; N a151 ; B 35 -14 754 705 ;
+C 204 ; WX 788 ; N a152 ; B 35 -14 754 705 ;
+C 205 ; WX 788 ; N a153 ; B 35 -14 754 705 ;
+C 206 ; WX 788 ; N a154 ; B 35 -14 754 705 ;
+C 207 ; WX 788 ; N a155 ; B 35 -14 754 705 ;
+C 208 ; WX 788 ; N a156 ; B 35 -14 754 705 ;
+C 209 ; WX 788 ; N a157 ; B 35 -14 754 705 ;
+C 210 ; WX 788 ; N a158 ; B 35 -14 754 705 ;
+C 211 ; WX 788 ; N a159 ; B 35 -14 754 705 ;
+C 212 ; WX 894 ; N a160 ; B 35 58 860 634 ;
+C 213 ; WX 838 ; N a161 ; B 35 152 803 540 ;
+C 214 ; WX 1016 ; N a163 ; B 34 152 981 540 ;
+C 215 ; WX 458 ; N a164 ; B 35 -127 422 820 ;
+C 216 ; WX 748 ; N a196 ; B 35 94 698 597 ;
+C 217 ; WX 924 ; N a165 ; B 35 140 890 552 ;
+C 218 ; WX 748 ; N a192 ; B 35 94 698 597 ;
+C 219 ; WX 918 ; N a166 ; B 35 166 884 526 ;
+C 220 ; WX 927 ; N a167 ; B 35 32 892 660 ;
+C 221 ; WX 928 ; N a168 ; B 35 129 891 562 ;
+C 222 ; WX 928 ; N a169 ; B 35 128 893 563 ;
+C 223 ; WX 834 ; N a170 ; B 35 155 799 537 ;
+C 224 ; WX 873 ; N a171 ; B 35 93 838 599 ;
+C 225 ; WX 828 ; N a172 ; B 35 104 791 588 ;
+C 226 ; WX 924 ; N a173 ; B 35 98 889 594 ;
+C 227 ; WX 924 ; N a162 ; B 35 98 889 594 ;
+C 228 ; WX 917 ; N a174 ; B 35 0 882 692 ;
+C 229 ; WX 930 ; N a175 ; B 35 84 896 608 ;
+C 230 ; WX 931 ; N a176 ; B 35 84 896 608 ;
+C 231 ; WX 463 ; N a177 ; B 35 -99 429 791 ;
+C 232 ; WX 883 ; N a178 ; B 35 71 848 623 ;
+C 233 ; WX 836 ; N a179 ; B 35 44 802 648 ;
+C 234 ; WX 836 ; N a193 ; B 35 44 802 648 ;
+C 235 ; WX 867 ; N a180 ; B 35 101 832 591 ;
+C 236 ; WX 867 ; N a199 ; B 35 101 832 591 ;
+C 237 ; WX 696 ; N a181 ; B 35 44 661 648 ;
+C 238 ; WX 696 ; N a200 ; B 35 44 661 648 ;
+C 239 ; WX 874 ; N a182 ; B 35 77 840 619 ;
+C 241 ; WX 874 ; N a201 ; B 35 73 840 615 ;
+C 242 ; WX 760 ; N a183 ; B 35 0 725 692 ;
+C 243 ; WX 946 ; N a184 ; B 35 160 911 533 ;
+C 244 ; WX 771 ; N a197 ; B 34 37 736 655 ;
+C 245 ; WX 865 ; N a185 ; B 35 207 830 481 ;
+C 246 ; WX 771 ; N a194 ; B 34 37 736 655 ;
+C 247 ; WX 888 ; N a198 ; B 34 -19 853 712 ;
+C 248 ; WX 967 ; N a186 ; B 35 124 932 568 ;
+C 249 ; WX 888 ; N a195 ; B 34 -19 853 712 ;
+C 250 ; WX 831 ; N a187 ; B 35 113 796 579 ;
+C 251 ; WX 873 ; N a188 ; B 36 118 838 578 ;
+C 252 ; WX 927 ; N a189 ; B 35 150 891 542 ;
+C 253 ; WX 970 ; N a190 ; B 35 76 931 616 ;
+C 254 ; WX 918 ; N a191 ; B 34 99 884 593 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/readme.txt b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/readme.txt
new file mode 100644
index 0000000000000000000000000000000000000000..047ae70a17540bae9a246cb206480a1760d820b2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/readme.txt
@@ -0,0 +1,15 @@
+Font Metrics for the 14 PDF Core Fonts
+======================================
+
+This directory contains font metrics for the 14 PDF Core Fonts,
+downloaded from Adobe. The title and this paragraph were added by
+Matplotlib developers. The download URL was
+.
+
+This file and the 14 PostScript(R) AFM files it accompanies may be used, copied,
+and distributed for any purpose and without charge, with or without modification,
+provided that all copyright notices are retained; that the AFM files are not
+distributed without this file; that all modifications to this file or any of
+the AFM files are prominently noted in the modified file(s); and that this
+paragraph is not modified. Adobe Systems has no responsibility or obligation
+to support the use of the AFM files.
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..39699bd84ae17507955ce571caa859ad14997f4e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b184b89e3c1075f22f6b71575b6fc20d4972b3cfd3b23322ca6fd596dcaef167
+size 704128
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..cfd64ecefe8e5cdf2a0ea7d128019650a68ee682
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6edf0283160186af451cbee71e7b845f2e4cabf264bb992ce668c83c25465e6f
+size 641720
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..4ddf1fabb8648ac811ba05df9980121ec6a36452
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ccdf74b350f11fd3dd5774de50e5e6346a1a5da1f5b7d5fb83590665e97a5213
+size 633840
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..f47aa8cffd396d3ac221ad7a2a518bc9a554c9ce
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3fdf69cabf06049ea70a00b5919340e2ce1e6d02b0cc3c4b44fb6801bd1e0d22
+size 756072
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..36758a2e70b6eb05ef8f8694828ed0eb157be3d3
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..fb4675de56d8c2b2b09734578191896c7bf6948e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:baada9a5172fe20886251aff0433fc38461912d5daf07287e7bee56620a8da96
+size 331536
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..ee85d0dc6915489b236e932ab89838b200443aba
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a69081c15c76c827e0a27a5a7f5c74b6135c843499955495ffa8c20d3a98288b
+size 253116
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..ba2147393770bbb669b1a6c735190f3cf6e732a8
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:28052813f7a709fc89f52d192dc995ef4f0fdc5c3d7b73a49d6849b1916d0cd0
+size 251472
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..60f42148ea53bf551fc77faef9d6382ff0efcb16
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:602ec86b8948cfcd956482fe64f94c36c867770149ef2f791d4613f443bcecb3
+size 340240
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..0137479cb3e30823976d9137d9e75ed8e225c57f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c3753f2ed6bc673f15846dc45addbeb3b9c872f32fb18fd53a21f1bef1ed7676
+size 355692
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..b89793b3fe00a3510c0a22b54d9ee7735e5e0803
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d93efec7a9d2e826768d1a2ee95b95870e15e29599a84f3484af1de1cec2e181
+size 347064
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..c1a70e8374879a0c04e370eb5c57861c4fd5abce
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3e7994fbc54fa10ce3352a42d548fadd7d9cadb69cb1109bc9d960f6dac57f04
+size 345612
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..73a4204e08a49a83ab7336cb55add8b32200ae24
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:107244956e9962b9e96faccdc551825e0ae0898ae13737133e1b921a2fd35ffa
+size 379740
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..1623714d223f2b4ae5460269cf3afa34e91dd811
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/LICENSE_DEJAVU b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/LICENSE_DEJAVU
new file mode 100644
index 0000000000000000000000000000000000000000..254e2cc42a6d0135cccf2047768b9e44f25e666f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/LICENSE_DEJAVU
@@ -0,0 +1,99 @@
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute the
+Font Software, including without limitation the rights to use, copy, merge,
+publish, distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright and trademark notices and this permission notice shall
+be included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional glyphs or characters may be added to the Fonts, only if the fonts
+are renamed to names not containing either the words "Bitstream" or the word
+"Vera".
+
+This License becomes null and void to the extent applicable to Fonts or Font
+Software that has been modified and is distributed under the "Bitstream
+Vera" names.
+
+The Font Software may be sold as part of a larger software package but no
+copy of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
+ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+associated documentation files (the "Font Software"), to reproduce
+and distribute the modifications to the Bitstream Vera Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Tavmjong Bah" or the word "Arev".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Tavmjong Bah Arev" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/LICENSE_STIX b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/LICENSE_STIX
new file mode 100644
index 0000000000000000000000000000000000000000..6034d94748146993f78c8059c345adafc860317b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/LICENSE_STIX
@@ -0,0 +1,124 @@
+The STIX fonts distributed with matplotlib have been modified from
+their canonical form. They have been converted from OTF to TTF format
+using Fontforge and this script:
+
+ #!/usr/bin/env fontforge
+ i=1
+ while ( i<$argc )
+ Open($argv[i])
+ Generate($argv[i]:r + ".ttf")
+ i = i+1
+ endloop
+
+The original STIX Font License begins below.
+
+-----------------------------------------------------------
+
+STIX Font License
+
+24 May 2010
+
+Copyright (c) 2001-2010 by the STI Pub Companies, consisting of the American
+Institute of Physics, the American Chemical Society, the American Mathematical
+Society, the American Physical Society, Elsevier, Inc., and The Institute of
+Electrical and Electronic Engineers, Inc. (www.stixfonts.org), with Reserved
+Font Name STIX Fonts, STIX Fonts (TM) is a trademark of The Institute of
+Electrical and Electronics Engineers, Inc.
+
+Portions copyright (c) 1998-2003 by MicroPress, Inc. (www.micropress-inc.com),
+with Reserved Font Name TM Math. To obtain additional mathematical fonts, please
+contact MicroPress, Inc., 68-30 Harrow Street, Forest Hills, NY 11375, USA,
+Phone: (718) 575-1816.
+
+Portions copyright (c) 1990 by Elsevier, Inc.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..cbf159757c5c0a47f1d8f2b984f0c20f52b7e25b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:167378031e2dddc6216d67819c9260e9a06ffc4c478e4e23cb98a6fd44b183c2
+size 448228
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..64ca51419ec522ee78c91e6f36f57fdb3bde290b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e8533dc7083fa346bda1933d60ea4a83b67d0945bceaf1b3541f82b4a0e2c6a0
+size 237360
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..585e03c626a96775f018e690fc6e10f6cd3e4047
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:98788fd4ba48dfbb2bd026c0e20a247a8b06c7372879628b7a6bb0d5bb09736c
+size 181152
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..67ce0a807f6ae1dc2b4f7b863ce99620d29e6cb7
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6cfcb333d22b7c3c623bdfd40174f14c85c3d6731ca6166c1edc80140eae8527
+size 175040
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..a70c797d092dee1819ec9dcdffa2e6c62b92e12b
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..ccbd9a6011694d06644eb3e4a97f377145eebf19
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..e75e09e6c0c376e117789e9287d1a95de9acb009
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..c27d42fb250a5881efeccbf9c9dabca18efe958b
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..f81717ec1d0c057dcf1be2b9ac4e282fdfbe7987
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..855dec982374b3f452120cbc47e5a892ef51c02b
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..f955ca2ad1ba36cd36e185671843448854d517dc
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..6ffd35778fd8eebf304a4842edd92ee49398567a
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..01ed101cd477dfb112616440bdb6a8a54a30cb67
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..1ccf365e8f1635df3d0672a92c416dca0e8ef033
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..bf2b66af7620441b260a339358adf5c9628a4d4e
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..bd5f93f0e48d5d993b349c160cb2efac071e8599
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..73b9930f6cf7388f3337355ffc5cc0035c053e34
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmb10.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmb10.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..189bdf0f0a01c2fb07e436ee4e348ae681b5dd97
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmb10.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmex10.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmex10.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..e4b468dcd37369fba8329f015fa644d9053848da
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmex10.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmmi10.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmmi10.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..8d326610412db20c62cd2f9aa0cfc06f334ab648
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmmi10.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmr10.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmr10.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..8bc44966a84421629220f03a87d1cf479733d2aa
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmr10.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmss10.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmss10.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..ef70532c71098c1ebb1e1666ae89ecc70ac8708a
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmss10.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..45d8421a5b118b1f49132337f80fb7730521483a
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmtt10.ttf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmtt10.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..15bdb68b3f7165c68547949e0b2c0263a7438add
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmtt10.ttf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back-symbolic.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0c2d653cbe8f9047f989226d5a00f7fd74ac0695
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back-symbolic.svg
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..79709d8f435ef2031ee4129b2180e725dd1a8d21
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back.pdf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3c4b5815487bc1e5fef303915d494e3aaf015d4
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0c2d653cbe8f9047f989226d5a00f7fd74ac0695
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back.svg
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..e44a70a9cd230e86135e1f27492fc649bc464f00
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/back_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave-symbolic.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..856721b6b5e2b671f28838a9e3569bc22b38215e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave-symbolic.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..794a1152f602306c0326b0bac9b194323c521d83
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave.pdf
@@ -0,0 +1,70 @@
+%PDF-1.4
+%† Ǽ
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+10 0 obj
+<< /Annots [ ] /Contents 9 0 R
+/Group << /CS /DeviceRGB /S /Transparency /Type /Group >>
+/MediaBox [ 0 0 72 72 ] /Parent 2 0 R /Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 11 0 R >>
+stream
+xÚu”Kn1D÷:…N@‹©Ï2A€Ùç
+
ljÑÉÆ@|ý”¤Ö§£ÛoÄb‘"‡í«yúÄöåÍ:ûŠ¿wËöaŸ¾<ÿþq<}|¶Ç›qà?Mòxœõ�Ï Øß�ùß^]UõÝÏ$>zµìȧœ2“y¡,ZßN£Ž4¹ÿÓ_ªþ«:™�äb;¸P&—š¤Ê®§)”˜k¬2¥r#‡ñæ�†3yëû¢§ë™bc—“Ã,¶ÕÁÈËÍ«+òc*aT»1&'ÍÞ´
+Z¸šê•-pñðÞ"=�¹\Â-X´œ Æÿaz�Ý«D’¼IT´™ób%ãû\Õ5Qr½ÿZˆ¥�uzõ(ŠT?PTÅùˆRJwåï@®–f Ûüˆ£¬4„JÁôlãWÅA|Z¨šÓ-AðfäHêEÞ‚ðŒm 6ÒLft"†Vë¹Sô+÷È®¾šº\,Áiøv!³²íRfý#ÉF¦™½‹º2–oƒáív#æ§÷ `:lô¤±%ÅN»vÇLEÚµ¡Âз]J=ÒÎö«ÆHôÑ‹a,Ý5c`¿ªf=¿ài0W¥·`5E?Öu!7†x¢2vk’t
þÔÖ±Ó‚®ÅBa1HÒëwgÀÕ½!¿H¦ð3û͘?»\ª
+endstream
+endobj
+11 0 obj
+500
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 0 >>
+/A2 << /CA 1 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 10 0 R ] /Type /Pages >>
+endobj
+12 0 obj
+<< /CreationDate (D:20160601123507-04'00')
+/Creator (matplotlib 1.5.2rc2.post1933.dev0+gb775a26, http://matplotlib.org)
+/Producer (matplotlib pdf backend) >>
+endobj
+xref
+0 13
+0000000000 65535 f
+0000000016 00000 n
+0000001161 00000 n
+0000000978 00000 n
+0000000999 00000 n
+0000001098 00000 n
+0000001119 00000 n
+0000001140 00000 n
+0000000065 00000 n
+0000000383 00000 n
+0000000208 00000 n
+0000000958 00000 n
+0000001221 00000 n
+trailer
+<< /Info 12 0 R /Root 1 0 R /Size 13 >>
+startxref
+1395
+%%EOF
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave.png
new file mode 100644
index 0000000000000000000000000000000000000000..919e40bf58297a9abc727b7fbce9f53cf039ba5e
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave.svg
new file mode 100644
index 0000000000000000000000000000000000000000..856721b6b5e2b671f28838a9e3569bc22b38215e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..a39b55a6166ba51063ad780cb1f4ea09d08dc1a4
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/filesave_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward-symbolic.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..08b6fe174602b0c729b60cfe63af22468798ee95
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward-symbolic.svg
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..ce4a1bcb1321efbc3836dc6358c0e5fe0ecca68d
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward.pdf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward.png
new file mode 100644
index 0000000000000000000000000000000000000000..59400feb49df053526e5c549079dcf0e40298cbd
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward.svg
new file mode 100644
index 0000000000000000000000000000000000000000..08b6fe174602b0c729b60cfe63af22468798ee95
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward.svg
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..de65815bba205d862508648dc1fe0d0ed2b058a3
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/forward_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/hand.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/hand.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..13088169ee504507746571ce23c1686181e40576
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/hand.pdf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/hand.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/hand.png
new file mode 100644
index 0000000000000000000000000000000000000000..d956c5fd72aed98763c20370c025835202484fa8
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/hand.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/hand.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/hand.svg
new file mode 100644
index 0000000000000000000000000000000000000000..28b96a2a9c4ac8abfabe8a0d8ba3d8ff0a62b573
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/hand.svg
@@ -0,0 +1,130 @@
+
+
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help-symbolic.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..260528d607aa59c7fabd5e8645a941064967645f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help-symbolic.svg
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..38178d05b2725addadec9f4c005a1a764f1096d1
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help.pdf
@@ -0,0 +1,68 @@
+%PDF-1.4
+%† Ǽ
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+10 0 obj
+<< /Annots [ ] /Contents 9 0 R
+/Group << /CS /DeviceRGB /S /Transparency /Type /Group >>
+/MediaBox [ 0 0 72 72 ] /Parent 2 0 R /Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 11 0 R >>
+stream
+xÚm“ÍŽS1…÷yŠûžØÎ�³!Íž-bU†�ªÌf$x}Ž�ôÞ^„Ô*Õ×8>>¶y»¦§¼½¾oy»âûgû²}Åùmãíy{úôòûçååóóÇíòž2ø¯ÔÇÍ|n€qüHé
/É|éÑ×ý¥Œ—ÀR*Jb\›nÜ(—¢½âÁ5sX„Z’�;áGL½ ›è’÷j;Ý»%e2éÆz@ ÖÁ÷'´´\ÒA
µû{;Ëc¬à•¢Rá‰v);º¤Côoé(n‡‡{ŠG4•\`r$=´¨ò‡�,(8#¶÷ˆJÚT¿ž‹µ¡ÚLší¥PÕ‘]\¦6œd‚9Æâ’lD X–xÍeò¼Å•vï�†Ã@&Y=vÙrX:„4צ�¸TOzsª¥xZE¹5æ�Ù"cœÞ3B\�‘óœi!ruãnzͲš]²xK&©6»¨ê ™C‰º×ÁX(×0i/V+5É蜣ÁÕ©FÊ0
Ùó-�Á<îU9²eYó›M©´Q©±£Î"Ñ}žBå™’mùãÂpê½¶Vγïîàƒ!XØþÙbf9/„0†T"¸µ$÷�ŒbŒÍŽbëé^ìC±Òìz¹*Œ!½¬…¸3^£?}Y–æp«ø‡WRñKbú\ÁŒðò®]Ô˜m^ñÃ~1Õ¹‰ÿÙ9_Åô=¥¿ñb«
+endstream
+endobj
+11 0 obj
+549
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 0 >>
+/A2 << /CA 1 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 10 0 R ] /Type /Pages >>
+endobj
+12 0 obj
+<< /CreationDate (D:20170812144045-04'00')
+/Creator (matplotlib 2.0.2.post4623.dev0+gcb3aea0db, http://matplotlib.org)
+/Producer (matplotlib pdf backend 2.0.2.post4623.dev0+gcb3aea0db) >>
+endobj
+xref
+0 13
+0000000000 65535 f
+0000000016 00000 n
+0000001210 00000 n
+0000001027 00000 n
+0000001048 00000 n
+0000001147 00000 n
+0000001168 00000 n
+0000001189 00000 n
+0000000065 00000 n
+0000000383 00000 n
+0000000208 00000 n
+0000001007 00000 n
+0000001270 00000 n
+trailer
+<< /Info 12 0 R /Root 1 0 R /Size 13 >>
+startxref
+1474
+%%EOF
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help.png
new file mode 100644
index 0000000000000000000000000000000000000000..a52fbbe819e2fcbb1c1d5f8f143fc8d069688b3a
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help.svg
new file mode 100644
index 0000000000000000000000000000000000000000..260528d607aa59c7fabd5e8645a941064967645f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help.svg
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..3f3d4dfed7e99421bd024c14046bf4616b4ca2ff
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/help_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home-symbolic.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..db140d43d156b52336d1c7e9d74afb0a0726b6b5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home-symbolic.svg
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..f9c6439b9b84bfdf40a2570d6971951b94977841
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home.pdf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home.png
new file mode 100644
index 0000000000000000000000000000000000000000..6e5fdebb357c5fe4e87fa01756bfe33b40ac25f4
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home.svg
new file mode 100644
index 0000000000000000000000000000000000000000..db140d43d156b52336d1c7e9d74afb0a0726b6b5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home.svg
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..3357bfeb9011141ebce16febf6539f610f12ecc9
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/home_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..6c44566ad819ba31e93bf50d3b7c2b9fa1bb8f64
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib.pdf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib.png
new file mode 100644
index 0000000000000000000000000000000000000000..8eedfa7cc8fc7d68be0e1781421f845ef4f54db0
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib.svg
new file mode 100644
index 0000000000000000000000000000000000000000..95d1b61203bfad44c8b31fb7bf215c4107d8a830
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib.svg
@@ -0,0 +1,3171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..c7dcfe6cb8255b332d9c6d15d8049ec63c3141e5
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/matplotlib_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move-symbolic.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..f7e23ab0451ce2c3f6290e1f093771a0a1a58c9b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move-symbolic.svg
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..d883d9224a6e61436205a18182f5e66708312976
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move.pdf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move.png
new file mode 100644
index 0000000000000000000000000000000000000000..4fbbaef41bbadaab211d778f32f6febf31e5251c
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move.svg
new file mode 100644
index 0000000000000000000000000000000000000000..f7e23ab0451ce2c3f6290e1f093771a0a1a58c9b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move.svg
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..96351c115f0b504c28b10ad7518915638373eeb8
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/move_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..a92f2cc3aaee87f45926546bcc135f40a357160a
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options.pdf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options.png
new file mode 100644
index 0000000000000000000000000000000000000000..792ec81244e3b8f3fe3cd28ef4898d934a1d73a3
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options.svg
new file mode 100644
index 0000000000000000000000000000000000000000..02adfbc4ae11f473606591813e3360cbf39111fe
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options.svg
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..46d52c91c9bfe19b86b24fd92326c31b52a03ece
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/qt4_editor_options_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots-symbolic.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..9a0fa90972ff2788edb7791e057e2aced59cf84f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots-symbolic.svg
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..f404665579a0b1916174203f13f7339a75a347a5
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots.pdf differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb0318c40e72343d52227ef29048224b5c7177ae
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots.svg
new file mode 100644
index 0000000000000000000000000000000000000000..9a0fa90972ff2788edb7791e057e2aced59cf84f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots.svg
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..4440af1759ee448e56727ad09e6861eab94ecca5
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/subplots_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect-symbolic.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..44e59a8a4fdca9148d0912d4699c5626dd676834
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect-symbolic.svg
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect.pdf b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..22add33bb8f9f99112681f4ddc52ec96dca8c537
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect.pdf
@@ -0,0 +1,68 @@
+%PDF-1.4
+%† Ǽ
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+10 0 obj
+<< /Annots [ ] /Contents 9 0 R
+/Group << /CS /DeviceRGB /S /Transparency /Type /Group >>
+/MediaBox [ 0 0 72 72 ] /Parent 2 0 R /Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 11 0 R >>
+stream
+xÚmRKn1Ûë>�b},ÛËÞ¾W$iƒ×n´×/m÷�H€Ø $’’,é�ž¾Hz}O9½áÿ›$ÝÒÓ·ç??�çï·¯éx§üUÅq¾;Àyü ú
]¯¨,·q'òÆâ¹[MÖÙ¥Gk º ™³7·–>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 0 >>
+/A2 << /CA 1 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 10 0 R ] /Type /Pages >>
+endobj
+12 0 obj
+<< /CreationDate (D:20160601123507-04'00')
+/Creator (matplotlib 1.5.2rc2.post1933.dev0+gb775a26, http://matplotlib.org)
+/Producer (matplotlib pdf backend) >>
+endobj
+xref
+0 13
+0000000000 65535 f
+0000000016 00000 n
+0000001036 00000 n
+0000000853 00000 n
+0000000874 00000 n
+0000000973 00000 n
+0000000994 00000 n
+0000001015 00000 n
+0000000065 00000 n
+0000000383 00000 n
+0000000208 00000 n
+0000000833 00000 n
+0000001096 00000 n
+trailer
+<< /Info 12 0 R /Root 1 0 R /Size 13 >>
+startxref
+1270
+%%EOF
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect.png
new file mode 100644
index 0000000000000000000000000000000000000000..12afa252481c3d00f5486cca5d9016638c7c0e9a
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect.svg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect.svg
new file mode 100644
index 0000000000000000000000000000000000000000..44e59a8a4fdca9148d0912d4699c5626dd676834
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect.svg
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect_large.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..5963603bb681f76a1f2ce3e3fc120b5a089c27fa
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/images/zoom_to_rect_large.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/kpsewhich.lua b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/kpsewhich.lua
new file mode 100644
index 0000000000000000000000000000000000000000..8e9172a45082567bee6802113aa40664d1bfc010
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/kpsewhich.lua
@@ -0,0 +1,3 @@
+-- see dviread._LuatexKpsewhich
+kpse.set_program_name("latex")
+while true do print(kpse.lookup(io.read():gsub("\r", ""))); io.flush(); end
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/matplotlibrc b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/matplotlibrc
new file mode 100644
index 0000000000000000000000000000000000000000..df4f4afadf96bfb30c5eb62f2b9301994cc395fd
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/matplotlibrc
@@ -0,0 +1,813 @@
+#### MATPLOTLIBRC FORMAT
+
+## NOTE FOR END USERS: DO NOT EDIT THIS FILE!
+##
+## This is a sample Matplotlib configuration file - you can find a copy
+## of it on your system in site-packages/matplotlib/mpl-data/matplotlibrc
+## (relative to your Python installation location).
+## DO NOT EDIT IT!
+##
+## If you wish to change your default style, copy this file to one of the
+## following locations:
+## Unix/Linux:
+## $HOME/.config/matplotlib/matplotlibrc OR
+## $XDG_CONFIG_HOME/matplotlib/matplotlibrc (if $XDG_CONFIG_HOME is set)
+## Other platforms:
+## $HOME/.matplotlib/matplotlibrc
+## and edit that copy.
+##
+## See https://matplotlib.org/stable/users/explain/customizing.html#customizing-with-matplotlibrc-files
+## for more details on the paths which are checked for the configuration file.
+##
+## Blank lines, or lines starting with a comment symbol, are ignored, as are
+## trailing comments. Other lines must have the format:
+## key: val # optional comment
+##
+## Formatting: Use PEP8-like style (as enforced in the rest of the codebase).
+## All lines start with an additional '#', so that removing all leading '#'s
+## yields a valid style file.
+##
+## Colors: for the color values below, you can either use
+## - a Matplotlib color string, such as r, k, or b
+## - an RGB tuple, such as (1.0, 0.5, 0.0)
+## - a double-quoted hex string, such as "#ff00ff".
+## The unquoted string ff00ff is also supported for backward
+## compatibility, but is discouraged.
+## - a scalar grayscale intensity such as 0.75
+## - a legal html color name, e.g., red, blue, darkslategray
+##
+## String values may optionally be enclosed in double quotes, which allows
+## using the comment character # in the string.
+##
+## This file (and other style files) must be encoded as utf-8.
+##
+## Matplotlib configuration are currently divided into following parts:
+## - BACKENDS
+## - LINES
+## - PATCHES
+## - HATCHES
+## - BOXPLOT
+## - FONT
+## - TEXT
+## - LaTeX
+## - AXES
+## - DATES
+## - TICKS
+## - GRIDS
+## - LEGEND
+## - FIGURE
+## - IMAGES
+## - CONTOUR PLOTS
+## - ERRORBAR PLOTS
+## - HISTOGRAM PLOTS
+## - SCATTER PLOTS
+## - AGG RENDERING
+## - PATHS
+## - SAVING FIGURES
+## - INTERACTIVE KEYMAPS
+## - ANIMATION
+
+##### CONFIGURATION BEGINS HERE
+
+
+## ***************************************************************************
+## * BACKENDS *
+## ***************************************************************************
+## The default backend. If you omit this parameter, the first working
+## backend from the following list is used:
+## MacOSX QtAgg Gtk4Agg Gtk3Agg TkAgg WxAgg Agg
+## Other choices include:
+## QtCairo GTK4Cairo GTK3Cairo TkCairo WxCairo Cairo
+## Qt5Agg Qt5Cairo Wx # deprecated.
+## PS PDF SVG Template
+## You can also deploy your own backend outside of Matplotlib by referring to
+## the module name (which must be in the PYTHONPATH) as 'module://my_backend'.
+##backend: Agg
+
+## The port to use for the web server in the WebAgg backend.
+#webagg.port: 8988
+
+## The address on which the WebAgg web server should be reachable
+#webagg.address: 127.0.0.1
+
+## If webagg.port is unavailable, a number of other random ports will
+## be tried until one that is available is found.
+#webagg.port_retries: 50
+
+## When True, open the web browser to the plot that is shown
+#webagg.open_in_browser: True
+
+## If you are running pyplot inside a GUI and your backend choice
+## conflicts, we will automatically try to find a compatible one for
+## you if backend_fallback is True
+#backend_fallback: True
+
+#interactive: False
+#figure.hooks: # list of dotted.module.name:dotted.callable.name
+#toolbar: toolbar2 # {None, toolbar2, toolmanager}
+#timezone: UTC # a pytz timezone string, e.g., US/Central or Europe/Paris
+
+
+## ***************************************************************************
+## * LINES *
+## ***************************************************************************
+## See https://matplotlib.org/stable/api/artist_api.html#module-matplotlib.lines
+## for more information on line properties.
+#lines.linewidth: 1.5 # line width in points
+#lines.linestyle: - # solid line
+#lines.color: C0 # has no affect on plot(); see axes.prop_cycle
+#lines.marker: None # the default marker
+#lines.markerfacecolor: auto # the default marker face color
+#lines.markeredgecolor: auto # the default marker edge color
+#lines.markeredgewidth: 1.0 # the line width around the marker symbol
+#lines.markersize: 6 # marker size, in points
+#lines.dash_joinstyle: round # {miter, round, bevel}
+#lines.dash_capstyle: butt # {butt, round, projecting}
+#lines.solid_joinstyle: round # {miter, round, bevel}
+#lines.solid_capstyle: projecting # {butt, round, projecting}
+#lines.antialiased: True # render lines in antialiased (no jaggies)
+
+## The three standard dash patterns. These are scaled by the linewidth.
+#lines.dashed_pattern: 3.7, 1.6
+#lines.dashdot_pattern: 6.4, 1.6, 1, 1.6
+#lines.dotted_pattern: 1, 1.65
+#lines.scale_dashes: True
+
+#markers.fillstyle: full # {full, left, right, bottom, top, none}
+
+#pcolor.shading: auto
+#pcolormesh.snap: True # Whether to snap the mesh to pixel boundaries. This is
+ # provided solely to allow old test images to remain
+ # unchanged. Set to False to obtain the previous behavior.
+
+## ***************************************************************************
+## * PATCHES *
+## ***************************************************************************
+## Patches are graphical objects that fill 2D space, like polygons or circles.
+## See https://matplotlib.org/stable/api/artist_api.html#module-matplotlib.patches
+## for more information on patch properties.
+#patch.linewidth: 1.0 # edge width in points.
+#patch.facecolor: C0
+#patch.edgecolor: black # By default, Patches and Collections do not draw edges.
+ # This value is only used if facecolor is "none"
+ # (an Artist without facecolor and edgecolor would be
+ # invisible) or if patch.force_edgecolor is True.
+#patch.force_edgecolor: False # By default, Patches and Collections do not draw edges.
+ # Set this to True to draw edges with patch.edgedcolor
+ # as the default edgecolor.
+ # This is mainly relevant for styles.
+#patch.antialiased: True # render patches in antialiased (no jaggies)
+
+
+## ***************************************************************************
+## * HATCHES *
+## ***************************************************************************
+#hatch.color: black
+#hatch.linewidth: 1.0
+
+
+## ***************************************************************************
+## * BOXPLOT *
+## ***************************************************************************
+#boxplot.notch: False
+#boxplot.vertical: True
+#boxplot.whiskers: 1.5
+#boxplot.bootstrap: None
+#boxplot.patchartist: False
+#boxplot.showmeans: False
+#boxplot.showcaps: True
+#boxplot.showbox: True
+#boxplot.showfliers: True
+#boxplot.meanline: False
+
+#boxplot.flierprops.color: black
+#boxplot.flierprops.marker: o
+#boxplot.flierprops.markerfacecolor: none
+#boxplot.flierprops.markeredgecolor: black
+#boxplot.flierprops.markeredgewidth: 1.0
+#boxplot.flierprops.markersize: 6
+#boxplot.flierprops.linestyle: none
+#boxplot.flierprops.linewidth: 1.0
+
+#boxplot.boxprops.color: black
+#boxplot.boxprops.linewidth: 1.0
+#boxplot.boxprops.linestyle: -
+
+#boxplot.whiskerprops.color: black
+#boxplot.whiskerprops.linewidth: 1.0
+#boxplot.whiskerprops.linestyle: -
+
+#boxplot.capprops.color: black
+#boxplot.capprops.linewidth: 1.0
+#boxplot.capprops.linestyle: -
+
+#boxplot.medianprops.color: C1
+#boxplot.medianprops.linewidth: 1.0
+#boxplot.medianprops.linestyle: -
+
+#boxplot.meanprops.color: C2
+#boxplot.meanprops.marker: ^
+#boxplot.meanprops.markerfacecolor: C2
+#boxplot.meanprops.markeredgecolor: C2
+#boxplot.meanprops.markersize: 6
+#boxplot.meanprops.linestyle: --
+#boxplot.meanprops.linewidth: 1.0
+
+
+## ***************************************************************************
+## * FONT *
+## ***************************************************************************
+## The font properties used by `text.Text`.
+## See https://matplotlib.org/stable/api/font_manager_api.html for more information
+## on font properties. The 6 font properties used for font matching are
+## given below with their default values.
+##
+## The font.family property can take either a single or multiple entries of any
+## combination of concrete font names (not supported when rendering text with
+## usetex) or the following five generic values:
+## - 'serif' (e.g., Times),
+## - 'sans-serif' (e.g., Helvetica),
+## - 'cursive' (e.g., Zapf-Chancery),
+## - 'fantasy' (e.g., Western), and
+## - 'monospace' (e.g., Courier).
+## Each of these values has a corresponding default list of font names
+## (font.serif, etc.); the first available font in the list is used. Note that
+## for font.serif, font.sans-serif, and font.monospace, the first element of
+## the list (a DejaVu font) will always be used because DejaVu is shipped with
+## Matplotlib and is thus guaranteed to be available; the other entries are
+## left as examples of other possible values.
+##
+## The font.style property has three values: normal (or roman), italic
+## or oblique. The oblique style will be used for italic, if it is not
+## present.
+##
+## The font.variant property has two values: normal or small-caps. For
+## TrueType fonts, which are scalable fonts, small-caps is equivalent
+## to using a font size of 'smaller', or about 83 % of the current font
+## size.
+##
+## The font.weight property has effectively 13 values: normal, bold,
+## bolder, lighter, 100, 200, 300, ..., 900. Normal is the same as
+## 400, and bold is 700. bolder and lighter are relative values with
+## respect to the current weight.
+##
+## The font.stretch property has 11 values: ultra-condensed,
+## extra-condensed, condensed, semi-condensed, normal, semi-expanded,
+## expanded, extra-expanded, ultra-expanded, wider, and narrower. This
+## property is not currently implemented.
+##
+## The font.size property is the default font size for text, given in points.
+## 10 pt is the standard value.
+##
+## Note that font.size controls default text sizes. To configure
+## special text sizes tick labels, axes, labels, title, etc., see the rc
+## settings for axes and ticks. Special text sizes can be defined
+## relative to font.size, using the following values: xx-small, x-small,
+## small, medium, large, x-large, xx-large, larger, or smaller
+
+#font.family: sans-serif
+#font.style: normal
+#font.variant: normal
+#font.weight: normal
+#font.stretch: normal
+#font.size: 10.0
+
+#font.serif: DejaVu Serif, Bitstream Vera Serif, Computer Modern Roman, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif
+#font.sans-serif: DejaVu Sans, Bitstream Vera Sans, Computer Modern Sans Serif, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif
+#font.cursive: Apple Chancery, Textile, Zapf Chancery, Sand, Script MT, Felipa, Comic Neue, Comic Sans MS, cursive
+#font.fantasy: Chicago, Charcoal, Impact, Western, xkcd script, fantasy
+#font.monospace: DejaVu Sans Mono, Bitstream Vera Sans Mono, Computer Modern Typewriter, Andale Mono, Nimbus Mono L, Courier New, Courier, Fixed, Terminal, monospace
+
+
+## ***************************************************************************
+## * TEXT *
+## ***************************************************************************
+## The text properties used by `text.Text`.
+## See https://matplotlib.org/stable/api/artist_api.html#module-matplotlib.text
+## for more information on text properties
+#text.color: black
+
+## FreeType hinting flag ("foo" corresponds to FT_LOAD_FOO); may be one of the
+## following (Proprietary Matplotlib-specific synonyms are given in parentheses,
+## but their use is discouraged):
+## - default: Use the font's native hinter if possible, else FreeType's auto-hinter.
+## ("either" is a synonym).
+## - no_autohint: Use the font's native hinter if possible, else don't hint.
+## ("native" is a synonym.)
+## - force_autohint: Use FreeType's auto-hinter. ("auto" is a synonym.)
+## - no_hinting: Disable hinting. ("none" is a synonym.)
+#text.hinting: force_autohint
+
+#text.hinting_factor: 8 # Specifies the amount of softness for hinting in the
+ # horizontal direction. A value of 1 will hint to full
+ # pixels. A value of 2 will hint to half pixels etc.
+#text.kerning_factor: 0 # Specifies the scaling factor for kerning values. This
+ # is provided solely to allow old test images to remain
+ # unchanged. Set to 6 to obtain previous behavior.
+ # Values other than 0 or 6 have no defined meaning.
+#text.antialiased: True # If True (default), the text will be antialiased.
+ # This only affects raster outputs.
+#text.parse_math: True # Use mathtext if there is an even number of unescaped
+ # dollar signs.
+
+
+## ***************************************************************************
+## * LaTeX *
+## ***************************************************************************
+## For more information on LaTeX properties, see
+## https://matplotlib.org/stable/users/explain/text/usetex.html
+#text.usetex: False # use latex for all text handling. The following fonts
+ # are supported through the usual rc parameter settings:
+ # new century schoolbook, bookman, times, palatino,
+ # zapf chancery, charter, serif, sans-serif, helvetica,
+ # avant garde, courier, monospace, computer modern roman,
+ # computer modern sans serif, computer modern typewriter
+#text.latex.preamble: # IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES
+ # AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP
+ # IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO.
+ # text.latex.preamble is a single line of LaTeX code that
+ # will be passed on to the LaTeX system. It may contain
+ # any code that is valid for the LaTeX "preamble", i.e.
+ # between the "\documentclass" and "\begin{document}"
+ # statements.
+ # Note that it has to be put on a single line, which may
+ # become quite long.
+ # The following packages are always loaded with usetex,
+ # so beware of package collisions:
+ # geometry, inputenc, type1cm.
+ # PostScript (PSNFSS) font packages may also be
+ # loaded, depending on your font settings.
+
+## The following settings allow you to select the fonts in math mode.
+#mathtext.fontset: dejavusans # Should be 'dejavusans' (default),
+ # 'dejavuserif', 'cm' (Computer Modern), 'stix',
+ # 'stixsans' or 'custom'
+## "mathtext.fontset: custom" is defined by the mathtext.bf, .cal, .it, ...
+## settings which map a TeX font name to a fontconfig font pattern. (These
+## settings are not used for other font sets.)
+#mathtext.bf: sans:bold
+#mathtext.bfit: sans:italic:bold
+#mathtext.cal: cursive
+#mathtext.it: sans:italic
+#mathtext.rm: sans
+#mathtext.sf: sans
+#mathtext.tt: monospace
+#mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix'
+ # 'stixsans'] when a symbol cannot be found in one of the
+ # custom math fonts. Select 'None' to not perform fallback
+ # and replace the missing character by a dummy symbol.
+#mathtext.default: it # The default font to use for math.
+ # Can be any of the LaTeX font names, including
+ # the special name "regular" for the same font
+ # used in regular text.
+
+
+## ***************************************************************************
+## * AXES *
+## ***************************************************************************
+## Following are default face and edge colors, default tick sizes,
+## default font sizes for tick labels, and so on. See
+## https://matplotlib.org/stable/api/axes_api.html#module-matplotlib.axes
+#axes.facecolor: white # axes background color
+#axes.edgecolor: black # axes edge color
+#axes.linewidth: 0.8 # edge line width
+#axes.grid: False # display grid or not
+#axes.grid.axis: both # which axis the grid should apply to
+#axes.grid.which: major # grid lines at {major, minor, both} ticks
+#axes.titlelocation: center # alignment of the title: {left, right, center}
+#axes.titlesize: large # font size of the axes title
+#axes.titleweight: normal # font weight of title
+#axes.titlecolor: auto # color of the axes title, auto falls back to
+ # text.color as default value
+#axes.titley: None # position title (axes relative units). None implies auto
+#axes.titlepad: 6.0 # pad between axes and title in points
+#axes.labelsize: medium # font size of the x and y labels
+#axes.labelpad: 4.0 # space between label and axis
+#axes.labelweight: normal # weight of the x and y labels
+#axes.labelcolor: black
+#axes.axisbelow: line # draw axis gridlines and ticks:
+ # - below patches (True)
+ # - above patches but below lines ('line')
+ # - above all (False)
+
+#axes.formatter.limits: -5, 6 # use scientific notation if log10
+ # of the axis range is smaller than the
+ # first or larger than the second
+#axes.formatter.use_locale: False # When True, format tick labels
+ # according to the user's locale.
+ # For example, use ',' as a decimal
+ # separator in the fr_FR locale.
+#axes.formatter.use_mathtext: False # When True, use mathtext for scientific
+ # notation.
+#axes.formatter.min_exponent: 0 # minimum exponent to format in scientific notation
+#axes.formatter.useoffset: True # If True, the tick label formatter
+ # will default to labeling ticks relative
+ # to an offset when the data range is
+ # small compared to the minimum absolute
+ # value of the data.
+#axes.formatter.offset_threshold: 4 # When useoffset is True, the offset
+ # will be used when it can remove
+ # at least this number of significant
+ # digits from tick labels.
+
+#axes.spines.left: True # display axis spines
+#axes.spines.bottom: True
+#axes.spines.top: True
+#axes.spines.right: True
+
+#axes.unicode_minus: True # use Unicode for the minus symbol rather than hyphen. See
+ # https://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes
+#axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf'])
+ # color cycle for plot lines as list of string color specs:
+ # single letter, long name, or web-style hex
+ # As opposed to all other parameters in this file, the color
+ # values must be enclosed in quotes for this parameter,
+ # e.g. '1f77b4', instead of 1f77b4.
+ # See also https://matplotlib.org/stable/users/explain/artists/color_cycle.html
+ # for more details on prop_cycle usage.
+#axes.xmargin: .05 # x margin. See `axes.Axes.margins`
+#axes.ymargin: .05 # y margin. See `axes.Axes.margins`
+#axes.zmargin: .05 # z margin. See `axes.Axes.margins`
+#axes.autolimit_mode: data # If "data", use axes.xmargin and axes.ymargin as is.
+ # If "round_numbers", after application of margins, axis
+ # limits are further expanded to the nearest "round" number.
+#polaraxes.grid: True # display grid on polar axes
+#axes3d.grid: True # display grid on 3D axes
+#axes3d.automargin: False # automatically add margin when manually setting 3D axis limits
+
+#axes3d.xaxis.panecolor: (0.95, 0.95, 0.95, 0.5) # background pane on 3D axes
+#axes3d.yaxis.panecolor: (0.90, 0.90, 0.90, 0.5) # background pane on 3D axes
+#axes3d.zaxis.panecolor: (0.925, 0.925, 0.925, 0.5) # background pane on 3D axes
+
+#axes3d.mouserotationstyle: arcball # {azel, trackball, sphere, arcball}
+ # See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse
+#axes3d.trackballsize: 0.667 # trackball diameter, in units of the Axes bbox
+#axes3d.trackballborder: 0.2 # trackball border width, in units of the Axes bbox (only for 'sphere' and 'arcball' style)
+
+## ***************************************************************************
+## * AXIS *
+## ***************************************************************************
+#xaxis.labellocation: center # alignment of the xaxis label: {left, right, center}
+#yaxis.labellocation: center # alignment of the yaxis label: {bottom, top, center}
+
+
+## ***************************************************************************
+## * DATES *
+## ***************************************************************************
+## These control the default format strings used in AutoDateFormatter.
+## Any valid format datetime format string can be used (see the python
+## `datetime` for details). For example, by using:
+## - '%x' will use the locale date representation
+## - '%X' will use the locale time representation
+## - '%c' will use the full locale datetime representation
+## These values map to the scales:
+## {'year': 365, 'month': 30, 'day': 1, 'hour': 1/24, 'minute': 1 / (24 * 60)}
+
+#date.autoformatter.year: %Y
+#date.autoformatter.month: %Y-%m
+#date.autoformatter.day: %Y-%m-%d
+#date.autoformatter.hour: %m-%d %H
+#date.autoformatter.minute: %d %H:%M
+#date.autoformatter.second: %H:%M:%S
+#date.autoformatter.microsecond: %M:%S.%f
+## The reference date for Matplotlib's internal date representation
+## See https://matplotlib.org/stable/gallery/ticks/date_precision_and_epochs.html
+#date.epoch: 1970-01-01T00:00:00
+## 'auto', 'concise':
+#date.converter: auto
+## For auto converter whether to use interval_multiples:
+#date.interval_multiples: True
+
+## ***************************************************************************
+## * TICKS *
+## ***************************************************************************
+## See https://matplotlib.org/stable/api/axis_api.html#matplotlib.axis.Tick
+#xtick.top: False # draw ticks on the top side
+#xtick.bottom: True # draw ticks on the bottom side
+#xtick.labeltop: False # draw label on the top
+#xtick.labelbottom: True # draw label on the bottom
+#xtick.major.size: 3.5 # major tick size in points
+#xtick.minor.size: 2 # minor tick size in points
+#xtick.major.width: 0.8 # major tick width in points
+#xtick.minor.width: 0.6 # minor tick width in points
+#xtick.major.pad: 3.5 # distance to major tick label in points
+#xtick.minor.pad: 3.4 # distance to the minor tick label in points
+#xtick.color: black # color of the ticks
+#xtick.labelcolor: inherit # color of the tick labels or inherit from xtick.color
+#xtick.labelsize: medium # font size of the tick labels
+#xtick.direction: out # direction: {in, out, inout}
+#xtick.minor.visible: False # visibility of minor ticks on x-axis
+#xtick.major.top: True # draw x axis top major ticks
+#xtick.major.bottom: True # draw x axis bottom major ticks
+#xtick.minor.top: True # draw x axis top minor ticks
+#xtick.minor.bottom: True # draw x axis bottom minor ticks
+#xtick.minor.ndivs: auto # number of minor ticks between the major ticks on x-axis
+#xtick.alignment: center # alignment of xticks
+
+#ytick.left: True # draw ticks on the left side
+#ytick.right: False # draw ticks on the right side
+#ytick.labelleft: True # draw tick labels on the left side
+#ytick.labelright: False # draw tick labels on the right side
+#ytick.major.size: 3.5 # major tick size in points
+#ytick.minor.size: 2 # minor tick size in points
+#ytick.major.width: 0.8 # major tick width in points
+#ytick.minor.width: 0.6 # minor tick width in points
+#ytick.major.pad: 3.5 # distance to major tick label in points
+#ytick.minor.pad: 3.4 # distance to the minor tick label in points
+#ytick.color: black # color of the ticks
+#ytick.labelcolor: inherit # color of the tick labels or inherit from ytick.color
+#ytick.labelsize: medium # font size of the tick labels
+#ytick.direction: out # direction: {in, out, inout}
+#ytick.minor.visible: False # visibility of minor ticks on y-axis
+#ytick.major.left: True # draw y axis left major ticks
+#ytick.major.right: True # draw y axis right major ticks
+#ytick.minor.left: True # draw y axis left minor ticks
+#ytick.minor.right: True # draw y axis right minor ticks
+#ytick.minor.ndivs: auto # number of minor ticks between the major ticks on y-axis
+#ytick.alignment: center_baseline # alignment of yticks
+
+
+## ***************************************************************************
+## * GRIDS *
+## ***************************************************************************
+#grid.color: "#b0b0b0" # grid color
+#grid.linestyle: - # solid
+#grid.linewidth: 0.8 # in points
+#grid.alpha: 1.0 # transparency, between 0.0 and 1.0
+
+
+## ***************************************************************************
+## * LEGEND *
+## ***************************************************************************
+#legend.loc: best
+#legend.frameon: True # if True, draw the legend on a background patch
+#legend.framealpha: 0.8 # legend patch transparency
+#legend.facecolor: inherit # inherit from axes.facecolor; or color spec
+#legend.edgecolor: 0.8 # background patch boundary color
+#legend.fancybox: True # if True, use a rounded box for the
+ # legend background, else a rectangle
+#legend.shadow: False # if True, give background a shadow effect
+#legend.numpoints: 1 # the number of marker points in the legend line
+#legend.scatterpoints: 1 # number of scatter points
+#legend.markerscale: 1.0 # the relative size of legend markers vs. original
+#legend.fontsize: medium
+#legend.labelcolor: None
+#legend.title_fontsize: None # None sets to the same as the default axes.
+
+## Dimensions as fraction of font size:
+#legend.borderpad: 0.4 # border whitespace
+#legend.labelspacing: 0.5 # the vertical space between the legend entries
+#legend.handlelength: 2.0 # the length of the legend lines
+#legend.handleheight: 0.7 # the height of the legend handle
+#legend.handletextpad: 0.8 # the space between the legend line and legend text
+#legend.borderaxespad: 0.5 # the border between the axes and legend edge
+#legend.columnspacing: 2.0 # column separation
+
+
+## ***************************************************************************
+## * FIGURE *
+## ***************************************************************************
+## See https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure
+#figure.titlesize: large # size of the figure title (``Figure.suptitle()``)
+#figure.titleweight: normal # weight of the figure title
+#figure.labelsize: large # size of the figure label (``Figure.sup[x|y]label()``)
+#figure.labelweight: normal # weight of the figure label
+#figure.figsize: 6.4, 4.8 # figure size in inches
+#figure.dpi: 100 # figure dots per inch
+#figure.facecolor: white # figure face color
+#figure.edgecolor: white # figure edge color
+#figure.frameon: True # enable figure frame
+#figure.max_open_warning: 20 # The maximum number of figures to open through
+ # the pyplot interface before emitting a warning.
+ # If less than one this feature is disabled.
+#figure.raise_window : True # Raise the GUI window to front when show() is called.
+
+## The figure subplot parameters. All dimensions are a fraction of the figure width and height.
+#figure.subplot.left: 0.125 # the left side of the subplots of the figure
+#figure.subplot.right: 0.9 # the right side of the subplots of the figure
+#figure.subplot.bottom: 0.11 # the bottom of the subplots of the figure
+#figure.subplot.top: 0.88 # the top of the subplots of the figure
+#figure.subplot.wspace: 0.2 # the amount of width reserved for space between subplots,
+ # expressed as a fraction of the average axis width
+#figure.subplot.hspace: 0.2 # the amount of height reserved for space between subplots,
+ # expressed as a fraction of the average axis height
+
+## Figure layout
+#figure.autolayout: False # When True, automatically adjust subplot
+ # parameters to make the plot fit the figure
+ # using `tight_layout`
+#figure.constrained_layout.use: False # When True, automatically make plot
+ # elements fit on the figure. (Not
+ # compatible with `autolayout`, above).
+## Padding (in inches) around axes; defaults to 3/72 inches, i.e. 3 points.
+#figure.constrained_layout.h_pad: 0.04167
+#figure.constrained_layout.w_pad: 0.04167
+## Spacing between subplots, relative to the subplot sizes. Much smaller than for
+## tight_layout (figure.subplot.hspace, figure.subplot.wspace) as constrained_layout
+## already takes surrounding texts (titles, labels, # ticklabels) into account.
+#figure.constrained_layout.hspace: 0.02
+#figure.constrained_layout.wspace: 0.02
+
+
+## ***************************************************************************
+## * IMAGES *
+## ***************************************************************************
+#image.aspect: equal # {equal, auto} or a number
+#image.interpolation: auto # see help(imshow) for options
+#image.interpolation_stage: auto # see help(imshow) for options
+#image.cmap: viridis # A colormap name (plasma, magma, etc.)
+#image.lut: 256 # the size of the colormap lookup table
+#image.origin: upper # {lower, upper}
+#image.resample: True
+#image.composite_image: True # When True, all the images on a set of axes are
+ # combined into a single composite image before
+ # saving a figure as a vector graphics file,
+ # such as a PDF.
+
+
+## ***************************************************************************
+## * CONTOUR PLOTS *
+## ***************************************************************************
+#contour.negative_linestyle: dashed # string or on-off ink sequence
+#contour.corner_mask: True # {True, False}
+#contour.linewidth: None # {float, None} Size of the contour line
+ # widths. If set to None, it falls back to
+ # `line.linewidth`.
+#contour.algorithm: mpl2014 # {mpl2005, mpl2014, serial, threaded}
+
+
+## ***************************************************************************
+## * ERRORBAR PLOTS *
+## ***************************************************************************
+#errorbar.capsize: 0 # length of end cap on error bars in pixels
+
+
+## ***************************************************************************
+## * HISTOGRAM PLOTS *
+## ***************************************************************************
+#hist.bins: 10 # The default number of histogram bins or 'auto'.
+
+
+## ***************************************************************************
+## * SCATTER PLOTS *
+## ***************************************************************************
+#scatter.marker: o # The default marker type for scatter plots.
+#scatter.edgecolors: face # The default edge colors for scatter plots.
+
+
+## ***************************************************************************
+## * AGG RENDERING *
+## ***************************************************************************
+## Warning: experimental, 2008/10/10
+#agg.path.chunksize: 0 # 0 to disable; values in the range
+ # 10000 to 100000 can improve speed slightly
+ # and prevent an Agg rendering failure
+ # when plotting very large data sets,
+ # especially if they are very gappy.
+ # It may cause minor artifacts, though.
+ # A value of 20000 is probably a good
+ # starting point.
+
+
+## ***************************************************************************
+## * PATHS *
+## ***************************************************************************
+#path.simplify: True # When True, simplify paths by removing "invisible"
+ # points to reduce file size and increase rendering
+ # speed
+#path.simplify_threshold: 0.111111111111 # The threshold of similarity below
+ # which vertices will be removed in
+ # the simplification process.
+#path.snap: True # When True, rectilinear axis-aligned paths will be snapped
+ # to the nearest pixel when certain criteria are met.
+ # When False, paths will never be snapped.
+#path.sketch: None # May be None, or a tuple of the form:
+ # path.sketch: (scale, length, randomness)
+ # - *scale* is the amplitude of the wiggle
+ # perpendicular to the line (in pixels).
+ # - *length* is the length of the wiggle along the
+ # line (in pixels).
+ # - *randomness* is the factor by which the length is
+ # randomly scaled.
+#path.effects:
+
+
+## ***************************************************************************
+## * SAVING FIGURES *
+## ***************************************************************************
+## The default savefig parameters can be different from the display parameters
+## e.g., you may want a higher resolution, or to make the figure
+## background white
+#savefig.dpi: figure # figure dots per inch or 'figure'
+#savefig.facecolor: auto # figure face color when saving
+#savefig.edgecolor: auto # figure edge color when saving
+#savefig.format: png # {png, ps, pdf, svg}
+#savefig.bbox: standard # {tight, standard}
+ # 'tight' is incompatible with generating frames
+ # for animation
+#savefig.pad_inches: 0.1 # padding to be used, when bbox is set to 'tight'
+#savefig.directory: ~ # default directory in savefig dialog, gets updated after
+ # interactive saves, unless set to the empty string (i.e.
+ # the current directory); use '.' to start at the current
+ # directory but update after interactive saves
+#savefig.transparent: False # whether figures are saved with a transparent
+ # background by default
+#savefig.orientation: portrait # orientation of saved figure, for PostScript output only
+
+### macosx backend params
+#macosx.window_mode : system # How to open new figures (system, tab, window)
+ # system uses the MacOS system preferences
+
+### tk backend params
+#tk.window_focus: False # Maintain shell focus for TkAgg
+
+### ps backend params
+#ps.papersize: letter # {figure, letter, legal, ledger, A0-A10, B0-B10}
+#ps.useafm: False # use AFM fonts, results in small files
+#ps.usedistiller: False # {ghostscript, xpdf, None}
+ # Experimental: may produce smaller files.
+ # xpdf intended for production of publication quality files,
+ # but requires ghostscript, xpdf and ps2eps
+#ps.distiller.res: 6000 # dpi
+#ps.fonttype: 3 # Output Type 3 (Type3) or Type 42 (TrueType)
+
+### PDF backend params
+#pdf.compression: 6 # integer from 0 to 9
+ # 0 disables compression (good for debugging)
+#pdf.fonttype: 3 # Output Type 3 (Type3) or Type 42 (TrueType)
+#pdf.use14corefonts: False
+#pdf.inheritcolor: False
+
+### SVG backend params
+#svg.image_inline: True # Write raster image data directly into the SVG file
+#svg.fonttype: path # How to handle SVG fonts:
+ # path: Embed characters as paths -- supported
+ # by most SVG renderers
+ # None: Assume fonts are installed on the
+ # machine where the SVG will be viewed.
+#svg.hashsalt: None # If not None, use this string as hash salt instead of uuid4
+#svg.id: None # If not None, use this string as the value for the `id`
+ # attribute in the top tag
+
+### pgf parameter
+## See https://matplotlib.org/stable/tutorials/text/pgf.html for more information.
+#pgf.rcfonts: True
+#pgf.preamble: # See text.latex.preamble for documentation
+#pgf.texsystem: xelatex
+
+### docstring params
+#docstring.hardcopy: False # set this when you want to generate hardcopy docstring
+
+
+## ***************************************************************************
+## * INTERACTIVE KEYMAPS *
+## ***************************************************************************
+## Event keys to interact with figures/plots via keyboard.
+## See https://matplotlib.org/stable/users/explain/interactive.html for more
+## details on interactive navigation. Customize these settings according to
+## your needs. Leave the field(s) empty if you don't need a key-map. (i.e.,
+## fullscreen : '')
+#keymap.fullscreen: f, ctrl+f # toggling
+#keymap.home: h, r, home # home or reset mnemonic
+#keymap.back: left, c, backspace, MouseButton.BACK # forward / backward keys
+#keymap.forward: right, v, MouseButton.FORWARD # for quick navigation
+#keymap.pan: p # pan mnemonic
+#keymap.zoom: o # zoom mnemonic
+#keymap.save: s, ctrl+s # saving current figure
+#keymap.help: f1 # display help about active tools
+#keymap.quit: ctrl+w, cmd+w, q # close the current figure
+#keymap.quit_all: # close all figures
+#keymap.grid: g # switching on/off major grids in current axes
+#keymap.grid_minor: G # switching on/off minor grids in current axes
+#keymap.yscale: l # toggle scaling of y-axes ('log'/'linear')
+#keymap.xscale: k, L # toggle scaling of x-axes ('log'/'linear')
+#keymap.copy: ctrl+c, cmd+c # copy figure to clipboard
+
+
+## ***************************************************************************
+## * ANIMATION *
+## ***************************************************************************
+#animation.html: none # How to display the animation as HTML in
+ # the IPython notebook:
+ # - 'html5' uses HTML5 video tag
+ # - 'jshtml' creates a JavaScript animation
+#animation.writer: ffmpeg # MovieWriter 'backend' to use
+#animation.codec: h264 # Codec to use for writing movie
+#animation.bitrate: -1 # Controls size/quality trade-off for movie.
+ # -1 implies let utility auto-determine
+#animation.frame_format: png # Controls frame format used by temp files
+
+## Path to ffmpeg binary. Unqualified paths are resolved by subprocess.Popen.
+#animation.ffmpeg_path: ffmpeg
+## Additional arguments to pass to ffmpeg.
+#animation.ffmpeg_args:
+
+## Path to ImageMagick's convert binary. Unqualified paths are resolved by
+## subprocess.Popen, except that on Windows, we look up an install of
+## ImageMagick in the registry (as convert is also the name of a system tool).
+#animation.convert_path: convert
+## Additional arguments to pass to convert.
+#animation.convert_args: -layers, OptimizePlus
+#
+#animation.embed_limit: 20.0 # Limit, in MB, of size of base64 encoded
+ # animation in HTML (i.e. IPython notebook)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/plot_directive/plot_directive.css b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/plot_directive/plot_directive.css
new file mode 100644
index 0000000000000000000000000000000000000000..d45593c93c95d3718fb9e53a780c8022371e619c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/plot_directive/plot_directive.css
@@ -0,0 +1,16 @@
+/*
+ * plot_directive.css
+ * ~~~~~~~~~~~~
+ *
+ * Stylesheet controlling images created using the `plot` directive within
+ * Sphinx.
+ *
+ * :copyright: Copyright 2020-* by the Matplotlib development team.
+ * :license: Matplotlib, see LICENSE for details.
+ *
+ */
+
+img.plot-directive {
+ border: 0;
+ max-width: 100%;
+}
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/Minduka_Present_Blue_Pack.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/Minduka_Present_Blue_Pack.png
new file mode 100644
index 0000000000000000000000000000000000000000..2b1aff4e3c4b050e2f00a1ed34dbfee8a4558db2
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/Minduka_Present_Blue_Pack.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/README.txt b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..75a534951d60ef83ede2da26aec25747fbfb95b3
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/README.txt
@@ -0,0 +1,2 @@
+This is the sample data needed for some of matplotlib's examples and
+docs. See matplotlib.cbook.get_sample_data for more info.
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/Stocks.csv b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/Stocks.csv
new file mode 100644
index 0000000000000000000000000000000000000000..575d353dffe23db9213e2f7edece89d7f3365877
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/Stocks.csv
@@ -0,0 +1,526 @@
+# Data source: https://finance.yahoo.com
+Date,IBM,AAPL,MSFT,XRX,AMZN,DELL,GOOGL,ADBE,^GSPC,^IXIC
+1990-01-01,10.970438003540039,0.24251236021518707,0.40375930070877075,11.202081680297852,,,,1.379060983657837,329.0799865722656,415.79998779296875
+1990-02-01,11.554415702819824,0.24251236021518707,0.43104037642478943,10.39472484588623,,,,1.7790844440460205,331.8900146484375,425.79998779296875
+1990-02-05,,,,,,,,,,
+1990-03-01,11.951693534851074,0.28801724314689636,0.4834197461605072,11.394058227539062,,,,2.2348830699920654,339.94000244140625,435.5
+1990-04-01,12.275476455688477,0.2817564308643341,0.5063362717628479,10.139430046081543,,,,2.2531447410583496,330.79998779296875,420.1000061035156
+1990-05-01,13.514284133911133,0.295173317193985,0.6372847557067871,9.704153060913086,,,,2.0985164642333984,361.2300109863281,459.0
+1990-05-04,,,,,,,,,,
+1990-06-01,13.380948066711426,0.3211067020893097,0.6634747385978699,9.749448776245117,,,,2.164785146713257,358.0199890136719,462.29998779296875
+1990-07-01,12.697657585144043,0.3013734817504883,0.5805402994155884,9.489465713500977,,,,2.069063901901245,356.1499938964844,438.20001220703125
+1990-08-01,11.601561546325684,0.26549553871154785,0.5368908047676086,8.553519248962402,,,,1.4750508069992065,322.55999755859375,381.20001220703125
+1990-08-06,,,,,,,,,,
+1990-09-01,12.251119613647461,0.20872052013874054,0.5499854683876038,7.255113124847412,,,,1.1210384368896484,306.04998779296875,344.5
+1990-10-01,12.15034294128418,0.22131578624248505,0.5565334558486938,6.248927116394043,,,,1.416249394416809,304.0,329.79998779296875
+1990-11-01,13.08609390258789,0.26449888944625854,0.6307373642921448,7.361023426055908,,,,1.4900128841400146,322.2200012207031,359.1000061035156
+1990-11-05,,,,,,,,,,
+1990-12-01,13.161062240600586,0.31051647663116455,0.6569272875785828,7.519896507263184,,,,1.7186784744262695,330.2200012207031,373.79998779296875
+1991-01-01,14.762505531311035,0.40078282356262207,0.8566243648529053,10.554410934448242,,,,2.242394208908081,343.92999267578125,414.20001220703125
+1991-02-01,14.995450973510742,0.4134202301502228,0.9057300686836243,12.286416053771973,,,,2.8402483463287354,367.07000732421875,453.1000061035156
+1991-02-04,,,,,,,,,,
+1991-03-01,13.39067268371582,0.49208223819732666,0.92646324634552,12.511940002441406,,,,3.1869795322418213,375.2200012207031,482.29998779296875
+1991-04-01,12.111870765686035,0.39800751209259033,0.8642628788948059,12.511940002441406,,,,3.0589873790740967,375.3399963378906,484.7200012207031
+1991-05-01,12.479341506958008,0.34011581540107727,0.9581103920936584,12.73144817352295,,,,2.9850986003875732,389.8299865722656,506.1099853515625
+1991-05-06,,,,,,,,,,
+1991-06-01,11.555957794189453,0.30108359456062317,0.89208984375,11.853414535522461,,,,2.5565452575683594,371.1600036621094,475.9200134277344
+1991-07-01,12.04675579071045,0.33554431796073914,0.9624748229980469,12.397871017456055,,,,3.1678967475891113,387.80999755859375,502.0400085449219
+1991-08-01,11.526222229003906,0.3845158815383911,1.1163398027420044,13.037229537963867,,,,3.012463331222534,395.42999267578125,525.6799926757812
+1991-08-06,,,,,,,,,,
+1991-09-01,12.478833198547363,0.3599340617656708,1.1654454469680786,13.740047454833984,,,,3.0346662998199463,387.8599853515625,526.8800048828125
+1991-10-01,11.83155345916748,0.37447676062583923,1.2292828559875488,14.41578483581543,,,,3.1431360244750977,392.45001220703125,542.97998046875
+1991-11-01,11.139124870300293,0.36902356147766113,1.2734787464141846,13.965290069580078,,,,2.846613883972168,375.2200012207031,523.9000244140625
+1991-11-04,,,,,,,,,,
+1991-12-01,10.851117134094238,0.4109107553958893,1.4568067789077759,15.42939281463623,,,,3.8844411373138428,417.0899963378906,586.3400268554688
+1992-01-01,10.973037719726562,0.4719553291797638,1.5746610164642334,17.584869384765625,,,,3.639812469482422,408.7799987792969,620.2100219726562
+1992-02-01,10.592026710510254,0.49199995398521423,1.61721932888031,18.04088020324707,,,,3.3547844886779785,412.70001220703125,633.469970703125
+1992-02-06,,,,,,,,,,
+1992-03-01,10.317353248596191,0.4253714978694916,1.5517452955245972,16.302349090576172,,,,3.043056011199951,403.69000244140625,603.77001953125
+1992-04-01,11.213163375854492,0.43906375765800476,1.4437123537063599,17.062589645385742,,,,2.631263494491577,414.95001220703125,578.6799926757812
+1992-05-01,11.213163375854492,0.4363253116607666,1.5844820737838745,17.263996124267578,,,,2.705594062805176,415.3500061035156,585.3099975585938
+1992-05-07,,,,,,,,,,
+1992-06-01,12.25231647491455,0.3505205810070038,1.3749638795852661,16.055517196655273,,,,2.705594062805176,408.1400146484375,563.5999755859375
+1992-07-01,11.861115455627441,0.34207984805107117,1.4289811849594116,17.38025665283203,,,,2.263554334640503,424.2099914550781,580.8300170898438
+1992-08-01,10.84400749206543,0.33659130334854126,1.4633547067642212,17.525571823120117,,,,1.9359338283538818,414.0299987792969,563.1199951171875
+1992-08-06,,,,,,,,,,
+1992-09-01,10.243829727172852,0.3310767114162445,1.58120858669281,18.464359283447266,,,,1.6753284931182861,417.79998779296875,583.27001953125
+1992-10-01,8.483662605285645,0.38518592715263367,1.7432584762573242,17.436922073364258,,,,2.12785267829895,418.67999267578125,605.1699829101562
+1992-11-01,8.658098220825195,0.42187052965164185,1.8291932344436646,18.493711471557617,,,,2.030792474746704,431.3500061035156,652.72998046875
+1992-11-05,,,,,,,,,,
+1992-12-01,6.505843639373779,0.43931084871292114,1.6769649982452393,18.788379669189453,,,,1.8814688920974731,435.7099914550781,676.9500122070312
+1993-01-01,6.651134490966797,0.43747296929359436,1.6990629434585571,20.329378128051758,,,,2.4787611961364746,438.7799987792969,696.3400268554688
+1993-02-01,7.022435188293457,0.38968151807785034,1.6376806497573853,19.618152618408203,,,,2.670894145965576,443.3800048828125,670.77001953125
+1993-02-04,,,,,,,,,,
+1993-03-01,6.6405534744262695,0.37947842478752136,1.8169171810150146,19.766319274902344,,,,2.573633909225464,451.6700134277344,690.1300048828125
+1993-04-01,6.346868991851807,0.3776364326477051,1.6794201135635376,18.451818466186523,,,,3.434307336807251,440.19000244140625,661.4199829101562
+1993-05-01,6.885295867919922,0.4172421991825104,1.8193715810775757,18.12286949157715,,,,3.959200859069824,450.19000244140625,700.530029296875
+1993-05-06,,,,,,,,,,
+1993-06-01,6.51603364944458,0.29166528582572937,1.7285257577896118,19.299583435058594,,,,3.7042527198791504,450.5299987792969,703.9500122070312
+1993-07-01,5.872671604156494,0.2049039751291275,1.453533411026001,17.638439178466797,,,,2.9742372035980225,448.1300048828125,704.7000122070312
+1993-08-01,6.037637233734131,0.19567380845546722,1.4756313562393188,17.759246826171875,,,,2.5536391735076904,463.55999755859375,742.8400268554688
+1993-08-05,,,,,,,,,,
+1993-09-01,5.574150562286377,0.1733584851026535,1.620492935180664,17.849544525146484,,,,2.1931254863739014,458.92999267578125,762.780029296875
+1993-10-01,6.105024337768555,0.22805523872375488,1.5738422870635986,19.34462547302246,,,,2.6137242317199707,467.8299865722656,779.260009765625
+1993-11-01,7.150176048278809,0.2336171418428421,1.5713871717453003,20.107433319091797,,,,2.786593437194824,461.7900085449219,754.3900146484375
+1993-11-04,,,,,,,,,,
+1993-12-01,7.535686016082764,0.21771006286144257,1.5836634635925293,22.01595687866211,,,,2.68115496635437,466.45001220703125,776.7999877929688
+1994-01-01,7.535686016082764,0.24376071989536285,1.672054648399353,24.171361923217773,,,,3.64516544342041,481.6099853515625,800.469970703125
+1994-02-01,7.052198886871338,0.27167221903800964,1.620492935180664,23.894229888916016,,,,3.5310842990875244,467.1400146484375,792.5
+1994-02-04,,,,,,,,,,
+1994-03-01,7.31842041015625,0.24837146699428558,1.6646885871887207,23.706161499023438,,,,2.9274797439575195,445.7699890136719,743.4600219726562
+1994-04-01,7.703606128692627,0.22409436106681824,1.8169171810150146,24.543949127197266,,,,3.2354440689086914,450.9100036621094,733.8400268554688
+1994-05-01,8.440468788146973,0.21849241852760315,2.1115520000457764,24.947307586669922,,,,3.4773480892181396,456.5,735.1900024414062
+1994-05-05,,,,,,,,,,
+1994-06-01,7.905290126800537,0.19873161613941193,2.028071165084839,24.444643020629883,,,,3.2959203720092773,444.2699890136719,705.9600219726562
+1994-07-01,8.325791358947754,0.2526327669620514,2.023160934448242,25.569963455200195,,,,3.756444215774536,458.260009765625,722.1599731445312
+1994-08-01,9.217235565185547,0.27138155698776245,2.2834222316741943,26.789077758789062,,,,3.847325563430786,475.489990234375,765.6199951171875
+1994-08-04,,,,,,,,,,
+1994-09-01,9.405942916870117,0.25350791215896606,2.2048532962799072,26.88198471069336,,,,3.9382081031799316,462.7099914550781,764.2899780273438
+1994-10-01,10.064526557922363,0.324998676776886,2.474935293197632,25.811735153198242,,,,4.368794918060303,472.3500061035156,777.489990234375
+1994-11-01,9.557921409606934,0.28031668066978455,2.470024824142456,24.741479873657227,,,,4.004726409912109,453.69000244140625,750.3200073242188
+1994-11-04,,,,,,,,,,
+1994-12-01,9.9633207321167,0.2943686842918396,2.401276111602783,25.12115478515625,,,,3.6103241443634033,459.2699890136719,751.9600219726562
+1995-01-01,9.77692985534668,0.3047473132610321,2.332528591156006,27.753808975219727,,,,3.5117223262786865,470.4200134277344,755.2000122070312
+1995-02-01,10.200541496276855,0.29814332723617554,2.474935293197632,28.13443946838379,,,,4.345465660095215,487.3900146484375,793.72998046875
+1995-02-06,,,,,,,,,,
+1995-03-01,11.169903755187988,0.2667955756187439,2.7941226959228516,29.989917755126953,,,,6.016797065734863,500.7099914550781,817.2100219726562
+1995-04-01,12.870043754577637,0.2895018756389618,3.2115228176116943,31.52293586730957,,,,7.08742618560791,514.7100219726562,843.97998046875
+1995-05-01,12.649023056030273,0.31457316875457764,3.326920747756958,28.96788215637207,,,,6.326970100402832,533.4000244140625,864.5800170898438
+1995-05-04,,,,,,,,,,
+1995-06-01,13.091790199279785,0.3524453043937683,3.5503528118133545,30.15083122253418,,,,7.057007789611816,544.75,933.4500122070312
+1995-07-01,14.847586631774902,0.3415350615978241,3.5552642345428467,30.697277069091797,,,,7.513277530670166,562.0599975585938,1001.2100219726562
+1995-08-01,14.09753131866455,0.32635587453842163,3.6338343620300293,31.08300018310547,,,,6.210629463195801,561.8800048828125,1020.1099853515625
+1995-08-08,,,,,,,,,,
+1995-09-01,12.916881561279297,0.28348639607429504,3.5552642345428467,34.767181396484375,,,,6.301961898803711,584.4099731445312,1043.5400390625
+1995-10-01,13.292764663696289,0.27635207772254944,3.92846941947937,33.5381965637207,,,,6.941293239593506,581.5,1036.06005859375
+1995-11-01,13.207347869873047,0.2901458144187927,3.4226772785186768,35.478694915771484,,,,8.243063926696777,605.3699951171875,1059.199951171875
+1995-11-08,,,,,,,,,,
+1995-12-01,12.52143669128418,0.24333631992340088,3.447230815887451,35.68303298950195,,,,7.557409763336182,615.9299926757812,1052.1300048828125
+1996-01-01,14.868143081665039,0.21089188754558563,3.6338343620300293,32.199371337890625,,,,4.14438533782959,636.02001953125,1059.7900390625
+1996-02-01,16.80373764038086,0.2099376916885376,3.876908779144287,33.924922943115234,,,,4.089058876037598,640.4299926757812,1100.050048828125
+1996-02-07,,,,,,,,,,
+1996-03-01,15.278267860412598,0.1875123232603073,4.051232814788818,32.900360107421875,,,,3.936483860015869,645.5,1101.4000244140625
+1996-04-01,14.797598838806152,0.1860809624195099,4.448990821838379,38.40559768676758,,,,5.248644828796387,654.1699829101562,1190.52001953125
+1996-05-01,14.660264015197754,0.1994406133890152,4.6650567054748535,41.25652313232422,,,,4.538435459136963,669.1199951171875,1243.4300537109375
+1996-05-08,,,,,,,,,,
+1996-06-01,13.641029357910156,0.1603158563375473,4.719073295593262,42.07575225830078,,,,4.385625839233398,670.6300048828125,1185.02001953125
+1996-07-01,14.812233924865723,0.1679503321647644,4.630680561065674,39.836021423339844,,,,3.7132644653320312,639.9500122070312,1080.5899658203125
+1996-08-01,15.759532928466797,0.18512675166130066,4.812370777130127,43.394588470458984,,,,4.269299030303955,651.989990234375,1141.5
+1996-08-07,,,,,,,,,,
+1996-09-01,17.209583282470703,0.16938161849975586,5.180670261383057,42.40607833862305,,,,4.5600385665893555,687.3300170898438,1226.9200439453125
+1996-10-01,17.831613540649414,0.17558389902114868,5.391822814941406,36.86507034301758,,,,4.244296073913574,705.27001953125,1221.510009765625
+1996-11-01,22.03032875061035,0.184172585606575,6.162785530090332,38.95176315307617,,,,4.841867923736572,757.02001953125,1292.6099853515625
+1996-11-06,,,,,,,,,,
+1996-12-01,20.998197555541992,0.15936164557933807,6.491794109344482,41.83340072631836,,,,4.581387996673584,740.739990234375,1291.030029296875
+1997-01-01,21.743183135986328,0.12691716849803925,8.01407527923584,46.8797492980957,,,,4.642676830291748,786.1599731445312,1379.8499755859375
+1997-02-01,19.924028396606445,0.1240537017583847,7.660515785217285,49.97840881347656,,,,4.47982120513916,790.8200073242188,1309.0
+1997-02-06,,,,,,,,,,
+1997-03-01,19.067983627319336,0.13932174444198608,7.203826904296875,45.4803581237793,,,,4.924733638763428,757.1199951171875,1221.699951171875
+1997-04-01,22.298084259033203,0.12977975606918335,9.546177864074707,49.431339263916016,,,,4.807621955871582,801.3400268554688,1260.760009765625
+1997-05-01,24.034704208374023,0.12691716849803925,9.742606163024902,54.45485305786133,,,,5.483453750610352,848.280029296875,1400.3199462890625
+1997-05-07,,,,,,,,,,
+1997-05-28,,,,,,,,,,
+1997-06-01,25.13742446899414,0.10878564417362213,9.929201126098633,63.396705627441406,0.07708299905061722,,,4.3084282875061035,885.1400146484375,1442.0699462890625
+1997-07-01,29.45465660095215,0.1335965245962143,11.107746124267578,66.42845916748047,0.11979199945926666,,,4.592585563659668,954.3099975585938,1593.81005859375
+1997-08-01,28.23609161376953,0.1660410314798355,10.385887145996094,60.97687911987305,0.11692699790000916,,,4.851738929748535,899.469970703125,1587.3199462890625
+1997-08-07,,,,,,,,,,
+1997-09-01,29.579116821289062,0.16556398570537567,10.395710945129395,67.9932632446289,0.21692700684070587,,,6.2071452140808105,947.280029296875,1685.68994140625
+1997-10-01,27.48627281188965,0.13001829385757446,10.214018821716309,64.32245635986328,0.25416699051856995,,,5.889601707458496,914.6199951171875,1593.6099853515625
+1997-11-01,30.555805206298828,0.13550494611263275,11.117568969726562,63.00460433959961,0.20624999701976776,,,5.180382251739502,955.4000244140625,1600.550048828125
+1997-11-06,,,,,,,,,,
+1997-12-01,29.25237274169922,0.10019782930612564,10.155089378356934,59.91267013549805,0.2510420083999634,,,5.087874889373779,970.4299926757812,1570.3499755859375
+1998-01-01,27.609769821166992,0.1397988647222519,11.721570014953613,65.44635009765625,0.24583299458026886,,,4.750911235809326,980.280029296875,1619.3599853515625
+1998-02-01,29.19993782043457,0.1803557574748993,13.317505836486816,72.36756134033203,0.3208329975605011,,,5.452751159667969,1049.3399658203125,1770.510009765625
+1998-02-06,,,,,,,,,,
+1998-03-01,29.10112953186035,0.2099376916885376,14.06391716003418,86.66805267333984,0.35637998580932617,,,5.576151371002197,1101.75,1835.6800537109375
+1998-04-01,32.4630012512207,0.20898354053497314,14.162128448486328,92.79229736328125,0.3822920024394989,,,6.177727699279785,1111.75,1868.4100341796875
+1998-05-01,32.918251037597656,0.2032574564218521,13.327329635620117,84.10579681396484,0.3671880066394806,,,4.930263519287109,1090.8199462890625,1778.8699951171875
+1998-05-06,,,,,,,,,,
+1998-06-01,32.225502014160156,0.2190026193857193,17.029911041259766,83.08383178710938,0.831250011920929,,,5.238887786865234,1133.8399658203125,1894.739990234375
+1998-07-01,37.19002914428711,0.26433059573173523,17.27544403076172,86.6082992553711,0.9239580035209656,,,3.988961935043335,1120.6700439453125,1872.3900146484375
+1998-08-01,31.611520767211914,0.23808826506137848,15.075493812561035,72.04536437988281,0.6979169845581055,,,3.2419238090515137,957.280029296875,1499.25
+1998-08-06,,,,,,,,,,
+1998-09-01,36.12905502319336,0.2910497486591339,17.295076370239258,69.53275299072266,0.9302080273628235,,,4.283971786499023,1017.010009765625,1693.8399658203125
+1998-10-01,41.75224685668945,0.2834153473377228,16.637067794799805,80.36154174804688,1.0536459684371948,,,4.59221076965332,1098.6700439453125,1771.3900146484375
+1998-11-01,46.4265251159668,0.2438134402036667,19.170928955078125,88.54693603515625,1.600000023841858,,,5.535391330718994,1163.6300048828125,1949.5400390625
+1998-11-06,,,,,,,,,,
+1998-12-01,51.91548538208008,0.3125200569629669,21.793180465698242,97.19573974609375,2.6770830154418945,,,5.782783031463623,1229.22998046875,2192.68994140625
+1999-01-01,51.59874725341797,0.3144294023513794,27.499271392822266,102.47120666503906,2.92343807220459,,,5.912916660308838,1279.6400146484375,2505.889892578125
+1999-02-01,47.797481536865234,0.2657618522644043,23.5904541015625,91.21175384521484,3.203125,,,4.984185218811035,1238.3299560546875,2288.030029296875
+1999-02-08,,,,,,,,,,
+1999-03-01,49.97552490234375,0.27435049414634705,28.16712188720703,88.21611785888672,4.304687976837158,,,7.027392387390137,1286.3699951171875,2461.39990234375
+1999-04-01,58.98025894165039,0.35116779804229736,25.554689407348633,97.44929504394531,4.301562786102295,,,7.854666709899902,1335.1800537109375,2542.860107421875
+1999-05-01,65.41220092773438,0.3363769054412842,25.35825538635254,93.19886779785156,2.96875,,,9.187017440795898,1301.8399658203125,2470.52001953125
+1999-05-06,,,,,,,,,,
+1999-05-27,,,,,,,,,,
+1999-06-01,72.96644592285156,0.35355332493782043,28.343910217285156,97.96764373779297,3.128124952316284,,,10.182408332824707,1372.7099609375,2686.1201171875
+1999-07-01,70.95524597167969,0.4251234233379364,26.96893310546875,81.35432434082031,2.50156307220459,,,10.634023666381836,1328.719970703125,2638.489990234375
+1999-08-01,70.32015991210938,0.49812400341033936,29.090301513671875,79.7938461303711,3.109375,,,12.354691505432129,1320.4100341796875,2739.35009765625
+1999-08-06,,,,,,,,,,
+1999-09-01,68.37564849853516,0.48333317041397095,28.46175193786621,69.8066177368164,3.996875047683716,,,14.07535457611084,1282.7099609375,2746.159912109375
+1999-10-01,55.519859313964844,0.6116815209388733,29.090301513671875,47.42917251586914,3.53125,,,17.35419464111328,1362.9300537109375,2966.429931640625
+1999-11-01,58.239349365234375,0.747186541557312,28.613975524902344,45.75767135620117,4.253125190734863,,,17.044021606445312,1388.9100341796875,3336.159912109375
+1999-11-08,,,,,,,,,,
+1999-12-01,61.04003143310547,0.7848799824714661,36.6919059753418,37.92245101928711,3.8062500953674316,,,16.687326431274414,1469.25,4069.31005859375
+2000-01-01,63.515560150146484,0.7920366525650024,30.75990104675293,35.14961242675781,3.2281250953674316,,,13.668244361877441,1394.4599609375,3940.35009765625
+2000-02-01,58.14006042480469,0.8750578165054321,28.088550567626953,36.62297058105469,3.4437499046325684,,,25.31960105895996,1366.4200439453125,4696.68994140625
+2000-02-08,,,,,,,,,,
+2000-03-01,67.05181884765625,1.0368047952651978,33.39197540283203,43.779178619384766,3.3499999046325684,,,27.631258010864258,1498.5799560546875,4572.830078125
+2000-04-01,63.157623291015625,0.947104275226593,21.920852661132812,45.03520965576172,2.7593750953674316,,,30.027847290039062,1452.4300537109375,3860.659912109375
+2000-05-01,60.785640716552734,0.6412634253501892,19.661985397338867,46.097347259521484,2.4156250953674316,,,27.948402404785156,1420.5999755859375,3400.909912109375
+2000-05-08,,,,,,,,,,
+2000-06-01,62.13500213623047,0.7996708750724792,25.142194747924805,35.529056549072266,1.8156249523162842,,,32.277984619140625,1454.5999755859375,3966.110107421875
+2000-07-01,63.65913009643555,0.7758141756057739,21.940492630004883,25.79067039489746,1.506250023841858,,,28.43518829345703,1430.8299560546875,3766.989990234375
+2000-08-01,74.86860656738281,0.9304049015045166,21.940492630004883,27.82395362854004,2.075000047683716,,,32.28449630737305,1517.6800537109375,4206.35009765625
+2000-08-08,,,,,,,,,,
+2000-09-01,63.94328689575195,0.3931553065776825,18.95485496520996,25.980575561523438,1.921875,,,38.555137634277344,1436.510009765625,3672.820068359375
+2000-10-01,55.92375183105469,0.29868337512016296,21.645862579345703,14.6140718460083,1.8312499523162842,,,37.785430908203125,1429.4000244140625,3369.6298828125
+2000-11-01,53.08496856689453,0.2519250810146332,18.03167152404785,12.016016960144043,1.234375,,,31.482683181762695,1314.949951171875,2597.929931640625
+2000-11-08,,,,,,,,,,
+2000-12-01,48.32046127319336,0.22711420059204102,13.631781578063965,8.067792892456055,0.778124988079071,,,28.90570068359375,1320.280029296875,2470.52001953125
+2001-01-01,63.6693229675293,0.33017459511756897,19.190568923950195,14.251648902893066,0.8656250238418579,,,21.702556610107422,1366.010009765625,2772.72998046875
+2001-02-01,56.790767669677734,0.2786443829536438,18.54237174987793,10.536102294921875,0.5093749761581421,,,14.440549850463867,1239.93994140625,2151.830078125
+2001-02-07,,,,,,,,,,
+2001-03-01,54.73835754394531,0.3369686007499695,17.18704605102539,10.535667419433594,0.5115000009536743,,,17.375865936279297,1160.3299560546875,1840.260009765625
+2001-04-01,65.52893829345703,0.38918623328208923,21.292295455932617,15.900238990783691,0.7889999747276306,,,22.32872772216797,1249.4599609375,2116.239990234375
+2001-05-01,63.62804412841797,0.3046000301837921,21.741714477539062,17.430465698242188,0.8345000147819519,,,19.768779754638672,1255.8199462890625,2110.489990234375
+2001-05-08,,,,,,,,,,
+2001-06-01,64.6737060546875,0.35498547554016113,22.942251205444336,16.83243751525879,0.7074999809265137,,,23.362648010253906,1224.3800048828125,2160.5400390625
+2001-07-01,59.949947357177734,0.28688937425613403,20.80202293395996,14.035829544067383,0.6244999766349792,,,18.640790939331055,1211.22998046875,2027.1300048828125
+2001-08-01,56.952762603759766,0.2832247018814087,17.929533004760742,16.18165397644043,0.44699999690055847,,,16.711578369140625,1133.5799560546875,1805.4300537109375
+2001-08-08,,,,,,,,,,
+2001-09-01,52.33213424682617,0.23680917918682098,16.081575393676758,13.6312894821167,0.2985000014305115,,,11.92334270477295,1040.93994140625,1498.800048828125
+2001-10-01,61.660858154296875,0.26810887455940247,18.275238037109375,12.312134742736816,0.3490000069141388,,,13.133195877075195,1059.780029296875,1690.199951171875
+2001-11-01,65.95150756835938,0.3252120614051819,20.17975616455078,14.774558067321777,0.5659999847412109,,,15.958827018737793,1139.449951171875,1930.5799560546875
+2001-11-07,,,,,,,,,,
+2001-12-01,69.1006088256836,0.3343726694583893,20.820878982543945,18.32748794555664,0.5410000085830688,,,15.446427345275879,1148.0799560546875,1950.4000244140625
+2002-01-01,61.63407897949219,0.37742963433265686,20.022619247436523,19.928070068359375,0.7095000147819519,,,16.771486282348633,1130.199951171875,1934.030029296875
+2002-02-01,56.052799224853516,0.3313194811344147,18.334945678710938,17.07868194580078,0.7049999833106995,,,18.105239868164062,1106.72998046875,1731.489990234375
+2002-02-06,,,,,,,,,,
+2002-03-01,59.49020767211914,0.3613981306552887,18.95407485961914,18.907917022705078,0.7149999737739563,,,20.051130294799805,1147.3900146484375,1845.3499755859375
+2002-04-01,47.912540435791016,0.3705587685108185,16.424144744873047,15.56605339050293,0.8345000147819519,,,19.893611907958984,1076.9200439453125,1688.22998046875
+2002-05-01,46.01911926269531,0.35574817657470703,15.999865531921387,15.777122497558594,0.9114999771118164,,,17.971961975097656,1067.1400146484375,1615.72998046875
+2002-05-08,,,,,,,,,,
+2002-06-01,41.26642990112305,0.2705524265766144,17.190977096557617,10.55325984954834,0.8125,,,14.188389778137207,989.8200073242188,1463.2099609375
+2002-07-01,40.349422454833984,0.23299239575862885,15.079033851623535,12.224191665649414,0.7225000262260437,,,11.933782577514648,911.6199951171875,1328.260009765625
+2002-08-01,43.203697204589844,0.22520573437213898,15.424735069274902,12.329721450805664,0.746999979019165,,,10.01123046875,916.0700073242188,1314.8499755859375
+2002-08-07,,,,,,,,,,
+2002-09-01,33.49409484863281,0.22138899564743042,13.746496200561523,8.706437110900879,0.796500027179718,,,9.513158798217773,815.280029296875,1172.06005859375
+2002-10-01,45.344242095947266,0.24535933136940002,16.804426193237305,11.678934097290039,0.9679999947547913,,,11.782204627990723,885.760009765625,1329.75
+2002-11-01,49.92806625366211,0.23665708303451538,18.127525329589844,15.337401390075684,1.1675000190734863,,,14.71778678894043,936.3099975585938,1478.780029296875
+2002-11-06,,,,,,,,,,
+2002-12-01,44.5990104675293,0.2187930941581726,16.248149871826172,14.15894889831543,0.9445000290870667,,,12.360349655151367,879.8200073242188,1335.510009765625
+2003-01-01,45.00184631347656,0.21925139427185059,14.91561222076416,15.56605339050293,1.0924999713897705,,,13.167760848999023,855.7000122070312,1320.9100341796875
+2003-02-01,44.85797119140625,0.22917553782463074,14.896756172180176,15.829883575439453,1.1004999876022339,,,13.712512969970703,841.1500244140625,1337.52001953125
+2003-02-06,,,,,,,,,,
+2003-03-01,45.22197723388672,0.2158920168876648,15.266251564025879,15.302220344543457,1.3014999628067017,,,15.372973442077637,848.1799926757812,1341.1700439453125
+2003-04-01,48.95253372192383,0.21711383759975433,16.123830795288086,17.342517852783203,1.434499979019165,,,17.22471046447754,916.9199829101562,1464.31005859375
+2003-05-01,50.76301956176758,0.2740640342235565,15.518482208251953,19.224523544311523,1.7944999933242798,,,17.618791580200195,963.5900268554688,1595.9100341796875
+2003-05-07,,,,,,,,,,
+2003-06-01,47.655845642089844,0.29101142287254333,16.16796875,18.626508712768555,1.815999984741211,,,15.997578620910645,974.5,1622.800048828125
+2003-07-01,46.933773040771484,0.32185351848602295,16.653514862060547,18.995861053466797,2.0820000171661377,,,16.333431243896484,990.3099975585938,1735.02001953125
+2003-08-01,47.37279510498047,0.34521347284317017,16.722881317138672,18.960683822631836,2.315999984741211,,,19.37755012512207,1008.010009765625,1810.449951171875
+2003-08-06,,,,,,,,,,
+2003-09-01,51.1259651184082,0.3163566291332245,17.530014038085938,18.046072006225586,2.4214999675750732,,,19.657007217407227,995.969970703125,1786.93994140625
+2003-10-01,51.79159927368164,0.3494885563850403,16.48325538635254,18.468202590942383,2.7214999198913574,,,21.84463119506836,1050.7099609375,1932.2099609375
+2003-11-01,52.405128479003906,0.3192576766014099,16.303056716918945,21.423110961914062,2.698499917984009,,,20.621614456176758,1058.199951171875,1960.260009765625
+2003-11-06,,,,,,,,,,
+2003-12-01,53.74093246459961,0.32628077268600464,17.35569190979004,24.272485733032227,2.63100004196167,,,19.5084171295166,1111.9200439453125,2003.3699951171875
+2004-01-01,57.53900146484375,0.3444499373435974,17.533241271972656,25.74994659423828,2.5199999809265137,,,19.119047164916992,1131.1300048828125,2066.14990234375
+2004-02-01,55.95598602294922,0.36521488428115845,16.82303810119629,24.87051010131836,2.1505000591278076,,,18.600963592529297,1144.93994140625,2029.8199462890625
+2004-02-06,,,,,,,,,,
+2004-03-01,53.3401985168457,0.4128514230251312,15.808448791503906,25.6268310546875,2.1640000343322754,,,19.62464141845703,1126.2099609375,1994.219970703125
+2004-04-01,51.208683013916016,0.39361342787742615,16.56939125061035,23.6217041015625,2.180000066757202,,,20.729951858520508,1107.300048828125,1920.1500244140625
+2004-05-01,51.45262145996094,0.4284247159957886,16.632802963256836,23.815183639526367,2.424999952316284,,,22.293439865112305,1120.6800537109375,1986.739990234375
+2004-05-06,,,,,,,,,,
+2004-06-01,51.300846099853516,0.4968261420726776,18.110280990600586,25.503700256347656,2.7200000286102295,,,23.227535247802734,1140.8399658203125,2047.7900390625
+2004-07-01,50.672325134277344,0.4937727451324463,18.065902709960938,24.378023147583008,1.9459999799728394,,,21.075881958007812,1101.719970703125,1887.3599853515625
+2004-08-01,49.28722381591797,0.5265995860099792,17.31130027770996,23.6217041015625,1.906999945640564,,,22.91964340209961,1104.239990234375,1838.0999755859375
+2004-08-06,,,,,,,,,,
+2004-09-01,50.00397872924805,0.5916416049003601,17.5849609375,24.764976501464844,2.0429999828338623,,64.8648681640625,24.718441009521484,1114.5799560546875,1896.8399658203125
+2004-10-01,52.34260559082031,0.800052285194397,17.788476943969727,25.978591918945312,1.7065000534057617,,95.41541290283203,28.003637313842773,1130.199951171875,1974.989990234375
+2004-11-01,54.96117401123047,1.0237311124801636,17.050731658935547,26.945974349975586,1.9839999675750732,,91.0810775756836,30.26772117614746,1173.8199462890625,2096.81005859375
+2004-11-08,,,,,,,,,,
+2004-12-01,57.60344696044922,0.9832708239555359,18.939943313598633,29.918479919433594,2.2144999504089355,,96.49149322509766,31.357276916503906,1211.9200439453125,2175.43994140625
+2005-01-01,54.58831024169922,1.1741228103637695,18.628063201904297,27.930959701538086,2.1610000133514404,,97.90790557861328,28.444419860839844,1181.27001953125,2062.409912109375
+2005-02-01,54.09749221801758,1.3698610067367554,17.83417510986328,27.438472747802734,1.7589999437332153,,94.0890884399414,30.86894416809082,1203.5999755859375,2051.719970703125
+2005-02-08,,,,,,,,,,
+2005-03-01,53.49815368652344,1.2724499702453613,17.18527603149414,26.6469669342041,1.7135000228881836,,90.34534454345703,33.57841110229492,1180.5899658203125,1999.22998046875
+2005-04-01,44.7164421081543,1.1011403799057007,17.988739013671875,23.305103302001953,1.6180000305175781,,110.110107421875,29.735000610351562,1156.8499755859375,1921.6500244140625
+2005-05-01,44.23051071166992,1.2141252756118774,18.3442440032959,23.867952346801758,1.7755000591278076,,138.77377319335938,33.119998931884766,1191.5,2068.219970703125
+2005-05-06,,,,,,,,,,
+2005-06-01,43.555538177490234,1.124043345451355,17.71768569946289,24.254899978637695,1.6545000076293945,,147.22222900390625,28.610000610351562,1191.3299560546875,2056.9599609375
+2005-07-01,48.991188049316406,1.3023751974105835,18.26690673828125,23.234752655029297,2.257499933242798,,144.02401733398438,29.639999389648438,1234.1800537109375,2184.830078125
+2005-08-01,47.3240966796875,1.431849479675293,19.529403686523438,23.58652687072754,2.134999990463257,,143.1431427001953,27.040000915527344,1220.3299560546875,2152.090087890625
+2005-08-08,,,,,,,,,,
+2005-09-01,47.202552795410156,1.6370543241500854,18.40694236755371,24.00865936279297,2.265000104904175,,158.3883819580078,29.850000381469727,1228.81005859375,2151.68994140625
+2005-10-01,48.1793098449707,1.7585887908935547,18.385473251342773,23.867952346801758,1.9930000305175781,,186.25625610351562,32.25,1207.010009765625,2120.300048828125
+2005-11-01,52.309974670410156,2.0709757804870605,19.80194664001465,24.976051330566406,2.4230000972747803,,202.65765380859375,32.61000061035156,1249.47998046875,2232.820068359375
+2005-11-08,,,,,,,,,,
+2005-12-01,48.483585357666016,2.1952593326568604,18.762245178222656,25.76755142211914,2.3575000762939453,,207.63763427734375,36.959999084472656,1248.2900390625,2205.320068359375
+2006-01-01,47.95273208618164,2.305800437927246,20.19721794128418,25.169517517089844,2.240999937057495,,216.5465545654297,39.72999954223633,1280.0799560546875,2305.820068359375
+2006-02-01,47.32752227783203,2.0914342403411865,19.27882957458496,26.207246780395508,1.871999979019165,,181.49148559570312,38.54999923706055,1280.6600341796875,2281.389892578125
+2006-02-08,,,,,,,,,,
+2006-03-01,48.76497268676758,1.9152405261993408,19.588937759399414,26.734920501708984,1.8265000581741333,,195.1951904296875,34.95000076293945,1294.8699951171875,2339.7900390625
+2006-04-01,48.6880989074707,2.149454355239868,17.385986328125,24.694625854492188,1.7604999542236328,,209.17918395996094,39.20000076293945,1310.6099853515625,2322.570068359375
+2006-05-01,47.24531936645508,1.8251585960388184,16.306108474731445,24.149375915527344,1.7304999828338623,,186.09609985351562,28.6299991607666,1270.0899658203125,2178.8798828125
+2006-05-08,,,,,,,,,,
+2006-06-01,45.58832931518555,1.7488168478012085,16.83946990966797,24.465961456298828,1.934000015258789,,209.8748779296875,30.360000610351562,1270.199951171875,2172.090087890625
+2006-07-01,45.938453674316406,2.075251340866089,17.388734817504883,24.78256607055664,1.344499945640564,,193.49349975585938,28.510000228881836,1276.6600341796875,2091.469970703125
+2006-08-01,48.051090240478516,2.0718915462493896,18.574007034301758,26.048952102661133,1.5414999723434448,,189.45445251464844,32.439998626708984,1303.8199462890625,2183.75
+2006-08-08,,,,,,,,,,
+2006-09-01,48.820682525634766,2.350689172744751,19.839282989501953,27.368104934692383,1.6059999465942383,,201.15115356445312,37.459999084472656,1335.8499755859375,2258.429931640625
+2006-10-01,55.01115798950195,2.475886821746826,20.82581329345703,29.90088653564453,1.9045000076293945,,238.4334259033203,38.25,1377.93994140625,2366.7099609375
+2006-11-01,54.766883850097656,2.798962354660034,21.29731559753418,29.021446228027344,2.0169999599456787,,242.64764404296875,40.15999984741211,1400.6300048828125,2431.77001953125
+2006-11-08,,,,,,,,,,
+2006-12-01,58.07079315185547,2.5907039642333984,21.73406219482422,29.812957763671875,1.9730000495910645,,230.47047424316406,41.119998931884766,1418.300048828125,2415.2900390625
+2007-01-01,59.266300201416016,2.617882013320923,22.461923599243164,30.252676010131836,1.8834999799728394,,251.00100708007812,38.869998931884766,1438.239990234375,2463.929931640625
+2007-02-01,55.55428695678711,2.583681344985962,20.50397491455078,30.37578582763672,1.9570000171661377,,224.949951171875,39.25,1406.8199462890625,2416.14990234375
+2007-02-07,,,,,,,,,,
+2007-03-01,56.51309585571289,2.8371317386627197,20.3559513092041,29.707414627075195,1.9895000457763672,,229.30931091308594,41.70000076293945,1420.8599853515625,2421.639892578125
+2007-04-01,61.27952575683594,3.0475287437438965,21.867843627929688,32.539215087890625,3.066499948501587,,235.92591857910156,41.560001373291016,1482.3699951171875,2525.090087890625
+2007-05-01,63.91150665283203,3.700700044631958,22.415639877319336,33.18999099731445,3.4570000171661377,,249.20420837402344,44.060001373291016,1530.6199951171875,2604.52001953125
+2007-05-08,,,,,,,,,,
+2007-06-01,63.347747802734375,3.7266552448272705,21.59428596496582,32.5040397644043,3.4205000400543213,,261.6116027832031,40.150001525878906,1503.3499755859375,2603.22998046875
+2007-07-01,66.59786987304688,4.023471355438232,21.242568969726562,30.70998764038086,3.927000045776367,,255.2552490234375,40.290000915527344,1455.27001953125,2546.27001953125
+2007-08-01,70.23324584960938,4.228675365447998,21.052053451538086,30.12954330444336,3.995500087738037,,257.88287353515625,42.75,1473.989990234375,2596.360107421875
+2007-08-08,,,,,,,,,,
+2007-09-01,71.1520004272461,4.68641471862793,21.662628173828125,30.49891471862793,4.65749979019165,,283.9189147949219,43.65999984741211,1526.75,2701.5
+2007-10-01,70.13726806640625,5.800380706787109,27.067256927490234,30.674802780151367,4.457499980926514,,353.8538513183594,47.900001525878906,1549.3800048828125,2859.1201171875
+2007-11-01,63.529457092285156,5.564334392547607,24.70686912536621,29.6898193359375,4.5279998779296875,,346.8468322753906,42.13999938964844,1481.1400146484375,2660.9599609375
+2007-11-07,,,,,,,,,,
+2007-12-01,65.52474212646484,6.048642158508301,26.26405906677246,28.4762020111084,4.631999969482422,,346.0860900878906,42.72999954223633,1468.3599853515625,2652.280029296875
+2008-01-01,64.92465209960938,4.133399963378906,24.050800323486328,27.21040916442871,3.884999990463257,,282.43243408203125,34.93000030517578,1378.550048828125,2389.860107421875
+2008-02-01,69.0161361694336,3.8176541328430176,20.066930770874023,25.923078536987305,3.2235000133514404,,235.82582092285156,33.650001525878906,1330.6300048828125,2271.47998046875
+2008-02-06,,,,,,,,,,
+2008-03-01,70.05885314941406,4.381966590881348,21.01883316040039,26.399211883544922,3.565000057220459,,220.45545959472656,35.59000015258789,1322.699951171875,2279.10009765625
+2008-04-01,73.44194030761719,5.311798572540283,21.122520446777344,24.706567764282227,3.93149995803833,,287.43243408203125,37.290000915527344,1385.5899658203125,2412.800048828125
+2008-05-01,78.75386810302734,5.763737201690674,20.974388122558594,24.016834259033203,4.080999851226807,,293.1932067871094,44.060001373291016,1400.3800048828125,2522.659912109375
+2008-05-07,,,,,,,,,,
+2008-06-01,72.41636657714844,5.11300802230835,20.449499130249023,23.981456756591797,3.6665000915527344,,263.4734802246094,39.38999938964844,1280.0,2292.97998046875
+2008-07-01,78.18988800048828,4.853753566741943,19.118900299072266,24.197914123535156,3.816999912261963,,237.1121063232422,41.349998474121094,1267.3800048828125,2325.550048828125
+2008-08-01,74.37141418457031,5.176827907562256,20.285959243774414,24.712379455566406,4.040500164031982,,231.8768768310547,42.83000183105469,1282.8299560546875,2367.52001953125
+2008-08-06,,,,,,,,,,
+2008-09-01,71.73551177978516,3.470762014389038,19.91908073425293,20.454687118530273,3.638000011444092,,200.46046447753906,39.470001220703125,1166.3599853515625,2091.8798828125
+2008-10-01,57.02163314819336,3.2854056358337402,16.66515350341797,14.2785062789917,2.861999988555908,,179.85986328125,26.639999389648438,968.75,1720.949951171875
+2008-11-01,50.04803466796875,2.8298041820526123,15.090431213378906,12.444729804992676,2.134999990463257,,146.6266326904297,23.15999984741211,896.239990234375,1535.5699462890625
+2008-11-06,,,,,,,,,,
+2008-12-01,51.90673828125,2.6062774658203125,14.606595039367676,14.189484596252441,2.563999891281128,,153.97897338867188,21.290000915527344,903.25,1577.030029296875
+2009-01-01,56.52627944946289,2.7522425651550293,12.848398208618164,11.888165473937988,2.940999984741211,,169.43443298339844,19.309999465942383,825.8800048828125,1476.4200439453125
+2009-02-01,56.76066589355469,2.7272019386291504,12.13459587097168,9.274202346801758,3.239500045776367,,169.16416931152344,16.700000762939453,735.0900268554688,1377.8399658203125
+2009-02-06,,,,,,,,,,
+2009-03-01,60.08320999145508,3.209982395172119,13.897279739379883,8.146258354187012,3.671999931335449,,174.20420837402344,21.389999389648438,797.8699951171875,1528.5899658203125
+2009-04-01,64.00231170654297,3.8423893451690674,15.3270902633667,11.028571128845215,4.026000022888184,,198.1831817626953,27.350000381469727,872.8099975585938,1717.300048828125
+2009-05-01,65.90607452392578,4.147140979766846,15.803704261779785,12.274019241333008,3.8994998931884766,,208.82382202148438,28.18000030517578,919.1400146484375,1774.3299560546875
+2009-05-06,,,,,,,,,,
+2009-06-01,65.09091186523438,4.349293231964111,18.0966796875,11.69642448425293,4.183000087738037,,211.00601196289062,28.299999237060547,919.3200073242188,1835.0400390625
+2009-07-01,73.51245880126953,4.989336013793945,17.906352996826172,14.879191398620605,4.288000106811523,,221.7467498779297,32.41999816894531,987.47998046875,1978.5
+2009-08-01,73.58724975585938,5.1365203857421875,18.766645431518555,15.714898109436035,4.059500217437744,,231.06607055664062,31.420000076293945,1020.6199951171875,2009.06005859375
+2009-08-06,,,,,,,,,,
+2009-09-01,74.90744018554688,5.659914016723633,19.69136619567871,14.061647415161133,4.668000221252441,,248.1731719970703,33.040000915527344,1057.0799560546875,2122.419921875
+2009-10-01,75.53370666503906,5.756102561950684,21.230228424072266,13.72740650177002,5.940499782562256,,268.3283386230469,32.939998626708984,1036.18994140625,2045.1099853515625
+2009-11-01,79.12847900390625,6.1045241355896,22.51645278930664,14.055986404418945,6.795499801635742,,291.7917785644531,35.08000183105469,1095.6300048828125,2144.60009765625
+2009-11-06,,,,,,,,,,
+2009-12-01,82.34589385986328,6.434925556182861,23.438796997070312,15.443329811096191,6.72599983215332,,310.30029296875,36.779998779296875,1115.0999755859375,2269.14990234375
+2010-01-01,76.99246215820312,5.86481237411499,21.670120239257812,15.999075889587402,6.270500183105469,,265.2352294921875,32.29999923706055,1073.8699951171875,2147.35009765625
+2010-02-01,79.99315643310547,6.248349666595459,22.04693031311035,17.19167137145996,5.920000076293945,,263.6636657714844,34.650001525878906,1104.489990234375,2238.260009765625
+2010-02-08,,,,,,,,,,
+2010-03-01,81.0396728515625,7.176041126251221,22.629026412963867,17.88887596130371,6.78849983215332,,283.8438415527344,35.369998931884766,1169.4300537109375,2397.9599609375
+2010-04-01,81.51359558105469,7.972740650177002,23.594758987426758,20.0878963470459,6.855000019073486,,263.11309814453125,33.599998474121094,1186.68994140625,2461.18994140625
+2010-05-01,79.1503677368164,7.84417724609375,19.932706832885742,17.157638549804688,6.2729997634887695,,243.0580596923828,32.08000183105469,1089.4100341796875,2257.0400390625
+2010-05-06,,,,,,,,,,
+2010-06-01,78.42550659179688,7.680809020996094,17.857412338256836,14.817129135131836,5.4629998207092285,,222.69769287109375,26.43000030517578,1030.7099609375,2109.239990234375
+2010-07-01,81.55033874511719,7.855478763580322,20.03040885925293,18.038850784301758,5.894499778747559,,242.66766357421875,28.719999313354492,1101.5999755859375,2254.699951171875
+2010-08-01,78.20323944091797,7.4233856201171875,18.214401245117188,15.64971923828125,6.241499900817871,,225.2352294921875,27.700000762939453,1049.3299560546875,2114.030029296875
+2010-08-06,,,,,,,,,,
+2010-09-01,85.6181411743164,8.664690971374512,19.10737419128418,19.168588638305664,7.853000164031982,,263.1581726074219,26.149999618530273,1141.199951171875,2368.6201171875
+2010-10-01,91.65619659423828,9.190831184387207,20.808244705200195,21.757949829101562,8.261500358581543,,307.15716552734375,28.149999618530273,1183.260009765625,2507.409912109375
+2010-11-01,90.290283203125,9.501388549804688,19.70814323425293,21.311634063720703,8.770000457763672,,278.1331481933594,27.799999237060547,1180.550048828125,2498.22998046875
+2010-11-08,,,,,,,,,,
+2010-12-01,94.08943176269531,9.84980583190918,21.909496307373047,21.423206329345703,9.0,,297.28228759765625,30.780000686645508,1257.6400146484375,2652.8701171875
+2011-01-01,103.8599853515625,10.361597061157227,21.76820182800293,19.823131561279297,8.482000350952148,,300.48046875,33.04999923706055,1286.1199951171875,2700.080078125
+2011-02-01,103.78302001953125,10.785745620727539,20.865453720092773,20.065792083740234,8.66450023651123,,307.00701904296875,34.5,1327.219970703125,2782.27001953125
+2011-02-08,,,,,,,,,,
+2011-03-01,104.95984649658203,10.64222526550293,20.04909324645996,19.879127502441406,9.006500244140625,,293.6736755371094,33.15999984741211,1325.8299560546875,2781.070068359375
+2011-04-01,109.79368591308594,10.691693305969238,20.467607498168945,18.910266876220703,9.790499687194824,,272.32232666015625,33.54999923706055,1363.6099853515625,2873.5400390625
+2011-05-01,108.73165130615234,10.621461868286133,19.7490291595459,19.135168075561523,9.834500312805176,,264.7747802734375,34.630001068115234,1345.199951171875,2835.300048828125
+2011-05-06,,,,,,,,,,
+2011-06-01,110.91179656982422,10.25013542175293,20.665348052978516,19.510000228881836,10.224499702453613,,253.4434356689453,31.450000762939453,1320.6400146484375,2773.52001953125
+2011-07-01,117.571044921875,11.923834800720215,21.778095245361328,17.562105178833008,11.12600040435791,,302.14715576171875,27.709999084472656,1292.280029296875,2756.3798828125
+2011-08-01,111.14454650878906,11.75130844116211,21.142244338989258,15.623312950134277,10.761500358581543,,270.7507629394531,25.239999771118164,1218.8900146484375,2579.4599609375
+2011-08-08,,,,,,,,,,
+2011-09-01,113.55061340332031,11.644124984741211,19.90796661376953,13.119815826416016,10.81149959564209,,257.77777099609375,24.170000076293945,1131.4200439453125,2415.39990234375
+2011-10-01,119.88819122314453,12.360504150390625,21.299680709838867,15.485393524169922,10.67549991607666,,296.6166076660156,29.40999984741211,1253.300048828125,2684.409912109375
+2011-11-01,122.07645416259766,11.670994758605957,20.459848403930664,15.42860221862793,9.614500045776367,,299.9949951171875,27.420000076293945,1246.9599609375,2620.340087890625
+2011-11-08,,,,,,,,,,
+2011-12-01,119.88117218017578,12.367225646972656,20.92014503479004,15.068914413452148,8.654999732971191,,323.2732849121094,28.270000457763672,1257.5999755859375,2605.14990234375
+2012-01-01,125.56623077392578,13.939234733581543,23.79706382751465,14.749091148376465,9.722000122070312,,290.3453369140625,30.950000762939453,1312.4100341796875,2813.840087890625
+2012-02-01,128.25875854492188,16.564136505126953,25.57801628112793,15.662582397460938,8.98449993133545,,309.4344482421875,32.88999938964844,1365.6800537109375,2966.889892578125
+2012-02-08,,,,,,,,,,
+2012-03-01,136.5597381591797,18.308074951171875,26.1682071685791,15.377117156982422,10.125499725341797,,320.9409484863281,34.310001373291016,1408.469970703125,3091.570068359375
+2012-04-01,135.53221130371094,17.83262062072754,25.97353172302246,14.882647514343262,11.595000267028809,,302.72772216796875,33.54999923706055,1397.9100341796875,3046.360107421875
+2012-05-01,126.25150299072266,17.64176368713379,23.677928924560547,13.811394691467285,10.645500183105469,,290.7207336425781,31.049999237060547,1310.3299560546875,2827.340087890625
+2012-05-08,,,,,,,,,,
+2012-06-01,128.5418243408203,17.83323097229004,24.976381301879883,15.054808616638184,11.417499542236328,,290.3253173828125,32.369998931884766,1362.1600341796875,2935.050048828125
+2012-07-01,128.80467224121094,18.650381088256836,24.06191635131836,13.332178115844727,11.664999961853027,,316.8017883300781,30.8799991607666,1379.3199462890625,2939.52001953125
+2012-08-01,128.06199645996094,20.314008712768555,25.1641845703125,14.178669929504395,12.41349983215332,,342.88787841796875,31.270000457763672,1406.5799560546875,3066.9599609375
+2012-08-08,,,,,,,,,,
+2012-09-01,136.92532348632812,20.458269119262695,24.459667205810547,14.120949745178223,12.715999603271484,,377.62762451171875,32.439998626708984,1440.6700439453125,3116.22998046875
+2012-10-01,128.39756774902344,18.256948471069336,23.456958770751953,12.4627103805542,11.644499778747559,,340.490478515625,34.029998779296875,1412.1600341796875,2977.22998046875
+2012-11-01,125.45381927490234,17.94904899597168,21.878910064697266,13.178736686706543,12.602499961853027,,349.5345458984375,34.61000061035156,1416.1800537109375,3010.239990234375
+2012-11-07,,,,,,,,,,
+2012-12-01,126.98394775390625,16.39484405517578,22.13326644897461,13.19808578491211,12.543499946594238,,354.0440368652344,37.68000030517578,1426.18994140625,3019.510009765625
+2013-01-01,134.6208953857422,14.03252124786377,22.746475219726562,15.598185539245605,13.274999618530273,,378.2232360839844,37.83000183105469,1498.1099853515625,3142.1298828125
+2013-02-01,133.1359405517578,13.598445892333984,23.036500930786133,15.79291820526123,13.213500022888184,,401.0010070800781,39.310001373291016,1514.6800537109375,3160.18994140625
+2013-02-06,,,,,,,,,,
+2013-03-01,141.99786376953125,13.716739654541016,23.903995513916016,16.74711036682129,13.32450008392334,,397.49249267578125,43.52000045776367,1569.18994140625,3267.52001953125
+2013-04-01,134.8347625732422,13.720457077026367,27.655447006225586,16.822824478149414,12.690500259399414,,412.69769287109375,45.08000183105469,1597.5699462890625,3328.7900390625
+2013-05-01,138.48284912109375,13.935823440551758,29.15936851501465,17.234567642211914,13.460000038146973,,436.0460510253906,42.90999984741211,1630.739990234375,3455.909912109375
+2013-05-08,,,,,,,,,,
+2013-06-01,127.82191467285156,12.368638038635254,29.060949325561523,17.783567428588867,13.884499549865723,,440.6256408691406,45.560001373291016,1606.280029296875,3403.25
+2013-07-01,130.45040893554688,14.115401268005371,26.789243698120117,19.142587661743164,15.060999870300293,,444.3193054199219,47.279998779296875,1685.72998046875,3626.3701171875
+2013-08-01,121.90938568115234,15.19745922088623,28.101774215698242,19.695158004760742,14.048999786376953,,423.8738708496094,45.75,1632.969970703125,3589.8701171875
+2013-08-07,,,,,,,,,,
+2013-09-01,124.47478485107422,14.96906566619873,28.19812774658203,20.306934356689453,15.631999969482422,,438.3934020996094,51.939998626708984,1681.550048828125,3771.47998046875
+2013-10-01,120.46192169189453,16.41180992126465,30.002870559692383,19.72601890563965,18.201499938964844,,515.8057861328125,54.220001220703125,1756.5400390625,3919.7099609375
+2013-11-01,120.77778625488281,17.45956802368164,32.30753707885742,22.58371353149414,19.680999755859375,,530.3253173828125,56.779998779296875,1805.81005859375,4059.889892578125
+2013-11-06,,,,,,,,,,
+2013-12-01,126.7584228515625,17.71782684326172,31.937856674194336,24.151473999023438,19.93950080871582,,560.9158935546875,59.880001068115234,1848.3599853515625,4176.58984375
+2014-01-01,119.39906311035156,15.80967903137207,32.304969787597656,21.634523391723633,17.934499740600586,,591.0760498046875,59.189998626708984,1782.5899658203125,4103.8798828125
+2014-02-01,125.13655090332031,16.61942481994629,32.70622634887695,21.913677215576172,18.104999542236328,,608.4334106445312,68.62999725341797,1859.449951171875,4308.1201171875
+2014-02-06,,,,,,,,,,
+2014-03-01,130.79644775390625,17.052499771118164,35.256614685058594,22.53180694580078,16.818500518798828,,557.8128051757812,65.73999786376953,1872.3399658203125,4198.990234375
+2014-04-01,133.50091552734375,18.747455596923828,34.7491340637207,24.246538162231445,15.206500053405762,,534.8800048828125,61.689998626708984,1883.949951171875,4114.56005859375
+2014-05-01,125.27215576171875,20.11072540283203,35.21359634399414,24.767969131469727,15.6274995803833,,571.6500244140625,64.54000091552734,1923.5699462890625,4242.6201171875
+2014-05-07,,,,,,,,,,
+2014-06-01,123.88963317871094,20.782461166381836,36.12032699584961,24.94846534729004,16.23900032043457,,584.6699829101562,72.36000061035156,1960.22998046875,4408.18017578125
+2014-07-01,130.99761962890625,21.37957000732422,37.38497543334961,26.72607421875,15.649499893188477,,579.5499877929688,69.25,1930.6700439453125,4369.77001953125
+2014-08-01,131.4281463623047,22.922651290893555,39.35124206542969,27.834640502929688,16.95199966430664,,582.3599853515625,71.9000015258789,2003.3699951171875,4580.27001953125
+2014-08-06,,,,,,,,,,
+2014-09-01,130.50729370117188,22.643360137939453,40.40761947631836,26.66560935974121,16.121999740600586,,588.4099731445312,69.19000244140625,1972.2900390625,4493.39013671875
+2014-10-01,113.0242691040039,24.27278709411621,40.92185974121094,26.89417266845703,15.27299976348877,,567.8699951171875,70.12000274658203,2018.050048828125,4630.740234375
+2014-11-01,111.49117279052734,26.729284286499023,41.67144012451172,28.27128028869629,16.93199920654297,,549.0800170898438,73.68000030517578,2067.56005859375,4791.6298828125
+2014-11-06,,,,,,,,,,
+2014-12-01,111.05672454833984,24.915250778198242,40.74142074584961,28.068769454956055,15.517499923706055,,530.6599731445312,72.69999694824219,2058.89990234375,4736.0498046875
+2015-01-01,106.12132263183594,26.445661544799805,35.434940338134766,26.79075813293457,17.726499557495117,,537.5499877929688,70.12999725341797,1994.989990234375,4635.240234375
+2015-02-01,112.09505462646484,28.996322631835938,38.460933685302734,27.767194747924805,19.007999420166016,,562.6300048828125,79.0999984741211,2104.5,4963.52978515625
+2015-02-06,,,,,,,,,,
+2015-03-01,111.87759399414062,28.197509765625,35.91679000854492,26.139816284179688,18.604999542236328,,554.7000122070312,73.94000244140625,2067.889892578125,4900.8798828125
+2015-04-01,119.3988265991211,28.360668182373047,42.96587371826172,23.521541595458984,21.089000701904297,,548.77001953125,76.05999755859375,2085.510009765625,4941.419921875
+2015-05-01,118.25563049316406,29.523195266723633,41.39352035522461,23.3579158782959,21.46150016784668,,545.3200073242188,79.08999633789062,2107.389892578125,5070.02978515625
+2015-05-06,,,,,,,,,,
+2015-06-01,114.24130249023438,28.542850494384766,39.253108978271484,21.762542724609375,21.704500198364258,,540.0399780273438,81.01000213623047,2063.110107421875,4986.8701171875
+2015-07-01,113.77072143554688,27.60302734375,41.520286560058594,22.683992385864258,26.8075008392334,,657.5,81.98999786376953,2103.840087890625,5128.27978515625
+2015-08-01,103.86784362792969,25.65966796875,38.692989349365234,20.93431854248047,25.644500732421875,,647.8200073242188,78.56999969482422,1972.1800537109375,4776.509765625
+2015-08-06,,,,,,,,,,
+2015-09-01,102.66226959228516,25.21347999572754,39.61040496826172,20.028608322143555,25.594499588012695,,638.3699951171875,82.22000122070312,1920.030029296875,4620.16015625
+2015-10-01,99.1993637084961,27.31651496887207,47.11008071899414,19.46390151977539,31.295000076293945,,737.3900146484375,88.66000366210938,2079.360107421875,5053.75
+2015-11-01,98.7319564819336,27.042200088500977,48.64043045043945,21.868392944335938,33.2400016784668,,762.8499755859375,91.45999908447266,2080.409912109375,5108.669921875
+2015-11-06,,,,,,,,,,
+2015-12-01,98.37144470214844,24.16438102722168,49.98638916015625,22.03421974182129,33.794498443603516,,778.010009765625,93.94000244140625,2043.93994140625,5007.41015625
+2016-01-01,89.20050811767578,22.3461971282959,49.63501739501953,20.343839645385742,29.350000381469727,,761.3499755859375,89.12999725341797,1940.239990234375,4613.9501953125
+2016-02-01,93.6608657836914,22.196985244750977,45.841888427734375,20.051719665527344,27.625999450683594,,717.219970703125,85.1500015258789,1932.22998046875,4557.9501953125
+2016-02-08,,,,,,,,,,
+2016-03-01,109.36297607421875,25.15644073486328,50.11842727661133,23.285871505737305,29.68199920654297,,762.9000244140625,93.80000305175781,2059.739990234375,4869.85009765625
+2016-04-01,105.3841552734375,21.636524200439453,45.25450134277344,20.178363800048828,32.97949981689453,,707.8800048828125,94.22000122070312,2065.300048828125,4775.35986328125
+2016-05-01,111.01663208007812,23.049110412597656,48.094825744628906,20.956079483032227,36.13949966430664,,748.8499755859375,99.47000122070312,2096.949951171875,4948.0498046875
+2016-05-06,,,,,,,,,,
+2016-06-01,110.65898895263672,22.20018196105957,46.75897216796875,19.947153091430664,35.78099822998047,,703.530029296875,95.79000091552734,2098.860107421875,4842.669921875
+2016-07-01,117.10401916503906,24.199600219726562,51.79398727416992,21.839828491210938,37.94049835205078,,791.3400268554688,97.86000061035156,2173.60009765625,5162.1298828125
+2016-08-01,115.83541107177734,24.638490676879883,52.506752014160156,20.885658264160156,38.45800018310547,,789.8499755859375,102.30999755859375,2170.949951171875,5213.22021484375
+2016-08-08,,,,,,,,,,
+2016-09-01,116.81381225585938,26.394628524780273,52.96271896362305,21.479366302490234,41.865501403808594,13.321450233459473,804.0599975585938,108.54000091552734,2168.27001953125,5312.0
+2016-10-01,113.019287109375,26.509033203125,55.095947265625,20.878395080566406,39.49100112915039,13.680961608886719,809.9000244140625,107.51000213623047,2126.14990234375,5189.14013671875
+2016-11-01,119.29200744628906,25.803936004638672,55.40858840942383,19.980859756469727,37.528499603271484,14.926712036132812,775.8800048828125,102.80999755859375,2198.81005859375,5323.68017578125
+2016-11-08,,,,,,,,,,
+2016-12-01,123.1717300415039,27.180198669433594,57.52321243286133,18.655929565429688,37.493499755859375,15.31966781616211,792.4500122070312,102.94999694824219,2238.830078125,5383.1201171875
+2017-01-01,129.5013427734375,28.47795867919922,59.84672927856445,22.670724868774414,41.17399978637695,17.554771423339844,820.1900024414062,113.37999725341797,2278.8701171875,5614.7900390625
+2017-02-01,133.43417358398438,32.148292541503906,59.22650909423828,24.339130401611328,42.25199890136719,17.69411849975586,844.9299926757812,118.33999633789062,2363.639892578125,5825.43994140625
+2017-02-08,,,,,,,,,,
+2017-03-01,130.24111938476562,33.85976028442383,61.33644485473633,24.011991500854492,44.32699966430664,17.858545303344727,847.7999877929688,130.1300048828125,2362.719970703125,5911.740234375
+2017-04-01,119.88253784179688,33.85739517211914,63.75787353515625,23.72492027282715,46.2495002746582,18.70298194885254,924.52001953125,133.74000549316406,2384.199951171875,6047.60986328125
+2017-05-01,114.1535415649414,36.00456237792969,65.0430908203125,23.328956604003906,49.73099899291992,19.338397979736328,987.0900268554688,141.86000061035156,2411.800048828125,6198.52001953125
+2017-05-08,,,,,,,,,,
+2017-06-01,116.17494201660156,34.084716796875,64.56354522705078,23.700172424316406,48.400001525878906,17.030832290649414,929.6799926757812,141.44000244140625,2423.409912109375,6140.419921875
+2017-07-01,109.25714874267578,35.19940948486328,68.0947265625,25.520694732666016,49.388999938964844,17.911497116088867,945.5,146.49000549316406,2470.300048828125,6348.1201171875
+2017-08-01,108.01860809326172,38.81330490112305,70.03360748291016,26.852062225341797,49.029998779296875,20.882347106933594,955.239990234375,155.16000366210938,2471.64990234375,6428.66015625
+2017-08-08,,,,,,,,,,
+2017-09-01,110.72441864013672,36.6182861328125,70.14307403564453,27.700807571411133,48.067501068115234,21.517763137817383,973.719970703125,149.17999267578125,2519.360107421875,6495.9599609375
+2017-10-01,117.57792663574219,40.163211822509766,78.32596588134766,25.408233642578125,55.263999938964844,23.067289352416992,1033.0400390625,175.16000366210938,2575.260009765625,6727.669921875
+2017-11-01,117.50923919677734,40.83085632324219,79.2582015991211,24.86334991455078,58.837501525878906,21.80481719970703,1036.1700439453125,181.47000122070312,2647.580078125,6873.97021484375
+2017-11-09,,,,,,,,,,
+2017-12-01,118.25981140136719,40.3528938293457,80.95279693603516,24.43583106994629,58.4734992980957,22.65203857421875,1053.4000244140625,175.24000549316406,2673.610107421875,6903.39013671875
+2018-01-01,126.18390655517578,39.9236946105957,89.91493225097656,28.855159759521484,72.54450225830078,19.982173919677734,1182.219970703125,199.75999450683594,2823.81005859375,7411.47998046875
+2018-02-01,120.11751556396484,42.472713470458984,88.74141693115234,25.634004592895508,75.62249755859375,20.7039852142334,1103.9200439453125,209.1300048828125,2713.830078125,7273.009765625
+2018-02-08,,,,,,,,,,
+2018-03-01,119.43197631835938,40.170265197753906,86.7812271118164,24.33201026916504,72.36699676513672,20.402997970581055,1037.1400146484375,216.0800018310547,2640.8701171875,7063.4501953125
+2018-04-01,112.83880615234375,39.566917419433594,88.92057037353516,26.82056999206543,78.30650329589844,20.00168228149414,1018.5800170898438,221.60000610351562,2648.050048828125,7066.27001953125
+2018-05-01,109.99760437011719,44.7408332824707,93.97891998291016,23.179109573364258,81.48100280761719,22.479249954223633,1100.0,249.27999877929688,2705.27001953125,7442.1201171875
+2018-05-09,,,,,,,,,,
+2018-06-01,109.95153045654297,44.4903450012207,94.16664123535156,20.467208862304688,84.98999786376953,23.571720123291016,1129.18994140625,243.80999755859375,2718.3701171875,7510.2998046875
+2018-07-01,114.06781768798828,45.73533630371094,101.30004119873047,22.375450134277344,88.87200164794922,25.784528732299805,1227.219970703125,244.67999267578125,2816.2900390625,7671.7900390625
+2018-08-01,115.2877197265625,54.709842681884766,107.2684097290039,24.00385284423828,100.635498046875,26.8017520904541,1231.800048828125,263.510009765625,2901.52001953125,8109.5400390625
+2018-08-09,,,,,,,,,,
+2018-09-01,120.2962646484375,54.445865631103516,109.63678741455078,23.24565315246582,100.1500015258789,27.066509246826172,1207.0799560546875,269.95001220703125,2913.97998046875,8046.35009765625
+2018-10-01,91.83122253417969,52.78649139404297,102.38966369628906,24.236047744750977,79.90049743652344,25.190916061401367,1090.5799560546875,245.75999450683594,2711.739990234375,7305.89990234375
+2018-11-01,98.86396026611328,43.07142639160156,106.3008041381836,23.4099178314209,84.50849914550781,29.396371841430664,1109.6500244140625,250.88999938964844,2760.169921875,7330.5400390625
+2018-11-08,,,,,,,,,,
+2018-12-01,91.58279418945312,38.17780685424805,97.78714752197266,17.18350601196289,75.09850311279297,24.59708595275879,1044.9599609375,226.24000549316406,2506.85009765625,6635.27978515625
+2019-01-01,108.30086517333984,40.28346252441406,100.5406265258789,24.84735679626465,85.9365005493164,24.456159591674805,1125.8900146484375,247.82000732421875,2704.10009765625,7281.740234375
+2019-02-01,111.28997039794922,41.90748596191406,107.85758972167969,27.216707229614258,81.99150085449219,28.095136642456055,1126.550048828125,262.5,2784.489990234375,7532.52978515625
+2019-02-07,,,,,,,,,,
+2019-03-01,115.00740814208984,46.17075729370117,114.03240966796875,28.167972564697266,89.0374984741211,29.539655685424805,1176.8900146484375,266.489990234375,2834.39990234375,7729.31982421875
+2019-04-01,114.33090209960938,48.77644348144531,126.27296447753906,29.61660385131836,96.32599639892578,33.9285774230957,1198.9599609375,289.25,2945.830078125,8095.39013671875
+2019-05-01,103.50668334960938,42.55390930175781,119.58222198486328,27.175188064575195,88.75350189208984,29.972509384155273,1106.5,270.8999938964844,2752.06005859375,7453.14990234375
+2019-05-09,,,,,,,,,,
+2019-06-01,113.73433685302734,48.293270111083984,130.00108337402344,31.43656349182129,94.68150329589844,25.56848907470703,1082.800048828125,294.6499938964844,2941.760009765625,8006.240234375
+2019-07-01,122.26231384277344,51.98261260986328,132.24281311035156,28.701255798339844,93.33899688720703,29.061506271362305,1218.199951171875,298.8599853515625,2980.3798828125,8175.419921875
+2019-08-01,111.7796401977539,50.93339920043945,133.78579711914062,25.92054557800293,88.81449890136719,25.9359073638916,1190.530029296875,284.510009765625,2926.4599609375,7962.8798828125
+2019-08-08,,,,,,,,,,
+2019-09-01,121.34967803955078,54.85721969604492,135.37051391601562,26.743131637573242,86.79550170898438,26.10200309753418,1221.1400146484375,276.25,2976.739990234375,7999.33984375
+2019-10-01,111.59463500976562,60.929054260253906,139.59625244140625,30.58913803100586,88.83300018310547,26.620418548583984,1258.800048828125,277.92999267578125,3037.56005859375,8292.3603515625
+2019-11-01,112.19547271728516,65.45783996582031,147.39544677734375,35.09682083129883,90.04000091552734,24.40582847595215,1304.0899658203125,309.5299987792969,3140.97998046875,8665.4697265625
+2019-11-07,,,,,,,,,,
+2019-12-01,113.17443084716797,72.13994598388672,154.07154846191406,33.23965072631836,92.39199829101562,25.86544418334961,1339.3900146484375,329.80999755859375,3230.780029296875,8972.599609375
+2020-01-01,121.35601806640625,76.03622436523438,166.31326293945312,32.28398132324219,100.43599700927734,24.546754837036133,1432.780029296875,351.1400146484375,3225.52001953125,9150.9404296875
+2020-02-01,109.88997650146484,67.1553726196289,158.28240966796875,29.225305557250977,94.1875,20.364192962646484,1339.25,345.1199951171875,2954.219970703125,8567.3701171875
+2020-02-07,,,,,,,,,,
+2020-03-01,94.6399154663086,62.61878204345703,154.502197265625,17.190288543701172,97.48600006103516,19.90617561340332,1161.949951171875,318.239990234375,2584.590087890625,7700.10009765625
+2020-04-01,107.12150573730469,72.34808349609375,175.5648956298828,16.6003360748291,123.69999694824219,21.486589431762695,1346.699951171875,353.6400146484375,2912.429931640625,8889.5498046875
+2020-05-01,106.55841827392578,78.29256439208984,179.52272033691406,14.412978172302246,122.11849975585938,24.98464012145996,1433.52001953125,386.6000061035156,3044.31005859375,9489.8701171875
+2020-05-07,,,,,,,,,,
+2020-06-01,104.41674041748047,90.0749740600586,199.92588806152344,13.877481460571289,137.9409942626953,27.652219772338867,1418.050048828125,435.30999755859375,3100.2900390625,10058.76953125
+2020-07-01,106.29290008544922,104.9491958618164,201.3994598388672,15.366937637329102,158.23399353027344,30.11343765258789,1487.949951171875,444.32000732421875,3271.1201171875,10745.26953125
+2020-08-01,106.61280059814453,127.44819641113281,221.55807495117188,17.406635284423828,172.54800415039062,33.25917053222656,1629.530029296875,513.3900146484375,3500.31005859375,11775.4599609375
+2020-08-07,,,,,,,,,,
+2020-09-01,106.57222747802734,114.5876235961914,207.12527465820312,17.323570251464844,157.43649291992188,34.06950759887695,1465.5999755859375,490.42999267578125,3363.0,11167.509765625
+2020-10-01,97.80435180664062,107.71099090576172,199.38504028320312,16.259458541870117,151.8074951171875,30.329862594604492,1616.1099853515625,447.1000061035156,3269.9599609375,10911.58984375
+2020-11-01,108.19266510009766,117.79344177246094,210.8082733154297,20.478687286376953,158.40199279785156,34.74394989013672,1754.4000244140625,478.4700012207031,3621.6298828125,12198.740234375
+2020-11-09,,,,,,,,,,
+2020-12-01,111.85865020751953,131.51597595214844,219.60447692871094,21.694869995117188,162.84649658203125,36.88808059692383,1752.6400146484375,500.1199951171875,3756.070068359375,12888.2802734375
+2021-01-01,105.84272766113281,130.7924346923828,229.0237274169922,19.891191482543945,160.30999755859375,36.6867561340332,1827.3599853515625,458.7699890136719,3714.239990234375,13070.6904296875
+2021-02-01,105.68277740478516,120.18710327148438,229.4384002685547,24.100215911865234,154.64649963378906,40.80388259887695,2021.9100341796875,459.6700134277344,3811.14990234375,13192.349609375
+2021-02-09,,,,,,,,,,
+2021-03-01,119.9990005493164,121.25013732910156,233.32164001464844,22.955739974975586,154.70399475097656,44.367366790771484,2062.52001953125,475.3699951171875,3972.889892578125,13246.8701171875
+2021-04-01,127.76121520996094,130.49156188964844,249.56121826171875,23.070621490478516,173.37100219726562,49.49113082885742,2353.5,508.3399963378906,4181.169921875,13962.6796875
+2021-05-01,129.4361114501953,123.6920166015625,247.08718872070312,22.41118812561035,161.15350341796875,49.64715576171875,2356.85009765625,504.5799865722656,4204.10986328125,13748.740234375
+2021-05-07,,,,,,,,,,
+2021-06-01,133.47738647460938,136.1819610595703,268.70587158203125,22.44941520690918,172.00799560546875,50.16557312011719,2441.7900390625,585.6400146484375,4297.5,14503.9501953125
+2021-07-01,128.35101318359375,145.03138732910156,282.6023864746094,23.305561065673828,166.37950134277344,48.63045883178711,2694.530029296875,621.6300048828125,4395.259765625,14672.6796875
+2021-08-01,127.78646087646484,150.96749877929688,299.4349365234375,21.74091148376465,173.5395050048828,49.053245544433594,2893.949951171875,663.7000122070312,4522.68017578125,15259.240234375
+2021-08-09,,,,,,,,,,
+2021-09-01,127.95899963378906,140.906982421875,280.1719665527344,19.48086166381836,164.2519989013672,52.36507034301758,2673.52001953125,575.719970703125,4307.5400390625,14448.580078125
+2021-10-01,115.22111511230469,149.1721954345703,329.56378173828125,17.397972106933594,168.6215057373047,55.35980224609375,2960.919921875,650.3599853515625,4605.3798828125,15498.3896484375
+2021-11-01,112.8140869140625,164.60723876953125,328.5401611328125,18.003969192504883,175.35350036621094,56.077186584472656,2837.949951171875,669.8499755859375,4567.0,15537.6904296875
+2021-11-04,,,,,,,,,,
+2021-11-09,,,,,,,,,,
+2021-12-01,130.48629760742188,177.08387756347656,334.8461608886719,22.1286563873291,166.7169952392578,55.77927017211914,2897.0400390625,567.0599975585938,4766.18017578125,15644.9697265625
+2022-01-01,130.3984375,174.30149841308594,309.6171875,20.856517791748047,149.57350158691406,56.41482162475586,2706.070068359375,534.2999877929688,4515.5498046875,14239.8798828125
+2022-02-01,119.60104370117188,164.66795349121094,297.4805908203125,19.47332763671875,153.56300354003906,50.60551452636719,2701.139892578125,467.67999267578125,4373.93994140625,13751.400390625
+2022-02-10,,,,,,,,,,
+2022-03-01,128.46168518066406,174.3538360595703,307.59356689453125,19.927804946899414,162.99749755859375,49.84086990356445,2781.35009765625,455.6199951171875,4530.41015625,14220.51953125
+2022-04-01,130.6254425048828,157.418701171875,276.8751220703125,17.399999618530273,124.28150177001953,46.68299102783203,2282.18994140625,395.95001220703125,4131.93017578125,12334.6396484375
+2022-05-01,137.1759796142578,148.6216278076172,271.2382507324219,18.81999969482422,120.20950317382812,49.939998626708984,2275.239990234375,416.4800109863281,4132.14990234375,12081.3896484375
+2022-05-09,,,,,,,,,,
+2022-06-01,141.86000061035156,137.44000244140625,256.4800109863281,15.819999694824219,107.4000015258789,48.939998626708984,2240.14990234375,365.6300048828125,3821.550048828125,11181.5400390625
+2022-06-28,141.86000061035156,137.44000244140625,256.4800109863281,15.819999694824219,107.4000015258789,48.939998626708984,2240.14990234375,365.6300048828125,3821.550048828125,11181.5400390625
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/axes_grid/bivariate_normal.npy b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/axes_grid/bivariate_normal.npy
new file mode 100644
index 0000000000000000000000000000000000000000..a76ff8b1f63c970d5e02b481a16b3231408aebe4
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/axes_grid/bivariate_normal.npy
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0e9599f6e74087aa2ca58aa77846b6ec3e8491180e445c07a2c69c65756ef7c5
+size 1880
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/data_x_x2_x3.csv b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/data_x_x2_x3.csv
new file mode 100644
index 0000000000000000000000000000000000000000..521da1453204a97f4189ecd3c82e99970786f208
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/data_x_x2_x3.csv
@@ -0,0 +1,11 @@
+ 0 0 0
+ 1 1 1
+ 2 4 8
+ 3 9 27
+ 4 16 64
+ 5 25 125
+ 6 36 216
+ 7 49 343
+ 8 64 512
+ 9 81 729
+10 100 1000
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/eeg.dat b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/eeg.dat
new file mode 100644
index 0000000000000000000000000000000000000000..c666c65053bfa37d63c9bdc1e5337e18484ec688
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/eeg.dat differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/embedding_in_wx3.xrc b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/embedding_in_wx3.xrc
new file mode 100644
index 0000000000000000000000000000000000000000..220656d735346d99363d97721fe738d8fcb4feeb
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/embedding_in_wx3.xrc
@@ -0,0 +1,65 @@
+
+
+
+ embedding_in_wx3
+
+
+ wxVERTICAL
+
+
+ Check out this whizz-bang stuff!
+
+
+ 0
+ wxALL|wxEXPAND
+ 5
+
+
+
+ wxHORIZONTAL
+
+
+ whiz
+
+ 0
+ wxALL|wxEXPAND
+ 2
+
+
+
+ bang
+
+ 0
+ wxALL|wxEXPAND
+ 2
+
+
+
+ bang count:
+
+
+ 1
+ wxALL|wxEXPAND
+ 2
+
+
+
+ 0
+
+ 0
+ wxEXPAND
+
+
+ 0
+ wxLEFT|wxRIGHT|wxEXPAND
+ 5
+
+
+
+ 1
+ wxEXPAND
+
+
+
+
+
\ No newline at end of file
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/goog.npz b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/goog.npz
new file mode 100644
index 0000000000000000000000000000000000000000..bd82e71bf7d72da7db030381c31f769a3d9736bb
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/goog.npz
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:400917cf30e6b664f7b0da93d7c745860d3aa9008da8b7f160d2dd12e6a318b1
+size 22845
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/grace_hopper.jpg b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/grace_hopper.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..478720d6694a56b8962630e6dde8a230fc937049
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/grace_hopper.jpg differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/jacksboro_fault_dem.npz b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/jacksboro_fault_dem.npz
new file mode 100644
index 0000000000000000000000000000000000000000..065732313f852fe861ea6852a84f8c59666c90b1
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/jacksboro_fault_dem.npz
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d493f50a33e82a4420494c54d1fca1539d177bdc27ab190bc5fe6e92f62fb637
+size 174061
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/logo2.png b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/logo2.png
new file mode 100644
index 0000000000000000000000000000000000000000..72843ab1febbed35a00fc5c248f308f66d38cc24
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/logo2.png differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/membrane.dat b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/membrane.dat
new file mode 100644
index 0000000000000000000000000000000000000000..68f5e6b219448dac694aacba66b819564ba33467
Binary files /dev/null and b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/membrane.dat differ
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/msft.csv b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/msft.csv
new file mode 100644
index 0000000000000000000000000000000000000000..727b1befe36efde44fb5e2558e02410dad75a390
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/msft.csv
@@ -0,0 +1,66 @@
+Date,Open,High,Low,Close,Volume,Adj. Close*
+19-Sep-03,29.76,29.97,29.52,29.96,92433800,29.79
+18-Sep-03,28.49,29.51,28.42,29.50,67268096,29.34
+17-Sep-03,28.76,28.95,28.47,28.50,47221600,28.34
+16-Sep-03,28.41,28.95,28.32,28.90,52060600,28.74
+15-Sep-03,28.37,28.61,28.33,28.36,41432300,28.20
+12-Sep-03,27.48,28.40,27.45,28.34,55777200,28.18
+11-Sep-03,27.66,28.11,27.59,27.84,37813300,27.68
+10-Sep-03,28.03,28.18,27.48,27.55,54763500,27.40
+9-Sep-03,28.65,28.71,28.31,28.37,44315200,28.21
+8-Sep-03,28.39,28.92,28.34,28.84,46105300,28.68
+5-Sep-03,28.23,28.75,28.17,28.38,64024500,28.22
+4-Sep-03,28.10,28.47,27.99,28.43,59840800,28.27
+3-Sep-03,27.42,28.40,27.38,28.30,109437800,28.14
+2-Sep-03,26.70,27.30,26.47,27.26,74168896,27.11
+29-Aug-03,26.46,26.55,26.35,26.52,34503000,26.37
+28-Aug-03,26.50,26.58,26.24,26.51,46211200,26.36
+27-Aug-03,26.51,26.58,26.30,26.42,30633900,26.27
+26-Aug-03,26.31,26.67,25.96,26.57,47546000,26.42
+25-Aug-03,26.31,26.54,26.23,26.50,36132900,26.35
+22-Aug-03,26.78,26.95,26.21,26.22,65846300,26.07
+21-Aug-03,26.65,26.73,26.13,26.24,63802700,26.09
+20-Aug-03,26.30,26.53,26.00,26.45,56739300,26.30
+19-Aug-03,25.85,26.65,25.77,26.62,72952896,26.47
+18-Aug-03,25.56,25.83,25.46,25.70,45817400,25.56
+15-Aug-03,25.61,25.66,25.43,25.54,27607900,25.40
+14-Aug-03,25.66,25.71,25.52,25.63,37338300,25.49
+13-Aug-03,25.79,25.89,25.50,25.60,39636900,25.46
+12-Aug-03,25.71,25.77,25.45,25.73,38208400,25.59
+11-Aug-03,25.61,25.99,25.54,25.61,36433900,25.47
+8-Aug-03,25.88,25.98,25.50,25.58,33241400,25.44
+7-Aug-03,25.72,25.81,25.45,25.71,44258500,25.57
+6-Aug-03,25.54,26.19,25.43,25.65,56294900,25.51
+5-Aug-03,26.31,26.54,25.60,25.66,58825800,25.52
+4-Aug-03,26.15,26.41,25.75,26.18,51825600,26.03
+1-Aug-03,26.33,26.51,26.12,26.17,42649700,26.02
+31-Jul-03,26.60,26.99,26.31,26.41,64504800,26.26
+30-Jul-03,26.46,26.57,26.17,26.23,41240300,26.08
+29-Jul-03,26.88,26.90,26.24,26.47,62391100,26.32
+28-Jul-03,26.94,27.00,26.49,26.61,52658300,26.46
+25-Jul-03,26.28,26.95,26.07,26.89,54173000,26.74
+24-Jul-03,26.78,26.92,25.98,26.00,53556600,25.85
+23-Jul-03,26.42,26.65,26.14,26.45,49828200,26.30
+22-Jul-03,26.28,26.56,26.13,26.38,51791000,26.23
+21-Jul-03,26.87,26.91,26.00,26.04,48480800,25.89
+18-Jul-03,27.11,27.23,26.75,26.89,63388400,26.74
+17-Jul-03,27.14,27.27,26.54,26.69,72805000,26.54
+16-Jul-03,27.56,27.62,27.20,27.52,49838900,27.37
+15-Jul-03,27.47,27.53,27.10,27.27,53567600,27.12
+14-Jul-03,27.63,27.81,27.05,27.40,60464400,27.25
+11-Jul-03,26.95,27.45,26.89,27.31,50377300,27.16
+10-Jul-03,27.25,27.42,26.59,26.91,55350800,26.76
+9-Jul-03,27.56,27.70,27.25,27.47,62300700,27.32
+8-Jul-03,27.26,27.80,27.25,27.70,61896800,27.55
+7-Jul-03,27.02,27.55,26.95,27.42,88960800,27.27
+3-Jul-03,26.69,26.95,26.41,26.50,39440900,26.35
+2-Jul-03,26.50,26.93,26.45,26.88,94069296,26.73
+1-Jul-03,25.59,26.20,25.39,26.15,60926000,26.00
+30-Jun-03,25.94,26.12,25.50,25.64,48073100,25.50
+27-Jun-03,25.95,26.34,25.53,25.63,76040304,25.49
+26-Jun-03,25.39,26.51,25.21,25.75,51758100,25.61
+25-Jun-03,25.64,25.99,25.14,25.26,60483500,25.12
+24-Jun-03,25.65,26.04,25.52,25.70,51820300,25.56
+23-Jun-03,26.14,26.24,25.49,25.78,52584500,25.64
+20-Jun-03,26.34,26.38,26.01,26.33,86048896,26.18
+19-Jun-03,26.09,26.39,26.01,26.07,63626900,25.92
\ No newline at end of file
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/s1045.ima.gz b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/s1045.ima.gz
new file mode 100644
index 0000000000000000000000000000000000000000..29d1c7f70afaa882d9673861bc3d17a4ffd7235d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/s1045.ima.gz
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:32b424d64f62b7e71cb24d29fd53938ad5664d608055a67ab2b2af4369f8b89e
+size 33229
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/topobathy.npz b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/topobathy.npz
new file mode 100644
index 0000000000000000000000000000000000000000..67fc6c403643c5b4e0624005b7bd99ac59e856fd
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/topobathy.npz
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0244e03291702df45024dcb5cacbc4f3d4cb30d72dfa7fd371c4ac61c42b4fbf
+size 45224
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..418721314335afad04ff288d897636d24d89be70
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle
@@ -0,0 +1,53 @@
+# Solarized color palette taken from https://ethanschoonover.com/solarized/
+# Inspired by, and copied from ggthemes https://github.com/jrnold/ggthemes
+
+#TODO:
+# 1. Padding to title from face
+# 2. Remove top & right ticks
+# 3. Give Title a Magenta Color(?)
+
+#base00 ='#657b83'
+#base01 ='#93a1a1'
+#base2 ='#eee8d5'
+#base3 ='#fdf6e3'
+#base01 ='#586e75'
+#Magenta ='#d33682'
+#Blue ='#268bd2'
+#cyan ='#2aa198'
+#violet ='#6c71c4'
+#green ='#859900'
+#orange ='#cb4b16'
+
+figure.facecolor : FDF6E3
+
+patch.antialiased : True
+
+lines.linewidth : 2.0
+lines.solid_capstyle: butt
+
+axes.titlesize : 16
+axes.labelsize : 12
+axes.labelcolor : 657b83
+axes.facecolor : eee8d5
+axes.edgecolor : eee8d5
+axes.axisbelow : True
+axes.prop_cycle : cycler('color', ['268BD2', '2AA198', '859900', 'B58900', 'CB4B16', 'DC322F', 'D33682', '6C71C4'])
+# Blue
+# Cyan
+# Green
+# Yellow
+# Orange
+# Red
+# Magenta
+# Violet
+axes.grid : True
+grid.color : fdf6e3 # grid color
+grid.linestyle : - # line
+grid.linewidth : 1 # in points
+
+### TICKS
+xtick.color : 657b83
+xtick.direction : out
+
+ytick.color : 657b83
+ytick.direction : out
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..96f62f4ba5929594fd23da024b183195481012b8
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle
@@ -0,0 +1,6 @@
+# This patch should go on top of the "classic" style and exists solely to avoid
+# changing baseline images.
+
+text.kerning_factor : 6
+
+ytick.alignment: center_baseline
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/_mpl-gallery-nogrid.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/_mpl-gallery-nogrid.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..911658fe883335f69b3869dad36fc89cdcb92026
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/_mpl-gallery-nogrid.mplstyle
@@ -0,0 +1,19 @@
+# This style is used for the plot_types gallery. It is considered private.
+
+axes.grid: False
+axes.axisbelow: True
+
+figure.figsize: 2, 2
+# make it so the axes labels don't show up. Obviously
+# not good style for any quantitative analysis:
+figure.subplot.left: 0.01
+figure.subplot.right: 0.99
+figure.subplot.bottom: 0.01
+figure.subplot.top: 0.99
+
+xtick.major.size: 0.0
+ytick.major.size: 0.0
+
+# colors:
+image.cmap : Blues
+axes.prop_cycle: cycler('color', ['1f77b4', '82bbdb', 'ccdff1'])
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/_mpl-gallery.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/_mpl-gallery.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..75c95bf16a1f1c1265c6554fba5da038f75bab91
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/_mpl-gallery.mplstyle
@@ -0,0 +1,19 @@
+# This style is used for the plot_types gallery. It is considered part of the private API.
+
+axes.grid: True
+axes.axisbelow: True
+
+figure.figsize: 2, 2
+# make it so the axes labels don't show up. Obviously
+# not good style for any quantitative analysis:
+figure.subplot.left: 0.01
+figure.subplot.right: 0.99
+figure.subplot.bottom: 0.01
+figure.subplot.top: 0.99
+
+xtick.major.size: 0.0
+ytick.major.size: 0.0
+
+# colors:
+image.cmap : Blues
+axes.prop_cycle: cycler('color', ['1f77b4', '58a1cf', 'abd0e6'])
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/bmh.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/bmh.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..1b449cc09fbf25c79ee4cc4f93e26a889bef0b52
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/bmh.mplstyle
@@ -0,0 +1,29 @@
+#Author: Cameron Davidson-Pilon, original styles from Bayesian Methods for Hackers
+# https://github.com/CamDavidsonPilon/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers/
+
+lines.linewidth : 2.0
+
+patch.linewidth: 0.5
+patch.facecolor: blue
+patch.edgecolor: eeeeee
+patch.antialiased: True
+
+text.hinting_factor : 8
+
+mathtext.fontset : cm
+
+axes.facecolor: eeeeee
+axes.edgecolor: bcbcbc
+axes.grid : True
+axes.titlesize: x-large
+axes.labelsize: large
+axes.prop_cycle: cycler('color', ['348ABD', 'A60628', '7A68A6', '467821', 'D55E00', 'CC79A7', '56B4E9', '009E73', 'F0E442', '0072B2'])
+
+grid.color: b2b2b2
+grid.linestyle: --
+grid.linewidth: 0.5
+
+legend.fancybox: True
+
+xtick.direction: in
+ytick.direction: in
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/classic.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/classic.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..50516d831ae4c2a04002878daf94a4252bc4b102
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/classic.mplstyle
@@ -0,0 +1,492 @@
+### Classic matplotlib plotting style as of v1.5
+
+
+### LINES
+# See https://matplotlib.org/api/artist_api.html#module-matplotlib.lines for more
+# information on line properties.
+lines.linewidth : 1.0 # line width in points
+lines.linestyle : - # solid line
+lines.color : b # has no affect on plot(); see axes.prop_cycle
+lines.marker : None # the default marker
+lines.markerfacecolor : auto # the default markerfacecolor
+lines.markeredgecolor : auto # the default markeredgecolor
+lines.markeredgewidth : 0.5 # the line width around the marker symbol
+lines.markersize : 6 # markersize, in points
+lines.dash_joinstyle : round # miter|round|bevel
+lines.dash_capstyle : butt # butt|round|projecting
+lines.solid_joinstyle : round # miter|round|bevel
+lines.solid_capstyle : projecting # butt|round|projecting
+lines.antialiased : True # render lines in antialiased (no jaggies)
+lines.dashed_pattern : 6, 6
+lines.dashdot_pattern : 3, 5, 1, 5
+lines.dotted_pattern : 1, 3
+lines.scale_dashes: False
+
+### Marker props
+markers.fillstyle: full
+
+### PATCHES
+# Patches are graphical objects that fill 2D space, like polygons or
+# circles. See
+# https://matplotlib.org/api/artist_api.html#module-matplotlib.patches
+# information on patch properties
+patch.linewidth : 1.0 # edge width in points
+patch.facecolor : b
+patch.force_edgecolor : True
+patch.edgecolor : k
+patch.antialiased : True # render patches in antialiased (no jaggies)
+
+hatch.color : k
+hatch.linewidth : 1.0
+
+hist.bins : 10
+
+### FONT
+#
+# font properties used by text.Text. See
+# https://matplotlib.org/api/font_manager_api.html for more
+# information on font properties. The 6 font properties used for font
+# matching are given below with their default values.
+#
+# The font.family property has five values: 'serif' (e.g., Times),
+# 'sans-serif' (e.g., Helvetica), 'cursive' (e.g., Zapf-Chancery),
+# 'fantasy' (e.g., Western), and 'monospace' (e.g., Courier). Each of
+# these font families has a default list of font names in decreasing
+# order of priority associated with them. When text.usetex is False,
+# font.family may also be one or more concrete font names.
+#
+# The font.style property has three values: normal (or roman), italic
+# or oblique. The oblique style will be used for italic, if it is not
+# present.
+#
+# The font.variant property has two values: normal or small-caps. For
+# TrueType fonts, which are scalable fonts, small-caps is equivalent
+# to using a font size of 'smaller', or about 83% of the current font
+# size.
+#
+# The font.weight property has effectively 13 values: normal, bold,
+# bolder, lighter, 100, 200, 300, ..., 900. Normal is the same as
+# 400, and bold is 700. bolder and lighter are relative values with
+# respect to the current weight.
+#
+# The font.stretch property has 11 values: ultra-condensed,
+# extra-condensed, condensed, semi-condensed, normal, semi-expanded,
+# expanded, extra-expanded, ultra-expanded, wider, and narrower. This
+# property is not currently implemented.
+#
+# The font.size property is the default font size for text, given in pts.
+# 12pt is the standard value.
+#
+font.family : sans-serif
+font.style : normal
+font.variant : normal
+font.weight : normal
+font.stretch : normal
+# note that font.size controls default text sizes. To configure
+# special text sizes tick labels, axes, labels, title, etc, see the rc
+# settings for axes and ticks. Special text sizes can be defined
+# relative to font.size, using the following values: xx-small, x-small,
+# small, medium, large, x-large, xx-large, larger, or smaller
+font.size : 12.0
+font.serif : DejaVu Serif, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif
+font.sans-serif: DejaVu Sans, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif
+font.cursive : Apple Chancery, Textile, Zapf Chancery, Sand, Script MT, Felipa, cursive
+font.fantasy : Comic Sans MS, Chicago, Charcoal, ImpactWestern, xkcd script, fantasy
+font.monospace : DejaVu Sans Mono, Andale Mono, Nimbus Mono L, Courier New, Courier, Fixed, Terminal, monospace
+
+### TEXT
+# text properties used by text.Text. See
+# https://matplotlib.org/api/artist_api.html#module-matplotlib.text for more
+# information on text properties
+
+text.color : k
+
+### LaTeX customizations. See http://www.scipy.org/Wiki/Cookbook/Matplotlib/UsingTex
+text.usetex : False # use latex for all text handling. The following fonts
+ # are supported through the usual rc parameter settings:
+ # new century schoolbook, bookman, times, palatino,
+ # zapf chancery, charter, serif, sans-serif, helvetica,
+ # avant garde, courier, monospace, computer modern roman,
+ # computer modern sans serif, computer modern typewriter
+ # If another font is desired which can loaded using the
+ # LaTeX \usepackage command, please inquire at the
+ # matplotlib mailing list
+text.latex.preamble : # IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES
+ # AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP
+ # IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO.
+ # text.latex.preamble is a single line of LaTeX code that
+ # will be passed on to the LaTeX system. It may contain
+ # any code that is valid for the LaTeX "preamble", i.e.
+ # between the "\documentclass" and "\begin{document}"
+ # statements.
+ # Note that it has to be put on a single line, which may
+ # become quite long.
+ # The following packages are always loaded with usetex, so
+ # beware of package collisions: color, geometry, graphicx,
+ # type1cm, textcomp.
+ # Adobe Postscript (PSSNFS) font packages may also be
+ # loaded, depending on your font settings.
+
+text.hinting : auto # May be one of the following:
+ # 'none': Perform no hinting
+ # 'auto': Use freetype's autohinter
+ # 'native': Use the hinting information in the
+ # font file, if available, and if your
+ # freetype library supports it
+ # 'either': Use the native hinting information,
+ # or the autohinter if none is available.
+ # For backward compatibility, this value may also be
+ # True === 'auto' or False === 'none'.
+text.hinting_factor : 8 # Specifies the amount of softness for hinting in the
+ # horizontal direction. A value of 1 will hint to full
+ # pixels. A value of 2 will hint to half pixels etc.
+
+text.antialiased : True # If True (default), the text will be antialiased.
+ # This only affects the Agg backend.
+
+# The following settings allow you to select the fonts in math mode.
+# They map from a TeX font name to a fontconfig font pattern.
+# These settings are only used if mathtext.fontset is 'custom'.
+# Note that this "custom" mode is unsupported and may go away in the
+# future.
+mathtext.cal : cursive
+mathtext.rm : serif
+mathtext.tt : monospace
+mathtext.it : serif:italic
+mathtext.bf : serif:bold
+mathtext.sf : sans\-serif
+mathtext.fontset : cm # Should be 'cm' (Computer Modern), 'stix',
+ # 'stixsans' or 'custom'
+mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix'
+ # 'stixsans'] when a symbol cannot be found in one of the
+ # custom math fonts. Select 'None' to not perform fallback
+ # and replace the missing character by a dummy.
+
+mathtext.default : it # The default font to use for math.
+ # Can be any of the LaTeX font names, including
+ # the special name "regular" for the same font
+ # used in regular text.
+
+### AXES
+# default face and edge color, default tick sizes,
+# default fontsizes for ticklabels, and so on. See
+# https://matplotlib.org/api/axes_api.html#module-matplotlib.axes
+axes.facecolor : w # axes background color
+axes.edgecolor : k # axes edge color
+axes.linewidth : 1.0 # edge linewidth
+axes.grid : False # display grid or not
+axes.grid.which : major
+axes.grid.axis : both
+axes.titlesize : large # fontsize of the axes title
+axes.titley : 1.0 # at the top, no autopositioning.
+axes.titlepad : 5.0 # pad between axes and title in points
+axes.titleweight : normal # font weight for axes title
+axes.labelsize : medium # fontsize of the x any y labels
+axes.labelpad : 5.0 # space between label and axis
+axes.labelweight : normal # weight of the x and y labels
+axes.labelcolor : k
+axes.axisbelow : False # whether axis gridlines and ticks are below
+ # the axes elements (lines, text, etc)
+
+axes.formatter.limits : -7, 7 # use scientific notation if log10
+ # of the axis range is smaller than the
+ # first or larger than the second
+axes.formatter.use_locale : False # When True, format tick labels
+ # according to the user's locale.
+ # For example, use ',' as a decimal
+ # separator in the fr_FR locale.
+axes.formatter.use_mathtext : False # When True, use mathtext for scientific
+ # notation.
+axes.formatter.useoffset : True # If True, the tick label formatter
+ # will default to labeling ticks relative
+ # to an offset when the data range is very
+ # small compared to the minimum absolute
+ # value of the data.
+axes.formatter.offset_threshold : 2 # When useoffset is True, the offset
+ # will be used when it can remove
+ # at least this number of significant
+ # digits from tick labels.
+
+axes.unicode_minus : True # use Unicode for the minus symbol
+ # rather than hyphen. See
+ # https://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes
+axes.prop_cycle : cycler('color', 'bgrcmyk')
+ # color cycle for plot lines
+ # as list of string colorspecs:
+ # single letter, long name, or
+ # web-style hex
+axes.autolimit_mode : round_numbers
+axes.xmargin : 0 # x margin. See `axes.Axes.margins`
+axes.ymargin : 0 # y margin See `axes.Axes.margins`
+axes.spines.bottom : True
+axes.spines.left : True
+axes.spines.right : True
+axes.spines.top : True
+polaraxes.grid : True # display grid on polar axes
+axes3d.grid : True # display grid on 3D axes
+axes3d.automargin : False # automatically add margin when manually setting 3D axis limits
+
+date.autoformatter.year : %Y
+date.autoformatter.month : %b %Y
+date.autoformatter.day : %b %d %Y
+date.autoformatter.hour : %H:%M:%S
+date.autoformatter.minute : %H:%M:%S.%f
+date.autoformatter.second : %H:%M:%S.%f
+date.autoformatter.microsecond : %H:%M:%S.%f
+date.converter: auto # 'auto', 'concise'
+
+### TICKS
+# see https://matplotlib.org/api/axis_api.html#matplotlib.axis.Tick
+
+xtick.top : True # draw ticks on the top side
+xtick.bottom : True # draw ticks on the bottom side
+xtick.major.size : 4 # major tick size in points
+xtick.minor.size : 2 # minor tick size in points
+xtick.minor.visible : False
+xtick.major.width : 0.5 # major tick width in points
+xtick.minor.width : 0.5 # minor tick width in points
+xtick.major.pad : 4 # distance to major tick label in points
+xtick.minor.pad : 4 # distance to the minor tick label in points
+xtick.color : k # color of the tick labels
+xtick.labelsize : medium # fontsize of the tick labels
+xtick.direction : in # direction: in, out, or inout
+xtick.major.top : True # draw x axis top major ticks
+xtick.major.bottom : True # draw x axis bottom major ticks
+xtick.minor.top : True # draw x axis top minor ticks
+xtick.minor.bottom : True # draw x axis bottom minor ticks
+xtick.alignment : center
+
+ytick.left : True # draw ticks on the left side
+ytick.right : True # draw ticks on the right side
+ytick.major.size : 4 # major tick size in points
+ytick.minor.size : 2 # minor tick size in points
+ytick.minor.visible : False
+ytick.major.width : 0.5 # major tick width in points
+ytick.minor.width : 0.5 # minor tick width in points
+ytick.major.pad : 4 # distance to major tick label in points
+ytick.minor.pad : 4 # distance to the minor tick label in points
+ytick.color : k # color of the tick labels
+ytick.labelsize : medium # fontsize of the tick labels
+ytick.direction : in # direction: in, out, or inout
+ytick.major.left : True # draw y axis left major ticks
+ytick.major.right : True # draw y axis right major ticks
+ytick.minor.left : True # draw y axis left minor ticks
+ytick.minor.right : True # draw y axis right minor ticks
+ytick.alignment : center
+
+### GRIDS
+grid.color : k # grid color
+grid.linestyle : : # dotted
+grid.linewidth : 0.5 # in points
+grid.alpha : 1.0 # transparency, between 0.0 and 1.0
+
+### Legend
+legend.fancybox : False # if True, use a rounded box for the
+ # legend, else a rectangle
+legend.loc : upper right
+legend.numpoints : 2 # the number of points in the legend line
+legend.fontsize : large
+legend.borderpad : 0.4 # border whitespace in fontsize units
+legend.markerscale : 1.0 # the relative size of legend markers vs. original
+# the following dimensions are in axes coords
+legend.labelspacing : 0.5 # the vertical space between the legend entries in fraction of fontsize
+legend.handlelength : 2. # the length of the legend lines in fraction of fontsize
+legend.handleheight : 0.7 # the height of the legend handle in fraction of fontsize
+legend.handletextpad : 0.8 # the space between the legend line and legend text in fraction of fontsize
+legend.borderaxespad : 0.5 # the border between the axes and legend edge in fraction of fontsize
+legend.columnspacing : 2. # the border between the axes and legend edge in fraction of fontsize
+legend.shadow : False
+legend.frameon : True # whether or not to draw a frame around legend
+legend.framealpha : None # opacity of legend frame
+legend.scatterpoints : 3 # number of scatter points
+legend.facecolor : inherit # legend background color (when 'inherit' uses axes.facecolor)
+legend.edgecolor : inherit # legend edge color (when 'inherit' uses axes.edgecolor)
+
+
+
+### FIGURE
+# See https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure
+figure.titlesize : medium # size of the figure title
+figure.titleweight : normal # weight of the figure title
+figure.labelsize: medium # size of the figure label
+figure.labelweight: normal # weight of the figure label
+figure.figsize : 8, 6 # figure size in inches
+figure.dpi : 80 # figure dots per inch
+figure.facecolor : 0.75 # figure facecolor; 0.75 is scalar gray
+figure.edgecolor : w # figure edgecolor
+figure.autolayout : False # When True, automatically adjust subplot
+ # parameters to make the plot fit the figure
+figure.frameon : True
+
+# The figure subplot parameters. All dimensions are a fraction of the
+# figure width or height
+figure.subplot.left : 0.125 # the left side of the subplots of the figure
+figure.subplot.right : 0.9 # the right side of the subplots of the figure
+figure.subplot.bottom : 0.1 # the bottom of the subplots of the figure
+figure.subplot.top : 0.9 # the top of the subplots of the figure
+figure.subplot.wspace : 0.2 # the amount of width reserved for space between subplots,
+ # expressed as a fraction of the average axis width
+figure.subplot.hspace : 0.2 # the amount of height reserved for space between subplots,
+ # expressed as a fraction of the average axis height
+
+### IMAGES
+image.aspect : equal # equal | auto | a number
+image.interpolation : bilinear # see help(imshow) for options
+image.cmap : jet # gray | jet | ...
+image.lut : 256 # the size of the colormap lookup table
+image.origin : upper # lower | upper
+image.resample : False
+image.composite_image : True
+
+### CONTOUR PLOTS
+contour.negative_linestyle : dashed # dashed | solid
+contour.corner_mask : True
+
+# errorbar props
+errorbar.capsize: 3
+
+# scatter props
+scatter.marker: o
+
+### Boxplots
+boxplot.bootstrap: None
+boxplot.boxprops.color: b
+boxplot.boxprops.linestyle: -
+boxplot.boxprops.linewidth: 1.0
+boxplot.capprops.color: k
+boxplot.capprops.linestyle: -
+boxplot.capprops.linewidth: 1.0
+boxplot.flierprops.color: b
+boxplot.flierprops.linestyle: none
+boxplot.flierprops.linewidth: 1.0
+boxplot.flierprops.marker: +
+boxplot.flierprops.markeredgecolor: k
+boxplot.flierprops.markerfacecolor: auto
+boxplot.flierprops.markersize: 6.0
+boxplot.meanline: False
+boxplot.meanprops.color: r
+boxplot.meanprops.linestyle: -
+boxplot.meanprops.linewidth: 1.0
+boxplot.medianprops.color: r
+boxplot.meanprops.marker: s
+boxplot.meanprops.markerfacecolor: r
+boxplot.meanprops.markeredgecolor: k
+boxplot.meanprops.markersize: 6.0
+boxplot.medianprops.linestyle: -
+boxplot.medianprops.linewidth: 1.0
+boxplot.notch: False
+boxplot.patchartist: False
+boxplot.showbox: True
+boxplot.showcaps: True
+boxplot.showfliers: True
+boxplot.showmeans: False
+boxplot.whiskerprops.color: b
+boxplot.whiskerprops.linestyle: --
+boxplot.whiskerprops.linewidth: 1.0
+boxplot.whiskers: 1.5
+
+### Agg rendering
+### Warning: experimental, 2008/10/10
+agg.path.chunksize : 0 # 0 to disable; values in the range
+ # 10000 to 100000 can improve speed slightly
+ # and prevent an Agg rendering failure
+ # when plotting very large data sets,
+ # especially if they are very gappy.
+ # It may cause minor artifacts, though.
+ # A value of 20000 is probably a good
+ # starting point.
+### SAVING FIGURES
+path.simplify : True # When True, simplify paths by removing "invisible"
+ # points to reduce file size and increase rendering
+ # speed
+path.simplify_threshold : 0.1111111111111111
+ # The threshold of similarity below which
+ # vertices will be removed in the simplification
+ # process
+path.snap : True # When True, rectilinear axis-aligned paths will be snapped to
+ # the nearest pixel when certain criteria are met. When False,
+ # paths will never be snapped.
+path.sketch : None # May be none, or a 3-tuple of the form (scale, length,
+ # randomness).
+ # *scale* is the amplitude of the wiggle
+ # perpendicular to the line (in pixels). *length*
+ # is the length of the wiggle along the line (in
+ # pixels). *randomness* is the factor by which
+ # the length is randomly scaled.
+
+# the default savefig params can be different from the display params
+# e.g., you may want a higher resolution, or to make the figure
+# background white
+savefig.dpi : 100 # figure dots per inch
+savefig.facecolor : w # figure facecolor when saving
+savefig.edgecolor : w # figure edgecolor when saving
+savefig.format : png # png, ps, pdf, svg
+savefig.bbox : standard # 'tight' or 'standard'.
+ # 'tight' is incompatible with pipe-based animation
+ # backends (e.g. 'ffmpeg') but will work with those
+ # based on temporary files (e.g. 'ffmpeg_file')
+savefig.pad_inches : 0.1 # Padding to be used when bbox is set to 'tight'
+savefig.transparent : False # setting that controls whether figures are saved with a
+ # transparent background by default
+savefig.orientation : portrait
+
+# ps backend params
+ps.papersize : letter # auto, letter, legal, ledger, A0-A10, B0-B10
+ps.useafm : False # use of afm fonts, results in small files
+ps.usedistiller : False # can be: None, ghostscript or xpdf
+ # Experimental: may produce smaller files.
+ # xpdf intended for production of publication quality files,
+ # but requires ghostscript, xpdf and ps2eps
+ps.distiller.res : 6000 # dpi
+ps.fonttype : 3 # Output Type 3 (Type3) or Type 42 (TrueType)
+
+# pdf backend params
+pdf.compression : 6 # integer from 0 to 9
+ # 0 disables compression (good for debugging)
+pdf.fonttype : 3 # Output Type 3 (Type3) or Type 42 (TrueType)
+pdf.inheritcolor : False
+pdf.use14corefonts : False
+
+# pgf backend params
+pgf.texsystem : xelatex
+pgf.rcfonts : True
+pgf.preamble :
+
+# svg backend params
+svg.image_inline : True # write raster image data directly into the svg file
+svg.fonttype : path # How to handle SVG fonts:
+# 'none': Assume fonts are installed on the machine where the SVG will be viewed.
+# 'path': Embed characters as paths -- supported by most SVG renderers
+
+# Event keys to interact with figures/plots via keyboard.
+# Customize these settings according to your needs.
+# Leave the field(s) empty if you don't need a key-map. (i.e., fullscreen : '')
+
+keymap.fullscreen : f, ctrl+f # toggling
+keymap.home : h, r, home # home or reset mnemonic
+keymap.back : left, c, backspace # forward / backward keys to enable
+keymap.forward : right, v # left handed quick navigation
+keymap.pan : p # pan mnemonic
+keymap.zoom : o # zoom mnemonic
+keymap.save : s, ctrl+s # saving current figure
+keymap.quit : ctrl+w, cmd+w # close the current figure
+keymap.grid : g # switching on/off a grid in current axes
+keymap.yscale : l # toggle scaling of y-axes ('log'/'linear')
+keymap.xscale : k, L # toggle scaling of x-axes ('log'/'linear')
+
+###ANIMATION settings
+animation.writer : ffmpeg # MovieWriter 'backend' to use
+animation.codec : mpeg4 # Codec to use for writing movie
+animation.bitrate: -1 # Controls size/quality tradeoff for movie.
+ # -1 implies let utility auto-determine
+animation.frame_format: png # Controls frame format used by temp files
+animation.ffmpeg_path: ffmpeg # Path to ffmpeg binary. Without full path
+ # $PATH is searched
+animation.ffmpeg_args: # Additional arguments to pass to ffmpeg
+animation.convert_path: convert # Path to ImageMagick's convert binary.
+ # On Windows use the full path since convert
+ # is also the name of a system tool.
+animation.convert_args:
+animation.html: none
+
+_internal.classic_mode: True
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/dark_background.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/dark_background.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..61a99f3c0d10c3676ec466b53f716d69df40bb1f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/dark_background.mplstyle
@@ -0,0 +1,26 @@
+# Set black background default line colors to white.
+
+lines.color: white
+patch.edgecolor: white
+
+text.color: white
+
+axes.facecolor: black
+axes.edgecolor: white
+axes.labelcolor: white
+axes.prop_cycle: cycler('color', ['8dd3c7', 'feffb3', 'bfbbd9', 'fa8174', '81b1d2', 'fdb462', 'b3de69', 'bc82bd', 'ccebc4', 'ffed6f'])
+
+xtick.color: white
+ytick.color: white
+
+grid.color: white
+
+figure.facecolor: black
+figure.edgecolor: black
+
+### Boxplots
+boxplot.boxprops.color: white
+boxplot.capprops.color: white
+boxplot.flierprops.color: white
+boxplot.flierprops.markeredgecolor: white
+boxplot.whiskerprops.color: white
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/fast.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/fast.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..1f7be7d4632a352f5c96959c8ecdba06c9a06b86
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/fast.mplstyle
@@ -0,0 +1,11 @@
+# a small set of changes that will make your plotting FAST (1).
+#
+# (1) in some cases
+
+# Maximally simplify lines.
+path.simplify: True
+path.simplify_threshold: 1.0
+
+# chunk up large lines into smaller lines!
+# simple trick to avoid those pesky O(>n) algorithms!
+agg.path.chunksize: 10000
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..cd56d404c3b540e641f0f961504942464f814a31
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle
@@ -0,0 +1,37 @@
+#Author: Cameron Davidson-Pilon, replicated styles from FiveThirtyEight.com
+# See https://www.dataorigami.net/blogs/fivethirtyeight-mpl
+
+lines.linewidth: 4
+lines.solid_capstyle: butt
+
+legend.fancybox: true
+
+axes.prop_cycle: cycler('color', ['008fd5', 'fc4f30', 'e5ae38', '6d904f', '8b8b8b', '810f7c'])
+axes.facecolor: f0f0f0
+axes.labelsize: large
+axes.axisbelow: true
+axes.grid: true
+axes.edgecolor: f0f0f0
+axes.linewidth: 3.0
+axes.titlesize: x-large
+
+patch.edgecolor: f0f0f0
+patch.linewidth: 0.5
+
+svg.fonttype: path
+
+grid.linestyle: -
+grid.linewidth: 1.0
+grid.color: cbcbcb
+
+xtick.major.size: 0
+xtick.minor.size: 0
+ytick.major.size: 0
+ytick.minor.size: 0
+
+font.size: 14.0
+
+figure.subplot.left: 0.08
+figure.subplot.right: 0.95
+figure.subplot.bottom: 0.07
+figure.facecolor: f0f0f0
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/ggplot.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/ggplot.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..d1b3ba2e337b02760ad0c3429f8391b50dea6622
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/ggplot.mplstyle
@@ -0,0 +1,39 @@
+# from https://everyhue.me/posts/sane-color-scheme-for-matplotlib/
+
+patch.linewidth: 0.5
+patch.facecolor: 348ABD # blue
+patch.edgecolor: EEEEEE
+patch.antialiased: True
+
+font.size: 10.0
+
+axes.facecolor: E5E5E5
+axes.edgecolor: white
+axes.linewidth: 1
+axes.grid: True
+axes.titlesize: x-large
+axes.labelsize: large
+axes.labelcolor: 555555
+axes.axisbelow: True # grid/ticks are below elements (e.g., lines, text)
+
+axes.prop_cycle: cycler('color', ['E24A33', '348ABD', '988ED5', '777777', 'FBC15E', '8EBA42', 'FFB5B8'])
+ # E24A33 : red
+ # 348ABD : blue
+ # 988ED5 : purple
+ # 777777 : gray
+ # FBC15E : yellow
+ # 8EBA42 : green
+ # FFB5B8 : pink
+
+xtick.color: 555555
+xtick.direction: out
+
+ytick.color: 555555
+ytick.direction: out
+
+grid.color: white
+grid.linestyle: - # solid line
+
+figure.facecolor: white
+figure.edgecolor: 0.50
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/grayscale.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/grayscale.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..6a1114e4069882f09c8e518f362a651a5b90ed9d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/grayscale.mplstyle
@@ -0,0 +1,29 @@
+# Set all colors to grayscale
+# Note: strings of float values are interpreted by matplotlib as gray values.
+
+
+lines.color: black
+patch.facecolor: gray
+patch.edgecolor: black
+
+text.color: black
+
+axes.facecolor: white
+axes.edgecolor: black
+axes.labelcolor: black
+# black to light gray
+axes.prop_cycle: cycler('color', ['0.00', '0.40', '0.60', '0.70'])
+
+xtick.color: black
+ytick.color: black
+
+grid.color: black
+
+figure.facecolor: 0.75
+figure.edgecolor: white
+
+image.cmap: gray
+
+savefig.facecolor: white
+savefig.edgecolor: white
+
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/petroff10.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/petroff10.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..62d1262a09cd293cf7b8eb42a3bf1cccfd533aa2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/petroff10.mplstyle
@@ -0,0 +1,5 @@
+# Color cycle survey palette from Petroff (2021):
+# https://arxiv.org/abs/2107.02270
+# https://github.com/mpetroff/accessible-color-cycles
+axes.prop_cycle: cycler('color', ['3f90da', 'ffa90e', 'bd1f01', '94a4a2', '832db6', 'a96b59', 'e76300', 'b9ac70', '717581', '92dadd'])
+patch.facecolor: 3f90da
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-bright.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-bright.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..5e9e949378159bf83de825101b088afe23bbc5ec
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-bright.mplstyle
@@ -0,0 +1,3 @@
+# Seaborn bright palette
+axes.prop_cycle: cycler('color', ['003FFF', '03ED3A', 'E8000B', '8A2BE2', 'FFC400', '00D7FF'])
+patch.facecolor: 003FFF
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-colorblind.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-colorblind.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..e13b7aade323933472751664088c68a682cecf6e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-colorblind.mplstyle
@@ -0,0 +1,3 @@
+# Seaborn colorblind palette
+axes.prop_cycle: cycler('color', ['0072B2', '009E73', 'D55E00', 'CC79A7', 'F0E442', '56B4E9'])
+patch.facecolor: 0072B2
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark-palette.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark-palette.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..30160ae2506c63788b799fa80e3f3dedef7bca77
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark-palette.mplstyle
@@ -0,0 +1,3 @@
+# Seaborn dark palette
+axes.prop_cycle: cycler('color', ['001C7F', '017517', '8C0900', '7600A1', 'B8860B', '006374'])
+patch.facecolor: 001C7F
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..55b50b5bdd2689798504291cf4bca6ed775690d9
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-dark.mplstyle
@@ -0,0 +1,30 @@
+# Seaborn common parameters
+# .15 = dark_gray
+# .8 = light_gray
+figure.facecolor: white
+text.color: .15
+axes.labelcolor: .15
+legend.frameon: False
+legend.numpoints: 1
+legend.scatterpoints: 1
+xtick.direction: out
+ytick.direction: out
+xtick.color: .15
+ytick.color: .15
+axes.axisbelow: True
+image.cmap: Greys
+font.family: sans-serif
+font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
+grid.linestyle: -
+lines.solid_capstyle: round
+
+# Seaborn dark parameters
+axes.grid: False
+axes.facecolor: EAEAF2
+axes.edgecolor: white
+axes.linewidth: 0
+grid.color: white
+xtick.major.size: 0
+ytick.major.size: 0
+xtick.minor.size: 0
+ytick.minor.size: 0
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-darkgrid.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-darkgrid.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..0f5d955d7df60665cb30067c0fc83907438b319a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-darkgrid.mplstyle
@@ -0,0 +1,30 @@
+# Seaborn common parameters
+# .15 = dark_gray
+# .8 = light_gray
+figure.facecolor: white
+text.color: .15
+axes.labelcolor: .15
+legend.frameon: False
+legend.numpoints: 1
+legend.scatterpoints: 1
+xtick.direction: out
+ytick.direction: out
+xtick.color: .15
+ytick.color: .15
+axes.axisbelow: True
+image.cmap: Greys
+font.family: sans-serif
+font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
+grid.linestyle: -
+lines.solid_capstyle: round
+
+# Seaborn darkgrid parameters
+axes.grid: True
+axes.facecolor: EAEAF2
+axes.edgecolor: white
+axes.linewidth: 0
+grid.color: white
+xtick.major.size: 0
+ytick.major.size: 0
+xtick.minor.size: 0
+ytick.minor.size: 0
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-deep.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-deep.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..5d6b7c56009882e8f3a1cee0b8c8c8a52eb1fcfd
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-deep.mplstyle
@@ -0,0 +1,3 @@
+# Seaborn deep palette
+axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD'])
+patch.facecolor: 4C72B0
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-muted.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-muted.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..4a71646ce9036116fedfb2094d9f21d52ac3e40a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-muted.mplstyle
@@ -0,0 +1,3 @@
+# Seaborn muted palette
+axes.prop_cycle: cycler('color', ['4878CF', '6ACC65', 'D65F5F', 'B47CC7', 'C4AD66', '77BEDB'])
+patch.facecolor: 4878CF
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-notebook.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-notebook.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..18bcf3e1204208f50834f6438d67d26e20b90482
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-notebook.mplstyle
@@ -0,0 +1,21 @@
+# Seaborn notebook context
+figure.figsize: 8.0, 5.5
+axes.labelsize: 11
+axes.titlesize: 12
+xtick.labelsize: 10
+ytick.labelsize: 10
+legend.fontsize: 10
+
+grid.linewidth: 1
+lines.linewidth: 1.75
+patch.linewidth: .3
+lines.markersize: 7
+lines.markeredgewidth: 0
+
+xtick.major.width: 1
+ytick.major.width: 1
+xtick.minor.width: .5
+ytick.minor.width: .5
+
+xtick.major.pad: 7
+ytick.major.pad: 7
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-paper.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-paper.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..3326be4333b875b481ad294adb8235a7106838e5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-paper.mplstyle
@@ -0,0 +1,21 @@
+# Seaborn paper context
+figure.figsize: 6.4, 4.4
+axes.labelsize: 8.8
+axes.titlesize: 9.6
+xtick.labelsize: 8
+ytick.labelsize: 8
+legend.fontsize: 8
+
+grid.linewidth: 0.8
+lines.linewidth: 1.4
+patch.linewidth: 0.24
+lines.markersize: 5.6
+lines.markeredgewidth: 0
+
+xtick.major.width: 0.8
+ytick.major.width: 0.8
+xtick.minor.width: 0.4
+ytick.minor.width: 0.4
+
+xtick.major.pad: 5.6
+ytick.major.pad: 5.6
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-pastel.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-pastel.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..dff67482c085da8d5d508a4dc5ce946b7230b513
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-pastel.mplstyle
@@ -0,0 +1,3 @@
+# Seaborn pastel palette
+axes.prop_cycle: cycler('color', ['92C6FF', '97F0AA', 'FF9F9A', 'D0BBFF', 'FFFEA3', 'B0E0E6'])
+patch.facecolor: 92C6FF
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-poster.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-poster.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..47f237006caeac212840f23a8c2568947523c596
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-poster.mplstyle
@@ -0,0 +1,21 @@
+# Seaborn poster context
+figure.figsize: 12.8, 8.8
+axes.labelsize: 17.6
+axes.titlesize: 19.2
+xtick.labelsize: 16
+ytick.labelsize: 16
+legend.fontsize: 16
+
+grid.linewidth: 1.6
+lines.linewidth: 2.8
+patch.linewidth: 0.48
+lines.markersize: 11.2
+lines.markeredgewidth: 0
+
+xtick.major.width: 1.6
+ytick.major.width: 1.6
+xtick.minor.width: 0.8
+ytick.minor.width: 0.8
+
+xtick.major.pad: 11.2
+ytick.major.pad: 11.2
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-talk.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-talk.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..29a77c53c4a844a10c1970176f37a6354ad27680
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-talk.mplstyle
@@ -0,0 +1,21 @@
+# Seaborn talk context
+figure.figsize: 10.4, 7.15
+axes.labelsize: 14.3
+axes.titlesize: 15.6
+xtick.labelsize: 13
+ytick.labelsize: 13
+legend.fontsize: 13
+
+grid.linewidth: 1.3
+lines.linewidth: 2.275
+patch.linewidth: 0.39
+lines.markersize: 9.1
+lines.markeredgewidth: 0
+
+xtick.major.width: 1.3
+ytick.major.width: 1.3
+xtick.minor.width: 0.65
+ytick.minor.width: 0.65
+
+xtick.major.pad: 9.1
+ytick.major.pad: 9.1
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-ticks.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-ticks.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..c2a1cab9a5ebb7eee134f7c15133954d5ea9f878
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-ticks.mplstyle
@@ -0,0 +1,30 @@
+# Seaborn common parameters
+# .15 = dark_gray
+# .8 = light_gray
+figure.facecolor: white
+text.color: .15
+axes.labelcolor: .15
+legend.frameon: False
+legend.numpoints: 1
+legend.scatterpoints: 1
+xtick.direction: out
+ytick.direction: out
+xtick.color: .15
+ytick.color: .15
+axes.axisbelow: True
+image.cmap: Greys
+font.family: sans-serif
+font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
+grid.linestyle: -
+lines.solid_capstyle: round
+
+# Seaborn white parameters
+axes.grid: False
+axes.facecolor: white
+axes.edgecolor: .15
+axes.linewidth: 1.25
+grid.color: .8
+xtick.major.size: 6
+ytick.major.size: 6
+xtick.minor.size: 3
+ytick.minor.size: 3
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-white.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-white.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..dcbe3acf31dad3f06521e254cd54a74252c77839
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-white.mplstyle
@@ -0,0 +1,30 @@
+# Seaborn common parameters
+# .15 = dark_gray
+# .8 = light_gray
+figure.facecolor: white
+text.color: .15
+axes.labelcolor: .15
+legend.frameon: False
+legend.numpoints: 1
+legend.scatterpoints: 1
+xtick.direction: out
+ytick.direction: out
+xtick.color: .15
+ytick.color: .15
+axes.axisbelow: True
+image.cmap: Greys
+font.family: sans-serif
+font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
+grid.linestyle: -
+lines.solid_capstyle: round
+
+# Seaborn white parameters
+axes.grid: False
+axes.facecolor: white
+axes.edgecolor: .15
+axes.linewidth: 1.25
+grid.color: .8
+xtick.major.size: 0
+ytick.major.size: 0
+xtick.minor.size: 0
+ytick.minor.size: 0
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-whitegrid.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-whitegrid.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..612e21813e198296fe9ab95dbb73f8509f97e678
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8-whitegrid.mplstyle
@@ -0,0 +1,30 @@
+# Seaborn common parameters
+# .15 = dark_gray
+# .8 = light_gray
+figure.facecolor: white
+text.color: .15
+axes.labelcolor: .15
+legend.frameon: False
+legend.numpoints: 1
+legend.scatterpoints: 1
+xtick.direction: out
+ytick.direction: out
+xtick.color: .15
+ytick.color: .15
+axes.axisbelow: True
+image.cmap: Greys
+font.family: sans-serif
+font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
+grid.linestyle: -
+lines.solid_capstyle: round
+
+# Seaborn whitegrid parameters
+axes.grid: True
+axes.facecolor: white
+axes.edgecolor: .8
+axes.linewidth: 1
+grid.color: .8
+xtick.major.size: 0
+ytick.major.size: 0
+xtick.minor.size: 0
+ytick.minor.size: 0
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..94b1bc837a4728887cefffc6fdfa622ea4dd786c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/seaborn-v0_8.mplstyle
@@ -0,0 +1,57 @@
+# default seaborn aesthetic
+# darkgrid + deep palette + notebook context
+
+axes.axisbelow: True
+axes.edgecolor: white
+axes.facecolor: EAEAF2
+axes.grid: True
+axes.labelcolor: .15
+axes.labelsize: 11
+axes.linewidth: 0
+axes.prop_cycle: cycler('color', ['4C72B0', '55A868', 'C44E52', '8172B2', 'CCB974', '64B5CD'])
+axes.titlesize: 12
+
+figure.facecolor: white
+figure.figsize: 8.0, 5.5
+
+font.family: sans-serif
+font.sans-serif: Arial, Liberation Sans, DejaVu Sans, Bitstream Vera Sans, sans-serif
+
+grid.color: white
+grid.linestyle: -
+grid.linewidth: 1
+
+image.cmap: Greys
+
+legend.fontsize: 10
+legend.frameon: False
+legend.numpoints: 1
+legend.scatterpoints: 1
+
+lines.linewidth: 1.75
+lines.markeredgewidth: 0
+lines.markersize: 7
+lines.solid_capstyle: round
+
+patch.facecolor: 4C72B0
+patch.linewidth: .3
+
+text.color: .15
+
+xtick.color: .15
+xtick.direction: out
+xtick.labelsize: 10
+xtick.major.pad: 7
+xtick.major.size: 0
+xtick.major.width: 1
+xtick.minor.size: 0
+xtick.minor.width: .5
+
+ytick.color: .15
+ytick.direction: out
+ytick.labelsize: 10
+ytick.major.pad: 7
+ytick.major.size: 0
+ytick.major.width: 1
+ytick.minor.size: 0
+ytick.minor.width: .5
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/tableau-colorblind10.mplstyle b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/tableau-colorblind10.mplstyle
new file mode 100644
index 0000000000000000000000000000000000000000..2d8cb0208d5a2af8915bc605931931605229e263
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib/tableau-colorblind10.mplstyle
@@ -0,0 +1,3 @@
+# Tableau colorblind 10 palette
+axes.prop_cycle: cycler('color', ['006BA4', 'FF800E', 'ABABAB', '595959', '5F9ED1', 'C85200', '898989', 'A2C8EC', 'FFBC79', 'CFCFCF'])
+patch.facecolor: 006BA4
\ No newline at end of file
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/offsetbox.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/offsetbox.py
new file mode 100644
index 0000000000000000000000000000000000000000..067766a9d746adf3f473795e81d925d32fdda956
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/offsetbox.py
@@ -0,0 +1,1613 @@
+r"""
+Container classes for `.Artist`\s.
+
+`OffsetBox`
+ The base of all container artists defined in this module.
+
+`AnchoredOffsetbox`, `AnchoredText`
+ Anchor and align an arbitrary `.Artist` or a text relative to the parent
+ axes or a specific anchor point.
+
+`DrawingArea`
+ A container with fixed width and height. Children have a fixed position
+ inside the container and may be clipped.
+
+`HPacker`, `VPacker`
+ Containers for layouting their children vertically or horizontally.
+
+`PaddedBox`
+ A container to add a padding around an `.Artist`.
+
+`TextArea`
+ Contains a single `.Text` instance.
+"""
+
+import functools
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api, _docstring
+import matplotlib.artist as martist
+import matplotlib.path as mpath
+import matplotlib.text as mtext
+import matplotlib.transforms as mtransforms
+from matplotlib.font_manager import FontProperties
+from matplotlib.image import BboxImage
+from matplotlib.patches import (
+ FancyBboxPatch, FancyArrowPatch, bbox_artist as mbbox_artist)
+from matplotlib.transforms import Bbox, BboxBase, TransformedBbox
+
+
+DEBUG = False
+
+
+def _compat_get_offset(meth):
+ """
+ Decorator for the get_offset method of OffsetBox and subclasses, that
+ allows supporting both the new signature (self, bbox, renderer) and the old
+ signature (self, width, height, xdescent, ydescent, renderer).
+ """
+ sigs = [lambda self, width, height, xdescent, ydescent, renderer: locals(),
+ lambda self, bbox, renderer: locals()]
+
+ @functools.wraps(meth)
+ def get_offset(self, *args, **kwargs):
+ params = _api.select_matching_signature(sigs, self, *args, **kwargs)
+ bbox = (params["bbox"] if "bbox" in params else
+ Bbox.from_bounds(-params["xdescent"], -params["ydescent"],
+ params["width"], params["height"]))
+ return meth(params["self"], bbox, params["renderer"])
+ return get_offset
+
+
+# for debugging use
+def _bbox_artist(*args, **kwargs):
+ if DEBUG:
+ mbbox_artist(*args, **kwargs)
+
+
+def _get_packed_offsets(widths, total, sep, mode="fixed"):
+ r"""
+ Pack boxes specified by their *widths*.
+
+ For simplicity of the description, the terminology used here assumes a
+ horizontal layout, but the function works equally for a vertical layout.
+
+ There are three packing *mode*\s:
+
+ - 'fixed': The elements are packed tight to the left with a spacing of
+ *sep* in between. If *total* is *None* the returned total will be the
+ right edge of the last box. A non-*None* total will be passed unchecked
+ to the output. In particular this means that right edge of the last
+ box may be further to the right than the returned total.
+
+ - 'expand': Distribute the boxes with equal spacing so that the left edge
+ of the first box is at 0, and the right edge of the last box is at
+ *total*. The parameter *sep* is ignored in this mode. A total of *None*
+ is accepted and considered equal to 1. The total is returned unchanged
+ (except for the conversion *None* to 1). If the total is smaller than
+ the sum of the widths, the laid out boxes will overlap.
+
+ - 'equal': If *total* is given, the total space is divided in N equal
+ ranges and each box is left-aligned within its subspace.
+ Otherwise (*total* is *None*), *sep* must be provided and each box is
+ left-aligned in its subspace of width ``(max(widths) + sep)``. The
+ total width is then calculated to be ``N * (max(widths) + sep)``.
+
+ Parameters
+ ----------
+ widths : list of float
+ Widths of boxes to be packed.
+ total : float or None
+ Intended total length. *None* if not used.
+ sep : float or None
+ Spacing between boxes.
+ mode : {'fixed', 'expand', 'equal'}
+ The packing mode.
+
+ Returns
+ -------
+ total : float
+ The total width needed to accommodate the laid out boxes.
+ offsets : array of float
+ The left offsets of the boxes.
+ """
+ _api.check_in_list(["fixed", "expand", "equal"], mode=mode)
+
+ if mode == "fixed":
+ offsets_ = np.cumsum([0] + [w + sep for w in widths])
+ offsets = offsets_[:-1]
+ if total is None:
+ total = offsets_[-1] - sep
+ return total, offsets
+
+ elif mode == "expand":
+ # This is a bit of a hack to avoid a TypeError when *total*
+ # is None and used in conjugation with tight layout.
+ if total is None:
+ total = 1
+ if len(widths) > 1:
+ sep = (total - sum(widths)) / (len(widths) - 1)
+ else:
+ sep = 0
+ offsets_ = np.cumsum([0] + [w + sep for w in widths])
+ offsets = offsets_[:-1]
+ return total, offsets
+
+ elif mode == "equal":
+ maxh = max(widths)
+ if total is None:
+ if sep is None:
+ raise ValueError("total and sep cannot both be None when "
+ "using layout mode 'equal'")
+ total = (maxh + sep) * len(widths)
+ else:
+ sep = total / len(widths) - maxh
+ offsets = (maxh + sep) * np.arange(len(widths))
+ return total, offsets
+
+
+def _get_aligned_offsets(yspans, height, align="baseline"):
+ """
+ Align boxes each specified by their ``(y0, y1)`` spans.
+
+ For simplicity of the description, the terminology used here assumes a
+ horizontal layout (i.e., vertical alignment), but the function works
+ equally for a vertical layout.
+
+ Parameters
+ ----------
+ yspans
+ List of (y0, y1) spans of boxes to be aligned.
+ height : float or None
+ Intended total height. If None, the maximum of the heights
+ (``y1 - y0``) in *yspans* is used.
+ align : {'baseline', 'left', 'top', 'right', 'bottom', 'center'}
+ The alignment anchor of the boxes.
+
+ Returns
+ -------
+ (y0, y1)
+ y range spanned by the packing. If a *height* was originally passed
+ in, then for all alignments other than "baseline", a span of ``(0,
+ height)`` is used without checking that it is actually large enough).
+ descent
+ The descent of the packing.
+ offsets
+ The bottom offsets of the boxes.
+ """
+
+ _api.check_in_list(
+ ["baseline", "left", "top", "right", "bottom", "center"], align=align)
+ if height is None:
+ height = max(y1 - y0 for y0, y1 in yspans)
+
+ if align == "baseline":
+ yspan = (min(y0 for y0, y1 in yspans), max(y1 for y0, y1 in yspans))
+ offsets = [0] * len(yspans)
+ elif align in ["left", "bottom"]:
+ yspan = (0, height)
+ offsets = [-y0 for y0, y1 in yspans]
+ elif align in ["right", "top"]:
+ yspan = (0, height)
+ offsets = [height - y1 for y0, y1 in yspans]
+ elif align == "center":
+ yspan = (0, height)
+ offsets = [(height - (y1 - y0)) * .5 - y0 for y0, y1 in yspans]
+
+ return yspan, offsets
+
+
+class OffsetBox(martist.Artist):
+ """
+ A simple container artist.
+
+ The child artists are meant to be drawn at a relative position to its
+ parent.
+
+ Being an artist itself, all parameters are passed on to `.Artist`.
+ """
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args)
+ self._internal_update(kwargs)
+ # Clipping has not been implemented in the OffsetBox family, so
+ # disable the clip flag for consistency. It can always be turned back
+ # on to zero effect.
+ self.set_clip_on(False)
+ self._children = []
+ self._offset = (0, 0)
+
+ def set_figure(self, fig):
+ """
+ Set the `.Figure` for the `.OffsetBox` and all its children.
+
+ Parameters
+ ----------
+ fig : `~matplotlib.figure.Figure`
+ """
+ super().set_figure(fig)
+ for c in self.get_children():
+ c.set_figure(fig)
+
+ @martist.Artist.axes.setter
+ def axes(self, ax):
+ # TODO deal with this better
+ martist.Artist.axes.fset(self, ax)
+ for c in self.get_children():
+ if c is not None:
+ c.axes = ax
+
+ def contains(self, mouseevent):
+ """
+ Delegate the mouse event contains-check to the children.
+
+ As a container, the `.OffsetBox` does not respond itself to
+ mouseevents.
+
+ Parameters
+ ----------
+ mouseevent : `~matplotlib.backend_bases.MouseEvent`
+
+ Returns
+ -------
+ contains : bool
+ Whether any values are within the radius.
+ details : dict
+ An artist-specific dictionary of details of the event context,
+ such as which points are contained in the pick radius. See the
+ individual Artist subclasses for details.
+
+ See Also
+ --------
+ .Artist.contains
+ """
+ if self._different_canvas(mouseevent):
+ return False, {}
+ for c in self.get_children():
+ a, b = c.contains(mouseevent)
+ if a:
+ return a, b
+ return False, {}
+
+ def set_offset(self, xy):
+ """
+ Set the offset.
+
+ Parameters
+ ----------
+ xy : (float, float) or callable
+ The (x, y) coordinates of the offset in display units. These can
+ either be given explicitly as a tuple (x, y), or by providing a
+ function that converts the extent into the offset. This function
+ must have the signature::
+
+ def offset(width, height, xdescent, ydescent, renderer) \
+-> (float, float)
+ """
+ self._offset = xy
+ self.stale = True
+
+ @_compat_get_offset
+ def get_offset(self, bbox, renderer):
+ """
+ Return the offset as a tuple (x, y).
+
+ The extent parameters have to be provided to handle the case where the
+ offset is dynamically determined by a callable (see
+ `~.OffsetBox.set_offset`).
+
+ Parameters
+ ----------
+ bbox : `.Bbox`
+ renderer : `.RendererBase` subclass
+ """
+ return (
+ self._offset(bbox.width, bbox.height, -bbox.x0, -bbox.y0, renderer)
+ if callable(self._offset)
+ else self._offset)
+
+ def set_width(self, width):
+ """
+ Set the width of the box.
+
+ Parameters
+ ----------
+ width : float
+ """
+ self.width = width
+ self.stale = True
+
+ def set_height(self, height):
+ """
+ Set the height of the box.
+
+ Parameters
+ ----------
+ height : float
+ """
+ self.height = height
+ self.stale = True
+
+ def get_visible_children(self):
+ r"""Return a list of the visible child `.Artist`\s."""
+ return [c for c in self._children if c.get_visible()]
+
+ def get_children(self):
+ r"""Return a list of the child `.Artist`\s."""
+ return self._children
+
+ def _get_bbox_and_child_offsets(self, renderer):
+ """
+ Return the bbox of the offsetbox and the child offsets.
+
+ The bbox should satisfy ``x0 <= x1 and y0 <= y1``.
+
+ Parameters
+ ----------
+ renderer : `.RendererBase` subclass
+
+ Returns
+ -------
+ bbox
+ list of (xoffset, yoffset) pairs
+ """
+ raise NotImplementedError(
+ "get_bbox_and_offsets must be overridden in derived classes")
+
+ def get_bbox(self, renderer):
+ """Return the bbox of the offsetbox, ignoring parent offsets."""
+ bbox, offsets = self._get_bbox_and_child_offsets(renderer)
+ return bbox
+
+ def get_window_extent(self, renderer=None):
+ # docstring inherited
+ if renderer is None:
+ renderer = self.get_figure(root=True)._get_renderer()
+ bbox = self.get_bbox(renderer)
+ try: # Some subclasses redefine get_offset to take no args.
+ px, py = self.get_offset(bbox, renderer)
+ except TypeError:
+ px, py = self.get_offset()
+ return bbox.translated(px, py)
+
+ def draw(self, renderer):
+ """
+ Update the location of children if necessary and draw them
+ to the given *renderer*.
+ """
+ bbox, offsets = self._get_bbox_and_child_offsets(renderer)
+ px, py = self.get_offset(bbox, renderer)
+ for c, (ox, oy) in zip(self.get_visible_children(), offsets):
+ c.set_offset((px + ox, py + oy))
+ c.draw(renderer)
+ _bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
+ self.stale = False
+
+
+class PackerBase(OffsetBox):
+ def __init__(self, pad=0., sep=0., width=None, height=None,
+ align="baseline", mode="fixed", children=None):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0.0
+ The boundary padding in points.
+
+ sep : float, default: 0.0
+ The spacing between items in points.
+
+ width, height : float, optional
+ Width and height of the container box in pixels, calculated if
+ *None*.
+
+ align : {'top', 'bottom', 'left', 'right', 'center', 'baseline'}, \
+default: 'baseline'
+ Alignment of boxes.
+
+ mode : {'fixed', 'expand', 'equal'}, default: 'fixed'
+ The packing mode.
+
+ - 'fixed' packs the given `.Artist`\\s tight with *sep* spacing.
+ - 'expand' uses the maximal available space to distribute the
+ artists with equal spacing in between.
+ - 'equal': Each artist an equal fraction of the available space
+ and is left-aligned (or top-aligned) therein.
+
+ children : list of `.Artist`
+ The artists to pack.
+
+ Notes
+ -----
+ *pad* and *sep* are in points and will be scaled with the renderer
+ dpi, while *width* and *height* are in pixels.
+ """
+ super().__init__()
+ self.height = height
+ self.width = width
+ self.sep = sep
+ self.pad = pad
+ self.mode = mode
+ self.align = align
+ self._children = children
+
+
+class VPacker(PackerBase):
+ """
+ VPacker packs its children vertically, automatically adjusting their
+ relative positions at draw time.
+
+ .. code-block:: none
+
+ +---------+
+ | Child 1 |
+ | Child 2 |
+ | Child 3 |
+ +---------+
+ """
+
+ def _get_bbox_and_child_offsets(self, renderer):
+ # docstring inherited
+ dpicor = renderer.points_to_pixels(1.)
+ pad = self.pad * dpicor
+ sep = self.sep * dpicor
+
+ if self.width is not None:
+ for c in self.get_visible_children():
+ if isinstance(c, PackerBase) and c.mode == "expand":
+ c.set_width(self.width)
+
+ bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
+ (x0, x1), xoffsets = _get_aligned_offsets(
+ [bbox.intervalx for bbox in bboxes], self.width, self.align)
+ height, yoffsets = _get_packed_offsets(
+ [bbox.height for bbox in bboxes], self.height, sep, self.mode)
+
+ yoffsets = height - (yoffsets + [bbox.y1 for bbox in bboxes])
+ ydescent = yoffsets[0]
+ yoffsets = yoffsets - ydescent
+
+ return (
+ Bbox.from_bounds(x0, -ydescent, x1 - x0, height).padded(pad),
+ [*zip(xoffsets, yoffsets)])
+
+
+class HPacker(PackerBase):
+ """
+ HPacker packs its children horizontally, automatically adjusting their
+ relative positions at draw time.
+
+ .. code-block:: none
+
+ +-------------------------------+
+ | Child 1 Child 2 Child 3 |
+ +-------------------------------+
+ """
+
+ def _get_bbox_and_child_offsets(self, renderer):
+ # docstring inherited
+ dpicor = renderer.points_to_pixels(1.)
+ pad = self.pad * dpicor
+ sep = self.sep * dpicor
+
+ bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
+ if not bboxes:
+ return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
+
+ (y0, y1), yoffsets = _get_aligned_offsets(
+ [bbox.intervaly for bbox in bboxes], self.height, self.align)
+ width, xoffsets = _get_packed_offsets(
+ [bbox.width for bbox in bboxes], self.width, sep, self.mode)
+
+ x0 = bboxes[0].x0
+ xoffsets -= ([bbox.x0 for bbox in bboxes] - x0)
+
+ return (Bbox.from_bounds(x0, y0, width, y1 - y0).padded(pad),
+ [*zip(xoffsets, yoffsets)])
+
+
+class PaddedBox(OffsetBox):
+ """
+ A container to add a padding around an `.Artist`.
+
+ The `.PaddedBox` contains a `.FancyBboxPatch` that is used to visualize
+ it when rendering.
+
+ .. code-block:: none
+
+ +----------------------------+
+ | |
+ | |
+ | |
+ | <--pad--> Artist |
+ | ^ |
+ | pad |
+ | v |
+ +----------------------------+
+
+ Attributes
+ ----------
+ pad : float
+ The padding in points.
+ patch : `.FancyBboxPatch`
+ When *draw_frame* is True, this `.FancyBboxPatch` is made visible and
+ creates a border around the box.
+ """
+
+ def __init__(self, child, pad=0., *, draw_frame=False, patch_attrs=None):
+ """
+ Parameters
+ ----------
+ child : `~matplotlib.artist.Artist`
+ The contained `.Artist`.
+ pad : float, default: 0.0
+ The padding in points. This will be scaled with the renderer dpi.
+ In contrast, *width* and *height* are in *pixels* and thus not
+ scaled.
+ draw_frame : bool
+ Whether to draw the contained `.FancyBboxPatch`.
+ patch_attrs : dict or None
+ Additional parameters passed to the contained `.FancyBboxPatch`.
+ """
+ super().__init__()
+ self.pad = pad
+ self._children = [child]
+ self.patch = FancyBboxPatch(
+ xy=(0.0, 0.0), width=1., height=1.,
+ facecolor='w', edgecolor='k',
+ mutation_scale=1, # self.prop.get_size_in_points(),
+ snap=True,
+ visible=draw_frame,
+ boxstyle="square,pad=0",
+ )
+ if patch_attrs is not None:
+ self.patch.update(patch_attrs)
+
+ def _get_bbox_and_child_offsets(self, renderer):
+ # docstring inherited.
+ pad = self.pad * renderer.points_to_pixels(1.)
+ return (self._children[0].get_bbox(renderer).padded(pad), [(0, 0)])
+
+ def draw(self, renderer):
+ # docstring inherited
+ bbox, offsets = self._get_bbox_and_child_offsets(renderer)
+ px, py = self.get_offset(bbox, renderer)
+ for c, (ox, oy) in zip(self.get_visible_children(), offsets):
+ c.set_offset((px + ox, py + oy))
+
+ self.draw_frame(renderer)
+
+ for c in self.get_visible_children():
+ c.draw(renderer)
+
+ self.stale = False
+
+ def update_frame(self, bbox, fontsize=None):
+ self.patch.set_bounds(bbox.bounds)
+ if fontsize:
+ self.patch.set_mutation_scale(fontsize)
+ self.stale = True
+
+ def draw_frame(self, renderer):
+ # update the location and size of the legend
+ self.update_frame(self.get_window_extent(renderer))
+ self.patch.draw(renderer)
+
+
+class DrawingArea(OffsetBox):
+ """
+ The DrawingArea can contain any Artist as a child. The DrawingArea
+ has a fixed width and height. The position of children relative to
+ the parent is fixed. The children can be clipped at the
+ boundaries of the parent.
+ """
+
+ def __init__(self, width, height, xdescent=0., ydescent=0., clip=False):
+ """
+ Parameters
+ ----------
+ width, height : float
+ Width and height of the container box.
+ xdescent, ydescent : float
+ Descent of the box in x- and y-direction.
+ clip : bool
+ Whether to clip the children to the box.
+ """
+ super().__init__()
+ self.width = width
+ self.height = height
+ self.xdescent = xdescent
+ self.ydescent = ydescent
+ self._clip_children = clip
+ self.offset_transform = mtransforms.Affine2D()
+ self.dpi_transform = mtransforms.Affine2D()
+
+ @property
+ def clip_children(self):
+ """
+ If the children of this DrawingArea should be clipped
+ by DrawingArea bounding box.
+ """
+ return self._clip_children
+
+ @clip_children.setter
+ def clip_children(self, val):
+ self._clip_children = bool(val)
+ self.stale = True
+
+ def get_transform(self):
+ """
+ Return the `~matplotlib.transforms.Transform` applied to the children.
+ """
+ return self.dpi_transform + self.offset_transform
+
+ def set_transform(self, t):
+ """
+ set_transform is ignored.
+ """
+
+ def set_offset(self, xy):
+ """
+ Set the offset of the container.
+
+ Parameters
+ ----------
+ xy : (float, float)
+ The (x, y) coordinates of the offset in display units.
+ """
+ self._offset = xy
+ self.offset_transform.clear()
+ self.offset_transform.translate(xy[0], xy[1])
+ self.stale = True
+
+ def get_offset(self):
+ """Return offset of the container."""
+ return self._offset
+
+ def get_bbox(self, renderer):
+ # docstring inherited
+ dpi_cor = renderer.points_to_pixels(1.)
+ return Bbox.from_bounds(
+ -self.xdescent * dpi_cor, -self.ydescent * dpi_cor,
+ self.width * dpi_cor, self.height * dpi_cor)
+
+ def add_artist(self, a):
+ """Add an `.Artist` to the container box."""
+ self._children.append(a)
+ if not a.is_transform_set():
+ a.set_transform(self.get_transform())
+ if self.axes is not None:
+ a.axes = self.axes
+ fig = self.get_figure(root=False)
+ if fig is not None:
+ a.set_figure(fig)
+
+ def draw(self, renderer):
+ # docstring inherited
+
+ dpi_cor = renderer.points_to_pixels(1.)
+ self.dpi_transform.clear()
+ self.dpi_transform.scale(dpi_cor)
+
+ # At this point the DrawingArea has a transform
+ # to the display space so the path created is
+ # good for clipping children
+ tpath = mtransforms.TransformedPath(
+ mpath.Path([[0, 0], [0, self.height],
+ [self.width, self.height],
+ [self.width, 0]]),
+ self.get_transform())
+ for c in self._children:
+ if self._clip_children and not (c.clipbox or c._clippath):
+ c.set_clip_path(tpath)
+ c.draw(renderer)
+
+ _bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
+ self.stale = False
+
+
+class TextArea(OffsetBox):
+ """
+ The TextArea is a container artist for a single Text instance.
+
+ The text is placed at (0, 0) with baseline+left alignment, by default. The
+ width and height of the TextArea instance is the width and height of its
+ child text.
+ """
+
+ def __init__(self, s,
+ *,
+ textprops=None,
+ multilinebaseline=False,
+ ):
+ """
+ Parameters
+ ----------
+ s : str
+ The text to be displayed.
+ textprops : dict, default: {}
+ Dictionary of keyword parameters to be passed to the `.Text`
+ instance in the TextArea.
+ multilinebaseline : bool, default: False
+ Whether the baseline for multiline text is adjusted so that it
+ is (approximately) center-aligned with single-line text.
+ """
+ if textprops is None:
+ textprops = {}
+ self._text = mtext.Text(0, 0, s, **textprops)
+ super().__init__()
+ self._children = [self._text]
+ self.offset_transform = mtransforms.Affine2D()
+ self._baseline_transform = mtransforms.Affine2D()
+ self._text.set_transform(self.offset_transform +
+ self._baseline_transform)
+ self._multilinebaseline = multilinebaseline
+
+ def set_text(self, s):
+ """Set the text of this area as a string."""
+ self._text.set_text(s)
+ self.stale = True
+
+ def get_text(self):
+ """Return the string representation of this area's text."""
+ return self._text.get_text()
+
+ def set_multilinebaseline(self, t):
+ """
+ Set multilinebaseline.
+
+ If True, the baseline for multiline text is adjusted so that it is
+ (approximately) center-aligned with single-line text. This is used
+ e.g. by the legend implementation so that single-line labels are
+ baseline-aligned, but multiline labels are "center"-aligned with them.
+ """
+ self._multilinebaseline = t
+ self.stale = True
+
+ def get_multilinebaseline(self):
+ """
+ Get multilinebaseline.
+ """
+ return self._multilinebaseline
+
+ def set_transform(self, t):
+ """
+ set_transform is ignored.
+ """
+
+ def set_offset(self, xy):
+ """
+ Set the offset of the container.
+
+ Parameters
+ ----------
+ xy : (float, float)
+ The (x, y) coordinates of the offset in display units.
+ """
+ self._offset = xy
+ self.offset_transform.clear()
+ self.offset_transform.translate(xy[0], xy[1])
+ self.stale = True
+
+ def get_offset(self):
+ """Return offset of the container."""
+ return self._offset
+
+ def get_bbox(self, renderer):
+ _, h_, d_ = mtext._get_text_metrics_with_cache(
+ renderer, "lp", self._text._fontproperties,
+ ismath="TeX" if self._text.get_usetex() else False,
+ dpi=self.get_figure(root=True).dpi)
+
+ bbox, info, yd = self._text._get_layout(renderer)
+ w, h = bbox.size
+
+ self._baseline_transform.clear()
+
+ if len(info) > 1 and self._multilinebaseline:
+ yd_new = 0.5 * h - 0.5 * (h_ - d_)
+ self._baseline_transform.translate(0, yd - yd_new)
+ yd = yd_new
+ else: # single line
+ h_d = max(h_ - d_, h - yd)
+ h = h_d + yd
+
+ ha = self._text.get_horizontalalignment()
+ x0 = {"left": 0, "center": -w / 2, "right": -w}[ha]
+
+ return Bbox.from_bounds(x0, -yd, w, h)
+
+ def draw(self, renderer):
+ # docstring inherited
+ self._text.draw(renderer)
+ _bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
+ self.stale = False
+
+
+class AuxTransformBox(OffsetBox):
+ """
+ An OffsetBox with an auxiliary transform.
+
+ All child artists are first transformed with *aux_transform*, then
+ translated with an offset (the same for all children) so the bounding
+ box of the children matches the drawn box. (In other words, adding an
+ arbitrary translation to *aux_transform* has no effect as it will be
+ cancelled out by the later offsetting.)
+
+ `AuxTransformBox` is similar to `.DrawingArea`, except that the extent of
+ the box is not predetermined but calculated from the window extent of its
+ children, and the extent of the children will be calculated in the
+ transformed coordinate.
+ """
+ def __init__(self, aux_transform):
+ self.aux_transform = aux_transform
+ super().__init__()
+ self.offset_transform = mtransforms.Affine2D()
+ # ref_offset_transform makes offset_transform always relative to the
+ # lower-left corner of the bbox of its children.
+ self.ref_offset_transform = mtransforms.Affine2D()
+
+ def add_artist(self, a):
+ """Add an `.Artist` to the container box."""
+ self._children.append(a)
+ a.set_transform(self.get_transform())
+ self.stale = True
+
+ def get_transform(self):
+ """Return the `.Transform` applied to the children."""
+ return (self.aux_transform
+ + self.ref_offset_transform
+ + self.offset_transform)
+
+ def set_transform(self, t):
+ """
+ set_transform is ignored.
+ """
+
+ def set_offset(self, xy):
+ """
+ Set the offset of the container.
+
+ Parameters
+ ----------
+ xy : (float, float)
+ The (x, y) coordinates of the offset in display units.
+ """
+ self._offset = xy
+ self.offset_transform.clear()
+ self.offset_transform.translate(xy[0], xy[1])
+ self.stale = True
+
+ def get_offset(self):
+ """Return offset of the container."""
+ return self._offset
+
+ def get_bbox(self, renderer):
+ # clear the offset transforms
+ _off = self.offset_transform.get_matrix() # to be restored later
+ self.ref_offset_transform.clear()
+ self.offset_transform.clear()
+ # calculate the extent
+ bboxes = [c.get_window_extent(renderer) for c in self._children]
+ ub = Bbox.union(bboxes)
+ # adjust ref_offset_transform
+ self.ref_offset_transform.translate(-ub.x0, -ub.y0)
+ # restore offset transform
+ self.offset_transform.set_matrix(_off)
+ return Bbox.from_bounds(0, 0, ub.width, ub.height)
+
+ def draw(self, renderer):
+ # docstring inherited
+ for c in self._children:
+ c.draw(renderer)
+ _bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
+ self.stale = False
+
+
+class AnchoredOffsetbox(OffsetBox):
+ """
+ An OffsetBox placed according to location *loc*.
+
+ AnchoredOffsetbox has a single child. When multiple children are needed,
+ use an extra OffsetBox to enclose them. By default, the offset box is
+ anchored against its parent Axes. You may explicitly specify the
+ *bbox_to_anchor*.
+ """
+ zorder = 5 # zorder of the legend
+
+ # Location codes
+ codes = {'upper right': 1,
+ 'upper left': 2,
+ 'lower left': 3,
+ 'lower right': 4,
+ 'right': 5,
+ 'center left': 6,
+ 'center right': 7,
+ 'lower center': 8,
+ 'upper center': 9,
+ 'center': 10,
+ }
+
+ def __init__(self, loc, *,
+ pad=0.4, borderpad=0.5,
+ child=None, prop=None, frameon=True,
+ bbox_to_anchor=None,
+ bbox_transform=None,
+ **kwargs):
+ """
+ Parameters
+ ----------
+ loc : str
+ The box location. Valid locations are
+ 'upper left', 'upper center', 'upper right',
+ 'center left', 'center', 'center right',
+ 'lower left', 'lower center', 'lower right'.
+ For backward compatibility, numeric values are accepted as well.
+ See the parameter *loc* of `.Legend` for details.
+ pad : float, default: 0.4
+ Padding around the child as fraction of the fontsize.
+ borderpad : float, default: 0.5
+ Padding between the offsetbox frame and the *bbox_to_anchor*.
+ child : `.OffsetBox`
+ The box that will be anchored.
+ prop : `.FontProperties`
+ This is only used as a reference for paddings. If not given,
+ :rc:`legend.fontsize` is used.
+ frameon : bool
+ Whether to draw a frame around the box.
+ bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats
+ Box that is used to position the legend in conjunction with *loc*.
+ bbox_transform : None or :class:`matplotlib.transforms.Transform`
+ The transform for the bounding box (*bbox_to_anchor*).
+ **kwargs
+ All other parameters are passed on to `.OffsetBox`.
+
+ Notes
+ -----
+ See `.Legend` for a detailed description of the anchoring mechanism.
+ """
+ super().__init__(**kwargs)
+
+ self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
+ self.set_child(child)
+
+ if isinstance(loc, str):
+ loc = _api.check_getitem(self.codes, loc=loc)
+
+ self.loc = loc
+ self.borderpad = borderpad
+ self.pad = pad
+
+ if prop is None:
+ self.prop = FontProperties(size=mpl.rcParams["legend.fontsize"])
+ else:
+ self.prop = FontProperties._from_any(prop)
+ if isinstance(prop, dict) and "size" not in prop:
+ self.prop.set_size(mpl.rcParams["legend.fontsize"])
+
+ self.patch = FancyBboxPatch(
+ xy=(0.0, 0.0), width=1., height=1.,
+ facecolor='w', edgecolor='k',
+ mutation_scale=self.prop.get_size_in_points(),
+ snap=True,
+ visible=frameon,
+ boxstyle="square,pad=0",
+ )
+
+ def set_child(self, child):
+ """Set the child to be anchored."""
+ self._child = child
+ if child is not None:
+ child.axes = self.axes
+ self.stale = True
+
+ def get_child(self):
+ """Return the child."""
+ return self._child
+
+ def get_children(self):
+ """Return the list of children."""
+ return [self._child]
+
+ def get_bbox(self, renderer):
+ # docstring inherited
+ fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
+ pad = self.pad * fontsize
+ return self.get_child().get_bbox(renderer).padded(pad)
+
+ def get_bbox_to_anchor(self):
+ """Return the bbox that the box is anchored to."""
+ if self._bbox_to_anchor is None:
+ return self.axes.bbox
+ else:
+ transform = self._bbox_to_anchor_transform
+ if transform is None:
+ return self._bbox_to_anchor
+ else:
+ return TransformedBbox(self._bbox_to_anchor, transform)
+
+ def set_bbox_to_anchor(self, bbox, transform=None):
+ """
+ Set the bbox that the box is anchored to.
+
+ *bbox* can be a Bbox instance, a list of [left, bottom, width,
+ height], or a list of [left, bottom] where the width and
+ height will be assumed to be zero. The bbox will be
+ transformed to display coordinate by the given transform.
+ """
+ if bbox is None or isinstance(bbox, BboxBase):
+ self._bbox_to_anchor = bbox
+ else:
+ try:
+ l = len(bbox)
+ except TypeError as err:
+ raise ValueError(f"Invalid bbox: {bbox}") from err
+
+ if l == 2:
+ bbox = [bbox[0], bbox[1], 0, 0]
+
+ self._bbox_to_anchor = Bbox.from_bounds(*bbox)
+
+ self._bbox_to_anchor_transform = transform
+ self.stale = True
+
+ @_compat_get_offset
+ def get_offset(self, bbox, renderer):
+ # docstring inherited
+ pad = (self.borderpad
+ * renderer.points_to_pixels(self.prop.get_size_in_points()))
+ bbox_to_anchor = self.get_bbox_to_anchor()
+ x0, y0 = _get_anchored_bbox(
+ self.loc, Bbox.from_bounds(0, 0, bbox.width, bbox.height),
+ bbox_to_anchor, pad)
+ return x0 - bbox.x0, y0 - bbox.y0
+
+ def update_frame(self, bbox, fontsize=None):
+ self.patch.set_bounds(bbox.bounds)
+ if fontsize:
+ self.patch.set_mutation_scale(fontsize)
+
+ def draw(self, renderer):
+ # docstring inherited
+ if not self.get_visible():
+ return
+
+ # update the location and size of the legend
+ bbox = self.get_window_extent(renderer)
+ fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
+ self.update_frame(bbox, fontsize)
+ self.patch.draw(renderer)
+
+ px, py = self.get_offset(self.get_bbox(renderer), renderer)
+ self.get_child().set_offset((px, py))
+ self.get_child().draw(renderer)
+ self.stale = False
+
+
+def _get_anchored_bbox(loc, bbox, parentbbox, borderpad):
+ """
+ Return the (x, y) position of the *bbox* anchored at the *parentbbox* with
+ the *loc* code with the *borderpad*.
+ """
+ # This is only called internally and *loc* should already have been
+ # validated. If 0 (None), we just let ``bbox.anchored`` raise.
+ c = [None, "NE", "NW", "SW", "SE", "E", "W", "E", "S", "N", "C"][loc]
+ container = parentbbox.padded(-borderpad)
+ return bbox.anchored(c, container=container).p0
+
+
+class AnchoredText(AnchoredOffsetbox):
+ """
+ AnchoredOffsetbox with Text.
+ """
+
+ def __init__(self, s, loc, *, pad=0.4, borderpad=0.5, prop=None, **kwargs):
+ """
+ Parameters
+ ----------
+ s : str
+ Text.
+
+ loc : str
+ Location code. See `AnchoredOffsetbox`.
+
+ pad : float, default: 0.4
+ Padding around the text as fraction of the fontsize.
+
+ borderpad : float, default: 0.5
+ Spacing between the offsetbox frame and the *bbox_to_anchor*.
+
+ prop : dict, optional
+ Dictionary of keyword parameters to be passed to the
+ `~matplotlib.text.Text` instance contained inside AnchoredText.
+
+ **kwargs
+ All other parameters are passed to `AnchoredOffsetbox`.
+ """
+
+ if prop is None:
+ prop = {}
+ badkwargs = {'va', 'verticalalignment'}
+ if badkwargs & set(prop):
+ raise ValueError(
+ 'Mixing verticalalignment with AnchoredText is not supported.')
+
+ self.txt = TextArea(s, textprops=prop)
+ fp = self.txt._text.get_fontproperties()
+ super().__init__(
+ loc, pad=pad, borderpad=borderpad, child=self.txt, prop=fp,
+ **kwargs)
+
+
+class OffsetImage(OffsetBox):
+
+ def __init__(self, arr, *,
+ zoom=1,
+ cmap=None,
+ norm=None,
+ interpolation=None,
+ origin=None,
+ filternorm=True,
+ filterrad=4.0,
+ resample=False,
+ dpi_cor=True,
+ **kwargs
+ ):
+
+ super().__init__()
+ self._dpi_cor = dpi_cor
+
+ self.image = BboxImage(bbox=self.get_window_extent,
+ cmap=cmap,
+ norm=norm,
+ interpolation=interpolation,
+ origin=origin,
+ filternorm=filternorm,
+ filterrad=filterrad,
+ resample=resample,
+ **kwargs
+ )
+
+ self._children = [self.image]
+
+ self.set_zoom(zoom)
+ self.set_data(arr)
+
+ def set_data(self, arr):
+ self._data = np.asarray(arr)
+ self.image.set_data(self._data)
+ self.stale = True
+
+ def get_data(self):
+ return self._data
+
+ def set_zoom(self, zoom):
+ self._zoom = zoom
+ self.stale = True
+
+ def get_zoom(self):
+ return self._zoom
+
+ def get_offset(self):
+ """Return offset of the container."""
+ return self._offset
+
+ def get_children(self):
+ return [self.image]
+
+ def get_bbox(self, renderer):
+ dpi_cor = renderer.points_to_pixels(1.) if self._dpi_cor else 1.
+ zoom = self.get_zoom()
+ data = self.get_data()
+ ny, nx = data.shape[:2]
+ w, h = dpi_cor * nx * zoom, dpi_cor * ny * zoom
+ return Bbox.from_bounds(0, 0, w, h)
+
+ def draw(self, renderer):
+ # docstring inherited
+ self.image.draw(renderer)
+ # bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
+ self.stale = False
+
+
+class AnnotationBbox(martist.Artist, mtext._AnnotationBase):
+ """
+ Container for an `OffsetBox` referring to a specific position *xy*.
+
+ Optionally an arrow pointing from the offsetbox to *xy* can be drawn.
+
+ This is like `.Annotation`, but with `OffsetBox` instead of `.Text`.
+ """
+
+ zorder = 3
+
+ def __str__(self):
+ return f"AnnotationBbox({self.xy[0]:g},{self.xy[1]:g})"
+
+ @_docstring.interpd
+ def __init__(self, offsetbox, xy, xybox=None, xycoords='data', boxcoords=None, *,
+ frameon=True, pad=0.4, # FancyBboxPatch boxstyle.
+ annotation_clip=None,
+ box_alignment=(0.5, 0.5),
+ bboxprops=None,
+ arrowprops=None,
+ fontsize=None,
+ **kwargs):
+ """
+ Parameters
+ ----------
+ offsetbox : `OffsetBox`
+
+ xy : (float, float)
+ The point *(x, y)* to annotate. The coordinate system is determined
+ by *xycoords*.
+
+ xybox : (float, float), default: *xy*
+ The position *(x, y)* to place the text at. The coordinate system
+ is determined by *boxcoords*.
+
+ xycoords : single or two-tuple of str or `.Artist` or `.Transform` or \
+callable, default: 'data'
+ The coordinate system that *xy* is given in. See the parameter
+ *xycoords* in `.Annotation` for a detailed description.
+
+ boxcoords : single or two-tuple of str or `.Artist` or `.Transform` \
+or callable, default: value of *xycoords*
+ The coordinate system that *xybox* is given in. See the parameter
+ *textcoords* in `.Annotation` for a detailed description.
+
+ frameon : bool, default: True
+ By default, the text is surrounded by a white `.FancyBboxPatch`
+ (accessible as the ``patch`` attribute of the `.AnnotationBbox`).
+ If *frameon* is set to False, this patch is made invisible.
+
+ annotation_clip: bool or None, default: None
+ Whether to clip (i.e. not draw) the annotation when the annotation
+ point *xy* is outside the Axes area.
+
+ - If *True*, the annotation will be clipped when *xy* is outside
+ the Axes.
+ - If *False*, the annotation will always be drawn.
+ - If *None*, the annotation will be clipped when *xy* is outside
+ the Axes and *xycoords* is 'data'.
+
+ pad : float, default: 0.4
+ Padding around the offsetbox.
+
+ box_alignment : (float, float)
+ A tuple of two floats for a vertical and horizontal alignment of
+ the offset box w.r.t. the *boxcoords*.
+ The lower-left corner is (0, 0) and upper-right corner is (1, 1).
+
+ bboxprops : dict, optional
+ A dictionary of properties to set for the annotation bounding box,
+ for example *boxstyle* and *alpha*. See `.FancyBboxPatch` for
+ details.
+
+ arrowprops: dict, optional
+ Arrow properties, see `.Annotation` for description.
+
+ fontsize: float or str, optional
+ Translated to points and passed as *mutation_scale* into
+ `.FancyBboxPatch` to scale attributes of the box style (e.g. pad
+ or rounding_size). The name is chosen in analogy to `.Text` where
+ *fontsize* defines the mutation scale as well. If not given,
+ :rc:`legend.fontsize` is used. See `.Text.set_fontsize` for valid
+ values.
+
+ **kwargs
+ Other `AnnotationBbox` properties. See `.AnnotationBbox.set` for
+ a list.
+ """
+
+ martist.Artist.__init__(self)
+ mtext._AnnotationBase.__init__(
+ self, xy, xycoords=xycoords, annotation_clip=annotation_clip)
+
+ self.offsetbox = offsetbox
+ self.arrowprops = arrowprops.copy() if arrowprops is not None else None
+ self.set_fontsize(fontsize)
+ self.xybox = xybox if xybox is not None else xy
+ self.boxcoords = boxcoords if boxcoords is not None else xycoords
+ self._box_alignment = box_alignment
+
+ if arrowprops is not None:
+ self._arrow_relpos = self.arrowprops.pop("relpos", (0.5, 0.5))
+ self.arrow_patch = FancyArrowPatch((0, 0), (1, 1),
+ **self.arrowprops)
+ else:
+ self._arrow_relpos = None
+ self.arrow_patch = None
+
+ self.patch = FancyBboxPatch( # frame
+ xy=(0.0, 0.0), width=1., height=1.,
+ facecolor='w', edgecolor='k',
+ mutation_scale=self.prop.get_size_in_points(),
+ snap=True,
+ visible=frameon,
+ )
+ self.patch.set_boxstyle("square", pad=pad)
+ if bboxprops:
+ self.patch.set(**bboxprops)
+
+ self._internal_update(kwargs)
+
+ @property
+ def xyann(self):
+ return self.xybox
+
+ @xyann.setter
+ def xyann(self, xyann):
+ self.xybox = xyann
+ self.stale = True
+
+ @property
+ def anncoords(self):
+ return self.boxcoords
+
+ @anncoords.setter
+ def anncoords(self, coords):
+ self.boxcoords = coords
+ self.stale = True
+
+ def contains(self, mouseevent):
+ if self._different_canvas(mouseevent):
+ return False, {}
+ if not self._check_xy(None):
+ return False, {}
+ return self.offsetbox.contains(mouseevent)
+ # self.arrow_patch is currently not checked as this can be a line - JJ
+
+ def get_children(self):
+ children = [self.offsetbox, self.patch]
+ if self.arrow_patch:
+ children.append(self.arrow_patch)
+ return children
+
+ def set_figure(self, fig):
+ if self.arrow_patch is not None:
+ self.arrow_patch.set_figure(fig)
+ self.offsetbox.set_figure(fig)
+ martist.Artist.set_figure(self, fig)
+
+ def set_fontsize(self, s=None):
+ """
+ Set the fontsize in points.
+
+ If *s* is not given, reset to :rc:`legend.fontsize`.
+ """
+ if s is None:
+ s = mpl.rcParams["legend.fontsize"]
+
+ self.prop = FontProperties(size=s)
+ self.stale = True
+
+ def get_fontsize(self):
+ """Return the fontsize in points."""
+ return self.prop.get_size_in_points()
+
+ def get_window_extent(self, renderer=None):
+ # docstring inherited
+ if renderer is None:
+ renderer = self.get_figure(root=True)._get_renderer()
+ self.update_positions(renderer)
+ return Bbox.union([child.get_window_extent(renderer)
+ for child in self.get_children()])
+
+ def get_tightbbox(self, renderer=None):
+ # docstring inherited
+ if renderer is None:
+ renderer = self.get_figure(root=True)._get_renderer()
+ self.update_positions(renderer)
+ return Bbox.union([child.get_tightbbox(renderer)
+ for child in self.get_children()])
+
+ def update_positions(self, renderer):
+ """Update pixel positions for the annotated point, the text, and the arrow."""
+
+ ox0, oy0 = self._get_xy(renderer, self.xybox, self.boxcoords)
+ bbox = self.offsetbox.get_bbox(renderer)
+ fw, fh = self._box_alignment
+ self.offsetbox.set_offset(
+ (ox0 - fw*bbox.width - bbox.x0, oy0 - fh*bbox.height - bbox.y0))
+
+ bbox = self.offsetbox.get_window_extent(renderer)
+ self.patch.set_bounds(bbox.bounds)
+
+ mutation_scale = renderer.points_to_pixels(self.get_fontsize())
+ self.patch.set_mutation_scale(mutation_scale)
+
+ if self.arrowprops:
+ # Use FancyArrowPatch if self.arrowprops has "arrowstyle" key.
+
+ # Adjust the starting point of the arrow relative to the textbox.
+ # TODO: Rotation needs to be accounted.
+ arrow_begin = bbox.p0 + bbox.size * self._arrow_relpos
+ arrow_end = self._get_position_xy(renderer)
+ # The arrow (from arrow_begin to arrow_end) will be first clipped
+ # by patchA and patchB, then shrunk by shrinkA and shrinkB (in
+ # points). If patch A is not set, self.bbox_patch is used.
+ self.arrow_patch.set_positions(arrow_begin, arrow_end)
+
+ if "mutation_scale" in self.arrowprops:
+ mutation_scale = renderer.points_to_pixels(
+ self.arrowprops["mutation_scale"])
+ # Else, use fontsize-based mutation_scale defined above.
+ self.arrow_patch.set_mutation_scale(mutation_scale)
+
+ patchA = self.arrowprops.get("patchA", self.patch)
+ self.arrow_patch.set_patchA(patchA)
+
+ def draw(self, renderer):
+ # docstring inherited
+ if not self.get_visible() or not self._check_xy(renderer):
+ return
+ renderer.open_group(self.__class__.__name__, gid=self.get_gid())
+ self.update_positions(renderer)
+ if self.arrow_patch is not None:
+ if (self.arrow_patch.get_figure(root=False) is None and
+ (fig := self.get_figure(root=False)) is not None):
+ self.arrow_patch.set_figure(fig)
+ self.arrow_patch.draw(renderer)
+ self.patch.draw(renderer)
+ self.offsetbox.draw(renderer)
+ renderer.close_group(self.__class__.__name__)
+ self.stale = False
+
+
+class DraggableBase:
+ """
+ Helper base class for a draggable artist (legend, offsetbox).
+
+ Derived classes must override the following methods::
+
+ def save_offset(self):
+ '''
+ Called when the object is picked for dragging; should save the
+ reference position of the artist.
+ '''
+
+ def update_offset(self, dx, dy):
+ '''
+ Called during the dragging; (*dx*, *dy*) is the pixel offset from
+ the point where the mouse drag started.
+ '''
+
+ Optionally, you may override the following method::
+
+ def finalize_offset(self):
+ '''Called when the mouse is released.'''
+
+ In the current implementation of `.DraggableLegend` and
+ `DraggableAnnotation`, `update_offset` places the artists in display
+ coordinates, and `finalize_offset` recalculates their position in axes
+ coordinate and set a relevant attribute.
+ """
+
+ def __init__(self, ref_artist, use_blit=False):
+ self.ref_artist = ref_artist
+ if not ref_artist.pickable():
+ ref_artist.set_picker(self._picker)
+ self.got_artist = False
+ self._use_blit = use_blit and self.canvas.supports_blit
+ callbacks = self.canvas.callbacks
+ self._disconnectors = [
+ functools.partial(
+ callbacks.disconnect, callbacks._connect_picklable(name, func))
+ for name, func in [
+ ("pick_event", self.on_pick),
+ ("button_release_event", self.on_release),
+ ("motion_notify_event", self.on_motion),
+ ]
+ ]
+
+ @staticmethod
+ def _picker(artist, mouseevent):
+ # A custom picker to prevent dragging on mouse scroll events
+ if mouseevent.name == "scroll_event":
+ return False, {}
+ return artist.contains(mouseevent)
+
+ # A property, not an attribute, to maintain picklability.
+ canvas = property(lambda self: self.ref_artist.get_figure(root=True).canvas)
+ cids = property(lambda self: [
+ disconnect.args[0] for disconnect in self._disconnectors[:2]])
+
+ def on_motion(self, evt):
+ if self._check_still_parented() and self.got_artist:
+ dx = evt.x - self.mouse_x
+ dy = evt.y - self.mouse_y
+ self.update_offset(dx, dy)
+ if self._use_blit:
+ self.canvas.restore_region(self.background)
+ self.ref_artist.draw(
+ self.ref_artist.get_figure(root=True)._get_renderer())
+ self.canvas.blit()
+ else:
+ self.canvas.draw()
+
+ def on_pick(self, evt):
+ if self._check_still_parented():
+ if evt.artist == self.ref_artist:
+ self.mouse_x = evt.mouseevent.x
+ self.mouse_y = evt.mouseevent.y
+ self.save_offset()
+ self.got_artist = True
+ if self.got_artist and self._use_blit:
+ self.ref_artist.set_animated(True)
+ self.canvas.draw()
+ fig = self.ref_artist.get_figure(root=False)
+ self.background = self.canvas.copy_from_bbox(fig.bbox)
+ self.ref_artist.draw(fig._get_renderer())
+ self.canvas.blit()
+
+ def on_release(self, event):
+ if self._check_still_parented() and self.got_artist:
+ self.finalize_offset()
+ self.got_artist = False
+ if self._use_blit:
+ self.canvas.restore_region(self.background)
+ self.ref_artist.draw(self.ref_artist.figure._get_renderer())
+ self.canvas.blit()
+ self.ref_artist.set_animated(False)
+
+ def _check_still_parented(self):
+ if self.ref_artist.get_figure(root=False) is None:
+ self.disconnect()
+ return False
+ else:
+ return True
+
+ def disconnect(self):
+ """Disconnect the callbacks."""
+ for disconnector in self._disconnectors:
+ disconnector()
+
+ def save_offset(self):
+ pass
+
+ def update_offset(self, dx, dy):
+ pass
+
+ def finalize_offset(self):
+ pass
+
+
+class DraggableOffsetBox(DraggableBase):
+ def __init__(self, ref_artist, offsetbox, use_blit=False):
+ super().__init__(ref_artist, use_blit=use_blit)
+ self.offsetbox = offsetbox
+
+ def save_offset(self):
+ offsetbox = self.offsetbox
+ renderer = offsetbox.get_figure(root=True)._get_renderer()
+ offset = offsetbox.get_offset(offsetbox.get_bbox(renderer), renderer)
+ self.offsetbox_x, self.offsetbox_y = offset
+ self.offsetbox.set_offset(offset)
+
+ def update_offset(self, dx, dy):
+ loc_in_canvas = self.offsetbox_x + dx, self.offsetbox_y + dy
+ self.offsetbox.set_offset(loc_in_canvas)
+
+ def get_loc_in_canvas(self):
+ offsetbox = self.offsetbox
+ renderer = offsetbox.get_figure(root=True)._get_renderer()
+ bbox = offsetbox.get_bbox(renderer)
+ ox, oy = offsetbox._offset
+ loc_in_canvas = (ox + bbox.x0, oy + bbox.y0)
+ return loc_in_canvas
+
+
+class DraggableAnnotation(DraggableBase):
+ def __init__(self, annotation, use_blit=False):
+ super().__init__(annotation, use_blit=use_blit)
+ self.annotation = annotation
+
+ def save_offset(self):
+ ann = self.annotation
+ self.ox, self.oy = ann.get_transform().transform(ann.xyann)
+
+ def update_offset(self, dx, dy):
+ ann = self.annotation
+ ann.xyann = ann.get_transform().inverted().transform(
+ (self.ox + dx, self.oy + dy))
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/offsetbox.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/offsetbox.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..3b1520e171385a334e0b4893de6bf152c379c52a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/offsetbox.pyi
@@ -0,0 +1,298 @@
+import matplotlib.artist as martist
+from matplotlib.backend_bases import RendererBase, Event, FigureCanvasBase
+from matplotlib.colors import Colormap, Normalize
+import matplotlib.text as mtext
+from matplotlib.figure import Figure, SubFigure
+from matplotlib.font_manager import FontProperties
+from matplotlib.image import BboxImage
+from matplotlib.patches import FancyArrowPatch, FancyBboxPatch
+from matplotlib.transforms import Bbox, BboxBase, Transform
+from matplotlib.typing import CoordsType
+
+import numpy as np
+from numpy.typing import ArrayLike
+from collections.abc import Callable, Sequence
+from typing import Any, Literal, overload
+
+DEBUG: bool
+
+def _get_packed_offsets(
+ widths: Sequence[float],
+ total: float | None,
+ sep: float | None,
+ mode: Literal["fixed", "expand", "equal"] = ...,
+) -> tuple[float, np.ndarray]: ...
+
+class OffsetBox(martist.Artist):
+ width: float | None
+ height: float | None
+ def __init__(self, *args, **kwargs) -> None: ...
+ def set_figure(self, fig: Figure | SubFigure) -> None: ...
+ def set_offset(
+ self,
+ xy: tuple[float, float]
+ | Callable[[float, float, float, float, RendererBase], tuple[float, float]],
+ ) -> None: ...
+
+ @overload
+ def get_offset(self, bbox: Bbox, renderer: RendererBase) -> tuple[float, float]: ...
+ @overload
+ def get_offset(
+ self,
+ width: float,
+ height: float,
+ xdescent: float,
+ ydescent: float,
+ renderer: RendererBase
+ ) -> tuple[float, float]: ...
+
+ def set_width(self, width: float) -> None: ...
+ def set_height(self, height: float) -> None: ...
+ def get_visible_children(self) -> list[martist.Artist]: ...
+ def get_children(self) -> list[martist.Artist]: ...
+ def get_bbox(self, renderer: RendererBase) -> Bbox: ...
+ def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ...
+
+class PackerBase(OffsetBox):
+ height: float | None
+ width: float | None
+ sep: float | None
+ pad: float | None
+ mode: Literal["fixed", "expand", "equal"]
+ align: Literal["top", "bottom", "left", "right", "center", "baseline"]
+ def __init__(
+ self,
+ pad: float | None = ...,
+ sep: float | None = ...,
+ width: float | None = ...,
+ height: float | None = ...,
+ align: Literal["top", "bottom", "left", "right", "center", "baseline"] = ...,
+ mode: Literal["fixed", "expand", "equal"] = ...,
+ children: list[martist.Artist] | None = ...,
+ ) -> None: ...
+
+class VPacker(PackerBase): ...
+class HPacker(PackerBase): ...
+
+class PaddedBox(OffsetBox):
+ pad: float | None
+ patch: FancyBboxPatch
+ def __init__(
+ self,
+ child: martist.Artist,
+ pad: float | None = ...,
+ *,
+ draw_frame: bool = ...,
+ patch_attrs: dict[str, Any] | None = ...,
+ ) -> None: ...
+ def update_frame(self, bbox: Bbox, fontsize: float | None = ...) -> None: ...
+ def draw_frame(self, renderer: RendererBase) -> None: ...
+
+class DrawingArea(OffsetBox):
+ width: float
+ height: float
+ xdescent: float
+ ydescent: float
+ offset_transform: Transform
+ dpi_transform: Transform
+ def __init__(
+ self,
+ width: float,
+ height: float,
+ xdescent: float = ...,
+ ydescent: float = ...,
+ clip: bool = ...,
+ ) -> None: ...
+ @property
+ def clip_children(self) -> bool: ...
+ @clip_children.setter
+ def clip_children(self, val: bool) -> None: ...
+ def get_transform(self) -> Transform: ...
+
+ # does not accept all options of superclass
+ def set_offset(self, xy: tuple[float, float]) -> None: ... # type: ignore[override]
+ def get_offset(self) -> tuple[float, float]: ... # type: ignore[override]
+ def add_artist(self, a: martist.Artist) -> None: ...
+
+class TextArea(OffsetBox):
+ offset_transform: Transform
+ def __init__(
+ self,
+ s: str,
+ *,
+ textprops: dict[str, Any] | None = ...,
+ multilinebaseline: bool = ...,
+ ) -> None: ...
+ def set_text(self, s: str) -> None: ...
+ def get_text(self) -> str: ...
+ def set_multilinebaseline(self, t: bool) -> None: ...
+ def get_multilinebaseline(self) -> bool: ...
+
+ # does not accept all options of superclass
+ def set_offset(self, xy: tuple[float, float]) -> None: ... # type: ignore[override]
+ def get_offset(self) -> tuple[float, float]: ... # type: ignore[override]
+
+class AuxTransformBox(OffsetBox):
+ aux_transform: Transform
+ offset_transform: Transform
+ ref_offset_transform: Transform
+ def __init__(self, aux_transform: Transform) -> None: ...
+ def add_artist(self, a: martist.Artist) -> None: ...
+ def get_transform(self) -> Transform: ...
+
+ # does not accept all options of superclass
+ def set_offset(self, xy: tuple[float, float]) -> None: ... # type: ignore[override]
+ def get_offset(self) -> tuple[float, float]: ... # type: ignore[override]
+
+class AnchoredOffsetbox(OffsetBox):
+ zorder: float
+ codes: dict[str, int]
+ loc: int
+ borderpad: float
+ pad: float
+ prop: FontProperties
+ patch: FancyBboxPatch
+ def __init__(
+ self,
+ loc: str,
+ *,
+ pad: float = ...,
+ borderpad: float = ...,
+ child: OffsetBox | None = ...,
+ prop: FontProperties | None = ...,
+ frameon: bool = ...,
+ bbox_to_anchor: BboxBase
+ | tuple[float, float]
+ | tuple[float, float, float, float]
+ | None = ...,
+ bbox_transform: Transform | None = ...,
+ **kwargs
+ ) -> None: ...
+ def set_child(self, child: OffsetBox | None) -> None: ...
+ def get_child(self) -> OffsetBox | None: ...
+ def get_children(self) -> list[martist.Artist]: ...
+ def get_bbox_to_anchor(self) -> Bbox: ...
+ def set_bbox_to_anchor(
+ self, bbox: BboxBase, transform: Transform | None = ...
+ ) -> None: ...
+ def update_frame(self, bbox: Bbox, fontsize: float | None = ...) -> None: ...
+
+class AnchoredText(AnchoredOffsetbox):
+ txt: TextArea
+ def __init__(
+ self,
+ s: str,
+ loc: str,
+ *,
+ pad: float = ...,
+ borderpad: float = ...,
+ prop: dict[str, Any] | None = ...,
+ **kwargs
+ ) -> None: ...
+
+class OffsetImage(OffsetBox):
+ image: BboxImage
+ def __init__(
+ self,
+ arr: ArrayLike,
+ *,
+ zoom: float = ...,
+ cmap: Colormap | str | None = ...,
+ norm: Normalize | str | None = ...,
+ interpolation: str | None = ...,
+ origin: Literal["upper", "lower"] | None = ...,
+ filternorm: bool = ...,
+ filterrad: float = ...,
+ resample: bool = ...,
+ dpi_cor: bool = ...,
+ **kwargs
+ ) -> None: ...
+ stale: bool
+ def set_data(self, arr: ArrayLike | None) -> None: ...
+ def get_data(self) -> ArrayLike | None: ...
+ def set_zoom(self, zoom: float) -> None: ...
+ def get_zoom(self) -> float: ...
+ def get_children(self) -> list[martist.Artist]: ...
+ def get_offset(self) -> tuple[float, float]: ... # type: ignore[override]
+
+class AnnotationBbox(martist.Artist, mtext._AnnotationBase):
+ zorder: float
+ offsetbox: OffsetBox
+ arrowprops: dict[str, Any] | None
+ xybox: tuple[float, float]
+ boxcoords: CoordsType
+ arrow_patch: FancyArrowPatch | None
+ patch: FancyBboxPatch
+ prop: FontProperties
+ def __init__(
+ self,
+ offsetbox: OffsetBox,
+ xy: tuple[float, float],
+ xybox: tuple[float, float] | None = ...,
+ xycoords: CoordsType = ...,
+ boxcoords: CoordsType | None = ...,
+ *,
+ frameon: bool = ...,
+ pad: float = ...,
+ annotation_clip: bool | None = ...,
+ box_alignment: tuple[float, float] = ...,
+ bboxprops: dict[str, Any] | None = ...,
+ arrowprops: dict[str, Any] | None = ...,
+ fontsize: float | str | None = ...,
+ **kwargs
+ ) -> None: ...
+ @property
+ def xyann(self) -> tuple[float, float]: ...
+ @xyann.setter
+ def xyann(self, xyann: tuple[float, float]) -> None: ...
+ @property
+ def anncoords(
+ self,
+ ) -> CoordsType: ...
+ @anncoords.setter
+ def anncoords(
+ self,
+ coords: CoordsType,
+ ) -> None: ...
+ def get_children(self) -> list[martist.Artist]: ...
+ def set_figure(self, fig: Figure | SubFigure) -> None: ...
+ def set_fontsize(self, s: str | float | None = ...) -> None: ...
+ def get_fontsize(self) -> float: ...
+ def get_tightbbox(self, renderer: RendererBase | None = ...) -> Bbox: ...
+ def update_positions(self, renderer: RendererBase) -> None: ...
+
+class DraggableBase:
+ ref_artist: martist.Artist
+ got_artist: bool
+ mouse_x: int
+ mouse_y: int
+ background: Any
+
+ @property
+ def canvas(self) -> FigureCanvasBase: ...
+ @property
+ def cids(self) -> list[int]: ...
+
+ def __init__(self, ref_artist: martist.Artist, use_blit: bool = ...) -> None: ...
+ def on_motion(self, evt: Event) -> None: ...
+ def on_pick(self, evt: Event) -> None: ...
+ def on_release(self, event: Event) -> None: ...
+ def disconnect(self) -> None: ...
+ def save_offset(self) -> None: ...
+ def update_offset(self, dx: float, dy: float) -> None: ...
+ def finalize_offset(self) -> None: ...
+
+class DraggableOffsetBox(DraggableBase):
+ offsetbox: OffsetBox
+ def __init__(
+ self, ref_artist: martist.Artist, offsetbox: OffsetBox, use_blit: bool = ...
+ ) -> None: ...
+ def save_offset(self) -> None: ...
+ def update_offset(self, dx: float, dy: float) -> None: ...
+ def get_loc_in_canvas(self) -> tuple[float, float]: ...
+
+class DraggableAnnotation(DraggableBase):
+ annotation: mtext.Annotation
+ def __init__(self, annotation: mtext.Annotation, use_blit: bool = ...) -> None: ...
+ def save_offset(self) -> None: ...
+ def update_offset(self, dx: float, dy: float) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patches.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patches.py
new file mode 100644
index 0000000000000000000000000000000000000000..96b9c4f11066c3a707a828ad5c90590c03318759
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patches.py
@@ -0,0 +1,4760 @@
+r"""
+Patches are `.Artist`\s with a face color and an edge color.
+"""
+
+import functools
+import inspect
+import math
+from numbers import Number, Real
+import textwrap
+from types import SimpleNamespace
+from collections import namedtuple
+from matplotlib.transforms import Affine2D
+
+import numpy as np
+
+import matplotlib as mpl
+from . import (_api, artist, cbook, colors, _docstring, hatch as mhatch,
+ lines as mlines, transforms)
+from .bezier import (
+ NonIntersectingPathException, get_cos_sin, get_intersection,
+ get_parallels, inside_circle, make_wedged_bezier2,
+ split_bezier_intersecting_with_closedpath, split_path_inout)
+from .path import Path
+from ._enums import JoinStyle, CapStyle
+
+
+@_docstring.interpd
+@_api.define_aliases({
+ "antialiased": ["aa"],
+ "edgecolor": ["ec"],
+ "facecolor": ["fc"],
+ "linestyle": ["ls"],
+ "linewidth": ["lw"],
+})
+class Patch(artist.Artist):
+ """
+ A patch is a 2D artist with a face color and an edge color.
+
+ If any of *edgecolor*, *facecolor*, *linewidth*, or *antialiased*
+ are *None*, they default to their rc params setting.
+ """
+ zorder = 1
+
+ # Whether to draw an edge by default. Set on a
+ # subclass-by-subclass basis.
+ _edge_default = False
+
+ def __init__(self, *,
+ edgecolor=None,
+ facecolor=None,
+ color=None,
+ linewidth=None,
+ linestyle=None,
+ antialiased=None,
+ hatch=None,
+ fill=True,
+ capstyle=None,
+ joinstyle=None,
+ **kwargs):
+ """
+ The following kwarg properties are supported
+
+ %(Patch:kwdoc)s
+ """
+ super().__init__()
+
+ if linestyle is None:
+ linestyle = "solid"
+ if capstyle is None:
+ capstyle = CapStyle.butt
+ if joinstyle is None:
+ joinstyle = JoinStyle.miter
+
+ self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color'])
+ self._hatch_linewidth = mpl.rcParams['hatch.linewidth']
+ self._fill = bool(fill) # needed for set_facecolor call
+ if color is not None:
+ if edgecolor is not None or facecolor is not None:
+ _api.warn_external(
+ "Setting the 'color' property will override "
+ "the edgecolor or facecolor properties.")
+ self.set_color(color)
+ else:
+ self.set_edgecolor(edgecolor)
+ self.set_facecolor(facecolor)
+
+ self._linewidth = 0
+ self._unscaled_dash_pattern = (0, None) # offset, dash
+ self._dash_pattern = (0, None) # offset, dash (scaled by linewidth)
+
+ self.set_linestyle(linestyle)
+ self.set_linewidth(linewidth)
+ self.set_antialiased(antialiased)
+ self.set_hatch(hatch)
+ self.set_capstyle(capstyle)
+ self.set_joinstyle(joinstyle)
+
+ if len(kwargs):
+ self._internal_update(kwargs)
+
+ def get_verts(self):
+ """
+ Return a copy of the vertices used in this patch.
+
+ If the patch contains Bézier curves, the curves will be interpolated by
+ line segments. To access the curves as curves, use `get_path`.
+ """
+ trans = self.get_transform()
+ path = self.get_path()
+ polygons = path.to_polygons(trans)
+ if len(polygons):
+ return polygons[0]
+ return []
+
+ def _process_radius(self, radius):
+ if radius is not None:
+ return radius
+ if isinstance(self._picker, Number):
+ _radius = self._picker
+ else:
+ if self.get_edgecolor()[3] == 0:
+ _radius = 0
+ else:
+ _radius = self.get_linewidth()
+ return _radius
+
+ def contains(self, mouseevent, radius=None):
+ """
+ Test whether the mouse event occurred in the patch.
+
+ Parameters
+ ----------
+ mouseevent : `~matplotlib.backend_bases.MouseEvent`
+ Where the user clicked.
+
+ radius : float, optional
+ Additional margin on the patch in target coordinates of
+ `.Patch.get_transform`. See `.Path.contains_point` for further
+ details.
+
+ If `None`, the default value depends on the state of the object:
+
+ - If `.Artist.get_picker` is a number, the default
+ is that value. This is so that picking works as expected.
+ - Otherwise if the edge color has a non-zero alpha, the default
+ is half of the linewidth. This is so that all the colored
+ pixels are "in" the patch.
+ - Finally, if the edge has 0 alpha, the default is 0. This is
+ so that patches without a stroked edge do not have points
+ outside of the filled region report as "in" due to an
+ invisible edge.
+
+
+ Returns
+ -------
+ (bool, empty dict)
+ """
+ if self._different_canvas(mouseevent):
+ return False, {}
+ radius = self._process_radius(radius)
+ codes = self.get_path().codes
+ if codes is not None:
+ vertices = self.get_path().vertices
+ # if the current path is concatenated by multiple sub paths.
+ # get the indexes of the starting code(MOVETO) of all sub paths
+ idxs, = np.where(codes == Path.MOVETO)
+ # Don't split before the first MOVETO.
+ idxs = idxs[1:]
+ subpaths = map(
+ Path, np.split(vertices, idxs), np.split(codes, idxs))
+ else:
+ subpaths = [self.get_path()]
+ inside = any(
+ subpath.contains_point(
+ (mouseevent.x, mouseevent.y), self.get_transform(), radius)
+ for subpath in subpaths)
+ return inside, {}
+
+ def contains_point(self, point, radius=None):
+ """
+ Return whether the given point is inside the patch.
+
+ Parameters
+ ----------
+ point : (float, float)
+ The point (x, y) to check, in target coordinates of
+ ``.Patch.get_transform()``. These are display coordinates for patches
+ that are added to a figure or Axes.
+ radius : float, optional
+ Additional margin on the patch in target coordinates of
+ `.Patch.get_transform`. See `.Path.contains_point` for further
+ details.
+
+ If `None`, the default value depends on the state of the object:
+
+ - If `.Artist.get_picker` is a number, the default
+ is that value. This is so that picking works as expected.
+ - Otherwise if the edge color has a non-zero alpha, the default
+ is half of the linewidth. This is so that all the colored
+ pixels are "in" the patch.
+ - Finally, if the edge has 0 alpha, the default is 0. This is
+ so that patches without a stroked edge do not have points
+ outside of the filled region report as "in" due to an
+ invisible edge.
+
+ Returns
+ -------
+ bool
+
+ Notes
+ -----
+ The proper use of this method depends on the transform of the patch.
+ Isolated patches do not have a transform. In this case, the patch
+ creation coordinates and the point coordinates match. The following
+ example checks that the center of a circle is within the circle
+
+ >>> center = 0, 0
+ >>> c = Circle(center, radius=1)
+ >>> c.contains_point(center)
+ True
+
+ The convention of checking against the transformed patch stems from
+ the fact that this method is predominantly used to check if display
+ coordinates (e.g. from mouse events) are within the patch. If you want
+ to do the above check with data coordinates, you have to properly
+ transform them first:
+
+ >>> center = 0, 0
+ >>> c = Circle(center, radius=3)
+ >>> plt.gca().add_patch(c)
+ >>> transformed_interior_point = c.get_data_transform().transform((0, 2))
+ >>> c.contains_point(transformed_interior_point)
+ True
+
+ """
+ radius = self._process_radius(radius)
+ return self.get_path().contains_point(point,
+ self.get_transform(),
+ radius)
+
+ def contains_points(self, points, radius=None):
+ """
+ Return whether the given points are inside the patch.
+
+ Parameters
+ ----------
+ points : (N, 2) array
+ The points to check, in target coordinates of
+ ``self.get_transform()``. These are display coordinates for patches
+ that are added to a figure or Axes. Columns contain x and y values.
+ radius : float, optional
+ Additional margin on the patch in target coordinates of
+ `.Patch.get_transform`. See `.Path.contains_point` for further
+ details.
+
+ If `None`, the default value depends on the state of the object:
+
+ - If `.Artist.get_picker` is a number, the default
+ is that value. This is so that picking works as expected.
+ - Otherwise if the edge color has a non-zero alpha, the default
+ is half of the linewidth. This is so that all the colored
+ pixels are "in" the patch.
+ - Finally, if the edge has 0 alpha, the default is 0. This is
+ so that patches without a stroked edge do not have points
+ outside of the filled region report as "in" due to an
+ invisible edge.
+
+ Returns
+ -------
+ length-N bool array
+
+ Notes
+ -----
+ The proper use of this method depends on the transform of the patch.
+ See the notes on `.Patch.contains_point`.
+ """
+ radius = self._process_radius(radius)
+ return self.get_path().contains_points(points,
+ self.get_transform(),
+ radius)
+
+ def update_from(self, other):
+ # docstring inherited.
+ super().update_from(other)
+ # For some properties we don't need or don't want to go through the
+ # getters/setters, so we just copy them directly.
+ self._edgecolor = other._edgecolor
+ self._facecolor = other._facecolor
+ self._original_edgecolor = other._original_edgecolor
+ self._original_facecolor = other._original_facecolor
+ self._fill = other._fill
+ self._hatch = other._hatch
+ self._hatch_color = other._hatch_color
+ self._unscaled_dash_pattern = other._unscaled_dash_pattern
+ self.set_linewidth(other._linewidth) # also sets scaled dashes
+ self.set_transform(other.get_data_transform())
+ # If the transform of other needs further initialization, then it will
+ # be the case for this artist too.
+ self._transformSet = other.is_transform_set()
+
+ def get_extents(self):
+ """
+ Return the `Patch`'s axis-aligned extents as a `~.transforms.Bbox`.
+ """
+ return self.get_path().get_extents(self.get_transform())
+
+ def get_transform(self):
+ """Return the `~.transforms.Transform` applied to the `Patch`."""
+ return self.get_patch_transform() + artist.Artist.get_transform(self)
+
+ def get_data_transform(self):
+ """
+ Return the `~.transforms.Transform` mapping data coordinates to
+ physical coordinates.
+ """
+ return artist.Artist.get_transform(self)
+
+ def get_patch_transform(self):
+ """
+ Return the `~.transforms.Transform` instance mapping patch coordinates
+ to data coordinates.
+
+ For example, one may define a patch of a circle which represents a
+ radius of 5 by providing coordinates for a unit circle, and a
+ transform which scales the coordinates (the patch coordinate) by 5.
+ """
+ return transforms.IdentityTransform()
+
+ def get_antialiased(self):
+ """Return whether antialiasing is used for drawing."""
+ return self._antialiased
+
+ def get_edgecolor(self):
+ """Return the edge color."""
+ return self._edgecolor
+
+ def get_facecolor(self):
+ """Return the face color."""
+ return self._facecolor
+
+ def get_linewidth(self):
+ """Return the line width in points."""
+ return self._linewidth
+
+ def get_linestyle(self):
+ """Return the linestyle."""
+ return self._linestyle
+
+ def set_antialiased(self, aa):
+ """
+ Set whether to use antialiased rendering.
+
+ Parameters
+ ----------
+ aa : bool or None
+ """
+ if aa is None:
+ aa = mpl.rcParams['patch.antialiased']
+ self._antialiased = aa
+ self.stale = True
+
+ def _set_edgecolor(self, color):
+ set_hatch_color = True
+ if color is None:
+ if (mpl.rcParams['patch.force_edgecolor'] or
+ not self._fill or self._edge_default):
+ color = mpl.rcParams['patch.edgecolor']
+ else:
+ color = 'none'
+ set_hatch_color = False
+
+ self._edgecolor = colors.to_rgba(color, self._alpha)
+ if set_hatch_color:
+ self._hatch_color = self._edgecolor
+ self.stale = True
+
+ def set_edgecolor(self, color):
+ """
+ Set the patch edge color.
+
+ Parameters
+ ----------
+ color : :mpltype:`color` or None
+ """
+ self._original_edgecolor = color
+ self._set_edgecolor(color)
+
+ def _set_facecolor(self, color):
+ if color is None:
+ color = mpl.rcParams['patch.facecolor']
+ alpha = self._alpha if self._fill else 0
+ self._facecolor = colors.to_rgba(color, alpha)
+ self.stale = True
+
+ def set_facecolor(self, color):
+ """
+ Set the patch face color.
+
+ Parameters
+ ----------
+ color : :mpltype:`color` or None
+ """
+ self._original_facecolor = color
+ self._set_facecolor(color)
+
+ def set_color(self, c):
+ """
+ Set both the edgecolor and the facecolor.
+
+ Parameters
+ ----------
+ c : :mpltype:`color`
+
+ See Also
+ --------
+ Patch.set_facecolor, Patch.set_edgecolor
+ For setting the edge or face color individually.
+ """
+ self.set_facecolor(c)
+ self.set_edgecolor(c)
+
+ def set_alpha(self, alpha):
+ # docstring inherited
+ super().set_alpha(alpha)
+ self._set_facecolor(self._original_facecolor)
+ self._set_edgecolor(self._original_edgecolor)
+ # stale is already True
+
+ def set_linewidth(self, w):
+ """
+ Set the patch linewidth in points.
+
+ Parameters
+ ----------
+ w : float or None
+ """
+ if w is None:
+ w = mpl.rcParams['patch.linewidth']
+ self._linewidth = float(w)
+ self._dash_pattern = mlines._scale_dashes(
+ *self._unscaled_dash_pattern, w)
+ self.stale = True
+
+ def set_linestyle(self, ls):
+ """
+ Set the patch linestyle.
+
+ ========================================== =================
+ linestyle description
+ ========================================== =================
+ ``'-'`` or ``'solid'`` solid line
+ ``'--'`` or ``'dashed'`` dashed line
+ ``'-.'`` or ``'dashdot'`` dash-dotted line
+ ``':'`` or ``'dotted'`` dotted line
+ ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing
+ ========================================== =================
+
+ Alternatively a dash tuple of the following form can be provided::
+
+ (offset, onoffseq)
+
+ where ``onoffseq`` is an even length tuple of on and off ink in points.
+
+ Parameters
+ ----------
+ ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
+ The line style.
+ """
+ if ls is None:
+ ls = "solid"
+ if ls in [' ', '', 'none']:
+ ls = 'None'
+ self._linestyle = ls
+ self._unscaled_dash_pattern = mlines._get_dash_pattern(ls)
+ self._dash_pattern = mlines._scale_dashes(
+ *self._unscaled_dash_pattern, self._linewidth)
+ self.stale = True
+
+ def set_fill(self, b):
+ """
+ Set whether to fill the patch.
+
+ Parameters
+ ----------
+ b : bool
+ """
+ self._fill = bool(b)
+ self._set_facecolor(self._original_facecolor)
+ self._set_edgecolor(self._original_edgecolor)
+ self.stale = True
+
+ def get_fill(self):
+ """Return whether the patch is filled."""
+ return self._fill
+
+ # Make fill a property so as to preserve the long-standing
+ # but somewhat inconsistent behavior in which fill was an
+ # attribute.
+ fill = property(get_fill, set_fill)
+
+ @_docstring.interpd
+ def set_capstyle(self, s):
+ """
+ Set the `.CapStyle`.
+
+ The default capstyle is 'round' for `.FancyArrowPatch` and 'butt' for
+ all other patches.
+
+ Parameters
+ ----------
+ s : `.CapStyle` or %(CapStyle)s
+ """
+ cs = CapStyle(s)
+ self._capstyle = cs
+ self.stale = True
+
+ def get_capstyle(self):
+ """Return the capstyle."""
+ return self._capstyle.name
+
+ @_docstring.interpd
+ def set_joinstyle(self, s):
+ """
+ Set the `.JoinStyle`.
+
+ The default joinstyle is 'round' for `.FancyArrowPatch` and 'miter' for
+ all other patches.
+
+ Parameters
+ ----------
+ s : `.JoinStyle` or %(JoinStyle)s
+ """
+ js = JoinStyle(s)
+ self._joinstyle = js
+ self.stale = True
+
+ def get_joinstyle(self):
+ """Return the joinstyle."""
+ return self._joinstyle.name
+
+ def set_hatch(self, hatch):
+ r"""
+ Set the hatching pattern.
+
+ *hatch* can be one of::
+
+ / - diagonal hatching
+ \ - back diagonal
+ | - vertical
+ - - horizontal
+ + - crossed
+ x - crossed diagonal
+ o - small circle
+ O - large circle
+ . - dots
+ * - stars
+
+ Letters can be combined, in which case all the specified
+ hatchings are done. If same letter repeats, it increases the
+ density of hatching of that pattern.
+
+ Parameters
+ ----------
+ hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'}
+ """
+ # Use validate_hatch(list) after deprecation.
+ mhatch._validate_hatch_pattern(hatch)
+ self._hatch = hatch
+ self.stale = True
+
+ def get_hatch(self):
+ """Return the hatching pattern."""
+ return self._hatch
+
+ def set_hatch_linewidth(self, lw):
+ """Set the hatch linewidth."""
+ self._hatch_linewidth = lw
+
+ def get_hatch_linewidth(self):
+ """Return the hatch linewidth."""
+ return self._hatch_linewidth
+
+ def _draw_paths_with_artist_properties(
+ self, renderer, draw_path_args_list):
+ """
+ ``draw()`` helper factored out for sharing with `FancyArrowPatch`.
+
+ Configure *renderer* and the associated graphics context *gc*
+ from the artist properties, then repeatedly call
+ ``renderer.draw_path(gc, *draw_path_args)`` for each tuple
+ *draw_path_args* in *draw_path_args_list*.
+ """
+
+ renderer.open_group('patch', self.get_gid())
+ gc = renderer.new_gc()
+
+ gc.set_foreground(self._edgecolor, isRGBA=True)
+
+ lw = self._linewidth
+ if self._edgecolor[3] == 0 or self._linestyle == 'None':
+ lw = 0
+ gc.set_linewidth(lw)
+ gc.set_dashes(*self._dash_pattern)
+ gc.set_capstyle(self._capstyle)
+ gc.set_joinstyle(self._joinstyle)
+
+ gc.set_antialiased(self._antialiased)
+ self._set_gc_clip(gc)
+ gc.set_url(self._url)
+ gc.set_snap(self.get_snap())
+
+ gc.set_alpha(self._alpha)
+
+ if self._hatch:
+ gc.set_hatch(self._hatch)
+ gc.set_hatch_color(self._hatch_color)
+ gc.set_hatch_linewidth(self._hatch_linewidth)
+
+ if self.get_sketch_params() is not None:
+ gc.set_sketch_params(*self.get_sketch_params())
+
+ if self.get_path_effects():
+ from matplotlib.patheffects import PathEffectRenderer
+ renderer = PathEffectRenderer(self.get_path_effects(), renderer)
+
+ for draw_path_args in draw_path_args_list:
+ renderer.draw_path(gc, *draw_path_args)
+
+ gc.restore()
+ renderer.close_group('patch')
+ self.stale = False
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ # docstring inherited
+ if not self.get_visible():
+ return
+ path = self.get_path()
+ transform = self.get_transform()
+ tpath = transform.transform_path_non_affine(path)
+ affine = transform.get_affine()
+ self._draw_paths_with_artist_properties(
+ renderer,
+ [(tpath, affine,
+ # Work around a bug in the PDF and SVG renderers, which
+ # do not draw the hatches if the facecolor is fully
+ # transparent, but do if it is None.
+ self._facecolor if self._facecolor[3] else None)])
+
+ def get_path(self):
+ """Return the path of this patch."""
+ raise NotImplementedError('Derived must override')
+
+ def get_window_extent(self, renderer=None):
+ return self.get_path().get_extents(self.get_transform())
+
+ def _convert_xy_units(self, xy):
+ """Convert x and y units for a tuple (x, y)."""
+ x = self.convert_xunits(xy[0])
+ y = self.convert_yunits(xy[1])
+ return x, y
+
+
+class Shadow(Patch):
+ def __str__(self):
+ return f"Shadow({self.patch})"
+
+ @_docstring.interpd
+ def __init__(self, patch, ox, oy, *, shade=0.7, **kwargs):
+ """
+ Create a shadow of the given *patch*.
+
+ By default, the shadow will have the same face color as the *patch*,
+ but darkened. The darkness can be controlled by *shade*.
+
+ Parameters
+ ----------
+ patch : `~matplotlib.patches.Patch`
+ The patch to create the shadow for.
+ ox, oy : float
+ The shift of the shadow in data coordinates, scaled by a factor
+ of dpi/72.
+ shade : float, default: 0.7
+ How the darkness of the shadow relates to the original color. If 1, the
+ shadow is black, if 0, the shadow has the same color as the *patch*.
+
+ .. versionadded:: 3.8
+
+ **kwargs
+ Properties of the shadow patch. Supported keys are:
+
+ %(Patch:kwdoc)s
+ """
+ super().__init__()
+ self.patch = patch
+ self._ox, self._oy = ox, oy
+ self._shadow_transform = transforms.Affine2D()
+
+ self.update_from(self.patch)
+ if not 0 <= shade <= 1:
+ raise ValueError("shade must be between 0 and 1.")
+ color = (1 - shade) * np.asarray(colors.to_rgb(self.patch.get_facecolor()))
+ self.update({'facecolor': color, 'edgecolor': color, 'alpha': 0.5,
+ # Place shadow patch directly behind the inherited patch.
+ 'zorder': np.nextafter(self.patch.zorder, -np.inf),
+ **kwargs})
+
+ def _update_transform(self, renderer):
+ ox = renderer.points_to_pixels(self._ox)
+ oy = renderer.points_to_pixels(self._oy)
+ self._shadow_transform.clear().translate(ox, oy)
+
+ def get_path(self):
+ return self.patch.get_path()
+
+ def get_patch_transform(self):
+ return self.patch.get_patch_transform() + self._shadow_transform
+
+ def draw(self, renderer):
+ self._update_transform(renderer)
+ super().draw(renderer)
+
+
+class Rectangle(Patch):
+ """
+ A rectangle defined via an anchor point *xy* and its *width* and *height*.
+
+ The rectangle extends from ``xy[0]`` to ``xy[0] + width`` in x-direction
+ and from ``xy[1]`` to ``xy[1] + height`` in y-direction. ::
+
+ : +------------------+
+ : | |
+ : height |
+ : | |
+ : (xy)---- width -----+
+
+ One may picture *xy* as the bottom left corner, but which corner *xy* is
+ actually depends on the direction of the axis and the sign of *width*
+ and *height*; e.g. *xy* would be the bottom right corner if the x-axis
+ was inverted or if *width* was negative.
+ """
+
+ def __str__(self):
+ pars = self._x0, self._y0, self._width, self._height, self.angle
+ fmt = "Rectangle(xy=(%g, %g), width=%g, height=%g, angle=%g)"
+ return fmt % pars
+
+ @_docstring.interpd
+ def __init__(self, xy, width, height, *,
+ angle=0.0, rotation_point='xy', **kwargs):
+ """
+ Parameters
+ ----------
+ xy : (float, float)
+ The anchor point.
+ width : float
+ Rectangle width.
+ height : float
+ Rectangle height.
+ angle : float, default: 0
+ Rotation in degrees anti-clockwise about the rotation point.
+ rotation_point : {'xy', 'center', (number, number)}, default: 'xy'
+ If ``'xy'``, rotate around the anchor point. If ``'center'`` rotate
+ around the center. If 2-tuple of number, rotate around this
+ coordinate.
+
+ Other Parameters
+ ----------------
+ **kwargs : `~matplotlib.patches.Patch` properties
+ %(Patch:kwdoc)s
+ """
+ super().__init__(**kwargs)
+ self._x0 = xy[0]
+ self._y0 = xy[1]
+ self._width = width
+ self._height = height
+ self.angle = float(angle)
+ self.rotation_point = rotation_point
+ # Required for RectangleSelector with axes aspect ratio != 1
+ # The patch is defined in data coordinates and when changing the
+ # selector with square modifier and not in data coordinates, we need
+ # to correct for the aspect ratio difference between the data and
+ # display coordinate systems. Its value is typically provide by
+ # Axes._get_aspect_ratio()
+ self._aspect_ratio_correction = 1.0
+ self._convert_units() # Validate the inputs.
+
+ def get_path(self):
+ """Return the vertices of the rectangle."""
+ return Path.unit_rectangle()
+
+ def _convert_units(self):
+ """Convert bounds of the rectangle."""
+ x0 = self.convert_xunits(self._x0)
+ y0 = self.convert_yunits(self._y0)
+ x1 = self.convert_xunits(self._x0 + self._width)
+ y1 = self.convert_yunits(self._y0 + self._height)
+ return x0, y0, x1, y1
+
+ def get_patch_transform(self):
+ # Note: This cannot be called until after this has been added to
+ # an Axes, otherwise unit conversion will fail. This makes it very
+ # important to call the accessor method and not directly access the
+ # transformation member variable.
+ bbox = self.get_bbox()
+ if self.rotation_point == 'center':
+ width, height = bbox.x1 - bbox.x0, bbox.y1 - bbox.y0
+ rotation_point = bbox.x0 + width / 2., bbox.y0 + height / 2.
+ elif self.rotation_point == 'xy':
+ rotation_point = bbox.x0, bbox.y0
+ else:
+ rotation_point = self.rotation_point
+ return transforms.BboxTransformTo(bbox) \
+ + transforms.Affine2D() \
+ .translate(-rotation_point[0], -rotation_point[1]) \
+ .scale(1, self._aspect_ratio_correction) \
+ .rotate_deg(self.angle) \
+ .scale(1, 1 / self._aspect_ratio_correction) \
+ .translate(*rotation_point)
+
+ @property
+ def rotation_point(self):
+ """The rotation point of the patch."""
+ return self._rotation_point
+
+ @rotation_point.setter
+ def rotation_point(self, value):
+ if value in ['center', 'xy'] or (
+ isinstance(value, tuple) and len(value) == 2 and
+ isinstance(value[0], Real) and isinstance(value[1], Real)
+ ):
+ self._rotation_point = value
+ else:
+ raise ValueError("`rotation_point` must be one of "
+ "{'xy', 'center', (number, number)}.")
+
+ def get_x(self):
+ """Return the left coordinate of the rectangle."""
+ return self._x0
+
+ def get_y(self):
+ """Return the bottom coordinate of the rectangle."""
+ return self._y0
+
+ def get_xy(self):
+ """Return the left and bottom coords of the rectangle as a tuple."""
+ return self._x0, self._y0
+
+ def get_corners(self):
+ """
+ Return the corners of the rectangle, moving anti-clockwise from
+ (x0, y0).
+ """
+ return self.get_patch_transform().transform(
+ [(0, 0), (1, 0), (1, 1), (0, 1)])
+
+ def get_center(self):
+ """Return the centre of the rectangle."""
+ return self.get_patch_transform().transform((0.5, 0.5))
+
+ def get_width(self):
+ """Return the width of the rectangle."""
+ return self._width
+
+ def get_height(self):
+ """Return the height of the rectangle."""
+ return self._height
+
+ def get_angle(self):
+ """Get the rotation angle in degrees."""
+ return self.angle
+
+ def set_x(self, x):
+ """Set the left coordinate of the rectangle."""
+ self._x0 = x
+ self.stale = True
+
+ def set_y(self, y):
+ """Set the bottom coordinate of the rectangle."""
+ self._y0 = y
+ self.stale = True
+
+ def set_angle(self, angle):
+ """
+ Set the rotation angle in degrees.
+
+ The rotation is performed anti-clockwise around *xy*.
+ """
+ self.angle = angle
+ self.stale = True
+
+ def set_xy(self, xy):
+ """
+ Set the left and bottom coordinates of the rectangle.
+
+ Parameters
+ ----------
+ xy : (float, float)
+ """
+ self._x0, self._y0 = xy
+ self.stale = True
+
+ def set_width(self, w):
+ """Set the width of the rectangle."""
+ self._width = w
+ self.stale = True
+
+ def set_height(self, h):
+ """Set the height of the rectangle."""
+ self._height = h
+ self.stale = True
+
+ def set_bounds(self, *args):
+ """
+ Set the bounds of the rectangle as *left*, *bottom*, *width*, *height*.
+
+ The values may be passed as separate parameters or as a tuple::
+
+ set_bounds(left, bottom, width, height)
+ set_bounds((left, bottom, width, height))
+
+ .. ACCEPTS: (left, bottom, width, height)
+ """
+ if len(args) == 1:
+ l, b, w, h = args[0]
+ else:
+ l, b, w, h = args
+ self._x0 = l
+ self._y0 = b
+ self._width = w
+ self._height = h
+ self.stale = True
+
+ def get_bbox(self):
+ """Return the `.Bbox`."""
+ return transforms.Bbox.from_extents(*self._convert_units())
+
+ xy = property(get_xy, set_xy)
+
+
+class RegularPolygon(Patch):
+ """A regular polygon patch."""
+
+ def __str__(self):
+ s = "RegularPolygon((%g, %g), %d, radius=%g, orientation=%g)"
+ return s % (self.xy[0], self.xy[1], self.numvertices, self.radius,
+ self.orientation)
+
+ @_docstring.interpd
+ def __init__(self, xy, numVertices, *,
+ radius=5, orientation=0, **kwargs):
+ """
+ Parameters
+ ----------
+ xy : (float, float)
+ The center position.
+
+ numVertices : int
+ The number of vertices.
+
+ radius : float
+ The distance from the center to each of the vertices.
+
+ orientation : float
+ The polygon rotation angle (in radians).
+
+ **kwargs
+ `Patch` properties:
+
+ %(Patch:kwdoc)s
+ """
+ self.xy = xy
+ self.numvertices = numVertices
+ self.orientation = orientation
+ self.radius = radius
+ self._path = Path.unit_regular_polygon(numVertices)
+ self._patch_transform = transforms.Affine2D()
+ super().__init__(**kwargs)
+
+ def get_path(self):
+ return self._path
+
+ def get_patch_transform(self):
+ return self._patch_transform.clear() \
+ .scale(self.radius) \
+ .rotate(self.orientation) \
+ .translate(*self.xy)
+
+
+class PathPatch(Patch):
+ """A general polycurve path patch."""
+
+ _edge_default = True
+
+ def __str__(self):
+ s = "PathPatch%d((%g, %g) ...)"
+ return s % (len(self._path.vertices), *tuple(self._path.vertices[0]))
+
+ @_docstring.interpd
+ def __init__(self, path, **kwargs):
+ """
+ *path* is a `.Path` object.
+
+ Valid keyword arguments are:
+
+ %(Patch:kwdoc)s
+ """
+ super().__init__(**kwargs)
+ self._path = path
+
+ def get_path(self):
+ return self._path
+
+ def set_path(self, path):
+ self._path = path
+
+
+class StepPatch(PathPatch):
+ """
+ A path patch describing a stepwise constant function.
+
+ By default, the path is not closed and starts and stops at
+ baseline value.
+ """
+
+ _edge_default = False
+
+ @_docstring.interpd
+ def __init__(self, values, edges, *,
+ orientation='vertical', baseline=0, **kwargs):
+ """
+ Parameters
+ ----------
+ values : array-like
+ The step heights.
+
+ edges : array-like
+ The edge positions, with ``len(edges) == len(vals) + 1``,
+ between which the curve takes on vals values.
+
+ orientation : {'vertical', 'horizontal'}, default: 'vertical'
+ The direction of the steps. Vertical means that *values* are
+ along the y-axis, and edges are along the x-axis.
+
+ baseline : float, array-like or None, default: 0
+ The bottom value of the bounding edges or when
+ ``fill=True``, position of lower edge. If *fill* is
+ True or an array is passed to *baseline*, a closed
+ path is drawn.
+
+ **kwargs
+ `Patch` properties:
+
+ %(Patch:kwdoc)s
+ """
+ self.orientation = orientation
+ self._edges = np.asarray(edges)
+ self._values = np.asarray(values)
+ self._baseline = np.asarray(baseline) if baseline is not None else None
+ self._update_path()
+ super().__init__(self._path, **kwargs)
+
+ def _update_path(self):
+ if np.isnan(np.sum(self._edges)):
+ raise ValueError('Nan values in "edges" are disallowed')
+ if self._edges.size - 1 != self._values.size:
+ raise ValueError('Size mismatch between "values" and "edges". '
+ "Expected `len(values) + 1 == len(edges)`, but "
+ f"`len(values) = {self._values.size}` and "
+ f"`len(edges) = {self._edges.size}`.")
+ # Initializing with empty arrays allows supporting empty stairs.
+ verts, codes = [np.empty((0, 2))], [np.empty(0, dtype=Path.code_type)]
+
+ _nan_mask = np.isnan(self._values)
+ if self._baseline is not None:
+ _nan_mask |= np.isnan(self._baseline)
+ for idx0, idx1 in cbook.contiguous_regions(~_nan_mask):
+ x = np.repeat(self._edges[idx0:idx1+1], 2)
+ y = np.repeat(self._values[idx0:idx1], 2)
+ if self._baseline is None:
+ y = np.concatenate([y[:1], y, y[-1:]])
+ elif self._baseline.ndim == 0: # single baseline value
+ y = np.concatenate([[self._baseline], y, [self._baseline]])
+ elif self._baseline.ndim == 1: # baseline array
+ base = np.repeat(self._baseline[idx0:idx1], 2)[::-1]
+ x = np.concatenate([x, x[::-1]])
+ y = np.concatenate([base[-1:], y, base[:1],
+ base[:1], base, base[-1:]])
+ else: # no baseline
+ raise ValueError('Invalid `baseline` specified')
+ if self.orientation == 'vertical':
+ xy = np.column_stack([x, y])
+ else:
+ xy = np.column_stack([y, x])
+ verts.append(xy)
+ codes.append([Path.MOVETO] + [Path.LINETO]*(len(xy)-1))
+ self._path = Path(np.concatenate(verts), np.concatenate(codes))
+
+ def get_data(self):
+ """Get `.StepPatch` values, edges and baseline as namedtuple."""
+ StairData = namedtuple('StairData', 'values edges baseline')
+ return StairData(self._values, self._edges, self._baseline)
+
+ def set_data(self, values=None, edges=None, baseline=None):
+ """
+ Set `.StepPatch` values, edges and baseline.
+
+ Parameters
+ ----------
+ values : 1D array-like or None
+ Will not update values, if passing None
+ edges : 1D array-like, optional
+ baseline : float, 1D array-like or None
+ """
+ if values is None and edges is None and baseline is None:
+ raise ValueError("Must set *values*, *edges* or *baseline*.")
+ if values is not None:
+ self._values = np.asarray(values)
+ if edges is not None:
+ self._edges = np.asarray(edges)
+ if baseline is not None:
+ self._baseline = np.asarray(baseline)
+ self._update_path()
+ self.stale = True
+
+
+class Polygon(Patch):
+ """A general polygon patch."""
+
+ def __str__(self):
+ if len(self._path.vertices):
+ s = "Polygon%d((%g, %g) ...)"
+ return s % (len(self._path.vertices), *self._path.vertices[0])
+ else:
+ return "Polygon0()"
+
+ @_docstring.interpd
+ def __init__(self, xy, *, closed=True, **kwargs):
+ """
+ Parameters
+ ----------
+ xy : (N, 2) array
+
+ closed : bool, default: True
+ Whether the polygon is closed (i.e., has identical start and end
+ points).
+
+ **kwargs
+ %(Patch:kwdoc)s
+ """
+ super().__init__(**kwargs)
+ self._closed = closed
+ self.set_xy(xy)
+
+ def get_path(self):
+ """Get the `.Path` of the polygon."""
+ return self._path
+
+ def get_closed(self):
+ """Return whether the polygon is closed."""
+ return self._closed
+
+ def set_closed(self, closed):
+ """
+ Set whether the polygon is closed.
+
+ Parameters
+ ----------
+ closed : bool
+ True if the polygon is closed
+ """
+ if self._closed == bool(closed):
+ return
+ self._closed = bool(closed)
+ self.set_xy(self.get_xy())
+ self.stale = True
+
+ def get_xy(self):
+ """
+ Get the vertices of the path.
+
+ Returns
+ -------
+ (N, 2) array
+ The coordinates of the vertices.
+ """
+ return self._path.vertices
+
+ def set_xy(self, xy):
+ """
+ Set the vertices of the polygon.
+
+ Parameters
+ ----------
+ xy : (N, 2) array-like
+ The coordinates of the vertices.
+
+ Notes
+ -----
+ Unlike `.Path`, we do not ignore the last input vertex. If the
+ polygon is meant to be closed, and the last point of the polygon is not
+ equal to the first, we assume that the user has not explicitly passed a
+ ``CLOSEPOLY`` vertex, and add it ourselves.
+ """
+ xy = np.asarray(xy)
+ nverts, _ = xy.shape
+ if self._closed:
+ # if the first and last vertex are the "same", then we assume that
+ # the user explicitly passed the CLOSEPOLY vertex. Otherwise, we
+ # have to append one since the last vertex will be "ignored" by
+ # Path
+ if nverts == 1 or nverts > 1 and (xy[0] != xy[-1]).any():
+ xy = np.concatenate([xy, [xy[0]]])
+ else:
+ # if we aren't closed, and the last vertex matches the first, then
+ # we assume we have an unnecessary CLOSEPOLY vertex and remove it
+ if nverts > 2 and (xy[0] == xy[-1]).all():
+ xy = xy[:-1]
+ self._path = Path(xy, closed=self._closed)
+ self.stale = True
+
+ xy = property(get_xy, set_xy,
+ doc='The vertices of the path as a (N, 2) array.')
+
+
+class Wedge(Patch):
+ """Wedge shaped patch."""
+
+ def __str__(self):
+ pars = (self.center[0], self.center[1], self.r,
+ self.theta1, self.theta2, self.width)
+ fmt = "Wedge(center=(%g, %g), r=%g, theta1=%g, theta2=%g, width=%s)"
+ return fmt % pars
+
+ @_docstring.interpd
+ def __init__(self, center, r, theta1, theta2, *, width=None, **kwargs):
+ """
+ A wedge centered at *x*, *y* center with radius *r* that
+ sweeps *theta1* to *theta2* (in degrees). If *width* is given,
+ then a partial wedge is drawn from inner radius *r* - *width*
+ to outer radius *r*.
+
+ Valid keyword arguments are:
+
+ %(Patch:kwdoc)s
+ """
+ super().__init__(**kwargs)
+ self.center = center
+ self.r, self.width = r, width
+ self.theta1, self.theta2 = theta1, theta2
+ self._patch_transform = transforms.IdentityTransform()
+ self._recompute_path()
+
+ def _recompute_path(self):
+ # Inner and outer rings are connected unless the annulus is complete
+ if abs((self.theta2 - self.theta1) - 360) <= 1e-12:
+ theta1, theta2 = 0, 360
+ connector = Path.MOVETO
+ else:
+ theta1, theta2 = self.theta1, self.theta2
+ connector = Path.LINETO
+
+ # Form the outer ring
+ arc = Path.arc(theta1, theta2)
+
+ if self.width is not None:
+ # Partial annulus needs to draw the outer ring
+ # followed by a reversed and scaled inner ring
+ v1 = arc.vertices
+ v2 = arc.vertices[::-1] * (self.r - self.width) / self.r
+ v = np.concatenate([v1, v2, [(0, 0)]])
+ c = [*arc.codes, connector, *arc.codes[1:], Path.CLOSEPOLY]
+ else:
+ # Wedge doesn't need an inner ring
+ v = np.concatenate([arc.vertices, [(0, 0), (0, 0)]])
+ c = [*arc.codes, connector, Path.CLOSEPOLY]
+
+ # Shift and scale the wedge to the final location.
+ self._path = Path(v * self.r + self.center, c)
+
+ def set_center(self, center):
+ self._path = None
+ self.center = center
+ self.stale = True
+
+ def set_radius(self, radius):
+ self._path = None
+ self.r = radius
+ self.stale = True
+
+ def set_theta1(self, theta1):
+ self._path = None
+ self.theta1 = theta1
+ self.stale = True
+
+ def set_theta2(self, theta2):
+ self._path = None
+ self.theta2 = theta2
+ self.stale = True
+
+ def set_width(self, width):
+ self._path = None
+ self.width = width
+ self.stale = True
+
+ def get_path(self):
+ if self._path is None:
+ self._recompute_path()
+ return self._path
+
+
+# COVERAGE NOTE: Not used internally or from examples
+class Arrow(Patch):
+ """An arrow patch."""
+
+ def __str__(self):
+ return "Arrow()"
+
+ _path = Path._create_closed([
+ [0.0, 0.1], [0.0, -0.1], [0.8, -0.1], [0.8, -0.3], [1.0, 0.0],
+ [0.8, 0.3], [0.8, 0.1]])
+
+ @_docstring.interpd
+ def __init__(self, x, y, dx, dy, *, width=1.0, **kwargs):
+ """
+ Draws an arrow from (*x*, *y*) to (*x* + *dx*, *y* + *dy*).
+ The width of the arrow is scaled by *width*.
+
+ Parameters
+ ----------
+ x : float
+ x coordinate of the arrow tail.
+ y : float
+ y coordinate of the arrow tail.
+ dx : float
+ Arrow length in the x direction.
+ dy : float
+ Arrow length in the y direction.
+ width : float, default: 1
+ Scale factor for the width of the arrow. With a default value of 1,
+ the tail width is 0.2 and head width is 0.6.
+ **kwargs
+ Keyword arguments control the `Patch` properties:
+
+ %(Patch:kwdoc)s
+
+ See Also
+ --------
+ FancyArrow
+ Patch that allows independent control of the head and tail
+ properties.
+ """
+ super().__init__(**kwargs)
+ self.set_data(x, y, dx, dy, width)
+
+ def get_path(self):
+ return self._path
+
+ def get_patch_transform(self):
+ return self._patch_transform
+
+ def set_data(self, x=None, y=None, dx=None, dy=None, width=None):
+ """
+ Set `.Arrow` x, y, dx, dy and width.
+ Values left as None will not be updated.
+
+ Parameters
+ ----------
+ x, y : float or None, default: None
+ The x and y coordinates of the arrow base.
+
+ dx, dy : float or None, default: None
+ The length of the arrow along x and y direction.
+
+ width : float or None, default: None
+ Width of full arrow tail.
+ """
+ if x is not None:
+ self._x = x
+ if y is not None:
+ self._y = y
+ if dx is not None:
+ self._dx = dx
+ if dy is not None:
+ self._dy = dy
+ if width is not None:
+ self._width = width
+ self._patch_transform = (
+ transforms.Affine2D()
+ .scale(np.hypot(self._dx, self._dy), self._width)
+ .rotate(np.arctan2(self._dy, self._dx))
+ .translate(self._x, self._y)
+ .frozen())
+
+
+class FancyArrow(Polygon):
+ """
+ Like Arrow, but lets you set head width and head height independently.
+ """
+
+ _edge_default = True
+
+ def __str__(self):
+ return "FancyArrow()"
+
+ @_docstring.interpd
+ def __init__(self, x, y, dx, dy, *,
+ width=0.001, length_includes_head=False, head_width=None,
+ head_length=None, shape='full', overhang=0,
+ head_starts_at_zero=False, **kwargs):
+ """
+ Parameters
+ ----------
+ x, y : float
+ The x and y coordinates of the arrow base.
+
+ dx, dy : float
+ The length of the arrow along x and y direction.
+
+ width : float, default: 0.001
+ Width of full arrow tail.
+
+ length_includes_head : bool, default: False
+ True if head is to be counted in calculating the length.
+
+ head_width : float or None, default: 3*width
+ Total width of the full arrow head.
+
+ head_length : float or None, default: 1.5*head_width
+ Length of arrow head.
+
+ shape : {'full', 'left', 'right'}, default: 'full'
+ Draw the left-half, right-half, or full arrow.
+
+ overhang : float, default: 0
+ Fraction that the arrow is swept back (0 overhang means
+ triangular shape). Can be negative or greater than one.
+
+ head_starts_at_zero : bool, default: False
+ If True, the head starts being drawn at coordinate 0
+ instead of ending at coordinate 0.
+
+ **kwargs
+ `.Patch` properties:
+
+ %(Patch:kwdoc)s
+ """
+ self._x = x
+ self._y = y
+ self._dx = dx
+ self._dy = dy
+ self._width = width
+ self._length_includes_head = length_includes_head
+ self._head_width = head_width
+ self._head_length = head_length
+ self._shape = shape
+ self._overhang = overhang
+ self._head_starts_at_zero = head_starts_at_zero
+ self._make_verts()
+ super().__init__(self.verts, closed=True, **kwargs)
+
+ def set_data(self, *, x=None, y=None, dx=None, dy=None, width=None,
+ head_width=None, head_length=None):
+ """
+ Set `.FancyArrow` x, y, dx, dy, width, head_with, and head_length.
+ Values left as None will not be updated.
+
+ Parameters
+ ----------
+ x, y : float or None, default: None
+ The x and y coordinates of the arrow base.
+
+ dx, dy : float or None, default: None
+ The length of the arrow along x and y direction.
+
+ width : float or None, default: None
+ Width of full arrow tail.
+
+ head_width : float or None, default: None
+ Total width of the full arrow head.
+
+ head_length : float or None, default: None
+ Length of arrow head.
+ """
+ if x is not None:
+ self._x = x
+ if y is not None:
+ self._y = y
+ if dx is not None:
+ self._dx = dx
+ if dy is not None:
+ self._dy = dy
+ if width is not None:
+ self._width = width
+ if head_width is not None:
+ self._head_width = head_width
+ if head_length is not None:
+ self._head_length = head_length
+ self._make_verts()
+ self.set_xy(self.verts)
+
+ def _make_verts(self):
+ if self._head_width is None:
+ head_width = 3 * self._width
+ else:
+ head_width = self._head_width
+ if self._head_length is None:
+ head_length = 1.5 * head_width
+ else:
+ head_length = self._head_length
+
+ distance = np.hypot(self._dx, self._dy)
+
+ if self._length_includes_head:
+ length = distance
+ else:
+ length = distance + head_length
+ if np.size(length) == 0:
+ self.verts = np.empty([0, 2]) # display nothing if empty
+ else:
+ # start by drawing horizontal arrow, point at (0, 0)
+ hw, hl = head_width, head_length
+ hs, lw = self._overhang, self._width
+ left_half_arrow = np.array([
+ [0.0, 0.0], # tip
+ [-hl, -hw / 2], # leftmost
+ [-hl * (1 - hs), -lw / 2], # meets stem
+ [-length, -lw / 2], # bottom left
+ [-length, 0],
+ ])
+ # if we're not including the head, shift up by head length
+ if not self._length_includes_head:
+ left_half_arrow += [head_length, 0]
+ # if the head starts at 0, shift up by another head length
+ if self._head_starts_at_zero:
+ left_half_arrow += [head_length / 2, 0]
+ # figure out the shape, and complete accordingly
+ if self._shape == 'left':
+ coords = left_half_arrow
+ else:
+ right_half_arrow = left_half_arrow * [1, -1]
+ if self._shape == 'right':
+ coords = right_half_arrow
+ elif self._shape == 'full':
+ # The half-arrows contain the midpoint of the stem,
+ # which we can omit from the full arrow. Including it
+ # twice caused a problem with xpdf.
+ coords = np.concatenate([left_half_arrow[:-1],
+ right_half_arrow[-2::-1]])
+ else:
+ raise ValueError(f"Got unknown shape: {self._shape!r}")
+ if distance != 0:
+ cx = self._dx / distance
+ sx = self._dy / distance
+ else:
+ # Account for division by zero
+ cx, sx = 0, 1
+ M = [[cx, sx], [-sx, cx]]
+ self.verts = np.dot(coords, M) + [
+ self._x + self._dx,
+ self._y + self._dy,
+ ]
+
+
+_docstring.interpd.register(
+ FancyArrow="\n".join(
+ (inspect.getdoc(FancyArrow.__init__) or "").splitlines()[2:]))
+
+
+class CirclePolygon(RegularPolygon):
+ """A polygon-approximation of a circle patch."""
+
+ def __str__(self):
+ s = "CirclePolygon((%g, %g), radius=%g, resolution=%d)"
+ return s % (self.xy[0], self.xy[1], self.radius, self.numvertices)
+
+ @_docstring.interpd
+ def __init__(self, xy, radius=5, *,
+ resolution=20, # the number of vertices
+ ** kwargs):
+ """
+ Create a circle at *xy* = (*x*, *y*) with given *radius*.
+
+ This circle is approximated by a regular polygon with *resolution*
+ sides. For a smoother circle drawn with splines, see `Circle`.
+
+ Valid keyword arguments are:
+
+ %(Patch:kwdoc)s
+ """
+ super().__init__(
+ xy, resolution, radius=radius, orientation=0, **kwargs)
+
+
+class Ellipse(Patch):
+ """A scale-free ellipse."""
+
+ def __str__(self):
+ pars = (self._center[0], self._center[1],
+ self.width, self.height, self.angle)
+ fmt = "Ellipse(xy=(%s, %s), width=%s, height=%s, angle=%s)"
+ return fmt % pars
+
+ @_docstring.interpd
+ def __init__(self, xy, width, height, *, angle=0, **kwargs):
+ """
+ Parameters
+ ----------
+ xy : (float, float)
+ xy coordinates of ellipse centre.
+ width : float
+ Total length (diameter) of horizontal axis.
+ height : float
+ Total length (diameter) of vertical axis.
+ angle : float, default: 0
+ Rotation in degrees anti-clockwise.
+
+ Notes
+ -----
+ Valid keyword arguments are:
+
+ %(Patch:kwdoc)s
+ """
+ super().__init__(**kwargs)
+
+ self._center = xy
+ self._width, self._height = width, height
+ self._angle = angle
+ self._path = Path.unit_circle()
+ # Required for EllipseSelector with axes aspect ratio != 1
+ # The patch is defined in data coordinates and when changing the
+ # selector with square modifier and not in data coordinates, we need
+ # to correct for the aspect ratio difference between the data and
+ # display coordinate systems.
+ self._aspect_ratio_correction = 1.0
+ # Note: This cannot be calculated until this is added to an Axes
+ self._patch_transform = transforms.IdentityTransform()
+
+ def _recompute_transform(self):
+ """
+ Notes
+ -----
+ This cannot be called until after this has been added to an Axes,
+ otherwise unit conversion will fail. This makes it very important to
+ call the accessor method and not directly access the transformation
+ member variable.
+ """
+ center = (self.convert_xunits(self._center[0]),
+ self.convert_yunits(self._center[1]))
+ width = self.convert_xunits(self._width)
+ height = self.convert_yunits(self._height)
+ self._patch_transform = transforms.Affine2D() \
+ .scale(width * 0.5, height * 0.5 * self._aspect_ratio_correction) \
+ .rotate_deg(self.angle) \
+ .scale(1, 1 / self._aspect_ratio_correction) \
+ .translate(*center)
+
+ def get_path(self):
+ """Return the path of the ellipse."""
+ return self._path
+
+ def get_patch_transform(self):
+ self._recompute_transform()
+ return self._patch_transform
+
+ def set_center(self, xy):
+ """
+ Set the center of the ellipse.
+
+ Parameters
+ ----------
+ xy : (float, float)
+ """
+ self._center = xy
+ self.stale = True
+
+ def get_center(self):
+ """Return the center of the ellipse."""
+ return self._center
+
+ center = property(get_center, set_center)
+
+ def set_width(self, width):
+ """
+ Set the width of the ellipse.
+
+ Parameters
+ ----------
+ width : float
+ """
+ self._width = width
+ self.stale = True
+
+ def get_width(self):
+ """
+ Return the width of the ellipse.
+ """
+ return self._width
+
+ width = property(get_width, set_width)
+
+ def set_height(self, height):
+ """
+ Set the height of the ellipse.
+
+ Parameters
+ ----------
+ height : float
+ """
+ self._height = height
+ self.stale = True
+
+ def get_height(self):
+ """Return the height of the ellipse."""
+ return self._height
+
+ height = property(get_height, set_height)
+
+ def set_angle(self, angle):
+ """
+ Set the angle of the ellipse.
+
+ Parameters
+ ----------
+ angle : float
+ """
+ self._angle = angle
+ self.stale = True
+
+ def get_angle(self):
+ """Return the angle of the ellipse."""
+ return self._angle
+
+ angle = property(get_angle, set_angle)
+
+ def get_corners(self):
+ """
+ Return the corners of the ellipse bounding box.
+
+ The bounding box orientation is moving anti-clockwise from the
+ lower left corner defined before rotation.
+ """
+ return self.get_patch_transform().transform(
+ [(-1, -1), (1, -1), (1, 1), (-1, 1)])
+
+ def get_vertices(self):
+ """
+ Return the vertices coordinates of the ellipse.
+
+ The definition can be found `here `_
+
+ .. versionadded:: 3.8
+ """
+ if self.width < self.height:
+ ret = self.get_patch_transform().transform([(0, 1), (0, -1)])
+ else:
+ ret = self.get_patch_transform().transform([(1, 0), (-1, 0)])
+ return [tuple(x) for x in ret]
+
+ def get_co_vertices(self):
+ """
+ Return the co-vertices coordinates of the ellipse.
+
+ The definition can be found `here `_
+
+ .. versionadded:: 3.8
+ """
+ if self.width < self.height:
+ ret = self.get_patch_transform().transform([(1, 0), (-1, 0)])
+ else:
+ ret = self.get_patch_transform().transform([(0, 1), (0, -1)])
+ return [tuple(x) for x in ret]
+
+
+class Annulus(Patch):
+ """
+ An elliptical annulus.
+ """
+
+ @_docstring.interpd
+ def __init__(self, xy, r, width, angle=0.0, **kwargs):
+ """
+ Parameters
+ ----------
+ xy : (float, float)
+ xy coordinates of annulus centre.
+ r : float or (float, float)
+ The radius, or semi-axes:
+
+ - If float: radius of the outer circle.
+ - If two floats: semi-major and -minor axes of outer ellipse.
+ width : float
+ Width (thickness) of the annular ring. The width is measured inward
+ from the outer ellipse so that for the inner ellipse the semi-axes
+ are given by ``r - width``. *width* must be less than or equal to
+ the semi-minor axis.
+ angle : float, default: 0
+ Rotation angle in degrees (anti-clockwise from the positive
+ x-axis). Ignored for circular annuli (i.e., if *r* is a scalar).
+ **kwargs
+ Keyword arguments control the `Patch` properties:
+
+ %(Patch:kwdoc)s
+ """
+ super().__init__(**kwargs)
+
+ self.set_radii(r)
+ self.center = xy
+ self.width = width
+ self.angle = angle
+ self._path = None
+
+ def __str__(self):
+ if self.a == self.b:
+ r = self.a
+ else:
+ r = (self.a, self.b)
+
+ return "Annulus(xy=(%s, %s), r=%s, width=%s, angle=%s)" % \
+ (*self.center, r, self.width, self.angle)
+
+ def set_center(self, xy):
+ """
+ Set the center of the annulus.
+
+ Parameters
+ ----------
+ xy : (float, float)
+ """
+ self._center = xy
+ self._path = None
+ self.stale = True
+
+ def get_center(self):
+ """Return the center of the annulus."""
+ return self._center
+
+ center = property(get_center, set_center)
+
+ def set_width(self, width):
+ """
+ Set the width (thickness) of the annulus ring.
+
+ The width is measured inwards from the outer ellipse.
+
+ Parameters
+ ----------
+ width : float
+ """
+ if width > min(self.a, self.b):
+ raise ValueError(
+ 'Width of annulus must be less than or equal to semi-minor axis')
+
+ self._width = width
+ self._path = None
+ self.stale = True
+
+ def get_width(self):
+ """Return the width (thickness) of the annulus ring."""
+ return self._width
+
+ width = property(get_width, set_width)
+
+ def set_angle(self, angle):
+ """
+ Set the tilt angle of the annulus.
+
+ Parameters
+ ----------
+ angle : float
+ """
+ self._angle = angle
+ self._path = None
+ self.stale = True
+
+ def get_angle(self):
+ """Return the angle of the annulus."""
+ return self._angle
+
+ angle = property(get_angle, set_angle)
+
+ def set_semimajor(self, a):
+ """
+ Set the semi-major axis *a* of the annulus.
+
+ Parameters
+ ----------
+ a : float
+ """
+ self.a = float(a)
+ self._path = None
+ self.stale = True
+
+ def set_semiminor(self, b):
+ """
+ Set the semi-minor axis *b* of the annulus.
+
+ Parameters
+ ----------
+ b : float
+ """
+ self.b = float(b)
+ self._path = None
+ self.stale = True
+
+ def set_radii(self, r):
+ """
+ Set the semi-major (*a*) and semi-minor radii (*b*) of the annulus.
+
+ Parameters
+ ----------
+ r : float or (float, float)
+ The radius, or semi-axes:
+
+ - If float: radius of the outer circle.
+ - If two floats: semi-major and -minor axes of outer ellipse.
+ """
+ if np.shape(r) == (2,):
+ self.a, self.b = r
+ elif np.shape(r) == ():
+ self.a = self.b = float(r)
+ else:
+ raise ValueError("Parameter 'r' must be one or two floats.")
+
+ self._path = None
+ self.stale = True
+
+ def get_radii(self):
+ """Return the semi-major and semi-minor radii of the annulus."""
+ return self.a, self.b
+
+ radii = property(get_radii, set_radii)
+
+ def _transform_verts(self, verts, a, b):
+ return transforms.Affine2D() \
+ .scale(*self._convert_xy_units((a, b))) \
+ .rotate_deg(self.angle) \
+ .translate(*self._convert_xy_units(self.center)) \
+ .transform(verts)
+
+ def _recompute_path(self):
+ # circular arc
+ arc = Path.arc(0, 360)
+
+ # annulus needs to draw an outer ring
+ # followed by a reversed and scaled inner ring
+ a, b, w = self.a, self.b, self.width
+ v1 = self._transform_verts(arc.vertices, a, b)
+ v2 = self._transform_verts(arc.vertices[::-1], a - w, b - w)
+ v = np.vstack([v1, v2, v1[0, :], (0, 0)])
+ c = np.hstack([arc.codes, Path.MOVETO,
+ arc.codes[1:], Path.MOVETO,
+ Path.CLOSEPOLY])
+ self._path = Path(v, c)
+
+ def get_path(self):
+ if self._path is None:
+ self._recompute_path()
+ return self._path
+
+
+class Circle(Ellipse):
+ """
+ A circle patch.
+ """
+ def __str__(self):
+ pars = self.center[0], self.center[1], self.radius
+ fmt = "Circle(xy=(%g, %g), radius=%g)"
+ return fmt % pars
+
+ @_docstring.interpd
+ def __init__(self, xy, radius=5, **kwargs):
+ """
+ Create a true circle at center *xy* = (*x*, *y*) with given *radius*.
+
+ Unlike `CirclePolygon` which is a polygonal approximation, this uses
+ Bezier splines and is much closer to a scale-free circle.
+
+ Valid keyword arguments are:
+
+ %(Patch:kwdoc)s
+ """
+ super().__init__(xy, radius * 2, radius * 2, **kwargs)
+ self.radius = radius
+
+ def set_radius(self, radius):
+ """
+ Set the radius of the circle.
+
+ Parameters
+ ----------
+ radius : float
+ """
+ self.width = self.height = 2 * radius
+ self.stale = True
+
+ def get_radius(self):
+ """Return the radius of the circle."""
+ return self.width / 2.
+
+ radius = property(get_radius, set_radius)
+
+
+class Arc(Ellipse):
+ """
+ An elliptical arc, i.e. a segment of an ellipse.
+
+ Due to internal optimizations, the arc cannot be filled.
+ """
+
+ def __str__(self):
+ pars = (self.center[0], self.center[1], self.width,
+ self.height, self.angle, self.theta1, self.theta2)
+ fmt = ("Arc(xy=(%g, %g), width=%g, "
+ "height=%g, angle=%g, theta1=%g, theta2=%g)")
+ return fmt % pars
+
+ @_docstring.interpd
+ def __init__(self, xy, width, height, *,
+ angle=0.0, theta1=0.0, theta2=360.0, **kwargs):
+ """
+ Parameters
+ ----------
+ xy : (float, float)
+ The center of the ellipse.
+
+ width : float
+ The length of the horizontal axis.
+
+ height : float
+ The length of the vertical axis.
+
+ angle : float
+ Rotation of the ellipse in degrees (counterclockwise).
+
+ theta1, theta2 : float, default: 0, 360
+ Starting and ending angles of the arc in degrees. These values
+ are relative to *angle*, e.g. if *angle* = 45 and *theta1* = 90
+ the absolute starting angle is 135.
+ Default *theta1* = 0, *theta2* = 360, i.e. a complete ellipse.
+ The arc is drawn in the counterclockwise direction.
+ Angles greater than or equal to 360, or smaller than 0, are
+ represented by an equivalent angle in the range [0, 360), by
+ taking the input value mod 360.
+
+ Other Parameters
+ ----------------
+ **kwargs : `~matplotlib.patches.Patch` properties
+ Most `.Patch` properties are supported as keyword arguments,
+ except *fill* and *facecolor* because filling is not supported.
+
+ %(Patch:kwdoc)s
+ """
+ fill = kwargs.setdefault('fill', False)
+ if fill:
+ raise ValueError("Arc objects cannot be filled")
+
+ super().__init__(xy, width, height, angle=angle, **kwargs)
+
+ self.theta1 = theta1
+ self.theta2 = theta2
+ (self._theta1, self._theta2, self._stretched_width,
+ self._stretched_height) = self._theta_stretch()
+ self._path = Path.arc(self._theta1, self._theta2)
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ """
+ Draw the arc to the given *renderer*.
+
+ Notes
+ -----
+ Ellipses are normally drawn using an approximation that uses
+ eight cubic Bezier splines. The error of this approximation
+ is 1.89818e-6, according to this unverified source:
+
+ Lancaster, Don. *Approximating a Circle or an Ellipse Using
+ Four Bezier Cubic Splines.*
+
+ https://www.tinaja.com/glib/ellipse4.pdf
+
+ There is a use case where very large ellipses must be drawn
+ with very high accuracy, and it is too expensive to render the
+ entire ellipse with enough segments (either splines or line
+ segments). Therefore, in the case where either radius of the
+ ellipse is large enough that the error of the spline
+ approximation will be visible (greater than one pixel offset
+ from the ideal), a different technique is used.
+
+ In that case, only the visible parts of the ellipse are drawn,
+ with each visible arc using a fixed number of spline segments
+ (8). The algorithm proceeds as follows:
+
+ 1. The points where the ellipse intersects the axes (or figure)
+ bounding box are located. (This is done by performing an inverse
+ transformation on the bbox such that it is relative to the unit
+ circle -- this makes the intersection calculation much easier than
+ doing rotated ellipse intersection directly.)
+
+ This uses the "line intersecting a circle" algorithm from:
+
+ Vince, John. *Geometry for Computer Graphics: Formulae,
+ Examples & Proofs.* London: Springer-Verlag, 2005.
+
+ 2. The angles of each of the intersection points are calculated.
+
+ 3. Proceeding counterclockwise starting in the positive
+ x-direction, each of the visible arc-segments between the
+ pairs of vertices are drawn using the Bezier arc
+ approximation technique implemented in `.Path.arc`.
+ """
+ if not self.get_visible():
+ return
+
+ self._recompute_transform()
+
+ self._update_path()
+ # Get width and height in pixels we need to use
+ # `self.get_data_transform` rather than `self.get_transform`
+ # because we want the transform from dataspace to the
+ # screen space to estimate how big the arc will be in physical
+ # units when rendered (the transform that we get via
+ # `self.get_transform()` goes from an idealized unit-radius
+ # space to screen space).
+ data_to_screen_trans = self.get_data_transform()
+ pwidth, pheight = (
+ data_to_screen_trans.transform((self._stretched_width,
+ self._stretched_height)) -
+ data_to_screen_trans.transform((0, 0)))
+ inv_error = (1.0 / 1.89818e-6) * 0.5
+
+ if pwidth < inv_error and pheight < inv_error:
+ return Patch.draw(self, renderer)
+
+ def line_circle_intersect(x0, y0, x1, y1):
+ dx = x1 - x0
+ dy = y1 - y0
+ dr2 = dx * dx + dy * dy
+ D = x0 * y1 - x1 * y0
+ D2 = D * D
+ discrim = dr2 - D2
+ if discrim >= 0.0:
+ sign_dy = np.copysign(1, dy) # +/-1, never 0.
+ sqrt_discrim = np.sqrt(discrim)
+ return np.array(
+ [[(D * dy + sign_dy * dx * sqrt_discrim) / dr2,
+ (-D * dx + abs(dy) * sqrt_discrim) / dr2],
+ [(D * dy - sign_dy * dx * sqrt_discrim) / dr2,
+ (-D * dx - abs(dy) * sqrt_discrim) / dr2]])
+ else:
+ return np.empty((0, 2))
+
+ def segment_circle_intersect(x0, y0, x1, y1):
+ epsilon = 1e-9
+ if x1 < x0:
+ x0e, x1e = x1, x0
+ else:
+ x0e, x1e = x0, x1
+ if y1 < y0:
+ y0e, y1e = y1, y0
+ else:
+ y0e, y1e = y0, y1
+ xys = line_circle_intersect(x0, y0, x1, y1)
+ xs, ys = xys.T
+ return xys[
+ (x0e - epsilon < xs) & (xs < x1e + epsilon)
+ & (y0e - epsilon < ys) & (ys < y1e + epsilon)
+ ]
+
+ # Transform the Axes (or figure) box_path so that it is relative to
+ # the unit circle in the same way that it is relative to the desired
+ # ellipse.
+ box_path_transform = (
+ transforms.BboxTransformTo((self.axes or self.get_figure(root=False)).bbox)
+ - self.get_transform())
+ box_path = Path.unit_rectangle().transformed(box_path_transform)
+
+ thetas = set()
+ # For each of the point pairs, there is a line segment
+ for p0, p1 in zip(box_path.vertices[:-1], box_path.vertices[1:]):
+ xy = segment_circle_intersect(*p0, *p1)
+ x, y = xy.T
+ # arctan2 return [-pi, pi), the rest of our angles are in
+ # [0, 360], adjust as needed.
+ theta = (np.rad2deg(np.arctan2(y, x)) + 360) % 360
+ thetas.update(
+ theta[(self._theta1 < theta) & (theta < self._theta2)])
+ thetas = sorted(thetas) + [self._theta2]
+ last_theta = self._theta1
+ theta1_rad = np.deg2rad(self._theta1)
+ inside = box_path.contains_point(
+ (np.cos(theta1_rad), np.sin(theta1_rad))
+ )
+
+ # save original path
+ path_original = self._path
+ for theta in thetas:
+ if inside:
+ self._path = Path.arc(last_theta, theta, 8)
+ Patch.draw(self, renderer)
+ inside = False
+ else:
+ inside = True
+ last_theta = theta
+
+ # restore original path
+ self._path = path_original
+
+ def _update_path(self):
+ # Compute new values and update and set new _path if any value changed
+ stretched = self._theta_stretch()
+ if any(a != b for a, b in zip(
+ stretched, (self._theta1, self._theta2, self._stretched_width,
+ self._stretched_height))):
+ (self._theta1, self._theta2, self._stretched_width,
+ self._stretched_height) = stretched
+ self._path = Path.arc(self._theta1, self._theta2)
+
+ def _theta_stretch(self):
+ # If the width and height of ellipse are not equal, take into account
+ # stretching when calculating angles to draw between
+ def theta_stretch(theta, scale):
+ theta = np.deg2rad(theta)
+ x = np.cos(theta)
+ y = np.sin(theta)
+ stheta = np.rad2deg(np.arctan2(scale * y, x))
+ # arctan2 has the range [-pi, pi], we expect [0, 2*pi]
+ return (stheta + 360) % 360
+
+ width = self.convert_xunits(self.width)
+ height = self.convert_yunits(self.height)
+ if (
+ # if we need to stretch the angles because we are distorted
+ width != height
+ # and we are not doing a full circle.
+ #
+ # 0 and 360 do not exactly round-trip through the angle
+ # stretching (due to both float precision limitations and
+ # the difference between the range of arctan2 [-pi, pi] and
+ # this method [0, 360]) so avoid doing it if we don't have to.
+ and not (self.theta1 != self.theta2 and
+ self.theta1 % 360 == self.theta2 % 360)
+ ):
+ theta1 = theta_stretch(self.theta1, width / height)
+ theta2 = theta_stretch(self.theta2, width / height)
+ return theta1, theta2, width, height
+ return self.theta1, self.theta2, width, height
+
+
+def bbox_artist(artist, renderer, props=None, fill=True):
+ """
+ A debug function to draw a rectangle around the bounding
+ box returned by an artist's `.Artist.get_window_extent`
+ to test whether the artist is returning the correct bbox.
+
+ *props* is a dict of rectangle props with the additional property
+ 'pad' that sets the padding around the bbox in points.
+ """
+ if props is None:
+ props = {}
+ props = props.copy() # don't want to alter the pad externally
+ pad = props.pop('pad', 4)
+ pad = renderer.points_to_pixels(pad)
+ bbox = artist.get_window_extent(renderer)
+ r = Rectangle(
+ xy=(bbox.x0 - pad / 2, bbox.y0 - pad / 2),
+ width=bbox.width + pad, height=bbox.height + pad,
+ fill=fill, transform=transforms.IdentityTransform(), clip_on=False)
+ r.update(props)
+ r.draw(renderer)
+
+
+def draw_bbox(bbox, renderer, color='k', trans=None):
+ """
+ A debug function to draw a rectangle around the bounding
+ box returned by an artist's `.Artist.get_window_extent`
+ to test whether the artist is returning the correct bbox.
+ """
+ r = Rectangle(xy=bbox.p0, width=bbox.width, height=bbox.height,
+ edgecolor=color, fill=False, clip_on=False)
+ if trans is not None:
+ r.set_transform(trans)
+ r.draw(renderer)
+
+
+class _Style:
+ """
+ A base class for the Styles. It is meant to be a container class,
+ where actual styles are declared as subclass of it, and it
+ provides some helper functions.
+ """
+
+ def __init_subclass__(cls):
+ # Automatically perform docstring interpolation on the subclasses:
+ # This allows listing the supported styles via
+ # - %(BoxStyle:table)s
+ # - %(ConnectionStyle:table)s
+ # - %(ArrowStyle:table)s
+ # and additionally adding .. ACCEPTS: blocks via
+ # - %(BoxStyle:table_and_accepts)s
+ # - %(ConnectionStyle:table_and_accepts)s
+ # - %(ArrowStyle:table_and_accepts)s
+ _docstring.interpd.register(**{
+ f"{cls.__name__}:table": cls.pprint_styles(),
+ f"{cls.__name__}:table_and_accepts": (
+ cls.pprint_styles()
+ + "\n\n .. ACCEPTS: ["
+ + "|".join(map(" '{}' ".format, cls._style_list))
+ + "]")
+ })
+
+ def __new__(cls, stylename, **kwargs):
+ """Return the instance of the subclass with the given style name."""
+ # The "class" should have the _style_list attribute, which is a mapping
+ # of style names to style classes.
+ _list = stylename.replace(" ", "").split(",")
+ _name = _list[0].lower()
+ try:
+ _cls = cls._style_list[_name]
+ except KeyError as err:
+ raise ValueError(f"Unknown style: {stylename!r}") from err
+ try:
+ _args_pair = [cs.split("=") for cs in _list[1:]]
+ _args = {k: float(v) for k, v in _args_pair}
+ except ValueError as err:
+ raise ValueError(
+ f"Incorrect style argument: {stylename!r}") from err
+ return _cls(**{**_args, **kwargs})
+
+ @classmethod
+ def get_styles(cls):
+ """Return a dictionary of available styles."""
+ return cls._style_list
+
+ @classmethod
+ def pprint_styles(cls):
+ """Return the available styles as pretty-printed string."""
+ table = [('Class', 'Name', 'Parameters'),
+ *[(cls.__name__,
+ # Add backquotes, as - and | have special meaning in reST.
+ f'``{name}``',
+ # [1:-1] drops the surrounding parentheses.
+ str(inspect.signature(cls))[1:-1] or 'None')
+ for name, cls in cls._style_list.items()]]
+ # Convert to rst table.
+ col_len = [max(len(cell) for cell in column) for column in zip(*table)]
+ table_formatstr = ' '.join('=' * cl for cl in col_len)
+ rst_table = '\n'.join([
+ '',
+ table_formatstr,
+ ' '.join(cell.ljust(cl) for cell, cl in zip(table[0], col_len)),
+ table_formatstr,
+ *[' '.join(cell.ljust(cl) for cell, cl in zip(row, col_len))
+ for row in table[1:]],
+ table_formatstr,
+ ])
+ return textwrap.indent(rst_table, prefix=' ' * 4)
+
+ @classmethod
+ @_api.deprecated(
+ '3.10.0',
+ message="This method is never used internally.",
+ alternative="No replacement. Please open an issue if you use this."
+ )
+ def register(cls, name, style):
+ """Register a new style."""
+ if not issubclass(style, cls._Base):
+ raise ValueError(f"{style} must be a subclass of {cls._Base}")
+ cls._style_list[name] = style
+
+
+def _register_style(style_list, cls=None, *, name=None):
+ """Class decorator that stashes a class in a (style) dictionary."""
+ if cls is None:
+ return functools.partial(_register_style, style_list, name=name)
+ style_list[name or cls.__name__.lower()] = cls
+ return cls
+
+
+@_docstring.interpd
+class BoxStyle(_Style):
+ """
+ `BoxStyle` is a container class which defines several
+ boxstyle classes, which are used for `FancyBboxPatch`.
+
+ A style object can be created as::
+
+ BoxStyle.Round(pad=0.2)
+
+ or::
+
+ BoxStyle("Round", pad=0.2)
+
+ or::
+
+ BoxStyle("Round, pad=0.2")
+
+ The following boxstyle classes are defined.
+
+ %(BoxStyle:table)s
+
+ An instance of a boxstyle class is a callable object, with the signature ::
+
+ __call__(self, x0, y0, width, height, mutation_size) -> Path
+
+ *x0*, *y0*, *width* and *height* specify the location and size of the box
+ to be drawn; *mutation_size* scales the outline properties such as padding.
+ """
+
+ _style_list = {}
+
+ @_register_style(_style_list)
+ class Square:
+ """A square box."""
+
+ def __init__(self, pad=0.3):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0.3
+ The amount of padding around the original box.
+ """
+ self.pad = pad
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+ pad = mutation_size * self.pad
+ # width and height with padding added.
+ width, height = width + 2 * pad, height + 2 * pad
+ # boundary of the padded box
+ x0, y0 = x0 - pad, y0 - pad
+ x1, y1 = x0 + width, y0 + height
+ return Path._create_closed(
+ [(x0, y0), (x1, y0), (x1, y1), (x0, y1)])
+
+ @_register_style(_style_list)
+ class Circle:
+ """A circular box."""
+
+ def __init__(self, pad=0.3):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0.3
+ The amount of padding around the original box.
+ """
+ self.pad = pad
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+ pad = mutation_size * self.pad
+ width, height = width + 2 * pad, height + 2 * pad
+ # boundary of the padded box
+ x0, y0 = x0 - pad, y0 - pad
+ return Path.circle((x0 + width / 2, y0 + height / 2),
+ max(width, height) / 2)
+
+ @_register_style(_style_list)
+ class Ellipse:
+ """
+ An elliptical box.
+
+ .. versionadded:: 3.7
+ """
+
+ def __init__(self, pad=0.3):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0.3
+ The amount of padding around the original box.
+ """
+ self.pad = pad
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+ pad = mutation_size * self.pad
+ width, height = width + 2 * pad, height + 2 * pad
+ # boundary of the padded box
+ x0, y0 = x0 - pad, y0 - pad
+ a = width / math.sqrt(2)
+ b = height / math.sqrt(2)
+ trans = Affine2D().scale(a, b).translate(x0 + width / 2,
+ y0 + height / 2)
+ return trans.transform_path(Path.unit_circle())
+
+ @_register_style(_style_list)
+ class LArrow:
+ """A box in the shape of a left-pointing arrow."""
+
+ def __init__(self, pad=0.3):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0.3
+ The amount of padding around the original box.
+ """
+ self.pad = pad
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+ # padding
+ pad = mutation_size * self.pad
+ # width and height with padding added.
+ width, height = width + 2 * pad, height + 2 * pad
+ # boundary of the padded box
+ x0, y0 = x0 - pad, y0 - pad,
+ x1, y1 = x0 + width, y0 + height
+
+ dx = (y1 - y0) / 2
+ dxx = dx / 2
+ x0 = x0 + pad / 1.4 # adjust by ~sqrt(2)
+
+ return Path._create_closed(
+ [(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1),
+ (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx),
+ (x0 + dxx, y0 - dxx), # arrow
+ (x0 + dxx, y0)])
+
+ @_register_style(_style_list)
+ class RArrow(LArrow):
+ """A box in the shape of a right-pointing arrow."""
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+ p = BoxStyle.LArrow.__call__(
+ self, x0, y0, width, height, mutation_size)
+ p.vertices[:, 0] = 2 * x0 + width - p.vertices[:, 0]
+ return p
+
+ @_register_style(_style_list)
+ class DArrow:
+ """A box in the shape of a two-way arrow."""
+ # Modified from LArrow to add a right arrow to the bbox.
+
+ def __init__(self, pad=0.3):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0.3
+ The amount of padding around the original box.
+ """
+ self.pad = pad
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+ # padding
+ pad = mutation_size * self.pad
+ # width and height with padding added.
+ # The width is padded by the arrows, so we don't need to pad it.
+ height = height + 2 * pad
+ # boundary of the padded box
+ x0, y0 = x0 - pad, y0 - pad
+ x1, y1 = x0 + width, y0 + height
+
+ dx = (y1 - y0) / 2
+ dxx = dx / 2
+ x0 = x0 + pad / 1.4 # adjust by ~sqrt(2)
+
+ return Path._create_closed([
+ (x0 + dxx, y0), (x1, y0), # bot-segment
+ (x1, y0 - dxx), (x1 + dx + dxx, y0 + dx),
+ (x1, y1 + dxx), # right-arrow
+ (x1, y1), (x0 + dxx, y1), # top-segment
+ (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx),
+ (x0 + dxx, y0 - dxx), # left-arrow
+ (x0 + dxx, y0)])
+
+ @_register_style(_style_list)
+ class Round:
+ """A box with round corners."""
+
+ def __init__(self, pad=0.3, rounding_size=None):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0.3
+ The amount of padding around the original box.
+ rounding_size : float, default: *pad*
+ Radius of the corners.
+ """
+ self.pad = pad
+ self.rounding_size = rounding_size
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+
+ # padding
+ pad = mutation_size * self.pad
+
+ # size of the rounding corner
+ if self.rounding_size:
+ dr = mutation_size * self.rounding_size
+ else:
+ dr = pad
+
+ width, height = width + 2 * pad, height + 2 * pad
+
+ x0, y0 = x0 - pad, y0 - pad,
+ x1, y1 = x0 + width, y0 + height
+
+ # Round corners are implemented as quadratic Bezier, e.g.,
+ # [(x0, y0-dr), (x0, y0), (x0+dr, y0)] for lower left corner.
+ cp = [(x0 + dr, y0),
+ (x1 - dr, y0),
+ (x1, y0), (x1, y0 + dr),
+ (x1, y1 - dr),
+ (x1, y1), (x1 - dr, y1),
+ (x0 + dr, y1),
+ (x0, y1), (x0, y1 - dr),
+ (x0, y0 + dr),
+ (x0, y0), (x0 + dr, y0),
+ (x0 + dr, y0)]
+
+ com = [Path.MOVETO,
+ Path.LINETO,
+ Path.CURVE3, Path.CURVE3,
+ Path.LINETO,
+ Path.CURVE3, Path.CURVE3,
+ Path.LINETO,
+ Path.CURVE3, Path.CURVE3,
+ Path.LINETO,
+ Path.CURVE3, Path.CURVE3,
+ Path.CLOSEPOLY]
+
+ return Path(cp, com)
+
+ @_register_style(_style_list)
+ class Round4:
+ """A box with rounded edges."""
+
+ def __init__(self, pad=0.3, rounding_size=None):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0.3
+ The amount of padding around the original box.
+ rounding_size : float, default: *pad*/2
+ Rounding of edges.
+ """
+ self.pad = pad
+ self.rounding_size = rounding_size
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+
+ # padding
+ pad = mutation_size * self.pad
+
+ # Rounding size; defaults to half of the padding.
+ if self.rounding_size:
+ dr = mutation_size * self.rounding_size
+ else:
+ dr = pad / 2.
+
+ width = width + 2 * pad - 2 * dr
+ height = height + 2 * pad - 2 * dr
+
+ x0, y0 = x0 - pad + dr, y0 - pad + dr,
+ x1, y1 = x0 + width, y0 + height
+
+ cp = [(x0, y0),
+ (x0 + dr, y0 - dr), (x1 - dr, y0 - dr), (x1, y0),
+ (x1 + dr, y0 + dr), (x1 + dr, y1 - dr), (x1, y1),
+ (x1 - dr, y1 + dr), (x0 + dr, y1 + dr), (x0, y1),
+ (x0 - dr, y1 - dr), (x0 - dr, y0 + dr), (x0, y0),
+ (x0, y0)]
+
+ com = [Path.MOVETO,
+ Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.CLOSEPOLY]
+
+ return Path(cp, com)
+
+ @_register_style(_style_list)
+ class Sawtooth:
+ """A box with a sawtooth outline."""
+
+ def __init__(self, pad=0.3, tooth_size=None):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0.3
+ The amount of padding around the original box.
+ tooth_size : float, default: *pad*/2
+ Size of the sawtooth.
+ """
+ self.pad = pad
+ self.tooth_size = tooth_size
+
+ def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size):
+
+ # padding
+ pad = mutation_size * self.pad
+
+ # size of sawtooth
+ if self.tooth_size is None:
+ tooth_size = self.pad * .5 * mutation_size
+ else:
+ tooth_size = self.tooth_size * mutation_size
+
+ hsz = tooth_size / 2
+ width = width + 2 * pad - tooth_size
+ height = height + 2 * pad - tooth_size
+
+ # the sizes of the vertical and horizontal sawtooth are
+ # separately adjusted to fit the given box size.
+ dsx_n = round((width - tooth_size) / (tooth_size * 2)) * 2
+ dsy_n = round((height - tooth_size) / (tooth_size * 2)) * 2
+
+ x0, y0 = x0 - pad + hsz, y0 - pad + hsz
+ x1, y1 = x0 + width, y0 + height
+
+ xs = [
+ x0, *np.linspace(x0 + hsz, x1 - hsz, 2 * dsx_n + 1), # bottom
+ *([x1, x1 + hsz, x1, x1 - hsz] * dsy_n)[:2*dsy_n+2], # right
+ x1, *np.linspace(x1 - hsz, x0 + hsz, 2 * dsx_n + 1), # top
+ *([x0, x0 - hsz, x0, x0 + hsz] * dsy_n)[:2*dsy_n+2], # left
+ ]
+ ys = [
+ *([y0, y0 - hsz, y0, y0 + hsz] * dsx_n)[:2*dsx_n+2], # bottom
+ y0, *np.linspace(y0 + hsz, y1 - hsz, 2 * dsy_n + 1), # right
+ *([y1, y1 + hsz, y1, y1 - hsz] * dsx_n)[:2*dsx_n+2], # top
+ y1, *np.linspace(y1 - hsz, y0 + hsz, 2 * dsy_n + 1), # left
+ ]
+
+ return [*zip(xs, ys), (xs[0], ys[0])]
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+ saw_vertices = self._get_sawtooth_vertices(x0, y0, width,
+ height, mutation_size)
+ return Path(saw_vertices, closed=True)
+
+ @_register_style(_style_list)
+ class Roundtooth(Sawtooth):
+ """A box with a rounded sawtooth outline."""
+
+ def __call__(self, x0, y0, width, height, mutation_size):
+ saw_vertices = self._get_sawtooth_vertices(x0, y0,
+ width, height,
+ mutation_size)
+ # Add a trailing vertex to allow us to close the polygon correctly
+ saw_vertices = np.concatenate([saw_vertices, [saw_vertices[0]]])
+ codes = ([Path.MOVETO] +
+ [Path.CURVE3, Path.CURVE3] * ((len(saw_vertices)-1)//2) +
+ [Path.CLOSEPOLY])
+ return Path(saw_vertices, codes)
+
+
+@_docstring.interpd
+class ConnectionStyle(_Style):
+ """
+ `ConnectionStyle` is a container class which defines
+ several connectionstyle classes, which is used to create a path
+ between two points. These are mainly used with `FancyArrowPatch`.
+
+ A connectionstyle object can be either created as::
+
+ ConnectionStyle.Arc3(rad=0.2)
+
+ or::
+
+ ConnectionStyle("Arc3", rad=0.2)
+
+ or::
+
+ ConnectionStyle("Arc3, rad=0.2")
+
+ The following classes are defined
+
+ %(ConnectionStyle:table)s
+
+ An instance of any connection style class is a callable object,
+ whose call signature is::
+
+ __call__(self, posA, posB,
+ patchA=None, patchB=None,
+ shrinkA=2., shrinkB=2.)
+
+ and it returns a `.Path` instance. *posA* and *posB* are
+ tuples of (x, y) coordinates of the two points to be
+ connected. *patchA* (or *patchB*) is given, the returned path is
+ clipped so that it start (or end) from the boundary of the
+ patch. The path is further shrunk by *shrinkA* (or *shrinkB*)
+ which is given in points.
+ """
+
+ _style_list = {}
+
+ class _Base:
+ """
+ A base class for connectionstyle classes. The subclass needs
+ to implement a *connect* method whose call signature is::
+
+ connect(posA, posB)
+
+ where posA and posB are tuples of x, y coordinates to be
+ connected. The method needs to return a path connecting two
+ points. This base class defines a __call__ method, and a few
+ helper methods.
+ """
+ def _in_patch(self, patch):
+ """
+ Return a predicate function testing whether a point *xy* is
+ contained in *patch*.
+ """
+ return lambda xy: patch.contains(
+ SimpleNamespace(x=xy[0], y=xy[1]))[0]
+
+ def _clip(self, path, in_start, in_stop):
+ """
+ Clip *path* at its start by the region where *in_start* returns
+ True, and at its stop by the region where *in_stop* returns True.
+
+ The original path is assumed to start in the *in_start* region and
+ to stop in the *in_stop* region.
+ """
+ if in_start:
+ try:
+ _, path = split_path_inout(path, in_start)
+ except ValueError:
+ pass
+ if in_stop:
+ try:
+ path, _ = split_path_inout(path, in_stop)
+ except ValueError:
+ pass
+ return path
+
+ def __call__(self, posA, posB,
+ shrinkA=2., shrinkB=2., patchA=None, patchB=None):
+ """
+ Call the *connect* method to create a path between *posA* and
+ *posB*; then clip and shrink the path.
+ """
+ path = self.connect(posA, posB)
+ path = self._clip(
+ path,
+ self._in_patch(patchA) if patchA else None,
+ self._in_patch(patchB) if patchB else None,
+ )
+ path = self._clip(
+ path,
+ inside_circle(*path.vertices[0], shrinkA) if shrinkA else None,
+ inside_circle(*path.vertices[-1], shrinkB) if shrinkB else None
+ )
+ return path
+
+ @_register_style(_style_list)
+ class Arc3(_Base):
+ """
+ Creates a simple quadratic Bézier curve between two
+ points. The curve is created so that the middle control point
+ (C1) is located at the same distance from the start (C0) and
+ end points(C2) and the distance of the C1 to the line
+ connecting C0-C2 is *rad* times the distance of C0-C2.
+ """
+
+ def __init__(self, rad=0.):
+ """
+ Parameters
+ ----------
+ rad : float
+ Curvature of the curve.
+ """
+ self.rad = rad
+
+ def connect(self, posA, posB):
+ x1, y1 = posA
+ x2, y2 = posB
+ x12, y12 = (x1 + x2) / 2., (y1 + y2) / 2.
+ dx, dy = x2 - x1, y2 - y1
+
+ f = self.rad
+
+ cx, cy = x12 + f * dy, y12 - f * dx
+
+ vertices = [(x1, y1),
+ (cx, cy),
+ (x2, y2)]
+ codes = [Path.MOVETO,
+ Path.CURVE3,
+ Path.CURVE3]
+
+ return Path(vertices, codes)
+
+ @_register_style(_style_list)
+ class Angle3(_Base):
+ """
+ Creates a simple quadratic Bézier curve between two points. The middle
+ control point is placed at the intersecting point of two lines which
+ cross the start and end point, and have a slope of *angleA* and
+ *angleB*, respectively.
+ """
+
+ def __init__(self, angleA=90, angleB=0):
+ """
+ Parameters
+ ----------
+ angleA : float
+ Starting angle of the path.
+
+ angleB : float
+ Ending angle of the path.
+ """
+
+ self.angleA = angleA
+ self.angleB = angleB
+
+ def connect(self, posA, posB):
+ x1, y1 = posA
+ x2, y2 = posB
+
+ cosA = math.cos(math.radians(self.angleA))
+ sinA = math.sin(math.radians(self.angleA))
+ cosB = math.cos(math.radians(self.angleB))
+ sinB = math.sin(math.radians(self.angleB))
+
+ cx, cy = get_intersection(x1, y1, cosA, sinA,
+ x2, y2, cosB, sinB)
+
+ vertices = [(x1, y1), (cx, cy), (x2, y2)]
+ codes = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
+
+ return Path(vertices, codes)
+
+ @_register_style(_style_list)
+ class Angle(_Base):
+ """
+ Creates a piecewise continuous quadratic Bézier path between two
+ points. The path has a one passing-through point placed at the
+ intersecting point of two lines which cross the start and end point,
+ and have a slope of *angleA* and *angleB*, respectively.
+ The connecting edges are rounded with *rad*.
+ """
+
+ def __init__(self, angleA=90, angleB=0, rad=0.):
+ """
+ Parameters
+ ----------
+ angleA : float
+ Starting angle of the path.
+
+ angleB : float
+ Ending angle of the path.
+
+ rad : float
+ Rounding radius of the edge.
+ """
+
+ self.angleA = angleA
+ self.angleB = angleB
+
+ self.rad = rad
+
+ def connect(self, posA, posB):
+ x1, y1 = posA
+ x2, y2 = posB
+
+ cosA = math.cos(math.radians(self.angleA))
+ sinA = math.sin(math.radians(self.angleA))
+ cosB = math.cos(math.radians(self.angleB))
+ sinB = math.sin(math.radians(self.angleB))
+
+ cx, cy = get_intersection(x1, y1, cosA, sinA,
+ x2, y2, cosB, sinB)
+
+ vertices = [(x1, y1)]
+ codes = [Path.MOVETO]
+
+ if self.rad == 0.:
+ vertices.append((cx, cy))
+ codes.append(Path.LINETO)
+ else:
+ dx1, dy1 = x1 - cx, y1 - cy
+ d1 = np.hypot(dx1, dy1)
+ f1 = self.rad / d1
+ dx2, dy2 = x2 - cx, y2 - cy
+ d2 = np.hypot(dx2, dy2)
+ f2 = self.rad / d2
+ vertices.extend([(cx + dx1 * f1, cy + dy1 * f1),
+ (cx, cy),
+ (cx + dx2 * f2, cy + dy2 * f2)])
+ codes.extend([Path.LINETO, Path.CURVE3, Path.CURVE3])
+
+ vertices.append((x2, y2))
+ codes.append(Path.LINETO)
+
+ return Path(vertices, codes)
+
+ @_register_style(_style_list)
+ class Arc(_Base):
+ """
+ Creates a piecewise continuous quadratic Bézier path between two
+ points. The path can have two passing-through points, a
+ point placed at the distance of *armA* and angle of *angleA* from
+ point A, another point with respect to point B. The edges are
+ rounded with *rad*.
+ """
+
+ def __init__(self, angleA=0, angleB=0, armA=None, armB=None, rad=0.):
+ """
+ Parameters
+ ----------
+ angleA : float
+ Starting angle of the path.
+
+ angleB : float
+ Ending angle of the path.
+
+ armA : float or None
+ Length of the starting arm.
+
+ armB : float or None
+ Length of the ending arm.
+
+ rad : float
+ Rounding radius of the edges.
+ """
+
+ self.angleA = angleA
+ self.angleB = angleB
+ self.armA = armA
+ self.armB = armB
+
+ self.rad = rad
+
+ def connect(self, posA, posB):
+ x1, y1 = posA
+ x2, y2 = posB
+
+ vertices = [(x1, y1)]
+ rounded = []
+ codes = [Path.MOVETO]
+
+ if self.armA:
+ cosA = math.cos(math.radians(self.angleA))
+ sinA = math.sin(math.radians(self.angleA))
+ # x_armA, y_armB
+ d = self.armA - self.rad
+ rounded.append((x1 + d * cosA, y1 + d * sinA))
+ d = self.armA
+ rounded.append((x1 + d * cosA, y1 + d * sinA))
+
+ if self.armB:
+ cosB = math.cos(math.radians(self.angleB))
+ sinB = math.sin(math.radians(self.angleB))
+ x_armB, y_armB = x2 + self.armB * cosB, y2 + self.armB * sinB
+
+ if rounded:
+ xp, yp = rounded[-1]
+ dx, dy = x_armB - xp, y_armB - yp
+ dd = (dx * dx + dy * dy) ** .5
+
+ rounded.append((xp + self.rad * dx / dd,
+ yp + self.rad * dy / dd))
+ vertices.extend(rounded)
+ codes.extend([Path.LINETO,
+ Path.CURVE3,
+ Path.CURVE3])
+ else:
+ xp, yp = vertices[-1]
+ dx, dy = x_armB - xp, y_armB - yp
+ dd = (dx * dx + dy * dy) ** .5
+
+ d = dd - self.rad
+ rounded = [(xp + d * dx / dd, yp + d * dy / dd),
+ (x_armB, y_armB)]
+
+ if rounded:
+ xp, yp = rounded[-1]
+ dx, dy = x2 - xp, y2 - yp
+ dd = (dx * dx + dy * dy) ** .5
+
+ rounded.append((xp + self.rad * dx / dd,
+ yp + self.rad * dy / dd))
+ vertices.extend(rounded)
+ codes.extend([Path.LINETO,
+ Path.CURVE3,
+ Path.CURVE3])
+
+ vertices.append((x2, y2))
+ codes.append(Path.LINETO)
+
+ return Path(vertices, codes)
+
+ @_register_style(_style_list)
+ class Bar(_Base):
+ """
+ A line with *angle* between A and B with *armA* and *armB*. One of the
+ arms is extended so that they are connected in a right angle. The
+ length of *armA* is determined by (*armA* + *fraction* x AB distance).
+ Same for *armB*.
+ """
+
+ def __init__(self, armA=0., armB=0., fraction=0.3, angle=None):
+ """
+ Parameters
+ ----------
+ armA : float
+ Minimum length of armA.
+
+ armB : float
+ Minimum length of armB.
+
+ fraction : float
+ A fraction of the distance between two points that will be
+ added to armA and armB.
+
+ angle : float or None
+ Angle of the connecting line (if None, parallel to A and B).
+ """
+ self.armA = armA
+ self.armB = armB
+ self.fraction = fraction
+ self.angle = angle
+
+ def connect(self, posA, posB):
+ x1, y1 = posA
+ x20, y20 = x2, y2 = posB
+
+ theta1 = math.atan2(y2 - y1, x2 - x1)
+ dx, dy = x2 - x1, y2 - y1
+ dd = (dx * dx + dy * dy) ** .5
+ ddx, ddy = dx / dd, dy / dd
+
+ armA, armB = self.armA, self.armB
+
+ if self.angle is not None:
+ theta0 = np.deg2rad(self.angle)
+ dtheta = theta1 - theta0
+ dl = dd * math.sin(dtheta)
+ dL = dd * math.cos(dtheta)
+ x2, y2 = x1 + dL * math.cos(theta0), y1 + dL * math.sin(theta0)
+ armB = armB - dl
+
+ # update
+ dx, dy = x2 - x1, y2 - y1
+ dd2 = (dx * dx + dy * dy) ** .5
+ ddx, ddy = dx / dd2, dy / dd2
+
+ arm = max(armA, armB)
+ f = self.fraction * dd + arm
+
+ cx1, cy1 = x1 + f * ddy, y1 - f * ddx
+ cx2, cy2 = x2 + f * ddy, y2 - f * ddx
+
+ vertices = [(x1, y1),
+ (cx1, cy1),
+ (cx2, cy2),
+ (x20, y20)]
+ codes = [Path.MOVETO,
+ Path.LINETO,
+ Path.LINETO,
+ Path.LINETO]
+
+ return Path(vertices, codes)
+
+
+def _point_along_a_line(x0, y0, x1, y1, d):
+ """
+ Return the point on the line connecting (*x0*, *y0*) -- (*x1*, *y1*) whose
+ distance from (*x0*, *y0*) is *d*.
+ """
+ dx, dy = x0 - x1, y0 - y1
+ ff = d / (dx * dx + dy * dy) ** .5
+ x2, y2 = x0 - ff * dx, y0 - ff * dy
+
+ return x2, y2
+
+
+@_docstring.interpd
+class ArrowStyle(_Style):
+ """
+ `ArrowStyle` is a container class which defines several
+ arrowstyle classes, which is used to create an arrow path along a
+ given path. These are mainly used with `FancyArrowPatch`.
+
+ An arrowstyle object can be either created as::
+
+ ArrowStyle.Fancy(head_length=.4, head_width=.4, tail_width=.4)
+
+ or::
+
+ ArrowStyle("Fancy", head_length=.4, head_width=.4, tail_width=.4)
+
+ or::
+
+ ArrowStyle("Fancy, head_length=.4, head_width=.4, tail_width=.4")
+
+ The following classes are defined
+
+ %(ArrowStyle:table)s
+
+ For an overview of the visual appearance, see
+ :doc:`/gallery/text_labels_and_annotations/fancyarrow_demo`.
+
+ An instance of any arrow style class is a callable object,
+ whose call signature is::
+
+ __call__(self, path, mutation_size, linewidth, aspect_ratio=1.)
+
+ and it returns a tuple of a `.Path` instance and a boolean
+ value. *path* is a `.Path` instance along which the arrow
+ will be drawn. *mutation_size* and *aspect_ratio* have the same
+ meaning as in `BoxStyle`. *linewidth* is a line width to be
+ stroked. This is meant to be used to correct the location of the
+ head so that it does not overshoot the destination point, but not all
+ classes support it.
+
+ Notes
+ -----
+ *angleA* and *angleB* specify the orientation of the bracket, as either a
+ clockwise or counterclockwise angle depending on the arrow type. 0 degrees
+ means perpendicular to the line connecting the arrow's head and tail.
+
+ .. plot:: gallery/text_labels_and_annotations/angles_on_bracket_arrows.py
+ """
+
+ _style_list = {}
+
+ class _Base:
+ """
+ Arrow Transmuter Base class
+
+ ArrowTransmuterBase and its derivatives are used to make a fancy
+ arrow around a given path. The __call__ method returns a path
+ (which will be used to create a PathPatch instance) and a boolean
+ value indicating the path is open therefore is not fillable. This
+ class is not an artist and actual drawing of the fancy arrow is
+ done by the FancyArrowPatch class.
+ """
+
+ # The derived classes are required to be able to be initialized
+ # w/o arguments, i.e., all its argument (except self) must have
+ # the default values.
+
+ @staticmethod
+ def ensure_quadratic_bezier(path):
+ """
+ Some ArrowStyle classes only works with a simple quadratic
+ Bézier curve (created with `.ConnectionStyle.Arc3` or
+ `.ConnectionStyle.Angle3`). This static method checks if the
+ provided path is a simple quadratic Bézier curve and returns its
+ control points if true.
+ """
+ segments = list(path.iter_segments())
+ if (len(segments) != 2 or segments[0][1] != Path.MOVETO or
+ segments[1][1] != Path.CURVE3):
+ raise ValueError(
+ "'path' is not a valid quadratic Bezier curve")
+ return [*segments[0][0], *segments[1][0]]
+
+ def transmute(self, path, mutation_size, linewidth):
+ """
+ The transmute method is the very core of the ArrowStyle class and
+ must be overridden in the subclasses. It receives the *path*
+ object along which the arrow will be drawn, and the
+ *mutation_size*, with which the arrow head etc. will be scaled.
+ The *linewidth* may be used to adjust the path so that it does not
+ pass beyond the given points. It returns a tuple of a `.Path`
+ instance and a boolean. The boolean value indicate whether the
+ path can be filled or not. The return value can also be a list of
+ paths and list of booleans of the same length.
+ """
+ raise NotImplementedError('Derived must override')
+
+ def __call__(self, path, mutation_size, linewidth,
+ aspect_ratio=1.):
+ """
+ The __call__ method is a thin wrapper around the transmute method
+ and takes care of the aspect ratio.
+ """
+
+ if aspect_ratio is not None:
+ # Squeeze the given height by the aspect_ratio
+ vertices = path.vertices / [1, aspect_ratio]
+ path_shrunk = Path(vertices, path.codes)
+ # call transmute method with squeezed height.
+ path_mutated, fillable = self.transmute(path_shrunk,
+ mutation_size,
+ linewidth)
+ if np.iterable(fillable):
+ # Restore the height
+ path_list = [Path(p.vertices * [1, aspect_ratio], p.codes)
+ for p in path_mutated]
+ return path_list, fillable
+ else:
+ return path_mutated, fillable
+ else:
+ return self.transmute(path, mutation_size, linewidth)
+
+ class _Curve(_Base):
+ """
+ A simple arrow which will work with any path instance. The
+ returned path is the concatenation of the original path, and at
+ most two paths representing the arrow head or bracket at the start
+ point and at the end point. The arrow heads can be either open
+ or closed.
+ """
+
+ arrow = "-"
+ fillbegin = fillend = False # Whether arrows are filled.
+
+ def __init__(self, head_length=.4, head_width=.2, widthA=1., widthB=1.,
+ lengthA=0.2, lengthB=0.2, angleA=0, angleB=0, scaleA=None,
+ scaleB=None):
+ """
+ Parameters
+ ----------
+ head_length : float, default: 0.4
+ Length of the arrow head, relative to *mutation_size*.
+ head_width : float, default: 0.2
+ Width of the arrow head, relative to *mutation_size*.
+ widthA, widthB : float, default: 1.0
+ Width of the bracket.
+ lengthA, lengthB : float, default: 0.2
+ Length of the bracket.
+ angleA, angleB : float, default: 0
+ Orientation of the bracket, as a counterclockwise angle.
+ 0 degrees means perpendicular to the line.
+ scaleA, scaleB : float, default: *mutation_size*
+ The scale of the brackets.
+ """
+
+ self.head_length, self.head_width = head_length, head_width
+ self.widthA, self.widthB = widthA, widthB
+ self.lengthA, self.lengthB = lengthA, lengthB
+ self.angleA, self.angleB = angleA, angleB
+ self.scaleA, self.scaleB = scaleA, scaleB
+
+ self._beginarrow_head = False
+ self._beginarrow_bracket = False
+ self._endarrow_head = False
+ self._endarrow_bracket = False
+
+ if "-" not in self.arrow:
+ raise ValueError("arrow must have the '-' between "
+ "the two heads")
+
+ beginarrow, endarrow = self.arrow.split("-", 1)
+
+ if beginarrow == "<":
+ self._beginarrow_head = True
+ self._beginarrow_bracket = False
+ elif beginarrow == "<|":
+ self._beginarrow_head = True
+ self._beginarrow_bracket = False
+ self.fillbegin = True
+ elif beginarrow in ("]", "|"):
+ self._beginarrow_head = False
+ self._beginarrow_bracket = True
+
+ if endarrow == ">":
+ self._endarrow_head = True
+ self._endarrow_bracket = False
+ elif endarrow == "|>":
+ self._endarrow_head = True
+ self._endarrow_bracket = False
+ self.fillend = True
+ elif endarrow in ("[", "|"):
+ self._endarrow_head = False
+ self._endarrow_bracket = True
+
+ super().__init__()
+
+ def _get_arrow_wedge(self, x0, y0, x1, y1,
+ head_dist, cos_t, sin_t, linewidth):
+ """
+ Return the paths for arrow heads. Since arrow lines are
+ drawn with capstyle=projected, The arrow goes beyond the
+ desired point. This method also returns the amount of the path
+ to be shrunken so that it does not overshoot.
+ """
+
+ # arrow from x0, y0 to x1, y1
+ dx, dy = x0 - x1, y0 - y1
+
+ cp_distance = np.hypot(dx, dy)
+
+ # pad_projected : amount of pad to account the
+ # overshooting of the projection of the wedge
+ pad_projected = (.5 * linewidth / sin_t)
+
+ # Account for division by zero
+ if cp_distance == 0:
+ cp_distance = 1
+
+ # apply pad for projected edge
+ ddx = pad_projected * dx / cp_distance
+ ddy = pad_projected * dy / cp_distance
+
+ # offset for arrow wedge
+ dx = dx / cp_distance * head_dist
+ dy = dy / cp_distance * head_dist
+
+ dx1, dy1 = cos_t * dx + sin_t * dy, -sin_t * dx + cos_t * dy
+ dx2, dy2 = cos_t * dx - sin_t * dy, sin_t * dx + cos_t * dy
+
+ vertices_arrow = [(x1 + ddx + dx1, y1 + ddy + dy1),
+ (x1 + ddx, y1 + ddy),
+ (x1 + ddx + dx2, y1 + ddy + dy2)]
+ codes_arrow = [Path.MOVETO,
+ Path.LINETO,
+ Path.LINETO]
+
+ return vertices_arrow, codes_arrow, ddx, ddy
+
+ def _get_bracket(self, x0, y0,
+ x1, y1, width, length, angle):
+
+ cos_t, sin_t = get_cos_sin(x1, y1, x0, y0)
+
+ # arrow from x0, y0 to x1, y1
+ from matplotlib.bezier import get_normal_points
+ x1, y1, x2, y2 = get_normal_points(x0, y0, cos_t, sin_t, width)
+
+ dx, dy = length * cos_t, length * sin_t
+
+ vertices_arrow = [(x1 + dx, y1 + dy),
+ (x1, y1),
+ (x2, y2),
+ (x2 + dx, y2 + dy)]
+ codes_arrow = [Path.MOVETO,
+ Path.LINETO,
+ Path.LINETO,
+ Path.LINETO]
+
+ if angle:
+ trans = transforms.Affine2D().rotate_deg_around(x0, y0, angle)
+ vertices_arrow = trans.transform(vertices_arrow)
+
+ return vertices_arrow, codes_arrow
+
+ def transmute(self, path, mutation_size, linewidth):
+ # docstring inherited
+ if self._beginarrow_head or self._endarrow_head:
+ head_length = self.head_length * mutation_size
+ head_width = self.head_width * mutation_size
+ head_dist = np.hypot(head_length, head_width)
+ cos_t, sin_t = head_length / head_dist, head_width / head_dist
+
+ scaleA = mutation_size if self.scaleA is None else self.scaleA
+ scaleB = mutation_size if self.scaleB is None else self.scaleB
+
+ # begin arrow
+ x0, y0 = path.vertices[0]
+ x1, y1 = path.vertices[1]
+
+ # If there is no room for an arrow and a line, then skip the arrow
+ has_begin_arrow = self._beginarrow_head and (x0, y0) != (x1, y1)
+ verticesA, codesA, ddxA, ddyA = (
+ self._get_arrow_wedge(x1, y1, x0, y0,
+ head_dist, cos_t, sin_t, linewidth)
+ if has_begin_arrow
+ else ([], [], 0, 0)
+ )
+
+ # end arrow
+ x2, y2 = path.vertices[-2]
+ x3, y3 = path.vertices[-1]
+
+ # If there is no room for an arrow and a line, then skip the arrow
+ has_end_arrow = self._endarrow_head and (x2, y2) != (x3, y3)
+ verticesB, codesB, ddxB, ddyB = (
+ self._get_arrow_wedge(x2, y2, x3, y3,
+ head_dist, cos_t, sin_t, linewidth)
+ if has_end_arrow
+ else ([], [], 0, 0)
+ )
+
+ # This simple code will not work if ddx, ddy is greater than the
+ # separation between vertices.
+ paths = [Path(np.concatenate([[(x0 + ddxA, y0 + ddyA)],
+ path.vertices[1:-1],
+ [(x3 + ddxB, y3 + ddyB)]]),
+ path.codes)]
+ fills = [False]
+
+ if has_begin_arrow:
+ if self.fillbegin:
+ paths.append(
+ Path([*verticesA, (0, 0)], [*codesA, Path.CLOSEPOLY]))
+ fills.append(True)
+ else:
+ paths.append(Path(verticesA, codesA))
+ fills.append(False)
+ elif self._beginarrow_bracket:
+ x0, y0 = path.vertices[0]
+ x1, y1 = path.vertices[1]
+ verticesA, codesA = self._get_bracket(x0, y0, x1, y1,
+ self.widthA * scaleA,
+ self.lengthA * scaleA,
+ self.angleA)
+
+ paths.append(Path(verticesA, codesA))
+ fills.append(False)
+
+ if has_end_arrow:
+ if self.fillend:
+ fills.append(True)
+ paths.append(
+ Path([*verticesB, (0, 0)], [*codesB, Path.CLOSEPOLY]))
+ else:
+ fills.append(False)
+ paths.append(Path(verticesB, codesB))
+ elif self._endarrow_bracket:
+ x0, y0 = path.vertices[-1]
+ x1, y1 = path.vertices[-2]
+ verticesB, codesB = self._get_bracket(x0, y0, x1, y1,
+ self.widthB * scaleB,
+ self.lengthB * scaleB,
+ self.angleB)
+
+ paths.append(Path(verticesB, codesB))
+ fills.append(False)
+
+ return paths, fills
+
+ @_register_style(_style_list, name="-")
+ class Curve(_Curve):
+ """A simple curve without any arrow head."""
+
+ def __init__(self): # hide head_length, head_width
+ # These attributes (whose values come from backcompat) only matter
+ # if someone modifies beginarrow/etc. on an ArrowStyle instance.
+ super().__init__(head_length=.2, head_width=.1)
+
+ @_register_style(_style_list, name="<-")
+ class CurveA(_Curve):
+ """An arrow with a head at its start point."""
+ arrow = "<-"
+
+ @_register_style(_style_list, name="->")
+ class CurveB(_Curve):
+ """An arrow with a head at its end point."""
+ arrow = "->"
+
+ @_register_style(_style_list, name="<->")
+ class CurveAB(_Curve):
+ """An arrow with heads both at the start and the end point."""
+ arrow = "<->"
+
+ @_register_style(_style_list, name="<|-")
+ class CurveFilledA(_Curve):
+ """An arrow with filled triangle head at the start."""
+ arrow = "<|-"
+
+ @_register_style(_style_list, name="-|>")
+ class CurveFilledB(_Curve):
+ """An arrow with filled triangle head at the end."""
+ arrow = "-|>"
+
+ @_register_style(_style_list, name="<|-|>")
+ class CurveFilledAB(_Curve):
+ """An arrow with filled triangle heads at both ends."""
+ arrow = "<|-|>"
+
+ @_register_style(_style_list, name="]-")
+ class BracketA(_Curve):
+ """An arrow with an outward square bracket at its start."""
+ arrow = "]-"
+
+ def __init__(self, widthA=1., lengthA=0.2, angleA=0):
+ """
+ Parameters
+ ----------
+ widthA : float, default: 1.0
+ Width of the bracket.
+ lengthA : float, default: 0.2
+ Length of the bracket.
+ angleA : float, default: 0 degrees
+ Orientation of the bracket, as a counterclockwise angle.
+ 0 degrees means perpendicular to the line.
+ """
+ super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA)
+
+ @_register_style(_style_list, name="-[")
+ class BracketB(_Curve):
+ """An arrow with an outward square bracket at its end."""
+ arrow = "-["
+
+ def __init__(self, widthB=1., lengthB=0.2, angleB=0):
+ """
+ Parameters
+ ----------
+ widthB : float, default: 1.0
+ Width of the bracket.
+ lengthB : float, default: 0.2
+ Length of the bracket.
+ angleB : float, default: 0 degrees
+ Orientation of the bracket, as a counterclockwise angle.
+ 0 degrees means perpendicular to the line.
+ """
+ super().__init__(widthB=widthB, lengthB=lengthB, angleB=angleB)
+
+ @_register_style(_style_list, name="]-[")
+ class BracketAB(_Curve):
+ """An arrow with outward square brackets at both ends."""
+ arrow = "]-["
+
+ def __init__(self,
+ widthA=1., lengthA=0.2, angleA=0,
+ widthB=1., lengthB=0.2, angleB=0):
+ """
+ Parameters
+ ----------
+ widthA, widthB : float, default: 1.0
+ Width of the bracket.
+ lengthA, lengthB : float, default: 0.2
+ Length of the bracket.
+ angleA, angleB : float, default: 0 degrees
+ Orientation of the bracket, as a counterclockwise angle.
+ 0 degrees means perpendicular to the line.
+ """
+ super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA,
+ widthB=widthB, lengthB=lengthB, angleB=angleB)
+
+ @_register_style(_style_list, name="|-|")
+ class BarAB(_Curve):
+ """An arrow with vertical bars ``|`` at both ends."""
+ arrow = "|-|"
+
+ def __init__(self, widthA=1., angleA=0, widthB=1., angleB=0):
+ """
+ Parameters
+ ----------
+ widthA, widthB : float, default: 1.0
+ Width of the bracket.
+ angleA, angleB : float, default: 0 degrees
+ Orientation of the bracket, as a counterclockwise angle.
+ 0 degrees means perpendicular to the line.
+ """
+ super().__init__(widthA=widthA, lengthA=0, angleA=angleA,
+ widthB=widthB, lengthB=0, angleB=angleB)
+
+ @_register_style(_style_list, name=']->')
+ class BracketCurve(_Curve):
+ """
+ An arrow with an outward square bracket at its start and a head at
+ the end.
+ """
+ arrow = "]->"
+
+ def __init__(self, widthA=1., lengthA=0.2, angleA=None):
+ """
+ Parameters
+ ----------
+ widthA : float, default: 1.0
+ Width of the bracket.
+ lengthA : float, default: 0.2
+ Length of the bracket.
+ angleA : float, default: 0 degrees
+ Orientation of the bracket, as a counterclockwise angle.
+ 0 degrees means perpendicular to the line.
+ """
+ super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA)
+
+ @_register_style(_style_list, name='<-[')
+ class CurveBracket(_Curve):
+ """
+ An arrow with an outward square bracket at its end and a head at
+ the start.
+ """
+ arrow = "<-["
+
+ def __init__(self, widthB=1., lengthB=0.2, angleB=None):
+ """
+ Parameters
+ ----------
+ widthB : float, default: 1.0
+ Width of the bracket.
+ lengthB : float, default: 0.2
+ Length of the bracket.
+ angleB : float, default: 0 degrees
+ Orientation of the bracket, as a counterclockwise angle.
+ 0 degrees means perpendicular to the line.
+ """
+ super().__init__(widthB=widthB, lengthB=lengthB, angleB=angleB)
+
+ @_register_style(_style_list)
+ class Simple(_Base):
+ """A simple arrow. Only works with a quadratic Bézier curve."""
+
+ def __init__(self, head_length=.5, head_width=.5, tail_width=.2):
+ """
+ Parameters
+ ----------
+ head_length : float, default: 0.5
+ Length of the arrow head.
+
+ head_width : float, default: 0.5
+ Width of the arrow head.
+
+ tail_width : float, default: 0.2
+ Width of the arrow tail.
+ """
+ self.head_length, self.head_width, self.tail_width = \
+ head_length, head_width, tail_width
+ super().__init__()
+
+ def transmute(self, path, mutation_size, linewidth):
+ # docstring inherited
+ x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path)
+
+ # divide the path into a head and a tail
+ head_length = self.head_length * mutation_size
+ in_f = inside_circle(x2, y2, head_length)
+ arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
+
+ try:
+ arrow_out, arrow_in = \
+ split_bezier_intersecting_with_closedpath(arrow_path, in_f)
+ except NonIntersectingPathException:
+ # if this happens, make a straight line of the head_length
+ # long.
+ x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length)
+ x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2)
+ arrow_in = [(x0, y0), (x1n, y1n), (x2, y2)]
+ arrow_out = None
+
+ # head
+ head_width = self.head_width * mutation_size
+ head_left, head_right = make_wedged_bezier2(arrow_in,
+ head_width / 2., wm=.5)
+
+ # tail
+ if arrow_out is not None:
+ tail_width = self.tail_width * mutation_size
+ tail_left, tail_right = get_parallels(arrow_out,
+ tail_width / 2.)
+
+ patch_path = [(Path.MOVETO, tail_right[0]),
+ (Path.CURVE3, tail_right[1]),
+ (Path.CURVE3, tail_right[2]),
+ (Path.LINETO, head_right[0]),
+ (Path.CURVE3, head_right[1]),
+ (Path.CURVE3, head_right[2]),
+ (Path.CURVE3, head_left[1]),
+ (Path.CURVE3, head_left[0]),
+ (Path.LINETO, tail_left[2]),
+ (Path.CURVE3, tail_left[1]),
+ (Path.CURVE3, tail_left[0]),
+ (Path.LINETO, tail_right[0]),
+ (Path.CLOSEPOLY, tail_right[0]),
+ ]
+ else:
+ patch_path = [(Path.MOVETO, head_right[0]),
+ (Path.CURVE3, head_right[1]),
+ (Path.CURVE3, head_right[2]),
+ (Path.CURVE3, head_left[1]),
+ (Path.CURVE3, head_left[0]),
+ (Path.CLOSEPOLY, head_left[0]),
+ ]
+
+ path = Path([p for c, p in patch_path], [c for c, p in patch_path])
+
+ return path, True
+
+ @_register_style(_style_list)
+ class Fancy(_Base):
+ """A fancy arrow. Only works with a quadratic Bézier curve."""
+
+ def __init__(self, head_length=.4, head_width=.4, tail_width=.4):
+ """
+ Parameters
+ ----------
+ head_length : float, default: 0.4
+ Length of the arrow head.
+
+ head_width : float, default: 0.4
+ Width of the arrow head.
+
+ tail_width : float, default: 0.4
+ Width of the arrow tail.
+ """
+ self.head_length, self.head_width, self.tail_width = \
+ head_length, head_width, tail_width
+ super().__init__()
+
+ def transmute(self, path, mutation_size, linewidth):
+ # docstring inherited
+ x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path)
+
+ # divide the path into a head and a tail
+ head_length = self.head_length * mutation_size
+ arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
+
+ # path for head
+ in_f = inside_circle(x2, y2, head_length)
+ try:
+ path_out, path_in = split_bezier_intersecting_with_closedpath(
+ arrow_path, in_f)
+ except NonIntersectingPathException:
+ # if this happens, make a straight line of the head_length
+ # long.
+ x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length)
+ x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2)
+ arrow_path = [(x0, y0), (x1n, y1n), (x2, y2)]
+ path_head = arrow_path
+ else:
+ path_head = path_in
+
+ # path for head
+ in_f = inside_circle(x2, y2, head_length * .8)
+ path_out, path_in = split_bezier_intersecting_with_closedpath(
+ arrow_path, in_f)
+ path_tail = path_out
+
+ # head
+ head_width = self.head_width * mutation_size
+ head_l, head_r = make_wedged_bezier2(path_head,
+ head_width / 2.,
+ wm=.6)
+
+ # tail
+ tail_width = self.tail_width * mutation_size
+ tail_left, tail_right = make_wedged_bezier2(path_tail,
+ tail_width * .5,
+ w1=1., wm=0.6, w2=0.3)
+
+ # path for head
+ in_f = inside_circle(x0, y0, tail_width * .3)
+ path_in, path_out = split_bezier_intersecting_with_closedpath(
+ arrow_path, in_f)
+ tail_start = path_in[-1]
+
+ head_right, head_left = head_r, head_l
+ patch_path = [(Path.MOVETO, tail_start),
+ (Path.LINETO, tail_right[0]),
+ (Path.CURVE3, tail_right[1]),
+ (Path.CURVE3, tail_right[2]),
+ (Path.LINETO, head_right[0]),
+ (Path.CURVE3, head_right[1]),
+ (Path.CURVE3, head_right[2]),
+ (Path.CURVE3, head_left[1]),
+ (Path.CURVE3, head_left[0]),
+ (Path.LINETO, tail_left[2]),
+ (Path.CURVE3, tail_left[1]),
+ (Path.CURVE3, tail_left[0]),
+ (Path.LINETO, tail_start),
+ (Path.CLOSEPOLY, tail_start),
+ ]
+ path = Path([p for c, p in patch_path], [c for c, p in patch_path])
+
+ return path, True
+
+ @_register_style(_style_list)
+ class Wedge(_Base):
+ """
+ Wedge(?) shape. Only works with a quadratic Bézier curve. The
+ start point has a width of the *tail_width* and the end point has a
+ width of 0. At the middle, the width is *shrink_factor*x*tail_width*.
+ """
+
+ def __init__(self, tail_width=.3, shrink_factor=0.5):
+ """
+ Parameters
+ ----------
+ tail_width : float, default: 0.3
+ Width of the tail.
+
+ shrink_factor : float, default: 0.5
+ Fraction of the arrow width at the middle point.
+ """
+ self.tail_width = tail_width
+ self.shrink_factor = shrink_factor
+ super().__init__()
+
+ def transmute(self, path, mutation_size, linewidth):
+ # docstring inherited
+ x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path)
+
+ arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
+ b_plus, b_minus = make_wedged_bezier2(
+ arrow_path,
+ self.tail_width * mutation_size / 2.,
+ wm=self.shrink_factor)
+
+ patch_path = [(Path.MOVETO, b_plus[0]),
+ (Path.CURVE3, b_plus[1]),
+ (Path.CURVE3, b_plus[2]),
+ (Path.LINETO, b_minus[2]),
+ (Path.CURVE3, b_minus[1]),
+ (Path.CURVE3, b_minus[0]),
+ (Path.CLOSEPOLY, b_minus[0]),
+ ]
+ path = Path([p for c, p in patch_path], [c for c, p in patch_path])
+
+ return path, True
+
+
+class FancyBboxPatch(Patch):
+ """
+ A fancy box around a rectangle with lower left at *xy* = (*x*, *y*)
+ with specified width and height.
+
+ `.FancyBboxPatch` is similar to `.Rectangle`, but it draws a fancy box
+ around the rectangle. The transformation of the rectangle box to the
+ fancy box is delegated to the style classes defined in `.BoxStyle`.
+ """
+
+ _edge_default = True
+
+ def __str__(self):
+ s = self.__class__.__name__ + "((%g, %g), width=%g, height=%g)"
+ return s % (self._x, self._y, self._width, self._height)
+
+ @_docstring.interpd
+ def __init__(self, xy, width, height, boxstyle="round", *,
+ mutation_scale=1, mutation_aspect=1, **kwargs):
+ """
+ Parameters
+ ----------
+ xy : (float, float)
+ The lower left corner of the box.
+
+ width : float
+ The width of the box.
+
+ height : float
+ The height of the box.
+
+ boxstyle : str or `~matplotlib.patches.BoxStyle`
+ The style of the fancy box. This can either be a `.BoxStyle`
+ instance or a string of the style name and optionally comma
+ separated attributes (e.g. "Round, pad=0.2"). This string is
+ passed to `.BoxStyle` to construct a `.BoxStyle` object. See
+ there for a full documentation.
+
+ The following box styles are available:
+
+ %(BoxStyle:table)s
+
+ mutation_scale : float, default: 1
+ Scaling factor applied to the attributes of the box style
+ (e.g. pad or rounding_size).
+
+ mutation_aspect : float, default: 1
+ The height of the rectangle will be squeezed by this value before
+ the mutation and the mutated box will be stretched by the inverse
+ of it. For example, this allows different horizontal and vertical
+ padding.
+
+ Other Parameters
+ ----------------
+ **kwargs : `~matplotlib.patches.Patch` properties
+
+ %(Patch:kwdoc)s
+ """
+
+ super().__init__(**kwargs)
+ self._x, self._y = xy
+ self._width = width
+ self._height = height
+ self.set_boxstyle(boxstyle)
+ self._mutation_scale = mutation_scale
+ self._mutation_aspect = mutation_aspect
+ self.stale = True
+
+ @_docstring.interpd
+ def set_boxstyle(self, boxstyle=None, **kwargs):
+ """
+ Set the box style, possibly with further attributes.
+
+ Attributes from the previous box style are not reused.
+
+ Without argument (or with ``boxstyle=None``), the available box styles
+ are returned as a human-readable string.
+
+ Parameters
+ ----------
+ boxstyle : str or `~matplotlib.patches.BoxStyle`
+ The style of the box: either a `.BoxStyle` instance, or a string,
+ which is the style name and optionally comma separated attributes
+ (e.g. "Round,pad=0.2"). Such a string is used to construct a
+ `.BoxStyle` object, as documented in that class.
+
+ The following box styles are available:
+
+ %(BoxStyle:table_and_accepts)s
+
+ **kwargs
+ Additional attributes for the box style. See the table above for
+ supported parameters.
+
+ Examples
+ --------
+ ::
+
+ set_boxstyle("Round,pad=0.2")
+ set_boxstyle("round", pad=0.2)
+ """
+ if boxstyle is None:
+ return BoxStyle.pprint_styles()
+ self._bbox_transmuter = (
+ BoxStyle(boxstyle, **kwargs)
+ if isinstance(boxstyle, str) else boxstyle)
+ self.stale = True
+
+ def get_boxstyle(self):
+ """Return the boxstyle object."""
+ return self._bbox_transmuter
+
+ def set_mutation_scale(self, scale):
+ """
+ Set the mutation scale.
+
+ Parameters
+ ----------
+ scale : float
+ """
+ self._mutation_scale = scale
+ self.stale = True
+
+ def get_mutation_scale(self):
+ """Return the mutation scale."""
+ return self._mutation_scale
+
+ def set_mutation_aspect(self, aspect):
+ """
+ Set the aspect ratio of the bbox mutation.
+
+ Parameters
+ ----------
+ aspect : float
+ """
+ self._mutation_aspect = aspect
+ self.stale = True
+
+ def get_mutation_aspect(self):
+ """Return the aspect ratio of the bbox mutation."""
+ return (self._mutation_aspect if self._mutation_aspect is not None
+ else 1) # backcompat.
+
+ def get_path(self):
+ """Return the mutated path of the rectangle."""
+ boxstyle = self.get_boxstyle()
+ m_aspect = self.get_mutation_aspect()
+ # Call boxstyle with y, height squeezed by aspect_ratio.
+ path = boxstyle(self._x, self._y / m_aspect,
+ self._width, self._height / m_aspect,
+ self.get_mutation_scale())
+ return Path(path.vertices * [1, m_aspect], path.codes) # Unsqueeze y.
+
+ # Following methods are borrowed from the Rectangle class.
+
+ def get_x(self):
+ """Return the left coord of the rectangle."""
+ return self._x
+
+ def get_y(self):
+ """Return the bottom coord of the rectangle."""
+ return self._y
+
+ def get_width(self):
+ """Return the width of the rectangle."""
+ return self._width
+
+ def get_height(self):
+ """Return the height of the rectangle."""
+ return self._height
+
+ def set_x(self, x):
+ """
+ Set the left coord of the rectangle.
+
+ Parameters
+ ----------
+ x : float
+ """
+ self._x = x
+ self.stale = True
+
+ def set_y(self, y):
+ """
+ Set the bottom coord of the rectangle.
+
+ Parameters
+ ----------
+ y : float
+ """
+ self._y = y
+ self.stale = True
+
+ def set_width(self, w):
+ """
+ Set the rectangle width.
+
+ Parameters
+ ----------
+ w : float
+ """
+ self._width = w
+ self.stale = True
+
+ def set_height(self, h):
+ """
+ Set the rectangle height.
+
+ Parameters
+ ----------
+ h : float
+ """
+ self._height = h
+ self.stale = True
+
+ def set_bounds(self, *args):
+ """
+ Set the bounds of the rectangle.
+
+ Call signatures::
+
+ set_bounds(left, bottom, width, height)
+ set_bounds((left, bottom, width, height))
+
+ Parameters
+ ----------
+ left, bottom : float
+ The coordinates of the bottom left corner of the rectangle.
+ width, height : float
+ The width/height of the rectangle.
+ """
+ if len(args) == 1:
+ l, b, w, h = args[0]
+ else:
+ l, b, w, h = args
+ self._x = l
+ self._y = b
+ self._width = w
+ self._height = h
+ self.stale = True
+
+ def get_bbox(self):
+ """Return the `.Bbox`."""
+ return transforms.Bbox.from_bounds(self._x, self._y,
+ self._width, self._height)
+
+
+class FancyArrowPatch(Patch):
+ """
+ A fancy arrow patch.
+
+ It draws an arrow using the `ArrowStyle`. It is primarily used by the
+ `~.axes.Axes.annotate` method. For most purposes, use the annotate method for
+ drawing arrows.
+
+ The head and tail positions are fixed at the specified start and end points
+ of the arrow, but the size and shape (in display coordinates) of the arrow
+ does not change when the axis is moved or zoomed.
+ """
+ _edge_default = True
+
+ def __str__(self):
+ if self._posA_posB is not None:
+ (x1, y1), (x2, y2) = self._posA_posB
+ return f"{type(self).__name__}(({x1:g}, {y1:g})->({x2:g}, {y2:g}))"
+ else:
+ return f"{type(self).__name__}({self._path_original})"
+
+ @_docstring.interpd
+ def __init__(self, posA=None, posB=None, *,
+ path=None, arrowstyle="simple", connectionstyle="arc3",
+ patchA=None, patchB=None, shrinkA=2, shrinkB=2,
+ mutation_scale=1, mutation_aspect=1, **kwargs):
+ """
+ **Defining the arrow position and path**
+
+ There are two ways to define the arrow position and path:
+
+ - **Start, end and connection**:
+ The typical approach is to define the start and end points of the
+ arrow using *posA* and *posB*. The curve between these two can
+ further be configured using *connectionstyle*.
+
+ If given, the arrow curve is clipped by *patchA* and *patchB*,
+ allowing it to start/end at the border of these patches.
+ Additionally, the arrow curve can be shortened by *shrinkA* and *shrinkB*
+ to create a margin between start/end (after possible clipping) and the
+ drawn arrow.
+
+ - **path**: Alternatively if *path* is provided, an arrow is drawn along
+ this Path. In this case, *connectionstyle*, *patchA*, *patchB*,
+ *shrinkA*, and *shrinkB* are ignored.
+
+ **Styling**
+
+ The *arrowstyle* defines the styling of the arrow head, tail and shaft.
+ The resulting arrows can be styled further by setting the `.Patch`
+ properties such as *linewidth*, *color*, *facecolor*, *edgecolor*
+ etc. via keyword arguments.
+
+ Parameters
+ ----------
+ posA, posB : (float, float), optional
+ (x, y) coordinates of start and end point of the arrow.
+ The actually drawn start and end positions may be modified
+ through *patchA*, *patchB*, *shrinkA*, and *shrinkB*.
+
+ *posA*, *posB* are exclusive of *path*.
+
+ path : `~matplotlib.path.Path`, optional
+ If provided, an arrow is drawn along this path and *patchA*,
+ *patchB*, *shrinkA*, and *shrinkB* are ignored.
+
+ *path* is exclusive of *posA*, *posB*.
+
+ arrowstyle : str or `.ArrowStyle`, default: 'simple'
+ The styling of arrow head, tail and shaft. This can be
+
+ - `.ArrowStyle` or one of its subclasses
+ - The shorthand string name (e.g. "->") as given in the table below,
+ optionally containing a comma-separated list of style parameters,
+ e.g. "->, head_length=10, head_width=5".
+
+ The style parameters are scaled by *mutation_scale*.
+
+ The following arrow styles are available. See also
+ :doc:`/gallery/text_labels_and_annotations/fancyarrow_demo`.
+
+ %(ArrowStyle:table)s
+
+ Only the styles ``<|-``, ``-|>``, ``<|-|>`` ``simple``, ``fancy``
+ and ``wedge`` contain closed paths and can be filled.
+
+ connectionstyle : str or `.ConnectionStyle` or None, optional, \
+default: 'arc3'
+ `.ConnectionStyle` with which *posA* and *posB* are connected.
+ This can be
+
+ - `.ConnectionStyle` or one of its subclasses
+ - The shorthand string name as given in the table below, e.g. "arc3".
+
+ %(ConnectionStyle:table)s
+
+ Ignored if *path* is provided.
+
+ patchA, patchB : `~matplotlib.patches.Patch`, default: None
+ Optional Patches at *posA* and *posB*, respectively. If given,
+ the arrow path is clipped by these patches such that head and tail
+ are at the border of the patches.
+
+ Ignored if *path* is provided.
+
+ shrinkA, shrinkB : float, default: 2
+ Shorten the arrow path at *posA* and *posB* by this amount in points.
+ This allows to add a margin between the intended start/end points and
+ the arrow.
+
+ Ignored if *path* is provided.
+
+ mutation_scale : float, default: 1
+ Value with which attributes of *arrowstyle* (e.g., *head_length*)
+ will be scaled.
+
+ mutation_aspect : None or float, default: None
+ The height of the rectangle will be squeezed by this value before
+ the mutation and the mutated box will be stretched by the inverse
+ of it.
+
+ Other Parameters
+ ----------------
+ **kwargs : `~matplotlib.patches.Patch` properties, optional
+ Here is a list of available `.Patch` properties:
+
+ %(Patch:kwdoc)s
+
+ In contrast to other patches, the default ``capstyle`` and
+ ``joinstyle`` for `FancyArrowPatch` are set to ``"round"``.
+ """
+ # Traditionally, the cap- and joinstyle for FancyArrowPatch are round
+ kwargs.setdefault("joinstyle", JoinStyle.round)
+ kwargs.setdefault("capstyle", CapStyle.round)
+
+ super().__init__(**kwargs)
+
+ if posA is not None and posB is not None and path is None:
+ self._posA_posB = [posA, posB]
+
+ if connectionstyle is None:
+ connectionstyle = "arc3"
+ self.set_connectionstyle(connectionstyle)
+
+ elif posA is None and posB is None and path is not None:
+ self._posA_posB = None
+ else:
+ raise ValueError("Either posA and posB, or path need to provided")
+
+ self.patchA = patchA
+ self.patchB = patchB
+ self.shrinkA = shrinkA
+ self.shrinkB = shrinkB
+
+ self._path_original = path
+
+ self.set_arrowstyle(arrowstyle)
+
+ self._mutation_scale = mutation_scale
+ self._mutation_aspect = mutation_aspect
+
+ self._dpi_cor = 1.0
+
+ def set_positions(self, posA, posB):
+ """
+ Set the start and end positions of the connecting path.
+
+ Parameters
+ ----------
+ posA, posB : None, tuple
+ (x, y) coordinates of arrow tail and arrow head respectively. If
+ `None` use current value.
+ """
+ if posA is not None:
+ self._posA_posB[0] = posA
+ if posB is not None:
+ self._posA_posB[1] = posB
+ self.stale = True
+
+ def set_patchA(self, patchA):
+ """
+ Set the tail patch.
+
+ Parameters
+ ----------
+ patchA : `.patches.Patch`
+ """
+ self.patchA = patchA
+ self.stale = True
+
+ def set_patchB(self, patchB):
+ """
+ Set the head patch.
+
+ Parameters
+ ----------
+ patchB : `.patches.Patch`
+ """
+ self.patchB = patchB
+ self.stale = True
+
+ @_docstring.interpd
+ def set_connectionstyle(self, connectionstyle=None, **kwargs):
+ """
+ Set the connection style, possibly with further attributes.
+
+ Attributes from the previous connection style are not reused.
+
+ Without argument (or with ``connectionstyle=None``), the available box
+ styles are returned as a human-readable string.
+
+ Parameters
+ ----------
+ connectionstyle : str or `~matplotlib.patches.ConnectionStyle`
+ The style of the connection: either a `.ConnectionStyle` instance,
+ or a string, which is the style name and optionally comma separated
+ attributes (e.g. "Arc,armA=30,rad=10"). Such a string is used to
+ construct a `.ConnectionStyle` object, as documented in that class.
+
+ The following connection styles are available:
+
+ %(ConnectionStyle:table_and_accepts)s
+
+ **kwargs
+ Additional attributes for the connection style. See the table above
+ for supported parameters.
+
+ Examples
+ --------
+ ::
+
+ set_connectionstyle("Arc,armA=30,rad=10")
+ set_connectionstyle("arc", armA=30, rad=10)
+ """
+ if connectionstyle is None:
+ return ConnectionStyle.pprint_styles()
+ self._connector = (
+ ConnectionStyle(connectionstyle, **kwargs)
+ if isinstance(connectionstyle, str) else connectionstyle)
+ self.stale = True
+
+ def get_connectionstyle(self):
+ """Return the `ConnectionStyle` used."""
+ return self._connector
+
+ @_docstring.interpd
+ def set_arrowstyle(self, arrowstyle=None, **kwargs):
+ """
+ Set the arrow style, possibly with further attributes.
+
+ Attributes from the previous arrow style are not reused.
+
+ Without argument (or with ``arrowstyle=None``), the available box
+ styles are returned as a human-readable string.
+
+ Parameters
+ ----------
+ arrowstyle : str or `~matplotlib.patches.ArrowStyle`
+ The style of the arrow: either a `.ArrowStyle` instance, or a
+ string, which is the style name and optionally comma separated
+ attributes (e.g. "Fancy,head_length=0.2"). Such a string is used to
+ construct a `.ArrowStyle` object, as documented in that class.
+
+ The following arrow styles are available:
+
+ %(ArrowStyle:table_and_accepts)s
+
+ **kwargs
+ Additional attributes for the arrow style. See the table above for
+ supported parameters.
+
+ Examples
+ --------
+ ::
+
+ set_arrowstyle("Fancy,head_length=0.2")
+ set_arrowstyle("fancy", head_length=0.2)
+ """
+ if arrowstyle is None:
+ return ArrowStyle.pprint_styles()
+ self._arrow_transmuter = (
+ ArrowStyle(arrowstyle, **kwargs)
+ if isinstance(arrowstyle, str) else arrowstyle)
+ self.stale = True
+
+ def get_arrowstyle(self):
+ """Return the arrowstyle object."""
+ return self._arrow_transmuter
+
+ def set_mutation_scale(self, scale):
+ """
+ Set the mutation scale.
+
+ Parameters
+ ----------
+ scale : float
+ """
+ self._mutation_scale = scale
+ self.stale = True
+
+ def get_mutation_scale(self):
+ """
+ Return the mutation scale.
+
+ Returns
+ -------
+ scalar
+ """
+ return self._mutation_scale
+
+ def set_mutation_aspect(self, aspect):
+ """
+ Set the aspect ratio of the bbox mutation.
+
+ Parameters
+ ----------
+ aspect : float
+ """
+ self._mutation_aspect = aspect
+ self.stale = True
+
+ def get_mutation_aspect(self):
+ """Return the aspect ratio of the bbox mutation."""
+ return (self._mutation_aspect if self._mutation_aspect is not None
+ else 1) # backcompat.
+
+ def get_path(self):
+ """Return the path of the arrow in the data coordinates."""
+ # The path is generated in display coordinates, then converted back to
+ # data coordinates.
+ _path, fillable = self._get_path_in_displaycoord()
+ if np.iterable(fillable):
+ _path = Path.make_compound_path(*_path)
+ return self.get_transform().inverted().transform_path(_path)
+
+ def _get_path_in_displaycoord(self):
+ """Return the mutated path of the arrow in display coordinates."""
+ dpi_cor = self._dpi_cor
+
+ if self._posA_posB is not None:
+ posA = self._convert_xy_units(self._posA_posB[0])
+ posB = self._convert_xy_units(self._posA_posB[1])
+ (posA, posB) = self.get_transform().transform((posA, posB))
+ _path = self.get_connectionstyle()(posA, posB,
+ patchA=self.patchA,
+ patchB=self.patchB,
+ shrinkA=self.shrinkA * dpi_cor,
+ shrinkB=self.shrinkB * dpi_cor
+ )
+ else:
+ _path = self.get_transform().transform_path(self._path_original)
+
+ _path, fillable = self.get_arrowstyle()(
+ _path,
+ self.get_mutation_scale() * dpi_cor,
+ self.get_linewidth() * dpi_cor,
+ self.get_mutation_aspect())
+
+ return _path, fillable
+
+ def draw(self, renderer):
+ if not self.get_visible():
+ return
+
+ # FIXME: dpi_cor is for the dpi-dependency of the linewidth. There
+ # could be room for improvement. Maybe _get_path_in_displaycoord could
+ # take a renderer argument, but get_path should be adapted too.
+ self._dpi_cor = renderer.points_to_pixels(1.)
+ path, fillable = self._get_path_in_displaycoord()
+
+ if not np.iterable(fillable):
+ path = [path]
+ fillable = [fillable]
+
+ affine = transforms.IdentityTransform()
+
+ self._draw_paths_with_artist_properties(
+ renderer,
+ [(p, affine, self._facecolor if f and self._facecolor[3] else None)
+ for p, f in zip(path, fillable)])
+
+
+class ConnectionPatch(FancyArrowPatch):
+ """A patch that connects two points (possibly in different Axes)."""
+
+ def __str__(self):
+ return "ConnectionPatch((%g, %g), (%g, %g))" % \
+ (self.xy1[0], self.xy1[1], self.xy2[0], self.xy2[1])
+
+ @_docstring.interpd
+ def __init__(self, xyA, xyB, coordsA, coordsB=None, *,
+ axesA=None, axesB=None,
+ arrowstyle="-",
+ connectionstyle="arc3",
+ patchA=None,
+ patchB=None,
+ shrinkA=0.,
+ shrinkB=0.,
+ mutation_scale=10.,
+ mutation_aspect=None,
+ clip_on=False,
+ **kwargs):
+ """
+ Connect point *xyA* in *coordsA* with point *xyB* in *coordsB*.
+
+ Valid keys are
+
+ =============== ======================================================
+ Key Description
+ =============== ======================================================
+ arrowstyle the arrow style
+ connectionstyle the connection style
+ relpos default is (0.5, 0.5)
+ patchA default is bounding box of the text
+ patchB default is None
+ shrinkA default is 2 points
+ shrinkB default is 2 points
+ mutation_scale default is text size (in points)
+ mutation_aspect default is 1.
+ ? any key for `matplotlib.patches.PathPatch`
+ =============== ======================================================
+
+ *coordsA* and *coordsB* are strings that indicate the
+ coordinates of *xyA* and *xyB*.
+
+ ==================== ==================================================
+ Property Description
+ ==================== ==================================================
+ 'figure points' points from the lower left corner of the figure
+ 'figure pixels' pixels from the lower left corner of the figure
+ 'figure fraction' 0, 0 is lower left of figure and 1, 1 is upper
+ right
+ 'subfigure points' points from the lower left corner of the subfigure
+ 'subfigure pixels' pixels from the lower left corner of the subfigure
+ 'subfigure fraction' fraction of the subfigure, 0, 0 is lower left.
+ 'axes points' points from lower left corner of the Axes
+ 'axes pixels' pixels from lower left corner of the Axes
+ 'axes fraction' 0, 0 is lower left of Axes and 1, 1 is upper right
+ 'data' use the coordinate system of the object being
+ annotated (default)
+ 'offset points' offset (in points) from the *xy* value
+ 'polar' you can specify *theta*, *r* for the annotation,
+ even in cartesian plots. Note that if you are
+ using a polar Axes, you do not need to specify
+ polar for the coordinate system since that is the
+ native "data" coordinate system.
+ ==================== ==================================================
+
+ Alternatively they can be set to any valid
+ `~matplotlib.transforms.Transform`.
+
+ Note that 'subfigure pixels' and 'figure pixels' are the same
+ for the parent figure, so users who want code that is usable in
+ a subfigure can use 'subfigure pixels'.
+
+ .. note::
+
+ Using `ConnectionPatch` across two `~.axes.Axes` instances
+ is not directly compatible with :ref:`constrained layout
+ `. Add the artist
+ directly to the `.Figure` instead of adding it to a specific Axes,
+ or exclude it from the layout using ``con.set_in_layout(False)``.
+
+ .. code-block:: default
+
+ fig, ax = plt.subplots(1, 2, constrained_layout=True)
+ con = ConnectionPatch(..., axesA=ax[0], axesB=ax[1])
+ fig.add_artist(con)
+
+ """
+ if coordsB is None:
+ coordsB = coordsA
+ # we'll draw ourself after the artist we annotate by default
+ self.xy1 = xyA
+ self.xy2 = xyB
+ self.coords1 = coordsA
+ self.coords2 = coordsB
+
+ self.axesA = axesA
+ self.axesB = axesB
+
+ super().__init__(posA=(0, 0), posB=(1, 1),
+ arrowstyle=arrowstyle,
+ connectionstyle=connectionstyle,
+ patchA=patchA, patchB=patchB,
+ shrinkA=shrinkA, shrinkB=shrinkB,
+ mutation_scale=mutation_scale,
+ mutation_aspect=mutation_aspect,
+ clip_on=clip_on,
+ **kwargs)
+ # if True, draw annotation only if self.xy is inside the Axes
+ self._annotation_clip = None
+
+ def _get_xy(self, xy, s, axes=None):
+ """Calculate the pixel position of given point."""
+ s0 = s # For the error message, if needed.
+ if axes is None:
+ axes = self.axes
+
+ # preserve mixed type input (such as str, int)
+ x = np.array(xy[0])
+ y = np.array(xy[1])
+
+ fig = self.get_figure(root=False)
+ if s in ["figure points", "axes points"]:
+ x = x * fig.dpi / 72
+ y = y * fig.dpi / 72
+ s = s.replace("points", "pixels")
+ elif s == "figure fraction":
+ s = fig.transFigure
+ elif s == "subfigure fraction":
+ s = fig.transSubfigure
+ elif s == "axes fraction":
+ s = axes.transAxes
+
+ if s == 'data':
+ trans = axes.transData
+ x = cbook._to_unmasked_float_array(axes.xaxis.convert_units(x))
+ y = cbook._to_unmasked_float_array(axes.yaxis.convert_units(y))
+ return trans.transform((x, y))
+ elif s == 'offset points':
+ if self.xycoords == 'offset points': # prevent recursion
+ return self._get_xy(self.xy, 'data')
+ return (
+ self._get_xy(self.xy, self.xycoords) # converted data point
+ + xy * self.get_figure(root=True).dpi / 72) # converted offset
+ elif s == 'polar':
+ theta, r = x, y
+ x = r * np.cos(theta)
+ y = r * np.sin(theta)
+ trans = axes.transData
+ return trans.transform((x, y))
+ elif s == 'figure pixels':
+ # pixels from the lower left corner of the figure
+ bb = self.get_figure(root=False).figbbox
+ x = bb.x0 + x if x >= 0 else bb.x1 + x
+ y = bb.y0 + y if y >= 0 else bb.y1 + y
+ return x, y
+ elif s == 'subfigure pixels':
+ # pixels from the lower left corner of the figure
+ bb = self.get_figure(root=False).bbox
+ x = bb.x0 + x if x >= 0 else bb.x1 + x
+ y = bb.y0 + y if y >= 0 else bb.y1 + y
+ return x, y
+ elif s == 'axes pixels':
+ # pixels from the lower left corner of the Axes
+ bb = axes.bbox
+ x = bb.x0 + x if x >= 0 else bb.x1 + x
+ y = bb.y0 + y if y >= 0 else bb.y1 + y
+ return x, y
+ elif isinstance(s, transforms.Transform):
+ return s.transform(xy)
+ else:
+ raise ValueError(f"{s0} is not a valid coordinate transformation")
+
+ def set_annotation_clip(self, b):
+ """
+ Set the annotation's clipping behavior.
+
+ Parameters
+ ----------
+ b : bool or None
+ - True: The annotation will be clipped when ``self.xy`` is
+ outside the Axes.
+ - False: The annotation will always be drawn.
+ - None: The annotation will be clipped when ``self.xy`` is
+ outside the Axes and ``self.xycoords == "data"``.
+ """
+ self._annotation_clip = b
+ self.stale = True
+
+ def get_annotation_clip(self):
+ """
+ Return the clipping behavior.
+
+ See `.set_annotation_clip` for the meaning of the return value.
+ """
+ return self._annotation_clip
+
+ def _get_path_in_displaycoord(self):
+ """Return the mutated path of the arrow in display coordinates."""
+ dpi_cor = self._dpi_cor
+ posA = self._get_xy(self.xy1, self.coords1, self.axesA)
+ posB = self._get_xy(self.xy2, self.coords2, self.axesB)
+ path = self.get_connectionstyle()(
+ posA, posB,
+ patchA=self.patchA, patchB=self.patchB,
+ shrinkA=self.shrinkA * dpi_cor, shrinkB=self.shrinkB * dpi_cor,
+ )
+ path, fillable = self.get_arrowstyle()(
+ path,
+ self.get_mutation_scale() * dpi_cor,
+ self.get_linewidth() * dpi_cor,
+ self.get_mutation_aspect()
+ )
+ return path, fillable
+
+ def _check_xy(self, renderer):
+ """Check whether the annotation needs to be drawn."""
+
+ b = self.get_annotation_clip()
+
+ if b or (b is None and self.coords1 == "data"):
+ xy_pixel = self._get_xy(self.xy1, self.coords1, self.axesA)
+ if self.axesA is None:
+ axes = self.axes
+ else:
+ axes = self.axesA
+ if not axes.contains_point(xy_pixel):
+ return False
+
+ if b or (b is None and self.coords2 == "data"):
+ xy_pixel = self._get_xy(self.xy2, self.coords2, self.axesB)
+ if self.axesB is None:
+ axes = self.axes
+ else:
+ axes = self.axesB
+ if not axes.contains_point(xy_pixel):
+ return False
+
+ return True
+
+ def draw(self, renderer):
+ if not self.get_visible() or not self._check_xy(renderer):
+ return
+ super().draw(renderer)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patches.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patches.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..0645479ee5e75de2279a0d619b8860d7abe4bd32
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patches.pyi
@@ -0,0 +1,757 @@
+from . import artist
+from .axes import Axes
+from .backend_bases import RendererBase, MouseEvent
+from .path import Path
+from .transforms import Transform, Bbox
+
+from typing import Any, Literal, overload
+
+import numpy as np
+from numpy.typing import ArrayLike
+from .typing import ColorType, LineStyleType, CapStyleType, JoinStyleType
+
+class Patch(artist.Artist):
+ zorder: float
+ def __init__(
+ self,
+ *,
+ edgecolor: ColorType | None = ...,
+ facecolor: ColorType | None = ...,
+ color: ColorType | None = ...,
+ linewidth: float | None = ...,
+ linestyle: LineStyleType | None = ...,
+ antialiased: bool | None = ...,
+ hatch: str | None = ...,
+ fill: bool = ...,
+ capstyle: CapStyleType | None = ...,
+ joinstyle: JoinStyleType | None = ...,
+ **kwargs,
+ ) -> None: ...
+ def get_verts(self) -> ArrayLike: ...
+ def contains(self, mouseevent: MouseEvent, radius: float | None = None) -> tuple[bool, dict[Any, Any]]: ...
+ def contains_point(
+ self, point: tuple[float, float], radius: float | None = ...
+ ) -> bool: ...
+ def contains_points(
+ self, points: ArrayLike, radius: float | None = ...
+ ) -> np.ndarray: ...
+ def get_extents(self) -> Bbox: ...
+ def get_transform(self) -> Transform: ...
+ def get_data_transform(self) -> Transform: ...
+ def get_patch_transform(self) -> Transform: ...
+ def get_antialiased(self) -> bool: ...
+ def get_edgecolor(self) -> ColorType: ...
+ def get_facecolor(self) -> ColorType: ...
+ def get_linewidth(self) -> float: ...
+ def get_linestyle(self) -> LineStyleType: ...
+ def set_antialiased(self, aa: bool | None) -> None: ...
+ def set_edgecolor(self, color: ColorType | None) -> None: ...
+ def set_facecolor(self, color: ColorType | None) -> None: ...
+ def set_color(self, c: ColorType | None) -> None: ...
+ def set_alpha(self, alpha: float | None) -> None: ...
+ def set_linewidth(self, w: float | None) -> None: ...
+ def set_linestyle(self, ls: LineStyleType | None) -> None: ...
+ def set_fill(self, b: bool) -> None: ...
+ def get_fill(self) -> bool: ...
+ fill = property(get_fill, set_fill)
+ def set_capstyle(self, s: CapStyleType) -> None: ...
+ def get_capstyle(self) -> Literal["butt", "projecting", "round"]: ...
+ def set_joinstyle(self, s: JoinStyleType) -> None: ...
+ def get_joinstyle(self) -> Literal["miter", "round", "bevel"]: ...
+ def set_hatch(self, hatch: str) -> None: ...
+ def set_hatch_linewidth(self, lw: float) -> None: ...
+ def get_hatch_linewidth(self) -> float: ...
+ def get_hatch(self) -> str: ...
+ def get_path(self) -> Path: ...
+
+class Shadow(Patch):
+ patch: Patch
+ def __init__(self, patch: Patch, ox: float, oy: float, *, shade: float = ..., **kwargs) -> None: ...
+
+class Rectangle(Patch):
+ angle: float
+ def __init__(
+ self,
+ xy: tuple[float, float],
+ width: float,
+ height: float,
+ *,
+ angle: float = ...,
+ rotation_point: Literal["xy", "center"] | tuple[float, float] = ...,
+ **kwargs,
+ ) -> None: ...
+ @property
+ def rotation_point(self) -> Literal["xy", "center"] | tuple[float, float]: ...
+ @rotation_point.setter
+ def rotation_point(
+ self, value: Literal["xy", "center"] | tuple[float, float]
+ ) -> None: ...
+ def get_x(self) -> float: ...
+ def get_y(self) -> float: ...
+ def get_xy(self) -> tuple[float, float]: ...
+ def get_corners(self) -> np.ndarray: ...
+ def get_center(self) -> np.ndarray: ...
+ def get_width(self) -> float: ...
+ def get_height(self) -> float: ...
+ def get_angle(self) -> float: ...
+ def set_x(self, x: float) -> None: ...
+ def set_y(self, y: float) -> None: ...
+ def set_angle(self, angle: float) -> None: ...
+ def set_xy(self, xy: tuple[float, float]) -> None: ...
+ def set_width(self, w: float) -> None: ...
+ def set_height(self, h: float) -> None: ...
+ @overload
+ def set_bounds(self, args: tuple[float, float, float, float], /) -> None: ...
+ @overload
+ def set_bounds(
+ self, left: float, bottom: float, width: float, height: float, /
+ ) -> None: ...
+ def get_bbox(self) -> Bbox: ...
+ xy = property(get_xy, set_xy)
+
+class RegularPolygon(Patch):
+ xy: tuple[float, float]
+ numvertices: int
+ orientation: float
+ radius: float
+ def __init__(
+ self,
+ xy: tuple[float, float],
+ numVertices: int,
+ *,
+ radius: float = ...,
+ orientation: float = ...,
+ **kwargs,
+ ) -> None: ...
+
+class PathPatch(Patch):
+ def __init__(self, path: Path, **kwargs) -> None: ...
+ def set_path(self, path: Path) -> None: ...
+
+class StepPatch(PathPatch):
+ orientation: Literal["vertical", "horizontal"]
+ def __init__(
+ self,
+ values: ArrayLike,
+ edges: ArrayLike,
+ *,
+ orientation: Literal["vertical", "horizontal"] = ...,
+ baseline: float = ...,
+ **kwargs,
+ ) -> None: ...
+
+ # NamedTuple StairData, defined in body of method
+ def get_data(self) -> tuple[np.ndarray, np.ndarray, float]: ...
+ def set_data(
+ self,
+ values: ArrayLike | None = ...,
+ edges: ArrayLike | None = ...,
+ baseline: float | None = ...,
+ ) -> None: ...
+
+class Polygon(Patch):
+ def __init__(self, xy: ArrayLike, *, closed: bool = ..., **kwargs) -> None: ...
+ def get_closed(self) -> bool: ...
+ def set_closed(self, closed: bool) -> None: ...
+ def get_xy(self) -> np.ndarray: ...
+ def set_xy(self, xy: ArrayLike) -> None: ...
+ xy = property(get_xy, set_xy)
+
+class Wedge(Patch):
+ center: tuple[float, float]
+ r: float
+ theta1: float
+ theta2: float
+ width: float | None
+ def __init__(
+ self,
+ center: tuple[float, float],
+ r: float,
+ theta1: float,
+ theta2: float,
+ *,
+ width: float | None = ...,
+ **kwargs,
+ ) -> None: ...
+ def set_center(self, center: tuple[float, float]) -> None: ...
+ def set_radius(self, radius: float) -> None: ...
+ def set_theta1(self, theta1: float) -> None: ...
+ def set_theta2(self, theta2: float) -> None: ...
+ def set_width(self, width: float | None) -> None: ...
+
+class Arrow(Patch):
+ def __init__(
+ self, x: float, y: float, dx: float, dy: float, *, width: float = ..., **kwargs
+ ) -> None: ...
+ def set_data(
+ self,
+ x: float | None = ...,
+ y: float | None = ...,
+ dx: float | None = ...,
+ dy: float | None = ...,
+ width: float | None = ...,
+ ) -> None: ...
+class FancyArrow(Polygon):
+ def __init__(
+ self,
+ x: float,
+ y: float,
+ dx: float,
+ dy: float,
+ *,
+ width: float = ...,
+ length_includes_head: bool = ...,
+ head_width: float | None = ...,
+ head_length: float | None = ...,
+ shape: Literal["full", "left", "right"] = ...,
+ overhang: float = ...,
+ head_starts_at_zero: bool = ...,
+ **kwargs,
+ ) -> None: ...
+ def set_data(
+ self,
+ *,
+ x: float | None = ...,
+ y: float | None = ...,
+ dx: float | None = ...,
+ dy: float | None = ...,
+ width: float | None = ...,
+ head_width: float | None = ...,
+ head_length: float | None = ...,
+ ) -> None: ...
+
+class CirclePolygon(RegularPolygon):
+ def __init__(
+ self,
+ xy: tuple[float, float],
+ radius: float = ...,
+ *,
+ resolution: int = ...,
+ **kwargs,
+ ) -> None: ...
+
+class Ellipse(Patch):
+ def __init__(
+ self,
+ xy: tuple[float, float],
+ width: float,
+ height: float,
+ *,
+ angle: float = ...,
+ **kwargs,
+ ) -> None: ...
+ def set_center(self, xy: tuple[float, float]) -> None: ...
+ def get_center(self) -> float: ...
+ center = property(get_center, set_center)
+
+ def set_width(self, width: float) -> None: ...
+ def get_width(self) -> float: ...
+ width = property(get_width, set_width)
+
+ def set_height(self, height: float) -> None: ...
+ def get_height(self) -> float: ...
+ height = property(get_height, set_height)
+
+ def set_angle(self, angle: float) -> None: ...
+ def get_angle(self) -> float: ...
+ angle = property(get_angle, set_angle)
+
+ def get_corners(self) -> np.ndarray: ...
+
+ def get_vertices(self) -> list[tuple[float, float]]: ...
+ def get_co_vertices(self) -> list[tuple[float, float]]: ...
+
+
+class Annulus(Patch):
+ a: float
+ b: float
+ def __init__(
+ self,
+ xy: tuple[float, float],
+ r: float | tuple[float, float],
+ width: float,
+ angle: float = ...,
+ **kwargs,
+ ) -> None: ...
+ def set_center(self, xy: tuple[float, float]) -> None: ...
+ def get_center(self) -> tuple[float, float]: ...
+ center = property(get_center, set_center)
+
+ def set_width(self, width: float) -> None: ...
+ def get_width(self) -> float: ...
+ width = property(get_width, set_width)
+
+ def set_angle(self, angle: float) -> None: ...
+ def get_angle(self) -> float: ...
+ angle = property(get_angle, set_angle)
+
+ def set_semimajor(self, a: float) -> None: ...
+ def set_semiminor(self, b: float) -> None: ...
+ def set_radii(self, r: float | tuple[float, float]) -> None: ...
+ def get_radii(self) -> tuple[float, float]: ...
+ radii = property(get_radii, set_radii)
+
+class Circle(Ellipse):
+ def __init__(
+ self, xy: tuple[float, float], radius: float = ..., **kwargs
+ ) -> None: ...
+ def set_radius(self, radius: float) -> None: ...
+ def get_radius(self) -> float: ...
+ radius = property(get_radius, set_radius)
+
+class Arc(Ellipse):
+ theta1: float
+ theta2: float
+ def __init__(
+ self,
+ xy: tuple[float, float],
+ width: float,
+ height: float,
+ *,
+ angle: float = ...,
+ theta1: float = ...,
+ theta2: float = ...,
+ **kwargs,
+ ) -> None: ...
+
+def bbox_artist(
+ artist: artist.Artist,
+ renderer: RendererBase,
+ props: dict[str, Any] | None = ...,
+ fill: bool = ...,
+) -> None: ...
+def draw_bbox(
+ bbox: Bbox,
+ renderer: RendererBase,
+ color: ColorType = ...,
+ trans: Transform | None = ...,
+) -> None: ...
+
+class _Style:
+ def __new__(cls, stylename, **kwargs): ...
+ @classmethod
+ def get_styles(cls) -> dict[str, type]: ...
+ @classmethod
+ def pprint_styles(cls) -> str: ...
+ @classmethod
+ def register(cls, name: str, style: type) -> None: ...
+
+class BoxStyle(_Style):
+ class Square(BoxStyle):
+ pad: float
+ def __init__(self, pad: float = ...) -> None: ...
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+ class Circle(BoxStyle):
+ pad: float
+ def __init__(self, pad: float = ...) -> None: ...
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+ class Ellipse(BoxStyle):
+ pad: float
+ def __init__(self, pad: float = ...) -> None: ...
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+ class LArrow(BoxStyle):
+ pad: float
+ def __init__(self, pad: float = ...) -> None: ...
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+ class RArrow(LArrow):
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+ class DArrow(BoxStyle):
+ pad: float
+ def __init__(self, pad: float = ...) -> None: ...
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+ class Round(BoxStyle):
+ pad: float
+ rounding_size: float | None
+ def __init__(
+ self, pad: float = ..., rounding_size: float | None = ...
+ ) -> None: ...
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+ class Round4(BoxStyle):
+ pad: float
+ rounding_size: float | None
+ def __init__(
+ self, pad: float = ..., rounding_size: float | None = ...
+ ) -> None: ...
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+ class Sawtooth(BoxStyle):
+ pad: float
+ tooth_size: float | None
+ def __init__(
+ self, pad: float = ..., tooth_size: float | None = ...
+ ) -> None: ...
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+ class Roundtooth(Sawtooth):
+ def __call__(
+ self,
+ x0: float,
+ y0: float,
+ width: float,
+ height: float,
+ mutation_size: float,
+ ) -> Path: ...
+
+class ConnectionStyle(_Style):
+ class _Base(ConnectionStyle):
+ def __call__(
+ self,
+ posA: tuple[float, float],
+ posB: tuple[float, float],
+ shrinkA: float = ...,
+ shrinkB: float = ...,
+ patchA: Patch | None = ...,
+ patchB: Patch | None = ...,
+ ) -> Path: ...
+
+ class Arc3(_Base):
+ rad: float
+ def __init__(self, rad: float = ...) -> None: ...
+ def connect(
+ self, posA: tuple[float, float], posB: tuple[float, float]
+ ) -> Path: ...
+
+ class Angle3(_Base):
+ angleA: float
+ angleB: float
+ def __init__(self, angleA: float = ..., angleB: float = ...) -> None: ...
+ def connect(
+ self, posA: tuple[float, float], posB: tuple[float, float]
+ ) -> Path: ...
+
+ class Angle(_Base):
+ angleA: float
+ angleB: float
+ rad: float
+ def __init__(
+ self, angleA: float = ..., angleB: float = ..., rad: float = ...
+ ) -> None: ...
+ def connect(
+ self, posA: tuple[float, float], posB: tuple[float, float]
+ ) -> Path: ...
+
+ class Arc(_Base):
+ angleA: float
+ angleB: float
+ armA: float | None
+ armB: float | None
+ rad: float
+ def __init__(
+ self,
+ angleA: float = ...,
+ angleB: float = ...,
+ armA: float | None = ...,
+ armB: float | None = ...,
+ rad: float = ...,
+ ) -> None: ...
+ def connect(
+ self, posA: tuple[float, float], posB: tuple[float, float]
+ ) -> Path: ...
+
+ class Bar(_Base):
+ armA: float
+ armB: float
+ fraction: float
+ angle: float | None
+ def __init__(
+ self,
+ armA: float = ...,
+ armB: float = ...,
+ fraction: float = ...,
+ angle: float | None = ...,
+ ) -> None: ...
+ def connect(
+ self, posA: tuple[float, float], posB: tuple[float, float]
+ ) -> Path: ...
+
+class ArrowStyle(_Style):
+ class _Base(ArrowStyle):
+ @staticmethod
+ def ensure_quadratic_bezier(path: Path) -> list[float]: ...
+ def transmute(
+ self, path: Path, mutation_size: float, linewidth: float
+ ) -> tuple[Path, bool]: ...
+ def __call__(
+ self,
+ path: Path,
+ mutation_size: float,
+ linewidth: float,
+ aspect_ratio: float = ...,
+ ) -> tuple[Path, bool]: ...
+
+ class _Curve(_Base):
+ arrow: str
+ fillbegin: bool
+ fillend: bool
+ def __init__(
+ self,
+ head_length: float = ...,
+ head_width: float = ...,
+ widthA: float = ...,
+ widthB: float = ...,
+ lengthA: float = ...,
+ lengthB: float = ...,
+ angleA: float | None = ...,
+ angleB: float | None = ...,
+ scaleA: float | None = ...,
+ scaleB: float | None = ...,
+ ) -> None: ...
+
+ class Curve(_Curve):
+ def __init__(self) -> None: ...
+
+ class CurveA(_Curve):
+ arrow: str
+
+ class CurveB(_Curve):
+ arrow: str
+
+ class CurveAB(_Curve):
+ arrow: str
+
+ class CurveFilledA(_Curve):
+ arrow: str
+
+ class CurveFilledB(_Curve):
+ arrow: str
+
+ class CurveFilledAB(_Curve):
+ arrow: str
+
+ class BracketA(_Curve):
+ arrow: str
+ def __init__(
+ self, widthA: float = ..., lengthA: float = ..., angleA: float = ...
+ ) -> None: ...
+
+ class BracketB(_Curve):
+ arrow: str
+ def __init__(
+ self, widthB: float = ..., lengthB: float = ..., angleB: float = ...
+ ) -> None: ...
+
+ class BracketAB(_Curve):
+ arrow: str
+ def __init__(
+ self,
+ widthA: float = ...,
+ lengthA: float = ...,
+ angleA: float = ...,
+ widthB: float = ...,
+ lengthB: float = ...,
+ angleB: float = ...,
+ ) -> None: ...
+
+ class BarAB(_Curve):
+ arrow: str
+ def __init__(
+ self,
+ widthA: float = ...,
+ angleA: float = ...,
+ widthB: float = ...,
+ angleB: float = ...,
+ ) -> None: ...
+
+ class BracketCurve(_Curve):
+ arrow: str
+ def __init__(
+ self, widthA: float = ..., lengthA: float = ..., angleA: float | None = ...
+ ) -> None: ...
+
+ class CurveBracket(_Curve):
+ arrow: str
+ def __init__(
+ self, widthB: float = ..., lengthB: float = ..., angleB: float | None = ...
+ ) -> None: ...
+
+ class Simple(_Base):
+ def __init__(
+ self,
+ head_length: float = ...,
+ head_width: float = ...,
+ tail_width: float = ...,
+ ) -> None: ...
+
+ class Fancy(_Base):
+ def __init__(
+ self,
+ head_length: float = ...,
+ head_width: float = ...,
+ tail_width: float = ...,
+ ) -> None: ...
+
+ class Wedge(_Base):
+ tail_width: float
+ shrink_factor: float
+ def __init__(
+ self, tail_width: float = ..., shrink_factor: float = ...
+ ) -> None: ...
+
+class FancyBboxPatch(Patch):
+ def __init__(
+ self,
+ xy: tuple[float, float],
+ width: float,
+ height: float,
+ boxstyle: str | BoxStyle = ...,
+ *,
+ mutation_scale: float = ...,
+ mutation_aspect: float = ...,
+ **kwargs,
+ ) -> None: ...
+ def set_boxstyle(self, boxstyle: str | BoxStyle | None = ..., **kwargs) -> None: ...
+ def get_boxstyle(self) -> BoxStyle: ...
+ def set_mutation_scale(self, scale: float) -> None: ...
+ def get_mutation_scale(self) -> float: ...
+ def set_mutation_aspect(self, aspect: float) -> None: ...
+ def get_mutation_aspect(self) -> float: ...
+ def get_x(self) -> float: ...
+ def get_y(self) -> float: ...
+ def get_width(self) -> float: ...
+ def get_height(self) -> float: ...
+ def set_x(self, x: float) -> None: ...
+ def set_y(self, y: float) -> None: ...
+ def set_width(self, w: float) -> None: ...
+ def set_height(self, h: float) -> None: ...
+ @overload
+ def set_bounds(self, args: tuple[float, float, float, float], /) -> None: ...
+ @overload
+ def set_bounds(
+ self, left: float, bottom: float, width: float, height: float, /
+ ) -> None: ...
+ def get_bbox(self) -> Bbox: ...
+
+class FancyArrowPatch(Patch):
+ patchA: Patch
+ patchB: Patch
+ shrinkA: float
+ shrinkB: float
+ def __init__(
+ self,
+ posA: tuple[float, float] | None = ...,
+ posB: tuple[float, float] | None = ...,
+ *,
+ path: Path | None = ...,
+ arrowstyle: str | ArrowStyle = ...,
+ connectionstyle: str | ConnectionStyle = ...,
+ patchA: Patch | None = ...,
+ patchB: Patch | None = ...,
+ shrinkA: float = ...,
+ shrinkB: float = ...,
+ mutation_scale: float = ...,
+ mutation_aspect: float | None = ...,
+ **kwargs,
+ ) -> None: ...
+ def set_positions(
+ self, posA: tuple[float, float], posB: tuple[float, float]
+ ) -> None: ...
+ def set_patchA(self, patchA: Patch) -> None: ...
+ def set_patchB(self, patchB: Patch) -> None: ...
+ def set_connectionstyle(self, connectionstyle: str | ConnectionStyle | None = ..., **kwargs) -> None: ...
+ def get_connectionstyle(self) -> ConnectionStyle: ...
+ def set_arrowstyle(self, arrowstyle: str | ArrowStyle | None = ..., **kwargs) -> None: ...
+ def get_arrowstyle(self) -> ArrowStyle: ...
+ def set_mutation_scale(self, scale: float) -> None: ...
+ def get_mutation_scale(self) -> float: ...
+ def set_mutation_aspect(self, aspect: float | None) -> None: ...
+ def get_mutation_aspect(self) -> float: ...
+
+class ConnectionPatch(FancyArrowPatch):
+ xy1: tuple[float, float]
+ xy2: tuple[float, float]
+ coords1: str | Transform
+ coords2: str | Transform | None
+ axesA: Axes | None
+ axesB: Axes | None
+ def __init__(
+ self,
+ xyA: tuple[float, float],
+ xyB: tuple[float, float],
+ coordsA: str | Transform,
+ coordsB: str | Transform | None = ...,
+ *,
+ axesA: Axes | None = ...,
+ axesB: Axes | None = ...,
+ arrowstyle: str | ArrowStyle = ...,
+ connectionstyle: str | ConnectionStyle = ...,
+ patchA: Patch | None = ...,
+ patchB: Patch | None = ...,
+ shrinkA: float = ...,
+ shrinkB: float = ...,
+ mutation_scale: float = ...,
+ mutation_aspect: float | None = ...,
+ clip_on: bool = ...,
+ **kwargs,
+ ) -> None: ...
+ def set_annotation_clip(self, b: bool | None) -> None: ...
+ def get_annotation_clip(self) -> bool | None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/path.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/path.py
new file mode 100644
index 0000000000000000000000000000000000000000..aef2e9044c6a3ee27b47ac17ce9c35a46d66970d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/path.py
@@ -0,0 +1,1134 @@
+r"""
+A module for dealing with the polylines used throughout Matplotlib.
+
+The primary class for polyline handling in Matplotlib is `Path`. Almost all
+vector drawing makes use of `Path`\s somewhere in the drawing pipeline.
+
+Whilst a `Path` instance itself cannot be drawn, some `.Artist` subclasses,
+such as `.PathPatch` and `.PathCollection`, can be used for convenient `Path`
+visualisation.
+"""
+
+import copy
+from functools import lru_cache
+from weakref import WeakValueDictionary
+
+import numpy as np
+
+import matplotlib as mpl
+from . import _api, _path
+from .cbook import _to_unmasked_float_array, simple_linear_interpolation
+from .bezier import BezierSegment
+
+
+class Path:
+ """
+ A series of possibly disconnected, possibly closed, line and curve
+ segments.
+
+ The underlying storage is made up of two parallel numpy arrays:
+
+ - *vertices*: an (N, 2) float array of vertices
+ - *codes*: an N-length `numpy.uint8` array of path codes, or None
+
+ These two arrays always have the same length in the first
+ dimension. For example, to represent a cubic curve, you must
+ provide three vertices and three `CURVE4` codes.
+
+ The code types are:
+
+ - `STOP` : 1 vertex (ignored)
+ A marker for the end of the entire path (currently not required and
+ ignored)
+
+ - `MOVETO` : 1 vertex
+ Pick up the pen and move to the given vertex.
+
+ - `LINETO` : 1 vertex
+ Draw a line from the current position to the given vertex.
+
+ - `CURVE3` : 1 control point, 1 endpoint
+ Draw a quadratic Bézier curve from the current position, with the given
+ control point, to the given end point.
+
+ - `CURVE4` : 2 control points, 1 endpoint
+ Draw a cubic Bézier curve from the current position, with the given
+ control points, to the given end point.
+
+ - `CLOSEPOLY` : 1 vertex (ignored)
+ Draw a line segment to the start point of the current polyline.
+
+ If *codes* is None, it is interpreted as a `MOVETO` followed by a series
+ of `LINETO`.
+
+ Users of Path objects should not access the vertices and codes arrays
+ directly. Instead, they should use `iter_segments` or `cleaned` to get the
+ vertex/code pairs. This helps, in particular, to consistently handle the
+ case of *codes* being None.
+
+ Some behavior of Path objects can be controlled by rcParams. See the
+ rcParams whose keys start with 'path.'.
+
+ .. note::
+
+ The vertices and codes arrays should be treated as
+ immutable -- there are a number of optimizations and assumptions
+ made up front in the constructor that will not change when the
+ data changes.
+ """
+
+ code_type = np.uint8
+
+ # Path codes
+ STOP = code_type(0) # 1 vertex
+ MOVETO = code_type(1) # 1 vertex
+ LINETO = code_type(2) # 1 vertex
+ CURVE3 = code_type(3) # 2 vertices
+ CURVE4 = code_type(4) # 3 vertices
+ CLOSEPOLY = code_type(79) # 1 vertex
+
+ #: A dictionary mapping Path codes to the number of vertices that the
+ #: code expects.
+ NUM_VERTICES_FOR_CODE = {STOP: 1,
+ MOVETO: 1,
+ LINETO: 1,
+ CURVE3: 2,
+ CURVE4: 3,
+ CLOSEPOLY: 1}
+
+ def __init__(self, vertices, codes=None, _interpolation_steps=1,
+ closed=False, readonly=False):
+ """
+ Create a new path with the given vertices and codes.
+
+ Parameters
+ ----------
+ vertices : (N, 2) array-like
+ The path vertices, as an array, masked array or sequence of pairs.
+ Masked values, if any, will be converted to NaNs, which are then
+ handled correctly by the Agg PathIterator and other consumers of
+ path data, such as :meth:`iter_segments`.
+ codes : array-like or None, optional
+ N-length array of integers representing the codes of the path.
+ If not None, codes must be the same length as vertices.
+ If None, *vertices* will be treated as a series of line segments.
+ _interpolation_steps : int, optional
+ Used as a hint to certain projections, such as Polar, that this
+ path should be linearly interpolated immediately before drawing.
+ This attribute is primarily an implementation detail and is not
+ intended for public use.
+ closed : bool, optional
+ If *codes* is None and closed is True, vertices will be treated as
+ line segments of a closed polygon. Note that the last vertex will
+ then be ignored (as the corresponding code will be set to
+ `CLOSEPOLY`).
+ readonly : bool, optional
+ Makes the path behave in an immutable way and sets the vertices
+ and codes as read-only arrays.
+ """
+ vertices = _to_unmasked_float_array(vertices)
+ _api.check_shape((None, 2), vertices=vertices)
+
+ if codes is not None and len(vertices):
+ codes = np.asarray(codes, self.code_type)
+ if codes.ndim != 1 or len(codes) != len(vertices):
+ raise ValueError("'codes' must be a 1D list or array with the "
+ "same length of 'vertices'. "
+ f"Your vertices have shape {vertices.shape} "
+ f"but your codes have shape {codes.shape}")
+ if len(codes) and codes[0] != self.MOVETO:
+ raise ValueError("The first element of 'code' must be equal "
+ f"to 'MOVETO' ({self.MOVETO}). "
+ f"Your first code is {codes[0]}")
+ elif closed and len(vertices):
+ codes = np.empty(len(vertices), dtype=self.code_type)
+ codes[0] = self.MOVETO
+ codes[1:-1] = self.LINETO
+ codes[-1] = self.CLOSEPOLY
+
+ self._vertices = vertices
+ self._codes = codes
+ self._interpolation_steps = _interpolation_steps
+ self._update_values()
+
+ if readonly:
+ self._vertices.flags.writeable = False
+ if self._codes is not None:
+ self._codes.flags.writeable = False
+ self._readonly = True
+ else:
+ self._readonly = False
+
+ @classmethod
+ def _fast_from_codes_and_verts(cls, verts, codes, internals_from=None):
+ """
+ Create a Path instance without the expense of calling the constructor.
+
+ Parameters
+ ----------
+ verts : array-like
+ codes : array
+ internals_from : Path or None
+ If not None, another `Path` from which the attributes
+ ``should_simplify``, ``simplify_threshold``, and
+ ``interpolation_steps`` will be copied. Note that ``readonly`` is
+ never copied, and always set to ``False`` by this constructor.
+ """
+ pth = cls.__new__(cls)
+ pth._vertices = _to_unmasked_float_array(verts)
+ pth._codes = codes
+ pth._readonly = False
+ if internals_from is not None:
+ pth._should_simplify = internals_from._should_simplify
+ pth._simplify_threshold = internals_from._simplify_threshold
+ pth._interpolation_steps = internals_from._interpolation_steps
+ else:
+ pth._should_simplify = True
+ pth._simplify_threshold = mpl.rcParams['path.simplify_threshold']
+ pth._interpolation_steps = 1
+ return pth
+
+ @classmethod
+ def _create_closed(cls, vertices):
+ """
+ Create a closed polygonal path going through *vertices*.
+
+ Unlike ``Path(..., closed=True)``, *vertices* should **not** end with
+ an entry for the CLOSEPATH; this entry is added by `._create_closed`.
+ """
+ v = _to_unmasked_float_array(vertices)
+ return cls(np.concatenate([v, v[:1]]), closed=True)
+
+ def _update_values(self):
+ self._simplify_threshold = mpl.rcParams['path.simplify_threshold']
+ self._should_simplify = (
+ self._simplify_threshold > 0 and
+ mpl.rcParams['path.simplify'] and
+ len(self._vertices) >= 128 and
+ (self._codes is None or np.all(self._codes <= Path.LINETO))
+ )
+
+ @property
+ def vertices(self):
+ """The vertices of the `Path` as an (N, 2) array."""
+ return self._vertices
+
+ @vertices.setter
+ def vertices(self, vertices):
+ if self._readonly:
+ raise AttributeError("Can't set vertices on a readonly Path")
+ self._vertices = vertices
+ self._update_values()
+
+ @property
+ def codes(self):
+ """
+ The list of codes in the `Path` as a 1D array.
+
+ Each code is one of `STOP`, `MOVETO`, `LINETO`, `CURVE3`, `CURVE4` or
+ `CLOSEPOLY`. For codes that correspond to more than one vertex
+ (`CURVE3` and `CURVE4`), that code will be repeated so that the length
+ of `vertices` and `codes` is always the same.
+ """
+ return self._codes
+
+ @codes.setter
+ def codes(self, codes):
+ if self._readonly:
+ raise AttributeError("Can't set codes on a readonly Path")
+ self._codes = codes
+ self._update_values()
+
+ @property
+ def simplify_threshold(self):
+ """
+ The fraction of a pixel difference below which vertices will
+ be simplified out.
+ """
+ return self._simplify_threshold
+
+ @simplify_threshold.setter
+ def simplify_threshold(self, threshold):
+ self._simplify_threshold = threshold
+
+ @property
+ def should_simplify(self):
+ """
+ `True` if the vertices array should be simplified.
+ """
+ return self._should_simplify
+
+ @should_simplify.setter
+ def should_simplify(self, should_simplify):
+ self._should_simplify = should_simplify
+
+ @property
+ def readonly(self):
+ """
+ `True` if the `Path` is read-only.
+ """
+ return self._readonly
+
+ def copy(self):
+ """
+ Return a shallow copy of the `Path`, which will share the
+ vertices and codes with the source `Path`.
+ """
+ return copy.copy(self)
+
+ def __deepcopy__(self, memo):
+ """
+ Return a deepcopy of the `Path`. The `Path` will not be
+ readonly, even if the source `Path` is.
+ """
+ # Deepcopying arrays (vertices, codes) strips the writeable=False flag.
+ cls = type(self)
+ memo[id(self)] = p = cls.__new__(cls)
+
+ for k, v in self.__dict__.items():
+ setattr(p, k, copy.deepcopy(v, memo))
+
+ p._readonly = False
+ return p
+
+ def deepcopy(self, memo=None):
+ """
+ Return a deep copy of the `Path`. The `Path` will not be readonly,
+ even if the source `Path` is.
+
+ Parameters
+ ----------
+ memo : dict, optional
+ A dictionary to use for memoizing, passed to `copy.deepcopy`.
+
+ Returns
+ -------
+ Path
+ A deep copy of the `Path`, but not readonly.
+ """
+ return copy.deepcopy(self, memo)
+
+ @classmethod
+ def make_compound_path_from_polys(cls, XY):
+ """
+ Make a compound `Path` object to draw a number of polygons with equal
+ numbers of sides.
+
+ .. plot:: gallery/misc/histogram_path.py
+
+ Parameters
+ ----------
+ XY : (numpolys, numsides, 2) array
+ """
+ # for each poly: 1 for the MOVETO, (numsides-1) for the LINETO, 1 for
+ # the CLOSEPOLY; the vert for the closepoly is ignored but we still
+ # need it to keep the codes aligned with the vertices
+ numpolys, numsides, two = XY.shape
+ if two != 2:
+ raise ValueError("The third dimension of 'XY' must be 2")
+ stride = numsides + 1
+ nverts = numpolys * stride
+ verts = np.zeros((nverts, 2))
+ codes = np.full(nverts, cls.LINETO, dtype=cls.code_type)
+ codes[0::stride] = cls.MOVETO
+ codes[numsides::stride] = cls.CLOSEPOLY
+ for i in range(numsides):
+ verts[i::stride] = XY[:, i]
+ return cls(verts, codes)
+
+ @classmethod
+ def make_compound_path(cls, *args):
+ r"""
+ Concatenate a list of `Path`\s into a single `Path`, removing all `STOP`\s.
+ """
+ if not args:
+ return Path(np.empty([0, 2], dtype=np.float32))
+ vertices = np.concatenate([path.vertices for path in args])
+ codes = np.empty(len(vertices), dtype=cls.code_type)
+ i = 0
+ for path in args:
+ size = len(path.vertices)
+ if path.codes is None:
+ if size:
+ codes[i] = cls.MOVETO
+ codes[i+1:i+size] = cls.LINETO
+ else:
+ codes[i:i+size] = path.codes
+ i += size
+ not_stop_mask = codes != cls.STOP # Remove STOPs, as internal STOPs are a bug.
+ return cls(vertices[not_stop_mask], codes[not_stop_mask])
+
+ def __repr__(self):
+ return f"Path({self.vertices!r}, {self.codes!r})"
+
+ def __len__(self):
+ return len(self.vertices)
+
+ def iter_segments(self, transform=None, remove_nans=True, clip=None,
+ snap=False, stroke_width=1.0, simplify=None,
+ curves=True, sketch=None):
+ """
+ Iterate over all curve segments in the path.
+
+ Each iteration returns a pair ``(vertices, code)``, where ``vertices``
+ is a sequence of 1-3 coordinate pairs, and ``code`` is a `Path` code.
+
+ Additionally, this method can provide a number of standard cleanups and
+ conversions to the path.
+
+ Parameters
+ ----------
+ transform : None or :class:`~matplotlib.transforms.Transform`
+ If not None, the given affine transformation will be applied to the
+ path.
+ remove_nans : bool, optional
+ Whether to remove all NaNs from the path and skip over them using
+ MOVETO commands.
+ clip : None or (float, float, float, float), optional
+ If not None, must be a four-tuple (x1, y1, x2, y2)
+ defining a rectangle in which to clip the path.
+ snap : None or bool, optional
+ If True, snap all nodes to pixels; if False, don't snap them.
+ If None, snap if the path contains only segments
+ parallel to the x or y axes, and no more than 1024 of them.
+ stroke_width : float, optional
+ The width of the stroke being drawn (used for path snapping).
+ simplify : None or bool, optional
+ Whether to simplify the path by removing vertices
+ that do not affect its appearance. If None, use the
+ :attr:`should_simplify` attribute. See also :rc:`path.simplify`
+ and :rc:`path.simplify_threshold`.
+ curves : bool, optional
+ If True, curve segments will be returned as curve segments.
+ If False, all curves will be converted to line segments.
+ sketch : None or sequence, optional
+ If not None, must be a 3-tuple of the form
+ (scale, length, randomness), representing the sketch parameters.
+ """
+ if not len(self):
+ return
+
+ cleaned = self.cleaned(transform=transform,
+ remove_nans=remove_nans, clip=clip,
+ snap=snap, stroke_width=stroke_width,
+ simplify=simplify, curves=curves,
+ sketch=sketch)
+
+ # Cache these object lookups for performance in the loop.
+ NUM_VERTICES_FOR_CODE = self.NUM_VERTICES_FOR_CODE
+ STOP = self.STOP
+
+ vertices = iter(cleaned.vertices)
+ codes = iter(cleaned.codes)
+ for curr_vertices, code in zip(vertices, codes):
+ if code == STOP:
+ break
+ extra_vertices = NUM_VERTICES_FOR_CODE[code] - 1
+ if extra_vertices:
+ for i in range(extra_vertices):
+ next(codes)
+ curr_vertices = np.append(curr_vertices, next(vertices))
+ yield curr_vertices, code
+
+ def iter_bezier(self, **kwargs):
+ """
+ Iterate over each Bézier curve (lines included) in a `Path`.
+
+ Parameters
+ ----------
+ **kwargs
+ Forwarded to `.iter_segments`.
+
+ Yields
+ ------
+ B : `~matplotlib.bezier.BezierSegment`
+ The Bézier curves that make up the current path. Note in particular
+ that freestanding points are Bézier curves of order 0, and lines
+ are Bézier curves of order 1 (with two control points).
+ code : `~matplotlib.path.Path.code_type`
+ The code describing what kind of curve is being returned.
+ `MOVETO`, `LINETO`, `CURVE3`, and `CURVE4` correspond to
+ Bézier curves with 1, 2, 3, and 4 control points (respectively).
+ `CLOSEPOLY` is a `LINETO` with the control points correctly
+ chosen based on the start/end points of the current stroke.
+ """
+ first_vert = None
+ prev_vert = None
+ for verts, code in self.iter_segments(**kwargs):
+ if first_vert is None:
+ if code != Path.MOVETO:
+ raise ValueError("Malformed path, must start with MOVETO.")
+ if code == Path.MOVETO: # a point is like "CURVE1"
+ first_vert = verts
+ yield BezierSegment(np.array([first_vert])), code
+ elif code == Path.LINETO: # "CURVE2"
+ yield BezierSegment(np.array([prev_vert, verts])), code
+ elif code == Path.CURVE3:
+ yield BezierSegment(np.array([prev_vert, verts[:2],
+ verts[2:]])), code
+ elif code == Path.CURVE4:
+ yield BezierSegment(np.array([prev_vert, verts[:2],
+ verts[2:4], verts[4:]])), code
+ elif code == Path.CLOSEPOLY:
+ yield BezierSegment(np.array([prev_vert, first_vert])), code
+ elif code == Path.STOP:
+ return
+ else:
+ raise ValueError(f"Invalid Path.code_type: {code}")
+ prev_vert = verts[-2:]
+
+ def _iter_connected_components(self):
+ """Return subpaths split at MOVETOs."""
+ if self.codes is None:
+ yield self
+ else:
+ idxs = np.append((self.codes == Path.MOVETO).nonzero()[0], len(self.codes))
+ for sl in map(slice, idxs, idxs[1:]):
+ yield Path._fast_from_codes_and_verts(
+ self.vertices[sl], self.codes[sl], self)
+
+ def cleaned(self, transform=None, remove_nans=False, clip=None,
+ *, simplify=False, curves=False,
+ stroke_width=1.0, snap=False, sketch=None):
+ """
+ Return a new `Path` with vertices and codes cleaned according to the
+ parameters.
+
+ See Also
+ --------
+ Path.iter_segments : for details of the keyword arguments.
+ """
+ vertices, codes = _path.cleanup_path(
+ self, transform, remove_nans, clip, snap, stroke_width, simplify,
+ curves, sketch)
+ pth = Path._fast_from_codes_and_verts(vertices, codes, self)
+ if not simplify:
+ pth._should_simplify = False
+ return pth
+
+ def transformed(self, transform):
+ """
+ Return a transformed copy of the path.
+
+ See Also
+ --------
+ matplotlib.transforms.TransformedPath
+ A specialized path class that will cache the transformed result and
+ automatically update when the transform changes.
+ """
+ return Path(transform.transform(self.vertices), self.codes,
+ self._interpolation_steps)
+
+ def contains_point(self, point, transform=None, radius=0.0):
+ """
+ Return whether the area enclosed by the path contains the given point.
+
+ The path is always treated as closed; i.e. if the last code is not
+ `CLOSEPOLY` an implicit segment connecting the last vertex to the first
+ vertex is assumed.
+
+ Parameters
+ ----------
+ point : (float, float)
+ The point (x, y) to check.
+ transform : `~matplotlib.transforms.Transform`, optional
+ If not ``None``, *point* will be compared to ``self`` transformed
+ by *transform*; i.e. for a correct check, *transform* should
+ transform the path into the coordinate system of *point*.
+ radius : float, default: 0
+ Additional margin on the path in coordinates of *point*.
+ The path is extended tangentially by *radius/2*; i.e. if you would
+ draw the path with a linewidth of *radius*, all points on the line
+ would still be considered to be contained in the area. Conversely,
+ negative values shrink the area: Points on the imaginary line
+ will be considered outside the area.
+
+ Returns
+ -------
+ bool
+
+ Notes
+ -----
+ The current algorithm has some limitations:
+
+ - The result is undefined for points exactly at the boundary
+ (i.e. at the path shifted by *radius/2*).
+ - The result is undefined if there is no enclosed area, i.e. all
+ vertices are on a straight line.
+ - If bounding lines start to cross each other due to *radius* shift,
+ the result is not guaranteed to be correct.
+ """
+ if transform is not None:
+ transform = transform.frozen()
+ # `point_in_path` does not handle nonlinear transforms, so we
+ # transform the path ourselves. If *transform* is affine, letting
+ # `point_in_path` handle the transform avoids allocating an extra
+ # buffer.
+ if transform and not transform.is_affine:
+ self = transform.transform_path(self)
+ transform = None
+ return _path.point_in_path(point[0], point[1], radius, self, transform)
+
+ def contains_points(self, points, transform=None, radius=0.0):
+ """
+ Return whether the area enclosed by the path contains the given points.
+
+ The path is always treated as closed; i.e. if the last code is not
+ `CLOSEPOLY` an implicit segment connecting the last vertex to the first
+ vertex is assumed.
+
+ Parameters
+ ----------
+ points : (N, 2) array
+ The points to check. Columns contain x and y values.
+ transform : `~matplotlib.transforms.Transform`, optional
+ If not ``None``, *points* will be compared to ``self`` transformed
+ by *transform*; i.e. for a correct check, *transform* should
+ transform the path into the coordinate system of *points*.
+ radius : float, default: 0
+ Additional margin on the path in coordinates of *points*.
+ The path is extended tangentially by *radius/2*; i.e. if you would
+ draw the path with a linewidth of *radius*, all points on the line
+ would still be considered to be contained in the area. Conversely,
+ negative values shrink the area: Points on the imaginary line
+ will be considered outside the area.
+
+ Returns
+ -------
+ length-N bool array
+
+ Notes
+ -----
+ The current algorithm has some limitations:
+
+ - The result is undefined for points exactly at the boundary
+ (i.e. at the path shifted by *radius/2*).
+ - The result is undefined if there is no enclosed area, i.e. all
+ vertices are on a straight line.
+ - If bounding lines start to cross each other due to *radius* shift,
+ the result is not guaranteed to be correct.
+ """
+ if transform is not None:
+ transform = transform.frozen()
+ result = _path.points_in_path(points, radius, self, transform)
+ return result.astype('bool')
+
+ def contains_path(self, path, transform=None):
+ """
+ Return whether this (closed) path completely contains the given path.
+
+ If *transform* is not ``None``, the path will be transformed before
+ checking for containment.
+ """
+ if transform is not None:
+ transform = transform.frozen()
+ return _path.path_in_path(self, None, path, transform)
+
+ def get_extents(self, transform=None, **kwargs):
+ """
+ Get Bbox of the path.
+
+ Parameters
+ ----------
+ transform : `~matplotlib.transforms.Transform`, optional
+ Transform to apply to path before computing extents, if any.
+ **kwargs
+ Forwarded to `.iter_bezier`.
+
+ Returns
+ -------
+ matplotlib.transforms.Bbox
+ The extents of the path Bbox([[xmin, ymin], [xmax, ymax]])
+ """
+ from .transforms import Bbox
+ if transform is not None:
+ self = transform.transform_path(self)
+ if self.codes is None:
+ xys = self.vertices
+ elif len(np.intersect1d(self.codes, [Path.CURVE3, Path.CURVE4])) == 0:
+ # Optimization for the straight line case.
+ # Instead of iterating through each curve, consider
+ # each line segment's end-points
+ # (recall that STOP and CLOSEPOLY vertices are ignored)
+ xys = self.vertices[np.isin(self.codes,
+ [Path.MOVETO, Path.LINETO])]
+ else:
+ xys = []
+ for curve, code in self.iter_bezier(**kwargs):
+ # places where the derivative is zero can be extrema
+ _, dzeros = curve.axis_aligned_extrema()
+ # as can the ends of the curve
+ xys.append(curve([0, *dzeros, 1]))
+ xys = np.concatenate(xys)
+ if len(xys):
+ return Bbox([xys.min(axis=0), xys.max(axis=0)])
+ else:
+ return Bbox.null()
+
+ def intersects_path(self, other, filled=True):
+ """
+ Return whether if this path intersects another given path.
+
+ If *filled* is True, then this also returns True if one path completely
+ encloses the other (i.e., the paths are treated as filled).
+ """
+ return _path.path_intersects_path(self, other, filled)
+
+ def intersects_bbox(self, bbox, filled=True):
+ """
+ Return whether this path intersects a given `~.transforms.Bbox`.
+
+ If *filled* is True, then this also returns True if the path completely
+ encloses the `.Bbox` (i.e., the path is treated as filled).
+
+ The bounding box is always considered filled.
+ """
+ return _path.path_intersects_rectangle(
+ self, bbox.x0, bbox.y0, bbox.x1, bbox.y1, filled)
+
+ def interpolated(self, steps):
+ """
+ Return a new path with each segment divided into *steps* parts.
+
+ Codes other than `LINETO`, `MOVETO`, and `CLOSEPOLY` are not handled correctly.
+
+ Parameters
+ ----------
+ steps : int
+ The number of segments in the new path for each in the original.
+
+ Returns
+ -------
+ Path
+ The interpolated path.
+ """
+ if steps == 1 or len(self) == 0:
+ return self
+
+ if self.codes is not None and self.MOVETO in self.codes[1:]:
+ return self.make_compound_path(
+ *(p.interpolated(steps) for p in self._iter_connected_components()))
+
+ if self.codes is not None and self.CLOSEPOLY in self.codes and not np.all(
+ self.vertices[self.codes == self.CLOSEPOLY] == self.vertices[0]):
+ vertices = self.vertices.copy()
+ vertices[self.codes == self.CLOSEPOLY] = vertices[0]
+ else:
+ vertices = self.vertices
+
+ vertices = simple_linear_interpolation(vertices, steps)
+ codes = self.codes
+ if codes is not None:
+ new_codes = np.full((len(codes) - 1) * steps + 1, Path.LINETO,
+ dtype=self.code_type)
+ new_codes[0::steps] = codes
+ else:
+ new_codes = None
+ return Path(vertices, new_codes)
+
+ def to_polygons(self, transform=None, width=0, height=0, closed_only=True):
+ """
+ Convert this path to a list of polygons or polylines. Each
+ polygon/polyline is an (N, 2) array of vertices. In other words,
+ each polygon has no `MOVETO` instructions or curves. This
+ is useful for displaying in backends that do not support
+ compound paths or Bézier curves.
+
+ If *width* and *height* are both non-zero then the lines will
+ be simplified so that vertices outside of (0, 0), (width,
+ height) will be clipped.
+
+ The resulting polygons will be simplified if the
+ :attr:`Path.should_simplify` attribute of the path is `True`.
+
+ If *closed_only* is `True` (default), only closed polygons,
+ with the last point being the same as the first point, will be
+ returned. Any unclosed polylines in the path will be
+ explicitly closed. If *closed_only* is `False`, any unclosed
+ polygons in the path will be returned as unclosed polygons,
+ and the closed polygons will be returned explicitly closed by
+ setting the last point to the same as the first point.
+ """
+ if len(self.vertices) == 0:
+ return []
+
+ if transform is not None:
+ transform = transform.frozen()
+
+ if self.codes is None and (width == 0 or height == 0):
+ vertices = self.vertices
+ if closed_only:
+ if len(vertices) < 3:
+ return []
+ elif np.any(vertices[0] != vertices[-1]):
+ vertices = [*vertices, vertices[0]]
+
+ if transform is None:
+ return [vertices]
+ else:
+ return [transform.transform(vertices)]
+
+ # Deal with the case where there are curves and/or multiple
+ # subpaths (using extension code)
+ return _path.convert_path_to_polygons(
+ self, transform, width, height, closed_only)
+
+ _unit_rectangle = None
+
+ @classmethod
+ def unit_rectangle(cls):
+ """
+ Return a `Path` instance of the unit rectangle from (0, 0) to (1, 1).
+ """
+ if cls._unit_rectangle is None:
+ cls._unit_rectangle = cls([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]],
+ closed=True, readonly=True)
+ return cls._unit_rectangle
+
+ _unit_regular_polygons = WeakValueDictionary()
+
+ @classmethod
+ def unit_regular_polygon(cls, numVertices):
+ """
+ Return a :class:`Path` instance for a unit regular polygon with the
+ given *numVertices* such that the circumscribing circle has radius 1.0,
+ centered at (0, 0).
+ """
+ if numVertices <= 16:
+ path = cls._unit_regular_polygons.get(numVertices)
+ else:
+ path = None
+ if path is None:
+ theta = ((2 * np.pi / numVertices) * np.arange(numVertices + 1)
+ # This initial rotation is to make sure the polygon always
+ # "points-up".
+ + np.pi / 2)
+ verts = np.column_stack((np.cos(theta), np.sin(theta)))
+ path = cls(verts, closed=True, readonly=True)
+ if numVertices <= 16:
+ cls._unit_regular_polygons[numVertices] = path
+ return path
+
+ _unit_regular_stars = WeakValueDictionary()
+
+ @classmethod
+ def unit_regular_star(cls, numVertices, innerCircle=0.5):
+ """
+ Return a :class:`Path` for a unit regular star with the given
+ numVertices and radius of 1.0, centered at (0, 0).
+ """
+ if numVertices <= 16:
+ path = cls._unit_regular_stars.get((numVertices, innerCircle))
+ else:
+ path = None
+ if path is None:
+ ns2 = numVertices * 2
+ theta = (2*np.pi/ns2 * np.arange(ns2 + 1))
+ # This initial rotation is to make sure the polygon always
+ # "points-up"
+ theta += np.pi / 2.0
+ r = np.ones(ns2 + 1)
+ r[1::2] = innerCircle
+ verts = (r * np.vstack((np.cos(theta), np.sin(theta)))).T
+ path = cls(verts, closed=True, readonly=True)
+ if numVertices <= 16:
+ cls._unit_regular_stars[(numVertices, innerCircle)] = path
+ return path
+
+ @classmethod
+ def unit_regular_asterisk(cls, numVertices):
+ """
+ Return a :class:`Path` for a unit regular asterisk with the given
+ numVertices and radius of 1.0, centered at (0, 0).
+ """
+ return cls.unit_regular_star(numVertices, 0.0)
+
+ _unit_circle = None
+
+ @classmethod
+ def unit_circle(cls):
+ """
+ Return the readonly :class:`Path` of the unit circle.
+
+ For most cases, :func:`Path.circle` will be what you want.
+ """
+ if cls._unit_circle is None:
+ cls._unit_circle = cls.circle(center=(0, 0), radius=1,
+ readonly=True)
+ return cls._unit_circle
+
+ @classmethod
+ def circle(cls, center=(0., 0.), radius=1., readonly=False):
+ """
+ Return a `Path` representing a circle of a given radius and center.
+
+ Parameters
+ ----------
+ center : (float, float), default: (0, 0)
+ The center of the circle.
+ radius : float, default: 1
+ The radius of the circle.
+ readonly : bool
+ Whether the created path should have the "readonly" argument
+ set when creating the Path instance.
+
+ Notes
+ -----
+ The circle is approximated using 8 cubic Bézier curves, as described in
+
+ Lancaster, Don. `Approximating a Circle or an Ellipse Using Four
+ Bezier Cubic Splines `_.
+ """
+ MAGIC = 0.2652031
+ SQRTHALF = np.sqrt(0.5)
+ MAGIC45 = SQRTHALF * MAGIC
+
+ vertices = np.array([[0.0, -1.0],
+
+ [MAGIC, -1.0],
+ [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45],
+ [SQRTHALF, -SQRTHALF],
+
+ [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45],
+ [1.0, -MAGIC],
+ [1.0, 0.0],
+
+ [1.0, MAGIC],
+ [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45],
+ [SQRTHALF, SQRTHALF],
+
+ [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45],
+ [MAGIC, 1.0],
+ [0.0, 1.0],
+
+ [-MAGIC, 1.0],
+ [-SQRTHALF+MAGIC45, SQRTHALF+MAGIC45],
+ [-SQRTHALF, SQRTHALF],
+
+ [-SQRTHALF-MAGIC45, SQRTHALF-MAGIC45],
+ [-1.0, MAGIC],
+ [-1.0, 0.0],
+
+ [-1.0, -MAGIC],
+ [-SQRTHALF-MAGIC45, -SQRTHALF+MAGIC45],
+ [-SQRTHALF, -SQRTHALF],
+
+ [-SQRTHALF+MAGIC45, -SQRTHALF-MAGIC45],
+ [-MAGIC, -1.0],
+ [0.0, -1.0],
+
+ [0.0, -1.0]],
+ dtype=float)
+
+ codes = [cls.CURVE4] * 26
+ codes[0] = cls.MOVETO
+ codes[-1] = cls.CLOSEPOLY
+ return Path(vertices * radius + center, codes, readonly=readonly)
+
+ _unit_circle_righthalf = None
+
+ @classmethod
+ def unit_circle_righthalf(cls):
+ """
+ Return a `Path` of the right half of a unit circle.
+
+ See `Path.circle` for the reference on the approximation used.
+ """
+ if cls._unit_circle_righthalf is None:
+ MAGIC = 0.2652031
+ SQRTHALF = np.sqrt(0.5)
+ MAGIC45 = SQRTHALF * MAGIC
+
+ vertices = np.array(
+ [[0.0, -1.0],
+
+ [MAGIC, -1.0],
+ [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45],
+ [SQRTHALF, -SQRTHALF],
+
+ [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45],
+ [1.0, -MAGIC],
+ [1.0, 0.0],
+
+ [1.0, MAGIC],
+ [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45],
+ [SQRTHALF, SQRTHALF],
+
+ [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45],
+ [MAGIC, 1.0],
+ [0.0, 1.0],
+
+ [0.0, -1.0]],
+
+ float)
+
+ codes = np.full(14, cls.CURVE4, dtype=cls.code_type)
+ codes[0] = cls.MOVETO
+ codes[-1] = cls.CLOSEPOLY
+
+ cls._unit_circle_righthalf = cls(vertices, codes, readonly=True)
+ return cls._unit_circle_righthalf
+
+ @classmethod
+ def arc(cls, theta1, theta2, n=None, is_wedge=False):
+ """
+ Return a `Path` for the unit circle arc from angles *theta1* to
+ *theta2* (in degrees).
+
+ *theta2* is unwrapped to produce the shortest arc within 360 degrees.
+ That is, if *theta2* > *theta1* + 360, the arc will be from *theta1* to
+ *theta2* - 360 and not a full circle plus some extra overlap.
+
+ If *n* is provided, it is the number of spline segments to make.
+ If *n* is not provided, the number of spline segments is
+ determined based on the delta between *theta1* and *theta2*.
+
+ Masionobe, L. 2003. `Drawing an elliptical arc using
+ polylines, quadratic or cubic Bezier curves
+ `_.
+ """
+ halfpi = np.pi * 0.5
+
+ eta1 = theta1
+ eta2 = theta2 - 360 * np.floor((theta2 - theta1) / 360)
+ # Ensure 2pi range is not flattened to 0 due to floating-point errors,
+ # but don't try to expand existing 0 range.
+ if theta2 != theta1 and eta2 <= eta1:
+ eta2 += 360
+ eta1, eta2 = np.deg2rad([eta1, eta2])
+
+ # number of curve segments to make
+ if n is None:
+ n = int(2 ** np.ceil((eta2 - eta1) / halfpi))
+ if n < 1:
+ raise ValueError("n must be >= 1 or None")
+
+ deta = (eta2 - eta1) / n
+ t = np.tan(0.5 * deta)
+ alpha = np.sin(deta) * (np.sqrt(4.0 + 3.0 * t * t) - 1) / 3.0
+
+ steps = np.linspace(eta1, eta2, n + 1, True)
+ cos_eta = np.cos(steps)
+ sin_eta = np.sin(steps)
+
+ xA = cos_eta[:-1]
+ yA = sin_eta[:-1]
+ xA_dot = -yA
+ yA_dot = xA
+
+ xB = cos_eta[1:]
+ yB = sin_eta[1:]
+ xB_dot = -yB
+ yB_dot = xB
+
+ if is_wedge:
+ length = n * 3 + 4
+ vertices = np.zeros((length, 2), float)
+ codes = np.full(length, cls.CURVE4, dtype=cls.code_type)
+ vertices[1] = [xA[0], yA[0]]
+ codes[0:2] = [cls.MOVETO, cls.LINETO]
+ codes[-2:] = [cls.LINETO, cls.CLOSEPOLY]
+ vertex_offset = 2
+ end = length - 2
+ else:
+ length = n * 3 + 1
+ vertices = np.empty((length, 2), float)
+ codes = np.full(length, cls.CURVE4, dtype=cls.code_type)
+ vertices[0] = [xA[0], yA[0]]
+ codes[0] = cls.MOVETO
+ vertex_offset = 1
+ end = length
+
+ vertices[vertex_offset:end:3, 0] = xA + alpha * xA_dot
+ vertices[vertex_offset:end:3, 1] = yA + alpha * yA_dot
+ vertices[vertex_offset+1:end:3, 0] = xB - alpha * xB_dot
+ vertices[vertex_offset+1:end:3, 1] = yB - alpha * yB_dot
+ vertices[vertex_offset+2:end:3, 0] = xB
+ vertices[vertex_offset+2:end:3, 1] = yB
+
+ return cls(vertices, codes, readonly=True)
+
+ @classmethod
+ def wedge(cls, theta1, theta2, n=None):
+ """
+ Return a `Path` for the unit circle wedge from angles *theta1* to
+ *theta2* (in degrees).
+
+ *theta2* is unwrapped to produce the shortest wedge within 360 degrees.
+ That is, if *theta2* > *theta1* + 360, the wedge will be from *theta1*
+ to *theta2* - 360 and not a full circle plus some extra overlap.
+
+ If *n* is provided, it is the number of spline segments to make.
+ If *n* is not provided, the number of spline segments is
+ determined based on the delta between *theta1* and *theta2*.
+
+ See `Path.arc` for the reference on the approximation used.
+ """
+ return cls.arc(theta1, theta2, n, True)
+
+ @staticmethod
+ @lru_cache(8)
+ def hatch(hatchpattern, density=6):
+ """
+ Given a hatch specifier, *hatchpattern*, generates a `Path` that
+ can be used in a repeated hatching pattern. *density* is the
+ number of lines per unit square.
+ """
+ from matplotlib.hatch import get_path
+ return (get_path(hatchpattern, density)
+ if hatchpattern is not None else None)
+
+ def clip_to_bbox(self, bbox, inside=True):
+ """
+ Clip the path to the given bounding box.
+
+ The path must be made up of one or more closed polygons. This
+ algorithm will not behave correctly for unclosed paths.
+
+ If *inside* is `True`, clip to the inside of the box, otherwise
+ to the outside of the box.
+ """
+ verts = _path.clip_path_to_rect(self, bbox, inside)
+ paths = [Path(poly) for poly in verts]
+ return self.make_compound_path(*paths)
+
+
+def get_path_collection_extents(
+ master_transform, paths, transforms, offsets, offset_transform):
+ r"""
+ Get bounding box of a `.PathCollection`\s internal objects.
+
+ That is, given a sequence of `Path`\s, `.Transform`\s objects, and offsets, as found
+ in a `.PathCollection`, return the bounding box that encapsulates all of them.
+
+ Parameters
+ ----------
+ master_transform : `~matplotlib.transforms.Transform`
+ Global transformation applied to all paths.
+ paths : list of `Path`
+ transforms : list of `~matplotlib.transforms.Affine2DBase`
+ If non-empty, this overrides *master_transform*.
+ offsets : (N, 2) array-like
+ offset_transform : `~matplotlib.transforms.Affine2DBase`
+ Transform applied to the offsets before offsetting the path.
+
+ Notes
+ -----
+ The way that *paths*, *transforms* and *offsets* are combined follows the same
+ method as for collections: each is iterated over independently, so if you have 3
+ paths (A, B, C), 2 transforms (α, β) and 1 offset (O), their combinations are as
+ follows:
+
+ - (A, α, O)
+ - (B, β, O)
+ - (C, α, O)
+ """
+ from .transforms import Bbox
+ if len(paths) == 0:
+ raise ValueError("No paths provided")
+ if len(offsets) == 0:
+ raise ValueError("No offsets provided")
+ extents, minpos = _path.get_path_collection_extents(
+ master_transform, paths, np.atleast_3d(transforms),
+ offsets, offset_transform)
+ return Bbox.from_extents(*extents, minpos=minpos)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/path.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/path.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..8a5a5c03792ea59a3eb673b0daa126a43a16bf3b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/path.pyi
@@ -0,0 +1,140 @@
+from .bezier import BezierSegment
+from .transforms import Affine2D, Transform, Bbox
+from collections.abc import Generator, Iterable, Sequence
+
+import numpy as np
+from numpy.typing import ArrayLike
+
+from typing import Any, overload
+
+class Path:
+ code_type: type[np.uint8]
+ STOP: np.uint8
+ MOVETO: np.uint8
+ LINETO: np.uint8
+ CURVE3: np.uint8
+ CURVE4: np.uint8
+ CLOSEPOLY: np.uint8
+ NUM_VERTICES_FOR_CODE: dict[np.uint8, int]
+
+ def __init__(
+ self,
+ vertices: ArrayLike,
+ codes: ArrayLike | None = ...,
+ _interpolation_steps: int = ...,
+ closed: bool = ...,
+ readonly: bool = ...,
+ ) -> None: ...
+ @property
+ def vertices(self) -> ArrayLike: ...
+ @vertices.setter
+ def vertices(self, vertices: ArrayLike) -> None: ...
+ @property
+ def codes(self) -> ArrayLike | None: ...
+ @codes.setter
+ def codes(self, codes: ArrayLike) -> None: ...
+ @property
+ def simplify_threshold(self) -> float: ...
+ @simplify_threshold.setter
+ def simplify_threshold(self, threshold: float) -> None: ...
+ @property
+ def should_simplify(self) -> bool: ...
+ @should_simplify.setter
+ def should_simplify(self, should_simplify: bool) -> None: ...
+ @property
+ def readonly(self) -> bool: ...
+ def copy(self) -> Path: ...
+ def __deepcopy__(self, memo: dict[int, Any]) -> Path: ...
+ def deepcopy(self, memo: dict[int, Any] | None = None) -> Path: ...
+
+ @classmethod
+ def make_compound_path_from_polys(cls, XY: ArrayLike) -> Path: ...
+ @classmethod
+ def make_compound_path(cls, *args: Path) -> Path: ...
+ def __len__(self) -> int: ...
+ def iter_segments(
+ self,
+ transform: Transform | None = ...,
+ remove_nans: bool = ...,
+ clip: tuple[float, float, float, float] | None = ...,
+ snap: bool | None = ...,
+ stroke_width: float = ...,
+ simplify: bool | None = ...,
+ curves: bool = ...,
+ sketch: tuple[float, float, float] | None = ...,
+ ) -> Generator[tuple[np.ndarray, np.uint8], None, None]: ...
+ def iter_bezier(self, **kwargs) -> Generator[BezierSegment, None, None]: ...
+ def cleaned(
+ self,
+ transform: Transform | None = ...,
+ remove_nans: bool = ...,
+ clip: tuple[float, float, float, float] | None = ...,
+ *,
+ simplify: bool | None = ...,
+ curves: bool = ...,
+ stroke_width: float = ...,
+ snap: bool | None = ...,
+ sketch: tuple[float, float, float] | None = ...
+ ) -> Path: ...
+ def transformed(self, transform: Transform) -> Path: ...
+ def contains_point(
+ self,
+ point: tuple[float, float],
+ transform: Transform | None = ...,
+ radius: float = ...,
+ ) -> bool: ...
+ def contains_points(
+ self, points: ArrayLike, transform: Transform | None = ..., radius: float = ...
+ ) -> np.ndarray: ...
+ def contains_path(self, path: Path, transform: Transform | None = ...) -> bool: ...
+ def get_extents(self, transform: Transform | None = ..., **kwargs) -> Bbox: ...
+ def intersects_path(self, other: Path, filled: bool = ...) -> bool: ...
+ def intersects_bbox(self, bbox: Bbox, filled: bool = ...) -> bool: ...
+ def interpolated(self, steps: int) -> Path: ...
+ def to_polygons(
+ self,
+ transform: Transform | None = ...,
+ width: float = ...,
+ height: float = ...,
+ closed_only: bool = ...,
+ ) -> list[ArrayLike]: ...
+ @classmethod
+ def unit_rectangle(cls) -> Path: ...
+ @classmethod
+ def unit_regular_polygon(cls, numVertices: int) -> Path: ...
+ @classmethod
+ def unit_regular_star(cls, numVertices: int, innerCircle: float = ...) -> Path: ...
+ @classmethod
+ def unit_regular_asterisk(cls, numVertices: int) -> Path: ...
+ @classmethod
+ def unit_circle(cls) -> Path: ...
+ @classmethod
+ def circle(
+ cls,
+ center: tuple[float, float] = ...,
+ radius: float = ...,
+ readonly: bool = ...,
+ ) -> Path: ...
+ @classmethod
+ def unit_circle_righthalf(cls) -> Path: ...
+ @classmethod
+ def arc(
+ cls, theta1: float, theta2: float, n: int | None = ..., is_wedge: bool = ...
+ ) -> Path: ...
+ @classmethod
+ def wedge(cls, theta1: float, theta2: float, n: int | None = ...) -> Path: ...
+ @overload
+ @staticmethod
+ def hatch(hatchpattern: str, density: float = ...) -> Path: ...
+ @overload
+ @staticmethod
+ def hatch(hatchpattern: None, density: float = ...) -> None: ...
+ def clip_to_bbox(self, bbox: Bbox, inside: bool = ...) -> Path: ...
+
+def get_path_collection_extents(
+ master_transform: Transform,
+ paths: Sequence[Path],
+ transforms: Iterable[Affine2D],
+ offsets: ArrayLike,
+ offset_transform: Affine2D,
+) -> Bbox: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patheffects.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patheffects.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7a0138bd7507a18f96e445572c59fdd2cde0e7c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patheffects.py
@@ -0,0 +1,511 @@
+"""
+Defines classes for path effects. The path effects are supported in `.Text`,
+`.Line2D` and `.Patch`.
+
+.. seealso::
+ :ref:`patheffects_guide`
+"""
+
+from matplotlib.backend_bases import RendererBase
+from matplotlib import colors as mcolors
+from matplotlib import patches as mpatches
+from matplotlib import transforms as mtransforms
+from matplotlib.path import Path
+import numpy as np
+
+
+class AbstractPathEffect:
+ """
+ A base class for path effects.
+
+ Subclasses should override the ``draw_path`` method to add effect
+ functionality.
+ """
+
+ def __init__(self, offset=(0., 0.)):
+ """
+ Parameters
+ ----------
+ offset : (float, float), default: (0, 0)
+ The (x, y) offset to apply to the path, measured in points.
+ """
+ self._offset = offset
+
+ def _offset_transform(self, renderer):
+ """Apply the offset to the given transform."""
+ return mtransforms.Affine2D().translate(
+ *map(renderer.points_to_pixels, self._offset))
+
+ def _update_gc(self, gc, new_gc_dict):
+ """
+ Update the given GraphicsContext with the given dict of properties.
+
+ The keys in the dictionary are used to identify the appropriate
+ ``set_`` method on the *gc*.
+ """
+ new_gc_dict = new_gc_dict.copy()
+
+ dashes = new_gc_dict.pop("dashes", None)
+ if dashes:
+ gc.set_dashes(**dashes)
+
+ for k, v in new_gc_dict.items():
+ set_method = getattr(gc, 'set_' + k, None)
+ if not callable(set_method):
+ raise AttributeError(f'Unknown property {k}')
+ set_method(v)
+ return gc
+
+ def draw_path(self, renderer, gc, tpath, affine, rgbFace=None):
+ """
+ Derived should override this method. The arguments are the same
+ as :meth:`matplotlib.backend_bases.RendererBase.draw_path`
+ except the first argument is a renderer.
+ """
+ # Get the real renderer, not a PathEffectRenderer.
+ if isinstance(renderer, PathEffectRenderer):
+ renderer = renderer._renderer
+ return renderer.draw_path(gc, tpath, affine, rgbFace)
+
+
+class PathEffectRenderer(RendererBase):
+ """
+ Implements a Renderer which contains another renderer.
+
+ This proxy then intercepts draw calls, calling the appropriate
+ :class:`AbstractPathEffect` draw method.
+
+ .. note::
+ Not all methods have been overridden on this RendererBase subclass.
+ It may be necessary to add further methods to extend the PathEffects
+ capabilities further.
+ """
+
+ def __init__(self, path_effects, renderer):
+ """
+ Parameters
+ ----------
+ path_effects : iterable of :class:`AbstractPathEffect`
+ The path effects which this renderer represents.
+ renderer : `~matplotlib.backend_bases.RendererBase` subclass
+
+ """
+ self._path_effects = path_effects
+ self._renderer = renderer
+
+ def copy_with_path_effect(self, path_effects):
+ return self.__class__(path_effects, self._renderer)
+
+ def __getattribute__(self, name):
+ if name in ['flipy', 'get_canvas_width_height', 'new_gc',
+ 'points_to_pixels', '_text2path', 'height', 'width']:
+ return getattr(self._renderer, name)
+ else:
+ return object.__getattribute__(self, name)
+
+ def draw_path(self, gc, tpath, affine, rgbFace=None):
+ for path_effect in self._path_effects:
+ path_effect.draw_path(self._renderer, gc, tpath, affine,
+ rgbFace)
+
+ def draw_markers(
+ self, gc, marker_path, marker_trans, path, *args, **kwargs):
+ # We do a little shimmy so that all markers are drawn for each path
+ # effect in turn. Essentially, we induce recursion (depth 1) which is
+ # terminated once we have just a single path effect to work with.
+ if len(self._path_effects) == 1:
+ # Call the base path effect function - this uses the unoptimised
+ # approach of calling "draw_path" multiple times.
+ return super().draw_markers(gc, marker_path, marker_trans, path,
+ *args, **kwargs)
+
+ for path_effect in self._path_effects:
+ renderer = self.copy_with_path_effect([path_effect])
+ # Recursively call this method, only next time we will only have
+ # one path effect.
+ renderer.draw_markers(gc, marker_path, marker_trans, path,
+ *args, **kwargs)
+
+ def draw_path_collection(self, gc, master_transform, paths, *args,
+ **kwargs):
+ # We do a little shimmy so that all paths are drawn for each path
+ # effect in turn. Essentially, we induce recursion (depth 1) which is
+ # terminated once we have just a single path effect to work with.
+ if len(self._path_effects) == 1:
+ # Call the base path effect function - this uses the unoptimised
+ # approach of calling "draw_path" multiple times.
+ return super().draw_path_collection(gc, master_transform, paths,
+ *args, **kwargs)
+
+ for path_effect in self._path_effects:
+ renderer = self.copy_with_path_effect([path_effect])
+ # Recursively call this method, only next time we will only have
+ # one path effect.
+ renderer.draw_path_collection(gc, master_transform, paths,
+ *args, **kwargs)
+
+ def open_group(self, s, gid=None):
+ return self._renderer.open_group(s, gid)
+
+ def close_group(self, s):
+ return self._renderer.close_group(s)
+
+
+class Normal(AbstractPathEffect):
+ """
+ The "identity" PathEffect.
+
+ The Normal PathEffect's sole purpose is to draw the original artist with
+ no special path effect.
+ """
+
+
+def _subclass_with_normal(effect_class):
+ """
+ Create a PathEffect class combining *effect_class* and a normal draw.
+ """
+
+ class withEffect(effect_class):
+ def draw_path(self, renderer, gc, tpath, affine, rgbFace):
+ super().draw_path(renderer, gc, tpath, affine, rgbFace)
+ renderer.draw_path(gc, tpath, affine, rgbFace)
+
+ withEffect.__name__ = f"with{effect_class.__name__}"
+ withEffect.__qualname__ = f"with{effect_class.__name__}"
+ withEffect.__doc__ = f"""
+ A shortcut PathEffect for applying `.{effect_class.__name__}` and then
+ drawing the original Artist.
+
+ With this class you can use ::
+
+ artist.set_path_effects([patheffects.with{effect_class.__name__}()])
+
+ as a shortcut for ::
+
+ artist.set_path_effects([patheffects.{effect_class.__name__}(),
+ patheffects.Normal()])
+ """
+ # Docstring inheritance doesn't work for locally-defined subclasses.
+ withEffect.draw_path.__doc__ = effect_class.draw_path.__doc__
+ return withEffect
+
+
+class Stroke(AbstractPathEffect):
+ """A line based PathEffect which re-draws a stroke."""
+
+ def __init__(self, offset=(0, 0), **kwargs):
+ """
+ The path will be stroked with its gc updated with the given
+ keyword arguments, i.e., the keyword arguments should be valid
+ gc parameter values.
+ """
+ super().__init__(offset)
+ self._gc = kwargs
+
+ def draw_path(self, renderer, gc, tpath, affine, rgbFace):
+ """Draw the path with updated gc."""
+ gc0 = renderer.new_gc() # Don't modify gc, but a copy!
+ gc0.copy_properties(gc)
+ gc0 = self._update_gc(gc0, self._gc)
+ renderer.draw_path(
+ gc0, tpath, affine + self._offset_transform(renderer), rgbFace)
+ gc0.restore()
+
+
+withStroke = _subclass_with_normal(effect_class=Stroke)
+
+
+class SimplePatchShadow(AbstractPathEffect):
+ """A simple shadow via a filled patch."""
+
+ def __init__(self, offset=(2, -2),
+ shadow_rgbFace=None, alpha=None,
+ rho=0.3, **kwargs):
+ """
+ Parameters
+ ----------
+ offset : (float, float), default: (2, -2)
+ The (x, y) offset of the shadow in points.
+ shadow_rgbFace : :mpltype:`color`
+ The shadow color.
+ alpha : float, default: 0.3
+ The alpha transparency of the created shadow patch.
+ rho : float, default: 0.3
+ A scale factor to apply to the rgbFace color if *shadow_rgbFace*
+ is not specified.
+ **kwargs
+ Extra keywords are stored and passed through to
+ :meth:`AbstractPathEffect._update_gc`.
+
+ """
+ super().__init__(offset)
+
+ if shadow_rgbFace is None:
+ self._shadow_rgbFace = shadow_rgbFace
+ else:
+ self._shadow_rgbFace = mcolors.to_rgba(shadow_rgbFace)
+
+ if alpha is None:
+ alpha = 0.3
+
+ self._alpha = alpha
+ self._rho = rho
+
+ #: The dictionary of keywords to update the graphics collection with.
+ self._gc = kwargs
+
+ def draw_path(self, renderer, gc, tpath, affine, rgbFace):
+ """
+ Overrides the standard draw_path to add the shadow offset and
+ necessary color changes for the shadow.
+ """
+ gc0 = renderer.new_gc() # Don't modify gc, but a copy!
+ gc0.copy_properties(gc)
+
+ if self._shadow_rgbFace is None:
+ r, g, b = (rgbFace or (1., 1., 1.))[:3]
+ # Scale the colors by a factor to improve the shadow effect.
+ shadow_rgbFace = (r * self._rho, g * self._rho, b * self._rho)
+ else:
+ shadow_rgbFace = self._shadow_rgbFace
+
+ gc0.set_foreground("none")
+ gc0.set_alpha(self._alpha)
+ gc0.set_linewidth(0)
+
+ gc0 = self._update_gc(gc0, self._gc)
+ renderer.draw_path(
+ gc0, tpath, affine + self._offset_transform(renderer),
+ shadow_rgbFace)
+ gc0.restore()
+
+
+withSimplePatchShadow = _subclass_with_normal(effect_class=SimplePatchShadow)
+
+
+class SimpleLineShadow(AbstractPathEffect):
+ """A simple shadow via a line."""
+
+ def __init__(self, offset=(2, -2),
+ shadow_color='k', alpha=0.3, rho=0.3, **kwargs):
+ """
+ Parameters
+ ----------
+ offset : (float, float), default: (2, -2)
+ The (x, y) offset to apply to the path, in points.
+ shadow_color : :mpltype:`color`, default: 'black'
+ The shadow color.
+ A value of ``None`` takes the original artist's color
+ with a scale factor of *rho*.
+ alpha : float, default: 0.3
+ The alpha transparency of the created shadow patch.
+ rho : float, default: 0.3
+ A scale factor to apply to the rgbFace color if *shadow_color*
+ is ``None``.
+ **kwargs
+ Extra keywords are stored and passed through to
+ :meth:`AbstractPathEffect._update_gc`.
+ """
+ super().__init__(offset)
+ if shadow_color is None:
+ self._shadow_color = shadow_color
+ else:
+ self._shadow_color = mcolors.to_rgba(shadow_color)
+ self._alpha = alpha
+ self._rho = rho
+ #: The dictionary of keywords to update the graphics collection with.
+ self._gc = kwargs
+
+ def draw_path(self, renderer, gc, tpath, affine, rgbFace):
+ """
+ Overrides the standard draw_path to add the shadow offset and
+ necessary color changes for the shadow.
+ """
+ gc0 = renderer.new_gc() # Don't modify gc, but a copy!
+ gc0.copy_properties(gc)
+
+ if self._shadow_color is None:
+ r, g, b = (gc0.get_foreground() or (1., 1., 1.))[:3]
+ # Scale the colors by a factor to improve the shadow effect.
+ shadow_rgbFace = (r * self._rho, g * self._rho, b * self._rho)
+ else:
+ shadow_rgbFace = self._shadow_color
+
+ gc0.set_foreground(shadow_rgbFace)
+ gc0.set_alpha(self._alpha)
+
+ gc0 = self._update_gc(gc0, self._gc)
+ renderer.draw_path(
+ gc0, tpath, affine + self._offset_transform(renderer))
+ gc0.restore()
+
+
+class PathPatchEffect(AbstractPathEffect):
+ """
+ Draws a `.PathPatch` instance whose Path comes from the original
+ PathEffect artist.
+ """
+
+ def __init__(self, offset=(0, 0), **kwargs):
+ """
+ Parameters
+ ----------
+ offset : (float, float), default: (0, 0)
+ The (x, y) offset to apply to the path, in points.
+ **kwargs
+ All keyword arguments are passed through to the
+ :class:`~matplotlib.patches.PathPatch` constructor. The
+ properties which cannot be overridden are "path", "clip_box"
+ "transform" and "clip_path".
+ """
+ super().__init__(offset=offset)
+ self.patch = mpatches.PathPatch([], **kwargs)
+
+ def draw_path(self, renderer, gc, tpath, affine, rgbFace):
+ self.patch._path = tpath
+ self.patch.set_transform(affine + self._offset_transform(renderer))
+ self.patch.set_clip_box(gc.get_clip_rectangle())
+ clip_path = gc.get_clip_path()
+ if clip_path and self.patch.get_clip_path() is None:
+ self.patch.set_clip_path(*clip_path)
+ self.patch.draw(renderer)
+
+
+class TickedStroke(AbstractPathEffect):
+ """
+ A line-based PathEffect which draws a path with a ticked style.
+
+ This line style is frequently used to represent constraints in
+ optimization. The ticks may be used to indicate that one side
+ of the line is invalid or to represent a closed boundary of a
+ domain (i.e. a wall or the edge of a pipe).
+
+ The spacing, length, and angle of ticks can be controlled.
+
+ This line style is sometimes referred to as a hatched line.
+
+ See also the :doc:`/gallery/misc/tickedstroke_demo` example.
+ """
+
+ def __init__(self, offset=(0, 0),
+ spacing=10.0, angle=45.0, length=np.sqrt(2),
+ **kwargs):
+ """
+ Parameters
+ ----------
+ offset : (float, float), default: (0, 0)
+ The (x, y) offset to apply to the path, in points.
+ spacing : float, default: 10.0
+ The spacing between ticks in points.
+ angle : float, default: 45.0
+ The angle between the path and the tick in degrees. The angle
+ is measured as if you were an ant walking along the curve, with
+ zero degrees pointing directly ahead, 90 to your left, -90
+ to your right, and 180 behind you. To change side of the ticks,
+ change sign of the angle.
+ length : float, default: 1.414
+ The length of the tick relative to spacing.
+ Recommended length = 1.414 (sqrt(2)) when angle=45, length=1.0
+ when angle=90 and length=2.0 when angle=60.
+ **kwargs
+ Extra keywords are stored and passed through to
+ :meth:`AbstractPathEffect._update_gc`.
+
+ Examples
+ --------
+ See :doc:`/gallery/misc/tickedstroke_demo`.
+ """
+ super().__init__(offset)
+
+ self._spacing = spacing
+ self._angle = angle
+ self._length = length
+ self._gc = kwargs
+
+ def draw_path(self, renderer, gc, tpath, affine, rgbFace):
+ """Draw the path with updated gc."""
+ # Do not modify the input! Use copy instead.
+ gc0 = renderer.new_gc()
+ gc0.copy_properties(gc)
+
+ gc0 = self._update_gc(gc0, self._gc)
+ trans = affine + self._offset_transform(renderer)
+
+ theta = -np.radians(self._angle)
+ trans_matrix = np.array([[np.cos(theta), -np.sin(theta)],
+ [np.sin(theta), np.cos(theta)]])
+
+ # Convert spacing parameter to pixels.
+ spacing_px = renderer.points_to_pixels(self._spacing)
+
+ # Transform before evaluation because to_polygons works at resolution
+ # of one -- assuming it is working in pixel space.
+ transpath = affine.transform_path(tpath)
+
+ # Evaluate path to straight line segments that can be used to
+ # construct line ticks.
+ polys = transpath.to_polygons(closed_only=False)
+
+ for p in polys:
+ x = p[:, 0]
+ y = p[:, 1]
+
+ # Can not interpolate points or draw line if only one point in
+ # polyline.
+ if x.size < 2:
+ continue
+
+ # Find distance between points on the line
+ ds = np.hypot(x[1:] - x[:-1], y[1:] - y[:-1])
+
+ # Build parametric coordinate along curve
+ s = np.concatenate(([0.0], np.cumsum(ds)))
+ s_total = s[-1]
+
+ num = int(np.ceil(s_total / spacing_px)) - 1
+ # Pick parameter values for ticks.
+ s_tick = np.linspace(spacing_px/2, s_total - spacing_px/2, num)
+
+ # Find points along the parameterized curve
+ x_tick = np.interp(s_tick, s, x)
+ y_tick = np.interp(s_tick, s, y)
+
+ # Find unit vectors in local direction of curve
+ delta_s = self._spacing * .001
+ u = (np.interp(s_tick + delta_s, s, x) - x_tick) / delta_s
+ v = (np.interp(s_tick + delta_s, s, y) - y_tick) / delta_s
+
+ # Normalize slope into unit slope vector.
+ n = np.hypot(u, v)
+ mask = n == 0
+ n[mask] = 1.0
+
+ uv = np.array([u / n, v / n]).T
+ uv[mask] = np.array([0, 0]).T
+
+ # Rotate and scale unit vector into tick vector
+ dxy = np.dot(uv, trans_matrix) * self._length * spacing_px
+
+ # Build tick endpoints
+ x_end = x_tick + dxy[:, 0]
+ y_end = y_tick + dxy[:, 1]
+
+ # Interleave ticks to form Path vertices
+ xyt = np.empty((2 * num, 2), dtype=x_tick.dtype)
+ xyt[0::2, 0] = x_tick
+ xyt[1::2, 0] = x_end
+ xyt[0::2, 1] = y_tick
+ xyt[1::2, 1] = y_end
+
+ # Build up vector of Path codes
+ codes = np.tile([Path.MOVETO, Path.LINETO], num)
+
+ # Construct and draw resulting path
+ h = Path(xyt, codes)
+ # Transform back to data space during render
+ renderer.draw_path(gc0, h, affine.inverted() + trans, rgbFace)
+
+ gc0.restore()
+
+
+withTickedStroke = _subclass_with_normal(effect_class=TickedStroke)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patheffects.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patheffects.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..2c1634ca9314700c437df83a41b2bb8a58f4dec6
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/patheffects.pyi
@@ -0,0 +1,106 @@
+from collections.abc import Iterable, Sequence
+from typing import Any
+
+from matplotlib.backend_bases import RendererBase, GraphicsContextBase
+from matplotlib.path import Path
+from matplotlib.patches import Patch
+from matplotlib.transforms import Transform
+
+from matplotlib.typing import ColorType
+
+class AbstractPathEffect:
+ def __init__(self, offset: tuple[float, float] = ...) -> None: ...
+ def draw_path(
+ self,
+ renderer: RendererBase,
+ gc: GraphicsContextBase,
+ tpath: Path,
+ affine: Transform,
+ rgbFace: ColorType | None = ...,
+ ) -> None: ...
+
+class PathEffectRenderer(RendererBase):
+ def __init__(
+ self, path_effects: Iterable[AbstractPathEffect], renderer: RendererBase
+ ) -> None: ...
+ def copy_with_path_effect(self, path_effects: Iterable[AbstractPathEffect]) -> PathEffectRenderer: ...
+ def draw_path(
+ self,
+ gc: GraphicsContextBase,
+ tpath: Path,
+ affine: Transform,
+ rgbFace: ColorType | None = ...,
+ ) -> None: ...
+ def draw_markers(
+ self,
+ gc: GraphicsContextBase,
+ marker_path: Path,
+ marker_trans: Transform,
+ path: Path,
+ *args,
+ **kwargs
+ ) -> None: ...
+ def draw_path_collection(
+ self,
+ gc: GraphicsContextBase,
+ master_transform: Transform,
+ paths: Sequence[Path],
+ *args,
+ **kwargs
+ ) -> None: ...
+ def __getattribute__(self, name: str) -> Any: ...
+
+class Normal(AbstractPathEffect): ...
+
+class Stroke(AbstractPathEffect):
+ def __init__(self, offset: tuple[float, float] = ..., **kwargs) -> None: ...
+ # rgbFace becomes non-optional
+ def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override]
+
+class withStroke(Stroke): ...
+
+class SimplePatchShadow(AbstractPathEffect):
+ def __init__(
+ self,
+ offset: tuple[float, float] = ...,
+ shadow_rgbFace: ColorType | None = ...,
+ alpha: float | None = ...,
+ rho: float = ...,
+ **kwargs
+ ) -> None: ...
+ # rgbFace becomes non-optional
+ def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override]
+
+class withSimplePatchShadow(SimplePatchShadow): ...
+
+class SimpleLineShadow(AbstractPathEffect):
+ def __init__(
+ self,
+ offset: tuple[float, float] = ...,
+ shadow_color: ColorType = ...,
+ alpha: float = ...,
+ rho: float = ...,
+ **kwargs
+ ) -> None: ...
+ # rgbFace becomes non-optional
+ def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override]
+
+class PathPatchEffect(AbstractPathEffect):
+ patch: Patch
+ def __init__(self, offset: tuple[float, float] = ..., **kwargs) -> None: ...
+ # rgbFace becomes non-optional
+ def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override]
+
+class TickedStroke(AbstractPathEffect):
+ def __init__(
+ self,
+ offset: tuple[float, float] = ...,
+ spacing: float = ...,
+ angle: float = ...,
+ length: float = ...,
+ **kwargs
+ ) -> None: ...
+ # rgbFace becomes non-optional
+ def draw_path(self, renderer: RendererBase, gc: GraphicsContextBase, tpath: Path, affine: Transform, rgbFace: ColorType) -> None: ... # type: ignore[override]
+
+class withTickedStroke(TickedStroke): ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/__init__.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7b46192a84ea047959f71183c1aa1627514fac4
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/__init__.py
@@ -0,0 +1,126 @@
+"""
+Non-separable transforms that map from data space to screen space.
+
+Projections are defined as `~.axes.Axes` subclasses. They include the
+following elements:
+
+- A transformation from data coordinates into display coordinates.
+
+- An inverse of that transformation. This is used, for example, to convert
+ mouse positions from screen space back into data space.
+
+- Transformations for the gridlines, ticks and ticklabels. Custom projections
+ will often need to place these elements in special locations, and Matplotlib
+ has a facility to help with doing so.
+
+- Setting up default values (overriding `~.axes.Axes.cla`), since the defaults
+ for a rectilinear Axes may not be appropriate.
+
+- Defining the shape of the Axes, for example, an elliptical Axes, that will be
+ used to draw the background of the plot and for clipping any data elements.
+
+- Defining custom locators and formatters for the projection. For example, in
+ a geographic projection, it may be more convenient to display the grid in
+ degrees, even if the data is in radians.
+
+- Set up interactive panning and zooming. This is left as an "advanced"
+ feature left to the reader, but there is an example of this for polar plots
+ in `matplotlib.projections.polar`.
+
+- Any additional methods for additional convenience or features.
+
+Once the projection Axes is defined, it can be used in one of two ways:
+
+- By defining the class attribute ``name``, the projection Axes can be
+ registered with `matplotlib.projections.register_projection` and subsequently
+ simply invoked by name::
+
+ fig.add_subplot(projection="my_proj_name")
+
+- For more complex, parameterisable projections, a generic "projection" object
+ may be defined which includes the method ``_as_mpl_axes``. ``_as_mpl_axes``
+ should take no arguments and return the projection's Axes subclass and a
+ dictionary of additional arguments to pass to the subclass' ``__init__``
+ method. Subsequently a parameterised projection can be initialised with::
+
+ fig.add_subplot(projection=MyProjection(param1=param1_value))
+
+ where MyProjection is an object which implements a ``_as_mpl_axes`` method.
+
+A full-fledged and heavily annotated example is in
+:doc:`/gallery/misc/custom_projection`. The polar plot functionality in
+`matplotlib.projections.polar` may also be of interest.
+"""
+
+from .. import axes, _docstring
+from .geo import AitoffAxes, HammerAxes, LambertAxes, MollweideAxes
+from .polar import PolarAxes
+
+try:
+ from mpl_toolkits.mplot3d import Axes3D
+except Exception:
+ import warnings
+ warnings.warn("Unable to import Axes3D. This may be due to multiple versions of "
+ "Matplotlib being installed (e.g. as a system package and as a pip "
+ "package). As a result, the 3D projection is not available.")
+ Axes3D = None
+
+
+class ProjectionRegistry:
+ """A mapping of registered projection names to projection classes."""
+
+ def __init__(self):
+ self._all_projection_types = {}
+
+ def register(self, *projections):
+ """Register a new set of projections."""
+ for projection in projections:
+ name = projection.name
+ self._all_projection_types[name] = projection
+
+ def get_projection_class(self, name):
+ """Get a projection class from its *name*."""
+ return self._all_projection_types[name]
+
+ def get_projection_names(self):
+ """Return the names of all projections currently registered."""
+ return sorted(self._all_projection_types)
+
+
+projection_registry = ProjectionRegistry()
+projection_registry.register(
+ axes.Axes,
+ PolarAxes,
+ AitoffAxes,
+ HammerAxes,
+ LambertAxes,
+ MollweideAxes,
+)
+if Axes3D is not None:
+ projection_registry.register(Axes3D)
+else:
+ # remove from namespace if not importable
+ del Axes3D
+
+
+def register_projection(cls):
+ projection_registry.register(cls)
+
+
+def get_projection_class(projection=None):
+ """
+ Get a projection class from its name.
+
+ If *projection* is None, a standard rectilinear projection is returned.
+ """
+ if projection is None:
+ projection = 'rectilinear'
+
+ try:
+ return projection_registry.get_projection_class(projection)
+ except KeyError as err:
+ raise ValueError("Unknown projection %r" % projection) from err
+
+
+get_projection_names = projection_registry.get_projection_names
+_docstring.interpd.register(projection_names=get_projection_names())
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/__init__.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/__init__.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..4e0d210f1c9e9d71160c32646850c4ef57494238
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/__init__.pyi
@@ -0,0 +1,20 @@
+from .geo import (
+ AitoffAxes as AitoffAxes,
+ HammerAxes as HammerAxes,
+ LambertAxes as LambertAxes,
+ MollweideAxes as MollweideAxes,
+)
+from .polar import PolarAxes as PolarAxes
+from ..axes import Axes
+
+class ProjectionRegistry:
+ def __init__(self) -> None: ...
+ def register(self, *projections: type[Axes]) -> None: ...
+ def get_projection_class(self, name: str) -> type[Axes]: ...
+ def get_projection_names(self) -> list[str]: ...
+
+projection_registry: ProjectionRegistry
+
+def register_projection(cls: type[Axes]) -> None: ...
+def get_projection_class(projection: str | None = ...) -> type[Axes]: ...
+def get_projection_names() -> list[str]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/geo.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/geo.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5ab3c746deab63e8fec0fe7053fd02eaade8cc5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/geo.py
@@ -0,0 +1,511 @@
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api
+from matplotlib.axes import Axes
+import matplotlib.axis as maxis
+from matplotlib.patches import Circle
+from matplotlib.path import Path
+import matplotlib.spines as mspines
+from matplotlib.ticker import (
+ Formatter, NullLocator, FixedLocator, NullFormatter)
+from matplotlib.transforms import Affine2D, BboxTransformTo, Transform
+
+
+class GeoAxes(Axes):
+ """An abstract base class for geographic projections."""
+
+ class ThetaFormatter(Formatter):
+ """
+ Used to format the theta tick labels. Converts the native
+ unit of radians into degrees and adds a degree symbol.
+ """
+ def __init__(self, round_to=1.0):
+ self._round_to = round_to
+
+ def __call__(self, x, pos=None):
+ degrees = round(np.rad2deg(x) / self._round_to) * self._round_to
+ return f"{degrees:0.0f}\N{DEGREE SIGN}"
+
+ RESOLUTION = 75
+
+ def _init_axis(self):
+ self.xaxis = maxis.XAxis(self, clear=False)
+ self.yaxis = maxis.YAxis(self, clear=False)
+ self.spines['geo'].register_axis(self.yaxis)
+
+ def clear(self):
+ # docstring inherited
+ super().clear()
+
+ self.set_longitude_grid(30)
+ self.set_latitude_grid(15)
+ self.set_longitude_grid_ends(75)
+ self.xaxis.set_minor_locator(NullLocator())
+ self.yaxis.set_minor_locator(NullLocator())
+ self.xaxis.set_ticks_position('none')
+ self.yaxis.set_ticks_position('none')
+ self.yaxis.set_tick_params(label1On=True)
+ # Why do we need to turn on yaxis tick labels, but
+ # xaxis tick labels are already on?
+
+ self.grid(mpl.rcParams['axes.grid'])
+
+ Axes.set_xlim(self, -np.pi, np.pi)
+ Axes.set_ylim(self, -np.pi / 2.0, np.pi / 2.0)
+
+ def _set_lim_and_transforms(self):
+ # A (possibly non-linear) projection on the (already scaled) data
+ self.transProjection = self._get_core_transform(self.RESOLUTION)
+
+ self.transAffine = self._get_affine_transform()
+
+ self.transAxes = BboxTransformTo(self.bbox)
+
+ # The complete data transformation stack -- from data all the
+ # way to display coordinates
+ self.transData = \
+ self.transProjection + \
+ self.transAffine + \
+ self.transAxes
+
+ # This is the transform for longitude ticks.
+ self._xaxis_pretransform = \
+ Affine2D() \
+ .scale(1, self._longitude_cap * 2) \
+ .translate(0, -self._longitude_cap)
+ self._xaxis_transform = \
+ self._xaxis_pretransform + \
+ self.transData
+ self._xaxis_text1_transform = \
+ Affine2D().scale(1, 0) + \
+ self.transData + \
+ Affine2D().translate(0, 4)
+ self._xaxis_text2_transform = \
+ Affine2D().scale(1, 0) + \
+ self.transData + \
+ Affine2D().translate(0, -4)
+
+ # This is the transform for latitude ticks.
+ yaxis_stretch = Affine2D().scale(np.pi * 2, 1).translate(-np.pi, 0)
+ yaxis_space = Affine2D().scale(1, 1.1)
+ self._yaxis_transform = \
+ yaxis_stretch + \
+ self.transData
+ yaxis_text_base = \
+ yaxis_stretch + \
+ self.transProjection + \
+ (yaxis_space +
+ self.transAffine +
+ self.transAxes)
+ self._yaxis_text1_transform = \
+ yaxis_text_base + \
+ Affine2D().translate(-8, 0)
+ self._yaxis_text2_transform = \
+ yaxis_text_base + \
+ Affine2D().translate(8, 0)
+
+ def _get_affine_transform(self):
+ transform = self._get_core_transform(1)
+ xscale, _ = transform.transform((np.pi, 0))
+ _, yscale = transform.transform((0, np.pi/2))
+ return Affine2D() \
+ .scale(0.5 / xscale, 0.5 / yscale) \
+ .translate(0.5, 0.5)
+
+ def get_xaxis_transform(self, which='grid'):
+ _api.check_in_list(['tick1', 'tick2', 'grid'], which=which)
+ return self._xaxis_transform
+
+ def get_xaxis_text1_transform(self, pad):
+ return self._xaxis_text1_transform, 'bottom', 'center'
+
+ def get_xaxis_text2_transform(self, pad):
+ return self._xaxis_text2_transform, 'top', 'center'
+
+ def get_yaxis_transform(self, which='grid'):
+ _api.check_in_list(['tick1', 'tick2', 'grid'], which=which)
+ return self._yaxis_transform
+
+ def get_yaxis_text1_transform(self, pad):
+ return self._yaxis_text1_transform, 'center', 'right'
+
+ def get_yaxis_text2_transform(self, pad):
+ return self._yaxis_text2_transform, 'center', 'left'
+
+ def _gen_axes_patch(self):
+ return Circle((0.5, 0.5), 0.5)
+
+ def _gen_axes_spines(self):
+ return {'geo': mspines.Spine.circular_spine(self, (0.5, 0.5), 0.5)}
+
+ def set_yscale(self, *args, **kwargs):
+ if args[0] != 'linear':
+ raise NotImplementedError
+
+ set_xscale = set_yscale
+
+ def set_xlim(self, *args, **kwargs):
+ """Not supported. Please consider using Cartopy."""
+ raise TypeError("Changing axes limits of a geographic projection is "
+ "not supported. Please consider using Cartopy.")
+
+ set_ylim = set_xlim
+ set_xbound = set_xlim
+ set_ybound = set_ylim
+
+ def invert_xaxis(self):
+ """Not supported. Please consider using Cartopy."""
+ raise TypeError("Changing axes limits of a geographic projection is "
+ "not supported. Please consider using Cartopy.")
+
+ invert_yaxis = invert_xaxis
+
+ def format_coord(self, lon, lat):
+ """Return a format string formatting the coordinate."""
+ lon, lat = np.rad2deg([lon, lat])
+ ns = 'N' if lat >= 0.0 else 'S'
+ ew = 'E' if lon >= 0.0 else 'W'
+ return ('%f\N{DEGREE SIGN}%s, %f\N{DEGREE SIGN}%s'
+ % (abs(lat), ns, abs(lon), ew))
+
+ def set_longitude_grid(self, degrees):
+ """
+ Set the number of degrees between each longitude grid.
+ """
+ # Skip -180 and 180, which are the fixed limits.
+ grid = np.arange(-180 + degrees, 180, degrees)
+ self.xaxis.set_major_locator(FixedLocator(np.deg2rad(grid)))
+ self.xaxis.set_major_formatter(self.ThetaFormatter(degrees))
+
+ def set_latitude_grid(self, degrees):
+ """
+ Set the number of degrees between each latitude grid.
+ """
+ # Skip -90 and 90, which are the fixed limits.
+ grid = np.arange(-90 + degrees, 90, degrees)
+ self.yaxis.set_major_locator(FixedLocator(np.deg2rad(grid)))
+ self.yaxis.set_major_formatter(self.ThetaFormatter(degrees))
+
+ def set_longitude_grid_ends(self, degrees):
+ """
+ Set the latitude(s) at which to stop drawing the longitude grids.
+ """
+ self._longitude_cap = np.deg2rad(degrees)
+ self._xaxis_pretransform \
+ .clear() \
+ .scale(1.0, self._longitude_cap * 2.0) \
+ .translate(0.0, -self._longitude_cap)
+
+ def get_data_ratio(self):
+ """Return the aspect ratio of the data itself."""
+ return 1.0
+
+ ### Interactive panning
+
+ def can_zoom(self):
+ """
+ Return whether this Axes supports the zoom box button functionality.
+
+ This Axes object does not support interactive zoom box.
+ """
+ return False
+
+ def can_pan(self):
+ """
+ Return whether this Axes supports the pan/zoom button functionality.
+
+ This Axes object does not support interactive pan/zoom.
+ """
+ return False
+
+ def start_pan(self, x, y, button):
+ pass
+
+ def end_pan(self):
+ pass
+
+ def drag_pan(self, button, key, x, y):
+ pass
+
+
+class _GeoTransform(Transform):
+ # Factoring out some common functionality.
+ input_dims = output_dims = 2
+
+ def __init__(self, resolution):
+ """
+ Create a new geographical transform.
+
+ Resolution is the number of steps to interpolate between each input
+ line segment to approximate its path in curved space.
+ """
+ super().__init__()
+ self._resolution = resolution
+
+ def __str__(self):
+ return f"{type(self).__name__}({self._resolution})"
+
+ def transform_path_non_affine(self, path):
+ # docstring inherited
+ ipath = path.interpolated(self._resolution)
+ return Path(self.transform(ipath.vertices), ipath.codes)
+
+
+class AitoffAxes(GeoAxes):
+ name = 'aitoff'
+
+ class AitoffTransform(_GeoTransform):
+ """The base Aitoff transform."""
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ longitude, latitude = values.T
+
+ # Pre-compute some values
+ half_long = longitude / 2.0
+ cos_latitude = np.cos(latitude)
+
+ alpha = np.arccos(cos_latitude * np.cos(half_long))
+ sinc_alpha = np.sinc(alpha / np.pi) # np.sinc is sin(pi*x)/(pi*x).
+
+ x = (cos_latitude * np.sin(half_long)) / sinc_alpha
+ y = np.sin(latitude) / sinc_alpha
+ return np.column_stack([x, y])
+
+ def inverted(self):
+ # docstring inherited
+ return AitoffAxes.InvertedAitoffTransform(self._resolution)
+
+ class InvertedAitoffTransform(_GeoTransform):
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ # MGDTODO: Math is hard ;(
+ return np.full_like(values, np.nan)
+
+ def inverted(self):
+ # docstring inherited
+ return AitoffAxes.AitoffTransform(self._resolution)
+
+ def __init__(self, *args, **kwargs):
+ self._longitude_cap = np.pi / 2.0
+ super().__init__(*args, **kwargs)
+ self.set_aspect(0.5, adjustable='box', anchor='C')
+ self.clear()
+
+ def _get_core_transform(self, resolution):
+ return self.AitoffTransform(resolution)
+
+
+class HammerAxes(GeoAxes):
+ name = 'hammer'
+
+ class HammerTransform(_GeoTransform):
+ """The base Hammer transform."""
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ longitude, latitude = values.T
+ half_long = longitude / 2.0
+ cos_latitude = np.cos(latitude)
+ sqrt2 = np.sqrt(2.0)
+ alpha = np.sqrt(1.0 + cos_latitude * np.cos(half_long))
+ x = (2.0 * sqrt2) * (cos_latitude * np.sin(half_long)) / alpha
+ y = (sqrt2 * np.sin(latitude)) / alpha
+ return np.column_stack([x, y])
+
+ def inverted(self):
+ # docstring inherited
+ return HammerAxes.InvertedHammerTransform(self._resolution)
+
+ class InvertedHammerTransform(_GeoTransform):
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ x, y = values.T
+ z = np.sqrt(1 - (x / 4) ** 2 - (y / 2) ** 2)
+ longitude = 2 * np.arctan((z * x) / (2 * (2 * z ** 2 - 1)))
+ latitude = np.arcsin(y*z)
+ return np.column_stack([longitude, latitude])
+
+ def inverted(self):
+ # docstring inherited
+ return HammerAxes.HammerTransform(self._resolution)
+
+ def __init__(self, *args, **kwargs):
+ self._longitude_cap = np.pi / 2.0
+ super().__init__(*args, **kwargs)
+ self.set_aspect(0.5, adjustable='box', anchor='C')
+ self.clear()
+
+ def _get_core_transform(self, resolution):
+ return self.HammerTransform(resolution)
+
+
+class MollweideAxes(GeoAxes):
+ name = 'mollweide'
+
+ class MollweideTransform(_GeoTransform):
+ """The base Mollweide transform."""
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ def d(theta):
+ delta = (-(theta + np.sin(theta) - pi_sin_l)
+ / (1 + np.cos(theta)))
+ return delta, np.abs(delta) > 0.001
+
+ longitude, latitude = values.T
+
+ clat = np.pi/2 - np.abs(latitude)
+ ihigh = clat < 0.087 # within 5 degrees of the poles
+ ilow = ~ihigh
+ aux = np.empty(latitude.shape, dtype=float)
+
+ if ilow.any(): # Newton-Raphson iteration
+ pi_sin_l = np.pi * np.sin(latitude[ilow])
+ theta = 2.0 * latitude[ilow]
+ delta, large_delta = d(theta)
+ while np.any(large_delta):
+ theta[large_delta] += delta[large_delta]
+ delta, large_delta = d(theta)
+ aux[ilow] = theta / 2
+
+ if ihigh.any(): # Taylor series-based approx. solution
+ e = clat[ihigh]
+ d = 0.5 * (3 * np.pi * e**2) ** (1.0/3)
+ aux[ihigh] = (np.pi/2 - d) * np.sign(latitude[ihigh])
+
+ xy = np.empty(values.shape, dtype=float)
+ xy[:, 0] = (2.0 * np.sqrt(2.0) / np.pi) * longitude * np.cos(aux)
+ xy[:, 1] = np.sqrt(2.0) * np.sin(aux)
+
+ return xy
+
+ def inverted(self):
+ # docstring inherited
+ return MollweideAxes.InvertedMollweideTransform(self._resolution)
+
+ class InvertedMollweideTransform(_GeoTransform):
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ x, y = values.T
+ # from Equations (7, 8) of
+ # https://mathworld.wolfram.com/MollweideProjection.html
+ theta = np.arcsin(y / np.sqrt(2))
+ longitude = (np.pi / (2 * np.sqrt(2))) * x / np.cos(theta)
+ latitude = np.arcsin((2 * theta + np.sin(2 * theta)) / np.pi)
+ return np.column_stack([longitude, latitude])
+
+ def inverted(self):
+ # docstring inherited
+ return MollweideAxes.MollweideTransform(self._resolution)
+
+ def __init__(self, *args, **kwargs):
+ self._longitude_cap = np.pi / 2.0
+ super().__init__(*args, **kwargs)
+ self.set_aspect(0.5, adjustable='box', anchor='C')
+ self.clear()
+
+ def _get_core_transform(self, resolution):
+ return self.MollweideTransform(resolution)
+
+
+class LambertAxes(GeoAxes):
+ name = 'lambert'
+
+ class LambertTransform(_GeoTransform):
+ """The base Lambert transform."""
+
+ def __init__(self, center_longitude, center_latitude, resolution):
+ """
+ Create a new Lambert transform. Resolution is the number of steps
+ to interpolate between each input line segment to approximate its
+ path in curved Lambert space.
+ """
+ _GeoTransform.__init__(self, resolution)
+ self._center_longitude = center_longitude
+ self._center_latitude = center_latitude
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ longitude, latitude = values.T
+ clong = self._center_longitude
+ clat = self._center_latitude
+ cos_lat = np.cos(latitude)
+ sin_lat = np.sin(latitude)
+ diff_long = longitude - clong
+ cos_diff_long = np.cos(diff_long)
+
+ inner_k = np.maximum( # Prevent divide-by-zero problems
+ 1 + np.sin(clat)*sin_lat + np.cos(clat)*cos_lat*cos_diff_long,
+ 1e-15)
+ k = np.sqrt(2 / inner_k)
+ x = k * cos_lat*np.sin(diff_long)
+ y = k * (np.cos(clat)*sin_lat - np.sin(clat)*cos_lat*cos_diff_long)
+
+ return np.column_stack([x, y])
+
+ def inverted(self):
+ # docstring inherited
+ return LambertAxes.InvertedLambertTransform(
+ self._center_longitude,
+ self._center_latitude,
+ self._resolution)
+
+ class InvertedLambertTransform(_GeoTransform):
+
+ def __init__(self, center_longitude, center_latitude, resolution):
+ _GeoTransform.__init__(self, resolution)
+ self._center_longitude = center_longitude
+ self._center_latitude = center_latitude
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ x, y = values.T
+ clong = self._center_longitude
+ clat = self._center_latitude
+ p = np.maximum(np.hypot(x, y), 1e-9)
+ c = 2 * np.arcsin(0.5 * p)
+ sin_c = np.sin(c)
+ cos_c = np.cos(c)
+
+ latitude = np.arcsin(cos_c*np.sin(clat) +
+ ((y*sin_c*np.cos(clat)) / p))
+ longitude = clong + np.arctan(
+ (x*sin_c) / (p*np.cos(clat)*cos_c - y*np.sin(clat)*sin_c))
+
+ return np.column_stack([longitude, latitude])
+
+ def inverted(self):
+ # docstring inherited
+ return LambertAxes.LambertTransform(
+ self._center_longitude,
+ self._center_latitude,
+ self._resolution)
+
+ def __init__(self, *args, center_longitude=0, center_latitude=0, **kwargs):
+ self._longitude_cap = np.pi / 2
+ self._center_longitude = center_longitude
+ self._center_latitude = center_latitude
+ super().__init__(*args, **kwargs)
+ self.set_aspect('equal', adjustable='box', anchor='C')
+ self.clear()
+
+ def clear(self):
+ # docstring inherited
+ super().clear()
+ self.yaxis.set_major_formatter(NullFormatter())
+
+ def _get_core_transform(self, resolution):
+ return self.LambertTransform(
+ self._center_longitude,
+ self._center_latitude,
+ resolution)
+
+ def _get_affine_transform(self):
+ return Affine2D() \
+ .scale(0.25) \
+ .translate(0.5, 0.5)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/geo.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/geo.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..93220f8cbcd58278b90bd18bbc58d5ef92d45d4f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/geo.pyi
@@ -0,0 +1,112 @@
+from matplotlib.axes import Axes
+from matplotlib.ticker import Formatter
+from matplotlib.transforms import Transform
+
+from typing import Any, Literal
+
+class GeoAxes(Axes):
+ class ThetaFormatter(Formatter):
+ def __init__(self, round_to: float = ...) -> None: ...
+ def __call__(self, x: float, pos: Any | None = ...): ...
+ RESOLUTION: float
+ def get_xaxis_transform(
+ self, which: Literal["tick1", "tick2", "grid"] = ...
+ ) -> Transform: ...
+ def get_xaxis_text1_transform(
+ self, pad: float
+ ) -> tuple[
+ Transform,
+ Literal["center", "top", "bottom", "baseline", "center_baseline"],
+ Literal["center", "left", "right"],
+ ]: ...
+ def get_xaxis_text2_transform(
+ self, pad: float
+ ) -> tuple[
+ Transform,
+ Literal["center", "top", "bottom", "baseline", "center_baseline"],
+ Literal["center", "left", "right"],
+ ]: ...
+ def get_yaxis_transform(
+ self, which: Literal["tick1", "tick2", "grid"] = ...
+ ) -> Transform: ...
+ def get_yaxis_text1_transform(
+ self, pad: float
+ ) -> tuple[
+ Transform,
+ Literal["center", "top", "bottom", "baseline", "center_baseline"],
+ Literal["center", "left", "right"],
+ ]: ...
+ def get_yaxis_text2_transform(
+ self, pad: float
+ ) -> tuple[
+ Transform,
+ Literal["center", "top", "bottom", "baseline", "center_baseline"],
+ Literal["center", "left", "right"],
+ ]: ...
+ def set_xlim(self, *args, **kwargs) -> tuple[float, float]: ...
+ def set_ylim(self, *args, **kwargs) -> tuple[float, float]: ...
+ def format_coord(self, lon: float, lat: float) -> str: ...
+ def set_longitude_grid(self, degrees: float) -> None: ...
+ def set_latitude_grid(self, degrees: float) -> None: ...
+ def set_longitude_grid_ends(self, degrees: float) -> None: ...
+ def get_data_ratio(self) -> float: ...
+ def can_zoom(self) -> bool: ...
+ def can_pan(self) -> bool: ...
+ def start_pan(self, x, y, button) -> None: ...
+ def end_pan(self) -> None: ...
+ def drag_pan(self, button, key, x, y) -> None: ...
+
+class _GeoTransform(Transform):
+ input_dims: int
+ output_dims: int
+ def __init__(self, resolution: int) -> None: ...
+
+class AitoffAxes(GeoAxes):
+ name: str
+
+ class AitoffTransform(_GeoTransform):
+ def inverted(self) -> AitoffAxes.InvertedAitoffTransform: ...
+
+ class InvertedAitoffTransform(_GeoTransform):
+ def inverted(self) -> AitoffAxes.AitoffTransform: ...
+
+class HammerAxes(GeoAxes):
+ name: str
+
+ class HammerTransform(_GeoTransform):
+ def inverted(self) -> HammerAxes.InvertedHammerTransform: ...
+
+ class InvertedHammerTransform(_GeoTransform):
+ def inverted(self) -> HammerAxes.HammerTransform: ...
+
+class MollweideAxes(GeoAxes):
+ name: str
+
+ class MollweideTransform(_GeoTransform):
+ def inverted(self) -> MollweideAxes.InvertedMollweideTransform: ...
+
+ class InvertedMollweideTransform(_GeoTransform):
+ def inverted(self) -> MollweideAxes.MollweideTransform: ...
+
+class LambertAxes(GeoAxes):
+ name: str
+
+ class LambertTransform(_GeoTransform):
+ def __init__(
+ self, center_longitude: float, center_latitude: float, resolution: int
+ ) -> None: ...
+ def inverted(self) -> LambertAxes.InvertedLambertTransform: ...
+
+ class InvertedLambertTransform(_GeoTransform):
+ def __init__(
+ self, center_longitude: float, center_latitude: float, resolution: int
+ ) -> None: ...
+ def inverted(self) -> LambertAxes.LambertTransform: ...
+
+ def __init__(
+ self,
+ *args,
+ center_longitude: float = ...,
+ center_latitude: float = ...,
+ **kwargs
+ ) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/polar.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/polar.py
new file mode 100644
index 0000000000000000000000000000000000000000..2630a7dac3aa300ce9f35dd0f9e64cc9bda2d279
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/polar.py
@@ -0,0 +1,1563 @@
+import math
+import types
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api, cbook
+from matplotlib.axes import Axes
+import matplotlib.axis as maxis
+import matplotlib.markers as mmarkers
+import matplotlib.patches as mpatches
+from matplotlib.path import Path
+import matplotlib.ticker as mticker
+import matplotlib.transforms as mtransforms
+from matplotlib.spines import Spine
+
+
+def _apply_theta_transforms_warn():
+ _api.warn_deprecated(
+ "3.9",
+ message=(
+ "Passing `apply_theta_transforms=True` (the default) "
+ "is deprecated since Matplotlib %(since)s. "
+ "Support for this will be removed in Matplotlib in %(removal)s. "
+ "To prevent this warning, set `apply_theta_transforms=False`, "
+ "and make sure to shift theta values before being passed to "
+ "this transform."
+ )
+ )
+
+
+class PolarTransform(mtransforms.Transform):
+ r"""
+ The base polar transform.
+
+ This transform maps polar coordinates :math:`\theta, r` into Cartesian
+ coordinates :math:`x, y = r \cos(\theta), r \sin(\theta)`
+ (but does not fully transform into Axes coordinates or
+ handle positioning in screen space).
+
+ This transformation is designed to be applied to data after any scaling
+ along the radial axis (e.g. log-scaling) has been applied to the input
+ data.
+
+ Path segments at a fixed radius are automatically transformed to circular
+ arcs as long as ``path._interpolation_steps > 1``.
+ """
+
+ input_dims = output_dims = 2
+
+ def __init__(self, axis=None, use_rmin=True, *,
+ apply_theta_transforms=True, scale_transform=None):
+ """
+ Parameters
+ ----------
+ axis : `~matplotlib.axis.Axis`, optional
+ Axis associated with this transform. This is used to get the
+ minimum radial limit.
+ use_rmin : `bool`, optional
+ If ``True``, subtract the minimum radial axis limit before
+ transforming to Cartesian coordinates. *axis* must also be
+ specified for this to take effect.
+ """
+ super().__init__()
+ self._axis = axis
+ self._use_rmin = use_rmin
+ self._apply_theta_transforms = apply_theta_transforms
+ self._scale_transform = scale_transform
+ if apply_theta_transforms:
+ _apply_theta_transforms_warn()
+
+ __str__ = mtransforms._make_str_method(
+ "_axis",
+ use_rmin="_use_rmin",
+ apply_theta_transforms="_apply_theta_transforms")
+
+ def _get_rorigin(self):
+ # Get lower r limit after being scaled by the radial scale transform
+ return self._scale_transform.transform(
+ (0, self._axis.get_rorigin()))[1]
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ theta, r = np.transpose(values)
+ # PolarAxes does not use the theta transforms here, but apply them for
+ # backwards-compatibility if not being used by it.
+ if self._apply_theta_transforms and self._axis is not None:
+ theta *= self._axis.get_theta_direction()
+ theta += self._axis.get_theta_offset()
+ if self._use_rmin and self._axis is not None:
+ r = (r - self._get_rorigin()) * self._axis.get_rsign()
+ r = np.where(r >= 0, r, np.nan)
+ return np.column_stack([r * np.cos(theta), r * np.sin(theta)])
+
+ def transform_path_non_affine(self, path):
+ # docstring inherited
+ if not len(path) or path._interpolation_steps == 1:
+ return Path(self.transform_non_affine(path.vertices), path.codes)
+ xys = []
+ codes = []
+ last_t = last_r = None
+ for trs, c in path.iter_segments():
+ trs = trs.reshape((-1, 2))
+ if c == Path.LINETO:
+ (t, r), = trs
+ if t == last_t: # Same angle: draw a straight line.
+ xys.extend(self.transform_non_affine(trs))
+ codes.append(Path.LINETO)
+ elif r == last_r: # Same radius: draw an arc.
+ # The following is complicated by Path.arc() being
+ # "helpful" and unwrapping the angles, but we don't want
+ # that behavior here.
+ last_td, td = np.rad2deg([last_t, t])
+ if self._use_rmin and self._axis is not None:
+ r = ((r - self._get_rorigin())
+ * self._axis.get_rsign())
+ if last_td <= td:
+ while td - last_td > 360:
+ arc = Path.arc(last_td, last_td + 360)
+ xys.extend(arc.vertices[1:] * r)
+ codes.extend(arc.codes[1:])
+ last_td += 360
+ arc = Path.arc(last_td, td)
+ xys.extend(arc.vertices[1:] * r)
+ codes.extend(arc.codes[1:])
+ else:
+ # The reverse version also relies on the fact that all
+ # codes but the first one are the same.
+ while last_td - td > 360:
+ arc = Path.arc(last_td - 360, last_td)
+ xys.extend(arc.vertices[::-1][1:] * r)
+ codes.extend(arc.codes[1:])
+ last_td -= 360
+ arc = Path.arc(td, last_td)
+ xys.extend(arc.vertices[::-1][1:] * r)
+ codes.extend(arc.codes[1:])
+ else: # Interpolate.
+ trs = cbook.simple_linear_interpolation(
+ np.vstack([(last_t, last_r), trs]),
+ path._interpolation_steps)[1:]
+ xys.extend(self.transform_non_affine(trs))
+ codes.extend([Path.LINETO] * len(trs))
+ else: # Not a straight line.
+ xys.extend(self.transform_non_affine(trs))
+ codes.extend([c] * len(trs))
+ last_t, last_r = trs[-1]
+ return Path(xys, codes)
+
+ def inverted(self):
+ # docstring inherited
+ return PolarAxes.InvertedPolarTransform(
+ self._axis, self._use_rmin,
+ apply_theta_transforms=self._apply_theta_transforms
+ )
+
+
+class PolarAffine(mtransforms.Affine2DBase):
+ r"""
+ The affine part of the polar projection.
+
+ Scales the output so that maximum radius rests on the edge of the Axes
+ circle and the origin is mapped to (0.5, 0.5). The transform applied is
+ the same to x and y components and given by:
+
+ .. math::
+
+ x_{1} = 0.5 \left [ \frac{x_{0}}{(r_{\max} - r_{\min})} + 1 \right ]
+
+ :math:`r_{\min}, r_{\max}` are the minimum and maximum radial limits after
+ any scaling (e.g. log scaling) has been removed.
+ """
+ def __init__(self, scale_transform, limits):
+ """
+ Parameters
+ ----------
+ scale_transform : `~matplotlib.transforms.Transform`
+ Scaling transform for the data. This is used to remove any scaling
+ from the radial view limits.
+ limits : `~matplotlib.transforms.BboxBase`
+ View limits of the data. The only part of its bounds that is used
+ is the y limits (for the radius limits).
+ """
+ super().__init__()
+ self._scale_transform = scale_transform
+ self._limits = limits
+ self.set_children(scale_transform, limits)
+ self._mtx = None
+
+ __str__ = mtransforms._make_str_method("_scale_transform", "_limits")
+
+ def get_matrix(self):
+ # docstring inherited
+ if self._invalid:
+ limits_scaled = self._limits.transformed(self._scale_transform)
+ yscale = limits_scaled.ymax - limits_scaled.ymin
+ affine = mtransforms.Affine2D() \
+ .scale(0.5 / yscale) \
+ .translate(0.5, 0.5)
+ self._mtx = affine.get_matrix()
+ self._inverted = None
+ self._invalid = 0
+ return self._mtx
+
+
+class InvertedPolarTransform(mtransforms.Transform):
+ """
+ The inverse of the polar transform, mapping Cartesian
+ coordinate space *x* and *y* back to *theta* and *r*.
+ """
+ input_dims = output_dims = 2
+
+ def __init__(self, axis=None, use_rmin=True,
+ *, apply_theta_transforms=True):
+ """
+ Parameters
+ ----------
+ axis : `~matplotlib.axis.Axis`, optional
+ Axis associated with this transform. This is used to get the
+ minimum radial limit.
+ use_rmin : `bool`, optional
+ If ``True``, add the minimum radial axis limit after
+ transforming from Cartesian coordinates. *axis* must also be
+ specified for this to take effect.
+ """
+ super().__init__()
+ self._axis = axis
+ self._use_rmin = use_rmin
+ self._apply_theta_transforms = apply_theta_transforms
+ if apply_theta_transforms:
+ _apply_theta_transforms_warn()
+
+ __str__ = mtransforms._make_str_method(
+ "_axis",
+ use_rmin="_use_rmin",
+ apply_theta_transforms="_apply_theta_transforms")
+
+ def transform_non_affine(self, values):
+ # docstring inherited
+ x, y = values.T
+ r = np.hypot(x, y)
+ theta = (np.arctan2(y, x) + 2 * np.pi) % (2 * np.pi)
+ # PolarAxes does not use the theta transforms here, but apply them for
+ # backwards-compatibility if not being used by it.
+ if self._apply_theta_transforms and self._axis is not None:
+ theta -= self._axis.get_theta_offset()
+ theta *= self._axis.get_theta_direction()
+ theta %= 2 * np.pi
+ if self._use_rmin and self._axis is not None:
+ r += self._axis.get_rorigin()
+ r *= self._axis.get_rsign()
+ return np.column_stack([theta, r])
+
+ def inverted(self):
+ # docstring inherited
+ return PolarAxes.PolarTransform(
+ self._axis, self._use_rmin,
+ apply_theta_transforms=self._apply_theta_transforms
+ )
+
+
+class ThetaFormatter(mticker.Formatter):
+ """
+ Used to format the *theta* tick labels. Converts the native
+ unit of radians into degrees and adds a degree symbol.
+ """
+
+ def __call__(self, x, pos=None):
+ vmin, vmax = self.axis.get_view_interval()
+ d = np.rad2deg(abs(vmax - vmin))
+ digits = max(-int(np.log10(d) - 1.5), 0)
+ return f"{np.rad2deg(x):0.{digits}f}\N{DEGREE SIGN}"
+
+
+class _AxisWrapper:
+ def __init__(self, axis):
+ self._axis = axis
+
+ def get_view_interval(self):
+ return np.rad2deg(self._axis.get_view_interval())
+
+ def set_view_interval(self, vmin, vmax):
+ self._axis.set_view_interval(*np.deg2rad((vmin, vmax)))
+
+ def get_minpos(self):
+ return np.rad2deg(self._axis.get_minpos())
+
+ def get_data_interval(self):
+ return np.rad2deg(self._axis.get_data_interval())
+
+ def set_data_interval(self, vmin, vmax):
+ self._axis.set_data_interval(*np.deg2rad((vmin, vmax)))
+
+ def get_tick_space(self):
+ return self._axis.get_tick_space()
+
+
+class ThetaLocator(mticker.Locator):
+ """
+ Used to locate theta ticks.
+
+ This will work the same as the base locator except in the case that the
+ view spans the entire circle. In such cases, the previously used default
+ locations of every 45 degrees are returned.
+ """
+
+ def __init__(self, base):
+ self.base = base
+ self.axis = self.base.axis = _AxisWrapper(self.base.axis)
+
+ def set_axis(self, axis):
+ self.axis = _AxisWrapper(axis)
+ self.base.set_axis(self.axis)
+
+ def __call__(self):
+ lim = self.axis.get_view_interval()
+ if _is_full_circle_deg(lim[0], lim[1]):
+ return np.deg2rad(min(lim)) + np.arange(8) * 2 * np.pi / 8
+ else:
+ return np.deg2rad(self.base())
+
+ def view_limits(self, vmin, vmax):
+ vmin, vmax = np.rad2deg((vmin, vmax))
+ return np.deg2rad(self.base.view_limits(vmin, vmax))
+
+
+class ThetaTick(maxis.XTick):
+ """
+ A theta-axis tick.
+
+ This subclass of `.XTick` provides angular ticks with some small
+ modification to their re-positioning such that ticks are rotated based on
+ tick location. This results in ticks that are correctly perpendicular to
+ the arc spine.
+
+ When 'auto' rotation is enabled, labels are also rotated to be parallel to
+ the spine. The label padding is also applied here since it's not possible
+ to use a generic axes transform to produce tick-specific padding.
+ """
+
+ def __init__(self, axes, *args, **kwargs):
+ self._text1_translate = mtransforms.ScaledTranslation(
+ 0, 0, axes.get_figure(root=False).dpi_scale_trans)
+ self._text2_translate = mtransforms.ScaledTranslation(
+ 0, 0, axes.get_figure(root=False).dpi_scale_trans)
+ super().__init__(axes, *args, **kwargs)
+ self.label1.set(
+ rotation_mode='anchor',
+ transform=self.label1.get_transform() + self._text1_translate)
+ self.label2.set(
+ rotation_mode='anchor',
+ transform=self.label2.get_transform() + self._text2_translate)
+
+ def _apply_params(self, **kwargs):
+ super()._apply_params(**kwargs)
+ # Ensure transform is correct; sometimes this gets reset.
+ trans = self.label1.get_transform()
+ if not trans.contains_branch(self._text1_translate):
+ self.label1.set_transform(trans + self._text1_translate)
+ trans = self.label2.get_transform()
+ if not trans.contains_branch(self._text2_translate):
+ self.label2.set_transform(trans + self._text2_translate)
+
+ def _update_padding(self, pad, angle):
+ padx = pad * np.cos(angle) / 72
+ pady = pad * np.sin(angle) / 72
+ self._text1_translate._t = (padx, pady)
+ self._text1_translate.invalidate()
+ self._text2_translate._t = (-padx, -pady)
+ self._text2_translate.invalidate()
+
+ def update_position(self, loc):
+ super().update_position(loc)
+ axes = self.axes
+ angle = loc * axes.get_theta_direction() + axes.get_theta_offset()
+ text_angle = np.rad2deg(angle) % 360 - 90
+ angle -= np.pi / 2
+
+ marker = self.tick1line.get_marker()
+ if marker in (mmarkers.TICKUP, '|'):
+ trans = mtransforms.Affine2D().scale(1, 1).rotate(angle)
+ elif marker == mmarkers.TICKDOWN:
+ trans = mtransforms.Affine2D().scale(1, -1).rotate(angle)
+ else:
+ # Don't modify custom tick line markers.
+ trans = self.tick1line._marker._transform
+ self.tick1line._marker._transform = trans
+
+ marker = self.tick2line.get_marker()
+ if marker in (mmarkers.TICKUP, '|'):
+ trans = mtransforms.Affine2D().scale(1, 1).rotate(angle)
+ elif marker == mmarkers.TICKDOWN:
+ trans = mtransforms.Affine2D().scale(1, -1).rotate(angle)
+ else:
+ # Don't modify custom tick line markers.
+ trans = self.tick2line._marker._transform
+ self.tick2line._marker._transform = trans
+
+ mode, user_angle = self._labelrotation
+ if mode == 'default':
+ text_angle = user_angle
+ else:
+ if text_angle > 90:
+ text_angle -= 180
+ elif text_angle < -90:
+ text_angle += 180
+ text_angle += user_angle
+ self.label1.set_rotation(text_angle)
+ self.label2.set_rotation(text_angle)
+
+ # This extra padding helps preserve the look from previous releases but
+ # is also needed because labels are anchored to their center.
+ pad = self._pad + 7
+ self._update_padding(pad,
+ self._loc * axes.get_theta_direction() +
+ axes.get_theta_offset())
+
+
+class ThetaAxis(maxis.XAxis):
+ """
+ A theta Axis.
+
+ This overrides certain properties of an `.XAxis` to provide special-casing
+ for an angular axis.
+ """
+ __name__ = 'thetaaxis'
+ axis_name = 'theta' #: Read-only name identifying the axis.
+ _tick_class = ThetaTick
+
+ def _wrap_locator_formatter(self):
+ self.set_major_locator(ThetaLocator(self.get_major_locator()))
+ self.set_major_formatter(ThetaFormatter())
+ self.isDefault_majloc = True
+ self.isDefault_majfmt = True
+
+ def clear(self):
+ # docstring inherited
+ super().clear()
+ self.set_ticks_position('none')
+ self._wrap_locator_formatter()
+
+ def _set_scale(self, value, **kwargs):
+ if value != 'linear':
+ raise NotImplementedError(
+ "The xscale cannot be set on a polar plot")
+ super()._set_scale(value, **kwargs)
+ # LinearScale.set_default_locators_and_formatters just set the major
+ # locator to be an AutoLocator, so we customize it here to have ticks
+ # at sensible degree multiples.
+ self.get_major_locator().set_params(steps=[1, 1.5, 3, 4.5, 9, 10])
+ self._wrap_locator_formatter()
+
+ def _copy_tick_props(self, src, dest):
+ """Copy the props from src tick to dest tick."""
+ if src is None or dest is None:
+ return
+ super()._copy_tick_props(src, dest)
+
+ # Ensure that tick transforms are independent so that padding works.
+ trans = dest._get_text1_transform()[0]
+ dest.label1.set_transform(trans + dest._text1_translate)
+ trans = dest._get_text2_transform()[0]
+ dest.label2.set_transform(trans + dest._text2_translate)
+
+
+class RadialLocator(mticker.Locator):
+ """
+ Used to locate radius ticks.
+
+ Ensures that all ticks are strictly positive. For all other tasks, it
+ delegates to the base `.Locator` (which may be different depending on the
+ scale of the *r*-axis).
+ """
+
+ def __init__(self, base, axes=None):
+ self.base = base
+ self._axes = axes
+
+ def set_axis(self, axis):
+ self.base.set_axis(axis)
+
+ def __call__(self):
+ # Ensure previous behaviour with full circle non-annular views.
+ if self._axes:
+ if _is_full_circle_rad(*self._axes.viewLim.intervalx):
+ rorigin = self._axes.get_rorigin() * self._axes.get_rsign()
+ if self._axes.get_rmin() <= rorigin:
+ return [tick for tick in self.base() if tick > rorigin]
+ return self.base()
+
+ def _zero_in_bounds(self):
+ """
+ Return True if zero is within the valid values for the
+ scale of the radial axis.
+ """
+ vmin, vmax = self._axes.yaxis._scale.limit_range_for_scale(0, 1, 1e-5)
+ return vmin == 0
+
+ def nonsingular(self, vmin, vmax):
+ # docstring inherited
+ if self._zero_in_bounds() and (vmin, vmax) == (-np.inf, np.inf):
+ # Initial view limits
+ return (0, 1)
+ else:
+ return self.base.nonsingular(vmin, vmax)
+
+ def view_limits(self, vmin, vmax):
+ vmin, vmax = self.base.view_limits(vmin, vmax)
+ if self._zero_in_bounds() and vmax > vmin:
+ # this allows inverted r/y-lims
+ vmin = min(0, vmin)
+ return mtransforms.nonsingular(vmin, vmax)
+
+
+class _ThetaShift(mtransforms.ScaledTranslation):
+ """
+ Apply a padding shift based on axes theta limits.
+
+ This is used to create padding for radial ticks.
+
+ Parameters
+ ----------
+ axes : `~matplotlib.axes.Axes`
+ The owning Axes; used to determine limits.
+ pad : float
+ The padding to apply, in points.
+ mode : {'min', 'max', 'rlabel'}
+ Whether to shift away from the start (``'min'``) or the end (``'max'``)
+ of the axes, or using the rlabel position (``'rlabel'``).
+ """
+ def __init__(self, axes, pad, mode):
+ super().__init__(pad, pad, axes.get_figure(root=False).dpi_scale_trans)
+ self.set_children(axes._realViewLim)
+ self.axes = axes
+ self.mode = mode
+ self.pad = pad
+
+ __str__ = mtransforms._make_str_method("axes", "pad", "mode")
+
+ def get_matrix(self):
+ if self._invalid:
+ if self.mode == 'rlabel':
+ angle = (
+ np.deg2rad(self.axes.get_rlabel_position()
+ * self.axes.get_theta_direction())
+ + self.axes.get_theta_offset()
+ - np.pi / 2
+ )
+ elif self.mode == 'min':
+ angle = self.axes._realViewLim.xmin - np.pi / 2
+ elif self.mode == 'max':
+ angle = self.axes._realViewLim.xmax + np.pi / 2
+ self._t = (self.pad * np.cos(angle) / 72, self.pad * np.sin(angle) / 72)
+ return super().get_matrix()
+
+
+class RadialTick(maxis.YTick):
+ """
+ A radial-axis tick.
+
+ This subclass of `.YTick` provides radial ticks with some small
+ modification to their re-positioning such that ticks are rotated based on
+ axes limits. This results in ticks that are correctly perpendicular to
+ the spine. Labels are also rotated to be perpendicular to the spine, when
+ 'auto' rotation is enabled.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.label1.set_rotation_mode('anchor')
+ self.label2.set_rotation_mode('anchor')
+
+ def _determine_anchor(self, mode, angle, start):
+ # Note: angle is the (spine angle - 90) because it's used for the tick
+ # & text setup, so all numbers below are -90 from (normed) spine angle.
+ if mode == 'auto':
+ if start:
+ if -90 <= angle <= 90:
+ return 'left', 'center'
+ else:
+ return 'right', 'center'
+ else:
+ if -90 <= angle <= 90:
+ return 'right', 'center'
+ else:
+ return 'left', 'center'
+ else:
+ if start:
+ if angle < -68.5:
+ return 'center', 'top'
+ elif angle < -23.5:
+ return 'left', 'top'
+ elif angle < 22.5:
+ return 'left', 'center'
+ elif angle < 67.5:
+ return 'left', 'bottom'
+ elif angle < 112.5:
+ return 'center', 'bottom'
+ elif angle < 157.5:
+ return 'right', 'bottom'
+ elif angle < 202.5:
+ return 'right', 'center'
+ elif angle < 247.5:
+ return 'right', 'top'
+ else:
+ return 'center', 'top'
+ else:
+ if angle < -68.5:
+ return 'center', 'bottom'
+ elif angle < -23.5:
+ return 'right', 'bottom'
+ elif angle < 22.5:
+ return 'right', 'center'
+ elif angle < 67.5:
+ return 'right', 'top'
+ elif angle < 112.5:
+ return 'center', 'top'
+ elif angle < 157.5:
+ return 'left', 'top'
+ elif angle < 202.5:
+ return 'left', 'center'
+ elif angle < 247.5:
+ return 'left', 'bottom'
+ else:
+ return 'center', 'bottom'
+
+ def update_position(self, loc):
+ super().update_position(loc)
+ axes = self.axes
+ thetamin = axes.get_thetamin()
+ thetamax = axes.get_thetamax()
+ direction = axes.get_theta_direction()
+ offset_rad = axes.get_theta_offset()
+ offset = np.rad2deg(offset_rad)
+ full = _is_full_circle_deg(thetamin, thetamax)
+
+ if full:
+ angle = (axes.get_rlabel_position() * direction +
+ offset) % 360 - 90
+ tick_angle = 0
+ else:
+ angle = (thetamin * direction + offset) % 360 - 90
+ if direction > 0:
+ tick_angle = np.deg2rad(angle)
+ else:
+ tick_angle = np.deg2rad(angle + 180)
+ text_angle = (angle + 90) % 180 - 90 # between -90 and +90.
+ mode, user_angle = self._labelrotation
+ if mode == 'auto':
+ text_angle += user_angle
+ else:
+ text_angle = user_angle
+
+ if full:
+ ha = self.label1.get_horizontalalignment()
+ va = self.label1.get_verticalalignment()
+ else:
+ ha, va = self._determine_anchor(mode, angle, direction > 0)
+ self.label1.set_horizontalalignment(ha)
+ self.label1.set_verticalalignment(va)
+ self.label1.set_rotation(text_angle)
+
+ marker = self.tick1line.get_marker()
+ if marker == mmarkers.TICKLEFT:
+ trans = mtransforms.Affine2D().rotate(tick_angle)
+ elif marker == '_':
+ trans = mtransforms.Affine2D().rotate(tick_angle + np.pi / 2)
+ elif marker == mmarkers.TICKRIGHT:
+ trans = mtransforms.Affine2D().scale(-1, 1).rotate(tick_angle)
+ else:
+ # Don't modify custom tick line markers.
+ trans = self.tick1line._marker._transform
+ self.tick1line._marker._transform = trans
+
+ if full:
+ self.label2.set_visible(False)
+ self.tick2line.set_visible(False)
+ angle = (thetamax * direction + offset) % 360 - 90
+ if direction > 0:
+ tick_angle = np.deg2rad(angle)
+ else:
+ tick_angle = np.deg2rad(angle + 180)
+ text_angle = (angle + 90) % 180 - 90 # between -90 and +90.
+ mode, user_angle = self._labelrotation
+ if mode == 'auto':
+ text_angle += user_angle
+ else:
+ text_angle = user_angle
+
+ ha, va = self._determine_anchor(mode, angle, direction < 0)
+ self.label2.set_ha(ha)
+ self.label2.set_va(va)
+ self.label2.set_rotation(text_angle)
+
+ marker = self.tick2line.get_marker()
+ if marker == mmarkers.TICKLEFT:
+ trans = mtransforms.Affine2D().rotate(tick_angle)
+ elif marker == '_':
+ trans = mtransforms.Affine2D().rotate(tick_angle + np.pi / 2)
+ elif marker == mmarkers.TICKRIGHT:
+ trans = mtransforms.Affine2D().scale(-1, 1).rotate(tick_angle)
+ else:
+ # Don't modify custom tick line markers.
+ trans = self.tick2line._marker._transform
+ self.tick2line._marker._transform = trans
+
+
+class RadialAxis(maxis.YAxis):
+ """
+ A radial Axis.
+
+ This overrides certain properties of a `.YAxis` to provide special-casing
+ for a radial axis.
+ """
+ __name__ = 'radialaxis'
+ axis_name = 'radius' #: Read-only name identifying the axis.
+ _tick_class = RadialTick
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.sticky_edges.y.append(0)
+
+ def _wrap_locator_formatter(self):
+ self.set_major_locator(RadialLocator(self.get_major_locator(),
+ self.axes))
+ self.isDefault_majloc = True
+
+ def clear(self):
+ # docstring inherited
+ super().clear()
+ self.set_ticks_position('none')
+ self._wrap_locator_formatter()
+
+ def _set_scale(self, value, **kwargs):
+ super()._set_scale(value, **kwargs)
+ self._wrap_locator_formatter()
+
+
+def _is_full_circle_deg(thetamin, thetamax):
+ """
+ Determine if a wedge (in degrees) spans the full circle.
+
+ The condition is derived from :class:`~matplotlib.patches.Wedge`.
+ """
+ return abs(abs(thetamax - thetamin) - 360.0) < 1e-12
+
+
+def _is_full_circle_rad(thetamin, thetamax):
+ """
+ Determine if a wedge (in radians) spans the full circle.
+
+ The condition is derived from :class:`~matplotlib.patches.Wedge`.
+ """
+ return abs(abs(thetamax - thetamin) - 2 * np.pi) < 1.74e-14
+
+
+class _WedgeBbox(mtransforms.Bbox):
+ """
+ Transform (theta, r) wedge Bbox into Axes bounding box.
+
+ Parameters
+ ----------
+ center : (float, float)
+ Center of the wedge
+ viewLim : `~matplotlib.transforms.Bbox`
+ Bbox determining the boundaries of the wedge
+ originLim : `~matplotlib.transforms.Bbox`
+ Bbox determining the origin for the wedge, if different from *viewLim*
+ """
+ def __init__(self, center, viewLim, originLim, **kwargs):
+ super().__init__([[0, 0], [1, 1]], **kwargs)
+ self._center = center
+ self._viewLim = viewLim
+ self._originLim = originLim
+ self.set_children(viewLim, originLim)
+
+ __str__ = mtransforms._make_str_method("_center", "_viewLim", "_originLim")
+
+ def get_points(self):
+ # docstring inherited
+ if self._invalid:
+ points = self._viewLim.get_points().copy()
+ # Scale angular limits to work with Wedge.
+ points[:, 0] *= 180 / np.pi
+ if points[0, 0] > points[1, 0]:
+ points[:, 0] = points[::-1, 0]
+
+ # Scale radial limits based on origin radius.
+ points[:, 1] -= self._originLim.y0
+
+ # Scale radial limits to match axes limits.
+ rscale = 0.5 / points[1, 1]
+ points[:, 1] *= rscale
+ width = min(points[1, 1] - points[0, 1], 0.5)
+
+ # Generate bounding box for wedge.
+ wedge = mpatches.Wedge(self._center, points[1, 1],
+ points[0, 0], points[1, 0],
+ width=width)
+ self.update_from_path(wedge.get_path())
+
+ # Ensure equal aspect ratio.
+ w, h = self._points[1] - self._points[0]
+ deltah = max(w - h, 0) / 2
+ deltaw = max(h - w, 0) / 2
+ self._points += np.array([[-deltaw, -deltah], [deltaw, deltah]])
+
+ self._invalid = 0
+
+ return self._points
+
+
+class PolarAxes(Axes):
+ """
+ A polar graph projection, where the input dimensions are *theta*, *r*.
+
+ Theta starts pointing east and goes anti-clockwise.
+ """
+ name = 'polar'
+
+ def __init__(self, *args,
+ theta_offset=0, theta_direction=1, rlabel_position=22.5,
+ **kwargs):
+ # docstring inherited
+ self._default_theta_offset = theta_offset
+ self._default_theta_direction = theta_direction
+ self._default_rlabel_position = np.deg2rad(rlabel_position)
+ super().__init__(*args, **kwargs)
+ self.use_sticky_edges = True
+ self.set_aspect('equal', adjustable='box', anchor='C')
+ self.clear()
+
+ def clear(self):
+ # docstring inherited
+ super().clear()
+
+ self.title.set_y(1.05)
+
+ start = self.spines.get('start', None)
+ if start:
+ start.set_visible(False)
+ end = self.spines.get('end', None)
+ if end:
+ end.set_visible(False)
+ self.set_xlim(0.0, 2 * np.pi)
+
+ self.grid(mpl.rcParams['polaraxes.grid'])
+ inner = self.spines.get('inner', None)
+ if inner:
+ inner.set_visible(False)
+
+ self.set_rorigin(None)
+ self.set_theta_offset(self._default_theta_offset)
+ self.set_theta_direction(self._default_theta_direction)
+
+ def _init_axis(self):
+ # This is moved out of __init__ because non-separable axes don't use it
+ self.xaxis = ThetaAxis(self, clear=False)
+ self.yaxis = RadialAxis(self, clear=False)
+ self.spines['polar'].register_axis(self.yaxis)
+ inner_spine = self.spines.get('inner', None)
+ if inner_spine is not None:
+ # Subclasses may not have inner spine.
+ inner_spine.register_axis(self.yaxis)
+
+ def _set_lim_and_transforms(self):
+ # A view limit where the minimum radius can be locked if the user
+ # specifies an alternate origin.
+ self._originViewLim = mtransforms.LockableBbox(self.viewLim)
+
+ # Handle angular offset and direction.
+ self._direction = mtransforms.Affine2D() \
+ .scale(self._default_theta_direction, 1.0)
+ self._theta_offset = mtransforms.Affine2D() \
+ .translate(self._default_theta_offset, 0.0)
+ self.transShift = self._direction + self._theta_offset
+ # A view limit shifted to the correct location after accounting for
+ # orientation and offset.
+ self._realViewLim = mtransforms.TransformedBbox(self.viewLim,
+ self.transShift)
+
+ # Transforms the x and y axis separately by a scale factor
+ # It is assumed that this part will have non-linear components
+ self.transScale = mtransforms.TransformWrapper(
+ mtransforms.IdentityTransform())
+
+ # Scale view limit into a bbox around the selected wedge. This may be
+ # smaller than the usual unit axes rectangle if not plotting the full
+ # circle.
+ self.axesLim = _WedgeBbox((0.5, 0.5),
+ self._realViewLim, self._originViewLim)
+
+ # Scale the wedge to fill the axes.
+ self.transWedge = mtransforms.BboxTransformFrom(self.axesLim)
+
+ # Scale the axes to fill the figure.
+ self.transAxes = mtransforms.BboxTransformTo(self.bbox)
+
+ # A (possibly non-linear) projection on the (already scaled)
+ # data. This one is aware of rmin
+ self.transProjection = self.PolarTransform(
+ self,
+ apply_theta_transforms=False,
+ scale_transform=self.transScale
+ )
+ # Add dependency on rorigin.
+ self.transProjection.set_children(self._originViewLim)
+
+ # An affine transformation on the data, generally to limit the
+ # range of the axes
+ self.transProjectionAffine = self.PolarAffine(self.transScale,
+ self._originViewLim)
+
+ # The complete data transformation stack -- from data all the
+ # way to display coordinates
+ #
+ # 1. Remove any radial axis scaling (e.g. log scaling)
+ # 2. Shift data in the theta direction
+ # 3. Project the data from polar to cartesian values
+ # (with the origin in the same place)
+ # 4. Scale and translate the cartesian values to Axes coordinates
+ # (here the origin is moved to the lower left of the Axes)
+ # 5. Move and scale to fill the Axes
+ # 6. Convert from Axes coordinates to Figure coordinates
+ self.transData = (
+ self.transScale +
+ self.transShift +
+ self.transProjection +
+ (
+ self.transProjectionAffine +
+ self.transWedge +
+ self.transAxes
+ )
+ )
+
+ # This is the transform for theta-axis ticks. It is
+ # equivalent to transData, except it always puts r == 0.0 and r == 1.0
+ # at the edge of the axis circles.
+ self._xaxis_transform = (
+ mtransforms.blended_transform_factory(
+ mtransforms.IdentityTransform(),
+ mtransforms.BboxTransformTo(self.viewLim)) +
+ self.transData)
+ # The theta labels are flipped along the radius, so that text 1 is on
+ # the outside by default. This should work the same as before.
+ flipr_transform = mtransforms.Affine2D() \
+ .translate(0.0, -0.5) \
+ .scale(1.0, -1.0) \
+ .translate(0.0, 0.5)
+ self._xaxis_text_transform = flipr_transform + self._xaxis_transform
+
+ # This is the transform for r-axis ticks. It scales the theta
+ # axis so the gridlines from 0.0 to 1.0, now go from thetamin to
+ # thetamax.
+ self._yaxis_transform = (
+ mtransforms.blended_transform_factory(
+ mtransforms.BboxTransformTo(self.viewLim),
+ mtransforms.IdentityTransform()) +
+ self.transData)
+ # The r-axis labels are put at an angle and padded in the r-direction
+ self._r_label_position = mtransforms.Affine2D() \
+ .translate(self._default_rlabel_position, 0.0)
+ self._yaxis_text_transform = mtransforms.TransformWrapper(
+ self._r_label_position + self.transData)
+
+ def get_xaxis_transform(self, which='grid'):
+ _api.check_in_list(['tick1', 'tick2', 'grid'], which=which)
+ return self._xaxis_transform
+
+ def get_xaxis_text1_transform(self, pad):
+ return self._xaxis_text_transform, 'center', 'center'
+
+ def get_xaxis_text2_transform(self, pad):
+ return self._xaxis_text_transform, 'center', 'center'
+
+ def get_yaxis_transform(self, which='grid'):
+ if which in ('tick1', 'tick2'):
+ return self._yaxis_text_transform
+ elif which == 'grid':
+ return self._yaxis_transform
+ else:
+ _api.check_in_list(['tick1', 'tick2', 'grid'], which=which)
+
+ def get_yaxis_text1_transform(self, pad):
+ thetamin, thetamax = self._realViewLim.intervalx
+ if _is_full_circle_rad(thetamin, thetamax):
+ return self._yaxis_text_transform, 'bottom', 'left'
+ elif self.get_theta_direction() > 0:
+ halign = 'left'
+ pad_shift = _ThetaShift(self, pad, 'min')
+ else:
+ halign = 'right'
+ pad_shift = _ThetaShift(self, pad, 'max')
+ return self._yaxis_text_transform + pad_shift, 'center', halign
+
+ def get_yaxis_text2_transform(self, pad):
+ if self.get_theta_direction() > 0:
+ halign = 'right'
+ pad_shift = _ThetaShift(self, pad, 'max')
+ else:
+ halign = 'left'
+ pad_shift = _ThetaShift(self, pad, 'min')
+ return self._yaxis_text_transform + pad_shift, 'center', halign
+
+ def draw(self, renderer):
+ self._unstale_viewLim()
+ thetamin, thetamax = np.rad2deg(self._realViewLim.intervalx)
+ if thetamin > thetamax:
+ thetamin, thetamax = thetamax, thetamin
+ rscale_tr = self.yaxis.get_transform()
+ rmin, rmax = ((rscale_tr.transform(self._realViewLim.intervaly) -
+ rscale_tr.transform(self.get_rorigin())) *
+ self.get_rsign())
+ if isinstance(self.patch, mpatches.Wedge):
+ # Backwards-compatibility: Any subclassed Axes might override the
+ # patch to not be the Wedge that PolarAxes uses.
+ center = self.transWedge.transform((0.5, 0.5))
+ self.patch.set_center(center)
+ self.patch.set_theta1(thetamin)
+ self.patch.set_theta2(thetamax)
+
+ edge, _ = self.transWedge.transform((1, 0))
+ radius = edge - center[0]
+ width = min(radius * (rmax - rmin) / rmax, radius)
+ self.patch.set_radius(radius)
+ self.patch.set_width(width)
+
+ inner_width = radius - width
+ inner = self.spines.get('inner', None)
+ if inner:
+ inner.set_visible(inner_width != 0.0)
+
+ visible = not _is_full_circle_deg(thetamin, thetamax)
+ # For backwards compatibility, any subclassed Axes might override the
+ # spines to not include start/end that PolarAxes uses.
+ start = self.spines.get('start', None)
+ end = self.spines.get('end', None)
+ if start:
+ start.set_visible(visible)
+ if end:
+ end.set_visible(visible)
+ if visible:
+ yaxis_text_transform = self._yaxis_transform
+ else:
+ yaxis_text_transform = self._r_label_position + self.transData
+ if self._yaxis_text_transform != yaxis_text_transform:
+ self._yaxis_text_transform.set(yaxis_text_transform)
+ self.yaxis.reset_ticks()
+ self.yaxis.set_clip_path(self.patch)
+
+ super().draw(renderer)
+
+ def _gen_axes_patch(self):
+ return mpatches.Wedge((0.5, 0.5), 0.5, 0.0, 360.0)
+
+ def _gen_axes_spines(self):
+ spines = {
+ 'polar': Spine.arc_spine(self, 'top', (0.5, 0.5), 0.5, 0, 360),
+ 'start': Spine.linear_spine(self, 'left'),
+ 'end': Spine.linear_spine(self, 'right'),
+ 'inner': Spine.arc_spine(self, 'bottom', (0.5, 0.5), 0.0, 0, 360),
+ }
+ spines['polar'].set_transform(self.transWedge + self.transAxes)
+ spines['inner'].set_transform(self.transWedge + self.transAxes)
+ spines['start'].set_transform(self._yaxis_transform)
+ spines['end'].set_transform(self._yaxis_transform)
+ return spines
+
+ def set_thetamax(self, thetamax):
+ """Set the maximum theta limit in degrees."""
+ self.viewLim.x1 = np.deg2rad(thetamax)
+
+ def get_thetamax(self):
+ """Return the maximum theta limit in degrees."""
+ return np.rad2deg(self.viewLim.xmax)
+
+ def set_thetamin(self, thetamin):
+ """Set the minimum theta limit in degrees."""
+ self.viewLim.x0 = np.deg2rad(thetamin)
+
+ def get_thetamin(self):
+ """Get the minimum theta limit in degrees."""
+ return np.rad2deg(self.viewLim.xmin)
+
+ def set_thetalim(self, *args, **kwargs):
+ r"""
+ Set the minimum and maximum theta values.
+
+ Can take the following signatures:
+
+ - ``set_thetalim(minval, maxval)``: Set the limits in radians.
+ - ``set_thetalim(thetamin=minval, thetamax=maxval)``: Set the limits
+ in degrees.
+
+ where minval and maxval are the minimum and maximum limits. Values are
+ wrapped in to the range :math:`[0, 2\pi]` (in radians), so for example
+ it is possible to do ``set_thetalim(-np.pi / 2, np.pi / 2)`` to have
+ an axis symmetric around 0. A ValueError is raised if the absolute
+ angle difference is larger than a full circle.
+ """
+ orig_lim = self.get_xlim() # in radians
+ if 'thetamin' in kwargs:
+ kwargs['xmin'] = np.deg2rad(kwargs.pop('thetamin'))
+ if 'thetamax' in kwargs:
+ kwargs['xmax'] = np.deg2rad(kwargs.pop('thetamax'))
+ new_min, new_max = self.set_xlim(*args, **kwargs)
+ # Parsing all permutations of *args, **kwargs is tricky; it is simpler
+ # to let set_xlim() do it and then validate the limits.
+ if abs(new_max - new_min) > 2 * np.pi:
+ self.set_xlim(orig_lim) # un-accept the change
+ raise ValueError("The angle range must be less than a full circle")
+ return tuple(np.rad2deg((new_min, new_max)))
+
+ def set_theta_offset(self, offset):
+ """
+ Set the offset for the location of 0 in radians.
+ """
+ mtx = self._theta_offset.get_matrix()
+ mtx[0, 2] = offset
+ self._theta_offset.invalidate()
+
+ def get_theta_offset(self):
+ """
+ Get the offset for the location of 0 in radians.
+ """
+ return self._theta_offset.get_matrix()[0, 2]
+
+ def set_theta_zero_location(self, loc, offset=0.0):
+ """
+ Set the location of theta's zero.
+
+ This simply calls `set_theta_offset` with the correct value in radians.
+
+ Parameters
+ ----------
+ loc : str
+ May be one of "N", "NW", "W", "SW", "S", "SE", "E", or "NE".
+ offset : float, default: 0
+ An offset in degrees to apply from the specified *loc*. **Note:**
+ this offset is *always* applied counter-clockwise regardless of
+ the direction setting.
+ """
+ mapping = {
+ 'N': np.pi * 0.5,
+ 'NW': np.pi * 0.75,
+ 'W': np.pi,
+ 'SW': np.pi * 1.25,
+ 'S': np.pi * 1.5,
+ 'SE': np.pi * 1.75,
+ 'E': 0,
+ 'NE': np.pi * 0.25}
+ return self.set_theta_offset(mapping[loc] + np.deg2rad(offset))
+
+ def set_theta_direction(self, direction):
+ """
+ Set the direction in which theta increases.
+
+ clockwise, -1:
+ Theta increases in the clockwise direction
+
+ counterclockwise, anticlockwise, 1:
+ Theta increases in the counterclockwise direction
+ """
+ mtx = self._direction.get_matrix()
+ if direction in ('clockwise', -1):
+ mtx[0, 0] = -1
+ elif direction in ('counterclockwise', 'anticlockwise', 1):
+ mtx[0, 0] = 1
+ else:
+ _api.check_in_list(
+ [-1, 1, 'clockwise', 'counterclockwise', 'anticlockwise'],
+ direction=direction)
+ self._direction.invalidate()
+
+ def get_theta_direction(self):
+ """
+ Get the direction in which theta increases.
+
+ -1:
+ Theta increases in the clockwise direction
+
+ 1:
+ Theta increases in the counterclockwise direction
+ """
+ return self._direction.get_matrix()[0, 0]
+
+ def set_rmax(self, rmax):
+ """
+ Set the outer radial limit.
+
+ Parameters
+ ----------
+ rmax : float
+ """
+ self.viewLim.y1 = rmax
+
+ def get_rmax(self):
+ """
+ Returns
+ -------
+ float
+ Outer radial limit.
+ """
+ return self.viewLim.ymax
+
+ def set_rmin(self, rmin):
+ """
+ Set the inner radial limit.
+
+ Parameters
+ ----------
+ rmin : float
+ """
+ self.viewLim.y0 = rmin
+
+ def get_rmin(self):
+ """
+ Returns
+ -------
+ float
+ The inner radial limit.
+ """
+ return self.viewLim.ymin
+
+ def set_rorigin(self, rorigin):
+ """
+ Update the radial origin.
+
+ Parameters
+ ----------
+ rorigin : float
+ """
+ self._originViewLim.locked_y0 = rorigin
+
+ def get_rorigin(self):
+ """
+ Returns
+ -------
+ float
+ """
+ return self._originViewLim.y0
+
+ def get_rsign(self):
+ return np.sign(self._originViewLim.y1 - self._originViewLim.y0)
+
+ def set_rlim(self, bottom=None, top=None, *,
+ emit=True, auto=False, **kwargs):
+ """
+ Set the radial axis view limits.
+
+ This function behaves like `.Axes.set_ylim`, but additionally supports
+ *rmin* and *rmax* as aliases for *bottom* and *top*.
+
+ See Also
+ --------
+ .Axes.set_ylim
+ """
+ if 'rmin' in kwargs:
+ if bottom is None:
+ bottom = kwargs.pop('rmin')
+ else:
+ raise ValueError('Cannot supply both positional "bottom"'
+ 'argument and kwarg "rmin"')
+ if 'rmax' in kwargs:
+ if top is None:
+ top = kwargs.pop('rmax')
+ else:
+ raise ValueError('Cannot supply both positional "top"'
+ 'argument and kwarg "rmax"')
+ return self.set_ylim(bottom=bottom, top=top, emit=emit, auto=auto,
+ **kwargs)
+
+ def get_rlabel_position(self):
+ """
+ Returns
+ -------
+ float
+ The theta position of the radius labels in degrees.
+ """
+ return np.rad2deg(self._r_label_position.get_matrix()[0, 2])
+
+ def set_rlabel_position(self, value):
+ """
+ Update the theta position of the radius labels.
+
+ Parameters
+ ----------
+ value : number
+ The angular position of the radius labels in degrees.
+ """
+ self._r_label_position.clear().translate(np.deg2rad(value), 0.0)
+
+ def set_yscale(self, *args, **kwargs):
+ super().set_yscale(*args, **kwargs)
+ self.yaxis.set_major_locator(
+ self.RadialLocator(self.yaxis.get_major_locator(), self))
+
+ def set_rscale(self, *args, **kwargs):
+ return Axes.set_yscale(self, *args, **kwargs)
+
+ def set_rticks(self, *args, **kwargs):
+ return Axes.set_yticks(self, *args, **kwargs)
+
+ def set_thetagrids(self, angles, labels=None, fmt=None, **kwargs):
+ """
+ Set the theta gridlines in a polar plot.
+
+ Parameters
+ ----------
+ angles : tuple with floats, degrees
+ The angles of the theta gridlines.
+
+ labels : tuple with strings or None
+ The labels to use at each theta gridline. The
+ `.projections.polar.ThetaFormatter` will be used if None.
+
+ fmt : str or None
+ Format string used in `matplotlib.ticker.FormatStrFormatter`.
+ For example '%f'. Note that the angle that is used is in
+ radians.
+
+ Returns
+ -------
+ lines : list of `.lines.Line2D`
+ The theta gridlines.
+
+ labels : list of `.text.Text`
+ The tick labels.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ *kwargs* are optional `.Text` properties for the labels.
+
+ .. warning::
+
+ This only sets the properties of the current ticks.
+ Ticks are not guaranteed to be persistent. Various operations
+ can create, delete and modify the Tick instances. There is an
+ imminent risk that these settings can get lost if you work on
+ the figure further (including also panning/zooming on a
+ displayed figure).
+
+ Use `.set_tick_params` instead if possible.
+
+ See Also
+ --------
+ .PolarAxes.set_rgrids
+ .Axis.get_gridlines
+ .Axis.get_ticklabels
+ """
+
+ # Make sure we take into account unitized data
+ angles = self.convert_yunits(angles)
+ angles = np.deg2rad(angles)
+ self.set_xticks(angles)
+ if labels is not None:
+ self.set_xticklabels(labels)
+ elif fmt is not None:
+ self.xaxis.set_major_formatter(mticker.FormatStrFormatter(fmt))
+ for t in self.xaxis.get_ticklabels():
+ t._internal_update(kwargs)
+ return self.xaxis.get_ticklines(), self.xaxis.get_ticklabels()
+
+ def set_rgrids(self, radii, labels=None, angle=None, fmt=None, **kwargs):
+ """
+ Set the radial gridlines on a polar plot.
+
+ Parameters
+ ----------
+ radii : tuple with floats
+ The radii for the radial gridlines
+
+ labels : tuple with strings or None
+ The labels to use at each radial gridline. The
+ `matplotlib.ticker.ScalarFormatter` will be used if None.
+
+ angle : float
+ The angular position of the radius labels in degrees.
+
+ fmt : str or None
+ Format string used in `matplotlib.ticker.FormatStrFormatter`.
+ For example '%f'.
+
+ Returns
+ -------
+ lines : list of `.lines.Line2D`
+ The radial gridlines.
+
+ labels : list of `.text.Text`
+ The tick labels.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ *kwargs* are optional `.Text` properties for the labels.
+
+ .. warning::
+
+ This only sets the properties of the current ticks.
+ Ticks are not guaranteed to be persistent. Various operations
+ can create, delete and modify the Tick instances. There is an
+ imminent risk that these settings can get lost if you work on
+ the figure further (including also panning/zooming on a
+ displayed figure).
+
+ Use `.set_tick_params` instead if possible.
+
+ See Also
+ --------
+ .PolarAxes.set_thetagrids
+ .Axis.get_gridlines
+ .Axis.get_ticklabels
+ """
+ # Make sure we take into account unitized data
+ radii = self.convert_xunits(radii)
+ radii = np.asarray(radii)
+
+ self.set_yticks(radii)
+ if labels is not None:
+ self.set_yticklabels(labels)
+ elif fmt is not None:
+ self.yaxis.set_major_formatter(mticker.FormatStrFormatter(fmt))
+ if angle is None:
+ angle = self.get_rlabel_position()
+ self.set_rlabel_position(angle)
+ for t in self.yaxis.get_ticklabels():
+ t._internal_update(kwargs)
+ return self.yaxis.get_gridlines(), self.yaxis.get_ticklabels()
+
+ def format_coord(self, theta, r):
+ # docstring inherited
+ screen_xy = self.transData.transform((theta, r))
+ screen_xys = screen_xy + np.stack(
+ np.meshgrid([-1, 0, 1], [-1, 0, 1])).reshape((2, -1)).T
+ ts, rs = self.transData.inverted().transform(screen_xys).T
+ delta_t = abs((ts - theta + np.pi) % (2 * np.pi) - np.pi).max()
+ delta_t_halfturns = delta_t / np.pi
+ delta_t_degrees = delta_t_halfturns * 180
+ delta_r = abs(rs - r).max()
+ if theta < 0:
+ theta += 2 * np.pi
+ theta_halfturns = theta / np.pi
+ theta_degrees = theta_halfturns * 180
+
+ # See ScalarFormatter.format_data_short. For r, use #g-formatting
+ # (as for linear axes), but for theta, use f-formatting as scientific
+ # notation doesn't make sense and the trailing dot is ugly.
+ def format_sig(value, delta, opt, fmt):
+ # For "f", only count digits after decimal point.
+ prec = (max(0, -math.floor(math.log10(delta))) if fmt == "f" else
+ cbook._g_sig_digits(value, delta))
+ return f"{value:-{opt}.{prec}{fmt}}"
+
+ # In case fmt_xdata was not specified, resort to default
+
+ if self.fmt_ydata is None:
+ r_label = format_sig(r, delta_r, "#", "g")
+ else:
+ r_label = self.format_ydata(r)
+
+ if self.fmt_xdata is None:
+ return ('\N{GREEK SMALL LETTER THETA}={}\N{GREEK SMALL LETTER PI} '
+ '({}\N{DEGREE SIGN}), r={}').format(
+ format_sig(theta_halfturns, delta_t_halfturns, "", "f"),
+ format_sig(theta_degrees, delta_t_degrees, "", "f"),
+ r_label
+ )
+ else:
+ return '\N{GREEK SMALL LETTER THETA}={}, r={}'.format(
+ self.format_xdata(theta),
+ r_label
+ )
+
+ def get_data_ratio(self):
+ """
+ Return the aspect ratio of the data itself. For a polar plot,
+ this should always be 1.0
+ """
+ return 1.0
+
+ # # # Interactive panning
+
+ def can_zoom(self):
+ """
+ Return whether this Axes supports the zoom box button functionality.
+
+ A polar Axes does not support zoom boxes.
+ """
+ return False
+
+ def can_pan(self):
+ """
+ Return whether this Axes supports the pan/zoom button functionality.
+
+ For a polar Axes, this is slightly misleading. Both panning and
+ zooming are performed by the same button. Panning is performed
+ in azimuth while zooming is done along the radial.
+ """
+ return True
+
+ def start_pan(self, x, y, button):
+ angle = np.deg2rad(self.get_rlabel_position())
+ mode = ''
+ if button == 1:
+ epsilon = np.pi / 45.0
+ t, r = self.transData.inverted().transform((x, y))
+ if angle - epsilon <= t <= angle + epsilon:
+ mode = 'drag_r_labels'
+ elif button == 3:
+ mode = 'zoom'
+
+ self._pan_start = types.SimpleNamespace(
+ rmax=self.get_rmax(),
+ trans=self.transData.frozen(),
+ trans_inverse=self.transData.inverted().frozen(),
+ r_label_angle=self.get_rlabel_position(),
+ x=x,
+ y=y,
+ mode=mode)
+
+ def end_pan(self):
+ del self._pan_start
+
+ def drag_pan(self, button, key, x, y):
+ p = self._pan_start
+
+ if p.mode == 'drag_r_labels':
+ (startt, startr), (t, r) = p.trans_inverse.transform(
+ [(p.x, p.y), (x, y)])
+
+ # Deal with theta
+ dt = np.rad2deg(startt - t)
+ self.set_rlabel_position(p.r_label_angle - dt)
+
+ trans, vert1, horiz1 = self.get_yaxis_text1_transform(0.0)
+ trans, vert2, horiz2 = self.get_yaxis_text2_transform(0.0)
+ for t in self.yaxis.majorTicks + self.yaxis.minorTicks:
+ t.label1.set_va(vert1)
+ t.label1.set_ha(horiz1)
+ t.label2.set_va(vert2)
+ t.label2.set_ha(horiz2)
+
+ elif p.mode == 'zoom':
+ (startt, startr), (t, r) = p.trans_inverse.transform(
+ [(p.x, p.y), (x, y)])
+
+ # Deal with r
+ scale = r / startr
+ self.set_rmax(p.rmax / scale)
+
+
+# To keep things all self-contained, we can put aliases to the Polar classes
+# defined above. This isn't strictly necessary, but it makes some of the
+# code more readable, and provides a backwards compatible Polar API. In
+# particular, this is used by the :doc:`/gallery/specialty_plots/radar_chart`
+# example to override PolarTransform on a PolarAxes subclass, so make sure that
+# that example is unaffected before changing this.
+PolarAxes.PolarTransform = PolarTransform
+PolarAxes.PolarAffine = PolarAffine
+PolarAxes.InvertedPolarTransform = InvertedPolarTransform
+PolarAxes.ThetaFormatter = ThetaFormatter
+PolarAxes.RadialLocator = RadialLocator
+PolarAxes.ThetaLocator = ThetaLocator
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/polar.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/polar.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..de1cbc293900382e25e6ebfb6e94a3409ad16462
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/projections/polar.pyi
@@ -0,0 +1,197 @@
+import matplotlib.axis as maxis
+import matplotlib.ticker as mticker
+import matplotlib.transforms as mtransforms
+from matplotlib.axes import Axes
+from matplotlib.lines import Line2D
+from matplotlib.text import Text
+
+import numpy as np
+from numpy.typing import ArrayLike
+from collections.abc import Sequence
+from typing import Any, ClassVar, Literal, overload
+
+class PolarTransform(mtransforms.Transform):
+ input_dims: int
+ output_dims: int
+ def __init__(
+ self,
+ axis: PolarAxes | None = ...,
+ use_rmin: bool = ...,
+ *,
+ apply_theta_transforms: bool = ...,
+ scale_transform: mtransforms.Transform | None = ...,
+ ) -> None: ...
+ def inverted(self) -> InvertedPolarTransform: ...
+
+class PolarAffine(mtransforms.Affine2DBase):
+ def __init__(
+ self, scale_transform: mtransforms.Transform, limits: mtransforms.BboxBase
+ ) -> None: ...
+
+class InvertedPolarTransform(mtransforms.Transform):
+ input_dims: int
+ output_dims: int
+ def __init__(
+ self,
+ axis: PolarAxes | None = ...,
+ use_rmin: bool = ...,
+ *,
+ apply_theta_transforms: bool = ...,
+ ) -> None: ...
+ def inverted(self) -> PolarTransform: ...
+
+class ThetaFormatter(mticker.Formatter): ...
+
+class _AxisWrapper:
+ def __init__(self, axis: maxis.Axis) -> None: ...
+ def get_view_interval(self) -> np.ndarray: ...
+ def set_view_interval(self, vmin: float, vmax: float) -> None: ...
+ def get_minpos(self) -> float: ...
+ def get_data_interval(self) -> np.ndarray: ...
+ def set_data_interval(self, vmin: float, vmax: float) -> None: ...
+ def get_tick_space(self) -> int: ...
+
+class ThetaLocator(mticker.Locator):
+ base: mticker.Locator
+ axis: _AxisWrapper | None
+ def __init__(self, base: mticker.Locator) -> None: ...
+
+class ThetaTick(maxis.XTick):
+ def __init__(self, axes: PolarAxes, *args, **kwargs) -> None: ...
+
+class ThetaAxis(maxis.XAxis):
+ axis_name: str
+
+class RadialLocator(mticker.Locator):
+ base: mticker.Locator
+ def __init__(self, base, axes: PolarAxes | None = ...) -> None: ...
+
+class RadialTick(maxis.YTick): ...
+
+class RadialAxis(maxis.YAxis):
+ axis_name: str
+
+class _WedgeBbox(mtransforms.Bbox):
+ def __init__(
+ self,
+ center: tuple[float, float],
+ viewLim: mtransforms.Bbox,
+ originLim: mtransforms.Bbox,
+ **kwargs,
+ ) -> None: ...
+
+class PolarAxes(Axes):
+
+ PolarTransform: ClassVar[type] = PolarTransform
+ PolarAffine: ClassVar[type] = PolarAffine
+ InvertedPolarTransform: ClassVar[type] = InvertedPolarTransform
+ ThetaFormatter: ClassVar[type] = ThetaFormatter
+ RadialLocator: ClassVar[type] = RadialLocator
+ ThetaLocator: ClassVar[type] = ThetaLocator
+
+ name: str
+ use_sticky_edges: bool
+ def __init__(
+ self,
+ *args,
+ theta_offset: float = ...,
+ theta_direction: float = ...,
+ rlabel_position: float = ...,
+ **kwargs,
+ ) -> None: ...
+ def get_xaxis_transform(
+ self, which: Literal["tick1", "tick2", "grid"] = ...
+ ) -> mtransforms.Transform: ...
+ def get_xaxis_text1_transform(
+ self, pad: float
+ ) -> tuple[
+ mtransforms.Transform,
+ Literal["center", "top", "bottom", "baseline", "center_baseline"],
+ Literal["center", "left", "right"],
+ ]: ...
+ def get_xaxis_text2_transform(
+ self, pad: float
+ ) -> tuple[
+ mtransforms.Transform,
+ Literal["center", "top", "bottom", "baseline", "center_baseline"],
+ Literal["center", "left", "right"],
+ ]: ...
+ def get_yaxis_transform(
+ self, which: Literal["tick1", "tick2", "grid"] = ...
+ ) -> mtransforms.Transform: ...
+ def get_yaxis_text1_transform(
+ self, pad: float
+ ) -> tuple[
+ mtransforms.Transform,
+ Literal["center", "top", "bottom", "baseline", "center_baseline"],
+ Literal["center", "left", "right"],
+ ]: ...
+ def get_yaxis_text2_transform(
+ self, pad: float
+ ) -> tuple[
+ mtransforms.Transform,
+ Literal["center", "top", "bottom", "baseline", "center_baseline"],
+ Literal["center", "left", "right"],
+ ]: ...
+ def set_thetamax(self, thetamax: float) -> None: ...
+ def get_thetamax(self) -> float: ...
+ def set_thetamin(self, thetamin: float) -> None: ...
+ def get_thetamin(self) -> float: ...
+ @overload
+ def set_thetalim(self, minval: float, maxval: float, /) -> tuple[float, float]: ...
+ @overload
+ def set_thetalim(self, *, thetamin: float, thetamax: float) -> tuple[float, float]: ...
+ def set_theta_offset(self, offset: float) -> None: ...
+ def get_theta_offset(self) -> float: ...
+ def set_theta_zero_location(
+ self,
+ loc: Literal["N", "NW", "W", "SW", "S", "SE", "E", "NE"],
+ offset: float = ...,
+ ) -> None: ...
+ def set_theta_direction(
+ self,
+ direction: Literal[-1, 1, "clockwise", "counterclockwise", "anticlockwise"],
+ ) -> None: ...
+ def get_theta_direction(self) -> Literal[-1, 1]: ...
+ def set_rmax(self, rmax: float) -> None: ...
+ def get_rmax(self) -> float: ...
+ def set_rmin(self, rmin: float) -> None: ...
+ def get_rmin(self) -> float: ...
+ def set_rorigin(self, rorigin: float | None) -> None: ...
+ def get_rorigin(self) -> float: ...
+ def get_rsign(self) -> float: ...
+ def set_rlim(
+ self,
+ bottom: float | tuple[float, float] | None = ...,
+ top: float | None = ...,
+ *,
+ emit: bool = ...,
+ auto: bool = ...,
+ **kwargs,
+ ) -> tuple[float, float]: ...
+ def get_rlabel_position(self) -> float: ...
+ def set_rlabel_position(self, value: float) -> None: ...
+ def set_rscale(self, *args, **kwargs) -> None: ...
+ def set_rticks(self, *args, **kwargs) -> None: ...
+ def set_thetagrids(
+ self,
+ angles: ArrayLike,
+ labels: Sequence[str | Text] | None = ...,
+ fmt: str | None = ...,
+ **kwargs,
+ ) -> tuple[list[Line2D], list[Text]]: ...
+ def set_rgrids(
+ self,
+ radii: ArrayLike,
+ labels: Sequence[str | Text] | None = ...,
+ angle: float | None = ...,
+ fmt: str | None = ...,
+ **kwargs,
+ ) -> tuple[list[Line2D], list[Text]]: ...
+ def format_coord(self, theta: float, r: float) -> str: ...
+ def get_data_ratio(self) -> float: ...
+ def can_zoom(self) -> bool: ...
+ def can_pan(self) -> bool: ...
+ def start_pan(self, x: float, y: float, button: int) -> None: ...
+ def end_pan(self) -> None: ...
+ def drag_pan(self, button: Any, key: Any, x: float, y: float) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/py.typed b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/pylab.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/pylab.py
new file mode 100644
index 0000000000000000000000000000000000000000..a50779cf6d264408f1f99a086a25cb5c9d525588
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/pylab.py
@@ -0,0 +1,67 @@
+"""
+`pylab` is a historic interface and its use is strongly discouraged. The equivalent
+replacement is `matplotlib.pyplot`. See :ref:`api_interfaces` for a full overview
+of Matplotlib interfaces.
+
+`pylab` was designed to support a MATLAB-like way of working with all plotting related
+functions directly available in the global namespace. This was achieved through a
+wildcard import (``from pylab import *``).
+
+.. warning::
+ The use of `pylab` is discouraged for the following reasons:
+
+ ``from pylab import *`` imports all the functions from `matplotlib.pyplot`, `numpy`,
+ `numpy.fft`, `numpy.linalg`, and `numpy.random`, and some additional functions into
+ the global namespace.
+
+ Such a pattern is considered bad practice in modern python, as it clutters the global
+ namespace. Even more severely, in the case of `pylab`, this will overwrite some
+ builtin functions (e.g. the builtin `sum` will be replaced by `numpy.sum`), which
+ can lead to unexpected behavior.
+
+"""
+
+from matplotlib.cbook import flatten, silent_list
+
+import matplotlib as mpl
+
+from matplotlib.dates import (
+ date2num, num2date, datestr2num, drange, DateFormatter, DateLocator,
+ RRuleLocator, YearLocator, MonthLocator, WeekdayLocator, DayLocator,
+ HourLocator, MinuteLocator, SecondLocator, rrule, MO, TU, WE, TH, FR,
+ SA, SU, YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY,
+ relativedelta)
+
+# bring all the symbols in so folks can import them from
+# pylab in one fell swoop
+
+## We are still importing too many things from mlab; more cleanup is needed.
+
+from matplotlib.mlab import (
+ detrend, detrend_linear, detrend_mean, detrend_none, window_hanning,
+ window_none)
+
+from matplotlib import cbook, mlab, pyplot as plt
+from matplotlib.pyplot import *
+
+from numpy import *
+from numpy.fft import *
+from numpy.random import *
+from numpy.linalg import *
+
+import numpy as np
+import numpy.ma as ma
+
+# don't let numpy's datetime hide stdlib
+import datetime
+
+# This is needed, or bytes will be numpy.random.bytes from
+# "from numpy.random import *" above
+bytes = __import__("builtins").bytes
+# We also don't want the numpy version of these functions
+abs = __import__("builtins").abs
+bool = __import__("builtins").bool
+max = __import__("builtins").max
+min = __import__("builtins").min
+pow = __import__("builtins").pow
+round = __import__("builtins").round
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/pyplot.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/pyplot.py
new file mode 100644
index 0000000000000000000000000000000000000000..562fc02715ecbb037c4237b7bf8e9fb0f72cdd8e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/pyplot.py
@@ -0,0 +1,4644 @@
+# Note: The first part of this file can be modified in place, but the latter
+# part is autogenerated by the boilerplate.py script.
+
+"""
+`matplotlib.pyplot` is a state-based interface to matplotlib. It provides
+an implicit, MATLAB-like, way of plotting. It also opens figures on your
+screen, and acts as the figure GUI manager.
+
+pyplot is mainly intended for interactive plots and simple cases of
+programmatic plot generation::
+
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ x = np.arange(0, 5, 0.1)
+ y = np.sin(x)
+ plt.plot(x, y)
+ plt.show()
+
+The explicit object-oriented API is recommended for complex plots, though
+pyplot is still usually used to create the figure and often the Axes in the
+figure. See `.pyplot.figure`, `.pyplot.subplots`, and
+`.pyplot.subplot_mosaic` to create figures, and
+:doc:`Axes API ` for the plotting methods on an Axes::
+
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ x = np.arange(0, 5, 0.1)
+ y = np.sin(x)
+ fig, ax = plt.subplots()
+ ax.plot(x, y)
+ plt.show()
+
+
+See :ref:`api_interfaces` for an explanation of the tradeoffs between the
+implicit and explicit interfaces.
+"""
+
+# fmt: off
+
+from __future__ import annotations
+
+from contextlib import AbstractContextManager, ExitStack
+from enum import Enum
+import functools
+import importlib
+import inspect
+import logging
+import sys
+import threading
+import time
+from typing import TYPE_CHECKING, cast, overload
+
+from cycler import cycler # noqa: F401
+import matplotlib
+import matplotlib.image
+from matplotlib import _api
+# Re-exported (import x as x) for typing.
+from matplotlib import get_backend as get_backend, rcParams as rcParams
+from matplotlib import cm as cm # noqa: F401
+from matplotlib import style as style # noqa: F401
+from matplotlib import _pylab_helpers
+from matplotlib import interactive # noqa: F401
+from matplotlib import cbook
+from matplotlib import _docstring
+from matplotlib.backend_bases import (
+ FigureCanvasBase, FigureManagerBase, MouseButton)
+from matplotlib.figure import Figure, FigureBase, figaspect
+from matplotlib.gridspec import GridSpec, SubplotSpec
+from matplotlib import rcsetup, rcParamsDefault, rcParamsOrig
+from matplotlib.artist import Artist
+from matplotlib.axes import Axes
+from matplotlib.axes import Subplot # noqa: F401
+from matplotlib.backends import BackendFilter, backend_registry
+from matplotlib.projections import PolarAxes
+from matplotlib.colorizer import _ColorizerInterface, ColorizingArtist, Colorizer
+from matplotlib import mlab # for detrend_none, window_hanning
+from matplotlib.scale import get_scale_names # noqa: F401
+
+from matplotlib.cm import _colormaps
+from matplotlib.colors import _color_sequences, Colormap
+
+import numpy as np
+
+if TYPE_CHECKING:
+ from collections.abc import Callable, Hashable, Iterable, Sequence
+ import datetime
+ import pathlib
+ import os
+ from typing import Any, BinaryIO, Literal, TypeVar
+ from typing_extensions import ParamSpec
+
+ import PIL.Image
+ from numpy.typing import ArrayLike
+
+ import matplotlib.axes
+ import matplotlib.artist
+ import matplotlib.backend_bases
+ from matplotlib.axis import Tick
+ from matplotlib.axes._base import _AxesBase
+ from matplotlib.backend_bases import Event
+ from matplotlib.cm import ScalarMappable
+ from matplotlib.contour import ContourSet, QuadContourSet
+ from matplotlib.collections import (
+ Collection,
+ FillBetweenPolyCollection,
+ LineCollection,
+ PolyCollection,
+ PathCollection,
+ EventCollection,
+ QuadMesh,
+ )
+ from matplotlib.colorbar import Colorbar
+ from matplotlib.container import (
+ BarContainer,
+ ErrorbarContainer,
+ StemContainer,
+ )
+ from matplotlib.figure import SubFigure
+ from matplotlib.legend import Legend
+ from matplotlib.mlab import GaussianKDE
+ from matplotlib.image import AxesImage, FigureImage
+ from matplotlib.patches import FancyArrow, StepPatch, Wedge
+ from matplotlib.quiver import Barbs, Quiver, QuiverKey
+ from matplotlib.scale import ScaleBase
+ from matplotlib.typing import (
+ ColorType,
+ CoordsType,
+ HashableList,
+ LineStyleType,
+ MarkerType,
+ )
+ from matplotlib.widgets import SubplotTool
+
+ _P = ParamSpec('_P')
+ _R = TypeVar('_R')
+ _T = TypeVar('_T')
+
+
+# We may not need the following imports here:
+from matplotlib.colors import Normalize
+from matplotlib.lines import Line2D, AxLine
+from matplotlib.text import Text, Annotation
+from matplotlib.patches import Arrow, Circle, Rectangle # noqa: F401
+from matplotlib.patches import Polygon
+from matplotlib.widgets import Button, Slider, Widget # noqa: F401
+
+from .ticker import ( # noqa: F401
+ TickHelper, Formatter, FixedFormatter, NullFormatter, FuncFormatter,
+ FormatStrFormatter, ScalarFormatter, LogFormatter, LogFormatterExponent,
+ LogFormatterMathtext, Locator, IndexLocator, FixedLocator, NullLocator,
+ LinearLocator, LogLocator, AutoLocator, MultipleLocator, MaxNLocator)
+
+_log = logging.getLogger(__name__)
+
+
+# Explicit rename instead of import-as for typing's sake.
+colormaps = _colormaps
+color_sequences = _color_sequences
+
+
+@overload
+def _copy_docstring_and_deprecators(
+ method: Any,
+ func: Literal[None] = None
+) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
+
+
+@overload
+def _copy_docstring_and_deprecators(
+ method: Any, func: Callable[_P, _R]) -> Callable[_P, _R]: ...
+
+
+def _copy_docstring_and_deprecators(
+ method: Any,
+ func: Callable[_P, _R] | None = None
+) -> Callable[[Callable[_P, _R]], Callable[_P, _R]] | Callable[_P, _R]:
+ if func is None:
+ return cast('Callable[[Callable[_P, _R]], Callable[_P, _R]]',
+ functools.partial(_copy_docstring_and_deprecators, method))
+ decorators: list[Callable[[Callable[_P, _R]], Callable[_P, _R]]] = [
+ _docstring.copy(method)
+ ]
+ # Check whether the definition of *method* includes @_api.rename_parameter
+ # or @_api.make_keyword_only decorators; if so, propagate them to the
+ # pyplot wrapper as well.
+ while hasattr(method, "__wrapped__"):
+ potential_decorator = _api.deprecation.DECORATORS.get(method)
+ if potential_decorator:
+ decorators.append(potential_decorator)
+ method = method.__wrapped__
+ for decorator in decorators[::-1]:
+ func = decorator(func)
+ _add_pyplot_note(func, method)
+ return func
+
+
+_NO_PYPLOT_NOTE = [
+ 'FigureBase._gci', # wrapped_func is private
+ '_AxesBase._sci', # wrapped_func is private
+ 'Artist.findobj', # not a standard pyplot wrapper because it does not operate
+ # on the current Figure / Axes. Explanation of relation would
+ # be more complex and is not too important.
+]
+
+
+def _add_pyplot_note(func, wrapped_func):
+ """
+ Add a note to the docstring of *func* that it is a pyplot wrapper.
+
+ The note is added to the "Notes" section of the docstring. If that does
+ not exist, a "Notes" section is created. In numpydoc, the "Notes"
+ section is the third last possible section, only potentially followed by
+ "References" and "Examples".
+ """
+ if not func.__doc__:
+ return # nothing to do
+
+ qualname = wrapped_func.__qualname__
+ if qualname in _NO_PYPLOT_NOTE:
+ return
+
+ wrapped_func_is_method = True
+ if "." not in qualname:
+ # method qualnames are prefixed by the class and ".", e.g. "Axes.plot"
+ wrapped_func_is_method = False
+ link = f"{wrapped_func.__module__}.{qualname}"
+ elif qualname.startswith("Axes."): # e.g. "Axes.plot"
+ link = ".axes." + qualname
+ elif qualname.startswith("_AxesBase."): # e.g. "_AxesBase.set_xlabel"
+ link = ".axes.Axes" + qualname[9:]
+ elif qualname.startswith("Figure."): # e.g. "Figure.figimage"
+ link = "." + qualname
+ elif qualname.startswith("FigureBase."): # e.g. "FigureBase.gca"
+ link = ".Figure" + qualname[10:]
+ elif qualname.startswith("FigureCanvasBase."): # "FigureBaseCanvas.mpl_connect"
+ link = "." + qualname
+ else:
+ raise RuntimeError(f"Wrapped method from unexpected class: {qualname}")
+
+ if wrapped_func_is_method:
+ message = f"This is the :ref:`pyplot wrapper ` for `{link}`."
+ else:
+ message = f"This is equivalent to `{link}`."
+
+ # Find the correct insert position:
+ # - either we already have a "Notes" section into which we can insert
+ # - or we create one before the next present section. Note that in numpydoc, the
+ # "Notes" section is the third last possible section, only potentially followed
+ # by "References" and "Examples".
+ # - or we append a new "Notes" section at the end.
+ doc = inspect.cleandoc(func.__doc__)
+ if "\nNotes\n-----" in doc:
+ before, after = doc.split("\nNotes\n-----", 1)
+ elif (index := doc.find("\nReferences\n----------")) != -1:
+ before, after = doc[:index], doc[index:]
+ elif (index := doc.find("\nExamples\n--------")) != -1:
+ before, after = doc[:index], doc[index:]
+ else:
+ # No "Notes", "References", or "Examples" --> append to the end.
+ before = doc + "\n"
+ after = ""
+
+ func.__doc__ = f"{before}\nNotes\n-----\n\n.. note::\n\n {message}\n{after}"
+
+
+## Global ##
+
+
+# The state controlled by {,un}install_repl_displayhook().
+_ReplDisplayHook = Enum("_ReplDisplayHook", ["NONE", "PLAIN", "IPYTHON"])
+_REPL_DISPLAYHOOK = _ReplDisplayHook.NONE
+
+
+def _draw_all_if_interactive() -> None:
+ if matplotlib.is_interactive():
+ draw_all()
+
+
+def install_repl_displayhook() -> None:
+ """
+ Connect to the display hook of the current shell.
+
+ The display hook gets called when the read-evaluate-print-loop (REPL) of
+ the shell has finished the execution of a command. We use this callback
+ to be able to automatically update a figure in interactive mode.
+
+ This works both with IPython and with vanilla python shells.
+ """
+ global _REPL_DISPLAYHOOK
+
+ if _REPL_DISPLAYHOOK is _ReplDisplayHook.IPYTHON:
+ return
+
+ # See if we have IPython hooks around, if so use them.
+ # Use ``sys.modules.get(name)`` rather than ``name in sys.modules`` as
+ # entries can also have been explicitly set to None.
+ mod_ipython = sys.modules.get("IPython")
+ if not mod_ipython:
+ _REPL_DISPLAYHOOK = _ReplDisplayHook.PLAIN
+ return
+ ip = mod_ipython.get_ipython()
+ if not ip:
+ _REPL_DISPLAYHOOK = _ReplDisplayHook.PLAIN
+ return
+
+ ip.events.register("post_execute", _draw_all_if_interactive)
+ _REPL_DISPLAYHOOK = _ReplDisplayHook.IPYTHON
+
+ if mod_ipython.version_info[:2] < (8, 24):
+ # Use of backend2gui is not needed for IPython >= 8.24 as that functionality
+ # has been moved to Matplotlib.
+ # This code can be removed when Python 3.12, the latest version supported by
+ # IPython < 8.24, reaches end-of-life in late 2028.
+ from IPython.core.pylabtools import backend2gui
+ ipython_gui_name = backend2gui.get(get_backend())
+ else:
+ _, ipython_gui_name = backend_registry.resolve_backend(get_backend())
+ # trigger IPython's eventloop integration, if available
+ if ipython_gui_name:
+ ip.enable_gui(ipython_gui_name)
+
+
+def uninstall_repl_displayhook() -> None:
+ """Disconnect from the display hook of the current shell."""
+ global _REPL_DISPLAYHOOK
+ if _REPL_DISPLAYHOOK is _ReplDisplayHook.IPYTHON:
+ from IPython import get_ipython
+ ip = get_ipython()
+ ip.events.unregister("post_execute", _draw_all_if_interactive)
+ _REPL_DISPLAYHOOK = _ReplDisplayHook.NONE
+
+
+draw_all = _pylab_helpers.Gcf.draw_all
+
+
+# Ensure this appears in the pyplot docs.
+@_copy_docstring_and_deprecators(matplotlib.set_loglevel)
+def set_loglevel(*args, **kwargs) -> None:
+ return matplotlib.set_loglevel(*args, **kwargs)
+
+
+@_copy_docstring_and_deprecators(Artist.findobj)
+def findobj(
+ o: Artist | None = None,
+ match: Callable[[Artist], bool] | type[Artist] | None = None,
+ include_self: bool = True
+) -> list[Artist]:
+ if o is None:
+ o = gcf()
+ return o.findobj(match, include_self=include_self)
+
+
+_backend_mod: type[matplotlib.backend_bases._Backend] | None = None
+
+
+def _get_backend_mod() -> type[matplotlib.backend_bases._Backend]:
+ """
+ Ensure that a backend is selected and return it.
+
+ This is currently private, but may be made public in the future.
+ """
+ if _backend_mod is None:
+ # Use rcParams._get("backend") to avoid going through the fallback
+ # logic (which will (re)import pyplot and then call switch_backend if
+ # we need to resolve the auto sentinel)
+ switch_backend(rcParams._get("backend"))
+ return cast(type[matplotlib.backend_bases._Backend], _backend_mod)
+
+
+def switch_backend(newbackend: str) -> None:
+ """
+ Set the pyplot backend.
+
+ Switching to an interactive backend is possible only if no event loop for
+ another interactive backend has started. Switching to and from
+ non-interactive backends is always possible.
+
+ If the new backend is different than the current backend then all open
+ Figures will be closed via ``plt.close('all')``.
+
+ Parameters
+ ----------
+ newbackend : str
+ The case-insensitive name of the backend to use.
+
+ """
+ global _backend_mod
+ # make sure the init is pulled up so we can assign to it later
+ import matplotlib.backends
+
+ if newbackend is rcsetup._auto_backend_sentinel:
+ current_framework = cbook._get_running_interactive_framework()
+
+ if (current_framework and
+ (backend := backend_registry.backend_for_gui_framework(
+ current_framework))):
+ candidates = [backend]
+ else:
+ candidates = []
+ candidates += [
+ "macosx", "qtagg", "gtk4agg", "gtk3agg", "tkagg", "wxagg"]
+
+ # Don't try to fallback on the cairo-based backends as they each have
+ # an additional dependency (pycairo) over the agg-based backend, and
+ # are of worse quality.
+ for candidate in candidates:
+ try:
+ switch_backend(candidate)
+ except ImportError:
+ continue
+ else:
+ rcParamsOrig['backend'] = candidate
+ return
+ else:
+ # Switching to Agg should always succeed; if it doesn't, let the
+ # exception propagate out.
+ switch_backend("agg")
+ rcParamsOrig["backend"] = "agg"
+ return
+ old_backend = rcParams._get('backend') # get without triggering backend resolution
+
+ module = backend_registry.load_backend_module(newbackend)
+ canvas_class = module.FigureCanvas
+
+ required_framework = canvas_class.required_interactive_framework
+ if required_framework is not None:
+ current_framework = cbook._get_running_interactive_framework()
+ if (current_framework and required_framework
+ and current_framework != required_framework):
+ raise ImportError(
+ "Cannot load backend {!r} which requires the {!r} interactive "
+ "framework, as {!r} is currently running".format(
+ newbackend, required_framework, current_framework))
+
+ # Load the new_figure_manager() and show() functions from the backend.
+
+ # Classically, backends can directly export these functions. This should
+ # keep working for backcompat.
+ new_figure_manager = getattr(module, "new_figure_manager", None)
+ show = getattr(module, "show", None)
+
+ # In that classical approach, backends are implemented as modules, but
+ # "inherit" default method implementations from backend_bases._Backend.
+ # This is achieved by creating a "class" that inherits from
+ # backend_bases._Backend and whose body is filled with the module globals.
+ class backend_mod(matplotlib.backend_bases._Backend):
+ locals().update(vars(module))
+
+ # However, the newer approach for defining new_figure_manager and
+ # show is to derive them from canvas methods. In that case, also
+ # update backend_mod accordingly; also, per-backend customization of
+ # draw_if_interactive is disabled.
+ if new_figure_manager is None:
+
+ def new_figure_manager_given_figure(num, figure):
+ return canvas_class.new_manager(figure, num)
+
+ def new_figure_manager(num, *args, FigureClass=Figure, **kwargs):
+ fig = FigureClass(*args, **kwargs)
+ return new_figure_manager_given_figure(num, fig)
+
+ def draw_if_interactive() -> None:
+ if matplotlib.is_interactive():
+ manager = _pylab_helpers.Gcf.get_active()
+ if manager:
+ manager.canvas.draw_idle()
+
+ backend_mod.new_figure_manager_given_figure = ( # type: ignore[method-assign]
+ new_figure_manager_given_figure)
+ backend_mod.new_figure_manager = ( # type: ignore[method-assign]
+ new_figure_manager)
+ backend_mod.draw_if_interactive = ( # type: ignore[method-assign]
+ draw_if_interactive)
+
+ # If the manager explicitly overrides pyplot_show, use it even if a global
+ # show is already present, as the latter may be here for backcompat.
+ manager_class = getattr(canvas_class, "manager_class", None)
+ # We can't compare directly manager_class.pyplot_show and FMB.pyplot_show because
+ # pyplot_show is a classmethod so the above constructs are bound classmethods, and
+ # thus always different (being bound to different classes). We also have to use
+ # getattr_static instead of vars as manager_class could have no __dict__.
+ manager_pyplot_show = inspect.getattr_static(manager_class, "pyplot_show", None)
+ base_pyplot_show = inspect.getattr_static(FigureManagerBase, "pyplot_show", None)
+ if (show is None
+ or (manager_pyplot_show is not None
+ and manager_pyplot_show != base_pyplot_show)):
+ if not manager_pyplot_show:
+ raise ValueError(
+ f"Backend {newbackend} defines neither FigureCanvas.manager_class nor "
+ f"a toplevel show function")
+ _pyplot_show = cast('Any', manager_class).pyplot_show
+ backend_mod.show = _pyplot_show # type: ignore[method-assign]
+
+ _log.debug("Loaded backend %s version %s.",
+ newbackend, backend_mod.backend_version)
+
+ if newbackend in ("ipympl", "widget"):
+ # ipympl < 0.9.4 expects rcParams["backend"] to be the fully-qualified backend
+ # name "module://ipympl.backend_nbagg" not short names "ipympl" or "widget".
+ import importlib.metadata as im
+ from matplotlib import _parse_to_version_info # type: ignore[attr-defined]
+ try:
+ module_version = im.version("ipympl")
+ if _parse_to_version_info(module_version) < (0, 9, 4):
+ newbackend = "module://ipympl.backend_nbagg"
+ except im.PackageNotFoundError:
+ pass
+
+ rcParams['backend'] = rcParamsDefault['backend'] = newbackend
+ _backend_mod = backend_mod
+ for func_name in ["new_figure_manager", "draw_if_interactive", "show"]:
+ globals()[func_name].__signature__ = inspect.signature(
+ getattr(backend_mod, func_name))
+
+ # Need to keep a global reference to the backend for compatibility reasons.
+ # See https://github.com/matplotlib/matplotlib/issues/6092
+ matplotlib.backends.backend = newbackend # type: ignore[attr-defined]
+
+ # Make sure the repl display hook is installed in case we become interactive.
+ install_repl_displayhook()
+
+
+def _warn_if_gui_out_of_main_thread() -> None:
+ warn = False
+ canvas_class = cast(type[FigureCanvasBase], _get_backend_mod().FigureCanvas)
+ if canvas_class.required_interactive_framework:
+ if hasattr(threading, 'get_native_id'):
+ # This compares native thread ids because even if Python-level
+ # Thread objects match, the underlying OS thread (which is what
+ # really matters) may be different on Python implementations with
+ # green threads.
+ if threading.get_native_id() != threading.main_thread().native_id:
+ warn = True
+ else:
+ # Fall back to Python-level Thread if native IDs are unavailable,
+ # mainly for PyPy.
+ if threading.current_thread() is not threading.main_thread():
+ warn = True
+ if warn:
+ _api.warn_external(
+ "Starting a Matplotlib GUI outside of the main thread will likely "
+ "fail.")
+
+
+# This function's signature is rewritten upon backend-load by switch_backend.
+def new_figure_manager(*args, **kwargs):
+ """Create a new figure manager instance."""
+ _warn_if_gui_out_of_main_thread()
+ return _get_backend_mod().new_figure_manager(*args, **kwargs)
+
+
+# This function's signature is rewritten upon backend-load by switch_backend.
+def draw_if_interactive(*args, **kwargs):
+ """
+ Redraw the current figure if in interactive mode.
+
+ .. warning::
+
+ End users will typically not have to call this function because the
+ the interactive mode takes care of this.
+ """
+ return _get_backend_mod().draw_if_interactive(*args, **kwargs)
+
+
+# This function's signature is rewritten upon backend-load by switch_backend.
+def show(*args, **kwargs) -> None:
+ """
+ Display all open figures.
+
+ Parameters
+ ----------
+ block : bool, optional
+ Whether to wait for all figures to be closed before returning.
+
+ If `True` block and run the GUI main loop until all figure windows
+ are closed.
+
+ If `False` ensure that all figure windows are displayed and return
+ immediately. In this case, you are responsible for ensuring
+ that the event loop is running to have responsive figures.
+
+ Defaults to True in non-interactive mode and to False in interactive
+ mode (see `.pyplot.isinteractive`).
+
+ See Also
+ --------
+ ion : Enable interactive mode, which shows / updates the figure after
+ every plotting command, so that calling ``show()`` is not necessary.
+ ioff : Disable interactive mode.
+ savefig : Save the figure to an image file instead of showing it on screen.
+
+ Notes
+ -----
+ **Saving figures to file and showing a window at the same time**
+
+ If you want an image file as well as a user interface window, use
+ `.pyplot.savefig` before `.pyplot.show`. At the end of (a blocking)
+ ``show()`` the figure is closed and thus unregistered from pyplot. Calling
+ `.pyplot.savefig` afterwards would save a new and thus empty figure. This
+ limitation of command order does not apply if the show is non-blocking or
+ if you keep a reference to the figure and use `.Figure.savefig`.
+
+ **Auto-show in jupyter notebooks**
+
+ The jupyter backends (activated via ``%matplotlib inline``,
+ ``%matplotlib notebook``, or ``%matplotlib widget``), call ``show()`` at
+ the end of every cell by default. Thus, you usually don't have to call it
+ explicitly there.
+ """
+ _warn_if_gui_out_of_main_thread()
+ return _get_backend_mod().show(*args, **kwargs)
+
+
+def isinteractive() -> bool:
+ """
+ Return whether plots are updated after every plotting command.
+
+ The interactive mode is mainly useful if you build plots from the command
+ line and want to see the effect of each command while you are building the
+ figure.
+
+ In interactive mode:
+
+ - newly created figures will be shown immediately;
+ - figures will automatically redraw on change;
+ - `.pyplot.show` will not block by default.
+
+ In non-interactive mode:
+
+ - newly created figures and changes to figures will not be reflected until
+ explicitly asked to be;
+ - `.pyplot.show` will block by default.
+
+ See Also
+ --------
+ ion : Enable interactive mode.
+ ioff : Disable interactive mode.
+ show : Show all figures (and maybe block).
+ pause : Show all figures, and block for a time.
+ """
+ return matplotlib.is_interactive()
+
+
+# Note: The return type of ioff being AbstractContextManager
+# instead of ExitStack is deliberate.
+# See https://github.com/matplotlib/matplotlib/issues/27659
+# and https://github.com/matplotlib/matplotlib/pull/27667 for more info.
+def ioff() -> AbstractContextManager:
+ """
+ Disable interactive mode.
+
+ See `.pyplot.isinteractive` for more details.
+
+ See Also
+ --------
+ ion : Enable interactive mode.
+ isinteractive : Whether interactive mode is enabled.
+ show : Show all figures (and maybe block).
+ pause : Show all figures, and block for a time.
+
+ Notes
+ -----
+ For a temporary change, this can be used as a context manager::
+
+ # if interactive mode is on
+ # then figures will be shown on creation
+ plt.ion()
+ # This figure will be shown immediately
+ fig = plt.figure()
+
+ with plt.ioff():
+ # interactive mode will be off
+ # figures will not automatically be shown
+ fig2 = plt.figure()
+ # ...
+
+ To enable optional usage as a context manager, this function returns a
+ context manager object, which is not intended to be stored or
+ accessed by the user.
+ """
+ stack = ExitStack()
+ stack.callback(ion if isinteractive() else ioff)
+ matplotlib.interactive(False)
+ uninstall_repl_displayhook()
+ return stack
+
+
+# Note: The return type of ion being AbstractContextManager
+# instead of ExitStack is deliberate.
+# See https://github.com/matplotlib/matplotlib/issues/27659
+# and https://github.com/matplotlib/matplotlib/pull/27667 for more info.
+def ion() -> AbstractContextManager:
+ """
+ Enable interactive mode.
+
+ See `.pyplot.isinteractive` for more details.
+
+ See Also
+ --------
+ ioff : Disable interactive mode.
+ isinteractive : Whether interactive mode is enabled.
+ show : Show all figures (and maybe block).
+ pause : Show all figures, and block for a time.
+
+ Notes
+ -----
+ For a temporary change, this can be used as a context manager::
+
+ # if interactive mode is off
+ # then figures will not be shown on creation
+ plt.ioff()
+ # This figure will not be shown immediately
+ fig = plt.figure()
+
+ with plt.ion():
+ # interactive mode will be on
+ # figures will automatically be shown
+ fig2 = plt.figure()
+ # ...
+
+ To enable optional usage as a context manager, this function returns a
+ context manager object, which is not intended to be stored or
+ accessed by the user.
+ """
+ stack = ExitStack()
+ stack.callback(ion if isinteractive() else ioff)
+ matplotlib.interactive(True)
+ install_repl_displayhook()
+ return stack
+
+
+def pause(interval: float) -> None:
+ """
+ Run the GUI event loop for *interval* seconds.
+
+ If there is an active figure, it will be updated and displayed before the
+ pause, and the GUI event loop (if any) will run during the pause.
+
+ This can be used for crude animation. For more complex animation use
+ :mod:`matplotlib.animation`.
+
+ If there is no active figure, sleep for *interval* seconds instead.
+
+ See Also
+ --------
+ matplotlib.animation : Proper animations
+ show : Show all figures and optional block until all figures are closed.
+ """
+ manager = _pylab_helpers.Gcf.get_active()
+ if manager is not None:
+ canvas = manager.canvas
+ if canvas.figure.stale:
+ canvas.draw_idle()
+ show(block=False)
+ canvas.start_event_loop(interval)
+ else:
+ time.sleep(interval)
+
+
+@_copy_docstring_and_deprecators(matplotlib.rc)
+def rc(group: str, **kwargs) -> None:
+ matplotlib.rc(group, **kwargs)
+
+
+@_copy_docstring_and_deprecators(matplotlib.rc_context)
+def rc_context(
+ rc: dict[str, Any] | None = None,
+ fname: str | pathlib.Path | os.PathLike | None = None,
+) -> AbstractContextManager[None]:
+ return matplotlib.rc_context(rc, fname)
+
+
+@_copy_docstring_and_deprecators(matplotlib.rcdefaults)
+def rcdefaults() -> None:
+ matplotlib.rcdefaults()
+ if matplotlib.is_interactive():
+ draw_all()
+
+
+# getp/get/setp are explicitly reexported so that they show up in pyplot docs.
+
+
+@_copy_docstring_and_deprecators(matplotlib.artist.getp)
+def getp(obj, *args, **kwargs):
+ return matplotlib.artist.getp(obj, *args, **kwargs)
+
+
+@_copy_docstring_and_deprecators(matplotlib.artist.get)
+def get(obj, *args, **kwargs):
+ return matplotlib.artist.get(obj, *args, **kwargs)
+
+
+@_copy_docstring_and_deprecators(matplotlib.artist.setp)
+def setp(obj, *args, **kwargs):
+ return matplotlib.artist.setp(obj, *args, **kwargs)
+
+
+def xkcd(
+ scale: float = 1, length: float = 100, randomness: float = 2
+) -> ExitStack:
+ """
+ Turn on `xkcd `_ sketch-style drawing mode.
+
+ This will only have an effect on things drawn after this function is called.
+
+ For best results, install the `xkcd script `_
+ font; xkcd fonts are not packaged with Matplotlib.
+
+ Parameters
+ ----------
+ scale : float, optional
+ The amplitude of the wiggle perpendicular to the source line.
+ length : float, optional
+ The length of the wiggle along the line.
+ randomness : float, optional
+ The scale factor by which the length is shrunken or expanded.
+
+ Notes
+ -----
+ This function works by a number of rcParams, so it will probably
+ override others you have set before.
+
+ If you want the effects of this function to be temporary, it can
+ be used as a context manager, for example::
+
+ with plt.xkcd():
+ # This figure will be in XKCD-style
+ fig1 = plt.figure()
+ # ...
+
+ # This figure will be in regular style
+ fig2 = plt.figure()
+ """
+ # This cannot be implemented in terms of contextmanager() or rc_context()
+ # because this needs to work as a non-contextmanager too.
+
+ if rcParams['text.usetex']:
+ raise RuntimeError(
+ "xkcd mode is not compatible with text.usetex = True")
+
+ stack = ExitStack()
+ stack.callback(rcParams._update_raw, rcParams.copy()) # type: ignore[arg-type]
+
+ from matplotlib import patheffects
+ rcParams.update({
+ 'font.family': ['xkcd', 'xkcd Script', 'Comic Neue', 'Comic Sans MS'],
+ 'font.size': 14.0,
+ 'path.sketch': (scale, length, randomness),
+ 'path.effects': [
+ patheffects.withStroke(linewidth=4, foreground="w")],
+ 'axes.linewidth': 1.5,
+ 'lines.linewidth': 2.0,
+ 'figure.facecolor': 'white',
+ 'grid.linewidth': 0.0,
+ 'axes.grid': False,
+ 'axes.unicode_minus': False,
+ 'axes.edgecolor': 'black',
+ 'xtick.major.size': 8,
+ 'xtick.major.width': 3,
+ 'ytick.major.size': 8,
+ 'ytick.major.width': 3,
+ })
+
+ return stack
+
+
+## Figures ##
+
+def figure(
+ # autoincrement if None, else integer from 1-N
+ num: int | str | Figure | SubFigure | None = None,
+ # defaults to rc figure.figsize
+ figsize: ArrayLike | None = None,
+ # defaults to rc figure.dpi
+ dpi: float | None = None,
+ *,
+ # defaults to rc figure.facecolor
+ facecolor: ColorType | None = None,
+ # defaults to rc figure.edgecolor
+ edgecolor: ColorType | None = None,
+ frameon: bool = True,
+ FigureClass: type[Figure] = Figure,
+ clear: bool = False,
+ **kwargs
+) -> Figure:
+ """
+ Create a new figure, or activate an existing figure.
+
+ Parameters
+ ----------
+ num : int or str or `.Figure` or `.SubFigure`, optional
+ A unique identifier for the figure.
+
+ If a figure with that identifier already exists, this figure is made
+ active and returned. An integer refers to the ``Figure.number``
+ attribute, a string refers to the figure label.
+
+ If there is no figure with the identifier or *num* is not given, a new
+ figure is created, made active and returned. If *num* is an int, it
+ will be used for the ``Figure.number`` attribute, otherwise, an
+ auto-generated integer value is used (starting at 1 and incremented
+ for each new figure). If *num* is a string, the figure label and the
+ window title is set to this value. If num is a ``SubFigure``, its
+ parent ``Figure`` is activated.
+
+ figsize : (float, float), default: :rc:`figure.figsize`
+ Width, height in inches.
+
+ dpi : float, default: :rc:`figure.dpi`
+ The resolution of the figure in dots-per-inch.
+
+ facecolor : :mpltype:`color`, default: :rc:`figure.facecolor`
+ The background color.
+
+ edgecolor : :mpltype:`color`, default: :rc:`figure.edgecolor`
+ The border color.
+
+ frameon : bool, default: True
+ If False, suppress drawing the figure frame.
+
+ FigureClass : subclass of `~matplotlib.figure.Figure`
+ If set, an instance of this subclass will be created, rather than a
+ plain `.Figure`.
+
+ clear : bool, default: False
+ If True and the figure already exists, then it is cleared.
+
+ 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 measurably slow down figure display.
+
+ - '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 `.Figure.set_tight_layout` for
+ further details.
+
+ - '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`.
+
+ **kwargs
+ Additional keyword arguments are passed to the `.Figure` constructor.
+
+ Returns
+ -------
+ `~matplotlib.figure.Figure`
+
+ Notes
+ -----
+ A newly created figure is passed to the `~.FigureCanvasBase.new_manager`
+ method or the `new_figure_manager` function provided by the current
+ backend, which install a canvas and a manager on the figure.
+
+ Once this is done, :rc:`figure.hooks` are called, one at a time, on the
+ figure; these hooks allow arbitrary customization of the figure (e.g.,
+ attaching callbacks) or of associated elements (e.g., modifying the
+ toolbar). See :doc:`/gallery/user_interfaces/mplcvd` for an example of
+ toolbar customization.
+
+ If you are creating many figures, make sure you explicitly call
+ `.pyplot.close` on the figures you are not using, because this will
+ enable pyplot to properly clean up the memory.
+
+ `~matplotlib.rcParams` defines the default values, which can be modified
+ in the matplotlibrc file.
+ """
+ allnums = get_fignums()
+
+ if isinstance(num, FigureBase):
+ # type narrowed to `Figure | SubFigure` by combination of input and isinstance
+ root_fig = num.get_figure(root=True)
+ if root_fig.canvas.manager is None:
+ raise ValueError("The passed figure is not managed by pyplot")
+ elif (any(param is not None for param in [figsize, dpi, facecolor, edgecolor])
+ or not frameon or kwargs) and root_fig.canvas.manager.num in allnums:
+ _api.warn_external(
+ "Ignoring specified arguments in this call because figure "
+ f"with num: {root_fig.canvas.manager.num} already exists")
+ _pylab_helpers.Gcf.set_active(root_fig.canvas.manager)
+ return root_fig
+
+ next_num = max(allnums) + 1 if allnums else 1
+ fig_label = ''
+ if num is None:
+ num = next_num
+ else:
+ if (any(param is not None for param in [figsize, dpi, facecolor, edgecolor])
+ or not frameon or kwargs) and num in allnums:
+ _api.warn_external(
+ "Ignoring specified arguments in this call "
+ f"because figure with num: {num} already exists")
+ if isinstance(num, str):
+ fig_label = num
+ all_labels = get_figlabels()
+ if fig_label not in all_labels:
+ if fig_label == 'all':
+ _api.warn_external("close('all') closes all existing figures.")
+ num = next_num
+ else:
+ inum = all_labels.index(fig_label)
+ num = allnums[inum]
+ else:
+ num = int(num) # crude validation of num argument
+
+ # Type of "num" has narrowed to int, but mypy can't quite see it
+ manager = _pylab_helpers.Gcf.get_fig_manager(num) # type: ignore[arg-type]
+ if manager is None:
+ max_open_warning = rcParams['figure.max_open_warning']
+ if len(allnums) == max_open_warning >= 1:
+ _api.warn_external(
+ f"More than {max_open_warning} figures have been opened. "
+ f"Figures created through the pyplot interface "
+ f"(`matplotlib.pyplot.figure`) are retained until explicitly "
+ f"closed and may consume too much memory. (To control this "
+ f"warning, see the rcParam `figure.max_open_warning`). "
+ f"Consider using `matplotlib.pyplot.close()`.",
+ RuntimeWarning)
+
+ manager = new_figure_manager(
+ num, figsize=figsize, dpi=dpi,
+ facecolor=facecolor, edgecolor=edgecolor, frameon=frameon,
+ FigureClass=FigureClass, **kwargs)
+ fig = manager.canvas.figure
+ if fig_label:
+ fig.set_label(fig_label)
+
+ for hookspecs in rcParams["figure.hooks"]:
+ module_name, dotted_name = hookspecs.split(":")
+ obj: Any = importlib.import_module(module_name)
+ for part in dotted_name.split("."):
+ obj = getattr(obj, part)
+ obj(fig)
+
+ _pylab_helpers.Gcf._set_new_active_manager(manager)
+
+ # make sure backends (inline) that we don't ship that expect this
+ # to be called in plotting commands to make the figure call show
+ # still work. There is probably a better way to do this in the
+ # FigureManager base class.
+ draw_if_interactive()
+
+ if _REPL_DISPLAYHOOK is _ReplDisplayHook.PLAIN:
+ fig.stale_callback = _auto_draw_if_interactive
+
+ if clear:
+ manager.canvas.figure.clear()
+
+ return manager.canvas.figure
+
+
+def _auto_draw_if_interactive(fig, val):
+ """
+ An internal helper function for making sure that auto-redrawing
+ works as intended in the plain python repl.
+
+ Parameters
+ ----------
+ fig : Figure
+ A figure object which is assumed to be associated with a canvas
+ """
+ if (val and matplotlib.is_interactive()
+ and not fig.canvas.is_saving()
+ and not fig.canvas._is_idle_drawing):
+ # Some artists can mark themselves as stale in the middle of drawing
+ # (e.g. axes position & tick labels being computed at draw time), but
+ # this shouldn't trigger a redraw because the current redraw will
+ # already take them into account.
+ with fig.canvas._idle_draw_cntx():
+ fig.canvas.draw_idle()
+
+
+def gcf() -> Figure:
+ """
+ Get the current figure.
+
+ If there is currently no figure on the pyplot figure stack, a new one is
+ created using `~.pyplot.figure()`. (To test whether there is currently a
+ figure on the pyplot figure stack, check whether `~.pyplot.get_fignums()`
+ is empty.)
+ """
+ manager = _pylab_helpers.Gcf.get_active()
+ if manager is not None:
+ return manager.canvas.figure
+ else:
+ return figure()
+
+
+def fignum_exists(num: int | str) -> bool:
+ """
+ Return whether the figure with the given id exists.
+
+ Parameters
+ ----------
+ num : int or str
+ A figure identifier.
+
+ Returns
+ -------
+ bool
+ Whether or not a figure with id *num* exists.
+ """
+ return (
+ _pylab_helpers.Gcf.has_fignum(num)
+ if isinstance(num, int)
+ else num in get_figlabels()
+ )
+
+
+def get_fignums() -> list[int]:
+ """Return a list of existing figure numbers."""
+ return sorted(_pylab_helpers.Gcf.figs)
+
+
+def get_figlabels() -> list[Any]:
+ """Return a list of existing figure labels."""
+ managers = _pylab_helpers.Gcf.get_all_fig_managers()
+ managers.sort(key=lambda m: m.num)
+ return [m.canvas.figure.get_label() for m in managers]
+
+
+def get_current_fig_manager() -> FigureManagerBase | None:
+ """
+ Return the figure manager of the current figure.
+
+ The figure manager is a container for the actual backend-depended window
+ that displays the figure on screen.
+
+ If no current figure exists, a new one is created, and its figure
+ manager is returned.
+
+ Returns
+ -------
+ `.FigureManagerBase` or backend-dependent subclass thereof
+ """
+ return gcf().canvas.manager
+
+
+@_copy_docstring_and_deprecators(FigureCanvasBase.mpl_connect)
+def connect(s: str, func: Callable[[Event], Any]) -> int:
+ return gcf().canvas.mpl_connect(s, func)
+
+
+@_copy_docstring_and_deprecators(FigureCanvasBase.mpl_disconnect)
+def disconnect(cid: int) -> None:
+ gcf().canvas.mpl_disconnect(cid)
+
+
+def close(fig: None | int | str | Figure | Literal["all"] = None) -> None:
+ """
+ Close a figure window, and unregister it from pyplot.
+
+ Parameters
+ ----------
+ fig : None or int or str or `.Figure`
+ The figure to close. There are a number of ways to specify this:
+
+ - *None*: the current figure
+ - `.Figure`: the given `.Figure` instance
+ - ``int``: a figure number
+ - ``str``: a figure name
+ - 'all': all figures
+
+ Notes
+ -----
+ pyplot maintains a reference to figures created with `figure()`. When
+ work on the figure is completed, it should be closed, i.e. deregistered
+ from pyplot, to free its memory (see also :rc:figure.max_open_warning).
+ Closing a figure window created by `show()` automatically deregisters the
+ figure. For all other use cases, most prominently `savefig()` without
+ `show()`, the figure must be deregistered explicitly using `close()`.
+ """
+ if fig is None:
+ manager = _pylab_helpers.Gcf.get_active()
+ if manager is None:
+ return
+ else:
+ _pylab_helpers.Gcf.destroy(manager)
+ elif fig == 'all':
+ _pylab_helpers.Gcf.destroy_all()
+ elif isinstance(fig, int):
+ _pylab_helpers.Gcf.destroy(fig)
+ elif hasattr(fig, 'int'):
+ # if we are dealing with a type UUID, we
+ # can use its integer representation
+ _pylab_helpers.Gcf.destroy(fig.int)
+ elif isinstance(fig, str):
+ all_labels = get_figlabels()
+ if fig in all_labels:
+ num = get_fignums()[all_labels.index(fig)]
+ _pylab_helpers.Gcf.destroy(num)
+ elif isinstance(fig, Figure):
+ _pylab_helpers.Gcf.destroy_fig(fig)
+ else:
+ raise TypeError("close() argument must be a Figure, an int, a string, "
+ "or None, not %s" % type(fig))
+
+
+def clf() -> None:
+ """Clear the current figure."""
+ gcf().clear()
+
+
+def draw() -> None:
+ """
+ Redraw the current figure.
+
+ This is used to update a figure that has been altered, but not
+ automatically re-drawn. If interactive mode is on (via `.ion()`), this
+ should be only rarely needed, but there may be ways to modify the state of
+ a figure without marking it as "stale". Please report these cases as bugs.
+
+ This is equivalent to calling ``fig.canvas.draw_idle()``, where ``fig`` is
+ the current figure.
+
+ See Also
+ --------
+ .FigureCanvasBase.draw_idle
+ .FigureCanvasBase.draw
+ """
+ gcf().canvas.draw_idle()
+
+
+@_copy_docstring_and_deprecators(Figure.savefig)
+def savefig(*args, **kwargs) -> None:
+ fig = gcf()
+ # savefig default implementation has no return, so mypy is unhappy
+ # presumably this is here because subclasses can return?
+ res = fig.savefig(*args, **kwargs) # type: ignore[func-returns-value]
+ fig.canvas.draw_idle() # Need this if 'transparent=True', to reset colors.
+ return res
+
+
+## Putting things in figures ##
+
+
+def figlegend(*args, **kwargs) -> Legend:
+ return gcf().legend(*args, **kwargs)
+if Figure.legend.__doc__:
+ figlegend.__doc__ = Figure.legend.__doc__ \
+ .replace(" legend(", " figlegend(") \
+ .replace("fig.legend(", "plt.figlegend(") \
+ .replace("ax.plot(", "plt.plot(")
+
+
+## Axes ##
+
+@_docstring.interpd
+def axes(
+ arg: None | tuple[float, float, float, float] = None,
+ **kwargs
+) -> matplotlib.axes.Axes:
+ """
+ Add an Axes to the current figure and make it the current Axes.
+
+ Call signatures::
+
+ plt.axes()
+ plt.axes(rect, projection=None, polar=False, **kwargs)
+ plt.axes(ax)
+
+ Parameters
+ ----------
+ arg : None or 4-tuple
+ The exact behavior of this function depends on the type:
+
+ - *None*: A new full window Axes is added using
+ ``subplot(**kwargs)``.
+ - 4-tuple of floats *rect* = ``(left, bottom, width, height)``.
+ A new Axes is added with dimensions *rect* in normalized
+ (0, 1) units using `~.Figure.add_axes` on the current figure.
+
+ projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \
+'polar', 'rectilinear', str}, optional
+ The projection type of the `~.axes.Axes`. *str* is the name of
+ a custom projection, see `~matplotlib.projections`. The default
+ None results in a 'rectilinear' projection.
+
+ polar : bool, default: False
+ If True, equivalent to projection='polar'.
+
+ sharex, sharey : `~matplotlib.axes.Axes`, optional
+ Share the x or y `~matplotlib.axis` with sharex and/or sharey.
+ The axis will have the same limits, ticks, and scale as the axis
+ of the shared Axes.
+
+ label : str
+ A label for the returned Axes.
+
+ Returns
+ -------
+ `~.axes.Axes`, or a subclass of `~.axes.Axes`
+ The returned Axes class depends on the projection used. It is
+ `~.axes.Axes` if rectilinear projection is used and
+ `.projections.polar.PolarAxes` if polar projection is used.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ This method also takes the keyword arguments for
+ the returned Axes class. The keyword arguments for the
+ rectilinear Axes class `~.axes.Axes` can be found in
+ the following table but there might also be other keyword
+ arguments if another projection is used, see the actual Axes
+ class.
+
+ %(Axes:kwdoc)s
+
+ See Also
+ --------
+ .Figure.add_axes
+ .pyplot.subplot
+ .Figure.add_subplot
+ .Figure.subplots
+ .pyplot.subplots
+
+ Examples
+ --------
+ ::
+
+ # Creating a new full window Axes
+ plt.axes()
+
+ # Creating a new Axes with specified dimensions and a grey background
+ plt.axes((left, bottom, width, height), facecolor='grey')
+ """
+ fig = gcf()
+ pos = kwargs.pop('position', None)
+ if arg is None:
+ if pos is None:
+ return fig.add_subplot(**kwargs)
+ else:
+ return fig.add_axes(pos, **kwargs)
+ else:
+ return fig.add_axes(arg, **kwargs)
+
+
+def delaxes(ax: matplotlib.axes.Axes | None = None) -> None:
+ """
+ Remove an `~.axes.Axes` (defaulting to the current Axes) from its figure.
+ """
+ if ax is None:
+ ax = gca()
+ ax.remove()
+
+
+def sca(ax: Axes) -> None:
+ """
+ Set the current Axes to *ax* and the current Figure to the parent of *ax*.
+ """
+ # Mypy sees ax.figure as potentially None,
+ # but if you are calling this, it won't be None
+ # Additionally the slight difference between `Figure` and `FigureBase` mypy catches
+ fig = ax.get_figure(root=False)
+ figure(fig) # type: ignore[arg-type]
+ fig.sca(ax) # type: ignore[union-attr]
+
+
+def cla() -> None:
+ """Clear the current Axes."""
+ # Not generated via boilerplate.py to allow a different docstring.
+ return gca().cla()
+
+
+## More ways of creating Axes ##
+
+@_docstring.interpd
+def subplot(*args, **kwargs) -> Axes:
+ """
+ Add an Axes to the current figure or retrieve an existing Axes.
+
+ This is a wrapper of `.Figure.add_subplot` which provides additional
+ behavior when working with the implicit API (see the notes section).
+
+ Call signatures::
+
+ subplot(nrows, ncols, index, **kwargs)
+ subplot(pos, **kwargs)
+ subplot(**kwargs)
+ subplot(ax)
+
+ Parameters
+ ----------
+ *args : int, (int, int, *index*), or `.SubplotSpec`, default: (1, 1, 1)
+ The position of the subplot described by one of
+
+ - Three integers (*nrows*, *ncols*, *index*). The subplot will take the
+ *index* position on a grid with *nrows* rows and *ncols* columns.
+ *index* starts at 1 in the upper left corner and increases to the
+ right. *index* can also be a two-tuple specifying the (*first*,
+ *last*) indices (1-based, and including *last*) of the subplot, e.g.,
+ ``fig.add_subplot(3, 1, (1, 2))`` makes a subplot that spans the
+ upper 2/3 of the figure.
+ - A 3-digit integer. The digits are interpreted as if given separately
+ as three single-digit integers, i.e. ``fig.add_subplot(235)`` is the
+ same as ``fig.add_subplot(2, 3, 5)``. Note that this can only be used
+ if there are no more than 9 subplots.
+ - A `.SubplotSpec`.
+
+ projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \
+'polar', 'rectilinear', str}, optional
+ The projection type of the subplot (`~.axes.Axes`). *str* is the name
+ of a custom projection, see `~matplotlib.projections`. The default
+ None results in a 'rectilinear' projection.
+
+ polar : bool, default: False
+ If True, equivalent to projection='polar'.
+
+ sharex, sharey : `~matplotlib.axes.Axes`, optional
+ Share the x or y `~matplotlib.axis` with sharex and/or sharey. The
+ axis will have the same limits, ticks, and scale as the axis of the
+ shared Axes.
+
+ label : str
+ A label for the returned Axes.
+
+ Returns
+ -------
+ `~.axes.Axes`
+
+ The Axes of the subplot. The returned Axes can actually be an instance
+ of a subclass, such as `.projections.polar.PolarAxes` for polar
+ projections.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ This method also takes the keyword arguments for the returned Axes
+ base class; except for the *figure* argument. The keyword arguments
+ for the rectilinear base class `~.axes.Axes` can be found in
+ the following table but there might also be other keyword
+ arguments if another projection is used.
+
+ %(Axes:kwdoc)s
+
+ Notes
+ -----
+ .. versionchanged:: 3.8
+ In versions prior to 3.8, any preexisting Axes that overlap with the new Axes
+ beyond sharing a boundary was deleted. Deletion does not happen in more
+ recent versions anymore. Use `.Axes.remove` explicitly if needed.
+
+ If you do not want this behavior, use the `.Figure.add_subplot` method
+ or the `.pyplot.axes` function instead.
+
+ If no *kwargs* are passed and there exists an Axes in the location
+ specified by *args* then that Axes will be returned rather than a new
+ Axes being created.
+
+ If *kwargs* are passed and there exists an Axes in the location
+ specified by *args*, the projection type is the same, and the
+ *kwargs* match with the existing Axes, then the existing Axes is
+ returned. Otherwise a new Axes is created with the specified
+ parameters. We save a reference to the *kwargs* which we use
+ for this comparison. If any of the values in *kwargs* are
+ mutable we will not detect the case where they are mutated.
+ In these cases we suggest using `.Figure.add_subplot` and the
+ explicit Axes API rather than the implicit pyplot API.
+
+ See Also
+ --------
+ .Figure.add_subplot
+ .pyplot.subplots
+ .pyplot.axes
+ .Figure.subplots
+
+ Examples
+ --------
+ ::
+
+ plt.subplot(221)
+
+ # equivalent but more general
+ ax1 = plt.subplot(2, 2, 1)
+
+ # add a subplot with no frame
+ ax2 = plt.subplot(222, frameon=False)
+
+ # add a polar subplot
+ plt.subplot(223, projection='polar')
+
+ # add a red subplot that shares the x-axis with ax1
+ plt.subplot(224, sharex=ax1, facecolor='red')
+
+ # delete ax2 from the figure
+ plt.delaxes(ax2)
+
+ # add ax2 to the figure again
+ plt.subplot(ax2)
+
+ # make the first Axes "current" again
+ plt.subplot(221)
+
+ """
+ # Here we will only normalize `polar=True` vs `projection='polar'` and let
+ # downstream code deal with the rest.
+ unset = object()
+ projection = kwargs.get('projection', unset)
+ polar = kwargs.pop('polar', unset)
+ if polar is not unset and polar:
+ # if we got mixed messages from the user, raise
+ if projection is not unset and projection != 'polar':
+ raise ValueError(
+ f"polar={polar}, yet projection={projection!r}. "
+ "Only one of these arguments should be supplied."
+ )
+ kwargs['projection'] = projection = 'polar'
+
+ # if subplot called without arguments, create subplot(1, 1, 1)
+ if len(args) == 0:
+ args = (1, 1, 1)
+
+ # This check was added because it is very easy to type subplot(1, 2, False)
+ # when subplots(1, 2, False) was intended (sharex=False, that is). In most
+ # cases, no error will ever occur, but mysterious behavior can result
+ # because what was intended to be the sharex argument is instead treated as
+ # a subplot index for subplot()
+ if len(args) >= 3 and isinstance(args[2], bool):
+ _api.warn_external("The subplot index argument to subplot() appears "
+ "to be a boolean. Did you intend to use "
+ "subplots()?")
+ # Check for nrows and ncols, which are not valid subplot args:
+ if 'nrows' in kwargs or 'ncols' in kwargs:
+ raise TypeError("subplot() got an unexpected keyword argument 'ncols' "
+ "and/or 'nrows'. Did you intend to call subplots()?")
+
+ fig = gcf()
+
+ # First, search for an existing subplot with a matching spec.
+ key = SubplotSpec._from_subplot_args(fig, args)
+
+ for ax in fig.axes:
+ # If we found an Axes at the position, we can reuse it if the user passed no
+ # kwargs or if the Axes class and kwargs are identical.
+ if (ax.get_subplotspec() == key
+ and (kwargs == {}
+ or (ax._projection_init
+ == fig._process_projection_requirements(**kwargs)))):
+ break
+ else:
+ # we have exhausted the known Axes and none match, make a new one!
+ ax = fig.add_subplot(*args, **kwargs)
+
+ fig.sca(ax)
+
+ return ax
+
+
+@overload
+def subplots(
+ nrows: Literal[1] = ...,
+ ncols: Literal[1] = ...,
+ *,
+ sharex: bool | Literal["none", "all", "row", "col"] = ...,
+ sharey: bool | Literal["none", "all", "row", "col"] = ...,
+ squeeze: Literal[True] = ...,
+ width_ratios: Sequence[float] | None = ...,
+ height_ratios: Sequence[float] | None = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ **fig_kw
+) -> tuple[Figure, Axes]:
+ ...
+
+
+@overload
+def subplots(
+ nrows: int = ...,
+ ncols: int = ...,
+ *,
+ sharex: bool | Literal["none", "all", "row", "col"] = ...,
+ sharey: bool | Literal["none", "all", "row", "col"] = ...,
+ squeeze: Literal[False],
+ width_ratios: Sequence[float] | None = ...,
+ height_ratios: Sequence[float] | None = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ **fig_kw
+) -> tuple[Figure, np.ndarray]: # TODO numpy/numpy#24738
+ ...
+
+
+@overload
+def subplots(
+ nrows: int = ...,
+ ncols: int = ...,
+ *,
+ sharex: bool | Literal["none", "all", "row", "col"] = ...,
+ sharey: bool | Literal["none", "all", "row", "col"] = ...,
+ squeeze: bool = ...,
+ width_ratios: Sequence[float] | None = ...,
+ height_ratios: Sequence[float] | None = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ **fig_kw
+) -> tuple[Figure, Any]:
+ ...
+
+
+def subplots(
+ nrows: int = 1, ncols: int = 1, *,
+ sharex: bool | Literal["none", "all", "row", "col"] = False,
+ sharey: bool | Literal["none", "all", "row", "col"] = False,
+ squeeze: bool = True,
+ width_ratios: Sequence[float] | None = None,
+ height_ratios: Sequence[float] | None = None,
+ subplot_kw: dict[str, Any] | None = None,
+ gridspec_kw: dict[str, Any] | None = None,
+ **fig_kw
+) -> tuple[Figure, Any]:
+ """
+ Create a figure and a set of subplots.
+
+ This utility wrapper makes it convenient to create common layouts of
+ subplots, including the enclosing figure object, in a single call.
+
+ Parameters
+ ----------
+ nrows, ncols : int, default: 1
+ Number of rows/columns of the subplot grid.
+
+ sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False
+ Controls sharing of properties among x (*sharex*) or y (*sharey*)
+ axes:
+
+ - True or 'all': x- or y-axis will be shared among all subplots.
+ - False or 'none': each subplot x- or y-axis will be independent.
+ - 'row': each subplot row will share an x- or y-axis.
+ - 'col': each subplot column will share an x- or y-axis.
+
+ When subplots have a shared x-axis along a column, only the x tick
+ labels of the bottom subplot are created. Similarly, when subplots
+ have a shared y-axis along a row, only the y tick labels of the first
+ column subplot are created. To later turn other subplots' ticklabels
+ on, use `~matplotlib.axes.Axes.tick_params`.
+
+ When subplots have a shared axis that has units, calling
+ `.Axis.set_units` will update each axis with the new units.
+
+ Note that it is not possible to unshare axes.
+
+ squeeze : bool, default: True
+ - If True, extra dimensions are squeezed out from the returned
+ array of `~matplotlib.axes.Axes`:
+
+ - if only one subplot is constructed (nrows=ncols=1), the
+ resulting single Axes object is returned as a scalar.
+ - for Nx1 or 1xM subplots, the returned object is a 1D numpy
+ object array of Axes objects.
+ - for NxM, subplots with N>1 and M>1 are returned as a 2D array.
+
+ - If False, no squeezing at all is done: the returned Axes object is
+ always a 2D array containing Axes instances, even if it ends up
+ being 1x1.
+
+ width_ratios : array-like of length *ncols*, optional
+ Defines the relative widths of the columns. Each column gets a
+ relative width of ``width_ratios[i] / sum(width_ratios)``.
+ If not given, all columns will have the same width. Equivalent
+ to ``gridspec_kw={'width_ratios': [...]}``.
+
+ height_ratios : array-like of length *nrows*, optional
+ Defines the relative heights of the rows. Each row gets a
+ relative height of ``height_ratios[i] / sum(height_ratios)``.
+ If not given, all rows will have the same height. Convenience
+ for ``gridspec_kw={'height_ratios': [...]}``.
+
+ subplot_kw : dict, optional
+ Dict with keywords passed to the
+ `~matplotlib.figure.Figure.add_subplot` call used to create each
+ subplot.
+
+ gridspec_kw : dict, optional
+ Dict with keywords passed to the `~matplotlib.gridspec.GridSpec`
+ constructor used to create the grid the subplots are placed on.
+
+ **fig_kw
+ All additional keyword arguments are passed to the
+ `.pyplot.figure` call.
+
+ Returns
+ -------
+ fig : `.Figure`
+
+ ax : `~matplotlib.axes.Axes` or array of Axes
+ *ax* can be either a single `~.axes.Axes` object, or an array of Axes
+ objects if more than one subplot was created. The dimensions of the
+ resulting array can be controlled with the squeeze keyword, see above.
+
+ Typical idioms for handling the return value are::
+
+ # using the variable ax for single a Axes
+ fig, ax = plt.subplots()
+
+ # using the variable axs for multiple Axes
+ fig, axs = plt.subplots(2, 2)
+
+ # using tuple unpacking for multiple Axes
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
+
+ The names ``ax`` and pluralized ``axs`` are preferred over ``axes``
+ because for the latter it's not clear if it refers to a single
+ `~.axes.Axes` instance or a collection of these.
+
+ See Also
+ --------
+ .pyplot.figure
+ .pyplot.subplot
+ .pyplot.axes
+ .Figure.subplots
+ .Figure.add_subplot
+
+ Examples
+ --------
+ ::
+
+ # First create some toy data:
+ x = np.linspace(0, 2*np.pi, 400)
+ y = np.sin(x**2)
+
+ # Create just a figure and only one subplot
+ fig, ax = plt.subplots()
+ ax.plot(x, y)
+ ax.set_title('Simple plot')
+
+ # Create two subplots and unpack the output array immediately
+ f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
+ ax1.plot(x, y)
+ ax1.set_title('Sharing Y axis')
+ ax2.scatter(x, y)
+
+ # Create four polar Axes and access them through the returned array
+ fig, axs = plt.subplots(2, 2, subplot_kw=dict(projection="polar"))
+ axs[0, 0].plot(x, y)
+ axs[1, 1].scatter(x, y)
+
+ # Share a X axis with each column of subplots
+ plt.subplots(2, 2, sharex='col')
+
+ # Share a Y axis with each row of subplots
+ plt.subplots(2, 2, sharey='row')
+
+ # Share both X and Y axes with all subplots
+ plt.subplots(2, 2, sharex='all', sharey='all')
+
+ # Note that this is the same as
+ plt.subplots(2, 2, sharex=True, sharey=True)
+
+ # Create figure number 10 with a single subplot
+ # and clears it if it already exists.
+ fig, ax = plt.subplots(num=10, clear=True)
+
+ """
+ fig = figure(**fig_kw)
+ axs = fig.subplots(nrows=nrows, ncols=ncols, sharex=sharex, sharey=sharey,
+ squeeze=squeeze, subplot_kw=subplot_kw,
+ gridspec_kw=gridspec_kw, height_ratios=height_ratios,
+ width_ratios=width_ratios)
+ return fig, axs
+
+
+@overload
+def subplot_mosaic(
+ mosaic: str,
+ *,
+ sharex: bool = ...,
+ sharey: bool = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ empty_sentinel: str = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ...,
+ **fig_kw: Any
+) -> tuple[Figure, dict[str, matplotlib.axes.Axes]]: ...
+
+
+@overload
+def subplot_mosaic(
+ mosaic: list[HashableList[_T]],
+ *,
+ sharex: bool = ...,
+ sharey: bool = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ empty_sentinel: _T = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ...,
+ **fig_kw: Any
+) -> tuple[Figure, dict[_T, matplotlib.axes.Axes]]: ...
+
+
+@overload
+def subplot_mosaic(
+ mosaic: list[HashableList[Hashable]],
+ *,
+ sharex: bool = ...,
+ sharey: bool = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ empty_sentinel: Any = ...,
+ subplot_kw: dict[str, Any] | None = ...,
+ gridspec_kw: dict[str, Any] | None = ...,
+ per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ...,
+ **fig_kw: Any
+) -> tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: ...
+
+
+def subplot_mosaic(
+ mosaic: str | list[HashableList[_T]] | list[HashableList[Hashable]],
+ *,
+ sharex: bool = False,
+ sharey: bool = False,
+ width_ratios: ArrayLike | None = None,
+ height_ratios: ArrayLike | None = None,
+ empty_sentinel: Any = '.',
+ subplot_kw: dict[str, Any] | None = None,
+ gridspec_kw: dict[str, Any] | None = None,
+ per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] |
+ dict[_T | tuple[_T, ...], dict[str, Any]] |
+ dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = None,
+ **fig_kw: Any
+) -> tuple[Figure, dict[str, matplotlib.axes.Axes]] | \
+ tuple[Figure, dict[_T, matplotlib.axes.Axes]] | \
+ tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]:
+ """
+ Build a layout of Axes based on ASCII art or nested lists.
+
+ This is a helper function to build complex GridSpec layouts visually.
+
+ See :ref:`mosaic`
+ for an example and full API documentation
+
+ Parameters
+ ----------
+ mosaic : list of list of {hashable or nested} or str
+
+ A visual layout of how you want your Axes to be arranged
+ labeled as strings. For example ::
+
+ x = [['A panel', 'A panel', 'edge'],
+ ['C panel', '.', 'edge']]
+
+ produces 4 Axes:
+
+ - 'A panel' which is 1 row high and spans the first two columns
+ - 'edge' which is 2 rows high and is on the right edge
+ - 'C panel' which in 1 row and 1 column wide in the bottom left
+ - a blank space 1 row and 1 column wide in the bottom center
+
+ Any of the entries in the layout can be a list of lists
+ of the same form to create nested layouts.
+
+ If input is a str, then it must be of the form ::
+
+ '''
+ AAE
+ C.E
+ '''
+
+ where each character is a column and each line is a row.
+ This only allows only single character Axes labels and does
+ not allow nesting but is very terse.
+
+ sharex, sharey : bool, default: False
+ If True, the x-axis (*sharex*) or y-axis (*sharey*) will be shared
+ among all subplots. In that case, tick label visibility and axis units
+ behave as for `subplots`. If False, each subplot's x- or y-axis will
+ be independent.
+
+ width_ratios : array-like of length *ncols*, optional
+ Defines the relative widths of the columns. Each column gets a
+ relative width of ``width_ratios[i] / sum(width_ratios)``.
+ If not given, all columns will have the same width. Convenience
+ for ``gridspec_kw={'width_ratios': [...]}``.
+
+ height_ratios : array-like of length *nrows*, optional
+ Defines the relative heights of the rows. Each row gets a
+ relative height of ``height_ratios[i] / sum(height_ratios)``.
+ If not given, all rows will have the same height. Convenience
+ for ``gridspec_kw={'height_ratios': [...]}``.
+
+ empty_sentinel : object, optional
+ Entry in the layout to mean "leave this space empty". Defaults
+ to ``'.'``. Note, if *layout* is a string, it is processed via
+ `inspect.cleandoc` to remove leading white space, which may
+ interfere with using white-space as the empty sentinel.
+
+ subplot_kw : dict, optional
+ Dictionary with keywords passed to the `.Figure.add_subplot` call
+ used to create each subplot. These values may be overridden by
+ values in *per_subplot_kw*.
+
+ per_subplot_kw : dict, optional
+ A dictionary mapping the Axes identifiers or tuples of identifiers
+ to a dictionary of keyword arguments to be passed to the
+ `.Figure.add_subplot` call used to create each subplot. The values
+ in these dictionaries have precedence over the values in
+ *subplot_kw*.
+
+ If *mosaic* is a string, and thus all keys are single characters,
+ it is possible to use a single string instead of a tuple as keys;
+ i.e. ``"AB"`` is equivalent to ``("A", "B")``.
+
+ .. versionadded:: 3.7
+
+ gridspec_kw : dict, optional
+ Dictionary with keywords passed to the `.GridSpec` constructor used
+ to create the grid the subplots are placed on.
+
+ **fig_kw
+ All additional keyword arguments are passed to the
+ `.pyplot.figure` call.
+
+ Returns
+ -------
+ fig : `.Figure`
+ The new figure
+
+ dict[label, Axes]
+ A dictionary mapping the labels to the Axes objects. The order of
+ the Axes is left-to-right and top-to-bottom of their position in the
+ total layout.
+
+ """
+ fig = figure(**fig_kw)
+ ax_dict = fig.subplot_mosaic( # type: ignore[misc]
+ mosaic, # type: ignore[arg-type]
+ sharex=sharex, sharey=sharey,
+ height_ratios=height_ratios, width_ratios=width_ratios,
+ subplot_kw=subplot_kw, gridspec_kw=gridspec_kw,
+ empty_sentinel=empty_sentinel,
+ per_subplot_kw=per_subplot_kw, # type: ignore[arg-type]
+ )
+ return fig, ax_dict
+
+
+def subplot2grid(
+ shape: tuple[int, int], loc: tuple[int, int],
+ rowspan: int = 1, colspan: int = 1,
+ fig: Figure | None = None,
+ **kwargs
+) -> matplotlib.axes.Axes:
+ """
+ Create a subplot at a specific location inside a regular grid.
+
+ Parameters
+ ----------
+ shape : (int, int)
+ Number of rows and of columns of the grid in which to place axis.
+ loc : (int, int)
+ Row number and column number of the axis location within the grid.
+ rowspan : int, default: 1
+ Number of rows for the axis to span downwards.
+ colspan : int, default: 1
+ Number of columns for the axis to span to the right.
+ fig : `.Figure`, optional
+ Figure to place the subplot in. Defaults to the current figure.
+ **kwargs
+ Additional keyword arguments are handed to `~.Figure.add_subplot`.
+
+ Returns
+ -------
+ `~.axes.Axes`
+
+ The Axes of the subplot. The returned Axes can actually be an instance
+ of a subclass, such as `.projections.polar.PolarAxes` for polar
+ projections.
+
+ Notes
+ -----
+ The following call ::
+
+ ax = subplot2grid((nrows, ncols), (row, col), rowspan, colspan)
+
+ is identical to ::
+
+ fig = gcf()
+ gs = fig.add_gridspec(nrows, ncols)
+ ax = fig.add_subplot(gs[row:row+rowspan, col:col+colspan])
+ """
+ if fig is None:
+ fig = gcf()
+ rows, cols = shape
+ gs = GridSpec._check_gridspec_exists(fig, rows, cols)
+ subplotspec = gs.new_subplotspec(loc, rowspan=rowspan, colspan=colspan)
+ return fig.add_subplot(subplotspec, **kwargs)
+
+
+def twinx(ax: matplotlib.axes.Axes | None = None) -> _AxesBase:
+ """
+ Make and return a second Axes that shares the *x*-axis. The new Axes will
+ overlay *ax* (or the current Axes if *ax* is *None*), and its ticks will be
+ on the right.
+
+ Examples
+ --------
+ :doc:`/gallery/subplots_axes_and_figures/two_scales`
+ """
+ if ax is None:
+ ax = gca()
+ ax1 = ax.twinx()
+ return ax1
+
+
+def twiny(ax: matplotlib.axes.Axes | None = None) -> _AxesBase:
+ """
+ Make and return a second Axes that shares the *y*-axis. The new Axes will
+ overlay *ax* (or the current Axes if *ax* is *None*), and its ticks will be
+ on the top.
+
+ Examples
+ --------
+ :doc:`/gallery/subplots_axes_and_figures/two_scales`
+ """
+ if ax is None:
+ ax = gca()
+ ax1 = ax.twiny()
+ return ax1
+
+
+def subplot_tool(targetfig: Figure | None = None) -> SubplotTool | None:
+ """
+ Launch a subplot tool window for a figure.
+
+ Returns
+ -------
+ `matplotlib.widgets.SubplotTool`
+ """
+ if targetfig is None:
+ targetfig = gcf()
+ tb = targetfig.canvas.manager.toolbar # type: ignore[union-attr]
+ if hasattr(tb, "configure_subplots"): # toolbar2
+ from matplotlib.backend_bases import NavigationToolbar2
+ return cast(NavigationToolbar2, tb).configure_subplots()
+ elif hasattr(tb, "trigger_tool"): # toolmanager
+ from matplotlib.backend_bases import ToolContainerBase
+ cast(ToolContainerBase, tb).trigger_tool("subplots")
+ return None
+ else:
+ raise ValueError("subplot_tool can only be launched for figures with "
+ "an associated toolbar")
+
+
+def box(on: bool | None = None) -> None:
+ """
+ Turn the Axes box on or off on the current Axes.
+
+ Parameters
+ ----------
+ on : bool or None
+ The new `~matplotlib.axes.Axes` box state. If ``None``, toggle
+ the state.
+
+ See Also
+ --------
+ :meth:`matplotlib.axes.Axes.set_frame_on`
+ :meth:`matplotlib.axes.Axes.get_frame_on`
+ """
+ ax = gca()
+ if on is None:
+ on = not ax.get_frame_on()
+ ax.set_frame_on(on)
+
+## Axis ##
+
+
+def xlim(*args, **kwargs) -> tuple[float, float]:
+ """
+ Get or set the x limits of the current Axes.
+
+ Call signatures::
+
+ left, right = xlim() # return the current xlim
+ xlim((left, right)) # set the xlim to left, right
+ xlim(left, right) # set the xlim to left, right
+
+ If you do not specify args, you can pass *left* or *right* as kwargs,
+ i.e.::
+
+ xlim(right=3) # adjust the right leaving left unchanged
+ xlim(left=1) # adjust the left leaving right unchanged
+
+ Setting limits turns autoscaling off for the x-axis.
+
+ Returns
+ -------
+ left, right
+ A tuple of the new x-axis limits.
+
+ Notes
+ -----
+ Calling this function with no arguments (e.g. ``xlim()``) is the pyplot
+ equivalent of calling `~.Axes.get_xlim` on the current Axes.
+ Calling this function with arguments is the pyplot equivalent of calling
+ `~.Axes.set_xlim` on the current Axes. All arguments are passed though.
+ """
+ ax = gca()
+ if not args and not kwargs:
+ return ax.get_xlim()
+ ret = ax.set_xlim(*args, **kwargs)
+ return ret
+
+
+def ylim(*args, **kwargs) -> tuple[float, float]:
+ """
+ Get or set the y-limits of the current Axes.
+
+ Call signatures::
+
+ bottom, top = ylim() # return the current ylim
+ ylim((bottom, top)) # set the ylim to bottom, top
+ ylim(bottom, top) # set the ylim to bottom, top
+
+ If you do not specify args, you can alternatively pass *bottom* or
+ *top* as kwargs, i.e.::
+
+ ylim(top=3) # adjust the top leaving bottom unchanged
+ ylim(bottom=1) # adjust the bottom leaving top unchanged
+
+ Setting limits turns autoscaling off for the y-axis.
+
+ Returns
+ -------
+ bottom, top
+ A tuple of the new y-axis limits.
+
+ Notes
+ -----
+ Calling this function with no arguments (e.g. ``ylim()``) is the pyplot
+ equivalent of calling `~.Axes.get_ylim` on the current Axes.
+ Calling this function with arguments is the pyplot equivalent of calling
+ `~.Axes.set_ylim` on the current Axes. All arguments are passed though.
+ """
+ ax = gca()
+ if not args and not kwargs:
+ return ax.get_ylim()
+ ret = ax.set_ylim(*args, **kwargs)
+ return ret
+
+
+def xticks(
+ ticks: ArrayLike | None = None,
+ labels: Sequence[str] | None = None,
+ *,
+ minor: bool = False,
+ **kwargs
+) -> tuple[list[Tick] | np.ndarray, list[Text]]:
+ """
+ Get or set the current tick locations and labels of the x-axis.
+
+ Pass no arguments to return the current values without modifying them.
+
+ Parameters
+ ----------
+ ticks : array-like, optional
+ The list of xtick locations. Passing an empty list removes all xticks.
+ labels : array-like, optional
+ The labels to place at the given *ticks* locations. This argument can
+ only be passed if *ticks* is passed as well.
+ minor : bool, default: False
+ If ``False``, get/set the major ticks/labels; if ``True``, the minor
+ ticks/labels.
+ **kwargs
+ `.Text` properties can be used to control the appearance of the labels.
+
+ .. warning::
+
+ This only sets the properties of the current ticks, which is
+ only sufficient if you either pass *ticks*, resulting in a
+ fixed list of ticks, or if the plot is static.
+
+ Ticks are not guaranteed to be persistent. Various operations
+ can create, delete and modify the Tick instances. There is an
+ imminent risk that these settings can get lost if you work on
+ the figure further (including also panning/zooming on a
+ displayed figure).
+
+ Use `~.pyplot.tick_params` instead if possible.
+
+
+ Returns
+ -------
+ locs
+ The list of xtick locations.
+ labels
+ The list of xlabel `.Text` objects.
+
+ Notes
+ -----
+ Calling this function with no arguments (e.g. ``xticks()``) is the pyplot
+ equivalent of calling `~.Axes.get_xticks` and `~.Axes.get_xticklabels` on
+ the current Axes.
+ Calling this function with arguments is the pyplot equivalent of calling
+ `~.Axes.set_xticks` and `~.Axes.set_xticklabels` on the current Axes.
+
+ Examples
+ --------
+ >>> locs, labels = xticks() # Get the current locations and labels.
+ >>> xticks(np.arange(0, 1, step=0.2)) # Set label locations.
+ >>> xticks(np.arange(3), ['Tom', 'Dick', 'Sue']) # Set text labels.
+ >>> xticks([0, 1, 2], ['January', 'February', 'March'],
+ ... rotation=20) # Set text labels and properties.
+ >>> xticks([]) # Disable xticks.
+ """
+ ax = gca()
+
+ locs: list[Tick] | np.ndarray
+ if ticks is None:
+ locs = ax.get_xticks(minor=minor)
+ if labels is not None:
+ raise TypeError("xticks(): Parameter 'labels' can't be set "
+ "without setting 'ticks'")
+ else:
+ locs = ax.set_xticks(ticks, minor=minor)
+
+ labels_out: list[Text] = []
+ if labels is None:
+ labels_out = ax.get_xticklabels(minor=minor)
+ for l in labels_out:
+ l._internal_update(kwargs)
+ else:
+ labels_out = ax.set_xticklabels(labels, minor=minor, **kwargs)
+
+ return locs, labels_out
+
+
+def yticks(
+ ticks: ArrayLike | None = None,
+ labels: Sequence[str] | None = None,
+ *,
+ minor: bool = False,
+ **kwargs
+) -> tuple[list[Tick] | np.ndarray, list[Text]]:
+ """
+ Get or set the current tick locations and labels of the y-axis.
+
+ Pass no arguments to return the current values without modifying them.
+
+ Parameters
+ ----------
+ ticks : array-like, optional
+ The list of ytick locations. Passing an empty list removes all yticks.
+ labels : array-like, optional
+ The labels to place at the given *ticks* locations. This argument can
+ only be passed if *ticks* is passed as well.
+ minor : bool, default: False
+ If ``False``, get/set the major ticks/labels; if ``True``, the minor
+ ticks/labels.
+ **kwargs
+ `.Text` properties can be used to control the appearance of the labels.
+
+ .. warning::
+
+ This only sets the properties of the current ticks, which is
+ only sufficient if you either pass *ticks*, resulting in a
+ fixed list of ticks, or if the plot is static.
+
+ Ticks are not guaranteed to be persistent. Various operations
+ can create, delete and modify the Tick instances. There is an
+ imminent risk that these settings can get lost if you work on
+ the figure further (including also panning/zooming on a
+ displayed figure).
+
+ Use `~.pyplot.tick_params` instead if possible.
+
+ Returns
+ -------
+ locs
+ The list of ytick locations.
+ labels
+ The list of ylabel `.Text` objects.
+
+ Notes
+ -----
+ Calling this function with no arguments (e.g. ``yticks()``) is the pyplot
+ equivalent of calling `~.Axes.get_yticks` and `~.Axes.get_yticklabels` on
+ the current Axes.
+ Calling this function with arguments is the pyplot equivalent of calling
+ `~.Axes.set_yticks` and `~.Axes.set_yticklabels` on the current Axes.
+
+ Examples
+ --------
+ >>> locs, labels = yticks() # Get the current locations and labels.
+ >>> yticks(np.arange(0, 1, step=0.2)) # Set label locations.
+ >>> yticks(np.arange(3), ['Tom', 'Dick', 'Sue']) # Set text labels.
+ >>> yticks([0, 1, 2], ['January', 'February', 'March'],
+ ... rotation=45) # Set text labels and properties.
+ >>> yticks([]) # Disable yticks.
+ """
+ ax = gca()
+
+ locs: list[Tick] | np.ndarray
+ if ticks is None:
+ locs = ax.get_yticks(minor=minor)
+ if labels is not None:
+ raise TypeError("yticks(): Parameter 'labels' can't be set "
+ "without setting 'ticks'")
+ else:
+ locs = ax.set_yticks(ticks, minor=minor)
+
+ labels_out: list[Text] = []
+ if labels is None:
+ labels_out = ax.get_yticklabels(minor=minor)
+ for l in labels_out:
+ l._internal_update(kwargs)
+ else:
+ labels_out = ax.set_yticklabels(labels, minor=minor, **kwargs)
+
+ return locs, labels_out
+
+
+def rgrids(
+ radii: ArrayLike | None = None,
+ labels: Sequence[str | Text] | None = None,
+ angle: float | None = None,
+ fmt: str | None = None,
+ **kwargs
+) -> tuple[list[Line2D], list[Text]]:
+ """
+ Get or set the radial gridlines on the current polar plot.
+
+ Call signatures::
+
+ lines, labels = rgrids()
+ lines, labels = rgrids(radii, labels=None, angle=22.5, fmt=None, **kwargs)
+
+ When called with no arguments, `.rgrids` simply returns the tuple
+ (*lines*, *labels*). When called with arguments, the labels will
+ appear at the specified radial distances and angle.
+
+ Parameters
+ ----------
+ radii : tuple with floats
+ The radii for the radial gridlines
+
+ labels : tuple with strings or None
+ The labels to use at each radial gridline. The
+ `matplotlib.ticker.ScalarFormatter` will be used if None.
+
+ angle : float
+ The angular position of the radius labels in degrees.
+
+ fmt : str or None
+ Format string used in `matplotlib.ticker.FormatStrFormatter`.
+ For example '%f'.
+
+ Returns
+ -------
+ lines : list of `.lines.Line2D`
+ The radial gridlines.
+
+ labels : list of `.text.Text`
+ The tick labels.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ *kwargs* are optional `.Text` properties for the labels.
+
+ See Also
+ --------
+ .pyplot.thetagrids
+ .projections.polar.PolarAxes.set_rgrids
+ .Axis.get_gridlines
+ .Axis.get_ticklabels
+
+ Examples
+ --------
+ ::
+
+ # set the locations of the radial gridlines
+ lines, labels = rgrids( (0.25, 0.5, 1.0) )
+
+ # set the locations and labels of the radial gridlines
+ lines, labels = rgrids( (0.25, 0.5, 1.0), ('Tom', 'Dick', 'Harry' ))
+ """
+ ax = gca()
+ if not isinstance(ax, PolarAxes):
+ raise RuntimeError('rgrids only defined for polar Axes')
+ if all(p is None for p in [radii, labels, angle, fmt]) and not kwargs:
+ lines_out: list[Line2D] = ax.yaxis.get_gridlines()
+ labels_out: list[Text] = ax.yaxis.get_ticklabels()
+ elif radii is None:
+ raise TypeError("'radii' cannot be None when other parameters are passed")
+ else:
+ lines_out, labels_out = ax.set_rgrids(
+ radii, labels=labels, angle=angle, fmt=fmt, **kwargs)
+ return lines_out, labels_out
+
+
+def thetagrids(
+ angles: ArrayLike | None = None,
+ labels: Sequence[str | Text] | None = None,
+ fmt: str | None = None,
+ **kwargs
+) -> tuple[list[Line2D], list[Text]]:
+ """
+ Get or set the theta gridlines on the current polar plot.
+
+ Call signatures::
+
+ lines, labels = thetagrids()
+ lines, labels = thetagrids(angles, labels=None, fmt=None, **kwargs)
+
+ When called with no arguments, `.thetagrids` simply returns the tuple
+ (*lines*, *labels*). When called with arguments, the labels will
+ appear at the specified angles.
+
+ Parameters
+ ----------
+ angles : tuple with floats, degrees
+ The angles of the theta gridlines.
+
+ labels : tuple with strings or None
+ The labels to use at each radial gridline. The
+ `.projections.polar.ThetaFormatter` will be used if None.
+
+ fmt : str or None
+ Format string used in `matplotlib.ticker.FormatStrFormatter`.
+ For example '%f'. Note that the angle in radians will be used.
+
+ Returns
+ -------
+ lines : list of `.lines.Line2D`
+ The theta gridlines.
+
+ labels : list of `.text.Text`
+ The tick labels.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ *kwargs* are optional `.Text` properties for the labels.
+
+ See Also
+ --------
+ .pyplot.rgrids
+ .projections.polar.PolarAxes.set_thetagrids
+ .Axis.get_gridlines
+ .Axis.get_ticklabels
+
+ Examples
+ --------
+ ::
+
+ # set the locations of the angular gridlines
+ lines, labels = thetagrids(range(45, 360, 90))
+
+ # set the locations and labels of the angular gridlines
+ lines, labels = thetagrids(range(45, 360, 90), ('NE', 'NW', 'SW', 'SE'))
+ """
+ ax = gca()
+ if not isinstance(ax, PolarAxes):
+ raise RuntimeError('thetagrids only defined for polar Axes')
+ if all(param is None for param in [angles, labels, fmt]) and not kwargs:
+ lines_out: list[Line2D] = ax.xaxis.get_ticklines()
+ labels_out: list[Text] = ax.xaxis.get_ticklabels()
+ elif angles is None:
+ raise TypeError("'angles' cannot be None when other parameters are passed")
+ else:
+ lines_out, labels_out = ax.set_thetagrids(angles,
+ labels=labels, fmt=fmt,
+ **kwargs)
+ return lines_out, labels_out
+
+
+@_api.deprecated("3.7", pending=True)
+def get_plot_commands() -> list[str]:
+ """
+ Get a sorted list of all of the plotting commands.
+ """
+ NON_PLOT_COMMANDS = {
+ 'connect', 'disconnect', 'get_current_fig_manager', 'ginput',
+ 'new_figure_manager', 'waitforbuttonpress'}
+ return [name for name in _get_pyplot_commands()
+ if name not in NON_PLOT_COMMANDS]
+
+
+def _get_pyplot_commands() -> list[str]:
+ # This works by searching for all functions in this module and removing
+ # a few hard-coded exclusions, as well as all of the colormap-setting
+ # functions, and anything marked as private with a preceding underscore.
+ exclude = {'colormaps', 'colors', 'get_plot_commands', *colormaps}
+ this_module = inspect.getmodule(get_plot_commands)
+ return sorted(
+ name for name, obj in globals().items()
+ if not name.startswith('_') and name not in exclude
+ and inspect.isfunction(obj)
+ and inspect.getmodule(obj) is this_module)
+
+
+## Plotting part 1: manually generated functions and wrappers ##
+
+
+@_copy_docstring_and_deprecators(Figure.colorbar)
+def colorbar(
+ mappable: ScalarMappable | ColorizingArtist | None = None,
+ cax: matplotlib.axes.Axes | None = None,
+ ax: matplotlib.axes.Axes | Iterable[matplotlib.axes.Axes] | None = None,
+ **kwargs
+) -> Colorbar:
+ if mappable is None:
+ mappable = gci()
+ if mappable is None:
+ raise RuntimeError('No mappable was found to use for colorbar '
+ 'creation. First define a mappable such as '
+ 'an image (with imshow) or a contour set ('
+ 'with contourf).')
+ ret = gcf().colorbar(mappable, cax=cax, ax=ax, **kwargs)
+ return ret
+
+
+def clim(vmin: float | None = None, vmax: float | None = None) -> None:
+ """
+ Set the color limits of the current image.
+
+ If either *vmin* or *vmax* is None, the image min/max respectively
+ will be used for color scaling.
+
+ If you want to set the clim of multiple images, use
+ `~.ScalarMappable.set_clim` on every image, for example::
+
+ for im in gca().get_images():
+ im.set_clim(0, 0.5)
+
+ """
+ im = gci()
+ if im is None:
+ raise RuntimeError('You must first define an image, e.g., with imshow')
+
+ im.set_clim(vmin, vmax)
+
+
+def get_cmap(name: Colormap | str | None = None, lut: int | None = None) -> Colormap:
+ """
+ Get a colormap instance, defaulting to rc values if *name* is None.
+
+ Parameters
+ ----------
+ name : `~matplotlib.colors.Colormap` or str or None, default: None
+ If a `.Colormap` instance, it will be returned. Otherwise, the name of
+ a colormap known to Matplotlib, which will be resampled by *lut*. The
+ default, None, means :rc:`image.cmap`.
+ lut : int or None, default: None
+ If *name* is not already a Colormap instance and *lut* is not None, the
+ colormap will be resampled to have *lut* entries in the lookup table.
+
+ Returns
+ -------
+ Colormap
+ """
+ if name is None:
+ name = rcParams['image.cmap']
+ if isinstance(name, Colormap):
+ return name
+ _api.check_in_list(sorted(_colormaps), name=name)
+ if lut is None:
+ return _colormaps[name]
+ else:
+ return _colormaps[name].resampled(lut)
+
+
+def set_cmap(cmap: Colormap | str) -> None:
+ """
+ Set the default colormap, and applies it to the current image if any.
+
+ Parameters
+ ----------
+ cmap : `~matplotlib.colors.Colormap` or str
+ A colormap instance or the name of a registered colormap.
+
+ See Also
+ --------
+ colormaps
+ get_cmap
+ """
+ cmap = get_cmap(cmap)
+
+ rc('image', cmap=cmap.name)
+ im = gci()
+
+ if im is not None:
+ im.set_cmap(cmap)
+
+
+@_copy_docstring_and_deprecators(matplotlib.image.imread)
+def imread(
+ fname: str | pathlib.Path | BinaryIO, format: str | None = None
+) -> np.ndarray:
+ return matplotlib.image.imread(fname, format)
+
+
+@_copy_docstring_and_deprecators(matplotlib.image.imsave)
+def imsave(
+ fname: str | os.PathLike | BinaryIO, arr: ArrayLike, **kwargs
+) -> None:
+ matplotlib.image.imsave(fname, arr, **kwargs)
+
+
+def matshow(A: ArrayLike, fignum: None | int = None, **kwargs) -> AxesImage:
+ """
+ Display a 2D array as a matrix in a new figure window.
+
+ The origin is set at the upper left hand corner.
+ The indexing is ``(row, column)`` so that the first index runs vertically
+ and the second index runs horizontally in the figure:
+
+ .. code-block:: none
+
+ A[0, 0] ⋯ A[0, M-1]
+ â‹® â‹®
+ A[N-1, 0] ⋯ A[N-1, M-1]
+
+ The aspect ratio of the figure window is that of the array,
+ unless this would make an excessively short or narrow figure.
+
+ Tick labels for the xaxis are placed on top.
+
+ Parameters
+ ----------
+ A : 2D array-like
+ The matrix to be displayed.
+
+ fignum : None or int
+ If *None*, create a new, appropriately sized figure window.
+
+ If 0, use the current Axes (creating one if there is none, without ever
+ adjusting the figure size).
+
+ Otherwise, create a new Axes on the figure with the given number
+ (creating it at the appropriate size if it does not exist, but not
+ adjusting the figure size otherwise). Note that this will be drawn on
+ top of any preexisting Axes on the figure.
+
+ Returns
+ -------
+ `~matplotlib.image.AxesImage`
+
+ Other Parameters
+ ----------------
+ **kwargs : `~matplotlib.axes.Axes.imshow` arguments
+
+ """
+ A = np.asanyarray(A)
+ if fignum == 0:
+ ax = gca()
+ else:
+ if fignum is not None and fignum_exists(fignum):
+ # Do not try to set a figure size.
+ figsize = None
+ else:
+ # Extract actual aspect ratio of array and make appropriately sized figure.
+ figsize = figaspect(A)
+ fig = figure(fignum, figsize=figsize)
+ ax = fig.add_axes((0.15, 0.09, 0.775, 0.775))
+ im = ax.matshow(A, **kwargs)
+ sci(im)
+ return im
+
+
+def polar(*args, **kwargs) -> list[Line2D]:
+ """
+ Make a polar plot.
+
+ call signature::
+
+ polar(theta, r, [fmt], **kwargs)
+
+ This is a convenience wrapper around `.pyplot.plot`. It ensures that the
+ current Axes is polar (or creates one if needed) and then passes all parameters
+ to ``.pyplot.plot``.
+
+ .. note::
+ When making polar plots using the :ref:`pyplot API `,
+ ``polar()`` should typically be the first command because that makes sure
+ a polar Axes is created. Using other commands such as ``plt.title()``
+ before this can lead to the implicit creation of a rectangular Axes, in which
+ case a subsequent ``polar()`` call will fail.
+ """
+ # If an axis already exists, check if it has a polar projection
+ if gcf().get_axes():
+ ax = gca()
+ if not isinstance(ax, PolarAxes):
+ _api.warn_deprecated(
+ "3.10",
+ message="There exists a non-polar current Axes. Therefore, the "
+ "resulting plot from 'polar()' is non-polar. You likely "
+ "should call 'polar()' before any other pyplot plotting "
+ "commands. "
+ "Support for this scenario is deprecated in %(since)s and "
+ "will raise an error in %(removal)s"
+ )
+ else:
+ ax = axes(projection="polar")
+ return ax.plot(*args, **kwargs)
+
+
+# If rcParams['backend_fallback'] is true, and an interactive backend is
+# requested, ignore rcParams['backend'] and force selection of a backend that
+# is compatible with the current running interactive framework.
+if rcParams["backend_fallback"]:
+ requested_backend = rcParams._get_backend_or_none() # type: ignore[attr-defined]
+ requested_backend = None if requested_backend is None else requested_backend.lower()
+ available_backends = backend_registry.list_builtin(BackendFilter.INTERACTIVE)
+ if (
+ requested_backend in (set(available_backends) - {'webagg', 'nbagg'})
+ and cbook._get_running_interactive_framework()
+ ):
+ rcParams._set("backend", rcsetup._auto_backend_sentinel)
+
+# fmt: on
+
+################# REMAINING CONTENT GENERATED BY boilerplate.py ##############
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Figure.figimage)
+def figimage(
+ X: ArrayLike,
+ xo: int = 0,
+ yo: int = 0,
+ alpha: float | None = None,
+ norm: str | Normalize | None = None,
+ cmap: str | Colormap | None = None,
+ vmin: float | None = None,
+ vmax: float | None = None,
+ origin: Literal["upper", "lower"] | None = None,
+ resize: bool = False,
+ *,
+ colorizer: Colorizer | None = None,
+ **kwargs,
+) -> FigureImage:
+ return gcf().figimage(
+ X,
+ xo=xo,
+ yo=yo,
+ alpha=alpha,
+ norm=norm,
+ cmap=cmap,
+ vmin=vmin,
+ vmax=vmax,
+ origin=origin,
+ resize=resize,
+ colorizer=colorizer,
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Figure.text)
+def figtext(
+ x: float, y: float, s: str, fontdict: dict[str, Any] | None = None, **kwargs
+) -> Text:
+ return gcf().text(x, y, s, fontdict=fontdict, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Figure.gca)
+def gca() -> Axes:
+ return gcf().gca()
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Figure._gci)
+def gci() -> ColorizingArtist | None:
+ return gcf()._gci()
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Figure.ginput)
+def ginput(
+ n: int = 1,
+ timeout: float = 30,
+ show_clicks: bool = True,
+ mouse_add: MouseButton = MouseButton.LEFT,
+ mouse_pop: MouseButton = MouseButton.RIGHT,
+ mouse_stop: MouseButton = MouseButton.MIDDLE,
+) -> list[tuple[int, int]]:
+ return gcf().ginput(
+ n=n,
+ timeout=timeout,
+ show_clicks=show_clicks,
+ mouse_add=mouse_add,
+ mouse_pop=mouse_pop,
+ mouse_stop=mouse_stop,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Figure.subplots_adjust)
+def subplots_adjust(
+ left: float | None = None,
+ bottom: float | None = None,
+ right: float | None = None,
+ top: float | None = None,
+ wspace: float | None = None,
+ hspace: float | None = None,
+) -> None:
+ gcf().subplots_adjust(
+ left=left, bottom=bottom, right=right, top=top, wspace=wspace, hspace=hspace
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Figure.suptitle)
+def suptitle(t: str, **kwargs) -> Text:
+ return gcf().suptitle(t, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Figure.tight_layout)
+def tight_layout(
+ *,
+ pad: float = 1.08,
+ h_pad: float | None = None,
+ w_pad: float | None = None,
+ rect: tuple[float, float, float, float] | None = None,
+) -> None:
+ gcf().tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Figure.waitforbuttonpress)
+def waitforbuttonpress(timeout: float = -1) -> None | bool:
+ return gcf().waitforbuttonpress(timeout=timeout)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.acorr)
+def acorr(
+ x: ArrayLike, *, data=None, **kwargs
+) -> tuple[np.ndarray, np.ndarray, LineCollection | Line2D, Line2D | None]:
+ return gca().acorr(x, **({"data": data} if data is not None else {}), **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.angle_spectrum)
+def angle_spectrum(
+ x: ArrayLike,
+ Fs: float | None = None,
+ Fc: int | None = None,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None,
+ pad_to: int | None = None,
+ sides: Literal["default", "onesided", "twosided"] | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[np.ndarray, np.ndarray, Line2D]:
+ return gca().angle_spectrum(
+ x,
+ Fs=Fs,
+ Fc=Fc,
+ window=window,
+ pad_to=pad_to,
+ sides=sides,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.annotate)
+def annotate(
+ text: str,
+ xy: tuple[float, float],
+ xytext: tuple[float, float] | None = None,
+ xycoords: CoordsType = "data",
+ textcoords: CoordsType | None = None,
+ arrowprops: dict[str, Any] | None = None,
+ annotation_clip: bool | None = None,
+ **kwargs,
+) -> Annotation:
+ return gca().annotate(
+ text,
+ xy,
+ xytext=xytext,
+ xycoords=xycoords,
+ textcoords=textcoords,
+ arrowprops=arrowprops,
+ annotation_clip=annotation_clip,
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.arrow)
+def arrow(x: float, y: float, dx: float, dy: float, **kwargs) -> FancyArrow:
+ return gca().arrow(x, y, dx, dy, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.autoscale)
+def autoscale(
+ enable: bool = True,
+ axis: Literal["both", "x", "y"] = "both",
+ tight: bool | None = None,
+) -> None:
+ gca().autoscale(enable=enable, axis=axis, tight=tight)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.axhline)
+def axhline(y: float = 0, xmin: float = 0, xmax: float = 1, **kwargs) -> Line2D:
+ return gca().axhline(y=y, xmin=xmin, xmax=xmax, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.axhspan)
+def axhspan(
+ ymin: float, ymax: float, xmin: float = 0, xmax: float = 1, **kwargs
+) -> Rectangle:
+ return gca().axhspan(ymin, ymax, xmin=xmin, xmax=xmax, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.axis)
+def axis(
+ arg: tuple[float, float, float, float] | bool | str | None = None,
+ /,
+ *,
+ emit: bool = True,
+ **kwargs,
+) -> tuple[float, float, float, float]:
+ return gca().axis(arg, emit=emit, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.axline)
+def axline(
+ xy1: tuple[float, float],
+ xy2: tuple[float, float] | None = None,
+ *,
+ slope: float | None = None,
+ **kwargs,
+) -> AxLine:
+ return gca().axline(xy1, xy2=xy2, slope=slope, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.axvline)
+def axvline(x: float = 0, ymin: float = 0, ymax: float = 1, **kwargs) -> Line2D:
+ return gca().axvline(x=x, ymin=ymin, ymax=ymax, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.axvspan)
+def axvspan(
+ xmin: float, xmax: float, ymin: float = 0, ymax: float = 1, **kwargs
+) -> Rectangle:
+ return gca().axvspan(xmin, xmax, ymin=ymin, ymax=ymax, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.bar)
+def bar(
+ x: float | ArrayLike,
+ height: float | ArrayLike,
+ width: float | ArrayLike = 0.8,
+ bottom: float | ArrayLike | None = None,
+ *,
+ align: Literal["center", "edge"] = "center",
+ data=None,
+ **kwargs,
+) -> BarContainer:
+ return gca().bar(
+ x,
+ height,
+ width=width,
+ bottom=bottom,
+ align=align,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.barbs)
+def barbs(*args, data=None, **kwargs) -> Barbs:
+ return gca().barbs(*args, **({"data": data} if data is not None else {}), **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.barh)
+def barh(
+ y: float | ArrayLike,
+ width: float | ArrayLike,
+ height: float | ArrayLike = 0.8,
+ left: float | ArrayLike | None = None,
+ *,
+ align: Literal["center", "edge"] = "center",
+ data=None,
+ **kwargs,
+) -> BarContainer:
+ return gca().barh(
+ y,
+ width,
+ height=height,
+ left=left,
+ align=align,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.bar_label)
+def bar_label(
+ container: BarContainer,
+ labels: ArrayLike | None = None,
+ *,
+ fmt: str | Callable[[float], str] = "%g",
+ label_type: Literal["center", "edge"] = "edge",
+ padding: float = 0,
+ **kwargs,
+) -> list[Annotation]:
+ return gca().bar_label(
+ container,
+ labels=labels,
+ fmt=fmt,
+ label_type=label_type,
+ padding=padding,
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.boxplot)
+def boxplot(
+ x: ArrayLike | Sequence[ArrayLike],
+ notch: bool | None = None,
+ sym: str | None = None,
+ vert: bool | None = None,
+ orientation: Literal["vertical", "horizontal"] = "vertical",
+ whis: float | tuple[float, float] | None = None,
+ positions: ArrayLike | None = None,
+ widths: float | ArrayLike | None = None,
+ patch_artist: bool | None = None,
+ bootstrap: int | None = None,
+ usermedians: ArrayLike | None = None,
+ conf_intervals: ArrayLike | None = None,
+ meanline: bool | None = None,
+ showmeans: bool | None = None,
+ showcaps: bool | None = None,
+ showbox: bool | None = None,
+ showfliers: bool | None = None,
+ boxprops: dict[str, Any] | None = None,
+ tick_labels: Sequence[str] | None = None,
+ flierprops: dict[str, Any] | None = None,
+ medianprops: dict[str, Any] | None = None,
+ meanprops: dict[str, Any] | None = None,
+ capprops: dict[str, Any] | None = None,
+ whiskerprops: dict[str, Any] | None = None,
+ manage_ticks: bool = True,
+ autorange: bool = False,
+ zorder: float | None = None,
+ capwidths: float | ArrayLike | None = None,
+ label: Sequence[str] | None = None,
+ *,
+ data=None,
+) -> dict[str, Any]:
+ return gca().boxplot(
+ x,
+ notch=notch,
+ sym=sym,
+ vert=vert,
+ orientation=orientation,
+ whis=whis,
+ positions=positions,
+ widths=widths,
+ patch_artist=patch_artist,
+ bootstrap=bootstrap,
+ usermedians=usermedians,
+ conf_intervals=conf_intervals,
+ meanline=meanline,
+ showmeans=showmeans,
+ showcaps=showcaps,
+ showbox=showbox,
+ showfliers=showfliers,
+ boxprops=boxprops,
+ tick_labels=tick_labels,
+ flierprops=flierprops,
+ medianprops=medianprops,
+ meanprops=meanprops,
+ capprops=capprops,
+ whiskerprops=whiskerprops,
+ manage_ticks=manage_ticks,
+ autorange=autorange,
+ zorder=zorder,
+ capwidths=capwidths,
+ label=label,
+ **({"data": data} if data is not None else {}),
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.broken_barh)
+def broken_barh(
+ xranges: Sequence[tuple[float, float]],
+ yrange: tuple[float, float],
+ *,
+ data=None,
+ **kwargs,
+) -> PolyCollection:
+ return gca().broken_barh(
+ xranges, yrange, **({"data": data} if data is not None else {}), **kwargs
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.clabel)
+def clabel(CS: ContourSet, levels: ArrayLike | None = None, **kwargs) -> list[Text]:
+ return gca().clabel(CS, levels=levels, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.cohere)
+def cohere(
+ x: ArrayLike,
+ y: ArrayLike,
+ NFFT: int = 256,
+ Fs: float = 2,
+ Fc: int = 0,
+ detrend: (
+ Literal["none", "mean", "linear"] | Callable[[ArrayLike], ArrayLike]
+ ) = mlab.detrend_none,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike = mlab.window_hanning,
+ noverlap: int = 0,
+ pad_to: int | None = None,
+ sides: Literal["default", "onesided", "twosided"] = "default",
+ scale_by_freq: bool | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[np.ndarray, np.ndarray]:
+ return gca().cohere(
+ x,
+ y,
+ NFFT=NFFT,
+ Fs=Fs,
+ Fc=Fc,
+ detrend=detrend,
+ window=window,
+ noverlap=noverlap,
+ pad_to=pad_to,
+ sides=sides,
+ scale_by_freq=scale_by_freq,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.contour)
+def contour(*args, data=None, **kwargs) -> QuadContourSet:
+ __ret = gca().contour(
+ *args, **({"data": data} if data is not None else {}), **kwargs
+ )
+ if __ret._A is not None: # type: ignore[attr-defined]
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.contourf)
+def contourf(*args, data=None, **kwargs) -> QuadContourSet:
+ __ret = gca().contourf(
+ *args, **({"data": data} if data is not None else {}), **kwargs
+ )
+ if __ret._A is not None: # type: ignore[attr-defined]
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.csd)
+def csd(
+ x: ArrayLike,
+ y: ArrayLike,
+ NFFT: int | None = None,
+ Fs: float | None = None,
+ Fc: int | None = None,
+ detrend: (
+ Literal["none", "mean", "linear"] | Callable[[ArrayLike], ArrayLike] | None
+ ) = None,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None,
+ noverlap: int | None = None,
+ pad_to: int | None = None,
+ sides: Literal["default", "onesided", "twosided"] | None = None,
+ scale_by_freq: bool | None = None,
+ return_line: bool | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[np.ndarray, np.ndarray] | tuple[np.ndarray, np.ndarray, Line2D]:
+ return gca().csd(
+ x,
+ y,
+ NFFT=NFFT,
+ Fs=Fs,
+ Fc=Fc,
+ detrend=detrend,
+ window=window,
+ noverlap=noverlap,
+ pad_to=pad_to,
+ sides=sides,
+ scale_by_freq=scale_by_freq,
+ return_line=return_line,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.ecdf)
+def ecdf(
+ x: ArrayLike,
+ weights: ArrayLike | None = None,
+ *,
+ complementary: bool = False,
+ orientation: Literal["vertical", "horizontal"] = "vertical",
+ compress: bool = False,
+ data=None,
+ **kwargs,
+) -> Line2D:
+ return gca().ecdf(
+ x,
+ weights=weights,
+ complementary=complementary,
+ orientation=orientation,
+ compress=compress,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.errorbar)
+def errorbar(
+ x: float | ArrayLike,
+ y: float | ArrayLike,
+ yerr: float | ArrayLike | None = None,
+ xerr: float | ArrayLike | None = None,
+ fmt: str = "",
+ ecolor: ColorType | None = None,
+ elinewidth: float | None = None,
+ capsize: float | None = None,
+ barsabove: bool = False,
+ lolims: bool | ArrayLike = False,
+ uplims: bool | ArrayLike = False,
+ xlolims: bool | ArrayLike = False,
+ xuplims: bool | ArrayLike = False,
+ errorevery: int | tuple[int, int] = 1,
+ capthick: float | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> ErrorbarContainer:
+ return gca().errorbar(
+ x,
+ y,
+ yerr=yerr,
+ xerr=xerr,
+ fmt=fmt,
+ ecolor=ecolor,
+ elinewidth=elinewidth,
+ capsize=capsize,
+ barsabove=barsabove,
+ lolims=lolims,
+ uplims=uplims,
+ xlolims=xlolims,
+ xuplims=xuplims,
+ errorevery=errorevery,
+ capthick=capthick,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.eventplot)
+def eventplot(
+ positions: ArrayLike | Sequence[ArrayLike],
+ orientation: Literal["horizontal", "vertical"] = "horizontal",
+ lineoffsets: float | Sequence[float] = 1,
+ linelengths: float | Sequence[float] = 1,
+ linewidths: float | Sequence[float] | None = None,
+ colors: ColorType | Sequence[ColorType] | None = None,
+ alpha: float | Sequence[float] | None = None,
+ linestyles: LineStyleType | Sequence[LineStyleType] = "solid",
+ *,
+ data=None,
+ **kwargs,
+) -> EventCollection:
+ return gca().eventplot(
+ positions,
+ orientation=orientation,
+ lineoffsets=lineoffsets,
+ linelengths=linelengths,
+ linewidths=linewidths,
+ colors=colors,
+ alpha=alpha,
+ linestyles=linestyles,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.fill)
+def fill(*args, data=None, **kwargs) -> list[Polygon]:
+ return gca().fill(*args, **({"data": data} if data is not None else {}), **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.fill_between)
+def fill_between(
+ x: ArrayLike,
+ y1: ArrayLike | float,
+ y2: ArrayLike | float = 0,
+ where: Sequence[bool] | None = None,
+ interpolate: bool = False,
+ step: Literal["pre", "post", "mid"] | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> FillBetweenPolyCollection:
+ return gca().fill_between(
+ x,
+ y1,
+ y2=y2,
+ where=where,
+ interpolate=interpolate,
+ step=step,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.fill_betweenx)
+def fill_betweenx(
+ y: ArrayLike,
+ x1: ArrayLike | float,
+ x2: ArrayLike | float = 0,
+ where: Sequence[bool] | None = None,
+ step: Literal["pre", "post", "mid"] | None = None,
+ interpolate: bool = False,
+ *,
+ data=None,
+ **kwargs,
+) -> FillBetweenPolyCollection:
+ return gca().fill_betweenx(
+ y,
+ x1,
+ x2=x2,
+ where=where,
+ step=step,
+ interpolate=interpolate,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.grid)
+def grid(
+ visible: bool | None = None,
+ which: Literal["major", "minor", "both"] = "major",
+ axis: Literal["both", "x", "y"] = "both",
+ **kwargs,
+) -> None:
+ gca().grid(visible=visible, which=which, axis=axis, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.hexbin)
+def hexbin(
+ x: ArrayLike,
+ y: ArrayLike,
+ C: ArrayLike | None = None,
+ gridsize: int | tuple[int, int] = 100,
+ bins: Literal["log"] | int | Sequence[float] | None = None,
+ xscale: Literal["linear", "log"] = "linear",
+ yscale: Literal["linear", "log"] = "linear",
+ extent: tuple[float, float, float, float] | None = None,
+ cmap: str | Colormap | None = None,
+ norm: str | Normalize | None = None,
+ vmin: float | None = None,
+ vmax: float | None = None,
+ alpha: float | None = None,
+ linewidths: float | None = None,
+ edgecolors: Literal["face", "none"] | ColorType = "face",
+ reduce_C_function: Callable[[np.ndarray | list[float]], float] = np.mean,
+ mincnt: int | None = None,
+ marginals: bool = False,
+ colorizer: Colorizer | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> PolyCollection:
+ __ret = gca().hexbin(
+ x,
+ y,
+ C=C,
+ gridsize=gridsize,
+ bins=bins,
+ xscale=xscale,
+ yscale=yscale,
+ extent=extent,
+ cmap=cmap,
+ norm=norm,
+ vmin=vmin,
+ vmax=vmax,
+ alpha=alpha,
+ linewidths=linewidths,
+ edgecolors=edgecolors,
+ reduce_C_function=reduce_C_function,
+ mincnt=mincnt,
+ marginals=marginals,
+ colorizer=colorizer,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.hist)
+def hist(
+ x: ArrayLike | Sequence[ArrayLike],
+ bins: int | Sequence[float] | str | None = None,
+ range: tuple[float, float] | None = None,
+ density: bool = False,
+ weights: ArrayLike | None = None,
+ cumulative: bool | float = False,
+ bottom: ArrayLike | float | None = None,
+ histtype: Literal["bar", "barstacked", "step", "stepfilled"] = "bar",
+ align: Literal["left", "mid", "right"] = "mid",
+ orientation: Literal["vertical", "horizontal"] = "vertical",
+ rwidth: float | None = None,
+ log: bool = False,
+ color: ColorType | Sequence[ColorType] | None = None,
+ label: str | Sequence[str] | None = None,
+ stacked: bool = False,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[
+ np.ndarray | list[np.ndarray],
+ np.ndarray,
+ BarContainer | Polygon | list[BarContainer | Polygon],
+]:
+ return gca().hist(
+ x,
+ bins=bins,
+ range=range,
+ density=density,
+ weights=weights,
+ cumulative=cumulative,
+ bottom=bottom,
+ histtype=histtype,
+ align=align,
+ orientation=orientation,
+ rwidth=rwidth,
+ log=log,
+ color=color,
+ label=label,
+ stacked=stacked,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.stairs)
+def stairs(
+ values: ArrayLike,
+ edges: ArrayLike | None = None,
+ *,
+ orientation: Literal["vertical", "horizontal"] = "vertical",
+ baseline: float | ArrayLike | None = 0,
+ fill: bool = False,
+ data=None,
+ **kwargs,
+) -> StepPatch:
+ return gca().stairs(
+ values,
+ edges=edges,
+ orientation=orientation,
+ baseline=baseline,
+ fill=fill,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.hist2d)
+def hist2d(
+ x: ArrayLike,
+ y: ArrayLike,
+ bins: None | int | tuple[int, int] | ArrayLike | tuple[ArrayLike, ArrayLike] = 10,
+ range: ArrayLike | None = None,
+ density: bool = False,
+ weights: ArrayLike | None = None,
+ cmin: float | None = None,
+ cmax: float | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[np.ndarray, np.ndarray, np.ndarray, QuadMesh]:
+ __ret = gca().hist2d(
+ x,
+ y,
+ bins=bins,
+ range=range,
+ density=density,
+ weights=weights,
+ cmin=cmin,
+ cmax=cmax,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+ sci(__ret[-1])
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.hlines)
+def hlines(
+ y: float | ArrayLike,
+ xmin: float | ArrayLike,
+ xmax: float | ArrayLike,
+ colors: ColorType | Sequence[ColorType] | None = None,
+ linestyles: LineStyleType = "solid",
+ label: str = "",
+ *,
+ data=None,
+ **kwargs,
+) -> LineCollection:
+ return gca().hlines(
+ y,
+ xmin,
+ xmax,
+ colors=colors,
+ linestyles=linestyles,
+ label=label,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.imshow)
+def imshow(
+ X: ArrayLike | PIL.Image.Image,
+ cmap: str | Colormap | None = None,
+ norm: str | Normalize | None = None,
+ *,
+ aspect: Literal["equal", "auto"] | float | None = None,
+ interpolation: str | None = None,
+ alpha: float | ArrayLike | None = None,
+ vmin: float | None = None,
+ vmax: float | None = None,
+ colorizer: Colorizer | None = None,
+ origin: Literal["upper", "lower"] | None = None,
+ extent: tuple[float, float, float, float] | None = None,
+ interpolation_stage: Literal["data", "rgba", "auto"] | None = None,
+ filternorm: bool = True,
+ filterrad: float = 4.0,
+ resample: bool | None = None,
+ url: str | None = None,
+ data=None,
+ **kwargs,
+) -> AxesImage:
+ __ret = gca().imshow(
+ X,
+ cmap=cmap,
+ norm=norm,
+ aspect=aspect,
+ interpolation=interpolation,
+ alpha=alpha,
+ vmin=vmin,
+ vmax=vmax,
+ colorizer=colorizer,
+ origin=origin,
+ extent=extent,
+ interpolation_stage=interpolation_stage,
+ filternorm=filternorm,
+ filterrad=filterrad,
+ resample=resample,
+ url=url,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.legend)
+def legend(*args, **kwargs) -> Legend:
+ return gca().legend(*args, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.locator_params)
+def locator_params(
+ axis: Literal["both", "x", "y"] = "both", tight: bool | None = None, **kwargs
+) -> None:
+ gca().locator_params(axis=axis, tight=tight, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.loglog)
+def loglog(*args, **kwargs) -> list[Line2D]:
+ return gca().loglog(*args, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.magnitude_spectrum)
+def magnitude_spectrum(
+ x: ArrayLike,
+ Fs: float | None = None,
+ Fc: int | None = None,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None,
+ pad_to: int | None = None,
+ sides: Literal["default", "onesided", "twosided"] | None = None,
+ scale: Literal["default", "linear", "dB"] | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[np.ndarray, np.ndarray, Line2D]:
+ return gca().magnitude_spectrum(
+ x,
+ Fs=Fs,
+ Fc=Fc,
+ window=window,
+ pad_to=pad_to,
+ sides=sides,
+ scale=scale,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.margins)
+def margins(
+ *margins: float,
+ x: float | None = None,
+ y: float | None = None,
+ tight: bool | None = True,
+) -> tuple[float, float] | None:
+ return gca().margins(*margins, x=x, y=y, tight=tight)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.minorticks_off)
+def minorticks_off() -> None:
+ gca().minorticks_off()
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.minorticks_on)
+def minorticks_on() -> None:
+ gca().minorticks_on()
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.pcolor)
+def pcolor(
+ *args: ArrayLike,
+ shading: Literal["flat", "nearest", "auto"] | None = None,
+ alpha: float | None = None,
+ norm: str | Normalize | None = None,
+ cmap: str | Colormap | None = None,
+ vmin: float | None = None,
+ vmax: float | None = None,
+ colorizer: Colorizer | None = None,
+ data=None,
+ **kwargs,
+) -> Collection:
+ __ret = gca().pcolor(
+ *args,
+ shading=shading,
+ alpha=alpha,
+ norm=norm,
+ cmap=cmap,
+ vmin=vmin,
+ vmax=vmax,
+ colorizer=colorizer,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.pcolormesh)
+def pcolormesh(
+ *args: ArrayLike,
+ alpha: float | None = None,
+ norm: str | Normalize | None = None,
+ cmap: str | Colormap | None = None,
+ vmin: float | None = None,
+ vmax: float | None = None,
+ colorizer: Colorizer | None = None,
+ shading: Literal["flat", "nearest", "gouraud", "auto"] | None = None,
+ antialiased: bool = False,
+ data=None,
+ **kwargs,
+) -> QuadMesh:
+ __ret = gca().pcolormesh(
+ *args,
+ alpha=alpha,
+ norm=norm,
+ cmap=cmap,
+ vmin=vmin,
+ vmax=vmax,
+ colorizer=colorizer,
+ shading=shading,
+ antialiased=antialiased,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.phase_spectrum)
+def phase_spectrum(
+ x: ArrayLike,
+ Fs: float | None = None,
+ Fc: int | None = None,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None,
+ pad_to: int | None = None,
+ sides: Literal["default", "onesided", "twosided"] | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[np.ndarray, np.ndarray, Line2D]:
+ return gca().phase_spectrum(
+ x,
+ Fs=Fs,
+ Fc=Fc,
+ window=window,
+ pad_to=pad_to,
+ sides=sides,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.pie)
+def pie(
+ x: ArrayLike,
+ explode: ArrayLike | None = None,
+ labels: Sequence[str] | None = None,
+ colors: ColorType | Sequence[ColorType] | None = None,
+ autopct: str | Callable[[float], str] | None = None,
+ pctdistance: float = 0.6,
+ shadow: bool = False,
+ labeldistance: float | None = 1.1,
+ startangle: float = 0,
+ radius: float = 1,
+ counterclock: bool = True,
+ wedgeprops: dict[str, Any] | None = None,
+ textprops: dict[str, Any] | None = None,
+ center: tuple[float, float] = (0, 0),
+ frame: bool = False,
+ rotatelabels: bool = False,
+ *,
+ normalize: bool = True,
+ hatch: str | Sequence[str] | None = None,
+ data=None,
+) -> tuple[list[Wedge], list[Text]] | tuple[list[Wedge], list[Text], list[Text]]:
+ return gca().pie(
+ x,
+ explode=explode,
+ labels=labels,
+ colors=colors,
+ autopct=autopct,
+ pctdistance=pctdistance,
+ shadow=shadow,
+ labeldistance=labeldistance,
+ startangle=startangle,
+ radius=radius,
+ counterclock=counterclock,
+ wedgeprops=wedgeprops,
+ textprops=textprops,
+ center=center,
+ frame=frame,
+ rotatelabels=rotatelabels,
+ normalize=normalize,
+ hatch=hatch,
+ **({"data": data} if data is not None else {}),
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.plot)
+def plot(
+ *args: float | ArrayLike | str,
+ scalex: bool = True,
+ scaley: bool = True,
+ data=None,
+ **kwargs,
+) -> list[Line2D]:
+ return gca().plot(
+ *args,
+ scalex=scalex,
+ scaley=scaley,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.plot_date)
+def plot_date(
+ x: ArrayLike,
+ y: ArrayLike,
+ fmt: str = "o",
+ tz: str | datetime.tzinfo | None = None,
+ xdate: bool = True,
+ ydate: bool = False,
+ *,
+ data=None,
+ **kwargs,
+) -> list[Line2D]:
+ return gca().plot_date(
+ x,
+ y,
+ fmt=fmt,
+ tz=tz,
+ xdate=xdate,
+ ydate=ydate,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.psd)
+def psd(
+ x: ArrayLike,
+ NFFT: int | None = None,
+ Fs: float | None = None,
+ Fc: int | None = None,
+ detrend: (
+ Literal["none", "mean", "linear"] | Callable[[ArrayLike], ArrayLike] | None
+ ) = None,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None,
+ noverlap: int | None = None,
+ pad_to: int | None = None,
+ sides: Literal["default", "onesided", "twosided"] | None = None,
+ scale_by_freq: bool | None = None,
+ return_line: bool | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[np.ndarray, np.ndarray] | tuple[np.ndarray, np.ndarray, Line2D]:
+ return gca().psd(
+ x,
+ NFFT=NFFT,
+ Fs=Fs,
+ Fc=Fc,
+ detrend=detrend,
+ window=window,
+ noverlap=noverlap,
+ pad_to=pad_to,
+ sides=sides,
+ scale_by_freq=scale_by_freq,
+ return_line=return_line,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.quiver)
+def quiver(*args, data=None, **kwargs) -> Quiver:
+ __ret = gca().quiver(
+ *args, **({"data": data} if data is not None else {}), **kwargs
+ )
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.quiverkey)
+def quiverkey(
+ Q: Quiver, X: float, Y: float, U: float, label: str, **kwargs
+) -> QuiverKey:
+ return gca().quiverkey(Q, X, Y, U, label, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.scatter)
+def scatter(
+ x: float | ArrayLike,
+ y: float | ArrayLike,
+ s: float | ArrayLike | None = None,
+ c: ArrayLike | Sequence[ColorType] | ColorType | None = None,
+ marker: MarkerType | None = None,
+ cmap: str | Colormap | None = None,
+ norm: str | Normalize | None = None,
+ vmin: float | None = None,
+ vmax: float | None = None,
+ alpha: float | None = None,
+ linewidths: float | Sequence[float] | None = None,
+ *,
+ edgecolors: Literal["face", "none"] | ColorType | Sequence[ColorType] | None = None,
+ colorizer: Colorizer | None = None,
+ plotnonfinite: bool = False,
+ data=None,
+ **kwargs,
+) -> PathCollection:
+ __ret = gca().scatter(
+ x,
+ y,
+ s=s,
+ c=c,
+ marker=marker,
+ cmap=cmap,
+ norm=norm,
+ vmin=vmin,
+ vmax=vmax,
+ alpha=alpha,
+ linewidths=linewidths,
+ edgecolors=edgecolors,
+ colorizer=colorizer,
+ plotnonfinite=plotnonfinite,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.semilogx)
+def semilogx(*args, **kwargs) -> list[Line2D]:
+ return gca().semilogx(*args, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.semilogy)
+def semilogy(*args, **kwargs) -> list[Line2D]:
+ return gca().semilogy(*args, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.specgram)
+def specgram(
+ x: ArrayLike,
+ NFFT: int | None = None,
+ Fs: float | None = None,
+ Fc: int | None = None,
+ detrend: (
+ Literal["none", "mean", "linear"] | Callable[[ArrayLike], ArrayLike] | None
+ ) = None,
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None,
+ noverlap: int | None = None,
+ cmap: str | Colormap | None = None,
+ xextent: tuple[float, float] | None = None,
+ pad_to: int | None = None,
+ sides: Literal["default", "onesided", "twosided"] | None = None,
+ scale_by_freq: bool | None = None,
+ mode: Literal["default", "psd", "magnitude", "angle", "phase"] | None = None,
+ scale: Literal["default", "linear", "dB"] | None = None,
+ vmin: float | None = None,
+ vmax: float | None = None,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[np.ndarray, np.ndarray, np.ndarray, AxesImage]:
+ __ret = gca().specgram(
+ x,
+ NFFT=NFFT,
+ Fs=Fs,
+ Fc=Fc,
+ detrend=detrend,
+ window=window,
+ noverlap=noverlap,
+ cmap=cmap,
+ xextent=xextent,
+ pad_to=pad_to,
+ sides=sides,
+ scale_by_freq=scale_by_freq,
+ mode=mode,
+ scale=scale,
+ vmin=vmin,
+ vmax=vmax,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+ sci(__ret[-1])
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.spy)
+def spy(
+ Z: ArrayLike,
+ precision: float | Literal["present"] = 0,
+ marker: str | None = None,
+ markersize: float | None = None,
+ aspect: Literal["equal", "auto"] | float | None = "equal",
+ origin: Literal["upper", "lower"] = "upper",
+ **kwargs,
+) -> AxesImage:
+ __ret = gca().spy(
+ Z,
+ precision=precision,
+ marker=marker,
+ markersize=markersize,
+ aspect=aspect,
+ origin=origin,
+ **kwargs,
+ )
+ if isinstance(__ret, _ColorizerInterface):
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.stackplot)
+def stackplot(
+ x, *args, labels=(), colors=None, hatch=None, baseline="zero", data=None, **kwargs
+):
+ return gca().stackplot(
+ x,
+ *args,
+ labels=labels,
+ colors=colors,
+ hatch=hatch,
+ baseline=baseline,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.stem)
+def stem(
+ *args: ArrayLike | str,
+ linefmt: str | None = None,
+ markerfmt: str | None = None,
+ basefmt: str | None = None,
+ bottom: float = 0,
+ label: str | None = None,
+ orientation: Literal["vertical", "horizontal"] = "vertical",
+ data=None,
+) -> StemContainer:
+ return gca().stem(
+ *args,
+ linefmt=linefmt,
+ markerfmt=markerfmt,
+ basefmt=basefmt,
+ bottom=bottom,
+ label=label,
+ orientation=orientation,
+ **({"data": data} if data is not None else {}),
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.step)
+def step(
+ x: ArrayLike,
+ y: ArrayLike,
+ *args,
+ where: Literal["pre", "post", "mid"] = "pre",
+ data=None,
+ **kwargs,
+) -> list[Line2D]:
+ return gca().step(
+ x,
+ y,
+ *args,
+ where=where,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.streamplot)
+def streamplot(
+ x,
+ y,
+ u,
+ v,
+ density=1,
+ linewidth=None,
+ color=None,
+ cmap=None,
+ norm=None,
+ arrowsize=1,
+ arrowstyle="-|>",
+ minlength=0.1,
+ transform=None,
+ zorder=None,
+ start_points=None,
+ maxlength=4.0,
+ integration_direction="both",
+ broken_streamlines=True,
+ *,
+ data=None,
+):
+ __ret = gca().streamplot(
+ x,
+ y,
+ u,
+ v,
+ density=density,
+ linewidth=linewidth,
+ color=color,
+ cmap=cmap,
+ norm=norm,
+ arrowsize=arrowsize,
+ arrowstyle=arrowstyle,
+ minlength=minlength,
+ transform=transform,
+ zorder=zorder,
+ start_points=start_points,
+ maxlength=maxlength,
+ integration_direction=integration_direction,
+ broken_streamlines=broken_streamlines,
+ **({"data": data} if data is not None else {}),
+ )
+ sci(__ret.lines)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.table)
+def table(
+ cellText=None,
+ cellColours=None,
+ cellLoc="right",
+ colWidths=None,
+ rowLabels=None,
+ rowColours=None,
+ rowLoc="left",
+ colLabels=None,
+ colColours=None,
+ colLoc="center",
+ loc="bottom",
+ bbox=None,
+ edges="closed",
+ **kwargs,
+):
+ return gca().table(
+ cellText=cellText,
+ cellColours=cellColours,
+ cellLoc=cellLoc,
+ colWidths=colWidths,
+ rowLabels=rowLabels,
+ rowColours=rowColours,
+ rowLoc=rowLoc,
+ colLabels=colLabels,
+ colColours=colColours,
+ colLoc=colLoc,
+ loc=loc,
+ bbox=bbox,
+ edges=edges,
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.text)
+def text(
+ x: float, y: float, s: str, fontdict: dict[str, Any] | None = None, **kwargs
+) -> Text:
+ return gca().text(x, y, s, fontdict=fontdict, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.tick_params)
+def tick_params(axis: Literal["both", "x", "y"] = "both", **kwargs) -> None:
+ gca().tick_params(axis=axis, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.ticklabel_format)
+def ticklabel_format(
+ *,
+ axis: Literal["both", "x", "y"] = "both",
+ style: Literal["", "sci", "scientific", "plain"] | None = None,
+ scilimits: tuple[int, int] | None = None,
+ useOffset: bool | float | None = None,
+ useLocale: bool | None = None,
+ useMathText: bool | None = None,
+) -> None:
+ gca().ticklabel_format(
+ axis=axis,
+ style=style,
+ scilimits=scilimits,
+ useOffset=useOffset,
+ useLocale=useLocale,
+ useMathText=useMathText,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.tricontour)
+def tricontour(*args, **kwargs):
+ __ret = gca().tricontour(*args, **kwargs)
+ if __ret._A is not None: # type: ignore[attr-defined]
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.tricontourf)
+def tricontourf(*args, **kwargs):
+ __ret = gca().tricontourf(*args, **kwargs)
+ if __ret._A is not None: # type: ignore[attr-defined]
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.tripcolor)
+def tripcolor(
+ *args,
+ alpha=1.0,
+ norm=None,
+ cmap=None,
+ vmin=None,
+ vmax=None,
+ shading="flat",
+ facecolors=None,
+ **kwargs,
+):
+ __ret = gca().tripcolor(
+ *args,
+ alpha=alpha,
+ norm=norm,
+ cmap=cmap,
+ vmin=vmin,
+ vmax=vmax,
+ shading=shading,
+ facecolors=facecolors,
+ **kwargs,
+ )
+ sci(__ret)
+ return __ret
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.triplot)
+def triplot(*args, **kwargs):
+ return gca().triplot(*args, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.violinplot)
+def violinplot(
+ dataset: ArrayLike | Sequence[ArrayLike],
+ positions: ArrayLike | None = None,
+ vert: bool | None = None,
+ orientation: Literal["vertical", "horizontal"] = "vertical",
+ widths: float | ArrayLike = 0.5,
+ showmeans: bool = False,
+ showextrema: bool = True,
+ showmedians: bool = False,
+ quantiles: Sequence[float | Sequence[float]] | None = None,
+ points: int = 100,
+ bw_method: (
+ Literal["scott", "silverman"] | float | Callable[[GaussianKDE], float] | None
+ ) = None,
+ side: Literal["both", "low", "high"] = "both",
+ *,
+ data=None,
+) -> dict[str, Collection]:
+ return gca().violinplot(
+ dataset,
+ positions=positions,
+ vert=vert,
+ orientation=orientation,
+ widths=widths,
+ showmeans=showmeans,
+ showextrema=showextrema,
+ showmedians=showmedians,
+ quantiles=quantiles,
+ points=points,
+ bw_method=bw_method,
+ side=side,
+ **({"data": data} if data is not None else {}),
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.vlines)
+def vlines(
+ x: float | ArrayLike,
+ ymin: float | ArrayLike,
+ ymax: float | ArrayLike,
+ colors: ColorType | Sequence[ColorType] | None = None,
+ linestyles: LineStyleType = "solid",
+ label: str = "",
+ *,
+ data=None,
+ **kwargs,
+) -> LineCollection:
+ return gca().vlines(
+ x,
+ ymin,
+ ymax,
+ colors=colors,
+ linestyles=linestyles,
+ label=label,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.xcorr)
+def xcorr(
+ x: ArrayLike,
+ y: ArrayLike,
+ normed: bool = True,
+ detrend: Callable[[ArrayLike], ArrayLike] = mlab.detrend_none,
+ usevlines: bool = True,
+ maxlags: int = 10,
+ *,
+ data=None,
+ **kwargs,
+) -> tuple[np.ndarray, np.ndarray, LineCollection | Line2D, Line2D | None]:
+ return gca().xcorr(
+ x,
+ y,
+ normed=normed,
+ detrend=detrend,
+ usevlines=usevlines,
+ maxlags=maxlags,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes._sci)
+def sci(im: ColorizingArtist) -> None:
+ gca()._sci(im)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.set_title)
+def title(
+ label: str,
+ fontdict: dict[str, Any] | None = None,
+ loc: Literal["left", "center", "right"] | None = None,
+ pad: float | None = None,
+ *,
+ y: float | None = None,
+ **kwargs,
+) -> Text:
+ return gca().set_title(label, fontdict=fontdict, loc=loc, pad=pad, y=y, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.set_xlabel)
+def xlabel(
+ xlabel: str,
+ fontdict: dict[str, Any] | None = None,
+ labelpad: float | None = None,
+ *,
+ loc: Literal["left", "center", "right"] | None = None,
+ **kwargs,
+) -> Text:
+ return gca().set_xlabel(
+ xlabel, fontdict=fontdict, labelpad=labelpad, loc=loc, **kwargs
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.set_ylabel)
+def ylabel(
+ ylabel: str,
+ fontdict: dict[str, Any] | None = None,
+ labelpad: float | None = None,
+ *,
+ loc: Literal["bottom", "center", "top"] | None = None,
+ **kwargs,
+) -> Text:
+ return gca().set_ylabel(
+ ylabel, fontdict=fontdict, labelpad=labelpad, loc=loc, **kwargs
+ )
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.set_xscale)
+def xscale(value: str | ScaleBase, **kwargs) -> None:
+ gca().set_xscale(value, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+@_copy_docstring_and_deprecators(Axes.set_yscale)
+def yscale(value: str | ScaleBase, **kwargs) -> None:
+ gca().set_yscale(value, **kwargs)
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def autumn() -> None:
+ """
+ Set the colormap to 'autumn'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("autumn")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def bone() -> None:
+ """
+ Set the colormap to 'bone'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("bone")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def cool() -> None:
+ """
+ Set the colormap to 'cool'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("cool")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def copper() -> None:
+ """
+ Set the colormap to 'copper'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("copper")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def flag() -> None:
+ """
+ Set the colormap to 'flag'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("flag")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def gray() -> None:
+ """
+ Set the colormap to 'gray'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("gray")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def hot() -> None:
+ """
+ Set the colormap to 'hot'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("hot")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def hsv() -> None:
+ """
+ Set the colormap to 'hsv'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("hsv")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def jet() -> None:
+ """
+ Set the colormap to 'jet'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("jet")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def pink() -> None:
+ """
+ Set the colormap to 'pink'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("pink")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def prism() -> None:
+ """
+ Set the colormap to 'prism'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("prism")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def spring() -> None:
+ """
+ Set the colormap to 'spring'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("spring")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def summer() -> None:
+ """
+ Set the colormap to 'summer'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("summer")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def winter() -> None:
+ """
+ Set the colormap to 'winter'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("winter")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def magma() -> None:
+ """
+ Set the colormap to 'magma'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("magma")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def inferno() -> None:
+ """
+ Set the colormap to 'inferno'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("inferno")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def plasma() -> None:
+ """
+ Set the colormap to 'plasma'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("plasma")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def viridis() -> None:
+ """
+ Set the colormap to 'viridis'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("viridis")
+
+
+# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
+def nipy_spectral() -> None:
+ """
+ Set the colormap to 'nipy_spectral'.
+
+ This changes the default colormap as well as the colormap of the current
+ image if there is one. See ``help(colormaps)`` for more information.
+ """
+ set_cmap("nipy_spectral")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/quiver.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/quiver.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9963b0e17162516b30206fd6ccb6c595a6a226f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/quiver.py
@@ -0,0 +1,1228 @@
+"""
+Support for plotting vector fields.
+
+Presently this contains Quiver and Barb. Quiver plots an arrow in the
+direction of the vector, with the size of the arrow related to the
+magnitude of the vector.
+
+Barbs are like quiver in that they point along a vector, but
+the magnitude of the vector is given schematically by the presence of barbs
+or flags on the barb.
+
+This will also become a home for things such as standard
+deviation ellipses, which can and will be derived very easily from
+the Quiver code.
+"""
+
+import math
+
+import numpy as np
+from numpy import ma
+
+from matplotlib import _api, cbook, _docstring
+import matplotlib.artist as martist
+import matplotlib.collections as mcollections
+from matplotlib.patches import CirclePolygon
+import matplotlib.text as mtext
+import matplotlib.transforms as transforms
+
+
+_quiver_doc = """
+Plot a 2D field of arrows.
+
+Call signature::
+
+ quiver([X, Y], U, V, [C], /, **kwargs)
+
+*X*, *Y* define the arrow locations, *U*, *V* define the arrow directions, and
+*C* optionally sets the color. The arguments *X*, *Y*, *U*, *V*, *C* are
+positional-only.
+
+**Arrow length**
+
+The default settings auto-scales the length of the arrows to a reasonable size.
+To change this behavior see the *scale* and *scale_units* parameters.
+
+**Arrow shape**
+
+The arrow shape is determined by *width*, *headwidth*, *headlength* and
+*headaxislength*. See the notes below.
+
+**Arrow styling**
+
+Each arrow is internally represented by a filled polygon with a default edge
+linewidth of 0. As a result, an arrow is rather a filled area, not a line with
+a head, and `.PolyCollection` properties like *linewidth*, *edgecolor*,
+*facecolor*, etc. act accordingly.
+
+
+Parameters
+----------
+X, Y : 1D or 2D array-like, optional
+ The x and y coordinates of the arrow locations.
+
+ If not given, they will be generated as a uniform integer meshgrid based
+ on the dimensions of *U* and *V*.
+
+ If *X* and *Y* are 1D but *U*, *V* are 2D, *X*, *Y* are expanded to 2D
+ using ``X, Y = np.meshgrid(X, Y)``. In this case ``len(X)`` and ``len(Y)``
+ must match the column and row dimensions of *U* and *V*.
+
+U, V : 1D or 2D array-like
+ The x and y direction components of the arrow vectors. The interpretation
+ of these components (in data or in screen space) depends on *angles*.
+
+ *U* and *V* must have the same number of elements, matching the number of
+ arrow locations in *X*, *Y*. *U* and *V* may be masked. Locations masked
+ in any of *U*, *V*, and *C* will not be drawn.
+
+C : 1D or 2D array-like, optional
+ Numeric data that defines the arrow colors by colormapping via *norm* and
+ *cmap*.
+
+ This does not support explicit colors. If you want to set colors directly,
+ use *color* instead. The size of *C* must match the number of arrow
+ locations.
+
+angles : {'uv', 'xy'} or array-like, default: 'uv'
+ Method for determining the angle of the arrows.
+
+ - 'uv': Arrow directions are based on
+ :ref:`display coordinates `; i.e. a 45° angle will
+ always show up as diagonal on the screen, irrespective of figure or Axes
+ aspect ratio or Axes data ranges. This is useful when the arrows represent
+ a quantity whose direction is not tied to the x and y data coordinates.
+
+ If *U* == *V* the orientation of the arrow on the plot is 45 degrees
+ counter-clockwise from the horizontal axis (positive to the right).
+
+ - 'xy': Arrow direction in data coordinates, i.e. the arrows point from
+ (x, y) to (x+u, y+v). This is ideal for vector fields or gradient plots
+ where the arrows should directly represent movements or gradients in the
+ x and y directions.
+
+ - Arbitrary angles may be specified explicitly as an array of values
+ in degrees, counter-clockwise from the horizontal axis.
+
+ In this case *U*, *V* is only used to determine the length of the
+ arrows.
+
+ For example, ``angles=[30, 60, 90]`` will orient the arrows at 30, 60, and 90
+ degrees respectively, regardless of the *U* and *V* components.
+
+ Note: inverting a data axis will correspondingly invert the
+ arrows only with ``angles='xy'``.
+
+pivot : {'tail', 'mid', 'middle', 'tip'}, default: 'tail'
+ The part of the arrow that is anchored to the *X*, *Y* grid. The arrow
+ rotates about this point.
+
+ 'mid' is a synonym for 'middle'.
+
+scale : float, optional
+ Scales the length of the arrow inversely.
+
+ Number of data values represented by one unit of arrow length on the plot.
+ For example, if the data represents velocity in meters per second (m/s), the
+ scale parameter determines how many meters per second correspond to one unit of
+ arrow length relative to the width of the plot.
+ Smaller scale parameter makes the arrow longer.
+
+ By default, an autoscaling algorithm is used to scale the arrow length to a
+ reasonable size, which is based on the average vector length and the number of
+ vectors.
+
+ The arrow length unit is given by the *scale_units* parameter.
+
+scale_units : {'width', 'height', 'dots', 'inches', 'x', 'y', 'xy'}, default: 'width'
+
+ The physical image unit, which is used for rendering the scaled arrow data *U*, *V*.
+
+ The rendered arrow length is given by
+
+ length in x direction = $\\frac{u}{\\mathrm{scale}} \\mathrm{scale_unit}$
+
+ length in y direction = $\\frac{v}{\\mathrm{scale}} \\mathrm{scale_unit}$
+
+ For example, ``(u, v) = (0.5, 0)`` with ``scale=10, scale_units="width"`` results
+ in a horizontal arrow with a length of *0.5 / 10 * "width"*, i.e. 0.05 times the
+ Axes width.
+
+ Supported values are:
+
+ - 'width' or 'height': The arrow length is scaled relative to the width or height
+ of the Axes.
+ For example, ``scale_units='width', scale=1.0``, will result in an arrow length
+ of width of the Axes.
+
+ - 'dots': The arrow length of the arrows is in measured in display dots (pixels).
+
+ - 'inches': Arrow lengths are scaled based on the DPI (dots per inch) of the figure.
+ This ensures that the arrows have a consistent physical size on the figure,
+ in inches, regardless of data values or plot scaling.
+ For example, ``(u, v) = (1, 0)`` with ``scale_units='inches', scale=2`` results
+ in a 0.5 inch-long arrow.
+
+ - 'x' or 'y': The arrow length is scaled relative to the x or y axis units.
+ For example, ``(u, v) = (0, 1)`` with ``scale_units='x', scale=1`` results
+ in a vertical arrow with the length of 1 x-axis unit.
+
+ - 'xy': Arrow length will be same as 'x' or 'y' units.
+ This is useful for creating vectors in the x-y plane where u and v have
+ the same units as x and y. To plot vectors in the x-y plane with u and v having
+ the same units as x and y, use ``angles='xy', scale_units='xy', scale=1``.
+
+ Note: Setting *scale_units* without setting scale does not have any effect because
+ the scale units only differ by a constant factor and that is rescaled through
+ autoscaling.
+
+units : {'width', 'height', 'dots', 'inches', 'x', 'y', 'xy'}, default: 'width'
+ Affects the arrow size (except for the length). In particular, the shaft
+ *width* is measured in multiples of this unit.
+
+ Supported values are:
+
+ - 'width', 'height': The width or height of the Axes.
+ - 'dots', 'inches': Pixels or inches based on the figure dpi.
+ - 'x', 'y', 'xy': *X*, *Y* or :math:`\\sqrt{X^2 + Y^2}` in data units.
+
+ The following table summarizes how these values affect the visible arrow
+ size under zooming and figure size changes:
+
+ ================= ================= ==================
+ units zoom figure size change
+ ================= ================= ==================
+ 'x', 'y', 'xy' arrow size scales —
+ 'width', 'height' — arrow size scales
+ 'dots', 'inches' — —
+ ================= ================= ==================
+
+width : float, optional
+ Shaft width in arrow units. All head parameters are relative to *width*.
+
+ The default depends on choice of *units* above, and number of vectors;
+ a typical starting value is about 0.005 times the width of the plot.
+
+headwidth : float, default: 3
+ Head width as multiple of shaft *width*. See the notes below.
+
+headlength : float, default: 5
+ Head length as multiple of shaft *width*. See the notes below.
+
+headaxislength : float, default: 4.5
+ Head length at shaft intersection as multiple of shaft *width*.
+ See the notes below.
+
+minshaft : float, default: 1
+ Length below which arrow scales, in units of head length. Do not
+ set this to less than 1, or small arrows will look terrible!
+
+minlength : float, default: 1
+ Minimum length as a multiple of shaft width; if an arrow length
+ is less than this, plot a dot (hexagon) of this diameter instead.
+
+color : :mpltype:`color` or list :mpltype:`color`, optional
+ Explicit color(s) for the arrows. If *C* has been set, *color* has no
+ effect.
+
+ This is a synonym for the `.PolyCollection` *facecolor* parameter.
+
+Other Parameters
+----------------
+data : indexable object, optional
+ DATA_PARAMETER_PLACEHOLDER
+
+**kwargs : `~matplotlib.collections.PolyCollection` properties, optional
+ All other keyword arguments are passed on to `.PolyCollection`:
+
+ %(PolyCollection:kwdoc)s
+
+Returns
+-------
+`~matplotlib.quiver.Quiver`
+
+See Also
+--------
+.Axes.quiverkey : Add a key to a quiver plot.
+
+Notes
+-----
+
+**Arrow shape**
+
+The arrow is drawn as a polygon using the nodes as shown below. The values
+*headwidth*, *headlength*, and *headaxislength* are in units of *width*.
+
+.. image:: /_static/quiver_sizes.svg
+ :width: 500px
+
+The defaults give a slightly swept-back arrow. Here are some guidelines how to
+get other head shapes:
+
+- To make the head a triangle, make *headaxislength* the same as *headlength*.
+- To make the arrow more pointed, reduce *headwidth* or increase *headlength*
+ and *headaxislength*.
+- To make the head smaller relative to the shaft, scale down all the head
+ parameters proportionally.
+- To remove the head completely, set all *head* parameters to 0.
+- To get a diamond-shaped head, make *headaxislength* larger than *headlength*.
+- Warning: For *headaxislength* < (*headlength* / *headwidth*), the "headaxis"
+ nodes (i.e. the ones connecting the head with the shaft) will protrude out
+ of the head in forward direction so that the arrow head looks broken.
+""" % _docstring.interpd.params
+
+_docstring.interpd.register(quiver_doc=_quiver_doc)
+
+
+class QuiverKey(martist.Artist):
+ """Labelled arrow for use as a quiver plot scale key."""
+ halign = {'N': 'center', 'S': 'center', 'E': 'left', 'W': 'right'}
+ valign = {'N': 'bottom', 'S': 'top', 'E': 'center', 'W': 'center'}
+ pivot = {'N': 'middle', 'S': 'middle', 'E': 'tip', 'W': 'tail'}
+
+ def __init__(self, Q, X, Y, U, label,
+ *, angle=0, coordinates='axes', color=None, labelsep=0.1,
+ labelpos='N', labelcolor=None, fontproperties=None,
+ zorder=None, **kwargs):
+ """
+ Add a key to a quiver plot.
+
+ The positioning of the key depends on *X*, *Y*, *coordinates*, and
+ *labelpos*. If *labelpos* is 'N' or 'S', *X*, *Y* give the position of
+ the middle of the key arrow. If *labelpos* is 'E', *X*, *Y* positions
+ the head, and if *labelpos* is 'W', *X*, *Y* positions the tail; in
+ either of these two cases, *X*, *Y* is somewhere in the middle of the
+ arrow+label key object.
+
+ Parameters
+ ----------
+ Q : `~matplotlib.quiver.Quiver`
+ A `.Quiver` object as returned by a call to `~.Axes.quiver()`.
+ X, Y : float
+ The location of the key.
+ U : float
+ The length of the key.
+ label : str
+ The key label (e.g., length and units of the key).
+ angle : float, default: 0
+ The angle of the key arrow, in degrees anti-clockwise from the
+ horizontal axis.
+ coordinates : {'axes', 'figure', 'data', 'inches'}, default: 'axes'
+ Coordinate system and units for *X*, *Y*: 'axes' and 'figure' are
+ normalized coordinate systems with (0, 0) in the lower left and
+ (1, 1) in the upper right; 'data' are the axes data coordinates
+ (used for the locations of the vectors in the quiver plot itself);
+ 'inches' is position in the figure in inches, with (0, 0) at the
+ lower left corner.
+ color : :mpltype:`color`
+ Overrides face and edge colors from *Q*.
+ labelpos : {'N', 'S', 'E', 'W'}
+ Position the label above, below, to the right, to the left of the
+ arrow, respectively.
+ labelsep : float, default: 0.1
+ Distance in inches between the arrow and the label.
+ labelcolor : :mpltype:`color`, default: :rc:`text.color`
+ Label color.
+ fontproperties : dict, optional
+ A dictionary with keyword arguments accepted by the
+ `~matplotlib.font_manager.FontProperties` initializer:
+ *family*, *style*, *variant*, *size*, *weight*.
+ zorder : float
+ The zorder of the key. The default is 0.1 above *Q*.
+ **kwargs
+ Any additional keyword arguments are used to override vector
+ properties taken from *Q*.
+ """
+ super().__init__()
+ self.Q = Q
+ self.X = X
+ self.Y = Y
+ self.U = U
+ self.angle = angle
+ self.coord = coordinates
+ self.color = color
+ self.label = label
+ self._labelsep_inches = labelsep
+
+ self.labelpos = labelpos
+ self.labelcolor = labelcolor
+ self.fontproperties = fontproperties or dict()
+ self.kw = kwargs
+ self.text = mtext.Text(
+ text=label,
+ horizontalalignment=self.halign[self.labelpos],
+ verticalalignment=self.valign[self.labelpos],
+ fontproperties=self.fontproperties)
+ if self.labelcolor is not None:
+ self.text.set_color(self.labelcolor)
+ self._dpi_at_last_init = None
+ self.zorder = zorder if zorder is not None else Q.zorder + 0.1
+
+ @property
+ def labelsep(self):
+ return self._labelsep_inches * self.Q.axes.get_figure(root=True).dpi
+
+ def _init(self):
+ if True: # self._dpi_at_last_init != self.axes.get_figure().dpi
+ if self.Q._dpi_at_last_init != self.Q.axes.get_figure(root=True).dpi:
+ self.Q._init()
+ self._set_transform()
+ with cbook._setattr_cm(self.Q, pivot=self.pivot[self.labelpos],
+ # Hack: save and restore the Umask
+ Umask=ma.nomask):
+ u = self.U * np.cos(np.radians(self.angle))
+ v = self.U * np.sin(np.radians(self.angle))
+ self.verts = self.Q._make_verts([[0., 0.]],
+ np.array([u]), np.array([v]), 'uv')
+ kwargs = self.Q.polykw
+ kwargs.update(self.kw)
+ self.vector = mcollections.PolyCollection(
+ self.verts,
+ offsets=[(self.X, self.Y)],
+ offset_transform=self.get_transform(),
+ **kwargs)
+ if self.color is not None:
+ self.vector.set_color(self.color)
+ self.vector.set_transform(self.Q.get_transform())
+ self.vector.set_figure(self.get_figure())
+ self._dpi_at_last_init = self.Q.axes.get_figure(root=True).dpi
+
+ def _text_shift(self):
+ return {
+ "N": (0, +self.labelsep),
+ "S": (0, -self.labelsep),
+ "E": (+self.labelsep, 0),
+ "W": (-self.labelsep, 0),
+ }[self.labelpos]
+
+ @martist.allow_rasterization
+ def draw(self, renderer):
+ self._init()
+ self.vector.draw(renderer)
+ pos = self.get_transform().transform((self.X, self.Y))
+ self.text.set_position(pos + self._text_shift())
+ self.text.draw(renderer)
+ self.stale = False
+
+ def _set_transform(self):
+ fig = self.Q.axes.get_figure(root=False)
+ self.set_transform(_api.check_getitem({
+ "data": self.Q.axes.transData,
+ "axes": self.Q.axes.transAxes,
+ "figure": fig.transFigure,
+ "inches": fig.dpi_scale_trans,
+ }, coordinates=self.coord))
+
+ def set_figure(self, fig):
+ super().set_figure(fig)
+ self.text.set_figure(fig)
+
+ def contains(self, mouseevent):
+ if self._different_canvas(mouseevent):
+ return False, {}
+ # Maybe the dictionary should allow one to
+ # distinguish between a text hit and a vector hit.
+ if (self.text.contains(mouseevent)[0] or
+ self.vector.contains(mouseevent)[0]):
+ return True, {}
+ return False, {}
+
+
+def _parse_args(*args, caller_name='function'):
+ """
+ Helper function to parse positional parameters for colored vector plots.
+
+ This is currently used for Quiver and Barbs.
+
+ Parameters
+ ----------
+ *args : list
+ list of 2-5 arguments. Depending on their number they are parsed to::
+
+ U, V
+ U, V, C
+ X, Y, U, V
+ X, Y, U, V, C
+
+ caller_name : str
+ Name of the calling method (used in error messages).
+ """
+ X = Y = C = None
+
+ nargs = len(args)
+ if nargs == 2:
+ # The use of atleast_1d allows for handling scalar arguments while also
+ # keeping masked arrays
+ U, V = np.atleast_1d(*args)
+ elif nargs == 3:
+ U, V, C = np.atleast_1d(*args)
+ elif nargs == 4:
+ X, Y, U, V = np.atleast_1d(*args)
+ elif nargs == 5:
+ X, Y, U, V, C = np.atleast_1d(*args)
+ else:
+ raise _api.nargs_error(caller_name, takes="from 2 to 5", given=nargs)
+
+ nr, nc = (1, U.shape[0]) if U.ndim == 1 else U.shape
+
+ if X is not None:
+ X = X.ravel()
+ Y = Y.ravel()
+ if len(X) == nc and len(Y) == nr:
+ X, Y = (a.ravel() for a in np.meshgrid(X, Y))
+ elif len(X) != len(Y):
+ raise ValueError('X and Y must be the same size, but '
+ f'X.size is {X.size} and Y.size is {Y.size}.')
+ else:
+ indexgrid = np.meshgrid(np.arange(nc), np.arange(nr))
+ X, Y = (np.ravel(a) for a in indexgrid)
+ # Size validation for U, V, C is left to the set_UVC method.
+ return X, Y, U, V, C
+
+
+def _check_consistent_shapes(*arrays):
+ all_shapes = {a.shape for a in arrays}
+ if len(all_shapes) != 1:
+ raise ValueError('The shapes of the passed in arrays do not match')
+
+
+class Quiver(mcollections.PolyCollection):
+ """
+ Specialized PolyCollection for arrows.
+
+ The only API method is set_UVC(), which can be used
+ to change the size, orientation, and color of the
+ arrows; their locations are fixed when the class is
+ instantiated. Possibly this method will be useful
+ in animations.
+
+ Much of the work in this class is done in the draw()
+ method so that as much information as possible is available
+ about the plot. In subsequent draw() calls, recalculation
+ is limited to things that might have changed, so there
+ should be no performance penalty from putting the calculations
+ in the draw() method.
+ """
+
+ _PIVOT_VALS = ('tail', 'middle', 'tip')
+
+ @_docstring.Substitution(_quiver_doc)
+ def __init__(self, ax, *args,
+ scale=None, headwidth=3, headlength=5, headaxislength=4.5,
+ minshaft=1, minlength=1, units='width', scale_units=None,
+ angles='uv', width=None, color='k', pivot='tail', **kwargs):
+ """
+ The constructor takes one required argument, an Axes
+ instance, followed by the args and kwargs described
+ by the following pyplot interface documentation:
+ %s
+ """
+ self._axes = ax # The attr actually set by the Artist.axes property.
+ X, Y, U, V, C = _parse_args(*args, caller_name='quiver')
+ self.X = X
+ self.Y = Y
+ self.XY = np.column_stack((X, Y))
+ self.N = len(X)
+ self.scale = scale
+ self.headwidth = headwidth
+ self.headlength = float(headlength)
+ self.headaxislength = headaxislength
+ self.minshaft = minshaft
+ self.minlength = minlength
+ self.units = units
+ self.scale_units = scale_units
+ self.angles = angles
+ self.width = width
+
+ if pivot.lower() == 'mid':
+ pivot = 'middle'
+ self.pivot = pivot.lower()
+ _api.check_in_list(self._PIVOT_VALS, pivot=self.pivot)
+
+ self.transform = kwargs.pop('transform', ax.transData)
+ kwargs.setdefault('facecolors', color)
+ kwargs.setdefault('linewidths', (0,))
+ super().__init__([], offsets=self.XY, offset_transform=self.transform,
+ closed=False, **kwargs)
+ self.polykw = kwargs
+ self.set_UVC(U, V, C)
+ self._dpi_at_last_init = None
+
+ def _init(self):
+ """
+ Initialization delayed until first draw;
+ allow time for axes setup.
+ """
+ # It seems that there are not enough event notifications
+ # available to have this work on an as-needed basis at present.
+ if True: # self._dpi_at_last_init != self.axes.figure.dpi
+ trans = self._set_transform()
+ self.span = trans.inverted().transform_bbox(self.axes.bbox).width
+ if self.width is None:
+ sn = np.clip(math.sqrt(self.N), 8, 25)
+ self.width = 0.06 * self.span / sn
+
+ # _make_verts sets self.scale if not already specified
+ if (self._dpi_at_last_init != self.axes.get_figure(root=True).dpi
+ and self.scale is None):
+ self._make_verts(self.XY, self.U, self.V, self.angles)
+
+ self._dpi_at_last_init = self.axes.get_figure(root=True).dpi
+
+ def get_datalim(self, transData):
+ trans = self.get_transform()
+ offset_trf = self.get_offset_transform()
+ full_transform = (trans - transData) + (offset_trf - transData)
+ XY = full_transform.transform(self.XY)
+ bbox = transforms.Bbox.null()
+ bbox.update_from_data_xy(XY, ignore=True)
+ return bbox
+
+ @martist.allow_rasterization
+ def draw(self, renderer):
+ self._init()
+ verts = self._make_verts(self.XY, self.U, self.V, self.angles)
+ self.set_verts(verts, closed=False)
+ super().draw(renderer)
+ self.stale = False
+
+ def set_UVC(self, U, V, C=None):
+ # We need to ensure we have a copy, not a reference
+ # to an array that might change before draw().
+ U = ma.masked_invalid(U, copy=True).ravel()
+ V = ma.masked_invalid(V, copy=True).ravel()
+ if C is not None:
+ C = ma.masked_invalid(C, copy=True).ravel()
+ for name, var in zip(('U', 'V', 'C'), (U, V, C)):
+ if not (var is None or var.size == self.N or var.size == 1):
+ raise ValueError(f'Argument {name} has a size {var.size}'
+ f' which does not match {self.N},'
+ ' the number of arrow positions')
+
+ mask = ma.mask_or(U.mask, V.mask, copy=False, shrink=True)
+ if C is not None:
+ mask = ma.mask_or(mask, C.mask, copy=False, shrink=True)
+ if mask is ma.nomask:
+ C = C.filled()
+ else:
+ C = ma.array(C, mask=mask, copy=False)
+ self.U = U.filled(1)
+ self.V = V.filled(1)
+ self.Umask = mask
+ if C is not None:
+ self.set_array(C)
+ self.stale = True
+
+ def _dots_per_unit(self, units):
+ """Return a scale factor for converting from units to pixels."""
+ bb = self.axes.bbox
+ vl = self.axes.viewLim
+ return _api.check_getitem({
+ 'x': bb.width / vl.width,
+ 'y': bb.height / vl.height,
+ 'xy': np.hypot(*bb.size) / np.hypot(*vl.size),
+ 'width': bb.width,
+ 'height': bb.height,
+ 'dots': 1.,
+ 'inches': self.axes.get_figure(root=True).dpi,
+ }, units=units)
+
+ def _set_transform(self):
+ """
+ Set the PolyCollection transform to go
+ from arrow width units to pixels.
+ """
+ dx = self._dots_per_unit(self.units)
+ self._trans_scale = dx # pixels per arrow width unit
+ trans = transforms.Affine2D().scale(dx)
+ self.set_transform(trans)
+ return trans
+
+ # Calculate angles and lengths for segment between (x, y), (x+u, y+v)
+ def _angles_lengths(self, XY, U, V, eps=1):
+ xy = self.axes.transData.transform(XY)
+ uv = np.column_stack((U, V))
+ xyp = self.axes.transData.transform(XY + eps * uv)
+ dxy = xyp - xy
+ angles = np.arctan2(dxy[:, 1], dxy[:, 0])
+ lengths = np.hypot(*dxy.T) / eps
+ return angles, lengths
+
+ # XY is stacked [X, Y].
+ # See quiver() doc for meaning of X, Y, U, V, angles.
+ def _make_verts(self, XY, U, V, angles):
+ uv = (U + V * 1j)
+ str_angles = angles if isinstance(angles, str) else ''
+ if str_angles == 'xy' and self.scale_units == 'xy':
+ # Here eps is 1 so that if we get U, V by diffing
+ # the X, Y arrays, the vectors will connect the
+ # points, regardless of the axis scaling (including log).
+ angles, lengths = self._angles_lengths(XY, U, V, eps=1)
+ elif str_angles == 'xy' or self.scale_units == 'xy':
+ # Calculate eps based on the extents of the plot
+ # so that we don't end up with roundoff error from
+ # adding a small number to a large.
+ eps = np.abs(self.axes.dataLim.extents).max() * 0.001
+ angles, lengths = self._angles_lengths(XY, U, V, eps=eps)
+
+ if str_angles and self.scale_units == 'xy':
+ a = lengths
+ else:
+ a = np.abs(uv)
+
+ if self.scale is None:
+ sn = max(10, math.sqrt(self.N))
+ if self.Umask is not ma.nomask:
+ amean = a[~self.Umask].mean()
+ else:
+ amean = a.mean()
+ # crude auto-scaling
+ # scale is typical arrow length as a multiple of the arrow width
+ scale = 1.8 * amean * sn / self.span
+
+ if self.scale_units is None:
+ if self.scale is None:
+ self.scale = scale
+ widthu_per_lenu = 1.0
+ else:
+ if self.scale_units == 'xy':
+ dx = 1
+ else:
+ dx = self._dots_per_unit(self.scale_units)
+ widthu_per_lenu = dx / self._trans_scale
+ if self.scale is None:
+ self.scale = scale * widthu_per_lenu
+ length = a * (widthu_per_lenu / (self.scale * self.width))
+ X, Y = self._h_arrows(length)
+ if str_angles == 'xy':
+ theta = angles
+ elif str_angles == 'uv':
+ theta = np.angle(uv)
+ else:
+ theta = ma.masked_invalid(np.deg2rad(angles)).filled(0)
+ theta = theta.reshape((-1, 1)) # for broadcasting
+ xy = (X + Y * 1j) * np.exp(1j * theta) * self.width
+ XY = np.stack((xy.real, xy.imag), axis=2)
+ if self.Umask is not ma.nomask:
+ XY = ma.array(XY)
+ XY[self.Umask] = ma.masked
+ # This might be handled more efficiently with nans, given
+ # that nans will end up in the paths anyway.
+
+ return XY
+
+ def _h_arrows(self, length):
+ """Length is in arrow width units."""
+ # It might be possible to streamline the code
+ # and speed it up a bit by using complex (x, y)
+ # instead of separate arrays; but any gain would be slight.
+ minsh = self.minshaft * self.headlength
+ N = len(length)
+ length = length.reshape(N, 1)
+ # This number is chosen based on when pixel values overflow in Agg
+ # causing rendering errors
+ # length = np.minimum(length, 2 ** 16)
+ np.clip(length, 0, 2 ** 16, out=length)
+ # x, y: normal horizontal arrow
+ x = np.array([0, -self.headaxislength,
+ -self.headlength, 0],
+ np.float64)
+ x = x + np.array([0, 1, 1, 1]) * length
+ y = 0.5 * np.array([1, 1, self.headwidth, 0], np.float64)
+ y = np.repeat(y[np.newaxis, :], N, axis=0)
+ # x0, y0: arrow without shaft, for short vectors
+ x0 = np.array([0, minsh - self.headaxislength,
+ minsh - self.headlength, minsh], np.float64)
+ y0 = 0.5 * np.array([1, 1, self.headwidth, 0], np.float64)
+ ii = [0, 1, 2, 3, 2, 1, 0, 0]
+ X = x[:, ii]
+ Y = y[:, ii]
+ Y[:, 3:-1] *= -1
+ X0 = x0[ii]
+ Y0 = y0[ii]
+ Y0[3:-1] *= -1
+ shrink = length / minsh if minsh != 0. else 0.
+ X0 = shrink * X0[np.newaxis, :]
+ Y0 = shrink * Y0[np.newaxis, :]
+ short = np.repeat(length < minsh, 8, axis=1)
+ # Now select X0, Y0 if short, otherwise X, Y
+ np.copyto(X, X0, where=short)
+ np.copyto(Y, Y0, where=short)
+ if self.pivot == 'middle':
+ X -= 0.5 * X[:, 3, np.newaxis]
+ elif self.pivot == 'tip':
+ # numpy bug? using -= does not work here unless we multiply by a
+ # float first, as with 'mid'.
+ X = X - X[:, 3, np.newaxis]
+ elif self.pivot != 'tail':
+ _api.check_in_list(["middle", "tip", "tail"], pivot=self.pivot)
+
+ tooshort = length < self.minlength
+ if tooshort.any():
+ # Use a heptagonal dot:
+ th = np.arange(0, 8, 1, np.float64) * (np.pi / 3.0)
+ x1 = np.cos(th) * self.minlength * 0.5
+ y1 = np.sin(th) * self.minlength * 0.5
+ X1 = np.repeat(x1[np.newaxis, :], N, axis=0)
+ Y1 = np.repeat(y1[np.newaxis, :], N, axis=0)
+ tooshort = np.repeat(tooshort, 8, 1)
+ np.copyto(X, X1, where=tooshort)
+ np.copyto(Y, Y1, where=tooshort)
+ # Mask handling is deferred to the caller, _make_verts.
+ return X, Y
+
+
+_barbs_doc = r"""
+Plot a 2D field of wind barbs.
+
+Call signature::
+
+ barbs([X, Y], U, V, [C], /, **kwargs)
+
+Where *X*, *Y* define the barb locations, *U*, *V* define the barb
+directions, and *C* optionally sets the color.
+
+The arguments *X*, *Y*, *U*, *V*, *C* are positional-only and may be
+1D or 2D. *U*, *V*, *C* may be masked arrays, but masked *X*, *Y*
+are not supported at present.
+
+Barbs are traditionally used in meteorology as a way to plot the speed
+and direction of wind observations, but can technically be used to
+plot any two dimensional vector quantity. As opposed to arrows, which
+give vector magnitude by the length of the arrow, the barbs give more
+quantitative information about the vector magnitude by putting slanted
+lines or a triangle for various increments in magnitude, as show
+schematically below::
+
+ : /\ \
+ : / \ \
+ : / \ \ \
+ : / \ \ \
+ : ------------------------------
+
+The largest increment is given by a triangle (or "flag"). After those
+come full lines (barbs). The smallest increment is a half line. There
+is only, of course, ever at most 1 half line. If the magnitude is
+small and only needs a single half-line and no full lines or
+triangles, the half-line is offset from the end of the barb so that it
+can be easily distinguished from barbs with a single full line. The
+magnitude for the barb shown above would nominally be 65, using the
+standard increments of 50, 10, and 5.
+
+See also https://en.wikipedia.org/wiki/Wind_barb.
+
+Parameters
+----------
+X, Y : 1D or 2D array-like, optional
+ The x and y coordinates of the barb locations. See *pivot* for how the
+ barbs are drawn to the x, y positions.
+
+ If not given, they will be generated as a uniform integer meshgrid based
+ on the dimensions of *U* and *V*.
+
+ If *X* and *Y* are 1D but *U*, *V* are 2D, *X*, *Y* are expanded to 2D
+ using ``X, Y = np.meshgrid(X, Y)``. In this case ``len(X)`` and ``len(Y)``
+ must match the column and row dimensions of *U* and *V*.
+
+U, V : 1D or 2D array-like
+ The x and y components of the barb shaft.
+
+C : 1D or 2D array-like, optional
+ Numeric data that defines the barb colors by colormapping via *norm* and
+ *cmap*.
+
+ This does not support explicit colors. If you want to set colors directly,
+ use *barbcolor* instead.
+
+length : float, default: 7
+ Length of the barb in points; the other parts of the barb
+ are scaled against this.
+
+pivot : {'tip', 'middle'} or float, default: 'tip'
+ The part of the arrow that is anchored to the *X*, *Y* grid. The barb
+ rotates about this point. This can also be a number, which shifts the
+ start of the barb that many points away from grid point.
+
+barbcolor : :mpltype:`color` or color sequence
+ The color of all parts of the barb except for the flags. This parameter
+ is analogous to the *edgecolor* parameter for polygons, which can be used
+ instead. However this parameter will override facecolor.
+
+flagcolor : :mpltype:`color` or color sequence
+ The color of any flags on the barb. This parameter is analogous to the
+ *facecolor* parameter for polygons, which can be used instead. However,
+ this parameter will override facecolor. If this is not set (and *C* has
+ not either) then *flagcolor* will be set to match *barbcolor* so that the
+ barb has a uniform color. If *C* has been set, *flagcolor* has no effect.
+
+sizes : dict, optional
+ A dictionary of coefficients specifying the ratio of a given
+ feature to the length of the barb. Only those values one wishes to
+ override need to be included. These features include:
+
+ - 'spacing' - space between features (flags, full/half barbs)
+ - 'height' - height (distance from shaft to top) of a flag or full barb
+ - 'width' - width of a flag, twice the width of a full barb
+ - 'emptybarb' - radius of the circle used for low magnitudes
+
+fill_empty : bool, default: False
+ Whether the empty barbs (circles) that are drawn should be filled with
+ the flag color. If they are not filled, the center is transparent.
+
+rounding : bool, default: True
+ Whether the vector magnitude should be rounded when allocating barb
+ components. If True, the magnitude is rounded to the nearest multiple
+ of the half-barb increment. If False, the magnitude is simply truncated
+ to the next lowest multiple.
+
+barb_increments : dict, optional
+ A dictionary of increments specifying values to associate with
+ different parts of the barb. Only those values one wishes to
+ override need to be included.
+
+ - 'half' - half barbs (Default is 5)
+ - 'full' - full barbs (Default is 10)
+ - 'flag' - flags (default is 50)
+
+flip_barb : bool or array-like of bool, default: False
+ Whether the lines and flags should point opposite to normal.
+ Normal behavior is for the barbs and lines to point right (comes from wind
+ barbs having these features point towards low pressure in the Northern
+ Hemisphere).
+
+ A single value is applied to all barbs. Individual barbs can be flipped by
+ passing a bool array of the same size as *U* and *V*.
+
+Returns
+-------
+barbs : `~matplotlib.quiver.Barbs`
+
+Other Parameters
+----------------
+data : indexable object, optional
+ DATA_PARAMETER_PLACEHOLDER
+
+**kwargs
+ The barbs can further be customized using `.PolyCollection` keyword
+ arguments:
+
+ %(PolyCollection:kwdoc)s
+""" % _docstring.interpd.params
+
+_docstring.interpd.register(barbs_doc=_barbs_doc)
+
+
+class Barbs(mcollections.PolyCollection):
+ """
+ Specialized PolyCollection for barbs.
+
+ The only API method is :meth:`set_UVC`, which can be used to
+ change the size, orientation, and color of the arrows. Locations
+ are changed using the :meth:`set_offsets` collection method.
+ Possibly this method will be useful in animations.
+
+ There is one internal function :meth:`_find_tails` which finds
+ exactly what should be put on the barb given the vector magnitude.
+ From there :meth:`_make_barbs` is used to find the vertices of the
+ polygon to represent the barb based on this information.
+ """
+
+ # This may be an abuse of polygons here to render what is essentially maybe
+ # 1 triangle and a series of lines. It works fine as far as I can tell
+ # however.
+
+ @_docstring.interpd
+ def __init__(self, ax, *args,
+ pivot='tip', length=7, barbcolor=None, flagcolor=None,
+ sizes=None, fill_empty=False, barb_increments=None,
+ rounding=True, flip_barb=False, **kwargs):
+ """
+ The constructor takes one required argument, an Axes
+ instance, followed by the args and kwargs described
+ by the following pyplot interface documentation:
+ %(barbs_doc)s
+ """
+ self.sizes = sizes or dict()
+ self.fill_empty = fill_empty
+ self.barb_increments = barb_increments or dict()
+ self.rounding = rounding
+ self.flip = np.atleast_1d(flip_barb)
+ transform = kwargs.pop('transform', ax.transData)
+ self._pivot = pivot
+ self._length = length
+
+ # Flagcolor and barbcolor provide convenience parameters for
+ # setting the facecolor and edgecolor, respectively, of the barb
+ # polygon. We also work here to make the flag the same color as the
+ # rest of the barb by default
+
+ if None in (barbcolor, flagcolor):
+ kwargs['edgecolors'] = 'face'
+ if flagcolor:
+ kwargs['facecolors'] = flagcolor
+ elif barbcolor:
+ kwargs['facecolors'] = barbcolor
+ else:
+ # Set to facecolor passed in or default to black
+ kwargs.setdefault('facecolors', 'k')
+ else:
+ kwargs['edgecolors'] = barbcolor
+ kwargs['facecolors'] = flagcolor
+
+ # Explicitly set a line width if we're not given one, otherwise
+ # polygons are not outlined and we get no barbs
+ if 'linewidth' not in kwargs and 'lw' not in kwargs:
+ kwargs['linewidth'] = 1
+
+ # Parse out the data arrays from the various configurations supported
+ x, y, u, v, c = _parse_args(*args, caller_name='barbs')
+ self.x = x
+ self.y = y
+ xy = np.column_stack((x, y))
+
+ # Make a collection
+ barb_size = self._length ** 2 / 4 # Empirically determined
+ super().__init__(
+ [], (barb_size,), offsets=xy, offset_transform=transform, **kwargs)
+ self.set_transform(transforms.IdentityTransform())
+
+ self.set_UVC(u, v, c)
+
+ def _find_tails(self, mag, rounding=True, half=5, full=10, flag=50):
+ """
+ Find how many of each of the tail pieces is necessary.
+
+ Parameters
+ ----------
+ mag : `~numpy.ndarray`
+ Vector magnitudes; must be non-negative (and an actual ndarray).
+ rounding : bool, default: True
+ Whether to round or to truncate to the nearest half-barb.
+ half, full, flag : float, defaults: 5, 10, 50
+ Increments for a half-barb, a barb, and a flag.
+
+ Returns
+ -------
+ n_flags, n_barbs : int array
+ For each entry in *mag*, the number of flags and barbs.
+ half_flag : bool array
+ For each entry in *mag*, whether a half-barb is needed.
+ empty_flag : bool array
+ For each entry in *mag*, whether nothing is drawn.
+ """
+ # If rounding, round to the nearest multiple of half, the smallest
+ # increment
+ if rounding:
+ mag = half * np.around(mag / half)
+ n_flags, mag = divmod(mag, flag)
+ n_barb, mag = divmod(mag, full)
+ half_flag = mag >= half
+ empty_flag = ~(half_flag | (n_flags > 0) | (n_barb > 0))
+ return n_flags.astype(int), n_barb.astype(int), half_flag, empty_flag
+
+ def _make_barbs(self, u, v, nflags, nbarbs, half_barb, empty_flag, length,
+ pivot, sizes, fill_empty, flip):
+ """
+ Create the wind barbs.
+
+ Parameters
+ ----------
+ u, v
+ Components of the vector in the x and y directions, respectively.
+
+ nflags, nbarbs, half_barb, empty_flag
+ Respectively, the number of flags, number of barbs, flag for
+ half a barb, and flag for empty barb, ostensibly obtained from
+ :meth:`_find_tails`.
+
+ length
+ The length of the barb staff in points.
+
+ pivot : {"tip", "middle"} or number
+ The point on the barb around which the entire barb should be
+ rotated. If a number, the start of the barb is shifted by that
+ many points from the origin.
+
+ sizes : dict
+ Coefficients specifying the ratio of a given feature to the length
+ of the barb. These features include:
+
+ - *spacing*: space between features (flags, full/half barbs).
+ - *height*: distance from shaft of top of a flag or full barb.
+ - *width*: width of a flag, twice the width of a full barb.
+ - *emptybarb*: radius of the circle used for low magnitudes.
+
+ fill_empty : bool
+ Whether the circle representing an empty barb should be filled or
+ not (this changes the drawing of the polygon).
+
+ flip : list of bool
+ Whether the features should be flipped to the other side of the
+ barb (useful for winds in the southern hemisphere).
+
+ Returns
+ -------
+ list of arrays of vertices
+ Polygon vertices for each of the wind barbs. These polygons have
+ been rotated to properly align with the vector direction.
+ """
+
+ # These control the spacing and size of barb elements relative to the
+ # length of the shaft
+ spacing = length * sizes.get('spacing', 0.125)
+ full_height = length * sizes.get('height', 0.4)
+ full_width = length * sizes.get('width', 0.25)
+ empty_rad = length * sizes.get('emptybarb', 0.15)
+
+ # Controls y point where to pivot the barb.
+ pivot_points = dict(tip=0.0, middle=-length / 2.)
+
+ endx = 0.0
+ try:
+ endy = float(pivot)
+ except ValueError:
+ endy = pivot_points[pivot.lower()]
+
+ # Get the appropriate angle for the vector components. The offset is
+ # due to the way the barb is initially drawn, going down the y-axis.
+ # This makes sense in a meteorological mode of thinking since there 0
+ # degrees corresponds to north (the y-axis traditionally)
+ angles = -(ma.arctan2(v, u) + np.pi / 2)
+
+ # Used for low magnitude. We just get the vertices, so if we make it
+ # out here, it can be reused. The center set here should put the
+ # center of the circle at the location(offset), rather than at the
+ # same point as the barb pivot; this seems more sensible.
+ circ = CirclePolygon((0, 0), radius=empty_rad).get_verts()
+ if fill_empty:
+ empty_barb = circ
+ else:
+ # If we don't want the empty one filled, we make a degenerate
+ # polygon that wraps back over itself
+ empty_barb = np.concatenate((circ, circ[::-1]))
+
+ barb_list = []
+ for index, angle in np.ndenumerate(angles):
+ # If the vector magnitude is too weak to draw anything, plot an
+ # empty circle instead
+ if empty_flag[index]:
+ # We can skip the transform since the circle has no preferred
+ # orientation
+ barb_list.append(empty_barb)
+ continue
+
+ poly_verts = [(endx, endy)]
+ offset = length
+
+ # Handle if this barb should be flipped
+ barb_height = -full_height if flip[index] else full_height
+
+ # Add vertices for each flag
+ for i in range(nflags[index]):
+ # The spacing that works for the barbs is a little to much for
+ # the flags, but this only occurs when we have more than 1
+ # flag.
+ if offset != length:
+ offset += spacing / 2.
+ poly_verts.extend(
+ [[endx, endy + offset],
+ [endx + barb_height, endy - full_width / 2 + offset],
+ [endx, endy - full_width + offset]])
+
+ offset -= full_width + spacing
+
+ # Add vertices for each barb. These really are lines, but works
+ # great adding 3 vertices that basically pull the polygon out and
+ # back down the line
+ for i in range(nbarbs[index]):
+ poly_verts.extend(
+ [(endx, endy + offset),
+ (endx + barb_height, endy + offset + full_width / 2),
+ (endx, endy + offset)])
+
+ offset -= spacing
+
+ # Add the vertices for half a barb, if needed
+ if half_barb[index]:
+ # If the half barb is the first on the staff, traditionally it
+ # is offset from the end to make it easy to distinguish from a
+ # barb with a full one
+ if offset == length:
+ poly_verts.append((endx, endy + offset))
+ offset -= 1.5 * spacing
+ poly_verts.extend(
+ [(endx, endy + offset),
+ (endx + barb_height / 2, endy + offset + full_width / 4),
+ (endx, endy + offset)])
+
+ # Rotate the barb according the angle. Making the barb first and
+ # then rotating it made the math for drawing the barb really easy.
+ # Also, the transform framework makes doing the rotation simple.
+ poly_verts = transforms.Affine2D().rotate(-angle).transform(
+ poly_verts)
+ barb_list.append(poly_verts)
+
+ return barb_list
+
+ def set_UVC(self, U, V, C=None):
+ # We need to ensure we have a copy, not a reference to an array that
+ # might change before draw().
+ self.u = ma.masked_invalid(U, copy=True).ravel()
+ self.v = ma.masked_invalid(V, copy=True).ravel()
+
+ # Flip needs to have the same number of entries as everything else.
+ # Use broadcast_to to avoid a bloated array of identical values.
+ # (can't rely on actual broadcasting)
+ if len(self.flip) == 1:
+ flip = np.broadcast_to(self.flip, self.u.shape)
+ else:
+ flip = self.flip
+
+ if C is not None:
+ c = ma.masked_invalid(C, copy=True).ravel()
+ x, y, u, v, c, flip = cbook.delete_masked_points(
+ self.x.ravel(), self.y.ravel(), self.u, self.v, c,
+ flip.ravel())
+ _check_consistent_shapes(x, y, u, v, c, flip)
+ else:
+ x, y, u, v, flip = cbook.delete_masked_points(
+ self.x.ravel(), self.y.ravel(), self.u, self.v, flip.ravel())
+ _check_consistent_shapes(x, y, u, v, flip)
+
+ magnitude = np.hypot(u, v)
+ flags, barbs, halves, empty = self._find_tails(
+ magnitude, self.rounding, **self.barb_increments)
+
+ # Get the vertices for each of the barbs
+
+ plot_barbs = self._make_barbs(u, v, flags, barbs, halves, empty,
+ self._length, self._pivot, self.sizes,
+ self.fill_empty, flip)
+ self.set_verts(plot_barbs)
+
+ # Set the color array
+ if C is not None:
+ self.set_array(c)
+
+ # Update the offsets in case the masked data changed
+ xy = np.column_stack((x, y))
+ self._offsets = xy
+ self.stale = True
+
+ def set_offsets(self, xy):
+ """
+ Set the offsets for the barb polygons. This saves the offsets passed
+ in and masks them as appropriate for the existing U/V data.
+
+ Parameters
+ ----------
+ xy : sequence of pairs of floats
+ """
+ self.x = xy[:, 0]
+ self.y = xy[:, 1]
+ x, y, u, v = cbook.delete_masked_points(
+ self.x.ravel(), self.y.ravel(), self.u, self.v)
+ _check_consistent_shapes(x, y, u, v)
+ xy = np.column_stack((x, y))
+ super().set_offsets(xy)
+ self.stale = True
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/quiver.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/quiver.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..8a14083c434863d229b4b14287df06c8c85b102d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/quiver.pyi
@@ -0,0 +1,184 @@
+import matplotlib.artist as martist
+import matplotlib.collections as mcollections
+from matplotlib.axes import Axes
+from matplotlib.figure import Figure, SubFigure
+from matplotlib.text import Text
+from matplotlib.transforms import Transform, Bbox
+
+
+import numpy as np
+from numpy.typing import ArrayLike
+from collections.abc import Sequence
+from typing import Any, Literal, overload
+from matplotlib.typing import ColorType
+
+class QuiverKey(martist.Artist):
+ halign: dict[Literal["N", "S", "E", "W"], Literal["left", "center", "right"]]
+ valign: dict[Literal["N", "S", "E", "W"], Literal["top", "center", "bottom"]]
+ pivot: dict[Literal["N", "S", "E", "W"], Literal["middle", "tip", "tail"]]
+ Q: Quiver
+ X: float
+ Y: float
+ U: float
+ angle: float
+ coord: Literal["axes", "figure", "data", "inches"]
+ color: ColorType | None
+ label: str
+ labelpos: Literal["N", "S", "E", "W"]
+ labelcolor: ColorType | None
+ fontproperties: dict[str, Any]
+ kw: dict[str, Any]
+ text: Text
+ zorder: float
+ def __init__(
+ self,
+ Q: Quiver,
+ X: float,
+ Y: float,
+ U: float,
+ label: str,
+ *,
+ angle: float = ...,
+ coordinates: Literal["axes", "figure", "data", "inches"] = ...,
+ color: ColorType | None = ...,
+ labelsep: float = ...,
+ labelpos: Literal["N", "S", "E", "W"] = ...,
+ labelcolor: ColorType | None = ...,
+ fontproperties: dict[str, Any] | None = ...,
+ zorder: float | None = ...,
+ **kwargs
+ ) -> None: ...
+ @property
+ def labelsep(self) -> float: ...
+ def set_figure(self, fig: Figure | SubFigure) -> None: ...
+
+class Quiver(mcollections.PolyCollection):
+ X: ArrayLike
+ Y: ArrayLike
+ XY: ArrayLike
+ U: ArrayLike
+ V: ArrayLike
+ Umask: ArrayLike
+ N: int
+ scale: float | None
+ headwidth: float
+ headlength: float
+ headaxislength: float
+ minshaft: float
+ minlength: float
+ units: Literal["width", "height", "dots", "inches", "x", "y", "xy"]
+ scale_units: Literal["width", "height", "dots", "inches", "x", "y", "xy"] | None
+ angles: Literal["uv", "xy"] | ArrayLike
+ width: float | None
+ pivot: Literal["tail", "middle", "tip"]
+ transform: Transform
+ polykw: dict[str, Any]
+
+ @overload
+ def __init__(
+ self,
+ ax: Axes,
+ U: ArrayLike,
+ V: ArrayLike,
+ C: ArrayLike = ...,
+ *,
+ scale: float | None = ...,
+ headwidth: float = ...,
+ headlength: float = ...,
+ headaxislength: float = ...,
+ minshaft: float = ...,
+ minlength: float = ...,
+ units: Literal["width", "height", "dots", "inches", "x", "y", "xy"] = ...,
+ scale_units: Literal["width", "height", "dots", "inches", "x", "y", "xy"]
+ | None = ...,
+ angles: Literal["uv", "xy"] | ArrayLike = ...,
+ width: float | None = ...,
+ color: ColorType | Sequence[ColorType] = ...,
+ pivot: Literal["tail", "mid", "middle", "tip"] = ...,
+ **kwargs
+ ) -> None: ...
+ @overload
+ def __init__(
+ self,
+ ax: Axes,
+ X: ArrayLike,
+ Y: ArrayLike,
+ U: ArrayLike,
+ V: ArrayLike,
+ C: ArrayLike = ...,
+ *,
+ scale: float | None = ...,
+ headwidth: float = ...,
+ headlength: float = ...,
+ headaxislength: float = ...,
+ minshaft: float = ...,
+ minlength: float = ...,
+ units: Literal["width", "height", "dots", "inches", "x", "y", "xy"] = ...,
+ scale_units: Literal["width", "height", "dots", "inches", "x", "y", "xy"]
+ | None = ...,
+ angles: Literal["uv", "xy"] | ArrayLike = ...,
+ width: float | None = ...,
+ color: ColorType | Sequence[ColorType] = ...,
+ pivot: Literal["tail", "mid", "middle", "tip"] = ...,
+ **kwargs
+ ) -> None: ...
+ def get_datalim(self, transData: Transform) -> Bbox: ...
+ def set_UVC(
+ self, U: ArrayLike, V: ArrayLike, C: ArrayLike | None = ...
+ ) -> None: ...
+
+class Barbs(mcollections.PolyCollection):
+ sizes: dict[str, float]
+ fill_empty: bool
+ barb_increments: dict[str, float]
+ rounding: bool
+ flip: np.ndarray
+ x: ArrayLike
+ y: ArrayLike
+ u: ArrayLike
+ v: ArrayLike
+
+ @overload
+ def __init__(
+ self,
+ ax: Axes,
+ U: ArrayLike,
+ V: ArrayLike,
+ C: ArrayLike = ...,
+ *,
+ pivot: str = ...,
+ length: int = ...,
+ barbcolor: ColorType | Sequence[ColorType] | None = ...,
+ flagcolor: ColorType | Sequence[ColorType] | None = ...,
+ sizes: dict[str, float] | None = ...,
+ fill_empty: bool = ...,
+ barb_increments: dict[str, float] | None = ...,
+ rounding: bool = ...,
+ flip_barb: bool | ArrayLike = ...,
+ **kwargs
+ ) -> None: ...
+ @overload
+ def __init__(
+ self,
+ ax: Axes,
+ X: ArrayLike,
+ Y: ArrayLike,
+ U: ArrayLike,
+ V: ArrayLike,
+ C: ArrayLike = ...,
+ *,
+ pivot: str = ...,
+ length: int = ...,
+ barbcolor: ColorType | Sequence[ColorType] | None = ...,
+ flagcolor: ColorType | Sequence[ColorType] | None = ...,
+ sizes: dict[str, float] | None = ...,
+ fill_empty: bool = ...,
+ barb_increments: dict[str, float] | None = ...,
+ rounding: bool = ...,
+ flip_barb: bool | ArrayLike = ...,
+ **kwargs
+ ) -> None: ...
+ def set_UVC(
+ self, U: ArrayLike, V: ArrayLike, C: ArrayLike | None = ...
+ ) -> None: ...
+ def set_offsets(self, xy: ArrayLike) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/rcsetup.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/rcsetup.py
new file mode 100644
index 0000000000000000000000000000000000000000..c23d9f818454cda653ed6e2b08f6bb0e1045cbf4
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/rcsetup.py
@@ -0,0 +1,1371 @@
+"""
+The rcsetup module contains the validation code for customization using
+Matplotlib's rc settings.
+
+Each rc setting is assigned a function used to validate any attempted changes
+to that setting. The validation functions are defined in the rcsetup module,
+and are used to construct the rcParams global object which stores the settings
+and is referenced throughout Matplotlib.
+
+The default values of the rc settings are set in the default matplotlibrc file.
+Any additions or deletions to the parameter set listed here should also be
+propagated to the :file:`lib/matplotlib/mpl-data/matplotlibrc` in Matplotlib's
+root source directory.
+"""
+
+import ast
+from functools import lru_cache, reduce
+from numbers import Real
+import operator
+import os
+import re
+
+import numpy as np
+
+from matplotlib import _api, cbook
+from matplotlib.backends import BackendFilter, backend_registry
+from matplotlib.cbook import ls_mapper
+from matplotlib.colors import Colormap, is_color_like
+from matplotlib._fontconfig_pattern import parse_fontconfig_pattern
+from matplotlib._enums import JoinStyle, CapStyle
+
+# Don't let the original cycler collide with our validating cycler
+from cycler import Cycler, cycler as ccycler
+
+
+@_api.caching_module_getattr
+class __getattr__:
+ @_api.deprecated(
+ "3.9",
+ alternative="``matplotlib.backends.backend_registry.list_builtin"
+ "(matplotlib.backends.BackendFilter.INTERACTIVE)``")
+ @property
+ def interactive_bk(self):
+ return backend_registry.list_builtin(BackendFilter.INTERACTIVE)
+
+ @_api.deprecated(
+ "3.9",
+ alternative="``matplotlib.backends.backend_registry.list_builtin"
+ "(matplotlib.backends.BackendFilter.NON_INTERACTIVE)``")
+ @property
+ def non_interactive_bk(self):
+ return backend_registry.list_builtin(BackendFilter.NON_INTERACTIVE)
+
+ @_api.deprecated(
+ "3.9",
+ alternative="``matplotlib.backends.backend_registry.list_builtin()``")
+ @property
+ def all_backends(self):
+ return backend_registry.list_builtin()
+
+
+class ValidateInStrings:
+ def __init__(self, key, valid, ignorecase=False, *,
+ _deprecated_since=None):
+ """*valid* is a list of legal strings."""
+ self.key = key
+ self.ignorecase = ignorecase
+ self._deprecated_since = _deprecated_since
+
+ def func(s):
+ if ignorecase:
+ return s.lower()
+ else:
+ return s
+ self.valid = {func(k): k for k in valid}
+
+ def __call__(self, s):
+ if self._deprecated_since:
+ name, = (k for k, v in globals().items() if v is self)
+ _api.warn_deprecated(
+ self._deprecated_since, name=name, obj_type="function")
+ if self.ignorecase and isinstance(s, str):
+ s = s.lower()
+ if s in self.valid:
+ return self.valid[s]
+ msg = (f"{s!r} is not a valid value for {self.key}; supported values "
+ f"are {[*self.valid.values()]}")
+ if (isinstance(s, str)
+ and (s.startswith('"') and s.endswith('"')
+ or s.startswith("'") and s.endswith("'"))
+ and s[1:-1] in self.valid):
+ msg += "; remove quotes surrounding your string"
+ raise ValueError(msg)
+
+
+@lru_cache
+def _listify_validator(scalar_validator, allow_stringlist=False, *,
+ n=None, doc=None):
+ def f(s):
+ if isinstance(s, str):
+ try:
+ val = [scalar_validator(v.strip()) for v in s.split(',')
+ if v.strip()]
+ except Exception:
+ if allow_stringlist:
+ # Sometimes, a list of colors might be a single string
+ # of single-letter colornames. So give that a shot.
+ val = [scalar_validator(v.strip()) for v in s if v.strip()]
+ else:
+ raise
+ # Allow any ordered sequence type -- generators, np.ndarray, pd.Series
+ # -- but not sets, whose iteration order is non-deterministic.
+ elif np.iterable(s) and not isinstance(s, (set, frozenset)):
+ # The condition on this list comprehension will preserve the
+ # behavior of filtering out any empty strings (behavior was
+ # from the original validate_stringlist()), while allowing
+ # any non-string/text scalar values such as numbers and arrays.
+ val = [scalar_validator(v) for v in s
+ if not isinstance(v, str) or v]
+ else:
+ raise ValueError(
+ f"Expected str or other non-set iterable, but got {s}")
+ if n is not None and len(val) != n:
+ raise ValueError(
+ f"Expected {n} values, but there are {len(val)} values in {s}")
+ return val
+
+ try:
+ f.__name__ = f"{scalar_validator.__name__}list"
+ except AttributeError: # class instance.
+ f.__name__ = f"{type(scalar_validator).__name__}List"
+ f.__qualname__ = f.__qualname__.rsplit(".", 1)[0] + "." + f.__name__
+ f.__doc__ = doc if doc is not None else scalar_validator.__doc__
+ return f
+
+
+def validate_any(s):
+ return s
+validate_anylist = _listify_validator(validate_any)
+
+
+def _validate_date(s):
+ try:
+ np.datetime64(s)
+ return s
+ except ValueError:
+ raise ValueError(
+ f'{s!r} should be a string that can be parsed by numpy.datetime64')
+
+
+def validate_bool(b):
+ """Convert b to ``bool`` or raise."""
+ if isinstance(b, str):
+ b = b.lower()
+ if b in ('t', 'y', 'yes', 'on', 'true', '1', 1, True):
+ return True
+ elif b in ('f', 'n', 'no', 'off', 'false', '0', 0, False):
+ return False
+ else:
+ raise ValueError(f'Cannot convert {b!r} to bool')
+
+
+def validate_axisbelow(s):
+ try:
+ return validate_bool(s)
+ except ValueError:
+ if isinstance(s, str):
+ if s == 'line':
+ return 'line'
+ raise ValueError(f'{s!r} cannot be interpreted as'
+ ' True, False, or "line"')
+
+
+def validate_dpi(s):
+ """Confirm s is string 'figure' or convert s to float or raise."""
+ if s == 'figure':
+ return s
+ try:
+ return float(s)
+ except ValueError as e:
+ raise ValueError(f'{s!r} is not string "figure" and '
+ f'could not convert {s!r} to float') from e
+
+
+def _make_type_validator(cls, *, allow_none=False):
+ """
+ Return a validator that converts inputs to *cls* or raises (and possibly
+ allows ``None`` as well).
+ """
+
+ def validator(s):
+ if (allow_none and
+ (s is None or cbook._str_lower_equal(s, "none"))):
+ return None
+ if cls is str and not isinstance(s, str):
+ raise ValueError(f'Could not convert {s!r} to str')
+ try:
+ return cls(s)
+ except (TypeError, ValueError) as e:
+ raise ValueError(
+ f'Could not convert {s!r} to {cls.__name__}') from e
+
+ validator.__name__ = f"validate_{cls.__name__}"
+ if allow_none:
+ validator.__name__ += "_or_None"
+ validator.__qualname__ = (
+ validator.__qualname__.rsplit(".", 1)[0] + "." + validator.__name__)
+ return validator
+
+
+validate_string = _make_type_validator(str)
+validate_string_or_None = _make_type_validator(str, allow_none=True)
+validate_stringlist = _listify_validator(
+ validate_string, doc='return a list of strings')
+validate_int = _make_type_validator(int)
+validate_int_or_None = _make_type_validator(int, allow_none=True)
+validate_float = _make_type_validator(float)
+validate_float_or_None = _make_type_validator(float, allow_none=True)
+validate_floatlist = _listify_validator(
+ validate_float, doc='return a list of floats')
+
+
+def _validate_marker(s):
+ try:
+ return validate_int(s)
+ except ValueError as e:
+ try:
+ return validate_string(s)
+ except ValueError as e:
+ raise ValueError('Supported markers are [string, int]') from e
+
+
+_validate_markerlist = _listify_validator(
+ _validate_marker, doc='return a list of markers')
+
+
+def _validate_pathlike(s):
+ if isinstance(s, (str, os.PathLike)):
+ # Store value as str because savefig.directory needs to distinguish
+ # between "" (cwd) and "." (cwd, but gets updated by user selections).
+ return os.fsdecode(s)
+ else:
+ return validate_string(s)
+
+
+def validate_fonttype(s):
+ """
+ Confirm that this is a Postscript or PDF font type that we know how to
+ convert to.
+ """
+ fonttypes = {'type3': 3,
+ 'truetype': 42}
+ try:
+ fonttype = validate_int(s)
+ except ValueError:
+ try:
+ return fonttypes[s.lower()]
+ except KeyError as e:
+ raise ValueError('Supported Postscript/PDF font types are %s'
+ % list(fonttypes)) from e
+ else:
+ if fonttype not in fonttypes.values():
+ raise ValueError(
+ 'Supported Postscript/PDF font types are %s' %
+ list(fonttypes.values()))
+ return fonttype
+
+
+_auto_backend_sentinel = object()
+
+
+def validate_backend(s):
+ if s is _auto_backend_sentinel or backend_registry.is_valid_backend(s):
+ return s
+ else:
+ msg = (f"'{s}' is not a valid value for backend; supported values are "
+ f"{backend_registry.list_all()}")
+ raise ValueError(msg)
+
+
+def _validate_toolbar(s):
+ s = ValidateInStrings(
+ 'toolbar', ['None', 'toolbar2', 'toolmanager'], ignorecase=True)(s)
+ if s == 'toolmanager':
+ _api.warn_external(
+ "Treat the new Tool classes introduced in v1.5 as experimental "
+ "for now; the API and rcParam may change in future versions.")
+ return s
+
+
+def validate_color_or_inherit(s):
+ """Return a valid color arg."""
+ if cbook._str_equal(s, 'inherit'):
+ return s
+ return validate_color(s)
+
+
+def validate_color_or_auto(s):
+ if cbook._str_equal(s, 'auto'):
+ return s
+ return validate_color(s)
+
+
+def validate_color_for_prop_cycle(s):
+ # N-th color cycle syntax can't go into the color cycle.
+ if isinstance(s, str) and re.match("^C[0-9]$", s):
+ raise ValueError(f"Cannot put cycle reference ({s!r}) in prop_cycler")
+ return validate_color(s)
+
+
+def _validate_color_or_linecolor(s):
+ if cbook._str_equal(s, 'linecolor'):
+ return s
+ elif cbook._str_equal(s, 'mfc') or cbook._str_equal(s, 'markerfacecolor'):
+ return 'markerfacecolor'
+ elif cbook._str_equal(s, 'mec') or cbook._str_equal(s, 'markeredgecolor'):
+ return 'markeredgecolor'
+ elif s is None:
+ return None
+ elif isinstance(s, str) and len(s) == 6 or len(s) == 8:
+ stmp = '#' + s
+ if is_color_like(stmp):
+ return stmp
+ if s.lower() == 'none':
+ return None
+ elif is_color_like(s):
+ return s
+
+ raise ValueError(f'{s!r} does not look like a color arg')
+
+
+def validate_color(s):
+ """Return a valid color arg."""
+ if isinstance(s, str):
+ if s.lower() == 'none':
+ return 'none'
+ if len(s) == 6 or len(s) == 8:
+ stmp = '#' + s
+ if is_color_like(stmp):
+ return stmp
+
+ if is_color_like(s):
+ return s
+
+ # If it is still valid, it must be a tuple (as a string from matplotlibrc).
+ try:
+ color = ast.literal_eval(s)
+ except (SyntaxError, ValueError):
+ pass
+ else:
+ if is_color_like(color):
+ return color
+
+ raise ValueError(f'{s!r} does not look like a color arg')
+
+
+validate_colorlist = _listify_validator(
+ validate_color, allow_stringlist=True, doc='return a list of colorspecs')
+
+
+def _validate_cmap(s):
+ _api.check_isinstance((str, Colormap), cmap=s)
+ return s
+
+
+def validate_aspect(s):
+ if s in ('auto', 'equal'):
+ return s
+ try:
+ return float(s)
+ except ValueError as e:
+ raise ValueError('not a valid aspect specification') from e
+
+
+def validate_fontsize_None(s):
+ if s is None or s == 'None':
+ return None
+ else:
+ return validate_fontsize(s)
+
+
+def validate_fontsize(s):
+ fontsizes = ['xx-small', 'x-small', 'small', 'medium', 'large',
+ 'x-large', 'xx-large', 'smaller', 'larger']
+ if isinstance(s, str):
+ s = s.lower()
+ if s in fontsizes:
+ return s
+ try:
+ return float(s)
+ except ValueError as e:
+ raise ValueError("%s is not a valid font size. Valid font sizes "
+ "are %s." % (s, ", ".join(fontsizes))) from e
+
+
+validate_fontsizelist = _listify_validator(validate_fontsize)
+
+
+def validate_fontweight(s):
+ weights = [
+ 'ultralight', 'light', 'normal', 'regular', 'book', 'medium', 'roman',
+ 'semibold', 'demibold', 'demi', 'bold', 'heavy', 'extra bold', 'black']
+ # Note: Historically, weights have been case-sensitive in Matplotlib
+ if s in weights:
+ return s
+ try:
+ return int(s)
+ except (ValueError, TypeError) as e:
+ raise ValueError(f'{s} is not a valid font weight.') from e
+
+
+def validate_fontstretch(s):
+ stretchvalues = [
+ 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed',
+ 'normal', 'semi-expanded', 'expanded', 'extra-expanded',
+ 'ultra-expanded']
+ # Note: Historically, stretchvalues have been case-sensitive in Matplotlib
+ if s in stretchvalues:
+ return s
+ try:
+ return int(s)
+ except (ValueError, TypeError) as e:
+ raise ValueError(f'{s} is not a valid font stretch.') from e
+
+
+def validate_font_properties(s):
+ parse_fontconfig_pattern(s)
+ return s
+
+
+def _validate_mathtext_fallback(s):
+ _fallback_fonts = ['cm', 'stix', 'stixsans']
+ if isinstance(s, str):
+ s = s.lower()
+ if s is None or s == 'none':
+ return None
+ elif s.lower() in _fallback_fonts:
+ return s
+ else:
+ raise ValueError(
+ f"{s} is not a valid fallback font name. Valid fallback font "
+ f"names are {','.join(_fallback_fonts)}. Passing 'None' will turn "
+ "fallback off.")
+
+
+def validate_whiskers(s):
+ try:
+ return _listify_validator(validate_float, n=2)(s)
+ except (TypeError, ValueError):
+ try:
+ return float(s)
+ except ValueError as e:
+ raise ValueError("Not a valid whisker value [float, "
+ "(float, float)]") from e
+
+
+def validate_ps_distiller(s):
+ if isinstance(s, str):
+ s = s.lower()
+ if s in ('none', None, 'false', False):
+ return None
+ else:
+ return ValidateInStrings('ps.usedistiller', ['ghostscript', 'xpdf'])(s)
+
+
+# A validator dedicated to the named line styles, based on the items in
+# ls_mapper, and a list of possible strings read from Line2D.set_linestyle
+_validate_named_linestyle = ValidateInStrings(
+ 'linestyle',
+ [*ls_mapper.keys(), *ls_mapper.values(), 'None', 'none', ' ', ''],
+ ignorecase=True)
+
+
+def _validate_linestyle(ls):
+ """
+ A validator for all possible line styles, the named ones *and*
+ the on-off ink sequences.
+ """
+ if isinstance(ls, str):
+ try: # Look first for a valid named line style, like '--' or 'solid'.
+ return _validate_named_linestyle(ls)
+ except ValueError:
+ pass
+ try:
+ ls = ast.literal_eval(ls) # Parsing matplotlibrc.
+ except (SyntaxError, ValueError):
+ pass # Will error with the ValueError at the end.
+
+ def _is_iterable_not_string_like(x):
+ # Explicitly exclude bytes/bytearrays so that they are not
+ # nonsensically interpreted as sequences of numbers (codepoints).
+ return np.iterable(x) and not isinstance(x, (str, bytes, bytearray))
+
+ if _is_iterable_not_string_like(ls):
+ if len(ls) == 2 and _is_iterable_not_string_like(ls[1]):
+ # (offset, (on, off, on, off, ...))
+ offset, onoff = ls
+ else:
+ # For backcompat: (on, off, on, off, ...); the offset is implicit.
+ offset = 0
+ onoff = ls
+
+ if (isinstance(offset, Real)
+ and len(onoff) % 2 == 0
+ and all(isinstance(elem, Real) for elem in onoff)):
+ return (offset, onoff)
+
+ raise ValueError(f"linestyle {ls!r} is not a valid on-off ink sequence.")
+
+
+validate_fillstyle = ValidateInStrings(
+ 'markers.fillstyle', ['full', 'left', 'right', 'bottom', 'top', 'none'])
+
+
+validate_fillstylelist = _listify_validator(validate_fillstyle)
+
+
+def validate_markevery(s):
+ """
+ Validate the markevery property of a Line2D object.
+
+ Parameters
+ ----------
+ s : None, int, (int, int), slice, float, (float, float), or list[int]
+
+ Returns
+ -------
+ None, int, (int, int), slice, float, (float, float), or list[int]
+ """
+ # Validate s against type slice float int and None
+ if isinstance(s, (slice, float, int, type(None))):
+ return s
+ # Validate s against type tuple
+ if isinstance(s, tuple):
+ if (len(s) == 2
+ and (all(isinstance(e, int) for e in s)
+ or all(isinstance(e, float) for e in s))):
+ return s
+ else:
+ raise TypeError(
+ "'markevery' tuple must be pair of ints or of floats")
+ # Validate s against type list
+ if isinstance(s, list):
+ if all(isinstance(e, int) for e in s):
+ return s
+ else:
+ raise TypeError(
+ "'markevery' list must have all elements of type int")
+ raise TypeError("'markevery' is of an invalid type")
+
+
+validate_markeverylist = _listify_validator(validate_markevery)
+
+
+def validate_bbox(s):
+ if isinstance(s, str):
+ s = s.lower()
+ if s == 'tight':
+ return s
+ if s == 'standard':
+ return None
+ raise ValueError("bbox should be 'tight' or 'standard'")
+ elif s is not None:
+ # Backwards compatibility. None is equivalent to 'standard'.
+ raise ValueError("bbox should be 'tight' or 'standard'")
+ return s
+
+
+def validate_sketch(s):
+
+ if isinstance(s, str):
+ s = s.lower().strip()
+ if s.startswith("(") and s.endswith(")"):
+ s = s[1:-1]
+ if s == 'none' or s is None:
+ return None
+ try:
+ return tuple(_listify_validator(validate_float, n=3)(s))
+ except ValueError as exc:
+ raise ValueError("Expected a (scale, length, randomness) tuple") from exc
+
+
+def _validate_greaterthan_minushalf(s):
+ s = validate_float(s)
+ if s > -0.5:
+ return s
+ else:
+ raise RuntimeError(f'Value must be >-0.5; got {s}')
+
+
+def _validate_greaterequal0_lessequal1(s):
+ s = validate_float(s)
+ if 0 <= s <= 1:
+ return s
+ else:
+ raise RuntimeError(f'Value must be >=0 and <=1; got {s}')
+
+
+def _validate_int_greaterequal0(s):
+ s = validate_int(s)
+ if s >= 0:
+ return s
+ else:
+ raise RuntimeError(f'Value must be >=0; got {s}')
+
+
+def validate_hatch(s):
+ r"""
+ Validate a hatch pattern.
+ A hatch pattern string can have any sequence of the following
+ characters: ``\ / | - + * . x o O``.
+ """
+ if not isinstance(s, str):
+ raise ValueError("Hatch pattern must be a string")
+ _api.check_isinstance(str, hatch_pattern=s)
+ unknown = set(s) - {'\\', '/', '|', '-', '+', '*', '.', 'x', 'o', 'O'}
+ if unknown:
+ raise ValueError("Unknown hatch symbol(s): %s" % list(unknown))
+ return s
+
+
+validate_hatchlist = _listify_validator(validate_hatch)
+validate_dashlist = _listify_validator(validate_floatlist)
+
+
+def _validate_minor_tick_ndivs(n):
+ """
+ Validate ndiv parameter related to the minor ticks.
+ It controls the number of minor ticks to be placed between
+ two major ticks.
+ """
+
+ if cbook._str_lower_equal(n, 'auto'):
+ return n
+ try:
+ n = _validate_int_greaterequal0(n)
+ return n
+ except (RuntimeError, ValueError):
+ pass
+
+ raise ValueError("'tick.minor.ndivs' must be 'auto' or non-negative int")
+
+
+_prop_validators = {
+ 'color': _listify_validator(validate_color_for_prop_cycle,
+ allow_stringlist=True),
+ 'linewidth': validate_floatlist,
+ 'linestyle': _listify_validator(_validate_linestyle),
+ 'facecolor': validate_colorlist,
+ 'edgecolor': validate_colorlist,
+ 'joinstyle': _listify_validator(JoinStyle),
+ 'capstyle': _listify_validator(CapStyle),
+ 'fillstyle': validate_fillstylelist,
+ 'markerfacecolor': validate_colorlist,
+ 'markersize': validate_floatlist,
+ 'markeredgewidth': validate_floatlist,
+ 'markeredgecolor': validate_colorlist,
+ 'markevery': validate_markeverylist,
+ 'alpha': validate_floatlist,
+ 'marker': _validate_markerlist,
+ 'hatch': validate_hatchlist,
+ 'dashes': validate_dashlist,
+ }
+_prop_aliases = {
+ 'c': 'color',
+ 'lw': 'linewidth',
+ 'ls': 'linestyle',
+ 'fc': 'facecolor',
+ 'ec': 'edgecolor',
+ 'mfc': 'markerfacecolor',
+ 'mec': 'markeredgecolor',
+ 'mew': 'markeredgewidth',
+ 'ms': 'markersize',
+ }
+
+
+def cycler(*args, **kwargs):
+ """
+ Create a `~cycler.Cycler` object much like :func:`cycler.cycler`,
+ but includes input validation.
+
+ Call signatures::
+
+ cycler(cycler)
+ cycler(label=values, label2=values2, ...)
+ cycler(label, values)
+
+ Form 1 copies a given `~cycler.Cycler` object.
+
+ Form 2 creates a `~cycler.Cycler` which cycles over one or more
+ properties simultaneously. If multiple properties are given, their
+ value lists must have the same length.
+
+ Form 3 creates a `~cycler.Cycler` for a single property. This form
+ exists for compatibility with the original cycler. Its use is
+ discouraged in favor of the kwarg form, i.e. ``cycler(label=values)``.
+
+ Parameters
+ ----------
+ cycler : Cycler
+ Copy constructor for Cycler.
+
+ label : str
+ The property key. Must be a valid `.Artist` property.
+ For example, 'color' or 'linestyle'. Aliases are allowed,
+ such as 'c' for 'color' and 'lw' for 'linewidth'.
+
+ values : iterable
+ Finite-length iterable of the property values. These values
+ are validated and will raise a ValueError if invalid.
+
+ Returns
+ -------
+ Cycler
+ A new :class:`~cycler.Cycler` for the given properties.
+
+ Examples
+ --------
+ Creating a cycler for a single property:
+
+ >>> c = cycler(color=['red', 'green', 'blue'])
+
+ Creating a cycler for simultaneously cycling over multiple properties
+ (e.g. red circle, green plus, blue cross):
+
+ >>> c = cycler(color=['red', 'green', 'blue'],
+ ... marker=['o', '+', 'x'])
+
+ """
+ if args and kwargs:
+ raise TypeError("cycler() can only accept positional OR keyword "
+ "arguments -- not both.")
+ elif not args and not kwargs:
+ raise TypeError("cycler() must have positional OR keyword arguments")
+
+ if len(args) == 1:
+ if not isinstance(args[0], Cycler):
+ raise TypeError("If only one positional argument given, it must "
+ "be a Cycler instance.")
+ return validate_cycler(args[0])
+ elif len(args) == 2:
+ pairs = [(args[0], args[1])]
+ elif len(args) > 2:
+ raise _api.nargs_error('cycler', '0-2', len(args))
+ else:
+ pairs = kwargs.items()
+
+ validated = []
+ for prop, vals in pairs:
+ norm_prop = _prop_aliases.get(prop, prop)
+ validator = _prop_validators.get(norm_prop, None)
+ if validator is None:
+ raise TypeError("Unknown artist property: %s" % prop)
+ vals = validator(vals)
+ # We will normalize the property names as well to reduce
+ # the amount of alias handling code elsewhere.
+ validated.append((norm_prop, vals))
+
+ return reduce(operator.add, (ccycler(k, v) for k, v in validated))
+
+
+class _DunderChecker(ast.NodeVisitor):
+ def visit_Attribute(self, node):
+ if node.attr.startswith("__") and node.attr.endswith("__"):
+ raise ValueError("cycler strings with dunders are forbidden")
+ self.generic_visit(node)
+
+
+# A validator dedicated to the named legend loc
+_validate_named_legend_loc = ValidateInStrings(
+ 'legend.loc',
+ [
+ "best",
+ "upper right", "upper left", "lower left", "lower right", "right",
+ "center left", "center right", "lower center", "upper center",
+ "center"],
+ ignorecase=True)
+
+
+def _validate_legend_loc(loc):
+ """
+ Confirm that loc is a type which rc.Params["legend.loc"] supports.
+
+ .. versionadded:: 3.8
+
+ Parameters
+ ----------
+ loc : str | int | (float, float) | str((float, float))
+ The location of the legend.
+
+ Returns
+ -------
+ loc : str | int | (float, float) or raise ValueError exception
+ The location of the legend.
+ """
+ if isinstance(loc, str):
+ try:
+ return _validate_named_legend_loc(loc)
+ except ValueError:
+ pass
+ try:
+ loc = ast.literal_eval(loc)
+ except (SyntaxError, ValueError):
+ pass
+ if isinstance(loc, int):
+ if 0 <= loc <= 10:
+ return loc
+ if isinstance(loc, tuple):
+ if len(loc) == 2 and all(isinstance(e, Real) for e in loc):
+ return loc
+ raise ValueError(f"{loc} is not a valid legend location.")
+
+
+def validate_cycler(s):
+ """Return a Cycler object from a string repr or the object itself."""
+ if isinstance(s, str):
+ # TODO: We might want to rethink this...
+ # While I think I have it quite locked down, it is execution of
+ # arbitrary code without sanitation.
+ # Combine this with the possibility that rcparams might come from the
+ # internet (future plans), this could be downright dangerous.
+ # I locked it down by only having the 'cycler()' function available.
+ # UPDATE: Partly plugging a security hole.
+ # I really should have read this:
+ # https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
+ # We should replace this eval with a combo of PyParsing and
+ # ast.literal_eval()
+ try:
+ _DunderChecker().visit(ast.parse(s))
+ s = eval(s, {'cycler': cycler, '__builtins__': {}})
+ except BaseException as e:
+ raise ValueError(f"{s!r} is not a valid cycler construction: {e}"
+ ) from e
+ # Should make sure what comes from the above eval()
+ # is a Cycler object.
+ if isinstance(s, Cycler):
+ cycler_inst = s
+ else:
+ raise ValueError(f"Object is not a string or Cycler instance: {s!r}")
+
+ unknowns = cycler_inst.keys - (set(_prop_validators) | set(_prop_aliases))
+ if unknowns:
+ raise ValueError("Unknown artist properties: %s" % unknowns)
+
+ # Not a full validation, but it'll at least normalize property names
+ # A fuller validation would require v0.10 of cycler.
+ checker = set()
+ for prop in cycler_inst.keys:
+ norm_prop = _prop_aliases.get(prop, prop)
+ if norm_prop != prop and norm_prop in cycler_inst.keys:
+ raise ValueError(f"Cannot specify both {norm_prop!r} and alias "
+ f"{prop!r} in the same prop_cycle")
+ if norm_prop in checker:
+ raise ValueError(f"Another property was already aliased to "
+ f"{norm_prop!r}. Collision normalizing {prop!r}.")
+ checker.update([norm_prop])
+
+ # This is just an extra-careful check, just in case there is some
+ # edge-case I haven't thought of.
+ assert len(checker) == len(cycler_inst.keys)
+
+ # Now, it should be safe to mutate this cycler
+ for prop in cycler_inst.keys:
+ norm_prop = _prop_aliases.get(prop, prop)
+ cycler_inst.change_key(prop, norm_prop)
+
+ for key, vals in cycler_inst.by_key().items():
+ _prop_validators[key](vals)
+
+ return cycler_inst
+
+
+def validate_hist_bins(s):
+ valid_strs = ["auto", "sturges", "fd", "doane", "scott", "rice", "sqrt"]
+ if isinstance(s, str) and s in valid_strs:
+ return s
+ try:
+ return int(s)
+ except (TypeError, ValueError):
+ pass
+ try:
+ return validate_floatlist(s)
+ except ValueError:
+ pass
+ raise ValueError(f"'hist.bins' must be one of {valid_strs}, an int or"
+ " a sequence of floats")
+
+
+class _ignorecase(list):
+ """A marker class indicating that a list-of-str is case-insensitive."""
+
+
+def _convert_validator_spec(key, conv):
+ if isinstance(conv, list):
+ ignorecase = isinstance(conv, _ignorecase)
+ return ValidateInStrings(key, conv, ignorecase=ignorecase)
+ else:
+ return conv
+
+
+# Mapping of rcParams to validators.
+# Converters given as lists or _ignorecase are converted to ValidateInStrings
+# immediately below.
+# The rcParams defaults are defined in lib/matplotlib/mpl-data/matplotlibrc, which
+# gets copied to matplotlib/mpl-data/matplotlibrc by the setup script.
+_validators = {
+ "backend": validate_backend,
+ "backend_fallback": validate_bool,
+ "figure.hooks": validate_stringlist,
+ "toolbar": _validate_toolbar,
+ "interactive": validate_bool,
+ "timezone": validate_string,
+
+ "webagg.port": validate_int,
+ "webagg.address": validate_string,
+ "webagg.open_in_browser": validate_bool,
+ "webagg.port_retries": validate_int,
+
+ # line props
+ "lines.linewidth": validate_float, # line width in points
+ "lines.linestyle": _validate_linestyle, # solid line
+ "lines.color": validate_color, # first color in color cycle
+ "lines.marker": _validate_marker, # marker name
+ "lines.markerfacecolor": validate_color_or_auto, # default color
+ "lines.markeredgecolor": validate_color_or_auto, # default color
+ "lines.markeredgewidth": validate_float,
+ "lines.markersize": validate_float, # markersize, in points
+ "lines.antialiased": validate_bool, # antialiased (no jaggies)
+ "lines.dash_joinstyle": JoinStyle,
+ "lines.solid_joinstyle": JoinStyle,
+ "lines.dash_capstyle": CapStyle,
+ "lines.solid_capstyle": CapStyle,
+ "lines.dashed_pattern": validate_floatlist,
+ "lines.dashdot_pattern": validate_floatlist,
+ "lines.dotted_pattern": validate_floatlist,
+ "lines.scale_dashes": validate_bool,
+
+ # marker props
+ "markers.fillstyle": validate_fillstyle,
+
+ ## pcolor(mesh) props:
+ "pcolor.shading": ["auto", "flat", "nearest", "gouraud"],
+ "pcolormesh.snap": validate_bool,
+
+ ## patch props
+ "patch.linewidth": validate_float, # line width in points
+ "patch.edgecolor": validate_color,
+ "patch.force_edgecolor": validate_bool,
+ "patch.facecolor": validate_color, # first color in cycle
+ "patch.antialiased": validate_bool, # antialiased (no jaggies)
+
+ ## hatch props
+ "hatch.color": validate_color,
+ "hatch.linewidth": validate_float,
+
+ ## Histogram properties
+ "hist.bins": validate_hist_bins,
+
+ ## Boxplot properties
+ "boxplot.notch": validate_bool,
+ "boxplot.vertical": validate_bool,
+ "boxplot.whiskers": validate_whiskers,
+ "boxplot.bootstrap": validate_int_or_None,
+ "boxplot.patchartist": validate_bool,
+ "boxplot.showmeans": validate_bool,
+ "boxplot.showcaps": validate_bool,
+ "boxplot.showbox": validate_bool,
+ "boxplot.showfliers": validate_bool,
+ "boxplot.meanline": validate_bool,
+
+ "boxplot.flierprops.color": validate_color,
+ "boxplot.flierprops.marker": _validate_marker,
+ "boxplot.flierprops.markerfacecolor": validate_color_or_auto,
+ "boxplot.flierprops.markeredgecolor": validate_color,
+ "boxplot.flierprops.markeredgewidth": validate_float,
+ "boxplot.flierprops.markersize": validate_float,
+ "boxplot.flierprops.linestyle": _validate_linestyle,
+ "boxplot.flierprops.linewidth": validate_float,
+
+ "boxplot.boxprops.color": validate_color,
+ "boxplot.boxprops.linewidth": validate_float,
+ "boxplot.boxprops.linestyle": _validate_linestyle,
+
+ "boxplot.whiskerprops.color": validate_color,
+ "boxplot.whiskerprops.linewidth": validate_float,
+ "boxplot.whiskerprops.linestyle": _validate_linestyle,
+
+ "boxplot.capprops.color": validate_color,
+ "boxplot.capprops.linewidth": validate_float,
+ "boxplot.capprops.linestyle": _validate_linestyle,
+
+ "boxplot.medianprops.color": validate_color,
+ "boxplot.medianprops.linewidth": validate_float,
+ "boxplot.medianprops.linestyle": _validate_linestyle,
+
+ "boxplot.meanprops.color": validate_color,
+ "boxplot.meanprops.marker": _validate_marker,
+ "boxplot.meanprops.markerfacecolor": validate_color,
+ "boxplot.meanprops.markeredgecolor": validate_color,
+ "boxplot.meanprops.markersize": validate_float,
+ "boxplot.meanprops.linestyle": _validate_linestyle,
+ "boxplot.meanprops.linewidth": validate_float,
+
+ ## font props
+ "font.family": validate_stringlist, # used by text object
+ "font.style": validate_string,
+ "font.variant": validate_string,
+ "font.stretch": validate_fontstretch,
+ "font.weight": validate_fontweight,
+ "font.size": validate_float, # Base font size in points
+ "font.serif": validate_stringlist,
+ "font.sans-serif": validate_stringlist,
+ "font.cursive": validate_stringlist,
+ "font.fantasy": validate_stringlist,
+ "font.monospace": validate_stringlist,
+
+ # text props
+ "text.color": validate_color,
+ "text.usetex": validate_bool,
+ "text.latex.preamble": validate_string,
+ "text.hinting": ["default", "no_autohint", "force_autohint",
+ "no_hinting", "auto", "native", "either", "none"],
+ "text.hinting_factor": validate_int,
+ "text.kerning_factor": validate_int,
+ "text.antialiased": validate_bool,
+ "text.parse_math": validate_bool,
+
+ "mathtext.cal": validate_font_properties,
+ "mathtext.rm": validate_font_properties,
+ "mathtext.tt": validate_font_properties,
+ "mathtext.it": validate_font_properties,
+ "mathtext.bf": validate_font_properties,
+ "mathtext.bfit": validate_font_properties,
+ "mathtext.sf": validate_font_properties,
+ "mathtext.fontset": ["dejavusans", "dejavuserif", "cm", "stix",
+ "stixsans", "custom"],
+ "mathtext.default": ["rm", "cal", "bfit", "it", "tt", "sf", "bf", "default",
+ "bb", "frak", "scr", "regular"],
+ "mathtext.fallback": _validate_mathtext_fallback,
+
+ "image.aspect": validate_aspect, # equal, auto, a number
+ "image.interpolation": validate_string,
+ "image.interpolation_stage": ["auto", "data", "rgba"],
+ "image.cmap": _validate_cmap, # gray, jet, etc.
+ "image.lut": validate_int, # lookup table
+ "image.origin": ["upper", "lower"],
+ "image.resample": validate_bool,
+ # Specify whether vector graphics backends will combine all images on a
+ # set of Axes into a single composite image
+ "image.composite_image": validate_bool,
+
+ # contour props
+ "contour.negative_linestyle": _validate_linestyle,
+ "contour.corner_mask": validate_bool,
+ "contour.linewidth": validate_float_or_None,
+ "contour.algorithm": ["mpl2005", "mpl2014", "serial", "threaded"],
+
+ # errorbar props
+ "errorbar.capsize": validate_float,
+
+ # axis props
+ # alignment of x/y axis title
+ "xaxis.labellocation": ["left", "center", "right"],
+ "yaxis.labellocation": ["bottom", "center", "top"],
+
+ # Axes props
+ "axes.axisbelow": validate_axisbelow,
+ "axes.facecolor": validate_color, # background color
+ "axes.edgecolor": validate_color, # edge color
+ "axes.linewidth": validate_float, # edge linewidth
+
+ "axes.spines.left": validate_bool, # Set visibility of axes spines,
+ "axes.spines.right": validate_bool, # i.e., the lines around the chart
+ "axes.spines.bottom": validate_bool, # denoting data boundary.
+ "axes.spines.top": validate_bool,
+
+ "axes.titlesize": validate_fontsize, # Axes title fontsize
+ "axes.titlelocation": ["left", "center", "right"], # Axes title alignment
+ "axes.titleweight": validate_fontweight, # Axes title font weight
+ "axes.titlecolor": validate_color_or_auto, # Axes title font color
+ # title location, axes units, None means auto
+ "axes.titley": validate_float_or_None,
+ # pad from Axes top decoration to title in points
+ "axes.titlepad": validate_float,
+ "axes.grid": validate_bool, # display grid or not
+ "axes.grid.which": ["minor", "both", "major"], # which grids are drawn
+ "axes.grid.axis": ["x", "y", "both"], # grid type
+ "axes.labelsize": validate_fontsize, # fontsize of x & y labels
+ "axes.labelpad": validate_float, # space between label and axis
+ "axes.labelweight": validate_fontweight, # fontsize of x & y labels
+ "axes.labelcolor": validate_color, # color of axis label
+ # use scientific notation if log10 of the axis range is smaller than the
+ # first or larger than the second
+ "axes.formatter.limits": _listify_validator(validate_int, n=2),
+ # use current locale to format ticks
+ "axes.formatter.use_locale": validate_bool,
+ "axes.formatter.use_mathtext": validate_bool,
+ # minimum exponent to format in scientific notation
+ "axes.formatter.min_exponent": validate_int,
+ "axes.formatter.useoffset": validate_bool,
+ "axes.formatter.offset_threshold": validate_int,
+ "axes.unicode_minus": validate_bool,
+ # This entry can be either a cycler object or a string repr of a
+ # cycler-object, which gets eval()'ed to create the object.
+ "axes.prop_cycle": validate_cycler,
+ # If "data", axes limits are set close to the data.
+ # If "round_numbers" axes limits are set to the nearest round numbers.
+ "axes.autolimit_mode": ["data", "round_numbers"],
+ "axes.xmargin": _validate_greaterthan_minushalf, # margin added to xaxis
+ "axes.ymargin": _validate_greaterthan_minushalf, # margin added to yaxis
+ "axes.zmargin": _validate_greaterthan_minushalf, # margin added to zaxis
+
+ "polaraxes.grid": validate_bool, # display polar grid or not
+ "axes3d.grid": validate_bool, # display 3d grid
+ "axes3d.automargin": validate_bool, # automatically add margin when
+ # manually setting 3D axis limits
+
+ "axes3d.xaxis.panecolor": validate_color, # 3d background pane
+ "axes3d.yaxis.panecolor": validate_color, # 3d background pane
+ "axes3d.zaxis.panecolor": validate_color, # 3d background pane
+
+ "axes3d.mouserotationstyle": ["azel", "trackball", "sphere", "arcball"],
+ "axes3d.trackballsize": validate_float,
+ "axes3d.trackballborder": validate_float,
+
+ # scatter props
+ "scatter.marker": _validate_marker,
+ "scatter.edgecolors": validate_string,
+
+ "date.epoch": _validate_date,
+ "date.autoformatter.year": validate_string,
+ "date.autoformatter.month": validate_string,
+ "date.autoformatter.day": validate_string,
+ "date.autoformatter.hour": validate_string,
+ "date.autoformatter.minute": validate_string,
+ "date.autoformatter.second": validate_string,
+ "date.autoformatter.microsecond": validate_string,
+
+ 'date.converter': ['auto', 'concise'],
+ # for auto date locator, choose interval_multiples
+ 'date.interval_multiples': validate_bool,
+
+ # legend properties
+ "legend.fancybox": validate_bool,
+ "legend.loc": _validate_legend_loc,
+
+ # the number of points in the legend line
+ "legend.numpoints": validate_int,
+ # the number of points in the legend line for scatter
+ "legend.scatterpoints": validate_int,
+ "legend.fontsize": validate_fontsize,
+ "legend.title_fontsize": validate_fontsize_None,
+ # color of the legend
+ "legend.labelcolor": _validate_color_or_linecolor,
+ # the relative size of legend markers vs. original
+ "legend.markerscale": validate_float,
+ # using dict in rcParams not yet supported, so make sure it is bool
+ "legend.shadow": validate_bool,
+ # whether or not to draw a frame around legend
+ "legend.frameon": validate_bool,
+ # alpha value of the legend frame
+ "legend.framealpha": validate_float_or_None,
+
+ ## the following dimensions are in fraction of the font size
+ "legend.borderpad": validate_float, # units are fontsize
+ # the vertical space between the legend entries
+ "legend.labelspacing": validate_float,
+ # the length of the legend lines
+ "legend.handlelength": validate_float,
+ # the length of the legend lines
+ "legend.handleheight": validate_float,
+ # the space between the legend line and legend text
+ "legend.handletextpad": validate_float,
+ # the border between the Axes and legend edge
+ "legend.borderaxespad": validate_float,
+ # the border between the Axes and legend edge
+ "legend.columnspacing": validate_float,
+ "legend.facecolor": validate_color_or_inherit,
+ "legend.edgecolor": validate_color_or_inherit,
+
+ # tick properties
+ "xtick.top": validate_bool, # draw ticks on top side
+ "xtick.bottom": validate_bool, # draw ticks on bottom side
+ "xtick.labeltop": validate_bool, # draw label on top
+ "xtick.labelbottom": validate_bool, # draw label on bottom
+ "xtick.major.size": validate_float, # major xtick size in points
+ "xtick.minor.size": validate_float, # minor xtick size in points
+ "xtick.major.width": validate_float, # major xtick width in points
+ "xtick.minor.width": validate_float, # minor xtick width in points
+ "xtick.major.pad": validate_float, # distance to label in points
+ "xtick.minor.pad": validate_float, # distance to label in points
+ "xtick.color": validate_color, # color of xticks
+ "xtick.labelcolor": validate_color_or_inherit, # color of xtick labels
+ "xtick.minor.visible": validate_bool, # visibility of minor xticks
+ "xtick.minor.top": validate_bool, # draw top minor xticks
+ "xtick.minor.bottom": validate_bool, # draw bottom minor xticks
+ "xtick.major.top": validate_bool, # draw top major xticks
+ "xtick.major.bottom": validate_bool, # draw bottom major xticks
+ # number of minor xticks
+ "xtick.minor.ndivs": _validate_minor_tick_ndivs,
+ "xtick.labelsize": validate_fontsize, # fontsize of xtick labels
+ "xtick.direction": ["out", "in", "inout"], # direction of xticks
+ "xtick.alignment": ["center", "right", "left"],
+
+ "ytick.left": validate_bool, # draw ticks on left side
+ "ytick.right": validate_bool, # draw ticks on right side
+ "ytick.labelleft": validate_bool, # draw tick labels on left side
+ "ytick.labelright": validate_bool, # draw tick labels on right side
+ "ytick.major.size": validate_float, # major ytick size in points
+ "ytick.minor.size": validate_float, # minor ytick size in points
+ "ytick.major.width": validate_float, # major ytick width in points
+ "ytick.minor.width": validate_float, # minor ytick width in points
+ "ytick.major.pad": validate_float, # distance to label in points
+ "ytick.minor.pad": validate_float, # distance to label in points
+ "ytick.color": validate_color, # color of yticks
+ "ytick.labelcolor": validate_color_or_inherit, # color of ytick labels
+ "ytick.minor.visible": validate_bool, # visibility of minor yticks
+ "ytick.minor.left": validate_bool, # draw left minor yticks
+ "ytick.minor.right": validate_bool, # draw right minor yticks
+ "ytick.major.left": validate_bool, # draw left major yticks
+ "ytick.major.right": validate_bool, # draw right major yticks
+ # number of minor yticks
+ "ytick.minor.ndivs": _validate_minor_tick_ndivs,
+ "ytick.labelsize": validate_fontsize, # fontsize of ytick labels
+ "ytick.direction": ["out", "in", "inout"], # direction of yticks
+ "ytick.alignment": [
+ "center", "top", "bottom", "baseline", "center_baseline"],
+
+ "grid.color": validate_color, # grid color
+ "grid.linestyle": _validate_linestyle, # solid
+ "grid.linewidth": validate_float, # in points
+ "grid.alpha": validate_float,
+
+ ## figure props
+ # figure title
+ "figure.titlesize": validate_fontsize,
+ "figure.titleweight": validate_fontweight,
+
+ # figure labels
+ "figure.labelsize": validate_fontsize,
+ "figure.labelweight": validate_fontweight,
+
+ # figure size in inches: width by height
+ "figure.figsize": _listify_validator(validate_float, n=2),
+ "figure.dpi": validate_float,
+ "figure.facecolor": validate_color,
+ "figure.edgecolor": validate_color,
+ "figure.frameon": validate_bool,
+ "figure.autolayout": validate_bool,
+ "figure.max_open_warning": validate_int,
+ "figure.raise_window": validate_bool,
+ "macosx.window_mode": ["system", "tab", "window"],
+
+ "figure.subplot.left": validate_float,
+ "figure.subplot.right": validate_float,
+ "figure.subplot.bottom": validate_float,
+ "figure.subplot.top": validate_float,
+ "figure.subplot.wspace": validate_float,
+ "figure.subplot.hspace": validate_float,
+
+ "figure.constrained_layout.use": validate_bool, # run constrained_layout?
+ # wspace and hspace are fraction of adjacent subplots to use for space.
+ # Much smaller than above because we don't need room for the text.
+ "figure.constrained_layout.hspace": validate_float,
+ "figure.constrained_layout.wspace": validate_float,
+ # buffer around the Axes, in inches.
+ "figure.constrained_layout.h_pad": validate_float,
+ "figure.constrained_layout.w_pad": validate_float,
+
+ ## Saving figure's properties
+ 'savefig.dpi': validate_dpi,
+ 'savefig.facecolor': validate_color_or_auto,
+ 'savefig.edgecolor': validate_color_or_auto,
+ 'savefig.orientation': ['landscape', 'portrait'],
+ "savefig.format": validate_string,
+ "savefig.bbox": validate_bbox, # "tight", or "standard" (= None)
+ "savefig.pad_inches": validate_float,
+ # default directory in savefig dialog box
+ "savefig.directory": _validate_pathlike,
+ "savefig.transparent": validate_bool,
+
+ "tk.window_focus": validate_bool, # Maintain shell focus for TkAgg
+
+ # Set the papersize/type
+ "ps.papersize": _ignorecase(
+ ["figure", "letter", "legal", "ledger",
+ *[f"{ab}{i}" for ab in "ab" for i in range(11)]]),
+ "ps.useafm": validate_bool,
+ # use ghostscript or xpdf to distill ps output
+ "ps.usedistiller": validate_ps_distiller,
+ "ps.distiller.res": validate_int, # dpi
+ "ps.fonttype": validate_fonttype, # 3 (Type3) or 42 (Truetype)
+ "pdf.compression": validate_int, # 0-9 compression level; 0 to disable
+ "pdf.inheritcolor": validate_bool, # skip color setting commands
+ # use only the 14 PDF core fonts embedded in every PDF viewing application
+ "pdf.use14corefonts": validate_bool,
+ "pdf.fonttype": validate_fonttype, # 3 (Type3) or 42 (Truetype)
+
+ "pgf.texsystem": ["xelatex", "lualatex", "pdflatex"], # latex variant used
+ "pgf.rcfonts": validate_bool, # use mpl's rc settings for font config
+ "pgf.preamble": validate_string, # custom LaTeX preamble
+
+ # write raster image data into the svg file
+ "svg.image_inline": validate_bool,
+ "svg.fonttype": ["none", "path"], # save text as text ("none") or "paths"
+ "svg.hashsalt": validate_string_or_None,
+ "svg.id": validate_string_or_None,
+
+ # set this when you want to generate hardcopy docstring
+ "docstring.hardcopy": validate_bool,
+
+ "path.simplify": validate_bool,
+ "path.simplify_threshold": _validate_greaterequal0_lessequal1,
+ "path.snap": validate_bool,
+ "path.sketch": validate_sketch,
+ "path.effects": validate_anylist,
+ "agg.path.chunksize": validate_int, # 0 to disable chunking
+
+ # key-mappings (multi-character mappings should be a list/tuple)
+ "keymap.fullscreen": validate_stringlist,
+ "keymap.home": validate_stringlist,
+ "keymap.back": validate_stringlist,
+ "keymap.forward": validate_stringlist,
+ "keymap.pan": validate_stringlist,
+ "keymap.zoom": validate_stringlist,
+ "keymap.save": validate_stringlist,
+ "keymap.quit": validate_stringlist,
+ "keymap.quit_all": validate_stringlist, # e.g.: "W", "cmd+W", "Q"
+ "keymap.grid": validate_stringlist,
+ "keymap.grid_minor": validate_stringlist,
+ "keymap.yscale": validate_stringlist,
+ "keymap.xscale": validate_stringlist,
+ "keymap.help": validate_stringlist,
+ "keymap.copy": validate_stringlist,
+
+ # Animation settings
+ "animation.html": ["html5", "jshtml", "none"],
+ # Limit, in MB, of size of base64 encoded animation in HTML
+ # (i.e. IPython notebook)
+ "animation.embed_limit": validate_float,
+ "animation.writer": validate_string,
+ "animation.codec": validate_string,
+ "animation.bitrate": validate_int,
+ # Controls image format when frames are written to disk
+ "animation.frame_format": ["png", "jpeg", "tiff", "raw", "rgba", "ppm",
+ "sgi", "bmp", "pbm", "svg"],
+ # Path to ffmpeg binary. If just binary name, subprocess uses $PATH.
+ "animation.ffmpeg_path": _validate_pathlike,
+ # Additional arguments for ffmpeg movie writer (using pipes)
+ "animation.ffmpeg_args": validate_stringlist,
+ # Path to convert binary. If just binary name, subprocess uses $PATH.
+ "animation.convert_path": _validate_pathlike,
+ # Additional arguments for convert movie writer (using pipes)
+ "animation.convert_args": validate_stringlist,
+
+ # Classic (pre 2.0) compatibility mode
+ # This is used for things that are hard to make backward compatible
+ # with a sane rcParam alone. This does *not* turn on classic mode
+ # altogether. For that use `matplotlib.style.use("classic")`.
+ "_internal.classic_mode": validate_bool
+}
+_hardcoded_defaults = { # Defaults not inferred from
+ # lib/matplotlib/mpl-data/matplotlibrc...
+ # ... because they are private:
+ "_internal.classic_mode": False,
+ # ... because they are deprecated:
+ # No current deprecations.
+ # backend is handled separately when constructing rcParamsDefault.
+}
+_validators = {k: _convert_validator_spec(k, conv)
+ for k, conv in _validators.items()}
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/rcsetup.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/rcsetup.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..1538dac7510ee54fff13977a408480bb2b45fa18
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/rcsetup.pyi
@@ -0,0 +1,159 @@
+from cycler import Cycler
+
+from collections.abc import Callable, Iterable
+from typing import Any, Literal, TypeVar
+from matplotlib.typing import ColorType, LineStyleType, MarkEveryType
+
+interactive_bk: list[str]
+non_interactive_bk: list[str]
+all_backends: list[str]
+
+_T = TypeVar("_T")
+
+def _listify_validator(s: Callable[[Any], _T]) -> Callable[[Any], list[_T]]: ...
+
+class ValidateInStrings:
+ key: str
+ ignorecase: bool
+ valid: dict[str, str]
+ def __init__(
+ self,
+ key: str,
+ valid: Iterable[str],
+ ignorecase: bool = ...,
+ *,
+ _deprecated_since: str | None = ...
+ ) -> None: ...
+ def __call__(self, s: Any) -> str: ...
+
+def validate_any(s: Any) -> Any: ...
+def validate_anylist(s: Any) -> list[Any]: ...
+def validate_bool(b: Any) -> bool: ...
+def validate_axisbelow(s: Any) -> bool | Literal["line"]: ...
+def validate_dpi(s: Any) -> Literal["figure"] | float: ...
+def validate_string(s: Any) -> str: ...
+def validate_string_or_None(s: Any) -> str | None: ...
+def validate_stringlist(s: Any) -> list[str]: ...
+def validate_int(s: Any) -> int: ...
+def validate_int_or_None(s: Any) -> int | None: ...
+def validate_float(s: Any) -> float: ...
+def validate_float_or_None(s: Any) -> float | None: ...
+def validate_floatlist(s: Any) -> list[float]: ...
+def _validate_marker(s: Any) -> int | str: ...
+def _validate_markerlist(s: Any) -> list[int | str]: ...
+def validate_fonttype(s: Any) -> int: ...
+
+_auto_backend_sentinel: object
+
+def validate_backend(s: Any) -> str: ...
+def validate_color_or_inherit(s: Any) -> Literal["inherit"] | ColorType: ...
+def validate_color_or_auto(s: Any) -> ColorType | Literal["auto"]: ...
+def validate_color_for_prop_cycle(s: Any) -> ColorType: ...
+def validate_color(s: Any) -> ColorType: ...
+def validate_colorlist(s: Any) -> list[ColorType]: ...
+def _validate_color_or_linecolor(
+ s: Any,
+) -> ColorType | Literal["linecolor", "markerfacecolor", "markeredgecolor"] | None: ...
+def validate_aspect(s: Any) -> Literal["auto", "equal"] | float: ...
+def validate_fontsize_None(
+ s: Any,
+) -> Literal[
+ "xx-small",
+ "x-small",
+ "small",
+ "medium",
+ "large",
+ "x-large",
+ "xx-large",
+ "smaller",
+ "larger",
+] | float | None: ...
+def validate_fontsize(
+ s: Any,
+) -> Literal[
+ "xx-small",
+ "x-small",
+ "small",
+ "medium",
+ "large",
+ "x-large",
+ "xx-large",
+ "smaller",
+ "larger",
+] | float: ...
+def validate_fontsizelist(
+ s: Any,
+) -> list[
+ Literal[
+ "xx-small",
+ "x-small",
+ "small",
+ "medium",
+ "large",
+ "x-large",
+ "xx-large",
+ "smaller",
+ "larger",
+ ]
+ | float
+]: ...
+def validate_fontweight(
+ s: Any,
+) -> Literal[
+ "ultralight",
+ "light",
+ "normal",
+ "regular",
+ "book",
+ "medium",
+ "roman",
+ "semibold",
+ "demibold",
+ "demi",
+ "bold",
+ "heavy",
+ "extra bold",
+ "black",
+] | int: ...
+def validate_fontstretch(
+ s: Any,
+) -> Literal[
+ "ultra-condensed",
+ "extra-condensed",
+ "condensed",
+ "semi-condensed",
+ "normal",
+ "semi-expanded",
+ "expanded",
+ "extra-expanded",
+ "ultra-expanded",
+] | int: ...
+def validate_font_properties(s: Any) -> dict[str, Any]: ...
+def validate_whiskers(s: Any) -> list[float] | float: ...
+def validate_ps_distiller(s: Any) -> None | Literal["ghostscript", "xpdf"]: ...
+
+validate_fillstyle: ValidateInStrings
+
+def validate_fillstylelist(
+ s: Any,
+) -> list[Literal["full", "left", "right", "bottom", "top", "none"]]: ...
+def validate_markevery(s: Any) -> MarkEveryType: ...
+def _validate_linestyle(s: Any) -> LineStyleType: ...
+def validate_markeverylist(s: Any) -> list[MarkEveryType]: ...
+def validate_bbox(s: Any) -> Literal["tight", "standard"] | None: ...
+def validate_sketch(s: Any) -> None | tuple[float, float, float]: ...
+def validate_hatch(s: Any) -> str: ...
+def validate_hatchlist(s: Any) -> list[str]: ...
+def validate_dashlist(s: Any) -> list[list[float]]: ...
+
+# TODO: copy cycler overloads?
+def cycler(*args, **kwargs) -> Cycler: ...
+def validate_cycler(s: Any) -> Cycler: ...
+def validate_hist_bins(
+ s: Any,
+) -> Literal["auto", "sturges", "fd", "doane", "scott", "rice", "sqrt"] | int | list[
+ float
+]: ...
+
+# At runtime is added in __init__.py
+defaultParams: dict[str, Any]
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sankey.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sankey.py
new file mode 100644
index 0000000000000000000000000000000000000000..637cfc849f9dc8ef3916883d5870bcbdde67e632
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sankey.py
@@ -0,0 +1,814 @@
+"""
+Module for creating Sankey diagrams using Matplotlib.
+"""
+
+import logging
+from types import SimpleNamespace
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib.path import Path
+from matplotlib.patches import PathPatch
+from matplotlib.transforms import Affine2D
+from matplotlib import _docstring
+
+_log = logging.getLogger(__name__)
+
+__author__ = "Kevin L. Davies"
+__credits__ = ["Yannick Copin"]
+__license__ = "BSD"
+__version__ = "2011/09/16"
+
+# Angles [deg/90]
+RIGHT = 0
+UP = 1
+# LEFT = 2
+DOWN = 3
+
+
+class Sankey:
+ """
+ Sankey diagram.
+
+ Sankey diagrams are a specific type of flow diagram, in which
+ the width of the arrows is shown proportionally to the flow
+ quantity. They are typically used to visualize energy or
+ material or cost transfers between processes.
+ `Wikipedia (6/1/2011) `_
+
+ """
+
+ def __init__(self, ax=None, scale=1.0, unit='', format='%G', gap=0.25,
+ radius=0.1, shoulder=0.03, offset=0.15, head_angle=100,
+ margin=0.4, tolerance=1e-6, **kwargs):
+ """
+ Create a new Sankey instance.
+
+ The optional arguments listed below are applied to all subdiagrams so
+ that there is consistent alignment and formatting.
+
+ In order to draw a complex Sankey diagram, create an instance of
+ `Sankey` by calling it without any kwargs::
+
+ sankey = Sankey()
+
+ Then add simple Sankey sub-diagrams::
+
+ sankey.add() # 1
+ sankey.add() # 2
+ #...
+ sankey.add() # n
+
+ Finally, create the full diagram::
+
+ sankey.finish()
+
+ Or, instead, simply daisy-chain those calls::
+
+ Sankey().add().add... .add().finish()
+
+ Other Parameters
+ ----------------
+ ax : `~matplotlib.axes.Axes`
+ Axes onto which the data should be plotted. If *ax* isn't
+ provided, new Axes will be created.
+ scale : float
+ Scaling factor for the flows. *scale* sizes the width of the paths
+ in order to maintain proper layout. The same scale is applied to
+ all subdiagrams. The value should be chosen such that the product
+ of the scale and the sum of the inputs is approximately 1.0 (and
+ the product of the scale and the sum of the outputs is
+ approximately -1.0).
+ unit : str
+ The physical unit associated with the flow quantities. If *unit*
+ is None, then none of the quantities are labeled.
+ format : str or callable
+ A Python number formatting string or callable used to label the
+ flows with their quantities (i.e., a number times a unit, where the
+ unit is given). If a format string is given, the label will be
+ ``format % quantity``. If a callable is given, it will be called
+ with ``quantity`` as an argument.
+ gap : float
+ Space between paths that break in/break away to/from the top or
+ bottom.
+ radius : float
+ Inner radius of the vertical paths.
+ shoulder : float
+ Size of the shoulders of output arrows.
+ offset : float
+ Text offset (from the dip or tip of the arrow).
+ head_angle : float
+ Angle, in degrees, of the arrow heads (and negative of the angle of
+ the tails).
+ margin : float
+ Minimum space between Sankey outlines and the edge of the plot
+ area.
+ tolerance : float
+ Acceptable maximum of the magnitude of the sum of flows. The
+ magnitude of the sum of connected flows cannot be greater than
+ *tolerance*.
+ **kwargs
+ Any additional keyword arguments will be passed to `add`, which
+ will create the first subdiagram.
+
+ See Also
+ --------
+ Sankey.add
+ Sankey.finish
+
+ Examples
+ --------
+ .. plot:: gallery/specialty_plots/sankey_basics.py
+ """
+ # Check the arguments.
+ if gap < 0:
+ raise ValueError(
+ "'gap' is negative, which is not allowed because it would "
+ "cause the paths to overlap")
+ if radius > gap:
+ raise ValueError(
+ "'radius' is greater than 'gap', which is not allowed because "
+ "it would cause the paths to overlap")
+ if head_angle < 0:
+ raise ValueError(
+ "'head_angle' is negative, which is not allowed because it "
+ "would cause inputs to look like outputs and vice versa")
+ if tolerance < 0:
+ raise ValueError(
+ "'tolerance' is negative, but it must be a magnitude")
+
+ # Create Axes if necessary.
+ if ax is None:
+ import matplotlib.pyplot as plt
+ fig = plt.figure()
+ ax = fig.add_subplot(1, 1, 1, xticks=[], yticks=[])
+
+ self.diagrams = []
+
+ # Store the inputs.
+ self.ax = ax
+ self.unit = unit
+ self.format = format
+ self.scale = scale
+ self.gap = gap
+ self.radius = radius
+ self.shoulder = shoulder
+ self.offset = offset
+ self.margin = margin
+ self.pitch = np.tan(np.pi * (1 - head_angle / 180.0) / 2.0)
+ self.tolerance = tolerance
+
+ # Initialize the vertices of tight box around the diagram(s).
+ self.extent = np.array((np.inf, -np.inf, np.inf, -np.inf))
+
+ # If there are any kwargs, create the first subdiagram.
+ if len(kwargs):
+ self.add(**kwargs)
+
+ def _arc(self, quadrant=0, cw=True, radius=1, center=(0, 0)):
+ """
+ Return the codes and vertices for a rotated, scaled, and translated
+ 90 degree arc.
+
+ Other Parameters
+ ----------------
+ quadrant : {0, 1, 2, 3}, default: 0
+ Uses 0-based indexing (0, 1, 2, or 3).
+ cw : bool, default: True
+ If True, the arc vertices are produced clockwise; counter-clockwise
+ otherwise.
+ radius : float, default: 1
+ The radius of the arc.
+ center : (float, float), default: (0, 0)
+ (x, y) tuple of the arc's center.
+ """
+ # Note: It would be possible to use matplotlib's transforms to rotate,
+ # scale, and translate the arc, but since the angles are discrete,
+ # it's just as easy and maybe more efficient to do it here.
+ ARC_CODES = [Path.LINETO,
+ Path.CURVE4,
+ Path.CURVE4,
+ Path.CURVE4,
+ Path.CURVE4,
+ Path.CURVE4,
+ Path.CURVE4]
+ # Vertices of a cubic Bezier curve approximating a 90 deg arc
+ # These can be determined by Path.arc(0, 90).
+ ARC_VERTICES = np.array([[1.00000000e+00, 0.00000000e+00],
+ [1.00000000e+00, 2.65114773e-01],
+ [8.94571235e-01, 5.19642327e-01],
+ [7.07106781e-01, 7.07106781e-01],
+ [5.19642327e-01, 8.94571235e-01],
+ [2.65114773e-01, 1.00000000e+00],
+ # Insignificant
+ # [6.12303177e-17, 1.00000000e+00]])
+ [0.00000000e+00, 1.00000000e+00]])
+ if quadrant in (0, 2):
+ if cw:
+ vertices = ARC_VERTICES
+ else:
+ vertices = ARC_VERTICES[:, ::-1] # Swap x and y.
+ else: # 1, 3
+ # Negate x.
+ if cw:
+ # Swap x and y.
+ vertices = np.column_stack((-ARC_VERTICES[:, 1],
+ ARC_VERTICES[:, 0]))
+ else:
+ vertices = np.column_stack((-ARC_VERTICES[:, 0],
+ ARC_VERTICES[:, 1]))
+ if quadrant > 1:
+ radius = -radius # Rotate 180 deg.
+ return list(zip(ARC_CODES, radius * vertices +
+ np.tile(center, (ARC_VERTICES.shape[0], 1))))
+
+ def _add_input(self, path, angle, flow, length):
+ """
+ Add an input to a path and return its tip and label locations.
+ """
+ if angle is None:
+ return [0, 0], [0, 0]
+ else:
+ x, y = path[-1][1] # Use the last point as a reference.
+ dipdepth = (flow / 2) * self.pitch
+ if angle == RIGHT:
+ x -= length
+ dip = [x + dipdepth, y + flow / 2.0]
+ path.extend([(Path.LINETO, [x, y]),
+ (Path.LINETO, dip),
+ (Path.LINETO, [x, y + flow]),
+ (Path.LINETO, [x + self.gap, y + flow])])
+ label_location = [dip[0] - self.offset, dip[1]]
+ else: # Vertical
+ x -= self.gap
+ if angle == UP:
+ sign = 1
+ else:
+ sign = -1
+
+ dip = [x - flow / 2, y - sign * (length - dipdepth)]
+ if angle == DOWN:
+ quadrant = 2
+ else:
+ quadrant = 1
+
+ # Inner arc isn't needed if inner radius is zero
+ if self.radius:
+ path.extend(self._arc(quadrant=quadrant,
+ cw=angle == UP,
+ radius=self.radius,
+ center=(x + self.radius,
+ y - sign * self.radius)))
+ else:
+ path.append((Path.LINETO, [x, y]))
+ path.extend([(Path.LINETO, [x, y - sign * length]),
+ (Path.LINETO, dip),
+ (Path.LINETO, [x - flow, y - sign * length])])
+ path.extend(self._arc(quadrant=quadrant,
+ cw=angle == DOWN,
+ radius=flow + self.radius,
+ center=(x + self.radius,
+ y - sign * self.radius)))
+ path.append((Path.LINETO, [x - flow, y + sign * flow]))
+ label_location = [dip[0], dip[1] - sign * self.offset]
+
+ return dip, label_location
+
+ def _add_output(self, path, angle, flow, length):
+ """
+ Append an output to a path and return its tip and label locations.
+
+ .. note:: *flow* is negative for an output.
+ """
+ if angle is None:
+ return [0, 0], [0, 0]
+ else:
+ x, y = path[-1][1] # Use the last point as a reference.
+ tipheight = (self.shoulder - flow / 2) * self.pitch
+ if angle == RIGHT:
+ x += length
+ tip = [x + tipheight, y + flow / 2.0]
+ path.extend([(Path.LINETO, [x, y]),
+ (Path.LINETO, [x, y + self.shoulder]),
+ (Path.LINETO, tip),
+ (Path.LINETO, [x, y - self.shoulder + flow]),
+ (Path.LINETO, [x, y + flow]),
+ (Path.LINETO, [x - self.gap, y + flow])])
+ label_location = [tip[0] + self.offset, tip[1]]
+ else: # Vertical
+ x += self.gap
+ if angle == UP:
+ sign, quadrant = 1, 3
+ else:
+ sign, quadrant = -1, 0
+
+ tip = [x - flow / 2.0, y + sign * (length + tipheight)]
+ # Inner arc isn't needed if inner radius is zero
+ if self.radius:
+ path.extend(self._arc(quadrant=quadrant,
+ cw=angle == UP,
+ radius=self.radius,
+ center=(x - self.radius,
+ y + sign * self.radius)))
+ else:
+ path.append((Path.LINETO, [x, y]))
+ path.extend([(Path.LINETO, [x, y + sign * length]),
+ (Path.LINETO, [x - self.shoulder,
+ y + sign * length]),
+ (Path.LINETO, tip),
+ (Path.LINETO, [x + self.shoulder - flow,
+ y + sign * length]),
+ (Path.LINETO, [x - flow, y + sign * length])])
+ path.extend(self._arc(quadrant=quadrant,
+ cw=angle == DOWN,
+ radius=self.radius - flow,
+ center=(x - self.radius,
+ y + sign * self.radius)))
+ path.append((Path.LINETO, [x - flow, y + sign * flow]))
+ label_location = [tip[0], tip[1] + sign * self.offset]
+ return tip, label_location
+
+ def _revert(self, path, first_action=Path.LINETO):
+ """
+ A path is not simply reversible by path[::-1] since the code
+ specifies an action to take from the **previous** point.
+ """
+ reverse_path = []
+ next_code = first_action
+ for code, position in path[::-1]:
+ reverse_path.append((next_code, position))
+ next_code = code
+ return reverse_path
+ # This might be more efficient, but it fails because 'tuple' object
+ # doesn't support item assignment:
+ # path[1] = path[1][-1:0:-1]
+ # path[1][0] = first_action
+ # path[2] = path[2][::-1]
+ # return path
+
+ @_docstring.interpd
+ def add(self, patchlabel='', flows=None, orientations=None, labels='',
+ trunklength=1.0, pathlengths=0.25, prior=None, connect=(0, 0),
+ rotation=0, **kwargs):
+ """
+ Add a simple Sankey diagram with flows at the same hierarchical level.
+
+ Parameters
+ ----------
+ patchlabel : str
+ Label to be placed at the center of the diagram.
+ Note that *label* (not *patchlabel*) can be passed as keyword
+ argument to create an entry in the legend.
+
+ flows : list of float
+ Array of flow values. By convention, inputs are positive and
+ outputs are negative.
+
+ Flows are placed along the top of the diagram from the inside out
+ in order of their index within *flows*. They are placed along the
+ sides of the diagram from the top down and along the bottom from
+ the outside in.
+
+ If the sum of the inputs and outputs is
+ nonzero, the discrepancy will appear as a cubic Bézier curve along
+ the top and bottom edges of the trunk.
+
+ orientations : list of {-1, 0, 1}
+ List of orientations of the flows (or a single orientation to be
+ used for all flows). Valid values are 0 (inputs from
+ the left, outputs to the right), 1 (from and to the top) or -1
+ (from and to the bottom).
+
+ labels : list of (str or None)
+ List of labels for the flows (or a single label to be used for all
+ flows). Each label may be *None* (no label), or a labeling string.
+ If an entry is a (possibly empty) string, then the quantity for the
+ corresponding flow will be shown below the string. However, if
+ the *unit* of the main diagram is None, then quantities are never
+ shown, regardless of the value of this argument.
+
+ trunklength : float
+ Length between the bases of the input and output groups (in
+ data-space units).
+
+ pathlengths : list of float
+ List of lengths of the vertical arrows before break-in or after
+ break-away. If a single value is given, then it will be applied to
+ the first (inside) paths on the top and bottom, and the length of
+ all other arrows will be justified accordingly. The *pathlengths*
+ are not applied to the horizontal inputs and outputs.
+
+ prior : int
+ Index of the prior diagram to which this diagram should be
+ connected.
+
+ connect : (int, int)
+ A (prior, this) tuple indexing the flow of the prior diagram and
+ the flow of this diagram which should be connected. If this is the
+ first diagram or *prior* is *None*, *connect* will be ignored.
+
+ rotation : float
+ Angle of rotation of the diagram in degrees. The interpretation of
+ the *orientations* argument will be rotated accordingly (e.g., if
+ *rotation* == 90, an *orientations* entry of 1 means to/from the
+ left). *rotation* is ignored if this diagram is connected to an
+ existing one (using *prior* and *connect*).
+
+ Returns
+ -------
+ Sankey
+ The current `.Sankey` instance.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ Additional keyword arguments set `matplotlib.patches.PathPatch`
+ properties, listed below. For example, one may want to use
+ ``fill=False`` or ``label="A legend entry"``.
+
+ %(Patch:kwdoc)s
+
+ See Also
+ --------
+ Sankey.finish
+ """
+ # Check and preprocess the arguments.
+ flows = np.array([1.0, -1.0]) if flows is None else np.array(flows)
+ n = flows.shape[0] # Number of flows
+ if rotation is None:
+ rotation = 0
+ else:
+ # In the code below, angles are expressed in deg/90.
+ rotation /= 90.0
+ if orientations is None:
+ orientations = 0
+ try:
+ orientations = np.broadcast_to(orientations, n)
+ except ValueError:
+ raise ValueError(
+ f"The shapes of 'flows' {np.shape(flows)} and 'orientations' "
+ f"{np.shape(orientations)} are incompatible"
+ ) from None
+ try:
+ labels = np.broadcast_to(labels, n)
+ except ValueError:
+ raise ValueError(
+ f"The shapes of 'flows' {np.shape(flows)} and 'labels' "
+ f"{np.shape(labels)} are incompatible"
+ ) from None
+ if trunklength < 0:
+ raise ValueError(
+ "'trunklength' is negative, which is not allowed because it "
+ "would cause poor layout")
+ if abs(np.sum(flows)) > self.tolerance:
+ _log.info("The sum of the flows is nonzero (%f; patchlabel=%r); "
+ "is the system not at steady state?",
+ np.sum(flows), patchlabel)
+ scaled_flows = self.scale * flows
+ gain = sum(max(flow, 0) for flow in scaled_flows)
+ loss = sum(min(flow, 0) for flow in scaled_flows)
+ if prior is not None:
+ if prior < 0:
+ raise ValueError("The index of the prior diagram is negative")
+ if min(connect) < 0:
+ raise ValueError(
+ "At least one of the connection indices is negative")
+ if prior >= len(self.diagrams):
+ raise ValueError(
+ f"The index of the prior diagram is {prior}, but there "
+ f"are only {len(self.diagrams)} other diagrams")
+ if connect[0] >= len(self.diagrams[prior].flows):
+ raise ValueError(
+ "The connection index to the source diagram is {}, but "
+ "that diagram has only {} flows".format(
+ connect[0], len(self.diagrams[prior].flows)))
+ if connect[1] >= n:
+ raise ValueError(
+ f"The connection index to this diagram is {connect[1]}, "
+ f"but this diagram has only {n} flows")
+ if self.diagrams[prior].angles[connect[0]] is None:
+ raise ValueError(
+ f"The connection cannot be made, which may occur if the "
+ f"magnitude of flow {connect[0]} of diagram {prior} is "
+ f"less than the specified tolerance")
+ flow_error = (self.diagrams[prior].flows[connect[0]] +
+ flows[connect[1]])
+ if abs(flow_error) >= self.tolerance:
+ raise ValueError(
+ f"The scaled sum of the connected flows is {flow_error}, "
+ f"which is not within the tolerance ({self.tolerance})")
+
+ # Determine if the flows are inputs.
+ are_inputs = [None] * n
+ for i, flow in enumerate(flows):
+ if flow >= self.tolerance:
+ are_inputs[i] = True
+ elif flow <= -self.tolerance:
+ are_inputs[i] = False
+ else:
+ _log.info(
+ "The magnitude of flow %d (%f) is below the tolerance "
+ "(%f).\nIt will not be shown, and it cannot be used in a "
+ "connection.", i, flow, self.tolerance)
+
+ # Determine the angles of the arrows (before rotation).
+ angles = [None] * n
+ for i, (orient, is_input) in enumerate(zip(orientations, are_inputs)):
+ if orient == 1:
+ if is_input:
+ angles[i] = DOWN
+ elif is_input is False:
+ # Be specific since is_input can be None.
+ angles[i] = UP
+ elif orient == 0:
+ if is_input is not None:
+ angles[i] = RIGHT
+ else:
+ if orient != -1:
+ raise ValueError(
+ f"The value of orientations[{i}] is {orient}, "
+ f"but it must be -1, 0, or 1")
+ if is_input:
+ angles[i] = UP
+ elif is_input is False:
+ angles[i] = DOWN
+
+ # Justify the lengths of the paths.
+ if np.iterable(pathlengths):
+ if len(pathlengths) != n:
+ raise ValueError(
+ f"The lengths of 'flows' ({n}) and 'pathlengths' "
+ f"({len(pathlengths)}) are incompatible")
+ else: # Make pathlengths into a list.
+ urlength = pathlengths
+ ullength = pathlengths
+ lrlength = pathlengths
+ lllength = pathlengths
+ d = dict(RIGHT=pathlengths)
+ pathlengths = [d.get(angle, 0) for angle in angles]
+ # Determine the lengths of the top-side arrows
+ # from the middle outwards.
+ for i, (angle, is_input, flow) in enumerate(zip(angles, are_inputs,
+ scaled_flows)):
+ if angle == DOWN and is_input:
+ pathlengths[i] = ullength
+ ullength += flow
+ elif angle == UP and is_input is False:
+ pathlengths[i] = urlength
+ urlength -= flow # Flow is negative for outputs.
+ # Determine the lengths of the bottom-side arrows
+ # from the middle outwards.
+ for i, (angle, is_input, flow) in enumerate(reversed(list(zip(
+ angles, are_inputs, scaled_flows)))):
+ if angle == UP and is_input:
+ pathlengths[n - i - 1] = lllength
+ lllength += flow
+ elif angle == DOWN and is_input is False:
+ pathlengths[n - i - 1] = lrlength
+ lrlength -= flow
+ # Determine the lengths of the left-side arrows
+ # from the bottom upwards.
+ has_left_input = False
+ for i, (angle, is_input, spec) in enumerate(reversed(list(zip(
+ angles, are_inputs, zip(scaled_flows, pathlengths))))):
+ if angle == RIGHT:
+ if is_input:
+ if has_left_input:
+ pathlengths[n - i - 1] = 0
+ else:
+ has_left_input = True
+ # Determine the lengths of the right-side arrows
+ # from the top downwards.
+ has_right_output = False
+ for i, (angle, is_input, spec) in enumerate(zip(
+ angles, are_inputs, list(zip(scaled_flows, pathlengths)))):
+ if angle == RIGHT:
+ if is_input is False:
+ if has_right_output:
+ pathlengths[i] = 0
+ else:
+ has_right_output = True
+
+ # Begin the subpaths, and smooth the transition if the sum of the flows
+ # is nonzero.
+ urpath = [(Path.MOVETO, [(self.gap - trunklength / 2.0), # Upper right
+ gain / 2.0]),
+ (Path.LINETO, [(self.gap - trunklength / 2.0) / 2.0,
+ gain / 2.0]),
+ (Path.CURVE4, [(self.gap - trunklength / 2.0) / 8.0,
+ gain / 2.0]),
+ (Path.CURVE4, [(trunklength / 2.0 - self.gap) / 8.0,
+ -loss / 2.0]),
+ (Path.LINETO, [(trunklength / 2.0 - self.gap) / 2.0,
+ -loss / 2.0]),
+ (Path.LINETO, [(trunklength / 2.0 - self.gap),
+ -loss / 2.0])]
+ llpath = [(Path.LINETO, [(trunklength / 2.0 - self.gap), # Lower left
+ loss / 2.0]),
+ (Path.LINETO, [(trunklength / 2.0 - self.gap) / 2.0,
+ loss / 2.0]),
+ (Path.CURVE4, [(trunklength / 2.0 - self.gap) / 8.0,
+ loss / 2.0]),
+ (Path.CURVE4, [(self.gap - trunklength / 2.0) / 8.0,
+ -gain / 2.0]),
+ (Path.LINETO, [(self.gap - trunklength / 2.0) / 2.0,
+ -gain / 2.0]),
+ (Path.LINETO, [(self.gap - trunklength / 2.0),
+ -gain / 2.0])]
+ lrpath = [(Path.LINETO, [(trunklength / 2.0 - self.gap), # Lower right
+ loss / 2.0])]
+ ulpath = [(Path.LINETO, [self.gap - trunklength / 2.0, # Upper left
+ gain / 2.0])]
+
+ # Add the subpaths and assign the locations of the tips and labels.
+ tips = np.zeros((n, 2))
+ label_locations = np.zeros((n, 2))
+ # Add the top-side inputs and outputs from the middle outwards.
+ for i, (angle, is_input, spec) in enumerate(zip(
+ angles, are_inputs, list(zip(scaled_flows, pathlengths)))):
+ if angle == DOWN and is_input:
+ tips[i, :], label_locations[i, :] = self._add_input(
+ ulpath, angle, *spec)
+ elif angle == UP and is_input is False:
+ tips[i, :], label_locations[i, :] = self._add_output(
+ urpath, angle, *spec)
+ # Add the bottom-side inputs and outputs from the middle outwards.
+ for i, (angle, is_input, spec) in enumerate(reversed(list(zip(
+ angles, are_inputs, list(zip(scaled_flows, pathlengths)))))):
+ if angle == UP and is_input:
+ tip, label_location = self._add_input(llpath, angle, *spec)
+ tips[n - i - 1, :] = tip
+ label_locations[n - i - 1, :] = label_location
+ elif angle == DOWN and is_input is False:
+ tip, label_location = self._add_output(lrpath, angle, *spec)
+ tips[n - i - 1, :] = tip
+ label_locations[n - i - 1, :] = label_location
+ # Add the left-side inputs from the bottom upwards.
+ has_left_input = False
+ for i, (angle, is_input, spec) in enumerate(reversed(list(zip(
+ angles, are_inputs, list(zip(scaled_flows, pathlengths)))))):
+ if angle == RIGHT and is_input:
+ if not has_left_input:
+ # Make sure the lower path extends
+ # at least as far as the upper one.
+ if llpath[-1][1][0] > ulpath[-1][1][0]:
+ llpath.append((Path.LINETO, [ulpath[-1][1][0],
+ llpath[-1][1][1]]))
+ has_left_input = True
+ tip, label_location = self._add_input(llpath, angle, *spec)
+ tips[n - i - 1, :] = tip
+ label_locations[n - i - 1, :] = label_location
+ # Add the right-side outputs from the top downwards.
+ has_right_output = False
+ for i, (angle, is_input, spec) in enumerate(zip(
+ angles, are_inputs, list(zip(scaled_flows, pathlengths)))):
+ if angle == RIGHT and is_input is False:
+ if not has_right_output:
+ # Make sure the upper path extends
+ # at least as far as the lower one.
+ if urpath[-1][1][0] < lrpath[-1][1][0]:
+ urpath.append((Path.LINETO, [lrpath[-1][1][0],
+ urpath[-1][1][1]]))
+ has_right_output = True
+ tips[i, :], label_locations[i, :] = self._add_output(
+ urpath, angle, *spec)
+ # Trim any hanging vertices.
+ if not has_left_input:
+ ulpath.pop()
+ llpath.pop()
+ if not has_right_output:
+ lrpath.pop()
+ urpath.pop()
+
+ # Concatenate the subpaths in the correct order (clockwise from top).
+ path = (urpath + self._revert(lrpath) + llpath + self._revert(ulpath) +
+ [(Path.CLOSEPOLY, urpath[0][1])])
+
+ # Create a patch with the Sankey outline.
+ codes, vertices = zip(*path)
+ vertices = np.array(vertices)
+
+ def _get_angle(a, r):
+ if a is None:
+ return None
+ else:
+ return a + r
+
+ if prior is None:
+ if rotation != 0: # By default, none of this is needed.
+ angles = [_get_angle(angle, rotation) for angle in angles]
+ rotate = Affine2D().rotate_deg(rotation * 90).transform_affine
+ tips = rotate(tips)
+ label_locations = rotate(label_locations)
+ vertices = rotate(vertices)
+ text = self.ax.text(0, 0, s=patchlabel, ha='center', va='center')
+ else:
+ rotation = (self.diagrams[prior].angles[connect[0]] -
+ angles[connect[1]])
+ angles = [_get_angle(angle, rotation) for angle in angles]
+ rotate = Affine2D().rotate_deg(rotation * 90).transform_affine
+ tips = rotate(tips)
+ offset = self.diagrams[prior].tips[connect[0]] - tips[connect[1]]
+ translate = Affine2D().translate(*offset).transform_affine
+ tips = translate(tips)
+ label_locations = translate(rotate(label_locations))
+ vertices = translate(rotate(vertices))
+ kwds = dict(s=patchlabel, ha='center', va='center')
+ text = self.ax.text(*offset, **kwds)
+ if mpl.rcParams['_internal.classic_mode']:
+ fc = kwargs.pop('fc', kwargs.pop('facecolor', '#bfd1d4'))
+ lw = kwargs.pop('lw', kwargs.pop('linewidth', 0.5))
+ else:
+ fc = kwargs.pop('fc', kwargs.pop('facecolor', None))
+ lw = kwargs.pop('lw', kwargs.pop('linewidth', None))
+ if fc is None:
+ fc = self.ax._get_patches_for_fill.get_next_color()
+ patch = PathPatch(Path(vertices, codes), fc=fc, lw=lw, **kwargs)
+ self.ax.add_patch(patch)
+
+ # Add the path labels.
+ texts = []
+ for number, angle, label, location in zip(flows, angles, labels,
+ label_locations):
+ if label is None or angle is None:
+ label = ''
+ elif self.unit is not None:
+ if isinstance(self.format, str):
+ quantity = self.format % abs(number) + self.unit
+ elif callable(self.format):
+ quantity = self.format(number)
+ else:
+ raise TypeError(
+ 'format must be callable or a format string')
+ if label != '':
+ label += "\n"
+ label += quantity
+ texts.append(self.ax.text(x=location[0], y=location[1],
+ s=label,
+ ha='center', va='center'))
+ # Text objects are placed even they are empty (as long as the magnitude
+ # of the corresponding flow is larger than the tolerance) in case the
+ # user wants to provide labels later.
+
+ # Expand the size of the diagram if necessary.
+ self.extent = (min(np.min(vertices[:, 0]),
+ np.min(label_locations[:, 0]),
+ self.extent[0]),
+ max(np.max(vertices[:, 0]),
+ np.max(label_locations[:, 0]),
+ self.extent[1]),
+ min(np.min(vertices[:, 1]),
+ np.min(label_locations[:, 1]),
+ self.extent[2]),
+ max(np.max(vertices[:, 1]),
+ np.max(label_locations[:, 1]),
+ self.extent[3]))
+ # Include both vertices _and_ label locations in the extents; there are
+ # where either could determine the margins (e.g., arrow shoulders).
+
+ # Add this diagram as a subdiagram.
+ self.diagrams.append(
+ SimpleNamespace(patch=patch, flows=flows, angles=angles, tips=tips,
+ text=text, texts=texts))
+
+ # Allow a daisy-chained call structure (see docstring for the class).
+ return self
+
+ def finish(self):
+ """
+ Adjust the Axes and return a list of information about the Sankey
+ subdiagram(s).
+
+ Returns a list of subdiagrams with the following fields:
+
+ ======== =============================================================
+ Field Description
+ ======== =============================================================
+ *patch* Sankey outline (a `~matplotlib.patches.PathPatch`).
+ *flows* Flow values (positive for input, negative for output).
+ *angles* List of angles of the arrows [deg/90].
+ For example, if the diagram has not been rotated,
+ an input to the top side has an angle of 3 (DOWN),
+ and an output from the top side has an angle of 1 (UP).
+ If a flow has been skipped (because its magnitude is less
+ than *tolerance*), then its angle will be *None*.
+ *tips* (N, 2)-array of the (x, y) positions of the tips (or "dips")
+ of the flow paths.
+ If the magnitude of a flow is less the *tolerance* of this
+ `Sankey` instance, the flow is skipped and its tip will be at
+ the center of the diagram.
+ *text* `.Text` instance for the diagram label.
+ *texts* List of `.Text` instances for the flow labels.
+ ======== =============================================================
+
+ See Also
+ --------
+ Sankey.add
+ """
+ self.ax.axis([self.extent[0] - self.margin,
+ self.extent[1] + self.margin,
+ self.extent[2] - self.margin,
+ self.extent[3] + self.margin])
+ self.ax.set_aspect('equal', adjustable='datalim')
+ return self.diagrams
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sankey.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sankey.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..33565b998a9caf00ead62bdfa0adb50e272dd6ac
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sankey.pyi
@@ -0,0 +1,61 @@
+from matplotlib.axes import Axes
+
+from collections.abc import Callable, Iterable
+from typing import Any
+from typing_extensions import Self # < Py 3.11
+
+import numpy as np
+
+__license__: str
+__credits__: list[str]
+__author__: str
+__version__: str
+
+RIGHT: int
+UP: int
+DOWN: int
+
+# TODO typing units
+class Sankey:
+ diagrams: list[Any]
+ ax: Axes
+ unit: Any
+ format: str | Callable[[float], str]
+ scale: float
+ gap: float
+ radius: float
+ shoulder: float
+ offset: float
+ margin: float
+ pitch: float
+ tolerance: float
+ extent: np.ndarray
+ def __init__(
+ self,
+ ax: Axes | None = ...,
+ scale: float = ...,
+ unit: Any = ...,
+ format: str | Callable[[float], str] = ...,
+ gap: float = ...,
+ radius: float = ...,
+ shoulder: float = ...,
+ offset: float = ...,
+ head_angle: float = ...,
+ margin: float = ...,
+ tolerance: float = ...,
+ **kwargs
+ ) -> None: ...
+ def add(
+ self,
+ patchlabel: str = ...,
+ flows: Iterable[float] | None = ...,
+ orientations: Iterable[int] | None = ...,
+ labels: str | Iterable[str | None] = ...,
+ trunklength: float = ...,
+ pathlengths: float | Iterable[float] = ...,
+ prior: int | None = ...,
+ connect: tuple[int, int] = ...,
+ rotation: float = ...,
+ **kwargs
+ ) -> Self: ...
+ def finish(self) -> list[Any]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/scale.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/scale.py
new file mode 100644
index 0000000000000000000000000000000000000000..7630ba6e54b91dacf533aa196ab12c5570cc76d2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/scale.py
@@ -0,0 +1,767 @@
+"""
+Scales define the distribution of data values on an axis, e.g. a log scaling.
+
+The mapping is implemented through `.Transform` subclasses.
+
+The following scales are built-in:
+
+.. _builtin_scales:
+
+============= ===================== ================================ =================================
+Name Class Transform Inverted transform
+============= ===================== ================================ =================================
+"asinh" `AsinhScale` `AsinhTransform` `InvertedAsinhTransform`
+"function" `FuncScale` `FuncTransform` `FuncTransform`
+"functionlog" `FuncScaleLog` `FuncTransform` + `LogTransform` `InvertedLogTransform` + `FuncTransform`
+"linear" `LinearScale` `.IdentityTransform` `.IdentityTransform`
+"log" `LogScale` `LogTransform` `InvertedLogTransform`
+"logit" `LogitScale` `LogitTransform` `LogisticTransform`
+"symlog" `SymmetricalLogScale` `SymmetricalLogTransform` `InvertedSymmetricalLogTransform`
+============= ===================== ================================ =================================
+
+A user will often only use the scale name, e.g. when setting the scale through
+`~.Axes.set_xscale`: ``ax.set_xscale("log")``.
+
+See also the :ref:`scales examples ` in the documentation.
+
+Custom scaling can be achieved through `FuncScale`, or by creating your own
+`ScaleBase` subclass and corresponding transforms (see :doc:`/gallery/scales/custom_scale`).
+Third parties can register their scales by name through `register_scale`.
+""" # noqa: E501
+
+import inspect
+import textwrap
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api, _docstring
+from matplotlib.ticker import (
+ NullFormatter, ScalarFormatter, LogFormatterSciNotation, LogitFormatter,
+ NullLocator, LogLocator, AutoLocator, AutoMinorLocator,
+ SymmetricalLogLocator, AsinhLocator, LogitLocator)
+from matplotlib.transforms import Transform, IdentityTransform
+
+
+class ScaleBase:
+ """
+ The base class for all scales.
+
+ Scales are separable transformations, working on a single dimension.
+
+ Subclasses should override
+
+ :attr:`name`
+ The scale's name.
+ :meth:`get_transform`
+ A method returning a `.Transform`, which converts data coordinates to
+ scaled coordinates. This transform should be invertible, so that e.g.
+ mouse positions can be converted back to data coordinates.
+ :meth:`set_default_locators_and_formatters`
+ A method that sets default locators and formatters for an `~.axis.Axis`
+ that uses this scale.
+ :meth:`limit_range_for_scale`
+ An optional method that "fixes" the axis range to acceptable values,
+ e.g. restricting log-scaled axes to positive values.
+ """
+
+ def __init__(self, axis):
+ r"""
+ Construct a new scale.
+
+ Notes
+ -----
+ The following note is for scale implementers.
+
+ For back-compatibility reasons, scales take an `~matplotlib.axis.Axis`
+ object as first argument. However, this argument should not
+ be used: a single scale object should be usable by multiple
+ `~matplotlib.axis.Axis`\es at the same time.
+ """
+
+ def get_transform(self):
+ """
+ Return the `.Transform` object associated with this scale.
+ """
+ raise NotImplementedError()
+
+ def set_default_locators_and_formatters(self, axis):
+ """
+ Set the locators and formatters of *axis* to instances suitable for
+ this scale.
+ """
+ raise NotImplementedError()
+
+ def limit_range_for_scale(self, vmin, vmax, minpos):
+ """
+ Return the range *vmin*, *vmax*, restricted to the
+ domain supported by this scale (if any).
+
+ *minpos* should be the minimum positive value in the data.
+ This is used by log scales to determine a minimum value.
+ """
+ return vmin, vmax
+
+
+class LinearScale(ScaleBase):
+ """
+ The default linear scale.
+ """
+
+ name = 'linear'
+
+ def __init__(self, axis):
+ # This method is present only to prevent inheritance of the base class'
+ # constructor docstring, which would otherwise end up interpolated into
+ # the docstring of Axis.set_scale.
+ """
+ """ # noqa: D419
+
+ def set_default_locators_and_formatters(self, axis):
+ # docstring inherited
+ axis.set_major_locator(AutoLocator())
+ axis.set_major_formatter(ScalarFormatter())
+ axis.set_minor_formatter(NullFormatter())
+ # update the minor locator for x and y axis based on rcParams
+ if (axis.axis_name == 'x' and mpl.rcParams['xtick.minor.visible'] or
+ axis.axis_name == 'y' and mpl.rcParams['ytick.minor.visible']):
+ axis.set_minor_locator(AutoMinorLocator())
+ else:
+ axis.set_minor_locator(NullLocator())
+
+ def get_transform(self):
+ """
+ Return the transform for linear scaling, which is just the
+ `~matplotlib.transforms.IdentityTransform`.
+ """
+ return IdentityTransform()
+
+
+class FuncTransform(Transform):
+ """
+ A simple transform that takes and arbitrary function for the
+ forward and inverse transform.
+ """
+
+ input_dims = output_dims = 1
+
+ def __init__(self, forward, inverse):
+ """
+ Parameters
+ ----------
+ forward : callable
+ The forward function for the transform. This function must have
+ an inverse and, for best behavior, be monotonic.
+ It must have the signature::
+
+ def forward(values: array-like) -> array-like
+
+ inverse : callable
+ The inverse of the forward function. Signature as ``forward``.
+ """
+ super().__init__()
+ if callable(forward) and callable(inverse):
+ self._forward = forward
+ self._inverse = inverse
+ else:
+ raise ValueError('arguments to FuncTransform must be functions')
+
+ def transform_non_affine(self, values):
+ return self._forward(values)
+
+ def inverted(self):
+ return FuncTransform(self._inverse, self._forward)
+
+
+class FuncScale(ScaleBase):
+ """
+ Provide an arbitrary scale with user-supplied function for the axis.
+ """
+
+ name = 'function'
+
+ def __init__(self, axis, functions):
+ """
+ Parameters
+ ----------
+ axis : `~matplotlib.axis.Axis`
+ The axis for the scale.
+ functions : (callable, callable)
+ two-tuple of the forward and inverse functions for the scale.
+ The forward function must be monotonic.
+
+ Both functions must have the signature::
+
+ def forward(values: array-like) -> array-like
+ """
+ forward, inverse = functions
+ transform = FuncTransform(forward, inverse)
+ self._transform = transform
+
+ def get_transform(self):
+ """Return the `.FuncTransform` associated with this scale."""
+ return self._transform
+
+ def set_default_locators_and_formatters(self, axis):
+ # docstring inherited
+ axis.set_major_locator(AutoLocator())
+ axis.set_major_formatter(ScalarFormatter())
+ axis.set_minor_formatter(NullFormatter())
+ # update the minor locator for x and y axis based on rcParams
+ if (axis.axis_name == 'x' and mpl.rcParams['xtick.minor.visible'] or
+ axis.axis_name == 'y' and mpl.rcParams['ytick.minor.visible']):
+ axis.set_minor_locator(AutoMinorLocator())
+ else:
+ axis.set_minor_locator(NullLocator())
+
+
+class LogTransform(Transform):
+ input_dims = output_dims = 1
+
+ def __init__(self, base, nonpositive='clip'):
+ super().__init__()
+ if base <= 0 or base == 1:
+ raise ValueError('The log base cannot be <= 0 or == 1')
+ self.base = base
+ self._clip = _api.check_getitem(
+ {"clip": True, "mask": False}, nonpositive=nonpositive)
+
+ def __str__(self):
+ return "{}(base={}, nonpositive={!r})".format(
+ type(self).__name__, self.base, "clip" if self._clip else "mask")
+
+ def transform_non_affine(self, values):
+ # Ignore invalid values due to nans being passed to the transform.
+ with np.errstate(divide="ignore", invalid="ignore"):
+ log = {np.e: np.log, 2: np.log2, 10: np.log10}.get(self.base)
+ if log: # If possible, do everything in a single call to NumPy.
+ out = log(values)
+ else:
+ out = np.log(values)
+ out /= np.log(self.base)
+ if self._clip:
+ # SVG spec says that conforming viewers must support values up
+ # to 3.4e38 (C float); however experiments suggest that
+ # Inkscape (which uses cairo for rendering) runs into cairo's
+ # 24-bit limit (which is apparently shared by Agg).
+ # Ghostscript (used for pdf rendering appears to overflow even
+ # earlier, with the max value around 2 ** 15 for the tests to
+ # pass. On the other hand, in practice, we want to clip beyond
+ # np.log10(np.nextafter(0, 1)) ~ -323
+ # so 1000 seems safe.
+ out[values <= 0] = -1000
+ return out
+
+ def inverted(self):
+ return InvertedLogTransform(self.base)
+
+
+class InvertedLogTransform(Transform):
+ input_dims = output_dims = 1
+
+ def __init__(self, base):
+ super().__init__()
+ self.base = base
+
+ def __str__(self):
+ return f"{type(self).__name__}(base={self.base})"
+
+ def transform_non_affine(self, values):
+ return np.power(self.base, values)
+
+ def inverted(self):
+ return LogTransform(self.base)
+
+
+class LogScale(ScaleBase):
+ """
+ A standard logarithmic scale. Care is taken to only plot positive values.
+ """
+ name = 'log'
+
+ def __init__(self, axis, *, base=10, subs=None, nonpositive="clip"):
+ """
+ Parameters
+ ----------
+ axis : `~matplotlib.axis.Axis`
+ The axis for the scale.
+ base : float, default: 10
+ The base of the logarithm.
+ nonpositive : {'clip', 'mask'}, default: 'clip'
+ Determines the behavior for non-positive values. They can either
+ be masked as invalid, or clipped to a very small positive number.
+ subs : sequence of int, default: None
+ Where to place the subticks between each major tick. For example,
+ in a log10 scale, ``[2, 3, 4, 5, 6, 7, 8, 9]`` will place 8
+ logarithmically spaced minor ticks between each major tick.
+ """
+ self._transform = LogTransform(base, nonpositive)
+ self.subs = subs
+
+ base = property(lambda self: self._transform.base)
+
+ def set_default_locators_and_formatters(self, axis):
+ # docstring inherited
+ axis.set_major_locator(LogLocator(self.base))
+ axis.set_major_formatter(LogFormatterSciNotation(self.base))
+ axis.set_minor_locator(LogLocator(self.base, self.subs))
+ axis.set_minor_formatter(
+ LogFormatterSciNotation(self.base,
+ labelOnlyBase=(self.subs is not None)))
+
+ def get_transform(self):
+ """Return the `.LogTransform` associated with this scale."""
+ return self._transform
+
+ def limit_range_for_scale(self, vmin, vmax, minpos):
+ """Limit the domain to positive values."""
+ if not np.isfinite(minpos):
+ minpos = 1e-300 # Should rarely (if ever) have a visible effect.
+
+ return (minpos if vmin <= 0 else vmin,
+ minpos if vmax <= 0 else vmax)
+
+
+class FuncScaleLog(LogScale):
+ """
+ Provide an arbitrary scale with user-supplied function for the axis and
+ then put on a logarithmic axes.
+ """
+
+ name = 'functionlog'
+
+ def __init__(self, axis, functions, base=10):
+ """
+ Parameters
+ ----------
+ axis : `~matplotlib.axis.Axis`
+ The axis for the scale.
+ functions : (callable, callable)
+ two-tuple of the forward and inverse functions for the scale.
+ The forward function must be monotonic.
+
+ Both functions must have the signature::
+
+ def forward(values: array-like) -> array-like
+
+ base : float, default: 10
+ Logarithmic base of the scale.
+ """
+ forward, inverse = functions
+ self.subs = None
+ self._transform = FuncTransform(forward, inverse) + LogTransform(base)
+
+ @property
+ def base(self):
+ return self._transform._b.base # Base of the LogTransform.
+
+ def get_transform(self):
+ """Return the `.Transform` associated with this scale."""
+ return self._transform
+
+
+class SymmetricalLogTransform(Transform):
+ input_dims = output_dims = 1
+
+ def __init__(self, base, linthresh, linscale):
+ super().__init__()
+ if base <= 1.0:
+ raise ValueError("'base' must be larger than 1")
+ if linthresh <= 0.0:
+ raise ValueError("'linthresh' must be positive")
+ if linscale <= 0.0:
+ raise ValueError("'linscale' must be positive")
+ self.base = base
+ self.linthresh = linthresh
+ self.linscale = linscale
+ self._linscale_adj = (linscale / (1.0 - self.base ** -1))
+ self._log_base = np.log(base)
+
+ def transform_non_affine(self, values):
+ abs_a = np.abs(values)
+ with np.errstate(divide="ignore", invalid="ignore"):
+ out = np.sign(values) * self.linthresh * (
+ self._linscale_adj +
+ np.log(abs_a / self.linthresh) / self._log_base)
+ inside = abs_a <= self.linthresh
+ out[inside] = values[inside] * self._linscale_adj
+ return out
+
+ def inverted(self):
+ return InvertedSymmetricalLogTransform(self.base, self.linthresh,
+ self.linscale)
+
+
+class InvertedSymmetricalLogTransform(Transform):
+ input_dims = output_dims = 1
+
+ def __init__(self, base, linthresh, linscale):
+ super().__init__()
+ symlog = SymmetricalLogTransform(base, linthresh, linscale)
+ self.base = base
+ self.linthresh = linthresh
+ self.invlinthresh = symlog.transform(linthresh)
+ self.linscale = linscale
+ self._linscale_adj = (linscale / (1.0 - self.base ** -1))
+
+ def transform_non_affine(self, values):
+ abs_a = np.abs(values)
+ with np.errstate(divide="ignore", invalid="ignore"):
+ out = np.sign(values) * self.linthresh * (
+ np.power(self.base,
+ abs_a / self.linthresh - self._linscale_adj))
+ inside = abs_a <= self.invlinthresh
+ out[inside] = values[inside] / self._linscale_adj
+ return out
+
+ def inverted(self):
+ return SymmetricalLogTransform(self.base,
+ self.linthresh, self.linscale)
+
+
+class SymmetricalLogScale(ScaleBase):
+ """
+ The symmetrical logarithmic scale is logarithmic in both the
+ positive and negative directions from the origin.
+
+ Since the values close to zero tend toward infinity, there is a
+ need to have a range around zero that is linear. The parameter
+ *linthresh* allows the user to specify the size of this range
+ (-*linthresh*, *linthresh*).
+
+ See :doc:`/gallery/scales/symlog_demo` for a detailed description.
+
+ Parameters
+ ----------
+ base : float, default: 10
+ The base of the logarithm.
+
+ linthresh : float, default: 2
+ Defines the range ``(-x, x)``, within which the plot is linear.
+ This avoids having the plot go to infinity around zero.
+
+ subs : sequence of int
+ Where to place the subticks between each major tick.
+ For example, in a log10 scale: ``[2, 3, 4, 5, 6, 7, 8, 9]`` will place
+ 8 logarithmically spaced minor ticks between each major tick.
+
+ linscale : float, optional
+ This allows the linear range ``(-linthresh, linthresh)`` to be
+ stretched relative to the logarithmic range. Its value is the number of
+ decades to use for each half of the linear range. For example, when
+ *linscale* == 1.0 (the default), the space used for the positive and
+ negative halves of the linear range will be equal to one decade in
+ the logarithmic range.
+ """
+ name = 'symlog'
+
+ def __init__(self, axis, *, base=10, linthresh=2, subs=None, linscale=1):
+ self._transform = SymmetricalLogTransform(base, linthresh, linscale)
+ self.subs = subs
+
+ base = property(lambda self: self._transform.base)
+ linthresh = property(lambda self: self._transform.linthresh)
+ linscale = property(lambda self: self._transform.linscale)
+
+ def set_default_locators_and_formatters(self, axis):
+ # docstring inherited
+ axis.set_major_locator(SymmetricalLogLocator(self.get_transform()))
+ axis.set_major_formatter(LogFormatterSciNotation(self.base))
+ axis.set_minor_locator(SymmetricalLogLocator(self.get_transform(),
+ self.subs))
+ axis.set_minor_formatter(NullFormatter())
+
+ def get_transform(self):
+ """Return the `.SymmetricalLogTransform` associated with this scale."""
+ return self._transform
+
+
+class AsinhTransform(Transform):
+ """Inverse hyperbolic-sine transformation used by `.AsinhScale`"""
+ input_dims = output_dims = 1
+
+ def __init__(self, linear_width):
+ super().__init__()
+ if linear_width <= 0.0:
+ raise ValueError("Scale parameter 'linear_width' " +
+ "must be strictly positive")
+ self.linear_width = linear_width
+
+ def transform_non_affine(self, values):
+ return self.linear_width * np.arcsinh(values / self.linear_width)
+
+ def inverted(self):
+ return InvertedAsinhTransform(self.linear_width)
+
+
+class InvertedAsinhTransform(Transform):
+ """Hyperbolic sine transformation used by `.AsinhScale`"""
+ input_dims = output_dims = 1
+
+ def __init__(self, linear_width):
+ super().__init__()
+ self.linear_width = linear_width
+
+ def transform_non_affine(self, values):
+ return self.linear_width * np.sinh(values / self.linear_width)
+
+ def inverted(self):
+ return AsinhTransform(self.linear_width)
+
+
+class AsinhScale(ScaleBase):
+ """
+ A quasi-logarithmic scale based on the inverse hyperbolic sine (asinh)
+
+ For values close to zero, this is essentially a linear scale,
+ but for large magnitude values (either positive or negative)
+ it is asymptotically logarithmic. The transition between these
+ linear and logarithmic regimes is smooth, and has no discontinuities
+ in the function gradient in contrast to
+ the `.SymmetricalLogScale` ("symlog") scale.
+
+ Specifically, the transformation of an axis coordinate :math:`a` is
+ :math:`a \\rightarrow a_0 \\sinh^{-1} (a / a_0)` where :math:`a_0`
+ is the effective width of the linear region of the transformation.
+ In that region, the transformation is
+ :math:`a \\rightarrow a + \\mathcal{O}(a^3)`.
+ For large values of :math:`a` the transformation behaves as
+ :math:`a \\rightarrow a_0 \\, \\mathrm{sgn}(a) \\ln |a| + \\mathcal{O}(1)`.
+
+ .. note::
+
+ This API is provisional and may be revised in the future
+ based on early user feedback.
+ """
+
+ name = 'asinh'
+
+ auto_tick_multipliers = {
+ 3: (2, ),
+ 4: (2, ),
+ 5: (2, ),
+ 8: (2, 4),
+ 10: (2, 5),
+ 16: (2, 4, 8),
+ 64: (4, 16),
+ 1024: (256, 512)
+ }
+
+ def __init__(self, axis, *, linear_width=1.0,
+ base=10, subs='auto', **kwargs):
+ """
+ Parameters
+ ----------
+ linear_width : float, default: 1
+ The scale parameter (elsewhere referred to as :math:`a_0`)
+ defining the extent of the quasi-linear region,
+ and the coordinate values beyond which the transformation
+ becomes asymptotically logarithmic.
+ base : int, default: 10
+ The number base used for rounding tick locations
+ on a logarithmic scale. If this is less than one,
+ then rounding is to the nearest integer multiple
+ of powers of ten.
+ subs : sequence of int
+ Multiples of the number base used for minor ticks.
+ If set to 'auto', this will use built-in defaults,
+ e.g. (2, 5) for base=10.
+ """
+ super().__init__(axis)
+ self._transform = AsinhTransform(linear_width)
+ self._base = int(base)
+ if subs == 'auto':
+ self._subs = self.auto_tick_multipliers.get(self._base)
+ else:
+ self._subs = subs
+
+ linear_width = property(lambda self: self._transform.linear_width)
+
+ def get_transform(self):
+ return self._transform
+
+ def set_default_locators_and_formatters(self, axis):
+ axis.set(major_locator=AsinhLocator(self.linear_width,
+ base=self._base),
+ minor_locator=AsinhLocator(self.linear_width,
+ base=self._base,
+ subs=self._subs),
+ minor_formatter=NullFormatter())
+ if self._base > 1:
+ axis.set_major_formatter(LogFormatterSciNotation(self._base))
+ else:
+ axis.set_major_formatter('{x:.3g}')
+
+
+class LogitTransform(Transform):
+ input_dims = output_dims = 1
+
+ def __init__(self, nonpositive='mask'):
+ super().__init__()
+ _api.check_in_list(['mask', 'clip'], nonpositive=nonpositive)
+ self._nonpositive = nonpositive
+ self._clip = {"clip": True, "mask": False}[nonpositive]
+
+ def transform_non_affine(self, values):
+ """logit transform (base 10), masked or clipped"""
+ with np.errstate(divide="ignore", invalid="ignore"):
+ out = np.log10(values / (1 - values))
+ if self._clip: # See LogTransform for choice of clip value.
+ out[values <= 0] = -1000
+ out[1 <= values] = 1000
+ return out
+
+ def inverted(self):
+ return LogisticTransform(self._nonpositive)
+
+ def __str__(self):
+ return f"{type(self).__name__}({self._nonpositive!r})"
+
+
+class LogisticTransform(Transform):
+ input_dims = output_dims = 1
+
+ def __init__(self, nonpositive='mask'):
+ super().__init__()
+ self._nonpositive = nonpositive
+
+ def transform_non_affine(self, values):
+ """logistic transform (base 10)"""
+ return 1.0 / (1 + 10**(-values))
+
+ def inverted(self):
+ return LogitTransform(self._nonpositive)
+
+ def __str__(self):
+ return f"{type(self).__name__}({self._nonpositive!r})"
+
+
+class LogitScale(ScaleBase):
+ """
+ Logit scale for data between zero and one, both excluded.
+
+ This scale is similar to a log scale close to zero and to one, and almost
+ linear around 0.5. It maps the interval ]0, 1[ onto ]-infty, +infty[.
+ """
+ name = 'logit'
+
+ def __init__(self, axis, nonpositive='mask', *,
+ one_half=r"\frac{1}{2}", use_overline=False):
+ r"""
+ Parameters
+ ----------
+ axis : `~matplotlib.axis.Axis`
+ Currently unused.
+ nonpositive : {'mask', 'clip'}
+ Determines the behavior for values beyond the open interval ]0, 1[.
+ They can either be masked as invalid, or clipped to a number very
+ close to 0 or 1.
+ use_overline : bool, default: False
+ Indicate the usage of survival notation (\overline{x}) in place of
+ standard notation (1-x) for probability close to one.
+ one_half : str, default: r"\frac{1}{2}"
+ The string used for ticks formatter to represent 1/2.
+ """
+ self._transform = LogitTransform(nonpositive)
+ self._use_overline = use_overline
+ self._one_half = one_half
+
+ def get_transform(self):
+ """Return the `.LogitTransform` associated with this scale."""
+ return self._transform
+
+ def set_default_locators_and_formatters(self, axis):
+ # docstring inherited
+ # ..., 0.01, 0.1, 0.5, 0.9, 0.99, ...
+ axis.set_major_locator(LogitLocator())
+ axis.set_major_formatter(
+ LogitFormatter(
+ one_half=self._one_half,
+ use_overline=self._use_overline
+ )
+ )
+ axis.set_minor_locator(LogitLocator(minor=True))
+ axis.set_minor_formatter(
+ LogitFormatter(
+ minor=True,
+ one_half=self._one_half,
+ use_overline=self._use_overline
+ )
+ )
+
+ def limit_range_for_scale(self, vmin, vmax, minpos):
+ """
+ Limit the domain to values between 0 and 1 (excluded).
+ """
+ if not np.isfinite(minpos):
+ minpos = 1e-7 # Should rarely (if ever) have a visible effect.
+ return (minpos if vmin <= 0 else vmin,
+ 1 - minpos if vmax >= 1 else vmax)
+
+
+_scale_mapping = {
+ 'linear': LinearScale,
+ 'log': LogScale,
+ 'symlog': SymmetricalLogScale,
+ 'asinh': AsinhScale,
+ 'logit': LogitScale,
+ 'function': FuncScale,
+ 'functionlog': FuncScaleLog,
+ }
+
+
+def get_scale_names():
+ """Return the names of the available scales."""
+ return sorted(_scale_mapping)
+
+
+def scale_factory(scale, axis, **kwargs):
+ """
+ Return a scale class by name.
+
+ Parameters
+ ----------
+ scale : {%(names)s}
+ axis : `~matplotlib.axis.Axis`
+ """
+ scale_cls = _api.check_getitem(_scale_mapping, scale=scale)
+ return scale_cls(axis, **kwargs)
+
+
+if scale_factory.__doc__:
+ scale_factory.__doc__ = scale_factory.__doc__ % {
+ "names": ", ".join(map(repr, get_scale_names()))}
+
+
+def register_scale(scale_class):
+ """
+ Register a new kind of scale.
+
+ Parameters
+ ----------
+ scale_class : subclass of `ScaleBase`
+ The scale to register.
+ """
+ _scale_mapping[scale_class.name] = scale_class
+
+
+def _get_scale_docs():
+ """
+ Helper function for generating docstrings related to scales.
+ """
+ docs = []
+ for name, scale_class in _scale_mapping.items():
+ docstring = inspect.getdoc(scale_class.__init__) or ""
+ docs.extend([
+ f" {name!r}",
+ "",
+ textwrap.indent(docstring, " " * 8),
+ ""
+ ])
+ return "\n".join(docs)
+
+
+_docstring.interpd.register(
+ scale_type='{%s}' % ', '.join([repr(x) for x in get_scale_names()]),
+ scale_docs=_get_scale_docs().rstrip(),
+ )
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/scale.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/scale.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..7fec8e68cc5ae0d31cd49ddda33a545ec1708e0a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/scale.pyi
@@ -0,0 +1,178 @@
+from matplotlib.axis import Axis
+from matplotlib.transforms import Transform
+
+from collections.abc import Callable, Iterable
+from typing import Literal
+from numpy.typing import ArrayLike
+
+class ScaleBase:
+ def __init__(self, axis: Axis | None) -> None: ...
+ def get_transform(self) -> Transform: ...
+ def set_default_locators_and_formatters(self, axis: Axis) -> None: ...
+ def limit_range_for_scale(
+ self, vmin: float, vmax: float, minpos: float
+ ) -> tuple[float, float]: ...
+
+class LinearScale(ScaleBase):
+ name: str
+
+class FuncTransform(Transform):
+ input_dims: int
+ output_dims: int
+ def __init__(
+ self,
+ forward: Callable[[ArrayLike], ArrayLike],
+ inverse: Callable[[ArrayLike], ArrayLike],
+ ) -> None: ...
+ def inverted(self) -> FuncTransform: ...
+
+class FuncScale(ScaleBase):
+ name: str
+ def __init__(
+ self,
+ axis: Axis | None,
+ functions: tuple[
+ Callable[[ArrayLike], ArrayLike], Callable[[ArrayLike], ArrayLike]
+ ],
+ ) -> None: ...
+
+class LogTransform(Transform):
+ input_dims: int
+ output_dims: int
+ base: float
+ def __init__(
+ self, base: float, nonpositive: Literal["clip", "mask"] = ...
+ ) -> None: ...
+ def inverted(self) -> InvertedLogTransform: ...
+
+class InvertedLogTransform(Transform):
+ input_dims: int
+ output_dims: int
+ base: float
+ def __init__(self, base: float) -> None: ...
+ def inverted(self) -> LogTransform: ...
+
+class LogScale(ScaleBase):
+ name: str
+ subs: Iterable[int] | None
+ def __init__(
+ self,
+ axis: Axis | None,
+ *,
+ base: float = ...,
+ subs: Iterable[int] | None = ...,
+ nonpositive: Literal["clip", "mask"] = ...
+ ) -> None: ...
+ @property
+ def base(self) -> float: ...
+ def get_transform(self) -> Transform: ...
+
+class FuncScaleLog(LogScale):
+ def __init__(
+ self,
+ axis: Axis | None,
+ functions: tuple[
+ Callable[[ArrayLike], ArrayLike], Callable[[ArrayLike], ArrayLike]
+ ],
+ base: float = ...,
+ ) -> None: ...
+ @property
+ def base(self) -> float: ...
+ def get_transform(self) -> Transform: ...
+
+class SymmetricalLogTransform(Transform):
+ input_dims: int
+ output_dims: int
+ base: float
+ linthresh: float
+ linscale: float
+ def __init__(self, base: float, linthresh: float, linscale: float) -> None: ...
+ def inverted(self) -> InvertedSymmetricalLogTransform: ...
+
+class InvertedSymmetricalLogTransform(Transform):
+ input_dims: int
+ output_dims: int
+ base: float
+ linthresh: float
+ invlinthresh: float
+ linscale: float
+ def __init__(self, base: float, linthresh: float, linscale: float) -> None: ...
+ def inverted(self) -> SymmetricalLogTransform: ...
+
+class SymmetricalLogScale(ScaleBase):
+ name: str
+ subs: Iterable[int] | None
+ def __init__(
+ self,
+ axis: Axis | None,
+ *,
+ base: float = ...,
+ linthresh: float = ...,
+ subs: Iterable[int] | None = ...,
+ linscale: float = ...
+ ) -> None: ...
+ @property
+ def base(self) -> float: ...
+ @property
+ def linthresh(self) -> float: ...
+ @property
+ def linscale(self) -> float: ...
+ def get_transform(self) -> SymmetricalLogTransform: ...
+
+class AsinhTransform(Transform):
+ input_dims: int
+ output_dims: int
+ linear_width: float
+ def __init__(self, linear_width: float) -> None: ...
+ def inverted(self) -> InvertedAsinhTransform: ...
+
+class InvertedAsinhTransform(Transform):
+ input_dims: int
+ output_dims: int
+ linear_width: float
+ def __init__(self, linear_width: float) -> None: ...
+ def inverted(self) -> AsinhTransform: ...
+
+class AsinhScale(ScaleBase):
+ name: str
+ auto_tick_multipliers: dict[int, tuple[int, ...]]
+ def __init__(
+ self,
+ axis: Axis | None,
+ *,
+ linear_width: float = ...,
+ base: float = ...,
+ subs: Iterable[int] | Literal["auto"] | None = ...,
+ **kwargs
+ ) -> None: ...
+ @property
+ def linear_width(self) -> float: ...
+ def get_transform(self) -> AsinhTransform: ...
+
+class LogitTransform(Transform):
+ input_dims: int
+ output_dims: int
+ def __init__(self, nonpositive: Literal["mask", "clip"] = ...) -> None: ...
+ def inverted(self) -> LogisticTransform: ...
+
+class LogisticTransform(Transform):
+ input_dims: int
+ output_dims: int
+ def __init__(self, nonpositive: Literal["mask", "clip"] = ...) -> None: ...
+ def inverted(self) -> LogitTransform: ...
+
+class LogitScale(ScaleBase):
+ name: str
+ def __init__(
+ self,
+ axis: Axis | None,
+ nonpositive: Literal["mask", "clip"] = ...,
+ *,
+ one_half: str = ...,
+ use_overline: bool = ...
+ ) -> None: ...
+ def get_transform(self) -> LogitTransform: ...
+
+def get_scale_names() -> list[str]: ...
+def scale_factory(scale: str, axis: Axis, **kwargs) -> ScaleBase: ...
+def register_scale(scale_class: type[ScaleBase]) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/__init__.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/figmpl_directive.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/figmpl_directive.py
new file mode 100644
index 0000000000000000000000000000000000000000..7cb9c6c04e8a130fe9d61c3a4c66f0ba4d80b1ad
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/figmpl_directive.py
@@ -0,0 +1,286 @@
+"""
+Add a ``figure-mpl`` directive that is a responsive version of ``figure``.
+
+This implementation is very similar to ``.. figure::``, except it also allows a
+``srcset=`` argument to be passed to the image tag, hence allowing responsive
+resolution images.
+
+There is no particular reason this could not be used standalone, but is meant
+to be used with :doc:`/api/sphinxext_plot_directive_api`.
+
+Note that the directory organization is a bit different than ``.. figure::``.
+See the *FigureMpl* documentation below.
+
+"""
+import os
+from os.path import relpath
+from pathlib import PurePath, Path
+import shutil
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.directives.images import Figure, Image
+from sphinx.errors import ExtensionError
+
+import matplotlib
+
+
+class figmplnode(nodes.General, nodes.Element):
+ pass
+
+
+class FigureMpl(Figure):
+ """
+ Implements a directive to allow an optional hidpi image.
+
+ Meant to be used with the *plot_srcset* configuration option in conf.py,
+ and gets set in the TEMPLATE of plot_directive.py
+
+ e.g.::
+
+ .. figure-mpl:: plot_directive/some_plots-1.png
+ :alt: bar
+ :srcset: plot_directive/some_plots-1.png,
+ plot_directive/some_plots-1.2x.png 2.00x
+ :class: plot-directive
+
+ The resulting html (at ``some_plots.html``) is::
+
+
+
+ Note that the handling of subdirectories is different than that used by the sphinx
+ figure directive::
+
+ .. figure-mpl:: plot_directive/nestedpage/index-1.png
+ :alt: bar
+ :srcset: plot_directive/nestedpage/index-1.png
+ plot_directive/nestedpage/index-1.2x.png 2.00x
+ :class: plot_directive
+
+ The resulting html (at ``nestedpage/index.html``)::
+
+
+
+ where the subdirectory is included in the image name for uniqueness.
+ """
+
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 2
+ final_argument_whitespace = False
+ option_spec = {
+ 'alt': directives.unchanged,
+ 'height': directives.length_or_unitless,
+ 'width': directives.length_or_percentage_or_unitless,
+ 'scale': directives.nonnegative_int,
+ 'align': Image.align,
+ 'class': directives.class_option,
+ 'caption': directives.unchanged,
+ 'srcset': directives.unchanged,
+ }
+
+ def run(self):
+
+ image_node = figmplnode()
+
+ imagenm = self.arguments[0]
+ image_node['alt'] = self.options.get('alt', '')
+ image_node['align'] = self.options.get('align', None)
+ image_node['class'] = self.options.get('class', None)
+ image_node['width'] = self.options.get('width', None)
+ image_node['height'] = self.options.get('height', None)
+ image_node['scale'] = self.options.get('scale', None)
+ image_node['caption'] = self.options.get('caption', None)
+
+ # we would like uri to be the highest dpi version so that
+ # latex etc will use that. But for now, lets just make
+ # imagenm... maybe pdf one day?
+
+ image_node['uri'] = imagenm
+ image_node['srcset'] = self.options.get('srcset', None)
+
+ return [image_node]
+
+
+def _parse_srcsetNodes(st):
+ """
+ parse srcset...
+ """
+ entries = st.split(',')
+ srcset = {}
+ for entry in entries:
+ spl = entry.strip().split(' ')
+ if len(spl) == 1:
+ srcset[0] = spl[0]
+ elif len(spl) == 2:
+ mult = spl[1][:-1]
+ srcset[float(mult)] = spl[0]
+ else:
+ raise ExtensionError(f'srcset argument "{entry}" is invalid.')
+ return srcset
+
+
+def _copy_images_figmpl(self, node):
+
+ # these will be the temporary place the plot-directive put the images eg:
+ # ../../../build/html/plot_directive/users/explain/artists/index-1.png
+ if node['srcset']:
+ srcset = _parse_srcsetNodes(node['srcset'])
+ else:
+ srcset = None
+
+ # the rst file's location: eg /Users/username/matplotlib/doc/users/explain/artists
+ docsource = PurePath(self.document['source']).parent
+
+ # get the relpath relative to root:
+ srctop = self.builder.srcdir
+ rel = relpath(docsource, srctop).replace('.', '').replace(os.sep, '-')
+ if len(rel):
+ rel += '-'
+ # eg: users/explain/artists
+
+ imagedir = PurePath(self.builder.outdir, self.builder.imagedir)
+ # eg: /Users/username/matplotlib/doc/build/html/_images/users/explain/artists
+
+ Path(imagedir).mkdir(parents=True, exist_ok=True)
+
+ # copy all the sources to the imagedir:
+ if srcset:
+ for src in srcset.values():
+ # the entries in srcset are relative to docsource's directory
+ abspath = PurePath(docsource, src)
+ name = rel + abspath.name
+ shutil.copyfile(abspath, imagedir / name)
+ else:
+ abspath = PurePath(docsource, node['uri'])
+ name = rel + abspath.name
+ shutil.copyfile(abspath, imagedir / name)
+
+ return imagedir, srcset, rel
+
+
+def visit_figmpl_html(self, node):
+
+ imagedir, srcset, rel = _copy_images_figmpl(self, node)
+
+ # /doc/examples/subd/plot_1.rst
+ docsource = PurePath(self.document['source'])
+ # /doc/
+ # make sure to add the trailing slash:
+ srctop = PurePath(self.builder.srcdir, '')
+ # examples/subd/plot_1.rst
+ relsource = relpath(docsource, srctop)
+ # /doc/build/html
+ desttop = PurePath(self.builder.outdir, '')
+ # /doc/build/html/examples/subd
+ dest = desttop / relsource
+
+ # ../../_images/ for dirhtml and ../_images/ for html
+ imagerel = PurePath(relpath(imagedir, dest.parent)).as_posix()
+ if self.builder.name == "dirhtml":
+ imagerel = f'..{imagerel}'
+
+ # make uri also be relative...
+ nm = PurePath(node['uri'][1:]).name
+ uri = f'{imagerel}/{rel}{nm}'
+ img_attrs = {'src': uri, 'alt': node['alt']}
+
+ # make srcset str. Need to change all the prefixes!
+ maxsrc = uri
+ if srcset:
+ maxmult = -1
+ srcsetst = ''
+ for mult, src in srcset.items():
+ nm = PurePath(src[1:]).name
+ # ../../_images/plot_1_2_0x.png
+ path = f'{imagerel}/{rel}{nm}'
+ srcsetst += path
+ if mult == 0:
+ srcsetst += ', '
+ else:
+ srcsetst += f' {mult:1.2f}x, '
+
+ if mult > maxmult:
+ maxmult = mult
+ maxsrc = path
+
+ # trim trailing comma and space...
+ img_attrs['srcset'] = srcsetst[:-2]
+
+ if node['class'] is not None:
+ img_attrs['class'] = ' '.join(node['class'])
+ for style in ['width', 'height', 'scale']:
+ if node[style]:
+ if 'style' not in img_attrs:
+ img_attrs['style'] = f'{style}: {node[style]};'
+ else:
+ img_attrs['style'] += f'{style}: {node[style]};'
+
+ #
+ #
+ #
+ #
+ #
+ # Figure caption is here....
+ #
+ #
+ #
+ self.body.append(
+ self.starttag(
+ node, 'figure',
+ CLASS=f'align-{node["align"]}' if node['align'] else 'align-center'))
+ self.body.append(
+ self.starttag(node, 'a', CLASS='reference internal image-reference',
+ href=maxsrc) +
+ self.emptytag(node, 'img', **img_attrs) +
+ '\n')
+ if node['caption']:
+ self.body.append(self.starttag(node, 'figcaption'))
+ self.body.append(self.starttag(node, 'p'))
+ self.body.append(self.starttag(node, 'span', CLASS='caption-text'))
+ self.body.append(node['caption'])
+ self.body.append('
\n')
+ self.body.append('\n')
+
+
+def visit_figmpl_latex(self, node):
+
+ if node['srcset'] is not None:
+ imagedir, srcset = _copy_images_figmpl(self, node)
+ maxmult = -1
+ # choose the highest res version for latex:
+ maxmult = max(srcset, default=-1)
+ node['uri'] = PurePath(srcset[maxmult]).name
+
+ self.visit_figure(node)
+
+
+def depart_figmpl_html(self, node):
+ pass
+
+
+def depart_figmpl_latex(self, node):
+ self.depart_figure(node)
+
+
+def figurempl_addnode(app):
+ app.add_node(figmplnode,
+ html=(visit_figmpl_html, depart_figmpl_html),
+ latex=(visit_figmpl_latex, depart_figmpl_latex))
+
+
+def setup(app):
+ app.add_directive("figure-mpl", FigureMpl)
+ figurempl_addnode(app)
+ metadata = {'parallel_read_safe': True, 'parallel_write_safe': True,
+ 'version': matplotlib.__version__}
+ return metadata
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/mathmpl.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/mathmpl.py
new file mode 100644
index 0000000000000000000000000000000000000000..30f0245242583c5ea162de9e5443da67d7111465
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/mathmpl.py
@@ -0,0 +1,242 @@
+r"""
+A role and directive to display mathtext in Sphinx
+==================================================
+
+The ``mathmpl`` Sphinx extension creates a mathtext image in Matplotlib and
+shows it in html output. Thus, it is a true and faithful representation of what
+you will see if you pass a given LaTeX string to Matplotlib (see
+:ref:`mathtext`).
+
+.. warning::
+ In most cases, you will likely want to use one of `Sphinx's builtin Math
+ extensions
+ `__
+ instead of this one. The builtin Sphinx math directive uses MathJax to
+ render mathematical expressions, and addresses accessibility concerns that
+ ``mathmpl`` doesn't address.
+
+Mathtext may be included in two ways:
+
+1. Inline, using the role::
+
+ This text uses inline math: :mathmpl:`\alpha > \beta`.
+
+ which produces:
+
+ This text uses inline math: :mathmpl:`\alpha > \beta`.
+
+2. Standalone, using the directive::
+
+ Here is some standalone math:
+
+ .. mathmpl::
+
+ \alpha > \beta
+
+ which produces:
+
+ Here is some standalone math:
+
+ .. mathmpl::
+
+ \alpha > \beta
+
+Options
+-------
+
+The ``mathmpl`` role and directive both support the following options:
+
+fontset : str, default: 'cm'
+ The font set to use when displaying math. See :rc:`mathtext.fontset`.
+
+fontsize : float
+ The font size, in points. Defaults to the value from the extension
+ configuration option defined below.
+
+Configuration options
+---------------------
+
+The mathtext extension has the following configuration options:
+
+mathmpl_fontsize : float, default: 10.0
+ Default font size, in points.
+
+mathmpl_srcset : list of str, default: []
+ Additional image sizes to generate when embedding in HTML, to support
+ `responsive resolution images
+ `__.
+ The list should contain additional x-descriptors (``'1.5x'``, ``'2x'``,
+ etc.) to generate (1x is the default and always included.)
+
+"""
+
+import hashlib
+from pathlib import Path
+
+from docutils import nodes
+from docutils.parsers.rst import Directive, directives
+import sphinx
+from sphinx.errors import ConfigError, ExtensionError
+
+import matplotlib as mpl
+from matplotlib import _api, mathtext
+from matplotlib.rcsetup import validate_float_or_None
+
+
+# Define LaTeX math node:
+class latex_math(nodes.General, nodes.Element):
+ pass
+
+
+def fontset_choice(arg):
+ return directives.choice(arg, mathtext.MathTextParser._font_type_mapping)
+
+
+def math_role(role, rawtext, text, lineno, inliner,
+ options={}, content=[]):
+ i = rawtext.find('`')
+ latex = rawtext[i+1:-1]
+ node = latex_math(rawtext)
+ node['latex'] = latex
+ node['fontset'] = options.get('fontset', 'cm')
+ node['fontsize'] = options.get('fontsize',
+ setup.app.config.mathmpl_fontsize)
+ return [node], []
+math_role.options = {'fontset': fontset_choice,
+ 'fontsize': validate_float_or_None}
+
+
+class MathDirective(Directive):
+ """
+ The ``.. mathmpl::`` directive, as documented in the module's docstring.
+ """
+ has_content = True
+ required_arguments = 0
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec = {'fontset': fontset_choice,
+ 'fontsize': validate_float_or_None}
+
+ def run(self):
+ latex = ''.join(self.content)
+ node = latex_math(self.block_text)
+ node['latex'] = latex
+ node['fontset'] = self.options.get('fontset', 'cm')
+ node['fontsize'] = self.options.get('fontsize',
+ setup.app.config.mathmpl_fontsize)
+ return [node]
+
+
+# This uses mathtext to render the expression
+def latex2png(latex, filename, fontset='cm', fontsize=10, dpi=100):
+ with mpl.rc_context({'mathtext.fontset': fontset, 'font.size': fontsize}):
+ try:
+ depth = mathtext.math_to_image(
+ f"${latex}$", filename, dpi=dpi, format="png")
+ except Exception:
+ _api.warn_external(f"Could not render math expression {latex}")
+ depth = 0
+ return depth
+
+
+# LaTeX to HTML translation stuff:
+def latex2html(node, source):
+ inline = isinstance(node.parent, nodes.TextElement)
+ latex = node['latex']
+ fontset = node['fontset']
+ fontsize = node['fontsize']
+ name = 'math-{}'.format(
+ hashlib.sha256(
+ f'{latex}{fontset}{fontsize}'.encode(),
+ usedforsecurity=False,
+ ).hexdigest()[-10:])
+
+ destdir = Path(setup.app.builder.outdir, '_images', 'mathmpl')
+ destdir.mkdir(parents=True, exist_ok=True)
+
+ dest = destdir / f'{name}.png'
+ depth = latex2png(latex, dest, fontset, fontsize=fontsize)
+
+ srcset = []
+ for size in setup.app.config.mathmpl_srcset:
+ filename = f'{name}-{size.replace(".", "_")}.png'
+ latex2png(latex, destdir / filename, fontset, fontsize=fontsize,
+ dpi=100 * float(size[:-1]))
+ srcset.append(
+ f'{setup.app.builder.imgpath}/mathmpl/{filename} {size}')
+ if srcset:
+ srcset = (f'srcset="{setup.app.builder.imgpath}/mathmpl/{name}.png, ' +
+ ', '.join(srcset) + '" ')
+
+ if inline:
+ cls = ''
+ else:
+ cls = 'class="center" '
+ if inline and depth != 0:
+ style = 'style="position: relative; bottom: -%dpx"' % (depth + 1)
+ else:
+ style = ''
+
+ return (f' ')
+
+
+def _config_inited(app, config):
+ # Check for srcset hidpi images
+ for i, size in enumerate(app.config.mathmpl_srcset):
+ if size[-1] == 'x': # "2x" = "2.0"
+ try:
+ float(size[:-1])
+ except ValueError:
+ raise ConfigError(
+ f'Invalid value for mathmpl_srcset parameter: {size!r}. '
+ 'Must be a list of strings with the multiplicative '
+ 'factor followed by an "x". e.g. ["2.0x", "1.5x"]')
+ else:
+ raise ConfigError(
+ f'Invalid value for mathmpl_srcset parameter: {size!r}. '
+ 'Must be a list of strings with the multiplicative '
+ 'factor followed by an "x". e.g. ["2.0x", "1.5x"]')
+
+
+def setup(app):
+ setup.app = app
+ app.add_config_value('mathmpl_fontsize', 10.0, True)
+ app.add_config_value('mathmpl_srcset', [], True)
+ try:
+ app.connect('config-inited', _config_inited) # Sphinx 1.8+
+ except ExtensionError:
+ app.connect('env-updated', lambda app, env: _config_inited(app, None))
+
+ # Add visit/depart methods to HTML-Translator:
+ def visit_latex_math_html(self, node):
+ source = self.document.attributes['source']
+ self.body.append(latex2html(node, source))
+
+ def depart_latex_math_html(self, node):
+ pass
+
+ # Add visit/depart methods to LaTeX-Translator:
+ def visit_latex_math_latex(self, node):
+ inline = isinstance(node.parent, nodes.TextElement)
+ if inline:
+ self.body.append('$%s$' % node['latex'])
+ else:
+ self.body.extend(['\\begin{equation}',
+ node['latex'],
+ '\\end{equation}'])
+
+ def depart_latex_math_latex(self, node):
+ pass
+
+ app.add_node(latex_math,
+ html=(visit_latex_math_html, depart_latex_math_html),
+ latex=(visit_latex_math_latex, depart_latex_math_latex))
+ app.add_role('mathmpl', math_role)
+ app.add_directive('mathmpl', MathDirective)
+ if sphinx.version_info < (1, 8):
+ app.add_role('math', math_role)
+ app.add_directive('math', MathDirective)
+
+ metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
+ return metadata
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/plot_directive.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/plot_directive.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab9b7d76955015186d148e1a04fa32f3ab245a3e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/plot_directive.py
@@ -0,0 +1,936 @@
+"""
+A directive for including a Matplotlib plot in a Sphinx document
+================================================================
+
+This is a Sphinx extension providing a reStructuredText directive
+``.. plot::`` for including a plot in a Sphinx document.
+
+In HTML output, ``.. plot::`` will include a .png file with a link
+to a high-res .png and .pdf. In LaTeX output, it will include a .pdf.
+
+The plot content may be defined in one of three ways:
+
+1. **A path to a source file** as the argument to the directive::
+
+ .. plot:: path/to/plot.py
+
+ When a path to a source file is given, the content of the
+ directive may optionally contain a caption for the plot::
+
+ .. plot:: path/to/plot.py
+
+ The plot caption.
+
+ Additionally, one may specify the name of a function to call (with
+ no arguments) immediately after importing the module::
+
+ .. plot:: path/to/plot.py plot_function1
+
+2. Included as **inline content** to the directive::
+
+ .. plot::
+
+ import matplotlib.pyplot as plt
+ plt.plot([1, 2, 3], [4, 5, 6])
+ plt.title("A plotting exammple")
+
+3. Using **doctest** syntax::
+
+ .. plot::
+
+ A plotting example:
+ >>> import matplotlib.pyplot as plt
+ >>> plt.plot([1, 2, 3], [4, 5, 6])
+
+Options
+-------
+
+The ``.. plot::`` directive supports the following options:
+
+``:format:`` : {'python', 'doctest'}
+ The format of the input. If unset, the format is auto-detected.
+
+``:include-source:`` : bool
+ Whether to display the source code. The default can be changed using
+ the ``plot_include_source`` variable in :file:`conf.py` (which itself
+ defaults to False).
+
+``:show-source-link:`` : bool
+ Whether to show a link to the source in HTML. The default can be
+ changed using the ``plot_html_show_source_link`` variable in
+ :file:`conf.py` (which itself defaults to True).
+
+``:context:`` : bool or str
+ If provided, the code will be run in the context of all previous plot
+ directives for which the ``:context:`` option was specified. This only
+ applies to inline code plot directives, not those run from files. If
+ the ``:context: reset`` option is specified, the context is reset
+ for this and future plots, and previous figures are closed prior to
+ running the code. ``:context: close-figs`` keeps the context but closes
+ previous figures before running the code.
+
+``:nofigs:`` : bool
+ If specified, the code block will be run, but no figures will be
+ inserted. This is usually useful with the ``:context:`` option.
+
+``:caption:`` : str
+ If specified, the option's argument will be used as a caption for the
+ figure. This overwrites the caption given in the content, when the plot
+ is generated from a file.
+
+Additionally, this directive supports all the options of the `image directive
+`_,
+except for ``:target:`` (since plot will add its own target). These include
+``:alt:``, ``:height:``, ``:width:``, ``:scale:``, ``:align:`` and ``:class:``.
+
+Configuration options
+---------------------
+
+The plot directive has the following configuration options:
+
+plot_include_source
+ Default value for the include-source option (default: False).
+
+plot_html_show_source_link
+ Whether to show a link to the source in HTML (default: True).
+
+plot_pre_code
+ Code that should be executed before each plot. If None (the default),
+ it will default to a string containing::
+
+ import numpy as np
+ from matplotlib import pyplot as plt
+
+plot_basedir
+ Base directory, to which ``plot::`` file names are relative to.
+ If None or empty (the default), file names are relative to the
+ directory where the file containing the directive is.
+
+plot_formats
+ File formats to generate (default: ['png', 'hires.png', 'pdf']).
+ List of tuples or strings::
+
+ [(suffix, dpi), suffix, ...]
+
+ that determine the file format and the DPI. For entries whose
+ DPI was omitted, sensible defaults are chosen. When passing from
+ the command line through sphinx_build the list should be passed as
+ suffix:dpi,suffix:dpi, ...
+
+plot_html_show_formats
+ Whether to show links to the files in HTML (default: True).
+
+plot_rcparams
+ A dictionary containing any non-standard rcParams that should
+ be applied before each plot (default: {}).
+
+plot_apply_rcparams
+ By default, rcParams are applied when ``:context:`` option is not used
+ in a plot directive. If set, this configuration option overrides this
+ behavior and applies rcParams before each plot.
+
+plot_working_directory
+ By default, the working directory will be changed to the directory of
+ the example, so the code can get at its data files, if any. Also its
+ path will be added to `sys.path` so it can import any helper modules
+ sitting beside it. This configuration option can be used to specify
+ a central directory (also added to `sys.path`) where data files and
+ helper modules for all code are located.
+
+plot_template
+ Provide a customized template for preparing restructured text.
+
+plot_srcset
+ Allow the srcset image option for responsive image resolutions. List of
+ strings with the multiplicative factors followed by an "x".
+ e.g. ["2.0x", "1.5x"]. "2.0x" will create a png with the default "png"
+ resolution from plot_formats, multiplied by 2. If plot_srcset is
+ specified, the plot directive uses the
+ :doc:`/api/sphinxext_figmpl_directive_api` (instead of the usual figure
+ directive) in the intermediary rst file that is generated.
+ The plot_srcset option is incompatible with *singlehtml* builds, and an
+ error will be raised.
+
+Notes on how it works
+---------------------
+
+The plot directive runs the code it is given, either in the source file or the
+code under the directive. The figure created (if any) is saved in the sphinx
+build directory under a subdirectory named ``plot_directive``. It then creates
+an intermediate rst file that calls a ``.. figure:`` directive (or
+``.. figmpl::`` directive if ``plot_srcset`` is being used) and has links to
+the ``*.png`` files in the ``plot_directive`` directory. These translations can
+be customized by changing the *plot_template*. See the source of
+:doc:`/api/sphinxext_plot_directive_api` for the templates defined in *TEMPLATE*
+and *TEMPLATE_SRCSET*.
+"""
+
+import contextlib
+import doctest
+from io import StringIO
+import itertools
+import os
+from os.path import relpath
+from pathlib import Path
+import re
+import shutil
+import sys
+import textwrap
+import traceback
+
+from docutils.parsers.rst import directives, Directive
+from docutils.parsers.rst.directives.images import Image
+import jinja2 # Sphinx dependency.
+
+from sphinx.errors import ExtensionError
+
+import matplotlib
+from matplotlib.backend_bases import FigureManagerBase
+import matplotlib.pyplot as plt
+from matplotlib import _pylab_helpers, cbook
+
+matplotlib.use("agg")
+
+__version__ = 2
+
+
+# -----------------------------------------------------------------------------
+# Registration hook
+# -----------------------------------------------------------------------------
+
+
+def _option_boolean(arg):
+ if not arg or not arg.strip():
+ # no argument given, assume used as a flag
+ return True
+ elif arg.strip().lower() in ('no', '0', 'false'):
+ return False
+ elif arg.strip().lower() in ('yes', '1', 'true'):
+ return True
+ else:
+ raise ValueError(f'{arg!r} unknown boolean')
+
+
+def _option_context(arg):
+ if arg in [None, 'reset', 'close-figs']:
+ return arg
+ raise ValueError("Argument should be None or 'reset' or 'close-figs'")
+
+
+def _option_format(arg):
+ return directives.choice(arg, ('python', 'doctest'))
+
+
+def mark_plot_labels(app, document):
+ """
+ To make plots referenceable, we need to move the reference from the
+ "htmlonly" (or "latexonly") node to the actual figure node itself.
+ """
+ for name, explicit in document.nametypes.items():
+ if not explicit:
+ continue
+ labelid = document.nameids[name]
+ if labelid is None:
+ continue
+ node = document.ids[labelid]
+ if node.tagname in ('html_only', 'latex_only'):
+ for n in node:
+ if n.tagname == 'figure':
+ sectname = name
+ for c in n:
+ if c.tagname == 'caption':
+ sectname = c.astext()
+ break
+
+ node['ids'].remove(labelid)
+ node['names'].remove(name)
+ n['ids'].append(labelid)
+ n['names'].append(name)
+ document.settings.env.labels[name] = \
+ document.settings.env.docname, labelid, sectname
+ break
+
+
+class PlotDirective(Directive):
+ """The ``.. plot::`` directive, as documented in the module's docstring."""
+
+ has_content = True
+ required_arguments = 0
+ optional_arguments = 2
+ final_argument_whitespace = False
+ option_spec = {
+ 'alt': directives.unchanged,
+ 'height': directives.length_or_unitless,
+ 'width': directives.length_or_percentage_or_unitless,
+ 'scale': directives.nonnegative_int,
+ 'align': Image.align,
+ 'class': directives.class_option,
+ 'include-source': _option_boolean,
+ 'show-source-link': _option_boolean,
+ 'format': _option_format,
+ 'context': _option_context,
+ 'nofigs': directives.flag,
+ 'caption': directives.unchanged,
+ }
+
+ def run(self):
+ """Run the plot directive."""
+ try:
+ return run(self.arguments, self.content, self.options,
+ self.state_machine, self.state, self.lineno)
+ except Exception as e:
+ raise self.error(str(e))
+
+
+def _copy_css_file(app, exc):
+ if exc is None and app.builder.format == 'html':
+ src = cbook._get_data_path('plot_directive/plot_directive.css')
+ dst = app.outdir / Path('_static')
+ dst.mkdir(exist_ok=True)
+ # Use copyfile because we do not want to copy src's permissions.
+ shutil.copyfile(src, dst / Path('plot_directive.css'))
+
+
+def setup(app):
+ setup.app = app
+ setup.config = app.config
+ setup.confdir = app.confdir
+ app.add_directive('plot', PlotDirective)
+ app.add_config_value('plot_pre_code', None, True)
+ app.add_config_value('plot_include_source', False, True)
+ app.add_config_value('plot_html_show_source_link', True, True)
+ app.add_config_value('plot_formats', ['png', 'hires.png', 'pdf'], True)
+ app.add_config_value('plot_basedir', None, True)
+ app.add_config_value('plot_html_show_formats', True, True)
+ app.add_config_value('plot_rcparams', {}, True)
+ app.add_config_value('plot_apply_rcparams', False, True)
+ app.add_config_value('plot_working_directory', None, True)
+ app.add_config_value('plot_template', None, True)
+ app.add_config_value('plot_srcset', [], True)
+ app.connect('doctree-read', mark_plot_labels)
+ app.add_css_file('plot_directive.css')
+ app.connect('build-finished', _copy_css_file)
+ metadata = {'parallel_read_safe': True, 'parallel_write_safe': True,
+ 'version': matplotlib.__version__}
+ return metadata
+
+
+# -----------------------------------------------------------------------------
+# Doctest handling
+# -----------------------------------------------------------------------------
+
+
+def contains_doctest(text):
+ try:
+ # check if it's valid Python as-is
+ compile(text, '', 'exec')
+ return False
+ except SyntaxError:
+ pass
+ r = re.compile(r'^\s*>>>', re.M)
+ m = r.search(text)
+ return bool(m)
+
+
+def _split_code_at_show(text, function_name):
+ """Split code at plt.show()."""
+
+ is_doctest = contains_doctest(text)
+ if function_name is None:
+ parts = []
+ part = []
+ for line in text.split("\n"):
+ if ((not is_doctest and line.startswith('plt.show(')) or
+ (is_doctest and line.strip() == '>>> plt.show()')):
+ part.append(line)
+ parts.append("\n".join(part))
+ part = []
+ else:
+ part.append(line)
+ if "\n".join(part).strip():
+ parts.append("\n".join(part))
+ else:
+ parts = [text]
+ return is_doctest, parts
+
+
+# -----------------------------------------------------------------------------
+# Template
+# -----------------------------------------------------------------------------
+
+_SOURCECODE = """
+{{ source_code }}
+
+.. only:: html
+
+ {% if src_name or (html_show_formats and not multi_image) %}
+ (
+ {%- if src_name -%}
+ :download:`Source code <{{ build_dir }}/{{ src_name }}>`
+ {%- endif -%}
+ {%- if html_show_formats and not multi_image -%}
+ {%- for img in images -%}
+ {%- for fmt in img.formats -%}
+ {%- if src_name or not loop.first -%}, {% endif -%}
+ :download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>`
+ {%- endfor -%}
+ {%- endfor -%}
+ {%- endif -%}
+ )
+ {% endif %}
+"""
+
+TEMPLATE_SRCSET = _SOURCECODE + """
+ {% for img in images %}
+ .. figure-mpl:: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }}
+ {% for option in options -%}
+ {{ option }}
+ {% endfor %}
+ {%- if caption -%}
+ {{ caption }} {# appropriate leading whitespace added beforehand #}
+ {% endif -%}
+ {%- if srcset -%}
+ :srcset: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }}
+ {%- for sr in srcset -%}
+ , {{ build_dir }}/{{ img.basename }}.{{ sr }}.{{ default_fmt }} {{sr}}
+ {%- endfor -%}
+ {% endif %}
+
+ {% if html_show_formats and multi_image %}
+ (
+ {%- for fmt in img.formats -%}
+ {%- if not loop.first -%}, {% endif -%}
+ :download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>`
+ {%- endfor -%}
+ )
+ {% endif %}
+
+
+ {% endfor %}
+
+.. only:: not html
+
+ {% for img in images %}
+ .. figure-mpl:: {{ build_dir }}/{{ img.basename }}.*
+ {% for option in options -%}
+ {{ option }}
+ {% endfor -%}
+
+ {{ caption }} {# appropriate leading whitespace added beforehand #}
+ {% endfor %}
+
+"""
+
+TEMPLATE = _SOURCECODE + """
+
+ {% for img in images %}
+ .. figure:: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }}
+ {% for option in options -%}
+ {{ option }}
+ {% endfor %}
+
+ {% if html_show_formats and multi_image -%}
+ (
+ {%- for fmt in img.formats -%}
+ {%- if not loop.first -%}, {% endif -%}
+ :download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>`
+ {%- endfor -%}
+ )
+ {%- endif -%}
+
+ {{ caption }} {# appropriate leading whitespace added beforehand #}
+ {% endfor %}
+
+.. only:: not html
+
+ {% for img in images %}
+ .. figure:: {{ build_dir }}/{{ img.basename }}.*
+ {% for option in options -%}
+ {{ option }}
+ {% endfor -%}
+
+ {{ caption }} {# appropriate leading whitespace added beforehand #}
+ {% endfor %}
+
+"""
+
+exception_template = """
+.. only:: html
+
+ [`source code <%(linkdir)s/%(basename)s.py>`__]
+
+Exception occurred rendering plot.
+
+"""
+
+# the context of the plot for all directives specified with the
+# :context: option
+plot_context = dict()
+
+
+class ImageFile:
+ def __init__(self, basename, dirname):
+ self.basename = basename
+ self.dirname = dirname
+ self.formats = []
+
+ def filename(self, format):
+ return os.path.join(self.dirname, f"{self.basename}.{format}")
+
+ def filenames(self):
+ return [self.filename(fmt) for fmt in self.formats]
+
+
+def out_of_date(original, derived, includes=None):
+ """
+ Return whether *derived* is out-of-date relative to *original* or any of
+ the RST files included in it using the RST include directive (*includes*).
+ *derived* and *original* are full paths, and *includes* is optionally a
+ list of full paths which may have been included in the *original*.
+ """
+ if not os.path.exists(derived):
+ return True
+
+ if includes is None:
+ includes = []
+ files_to_check = [original, *includes]
+
+ def out_of_date_one(original, derived_mtime):
+ return (os.path.exists(original) and
+ derived_mtime < os.stat(original).st_mtime)
+
+ derived_mtime = os.stat(derived).st_mtime
+ return any(out_of_date_one(f, derived_mtime) for f in files_to_check)
+
+
+class PlotError(RuntimeError):
+ pass
+
+
+def _run_code(code, code_path, ns=None, function_name=None):
+ """
+ Import a Python module from a path, and run the function given by
+ name, if function_name is not None.
+ """
+
+ # Change the working directory to the directory of the example, so
+ # it can get at its data files, if any. Add its path to sys.path
+ # so it can import any helper modules sitting beside it.
+ pwd = os.getcwd()
+ if setup.config.plot_working_directory is not None:
+ try:
+ os.chdir(setup.config.plot_working_directory)
+ except OSError as err:
+ raise OSError(f'{err}\n`plot_working_directory` option in '
+ f'Sphinx configuration file must be a valid '
+ f'directory path') from err
+ except TypeError as err:
+ raise TypeError(f'{err}\n`plot_working_directory` option in '
+ f'Sphinx configuration file must be a string or '
+ f'None') from err
+ elif code_path is not None:
+ dirname = os.path.abspath(os.path.dirname(code_path))
+ os.chdir(dirname)
+
+ with cbook._setattr_cm(
+ sys, argv=[code_path], path=[os.getcwd(), *sys.path]), \
+ contextlib.redirect_stdout(StringIO()):
+ try:
+ if ns is None:
+ ns = {}
+ if not ns:
+ if setup.config.plot_pre_code is None:
+ exec('import numpy as np\n'
+ 'from matplotlib import pyplot as plt\n', ns)
+ else:
+ exec(str(setup.config.plot_pre_code), ns)
+ if "__main__" in code:
+ ns['__name__'] = '__main__'
+
+ # Patch out non-interactive show() to avoid triggering a warning.
+ with cbook._setattr_cm(FigureManagerBase, show=lambda self: None):
+ exec(code, ns)
+ if function_name is not None:
+ exec(function_name + "()", ns)
+
+ except (Exception, SystemExit) as err:
+ raise PlotError(traceback.format_exc()) from err
+ finally:
+ os.chdir(pwd)
+ return ns
+
+
+def clear_state(plot_rcparams, close=True):
+ if close:
+ plt.close('all')
+ matplotlib.rc_file_defaults()
+ matplotlib.rcParams.update(plot_rcparams)
+
+
+def get_plot_formats(config):
+ default_dpi = {'png': 80, 'hires.png': 200, 'pdf': 200}
+ formats = []
+ plot_formats = config.plot_formats
+ for fmt in plot_formats:
+ if isinstance(fmt, str):
+ if ':' in fmt:
+ suffix, dpi = fmt.split(':')
+ formats.append((str(suffix), int(dpi)))
+ else:
+ formats.append((fmt, default_dpi.get(fmt, 80)))
+ elif isinstance(fmt, (tuple, list)) and len(fmt) == 2:
+ formats.append((str(fmt[0]), int(fmt[1])))
+ else:
+ raise PlotError('invalid image format "%r" in plot_formats' % fmt)
+ return formats
+
+
+def _parse_srcset(entries):
+ """
+ Parse srcset for multiples...
+ """
+ srcset = {}
+ for entry in entries:
+ entry = entry.strip()
+ if len(entry) >= 2:
+ mult = entry[:-1]
+ srcset[float(mult)] = entry
+ else:
+ raise ExtensionError(f'srcset argument {entry!r} is invalid.')
+ return srcset
+
+
+def render_figures(code, code_path, output_dir, output_base, context,
+ function_name, config, context_reset=False,
+ close_figs=False,
+ code_includes=None):
+ """
+ Run a pyplot script and save the images in *output_dir*.
+
+ Save the images under *output_dir* with file names derived from
+ *output_base*
+ """
+
+ if function_name is not None:
+ output_base = f'{output_base}_{function_name}'
+ formats = get_plot_formats(config)
+
+ # Try to determine if all images already exist
+
+ is_doctest, code_pieces = _split_code_at_show(code, function_name)
+ # Look for single-figure output files first
+ img = ImageFile(output_base, output_dir)
+ for format, dpi in formats:
+ if context or out_of_date(code_path, img.filename(format),
+ includes=code_includes):
+ all_exists = False
+ break
+ img.formats.append(format)
+ else:
+ all_exists = True
+
+ if all_exists:
+ return [(code, [img])]
+
+ # Then look for multi-figure output files
+ results = []
+ for i, code_piece in enumerate(code_pieces):
+ images = []
+ for j in itertools.count():
+ if len(code_pieces) > 1:
+ img = ImageFile('%s_%02d_%02d' % (output_base, i, j),
+ output_dir)
+ else:
+ img = ImageFile('%s_%02d' % (output_base, j), output_dir)
+ for fmt, dpi in formats:
+ if context or out_of_date(code_path, img.filename(fmt),
+ includes=code_includes):
+ all_exists = False
+ break
+ img.formats.append(fmt)
+
+ # assume that if we have one, we have them all
+ if not all_exists:
+ all_exists = (j > 0)
+ break
+ images.append(img)
+ if not all_exists:
+ break
+ results.append((code_piece, images))
+ else:
+ all_exists = True
+
+ if all_exists:
+ return results
+
+ # We didn't find the files, so build them
+
+ results = []
+ ns = plot_context if context else {}
+
+ if context_reset:
+ clear_state(config.plot_rcparams)
+ plot_context.clear()
+
+ close_figs = not context or close_figs
+
+ for i, code_piece in enumerate(code_pieces):
+
+ if not context or config.plot_apply_rcparams:
+ clear_state(config.plot_rcparams, close_figs)
+ elif close_figs:
+ plt.close('all')
+
+ _run_code(doctest.script_from_examples(code_piece) if is_doctest
+ else code_piece,
+ code_path, ns, function_name)
+
+ images = []
+ fig_managers = _pylab_helpers.Gcf.get_all_fig_managers()
+ for j, figman in enumerate(fig_managers):
+ if len(fig_managers) == 1 and len(code_pieces) == 1:
+ img = ImageFile(output_base, output_dir)
+ elif len(code_pieces) == 1:
+ img = ImageFile("%s_%02d" % (output_base, j), output_dir)
+ else:
+ img = ImageFile("%s_%02d_%02d" % (output_base, i, j),
+ output_dir)
+ images.append(img)
+
+ for fmt, dpi in formats:
+ try:
+ figman.canvas.figure.savefig(img.filename(fmt), dpi=dpi)
+ if fmt == formats[0][0] and config.plot_srcset:
+ # save a 2x, 3x etc version of the default...
+ srcset = _parse_srcset(config.plot_srcset)
+ for mult, suffix in srcset.items():
+ fm = f'{suffix}.{fmt}'
+ img.formats.append(fm)
+ figman.canvas.figure.savefig(img.filename(fm),
+ dpi=int(dpi * mult))
+ except Exception as err:
+ raise PlotError(traceback.format_exc()) from err
+ img.formats.append(fmt)
+
+ results.append((code_piece, images))
+
+ if not context or config.plot_apply_rcparams:
+ clear_state(config.plot_rcparams, close=not context)
+
+ return results
+
+
+def run(arguments, content, options, state_machine, state, lineno):
+ document = state_machine.document
+ config = document.settings.env.config
+ nofigs = 'nofigs' in options
+
+ if config.plot_srcset and setup.app.builder.name == 'singlehtml':
+ raise ExtensionError(
+ 'plot_srcset option not compatible with single HTML writer')
+
+ formats = get_plot_formats(config)
+ default_fmt = formats[0][0]
+
+ options.setdefault('include-source', config.plot_include_source)
+ options.setdefault('show-source-link', config.plot_html_show_source_link)
+
+ if 'class' in options:
+ # classes are parsed into a list of string, and output by simply
+ # printing the list, abusing the fact that RST guarantees to strip
+ # non-conforming characters
+ options['class'] = ['plot-directive'] + options['class']
+ else:
+ options.setdefault('class', ['plot-directive'])
+ keep_context = 'context' in options
+ context_opt = None if not keep_context else options['context']
+
+ rst_file = document.attributes['source']
+ rst_dir = os.path.dirname(rst_file)
+
+ if len(arguments):
+ if not config.plot_basedir:
+ source_file_name = os.path.join(setup.app.builder.srcdir,
+ directives.uri(arguments[0]))
+ else:
+ source_file_name = os.path.join(setup.confdir, config.plot_basedir,
+ directives.uri(arguments[0]))
+ # If there is content, it will be passed as a caption.
+ caption = '\n'.join(content)
+
+ # Enforce unambiguous use of captions.
+ if "caption" in options:
+ if caption:
+ raise ValueError(
+ 'Caption specified in both content and options.'
+ ' Please remove ambiguity.'
+ )
+ # Use caption option
+ caption = options["caption"]
+
+ # If the optional function name is provided, use it
+ if len(arguments) == 2:
+ function_name = arguments[1]
+ else:
+ function_name = None
+
+ code = Path(source_file_name).read_text(encoding='utf-8')
+ output_base = os.path.basename(source_file_name)
+ else:
+ source_file_name = rst_file
+ code = textwrap.dedent("\n".join(map(str, content)))
+ counter = document.attributes.get('_plot_counter', 0) + 1
+ document.attributes['_plot_counter'] = counter
+ base, ext = os.path.splitext(os.path.basename(source_file_name))
+ output_base = '%s-%d.py' % (base, counter)
+ function_name = None
+ caption = options.get('caption', '')
+
+ base, source_ext = os.path.splitext(output_base)
+ if source_ext in ('.py', '.rst', '.txt'):
+ output_base = base
+ else:
+ source_ext = ''
+
+ # ensure that LaTeX includegraphics doesn't choke in foo.bar.pdf filenames
+ output_base = output_base.replace('.', '-')
+
+ # is it in doctest format?
+ is_doctest = contains_doctest(code)
+ if 'format' in options:
+ if options['format'] == 'python':
+ is_doctest = False
+ else:
+ is_doctest = True
+
+ # determine output directory name fragment
+ source_rel_name = relpath(source_file_name, setup.confdir)
+ source_rel_dir = os.path.dirname(source_rel_name).lstrip(os.path.sep)
+
+ # build_dir: where to place output files (temporarily)
+ build_dir = os.path.join(os.path.dirname(setup.app.doctreedir),
+ 'plot_directive',
+ source_rel_dir)
+ # get rid of .. in paths, also changes pathsep
+ # see note in Python docs for warning about symbolic links on Windows.
+ # need to compare source and dest paths at end
+ build_dir = os.path.normpath(build_dir)
+ os.makedirs(build_dir, exist_ok=True)
+
+ # how to link to files from the RST file
+ try:
+ build_dir_link = relpath(build_dir, rst_dir).replace(os.path.sep, '/')
+ except ValueError:
+ # on Windows, relpath raises ValueError when path and start are on
+ # different mounts/drives
+ build_dir_link = build_dir
+
+ # get list of included rst files so that the output is updated when any
+ # plots in the included files change. These attributes are modified by the
+ # include directive (see the docutils.parsers.rst.directives.misc module).
+ try:
+ source_file_includes = [os.path.join(os.getcwd(), t[0])
+ for t in state.document.include_log]
+ except AttributeError:
+ # the document.include_log attribute only exists in docutils >=0.17,
+ # before that we need to inspect the state machine
+ possible_sources = {os.path.join(setup.confdir, t[0])
+ for t in state_machine.input_lines.items}
+ source_file_includes = [f for f in possible_sources
+ if os.path.isfile(f)]
+ # remove the source file itself from the includes
+ try:
+ source_file_includes.remove(source_file_name)
+ except ValueError:
+ pass
+
+ # save script (if necessary)
+ if options['show-source-link']:
+ Path(build_dir, output_base + source_ext).write_text(
+ doctest.script_from_examples(code)
+ if source_file_name == rst_file and is_doctest
+ else code,
+ encoding='utf-8')
+
+ # make figures
+ try:
+ results = render_figures(code=code,
+ code_path=source_file_name,
+ output_dir=build_dir,
+ output_base=output_base,
+ context=keep_context,
+ function_name=function_name,
+ config=config,
+ context_reset=context_opt == 'reset',
+ close_figs=context_opt == 'close-figs',
+ code_includes=source_file_includes)
+ errors = []
+ except PlotError as err:
+ reporter = state.memo.reporter
+ sm = reporter.system_message(
+ 2, "Exception occurred in plotting {}\n from {}:\n{}".format(
+ output_base, source_file_name, err),
+ line=lineno)
+ results = [(code, [])]
+ errors = [sm]
+
+ # Properly indent the caption
+ if caption and config.plot_srcset:
+ caption = ':caption: ' + caption.replace('\n', ' ')
+ elif caption:
+ caption = '\n' + '\n'.join(' ' + line.strip()
+ for line in caption.split('\n'))
+ # generate output restructuredtext
+ total_lines = []
+ for j, (code_piece, images) in enumerate(results):
+ if options['include-source']:
+ if is_doctest:
+ lines = ['', *code_piece.splitlines()]
+ else:
+ lines = ['.. code-block:: python', '',
+ *textwrap.indent(code_piece, ' ').splitlines()]
+ source_code = "\n".join(lines)
+ else:
+ source_code = ""
+
+ if nofigs:
+ images = []
+
+ if 'alt' in options:
+ options['alt'] = options['alt'].replace('\n', ' ')
+
+ opts = [
+ f':{key}: {val}' for key, val in options.items()
+ if key in ('alt', 'height', 'width', 'scale', 'align', 'class')]
+
+ # Not-None src_name signals the need for a source download in the
+ # generated html
+ if j == 0 and options['show-source-link']:
+ src_name = output_base + source_ext
+ else:
+ src_name = None
+ if config.plot_srcset:
+ srcset = [*_parse_srcset(config.plot_srcset).values()]
+ template = TEMPLATE_SRCSET
+ else:
+ srcset = None
+ template = TEMPLATE
+
+ result = jinja2.Template(config.plot_template or template).render(
+ default_fmt=default_fmt,
+ build_dir=build_dir_link,
+ src_name=src_name,
+ multi_image=len(images) > 1,
+ options=opts,
+ srcset=srcset,
+ images=images,
+ source_code=source_code,
+ html_show_formats=config.plot_html_show_formats and len(images),
+ caption=caption)
+ total_lines.extend(result.split("\n"))
+ total_lines.extend("\n")
+
+ if total_lines:
+ state_machine.insert_input(total_lines, source=source_file_name)
+
+ return errors
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/roles.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/roles.py
new file mode 100644
index 0000000000000000000000000000000000000000..301adcd8a5f5e0b18d28df297ee2025947e5c90d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/sphinxext/roles.py
@@ -0,0 +1,147 @@
+"""
+Custom roles for the Matplotlib documentation.
+
+.. warning::
+
+ These roles are considered semi-public. They are only intended to be used in
+ the Matplotlib documentation.
+
+However, it can happen that downstream packages end up pulling these roles into
+their documentation, which will result in documentation build errors. The following
+describes the exact mechanism and how to fix the errors.
+
+There are two ways, Matplotlib docstrings can end up in downstream documentation.
+You have to subclass a Matplotlib class and either use the ``:inherited-members:``
+option in your autodoc configuration, or you have to override a method without
+specifying a new docstring; the new method will inherit the original docstring and
+still render in your autodoc. If the docstring contains one of the custom sphinx
+roles, you'll see one of the following error messages:
+
+.. code-block:: none
+
+ Unknown interpreted text role "mpltype".
+ Unknown interpreted text role "rc".
+
+To fix this, you can add this module as extension to your sphinx :file:`conf.py`::
+
+ extensions = [
+ 'matplotlib.sphinxext.roles',
+ # Other extensions.
+ ]
+
+.. warning::
+
+ Direct use of these roles in other packages is not officially supported. We
+ reserve the right to modify or remove these roles without prior notification.
+"""
+
+from urllib.parse import urlsplit, urlunsplit
+
+from docutils import nodes
+
+import matplotlib
+from matplotlib import rcParamsDefault
+
+
+class _QueryReference(nodes.Inline, nodes.TextElement):
+ """
+ Wraps a reference or pending reference to add a query string.
+
+ The query string is generated from the attributes added to this node.
+
+ Also equivalent to a `~docutils.nodes.literal` node.
+ """
+
+ def to_query_string(self):
+ """Generate query string from node attributes."""
+ return '&'.join(f'{name}={value}' for name, value in self.attlist())
+
+
+def _visit_query_reference_node(self, node):
+ """
+ Resolve *node* into query strings on its ``reference`` children.
+
+ Then act as if this is a `~docutils.nodes.literal`.
+ """
+ query = node.to_query_string()
+ for refnode in node.findall(nodes.reference):
+ uri = urlsplit(refnode['refuri'])._replace(query=query)
+ refnode['refuri'] = urlunsplit(uri)
+
+ self.visit_literal(node)
+
+
+def _depart_query_reference_node(self, node):
+ """
+ Act as if this is a `~docutils.nodes.literal`.
+ """
+ self.depart_literal(node)
+
+
+def _rcparam_role(name, rawtext, text, lineno, inliner, options=None, content=None):
+ """
+ Sphinx role ``:rc:`` to highlight and link ``rcParams`` entries.
+
+ Usage: Give the desired ``rcParams`` key as parameter.
+
+ :code:`:rc:`figure.dpi`` will render as: :rc:`figure.dpi`
+ """
+ # Generate a pending cross-reference so that Sphinx will ensure this link
+ # isn't broken at some point in the future.
+ title = f'rcParams["{text}"]'
+ target = 'matplotlibrc-sample'
+ ref_nodes, messages = inliner.interpreted(title, f'{title} <{target}>',
+ 'ref', lineno)
+
+ qr = _QueryReference(rawtext, highlight=text)
+ qr += ref_nodes
+ node_list = [qr]
+
+ # The default backend would be printed as "agg", but that's not correct (as
+ # the default is actually determined by fallback).
+ if text in rcParamsDefault and text != "backend":
+ node_list.extend([
+ nodes.Text(' (default: '),
+ nodes.literal('', repr(rcParamsDefault[text])),
+ nodes.Text(')'),
+ ])
+
+ return node_list, messages
+
+
+def _mpltype_role(name, rawtext, text, lineno, inliner, options=None, content=None):
+ """
+ Sphinx role ``:mpltype:`` for custom matplotlib types.
+
+ In Matplotlib, there are a number of type-like concepts that do not have a
+ direct type representation; example: color. This role allows to properly
+ highlight them in the docs and link to their definition.
+
+ Currently supported values:
+
+ - :code:`:mpltype:`color`` will render as: :mpltype:`color`
+
+ """
+ mpltype = text
+ type_to_link_target = {
+ 'color': 'colors_def',
+ }
+ if mpltype not in type_to_link_target:
+ raise ValueError(f"Unknown mpltype: {mpltype!r}")
+
+ node_list, messages = inliner.interpreted(
+ mpltype, f'{mpltype} <{type_to_link_target[mpltype]}>', 'ref', lineno)
+ return node_list, messages
+
+
+def setup(app):
+ app.add_role("rc", _rcparam_role)
+ app.add_role("mpltype", _mpltype_role)
+ app.add_node(
+ _QueryReference,
+ html=(_visit_query_reference_node, _depart_query_reference_node),
+ latex=(_visit_query_reference_node, _depart_query_reference_node),
+ text=(_visit_query_reference_node, _depart_query_reference_node),
+ )
+ return {"version": matplotlib.__version__,
+ "parallel_read_safe": True, "parallel_write_safe": True}
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/spines.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/spines.py
new file mode 100644
index 0000000000000000000000000000000000000000..9732a2f3347adbe4ce1c1c76d23ef93398505e62
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/spines.py
@@ -0,0 +1,602 @@
+from collections.abc import MutableMapping
+import functools
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api, _docstring
+from matplotlib.artist import allow_rasterization
+import matplotlib.transforms as mtransforms
+import matplotlib.patches as mpatches
+import matplotlib.path as mpath
+
+
+class Spine(mpatches.Patch):
+ """
+ An axis spine -- the line noting the data area boundaries.
+
+ Spines are the lines connecting the axis tick marks and noting the
+ boundaries of the data area. They can be placed at arbitrary
+ positions. See `~.Spine.set_position` for more information.
+
+ The default position is ``('outward', 0)``.
+
+ Spines are subclasses of `.Patch`, and inherit much of their behavior.
+
+ Spines draw a line, a circle, or an arc depending on if
+ `~.Spine.set_patch_line`, `~.Spine.set_patch_circle`, or
+ `~.Spine.set_patch_arc` has been called. Line-like is the default.
+
+ For examples see :ref:`spines_examples`.
+ """
+ def __str__(self):
+ return "Spine"
+
+ @_docstring.interpd
+ def __init__(self, axes, spine_type, path, **kwargs):
+ """
+ Parameters
+ ----------
+ axes : `~matplotlib.axes.Axes`
+ The `~.axes.Axes` instance containing the spine.
+ spine_type : str
+ The spine type.
+ path : `~matplotlib.path.Path`
+ The `.Path` instance used to draw the spine.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ Valid keyword arguments are:
+
+ %(Patch:kwdoc)s
+ """
+ super().__init__(**kwargs)
+ self.axes = axes
+ self.set_figure(self.axes.get_figure(root=False))
+ self.spine_type = spine_type
+ self.set_facecolor('none')
+ self.set_edgecolor(mpl.rcParams['axes.edgecolor'])
+ self.set_linewidth(mpl.rcParams['axes.linewidth'])
+ self.set_capstyle('projecting')
+ self.axis = None
+
+ self.set_zorder(2.5)
+ self.set_transform(self.axes.transData) # default transform
+
+ self._bounds = None # default bounds
+
+ # Defer initial position determination. (Not much support for
+ # non-rectangular axes is currently implemented, and this lets
+ # them pass through the spines machinery without errors.)
+ self._position = None
+ _api.check_isinstance(mpath.Path, path=path)
+ self._path = path
+
+ # To support drawing both linear and circular spines, this
+ # class implements Patch behavior three ways. If
+ # self._patch_type == 'line', behave like a mpatches.PathPatch
+ # instance. If self._patch_type == 'circle', behave like a
+ # mpatches.Ellipse instance. If self._patch_type == 'arc', behave like
+ # a mpatches.Arc instance.
+ self._patch_type = 'line'
+
+ # Behavior copied from mpatches.Ellipse:
+ # Note: This cannot be calculated until this is added to an Axes
+ self._patch_transform = mtransforms.IdentityTransform()
+
+ def set_patch_arc(self, center, radius, theta1, theta2):
+ """Set the spine to be arc-like."""
+ self._patch_type = 'arc'
+ self._center = center
+ self._width = radius * 2
+ self._height = radius * 2
+ self._theta1 = theta1
+ self._theta2 = theta2
+ self._path = mpath.Path.arc(theta1, theta2)
+ # arc drawn on axes transform
+ self.set_transform(self.axes.transAxes)
+ self.stale = True
+
+ def set_patch_circle(self, center, radius):
+ """Set the spine to be circular."""
+ self._patch_type = 'circle'
+ self._center = center
+ self._width = radius * 2
+ self._height = radius * 2
+ # circle drawn on axes transform
+ self.set_transform(self.axes.transAxes)
+ self.stale = True
+
+ def set_patch_line(self):
+ """Set the spine to be linear."""
+ self._patch_type = 'line'
+ self.stale = True
+
+ # Behavior copied from mpatches.Ellipse:
+ def _recompute_transform(self):
+ """
+ Notes
+ -----
+ This cannot be called until after this has been added to an Axes,
+ otherwise unit conversion will fail. This makes it very important to
+ call the accessor method and not directly access the transformation
+ member variable.
+ """
+ assert self._patch_type in ('arc', 'circle')
+ center = (self.convert_xunits(self._center[0]),
+ self.convert_yunits(self._center[1]))
+ width = self.convert_xunits(self._width)
+ height = self.convert_yunits(self._height)
+ self._patch_transform = mtransforms.Affine2D() \
+ .scale(width * 0.5, height * 0.5) \
+ .translate(*center)
+
+ def get_patch_transform(self):
+ if self._patch_type in ('arc', 'circle'):
+ self._recompute_transform()
+ return self._patch_transform
+ else:
+ return super().get_patch_transform()
+
+ def get_window_extent(self, renderer=None):
+ """
+ Return the window extent of the spines in display space, including
+ padding for ticks (but not their labels)
+
+ See Also
+ --------
+ matplotlib.axes.Axes.get_tightbbox
+ matplotlib.axes.Axes.get_window_extent
+ """
+ # make sure the location is updated so that transforms etc are correct:
+ self._adjust_location()
+ bb = super().get_window_extent(renderer=renderer)
+ if self.axis is None or not self.axis.get_visible():
+ return bb
+ bboxes = [bb]
+ drawn_ticks = self.axis._update_ticks()
+
+ major_tick = next(iter({*drawn_ticks} & {*self.axis.majorTicks}), None)
+ minor_tick = next(iter({*drawn_ticks} & {*self.axis.minorTicks}), None)
+ for tick in [major_tick, minor_tick]:
+ if tick is None:
+ continue
+ bb0 = bb.frozen()
+ tickl = tick._size
+ tickdir = tick._tickdir
+ if tickdir == 'out':
+ padout = 1
+ padin = 0
+ elif tickdir == 'in':
+ padout = 0
+ padin = 1
+ else:
+ padout = 0.5
+ padin = 0.5
+ dpi = self.get_figure(root=True).dpi
+ padout = padout * tickl / 72 * dpi
+ padin = padin * tickl / 72 * dpi
+
+ if tick.tick1line.get_visible():
+ if self.spine_type == 'left':
+ bb0.x0 = bb0.x0 - padout
+ bb0.x1 = bb0.x1 + padin
+ elif self.spine_type == 'bottom':
+ bb0.y0 = bb0.y0 - padout
+ bb0.y1 = bb0.y1 + padin
+
+ if tick.tick2line.get_visible():
+ if self.spine_type == 'right':
+ bb0.x1 = bb0.x1 + padout
+ bb0.x0 = bb0.x0 - padin
+ elif self.spine_type == 'top':
+ bb0.y1 = bb0.y1 + padout
+ bb0.y0 = bb0.y0 - padout
+ bboxes.append(bb0)
+
+ return mtransforms.Bbox.union(bboxes)
+
+ def get_path(self):
+ return self._path
+
+ def _ensure_position_is_set(self):
+ if self._position is None:
+ # default position
+ self._position = ('outward', 0.0) # in points
+ self.set_position(self._position)
+
+ def register_axis(self, axis):
+ """
+ Register an axis.
+
+ An axis should be registered with its corresponding spine from
+ the Axes instance. This allows the spine to clear any axis
+ properties when needed.
+ """
+ self.axis = axis
+ self.stale = True
+
+ def clear(self):
+ """Clear the current spine."""
+ self._clear()
+ if self.axis is not None:
+ self.axis.clear()
+
+ def _clear(self):
+ """
+ Clear things directly related to the spine.
+
+ In this way it is possible to avoid clearing the Axis as well when calling
+ from library code where it is known that the Axis is cleared separately.
+ """
+ self._position = None # clear position
+
+ def _adjust_location(self):
+ """Automatically set spine bounds to the view interval."""
+
+ if self.spine_type == 'circle':
+ return
+
+ if self._bounds is not None:
+ low, high = self._bounds
+ elif self.spine_type in ('left', 'right'):
+ low, high = self.axes.viewLim.intervaly
+ elif self.spine_type in ('top', 'bottom'):
+ low, high = self.axes.viewLim.intervalx
+ else:
+ raise ValueError(f'unknown spine spine_type: {self.spine_type}')
+
+ if self._patch_type == 'arc':
+ if self.spine_type in ('bottom', 'top'):
+ try:
+ direction = self.axes.get_theta_direction()
+ except AttributeError:
+ direction = 1
+ try:
+ offset = self.axes.get_theta_offset()
+ except AttributeError:
+ offset = 0
+ low = low * direction + offset
+ high = high * direction + offset
+ if low > high:
+ low, high = high, low
+
+ self._path = mpath.Path.arc(np.rad2deg(low), np.rad2deg(high))
+
+ if self.spine_type == 'bottom':
+ if self.axis is None:
+ tr = mtransforms.IdentityTransform()
+ else:
+ tr = self.axis.get_transform()
+ rmin, rmax = tr.transform(self.axes.viewLim.intervaly)
+ try:
+ rorigin = self.axes.get_rorigin()
+ except AttributeError:
+ rorigin = rmin
+ else:
+ rorigin = tr.transform(rorigin)
+ scaled_diameter = (rmin - rorigin) / (rmax - rorigin)
+ self._height = scaled_diameter
+ self._width = scaled_diameter
+
+ else:
+ raise ValueError('unable to set bounds for spine "%s"' %
+ self.spine_type)
+ else:
+ v1 = self._path.vertices
+ assert v1.shape == (2, 2), 'unexpected vertices shape'
+ if self.spine_type in ['left', 'right']:
+ v1[0, 1] = low
+ v1[1, 1] = high
+ elif self.spine_type in ['bottom', 'top']:
+ v1[0, 0] = low
+ v1[1, 0] = high
+ else:
+ raise ValueError('unable to set bounds for spine "%s"' %
+ self.spine_type)
+
+ @allow_rasterization
+ def draw(self, renderer):
+ self._adjust_location()
+ ret = super().draw(renderer)
+ self.stale = False
+ return ret
+
+ def set_position(self, position):
+ """
+ Set the position of the spine.
+
+ Spine position is specified by a 2 tuple of (position type,
+ amount). The position types are:
+
+ * 'outward': place the spine out from the data area by the specified
+ number of points. (Negative values place the spine inwards.)
+ * 'axes': place the spine at the specified Axes coordinate (0 to 1).
+ * 'data': place the spine at the specified data coordinate.
+
+ Additionally, shorthand notations define a special positions:
+
+ * 'center' -> ``('axes', 0.5)``
+ * 'zero' -> ``('data', 0.0)``
+
+ Examples
+ --------
+ :doc:`/gallery/spines/spine_placement_demo`
+ """
+ if position in ('center', 'zero'): # special positions
+ pass
+ else:
+ if len(position) != 2:
+ raise ValueError("position should be 'center' or 2-tuple")
+ if position[0] not in ['outward', 'axes', 'data']:
+ raise ValueError("position[0] should be one of 'outward', "
+ "'axes', or 'data' ")
+ self._position = position
+ self.set_transform(self.get_spine_transform())
+ if self.axis is not None:
+ self.axis.reset_ticks()
+ self.stale = True
+
+ def get_position(self):
+ """Return the spine position."""
+ self._ensure_position_is_set()
+ return self._position
+
+ def get_spine_transform(self):
+ """Return the spine transform."""
+ self._ensure_position_is_set()
+
+ position = self._position
+ if isinstance(position, str):
+ if position == 'center':
+ position = ('axes', 0.5)
+ elif position == 'zero':
+ position = ('data', 0)
+ assert len(position) == 2, 'position should be 2-tuple'
+ position_type, amount = position
+ _api.check_in_list(['axes', 'outward', 'data'],
+ position_type=position_type)
+ if self.spine_type in ['left', 'right']:
+ base_transform = self.axes.get_yaxis_transform(which='grid')
+ elif self.spine_type in ['top', 'bottom']:
+ base_transform = self.axes.get_xaxis_transform(which='grid')
+ else:
+ raise ValueError(f'unknown spine spine_type: {self.spine_type!r}')
+
+ if position_type == 'outward':
+ if amount == 0: # short circuit commonest case
+ return base_transform
+ else:
+ offset_vec = {'left': (-1, 0), 'right': (1, 0),
+ 'bottom': (0, -1), 'top': (0, 1),
+ }[self.spine_type]
+ # calculate x and y offset in dots
+ offset_dots = amount * np.array(offset_vec) / 72
+ return (base_transform
+ + mtransforms.ScaledTranslation(
+ *offset_dots, self.get_figure(root=False).dpi_scale_trans))
+ elif position_type == 'axes':
+ if self.spine_type in ['left', 'right']:
+ # keep y unchanged, fix x at amount
+ return (mtransforms.Affine2D.from_values(0, 0, 0, 1, amount, 0)
+ + base_transform)
+ elif self.spine_type in ['bottom', 'top']:
+ # keep x unchanged, fix y at amount
+ return (mtransforms.Affine2D.from_values(1, 0, 0, 0, 0, amount)
+ + base_transform)
+ elif position_type == 'data':
+ if self.spine_type in ('right', 'top'):
+ # The right and top spines have a default position of 1 in
+ # axes coordinates. When specifying the position in data
+ # coordinates, we need to calculate the position relative to 0.
+ amount -= 1
+ if self.spine_type in ('left', 'right'):
+ return mtransforms.blended_transform_factory(
+ mtransforms.Affine2D().translate(amount, 0)
+ + self.axes.transData,
+ self.axes.transData)
+ elif self.spine_type in ('bottom', 'top'):
+ return mtransforms.blended_transform_factory(
+ self.axes.transData,
+ mtransforms.Affine2D().translate(0, amount)
+ + self.axes.transData)
+
+ def set_bounds(self, low=None, high=None):
+ """
+ Set the spine bounds.
+
+ Parameters
+ ----------
+ low : float or None, optional
+ The lower spine bound. Passing *None* leaves the limit unchanged.
+
+ The bounds may also be passed as the tuple (*low*, *high*) as the
+ first positional argument.
+
+ .. ACCEPTS: (low: float, high: float)
+
+ high : float or None, optional
+ The higher spine bound. Passing *None* leaves the limit unchanged.
+ """
+ if self.spine_type == 'circle':
+ raise ValueError(
+ 'set_bounds() method incompatible with circular spines')
+ if high is None and np.iterable(low):
+ low, high = low
+ old_low, old_high = self.get_bounds() or (None, None)
+ if low is None:
+ low = old_low
+ if high is None:
+ high = old_high
+ self._bounds = (low, high)
+ self.stale = True
+
+ def get_bounds(self):
+ """Get the bounds of the spine."""
+ return self._bounds
+
+ @classmethod
+ def linear_spine(cls, axes, spine_type, **kwargs):
+ """Create and return a linear `Spine`."""
+ # all values of 0.999 get replaced upon call to set_bounds()
+ if spine_type == 'left':
+ path = mpath.Path([(0.0, 0.999), (0.0, 0.999)])
+ elif spine_type == 'right':
+ path = mpath.Path([(1.0, 0.999), (1.0, 0.999)])
+ elif spine_type == 'bottom':
+ path = mpath.Path([(0.999, 0.0), (0.999, 0.0)])
+ elif spine_type == 'top':
+ path = mpath.Path([(0.999, 1.0), (0.999, 1.0)])
+ else:
+ raise ValueError('unable to make path for spine "%s"' % spine_type)
+ result = cls(axes, spine_type, path, **kwargs)
+ result.set_visible(mpl.rcParams[f'axes.spines.{spine_type}'])
+
+ return result
+
+ @classmethod
+ def arc_spine(cls, axes, spine_type, center, radius, theta1, theta2,
+ **kwargs):
+ """Create and return an arc `Spine`."""
+ path = mpath.Path.arc(theta1, theta2)
+ result = cls(axes, spine_type, path, **kwargs)
+ result.set_patch_arc(center, radius, theta1, theta2)
+ return result
+
+ @classmethod
+ def circular_spine(cls, axes, center, radius, **kwargs):
+ """Create and return a circular `Spine`."""
+ path = mpath.Path.unit_circle()
+ spine_type = 'circle'
+ result = cls(axes, spine_type, path, **kwargs)
+ result.set_patch_circle(center, radius)
+ return result
+
+ def set_color(self, c):
+ """
+ Set the edgecolor.
+
+ Parameters
+ ----------
+ c : :mpltype:`color`
+
+ Notes
+ -----
+ This method does not modify the facecolor (which defaults to "none"),
+ unlike the `.Patch.set_color` method defined in the parent class. Use
+ `.Patch.set_facecolor` to set the facecolor.
+ """
+ self.set_edgecolor(c)
+ self.stale = True
+
+
+class SpinesProxy:
+ """
+ A proxy to broadcast ``set_*()`` and ``set()`` method calls to contained `.Spines`.
+
+ The proxy cannot be used for any other operations on its members.
+
+ The supported methods are determined dynamically based on the contained
+ spines. If not all spines support a given method, it's executed only on
+ the subset of spines that support it.
+ """
+ def __init__(self, spine_dict):
+ self._spine_dict = spine_dict
+
+ def __getattr__(self, name):
+ broadcast_targets = [spine for spine in self._spine_dict.values()
+ if hasattr(spine, name)]
+ if (name != 'set' and not name.startswith('set_')) or not broadcast_targets:
+ raise AttributeError(
+ f"'SpinesProxy' object has no attribute '{name}'")
+
+ def x(_targets, _funcname, *args, **kwargs):
+ for spine in _targets:
+ getattr(spine, _funcname)(*args, **kwargs)
+ x = functools.partial(x, broadcast_targets, name)
+ x.__doc__ = broadcast_targets[0].__doc__
+ return x
+
+ def __dir__(self):
+ names = []
+ for spine in self._spine_dict.values():
+ names.extend(name
+ for name in dir(spine) if name.startswith('set_'))
+ return list(sorted(set(names)))
+
+
+class Spines(MutableMapping):
+ r"""
+ The container of all `.Spine`\s in an Axes.
+
+ The interface is dict-like mapping names (e.g. 'left') to `.Spine` objects.
+ Additionally, it implements some pandas.Series-like features like accessing
+ elements by attribute::
+
+ spines['top'].set_visible(False)
+ spines.top.set_visible(False)
+
+ Multiple spines can be addressed simultaneously by passing a list::
+
+ spines[['top', 'right']].set_visible(False)
+
+ Use an open slice to address all spines::
+
+ spines[:].set_visible(False)
+
+ The latter two indexing methods will return a `SpinesProxy` that broadcasts all
+ ``set_*()`` and ``set()`` calls to its members, but cannot be used for any other
+ operation.
+ """
+ def __init__(self, **kwargs):
+ self._dict = kwargs
+
+ @classmethod
+ def from_dict(cls, d):
+ return cls(**d)
+
+ def __getstate__(self):
+ return self._dict
+
+ def __setstate__(self, state):
+ self.__init__(**state)
+
+ def __getattr__(self, name):
+ try:
+ return self._dict[name]
+ except KeyError:
+ raise AttributeError(
+ f"'Spines' object does not contain a '{name}' spine")
+
+ def __getitem__(self, key):
+ if isinstance(key, list):
+ unknown_keys = [k for k in key if k not in self._dict]
+ if unknown_keys:
+ raise KeyError(', '.join(unknown_keys))
+ return SpinesProxy({k: v for k, v in self._dict.items()
+ if k in key})
+ if isinstance(key, tuple):
+ raise ValueError('Multiple spines must be passed as a single list')
+ if isinstance(key, slice):
+ if key.start is None and key.stop is None and key.step is None:
+ return SpinesProxy(self._dict)
+ else:
+ raise ValueError(
+ 'Spines does not support slicing except for the fully '
+ 'open slice [:] to access all spines.')
+ return self._dict[key]
+
+ def __setitem__(self, key, value):
+ # TODO: Do we want to deprecate adding spines?
+ self._dict[key] = value
+
+ def __delitem__(self, key):
+ # TODO: Do we want to deprecate deleting spines?
+ del self._dict[key]
+
+ def __iter__(self):
+ return iter(self._dict)
+
+ def __len__(self):
+ return len(self._dict)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/spines.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/spines.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..ff2a1a40bf947005692ccaf2b95977e57a1747b6
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/spines.pyi
@@ -0,0 +1,83 @@
+from collections.abc import Callable, Iterator, MutableMapping
+from typing import Literal, TypeVar, overload
+
+import matplotlib.patches as mpatches
+from matplotlib.axes import Axes
+from matplotlib.axis import Axis
+from matplotlib.path import Path
+from matplotlib.transforms import Transform
+from matplotlib.typing import ColorType
+
+class Spine(mpatches.Patch):
+ axes: Axes
+ spine_type: str
+ axis: Axis | None
+ def __init__(self, axes: Axes, spine_type: str, path: Path, **kwargs) -> None: ...
+ def set_patch_arc(
+ self, center: tuple[float, float], radius: float, theta1: float, theta2: float
+ ) -> None: ...
+ def set_patch_circle(self, center: tuple[float, float], radius: float) -> None: ...
+ def set_patch_line(self) -> None: ...
+ def get_patch_transform(self) -> Transform: ...
+ def get_path(self) -> Path: ...
+ def register_axis(self, axis: Axis) -> None: ...
+ def clear(self) -> None: ...
+ def set_position(
+ self,
+ position: Literal["center", "zero"]
+ | tuple[Literal["outward", "axes", "data"], float],
+ ) -> None: ...
+ def get_position(
+ self,
+ ) -> Literal["center", "zero"] | tuple[
+ Literal["outward", "axes", "data"], float
+ ]: ...
+ def get_spine_transform(self) -> Transform: ...
+ def set_bounds(self, low: float | None = ..., high: float | None = ...) -> None: ...
+ def get_bounds(self) -> tuple[float, float]: ...
+
+ _T = TypeVar("_T", bound=Spine)
+ @classmethod
+ def linear_spine(
+ cls: type[_T],
+ axes: Axes,
+ spine_type: Literal["left", "right", "bottom", "top"],
+ **kwargs
+ ) -> _T: ...
+ @classmethod
+ def arc_spine(
+ cls: type[_T],
+ axes: Axes,
+ spine_type: Literal["left", "right", "bottom", "top"],
+ center: tuple[float, float],
+ radius: float,
+ theta1: float,
+ theta2: float,
+ **kwargs
+ ) -> _T: ...
+ @classmethod
+ def circular_spine(
+ cls: type[_T], axes: Axes, center: tuple[float, float], radius: float, **kwargs
+ ) -> _T: ...
+ def set_color(self, c: ColorType | None) -> None: ...
+
+class SpinesProxy:
+ def __init__(self, spine_dict: dict[str, Spine]) -> None: ...
+ def __getattr__(self, name: str) -> Callable[..., None]: ...
+ def __dir__(self) -> list[str]: ...
+
+class Spines(MutableMapping[str, Spine]):
+ def __init__(self, **kwargs: Spine) -> None: ...
+ @classmethod
+ def from_dict(cls, d: dict[str, Spine]) -> Spines: ...
+ def __getattr__(self, name: str) -> Spine: ...
+ @overload
+ def __getitem__(self, key: str) -> Spine: ...
+ @overload
+ def __getitem__(self, key: list[str]) -> SpinesProxy: ...
+ @overload
+ def __getitem__(self, key: slice) -> SpinesProxy: ...
+ def __setitem__(self, key: str, value: Spine) -> None: ...
+ def __delitem__(self, key: str) -> None: ...
+ def __iter__(self) -> Iterator[str]: ...
+ def __len__(self) -> int: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/stackplot.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/stackplot.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd11558b0da99ad0774ea85b2996e699c6e15717
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/stackplot.py
@@ -0,0 +1,147 @@
+"""
+Stacked area plot for 1D arrays inspired by Douglas Y'barbo's stackoverflow
+answer:
+https://stackoverflow.com/q/2225995/
+
+(https://stackoverflow.com/users/66549/doug)
+"""
+
+import itertools
+
+import numpy as np
+
+from matplotlib import _api
+
+__all__ = ['stackplot']
+
+
+def stackplot(axes, x, *args,
+ labels=(), colors=None, hatch=None, baseline='zero',
+ **kwargs):
+ """
+ Draw a stacked area plot or a streamgraph.
+
+ Parameters
+ ----------
+ x : (N,) array-like
+
+ y : (M, N) array-like
+ The data can be either stacked or unstacked. Each of the following
+ calls is legal::
+
+ stackplot(x, y) # where y has shape (M, N) e.g. y = [y1, y2, y3, y4]
+ stackplot(x, y1, y2, y3, y4) # where y1, y2, y3, y4 have length N
+
+ baseline : {'zero', 'sym', 'wiggle', 'weighted_wiggle'}
+ Method used to calculate the baseline:
+
+ - ``'zero'``: Constant zero baseline, i.e. a simple stacked plot.
+ - ``'sym'``: Symmetric around zero and is sometimes called
+ 'ThemeRiver'.
+ - ``'wiggle'``: Minimizes the sum of the squared slopes.
+ - ``'weighted_wiggle'``: Does the same but weights to account for
+ size of each layer. It is also called 'Streamgraph'-layout. More
+ details can be found at http://leebyron.com/streamgraph/.
+
+ labels : list of str, optional
+ A sequence of labels to assign to each data series. If unspecified,
+ then no labels will be applied to artists.
+
+ colors : list of :mpltype:`color`, optional
+ A sequence of colors to be cycled through and used to color the stacked
+ areas. The sequence need not be exactly the same length as the number
+ of provided *y*, in which case the colors will repeat from the
+ beginning.
+
+ If not specified, the colors from the Axes property cycle will be used.
+
+ hatch : list of str, default: None
+ A sequence of hatching styles. See
+ :doc:`/gallery/shapes_and_collections/hatch_style_reference`.
+ The sequence will be cycled through for filling the
+ stacked areas from bottom to top.
+ It need not be exactly the same length as the number
+ of provided *y*, in which case the styles will repeat from the
+ beginning.
+
+ .. versionadded:: 3.9
+ Support for list input
+
+ data : indexable object, optional
+ DATA_PARAMETER_PLACEHOLDER
+
+ **kwargs
+ All other keyword arguments are passed to `.Axes.fill_between`.
+
+ Returns
+ -------
+ list of `.PolyCollection`
+ A list of `.PolyCollection` instances, one for each element in the
+ stacked area plot.
+ """
+
+ y = np.vstack(args)
+
+ labels = iter(labels)
+ if colors is not None:
+ colors = itertools.cycle(colors)
+ else:
+ colors = (axes._get_lines.get_next_color() for _ in y)
+
+ if hatch is None or isinstance(hatch, str):
+ hatch = itertools.cycle([hatch])
+ else:
+ hatch = itertools.cycle(hatch)
+
+ # Assume data passed has not been 'stacked', so stack it here.
+ # We'll need a float buffer for the upcoming calculations.
+ stack = np.cumsum(y, axis=0, dtype=np.promote_types(y.dtype, np.float32))
+
+ _api.check_in_list(['zero', 'sym', 'wiggle', 'weighted_wiggle'],
+ baseline=baseline)
+ if baseline == 'zero':
+ first_line = 0.
+
+ elif baseline == 'sym':
+ first_line = -np.sum(y, 0) * 0.5
+ stack += first_line[None, :]
+
+ elif baseline == 'wiggle':
+ m = y.shape[0]
+ first_line = (y * (m - 0.5 - np.arange(m)[:, None])).sum(0)
+ first_line /= -m
+ stack += first_line
+
+ elif baseline == 'weighted_wiggle':
+ total = np.sum(y, 0)
+ # multiply by 1/total (or zero) to avoid infinities in the division:
+ inv_total = np.zeros_like(total)
+ mask = total > 0
+ inv_total[mask] = 1.0 / total[mask]
+ increase = np.hstack((y[:, 0:1], np.diff(y)))
+ below_size = total - stack
+ below_size += 0.5 * y
+ move_up = below_size * inv_total
+ move_up[:, 0] = 0.5
+ center = (move_up - 0.5) * increase
+ center = np.cumsum(center.sum(0))
+ first_line = center - 0.5 * total
+ stack += first_line
+
+ # Color between x = 0 and the first array.
+ coll = axes.fill_between(x, first_line, stack[0, :],
+ facecolor=next(colors),
+ hatch=next(hatch),
+ label=next(labels, None),
+ **kwargs)
+ coll.sticky_edges.y[:] = [0]
+ r = [coll]
+
+ # Color between array i-1 and array i
+ for i in range(len(y) - 1):
+ r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :],
+ facecolor=next(colors),
+ hatch=next(hatch),
+ label=next(labels, None),
+ **kwargs))
+ return r
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/stackplot.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/stackplot.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..9509f858a4bfbf6fd54fa1111077341b31ec03b2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/stackplot.pyi
@@ -0,0 +1,20 @@
+from matplotlib.axes import Axes
+from matplotlib.collections import PolyCollection
+
+from collections.abc import Iterable
+from typing import Literal
+from numpy.typing import ArrayLike
+from matplotlib.typing import ColorType
+
+def stackplot(
+ axes: Axes,
+ x: ArrayLike,
+ *args: ArrayLike,
+ labels: Iterable[str] = ...,
+ colors: Iterable[ColorType] | None = ...,
+ hatch: Iterable[str] | str | None = ...,
+ baseline: Literal["zero", "sym", "wiggle", "weighted_wiggle"] = ...,
+ **kwargs
+) -> list[PolyCollection]: ...
+
+__all__ = ['stackplot']
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/streamplot.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/streamplot.py
new file mode 100644
index 0000000000000000000000000000000000000000..84f99732c709d25a0efb2518156ef213f3ee6a4d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/streamplot.py
@@ -0,0 +1,712 @@
+"""
+Streamline plotting for 2D vector fields.
+
+"""
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api, cm, patches
+import matplotlib.colors as mcolors
+import matplotlib.collections as mcollections
+import matplotlib.lines as mlines
+
+
+__all__ = ['streamplot']
+
+
+def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
+ cmap=None, norm=None, arrowsize=1, arrowstyle='-|>',
+ minlength=0.1, transform=None, zorder=None, start_points=None,
+ maxlength=4.0, integration_direction='both',
+ broken_streamlines=True):
+ """
+ Draw streamlines of a vector flow.
+
+ Parameters
+ ----------
+ x, y : 1D/2D arrays
+ Evenly spaced strictly increasing arrays to make a grid. If 2D, all
+ rows of *x* must be equal and all columns of *y* must be equal; i.e.,
+ they must be as if generated by ``np.meshgrid(x_1d, y_1d)``.
+ u, v : 2D arrays
+ *x* and *y*-velocities. The number of rows and columns must match
+ the length of *y* and *x*, respectively.
+ density : float or (float, float)
+ Controls the closeness of streamlines. When ``density = 1``, the domain
+ is divided into a 30x30 grid. *density* linearly scales this grid.
+ Each cell in the grid can have, at most, one traversing streamline.
+ For different densities in each direction, use a tuple
+ (density_x, density_y).
+ linewidth : float or 2D array
+ The width of the streamlines. With a 2D array the line width can be
+ varied across the grid. The array must have the same shape as *u*
+ and *v*.
+ color : :mpltype:`color` or 2D array
+ The streamline color. If given an array, its values are converted to
+ colors using *cmap* and *norm*. The array must have the same shape
+ as *u* and *v*.
+ cmap, norm
+ Data normalization and colormapping parameters for *color*; only used
+ if *color* is an array of floats. See `~.Axes.imshow` for a detailed
+ description.
+ arrowsize : float
+ Scaling factor for the arrow size.
+ arrowstyle : str
+ Arrow style specification.
+ See `~matplotlib.patches.FancyArrowPatch`.
+ minlength : float
+ Minimum length of streamline in axes coordinates.
+ start_points : (N, 2) array
+ Coordinates of starting points for the streamlines in data coordinates
+ (the same coordinates as the *x* and *y* arrays).
+ zorder : float
+ The zorder of the streamlines and arrows.
+ Artists with lower zorder values are drawn first.
+ maxlength : float
+ Maximum length of streamline in axes coordinates.
+ integration_direction : {'forward', 'backward', 'both'}, default: 'both'
+ Integrate the streamline in forward, backward or both directions.
+ data : indexable object, optional
+ DATA_PARAMETER_PLACEHOLDER
+ broken_streamlines : boolean, default: True
+ If False, forces streamlines to continue until they
+ leave the plot domain. If True, they may be terminated if they
+ come too close to another streamline.
+
+ Returns
+ -------
+ StreamplotSet
+ Container object with attributes
+
+ - ``lines``: `.LineCollection` of streamlines
+
+ - ``arrows``: `.PatchCollection` containing `.FancyArrowPatch`
+ objects representing the arrows half-way along streamlines.
+
+ This container will probably change in the future to allow changes
+ to the colormap, alpha, etc. for both lines and arrows, but these
+ changes should be backward compatible.
+ """
+ grid = Grid(x, y)
+ mask = StreamMask(density)
+ dmap = DomainMap(grid, mask)
+
+ if zorder is None:
+ zorder = mlines.Line2D.zorder
+
+ # default to data coordinates
+ if transform is None:
+ transform = axes.transData
+
+ if color is None:
+ color = axes._get_lines.get_next_color()
+
+ if linewidth is None:
+ linewidth = mpl.rcParams['lines.linewidth']
+
+ line_kw = {}
+ arrow_kw = dict(arrowstyle=arrowstyle, mutation_scale=10 * arrowsize)
+
+ _api.check_in_list(['both', 'forward', 'backward'],
+ integration_direction=integration_direction)
+
+ if integration_direction == 'both':
+ maxlength /= 2.
+
+ use_multicolor_lines = isinstance(color, np.ndarray)
+ if use_multicolor_lines:
+ if color.shape != grid.shape:
+ raise ValueError("If 'color' is given, it must match the shape of "
+ "the (x, y) grid")
+ line_colors = [[]] # Empty entry allows concatenation of zero arrays.
+ color = np.ma.masked_invalid(color)
+ else:
+ line_kw['color'] = color
+ arrow_kw['color'] = color
+
+ if isinstance(linewidth, np.ndarray):
+ if linewidth.shape != grid.shape:
+ raise ValueError("If 'linewidth' is given, it must match the "
+ "shape of the (x, y) grid")
+ line_kw['linewidth'] = []
+ else:
+ line_kw['linewidth'] = linewidth
+ arrow_kw['linewidth'] = linewidth
+
+ line_kw['zorder'] = zorder
+ arrow_kw['zorder'] = zorder
+
+ # Sanity checks.
+ if u.shape != grid.shape or v.shape != grid.shape:
+ raise ValueError("'u' and 'v' must match the shape of the (x, y) grid")
+
+ u = np.ma.masked_invalid(u)
+ v = np.ma.masked_invalid(v)
+
+ integrate = _get_integrator(u, v, dmap, minlength, maxlength,
+ integration_direction)
+
+ trajectories = []
+ if start_points is None:
+ for xm, ym in _gen_starting_points(mask.shape):
+ if mask[ym, xm] == 0:
+ xg, yg = dmap.mask2grid(xm, ym)
+ t = integrate(xg, yg, broken_streamlines)
+ if t is not None:
+ trajectories.append(t)
+ else:
+ sp2 = np.asanyarray(start_points, dtype=float).copy()
+
+ # Check if start_points are outside the data boundaries
+ for xs, ys in sp2:
+ if not (grid.x_origin <= xs <= grid.x_origin + grid.width and
+ grid.y_origin <= ys <= grid.y_origin + grid.height):
+ raise ValueError(f"Starting point ({xs}, {ys}) outside of "
+ "data boundaries")
+
+ # Convert start_points from data to array coords
+ # Shift the seed points from the bottom left of the data so that
+ # data2grid works properly.
+ sp2[:, 0] -= grid.x_origin
+ sp2[:, 1] -= grid.y_origin
+
+ for xs, ys in sp2:
+ xg, yg = dmap.data2grid(xs, ys)
+ # Floating point issues can cause xg, yg to be slightly out of
+ # bounds for xs, ys on the upper boundaries. Because we have
+ # already checked that the starting points are within the original
+ # grid, clip the xg, yg to the grid to work around this issue
+ xg = np.clip(xg, 0, grid.nx - 1)
+ yg = np.clip(yg, 0, grid.ny - 1)
+
+ t = integrate(xg, yg, broken_streamlines)
+ if t is not None:
+ trajectories.append(t)
+
+ if use_multicolor_lines:
+ if norm is None:
+ norm = mcolors.Normalize(color.min(), color.max())
+ cmap = cm._ensure_cmap(cmap)
+
+ streamlines = []
+ arrows = []
+ for t in trajectories:
+ tgx, tgy = t.T
+ # Rescale from grid-coordinates to data-coordinates.
+ tx, ty = dmap.grid2data(tgx, tgy)
+ tx += grid.x_origin
+ ty += grid.y_origin
+
+ # Create multiple tiny segments if varying width or color is given
+ if isinstance(linewidth, np.ndarray) or use_multicolor_lines:
+ points = np.transpose([tx, ty]).reshape(-1, 1, 2)
+ streamlines.extend(np.hstack([points[:-1], points[1:]]))
+ else:
+ points = np.transpose([tx, ty])
+ streamlines.append(points)
+
+ # Add arrows halfway along each trajectory.
+ s = np.cumsum(np.hypot(np.diff(tx), np.diff(ty)))
+ n = np.searchsorted(s, s[-1] / 2.)
+ arrow_tail = (tx[n], ty[n])
+ arrow_head = (np.mean(tx[n:n + 2]), np.mean(ty[n:n + 2]))
+
+ if isinstance(linewidth, np.ndarray):
+ line_widths = interpgrid(linewidth, tgx, tgy)[:-1]
+ line_kw['linewidth'].extend(line_widths)
+ arrow_kw['linewidth'] = line_widths[n]
+
+ if use_multicolor_lines:
+ color_values = interpgrid(color, tgx, tgy)[:-1]
+ line_colors.append(color_values)
+ arrow_kw['color'] = cmap(norm(color_values[n]))
+
+ p = patches.FancyArrowPatch(
+ arrow_tail, arrow_head, transform=transform, **arrow_kw)
+ arrows.append(p)
+
+ lc = mcollections.LineCollection(
+ streamlines, transform=transform, **line_kw)
+ lc.sticky_edges.x[:] = [grid.x_origin, grid.x_origin + grid.width]
+ lc.sticky_edges.y[:] = [grid.y_origin, grid.y_origin + grid.height]
+ if use_multicolor_lines:
+ lc.set_array(np.ma.hstack(line_colors))
+ lc.set_cmap(cmap)
+ lc.set_norm(norm)
+ axes.add_collection(lc)
+
+ ac = mcollections.PatchCollection(arrows)
+ # Adding the collection itself is broken; see #2341.
+ for p in arrows:
+ axes.add_patch(p)
+
+ axes.autoscale_view()
+ stream_container = StreamplotSet(lc, ac)
+ return stream_container
+
+
+class StreamplotSet:
+
+ def __init__(self, lines, arrows):
+ self.lines = lines
+ self.arrows = arrows
+
+
+# Coordinate definitions
+# ========================
+
+class DomainMap:
+ """
+ Map representing different coordinate systems.
+
+ Coordinate definitions:
+
+ * axes-coordinates goes from 0 to 1 in the domain.
+ * data-coordinates are specified by the input x-y coordinates.
+ * grid-coordinates goes from 0 to N and 0 to M for an N x M grid,
+ where N and M match the shape of the input data.
+ * mask-coordinates goes from 0 to N and 0 to M for an N x M mask,
+ where N and M are user-specified to control the density of streamlines.
+
+ This class also has methods for adding trajectories to the StreamMask.
+ Before adding a trajectory, run `start_trajectory` to keep track of regions
+ crossed by a given trajectory. Later, if you decide the trajectory is bad
+ (e.g., if the trajectory is very short) just call `undo_trajectory`.
+ """
+
+ def __init__(self, grid, mask):
+ self.grid = grid
+ self.mask = mask
+ # Constants for conversion between grid- and mask-coordinates
+ self.x_grid2mask = (mask.nx - 1) / (grid.nx - 1)
+ self.y_grid2mask = (mask.ny - 1) / (grid.ny - 1)
+
+ self.x_mask2grid = 1. / self.x_grid2mask
+ self.y_mask2grid = 1. / self.y_grid2mask
+
+ self.x_data2grid = 1. / grid.dx
+ self.y_data2grid = 1. / grid.dy
+
+ def grid2mask(self, xi, yi):
+ """Return nearest space in mask-coords from given grid-coords."""
+ return round(xi * self.x_grid2mask), round(yi * self.y_grid2mask)
+
+ def mask2grid(self, xm, ym):
+ return xm * self.x_mask2grid, ym * self.y_mask2grid
+
+ def data2grid(self, xd, yd):
+ return xd * self.x_data2grid, yd * self.y_data2grid
+
+ def grid2data(self, xg, yg):
+ return xg / self.x_data2grid, yg / self.y_data2grid
+
+ def start_trajectory(self, xg, yg, broken_streamlines=True):
+ xm, ym = self.grid2mask(xg, yg)
+ self.mask._start_trajectory(xm, ym, broken_streamlines)
+
+ def reset_start_point(self, xg, yg):
+ xm, ym = self.grid2mask(xg, yg)
+ self.mask._current_xy = (xm, ym)
+
+ def update_trajectory(self, xg, yg, broken_streamlines=True):
+ if not self.grid.within_grid(xg, yg):
+ raise InvalidIndexError
+ xm, ym = self.grid2mask(xg, yg)
+ self.mask._update_trajectory(xm, ym, broken_streamlines)
+
+ def undo_trajectory(self):
+ self.mask._undo_trajectory()
+
+
+class Grid:
+ """Grid of data."""
+ def __init__(self, x, y):
+
+ if np.ndim(x) == 1:
+ pass
+ elif np.ndim(x) == 2:
+ x_row = x[0]
+ if not np.allclose(x_row, x):
+ raise ValueError("The rows of 'x' must be equal")
+ x = x_row
+ else:
+ raise ValueError("'x' can have at maximum 2 dimensions")
+
+ if np.ndim(y) == 1:
+ pass
+ elif np.ndim(y) == 2:
+ yt = np.transpose(y) # Also works for nested lists.
+ y_col = yt[0]
+ if not np.allclose(y_col, yt):
+ raise ValueError("The columns of 'y' must be equal")
+ y = y_col
+ else:
+ raise ValueError("'y' can have at maximum 2 dimensions")
+
+ if not (np.diff(x) > 0).all():
+ raise ValueError("'x' must be strictly increasing")
+ if not (np.diff(y) > 0).all():
+ raise ValueError("'y' must be strictly increasing")
+
+ self.nx = len(x)
+ self.ny = len(y)
+
+ self.dx = x[1] - x[0]
+ self.dy = y[1] - y[0]
+
+ self.x_origin = x[0]
+ self.y_origin = y[0]
+
+ self.width = x[-1] - x[0]
+ self.height = y[-1] - y[0]
+
+ if not np.allclose(np.diff(x), self.width / (self.nx - 1)):
+ raise ValueError("'x' values must be equally spaced")
+ if not np.allclose(np.diff(y), self.height / (self.ny - 1)):
+ raise ValueError("'y' values must be equally spaced")
+
+ @property
+ def shape(self):
+ return self.ny, self.nx
+
+ def within_grid(self, xi, yi):
+ """Return whether (*xi*, *yi*) is a valid index of the grid."""
+ # Note that xi/yi can be floats; so, for example, we can't simply check
+ # `xi < self.nx` since *xi* can be `self.nx - 1 < xi < self.nx`
+ return 0 <= xi <= self.nx - 1 and 0 <= yi <= self.ny - 1
+
+
+class StreamMask:
+ """
+ Mask to keep track of discrete regions crossed by streamlines.
+
+ The resolution of this grid determines the approximate spacing between
+ trajectories. Streamlines are only allowed to pass through zeroed cells:
+ When a streamline enters a cell, that cell is set to 1, and no new
+ streamlines are allowed to enter.
+ """
+
+ def __init__(self, density):
+ try:
+ self.nx, self.ny = (30 * np.broadcast_to(density, 2)).astype(int)
+ except ValueError as err:
+ raise ValueError("'density' must be a scalar or be of length "
+ "2") from err
+ if self.nx < 0 or self.ny < 0:
+ raise ValueError("'density' must be positive")
+ self._mask = np.zeros((self.ny, self.nx))
+ self.shape = self._mask.shape
+
+ self._current_xy = None
+
+ def __getitem__(self, args):
+ return self._mask[args]
+
+ def _start_trajectory(self, xm, ym, broken_streamlines=True):
+ """Start recording streamline trajectory"""
+ self._traj = []
+ self._update_trajectory(xm, ym, broken_streamlines)
+
+ def _undo_trajectory(self):
+ """Remove current trajectory from mask"""
+ for t in self._traj:
+ self._mask[t] = 0
+
+ def _update_trajectory(self, xm, ym, broken_streamlines=True):
+ """
+ Update current trajectory position in mask.
+
+ If the new position has already been filled, raise `InvalidIndexError`.
+ """
+ if self._current_xy != (xm, ym):
+ if self[ym, xm] == 0:
+ self._traj.append((ym, xm))
+ self._mask[ym, xm] = 1
+ self._current_xy = (xm, ym)
+ else:
+ if broken_streamlines:
+ raise InvalidIndexError
+ else:
+ pass
+
+
+class InvalidIndexError(Exception):
+ pass
+
+
+class TerminateTrajectory(Exception):
+ pass
+
+
+# Integrator definitions
+# =======================
+
+def _get_integrator(u, v, dmap, minlength, maxlength, integration_direction):
+
+ # rescale velocity onto grid-coordinates for integrations.
+ u, v = dmap.data2grid(u, v)
+
+ # speed (path length) will be in axes-coordinates
+ u_ax = u / (dmap.grid.nx - 1)
+ v_ax = v / (dmap.grid.ny - 1)
+ speed = np.ma.sqrt(u_ax ** 2 + v_ax ** 2)
+
+ def forward_time(xi, yi):
+ if not dmap.grid.within_grid(xi, yi):
+ raise OutOfBounds
+ ds_dt = interpgrid(speed, xi, yi)
+ if ds_dt == 0:
+ raise TerminateTrajectory()
+ dt_ds = 1. / ds_dt
+ ui = interpgrid(u, xi, yi)
+ vi = interpgrid(v, xi, yi)
+ return ui * dt_ds, vi * dt_ds
+
+ def backward_time(xi, yi):
+ dxi, dyi = forward_time(xi, yi)
+ return -dxi, -dyi
+
+ def integrate(x0, y0, broken_streamlines=True):
+ """
+ Return x, y grid-coordinates of trajectory based on starting point.
+
+ Integrate both forward and backward in time from starting point in
+ grid coordinates.
+
+ Integration is terminated when a trajectory reaches a domain boundary
+ or when it crosses into an already occupied cell in the StreamMask. The
+ resulting trajectory is None if it is shorter than `minlength`.
+ """
+
+ stotal, xy_traj = 0., []
+
+ try:
+ dmap.start_trajectory(x0, y0, broken_streamlines)
+ except InvalidIndexError:
+ return None
+ if integration_direction in ['both', 'backward']:
+ s, xyt = _integrate_rk12(x0, y0, dmap, backward_time, maxlength,
+ broken_streamlines)
+ stotal += s
+ xy_traj += xyt[::-1]
+
+ if integration_direction in ['both', 'forward']:
+ dmap.reset_start_point(x0, y0)
+ s, xyt = _integrate_rk12(x0, y0, dmap, forward_time, maxlength,
+ broken_streamlines)
+ stotal += s
+ xy_traj += xyt[1:]
+
+ if stotal > minlength:
+ return np.broadcast_arrays(xy_traj, np.empty((1, 2)))[0]
+ else: # reject short trajectories
+ dmap.undo_trajectory()
+ return None
+
+ return integrate
+
+
+class OutOfBounds(IndexError):
+ pass
+
+
+def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True):
+ """
+ 2nd-order Runge-Kutta algorithm with adaptive step size.
+
+ This method is also referred to as the improved Euler's method, or Heun's
+ method. This method is favored over higher-order methods because:
+
+ 1. To get decent looking trajectories and to sample every mask cell
+ on the trajectory we need a small timestep, so a lower order
+ solver doesn't hurt us unless the data is *very* high resolution.
+ In fact, for cases where the user inputs
+ data smaller or of similar grid size to the mask grid, the higher
+ order corrections are negligible because of the very fast linear
+ interpolation used in `interpgrid`.
+
+ 2. For high resolution input data (i.e. beyond the mask
+ resolution), we must reduce the timestep. Therefore, an adaptive
+ timestep is more suited to the problem as this would be very hard
+ to judge automatically otherwise.
+
+ This integrator is about 1.5 - 2x as fast as RK4 and RK45 solvers (using
+ similar Python implementations) in most setups.
+ """
+ # This error is below that needed to match the RK4 integrator. It
+ # is set for visual reasons -- too low and corners start
+ # appearing ugly and jagged. Can be tuned.
+ maxerror = 0.003
+
+ # This limit is important (for all integrators) to avoid the
+ # trajectory skipping some mask cells. We could relax this
+ # condition if we use the code which is commented out below to
+ # increment the location gradually. However, due to the efficient
+ # nature of the interpolation, this doesn't boost speed by much
+ # for quite a bit of complexity.
+ maxds = min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1)
+
+ ds = maxds
+ stotal = 0
+ xi = x0
+ yi = y0
+ xyf_traj = []
+
+ while True:
+ try:
+ if dmap.grid.within_grid(xi, yi):
+ xyf_traj.append((xi, yi))
+ else:
+ raise OutOfBounds
+
+ # Compute the two intermediate gradients.
+ # f should raise OutOfBounds if the locations given are
+ # outside the grid.
+ k1x, k1y = f(xi, yi)
+ k2x, k2y = f(xi + ds * k1x, yi + ds * k1y)
+
+ except OutOfBounds:
+ # Out of the domain during this step.
+ # Take an Euler step to the boundary to improve neatness
+ # unless the trajectory is currently empty.
+ if xyf_traj:
+ ds, xyf_traj = _euler_step(xyf_traj, dmap, f)
+ stotal += ds
+ break
+ except TerminateTrajectory:
+ break
+
+ dx1 = ds * k1x
+ dy1 = ds * k1y
+ dx2 = ds * 0.5 * (k1x + k2x)
+ dy2 = ds * 0.5 * (k1y + k2y)
+
+ ny, nx = dmap.grid.shape
+ # Error is normalized to the axes coordinates
+ error = np.hypot((dx2 - dx1) / (nx - 1), (dy2 - dy1) / (ny - 1))
+
+ # Only save step if within error tolerance
+ if error < maxerror:
+ xi += dx2
+ yi += dy2
+ try:
+ dmap.update_trajectory(xi, yi, broken_streamlines)
+ except InvalidIndexError:
+ break
+ if stotal + ds > maxlength:
+ break
+ stotal += ds
+
+ # recalculate stepsize based on step error
+ if error == 0:
+ ds = maxds
+ else:
+ ds = min(maxds, 0.85 * ds * (maxerror / error) ** 0.5)
+
+ return stotal, xyf_traj
+
+
+def _euler_step(xyf_traj, dmap, f):
+ """Simple Euler integration step that extends streamline to boundary."""
+ ny, nx = dmap.grid.shape
+ xi, yi = xyf_traj[-1]
+ cx, cy = f(xi, yi)
+ if cx == 0:
+ dsx = np.inf
+ elif cx < 0:
+ dsx = xi / -cx
+ else:
+ dsx = (nx - 1 - xi) / cx
+ if cy == 0:
+ dsy = np.inf
+ elif cy < 0:
+ dsy = yi / -cy
+ else:
+ dsy = (ny - 1 - yi) / cy
+ ds = min(dsx, dsy)
+ xyf_traj.append((xi + cx * ds, yi + cy * ds))
+ return ds, xyf_traj
+
+
+# Utility functions
+# ========================
+
+def interpgrid(a, xi, yi):
+ """Fast 2D, linear interpolation on an integer grid"""
+
+ Ny, Nx = np.shape(a)
+ if isinstance(xi, np.ndarray):
+ x = xi.astype(int)
+ y = yi.astype(int)
+ # Check that xn, yn don't exceed max index
+ xn = np.clip(x + 1, 0, Nx - 1)
+ yn = np.clip(y + 1, 0, Ny - 1)
+ else:
+ x = int(xi)
+ y = int(yi)
+ # conditional is faster than clipping for integers
+ if x == (Nx - 1):
+ xn = x
+ else:
+ xn = x + 1
+ if y == (Ny - 1):
+ yn = y
+ else:
+ yn = y + 1
+
+ a00 = a[y, x]
+ a01 = a[y, xn]
+ a10 = a[yn, x]
+ a11 = a[yn, xn]
+ xt = xi - x
+ yt = yi - y
+ a0 = a00 * (1 - xt) + a01 * xt
+ a1 = a10 * (1 - xt) + a11 * xt
+ ai = a0 * (1 - yt) + a1 * yt
+
+ if not isinstance(xi, np.ndarray):
+ if np.ma.is_masked(ai):
+ raise TerminateTrajectory
+
+ return ai
+
+
+def _gen_starting_points(shape):
+ """
+ Yield starting points for streamlines.
+
+ Trying points on the boundary first gives higher quality streamlines.
+ This algorithm starts with a point on the mask corner and spirals inward.
+ This algorithm is inefficient, but fast compared to rest of streamplot.
+ """
+ ny, nx = shape
+ xfirst = 0
+ yfirst = 1
+ xlast = nx - 1
+ ylast = ny - 1
+ x, y = 0, 0
+ direction = 'right'
+ for i in range(nx * ny):
+ yield x, y
+
+ if direction == 'right':
+ x += 1
+ if x >= xlast:
+ xlast -= 1
+ direction = 'up'
+ elif direction == 'up':
+ y += 1
+ if y >= ylast:
+ ylast -= 1
+ direction = 'left'
+ elif direction == 'left':
+ x -= 1
+ if x <= xfirst:
+ xfirst += 1
+ direction = 'down'
+ elif direction == 'down':
+ y -= 1
+ if y <= yfirst:
+ yfirst += 1
+ direction = 'right'
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/streamplot.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/streamplot.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..be745802044907a32084934576269eaf228e07e4
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/streamplot.pyi
@@ -0,0 +1,84 @@
+from matplotlib.axes import Axes
+from matplotlib.colors import Normalize, Colormap
+from matplotlib.collections import LineCollection, PatchCollection
+from matplotlib.patches import ArrowStyle
+from matplotlib.transforms import Transform
+
+from typing import Literal
+from numpy.typing import ArrayLike
+from .typing import ColorType
+
+def streamplot(
+ axes: Axes,
+ x: ArrayLike,
+ y: ArrayLike,
+ u: ArrayLike,
+ v: ArrayLike,
+ density: float | tuple[float, float] = ...,
+ linewidth: float | ArrayLike | None = ...,
+ color: ColorType | ArrayLike | None = ...,
+ cmap: str | Colormap | None = ...,
+ norm: str | Normalize | None = ...,
+ arrowsize: float = ...,
+ arrowstyle: str | ArrowStyle = ...,
+ minlength: float = ...,
+ transform: Transform | None = ...,
+ zorder: float | None = ...,
+ start_points: ArrayLike | None = ...,
+ maxlength: float = ...,
+ integration_direction: Literal["forward", "backward", "both"] = ...,
+ broken_streamlines: bool = ...,
+) -> StreamplotSet: ...
+
+class StreamplotSet:
+ lines: LineCollection
+ arrows: PatchCollection
+ def __init__(self, lines: LineCollection, arrows: PatchCollection) -> None: ...
+
+class DomainMap:
+ grid: Grid
+ mask: StreamMask
+ x_grid2mask: float
+ y_grid2mask: float
+ x_mask2grid: float
+ y_mask2grid: float
+ x_data2grid: float
+ y_data2grid: float
+ def __init__(self, grid: Grid, mask: StreamMask) -> None: ...
+ def grid2mask(self, xi: float, yi: float) -> tuple[int, int]: ...
+ def mask2grid(self, xm: float, ym: float) -> tuple[float, float]: ...
+ def data2grid(self, xd: float, yd: float) -> tuple[float, float]: ...
+ def grid2data(self, xg: float, yg: float) -> tuple[float, float]: ...
+ def start_trajectory(
+ self, xg: float, yg: float, broken_streamlines: bool = ...
+ ) -> None: ...
+ def reset_start_point(self, xg: float, yg: float) -> None: ...
+ def update_trajectory(self, xg, yg, broken_streamlines: bool = ...) -> None: ...
+ def undo_trajectory(self) -> None: ...
+
+class Grid:
+ nx: int
+ ny: int
+ dx: float
+ dy: float
+ x_origin: float
+ y_origin: float
+ width: float
+ height: float
+ def __init__(self, x: ArrayLike, y: ArrayLike) -> None: ...
+ @property
+ def shape(self) -> tuple[int, int]: ...
+ def within_grid(self, xi: float, yi: float) -> bool: ...
+
+class StreamMask:
+ nx: int
+ ny: int
+ shape: tuple[int, int]
+ def __init__(self, density: float | tuple[float, float]) -> None: ...
+ def __getitem__(self, args): ...
+
+class InvalidIndexError(Exception): ...
+class TerminateTrajectory(Exception): ...
+class OutOfBounds(IndexError): ...
+
+__all__ = ['streamplot']
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/style/__init__.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/style/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..488c6d6ae1ec310cdd84bb209b4103da26fed284
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/style/__init__.py
@@ -0,0 +1,4 @@
+from .core import available, context, library, reload_library, use
+
+
+__all__ = ["available", "context", "library", "reload_library", "use"]
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/style/core.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/style/core.py
new file mode 100644
index 0000000000000000000000000000000000000000..e36c3c37a882492afbb71716b4d98a46208bfe55
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/style/core.py
@@ -0,0 +1,237 @@
+"""
+Core functions and attributes for the matplotlib style library:
+
+``use``
+ Select style sheet to override the current matplotlib settings.
+``context``
+ Context manager to use a style sheet temporarily.
+``available``
+ List available style sheets.
+``library``
+ A dictionary of style names and matplotlib settings.
+"""
+
+import contextlib
+import importlib.resources
+import logging
+import os
+from pathlib import Path
+import warnings
+
+import matplotlib as mpl
+from matplotlib import _api, _docstring, _rc_params_in_file, rcParamsDefault
+
+_log = logging.getLogger(__name__)
+
+__all__ = ['use', 'context', 'available', 'library', 'reload_library']
+
+
+BASE_LIBRARY_PATH = os.path.join(mpl.get_data_path(), 'stylelib')
+# Users may want multiple library paths, so store a list of paths.
+USER_LIBRARY_PATHS = [os.path.join(mpl.get_configdir(), 'stylelib')]
+STYLE_EXTENSION = 'mplstyle'
+# A list of rcParams that should not be applied from styles
+STYLE_BLACKLIST = {
+ 'interactive', 'backend', 'webagg.port', 'webagg.address',
+ 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback',
+ 'toolbar', 'timezone', 'figure.max_open_warning',
+ 'figure.raise_window', 'savefig.directory', 'tk.window_focus',
+ 'docstring.hardcopy', 'date.epoch'}
+
+
+@_docstring.Substitution(
+ "\n".join(map("- {}".format, sorted(STYLE_BLACKLIST, key=str.lower)))
+)
+def use(style):
+ """
+ Use Matplotlib style settings from a style specification.
+
+ The style name of 'default' is reserved for reverting back to
+ the default style settings.
+
+ .. note::
+
+ This updates the `.rcParams` with the settings from the style.
+ `.rcParams` not defined in the style are kept.
+
+ Parameters
+ ----------
+ style : str, dict, Path or list
+
+ A style specification. Valid options are:
+
+ str
+ - One of the style names in `.style.available` (a builtin style or
+ a style installed in the user library path).
+
+ - A dotted name of the form "package.style_name"; in that case,
+ "package" should be an importable Python package name, e.g. at
+ ``/path/to/package/__init__.py``; the loaded style file is
+ ``/path/to/package/style_name.mplstyle``. (Style files in
+ subpackages are likewise supported.)
+
+ - The path or URL to a style file, which gets loaded by
+ `.rc_params_from_file`.
+
+ dict
+ A mapping of key/value pairs for `matplotlib.rcParams`.
+
+ Path
+ The path to a style file, which gets loaded by
+ `.rc_params_from_file`.
+
+ list
+ A list of style specifiers (str, Path or dict), which are applied
+ from first to last in the list.
+
+ Notes
+ -----
+ The following `.rcParams` are not related to style and will be ignored if
+ found in a style specification:
+
+ %s
+ """
+ if isinstance(style, (str, Path)) or hasattr(style, 'keys'):
+ # If name is a single str, Path or dict, make it a single element list.
+ styles = [style]
+ else:
+ styles = style
+
+ style_alias = {'mpl20': 'default', 'mpl15': 'classic'}
+
+ for style in styles:
+ if isinstance(style, str):
+ style = style_alias.get(style, style)
+ if style == "default":
+ # Deprecation warnings were already handled when creating
+ # rcParamsDefault, no need to reemit them here.
+ with _api.suppress_matplotlib_deprecation_warning():
+ # don't trigger RcParams.__getitem__('backend')
+ style = {k: rcParamsDefault[k] for k in rcParamsDefault
+ if k not in STYLE_BLACKLIST}
+ elif style in library:
+ style = library[style]
+ elif "." in style:
+ pkg, _, name = style.rpartition(".")
+ try:
+ path = importlib.resources.files(pkg) / f"{name}.{STYLE_EXTENSION}"
+ style = _rc_params_in_file(path)
+ except (ModuleNotFoundError, OSError, TypeError) as exc:
+ # There is an ambiguity whether a dotted name refers to a
+ # package.style_name or to a dotted file path. Currently,
+ # we silently try the first form and then the second one;
+ # in the future, we may consider forcing file paths to
+ # either use Path objects or be prepended with "./" and use
+ # the slash as marker for file paths.
+ pass
+ if isinstance(style, (str, Path)):
+ try:
+ style = _rc_params_in_file(style)
+ except OSError as err:
+ raise OSError(
+ f"{style!r} is not a valid package style, path of style "
+ f"file, URL of style file, or library style name (library "
+ f"styles are listed in `style.available`)") from err
+ filtered = {}
+ for k in style: # don't trigger RcParams.__getitem__('backend')
+ if k in STYLE_BLACKLIST:
+ _api.warn_external(
+ f"Style includes a parameter, {k!r}, that is not "
+ f"related to style. Ignoring this parameter.")
+ else:
+ filtered[k] = style[k]
+ mpl.rcParams.update(filtered)
+
+
+@contextlib.contextmanager
+def context(style, after_reset=False):
+ """
+ Context manager for using style settings temporarily.
+
+ Parameters
+ ----------
+ style : str, dict, Path or list
+ A style specification. Valid options are:
+
+ str
+ - One of the style names in `.style.available` (a builtin style or
+ a style installed in the user library path).
+
+ - A dotted name of the form "package.style_name"; in that case,
+ "package" should be an importable Python package name, e.g. at
+ ``/path/to/package/__init__.py``; the loaded style file is
+ ``/path/to/package/style_name.mplstyle``. (Style files in
+ subpackages are likewise supported.)
+
+ - The path or URL to a style file, which gets loaded by
+ `.rc_params_from_file`.
+ dict
+ A mapping of key/value pairs for `matplotlib.rcParams`.
+
+ Path
+ The path to a style file, which gets loaded by
+ `.rc_params_from_file`.
+
+ list
+ A list of style specifiers (str, Path or dict), which are applied
+ from first to last in the list.
+
+ after_reset : bool
+ If True, apply style after resetting settings to their defaults;
+ otherwise, apply style on top of the current settings.
+ """
+ with mpl.rc_context():
+ if after_reset:
+ mpl.rcdefaults()
+ use(style)
+ yield
+
+
+def update_user_library(library):
+ """Update style library with user-defined rc files."""
+ for stylelib_path in map(os.path.expanduser, USER_LIBRARY_PATHS):
+ styles = read_style_directory(stylelib_path)
+ update_nested_dict(library, styles)
+ return library
+
+
+def read_style_directory(style_dir):
+ """Return dictionary of styles defined in *style_dir*."""
+ styles = dict()
+ for path in Path(style_dir).glob(f"*.{STYLE_EXTENSION}"):
+ with warnings.catch_warnings(record=True) as warns:
+ styles[path.stem] = _rc_params_in_file(path)
+ for w in warns:
+ _log.warning('In %s: %s', path, w.message)
+ return styles
+
+
+def update_nested_dict(main_dict, new_dict):
+ """
+ Update nested dict (only level of nesting) with new values.
+
+ Unlike `dict.update`, this assumes that the values of the parent dict are
+ dicts (or dict-like), so you shouldn't replace the nested dict if it
+ already exists. Instead you should update the sub-dict.
+ """
+ # update named styles specified by user
+ for name, rc_dict in new_dict.items():
+ main_dict.setdefault(name, {}).update(rc_dict)
+ return main_dict
+
+
+# Load style library
+# ==================
+_base_library = read_style_directory(BASE_LIBRARY_PATH)
+library = {}
+available = []
+
+
+def reload_library():
+ """Reload the style library."""
+ library.clear()
+ library.update(update_user_library(_base_library))
+ available[:] = sorted(library.keys())
+
+
+reload_library()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/style/core.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/style/core.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..5734b017f7c41ee09908c191d18e8f557ed814a2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/style/core.pyi
@@ -0,0 +1,21 @@
+from collections.abc import Generator
+import contextlib
+
+from matplotlib import RcParams
+from matplotlib.typing import RcStyleType
+
+USER_LIBRARY_PATHS: list[str] = ...
+STYLE_EXTENSION: str = ...
+
+def use(style: RcStyleType) -> None: ...
+@contextlib.contextmanager
+def context(
+ style: RcStyleType, after_reset: bool = ...
+) -> Generator[None, None, None]: ...
+
+library: dict[str, RcParams]
+available: list[str]
+
+def reload_library() -> None: ...
+
+__all__ = ['use', 'context', 'available', 'library', 'reload_library']
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/table.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/table.py
new file mode 100644
index 0000000000000000000000000000000000000000..370ce9fe922fab333b8acc418e36d57a228d74e5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/table.py
@@ -0,0 +1,846 @@
+# Original code by:
+# John Gill
+# Copyright 2004 John Gill and John Hunter
+#
+# Subsequent changes:
+# The Matplotlib development team
+# Copyright The Matplotlib development team
+
+"""
+Tables drawing.
+
+.. note::
+ The table implementation in Matplotlib is lightly maintained. For a more
+ featureful table implementation, you may wish to try `blume
+ `_.
+
+Use the factory function `~matplotlib.table.table` to create a ready-made
+table from texts. If you need more control, use the `.Table` class and its
+methods.
+
+The table consists of a grid of cells, which are indexed by (row, column).
+The cell (0, 0) is positioned at the top left.
+
+Thanks to John Gill for providing the class and table.
+"""
+
+import numpy as np
+
+from . import _api, _docstring
+from .artist import Artist, allow_rasterization
+from .patches import Rectangle
+from .text import Text
+from .transforms import Bbox
+from .path import Path
+
+from .cbook import _is_pandas_dataframe
+
+
+class Cell(Rectangle):
+ """
+ A cell is a `.Rectangle` with some associated `.Text`.
+
+ As a user, you'll most likely not creates cells yourself. Instead, you
+ should use either the `~matplotlib.table.table` factory function or
+ `.Table.add_cell`.
+ """
+
+ PAD = 0.1
+ """Padding between text and rectangle."""
+
+ _edges = 'BRTL'
+ _edge_aliases = {'open': '',
+ 'closed': _edges, # default
+ 'horizontal': 'BT',
+ 'vertical': 'RL'
+ }
+
+ def __init__(self, xy, width, height, *,
+ edgecolor='k', facecolor='w',
+ fill=True,
+ text='',
+ loc='right',
+ fontproperties=None,
+ visible_edges='closed',
+ ):
+ """
+ Parameters
+ ----------
+ xy : 2-tuple
+ The position of the bottom left corner of the cell.
+ width : float
+ The cell width.
+ height : float
+ The cell height.
+ edgecolor : :mpltype:`color`, default: 'k'
+ The color of the cell border.
+ facecolor : :mpltype:`color`, default: 'w'
+ The cell facecolor.
+ fill : bool, default: True
+ Whether the cell background is filled.
+ text : str, optional
+ The cell text.
+ loc : {'right', 'center', 'left'}
+ The alignment of the text within the cell.
+ fontproperties : dict, optional
+ A dict defining the font properties of the text. Supported keys and
+ values are the keyword arguments accepted by `.FontProperties`.
+ visible_edges : {'closed', 'open', 'horizontal', 'vertical'} or \
+substring of 'BRTL'
+ The cell edges to be drawn with a line: a substring of 'BRTL'
+ (bottom, right, top, left), or one of 'open' (no edges drawn),
+ 'closed' (all edges drawn), 'horizontal' (bottom and top),
+ 'vertical' (right and left).
+ """
+
+ # Call base
+ super().__init__(xy, width=width, height=height, fill=fill,
+ edgecolor=edgecolor, facecolor=facecolor)
+ self.set_clip_on(False)
+ self.visible_edges = visible_edges
+
+ # Create text object
+ self._loc = loc
+ self._text = Text(x=xy[0], y=xy[1], clip_on=False,
+ text=text, fontproperties=fontproperties,
+ horizontalalignment=loc, verticalalignment='center')
+
+ def set_transform(self, t):
+ super().set_transform(t)
+ # the text does not get the transform!
+ self.stale = True
+
+ def set_figure(self, fig):
+ super().set_figure(fig)
+ self._text.set_figure(fig)
+
+ def get_text(self):
+ """Return the cell `.Text` instance."""
+ return self._text
+
+ def set_fontsize(self, size):
+ """Set the text fontsize."""
+ self._text.set_fontsize(size)
+ self.stale = True
+
+ def get_fontsize(self):
+ """Return the cell fontsize."""
+ return self._text.get_fontsize()
+
+ def auto_set_font_size(self, renderer):
+ """Shrink font size until the text fits into the cell width."""
+ fontsize = self.get_fontsize()
+ required = self.get_required_width(renderer)
+ while fontsize > 1 and required > self.get_width():
+ fontsize -= 1
+ self.set_fontsize(fontsize)
+ required = self.get_required_width(renderer)
+
+ return fontsize
+
+ @allow_rasterization
+ def draw(self, renderer):
+ if not self.get_visible():
+ return
+ # draw the rectangle
+ super().draw(renderer)
+ # position the text
+ self._set_text_position(renderer)
+ self._text.draw(renderer)
+ self.stale = False
+
+ def _set_text_position(self, renderer):
+ """Set text up so it is drawn in the right place."""
+ bbox = self.get_window_extent(renderer)
+ # center vertically
+ y = bbox.y0 + bbox.height / 2
+ # position horizontally
+ loc = self._text.get_horizontalalignment()
+ if loc == 'center':
+ x = bbox.x0 + bbox.width / 2
+ elif loc == 'left':
+ x = bbox.x0 + bbox.width * self.PAD
+ else: # right.
+ x = bbox.x0 + bbox.width * (1 - self.PAD)
+ self._text.set_position((x, y))
+
+ def get_text_bounds(self, renderer):
+ """
+ Return the text bounds as *(x, y, width, height)* in table coordinates.
+ """
+ return (self._text.get_window_extent(renderer)
+ .transformed(self.get_data_transform().inverted())
+ .bounds)
+
+ def get_required_width(self, renderer):
+ """Return the minimal required width for the cell."""
+ l, b, w, h = self.get_text_bounds(renderer)
+ return w * (1.0 + (2.0 * self.PAD))
+
+ @_docstring.interpd
+ def set_text_props(self, **kwargs):
+ """
+ Update the text properties.
+
+ Valid keyword arguments are:
+
+ %(Text:kwdoc)s
+ """
+ self._text._internal_update(kwargs)
+ self.stale = True
+
+ @property
+ def visible_edges(self):
+ """
+ The cell edges to be drawn with a line.
+
+ Reading this property returns a substring of 'BRTL' (bottom, right,
+ top, left').
+
+ When setting this property, you can use a substring of 'BRTL' or one
+ of {'open', 'closed', 'horizontal', 'vertical'}.
+ """
+ return self._visible_edges
+
+ @visible_edges.setter
+ def visible_edges(self, value):
+ if value is None:
+ self._visible_edges = self._edges
+ elif value in self._edge_aliases:
+ self._visible_edges = self._edge_aliases[value]
+ else:
+ if any(edge not in self._edges for edge in value):
+ raise ValueError('Invalid edge param {}, must only be one of '
+ '{} or string of {}'.format(
+ value,
+ ", ".join(self._edge_aliases),
+ ", ".join(self._edges)))
+ self._visible_edges = value
+ self.stale = True
+
+ def get_path(self):
+ """Return a `.Path` for the `.visible_edges`."""
+ codes = [Path.MOVETO]
+ codes.extend(
+ Path.LINETO if edge in self._visible_edges else Path.MOVETO
+ for edge in self._edges)
+ if Path.MOVETO not in codes[1:]: # All sides are visible
+ codes[-1] = Path.CLOSEPOLY
+ return Path(
+ [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]],
+ codes,
+ readonly=True
+ )
+
+
+CustomCell = Cell # Backcompat. alias.
+
+
+class Table(Artist):
+ """
+ A table of cells.
+
+ The table consists of a grid of cells, which are indexed by (row, column).
+
+ For a simple table, you'll have a full grid of cells with indices from
+ (0, 0) to (num_rows-1, num_cols-1), in which the cell (0, 0) is positioned
+ at the top left. However, you can also add cells with negative indices.
+ You don't have to add a cell to every grid position, so you can create
+ tables that have holes.
+
+ *Note*: You'll usually not create an empty table from scratch. Instead use
+ `~matplotlib.table.table` to create a table from data.
+ """
+ codes = {'best': 0,
+ 'upper right': 1, # default
+ 'upper left': 2,
+ 'lower left': 3,
+ 'lower right': 4,
+ 'center left': 5,
+ 'center right': 6,
+ 'lower center': 7,
+ 'upper center': 8,
+ 'center': 9,
+ 'top right': 10,
+ 'top left': 11,
+ 'bottom left': 12,
+ 'bottom right': 13,
+ 'right': 14,
+ 'left': 15,
+ 'top': 16,
+ 'bottom': 17,
+ }
+ """Possible values where to place the table relative to the Axes."""
+
+ FONTSIZE = 10
+
+ AXESPAD = 0.02
+ """The border between the Axes and the table edge in Axes units."""
+
+ def __init__(self, ax, loc=None, bbox=None, **kwargs):
+ """
+ Parameters
+ ----------
+ ax : `~matplotlib.axes.Axes`
+ The `~.axes.Axes` to plot the table into.
+ loc : str, optional
+ The position of the cell with respect to *ax*. This must be one of
+ the `~.Table.codes`.
+ bbox : `.Bbox` or [xmin, ymin, width, height], optional
+ A bounding box to draw the table into. If this is not *None*, this
+ overrides *loc*.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ `.Artist` properties.
+ """
+
+ super().__init__()
+
+ if isinstance(loc, str):
+ if loc not in self.codes:
+ raise ValueError(
+ "Unrecognized location {!r}. Valid locations are\n\t{}"
+ .format(loc, '\n\t'.join(self.codes)))
+ loc = self.codes[loc]
+ self.set_figure(ax.get_figure(root=False))
+ self._axes = ax
+ self._loc = loc
+ self._bbox = bbox
+
+ # use axes coords
+ ax._unstale_viewLim()
+ self.set_transform(ax.transAxes)
+
+ self._cells = {}
+ self._edges = None
+ self._autoColumns = []
+ self._autoFontsize = True
+ self._internal_update(kwargs)
+
+ self.set_clip_on(False)
+
+ def add_cell(self, row, col, *args, **kwargs):
+ """
+ Create a cell and add it to the table.
+
+ Parameters
+ ----------
+ row : int
+ Row index.
+ col : int
+ Column index.
+ *args, **kwargs
+ All other parameters are passed on to `Cell`.
+
+ Returns
+ -------
+ `.Cell`
+ The created cell.
+
+ """
+ xy = (0, 0)
+ cell = Cell(xy, visible_edges=self.edges, *args, **kwargs)
+ self[row, col] = cell
+ return cell
+
+ def __setitem__(self, position, cell):
+ """
+ Set a custom cell in a given position.
+ """
+ _api.check_isinstance(Cell, cell=cell)
+ try:
+ row, col = position[0], position[1]
+ except Exception as err:
+ raise KeyError('Only tuples length 2 are accepted as '
+ 'coordinates') from err
+ cell.set_figure(self.get_figure(root=False))
+ cell.set_transform(self.get_transform())
+ cell.set_clip_on(False)
+ self._cells[row, col] = cell
+ self.stale = True
+
+ def __getitem__(self, position):
+ """Retrieve a custom cell from a given position."""
+ return self._cells[position]
+
+ @property
+ def edges(self):
+ """
+ The default value of `~.Cell.visible_edges` for newly added
+ cells using `.add_cell`.
+
+ Notes
+ -----
+ This setting does currently only affect newly created cells using
+ `.add_cell`.
+
+ To change existing cells, you have to set their edges explicitly::
+
+ for c in tab.get_celld().values():
+ c.visible_edges = 'horizontal'
+
+ """
+ return self._edges
+
+ @edges.setter
+ def edges(self, value):
+ self._edges = value
+ self.stale = True
+
+ def _approx_text_height(self):
+ return (self.FONTSIZE / 72.0 * self.get_figure(root=True).dpi /
+ self._axes.bbox.height * 1.2)
+
+ @allow_rasterization
+ def draw(self, renderer):
+ # docstring inherited
+
+ # Need a renderer to do hit tests on mouseevent; assume the last one
+ # will do
+ if renderer is None:
+ renderer = self.get_figure(root=True)._get_renderer()
+ if renderer is None:
+ raise RuntimeError('No renderer defined')
+
+ if not self.get_visible():
+ return
+ renderer.open_group('table', gid=self.get_gid())
+ self._update_positions(renderer)
+
+ for key in sorted(self._cells):
+ self._cells[key].draw(renderer)
+
+ renderer.close_group('table')
+ self.stale = False
+
+ def _get_grid_bbox(self, renderer):
+ """
+ Get a bbox, in axes coordinates for the cells.
+
+ Only include those in the range (0, 0) to (maxRow, maxCol).
+ """
+ boxes = [cell.get_window_extent(renderer)
+ for (row, col), cell in self._cells.items()
+ if row >= 0 and col >= 0]
+ bbox = Bbox.union(boxes)
+ return bbox.transformed(self.get_transform().inverted())
+
+ def contains(self, mouseevent):
+ # docstring inherited
+ if self._different_canvas(mouseevent):
+ return False, {}
+ # TODO: Return index of the cell containing the cursor so that the user
+ # doesn't have to bind to each one individually.
+ renderer = self.get_figure(root=True)._get_renderer()
+ if renderer is not None:
+ boxes = [cell.get_window_extent(renderer)
+ for (row, col), cell in self._cells.items()
+ if row >= 0 and col >= 0]
+ bbox = Bbox.union(boxes)
+ return bbox.contains(mouseevent.x, mouseevent.y), {}
+ else:
+ return False, {}
+
+ def get_children(self):
+ """Return the Artists contained by the table."""
+ return list(self._cells.values())
+
+ def get_window_extent(self, renderer=None):
+ # docstring inherited
+ if renderer is None:
+ renderer = self.get_figure(root=True)._get_renderer()
+ self._update_positions(renderer)
+ boxes = [cell.get_window_extent(renderer)
+ for cell in self._cells.values()]
+ return Bbox.union(boxes)
+
+ def _do_cell_alignment(self):
+ """
+ Calculate row heights and column widths; position cells accordingly.
+ """
+ # Calculate row/column widths
+ widths = {}
+ heights = {}
+ for (row, col), cell in self._cells.items():
+ height = heights.setdefault(row, 0.0)
+ heights[row] = max(height, cell.get_height())
+ width = widths.setdefault(col, 0.0)
+ widths[col] = max(width, cell.get_width())
+
+ # work out left position for each column
+ xpos = 0
+ lefts = {}
+ for col in sorted(widths):
+ lefts[col] = xpos
+ xpos += widths[col]
+
+ ypos = 0
+ bottoms = {}
+ for row in sorted(heights, reverse=True):
+ bottoms[row] = ypos
+ ypos += heights[row]
+
+ # set cell positions
+ for (row, col), cell in self._cells.items():
+ cell.set_x(lefts[col])
+ cell.set_y(bottoms[row])
+
+ def auto_set_column_width(self, col):
+ """
+ Automatically set the widths of given columns to optimal sizes.
+
+ Parameters
+ ----------
+ col : int or sequence of ints
+ The indices of the columns to auto-scale.
+ """
+ col1d = np.atleast_1d(col)
+ if not np.issubdtype(col1d.dtype, np.integer):
+ raise TypeError("col must be an int or sequence of ints.")
+ for cell in col1d:
+ self._autoColumns.append(cell)
+
+ self.stale = True
+
+ def _auto_set_column_width(self, col, renderer):
+ """Automatically set width for column."""
+ cells = [cell for key, cell in self._cells.items() if key[1] == col]
+ max_width = max((cell.get_required_width(renderer) for cell in cells),
+ default=0)
+ for cell in cells:
+ cell.set_width(max_width)
+
+ def auto_set_font_size(self, value=True):
+ """Automatically set font size."""
+ self._autoFontsize = value
+ self.stale = True
+
+ def _auto_set_font_size(self, renderer):
+
+ if len(self._cells) == 0:
+ return
+ fontsize = next(iter(self._cells.values())).get_fontsize()
+ cells = []
+ for key, cell in self._cells.items():
+ # ignore auto-sized columns
+ if key[1] in self._autoColumns:
+ continue
+ size = cell.auto_set_font_size(renderer)
+ fontsize = min(fontsize, size)
+ cells.append(cell)
+
+ # now set all fontsizes equal
+ for cell in self._cells.values():
+ cell.set_fontsize(fontsize)
+
+ def scale(self, xscale, yscale):
+ """Scale column widths by *xscale* and row heights by *yscale*."""
+ for c in self._cells.values():
+ c.set_width(c.get_width() * xscale)
+ c.set_height(c.get_height() * yscale)
+
+ def set_fontsize(self, size):
+ """
+ Set the font size, in points, of the cell text.
+
+ Parameters
+ ----------
+ size : float
+
+ Notes
+ -----
+ As long as auto font size has not been disabled, the value will be
+ clipped such that the text fits horizontally into the cell.
+
+ You can disable this behavior using `.auto_set_font_size`.
+
+ >>> the_table.auto_set_font_size(False)
+ >>> the_table.set_fontsize(20)
+
+ However, there is no automatic scaling of the row height so that the
+ text may exceed the cell boundary.
+ """
+ for cell in self._cells.values():
+ cell.set_fontsize(size)
+ self.stale = True
+
+ def _offset(self, ox, oy):
+ """Move all the artists by ox, oy (axes coords)."""
+ for c in self._cells.values():
+ x, y = c.get_x(), c.get_y()
+ c.set_x(x + ox)
+ c.set_y(y + oy)
+
+ def _update_positions(self, renderer):
+ # called from renderer to allow more precise estimates of
+ # widths and heights with get_window_extent
+
+ # Do any auto width setting
+ for col in self._autoColumns:
+ self._auto_set_column_width(col, renderer)
+
+ if self._autoFontsize:
+ self._auto_set_font_size(renderer)
+
+ # Align all the cells
+ self._do_cell_alignment()
+
+ bbox = self._get_grid_bbox(renderer)
+ l, b, w, h = bbox.bounds
+
+ if self._bbox is not None:
+ # Position according to bbox
+ if isinstance(self._bbox, Bbox):
+ rl, rb, rw, rh = self._bbox.bounds
+ else:
+ rl, rb, rw, rh = self._bbox
+ self.scale(rw / w, rh / h)
+ ox = rl - l
+ oy = rb - b
+ self._do_cell_alignment()
+ else:
+ # Position using loc
+ (BEST, UR, UL, LL, LR, CL, CR, LC, UC, C,
+ TR, TL, BL, BR, R, L, T, B) = range(len(self.codes))
+ # defaults for center
+ ox = (0.5 - w / 2) - l
+ oy = (0.5 - h / 2) - b
+ if self._loc in (UL, LL, CL): # left
+ ox = self.AXESPAD - l
+ if self._loc in (BEST, UR, LR, R, CR): # right
+ ox = 1 - (l + w + self.AXESPAD)
+ if self._loc in (BEST, UR, UL, UC): # upper
+ oy = 1 - (b + h + self.AXESPAD)
+ if self._loc in (LL, LR, LC): # lower
+ oy = self.AXESPAD - b
+ if self._loc in (LC, UC, C): # center x
+ ox = (0.5 - w / 2) - l
+ if self._loc in (CL, CR, C): # center y
+ oy = (0.5 - h / 2) - b
+
+ if self._loc in (TL, BL, L): # out left
+ ox = - (l + w)
+ if self._loc in (TR, BR, R): # out right
+ ox = 1.0 - l
+ if self._loc in (TR, TL, T): # out top
+ oy = 1.0 - b
+ if self._loc in (BL, BR, B): # out bottom
+ oy = - (b + h)
+
+ self._offset(ox, oy)
+
+ def get_celld(self):
+ r"""
+ Return a dict of cells in the table mapping *(row, column)* to
+ `.Cell`\s.
+
+ Notes
+ -----
+ You can also directly index into the Table object to access individual
+ cells::
+
+ cell = table[row, col]
+
+ """
+ return self._cells
+
+
+@_docstring.interpd
+def table(ax,
+ cellText=None, cellColours=None,
+ cellLoc='right', colWidths=None,
+ rowLabels=None, rowColours=None, rowLoc='left',
+ colLabels=None, colColours=None, colLoc='center',
+ loc='bottom', bbox=None, edges='closed',
+ **kwargs):
+ """
+ Add a table to an `~.axes.Axes`.
+
+ At least one of *cellText* or *cellColours* must be specified. These
+ parameters must be 2D lists, in which the outer lists define the rows and
+ the inner list define the column values per row. Each row must have the
+ same number of elements.
+
+ The table can optionally have row and column headers, which are configured
+ using *rowLabels*, *rowColours*, *rowLoc* and *colLabels*, *colColours*,
+ *colLoc* respectively.
+
+ For finer grained control over tables, use the `.Table` class and add it to
+ the Axes with `.Axes.add_table`.
+
+ Parameters
+ ----------
+ cellText : 2D list of str or pandas.DataFrame, optional
+ The texts to place into the table cells.
+
+ *Note*: Line breaks in the strings are currently not accounted for and
+ will result in the text exceeding the cell boundaries.
+
+ cellColours : 2D list of :mpltype:`color`, optional
+ The background colors of the cells.
+
+ cellLoc : {'right', 'center', 'left'}
+ The alignment of the text within the cells.
+
+ colWidths : list of float, optional
+ The column widths in units of the axes. If not given, all columns will
+ have a width of *1 / ncols*.
+
+ rowLabels : list of str, optional
+ The text of the row header cells.
+
+ rowColours : list of :mpltype:`color`, optional
+ The colors of the row header cells.
+
+ rowLoc : {'left', 'center', 'right'}
+ The text alignment of the row header cells.
+
+ colLabels : list of str, optional
+ The text of the column header cells.
+
+ colColours : list of :mpltype:`color`, optional
+ The colors of the column header cells.
+
+ colLoc : {'center', 'left', 'right'}
+ The text alignment of the column header cells.
+
+ loc : str, default: 'bottom'
+ The position of the cell with respect to *ax*. This must be one of
+ the `~.Table.codes`.
+
+ bbox : `.Bbox` or [xmin, ymin, width, height], optional
+ A bounding box to draw the table into. If this is not *None*, this
+ overrides *loc*.
+
+ edges : {'closed', 'open', 'horizontal', 'vertical'} or substring of 'BRTL'
+ The cell edges to be drawn with a line. See also
+ `~.Cell.visible_edges`.
+
+ Returns
+ -------
+ `~matplotlib.table.Table`
+ The created table.
+
+ Other Parameters
+ ----------------
+ **kwargs
+ `.Table` properties.
+
+ %(Table:kwdoc)s
+ """
+
+ if cellColours is None and cellText is None:
+ raise ValueError('At least one argument from "cellColours" or '
+ '"cellText" must be provided to create a table.')
+
+ # Check we have some cellText
+ if cellText is None:
+ # assume just colours are needed
+ rows = len(cellColours)
+ cols = len(cellColours[0])
+ cellText = [[''] * cols] * rows
+
+ # Check if we have a Pandas DataFrame
+ if _is_pandas_dataframe(cellText):
+ # if rowLabels/colLabels are empty, use DataFrame entries.
+ # Otherwise, throw an error.
+ if rowLabels is None:
+ rowLabels = cellText.index
+ else:
+ raise ValueError("rowLabels cannot be used alongside Pandas DataFrame")
+ if colLabels is None:
+ colLabels = cellText.columns
+ else:
+ raise ValueError("colLabels cannot be used alongside Pandas DataFrame")
+ # Update cellText with only values
+ cellText = cellText.values
+
+ rows = len(cellText)
+ cols = len(cellText[0])
+ for row in cellText:
+ if len(row) != cols:
+ raise ValueError(f"Each row in 'cellText' must have {cols} "
+ "columns")
+
+ if cellColours is not None:
+ if len(cellColours) != rows:
+ raise ValueError(f"'cellColours' must have {rows} rows")
+ for row in cellColours:
+ if len(row) != cols:
+ raise ValueError("Each row in 'cellColours' must have "
+ f"{cols} columns")
+ else:
+ cellColours = ['w' * cols] * rows
+
+ # Set colwidths if not given
+ if colWidths is None:
+ colWidths = [1.0 / cols] * cols
+
+ # Fill in missing information for column
+ # and row labels
+ rowLabelWidth = 0
+ if rowLabels is None:
+ if rowColours is not None:
+ rowLabels = [''] * rows
+ rowLabelWidth = colWidths[0]
+ elif rowColours is None:
+ rowColours = 'w' * rows
+
+ if rowLabels is not None:
+ if len(rowLabels) != rows:
+ raise ValueError(f"'rowLabels' must be of length {rows}")
+
+ # If we have column labels, need to shift
+ # the text and colour arrays down 1 row
+ offset = 1
+ if colLabels is None:
+ if colColours is not None:
+ colLabels = [''] * cols
+ else:
+ offset = 0
+ elif colColours is None:
+ colColours = 'w' * cols
+
+ # Set up cell colours if not given
+ if cellColours is None:
+ cellColours = ['w' * cols] * rows
+
+ # Now create the table
+ table = Table(ax, loc, bbox, **kwargs)
+ table.edges = edges
+ height = table._approx_text_height()
+
+ # Add the cells
+ for row in range(rows):
+ for col in range(cols):
+ table.add_cell(row + offset, col,
+ width=colWidths[col], height=height,
+ text=cellText[row][col],
+ facecolor=cellColours[row][col],
+ loc=cellLoc)
+ # Do column labels
+ if colLabels is not None:
+ for col in range(cols):
+ table.add_cell(0, col,
+ width=colWidths[col], height=height,
+ text=colLabels[col], facecolor=colColours[col],
+ loc=colLoc)
+
+ # Do row labels
+ if rowLabels is not None:
+ for row in range(rows):
+ table.add_cell(row + offset, -1,
+ width=rowLabelWidth or 1e-15, height=height,
+ text=rowLabels[row], facecolor=rowColours[row],
+ loc=rowLoc)
+ if rowLabelWidth == 0:
+ table.auto_set_column_width(-1)
+
+ # set_fontsize is only effective after cells are added
+ if "fontsize" in kwargs:
+ table.set_fontsize(kwargs["fontsize"])
+
+ ax.add_table(table)
+ return table
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/table.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/table.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..07d2427f66dc184efaf46af83e6733d157bc089b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/table.pyi
@@ -0,0 +1,87 @@
+from .artist import Artist
+from .axes import Axes
+from .backend_bases import RendererBase
+from .patches import Rectangle
+from .path import Path
+from .text import Text
+from .transforms import Bbox
+from .typing import ColorType
+
+from collections.abc import Sequence
+from typing import Any, Literal, TYPE_CHECKING
+
+from pandas import DataFrame
+
+class Cell(Rectangle):
+ PAD: float
+ def __init__(
+ self,
+ xy: tuple[float, float],
+ width: float,
+ height: float,
+ *,
+ edgecolor: ColorType = ...,
+ facecolor: ColorType = ...,
+ fill: bool = ...,
+ text: str = ...,
+ loc: Literal["left", "center", "right"] = ...,
+ fontproperties: dict[str, Any] | None = ...,
+ visible_edges: str | None = ...
+ ) -> None: ...
+ def get_text(self) -> Text: ...
+ def set_fontsize(self, size: float) -> None: ...
+ def get_fontsize(self) -> float: ...
+ def auto_set_font_size(self, renderer: RendererBase) -> float: ...
+ def get_text_bounds(
+ self, renderer: RendererBase
+ ) -> tuple[float, float, float, float]: ...
+ def get_required_width(self, renderer: RendererBase) -> float: ...
+ def set_text_props(self, **kwargs) -> None: ...
+ @property
+ def visible_edges(self) -> str: ...
+ @visible_edges.setter
+ def visible_edges(self, value: str | None) -> None: ...
+ def get_path(self) -> Path: ...
+
+CustomCell = Cell
+
+class Table(Artist):
+ codes: dict[str, int]
+ FONTSIZE: float
+ AXESPAD: float
+ def __init__(
+ self, ax: Axes, loc: str | None = ..., bbox: Bbox | None = ..., **kwargs
+ ) -> None: ...
+ def add_cell(self, row: int, col: int, *args, **kwargs) -> Cell: ...
+ def __setitem__(self, position: tuple[int, int], cell: Cell) -> None: ...
+ def __getitem__(self, position: tuple[int, int]) -> Cell: ...
+ @property
+ def edges(self) -> str | None: ...
+ @edges.setter
+ def edges(self, value: str | None) -> None: ...
+ def draw(self, renderer) -> None: ...
+ def get_children(self) -> list[Artist]: ...
+ def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ...
+ def auto_set_column_width(self, col: int | Sequence[int]) -> None: ...
+ def auto_set_font_size(self, value: bool = ...) -> None: ...
+ def scale(self, xscale: float, yscale: float) -> None: ...
+ def set_fontsize(self, size: float) -> None: ...
+ def get_celld(self) -> dict[tuple[int, int], Cell]: ...
+
+def table(
+ ax: Axes,
+ cellText: Sequence[Sequence[str]] | DataFrame | None = ...,
+ cellColours: Sequence[Sequence[ColorType]] | None = ...,
+ cellLoc: Literal["left", "center", "right"] = ...,
+ colWidths: Sequence[float] | None = ...,
+ rowLabels: Sequence[str] | None = ...,
+ rowColours: Sequence[ColorType] | None = ...,
+ rowLoc: Literal["left", "center", "right"] = ...,
+ colLabels: Sequence[str] | None = ...,
+ colColours: Sequence[ColorType] | None = ...,
+ colLoc: Literal["left", "center", "right"] = ...,
+ loc: str = ...,
+ bbox: Bbox | None = ...,
+ edges: str = ...,
+ **kwargs
+) -> Table: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/__init__.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..19113d3996268ca6d3cec637785f7a30da0aae2f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/__init__.py
@@ -0,0 +1,234 @@
+"""
+Helper functions for testing.
+"""
+from pathlib import Path
+from tempfile import TemporaryDirectory
+import locale
+import logging
+import os
+import subprocess
+import sys
+
+import matplotlib as mpl
+from matplotlib import _api
+
+_log = logging.getLogger(__name__)
+
+
+def set_font_settings_for_testing():
+ mpl.rcParams['font.family'] = 'DejaVu Sans'
+ mpl.rcParams['text.hinting'] = 'none'
+ mpl.rcParams['text.hinting_factor'] = 8
+
+
+def set_reproducibility_for_testing():
+ mpl.rcParams['svg.hashsalt'] = 'matplotlib'
+
+
+def setup():
+ # The baseline images are created in this locale, so we should use
+ # it during all of the tests.
+
+ try:
+ locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
+ except locale.Error:
+ try:
+ locale.setlocale(locale.LC_ALL, 'English_United States.1252')
+ except locale.Error:
+ _log.warning(
+ "Could not set locale to English/United States. "
+ "Some date-related tests may fail.")
+
+ mpl.use('Agg')
+
+ with _api.suppress_matplotlib_deprecation_warning():
+ mpl.rcdefaults() # Start with all defaults
+
+ # These settings *must* be hardcoded for running the comparison tests and
+ # are not necessarily the default values as specified in rcsetup.py.
+ set_font_settings_for_testing()
+ set_reproducibility_for_testing()
+
+
+def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None,
+ stderr=None, check=False, text=True,
+ capture_output=False):
+ """
+ Create and run a subprocess.
+
+ Thin wrapper around `subprocess.run`, intended for testing. Will
+ mark fork() failures on Cygwin as expected failures: not a
+ success, but not indicating a problem with the code either.
+
+ Parameters
+ ----------
+ args : list of str
+ env : dict[str, str]
+ timeout : float
+ stdout, stderr
+ check : bool
+ text : bool
+ Also called ``universal_newlines`` in subprocess. I chose this
+ name since the main effect is returning bytes (`False`) vs. str
+ (`True`), though it also tries to normalize newlines across
+ platforms.
+ capture_output : bool
+ Set stdout and stderr to subprocess.PIPE
+
+ Returns
+ -------
+ proc : subprocess.Popen
+
+ See Also
+ --------
+ subprocess.run
+
+ Raises
+ ------
+ pytest.xfail
+ If platform is Cygwin and subprocess reports a fork() failure.
+ """
+ if capture_output:
+ stdout = stderr = subprocess.PIPE
+ try:
+ proc = subprocess.run(
+ command, env=env,
+ timeout=timeout, check=check,
+ stdout=stdout, stderr=stderr,
+ text=text
+ )
+ except BlockingIOError:
+ if sys.platform == "cygwin":
+ # Might want to make this more specific
+ import pytest
+ pytest.xfail("Fork failure")
+ raise
+ return proc
+
+
+def subprocess_run_helper(func, *args, timeout, extra_env=None):
+ """
+ Run a function in a sub-process.
+
+ Parameters
+ ----------
+ func : function
+ The function to be run. It must be in a module that is importable.
+ *args : str
+ Any additional command line arguments to be passed in
+ the first argument to ``subprocess.run``.
+ extra_env : dict[str, str]
+ Any additional environment variables to be set for the subprocess.
+ """
+ target = func.__name__
+ module = func.__module__
+ file = func.__code__.co_filename
+ proc = subprocess_run_for_testing(
+ [
+ sys.executable,
+ "-c",
+ f"import importlib.util;"
+ f"_spec = importlib.util.spec_from_file_location({module!r}, {file!r});"
+ f"_module = importlib.util.module_from_spec(_spec);"
+ f"_spec.loader.exec_module(_module);"
+ f"_module.{target}()",
+ *args
+ ],
+ env={**os.environ, "SOURCE_DATE_EPOCH": "0", **(extra_env or {})},
+ timeout=timeout, check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+ return proc
+
+
+def _check_for_pgf(texsystem):
+ """
+ Check if a given TeX system + pgf is available
+
+ Parameters
+ ----------
+ texsystem : str
+ The executable name to check
+ """
+ with TemporaryDirectory() as tmpdir:
+ tex_path = Path(tmpdir, "test.tex")
+ tex_path.write_text(r"""
+ \documentclass{article}
+ \usepackage{pgf}
+ \begin{document}
+ \typeout{pgfversion=\pgfversion}
+ \makeatletter
+ \@@end
+ """, encoding="utf-8")
+ try:
+ subprocess.check_call(
+ [texsystem, "-halt-on-error", str(tex_path)], cwd=tmpdir,
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ except (OSError, subprocess.CalledProcessError):
+ return False
+ return True
+
+
+def _has_tex_package(package):
+ try:
+ mpl.dviread.find_tex_file(f"{package}.sty")
+ return True
+ except FileNotFoundError:
+ return False
+
+
+def ipython_in_subprocess(requested_backend_or_gui_framework, all_expected_backends):
+ import pytest
+ IPython = pytest.importorskip("IPython")
+
+ if sys.platform == "win32":
+ pytest.skip("Cannot change backend running IPython in subprocess on Windows")
+
+ if (IPython.version_info[:3] == (8, 24, 0) and
+ requested_backend_or_gui_framework == "osx"):
+ pytest.skip("Bug using macosx backend in IPython 8.24.0 fixed in 8.24.1")
+
+ # This code can be removed when Python 3.12, the latest version supported
+ # by IPython < 8.24, reaches end-of-life in late 2028.
+ for min_version, backend in all_expected_backends.items():
+ if IPython.version_info[:2] >= min_version:
+ expected_backend = backend
+ break
+
+ code = ("import matplotlib as mpl, matplotlib.pyplot as plt;"
+ "fig, ax=plt.subplots(); ax.plot([1, 3, 2]); mpl.get_backend()")
+ proc = subprocess_run_for_testing(
+ [
+ "ipython",
+ "--no-simple-prompt",
+ f"--matplotlib={requested_backend_or_gui_framework}",
+ "-c", code,
+ ],
+ check=True,
+ capture_output=True,
+ )
+
+ assert proc.stdout.strip().endswith(f"'{expected_backend}'")
+
+
+def is_ci_environment():
+ # Common CI variables
+ ci_environment_variables = [
+ 'CI', # Generic CI environment variable
+ 'CONTINUOUS_INTEGRATION', # Generic CI environment variable
+ 'TRAVIS', # Travis CI
+ 'CIRCLECI', # CircleCI
+ 'JENKINS', # Jenkins
+ 'GITLAB_CI', # GitLab CI
+ 'GITHUB_ACTIONS', # GitHub Actions
+ 'TEAMCITY_VERSION' # TeamCity
+ # Add other CI environment variables as needed
+ ]
+
+ for env_var in ci_environment_variables:
+ if os.getenv(env_var):
+ return True
+
+ return False
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/__init__.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/__init__.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..6917b6a5a38080b389f5d970beb8973c1c041206
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/__init__.pyi
@@ -0,0 +1,54 @@
+from collections.abc import Callable
+import subprocess
+from typing import Any, IO, Literal, overload
+
+def set_font_settings_for_testing() -> None: ...
+def set_reproducibility_for_testing() -> None: ...
+def setup() -> None: ...
+@overload
+def subprocess_run_for_testing(
+ command: list[str],
+ env: dict[str, str] | None = ...,
+ timeout: float | None = ...,
+ stdout: int | IO[Any] | None = ...,
+ stderr: int | IO[Any] | None = ...,
+ check: bool = ...,
+ *,
+ text: Literal[True],
+ capture_output: bool = ...,
+) -> subprocess.CompletedProcess[str]: ...
+@overload
+def subprocess_run_for_testing(
+ command: list[str],
+ env: dict[str, str] | None = ...,
+ timeout: float | None = ...,
+ stdout: int | IO[Any] | None = ...,
+ stderr: int | IO[Any] | None = ...,
+ check: bool = ...,
+ text: Literal[False] = ...,
+ capture_output: bool = ...,
+) -> subprocess.CompletedProcess[bytes]: ...
+@overload
+def subprocess_run_for_testing(
+ command: list[str],
+ env: dict[str, str] | None = ...,
+ timeout: float | None = ...,
+ stdout: int | IO[Any] | None = ...,
+ stderr: int | IO[Any] | None = ...,
+ check: bool = ...,
+ text: bool = ...,
+ capture_output: bool = ...,
+) -> subprocess.CompletedProcess[bytes] | subprocess.CompletedProcess[str]: ...
+def subprocess_run_helper(
+ func: Callable[[], None],
+ *args: Any,
+ timeout: float,
+ extra_env: dict[str, str] | None = ...,
+) -> subprocess.CompletedProcess[str]: ...
+def _check_for_pgf(texsystem: str) -> bool: ...
+def _has_tex_package(package: str) -> bool: ...
+def ipython_in_subprocess(
+ requested_backend_or_gui_framework: str,
+ all_expected_backends: dict[tuple[int, int], str],
+) -> None: ...
+def is_ci_environment() -> bool: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/_markers.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/_markers.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7ef8687a8b39766ba0385b5145b841043251de5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/_markers.py
@@ -0,0 +1,49 @@
+"""
+pytest markers for the internal Matplotlib test suite.
+"""
+
+import logging
+import shutil
+
+import pytest
+
+import matplotlib.testing
+import matplotlib.testing.compare
+from matplotlib import _get_executable_info, ExecutableNotFoundError
+
+
+_log = logging.getLogger(__name__)
+
+
+def _checkdep_usetex() -> bool:
+ if not shutil.which("tex"):
+ _log.warning("usetex mode requires TeX.")
+ return False
+ try:
+ _get_executable_info("dvipng")
+ except ExecutableNotFoundError:
+ _log.warning("usetex mode requires dvipng.")
+ return False
+ try:
+ _get_executable_info("gs")
+ except ExecutableNotFoundError:
+ _log.warning("usetex mode requires ghostscript.")
+ return False
+ return True
+
+
+needs_ghostscript = pytest.mark.skipif(
+ "eps" not in matplotlib.testing.compare.converter,
+ reason="This test needs a ghostscript installation")
+needs_pgf_lualatex = pytest.mark.skipif(
+ not matplotlib.testing._check_for_pgf('lualatex'),
+ reason='lualatex + pgf is required')
+needs_pgf_pdflatex = pytest.mark.skipif(
+ not matplotlib.testing._check_for_pgf('pdflatex'),
+ reason='pdflatex + pgf is required')
+needs_pgf_xelatex = pytest.mark.skipif(
+ not matplotlib.testing._check_for_pgf('xelatex'),
+ reason='xelatex + pgf is required')
+needs_usetex = pytest.mark.skipif(
+ not _checkdep_usetex(),
+ reason="This test needs a TeX installation")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/compare.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/compare.py
new file mode 100644
index 0000000000000000000000000000000000000000..3aeddcbe2b5e9cf99a665171cf5c58e33fc90497
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/compare.py
@@ -0,0 +1,527 @@
+"""
+Utilities for comparing image results.
+"""
+
+import atexit
+import functools
+import hashlib
+import logging
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+from tempfile import TemporaryDirectory, TemporaryFile
+import weakref
+import re
+
+import numpy as np
+from PIL import Image
+
+import matplotlib as mpl
+from matplotlib import cbook
+from matplotlib.testing.exceptions import ImageComparisonFailure
+
+_log = logging.getLogger(__name__)
+
+__all__ = ['calculate_rms', 'comparable_formats', 'compare_images']
+
+
+def make_test_filename(fname, purpose):
+ """
+ Make a new filename by inserting *purpose* before the file's extension.
+ """
+ base, ext = os.path.splitext(fname)
+ return f'{base}-{purpose}{ext}'
+
+
+def _get_cache_path():
+ cache_dir = Path(mpl.get_cachedir(), 'test_cache')
+ cache_dir.mkdir(parents=True, exist_ok=True)
+ return cache_dir
+
+
+def get_cache_dir():
+ return str(_get_cache_path())
+
+
+def get_file_hash(path, block_size=2 ** 20):
+ sha256 = hashlib.sha256(usedforsecurity=False)
+ with open(path, 'rb') as fd:
+ while True:
+ data = fd.read(block_size)
+ if not data:
+ break
+ sha256.update(data)
+
+ if Path(path).suffix == '.pdf':
+ sha256.update(str(mpl._get_executable_info("gs").version).encode('utf-8'))
+ elif Path(path).suffix == '.svg':
+ sha256.update(str(mpl._get_executable_info("inkscape").version).encode('utf-8'))
+
+ return sha256.hexdigest()
+
+
+class _ConverterError(Exception):
+ pass
+
+
+class _Converter:
+ def __init__(self):
+ self._proc = None
+ # Explicitly register deletion from an atexit handler because if we
+ # wait until the object is GC'd (which occurs later), then some module
+ # globals (e.g. signal.SIGKILL) has already been set to None, and
+ # kill() doesn't work anymore...
+ atexit.register(self.__del__)
+
+ def __del__(self):
+ if self._proc:
+ self._proc.kill()
+ self._proc.wait()
+ for stream in filter(None, [self._proc.stdin,
+ self._proc.stdout,
+ self._proc.stderr]):
+ stream.close()
+ self._proc = None
+
+ def _read_until(self, terminator):
+ """Read until the prompt is reached."""
+ buf = bytearray()
+ while True:
+ c = self._proc.stdout.read(1)
+ if not c:
+ raise _ConverterError(os.fsdecode(bytes(buf)))
+ buf.extend(c)
+ if buf.endswith(terminator):
+ return bytes(buf)
+
+
+class _GSConverter(_Converter):
+ def __call__(self, orig, dest):
+ if not self._proc:
+ self._proc = subprocess.Popen(
+ [mpl._get_executable_info("gs").executable,
+ "-dNOSAFER", "-dNOPAUSE", "-dEPSCrop", "-sDEVICE=png16m"],
+ # As far as I can see, ghostscript never outputs to stderr.
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ try:
+ self._read_until(b"\nGS")
+ except _ConverterError as e:
+ raise OSError(f"Failed to start Ghostscript:\n\n{e.args[0]}") from None
+
+ def encode_and_escape(name):
+ return (os.fsencode(name)
+ .replace(b"\\", b"\\\\")
+ .replace(b"(", br"\(")
+ .replace(b")", br"\)"))
+
+ self._proc.stdin.write(
+ b"<< /OutputFile ("
+ + encode_and_escape(dest)
+ + b") >> setpagedevice ("
+ + encode_and_escape(orig)
+ + b") run flush\n")
+ self._proc.stdin.flush()
+ # GS> if nothing left on the stack; GS if n items left on the stack.
+ err = self._read_until((b"GS<", b"GS>"))
+ stack = self._read_until(b">") if err.endswith(b"GS<") else b""
+ if stack or not os.path.exists(dest):
+ stack_size = int(stack[:-1]) if stack else 0
+ self._proc.stdin.write(b"pop\n" * stack_size)
+ # Using the systemencoding should at least get the filenames right.
+ raise ImageComparisonFailure(
+ (err + stack).decode(sys.getfilesystemencoding(), "replace"))
+
+
+class _SVGConverter(_Converter):
+ def __call__(self, orig, dest):
+ old_inkscape = mpl._get_executable_info("inkscape").version.major < 1
+ terminator = b"\n>" if old_inkscape else b"> "
+ if not hasattr(self, "_tmpdir"):
+ self._tmpdir = TemporaryDirectory()
+ # On Windows, we must make sure that self._proc has terminated
+ # (which __del__ does) before clearing _tmpdir.
+ weakref.finalize(self._tmpdir, self.__del__)
+ if (not self._proc # First run.
+ or self._proc.poll() is not None): # Inkscape terminated.
+ if self._proc is not None and self._proc.poll() is not None:
+ for stream in filter(None, [self._proc.stdin,
+ self._proc.stdout,
+ self._proc.stderr]):
+ stream.close()
+ env = {
+ **os.environ,
+ # If one passes e.g. a png file to Inkscape, it will try to
+ # query the user for conversion options via a GUI (even with
+ # `--without-gui`). Unsetting `DISPLAY` prevents this (and
+ # causes GTK to crash and Inkscape to terminate, but that'll
+ # just be reported as a regular exception below).
+ "DISPLAY": "",
+ # Do not load any user options.
+ "INKSCAPE_PROFILE_DIR": self._tmpdir.name,
+ }
+ # Old versions of Inkscape (e.g. 0.48.3.1) seem to sometimes
+ # deadlock when stderr is redirected to a pipe, so we redirect it
+ # to a temporary file instead. This is not necessary anymore as of
+ # Inkscape 0.92.1.
+ stderr = TemporaryFile()
+ self._proc = subprocess.Popen(
+ ["inkscape", "--without-gui", "--shell"] if old_inkscape else
+ ["inkscape", "--shell"],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr,
+ env=env, cwd=self._tmpdir.name)
+ # Slight abuse, but makes shutdown handling easier.
+ self._proc.stderr = stderr
+ try:
+ self._read_until(terminator)
+ except _ConverterError as err:
+ raise OSError(
+ "Failed to start Inkscape in interactive mode:\n\n"
+ + err.args[0]) from err
+
+ # Inkscape's shell mode does not support escaping metacharacters in the
+ # filename ("\n", and ":;" for inkscape>=1). Avoid any problems by
+ # running from a temporary directory and using fixed filenames.
+ inkscape_orig = Path(self._tmpdir.name, os.fsdecode(b"f.svg"))
+ inkscape_dest = Path(self._tmpdir.name, os.fsdecode(b"f.png"))
+ try:
+ inkscape_orig.symlink_to(Path(orig).resolve())
+ except OSError:
+ shutil.copyfile(orig, inkscape_orig)
+ self._proc.stdin.write(
+ b"f.svg --export-png=f.png\n" if old_inkscape else
+ b"file-open:f.svg;export-filename:f.png;export-do;file-close\n")
+ self._proc.stdin.flush()
+ try:
+ self._read_until(terminator)
+ except _ConverterError as err:
+ # Inkscape's output is not localized but gtk's is, so the output
+ # stream probably has a mixed encoding. Using the filesystem
+ # encoding should at least get the filenames right...
+ self._proc.stderr.seek(0)
+ raise ImageComparisonFailure(
+ self._proc.stderr.read().decode(
+ sys.getfilesystemencoding(), "replace")) from err
+ os.remove(inkscape_orig)
+ shutil.move(inkscape_dest, dest)
+
+ def __del__(self):
+ super().__del__()
+ if hasattr(self, "_tmpdir"):
+ self._tmpdir.cleanup()
+
+
+class _SVGWithMatplotlibFontsConverter(_SVGConverter):
+ """
+ A SVG converter which explicitly adds the fonts shipped by Matplotlib to
+ Inkspace's font search path, to better support `svg.fonttype = "none"`
+ (which is in particular used by certain mathtext tests).
+ """
+
+ def __call__(self, orig, dest):
+ if not hasattr(self, "_tmpdir"):
+ self._tmpdir = TemporaryDirectory()
+ shutil.copytree(cbook._get_data_path("fonts/ttf"),
+ Path(self._tmpdir.name, "fonts"))
+ return super().__call__(orig, dest)
+
+
+def _update_converter():
+ try:
+ mpl._get_executable_info("gs")
+ except mpl.ExecutableNotFoundError:
+ pass
+ else:
+ converter['pdf'] = converter['eps'] = _GSConverter()
+ try:
+ mpl._get_executable_info("inkscape")
+ except mpl.ExecutableNotFoundError:
+ pass
+ else:
+ converter['svg'] = _SVGConverter()
+
+
+#: A dictionary that maps filename extensions to functions which themselves
+#: convert between arguments `old` and `new` (filenames).
+converter = {}
+_update_converter()
+_svg_with_matplotlib_fonts_converter = _SVGWithMatplotlibFontsConverter()
+
+
+def comparable_formats():
+ """
+ Return the list of file formats that `.compare_images` can compare
+ on this system.
+
+ Returns
+ -------
+ list of str
+ E.g. ``['png', 'pdf', 'svg', 'eps']``.
+
+ """
+ return ['png', *converter]
+
+
+def convert(filename, cache):
+ """
+ Convert the named file to png; return the name of the created file.
+
+ If *cache* is True, the result of the conversion is cached in
+ `matplotlib.get_cachedir() + '/test_cache/'`. The caching is based on a
+ hash of the exact contents of the input file. Old cache entries are
+ automatically deleted as needed to keep the size of the cache capped to
+ twice the size of all baseline images.
+ """
+ path = Path(filename)
+ if not path.exists():
+ raise OSError(f"{path} does not exist")
+ if path.suffix[1:] not in converter:
+ import pytest
+ pytest.skip(f"Don't know how to convert {path.suffix} files to png")
+ newpath = path.parent / f"{path.stem}_{path.suffix[1:]}.png"
+
+ # Only convert the file if the destination doesn't already exist or
+ # is out of date.
+ if not newpath.exists() or newpath.stat().st_mtime < path.stat().st_mtime:
+ cache_dir = _get_cache_path() if cache else None
+
+ if cache_dir is not None:
+ _register_conversion_cache_cleaner_once()
+ hash_value = get_file_hash(path)
+ cached_path = cache_dir / (hash_value + newpath.suffix)
+ if cached_path.exists():
+ _log.debug("For %s: reusing cached conversion.", filename)
+ shutil.copyfile(cached_path, newpath)
+ return str(newpath)
+
+ _log.debug("For %s: converting to png.", filename)
+ convert = converter[path.suffix[1:]]
+ if path.suffix == ".svg":
+ contents = path.read_text()
+ # NOTE: This check should be kept in sync with font styling in
+ # `lib/matplotlib/backends/backend_svg.py`. If it changes, then be sure to
+ # re-generate any SVG test files using this mode, or else such tests will
+ # fail to use the converter for the expected images (but will for the
+ # results), and the tests will fail strangely.
+ if re.search(
+ # searches for attributes :
+ # style=[font|font-size|font-weight|
+ # font-family|font-variant|font-style]
+ # taking care of the possibility of multiple style attributes
+ # before the font styling (i.e. opacity)
+ r'style="[^"]*font(|-size|-weight|-family|-variant|-style):',
+ contents # raw contents of the svg file
+ ):
+ # for svg.fonttype = none, we explicitly patch the font search
+ # path so that fonts shipped by Matplotlib are found.
+ convert = _svg_with_matplotlib_fonts_converter
+ convert(path, newpath)
+
+ if cache_dir is not None:
+ _log.debug("For %s: caching conversion result.", filename)
+ shutil.copyfile(newpath, cached_path)
+
+ return str(newpath)
+
+
+def _clean_conversion_cache():
+ # This will actually ignore mpl_toolkits baseline images, but they're
+ # relatively small.
+ baseline_images_size = sum(
+ path.stat().st_size
+ for path in Path(mpl.__file__).parent.glob("**/baseline_images/**/*"))
+ # 2x: one full copy of baselines, and one full copy of test results
+ # (actually an overestimate: we don't convert png baselines and results).
+ max_cache_size = 2 * baseline_images_size
+ # Reduce cache until it fits.
+ with cbook._lock_path(_get_cache_path()):
+ cache_stat = {
+ path: path.stat() for path in _get_cache_path().glob("*")}
+ cache_size = sum(stat.st_size for stat in cache_stat.values())
+ paths_by_atime = sorted( # Oldest at the end.
+ cache_stat, key=lambda path: cache_stat[path].st_atime,
+ reverse=True)
+ while cache_size > max_cache_size:
+ path = paths_by_atime.pop()
+ cache_size -= cache_stat[path].st_size
+ path.unlink()
+
+
+@functools.cache # Ensure this is only registered once.
+def _register_conversion_cache_cleaner_once():
+ atexit.register(_clean_conversion_cache)
+
+
+def crop_to_same(actual_path, actual_image, expected_path, expected_image):
+ # clip the images to the same size -- this is useful only when
+ # comparing eps to pdf
+ if actual_path[-7:-4] == 'eps' and expected_path[-7:-4] == 'pdf':
+ aw, ah, ad = actual_image.shape
+ ew, eh, ed = expected_image.shape
+ actual_image = actual_image[int(aw / 2 - ew / 2):int(
+ aw / 2 + ew / 2), int(ah / 2 - eh / 2):int(ah / 2 + eh / 2)]
+ return actual_image, expected_image
+
+
+def calculate_rms(expected_image, actual_image):
+ """
+ Calculate the per-pixel errors, then compute the root mean square error.
+ """
+ if expected_image.shape != actual_image.shape:
+ raise ImageComparisonFailure(
+ f"Image sizes do not match expected size: {expected_image.shape} "
+ f"actual size {actual_image.shape}")
+ # Convert to float to avoid overflowing finite integer types.
+ return np.sqrt(((expected_image - actual_image).astype(float) ** 2).mean())
+
+
+# NOTE: compare_image and save_diff_image assume that the image does not have
+# 16-bit depth, as Pillow converts these to RGB incorrectly.
+
+
+def _load_image(path):
+ img = Image.open(path)
+ # In an RGBA image, if the smallest value in the alpha channel is 255, all
+ # values in it must be 255, meaning that the image is opaque. If so,
+ # discard the alpha channel so that it may compare equal to an RGB image.
+ if img.mode != "RGBA" or img.getextrema()[3][0] == 255:
+ img = img.convert("RGB")
+ return np.asarray(img)
+
+
+def compare_images(expected, actual, tol, in_decorator=False):
+ """
+ Compare two "image" files checking differences within a tolerance.
+
+ The two given filenames may point to files which are convertible to
+ PNG via the `.converter` dictionary. The underlying RMS is calculated
+ with the `.calculate_rms` function.
+
+ Parameters
+ ----------
+ expected : str
+ The filename of the expected image.
+ actual : str
+ The filename of the actual image.
+ tol : float
+ The tolerance (a color value difference, where 255 is the
+ maximal difference). The test fails if the average pixel
+ difference is greater than this value.
+ in_decorator : bool
+ Determines the output format. If called from image_comparison
+ decorator, this should be True. (default=False)
+
+ Returns
+ -------
+ None or dict or str
+ Return *None* if the images are equal within the given tolerance.
+
+ If the images differ, the return value depends on *in_decorator*.
+ If *in_decorator* is true, a dict with the following entries is
+ returned:
+
+ - *rms*: The RMS of the image difference.
+ - *expected*: The filename of the expected image.
+ - *actual*: The filename of the actual image.
+ - *diff_image*: The filename of the difference image.
+ - *tol*: The comparison tolerance.
+
+ Otherwise, a human-readable multi-line string representation of this
+ information is returned.
+
+ Examples
+ --------
+ ::
+
+ img1 = "./baseline/plot.png"
+ img2 = "./output/plot.png"
+ compare_images(img1, img2, 0.001)
+
+ """
+ actual = os.fspath(actual)
+ if not os.path.exists(actual):
+ raise Exception(f"Output image {actual} does not exist.")
+ if os.stat(actual).st_size == 0:
+ raise Exception(f"Output image file {actual} is empty.")
+
+ # Convert the image to png
+ expected = os.fspath(expected)
+ if not os.path.exists(expected):
+ raise OSError(f'Baseline image {expected!r} does not exist.')
+ extension = expected.split('.')[-1]
+ if extension != 'png':
+ actual = convert(actual, cache=True)
+ expected = convert(expected, cache=True)
+
+ # open the image files
+ expected_image = _load_image(expected)
+ actual_image = _load_image(actual)
+
+ actual_image, expected_image = crop_to_same(
+ actual, actual_image, expected, expected_image)
+
+ diff_image = make_test_filename(actual, 'failed-diff')
+
+ if tol <= 0:
+ if np.array_equal(expected_image, actual_image):
+ return None
+
+ # convert to signed integers, so that the images can be subtracted without
+ # overflow
+ expected_image = expected_image.astype(np.int16)
+ actual_image = actual_image.astype(np.int16)
+
+ rms = calculate_rms(expected_image, actual_image)
+
+ if rms <= tol:
+ return None
+
+ save_diff_image(expected, actual, diff_image)
+
+ results = dict(rms=rms, expected=str(expected),
+ actual=str(actual), diff=str(diff_image), tol=tol)
+
+ if not in_decorator:
+ # Then the results should be a string suitable for stdout.
+ template = ['Error: Image files did not match.',
+ 'RMS Value: {rms}',
+ 'Expected: \n {expected}',
+ 'Actual: \n {actual}',
+ 'Difference:\n {diff}',
+ 'Tolerance: \n {tol}', ]
+ results = '\n '.join([line.format(**results) for line in template])
+ return results
+
+
+def save_diff_image(expected, actual, output):
+ """
+ Parameters
+ ----------
+ expected : str
+ File path of expected image.
+ actual : str
+ File path of actual image.
+ output : str
+ File path to save difference image to.
+ """
+ expected_image = _load_image(expected)
+ actual_image = _load_image(actual)
+ actual_image, expected_image = crop_to_same(
+ actual, actual_image, expected, expected_image)
+ expected_image = np.array(expected_image, float)
+ actual_image = np.array(actual_image, float)
+ if expected_image.shape != actual_image.shape:
+ raise ImageComparisonFailure(
+ f"Image sizes do not match expected size: {expected_image.shape} "
+ f"actual size {actual_image.shape}")
+ abs_diff = np.abs(expected_image - actual_image)
+
+ # expand differences in luminance domain
+ abs_diff *= 10
+ abs_diff = np.clip(abs_diff, 0, 255).astype(np.uint8)
+
+ if abs_diff.shape[2] == 4: # Hard-code the alpha channel to fully solid
+ abs_diff[:, :, 3] = 255
+
+ Image.fromarray(abs_diff).save(output, format="png")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/compare.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/compare.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..8f11b3bebc1a0f4186448b1e8d6a8f11f06e3b4f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/compare.pyi
@@ -0,0 +1,32 @@
+from collections.abc import Callable
+from typing import Literal, overload
+
+from numpy.typing import NDArray
+
+__all__ = ["calculate_rms", "comparable_formats", "compare_images"]
+
+def make_test_filename(fname: str, purpose: str) -> str: ...
+def get_cache_dir() -> str: ...
+def get_file_hash(path: str, block_size: int = ...) -> str: ...
+
+converter: dict[str, Callable[[str, str], None]] = {}
+
+def comparable_formats() -> list[str]: ...
+def convert(filename: str, cache: bool) -> str: ...
+def crop_to_same(
+ actual_path: str, actual_image: NDArray, expected_path: str, expected_image: NDArray
+) -> tuple[NDArray, NDArray]: ...
+def calculate_rms(expected_image: NDArray, actual_image: NDArray) -> float: ...
+@overload
+def compare_images(
+ expected: str, actual: str, tol: float, in_decorator: Literal[True]
+) -> None | dict[str, float | str]: ...
+@overload
+def compare_images(
+ expected: str, actual: str, tol: float, in_decorator: Literal[False]
+) -> None | str: ...
+@overload
+def compare_images(
+ expected: str, actual: str, tol: float, in_decorator: bool = ...
+) -> None | str | dict[str, float | str]: ...
+def save_diff_image(expected: str, actual: str, output: str) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/conftest.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..2961e7f02f3f9081f9918f090233281e18db2c2e
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/conftest.py
@@ -0,0 +1,178 @@
+import pytest
+import sys
+import matplotlib
+from matplotlib import _api
+
+
+def pytest_configure(config):
+ # config is initialized here rather than in pytest.ini so that `pytest
+ # --pyargs matplotlib` (which would not find pytest.ini) works. The only
+ # entries in pytest.ini set minversion (which is checked earlier),
+ # testpaths/python_files, as they are required to properly find the tests
+ for key, value in [
+ ("markers", "flaky: (Provided by pytest-rerunfailures.)"),
+ ("markers", "timeout: (Provided by pytest-timeout.)"),
+ ("markers", "backend: Set alternate Matplotlib backend temporarily."),
+ ("markers", "baseline_images: Compare output against references."),
+ ("markers", "pytz: Tests that require pytz to be installed."),
+ ("filterwarnings", "error"),
+ ("filterwarnings",
+ "ignore:.*The py23 module has been deprecated:DeprecationWarning"),
+ ("filterwarnings",
+ r"ignore:DynamicImporter.find_spec\(\) not found; "
+ r"falling back to find_module\(\):ImportWarning"),
+ ]:
+ config.addinivalue_line(key, value)
+
+ matplotlib.use('agg', force=True)
+ matplotlib._called_from_pytest = True
+ matplotlib._init_tests()
+
+
+def pytest_unconfigure(config):
+ matplotlib._called_from_pytest = False
+
+
+@pytest.fixture(autouse=True)
+def mpl_test_settings(request):
+ from matplotlib.testing.decorators import _cleanup_cm
+
+ with _cleanup_cm():
+
+ backend = None
+ backend_marker = request.node.get_closest_marker('backend')
+ prev_backend = matplotlib.get_backend()
+ if backend_marker is not None:
+ assert len(backend_marker.args) == 1, \
+ "Marker 'backend' must specify 1 backend."
+ backend, = backend_marker.args
+ skip_on_importerror = backend_marker.kwargs.get(
+ 'skip_on_importerror', False)
+
+ # special case Qt backend importing to avoid conflicts
+ if backend.lower().startswith('qt5'):
+ if any(sys.modules.get(k) for k in ('PyQt4', 'PySide')):
+ pytest.skip('Qt4 binding already imported')
+
+ matplotlib.testing.setup()
+ with _api.suppress_matplotlib_deprecation_warning():
+ if backend is not None:
+ # This import must come after setup() so it doesn't load the
+ # default backend prematurely.
+ import matplotlib.pyplot as plt
+ try:
+ plt.switch_backend(backend)
+ except ImportError as exc:
+ # Should only occur for the cairo backend tests, if neither
+ # pycairo nor cairocffi are installed.
+ if 'cairo' in backend.lower() or skip_on_importerror:
+ pytest.skip("Failed to switch to backend "
+ f"{backend} ({exc}).")
+ else:
+ raise
+ # Default of cleanup and image_comparison too.
+ matplotlib.style.use(["classic", "_classic_test_patch"])
+ try:
+ yield
+ finally:
+ if backend is not None:
+ plt.close("all")
+ matplotlib.use(prev_backend)
+
+
+@pytest.fixture
+def pd():
+ """
+ Fixture to import and configure pandas. Using this fixture, the test is skipped when
+ pandas is not installed. Use this fixture instead of importing pandas in test files.
+
+ Examples
+ --------
+ Request the pandas fixture by passing in ``pd`` as an argument to the test ::
+
+ def test_matshow_pandas(pd):
+
+ df = pd.DataFrame({'x':[1,2,3], 'y':[4,5,6]})
+ im = plt.figure().subplots().matshow(df)
+ np.testing.assert_array_equal(im.get_array(), df)
+ """
+ pd = pytest.importorskip('pandas')
+ try:
+ from pandas.plotting import (
+ deregister_matplotlib_converters as deregister)
+ deregister()
+ except ImportError:
+ pass
+ return pd
+
+
+@pytest.fixture
+def xr():
+ """
+ Fixture to import xarray so that the test is skipped when xarray is not installed.
+ Use this fixture instead of importing xrray in test files.
+
+ Examples
+ --------
+ Request the xarray fixture by passing in ``xr`` as an argument to the test ::
+
+ def test_imshow_xarray(xr):
+
+ ds = xr.DataArray(np.random.randn(2, 3))
+ im = plt.figure().subplots().imshow(ds)
+ np.testing.assert_array_equal(im.get_array(), ds)
+ """
+
+ xr = pytest.importorskip('xarray')
+ return xr
+
+
+@pytest.fixture
+def text_placeholders(monkeypatch):
+ """
+ Replace texts with placeholder rectangles.
+
+ The rectangle size only depends on the font size and the number of characters. It is
+ thus insensitive to font properties and rendering details. This should be used for
+ tests that depend on text geometries but not the actual text rendering, e.g. layout
+ tests.
+ """
+ from matplotlib.patches import Rectangle
+
+ def patched_get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi):
+ """
+ Replace ``_get_text_metrics_with_cache`` with fixed results.
+
+ The usual ``renderer.get_text_width_height_descent`` would depend on font
+ metrics; instead the fixed results are based on font size and the length of the
+ string only.
+ """
+ # While get_window_extent returns pixels and font size is in points, font size
+ # includes ascenders and descenders. Leaving out this factor and setting
+ # descent=0 ends up with a box that is relatively close to DejaVu Sans.
+ height = fontprop.get_size()
+ width = len(text) * height / 1.618 # Golden ratio for character size.
+ descent = 0
+ return width, height, descent
+
+ def patched_text_draw(self, renderer):
+ """
+ Replace ``Text.draw`` with a fixed bounding box Rectangle.
+
+ The bounding box corresponds to ``Text.get_window_extent``, which ultimately
+ depends on the above patched ``_get_text_metrics_with_cache``.
+ """
+ if renderer is not None:
+ self._renderer = renderer
+ if not self.get_visible():
+ return
+ if self.get_text() == '':
+ return
+ bbox = self.get_window_extent()
+ rect = Rectangle(bbox.p0, bbox.width, bbox.height,
+ facecolor=self.get_color(), edgecolor='none')
+ rect.draw(renderer)
+
+ monkeypatch.setattr('matplotlib.text._get_text_metrics_with_cache',
+ patched_get_text_metrics_with_cache)
+ monkeypatch.setattr('matplotlib.text.Text.draw', patched_text_draw)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/conftest.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/conftest.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..f5d90bc88f738244c463b6a070a756ace8c56202
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/conftest.pyi
@@ -0,0 +1,14 @@
+from types import ModuleType
+
+import pytest
+
+def pytest_configure(config: pytest.Config) -> None: ...
+def pytest_unconfigure(config: pytest.Config) -> None: ...
+@pytest.fixture
+def mpl_test_settings(request: pytest.FixtureRequest) -> None: ...
+@pytest.fixture
+def pd() -> ModuleType: ...
+@pytest.fixture
+def xr() -> ModuleType: ...
+@pytest.fixture
+def text_placeholders(monkeypatch: pytest.MonkeyPatch) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/decorators.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/decorators.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f1af7debdb3160b9975b5278d2864e1b79d64e3
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/decorators.py
@@ -0,0 +1,464 @@
+import contextlib
+import functools
+import inspect
+import os
+from platform import uname
+from pathlib import Path
+import shutil
+import string
+import sys
+import warnings
+
+from packaging.version import parse as parse_version
+
+import matplotlib.style
+import matplotlib.units
+import matplotlib.testing
+from matplotlib import _pylab_helpers, cbook, ft2font, pyplot as plt, ticker
+from .compare import comparable_formats, compare_images, make_test_filename
+from .exceptions import ImageComparisonFailure
+
+
+@contextlib.contextmanager
+def _cleanup_cm():
+ orig_units_registry = matplotlib.units.registry.copy()
+ try:
+ with warnings.catch_warnings(), matplotlib.rc_context():
+ yield
+ finally:
+ matplotlib.units.registry.clear()
+ matplotlib.units.registry.update(orig_units_registry)
+ plt.close("all")
+
+
+def _check_freetype_version(ver):
+ if ver is None:
+ return True
+
+ if isinstance(ver, str):
+ ver = (ver, ver)
+ ver = [parse_version(x) for x in ver]
+ found = parse_version(ft2font.__freetype_version__)
+
+ return ver[0] <= found <= ver[1]
+
+
+def _checked_on_freetype_version(required_freetype_version):
+ import pytest
+ return pytest.mark.xfail(
+ not _check_freetype_version(required_freetype_version),
+ reason=f"Mismatched version of freetype. "
+ f"Test requires '{required_freetype_version}', "
+ f"you have '{ft2font.__freetype_version__}'",
+ raises=ImageComparisonFailure, strict=False)
+
+
+def remove_ticks_and_titles(figure):
+ figure.suptitle("")
+ null_formatter = ticker.NullFormatter()
+ def remove_ticks(ax):
+ """Remove ticks in *ax* and all its child Axes."""
+ ax.set_title("")
+ ax.xaxis.set_major_formatter(null_formatter)
+ ax.xaxis.set_minor_formatter(null_formatter)
+ ax.yaxis.set_major_formatter(null_formatter)
+ ax.yaxis.set_minor_formatter(null_formatter)
+ try:
+ ax.zaxis.set_major_formatter(null_formatter)
+ ax.zaxis.set_minor_formatter(null_formatter)
+ except AttributeError:
+ pass
+ for child in ax.child_axes:
+ remove_ticks(child)
+ for ax in figure.get_axes():
+ remove_ticks(ax)
+
+
+@contextlib.contextmanager
+def _collect_new_figures():
+ """
+ After::
+
+ with _collect_new_figures() as figs:
+ some_code()
+
+ the list *figs* contains the figures that have been created during the
+ execution of ``some_code``, sorted by figure number.
+ """
+ managers = _pylab_helpers.Gcf.figs
+ preexisting = [manager for manager in managers.values()]
+ new_figs = []
+ try:
+ yield new_figs
+ finally:
+ new_managers = sorted([manager for manager in managers.values()
+ if manager not in preexisting],
+ key=lambda manager: manager.num)
+ new_figs[:] = [manager.canvas.figure for manager in new_managers]
+
+
+def _raise_on_image_difference(expected, actual, tol):
+ __tracebackhide__ = True
+
+ err = compare_images(expected, actual, tol, in_decorator=True)
+ if err:
+ for key in ["actual", "expected", "diff"]:
+ err[key] = os.path.relpath(err[key])
+ raise ImageComparisonFailure(
+ ('images not close (RMS %(rms).3f):'
+ '\n\t%(actual)s\n\t%(expected)s\n\t%(diff)s') % err)
+
+
+class _ImageComparisonBase:
+ """
+ Image comparison base class
+
+ This class provides *just* the comparison-related functionality and avoids
+ any code that would be specific to any testing framework.
+ """
+
+ def __init__(self, func, tol, remove_text, savefig_kwargs):
+ self.func = func
+ self.baseline_dir, self.result_dir = _image_directories(func)
+ self.tol = tol
+ self.remove_text = remove_text
+ self.savefig_kwargs = savefig_kwargs
+
+ def copy_baseline(self, baseline, extension):
+ baseline_path = self.baseline_dir / baseline
+ orig_expected_path = baseline_path.with_suffix(f'.{extension}')
+ if extension == 'eps' and not orig_expected_path.exists():
+ orig_expected_path = orig_expected_path.with_suffix('.pdf')
+ expected_fname = make_test_filename(
+ self.result_dir / orig_expected_path.name, 'expected')
+ try:
+ # os.symlink errors if the target already exists.
+ with contextlib.suppress(OSError):
+ os.remove(expected_fname)
+ try:
+ if 'microsoft' in uname().release.lower():
+ raise OSError # On WSL, symlink breaks silently
+ os.symlink(orig_expected_path, expected_fname)
+ except OSError: # On Windows, symlink *may* be unavailable.
+ shutil.copyfile(orig_expected_path, expected_fname)
+ except OSError as err:
+ raise ImageComparisonFailure(
+ f"Missing baseline image {expected_fname} because the "
+ f"following file cannot be accessed: "
+ f"{orig_expected_path}") from err
+ return expected_fname
+
+ def compare(self, fig, baseline, extension, *, _lock=False):
+ __tracebackhide__ = True
+
+ if self.remove_text:
+ remove_ticks_and_titles(fig)
+
+ actual_path = (self.result_dir / baseline).with_suffix(f'.{extension}')
+ kwargs = self.savefig_kwargs.copy()
+ if extension == 'pdf':
+ kwargs.setdefault('metadata',
+ {'Creator': None, 'Producer': None,
+ 'CreationDate': None})
+
+ lock = (cbook._lock_path(actual_path)
+ if _lock else contextlib.nullcontext())
+ with lock:
+ try:
+ fig.savefig(actual_path, **kwargs)
+ finally:
+ # Matplotlib has an autouse fixture to close figures, but this
+ # makes things more convenient for third-party users.
+ plt.close(fig)
+ expected_path = self.copy_baseline(baseline, extension)
+ _raise_on_image_difference(expected_path, actual_path, self.tol)
+
+
+def _pytest_image_comparison(baseline_images, extensions, tol,
+ freetype_version, remove_text, savefig_kwargs,
+ style):
+ """
+ Decorate function with image comparison for pytest.
+
+ This function creates a decorator that wraps a figure-generating function
+ with image comparison code.
+ """
+ import pytest
+
+ KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY
+
+ def decorator(func):
+ old_sig = inspect.signature(func)
+
+ @functools.wraps(func)
+ @pytest.mark.parametrize('extension', extensions)
+ @matplotlib.style.context(style)
+ @_checked_on_freetype_version(freetype_version)
+ @functools.wraps(func)
+ def wrapper(*args, extension, request, **kwargs):
+ __tracebackhide__ = True
+ if 'extension' in old_sig.parameters:
+ kwargs['extension'] = extension
+ if 'request' in old_sig.parameters:
+ kwargs['request'] = request
+
+ if extension not in comparable_formats():
+ reason = {
+ 'pdf': 'because Ghostscript is not installed',
+ 'eps': 'because Ghostscript is not installed',
+ 'svg': 'because Inkscape is not installed',
+ }.get(extension, 'on this system')
+ pytest.skip(f"Cannot compare {extension} files {reason}")
+
+ img = _ImageComparisonBase(func, tol=tol, remove_text=remove_text,
+ savefig_kwargs=savefig_kwargs)
+ matplotlib.testing.set_font_settings_for_testing()
+
+ with _collect_new_figures() as figs:
+ func(*args, **kwargs)
+
+ # If the test is parametrized in any way other than applied via
+ # this decorator, then we need to use a lock to prevent two
+ # processes from touching the same output file.
+ needs_lock = any(
+ marker.args[0] != 'extension'
+ for marker in request.node.iter_markers('parametrize'))
+
+ if baseline_images is not None:
+ our_baseline_images = baseline_images
+ else:
+ # Allow baseline image list to be produced on the fly based on
+ # current parametrization.
+ our_baseline_images = request.getfixturevalue(
+ 'baseline_images')
+
+ assert len(figs) == len(our_baseline_images), (
+ f"Test generated {len(figs)} images but there are "
+ f"{len(our_baseline_images)} baseline images")
+ for fig, baseline in zip(figs, our_baseline_images):
+ img.compare(fig, baseline, extension, _lock=needs_lock)
+
+ parameters = list(old_sig.parameters.values())
+ if 'extension' not in old_sig.parameters:
+ parameters += [inspect.Parameter('extension', KEYWORD_ONLY)]
+ if 'request' not in old_sig.parameters:
+ parameters += [inspect.Parameter("request", KEYWORD_ONLY)]
+ new_sig = old_sig.replace(parameters=parameters)
+ wrapper.__signature__ = new_sig
+
+ # Reach a bit into pytest internals to hoist the marks from our wrapped
+ # function.
+ new_marks = getattr(func, 'pytestmark', []) + wrapper.pytestmark
+ wrapper.pytestmark = new_marks
+
+ return wrapper
+
+ return decorator
+
+
+def image_comparison(baseline_images, extensions=None, tol=0,
+ freetype_version=None, remove_text=False,
+ savefig_kwarg=None,
+ # Default of mpl_test_settings fixture and cleanup too.
+ style=("classic", "_classic_test_patch")):
+ """
+ Compare images generated by the test with those specified in
+ *baseline_images*, which must correspond, else an `.ImageComparisonFailure`
+ exception will be raised.
+
+ Parameters
+ ----------
+ baseline_images : list or None
+ A list of strings specifying the names of the images generated by
+ calls to `.Figure.savefig`.
+
+ If *None*, the test function must use the ``baseline_images`` fixture,
+ either as a parameter or with `pytest.mark.usefixtures`. This value is
+ only allowed when using pytest.
+
+ extensions : None or list of str
+ The list of extensions to test, e.g. ``['png', 'pdf']``.
+
+ If *None*, defaults to all supported extensions: png, pdf, and svg.
+
+ When testing a single extension, it can be directly included in the
+ names passed to *baseline_images*. In that case, *extensions* must not
+ be set.
+
+ In order to keep the size of the test suite from ballooning, we only
+ include the ``svg`` or ``pdf`` outputs if the test is explicitly
+ exercising a feature dependent on that backend (see also the
+ `check_figures_equal` decorator for that purpose).
+
+ tol : float, default: 0
+ The RMS threshold above which the test is considered failed.
+
+ Due to expected small differences in floating-point calculations, on
+ 32-bit systems an additional 0.06 is added to this threshold.
+
+ freetype_version : str or tuple
+ The expected freetype version or range of versions for this test to
+ pass.
+
+ remove_text : bool
+ Remove the title and tick text from the figure before comparison. This
+ is useful to make the baseline images independent of variations in text
+ rendering between different versions of FreeType.
+
+ This does not remove other, more deliberate, text, such as legends and
+ annotations.
+
+ savefig_kwarg : dict
+ Optional arguments that are passed to the savefig method.
+
+ style : str, dict, or list
+ The optional style(s) to apply to the image test. The test itself
+ can also apply additional styles if desired. Defaults to ``["classic",
+ "_classic_test_patch"]``.
+ """
+
+ if baseline_images is not None:
+ # List of non-empty filename extensions.
+ baseline_exts = [*filter(None, {Path(baseline).suffix[1:]
+ for baseline in baseline_images})]
+ if baseline_exts:
+ if extensions is not None:
+ raise ValueError(
+ "When including extensions directly in 'baseline_images', "
+ "'extensions' cannot be set as well")
+ if len(baseline_exts) > 1:
+ raise ValueError(
+ "When including extensions directly in 'baseline_images', "
+ "all baselines must share the same suffix")
+ extensions = baseline_exts
+ baseline_images = [ # Chop suffix out from baseline_images.
+ Path(baseline).stem for baseline in baseline_images]
+ if extensions is None:
+ # Default extensions to test, if not set via baseline_images.
+ extensions = ['png', 'pdf', 'svg']
+ if savefig_kwarg is None:
+ savefig_kwarg = dict() # default no kwargs to savefig
+ if sys.maxsize <= 2**32:
+ tol += 0.06
+ return _pytest_image_comparison(
+ baseline_images=baseline_images, extensions=extensions, tol=tol,
+ freetype_version=freetype_version, remove_text=remove_text,
+ savefig_kwargs=savefig_kwarg, style=style)
+
+
+def check_figures_equal(*, extensions=("png", "pdf", "svg"), tol=0):
+ """
+ Decorator for test cases that generate and compare two figures.
+
+ The decorated function must take two keyword arguments, *fig_test*
+ and *fig_ref*, and draw the test and reference images on them.
+ After the function returns, the figures are saved and compared.
+
+ This decorator should be preferred over `image_comparison` when possible in
+ order to keep the size of the test suite from ballooning.
+
+ Parameters
+ ----------
+ extensions : list, default: ["png", "pdf", "svg"]
+ The extensions to test.
+ tol : float
+ The RMS threshold above which the test is considered failed.
+
+ Raises
+ ------
+ RuntimeError
+ If any new figures are created (and not subsequently closed) inside
+ the test function.
+
+ Examples
+ --------
+ Check that calling `.Axes.plot` with a single argument plots it against
+ ``[0, 1, 2, ...]``::
+
+ @check_figures_equal()
+ def test_plot(fig_test, fig_ref):
+ fig_test.subplots().plot([1, 3, 5])
+ fig_ref.subplots().plot([0, 1, 2], [1, 3, 5])
+
+ """
+ ALLOWED_CHARS = set(string.digits + string.ascii_letters + '_-[]()')
+ KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY
+
+ def decorator(func):
+ import pytest
+
+ _, result_dir = _image_directories(func)
+ old_sig = inspect.signature(func)
+
+ if not {"fig_test", "fig_ref"}.issubset(old_sig.parameters):
+ raise ValueError("The decorated function must have at least the "
+ "parameters 'fig_test' and 'fig_ref', but your "
+ f"function has the signature {old_sig}")
+
+ @pytest.mark.parametrize("ext", extensions)
+ def wrapper(*args, ext, request, **kwargs):
+ if 'ext' in old_sig.parameters:
+ kwargs['ext'] = ext
+ if 'request' in old_sig.parameters:
+ kwargs['request'] = request
+
+ file_name = "".join(c for c in request.node.name
+ if c in ALLOWED_CHARS)
+ try:
+ fig_test = plt.figure("test")
+ fig_ref = plt.figure("reference")
+ with _collect_new_figures() as figs:
+ func(*args, fig_test=fig_test, fig_ref=fig_ref, **kwargs)
+ if figs:
+ raise RuntimeError('Number of open figures changed during '
+ 'test. Make sure you are plotting to '
+ 'fig_test or fig_ref, or if this is '
+ 'deliberate explicitly close the '
+ 'new figure(s) inside the test.')
+ test_image_path = result_dir / (file_name + "." + ext)
+ ref_image_path = result_dir / (file_name + "-expected." + ext)
+ fig_test.savefig(test_image_path)
+ fig_ref.savefig(ref_image_path)
+ _raise_on_image_difference(
+ ref_image_path, test_image_path, tol=tol
+ )
+ finally:
+ plt.close(fig_test)
+ plt.close(fig_ref)
+
+ parameters = [
+ param
+ for param in old_sig.parameters.values()
+ if param.name not in {"fig_test", "fig_ref"}
+ ]
+ if 'ext' not in old_sig.parameters:
+ parameters += [inspect.Parameter("ext", KEYWORD_ONLY)]
+ if 'request' not in old_sig.parameters:
+ parameters += [inspect.Parameter("request", KEYWORD_ONLY)]
+ new_sig = old_sig.replace(parameters=parameters)
+ wrapper.__signature__ = new_sig
+
+ # reach a bit into pytest internals to hoist the marks from
+ # our wrapped function
+ new_marks = getattr(func, "pytestmark", []) + wrapper.pytestmark
+ wrapper.pytestmark = new_marks
+
+ return wrapper
+
+ return decorator
+
+
+def _image_directories(func):
+ """
+ Compute the baseline and result image directories for testing *func*.
+
+ For test module ``foo.bar.test_baz``, the baseline directory is at
+ ``foo/bar/baseline_images/test_baz`` and the result directory at
+ ``$(pwd)/result_images/test_baz``. The result directory is created if it
+ doesn't exist.
+ """
+ module_path = Path(inspect.getfile(func))
+ baseline_dir = module_path.parent / "baseline_images" / module_path.stem
+ result_dir = Path().resolve() / "result_images" / module_path.stem
+ result_dir.mkdir(parents=True, exist_ok=True)
+ return baseline_dir, result_dir
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/decorators.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/decorators.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..f1b6c5e595cbca4899cf169a6a85b917ad98bfb4
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/decorators.pyi
@@ -0,0 +1,25 @@
+from collections.abc import Callable, Sequence
+from pathlib import Path
+from typing import Any, TypeVar
+from typing_extensions import ParamSpec
+
+from matplotlib.figure import Figure
+from matplotlib.typing import RcStyleType
+
+_P = ParamSpec("_P")
+_R = TypeVar("_R")
+
+def remove_ticks_and_titles(figure: Figure) -> None: ...
+def image_comparison(
+ baseline_images: list[str] | None,
+ extensions: list[str] | None = ...,
+ tol: float = ...,
+ freetype_version: tuple[str, str] | str | None = ...,
+ remove_text: bool = ...,
+ savefig_kwarg: dict[str, Any] | None = ...,
+ style: RcStyleType = ...,
+) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
+def check_figures_equal(
+ *, extensions: Sequence[str] = ..., tol: float = ...
+) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
+def _image_directories(func: Callable) -> tuple[Path, Path]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/exceptions.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..c39a39207747c75d768ea2dd498dfed364eb88c5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/exceptions.py
@@ -0,0 +1,4 @@
+class ImageComparisonFailure(AssertionError):
+ """
+ Raise this exception to mark a test as a comparison between two images.
+ """
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/Duration.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/Duration.py
new file mode 100644
index 0000000000000000000000000000000000000000..052c5a47c0fd8313795acfcfc2e1aaf851d05c60
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/Duration.py
@@ -0,0 +1,138 @@
+"""Duration module."""
+
+import functools
+import operator
+
+from matplotlib import _api
+
+
+class Duration:
+ """Class Duration in development."""
+
+ allowed = ["ET", "UTC"]
+
+ def __init__(self, frame, seconds):
+ """
+ Create a new Duration object.
+
+ = ERROR CONDITIONS
+ - If the input frame is not in the allowed list, an error is thrown.
+
+ = INPUT VARIABLES
+ - frame The frame of the duration. Must be 'ET' or 'UTC'
+ - seconds The number of seconds in the Duration.
+ """
+ _api.check_in_list(self.allowed, frame=frame)
+ self._frame = frame
+ self._seconds = seconds
+
+ def frame(self):
+ """Return the frame the duration is in."""
+ return self._frame
+
+ def __abs__(self):
+ """Return the absolute value of the duration."""
+ return Duration(self._frame, abs(self._seconds))
+
+ def __neg__(self):
+ """Return the negative value of this Duration."""
+ return Duration(self._frame, -self._seconds)
+
+ def seconds(self):
+ """Return the number of seconds in the Duration."""
+ return self._seconds
+
+ def __bool__(self):
+ return self._seconds != 0
+
+ def _cmp(self, op, rhs):
+ """
+ Check that *self* and *rhs* share frames; compare them using *op*.
+ """
+ self.checkSameFrame(rhs, "compare")
+ return op(self._seconds, rhs._seconds)
+
+ __eq__ = functools.partialmethod(_cmp, operator.eq)
+ __ne__ = functools.partialmethod(_cmp, operator.ne)
+ __lt__ = functools.partialmethod(_cmp, operator.lt)
+ __le__ = functools.partialmethod(_cmp, operator.le)
+ __gt__ = functools.partialmethod(_cmp, operator.gt)
+ __ge__ = functools.partialmethod(_cmp, operator.ge)
+
+ def __add__(self, rhs):
+ """
+ Add two Durations.
+
+ = ERROR CONDITIONS
+ - If the input rhs is not in the same frame, an error is thrown.
+
+ = INPUT VARIABLES
+ - rhs The Duration to add.
+
+ = RETURN VALUE
+ - Returns the sum of ourselves and the input Duration.
+ """
+ # Delay-load due to circular dependencies.
+ import matplotlib.testing.jpl_units as U
+
+ if isinstance(rhs, U.Epoch):
+ return rhs + self
+
+ self.checkSameFrame(rhs, "add")
+ return Duration(self._frame, self._seconds + rhs._seconds)
+
+ def __sub__(self, rhs):
+ """
+ Subtract two Durations.
+
+ = ERROR CONDITIONS
+ - If the input rhs is not in the same frame, an error is thrown.
+
+ = INPUT VARIABLES
+ - rhs The Duration to subtract.
+
+ = RETURN VALUE
+ - Returns the difference of ourselves and the input Duration.
+ """
+ self.checkSameFrame(rhs, "sub")
+ return Duration(self._frame, self._seconds - rhs._seconds)
+
+ def __mul__(self, rhs):
+ """
+ Scale a UnitDbl by a value.
+
+ = INPUT VARIABLES
+ - rhs The scalar to multiply by.
+
+ = RETURN VALUE
+ - Returns the scaled Duration.
+ """
+ return Duration(self._frame, self._seconds * float(rhs))
+
+ __rmul__ = __mul__
+
+ def __str__(self):
+ """Print the Duration."""
+ return f"{self._seconds:g} {self._frame}"
+
+ def __repr__(self):
+ """Print the Duration."""
+ return f"Duration('{self._frame}', {self._seconds:g})"
+
+ def checkSameFrame(self, rhs, func):
+ """
+ Check to see if frames are the same.
+
+ = ERROR CONDITIONS
+ - If the frame of the rhs Duration is not the same as our frame,
+ an error is thrown.
+
+ = INPUT VARIABLES
+ - rhs The Duration to check for the same frame
+ - func The name of the function doing the check.
+ """
+ if self._frame != rhs._frame:
+ raise ValueError(
+ f"Cannot {func} Durations with different frames.\n"
+ f"LHS: {self._frame}\n"
+ f"RHS: {rhs._frame}")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/Epoch.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/Epoch.py
new file mode 100644
index 0000000000000000000000000000000000000000..501b7fa38c792d5bc3c7c683e62e1393a63a4ff7
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/Epoch.py
@@ -0,0 +1,211 @@
+"""Epoch module."""
+
+import functools
+import operator
+import math
+import datetime as DT
+
+from matplotlib import _api
+from matplotlib.dates import date2num
+
+
+class Epoch:
+ # Frame conversion offsets in seconds
+ # t(TO) = t(FROM) + allowed[ FROM ][ TO ]
+ allowed = {
+ "ET": {
+ "UTC": +64.1839,
+ },
+ "UTC": {
+ "ET": -64.1839,
+ },
+ }
+
+ def __init__(self, frame, sec=None, jd=None, daynum=None, dt=None):
+ """
+ Create a new Epoch object.
+
+ Build an epoch 1 of 2 ways:
+
+ Using seconds past a Julian date:
+ # Epoch('ET', sec=1e8, jd=2451545)
+
+ or using a matplotlib day number
+ # Epoch('ET', daynum=730119.5)
+
+ = ERROR CONDITIONS
+ - If the input units are not in the allowed list, an error is thrown.
+
+ = INPUT VARIABLES
+ - frame The frame of the epoch. Must be 'ET' or 'UTC'
+ - sec The number of seconds past the input JD.
+ - jd The Julian date of the epoch.
+ - daynum The matplotlib day number of the epoch.
+ - dt A python datetime instance.
+ """
+ if ((sec is None and jd is not None) or
+ (sec is not None and jd is None) or
+ (daynum is not None and
+ (sec is not None or jd is not None)) or
+ (daynum is None and dt is None and
+ (sec is None or jd is None)) or
+ (daynum is not None and dt is not None) or
+ (dt is not None and (sec is not None or jd is not None)) or
+ ((dt is not None) and not isinstance(dt, DT.datetime))):
+ raise ValueError(
+ "Invalid inputs. Must enter sec and jd together, "
+ "daynum by itself, or dt (must be a python datetime).\n"
+ "Sec = %s\n"
+ "JD = %s\n"
+ "dnum= %s\n"
+ "dt = %s" % (sec, jd, daynum, dt))
+
+ _api.check_in_list(self.allowed, frame=frame)
+ self._frame = frame
+
+ if dt is not None:
+ daynum = date2num(dt)
+
+ if daynum is not None:
+ # 1-JAN-0001 in JD = 1721425.5
+ jd = float(daynum) + 1721425.5
+ self._jd = math.floor(jd)
+ self._seconds = (jd - self._jd) * 86400.0
+
+ else:
+ self._seconds = float(sec)
+ self._jd = float(jd)
+
+ # Resolve seconds down to [ 0, 86400)
+ deltaDays = math.floor(self._seconds / 86400)
+ self._jd += deltaDays
+ self._seconds -= deltaDays * 86400.0
+
+ def convert(self, frame):
+ if self._frame == frame:
+ return self
+
+ offset = self.allowed[self._frame][frame]
+
+ return Epoch(frame, self._seconds + offset, self._jd)
+
+ def frame(self):
+ return self._frame
+
+ def julianDate(self, frame):
+ t = self
+ if frame != self._frame:
+ t = self.convert(frame)
+
+ return t._jd + t._seconds / 86400.0
+
+ def secondsPast(self, frame, jd):
+ t = self
+ if frame != self._frame:
+ t = self.convert(frame)
+
+ delta = t._jd - jd
+ return t._seconds + delta * 86400
+
+ def _cmp(self, op, rhs):
+ """Compare Epochs *self* and *rhs* using operator *op*."""
+ t = self
+ if self._frame != rhs._frame:
+ t = self.convert(rhs._frame)
+ if t._jd != rhs._jd:
+ return op(t._jd, rhs._jd)
+ return op(t._seconds, rhs._seconds)
+
+ __eq__ = functools.partialmethod(_cmp, operator.eq)
+ __ne__ = functools.partialmethod(_cmp, operator.ne)
+ __lt__ = functools.partialmethod(_cmp, operator.lt)
+ __le__ = functools.partialmethod(_cmp, operator.le)
+ __gt__ = functools.partialmethod(_cmp, operator.gt)
+ __ge__ = functools.partialmethod(_cmp, operator.ge)
+
+ def __add__(self, rhs):
+ """
+ Add a duration to an Epoch.
+
+ = INPUT VARIABLES
+ - rhs The Epoch to subtract.
+
+ = RETURN VALUE
+ - Returns the difference of ourselves and the input Epoch.
+ """
+ t = self
+ if self._frame != rhs.frame():
+ t = self.convert(rhs._frame)
+
+ sec = t._seconds + rhs.seconds()
+
+ return Epoch(t._frame, sec, t._jd)
+
+ def __sub__(self, rhs):
+ """
+ Subtract two Epoch's or a Duration from an Epoch.
+
+ Valid:
+ Duration = Epoch - Epoch
+ Epoch = Epoch - Duration
+
+ = INPUT VARIABLES
+ - rhs The Epoch to subtract.
+
+ = RETURN VALUE
+ - Returns either the duration between to Epoch's or the a new
+ Epoch that is the result of subtracting a duration from an epoch.
+ """
+ # Delay-load due to circular dependencies.
+ import matplotlib.testing.jpl_units as U
+
+ # Handle Epoch - Duration
+ if isinstance(rhs, U.Duration):
+ return self + -rhs
+
+ t = self
+ if self._frame != rhs._frame:
+ t = self.convert(rhs._frame)
+
+ days = t._jd - rhs._jd
+ sec = t._seconds - rhs._seconds
+
+ return U.Duration(rhs._frame, days*86400 + sec)
+
+ def __str__(self):
+ """Print the Epoch."""
+ return f"{self.julianDate(self._frame):22.15e} {self._frame}"
+
+ def __repr__(self):
+ """Print the Epoch."""
+ return str(self)
+
+ @staticmethod
+ def range(start, stop, step):
+ """
+ Generate a range of Epoch objects.
+
+ Similar to the Python range() method. Returns the range [
+ start, stop) at the requested step. Each element will be a
+ Epoch object.
+
+ = INPUT VARIABLES
+ - start The starting value of the range.
+ - stop The stop value of the range.
+ - step Step to use.
+
+ = RETURN VALUE
+ - Returns a list containing the requested Epoch values.
+ """
+ elems = []
+
+ i = 0
+ while True:
+ d = start + i * step
+ if d >= stop:
+ break
+
+ elems.append(d)
+ i += 1
+
+ return elems
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/EpochConverter.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/EpochConverter.py
new file mode 100644
index 0000000000000000000000000000000000000000..1edc2acf2b24fe4aab242455dd32679456f69b57
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/EpochConverter.py
@@ -0,0 +1,94 @@
+"""EpochConverter module containing class EpochConverter."""
+
+from matplotlib import cbook, units
+import matplotlib.dates as date_ticker
+
+__all__ = ['EpochConverter']
+
+
+class EpochConverter(units.ConversionInterface):
+ """
+ Provides Matplotlib conversion functionality for Monte Epoch and Duration
+ classes.
+ """
+
+ jdRef = 1721425.5
+
+ @staticmethod
+ def axisinfo(unit, axis):
+ # docstring inherited
+ majloc = date_ticker.AutoDateLocator()
+ majfmt = date_ticker.AutoDateFormatter(majloc)
+ return units.AxisInfo(majloc=majloc, majfmt=majfmt, label=unit)
+
+ @staticmethod
+ def float2epoch(value, unit):
+ """
+ Convert a Matplotlib floating-point date into an Epoch of the specified
+ units.
+
+ = INPUT VARIABLES
+ - value The Matplotlib floating-point date.
+ - unit The unit system to use for the Epoch.
+
+ = RETURN VALUE
+ - Returns the value converted to an Epoch in the specified time system.
+ """
+ # Delay-load due to circular dependencies.
+ import matplotlib.testing.jpl_units as U
+
+ secPastRef = value * 86400.0 * U.UnitDbl(1.0, 'sec')
+ return U.Epoch(unit, secPastRef, EpochConverter.jdRef)
+
+ @staticmethod
+ def epoch2float(value, unit):
+ """
+ Convert an Epoch value to a float suitable for plotting as a python
+ datetime object.
+
+ = INPUT VARIABLES
+ - value An Epoch or list of Epochs that need to be converted.
+ - unit The units to use for an axis with Epoch data.
+
+ = RETURN VALUE
+ - Returns the value parameter converted to floats.
+ """
+ return value.julianDate(unit) - EpochConverter.jdRef
+
+ @staticmethod
+ def duration2float(value):
+ """
+ Convert a Duration value to a float suitable for plotting as a python
+ datetime object.
+
+ = INPUT VARIABLES
+ - value A Duration or list of Durations that need to be converted.
+
+ = RETURN VALUE
+ - Returns the value parameter converted to floats.
+ """
+ return value.seconds() / 86400.0
+
+ @staticmethod
+ def convert(value, unit, axis):
+ # docstring inherited
+
+ # Delay-load due to circular dependencies.
+ import matplotlib.testing.jpl_units as U
+
+ if not cbook.is_scalar_or_string(value):
+ return [EpochConverter.convert(x, unit, axis) for x in value]
+ if unit is None:
+ unit = EpochConverter.default_units(value, axis)
+ if isinstance(value, U.Duration):
+ return EpochConverter.duration2float(value)
+ else:
+ return EpochConverter.epoch2float(value, unit)
+
+ @staticmethod
+ def default_units(value, axis):
+ # docstring inherited
+ if cbook.is_scalar_or_string(value):
+ return value.frame()
+ else:
+ return EpochConverter.default_units(value[0], axis)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/StrConverter.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/StrConverter.py
new file mode 100644
index 0000000000000000000000000000000000000000..a62d4981dc79201214dc926eaa6a4c74ffcba078
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/StrConverter.py
@@ -0,0 +1,97 @@
+"""StrConverter module containing class StrConverter."""
+
+import numpy as np
+
+import matplotlib.units as units
+
+__all__ = ['StrConverter']
+
+
+class StrConverter(units.ConversionInterface):
+ """
+ A Matplotlib converter class for string data values.
+
+ Valid units for string are:
+ - 'indexed' : Values are indexed as they are specified for plotting.
+ - 'sorted' : Values are sorted alphanumerically.
+ - 'inverted' : Values are inverted so that the first value is on top.
+ - 'sorted-inverted' : A combination of 'sorted' and 'inverted'
+ """
+
+ @staticmethod
+ def axisinfo(unit, axis):
+ # docstring inherited
+ return None
+
+ @staticmethod
+ def convert(value, unit, axis):
+ # docstring inherited
+
+ if value == []:
+ return []
+
+ # we delay loading to make matplotlib happy
+ ax = axis.axes
+ if axis is ax.xaxis:
+ isXAxis = True
+ else:
+ isXAxis = False
+
+ axis.get_major_ticks()
+ ticks = axis.get_ticklocs()
+ labels = axis.get_ticklabels()
+
+ labels = [l.get_text() for l in labels if l.get_text()]
+
+ if not labels:
+ ticks = []
+ labels = []
+
+ if not np.iterable(value):
+ value = [value]
+
+ newValues = []
+ for v in value:
+ if v not in labels and v not in newValues:
+ newValues.append(v)
+
+ labels.extend(newValues)
+
+ # DISABLED: This is disabled because matplotlib bar plots do not
+ # DISABLED: recalculate the unit conversion of the data values
+ # DISABLED: this is due to design and is not really a bug.
+ # DISABLED: If this gets changed, then we can activate the following
+ # DISABLED: block of code. Note that this works for line plots.
+ # DISABLED if unit:
+ # DISABLED if unit.find("sorted") > -1:
+ # DISABLED labels.sort()
+ # DISABLED if unit.find("inverted") > -1:
+ # DISABLED labels = labels[::-1]
+
+ # add padding (so they do not appear on the axes themselves)
+ labels = [''] + labels + ['']
+ ticks = list(range(len(labels)))
+ ticks[0] = 0.5
+ ticks[-1] = ticks[-1] - 0.5
+
+ axis.set_ticks(ticks)
+ axis.set_ticklabels(labels)
+ # we have to do the following lines to make ax.autoscale_view work
+ loc = axis.get_major_locator()
+ loc.set_bounds(ticks[0], ticks[-1])
+
+ if isXAxis:
+ ax.set_xlim(ticks[0], ticks[-1])
+ else:
+ ax.set_ylim(ticks[0], ticks[-1])
+
+ result = [ticks[labels.index(v)] for v in value]
+
+ ax.viewLim.ignore(-1)
+ return result
+
+ @staticmethod
+ def default_units(value, axis):
+ # docstring inherited
+ # The default behavior for string indexing.
+ return "indexed"
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/UnitDbl.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/UnitDbl.py
new file mode 100644
index 0000000000000000000000000000000000000000..5226c06ad54b9f7718a59e7ed19198d442c04f31
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/UnitDbl.py
@@ -0,0 +1,180 @@
+"""UnitDbl module."""
+
+import functools
+import operator
+
+from matplotlib import _api
+
+
+class UnitDbl:
+ """Class UnitDbl in development."""
+
+ # Unit conversion table. Small subset of the full one but enough
+ # to test the required functions. First field is a scale factor to
+ # convert the input units to the units of the second field. Only
+ # units in this table are allowed.
+ allowed = {
+ "m": (0.001, "km"),
+ "km": (1, "km"),
+ "mile": (1.609344, "km"),
+
+ "rad": (1, "rad"),
+ "deg": (1.745329251994330e-02, "rad"),
+
+ "sec": (1, "sec"),
+ "min": (60.0, "sec"),
+ "hour": (3600, "sec"),
+ }
+
+ _types = {
+ "km": "distance",
+ "rad": "angle",
+ "sec": "time",
+ }
+
+ def __init__(self, value, units):
+ """
+ Create a new UnitDbl object.
+
+ Units are internally converted to km, rad, and sec. The only
+ valid inputs for units are [m, km, mile, rad, deg, sec, min, hour].
+
+ The field UnitDbl.value will contain the converted value. Use
+ the convert() method to get a specific type of units back.
+
+ = ERROR CONDITIONS
+ - If the input units are not in the allowed list, an error is thrown.
+
+ = INPUT VARIABLES
+ - value The numeric value of the UnitDbl.
+ - units The string name of the units the value is in.
+ """
+ data = _api.check_getitem(self.allowed, units=units)
+ self._value = float(value * data[0])
+ self._units = data[1]
+
+ def convert(self, units):
+ """
+ Convert the UnitDbl to a specific set of units.
+
+ = ERROR CONDITIONS
+ - If the input units are not in the allowed list, an error is thrown.
+
+ = INPUT VARIABLES
+ - units The string name of the units to convert to.
+
+ = RETURN VALUE
+ - Returns the value of the UnitDbl in the requested units as a floating
+ point number.
+ """
+ if self._units == units:
+ return self._value
+ data = _api.check_getitem(self.allowed, units=units)
+ if self._units != data[1]:
+ raise ValueError(f"Error trying to convert to different units.\n"
+ f" Invalid conversion requested.\n"
+ f" UnitDbl: {self}\n"
+ f" Units: {units}\n")
+ return self._value / data[0]
+
+ def __abs__(self):
+ """Return the absolute value of this UnitDbl."""
+ return UnitDbl(abs(self._value), self._units)
+
+ def __neg__(self):
+ """Return the negative value of this UnitDbl."""
+ return UnitDbl(-self._value, self._units)
+
+ def __bool__(self):
+ """Return the truth value of a UnitDbl."""
+ return bool(self._value)
+
+ def _cmp(self, op, rhs):
+ """Check that *self* and *rhs* share units; compare them using *op*."""
+ self.checkSameUnits(rhs, "compare")
+ return op(self._value, rhs._value)
+
+ __eq__ = functools.partialmethod(_cmp, operator.eq)
+ __ne__ = functools.partialmethod(_cmp, operator.ne)
+ __lt__ = functools.partialmethod(_cmp, operator.lt)
+ __le__ = functools.partialmethod(_cmp, operator.le)
+ __gt__ = functools.partialmethod(_cmp, operator.gt)
+ __ge__ = functools.partialmethod(_cmp, operator.ge)
+
+ def _binop_unit_unit(self, op, rhs):
+ """Check that *self* and *rhs* share units; combine them using *op*."""
+ self.checkSameUnits(rhs, op.__name__)
+ return UnitDbl(op(self._value, rhs._value), self._units)
+
+ __add__ = functools.partialmethod(_binop_unit_unit, operator.add)
+ __sub__ = functools.partialmethod(_binop_unit_unit, operator.sub)
+
+ def _binop_unit_scalar(self, op, scalar):
+ """Combine *self* and *scalar* using *op*."""
+ return UnitDbl(op(self._value, scalar), self._units)
+
+ __mul__ = functools.partialmethod(_binop_unit_scalar, operator.mul)
+ __rmul__ = functools.partialmethod(_binop_unit_scalar, operator.mul)
+
+ def __str__(self):
+ """Print the UnitDbl."""
+ return f"{self._value:g} *{self._units}"
+
+ def __repr__(self):
+ """Print the UnitDbl."""
+ return f"UnitDbl({self._value:g}, '{self._units}')"
+
+ def type(self):
+ """Return the type of UnitDbl data."""
+ return self._types[self._units]
+
+ @staticmethod
+ def range(start, stop, step=None):
+ """
+ Generate a range of UnitDbl objects.
+
+ Similar to the Python range() method. Returns the range [
+ start, stop) at the requested step. Each element will be a
+ UnitDbl object.
+
+ = INPUT VARIABLES
+ - start The starting value of the range.
+ - stop The stop value of the range.
+ - step Optional step to use. If set to None, then a UnitDbl of
+ value 1 w/ the units of the start is used.
+
+ = RETURN VALUE
+ - Returns a list containing the requested UnitDbl values.
+ """
+ if step is None:
+ step = UnitDbl(1, start._units)
+
+ elems = []
+
+ i = 0
+ while True:
+ d = start + i * step
+ if d >= stop:
+ break
+
+ elems.append(d)
+ i += 1
+
+ return elems
+
+ def checkSameUnits(self, rhs, func):
+ """
+ Check to see if units are the same.
+
+ = ERROR CONDITIONS
+ - If the units of the rhs UnitDbl are not the same as our units,
+ an error is thrown.
+
+ = INPUT VARIABLES
+ - rhs The UnitDbl to check for the same units
+ - func The name of the function doing the check.
+ """
+ if self._units != rhs._units:
+ raise ValueError(f"Cannot {func} units of different types.\n"
+ f"LHS: {self._units}\n"
+ f"RHS: {rhs._units}")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/UnitDblConverter.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/UnitDblConverter.py
new file mode 100644
index 0000000000000000000000000000000000000000..23065379f58193ae2f240d7bff44aaf70f8a8dca
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/UnitDblConverter.py
@@ -0,0 +1,85 @@
+"""UnitDblConverter module containing class UnitDblConverter."""
+
+import numpy as np
+
+from matplotlib import cbook, units
+import matplotlib.projections.polar as polar
+
+__all__ = ['UnitDblConverter']
+
+
+# A special function for use with the matplotlib FuncFormatter class
+# for formatting axes with radian units.
+# This was copied from matplotlib example code.
+def rad_fn(x, pos=None):
+ """Radian function formatter."""
+ n = int((x / np.pi) * 2.0 + 0.25)
+ if n == 0:
+ return str(x)
+ elif n == 1:
+ return r'$\pi/2$'
+ elif n == 2:
+ return r'$\pi$'
+ elif n % 2 == 0:
+ return fr'${n//2}\pi$'
+ else:
+ return fr'${n}\pi/2$'
+
+
+class UnitDblConverter(units.ConversionInterface):
+ """
+ Provides Matplotlib conversion functionality for the Monte UnitDbl class.
+ """
+ # default for plotting
+ defaults = {
+ "distance": 'km',
+ "angle": 'deg',
+ "time": 'sec',
+ }
+
+ @staticmethod
+ def axisinfo(unit, axis):
+ # docstring inherited
+
+ # Delay-load due to circular dependencies.
+ import matplotlib.testing.jpl_units as U
+
+ # Check to see if the value used for units is a string unit value
+ # or an actual instance of a UnitDbl so that we can use the unit
+ # value for the default axis label value.
+ if unit:
+ label = unit if isinstance(unit, str) else unit.label()
+ else:
+ label = None
+
+ if label == "deg" and isinstance(axis.axes, polar.PolarAxes):
+ # If we want degrees for a polar plot, use the PolarPlotFormatter
+ majfmt = polar.PolarAxes.ThetaFormatter()
+ else:
+ majfmt = U.UnitDblFormatter(useOffset=False)
+
+ return units.AxisInfo(majfmt=majfmt, label=label)
+
+ @staticmethod
+ def convert(value, unit, axis):
+ # docstring inherited
+ if not cbook.is_scalar_or_string(value):
+ return [UnitDblConverter.convert(x, unit, axis) for x in value]
+ # If no units were specified, then get the default units to use.
+ if unit is None:
+ unit = UnitDblConverter.default_units(value, axis)
+ # Convert the incoming UnitDbl value/values to float/floats
+ if isinstance(axis.axes, polar.PolarAxes) and value.type() == "angle":
+ # Guarantee that units are radians for polar plots.
+ return value.convert("rad")
+ return value.convert(unit)
+
+ @staticmethod
+ def default_units(value, axis):
+ # docstring inherited
+ # Determine the default units based on the user preferences set for
+ # default units when printing a UnitDbl.
+ if cbook.is_scalar_or_string(value):
+ return UnitDblConverter.defaults[value.type()]
+ else:
+ return UnitDblConverter.default_units(value[0], axis)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/UnitDblFormatter.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/UnitDblFormatter.py
new file mode 100644
index 0000000000000000000000000000000000000000..30a9914015bc7eb955c88e562a771591b82ad2e8
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/UnitDblFormatter.py
@@ -0,0 +1,28 @@
+"""UnitDblFormatter module containing class UnitDblFormatter."""
+
+import matplotlib.ticker as ticker
+
+__all__ = ['UnitDblFormatter']
+
+
+class UnitDblFormatter(ticker.ScalarFormatter):
+ """
+ The formatter for UnitDbl data types.
+
+ This allows for formatting with the unit string.
+ """
+
+ def __call__(self, x, pos=None):
+ # docstring inherited
+ if len(self.locs) == 0:
+ return ''
+ else:
+ return f'{x:.12}'
+
+ def format_data_short(self, value):
+ # docstring inherited
+ return f'{value:.12}'
+
+ def format_data(self, value):
+ # docstring inherited
+ return f'{value:.12}'
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/__init__.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8caa9a8957a250b78712c25175bec415507e416
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/jpl_units/__init__.py
@@ -0,0 +1,76 @@
+"""
+A sample set of units for use with testing unit conversion
+of Matplotlib routines. These are used because they use very strict
+enforcement of unitized data which will test the entire spectrum of how
+unitized data might be used (it is not always meaningful to convert to
+a float without specific units given).
+
+UnitDbl is essentially a unitized floating point number. It has a
+minimal set of supported units (enough for testing purposes). All
+of the mathematical operation are provided to fully test any behaviour
+that might occur with unitized data. Remember that unitized data has
+rules as to how it can be applied to one another (a value of distance
+cannot be added to a value of time). Thus we need to guard against any
+accidental "default" conversion that will strip away the meaning of the
+data and render it neutered.
+
+Epoch is different than a UnitDbl of time. Time is something that can be
+measured where an Epoch is a specific moment in time. Epochs are typically
+referenced as an offset from some predetermined epoch.
+
+A difference of two epochs is a Duration. The distinction between a Duration
+and a UnitDbl of time is made because an Epoch can have different frames (or
+units). In the case of our test Epoch class the two allowed frames are 'UTC'
+and 'ET' (Note that these are rough estimates provided for testing purposes
+and should not be used in production code where accuracy of time frames is
+desired). As such a Duration also has a frame of reference and therefore needs
+to be called out as different that a simple measurement of time since a delta-t
+in one frame may not be the same in another.
+"""
+
+from .Duration import Duration
+from .Epoch import Epoch
+from .UnitDbl import UnitDbl
+
+from .StrConverter import StrConverter
+from .EpochConverter import EpochConverter
+from .UnitDblConverter import UnitDblConverter
+
+from .UnitDblFormatter import UnitDblFormatter
+
+
+__version__ = "1.0"
+
+__all__ = [
+ 'register',
+ 'Duration',
+ 'Epoch',
+ 'UnitDbl',
+ 'UnitDblFormatter',
+ ]
+
+
+def register():
+ """Register the unit conversion classes with matplotlib."""
+ import matplotlib.units as mplU
+
+ mplU.registry[str] = StrConverter()
+ mplU.registry[Epoch] = EpochConverter()
+ mplU.registry[Duration] = EpochConverter()
+ mplU.registry[UnitDbl] = UnitDblConverter()
+
+
+# Some default unit instances
+# Distances
+m = UnitDbl(1.0, "m")
+km = UnitDbl(1.0, "km")
+mile = UnitDbl(1.0, "mile")
+# Angles
+deg = UnitDbl(1.0, "deg")
+rad = UnitDbl(1.0, "rad")
+# Time
+sec = UnitDbl(1.0, "sec")
+min = UnitDbl(1.0, "min")
+hr = UnitDbl(1.0, "hour")
+day = UnitDbl(24.0, "hour")
+sec = UnitDbl(1.0, "sec")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/widgets.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/widgets.py
new file mode 100644
index 0000000000000000000000000000000000000000..3962567aa7c03eb978db53e3d6007446bb177d28
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/widgets.py
@@ -0,0 +1,119 @@
+"""
+========================
+Widget testing utilities
+========================
+
+See also :mod:`matplotlib.tests.test_widgets`.
+"""
+
+from unittest import mock
+
+import matplotlib.pyplot as plt
+
+
+def get_ax():
+ """Create a plot and return its Axes."""
+ fig, ax = plt.subplots(1, 1)
+ ax.plot([0, 200], [0, 200])
+ ax.set_aspect(1.0)
+ fig.canvas.draw()
+ return ax
+
+
+def noop(*args, **kwargs):
+ pass
+
+
+def mock_event(ax, button=1, xdata=0, ydata=0, key=None, step=1):
+ r"""
+ Create a mock event that can stand in for `.Event` and its subclasses.
+
+ This event is intended to be used in tests where it can be passed into
+ event handling functions.
+
+ Parameters
+ ----------
+ ax : `~matplotlib.axes.Axes`
+ The Axes the event will be in.
+ xdata : float
+ x coord of mouse in data coords.
+ ydata : float
+ y coord of mouse in data coords.
+ button : None or `MouseButton` or {'up', 'down'}
+ The mouse button pressed in this event (see also `.MouseEvent`).
+ key : None or str
+ The key pressed when the mouse event triggered (see also `.KeyEvent`).
+ step : int
+ Number of scroll steps (positive for 'up', negative for 'down').
+
+ Returns
+ -------
+ event
+ A `.Event`\-like Mock instance.
+ """
+ event = mock.Mock()
+ event.button = button
+ event.x, event.y = ax.transData.transform([(xdata, ydata),
+ (xdata, ydata)])[0]
+ event.xdata, event.ydata = xdata, ydata
+ event.inaxes = ax
+ event.canvas = ax.get_figure(root=True).canvas
+ event.key = key
+ event.step = step
+ event.guiEvent = None
+ event.name = 'Custom'
+ return event
+
+
+def do_event(tool, etype, button=1, xdata=0, ydata=0, key=None, step=1):
+ """
+ Trigger an event on the given tool.
+
+ Parameters
+ ----------
+ tool : matplotlib.widgets.AxesWidget
+ etype : str
+ The event to trigger.
+ xdata : float
+ x coord of mouse in data coords.
+ ydata : float
+ y coord of mouse in data coords.
+ button : None or `MouseButton` or {'up', 'down'}
+ The mouse button pressed in this event (see also `.MouseEvent`).
+ key : None or str
+ The key pressed when the mouse event triggered (see also `.KeyEvent`).
+ step : int
+ Number of scroll steps (positive for 'up', negative for 'down').
+ """
+ event = mock_event(tool.ax, button, xdata, ydata, key, step)
+ func = getattr(tool, etype)
+ func(event)
+
+
+def click_and_drag(tool, start, end, key=None):
+ """
+ Helper to simulate a mouse drag operation.
+
+ Parameters
+ ----------
+ tool : `~matplotlib.widgets.Widget`
+ start : [float, float]
+ Starting point in data coordinates.
+ end : [float, float]
+ End point in data coordinates.
+ key : None or str
+ An optional key that is pressed during the whole operation
+ (see also `.KeyEvent`).
+ """
+ if key is not None:
+ # Press key
+ do_event(tool, 'on_key_press', xdata=start[0], ydata=start[1],
+ button=1, key=key)
+ # Click, move, and release mouse
+ do_event(tool, 'press', xdata=start[0], ydata=start[1], button=1)
+ do_event(tool, 'onmove', xdata=end[0], ydata=end[1], button=1)
+ do_event(tool, 'release', xdata=end[0], ydata=end[1], button=1)
+ if key is not None:
+ # Release key
+ do_event(tool, 'on_key_release', xdata=end[0], ydata=end[1],
+ button=1, key=key)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/widgets.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/widgets.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..858ff457158283bae9db974e05098890ef5ac260
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/testing/widgets.pyi
@@ -0,0 +1,31 @@
+from typing import Any, Literal
+
+from matplotlib.axes import Axes
+from matplotlib.backend_bases import Event, MouseButton
+from matplotlib.widgets import AxesWidget, Widget
+
+def get_ax() -> Axes: ...
+def noop(*args: Any, **kwargs: Any) -> None: ...
+def mock_event(
+ ax: Axes,
+ button: MouseButton | int | Literal["up", "down"] | None = ...,
+ xdata: float = ...,
+ ydata: float = ...,
+ key: str | None = ...,
+ step: int = ...,
+) -> Event: ...
+def do_event(
+ tool: AxesWidget,
+ etype: str,
+ button: MouseButton | int | Literal["up", "down"] | None = ...,
+ xdata: float = ...,
+ ydata: float = ...,
+ key: str | None = ...,
+ step: int = ...,
+) -> None: ...
+def click_and_drag(
+ tool: Widget,
+ start: tuple[float, float],
+ end: tuple[float, float],
+ key: str | None = ...,
+) -> None: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/__init__.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8cce4fe4558d30d6c71ec7a63e347a0fb9bf7ada
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/__init__.py
@@ -0,0 +1,10 @@
+from pathlib import Path
+
+
+# Check that the test directories exist.
+if not (Path(__file__).parent / 'baseline_images').exists():
+ raise OSError(
+ 'The baseline image directory does not exist. '
+ 'This is most likely because the test data is not installed. '
+ 'You may need to install matplotlib from source to get the '
+ 'test data.')
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/conftest.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..6afd566750e9de31bd05c37c6e60dde3d7172db7
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/conftest.py
@@ -0,0 +1,2 @@
+from matplotlib.testing.conftest import ( # noqa
+ mpl_test_settings, pytest_configure, pytest_unconfigure, pd, text_placeholders, xr)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_afm.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_afm.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5c6a83937cd68e3ae20a14d8babb9a99bd5a4f5
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_afm.py
@@ -0,0 +1,137 @@
+from io import BytesIO
+import pytest
+import logging
+
+from matplotlib import _afm
+from matplotlib import font_manager as fm
+
+
+# See note in afm.py re: use of comma as decimal separator in the
+# UnderlineThickness field and re: use of non-ASCII characters in the Notice
+# field.
+AFM_TEST_DATA = b"""StartFontMetrics 2.0
+Comment Comments are ignored.
+Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
+FontName MyFont-Bold
+EncodingScheme FontSpecific
+FullName My Font Bold
+FamilyName Test Fonts
+Weight Bold
+ItalicAngle 0.0
+IsFixedPitch false
+UnderlinePosition -100
+UnderlineThickness 56,789
+Version 001.000
+Notice Copyright \xa9 2017 No one.
+FontBBox 0 -321 1234 369
+StartCharMetrics 3
+C 0 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 42 ; WX 1141 ; N foo ; B 40 60 800 360 ;
+C 99 ; WX 583 ; N bar ; B 40 -10 543 210 ;
+EndCharMetrics
+EndFontMetrics
+"""
+
+
+def test_nonascii_str():
+ # This tests that we also decode bytes as utf-8 properly.
+ # Else, font files with non ascii characters fail to load.
+ inp_str = "привет"
+ byte_str = inp_str.encode("utf8")
+
+ ret = _afm._to_str(byte_str)
+ assert ret == inp_str
+
+
+def test_parse_header():
+ fh = BytesIO(AFM_TEST_DATA)
+ header = _afm._parse_header(fh)
+ assert header == {
+ b'StartFontMetrics': 2.0,
+ b'FontName': 'MyFont-Bold',
+ b'EncodingScheme': 'FontSpecific',
+ b'FullName': 'My Font Bold',
+ b'FamilyName': 'Test Fonts',
+ b'Weight': 'Bold',
+ b'ItalicAngle': 0.0,
+ b'IsFixedPitch': False,
+ b'UnderlinePosition': -100,
+ b'UnderlineThickness': 56.789,
+ b'Version': '001.000',
+ b'Notice': b'Copyright \xa9 2017 No one.',
+ b'FontBBox': [0, -321, 1234, 369],
+ b'StartCharMetrics': 3,
+ }
+
+
+def test_parse_char_metrics():
+ fh = BytesIO(AFM_TEST_DATA)
+ _afm._parse_header(fh) # position
+ metrics = _afm._parse_char_metrics(fh)
+ assert metrics == (
+ {0: (250.0, 'space', [0, 0, 0, 0]),
+ 42: (1141.0, 'foo', [40, 60, 800, 360]),
+ 99: (583.0, 'bar', [40, -10, 543, 210]),
+ },
+ {'space': (250.0, 'space', [0, 0, 0, 0]),
+ 'foo': (1141.0, 'foo', [40, 60, 800, 360]),
+ 'bar': (583.0, 'bar', [40, -10, 543, 210]),
+ })
+
+
+def test_get_familyname_guessed():
+ fh = BytesIO(AFM_TEST_DATA)
+ font = _afm.AFM(fh)
+ del font._header[b'FamilyName'] # remove FamilyName, so we have to guess
+ assert font.get_familyname() == 'My Font'
+
+
+def test_font_manager_weight_normalization():
+ font = _afm.AFM(BytesIO(
+ AFM_TEST_DATA.replace(b"Weight Bold\n", b"Weight Custom\n")))
+ assert fm.afmFontProperty("", font).weight == "normal"
+
+
+@pytest.mark.parametrize(
+ "afm_data",
+ [
+ b"""nope
+really nope""",
+ b"""StartFontMetrics 2.0
+Comment Comments are ignored.
+Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
+FontName MyFont-Bold
+EncodingScheme FontSpecific""",
+ ],
+)
+def test_bad_afm(afm_data):
+ fh = BytesIO(afm_data)
+ with pytest.raises(RuntimeError):
+ _afm._parse_header(fh)
+
+
+@pytest.mark.parametrize(
+ "afm_data",
+ [
+ b"""StartFontMetrics 2.0
+Comment Comments are ignored.
+Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
+Aardvark bob
+FontName MyFont-Bold
+EncodingScheme FontSpecific
+StartCharMetrics 3""",
+ b"""StartFontMetrics 2.0
+Comment Comments are ignored.
+Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
+ItalicAngle zero degrees
+FontName MyFont-Bold
+EncodingScheme FontSpecific
+StartCharMetrics 3""",
+ ],
+)
+def test_malformed_header(afm_data, caplog):
+ fh = BytesIO(afm_data)
+ with caplog.at_level(logging.ERROR):
+ _afm._parse_header(fh)
+
+ assert len(caplog.records) == 1
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_agg.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_agg.py
new file mode 100644
index 0000000000000000000000000000000000000000..59387793605a0fe0f0b609773cf4f0641a3f6191
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_agg.py
@@ -0,0 +1,340 @@
+import io
+
+import numpy as np
+from numpy.testing import assert_array_almost_equal
+from PIL import features, Image, TiffTags
+import pytest
+
+
+from matplotlib import (
+ collections, patheffects, pyplot as plt, transforms as mtransforms,
+ rcParams, rc_context)
+from matplotlib.backends.backend_agg import RendererAgg
+from matplotlib.figure import Figure
+from matplotlib.image import imread
+from matplotlib.path import Path
+from matplotlib.testing.decorators import image_comparison
+from matplotlib.transforms import IdentityTransform
+
+
+def test_repeated_save_with_alpha():
+ # We want an image which has a background color of bluish green, with an
+ # alpha of 0.25.
+
+ fig = Figure([1, 0.4])
+ fig.set_facecolor((0, 1, 0.4))
+ fig.patch.set_alpha(0.25)
+
+ # The target color is fig.patch.get_facecolor()
+
+ buf = io.BytesIO()
+
+ fig.savefig(buf,
+ facecolor=fig.get_facecolor(),
+ edgecolor='none')
+
+ # Save the figure again to check that the
+ # colors don't bleed from the previous renderer.
+ buf.seek(0)
+ fig.savefig(buf,
+ facecolor=fig.get_facecolor(),
+ edgecolor='none')
+
+ # Check the first pixel has the desired color & alpha
+ # (approx: 0, 1.0, 0.4, 0.25)
+ buf.seek(0)
+ assert_array_almost_equal(tuple(imread(buf)[0, 0]),
+ (0.0, 1.0, 0.4, 0.250),
+ decimal=3)
+
+
+def test_large_single_path_collection():
+ buff = io.BytesIO()
+
+ # Generates a too-large single path in a path collection that
+ # would cause a segfault if the draw_markers optimization is
+ # applied.
+ f, ax = plt.subplots()
+ collection = collections.PathCollection(
+ [Path([[-10, 5], [10, 5], [10, -5], [-10, -5], [-10, 5]])])
+ ax.add_artist(collection)
+ ax.set_xlim(10**-3, 1)
+ plt.savefig(buff)
+
+
+def test_marker_with_nan():
+ # This creates a marker with nans in it, which was segfaulting the
+ # Agg backend (see #3722)
+ fig, ax = plt.subplots(1)
+ steps = 1000
+ data = np.arange(steps)
+ ax.semilogx(data)
+ ax.fill_between(data, data*0.8, data*1.2)
+ buf = io.BytesIO()
+ fig.savefig(buf, format='png')
+
+
+def test_long_path():
+ buff = io.BytesIO()
+ fig = Figure()
+ ax = fig.subplots()
+ points = np.ones(100_000)
+ points[::2] *= -1
+ ax.plot(points)
+ fig.savefig(buff, format='png')
+
+
+@image_comparison(['agg_filter.png'], remove_text=True)
+def test_agg_filter():
+ def smooth1d(x, window_len):
+ # copied from https://scipy-cookbook.readthedocs.io/
+ s = np.r_[
+ 2*x[0] - x[window_len:1:-1], x, 2*x[-1] - x[-1:-window_len:-1]]
+ w = np.hanning(window_len)
+ y = np.convolve(w/w.sum(), s, mode='same')
+ return y[window_len-1:-window_len+1]
+
+ def smooth2d(A, sigma=3):
+ window_len = max(int(sigma), 3) * 2 + 1
+ A = np.apply_along_axis(smooth1d, 0, A, window_len)
+ A = np.apply_along_axis(smooth1d, 1, A, window_len)
+ return A
+
+ class BaseFilter:
+
+ def get_pad(self, dpi):
+ return 0
+
+ def process_image(self, padded_src, dpi):
+ raise NotImplementedError("Should be overridden by subclasses")
+
+ def __call__(self, im, dpi):
+ pad = self.get_pad(dpi)
+ padded_src = np.pad(im, [(pad, pad), (pad, pad), (0, 0)],
+ "constant")
+ tgt_image = self.process_image(padded_src, dpi)
+ return tgt_image, -pad, -pad
+
+ class OffsetFilter(BaseFilter):
+
+ def __init__(self, offsets=(0, 0)):
+ self.offsets = offsets
+
+ def get_pad(self, dpi):
+ return int(max(self.offsets) / 72 * dpi)
+
+ def process_image(self, padded_src, dpi):
+ ox, oy = self.offsets
+ a1 = np.roll(padded_src, int(ox / 72 * dpi), axis=1)
+ a2 = np.roll(a1, -int(oy / 72 * dpi), axis=0)
+ return a2
+
+ class GaussianFilter(BaseFilter):
+ """Simple Gaussian filter."""
+
+ def __init__(self, sigma, alpha=0.5, color=(0, 0, 0)):
+ self.sigma = sigma
+ self.alpha = alpha
+ self.color = color
+
+ def get_pad(self, dpi):
+ return int(self.sigma*3 / 72 * dpi)
+
+ def process_image(self, padded_src, dpi):
+ tgt_image = np.empty_like(padded_src)
+ tgt_image[:, :, :3] = self.color
+ tgt_image[:, :, 3] = smooth2d(padded_src[:, :, 3] * self.alpha,
+ self.sigma / 72 * dpi)
+ return tgt_image
+
+ class DropShadowFilter(BaseFilter):
+
+ def __init__(self, sigma, alpha=0.3, color=(0, 0, 0), offsets=(0, 0)):
+ self.gauss_filter = GaussianFilter(sigma, alpha, color)
+ self.offset_filter = OffsetFilter(offsets)
+
+ def get_pad(self, dpi):
+ return max(self.gauss_filter.get_pad(dpi),
+ self.offset_filter.get_pad(dpi))
+
+ def process_image(self, padded_src, dpi):
+ t1 = self.gauss_filter.process_image(padded_src, dpi)
+ t2 = self.offset_filter.process_image(t1, dpi)
+ return t2
+
+ fig, ax = plt.subplots()
+
+ # draw lines
+ line1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-",
+ mec="b", mfc="w", lw=5, mew=3, ms=10, label="Line 1")
+ line2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "ro-",
+ mec="r", mfc="w", lw=5, mew=3, ms=10, label="Line 1")
+
+ gauss = DropShadowFilter(4)
+
+ for line in [line1, line2]:
+
+ # draw shadows with same lines with slight offset.
+ xx = line.get_xdata()
+ yy = line.get_ydata()
+ shadow, = ax.plot(xx, yy)
+ shadow.update_from(line)
+
+ # offset transform
+ transform = mtransforms.offset_copy(
+ line.get_transform(), fig, x=4.0, y=-6.0, units='points')
+ shadow.set_transform(transform)
+
+ # adjust zorder of the shadow lines so that it is drawn below the
+ # original lines
+ shadow.set_zorder(line.get_zorder() - 0.5)
+ shadow.set_agg_filter(gauss)
+ shadow.set_rasterized(True) # to support mixed-mode renderers
+
+ ax.set_xlim(0., 1.)
+ ax.set_ylim(0., 1.)
+
+ ax.xaxis.set_visible(False)
+ ax.yaxis.set_visible(False)
+
+
+def test_too_large_image():
+ fig = plt.figure(figsize=(300, 2**25))
+ buff = io.BytesIO()
+ with pytest.raises(ValueError):
+ fig.savefig(buff)
+
+
+def test_chunksize():
+ x = range(200)
+
+ # Test without chunksize
+ fig, ax = plt.subplots()
+ ax.plot(x, np.sin(x))
+ fig.canvas.draw()
+
+ # Test with chunksize
+ fig, ax = plt.subplots()
+ rcParams['agg.path.chunksize'] = 105
+ ax.plot(x, np.sin(x))
+ fig.canvas.draw()
+
+
+@pytest.mark.backend('Agg')
+def test_jpeg_dpi():
+ # Check that dpi is set correctly in jpg files.
+ plt.plot([0, 1, 2], [0, 1, 0])
+ buf = io.BytesIO()
+ plt.savefig(buf, format="jpg", dpi=200)
+ im = Image.open(buf)
+ assert im.info['dpi'] == (200, 200)
+
+
+def test_pil_kwargs_png():
+ from PIL.PngImagePlugin import PngInfo
+ buf = io.BytesIO()
+ pnginfo = PngInfo()
+ pnginfo.add_text("Software", "test")
+ plt.figure().savefig(buf, format="png", pil_kwargs={"pnginfo": pnginfo})
+ im = Image.open(buf)
+ assert im.info["Software"] == "test"
+
+
+def test_pil_kwargs_tiff():
+ buf = io.BytesIO()
+ pil_kwargs = {"description": "test image"}
+ plt.figure().savefig(buf, format="tiff", pil_kwargs=pil_kwargs)
+ im = Image.open(buf)
+ tags = {TiffTags.TAGS_V2[k].name: v for k, v in im.tag_v2.items()}
+ assert tags["ImageDescription"] == "test image"
+
+
+@pytest.mark.skipif(not features.check("webp"), reason="WebP support not available")
+def test_pil_kwargs_webp():
+ plt.plot([0, 1, 2], [0, 1, 0])
+ buf_small = io.BytesIO()
+ pil_kwargs_low = {"quality": 1}
+ plt.savefig(buf_small, format="webp", pil_kwargs=pil_kwargs_low)
+ assert len(pil_kwargs_low) == 1
+ buf_large = io.BytesIO()
+ pil_kwargs_high = {"quality": 100}
+ plt.savefig(buf_large, format="webp", pil_kwargs=pil_kwargs_high)
+ assert len(pil_kwargs_high) == 1
+ assert buf_large.getbuffer().nbytes > buf_small.getbuffer().nbytes
+
+
+@pytest.mark.skipif(not features.check("webp"), reason="WebP support not available")
+def test_webp_alpha():
+ plt.plot([0, 1, 2], [0, 1, 0])
+ buf = io.BytesIO()
+ plt.savefig(buf, format="webp", transparent=True)
+ im = Image.open(buf)
+ assert im.mode == "RGBA"
+
+
+def test_draw_path_collection_error_handling():
+ fig, ax = plt.subplots()
+ ax.scatter([1], [1]).set_paths(Path([(0, 1), (2, 3)]))
+ with pytest.raises(TypeError):
+ fig.canvas.draw()
+
+
+def test_chunksize_fails():
+ # NOTE: This test covers multiple independent test scenarios in a single
+ # function, because each scenario uses ~2GB of memory and we don't
+ # want parallel test executors to accidentally run multiple of these
+ # at the same time.
+
+ N = 100_000
+ dpi = 500
+ w = 5*dpi
+ h = 6*dpi
+
+ # make a Path that spans the whole w-h rectangle
+ x = np.linspace(0, w, N)
+ y = np.ones(N) * h
+ y[::2] = 0
+ path = Path(np.vstack((x, y)).T)
+ # effectively disable path simplification (but leaving it "on")
+ path.simplify_threshold = 0
+
+ # setup the minimal GraphicsContext to draw a Path
+ ra = RendererAgg(w, h, dpi)
+ gc = ra.new_gc()
+ gc.set_linewidth(1)
+ gc.set_foreground('r')
+
+ gc.set_hatch('/')
+ with pytest.raises(OverflowError, match='cannot split hatched path'):
+ ra.draw_path(gc, path, IdentityTransform())
+ gc.set_hatch(None)
+
+ with pytest.raises(OverflowError, match='cannot split filled path'):
+ ra.draw_path(gc, path, IdentityTransform(), (1, 0, 0))
+
+ # Set to zero to disable, currently defaults to 0, but let's be sure.
+ with rc_context({'agg.path.chunksize': 0}):
+ with pytest.raises(OverflowError, match='Please set'):
+ ra.draw_path(gc, path, IdentityTransform())
+
+ # Set big enough that we do not try to chunk.
+ with rc_context({'agg.path.chunksize': 1_000_000}):
+ with pytest.raises(OverflowError, match='Please reduce'):
+ ra.draw_path(gc, path, IdentityTransform())
+
+ # Small enough we will try to chunk, but big enough we will fail to render.
+ with rc_context({'agg.path.chunksize': 90_000}):
+ with pytest.raises(OverflowError, match='Please reduce'):
+ ra.draw_path(gc, path, IdentityTransform())
+
+ path.should_simplify = False
+ with pytest.raises(OverflowError, match="should_simplify is False"):
+ ra.draw_path(gc, path, IdentityTransform())
+
+
+def test_non_tuple_rgbaface():
+ # This passes rgbaFace as a ndarray to draw_path.
+ fig = plt.figure()
+ fig.add_subplot(projection="3d").scatter(
+ [0, 1, 2], [0, 1, 2], path_effects=[patheffects.Stroke(linewidth=4)])
+ fig.canvas.draw()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_agg_filter.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_agg_filter.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc8cff6858ae52a85028b4f8b3266b4d1880f687
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_agg_filter.py
@@ -0,0 +1,33 @@
+import numpy as np
+
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import image_comparison
+
+
+@image_comparison(baseline_images=['agg_filter_alpha'],
+ extensions=['png', 'pdf'])
+def test_agg_filter_alpha():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ ax = plt.axes()
+ x, y = np.mgrid[0:7, 0:8]
+ data = x**2 - y**2
+ mesh = ax.pcolormesh(data, cmap='Reds', zorder=5)
+
+ def manual_alpha(im, dpi):
+ im[:, :, 3] *= 0.6
+ print('CALLED')
+ return im, 0, 0
+
+ # Note: Doing alpha like this is not the same as setting alpha on
+ # the mesh itself. Currently meshes are drawn as independent patches,
+ # and we see fine borders around the blocks of color. See the SO
+ # question for an example: https://stackoverflow.com/q/20678817/
+ mesh.set_agg_filter(manual_alpha)
+
+ # Currently we must enable rasterization for this to have an effect in
+ # the PDF backend.
+ mesh.set_rasterized(True)
+
+ ax.plot([0, 4, 7], [1, 3, 8])
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_animation.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_animation.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3382f662c73cb80cf93f949df533b5812bb3981
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_animation.py
@@ -0,0 +1,571 @@
+import os
+from pathlib import Path
+import platform
+import re
+import shutil
+import subprocess
+import sys
+import weakref
+
+import numpy as np
+import pytest
+
+import matplotlib as mpl
+from matplotlib import pyplot as plt
+from matplotlib import animation
+from matplotlib.animation import PillowWriter
+from matplotlib.testing.decorators import check_figures_equal
+
+
+@pytest.fixture()
+def anim(request):
+ """Create a simple animation (with options)."""
+ fig, ax = plt.subplots()
+ line, = ax.plot([], [])
+
+ ax.set_xlim(0, 10)
+ ax.set_ylim(-1, 1)
+
+ def init():
+ line.set_data([], [])
+ return line,
+
+ def animate(i):
+ x = np.linspace(0, 10, 100)
+ y = np.sin(x + i)
+ line.set_data(x, y)
+ return line,
+
+ # "klass" can be passed to determine the class returned by the fixture
+ kwargs = dict(getattr(request, 'param', {})) # make a copy
+ klass = kwargs.pop('klass', animation.FuncAnimation)
+ if 'frames' not in kwargs:
+ kwargs['frames'] = 5
+ return klass(fig=fig, func=animate, init_func=init, **kwargs)
+
+
+class NullMovieWriter(animation.AbstractMovieWriter):
+ """
+ A minimal MovieWriter. It doesn't actually write anything.
+ It just saves the arguments that were given to the setup() and
+ grab_frame() methods as attributes, and counts how many times
+ grab_frame() is called.
+
+ This class doesn't have an __init__ method with the appropriate
+ signature, and it doesn't define an isAvailable() method, so
+ it cannot be added to the 'writers' registry.
+ """
+
+ def setup(self, fig, outfile, dpi, *args):
+ self.fig = fig
+ self.outfile = outfile
+ self.dpi = dpi
+ self.args = args
+ self._count = 0
+
+ def grab_frame(self, **savefig_kwargs):
+ from matplotlib.animation import _validate_grabframe_kwargs
+ _validate_grabframe_kwargs(savefig_kwargs)
+ self.savefig_kwargs = savefig_kwargs
+ self._count += 1
+
+ def finish(self):
+ pass
+
+
+def test_null_movie_writer(anim):
+ # Test running an animation with NullMovieWriter.
+ plt.rcParams["savefig.facecolor"] = "auto"
+ filename = "unused.null"
+ dpi = 50
+ savefig_kwargs = dict(foo=0)
+ writer = NullMovieWriter()
+
+ anim.save(filename, dpi=dpi, writer=writer,
+ savefig_kwargs=savefig_kwargs)
+
+ assert writer.fig == plt.figure(1) # The figure used by anim fixture
+ assert writer.outfile == filename
+ assert writer.dpi == dpi
+ assert writer.args == ()
+ # we enrich the savefig kwargs to ensure we composite transparent
+ # output to an opaque background
+ for k, v in savefig_kwargs.items():
+ assert writer.savefig_kwargs[k] == v
+ assert writer._count == anim._save_count
+
+
+@pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim'])
+def test_animation_delete(anim):
+ if platform.python_implementation() == 'PyPy':
+ # Something in the test setup fixture lingers around into the test and
+ # breaks pytest.warns on PyPy. This garbage collection fixes it.
+ # https://foss.heptapod.net/pypy/pypy/-/issues/3536
+ np.testing.break_cycles()
+ anim = animation.FuncAnimation(**anim)
+ with pytest.warns(Warning, match='Animation was deleted'):
+ del anim
+ np.testing.break_cycles()
+
+
+def test_movie_writer_dpi_default():
+ class DummyMovieWriter(animation.MovieWriter):
+ def _run(self):
+ pass
+
+ # Test setting up movie writer with figure.dpi default.
+ fig = plt.figure()
+
+ filename = "unused.null"
+ fps = 5
+ codec = "unused"
+ bitrate = 1
+ extra_args = ["unused"]
+
+ writer = DummyMovieWriter(fps, codec, bitrate, extra_args)
+ writer.setup(fig, filename)
+ assert writer.dpi == fig.dpi
+
+
+@animation.writers.register('null')
+class RegisteredNullMovieWriter(NullMovieWriter):
+
+ # To be able to add NullMovieWriter to the 'writers' registry,
+ # we must define an __init__ method with a specific signature,
+ # and we must define the class method isAvailable().
+ # (These methods are not actually required to use an instance
+ # of this class as the 'writer' argument of Animation.save().)
+
+ def __init__(self, fps=None, codec=None, bitrate=None,
+ extra_args=None, metadata=None):
+ pass
+
+ @classmethod
+ def isAvailable(cls):
+ return True
+
+
+WRITER_OUTPUT = [
+ ('ffmpeg', 'movie.mp4'),
+ ('ffmpeg_file', 'movie.mp4'),
+ ('imagemagick', 'movie.gif'),
+ ('imagemagick_file', 'movie.gif'),
+ ('pillow', 'movie.gif'),
+ ('html', 'movie.html'),
+ ('null', 'movie.null')
+]
+
+
+def gen_writers():
+ for writer, output in WRITER_OUTPUT:
+ if not animation.writers.is_available(writer):
+ mark = pytest.mark.skip(
+ f"writer '{writer}' not available on this system")
+ yield pytest.param(writer, None, output, marks=[mark])
+ yield pytest.param(writer, None, Path(output), marks=[mark])
+ continue
+
+ writer_class = animation.writers[writer]
+ for frame_format in getattr(writer_class, 'supported_formats', [None]):
+ yield writer, frame_format, output
+ yield writer, frame_format, Path(output)
+
+
+# Smoke test for saving animations. In the future, we should probably
+# design more sophisticated tests which compare resulting frames a-la
+# matplotlib.testing.image_comparison
+@pytest.mark.parametrize('writer, frame_format, output', gen_writers())
+@pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim'])
+def test_save_animation_smoketest(tmpdir, writer, frame_format, output, anim):
+ if frame_format is not None:
+ plt.rcParams["animation.frame_format"] = frame_format
+ anim = animation.FuncAnimation(**anim)
+ dpi = None
+ codec = None
+ if writer == 'ffmpeg':
+ # Issue #8253
+ anim._fig.set_size_inches((10.85, 9.21))
+ dpi = 100.
+ codec = 'h264'
+
+ # Use temporary directory for the file-based writers, which produce a file
+ # per frame with known names.
+ with tmpdir.as_cwd():
+ anim.save(output, fps=30, writer=writer, bitrate=500, dpi=dpi,
+ codec=codec)
+
+ del anim
+
+
+@pytest.mark.parametrize('writer, frame_format, output', gen_writers())
+def test_grabframe(tmpdir, writer, frame_format, output):
+ WriterClass = animation.writers[writer]
+
+ if frame_format is not None:
+ plt.rcParams["animation.frame_format"] = frame_format
+
+ fig, ax = plt.subplots()
+
+ dpi = None
+ codec = None
+ if writer == 'ffmpeg':
+ # Issue #8253
+ fig.set_size_inches((10.85, 9.21))
+ dpi = 100.
+ codec = 'h264'
+
+ test_writer = WriterClass()
+ # Use temporary directory for the file-based writers, which produce a file
+ # per frame with known names.
+ with tmpdir.as_cwd():
+ with test_writer.saving(fig, output, dpi):
+ # smoke test it works
+ test_writer.grab_frame()
+ for k in {'dpi', 'bbox_inches', 'format'}:
+ with pytest.raises(
+ TypeError,
+ match=f"grab_frame got an unexpected keyword argument {k!r}"
+ ):
+ test_writer.grab_frame(**{k: object()})
+
+
+@pytest.mark.parametrize('writer', [
+ pytest.param(
+ 'ffmpeg', marks=pytest.mark.skipif(
+ not animation.FFMpegWriter.isAvailable(),
+ reason='Requires FFMpeg')),
+ pytest.param(
+ 'imagemagick', marks=pytest.mark.skipif(
+ not animation.ImageMagickWriter.isAvailable(),
+ reason='Requires ImageMagick')),
+])
+@pytest.mark.parametrize('html, want', [
+ ('none', None),
+ ('html5', ' 0
+ mpl.rcParams['animation.ffmpeg_path'] = "not_available_ever_xxxx"
+ assert not animation.writers.is_available("ffmpeg")
+ # something guaranteed to be available in path and exits immediately
+ bin = "true" if sys.platform != 'win32' else "where"
+ mpl.rcParams['animation.ffmpeg_path'] = bin
+ assert animation.writers.is_available("ffmpeg")
+
+
+@pytest.mark.parametrize(
+ "method_name",
+ [pytest.param("to_html5_video", marks=pytest.mark.skipif(
+ not animation.writers.is_available(mpl.rcParams["animation.writer"]),
+ reason="animation writer not installed")),
+ "to_jshtml"])
+@pytest.mark.parametrize('anim', [dict(frames=1)], indirect=['anim'])
+def test_embed_limit(method_name, caplog, tmpdir, anim):
+ caplog.set_level("WARNING")
+ with tmpdir.as_cwd():
+ with mpl.rc_context({"animation.embed_limit": 1e-6}): # ~1 byte.
+ getattr(anim, method_name)()
+ assert len(caplog.records) == 1
+ record, = caplog.records
+ assert (record.name == "matplotlib.animation"
+ and record.levelname == "WARNING")
+
+
+@pytest.mark.parametrize(
+ "method_name",
+ [pytest.param("to_html5_video", marks=pytest.mark.skipif(
+ not animation.writers.is_available(mpl.rcParams["animation.writer"]),
+ reason="animation writer not installed")),
+ "to_jshtml"])
+@pytest.mark.parametrize('anim', [dict(frames=1)], indirect=['anim'])
+def test_cleanup_temporaries(method_name, tmpdir, anim):
+ with tmpdir.as_cwd():
+ getattr(anim, method_name)()
+ assert list(Path(str(tmpdir)).iterdir()) == []
+
+
+@pytest.mark.skipif(shutil.which("/bin/sh") is None, reason="requires a POSIX OS")
+def test_failing_ffmpeg(tmpdir, monkeypatch, anim):
+ """
+ Test that we correctly raise a CalledProcessError when ffmpeg fails.
+
+ To do so, mock ffmpeg using a simple executable shell script that
+ succeeds when called with no arguments (so that it gets registered by
+ `isAvailable`), but fails otherwise, and add it to the $PATH.
+ """
+ with tmpdir.as_cwd():
+ monkeypatch.setenv("PATH", ".:" + os.environ["PATH"])
+ exe_path = Path(str(tmpdir), "ffmpeg")
+ exe_path.write_bytes(b"#!/bin/sh\n[[ $@ -eq 0 ]]\n")
+ os.chmod(exe_path, 0o755)
+ with pytest.raises(subprocess.CalledProcessError):
+ anim.save("test.mpeg")
+
+
+@pytest.mark.parametrize("cache_frame_data", [False, True])
+def test_funcanimation_cache_frame_data(cache_frame_data):
+ fig, ax = plt.subplots()
+ line, = ax.plot([], [])
+
+ class Frame(dict):
+ # this subclassing enables to use weakref.ref()
+ pass
+
+ def init():
+ line.set_data([], [])
+ return line,
+
+ def animate(frame):
+ line.set_data(frame['x'], frame['y'])
+ return line,
+
+ frames_generated = []
+
+ def frames_generator():
+ for _ in range(5):
+ x = np.linspace(0, 10, 100)
+ y = np.random.rand(100)
+
+ frame = Frame(x=x, y=y)
+
+ # collect weak references to frames
+ # to validate their references later
+ frames_generated.append(weakref.ref(frame))
+
+ yield frame
+
+ MAX_FRAMES = 100
+ anim = animation.FuncAnimation(fig, animate, init_func=init,
+ frames=frames_generator,
+ cache_frame_data=cache_frame_data,
+ save_count=MAX_FRAMES)
+
+ writer = NullMovieWriter()
+ anim.save('unused.null', writer=writer)
+ assert len(frames_generated) == 5
+ np.testing.break_cycles()
+ for f in frames_generated:
+ # If cache_frame_data is True, then the weakref should be alive;
+ # if cache_frame_data is False, then the weakref should be dead (None).
+ assert (f() is None) != cache_frame_data
+
+
+@pytest.mark.parametrize('return_value', [
+ # User forgot to return (returns None).
+ None,
+ # User returned a string.
+ 'string',
+ # User returned an int.
+ 1,
+ # User returns a sequence of other objects, e.g., string instead of Artist.
+ ('string', ),
+ # User forgot to return a sequence (handled in `animate` below.)
+ 'artist',
+])
+def test_draw_frame(return_value):
+ # test _draw_frame method
+
+ fig, ax = plt.subplots()
+ line, = ax.plot([])
+
+ def animate(i):
+ # general update func
+ line.set_data([0, 1], [0, i])
+ if return_value == 'artist':
+ # *not* a sequence
+ return line
+ else:
+ return return_value
+
+ with pytest.raises(RuntimeError):
+ animation.FuncAnimation(
+ fig, animate, blit=True, cache_frame_data=False
+ )
+
+
+def test_exhausted_animation(tmpdir):
+ fig, ax = plt.subplots()
+
+ def update(frame):
+ return []
+
+ anim = animation.FuncAnimation(
+ fig, update, frames=iter(range(10)), repeat=False,
+ cache_frame_data=False
+ )
+
+ with tmpdir.as_cwd():
+ anim.save("test.gif", writer='pillow')
+
+ with pytest.warns(UserWarning, match="exhausted"):
+ anim._start()
+
+
+def test_no_frame_warning(tmpdir):
+ fig, ax = plt.subplots()
+
+ def update(frame):
+ return []
+
+ anim = animation.FuncAnimation(
+ fig, update, frames=[], repeat=False,
+ cache_frame_data=False
+ )
+
+ with pytest.warns(UserWarning, match="exhausted"):
+ anim._start()
+
+
+@check_figures_equal(extensions=["png"])
+def test_animation_frame(tmpdir, fig_test, fig_ref):
+ # Test the expected image after iterating through a few frames
+ # we save the animation to get the iteration because we are not
+ # in an interactive framework.
+ ax = fig_test.add_subplot()
+ ax.set_xlim(0, 2 * np.pi)
+ ax.set_ylim(-1, 1)
+ x = np.linspace(0, 2 * np.pi, 100)
+ line, = ax.plot([], [])
+
+ def init():
+ line.set_data([], [])
+ return line,
+
+ def animate(i):
+ line.set_data(x, np.sin(x + i / 100))
+ return line,
+
+ anim = animation.FuncAnimation(
+ fig_test, animate, init_func=init, frames=5,
+ blit=True, repeat=False)
+ with tmpdir.as_cwd():
+ anim.save("test.gif")
+
+ # Reference figure without animation
+ ax = fig_ref.add_subplot()
+ ax.set_xlim(0, 2 * np.pi)
+ ax.set_ylim(-1, 1)
+
+ # 5th frame's data
+ ax.plot(x, np.sin(x + 4 / 100))
+
+
+@pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim'])
+def test_save_count_override_warnings_has_length(anim):
+
+ save_count = 5
+ frames = list(range(2))
+ match_target = (
+ f'You passed in an explicit {save_count=} '
+ "which is being ignored in favor of "
+ f"{len(frames)=}."
+ )
+
+ with pytest.warns(UserWarning, match=re.escape(match_target)):
+ anim = animation.FuncAnimation(
+ **{**anim, 'frames': frames, 'save_count': save_count}
+ )
+ assert anim._save_count == len(frames)
+ anim._init_draw()
+
+
+@pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim'])
+def test_save_count_override_warnings_scaler(anim):
+ save_count = 5
+ frames = 7
+ match_target = (
+ f'You passed in an explicit {save_count=} ' +
+ "which is being ignored in favor of " +
+ f"{frames=}."
+ )
+
+ with pytest.warns(UserWarning, match=re.escape(match_target)):
+ anim = animation.FuncAnimation(
+ **{**anim, 'frames': frames, 'save_count': save_count}
+ )
+
+ assert anim._save_count == frames
+ anim._init_draw()
+
+
+@pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim'])
+def test_disable_cache_warning(anim):
+ cache_frame_data = True
+ frames = iter(range(5))
+ match_target = (
+ f"{frames=!r} which we can infer the length of, "
+ "did not pass an explicit *save_count* "
+ f"and passed {cache_frame_data=}. To avoid a possibly "
+ "unbounded cache, frame data caching has been disabled. "
+ "To suppress this warning either pass "
+ "`cache_frame_data=False` or `save_count=MAX_FRAMES`."
+ )
+ with pytest.warns(UserWarning, match=re.escape(match_target)):
+ anim = animation.FuncAnimation(
+ **{**anim, 'cache_frame_data': cache_frame_data, 'frames': frames}
+ )
+ assert anim._cache_frame_data is False
+ anim._init_draw()
+
+
+def test_movie_writer_invalid_path(anim):
+ if sys.platform == "win32":
+ match_str = r"\[WinError 3] .*'\\\\foo\\\\bar\\\\aardvark'"
+ else:
+ match_str = r"\[Errno 2] .*'/foo"
+ with pytest.raises(FileNotFoundError, match=match_str):
+ anim.save("/foo/bar/aardvark/thiscannotreallyexist.mp4",
+ writer=animation.FFMpegFileWriter())
+
+
+def test_animation_with_transparency():
+ """Test animation exhaustion with transparency using PillowWriter directly"""
+ fig, ax = plt.subplots()
+ rect = plt.Rectangle((0, 0), 1, 1, color='red', alpha=0.5)
+ ax.add_patch(rect)
+ ax.set_xlim(0, 1)
+ ax.set_ylim(0, 1)
+
+ writer = PillowWriter(fps=30)
+ writer.setup(fig, 'unused.gif', dpi=100)
+ writer.grab_frame(transparent=True)
+ frame = writer._frames[-1]
+ # Check that the alpha channel is not 255, so frame has transparency
+ assert frame.getextrema()[3][0] < 255
+ plt.close(fig)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_api.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..81b7f9cdc96c1dc85f212b6ad8a6605306451a3c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_api.py
@@ -0,0 +1,157 @@
+from __future__ import annotations
+
+from collections.abc import Callable
+import re
+import typing
+from typing import Any, TypeVar
+
+import numpy as np
+import pytest
+
+import matplotlib as mpl
+from matplotlib import _api
+
+
+if typing.TYPE_CHECKING:
+ from typing_extensions import Self
+
+T = TypeVar('T')
+
+
+@pytest.mark.parametrize('target,shape_repr,test_shape',
+ [((None, ), "(N,)", (1, 3)),
+ ((None, 3), "(N, 3)", (1,)),
+ ((None, 3), "(N, 3)", (1, 2)),
+ ((1, 5), "(1, 5)", (1, 9)),
+ ((None, 2, None), "(M, 2, N)", (1, 3, 1))
+ ])
+def test_check_shape(target: tuple[int | None, ...],
+ shape_repr: str,
+ test_shape: tuple[int, ...]) -> None:
+ error_pattern = "^" + re.escape(
+ f"'aardvark' must be {len(target)}D with shape {shape_repr}, but your input "
+ f"has shape {test_shape}")
+ data = np.zeros(test_shape)
+ with pytest.raises(ValueError, match=error_pattern):
+ _api.check_shape(target, aardvark=data)
+
+
+def test_classproperty_deprecation() -> None:
+ class A:
+ @_api.deprecated("0.0.0")
+ @_api.classproperty
+ def f(cls: Self) -> None:
+ pass
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ A.f
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ a = A()
+ a.f
+
+
+def test_warn_deprecated():
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match=r'foo was deprecated in Matplotlib 3\.10 and will be '
+ r'removed in 3\.12\.'):
+ _api.warn_deprecated('3.10', name='foo')
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match=r'The foo class was deprecated in Matplotlib 3\.10 and '
+ r'will be removed in 3\.12\.'):
+ _api.warn_deprecated('3.10', name='foo', obj_type='class')
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match=r'foo was deprecated in Matplotlib 3\.10 and will be '
+ r'removed in 3\.12\. Use bar instead\.'):
+ _api.warn_deprecated('3.10', name='foo', alternative='bar')
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match=r'foo was deprecated in Matplotlib 3\.10 and will be '
+ r'removed in 3\.12\. More information\.'):
+ _api.warn_deprecated('3.10', name='foo', addendum='More information.')
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match=r'foo was deprecated in Matplotlib 3\.10 and will be '
+ r'removed in 4\.0\.'):
+ _api.warn_deprecated('3.10', name='foo', removal='4.0')
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match=r'foo was deprecated in Matplotlib 3\.10\.'):
+ _api.warn_deprecated('3.10', name='foo', removal=False)
+ with pytest.warns(PendingDeprecationWarning,
+ match=r'foo will be deprecated in a future version'):
+ _api.warn_deprecated('3.10', name='foo', pending=True)
+ with pytest.raises(ValueError, match=r'cannot have a scheduled removal'):
+ _api.warn_deprecated('3.10', name='foo', pending=True, removal='3.12')
+ with pytest.warns(mpl.MatplotlibDeprecationWarning, match=r'Complete replacement'):
+ _api.warn_deprecated('3.10', message='Complete replacement', name='foo',
+ alternative='bar', addendum='More information.',
+ obj_type='class', removal='4.0')
+
+
+def test_deprecate_privatize_attribute() -> None:
+ class C:
+ def __init__(self) -> None: self._attr = 1
+ def _meth(self, arg: T) -> T: return arg
+ attr: int = _api.deprecate_privatize_attribute("0.0")
+ meth: Callable = _api.deprecate_privatize_attribute("0.0")
+
+ c = C()
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ assert c.attr == 1
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ c.attr = 2
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ assert c.attr == 2
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ assert c.meth(42) == 42
+
+
+def test_delete_parameter() -> None:
+ @_api.delete_parameter("3.0", "foo")
+ def func1(foo: Any = None) -> None:
+ pass
+
+ @_api.delete_parameter("3.0", "foo")
+ def func2(**kwargs: Any) -> None:
+ pass
+
+ for func in [func1, func2]: # type: ignore[list-item]
+ func() # No warning.
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ func(foo="bar")
+
+ def pyplot_wrapper(foo: Any = _api.deprecation._deprecated_parameter) -> None:
+ func1(foo)
+
+ pyplot_wrapper() # No warning.
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ func(foo="bar")
+
+
+def test_make_keyword_only() -> None:
+ @_api.make_keyword_only("3.0", "arg")
+ def func(pre: Any, arg: Any, post: Any = None) -> None:
+ pass
+
+ func(1, arg=2) # Check that no warning is emitted.
+
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ func(1, 2)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ func(1, 2, 3)
+
+
+def test_deprecation_alternative() -> None:
+ alternative = "`.f1`, `f2`, `f3(x) <.f3>` or `f4(x)`"
+ @_api.deprecated("1", alternative=alternative)
+ def f() -> None:
+ pass
+ if f.__doc__ is None:
+ pytest.skip('Documentation is disabled')
+ assert alternative in f.__doc__
+
+
+def test_empty_check_in_list() -> None:
+ with pytest.raises(TypeError, match="No argument to check!"):
+ _api.check_in_list(["a"])
+
+
+def test_check_in_list_numpy() -> None:
+ with pytest.raises(ValueError, match=r"array\(5\) is not a valid value"):
+ _api.check_in_list(['a', 'b'], value=np.array(5))
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_arrow_patches.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_arrow_patches.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2b6d4fa80860670b09c19796de6de7b3eb37b3a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_arrow_patches.py
@@ -0,0 +1,179 @@
+import pytest
+import platform
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.patches as mpatches
+
+
+def draw_arrow(ax, t, r):
+ ax.annotate('', xy=(0.5, 0.5 + r), xytext=(0.5, 0.5), size=30,
+ arrowprops=dict(arrowstyle=t,
+ fc="b", ec='k'))
+
+
+@image_comparison(['fancyarrow_test_image.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.012)
+def test_fancyarrow():
+ # Added 0 to test division by zero error described in issue 3930
+ r = [0.4, 0.3, 0.2, 0.1, 0]
+ t = ["fancy", "simple", mpatches.ArrowStyle.Fancy()]
+
+ fig, axs = plt.subplots(len(t), len(r), squeeze=False,
+ figsize=(8, 4.5), subplot_kw=dict(aspect=1))
+
+ for i_r, r1 in enumerate(r):
+ for i_t, t1 in enumerate(t):
+ ax = axs[i_t, i_r]
+ draw_arrow(ax, t1, r1)
+ ax.tick_params(labelleft=False, labelbottom=False)
+
+
+@image_comparison(['boxarrow_test_image.png'])
+def test_boxarrow():
+
+ styles = mpatches.BoxStyle.get_styles()
+
+ n = len(styles)
+ spacing = 1.2
+
+ figheight = (n * spacing + .5)
+ fig = plt.figure(figsize=(4 / 1.5, figheight / 1.5))
+
+ fontsize = 0.3 * 72
+
+ for i, stylename in enumerate(sorted(styles)):
+ fig.text(0.5, ((n - i) * spacing - 0.5)/figheight, stylename,
+ ha="center",
+ size=fontsize,
+ transform=fig.transFigure,
+ bbox=dict(boxstyle=stylename, fc="w", ec="k"))
+
+
+def __prepare_fancyarrow_dpi_cor_test():
+ """
+ Convenience function that prepares and returns a FancyArrowPatch. It aims
+ at being used to test that the size of the arrow head does not depend on
+ the DPI value of the exported picture.
+
+ NB: this function *is not* a test in itself!
+ """
+ fig2 = plt.figure("fancyarrow_dpi_cor_test", figsize=(4, 3), dpi=50)
+ ax = fig2.add_subplot()
+ ax.set_xlim([0, 1])
+ ax.set_ylim([0, 1])
+ ax.add_patch(mpatches.FancyArrowPatch(posA=(0.3, 0.4), posB=(0.8, 0.6),
+ lw=3, arrowstyle='->',
+ mutation_scale=100))
+ return fig2
+
+
+@image_comparison(['fancyarrow_dpi_cor_100dpi.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.02,
+ savefig_kwarg=dict(dpi=100))
+def test_fancyarrow_dpi_cor_100dpi():
+ """
+ Check the export of a FancyArrowPatch @ 100 DPI. FancyArrowPatch is
+ instantiated through a dedicated function because another similar test
+ checks a similar export but with a different DPI value.
+
+ Remark: test only a rasterized format.
+ """
+
+ __prepare_fancyarrow_dpi_cor_test()
+
+
+@image_comparison(['fancyarrow_dpi_cor_200dpi.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.02,
+ savefig_kwarg=dict(dpi=200))
+def test_fancyarrow_dpi_cor_200dpi():
+ """
+ As test_fancyarrow_dpi_cor_100dpi, but exports @ 200 DPI. The relative size
+ of the arrow head should be the same.
+ """
+
+ __prepare_fancyarrow_dpi_cor_test()
+
+
+@image_comparison(['fancyarrow_dash.png'], remove_text=True, style='default')
+def test_fancyarrow_dash():
+ fig, ax = plt.subplots()
+ e = mpatches.FancyArrowPatch((0, 0), (0.5, 0.5),
+ arrowstyle='-|>',
+ connectionstyle='angle3,angleA=0,angleB=90',
+ mutation_scale=10.0,
+ linewidth=2,
+ linestyle='dashed',
+ color='k')
+ e2 = mpatches.FancyArrowPatch((0, 0), (0.5, 0.5),
+ arrowstyle='-|>',
+ connectionstyle='angle3',
+ mutation_scale=10.0,
+ linewidth=2,
+ linestyle='dotted',
+ color='k')
+ ax.add_patch(e)
+ ax.add_patch(e2)
+
+
+@image_comparison(['arrow_styles.png'], style='mpl20', remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.02)
+def test_arrow_styles():
+ styles = mpatches.ArrowStyle.get_styles()
+
+ n = len(styles)
+ fig, ax = plt.subplots(figsize=(8, 8))
+ ax.set_xlim(0, 1)
+ ax.set_ylim(-1, n)
+ fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
+
+ for i, stylename in enumerate(sorted(styles)):
+ patch = mpatches.FancyArrowPatch((0.1 + (i % 2)*0.05, i),
+ (0.45 + (i % 2)*0.05, i),
+ arrowstyle=stylename,
+ mutation_scale=25)
+ ax.add_patch(patch)
+
+ for i, stylename in enumerate([']-[', ']-', '-[', '|-|']):
+ style = stylename
+ if stylename[0] != '-':
+ style += ',angleA=ANGLE'
+ if stylename[-1] != '-':
+ style += ',angleB=ANGLE'
+
+ for j, angle in enumerate([-30, 60]):
+ arrowstyle = style.replace('ANGLE', str(angle))
+ patch = mpatches.FancyArrowPatch((0.55, 2*i + j), (0.9, 2*i + j),
+ arrowstyle=arrowstyle,
+ mutation_scale=25)
+ ax.add_patch(patch)
+
+
+@image_comparison(['connection_styles.png'], style='mpl20', remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.013)
+def test_connection_styles():
+ styles = mpatches.ConnectionStyle.get_styles()
+
+ n = len(styles)
+ fig, ax = plt.subplots(figsize=(6, 10))
+ ax.set_xlim(0, 1)
+ ax.set_ylim(-1, n)
+
+ for i, stylename in enumerate(sorted(styles)):
+ patch = mpatches.FancyArrowPatch((0.1, i), (0.8, i + 0.5),
+ arrowstyle="->",
+ connectionstyle=stylename,
+ mutation_scale=25)
+ ax.add_patch(patch)
+
+
+def test_invalid_intersection():
+ conn_style_1 = mpatches.ConnectionStyle.Angle3(angleA=20, angleB=200)
+ p1 = mpatches.FancyArrowPatch((.2, .2), (.5, .5),
+ connectionstyle=conn_style_1)
+ with pytest.raises(ValueError):
+ plt.gca().add_patch(p1)
+
+ conn_style_2 = mpatches.ConnectionStyle.Angle3(angleA=20, angleB=199.9)
+ p2 = mpatches.FancyArrowPatch((.2, .2), (.5, .5),
+ connectionstyle=conn_style_2)
+ plt.gca().add_patch(p2)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_artist.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_artist.py
new file mode 100644
index 0000000000000000000000000000000000000000..e75572d776ebf6c98cac0305a6993b35c30218b0
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_artist.py
@@ -0,0 +1,598 @@
+import io
+from itertools import chain
+
+import numpy as np
+
+import pytest
+
+import matplotlib.colors as mcolors
+import matplotlib.pyplot as plt
+import matplotlib.patches as mpatches
+import matplotlib.lines as mlines
+import matplotlib.path as mpath
+import matplotlib.transforms as mtransforms
+import matplotlib.collections as mcollections
+import matplotlib.artist as martist
+import matplotlib.backend_bases as mbackend_bases
+import matplotlib as mpl
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+
+
+def test_patch_transform_of_none():
+ # tests the behaviour of patches added to an Axes with various transform
+ # specifications
+
+ ax = plt.axes()
+ ax.set_xlim(1, 3)
+ ax.set_ylim(1, 3)
+
+ # Draw an ellipse over data coord (2, 2) by specifying device coords.
+ xy_data = (2, 2)
+ xy_pix = ax.transData.transform(xy_data)
+
+ # Not providing a transform of None puts the ellipse in data coordinates .
+ e = mpatches.Ellipse(xy_data, width=1, height=1, fc='yellow', alpha=0.5)
+ ax.add_patch(e)
+ assert e._transform == ax.transData
+
+ # Providing a transform of None puts the ellipse in device coordinates.
+ e = mpatches.Ellipse(xy_pix, width=120, height=120, fc='coral',
+ transform=None, alpha=0.5)
+ assert e.is_transform_set()
+ ax.add_patch(e)
+ assert isinstance(e._transform, mtransforms.IdentityTransform)
+
+ # Providing an IdentityTransform puts the ellipse in device coordinates.
+ e = mpatches.Ellipse(xy_pix, width=100, height=100,
+ transform=mtransforms.IdentityTransform(), alpha=0.5)
+ ax.add_patch(e)
+ assert isinstance(e._transform, mtransforms.IdentityTransform)
+
+ # Not providing a transform, and then subsequently "get_transform" should
+ # not mean that "is_transform_set".
+ e = mpatches.Ellipse(xy_pix, width=120, height=120, fc='coral',
+ alpha=0.5)
+ intermediate_transform = e.get_transform()
+ assert not e.is_transform_set()
+ ax.add_patch(e)
+ assert e.get_transform() != intermediate_transform
+ assert e.is_transform_set()
+ assert e._transform == ax.transData
+
+
+def test_collection_transform_of_none():
+ # tests the behaviour of collections added to an Axes with various
+ # transform specifications
+
+ ax = plt.axes()
+ ax.set_xlim(1, 3)
+ ax.set_ylim(1, 3)
+
+ # draw an ellipse over data coord (2, 2) by specifying device coords
+ xy_data = (2, 2)
+ xy_pix = ax.transData.transform(xy_data)
+
+ # not providing a transform of None puts the ellipse in data coordinates
+ e = mpatches.Ellipse(xy_data, width=1, height=1)
+ c = mcollections.PatchCollection([e], facecolor='yellow', alpha=0.5)
+ ax.add_collection(c)
+ # the collection should be in data coordinates
+ assert c.get_offset_transform() + c.get_transform() == ax.transData
+
+ # providing a transform of None puts the ellipse in device coordinates
+ e = mpatches.Ellipse(xy_pix, width=120, height=120)
+ c = mcollections.PatchCollection([e], facecolor='coral',
+ alpha=0.5)
+ c.set_transform(None)
+ ax.add_collection(c)
+ assert isinstance(c.get_transform(), mtransforms.IdentityTransform)
+
+ # providing an IdentityTransform puts the ellipse in device coordinates
+ e = mpatches.Ellipse(xy_pix, width=100, height=100)
+ c = mcollections.PatchCollection([e],
+ transform=mtransforms.IdentityTransform(),
+ alpha=0.5)
+ ax.add_collection(c)
+ assert isinstance(c.get_offset_transform(), mtransforms.IdentityTransform)
+
+
+@image_comparison(["clip_path_clipping"], remove_text=True)
+def test_clipping():
+ exterior = mpath.Path.unit_rectangle().deepcopy()
+ exterior.vertices *= 4
+ exterior.vertices -= 2
+ interior = mpath.Path.unit_circle().deepcopy()
+ interior.vertices = interior.vertices[::-1]
+ clip_path = mpath.Path.make_compound_path(exterior, interior)
+
+ star = mpath.Path.unit_regular_star(6).deepcopy()
+ star.vertices *= 2.6
+
+ fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, sharey=True)
+
+ col = mcollections.PathCollection([star], lw=5, edgecolor='blue',
+ facecolor='red', alpha=0.7, hatch='*')
+ col.set_clip_path(clip_path, ax1.transData)
+ ax1.add_collection(col)
+
+ patch = mpatches.PathPatch(star, lw=5, edgecolor='blue', facecolor='red',
+ alpha=0.7, hatch='*')
+ patch.set_clip_path(clip_path, ax2.transData)
+ ax2.add_patch(patch)
+
+ ax1.set_xlim([-3, 3])
+ ax1.set_ylim([-3, 3])
+
+
+@check_figures_equal(extensions=['png'])
+def test_clipping_zoom(fig_test, fig_ref):
+ # This test places the Axes and sets its limits such that the clip path is
+ # outside the figure entirely. This should not break the clip path.
+ ax_test = fig_test.add_axes([0, 0, 1, 1])
+ l, = ax_test.plot([-3, 3], [-3, 3])
+ # Explicit Path instead of a Rectangle uses clip path processing, instead
+ # of a clip box optimization.
+ p = mpath.Path([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
+ p = mpatches.PathPatch(p, transform=ax_test.transData)
+ l.set_clip_path(p)
+
+ ax_ref = fig_ref.add_axes([0, 0, 1, 1])
+ ax_ref.plot([-3, 3], [-3, 3])
+
+ ax_ref.set(xlim=(0.5, 0.75), ylim=(0.5, 0.75))
+ ax_test.set(xlim=(0.5, 0.75), ylim=(0.5, 0.75))
+
+
+def test_cull_markers():
+ x = np.random.random(20000)
+ y = np.random.random(20000)
+
+ fig, ax = plt.subplots()
+ ax.plot(x, y, 'k.')
+ ax.set_xlim(2, 3)
+
+ pdf = io.BytesIO()
+ fig.savefig(pdf, format="pdf")
+ assert len(pdf.getvalue()) < 8000
+
+ svg = io.BytesIO()
+ fig.savefig(svg, format="svg")
+ assert len(svg.getvalue()) < 20000
+
+
+@image_comparison(['hatching'], remove_text=True, style='default')
+def test_hatching():
+ fig, ax = plt.subplots(1, 1)
+
+ # Default hatch color.
+ rect1 = mpatches.Rectangle((0, 0), 3, 4, hatch='/')
+ ax.add_patch(rect1)
+
+ rect2 = mcollections.RegularPolyCollection(
+ 4, sizes=[16000], offsets=[(1.5, 6.5)], offset_transform=ax.transData,
+ hatch='/')
+ ax.add_collection(rect2)
+
+ # Ensure edge color is not applied to hatching.
+ rect3 = mpatches.Rectangle((4, 0), 3, 4, hatch='/', edgecolor='C1')
+ ax.add_patch(rect3)
+
+ rect4 = mcollections.RegularPolyCollection(
+ 4, sizes=[16000], offsets=[(5.5, 6.5)], offset_transform=ax.transData,
+ hatch='/', edgecolor='C1')
+ ax.add_collection(rect4)
+
+ ax.set_xlim(0, 7)
+ ax.set_ylim(0, 9)
+
+
+def test_remove():
+ fig, ax = plt.subplots()
+ im = ax.imshow(np.arange(36).reshape(6, 6))
+ ln, = ax.plot(range(5))
+
+ assert fig.stale
+ assert ax.stale
+
+ fig.canvas.draw()
+ assert not fig.stale
+ assert not ax.stale
+ assert not ln.stale
+
+ assert im in ax._mouseover_set
+ assert ln not in ax._mouseover_set
+ assert im.axes is ax
+
+ im.remove()
+ ln.remove()
+
+ for art in [im, ln]:
+ assert art.axes is None
+ assert art.get_figure() is None
+
+ assert im not in ax._mouseover_set
+ assert fig.stale
+ assert ax.stale
+
+
+@image_comparison(["default_edges.png"], remove_text=True, style='default')
+def test_default_edges():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['text.kerning_factor'] = 6
+
+ fig, [[ax1, ax2], [ax3, ax4]] = plt.subplots(2, 2)
+
+ ax1.plot(np.arange(10), np.arange(10), 'x',
+ np.arange(10) + 1, np.arange(10), 'o')
+ ax2.bar(np.arange(10), np.arange(10), align='edge')
+ ax3.text(0, 0, "BOX", size=24, bbox=dict(boxstyle='sawtooth'))
+ ax3.set_xlim((-1, 1))
+ ax3.set_ylim((-1, 1))
+ pp1 = mpatches.PathPatch(
+ mpath.Path([(0, 0), (1, 0), (1, 1), (0, 0)],
+ [mpath.Path.MOVETO, mpath.Path.CURVE3,
+ mpath.Path.CURVE3, mpath.Path.CLOSEPOLY]),
+ fc="none", transform=ax4.transData)
+ ax4.add_patch(pp1)
+
+
+def test_properties():
+ ln = mlines.Line2D([], [])
+ ln.properties() # Check that no warning is emitted.
+
+
+def test_setp():
+ # Check empty list
+ plt.setp([])
+ plt.setp([[]])
+
+ # Check arbitrary iterables
+ fig, ax = plt.subplots()
+ lines1 = ax.plot(range(3))
+ lines2 = ax.plot(range(3))
+ martist.setp(chain(lines1, lines2), 'lw', 5)
+ plt.setp(ax.spines.values(), color='green')
+
+ # Check *file* argument
+ sio = io.StringIO()
+ plt.setp(lines1, 'zorder', file=sio)
+ assert sio.getvalue() == ' zorder: float\n'
+
+
+def test_None_zorder():
+ fig, ax = plt.subplots()
+ ln, = ax.plot(range(5), zorder=None)
+ assert ln.get_zorder() == mlines.Line2D.zorder
+ ln.set_zorder(123456)
+ assert ln.get_zorder() == 123456
+ ln.set_zorder(None)
+ assert ln.get_zorder() == mlines.Line2D.zorder
+
+
+@pytest.mark.parametrize('accept_clause, expected', [
+ ('', 'unknown'),
+ ("ACCEPTS: [ '-' | '--' | '-.' ]", "[ '-' | '--' | '-.' ]"),
+ ('ACCEPTS: Some description.', 'Some description.'),
+ ('.. ACCEPTS: Some description.', 'Some description.'),
+ ('arg : int', 'int'),
+ ('*arg : int', 'int'),
+ ('arg : int\nACCEPTS: Something else.', 'Something else. '),
+])
+def test_artist_inspector_get_valid_values(accept_clause, expected):
+ class TestArtist(martist.Artist):
+ def set_f(self, arg):
+ pass
+
+ TestArtist.set_f.__doc__ = """
+ Some text.
+
+ %s
+ """ % accept_clause
+ valid_values = martist.ArtistInspector(TestArtist).get_valid_values('f')
+ assert valid_values == expected
+
+
+def test_artist_inspector_get_aliases():
+ # test the correct format and type of get_aliases method
+ ai = martist.ArtistInspector(mlines.Line2D)
+ aliases = ai.get_aliases()
+ assert aliases["linewidth"] == {"lw"}
+
+
+def test_set_alpha():
+ art = martist.Artist()
+ with pytest.raises(TypeError, match='^alpha must be numeric or None'):
+ art.set_alpha('string')
+ with pytest.raises(TypeError, match='^alpha must be numeric or None'):
+ art.set_alpha([1, 2, 3])
+ with pytest.raises(ValueError, match="outside 0-1 range"):
+ art.set_alpha(1.1)
+ with pytest.raises(ValueError, match="outside 0-1 range"):
+ art.set_alpha(np.nan)
+
+
+def test_set_alpha_for_array():
+ art = martist.Artist()
+ with pytest.raises(TypeError, match='^alpha must be numeric or None'):
+ art._set_alpha_for_array('string')
+ with pytest.raises(ValueError, match="outside 0-1 range"):
+ art._set_alpha_for_array(1.1)
+ with pytest.raises(ValueError, match="outside 0-1 range"):
+ art._set_alpha_for_array(np.nan)
+ with pytest.raises(ValueError, match="alpha must be between 0 and 1"):
+ art._set_alpha_for_array([0.5, 1.1])
+ with pytest.raises(ValueError, match="alpha must be between 0 and 1"):
+ art._set_alpha_for_array([0.5, np.nan])
+
+
+def test_callbacks():
+ def func(artist):
+ func.counter += 1
+
+ func.counter = 0
+
+ art = martist.Artist()
+ oid = art.add_callback(func)
+ assert func.counter == 0
+ art.pchanged() # must call the callback
+ assert func.counter == 1
+ art.set_zorder(10) # setting a property must also call the callback
+ assert func.counter == 2
+ art.remove_callback(oid)
+ art.pchanged() # must not call the callback anymore
+ assert func.counter == 2
+
+
+def test_set_signature():
+ """Test autogenerated ``set()`` for Artist subclasses."""
+ class MyArtist1(martist.Artist):
+ def set_myparam1(self, val):
+ pass
+
+ assert hasattr(MyArtist1.set, '_autogenerated_signature')
+ assert 'myparam1' in MyArtist1.set.__doc__
+
+ class MyArtist2(MyArtist1):
+ def set_myparam2(self, val):
+ pass
+
+ assert hasattr(MyArtist2.set, '_autogenerated_signature')
+ assert 'myparam1' in MyArtist2.set.__doc__
+ assert 'myparam2' in MyArtist2.set.__doc__
+
+
+def test_set_is_overwritten():
+ """set() defined in Artist subclasses should not be overwritten."""
+ class MyArtist3(martist.Artist):
+
+ def set(self, **kwargs):
+ """Not overwritten."""
+
+ assert not hasattr(MyArtist3.set, '_autogenerated_signature')
+ assert MyArtist3.set.__doc__ == "Not overwritten."
+
+ class MyArtist4(MyArtist3):
+ pass
+
+ assert MyArtist4.set is MyArtist3.set
+
+
+def test_format_cursor_data_BoundaryNorm():
+ """Test if cursor data is correct when using BoundaryNorm."""
+ X = np.empty((3, 3))
+ X[0, 0] = 0.9
+ X[0, 1] = 0.99
+ X[0, 2] = 0.999
+ X[1, 0] = -1
+ X[1, 1] = 0
+ X[1, 2] = 1
+ X[2, 0] = 0.09
+ X[2, 1] = 0.009
+ X[2, 2] = 0.0009
+
+ # map range -1..1 to 0..256 in 0.1 steps
+ fig, ax = plt.subplots()
+ fig.suptitle("-1..1 to 0..256 in 0.1")
+ norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 20), 256)
+ img = ax.imshow(X, cmap='RdBu_r', norm=norm)
+
+ labels_list = [
+ "[0.9]",
+ "[1.]",
+ "[1.]",
+ "[-1.0]",
+ "[0.0]",
+ "[1.0]",
+ "[0.09]",
+ "[0.009]",
+ "[0.0009]",
+ ]
+ for v, label in zip(X.flat, labels_list):
+ # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.1))
+ assert img.format_cursor_data(v) == label
+
+ plt.close()
+
+ # map range -1..1 to 0..256 in 0.01 steps
+ fig, ax = plt.subplots()
+ fig.suptitle("-1..1 to 0..256 in 0.01")
+ cmap = mpl.colormaps['RdBu_r'].resampled(200)
+ norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 200), 200)
+ img = ax.imshow(X, cmap=cmap, norm=norm)
+
+ labels_list = [
+ "[0.90]",
+ "[0.99]",
+ "[1.0]",
+ "[-1.00]",
+ "[0.00]",
+ "[1.00]",
+ "[0.09]",
+ "[0.009]",
+ "[0.0009]",
+ ]
+ for v, label in zip(X.flat, labels_list):
+ # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.01))
+ assert img.format_cursor_data(v) == label
+
+ plt.close()
+
+ # map range -1..1 to 0..256 in 0.01 steps
+ fig, ax = plt.subplots()
+ fig.suptitle("-1..1 to 0..256 in 0.001")
+ cmap = mpl.colormaps['RdBu_r'].resampled(2000)
+ norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 2000), 2000)
+ img = ax.imshow(X, cmap=cmap, norm=norm)
+
+ labels_list = [
+ "[0.900]",
+ "[0.990]",
+ "[0.999]",
+ "[-1.000]",
+ "[0.000]",
+ "[1.000]",
+ "[0.090]",
+ "[0.009]",
+ "[0.0009]",
+ ]
+ for v, label in zip(X.flat, labels_list):
+ # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.001))
+ assert img.format_cursor_data(v) == label
+
+ plt.close()
+
+ # different testing data set with
+ # out of bounds values for 0..1 range
+ X = np.empty((7, 1))
+ X[0] = -1.0
+ X[1] = 0.0
+ X[2] = 0.1
+ X[3] = 0.5
+ X[4] = 0.9
+ X[5] = 1.0
+ X[6] = 2.0
+
+ labels_list = [
+ "[-1.0]",
+ "[0.0]",
+ "[0.1]",
+ "[0.5]",
+ "[0.9]",
+ "[1.0]",
+ "[2.0]",
+ ]
+
+ fig, ax = plt.subplots()
+ fig.suptitle("noclip, neither")
+ norm = mcolors.BoundaryNorm(
+ np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='neither')
+ img = ax.imshow(X, cmap='RdBu_r', norm=norm)
+ for v, label in zip(X.flat, labels_list):
+ # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
+ assert img.format_cursor_data(v) == label
+
+ plt.close()
+
+ fig, ax = plt.subplots()
+ fig.suptitle("noclip, min")
+ norm = mcolors.BoundaryNorm(
+ np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='min')
+ img = ax.imshow(X, cmap='RdBu_r', norm=norm)
+ for v, label in zip(X.flat, labels_list):
+ # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
+ assert img.format_cursor_data(v) == label
+
+ plt.close()
+
+ fig, ax = plt.subplots()
+ fig.suptitle("noclip, max")
+ norm = mcolors.BoundaryNorm(
+ np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='max')
+ img = ax.imshow(X, cmap='RdBu_r', norm=norm)
+ for v, label in zip(X.flat, labels_list):
+ # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
+ assert img.format_cursor_data(v) == label
+
+ plt.close()
+
+ fig, ax = plt.subplots()
+ fig.suptitle("noclip, both")
+ norm = mcolors.BoundaryNorm(
+ np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='both')
+ img = ax.imshow(X, cmap='RdBu_r', norm=norm)
+ for v, label in zip(X.flat, labels_list):
+ # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
+ assert img.format_cursor_data(v) == label
+
+ plt.close()
+
+ fig, ax = plt.subplots()
+ fig.suptitle("clip, neither")
+ norm = mcolors.BoundaryNorm(
+ np.linspace(0, 1, 4, endpoint=True), 256, clip=True, extend='neither')
+ img = ax.imshow(X, cmap='RdBu_r', norm=norm)
+ for v, label in zip(X.flat, labels_list):
+ # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
+ assert img.format_cursor_data(v) == label
+
+ plt.close()
+
+
+def test_auto_no_rasterize():
+ class Gen1(martist.Artist):
+ ...
+
+ assert 'draw' in Gen1.__dict__
+ assert Gen1.__dict__['draw'] is Gen1.draw
+
+ class Gen2(Gen1):
+ ...
+
+ assert 'draw' not in Gen2.__dict__
+ assert Gen2.draw is Gen1.draw
+
+
+def test_draw_wraper_forward_input():
+ class TestKlass(martist.Artist):
+ def draw(self, renderer, extra):
+ return extra
+
+ art = TestKlass()
+ renderer = mbackend_bases.RendererBase()
+
+ assert 'aardvark' == art.draw(renderer, 'aardvark')
+ assert 'aardvark' == art.draw(renderer, extra='aardvark')
+
+
+def test_get_figure():
+ fig = plt.figure()
+ sfig1 = fig.subfigures()
+ sfig2 = sfig1.subfigures()
+ ax = sfig2.subplots()
+
+ assert fig.get_figure(root=True) is fig
+ assert fig.get_figure(root=False) is fig
+
+ assert ax.get_figure() is sfig2
+ assert ax.get_figure(root=False) is sfig2
+ assert ax.get_figure(root=True) is fig
+
+ # SubFigure.get_figure has separate implementation but should give consistent
+ # results to other artists.
+ assert sfig2.get_figure(root=False) is sfig1
+ assert sfig2.get_figure(root=True) is fig
+ # Currently different results by default.
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ assert sfig2.get_figure() is fig
+ # No deprecation warning if root and parent figure are the same.
+ assert sfig1.get_figure() is fig
+
+ # An artist not yet attached to anything has no figure.
+ ln = mlines.Line2D([], [])
+ assert ln.get_figure(root=True) is None
+ assert ln.get_figure(root=False) is None
+
+ # figure attribute is root for (Sub)Figures but parent for other artists.
+ assert ax.figure is sfig2
+ assert fig.figure is fig
+ assert sfig2.figure is fig
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_axes.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_axes.py
new file mode 100644
index 0000000000000000000000000000000000000000..159fc70282b859d50d0af96eb4e57d7f84bbd08a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_axes.py
@@ -0,0 +1,9665 @@
+import contextlib
+from collections import namedtuple, deque
+import datetime
+from decimal import Decimal
+from functools import partial
+import gc
+import inspect
+import io
+from itertools import product
+import platform
+import sys
+from types import SimpleNamespace
+
+import dateutil.tz
+
+import numpy as np
+from numpy import ma
+from cycler import cycler
+import pytest
+
+import matplotlib
+import matplotlib as mpl
+from matplotlib import rc_context, patheffects
+import matplotlib.colors as mcolors
+import matplotlib.dates as mdates
+from matplotlib.figure import Figure
+from matplotlib.axes import Axes
+from matplotlib.lines import Line2D
+from matplotlib.collections import PathCollection
+import matplotlib.font_manager as mfont_manager
+import matplotlib.markers as mmarkers
+import matplotlib.patches as mpatches
+import matplotlib.path as mpath
+from matplotlib.projections.geo import HammerAxes
+from matplotlib.projections.polar import PolarAxes
+import matplotlib.pyplot as plt
+import matplotlib.text as mtext
+import matplotlib.ticker as mticker
+import matplotlib.transforms as mtransforms
+import mpl_toolkits.axisartist as AA # type: ignore[import]
+from numpy.testing import (
+ assert_allclose, assert_array_equal, assert_array_almost_equal)
+from matplotlib.testing.decorators import (
+ image_comparison, check_figures_equal, remove_ticks_and_titles)
+from matplotlib.testing._markers import needs_usetex
+
+# Note: Some test cases are run twice: once normally and once with labeled data
+# These two must be defined in the same test function or need to have
+# different baseline images to prevent race conditions when pytest runs
+# the tests with multiple threads.
+
+
+@check_figures_equal(extensions=["png"])
+def test_invisible_axes(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ ax.set_visible(False)
+
+
+def test_get_labels():
+ fig, ax = plt.subplots()
+ ax.set_xlabel('x label')
+ ax.set_ylabel('y label')
+ assert ax.get_xlabel() == 'x label'
+ assert ax.get_ylabel() == 'y label'
+
+
+def test_repr():
+ fig, ax = plt.subplots()
+ ax.set_label('label')
+ ax.set_title('title')
+ ax.set_xlabel('x')
+ ax.set_ylabel('y')
+ assert repr(ax) == (
+ "")
+
+
+@check_figures_equal(extensions=['png'])
+def test_label_loc_vertical(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ sc = ax.scatter([1, 2], [1, 2], c=[1, 2], label='scatter')
+ ax.legend()
+ ax.set_ylabel('Y Label', loc='top')
+ ax.set_xlabel('X Label', loc='right')
+ cbar = fig_test.colorbar(sc)
+ cbar.set_label("Z Label", loc='top')
+
+ ax = fig_ref.subplots()
+ sc = ax.scatter([1, 2], [1, 2], c=[1, 2], label='scatter')
+ ax.legend()
+ ax.set_ylabel('Y Label', y=1, ha='right')
+ ax.set_xlabel('X Label', x=1, ha='right')
+ cbar = fig_ref.colorbar(sc)
+ cbar.set_label("Z Label", y=1, ha='right')
+
+
+@check_figures_equal(extensions=['png'])
+def test_label_loc_horizontal(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ sc = ax.scatter([1, 2], [1, 2], c=[1, 2], label='scatter')
+ ax.legend()
+ ax.set_ylabel('Y Label', loc='bottom')
+ ax.set_xlabel('X Label', loc='left')
+ cbar = fig_test.colorbar(sc, orientation='horizontal')
+ cbar.set_label("Z Label", loc='left')
+
+ ax = fig_ref.subplots()
+ sc = ax.scatter([1, 2], [1, 2], c=[1, 2], label='scatter')
+ ax.legend()
+ ax.set_ylabel('Y Label', y=0, ha='left')
+ ax.set_xlabel('X Label', x=0, ha='left')
+ cbar = fig_ref.colorbar(sc, orientation='horizontal')
+ cbar.set_label("Z Label", x=0, ha='left')
+
+
+@check_figures_equal(extensions=['png'])
+def test_label_loc_rc(fig_test, fig_ref):
+ with matplotlib.rc_context({"xaxis.labellocation": "right",
+ "yaxis.labellocation": "top"}):
+ ax = fig_test.subplots()
+ sc = ax.scatter([1, 2], [1, 2], c=[1, 2], label='scatter')
+ ax.legend()
+ ax.set_ylabel('Y Label')
+ ax.set_xlabel('X Label')
+ cbar = fig_test.colorbar(sc, orientation='horizontal')
+ cbar.set_label("Z Label")
+
+ ax = fig_ref.subplots()
+ sc = ax.scatter([1, 2], [1, 2], c=[1, 2], label='scatter')
+ ax.legend()
+ ax.set_ylabel('Y Label', y=1, ha='right')
+ ax.set_xlabel('X Label', x=1, ha='right')
+ cbar = fig_ref.colorbar(sc, orientation='horizontal')
+ cbar.set_label("Z Label", x=1, ha='right')
+
+
+def test_label_shift():
+ fig, ax = plt.subplots()
+
+ # Test label re-centering on x-axis
+ ax.set_xlabel("Test label", loc="left")
+ ax.set_xlabel("Test label", loc="center")
+ assert ax.xaxis.label.get_horizontalalignment() == "center"
+ ax.set_xlabel("Test label", loc="right")
+ assert ax.xaxis.label.get_horizontalalignment() == "right"
+ ax.set_xlabel("Test label", loc="center")
+ assert ax.xaxis.label.get_horizontalalignment() == "center"
+
+ # Test label re-centering on y-axis
+ ax.set_ylabel("Test label", loc="top")
+ ax.set_ylabel("Test label", loc="center")
+ assert ax.yaxis.label.get_horizontalalignment() == "center"
+ ax.set_ylabel("Test label", loc="bottom")
+ assert ax.yaxis.label.get_horizontalalignment() == "left"
+ ax.set_ylabel("Test label", loc="center")
+ assert ax.yaxis.label.get_horizontalalignment() == "center"
+
+
+@check_figures_equal(extensions=["png"])
+def test_acorr(fig_test, fig_ref):
+ np.random.seed(19680801)
+ Nx = 512
+ x = np.random.normal(0, 1, Nx).cumsum()
+ maxlags = Nx-1
+
+ ax_test = fig_test.subplots()
+ ax_test.acorr(x, maxlags=maxlags)
+
+ ax_ref = fig_ref.subplots()
+ # Normalized autocorrelation
+ norm_auto_corr = np.correlate(x, x, mode="full")/np.dot(x, x)
+ lags = np.arange(-maxlags, maxlags+1)
+ norm_auto_corr = norm_auto_corr[Nx-1-maxlags:Nx+maxlags]
+ ax_ref.vlines(lags, [0], norm_auto_corr)
+ ax_ref.axhline(y=0, xmin=0, xmax=1)
+
+
+@check_figures_equal(extensions=["png"])
+def test_acorr_integers(fig_test, fig_ref):
+ np.random.seed(19680801)
+ Nx = 51
+ x = (np.random.rand(Nx) * 10).cumsum()
+ x = (np.ceil(x)).astype(np.int64)
+ maxlags = Nx-1
+
+ ax_test = fig_test.subplots()
+ ax_test.acorr(x, maxlags=maxlags)
+
+ ax_ref = fig_ref.subplots()
+
+ # Normalized autocorrelation
+ norm_auto_corr = np.correlate(x, x, mode="full")/np.dot(x, x)
+ lags = np.arange(-maxlags, maxlags+1)
+ norm_auto_corr = norm_auto_corr[Nx-1-maxlags:Nx+maxlags]
+ ax_ref.vlines(lags, [0], norm_auto_corr)
+ ax_ref.axhline(y=0, xmin=0, xmax=1)
+
+
+@check_figures_equal(extensions=["png"])
+def test_spy(fig_test, fig_ref):
+ np.random.seed(19680801)
+ a = np.ones(32 * 32)
+ a[:16 * 32] = 0
+ np.random.shuffle(a)
+ a = a.reshape((32, 32))
+
+ axs_test = fig_test.subplots(2)
+ axs_test[0].spy(a)
+ axs_test[1].spy(a, marker=".", origin="lower")
+
+ axs_ref = fig_ref.subplots(2)
+ axs_ref[0].imshow(a, cmap="gray_r", interpolation="nearest")
+ axs_ref[0].xaxis.tick_top()
+ axs_ref[1].plot(*np.nonzero(a)[::-1], ".", markersize=10)
+ axs_ref[1].set(
+ aspect=1, xlim=axs_ref[0].get_xlim(), ylim=axs_ref[0].get_ylim()[::-1])
+ for ax in axs_ref:
+ ax.xaxis.set_ticks_position("both")
+
+
+def test_spy_invalid_kwargs():
+ fig, ax = plt.subplots()
+ for unsupported_kw in [{'interpolation': 'nearest'},
+ {'marker': 'o', 'linestyle': 'solid'}]:
+ with pytest.raises(TypeError):
+ ax.spy(np.eye(3, 3), **unsupported_kw)
+
+
+@check_figures_equal(extensions=["png"])
+def test_matshow(fig_test, fig_ref):
+ mpl.style.use("mpl20")
+ a = np.random.rand(32, 32)
+ fig_test.add_subplot().matshow(a)
+ ax_ref = fig_ref.add_subplot()
+ ax_ref.imshow(a)
+ ax_ref.xaxis.tick_top()
+ ax_ref.xaxis.set_ticks_position('both')
+
+
+@image_comparison([f'formatter_ticker_{i:03d}.png' for i in range(1, 6)],
+ tol=0 if platform.machine() == 'x86_64' else 0.031)
+def test_formatter_ticker():
+ import matplotlib.testing.jpl_units as units
+ units.register()
+
+ # This should affect the tick size. (Tests issue #543)
+ matplotlib.rcParams['lines.markeredgewidth'] = 30
+
+ # This essentially test to see if user specified labels get overwritten
+ # by the auto labeler functionality of the axes.
+ xdata = [x*units.sec for x in range(10)]
+ ydata1 = [(1.5*y - 0.5)*units.km for y in range(10)]
+ ydata2 = [(1.75*y - 1.0)*units.km for y in range(10)]
+
+ ax = plt.figure().subplots()
+ ax.set_xlabel("x-label 001")
+
+ ax = plt.figure().subplots()
+ ax.set_xlabel("x-label 001")
+ ax.plot(xdata, ydata1, color='blue', xunits="sec")
+
+ ax = plt.figure().subplots()
+ ax.set_xlabel("x-label 001")
+ ax.plot(xdata, ydata1, color='blue', xunits="sec")
+ ax.set_xlabel("x-label 003")
+
+ ax = plt.figure().subplots()
+ ax.plot(xdata, ydata1, color='blue', xunits="sec")
+ ax.plot(xdata, ydata2, color='green', xunits="hour")
+ ax.set_xlabel("x-label 004")
+
+ # See SF bug 2846058
+ # https://sourceforge.net/tracker/?func=detail&aid=2846058&group_id=80706&atid=560720
+ ax = plt.figure().subplots()
+ ax.plot(xdata, ydata1, color='blue', xunits="sec")
+ ax.plot(xdata, ydata2, color='green', xunits="hour")
+ ax.set_xlabel("x-label 005")
+ ax.autoscale_view()
+
+
+def test_funcformatter_auto_formatter():
+ def _formfunc(x, pos):
+ return ''
+
+ ax = plt.figure().subplots()
+
+ assert ax.xaxis.isDefault_majfmt
+ assert ax.xaxis.isDefault_minfmt
+ assert ax.yaxis.isDefault_majfmt
+ assert ax.yaxis.isDefault_minfmt
+
+ ax.xaxis.set_major_formatter(_formfunc)
+
+ assert not ax.xaxis.isDefault_majfmt
+ assert ax.xaxis.isDefault_minfmt
+ assert ax.yaxis.isDefault_majfmt
+ assert ax.yaxis.isDefault_minfmt
+
+ targ_funcformatter = mticker.FuncFormatter(_formfunc)
+
+ assert isinstance(ax.xaxis.get_major_formatter(),
+ mticker.FuncFormatter)
+
+ assert ax.xaxis.get_major_formatter().func == targ_funcformatter.func
+
+
+def test_strmethodformatter_auto_formatter():
+ formstr = '{x}_{pos}'
+
+ ax = plt.figure().subplots()
+
+ assert ax.xaxis.isDefault_majfmt
+ assert ax.xaxis.isDefault_minfmt
+ assert ax.yaxis.isDefault_majfmt
+ assert ax.yaxis.isDefault_minfmt
+
+ ax.yaxis.set_minor_formatter(formstr)
+
+ assert ax.xaxis.isDefault_majfmt
+ assert ax.xaxis.isDefault_minfmt
+ assert ax.yaxis.isDefault_majfmt
+ assert not ax.yaxis.isDefault_minfmt
+
+ targ_strformatter = mticker.StrMethodFormatter(formstr)
+
+ assert isinstance(ax.yaxis.get_minor_formatter(),
+ mticker.StrMethodFormatter)
+
+ assert ax.yaxis.get_minor_formatter().fmt == targ_strformatter.fmt
+
+
+@image_comparison(["twin_axis_locators_formatters.png"])
+def test_twin_axis_locators_formatters():
+ vals = np.linspace(0, 1, num=5, endpoint=True)
+ locs = np.sin(np.pi * vals / 2.0)
+
+ majl = plt.FixedLocator(locs)
+ minl = plt.FixedLocator([0.1, 0.2, 0.3])
+
+ fig = plt.figure()
+ ax1 = fig.add_subplot(1, 1, 1)
+ ax1.plot([0.1, 100], [0, 1])
+ ax1.yaxis.set_major_locator(majl)
+ ax1.yaxis.set_minor_locator(minl)
+ ax1.yaxis.set_major_formatter(plt.FormatStrFormatter('%08.2lf'))
+ ax1.yaxis.set_minor_formatter(plt.FixedFormatter(['tricks', 'mind',
+ 'jedi']))
+
+ ax1.xaxis.set_major_locator(plt.LinearLocator())
+ ax1.xaxis.set_minor_locator(plt.FixedLocator([15, 35, 55, 75]))
+ ax1.xaxis.set_major_formatter(plt.FormatStrFormatter('%05.2lf'))
+ ax1.xaxis.set_minor_formatter(plt.FixedFormatter(['c', '3', 'p', 'o']))
+ ax1.twiny()
+ ax1.twinx()
+
+
+def test_twinx_cla():
+ fig, ax = plt.subplots()
+ ax2 = ax.twinx()
+ ax3 = ax2.twiny()
+ plt.draw()
+ assert not ax2.xaxis.get_visible()
+ assert not ax2.patch.get_visible()
+ ax2.cla()
+ ax3.cla()
+
+ assert not ax2.xaxis.get_visible()
+ assert not ax2.patch.get_visible()
+ assert ax2.yaxis.get_visible()
+
+ assert ax3.xaxis.get_visible()
+ assert not ax3.patch.get_visible()
+ assert not ax3.yaxis.get_visible()
+
+ assert ax.xaxis.get_visible()
+ assert ax.patch.get_visible()
+ assert ax.yaxis.get_visible()
+
+
+@pytest.mark.parametrize('twin', ('x', 'y'))
+def test_twin_units(twin):
+ axis_name = f'{twin}axis'
+ twin_func = f'twin{twin}'
+
+ a = ['0', '1']
+ b = ['a', 'b']
+
+ fig = Figure()
+ ax1 = fig.subplots()
+ ax1.plot(a, b)
+ assert getattr(ax1, axis_name).units is not None
+ ax2 = getattr(ax1, twin_func)()
+ assert getattr(ax2, axis_name).units is not None
+ assert getattr(ax2, axis_name).units is getattr(ax1, axis_name).units
+
+
+@pytest.mark.parametrize('twin', ('x', 'y'))
+@check_figures_equal(extensions=['png'], tol=0.19)
+def test_twin_logscale(fig_test, fig_ref, twin):
+ twin_func = f'twin{twin}' # test twinx or twiny
+ set_scale = f'set_{twin}scale'
+ x = np.arange(1, 100)
+
+ # Change scale after twinning.
+ ax_test = fig_test.add_subplot(2, 1, 1)
+ ax_twin = getattr(ax_test, twin_func)()
+ getattr(ax_test, set_scale)('log')
+ ax_twin.plot(x, x)
+
+ # Twin after changing scale.
+ ax_test = fig_test.add_subplot(2, 1, 2)
+ getattr(ax_test, set_scale)('log')
+ ax_twin = getattr(ax_test, twin_func)()
+ ax_twin.plot(x, x)
+
+ for i in [1, 2]:
+ ax_ref = fig_ref.add_subplot(2, 1, i)
+ getattr(ax_ref, set_scale)('log')
+ ax_ref.plot(x, x)
+
+ # This is a hack because twinned Axes double-draw the frame.
+ # Remove this when that is fixed.
+ Path = matplotlib.path.Path
+ fig_ref.add_artist(
+ matplotlib.patches.PathPatch(
+ Path([[0, 0], [0, 1],
+ [0, 1], [1, 1],
+ [1, 1], [1, 0],
+ [1, 0], [0, 0]],
+ [Path.MOVETO, Path.LINETO] * 4),
+ transform=ax_ref.transAxes,
+ facecolor='none',
+ edgecolor=mpl.rcParams['axes.edgecolor'],
+ linewidth=mpl.rcParams['axes.linewidth'],
+ capstyle='projecting'))
+
+ remove_ticks_and_titles(fig_test)
+ remove_ticks_and_titles(fig_ref)
+
+
+@image_comparison(['twin_autoscale.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.009)
+def test_twinx_axis_scales():
+ x = np.array([0, 0.5, 1])
+ y = 0.5 * x
+ x2 = np.array([0, 1, 2])
+ y2 = 2 * x2
+
+ fig = plt.figure()
+ ax = fig.add_axes((0, 0, 1, 1), autoscalex_on=False, autoscaley_on=False)
+ ax.plot(x, y, color='blue', lw=10)
+
+ ax2 = plt.twinx(ax)
+ ax2.plot(x2, y2, 'r--', lw=5)
+
+ ax.margins(0, 0)
+ ax2.margins(0, 0)
+
+
+def test_twin_inherit_autoscale_setting():
+ fig, ax = plt.subplots()
+ ax_x_on = ax.twinx()
+ ax.set_autoscalex_on(False)
+ ax_x_off = ax.twinx()
+
+ assert ax_x_on.get_autoscalex_on()
+ assert not ax_x_off.get_autoscalex_on()
+
+ ax_y_on = ax.twiny()
+ ax.set_autoscaley_on(False)
+ ax_y_off = ax.twiny()
+
+ assert ax_y_on.get_autoscaley_on()
+ assert not ax_y_off.get_autoscaley_on()
+
+
+def test_inverted_cla():
+ # GitHub PR #5450. Setting autoscale should reset
+ # axes to be non-inverted.
+ # plotting an image, then 1d graph, axis is now down
+ fig = plt.figure(0)
+ ax = fig.gca()
+ # 1. test that a new axis is not inverted per default
+ assert not ax.xaxis_inverted()
+ assert not ax.yaxis_inverted()
+ img = np.random.random((100, 100))
+ ax.imshow(img)
+ # 2. test that a image axis is inverted
+ assert not ax.xaxis_inverted()
+ assert ax.yaxis_inverted()
+ # 3. test that clearing and plotting a line, axes are
+ # not inverted
+ ax.cla()
+ x = np.linspace(0, 2*np.pi, 100)
+ ax.plot(x, np.cos(x))
+ assert not ax.xaxis_inverted()
+ assert not ax.yaxis_inverted()
+
+ # 4. autoscaling should not bring back axes to normal
+ ax.cla()
+ ax.imshow(img)
+ plt.autoscale()
+ assert not ax.xaxis_inverted()
+ assert ax.yaxis_inverted()
+
+ for ax in fig.axes:
+ ax.remove()
+ # 5. two shared axes. Inverting the leader axis should invert the shared
+ # axes; clearing the leader axis should bring axes in shared
+ # axes back to normal.
+ ax0 = plt.subplot(211)
+ ax1 = plt.subplot(212, sharey=ax0)
+ ax0.yaxis.set_inverted(True)
+ assert ax1.yaxis_inverted()
+ ax1.plot(x, np.cos(x))
+ ax0.cla()
+ assert not ax1.yaxis_inverted()
+ ax1.cla()
+ # 6. clearing the follower should not touch limits
+ ax0.imshow(img)
+ ax1.plot(x, np.cos(x))
+ ax1.cla()
+ assert ax.yaxis_inverted()
+
+ # clean up
+ plt.close(fig)
+
+
+def test_subclass_clear_cla():
+ # Ensure that subclasses of Axes call cla/clear correctly.
+ # Note, we cannot use mocking here as we want to be sure that the
+ # superclass fallback does not recurse.
+
+ with pytest.warns(PendingDeprecationWarning,
+ match='Overriding `Axes.cla`'):
+ class ClaAxes(Axes):
+ def cla(self):
+ nonlocal called
+ called = True
+
+ with pytest.warns(PendingDeprecationWarning,
+ match='Overriding `Axes.cla`'):
+ class ClaSuperAxes(Axes):
+ def cla(self):
+ nonlocal called
+ called = True
+ super().cla()
+
+ class SubClaAxes(ClaAxes):
+ pass
+
+ class ClearAxes(Axes):
+ def clear(self):
+ nonlocal called
+ called = True
+
+ class ClearSuperAxes(Axes):
+ def clear(self):
+ nonlocal called
+ called = True
+ super().clear()
+
+ class SubClearAxes(ClearAxes):
+ pass
+
+ fig = Figure()
+ for axes_class in [ClaAxes, ClaSuperAxes, SubClaAxes,
+ ClearAxes, ClearSuperAxes, SubClearAxes]:
+ called = False
+ ax = axes_class(fig, [0, 0, 1, 1])
+ # Axes.__init__ has already called clear (which aliases to cla or is in
+ # the subclass).
+ assert called
+
+ called = False
+ ax.cla()
+ assert called
+
+
+def test_cla_not_redefined_internally():
+ for klass in Axes.__subclasses__():
+ # Check that cla does not get redefined in our Axes subclasses, except
+ # for in the above test function.
+ if 'test_subclass_clear_cla' not in klass.__qualname__:
+ assert 'cla' not in klass.__dict__
+
+
+@check_figures_equal(extensions=["png"])
+def test_minorticks_on_rcParams_both(fig_test, fig_ref):
+ with matplotlib.rc_context({"xtick.minor.visible": True,
+ "ytick.minor.visible": True}):
+ ax_test = fig_test.subplots()
+ ax_test.plot([0, 1], [0, 1])
+ ax_ref = fig_ref.subplots()
+ ax_ref.plot([0, 1], [0, 1])
+ ax_ref.minorticks_on()
+
+
+@image_comparison(["autoscale_tiny_range.png"], remove_text=True)
+def test_autoscale_tiny_range():
+ # github pull #904
+ fig, axs = plt.subplots(2, 2)
+ for i, ax in enumerate(axs.flat):
+ y1 = 10**(-11 - i)
+ ax.plot([0, 1], [1, 1 + y1])
+
+
+@mpl.style.context('default')
+def test_autoscale_tight():
+ fig, ax = plt.subplots(1, 1)
+ ax.plot([1, 2, 3, 4])
+ ax.autoscale(enable=True, axis='x', tight=False)
+ ax.autoscale(enable=True, axis='y', tight=True)
+ assert_allclose(ax.get_xlim(), (-0.15, 3.15))
+ assert_allclose(ax.get_ylim(), (1.0, 4.0))
+
+ # Check that autoscale is on
+ assert ax.get_autoscalex_on()
+ assert ax.get_autoscaley_on()
+ assert ax.get_autoscale_on()
+ # Set enable to None
+ ax.autoscale(enable=None)
+ # Same limits
+ assert_allclose(ax.get_xlim(), (-0.15, 3.15))
+ assert_allclose(ax.get_ylim(), (1.0, 4.0))
+ # autoscale still on
+ assert ax.get_autoscalex_on()
+ assert ax.get_autoscaley_on()
+ assert ax.get_autoscale_on()
+
+
+@mpl.style.context('default')
+def test_autoscale_log_shared():
+ # related to github #7587
+ # array starts at zero to trigger _minpos handling
+ x = np.arange(100, dtype=float)
+ fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
+ ax1.loglog(x, x)
+ ax2.semilogx(x, x)
+ ax1.autoscale(tight=True)
+ ax2.autoscale(tight=True)
+ plt.draw()
+ lims = (x[1], x[-1])
+ assert_allclose(ax1.get_xlim(), lims)
+ assert_allclose(ax1.get_ylim(), lims)
+ assert_allclose(ax2.get_xlim(), lims)
+ assert_allclose(ax2.get_ylim(), (x[0], x[-1]))
+
+
+@mpl.style.context('default')
+def test_use_sticky_edges():
+ fig, ax = plt.subplots()
+ ax.imshow([[0, 1], [2, 3]], origin='lower')
+ assert_allclose(ax.get_xlim(), (-0.5, 1.5))
+ assert_allclose(ax.get_ylim(), (-0.5, 1.5))
+ ax.use_sticky_edges = False
+ ax.autoscale()
+ xlim = (-0.5 - 2 * ax._xmargin, 1.5 + 2 * ax._xmargin)
+ ylim = (-0.5 - 2 * ax._ymargin, 1.5 + 2 * ax._ymargin)
+ assert_allclose(ax.get_xlim(), xlim)
+ assert_allclose(ax.get_ylim(), ylim)
+ # Make sure it is reversible:
+ ax.use_sticky_edges = True
+ ax.autoscale()
+ assert_allclose(ax.get_xlim(), (-0.5, 1.5))
+ assert_allclose(ax.get_ylim(), (-0.5, 1.5))
+
+
+@check_figures_equal(extensions=["png"])
+def test_sticky_shared_axes(fig_test, fig_ref):
+ # Check that sticky edges work whether they are set in an Axes that is a
+ # "leader" in a share, or an Axes that is a "follower".
+ Z = np.arange(15).reshape(3, 5)
+
+ ax0 = fig_test.add_subplot(211)
+ ax1 = fig_test.add_subplot(212, sharex=ax0)
+ ax1.pcolormesh(Z)
+
+ ax0 = fig_ref.add_subplot(212)
+ ax1 = fig_ref.add_subplot(211, sharex=ax0)
+ ax0.pcolormesh(Z)
+
+
+@image_comparison(['sticky_tolerance.png'], remove_text=True, style="mpl20")
+def test_sticky_tolerance():
+ fig, axs = plt.subplots(2, 2)
+
+ width = .1
+
+ axs.flat[0].bar(x=0, height=width, bottom=20000.6)
+ axs.flat[0].bar(x=1, height=width, bottom=20000.1)
+
+ axs.flat[1].bar(x=0, height=-width, bottom=20000.6)
+ axs.flat[1].bar(x=1, height=-width, bottom=20000.1)
+
+ axs.flat[2].barh(y=0, width=-width, left=-20000.6)
+ axs.flat[2].barh(y=1, width=-width, left=-20000.1)
+
+ axs.flat[3].barh(y=0, width=width, left=-20000.6)
+ axs.flat[3].barh(y=1, width=width, left=-20000.1)
+
+
+@image_comparison(['sticky_tolerance_cf.png'], remove_text=True, style="mpl20")
+def test_sticky_tolerance_contourf():
+ fig, ax = plt.subplots()
+
+ x = y = [14496.71, 14496.75]
+ data = [[0, 1], [2, 3]]
+
+ ax.contourf(x, y, data)
+
+
+def test_nargs_stem():
+ with pytest.raises(TypeError, match='0 were given'):
+ # stem() takes 1-3 arguments.
+ plt.stem()
+
+
+def test_nargs_legend():
+ with pytest.raises(TypeError, match='3 were given'):
+ ax = plt.subplot()
+ # legend() takes 0-2 arguments.
+ ax.legend(['First'], ['Second'], 3)
+
+
+def test_nargs_pcolorfast():
+ with pytest.raises(TypeError, match='2 were given'):
+ ax = plt.subplot()
+ # pcolorfast() takes 1 or 3 arguments,
+ # not passing any arguments fails at C = args[-1]
+ # before nargs_err is raised.
+ ax.pcolorfast([(0, 1), (0, 2)], [[1, 2, 3], [1, 2, 3]])
+
+
+@image_comparison(['offset_points'], remove_text=True)
+def test_basic_annotate():
+ # Setup some data
+ t = np.arange(0.0, 5.0, 0.01)
+ s = np.cos(2.0*np.pi * t)
+
+ # Offset Points
+
+ fig = plt.figure()
+ ax = fig.add_subplot(autoscale_on=False, xlim=(-1, 5), ylim=(-3, 5))
+ line, = ax.plot(t, s, lw=3, color='purple')
+
+ ax.annotate('local max', xy=(3, 1), xycoords='data',
+ xytext=(3, 3), textcoords='offset points')
+
+
+@image_comparison(['arrow_simple.png'], remove_text=True)
+def test_arrow_simple():
+ # Simple image test for ax.arrow
+ # kwargs that take discrete values
+ length_includes_head = (True, False)
+ shape = ('full', 'left', 'right')
+ head_starts_at_zero = (True, False)
+ # Create outer product of values
+ kwargs = product(length_includes_head, shape, head_starts_at_zero)
+
+ fig, axs = plt.subplots(3, 4)
+ for i, (ax, kwarg) in enumerate(zip(axs.flat, kwargs)):
+ ax.set_xlim(-2, 2)
+ ax.set_ylim(-2, 2)
+ # Unpack kwargs
+ (length_includes_head, shape, head_starts_at_zero) = kwarg
+ theta = 2 * np.pi * i / 12
+ # Draw arrow
+ ax.arrow(0, 0, np.sin(theta), np.cos(theta),
+ width=theta/100,
+ length_includes_head=length_includes_head,
+ shape=shape,
+ head_starts_at_zero=head_starts_at_zero,
+ head_width=theta / 10,
+ head_length=theta / 10)
+
+
+def test_arrow_empty():
+ _, ax = plt.subplots()
+ # Create an empty FancyArrow
+ ax.arrow(0, 0, 0, 0, head_length=0)
+
+
+def test_arrow_in_view():
+ _, ax = plt.subplots()
+ ax.arrow(1, 1, 1, 1)
+ assert ax.get_xlim() == (0.8, 2.2)
+ assert ax.get_ylim() == (0.8, 2.2)
+
+
+def test_annotate_default_arrow():
+ # Check that we can make an annotation arrow with only default properties.
+ fig, ax = plt.subplots()
+ ann = ax.annotate("foo", (0, 1), xytext=(2, 3))
+ assert ann.arrow_patch is None
+ ann = ax.annotate("foo", (0, 1), xytext=(2, 3), arrowprops={})
+ assert ann.arrow_patch is not None
+
+
+def test_annotate_signature():
+ """Check that the signature of Axes.annotate() matches Annotation."""
+ fig, ax = plt.subplots()
+ annotate_params = inspect.signature(ax.annotate).parameters
+ annotation_params = inspect.signature(mtext.Annotation).parameters
+ assert list(annotate_params.keys()) == list(annotation_params.keys())
+ for p1, p2 in zip(annotate_params.values(), annotation_params.values()):
+ assert p1 == p2
+
+
+@image_comparison(['fill_units.png'], savefig_kwarg={'dpi': 60})
+def test_fill_units():
+ import matplotlib.testing.jpl_units as units
+ units.register()
+
+ # generate some data
+ t = units.Epoch("ET", dt=datetime.datetime(2009, 4, 27))
+ value = 10.0 * units.deg
+ day = units.Duration("ET", 24.0 * 60.0 * 60.0)
+ dt = np.arange('2009-04-27', '2009-04-29', dtype='datetime64[D]')
+ dtn = mdates.date2num(dt)
+
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
+
+ ax1.plot([t], [value], yunits='deg', color='red')
+ ind = [0, 0, 1, 1]
+ ax1.fill(dtn[ind], [0.0, 0.0, 90.0, 0.0], 'b')
+
+ ax2.plot([t], [value], yunits='deg', color='red')
+ ax2.fill([t, t, t + day, t + day],
+ [0.0, 0.0, 90.0, 0.0], 'b')
+
+ ax3.plot([t], [value], yunits='deg', color='red')
+ ax3.fill(dtn[ind],
+ [0 * units.deg, 0 * units.deg, 90 * units.deg, 0 * units.deg],
+ 'b')
+
+ ax4.plot([t], [value], yunits='deg', color='red')
+ ax4.fill([t, t, t + day, t + day],
+ [0 * units.deg, 0 * units.deg, 90 * units.deg, 0 * units.deg],
+ facecolor="blue")
+ fig.autofmt_xdate()
+
+
+def test_plot_format_kwarg_redundant():
+ with pytest.warns(UserWarning, match="marker .* redundantly defined"):
+ plt.plot([0], [0], 'o', marker='x')
+ with pytest.warns(UserWarning, match="linestyle .* redundantly defined"):
+ plt.plot([0], [0], '-', linestyle='--')
+ with pytest.warns(UserWarning, match="color .* redundantly defined"):
+ plt.plot([0], [0], 'r', color='blue')
+ # smoke-test: should not warn
+ plt.errorbar([0], [0], fmt='none', color='blue')
+
+
+@check_figures_equal(extensions=["png"])
+def test_errorbar_dashes(fig_test, fig_ref):
+ x = [1, 2, 3, 4]
+ y = np.sin(x)
+
+ ax_ref = fig_ref.gca()
+ ax_test = fig_test.gca()
+
+ line, *_ = ax_ref.errorbar(x, y, xerr=np.abs(y), yerr=np.abs(y))
+ line.set_dashes([2, 2])
+
+ ax_test.errorbar(x, y, xerr=np.abs(y), yerr=np.abs(y), dashes=[2, 2])
+
+
+def test_errorbar_mapview_kwarg():
+ D = {ii: ii for ii in range(10)}
+ fig, ax = plt.subplots()
+ ax.errorbar(x=D.keys(), y=D.values(), xerr=D.values())
+
+
+@image_comparison(['single_point', 'single_point'])
+def test_single_point():
+ # Issue #1796: don't let lines.marker affect the grid
+ matplotlib.rcParams['lines.marker'] = 'o'
+ matplotlib.rcParams['axes.grid'] = True
+
+ fig, (ax1, ax2) = plt.subplots(2)
+ ax1.plot([0], [0], 'o')
+ ax2.plot([1], [1], 'o')
+
+ # Reuse testcase from above for a labeled data test
+ data = {'a': [0], 'b': [1]}
+
+ fig, (ax1, ax2) = plt.subplots(2)
+ ax1.plot('a', 'a', 'o', data=data)
+ ax2.plot('b', 'b', 'o', data=data)
+
+
+@image_comparison(['single_date.png'], style='mpl20')
+def test_single_date():
+
+ # use former defaults to match existing baseline image
+ plt.rcParams['axes.formatter.limits'] = -7, 7
+ dt = mdates.date2num(np.datetime64('0000-12-31'))
+
+ time1 = [721964.0]
+ data1 = [-65.54]
+
+ fig, ax = plt.subplots(2, 1)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ ax[0].plot_date(time1 + dt, data1, 'o', color='r')
+ ax[1].plot(time1, data1, 'o', color='r')
+
+
+@check_figures_equal(extensions=["png"])
+def test_shaped_data(fig_test, fig_ref):
+ row = np.arange(10).reshape((1, -1))
+ col = np.arange(0, 100, 10).reshape((-1, 1))
+
+ axs = fig_test.subplots(2)
+ axs[0].plot(row) # Actually plots nothing (columns are single points).
+ axs[1].plot(col) # Same as plotting 1d.
+
+ axs = fig_ref.subplots(2)
+ # xlim from the implicit "x=0", ylim from the row datalim.
+ axs[0].set(xlim=(-.06, .06), ylim=(0, 9))
+ axs[1].plot(col.ravel())
+
+
+def test_structured_data():
+ # support for structured data
+ pts = np.array([(1, 1), (2, 2)], dtype=[("ones", float), ("twos", float)])
+
+ # this should not read second name as a format and raise ValueError
+ axs = plt.figure().subplots(2)
+ axs[0].plot("ones", "twos", data=pts)
+ axs[1].plot("ones", "twos", "r", data=pts)
+
+
+@image_comparison(['aitoff_proj'], extensions=["png"],
+ remove_text=True, style='mpl20')
+def test_aitoff_proj():
+ """
+ Test aitoff projection ref.:
+ https://github.com/matplotlib/matplotlib/pull/14451
+ """
+ x = np.linspace(-np.pi, np.pi, 20)
+ y = np.linspace(-np.pi / 2, np.pi / 2, 20)
+ X, Y = np.meshgrid(x, y)
+
+ fig, ax = plt.subplots(figsize=(8, 4.2),
+ subplot_kw=dict(projection="aitoff"))
+ ax.grid()
+ ax.plot(X.flat, Y.flat, 'o', markersize=4)
+
+
+@image_comparison(['axvspan_epoch.png'])
+def test_axvspan_epoch():
+ import matplotlib.testing.jpl_units as units
+ units.register()
+
+ # generate some data
+ t0 = units.Epoch("ET", dt=datetime.datetime(2009, 1, 21))
+ tf = units.Epoch("ET", dt=datetime.datetime(2009, 1, 22))
+ dt = units.Duration("ET", units.day.convert("sec"))
+
+ ax = plt.gca()
+ ax.axvspan(t0, tf, facecolor="blue", alpha=0.25)
+ ax.set_xlim(t0 - 5.0*dt, tf + 5.0*dt)
+
+
+@image_comparison(['axhspan_epoch.png'], tol=0.02)
+def test_axhspan_epoch():
+ import matplotlib.testing.jpl_units as units
+ units.register()
+
+ # generate some data
+ t0 = units.Epoch("ET", dt=datetime.datetime(2009, 1, 21))
+ tf = units.Epoch("ET", dt=datetime.datetime(2009, 1, 22))
+ dt = units.Duration("ET", units.day.convert("sec"))
+
+ ax = plt.gca()
+ ax.axhspan(t0, tf, facecolor="blue", alpha=0.25)
+ ax.set_ylim(t0 - 5.0*dt, tf + 5.0*dt)
+
+
+@image_comparison(['hexbin_extent.png', 'hexbin_extent.png'], remove_text=True)
+def test_hexbin_extent():
+ # this test exposes sf bug 2856228
+ fig, ax = plt.subplots()
+ data = (np.arange(2000) / 2000).reshape((2, 1000))
+ x, y = data
+
+ ax.hexbin(x, y, extent=[.1, .3, .6, .7])
+
+ # Reuse testcase from above for a labeled data test
+ data = {"x": x, "y": y}
+
+ fig, ax = plt.subplots()
+ ax.hexbin("x", "y", extent=[.1, .3, .6, .7], data=data)
+
+
+def test_hexbin_bad_extents():
+ fig, ax = plt.subplots()
+ data = (np.arange(20) / 20).reshape((2, 10))
+ x, y = data
+
+ with pytest.raises(ValueError, match="In extent, xmax must be greater than xmin"):
+ ax.hexbin(x, y, extent=(1, 0, 0, 1))
+
+ with pytest.raises(ValueError, match="In extent, ymax must be greater than ymin"):
+ ax.hexbin(x, y, extent=(0, 1, 1, 0))
+
+
+def test_hexbin_string_norm():
+ fig, ax = plt.subplots()
+ hex = ax.hexbin(np.random.rand(10), np.random.rand(10), norm="log", vmin=2, vmax=5)
+ assert isinstance(hex, matplotlib.collections.PolyCollection)
+ assert isinstance(hex.norm, matplotlib.colors.LogNorm)
+ assert hex.norm.vmin == 2
+ assert hex.norm.vmax == 5
+
+
+@image_comparison(['hexbin_empty.png'], remove_text=True)
+def test_hexbin_empty():
+ # From #3886: creating hexbin from empty dataset raises ValueError
+ fig, ax = plt.subplots()
+ ax.hexbin([], [])
+ # From #23922: creating hexbin with log scaling from empty
+ # dataset raises ValueError
+ ax.hexbin([], [], bins='log')
+ # From #27103: np.max errors when handed empty data
+ ax.hexbin([], [], C=[], reduce_C_function=np.max)
+ # No string-comparison warning from NumPy.
+ ax.hexbin([], [], bins=np.arange(10))
+
+
+def test_hexbin_pickable():
+ # From #1973: Test that picking a hexbin collection works
+ fig, ax = plt.subplots()
+ data = (np.arange(200) / 200).reshape((2, 100))
+ x, y = data
+ hb = ax.hexbin(x, y, extent=[.1, .3, .6, .7], picker=-1)
+ mouse_event = SimpleNamespace(x=400, y=300)
+ assert hb.contains(mouse_event)[0]
+
+
+@image_comparison(['hexbin_log.png'], style='mpl20')
+def test_hexbin_log():
+ # Issue #1636 (and also test log scaled colorbar)
+
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ np.random.seed(19680801)
+ n = 100000
+ x = np.random.standard_normal(n)
+ y = 2.0 + 3.0 * x + 4.0 * np.random.standard_normal(n)
+ y = np.power(2, y * 0.5)
+
+ fig, ax = plt.subplots()
+ h = ax.hexbin(x, y, yscale='log', bins='log',
+ marginals=True, reduce_C_function=np.sum)
+ plt.colorbar(h)
+
+ # Make sure offsets are set
+ assert h.get_offsets().shape == (11558, 2)
+
+
+def test_hexbin_log_offsets():
+ x = np.geomspace(1, 100, 500)
+
+ fig, ax = plt.subplots()
+ h = ax.hexbin(x, x, xscale='log', yscale='log', gridsize=2)
+ np.testing.assert_almost_equal(
+ h.get_offsets(),
+ np.array(
+ [[0, 0],
+ [0, 2],
+ [1, 0],
+ [1, 2],
+ [2, 0],
+ [2, 2],
+ [0.5, 1],
+ [1.5, 1]]))
+
+
+@image_comparison(["hexbin_linear.png"], style="mpl20", remove_text=True)
+def test_hexbin_linear():
+ # Issue #21165
+ np.random.seed(19680801)
+ n = 100000
+ x = np.random.standard_normal(n)
+ y = 2.0 + 3.0 * x + 4.0 * np.random.standard_normal(n)
+
+ fig, ax = plt.subplots()
+ ax.hexbin(x, y, gridsize=(10, 5), marginals=True,
+ reduce_C_function=np.sum)
+
+
+def test_hexbin_log_clim():
+ x, y = np.arange(200).reshape((2, 100))
+ fig, ax = plt.subplots()
+ h = ax.hexbin(x, y, bins='log', vmin=2, vmax=100)
+ assert h.get_clim() == (2, 100)
+
+
+@check_figures_equal(extensions=['png'])
+def test_hexbin_mincnt_behavior_upon_C_parameter(fig_test, fig_ref):
+ # see: gh:12926
+ datapoints = [
+ # list of (x, y)
+ (0, 0),
+ (0, 0),
+ (6, 0),
+ (0, 6),
+ ]
+ X, Y = zip(*datapoints)
+ C = [1] * len(X)
+ extent = [-10., 10, -10., 10]
+ gridsize = (7, 7)
+
+ ax_test = fig_test.subplots()
+ ax_ref = fig_ref.subplots()
+
+ # without C parameter
+ ax_ref.hexbin(
+ X, Y,
+ extent=extent,
+ gridsize=gridsize,
+ mincnt=1,
+ )
+ ax_ref.set_facecolor("green") # for contrast of background
+
+ # with C parameter
+ ax_test.hexbin(
+ X, Y,
+ C=[1] * len(X),
+ reduce_C_function=lambda v: sum(v),
+ mincnt=1,
+ extent=extent,
+ gridsize=gridsize,
+ )
+ ax_test.set_facecolor("green")
+
+
+def test_inverted_limits():
+ # Test gh:1553
+ # Calling invert_xaxis prior to plotting should not disable autoscaling
+ # while still maintaining the inverted direction
+ fig, ax = plt.subplots()
+ ax.invert_xaxis()
+ ax.plot([-5, -3, 2, 4], [1, 2, -3, 5])
+
+ assert ax.get_xlim() == (4, -5)
+ assert ax.get_ylim() == (-3, 5)
+ plt.close()
+
+ fig, ax = plt.subplots()
+ ax.invert_yaxis()
+ ax.plot([-5, -3, 2, 4], [1, 2, -3, 5])
+
+ assert ax.get_xlim() == (-5, 4)
+ assert ax.get_ylim() == (5, -3)
+
+ # Test inverting nonlinear axes.
+ fig, ax = plt.subplots()
+ ax.set_yscale("log")
+ ax.set_ylim(10, 1)
+ assert ax.get_ylim() == (10, 1)
+
+
+@image_comparison(['nonfinite_limits'])
+def test_nonfinite_limits():
+ x = np.arange(0., np.e, 0.01)
+ # silence divide by zero warning from log(0)
+ with np.errstate(divide='ignore'):
+ y = np.log(x)
+ x[len(x)//2] = np.nan
+ fig, ax = plt.subplots()
+ ax.plot(x, y)
+
+
+@mpl.style.context('default')
+@pytest.mark.parametrize('plot_fun',
+ ['scatter', 'plot', 'fill_between'])
+@check_figures_equal(extensions=["png"])
+def test_limits_empty_data(plot_fun, fig_test, fig_ref):
+ # Check that plotting empty data doesn't change autoscaling of dates
+ x = np.arange("2010-01-01", "2011-01-01", dtype="datetime64[D]")
+
+ ax_test = fig_test.subplots()
+ ax_ref = fig_ref.subplots()
+
+ getattr(ax_test, plot_fun)([], [])
+
+ for ax in [ax_test, ax_ref]:
+ getattr(ax, plot_fun)(x, range(len(x)), color='C0')
+
+
+@image_comparison(['imshow', 'imshow'], remove_text=True, style='mpl20')
+def test_imshow():
+ # use former defaults to match existing baseline image
+ matplotlib.rcParams['image.interpolation'] = 'nearest'
+ # Create a NxN image
+ N = 100
+ (x, y) = np.indices((N, N))
+ x -= N//2
+ y -= N//2
+ r = np.sqrt(x**2+y**2-x*y)
+
+ # Create a contour plot at N/4 and extract both the clip path and transform
+ fig, ax = plt.subplots()
+ ax.imshow(r)
+
+ # Reuse testcase from above for a labeled data test
+ data = {"r": r}
+ fig, ax = plt.subplots()
+ ax.imshow("r", data=data)
+
+
+@image_comparison(['imshow_clip'], style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 1.24)
+def test_imshow_clip():
+ # As originally reported by Gellule Xg
+ # use former defaults to match existing baseline image
+ matplotlib.rcParams['image.interpolation'] = 'nearest'
+
+ # Create a NxN image
+ N = 100
+ (x, y) = np.indices((N, N))
+ x -= N//2
+ y -= N//2
+ r = np.sqrt(x**2+y**2-x*y)
+
+ # Create a contour plot at N/4 and extract both the clip path and transform
+ fig, ax = plt.subplots()
+
+ c = ax.contour(r, [N/4])
+ clip_path = mtransforms.TransformedPath(c.get_paths()[0], c.get_transform())
+
+ # Plot the image clipped by the contour
+ ax.imshow(r, clip_path=clip_path)
+
+
+def test_imshow_norm_vminvmax():
+ """Parameters vmin, vmax should error if norm is given."""
+ a = [[1, 2], [3, 4]]
+ ax = plt.axes()
+ with pytest.raises(ValueError,
+ match="Passing a Normalize instance simultaneously "
+ "with vmin/vmax is not supported."):
+ ax.imshow(a, norm=mcolors.Normalize(-10, 10), vmin=0, vmax=5)
+
+
+@image_comparison(['polycollection_joinstyle'], remove_text=True)
+def test_polycollection_joinstyle():
+ # Bug #2890979 reported by Matthew West
+ fig, ax = plt.subplots()
+ verts = np.array([[1, 1], [1, 2], [2, 2], [2, 1]])
+ c = mpl.collections.PolyCollection([verts], linewidths=40)
+ ax.add_collection(c)
+ ax.set_xbound(0, 3)
+ ax.set_ybound(0, 3)
+
+
+@pytest.mark.parametrize(
+ 'x, y1, y2', [
+ (np.zeros((2, 2)), 3, 3),
+ (np.arange(0.0, 2, 0.02), np.zeros((2, 2)), 3),
+ (np.arange(0.0, 2, 0.02), 3, np.zeros((2, 2)))
+ ], ids=[
+ '2d_x_input',
+ '2d_y1_input',
+ '2d_y2_input'
+ ]
+)
+def test_fill_between_input(x, y1, y2):
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.fill_between(x, y1, y2)
+
+
+@pytest.mark.parametrize(
+ 'y, x1, x2', [
+ (np.zeros((2, 2)), 3, 3),
+ (np.arange(0.0, 2, 0.02), np.zeros((2, 2)), 3),
+ (np.arange(0.0, 2, 0.02), 3, np.zeros((2, 2)))
+ ], ids=[
+ '2d_y_input',
+ '2d_x1_input',
+ '2d_x2_input'
+ ]
+)
+def test_fill_betweenx_input(y, x1, x2):
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.fill_betweenx(y, x1, x2)
+
+
+@image_comparison(['fill_between_interpolate.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.012)
+def test_fill_between_interpolate():
+ x = np.arange(0.0, 2, 0.02)
+ y1 = np.sin(2*np.pi*x)
+ y2 = 1.2*np.sin(4*np.pi*x)
+
+ fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
+ ax1.plot(x, y1, x, y2, color='black')
+ ax1.fill_between(x, y1, y2, where=y2 >= y1, facecolor='white', hatch='/',
+ interpolate=True)
+ ax1.fill_between(x, y1, y2, where=y2 <= y1, facecolor='red',
+ interpolate=True)
+
+ # Test support for masked arrays.
+ y2 = np.ma.masked_greater(y2, 1.0)
+ # Test that plotting works for masked arrays with the first element masked
+ y2[0] = np.ma.masked
+ ax2.plot(x, y1, x, y2, color='black')
+ ax2.fill_between(x, y1, y2, where=y2 >= y1, facecolor='green',
+ interpolate=True)
+ ax2.fill_between(x, y1, y2, where=y2 <= y1, facecolor='red',
+ interpolate=True)
+
+
+@image_comparison(['fill_between_interpolate_decreasing.png'],
+ style='mpl20', remove_text=True)
+def test_fill_between_interpolate_decreasing():
+ p = np.array([724.3, 700, 655])
+ t = np.array([9.4, 7, 2.2])
+ prof = np.array([7.9, 6.6, 3.8])
+
+ fig, ax = plt.subplots(figsize=(9, 9))
+
+ ax.plot(t, p, 'tab:red')
+ ax.plot(prof, p, 'k')
+
+ ax.fill_betweenx(p, t, prof, where=prof < t,
+ facecolor='blue', interpolate=True, alpha=0.4)
+ ax.fill_betweenx(p, t, prof, where=prof > t,
+ facecolor='red', interpolate=True, alpha=0.4)
+
+ ax.set_xlim(0, 30)
+ ax.set_ylim(800, 600)
+
+
+@image_comparison(['fill_between_interpolate_nan.png'], remove_text=True)
+def test_fill_between_interpolate_nan():
+ # Tests fix for issue #18986.
+ x = np.arange(10)
+ y1 = np.asarray([8, 18, np.nan, 18, 8, 18, 24, 18, 8, 18])
+ y2 = np.asarray([18, 11, 8, 11, 18, 26, 32, 30, np.nan, np.nan])
+
+ fig, ax = plt.subplots()
+
+ ax.plot(x, y1, c='k')
+ ax.plot(x, y2, c='b')
+ ax.fill_between(x, y1, y2, where=y2 >= y1, facecolor="green",
+ interpolate=True, alpha=0.5)
+ ax.fill_between(x, y1, y2, where=y1 >= y2, facecolor="red",
+ interpolate=True, alpha=0.5)
+
+
+# test_symlog and test_symlog2 used to have baseline images in all three
+# formats, but the png and svg baselines got invalidated by the removal of
+# minor tick overstriking.
+@image_comparison(['symlog.pdf'])
+def test_symlog():
+ x = np.array([0, 1, 2, 4, 6, 9, 12, 24])
+ y = np.array([1000000, 500000, 100000, 100, 5, 0, 0, 0])
+
+ fig, ax = plt.subplots()
+ ax.plot(x, y)
+ ax.set_yscale('symlog')
+ ax.set_xscale('linear')
+ ax.set_ylim(-1, 10000000)
+
+
+@image_comparison(['symlog2.pdf'], remove_text=True)
+def test_symlog2():
+ # Numbers from -50 to 50, with 0.1 as step
+ x = np.arange(-50, 50, 0.001)
+
+ fig, axs = plt.subplots(5, 1)
+ for ax, linthresh in zip(axs, [20., 2., 1., 0.1, 0.01]):
+ ax.plot(x, x)
+ ax.set_xscale('symlog', linthresh=linthresh)
+ ax.grid(True)
+ axs[-1].set_ylim(-0.1, 0.1)
+
+
+def test_pcolorargs_5205():
+ # Smoketest to catch issue found in gh:5205
+ x = [-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5]
+ y = [-1.5, -1.25, -1.0, -0.75, -0.5, -0.25, 0,
+ 0.25, 0.5, 0.75, 1.0, 1.25, 1.5]
+ X, Y = np.meshgrid(x, y)
+ Z = np.hypot(X, Y)
+
+ plt.pcolor(Z)
+ plt.pcolor(list(Z))
+ plt.pcolor(x, y, Z[:-1, :-1])
+ plt.pcolor(X, Y, list(Z[:-1, :-1]))
+
+
+@image_comparison(['pcolormesh'], remove_text=True)
+def test_pcolormesh():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ n = 12
+ x = np.linspace(-1.5, 1.5, n)
+ y = np.linspace(-1.5, 1.5, n*2)
+ X, Y = np.meshgrid(x, y)
+ Qx = np.cos(Y) - np.cos(X)
+ Qz = np.sin(Y) + np.sin(X)
+ Qx = (Qx + 1.1)
+ Z = np.hypot(X, Y) / 5
+ Z = (Z - Z.min()) / np.ptp(Z)
+
+ # The color array can include masked values:
+ Zm = ma.masked_where(np.abs(Qz) < 0.5 * np.max(Qz), Z)
+
+ _, (ax1, ax2, ax3) = plt.subplots(1, 3)
+ ax1.pcolormesh(Qx, Qz, Zm[:-1, :-1], lw=0.5, edgecolors='k')
+ ax2.pcolormesh(Qx, Qz, Zm[:-1, :-1], lw=2, edgecolors=['b', 'w'])
+ ax3.pcolormesh(Qx, Qz, Zm, shading="gouraud")
+
+
+@image_comparison(['pcolormesh_small'], extensions=["eps"])
+def test_pcolormesh_small():
+ n = 3
+ x = np.linspace(-1.5, 1.5, n)
+ y = np.linspace(-1.5, 1.5, n*2)
+ X, Y = np.meshgrid(x, y)
+ Qx = np.cos(Y) - np.cos(X)
+ Qz = np.sin(Y) + np.sin(X)
+ Qx = (Qx + 1.1)
+ Z = np.hypot(X, Y) / 5
+ Z = (Z - Z.min()) / np.ptp(Z)
+ Zm = ma.masked_where(np.abs(Qz) < 0.5 * np.max(Qz), Z)
+ Zm2 = ma.masked_where(Qz < -0.5 * np.max(Qz), Z)
+
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
+ ax1.pcolormesh(Qx, Qz, Zm[:-1, :-1], lw=0.5, edgecolors='k')
+ ax2.pcolormesh(Qx, Qz, Zm[:-1, :-1], lw=2, edgecolors=['b', 'w'])
+ # gouraud with Zm yields a blank plot; there are no unmasked triangles.
+ ax3.pcolormesh(Qx, Qz, Zm, shading="gouraud")
+ # Reduce the masking to get a plot.
+ ax4.pcolormesh(Qx, Qz, Zm2, shading="gouraud")
+
+ for ax in fig.axes:
+ ax.set_axis_off()
+
+
+@image_comparison(['pcolormesh_alpha'], extensions=["png", "pdf"],
+ remove_text=True)
+def test_pcolormesh_alpha():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ n = 12
+ X, Y = np.meshgrid(
+ np.linspace(-1.5, 1.5, n),
+ np.linspace(-1.5, 1.5, n*2)
+ )
+ Qx = X
+ Qy = Y + np.sin(X)
+ Z = np.hypot(X, Y) / 5
+ Z = (Z - Z.min()) / np.ptp(Z)
+ vir = mpl.colormaps["viridis"].resampled(16)
+ # make another colormap with varying alpha
+ colors = vir(np.arange(16))
+ colors[:, 3] = 0.5 + 0.5*np.sin(np.arange(16))
+ cmap = mcolors.ListedColormap(colors)
+
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
+ for ax in ax1, ax2, ax3, ax4:
+ ax.add_patch(mpatches.Rectangle(
+ (0, -1.5), 1.5, 3, facecolor=[.7, .1, .1, .5], zorder=0
+ ))
+ # ax1, ax2: constant alpha
+ ax1.pcolormesh(Qx, Qy, Z[:-1, :-1], cmap=vir, alpha=0.4,
+ shading='flat', zorder=1)
+ ax2.pcolormesh(Qx, Qy, Z, cmap=vir, alpha=0.4, shading='gouraud', zorder=1)
+ # ax3, ax4: alpha from colormap
+ ax3.pcolormesh(Qx, Qy, Z[:-1, :-1], cmap=cmap, shading='flat', zorder=1)
+ ax4.pcolormesh(Qx, Qy, Z, cmap=cmap, shading='gouraud', zorder=1)
+
+
+@pytest.mark.parametrize("dims,alpha", [(3, 1), (4, 0.5)])
+@check_figures_equal(extensions=["png"])
+def test_pcolormesh_rgba(fig_test, fig_ref, dims, alpha):
+ ax = fig_test.subplots()
+ c = np.ones((5, 6, dims), dtype=float) / 2
+ ax.pcolormesh(c)
+
+ ax = fig_ref.subplots()
+ ax.pcolormesh(c[..., 0], cmap="gray", vmin=0, vmax=1, alpha=alpha)
+
+
+@check_figures_equal(extensions=["png"])
+def test_pcolormesh_nearest_noargs(fig_test, fig_ref):
+ x = np.arange(4)
+ y = np.arange(7)
+ X, Y = np.meshgrid(x, y)
+ C = X + Y
+
+ ax = fig_test.subplots()
+ ax.pcolormesh(C, shading="nearest")
+
+ ax = fig_ref.subplots()
+ ax.pcolormesh(x, y, C, shading="nearest")
+
+
+@image_comparison(['pcolormesh_datetime_axis.png'], style='mpl20')
+def test_pcolormesh_datetime_axis():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ fig = plt.figure()
+ fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15)
+ base = datetime.datetime(2013, 1, 1)
+ x = np.array([base + datetime.timedelta(days=d) for d in range(21)])
+ y = np.arange(21)
+ z1, z2 = np.meshgrid(np.arange(20), np.arange(20))
+ z = z1 * z2
+ plt.subplot(221)
+ plt.pcolormesh(x[:-1], y[:-1], z[:-1, :-1])
+ plt.subplot(222)
+ plt.pcolormesh(x, y, z)
+ x = np.repeat(x[np.newaxis], 21, axis=0)
+ y = np.repeat(y[:, np.newaxis], 21, axis=1)
+ plt.subplot(223)
+ plt.pcolormesh(x[:-1, :-1], y[:-1, :-1], z[:-1, :-1])
+ plt.subplot(224)
+ plt.pcolormesh(x, y, z)
+ for ax in fig.get_axes():
+ for label in ax.get_xticklabels():
+ label.set_ha('right')
+ label.set_rotation(30)
+
+
+@image_comparison(['pcolor_datetime_axis.png'], style='mpl20')
+def test_pcolor_datetime_axis():
+ fig = plt.figure()
+ fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15)
+ base = datetime.datetime(2013, 1, 1)
+ x = np.array([base + datetime.timedelta(days=d) for d in range(21)])
+ y = np.arange(21)
+ z1, z2 = np.meshgrid(np.arange(20), np.arange(20))
+ z = z1 * z2
+ plt.subplot(221)
+ plt.pcolor(x[:-1], y[:-1], z[:-1, :-1])
+ plt.subplot(222)
+ plt.pcolor(x, y, z)
+ x = np.repeat(x[np.newaxis], 21, axis=0)
+ y = np.repeat(y[:, np.newaxis], 21, axis=1)
+ plt.subplot(223)
+ plt.pcolor(x[:-1, :-1], y[:-1, :-1], z[:-1, :-1])
+ plt.subplot(224)
+ plt.pcolor(x, y, z)
+ for ax in fig.get_axes():
+ for label in ax.get_xticklabels():
+ label.set_ha('right')
+ label.set_rotation(30)
+
+
+def test_pcolorargs():
+ n = 12
+ x = np.linspace(-1.5, 1.5, n)
+ y = np.linspace(-1.5, 1.5, n*2)
+ X, Y = np.meshgrid(x, y)
+ Z = np.hypot(X, Y) / 5
+
+ _, ax = plt.subplots()
+ with pytest.raises(TypeError):
+ ax.pcolormesh(y, x, Z)
+ with pytest.raises(TypeError):
+ ax.pcolormesh(X, Y, Z.T)
+ with pytest.raises(TypeError):
+ ax.pcolormesh(x, y, Z[:-1, :-1], shading="gouraud")
+ with pytest.raises(TypeError):
+ ax.pcolormesh(X, Y, Z[:-1, :-1], shading="gouraud")
+ x[0] = np.nan
+ with pytest.raises(ValueError):
+ ax.pcolormesh(x, y, Z[:-1, :-1])
+ with np.errstate(invalid='ignore'):
+ x = np.ma.array(x, mask=(x < 0))
+ with pytest.raises(ValueError):
+ ax.pcolormesh(x, y, Z[:-1, :-1])
+ # If the X or Y coords do not possess monotonicity in their respective
+ # directions, a warning indicating a bad grid will be triggered.
+ # The case of specifying coordinates by inputting 1D arrays.
+ x = [359, 0, 1]
+ y = [-10, 10]
+ X, Y = np.meshgrid(x, y)
+ Z = np.zeros(X.shape)
+ with pytest.warns(UserWarning,
+ match='are not monotonically increasing or decreasing'):
+ ax.pcolormesh(X, Y, Z, shading='auto')
+ # The case of specifying coordinates by inputting 2D arrays.
+ x = np.linspace(-1, 1, 3)
+ y = np.linspace(-1, 1, 3)
+ X, Y = np.meshgrid(x, y)
+ Z = np.zeros(X.shape)
+ np.random.seed(19680801)
+ noise_X = np.random.random(X.shape)
+ noise_Y = np.random.random(Y.shape)
+ with pytest.warns(UserWarning,
+ match='are not monotonically increasing or '
+ 'decreasing') as record:
+ # Small perturbations in coordinates will not disrupt the monotonicity
+ # of the X-coords and Y-coords in their respective directions.
+ # Therefore, no warnings will be triggered.
+ ax.pcolormesh(X+noise_X, Y+noise_Y, Z, shading='auto')
+ assert len(record) == 0
+ # Large perturbations have disrupted the monotonicity of the X-coords
+ # and Y-coords in their respective directions, thus resulting in two
+ # bad grid warnings.
+ ax.pcolormesh(X+10*noise_X, Y+10*noise_Y, Z, shading='auto')
+ assert len(record) == 2
+
+
+def test_pcolormesh_underflow_error():
+ """
+ Test that underflow errors don't crop up in pcolormesh. Probably
+ a numpy bug (https://github.com/numpy/numpy/issues/25810).
+ """
+ with np.errstate(under="raise"):
+ x = np.arange(0, 3, 0.1)
+ y = np.arange(0, 6, 0.1)
+ z = np.random.randn(len(y), len(x))
+ fig, ax = plt.subplots()
+ ax.pcolormesh(x, y, z)
+
+
+def test_pcolorargs_with_read_only():
+ x = np.arange(6).reshape(2, 3)
+ xmask = np.broadcast_to([False, True, False], x.shape) # read-only array
+ assert xmask.flags.writeable is False
+ masked_x = np.ma.array(x, mask=xmask)
+ plt.pcolormesh(masked_x)
+
+ x = np.linspace(0, 1, 10)
+ y = np.linspace(0, 1, 10)
+ X, Y = np.meshgrid(x, y)
+ Z = np.sin(2 * np.pi * X) * np.cos(2 * np.pi * Y)
+ mask = np.zeros(10, dtype=bool)
+ mask[-1] = True
+ mask = np.broadcast_to(mask, Z.shape)
+ assert mask.flags.writeable is False
+ masked_Z = np.ma.array(Z, mask=mask)
+ plt.pcolormesh(X, Y, masked_Z)
+
+ masked_X = np.ma.array(X, mask=mask)
+ masked_Y = np.ma.array(Y, mask=mask)
+ plt.pcolor(masked_X, masked_Y, masked_Z)
+
+
+@check_figures_equal(extensions=["png"])
+def test_pcolornearest(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ x = np.arange(0, 10)
+ y = np.arange(0, 3)
+ np.random.seed(19680801)
+ Z = np.random.randn(2, 9)
+ ax.pcolormesh(x, y, Z, shading='flat')
+
+ ax = fig_ref.subplots()
+ # specify the centers
+ x2 = x[:-1] + np.diff(x) / 2
+ y2 = y[:-1] + np.diff(y) / 2
+ ax.pcolormesh(x2, y2, Z, shading='nearest')
+
+
+@check_figures_equal(extensions=["png"])
+def test_pcolornearestunits(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ x = [datetime.datetime.fromtimestamp(x * 3600) for x in range(10)]
+ y = np.arange(0, 3)
+ np.random.seed(19680801)
+ Z = np.random.randn(2, 9)
+ ax.pcolormesh(x, y, Z, shading='flat')
+
+ ax = fig_ref.subplots()
+ # specify the centers
+ x2 = [datetime.datetime.fromtimestamp((x + 0.5) * 3600) for x in range(9)]
+ y2 = y[:-1] + np.diff(y) / 2
+ ax.pcolormesh(x2, y2, Z, shading='nearest')
+
+
+def test_pcolorflaterror():
+ fig, ax = plt.subplots()
+ x = np.arange(0, 9)
+ y = np.arange(0, 3)
+ np.random.seed(19680801)
+ Z = np.random.randn(3, 9)
+ with pytest.raises(TypeError, match='Dimensions of C'):
+ ax.pcolormesh(x, y, Z, shading='flat')
+
+
+def test_samesizepcolorflaterror():
+ fig, ax = plt.subplots()
+ x, y = np.meshgrid(np.arange(5), np.arange(3))
+ Z = x + y
+ with pytest.raises(TypeError, match=r".*one smaller than X"):
+ ax.pcolormesh(x, y, Z, shading='flat')
+
+
+@pytest.mark.parametrize('snap', [False, True])
+@check_figures_equal(extensions=["png"])
+def test_pcolorauto(fig_test, fig_ref, snap):
+ ax = fig_test.subplots()
+ x = np.arange(0, 10)
+ y = np.arange(0, 4)
+ np.random.seed(19680801)
+ Z = np.random.randn(3, 9)
+ # this is the same as flat; note that auto is default
+ ax.pcolormesh(x, y, Z, snap=snap)
+
+ ax = fig_ref.subplots()
+ # specify the centers
+ x2 = x[:-1] + np.diff(x) / 2
+ y2 = y[:-1] + np.diff(y) / 2
+ # this is same as nearest:
+ ax.pcolormesh(x2, y2, Z, snap=snap)
+
+
+@image_comparison(['canonical'],
+ tol=0 if platform.machine() == 'x86_64' else 0.02)
+def test_canonical():
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3])
+
+
+@image_comparison(['arc_angles.png'], remove_text=True, style='default')
+def test_arc_angles():
+ # Ellipse parameters
+ w = 2
+ h = 1
+ centre = (0.2, 0.5)
+ scale = 2
+
+ fig, axs = plt.subplots(3, 3)
+ for i, ax in enumerate(axs.flat):
+ theta2 = i * 360 / 9
+ theta1 = theta2 - 45
+
+ ax.add_patch(mpatches.Ellipse(centre, w, h, alpha=0.3))
+ ax.add_patch(mpatches.Arc(centre, w, h, theta1=theta1, theta2=theta2))
+ # Straight lines intersecting start and end of arc
+ ax.plot([scale * np.cos(np.deg2rad(theta1)) + centre[0],
+ centre[0],
+ scale * np.cos(np.deg2rad(theta2)) + centre[0]],
+ [scale * np.sin(np.deg2rad(theta1)) + centre[1],
+ centre[1],
+ scale * np.sin(np.deg2rad(theta2)) + centre[1]])
+
+ ax.set_xlim(-scale, scale)
+ ax.set_ylim(-scale, scale)
+
+ # This looks the same, but it triggers a different code path when it
+ # gets large enough.
+ w *= 10
+ h *= 10
+ centre = (centre[0] * 10, centre[1] * 10)
+ scale *= 10
+
+
+@image_comparison(['arc_ellipse'], remove_text=True)
+def test_arc_ellipse():
+ xcenter, ycenter = 0.38, 0.52
+ width, height = 1e-1, 3e-1
+ angle = -30
+
+ theta = np.deg2rad(np.arange(360))
+ x = width / 2. * np.cos(theta)
+ y = height / 2. * np.sin(theta)
+
+ rtheta = np.deg2rad(angle)
+ R = np.array([
+ [np.cos(rtheta), -np.sin(rtheta)],
+ [np.sin(rtheta), np.cos(rtheta)]])
+
+ x, y = np.dot(R, [x, y])
+ x += xcenter
+ y += ycenter
+
+ fig = plt.figure()
+ ax = fig.add_subplot(211, aspect='auto')
+ ax.fill(x, y, alpha=0.2, facecolor='yellow', edgecolor='yellow',
+ linewidth=1, zorder=1)
+
+ e1 = mpatches.Arc((xcenter, ycenter), width, height,
+ angle=angle, linewidth=2, fill=False, zorder=2)
+
+ ax.add_patch(e1)
+
+ ax = fig.add_subplot(212, aspect='equal')
+ ax.fill(x, y, alpha=0.2, facecolor='green', edgecolor='green', zorder=1)
+ e2 = mpatches.Arc((xcenter, ycenter), width, height,
+ angle=angle, linewidth=2, fill=False, zorder=2)
+
+ ax.add_patch(e2)
+
+
+def test_marker_as_markerstyle():
+ fix, ax = plt.subplots()
+ m = mmarkers.MarkerStyle('o')
+ ax.plot([1, 2, 3], [3, 2, 1], marker=m)
+ ax.scatter([1, 2, 3], [4, 3, 2], marker=m)
+ ax.errorbar([1, 2, 3], [5, 4, 3], marker=m)
+
+
+@image_comparison(['markevery.png'], remove_text=True)
+def test_markevery():
+ x = np.linspace(0, 10, 100)
+ y = np.sin(x) * np.sqrt(x/10 + 0.5)
+
+ # check marker only plot
+ fig, ax = plt.subplots()
+ ax.plot(x, y, 'o', label='default')
+ ax.plot(x, y, 'd', markevery=None, label='mark all')
+ ax.plot(x, y, 's', markevery=10, label='mark every 10')
+ ax.plot(x, y, '+', markevery=(5, 20), label='mark every 5 starting at 10')
+ ax.legend()
+
+
+@image_comparison(['markevery_line.png'], remove_text=True, tol=0.005)
+def test_markevery_line():
+ # TODO: a slight change in rendering between Inkscape versions may explain
+ # why one had to introduce a small non-zero tolerance for the SVG test
+ # to pass. One may try to remove this hack once Travis' Inkscape version
+ # is modern enough. FWIW, no failure with 0.92.3 on my computer (#11358).
+ x = np.linspace(0, 10, 100)
+ y = np.sin(x) * np.sqrt(x/10 + 0.5)
+
+ # check line/marker combos
+ fig, ax = plt.subplots()
+ ax.plot(x, y, '-o', label='default')
+ ax.plot(x, y, '-d', markevery=None, label='mark all')
+ ax.plot(x, y, '-s', markevery=10, label='mark every 10')
+ ax.plot(x, y, '-+', markevery=(5, 20), label='mark every 5 starting at 10')
+ ax.legend()
+
+
+@image_comparison(['markevery_linear_scales.png'], remove_text=True, tol=0.001)
+def test_markevery_linear_scales():
+ cases = [None,
+ 8,
+ (30, 8),
+ [16, 24, 30], [0, -1],
+ slice(100, 200, 3),
+ 0.1, 0.3, 1.5,
+ (0.0, 0.1), (0.45, 0.1)]
+
+ cols = 3
+ gs = matplotlib.gridspec.GridSpec(len(cases) // cols + 1, cols)
+
+ delta = 0.11
+ x = np.linspace(0, 10 - 2 * delta, 200) + delta
+ y = np.sin(x) + 1.0 + delta
+
+ for i, case in enumerate(cases):
+ row = (i // cols)
+ col = i % cols
+ plt.subplot(gs[row, col])
+ plt.title('markevery=%s' % str(case))
+ plt.plot(x, y, 'o', ls='-', ms=4, markevery=case)
+
+
+@image_comparison(['markevery_linear_scales_zoomed.png'], remove_text=True)
+def test_markevery_linear_scales_zoomed():
+ cases = [None,
+ 8,
+ (30, 8),
+ [16, 24, 30], [0, -1],
+ slice(100, 200, 3),
+ 0.1, 0.3, 1.5,
+ (0.0, 0.1), (0.45, 0.1)]
+
+ cols = 3
+ gs = matplotlib.gridspec.GridSpec(len(cases) // cols + 1, cols)
+
+ delta = 0.11
+ x = np.linspace(0, 10 - 2 * delta, 200) + delta
+ y = np.sin(x) + 1.0 + delta
+
+ for i, case in enumerate(cases):
+ row = (i // cols)
+ col = i % cols
+ plt.subplot(gs[row, col])
+ plt.title('markevery=%s' % str(case))
+ plt.plot(x, y, 'o', ls='-', ms=4, markevery=case)
+ plt.xlim((6, 6.7))
+ plt.ylim((1.1, 1.7))
+
+
+@image_comparison(['markevery_log_scales.png'], remove_text=True)
+def test_markevery_log_scales():
+ cases = [None,
+ 8,
+ (30, 8),
+ [16, 24, 30], [0, -1],
+ slice(100, 200, 3),
+ 0.1, 0.3, 1.5,
+ (0.0, 0.1), (0.45, 0.1)]
+
+ cols = 3
+ gs = matplotlib.gridspec.GridSpec(len(cases) // cols + 1, cols)
+
+ delta = 0.11
+ x = np.linspace(0, 10 - 2 * delta, 200) + delta
+ y = np.sin(x) + 1.0 + delta
+
+ for i, case in enumerate(cases):
+ row = (i // cols)
+ col = i % cols
+ plt.subplot(gs[row, col])
+ plt.title('markevery=%s' % str(case))
+ plt.xscale('log')
+ plt.yscale('log')
+ plt.plot(x, y, 'o', ls='-', ms=4, markevery=case)
+
+
+@image_comparison(['markevery_polar.png'], style='default', remove_text=True)
+def test_markevery_polar():
+ cases = [None,
+ 8,
+ (30, 8),
+ [16, 24, 30], [0, -1],
+ slice(100, 200, 3),
+ 0.1, 0.3, 1.5,
+ (0.0, 0.1), (0.45, 0.1)]
+
+ cols = 3
+ gs = matplotlib.gridspec.GridSpec(len(cases) // cols + 1, cols)
+
+ r = np.linspace(0, 3.0, 200)
+ theta = 2 * np.pi * r
+
+ for i, case in enumerate(cases):
+ row = (i // cols)
+ col = i % cols
+ plt.subplot(gs[row, col], polar=True)
+ plt.title('markevery=%s' % str(case))
+ plt.plot(theta, r, 'o', ls='-', ms=4, markevery=case)
+
+
+@image_comparison(['markevery_linear_scales_nans.png'], remove_text=True)
+def test_markevery_linear_scales_nans():
+ cases = [None,
+ 8,
+ (30, 8),
+ [16, 24, 30], [0, -1],
+ slice(100, 200, 3),
+ 0.1, 0.3, 1.5,
+ (0.0, 0.1), (0.45, 0.1)]
+
+ cols = 3
+ gs = matplotlib.gridspec.GridSpec(len(cases) // cols + 1, cols)
+
+ delta = 0.11
+ x = np.linspace(0, 10 - 2 * delta, 200) + delta
+ y = np.sin(x) + 1.0 + delta
+ y[:10] = y[-20:] = y[50:70] = np.nan
+
+ for i, case in enumerate(cases):
+ row = (i // cols)
+ col = i % cols
+ plt.subplot(gs[row, col])
+ plt.title('markevery=%s' % str(case))
+ plt.plot(x, y, 'o', ls='-', ms=4, markevery=case)
+
+
+@image_comparison(['marker_edges'], remove_text=True)
+def test_marker_edges():
+ x = np.linspace(0, 1, 10)
+ fig, ax = plt.subplots()
+ ax.plot(x, np.sin(x), 'y.', ms=30.0, mew=0, mec='r')
+ ax.plot(x+0.1, np.sin(x), 'y.', ms=30.0, mew=1, mec='r')
+ ax.plot(x+0.2, np.sin(x), 'y.', ms=30.0, mew=2, mec='b')
+
+
+@image_comparison(['bar_tick_label_single.png', 'bar_tick_label_single.png'])
+def test_bar_tick_label_single():
+ # From 2516: plot bar with array of string labels for x axis
+ ax = plt.gca()
+ ax.bar(0, 1, align='edge', tick_label='0')
+
+ # Reuse testcase from above for a labeled data test
+ data = {"a": 0, "b": 1}
+ fig, ax = plt.subplots()
+ ax = plt.gca()
+ ax.bar("a", "b", align='edge', tick_label='0', data=data)
+
+
+def test_nan_bar_values():
+ fig, ax = plt.subplots()
+ ax.bar([0, 1], [np.nan, 4])
+
+
+def test_bar_ticklabel_fail():
+ fig, ax = plt.subplots()
+ ax.bar([], [])
+
+
+@image_comparison(['bar_tick_label_multiple.png'])
+def test_bar_tick_label_multiple():
+ # From 2516: plot bar with array of string labels for x axis
+ ax = plt.gca()
+ ax.bar([1, 2.5], [1, 2], width=[0.2, 0.5], tick_label=['a', 'b'],
+ align='center')
+
+
+@image_comparison(['bar_tick_label_multiple_old_label_alignment.png'])
+def test_bar_tick_label_multiple_old_alignment():
+ # Test that the alignment for class is backward compatible
+ matplotlib.rcParams["ytick.alignment"] = "center"
+ ax = plt.gca()
+ ax.bar([1, 2.5], [1, 2], width=[0.2, 0.5], tick_label=['a', 'b'],
+ align='center')
+
+
+@check_figures_equal(extensions=["png"])
+def test_bar_decimal_center(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ x0 = [1.5, 8.4, 5.3, 4.2]
+ y0 = [1.1, 2.2, 3.3, 4.4]
+ x = [Decimal(x) for x in x0]
+ y = [Decimal(y) for y in y0]
+ # Test image - vertical, align-center bar chart with Decimal() input
+ ax.bar(x, y, align='center')
+ # Reference image
+ ax = fig_ref.subplots()
+ ax.bar(x0, y0, align='center')
+
+
+@check_figures_equal(extensions=["png"])
+def test_barh_decimal_center(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ x0 = [1.5, 8.4, 5.3, 4.2]
+ y0 = [1.1, 2.2, 3.3, 4.4]
+ x = [Decimal(x) for x in x0]
+ y = [Decimal(y) for y in y0]
+ # Test image - horizontal, align-center bar chart with Decimal() input
+ ax.barh(x, y, height=[0.5, 0.5, 1, 1], align='center')
+ # Reference image
+ ax = fig_ref.subplots()
+ ax.barh(x0, y0, height=[0.5, 0.5, 1, 1], align='center')
+
+
+@check_figures_equal(extensions=["png"])
+def test_bar_decimal_width(fig_test, fig_ref):
+ x = [1.5, 8.4, 5.3, 4.2]
+ y = [1.1, 2.2, 3.3, 4.4]
+ w0 = [0.7, 1.45, 1, 2]
+ w = [Decimal(i) for i in w0]
+ # Test image - vertical bar chart with Decimal() width
+ ax = fig_test.subplots()
+ ax.bar(x, y, width=w, align='center')
+ # Reference image
+ ax = fig_ref.subplots()
+ ax.bar(x, y, width=w0, align='center')
+
+
+@check_figures_equal(extensions=["png"])
+def test_barh_decimal_height(fig_test, fig_ref):
+ x = [1.5, 8.4, 5.3, 4.2]
+ y = [1.1, 2.2, 3.3, 4.4]
+ h0 = [0.7, 1.45, 1, 2]
+ h = [Decimal(i) for i in h0]
+ # Test image - horizontal bar chart with Decimal() height
+ ax = fig_test.subplots()
+ ax.barh(x, y, height=h, align='center')
+ # Reference image
+ ax = fig_ref.subplots()
+ ax.barh(x, y, height=h0, align='center')
+
+
+def test_bar_color_none_alpha():
+ ax = plt.gca()
+ rects = ax.bar([1, 2], [2, 4], alpha=0.3, color='none', edgecolor='r')
+ for rect in rects:
+ assert rect.get_facecolor() == (0, 0, 0, 0)
+ assert rect.get_edgecolor() == (1, 0, 0, 0.3)
+
+
+def test_bar_edgecolor_none_alpha():
+ ax = plt.gca()
+ rects = ax.bar([1, 2], [2, 4], alpha=0.3, color='r', edgecolor='none')
+ for rect in rects:
+ assert rect.get_facecolor() == (1, 0, 0, 0.3)
+ assert rect.get_edgecolor() == (0, 0, 0, 0)
+
+
+@image_comparison(['barh_tick_label.png'])
+def test_barh_tick_label():
+ # From 2516: plot barh with array of string labels for y axis
+ ax = plt.gca()
+ ax.barh([1, 2.5], [1, 2], height=[0.2, 0.5], tick_label=['a', 'b'],
+ align='center')
+
+
+def test_bar_timedelta():
+ """Smoketest that bar can handle width and height in delta units."""
+ fig, ax = plt.subplots()
+ ax.bar(datetime.datetime(2018, 1, 1), 1.,
+ width=datetime.timedelta(hours=3))
+ ax.bar(datetime.datetime(2018, 1, 1), 1.,
+ xerr=datetime.timedelta(hours=2),
+ width=datetime.timedelta(hours=3))
+ fig, ax = plt.subplots()
+ ax.barh(datetime.datetime(2018, 1, 1), 1,
+ height=datetime.timedelta(hours=3))
+ ax.barh(datetime.datetime(2018, 1, 1), 1,
+ height=datetime.timedelta(hours=3),
+ yerr=datetime.timedelta(hours=2))
+ fig, ax = plt.subplots()
+ ax.barh([datetime.datetime(2018, 1, 1), datetime.datetime(2018, 1, 1)],
+ np.array([1, 1.5]),
+ height=datetime.timedelta(hours=3))
+ ax.barh([datetime.datetime(2018, 1, 1), datetime.datetime(2018, 1, 1)],
+ np.array([1, 1.5]),
+ height=[datetime.timedelta(hours=t) for t in [1, 2]])
+ ax.broken_barh([(datetime.datetime(2018, 1, 1),
+ datetime.timedelta(hours=1))],
+ (10, 20))
+
+
+def test_bar_datetime_start():
+ """test that tickers are correct for datetimes"""
+ start = np.array([np.datetime64('2012-01-01'), np.datetime64('2012-02-01'),
+ np.datetime64('2012-01-15')])
+ stop = np.array([np.datetime64('2012-02-07'), np.datetime64('2012-02-13'),
+ np.datetime64('2012-02-12')])
+
+ fig, ax = plt.subplots()
+ ax.bar([0, 1, 3], height=stop-start, bottom=start)
+ assert isinstance(ax.yaxis.get_major_formatter(), mdates.AutoDateFormatter)
+
+ fig, ax = plt.subplots()
+ ax.barh([0, 1, 3], width=stop-start, left=start)
+ assert isinstance(ax.xaxis.get_major_formatter(), mdates.AutoDateFormatter)
+
+
+def test_boxplot_dates_pandas(pd):
+ # smoke test for boxplot and dates in pandas
+ data = np.random.rand(5, 2)
+ years = pd.date_range('1/1/2000',
+ periods=2, freq=pd.DateOffset(years=1)).year
+ plt.figure()
+ plt.boxplot(data, positions=years)
+
+
+def test_boxplot_capwidths():
+ data = np.random.rand(5, 3)
+ fig, axs = plt.subplots(9)
+
+ axs[0].boxplot(data, capwidths=[0.3, 0.2, 0.1], widths=[0.1, 0.2, 0.3])
+ axs[1].boxplot(data, capwidths=[0.3, 0.2, 0.1], widths=0.2)
+ axs[2].boxplot(data, capwidths=[0.3, 0.2, 0.1])
+
+ axs[3].boxplot(data, capwidths=0.5, widths=[0.1, 0.2, 0.3])
+ axs[4].boxplot(data, capwidths=0.5, widths=0.2)
+ axs[5].boxplot(data, capwidths=0.5)
+
+ axs[6].boxplot(data, widths=[0.1, 0.2, 0.3])
+ axs[7].boxplot(data, widths=0.2)
+ axs[8].boxplot(data)
+
+
+def test_pcolor_regression(pd):
+ from pandas.plotting import (
+ register_matplotlib_converters,
+ deregister_matplotlib_converters,
+ )
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111)
+
+ times = [datetime.datetime(2021, 1, 1)]
+ while len(times) < 7:
+ times.append(times[-1] + datetime.timedelta(seconds=120))
+
+ y_vals = np.arange(5)
+
+ time_axis, y_axis = np.meshgrid(times, y_vals)
+ shape = (len(y_vals) - 1, len(times) - 1)
+ z_data = np.arange(shape[0] * shape[1])
+
+ z_data.shape = shape
+ try:
+ register_matplotlib_converters()
+
+ im = ax.pcolormesh(time_axis, y_axis, z_data)
+ # make sure this does not raise!
+ fig.canvas.draw()
+ finally:
+ deregister_matplotlib_converters()
+
+
+def test_bar_pandas(pd):
+ # Smoke test for pandas
+ df = pd.DataFrame(
+ {'year': [2018, 2018, 2018],
+ 'month': [1, 1, 1],
+ 'day': [1, 2, 3],
+ 'value': [1, 2, 3]})
+ df['date'] = pd.to_datetime(df[['year', 'month', 'day']])
+
+ monthly = df[['date', 'value']].groupby(['date']).sum()
+ dates = monthly.index
+ forecast = monthly['value']
+ baseline = monthly['value']
+
+ fig, ax = plt.subplots()
+ ax.bar(dates, forecast, width=10, align='center')
+ ax.plot(dates, baseline, color='orange', lw=4)
+
+
+def test_bar_pandas_indexed(pd):
+ # Smoke test for indexed pandas
+ df = pd.DataFrame({"x": [1., 2., 3.], "width": [.2, .4, .6]},
+ index=[1, 2, 3])
+ fig, ax = plt.subplots()
+ ax.bar(df.x, 1., width=df.width)
+
+
+@mpl.style.context('default')
+@check_figures_equal(extensions=['png'])
+def test_bar_hatches(fig_test, fig_ref):
+ ax_test = fig_test.subplots()
+ ax_ref = fig_ref.subplots()
+
+ x = [1, 2]
+ y = [2, 3]
+ hatches = ['x', 'o']
+ for i in range(2):
+ ax_ref.bar(x[i], y[i], color='C0', hatch=hatches[i])
+
+ ax_test.bar(x, y, hatch=hatches)
+
+
+@pytest.mark.parametrize(
+ ("x", "width", "label", "expected_labels", "container_label"),
+ [
+ ("x", 1, "x", ["_nolegend_"], "x"),
+ (["a", "b", "c"], [10, 20, 15], ["A", "B", "C"],
+ ["A", "B", "C"], "_nolegend_"),
+ (["a", "b", "c"], [10, 20, 15], ["R", "Y", "_nolegend_"],
+ ["R", "Y", "_nolegend_"], "_nolegend_"),
+ (["a", "b", "c"], [10, 20, 15], "bars",
+ ["_nolegend_", "_nolegend_", "_nolegend_"], "bars"),
+ ]
+)
+def test_bar_labels(x, width, label, expected_labels, container_label):
+ _, ax = plt.subplots()
+ bar_container = ax.bar(x, width, label=label)
+ bar_labels = [bar.get_label() for bar in bar_container]
+ assert expected_labels == bar_labels
+ assert bar_container.get_label() == container_label
+
+
+def test_bar_labels_length():
+ _, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.bar(["x", "y"], [1, 2], label=["X", "Y", "Z"])
+ _, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.bar(["x", "y"], [1, 2], label=["X"])
+
+
+def test_pandas_minimal_plot(pd):
+ # smoke test that series and index objects do not warn
+ for x in [pd.Series([1, 2], dtype="float64"),
+ pd.Series([1, 2], dtype="Float64")]:
+ plt.plot(x, x)
+ plt.plot(x.index, x)
+ plt.plot(x)
+ plt.plot(x.index)
+ df = pd.DataFrame({'col': [1, 2, 3]})
+ plt.plot(df)
+ plt.plot(df, df)
+
+
+@image_comparison(['hist_log.png'], remove_text=True)
+def test_hist_log():
+ data0 = np.linspace(0, 1, 200)**3
+ data = np.concatenate([1 - data0, 1 + data0])
+ fig, ax = plt.subplots()
+ ax.hist(data, fill=False, log=True)
+
+
+@check_figures_equal(extensions=["png"])
+def test_hist_log_2(fig_test, fig_ref):
+ axs_test = fig_test.subplots(2, 3)
+ axs_ref = fig_ref.subplots(2, 3)
+ for i, histtype in enumerate(["bar", "step", "stepfilled"]):
+ # Set log scale, then call hist().
+ axs_test[0, i].set_yscale("log")
+ axs_test[0, i].hist(1, 1, histtype=histtype)
+ # Call hist(), then set log scale.
+ axs_test[1, i].hist(1, 1, histtype=histtype)
+ axs_test[1, i].set_yscale("log")
+ # Use hist(..., log=True).
+ for ax in axs_ref[:, i]:
+ ax.hist(1, 1, log=True, histtype=histtype)
+
+
+def test_hist_log_barstacked():
+ fig, axs = plt.subplots(2)
+ axs[0].hist([[0], [0, 1]], 2, histtype="barstacked")
+ axs[0].set_yscale("log")
+ axs[1].hist([0, 0, 1], 2, histtype="barstacked")
+ axs[1].set_yscale("log")
+ fig.canvas.draw()
+ assert axs[0].get_ylim() == axs[1].get_ylim()
+
+
+@image_comparison(['hist_bar_empty.png'], remove_text=True)
+def test_hist_bar_empty():
+ # From #3886: creating hist from empty dataset raises ValueError
+ ax = plt.gca()
+ ax.hist([], histtype='bar')
+
+
+def test_hist_float16():
+ np.random.seed(19680801)
+ values = np.clip(
+ np.random.normal(0.5, 0.3, size=1000), 0, 1).astype(np.float16)
+ h = plt.hist(values, bins=3, alpha=0.5)
+ bc = h[2]
+ # Check that there are no overlapping rectangles
+ for r in range(1, len(bc)):
+ rleft = bc[r-1].get_corners()
+ rright = bc[r].get_corners()
+ # right hand position of left rectangle <=
+ # left hand position of right rectangle
+ assert rleft[1][0] <= rright[0][0]
+
+
+@image_comparison(['hist_step_empty.png'], remove_text=True)
+def test_hist_step_empty():
+ # From #3886: creating hist from empty dataset raises ValueError
+ ax = plt.gca()
+ ax.hist([], histtype='step')
+
+
+@image_comparison(['hist_step_filled.png'], remove_text=True)
+def test_hist_step_filled():
+ np.random.seed(0)
+ x = np.random.randn(1000, 3)
+ n_bins = 10
+
+ kwargs = [{'fill': True}, {'fill': False}, {'fill': None}, {}]*2
+ types = ['step']*4+['stepfilled']*4
+ fig, axs = plt.subplots(nrows=2, ncols=4)
+
+ for kg, _type, ax in zip(kwargs, types, axs.flat):
+ ax.hist(x, n_bins, histtype=_type, stacked=True, **kg)
+ ax.set_title(f'{kg}/{_type}')
+ ax.set_ylim(bottom=-50)
+
+ patches = axs[0, 0].patches
+ assert all(p.get_facecolor() == p.get_edgecolor() for p in patches)
+
+
+@image_comparison(['hist_density.png'])
+def test_hist_density():
+ np.random.seed(19680801)
+ data = np.random.standard_normal(2000)
+ fig, ax = plt.subplots()
+ ax.hist(data, density=True)
+
+
+def test_hist_unequal_bins_density():
+ # Test correct behavior of normalized histogram with unequal bins
+ # https://github.com/matplotlib/matplotlib/issues/9557
+ rng = np.random.RandomState(57483)
+ t = rng.randn(100)
+ bins = [-3, -1, -0.5, 0, 1, 5]
+ mpl_heights, _, _ = plt.hist(t, bins=bins, density=True)
+ np_heights, _ = np.histogram(t, bins=bins, density=True)
+ assert_allclose(mpl_heights, np_heights)
+
+
+def test_hist_datetime_datasets():
+ data = [[datetime.datetime(2017, 1, 1), datetime.datetime(2017, 1, 1)],
+ [datetime.datetime(2017, 1, 1), datetime.datetime(2017, 1, 2)]]
+ fig, ax = plt.subplots()
+ ax.hist(data, stacked=True)
+ ax.hist(data, stacked=False)
+
+
+@pytest.mark.parametrize("bins_preprocess",
+ [mpl.dates.date2num,
+ lambda bins: bins,
+ lambda bins: np.asarray(bins, 'datetime64')],
+ ids=['date2num', 'datetime.datetime',
+ 'np.datetime64'])
+def test_hist_datetime_datasets_bins(bins_preprocess):
+ data = [[datetime.datetime(2019, 1, 5), datetime.datetime(2019, 1, 11),
+ datetime.datetime(2019, 2, 1), datetime.datetime(2019, 3, 1)],
+ [datetime.datetime(2019, 1, 11), datetime.datetime(2019, 2, 5),
+ datetime.datetime(2019, 2, 18), datetime.datetime(2019, 3, 1)]]
+
+ date_edges = [datetime.datetime(2019, 1, 1), datetime.datetime(2019, 2, 1),
+ datetime.datetime(2019, 3, 1)]
+
+ fig, ax = plt.subplots()
+ _, bins, _ = ax.hist(data, bins=bins_preprocess(date_edges), stacked=True)
+ np.testing.assert_allclose(bins, mpl.dates.date2num(date_edges))
+
+ _, bins, _ = ax.hist(data, bins=bins_preprocess(date_edges), stacked=False)
+ np.testing.assert_allclose(bins, mpl.dates.date2num(date_edges))
+
+
+@pytest.mark.parametrize('data, expected_number_of_hists',
+ [([], 1),
+ ([[]], 1),
+ ([[], []], 2)])
+def test_hist_with_empty_input(data, expected_number_of_hists):
+ hists, _, _ = plt.hist(data)
+ hists = np.asarray(hists)
+
+ if hists.ndim == 1:
+ assert 1 == expected_number_of_hists
+ else:
+ assert hists.shape[0] == expected_number_of_hists
+
+
+@pytest.mark.parametrize("histtype, zorder",
+ [("bar", mpl.patches.Patch.zorder),
+ ("step", mpl.lines.Line2D.zorder),
+ ("stepfilled", mpl.patches.Patch.zorder)])
+def test_hist_zorder(histtype, zorder):
+ ax = plt.figure().add_subplot()
+ ax.hist([1, 2], histtype=histtype)
+ assert ax.patches
+ for patch in ax.patches:
+ assert patch.get_zorder() == zorder
+
+
+def test_stairs_no_baseline_fill_warns():
+ fig, ax = plt.subplots()
+ with pytest.warns(UserWarning, match="baseline=None and fill=True"):
+ ax.stairs(
+ [4, 5, 1, 0, 2],
+ [1, 2, 3, 4, 5, 6],
+ facecolor="blue",
+ baseline=None,
+ fill=True
+ )
+
+
+@check_figures_equal(extensions=['png'])
+def test_stairs(fig_test, fig_ref):
+ import matplotlib.lines as mlines
+ y = np.array([6, 14, 32, 37, 48, 32, 21, 4]) # hist
+ x = np.array([1., 2., 3., 4., 5., 6., 7., 8., 9.]) # bins
+
+ test_axes = fig_test.subplots(3, 2).flatten()
+ test_axes[0].stairs(y, x, baseline=None)
+ test_axes[1].stairs(y, x, baseline=None, orientation='horizontal')
+ test_axes[2].stairs(y, x)
+ test_axes[3].stairs(y, x, orientation='horizontal')
+ test_axes[4].stairs(y, x)
+ test_axes[4].semilogy()
+ test_axes[5].stairs(y, x, orientation='horizontal')
+ test_axes[5].semilogx()
+
+ # defaults of `PathPatch` to be used for all following Line2D
+ style = {'solid_joinstyle': 'miter', 'solid_capstyle': 'butt'}
+
+ ref_axes = fig_ref.subplots(3, 2).flatten()
+ ref_axes[0].plot(x, np.append(y, y[-1]), drawstyle='steps-post', **style)
+ ref_axes[1].plot(np.append(y[0], y), x, drawstyle='steps-post', **style)
+
+ ref_axes[2].plot(x, np.append(y, y[-1]), drawstyle='steps-post', **style)
+ ref_axes[2].add_line(mlines.Line2D([x[0], x[0]], [0, y[0]], **style))
+ ref_axes[2].add_line(mlines.Line2D([x[-1], x[-1]], [0, y[-1]], **style))
+ ref_axes[2].set_ylim(0, None)
+
+ ref_axes[3].plot(np.append(y[0], y), x, drawstyle='steps-post', **style)
+ ref_axes[3].add_line(mlines.Line2D([0, y[0]], [x[0], x[0]], **style))
+ ref_axes[3].add_line(mlines.Line2D([0, y[-1]], [x[-1], x[-1]], **style))
+ ref_axes[3].set_xlim(0, None)
+
+ ref_axes[4].plot(x, np.append(y, y[-1]), drawstyle='steps-post', **style)
+ ref_axes[4].add_line(mlines.Line2D([x[0], x[0]], [0, y[0]], **style))
+ ref_axes[4].add_line(mlines.Line2D([x[-1], x[-1]], [0, y[-1]], **style))
+ ref_axes[4].semilogy()
+
+ ref_axes[5].plot(np.append(y[0], y), x, drawstyle='steps-post', **style)
+ ref_axes[5].add_line(mlines.Line2D([0, y[0]], [x[0], x[0]], **style))
+ ref_axes[5].add_line(mlines.Line2D([0, y[-1]], [x[-1], x[-1]], **style))
+ ref_axes[5].semilogx()
+
+
+@check_figures_equal(extensions=['png'])
+def test_stairs_fill(fig_test, fig_ref):
+ h, bins = [1, 2, 3, 4, 2], [0, 1, 2, 3, 4, 5]
+ bs = -2
+ # Test
+ test_axes = fig_test.subplots(2, 2).flatten()
+ test_axes[0].stairs(h, bins, fill=True)
+ test_axes[1].stairs(h, bins, orientation='horizontal', fill=True)
+ test_axes[2].stairs(h, bins, baseline=bs, fill=True)
+ test_axes[3].stairs(h, bins, baseline=bs, orientation='horizontal',
+ fill=True)
+
+ # # Ref
+ ref_axes = fig_ref.subplots(2, 2).flatten()
+ ref_axes[0].fill_between(bins, np.append(h, h[-1]), step='post', lw=0)
+ ref_axes[0].set_ylim(0, None)
+ ref_axes[1].fill_betweenx(bins, np.append(h, h[-1]), step='post', lw=0)
+ ref_axes[1].set_xlim(0, None)
+ ref_axes[2].fill_between(bins, np.append(h, h[-1]),
+ np.ones(len(h)+1)*bs, step='post', lw=0)
+ ref_axes[2].set_ylim(bs, None)
+ ref_axes[3].fill_betweenx(bins, np.append(h, h[-1]),
+ np.ones(len(h)+1)*bs, step='post', lw=0)
+ ref_axes[3].set_xlim(bs, None)
+
+
+@check_figures_equal(extensions=['png'])
+def test_stairs_update(fig_test, fig_ref):
+ # fixed ylim because stairs() does autoscale, but updating data does not
+ ylim = -3, 4
+ # Test
+ test_ax = fig_test.add_subplot()
+ h = test_ax.stairs([1, 2, 3])
+ test_ax.set_ylim(ylim)
+ h.set_data([3, 2, 1])
+ h.set_data(edges=np.arange(4)+2)
+ h.set_data([1, 2, 1], np.arange(4)/2)
+ h.set_data([1, 2, 3])
+ h.set_data(None, np.arange(4))
+ assert np.allclose(h.get_data()[0], np.arange(1, 4))
+ assert np.allclose(h.get_data()[1], np.arange(4))
+ h.set_data(baseline=-2)
+ assert h.get_data().baseline == -2
+
+ # Ref
+ ref_ax = fig_ref.add_subplot()
+ h = ref_ax.stairs([1, 2, 3], baseline=-2)
+ ref_ax.set_ylim(ylim)
+
+
+@check_figures_equal(extensions=['png'])
+def test_stairs_baseline_None(fig_test, fig_ref):
+ x = np.array([0, 2, 3, 5, 10])
+ y = np.array([1.148, 1.231, 1.248, 1.25])
+
+ test_axes = fig_test.add_subplot()
+ test_axes.stairs(y, x, baseline=None)
+
+ style = {'solid_joinstyle': 'miter', 'solid_capstyle': 'butt'}
+
+ ref_axes = fig_ref.add_subplot()
+ ref_axes.plot(x, np.append(y, y[-1]), drawstyle='steps-post', **style)
+
+
+def test_stairs_empty():
+ ax = plt.figure().add_subplot()
+ ax.stairs([], [42])
+ assert ax.get_xlim() == (39, 45)
+ assert ax.get_ylim() == (-0.06, 0.06)
+
+
+def test_stairs_invalid_nan():
+ with pytest.raises(ValueError, match='Nan values in "edges"'):
+ plt.stairs([1, 2], [0, np.nan, 1])
+
+
+def test_stairs_invalid_mismatch():
+ with pytest.raises(ValueError, match='Size mismatch'):
+ plt.stairs([1, 2], [0, 1])
+
+
+def test_stairs_invalid_update():
+ h = plt.stairs([1, 2], [0, 1, 2])
+ with pytest.raises(ValueError, match='Nan values in "edges"'):
+ h.set_data(edges=[1, np.nan, 2])
+
+
+def test_stairs_invalid_update2():
+ h = plt.stairs([1, 2], [0, 1, 2])
+ with pytest.raises(ValueError, match='Size mismatch'):
+ h.set_data(edges=np.arange(5))
+
+
+@image_comparison(['test_stairs_options.png'], remove_text=True)
+def test_stairs_options():
+ x, y = np.array([1, 2, 3, 4, 5]), np.array([1, 2, 3, 4]).astype(float)
+ yn = y.copy()
+ yn[1] = np.nan
+
+ fig, ax = plt.subplots()
+ ax.stairs(y*3, x, color='green', fill=True, label="A")
+ ax.stairs(y, x*3-3, color='red', fill=True,
+ orientation='horizontal', label="B")
+ ax.stairs(yn, x, color='orange', ls='--', lw=2, label="C")
+ ax.stairs(yn/3, x*3-2, ls='--', lw=2, baseline=0.5,
+ orientation='horizontal', label="D")
+ ax.stairs(y[::-1]*3+13, x-1, color='red', ls='--', lw=2, baseline=None,
+ label="E")
+ ax.stairs(y[::-1]*3+14, x, baseline=26,
+ color='purple', ls='--', lw=2, label="F")
+ ax.stairs(yn[::-1]*3+15, x+1, baseline=np.linspace(27, 25, len(y)),
+ color='blue', ls='--', label="G", fill=True)
+ ax.stairs(y[:-1][::-1]*2+11, x[:-1]+0.5, color='black', ls='--', lw=2,
+ baseline=12, hatch='//', label="H")
+ ax.legend(loc=0)
+
+
+@image_comparison(['test_stairs_datetime.png'])
+def test_stairs_datetime():
+ f, ax = plt.subplots(constrained_layout=True)
+ ax.stairs(np.arange(36),
+ np.arange(np.datetime64('2001-12-27'),
+ np.datetime64('2002-02-02')))
+ plt.xticks(rotation=30)
+
+
+@check_figures_equal(extensions=['png'])
+def test_stairs_edge_handling(fig_test, fig_ref):
+ # Test
+ test_ax = fig_test.add_subplot()
+ test_ax.stairs([1, 2, 3], color='red', fill=True)
+
+ # Ref
+ ref_ax = fig_ref.add_subplot()
+ st = ref_ax.stairs([1, 2, 3], fill=True)
+ st.set_color('red')
+
+
+def contour_dat():
+ x = np.linspace(-3, 5, 150)
+ y = np.linspace(-3, 5, 120)
+ z = np.cos(x) + np.sin(y[:, np.newaxis])
+ return x, y, z
+
+
+@image_comparison(['contour_hatching'], remove_text=True, style='mpl20')
+def test_contour_hatching():
+ x, y, z = contour_dat()
+ fig, ax = plt.subplots()
+ ax.contourf(x, y, z, 7, hatches=['/', '\\', '//', '-'],
+ cmap=mpl.colormaps['gray'],
+ extend='both', alpha=0.5)
+
+
+@image_comparison(['contour_colorbar'], style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.54)
+def test_contour_colorbar():
+ x, y, z = contour_dat()
+
+ fig, ax = plt.subplots()
+ cs = ax.contourf(x, y, z, levels=np.arange(-1.8, 1.801, 0.2),
+ cmap=mpl.colormaps['RdBu'],
+ vmin=-0.6,
+ vmax=0.6,
+ extend='both')
+ cs1 = ax.contour(x, y, z, levels=np.arange(-2.2, -0.599, 0.2),
+ colors=['y'],
+ linestyles='solid',
+ linewidths=2)
+ cs2 = ax.contour(x, y, z, levels=np.arange(0.6, 2.2, 0.2),
+ colors=['c'],
+ linewidths=2)
+ cbar = fig.colorbar(cs, ax=ax)
+ cbar.add_lines(cs1)
+ cbar.add_lines(cs2, erase=False)
+
+
+@image_comparison(['hist2d.png', 'hist2d.png'], remove_text=True, style='mpl20')
+def test_hist2d():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ np.random.seed(0)
+ # make it not symmetric in case we switch x and y axis
+ x = np.random.randn(100)*2+5
+ y = np.random.randn(100)-2
+ fig, ax = plt.subplots()
+ ax.hist2d(x, y, bins=10, rasterized=True)
+
+ # Reuse testcase from above for a labeled data test
+ data = {"x": x, "y": y}
+ fig, ax = plt.subplots()
+ ax.hist2d("x", "y", bins=10, data=data, rasterized=True)
+
+
+@image_comparison(['hist2d_transpose.png'], remove_text=True, style='mpl20')
+def test_hist2d_transpose():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ np.random.seed(0)
+ # make sure the output from np.histogram is transposed before
+ # passing to pcolorfast
+ x = np.array([5]*100)
+ y = np.random.randn(100)-2
+ fig, ax = plt.subplots()
+ ax.hist2d(x, y, bins=10, rasterized=True)
+
+
+def test_hist2d_density():
+ x, y = np.random.random((2, 100))
+ ax = plt.figure().subplots()
+ for obj in [ax, plt]:
+ obj.hist2d(x, y, density=True)
+
+
+class TestScatter:
+ @image_comparison(['scatter'], style='mpl20', remove_text=True)
+ def test_scatter_plot(self):
+ data = {"x": np.array([3, 4, 2, 6]), "y": np.array([2, 5, 2, 3]),
+ "c": ['r', 'y', 'b', 'lime'], "s": [24, 15, 19, 29],
+ "c2": ['0.5', '0.6', '0.7', '0.8']}
+
+ fig, ax = plt.subplots()
+ ax.scatter(data["x"] - 1., data["y"] - 1., c=data["c"], s=data["s"])
+ ax.scatter(data["x"] + 1., data["y"] + 1., c=data["c2"], s=data["s"])
+ ax.scatter("x", "y", c="c", s="s", data=data)
+
+ @image_comparison(['scatter_marker.png'], remove_text=True)
+ def test_scatter_marker(self):
+ fig, (ax0, ax1, ax2) = plt.subplots(ncols=3)
+ ax0.scatter([3, 4, 2, 6], [2, 5, 2, 3],
+ c=[(1, 0, 0), 'y', 'b', 'lime'],
+ s=[60, 50, 40, 30],
+ edgecolors=['k', 'r', 'g', 'b'],
+ marker='s')
+ ax1.scatter([3, 4, 2, 6], [2, 5, 2, 3],
+ c=[(1, 0, 0), 'y', 'b', 'lime'],
+ s=[60, 50, 40, 30],
+ edgecolors=['k', 'r', 'g', 'b'],
+ marker=mmarkers.MarkerStyle('o', fillstyle='top'))
+ # unit area ellipse
+ rx, ry = 3, 1
+ area = rx * ry * np.pi
+ theta = np.linspace(0, 2 * np.pi, 21)
+ verts = np.column_stack([np.cos(theta) * rx / area,
+ np.sin(theta) * ry / area])
+ ax2.scatter([3, 4, 2, 6], [2, 5, 2, 3],
+ c=[(1, 0, 0), 'y', 'b', 'lime'],
+ s=[60, 50, 40, 30],
+ edgecolors=['k', 'r', 'g', 'b'],
+ marker=verts)
+
+ @image_comparison(['scatter_2D'], remove_text=True, extensions=['png'])
+ def test_scatter_2D(self):
+ x = np.arange(3)
+ y = np.arange(2)
+ x, y = np.meshgrid(x, y)
+ z = x + y
+ fig, ax = plt.subplots()
+ ax.scatter(x, y, c=z, s=200, edgecolors='face')
+
+ @check_figures_equal(extensions=["png"])
+ def test_scatter_decimal(self, fig_test, fig_ref):
+ x0 = np.array([1.5, 8.4, 5.3, 4.2])
+ y0 = np.array([1.1, 2.2, 3.3, 4.4])
+ x = np.array([Decimal(i) for i in x0])
+ y = np.array([Decimal(i) for i in y0])
+ c = ['r', 'y', 'b', 'lime']
+ s = [24, 15, 19, 29]
+ # Test image - scatter plot with Decimal() input
+ ax = fig_test.subplots()
+ ax.scatter(x, y, c=c, s=s)
+ # Reference image
+ ax = fig_ref.subplots()
+ ax.scatter(x0, y0, c=c, s=s)
+
+ def test_scatter_color(self):
+ # Try to catch cases where 'c' kwarg should have been used.
+ with pytest.raises(ValueError):
+ plt.scatter([1, 2], [1, 2], color=[0.1, 0.2])
+ with pytest.raises(ValueError):
+ plt.scatter([1, 2, 3], [1, 2, 3], color=[1, 2, 3])
+
+ @pytest.mark.parametrize('kwargs',
+ [
+ {'cmap': 'gray'},
+ {'norm': mcolors.Normalize()},
+ {'vmin': 0},
+ {'vmax': 0}
+ ])
+ def test_scatter_color_warning(self, kwargs):
+ warn_match = "No data for colormapping provided "
+ # Warn for cases where 'cmap', 'norm', 'vmin', 'vmax'
+ # kwargs are being overridden
+ with pytest.warns(Warning, match=warn_match):
+ plt.scatter([], [], **kwargs)
+ with pytest.warns(Warning, match=warn_match):
+ plt.scatter([1, 2], [3, 4], c=[], **kwargs)
+ # Do not warn for cases where 'c' matches 'x' and 'y'
+ plt.scatter([], [], c=[], **kwargs)
+ plt.scatter([1, 2], [3, 4], c=[4, 5], **kwargs)
+
+ def test_scatter_unfilled(self):
+ coll = plt.scatter([0, 1, 2], [1, 3, 2], c=['0.1', '0.3', '0.5'],
+ marker=mmarkers.MarkerStyle('o', fillstyle='none'),
+ linewidths=[1.1, 1.2, 1.3])
+ assert coll.get_facecolors().shape == (0, 4) # no facecolors
+ assert_array_equal(coll.get_edgecolors(), [[0.1, 0.1, 0.1, 1],
+ [0.3, 0.3, 0.3, 1],
+ [0.5, 0.5, 0.5, 1]])
+ assert_array_equal(coll.get_linewidths(), [1.1, 1.2, 1.3])
+
+ @mpl.style.context('default')
+ def test_scatter_unfillable(self):
+ coll = plt.scatter([0, 1, 2], [1, 3, 2], c=['0.1', '0.3', '0.5'],
+ marker='x',
+ linewidths=[1.1, 1.2, 1.3])
+ assert_array_equal(coll.get_facecolors(), coll.get_edgecolors())
+ assert_array_equal(coll.get_edgecolors(), [[0.1, 0.1, 0.1, 1],
+ [0.3, 0.3, 0.3, 1],
+ [0.5, 0.5, 0.5, 1]])
+ assert_array_equal(coll.get_linewidths(), [1.1, 1.2, 1.3])
+
+ def test_scatter_size_arg_size(self):
+ x = np.arange(4)
+ with pytest.raises(ValueError, match='same size as x and y'):
+ plt.scatter(x, x, x[1:])
+ with pytest.raises(ValueError, match='same size as x and y'):
+ plt.scatter(x[1:], x[1:], x)
+ with pytest.raises(ValueError, match='float array-like'):
+ plt.scatter(x, x, 'foo')
+
+ def test_scatter_edgecolor_RGB(self):
+ # GitHub issue 19066
+ coll = plt.scatter([1, 2, 3], [1, np.nan, np.nan],
+ edgecolor=(1, 0, 0))
+ assert mcolors.same_color(coll.get_edgecolor(), (1, 0, 0))
+ coll = plt.scatter([1, 2, 3, 4], [1, np.nan, np.nan, 1],
+ edgecolor=(1, 0, 0, 1))
+ assert mcolors.same_color(coll.get_edgecolor(), (1, 0, 0, 1))
+
+ @check_figures_equal(extensions=["png"])
+ def test_scatter_invalid_color(self, fig_test, fig_ref):
+ ax = fig_test.subplots()
+ cmap = mpl.colormaps["viridis"].resampled(16)
+ cmap.set_bad("k", 1)
+ # Set a nonuniform size to prevent the last call to `scatter` (plotting
+ # the invalid points separately in fig_ref) from using the marker
+ # stamping fast path, which would result in slightly offset markers.
+ ax.scatter(range(4), range(4),
+ c=[1, np.nan, 2, np.nan], s=[1, 2, 3, 4],
+ cmap=cmap, plotnonfinite=True)
+ ax = fig_ref.subplots()
+ cmap = mpl.colormaps["viridis"].resampled(16)
+ ax.scatter([0, 2], [0, 2], c=[1, 2], s=[1, 3], cmap=cmap)
+ ax.scatter([1, 3], [1, 3], s=[2, 4], color="k")
+
+ @check_figures_equal(extensions=["png"])
+ def test_scatter_no_invalid_color(self, fig_test, fig_ref):
+ # With plotnonfinite=False we plot only 2 points.
+ ax = fig_test.subplots()
+ cmap = mpl.colormaps["viridis"].resampled(16)
+ cmap.set_bad("k", 1)
+ ax.scatter(range(4), range(4),
+ c=[1, np.nan, 2, np.nan], s=[1, 2, 3, 4],
+ cmap=cmap, plotnonfinite=False)
+ ax = fig_ref.subplots()
+ ax.scatter([0, 2], [0, 2], c=[1, 2], s=[1, 3], cmap=cmap)
+
+ def test_scatter_norm_vminvmax(self):
+ """Parameters vmin, vmax should error if norm is given."""
+ x = [1, 2, 3]
+ ax = plt.axes()
+ with pytest.raises(ValueError,
+ match="Passing a Normalize instance simultaneously "
+ "with vmin/vmax is not supported."):
+ ax.scatter(x, x, c=x, norm=mcolors.Normalize(-10, 10),
+ vmin=0, vmax=5)
+
+ @check_figures_equal(extensions=["png"])
+ def test_scatter_single_point(self, fig_test, fig_ref):
+ ax = fig_test.subplots()
+ ax.scatter(1, 1, c=1)
+ ax = fig_ref.subplots()
+ ax.scatter([1], [1], c=[1])
+
+ @check_figures_equal(extensions=["png"])
+ def test_scatter_different_shapes(self, fig_test, fig_ref):
+ x = np.arange(10)
+ ax = fig_test.subplots()
+ ax.scatter(x, x.reshape(2, 5), c=x.reshape(5, 2))
+ ax = fig_ref.subplots()
+ ax.scatter(x.reshape(5, 2), x, c=x.reshape(2, 5))
+
+ # Parameters for *test_scatter_c*. NB: assuming that the
+ # scatter plot will have 4 elements. The tuple scheme is:
+ # (*c* parameter case, exception regexp key or None if no exception)
+ params_test_scatter_c = [
+ # single string:
+ ('0.5', None),
+ # Single letter-sequences
+ (["rgby"], "conversion"),
+ # Special cases
+ ("red", None),
+ ("none", None),
+ (None, None),
+ (["r", "g", "b", "none"], None),
+ # Non-valid color spec (FWIW, 'jaune' means yellow in French)
+ ("jaune", "conversion"),
+ (["jaune"], "conversion"), # wrong type before wrong size
+ (["jaune"]*4, "conversion"),
+ # Value-mapping like
+ ([0.5]*3, None), # should emit a warning for user's eyes though
+ ([0.5]*4, None), # NB: no warning as matching size allows mapping
+ ([0.5]*5, "shape"),
+ # list of strings:
+ (['0.5', '0.4', '0.6', '0.7'], None),
+ (['0.5', 'red', '0.6', 'C5'], None),
+ (['0.5', 0.5, '0.6', 'C5'], "conversion"),
+ # RGB values
+ ([[1, 0, 0]], None),
+ ([[1, 0, 0]]*3, "shape"),
+ ([[1, 0, 0]]*4, None),
+ ([[1, 0, 0]]*5, "shape"),
+ # RGBA values
+ ([[1, 0, 0, 0.5]], None),
+ ([[1, 0, 0, 0.5]]*3, "shape"),
+ ([[1, 0, 0, 0.5]]*4, None),
+ ([[1, 0, 0, 0.5]]*5, "shape"),
+ # Mix of valid color specs
+ ([[1, 0, 0, 0.5]]*3 + [[1, 0, 0]], None),
+ ([[1, 0, 0, 0.5], "red", "0.0"], "shape"),
+ ([[1, 0, 0, 0.5], "red", "0.0", "C5"], None),
+ ([[1, 0, 0, 0.5], "red", "0.0", "C5", [0, 1, 0]], "shape"),
+ # Mix of valid and non valid color specs
+ ([[1, 0, 0, 0.5], "red", "jaune"], "conversion"),
+ ([[1, 0, 0, 0.5], "red", "0.0", "jaune"], "conversion"),
+ ([[1, 0, 0, 0.5], "red", "0.0", "C5", "jaune"], "conversion"),
+ ]
+
+ @pytest.mark.parametrize('c_case, re_key', params_test_scatter_c)
+ def test_scatter_c(self, c_case, re_key):
+ def get_next_color(): # pragma: no cover
+ return 'blue' # currently unused
+
+ xsize = 4
+ # Additional checking of *c* (introduced in #11383).
+ REGEXP = {
+ "shape": "^'c' argument has [0-9]+ elements", # shape mismatch
+ "conversion": "^'c' argument must be a color", # bad vals
+ }
+
+ assert_context = (
+ pytest.raises(ValueError, match=REGEXP[re_key])
+ if re_key is not None
+ else pytest.warns(match="argument looks like a single numeric RGB")
+ if isinstance(c_case, list) and len(c_case) == 3
+ else contextlib.nullcontext()
+ )
+ with assert_context:
+ mpl.axes.Axes._parse_scatter_color_args(
+ c=c_case, edgecolors="black", kwargs={}, xsize=xsize,
+ get_next_color_func=get_next_color)
+
+ @mpl.style.context('default')
+ @check_figures_equal(extensions=["png"])
+ def test_scatter_single_color_c(self, fig_test, fig_ref):
+ rgb = [[1, 0.5, 0.05]]
+ rgba = [[1, 0.5, 0.05, .5]]
+
+ # set via color kwarg
+ ax_ref = fig_ref.subplots()
+ ax_ref.scatter(np.ones(3), range(3), color=rgb)
+ ax_ref.scatter(np.ones(4)*2, range(4), color=rgba)
+
+ # set via broadcasting via c
+ ax_test = fig_test.subplots()
+ ax_test.scatter(np.ones(3), range(3), c=rgb)
+ ax_test.scatter(np.ones(4)*2, range(4), c=rgba)
+
+ def test_scatter_linewidths(self):
+ x = np.arange(5)
+
+ fig, ax = plt.subplots()
+ for i in range(3):
+ pc = ax.scatter(x, np.full(5, i), c=f'C{i}', marker='x', s=100,
+ linewidths=i + 1)
+ assert pc.get_linewidths() == i + 1
+
+ pc = ax.scatter(x, np.full(5, 3), c='C3', marker='x', s=100,
+ linewidths=[*range(1, 5), None])
+ assert_array_equal(pc.get_linewidths(),
+ [*range(1, 5), mpl.rcParams['lines.linewidth']])
+
+ def test_scatter_singular_plural_arguments(self):
+
+ with pytest.raises(TypeError,
+ match="Got both 'linewidth' and 'linewidths',\
+ which are aliases of one another"):
+ plt.scatter([1, 2, 3], [1, 2, 3], linewidths=[0.5, 0.4, 0.3], linewidth=0.2)
+
+ with pytest.raises(TypeError,
+ match="Got both 'edgecolor' and 'edgecolors',\
+ which are aliases of one another"):
+ plt.scatter([1, 2, 3], [1, 2, 3],
+ edgecolors=["#ffffff", "#000000", "#f0f0f0"],
+ edgecolor="#ffffff")
+
+ with pytest.raises(TypeError,
+ match="Got both 'facecolors' and 'facecolor',\
+ which are aliases of one another"):
+ plt.scatter([1, 2, 3], [1, 2, 3],
+ facecolors=["#ffffff", "#000000", "#f0f0f0"],
+ facecolor="#ffffff")
+
+
+def _params(c=None, xsize=2, *, edgecolors=None, **kwargs):
+ return (c, edgecolors, kwargs, xsize)
+_result = namedtuple('_result', 'c, colors')
+
+
+@pytest.mark.parametrize(
+ 'params, expected_result',
+ [(_params(),
+ _result(c='b', colors=np.array([[0, 0, 1, 1]]))),
+ (_params(c='r'),
+ _result(c='r', colors=np.array([[1, 0, 0, 1]]))),
+ (_params(c='r', colors='b'),
+ _result(c='r', colors=np.array([[1, 0, 0, 1]]))),
+ # color
+ (_params(color='b'),
+ _result(c='b', colors=np.array([[0, 0, 1, 1]]))),
+ (_params(color=['b', 'g']),
+ _result(c=['b', 'g'], colors=np.array([[0, 0, 1, 1], [0, .5, 0, 1]]))),
+ ])
+def test_parse_scatter_color_args(params, expected_result):
+ def get_next_color(): # pragma: no cover
+ return 'blue' # currently unused
+
+ c, colors, _edgecolors = mpl.axes.Axes._parse_scatter_color_args(
+ *params, get_next_color_func=get_next_color)
+ assert c == expected_result.c
+ assert_allclose(colors, expected_result.colors)
+
+del _params
+del _result
+
+
+@pytest.mark.parametrize(
+ 'kwargs, expected_edgecolors',
+ [(dict(), None),
+ (dict(c='b'), None),
+ (dict(edgecolors='r'), 'r'),
+ (dict(edgecolors=['r', 'g']), ['r', 'g']),
+ (dict(edgecolor='r'), 'r'),
+ (dict(edgecolors='face'), 'face'),
+ (dict(edgecolors='none'), 'none'),
+ (dict(edgecolor='r', edgecolors='g'), 'r'),
+ (dict(c='b', edgecolor='r', edgecolors='g'), 'r'),
+ (dict(color='r'), 'r'),
+ (dict(color='r', edgecolor='g'), 'g'),
+ ])
+def test_parse_scatter_color_args_edgecolors(kwargs, expected_edgecolors):
+ def get_next_color(): # pragma: no cover
+ return 'blue' # currently unused
+
+ c = kwargs.pop('c', None)
+ edgecolors = kwargs.pop('edgecolors', None)
+ _, _, result_edgecolors = \
+ mpl.axes.Axes._parse_scatter_color_args(
+ c, edgecolors, kwargs, xsize=2, get_next_color_func=get_next_color)
+ assert result_edgecolors == expected_edgecolors
+
+
+def test_parse_scatter_color_args_error():
+ def get_next_color(): # pragma: no cover
+ return 'blue' # currently unused
+
+ with pytest.raises(ValueError,
+ match="RGBA values should be within 0-1 range"):
+ c = np.array([[0.1, 0.2, 0.7], [0.2, 0.4, 1.4]]) # value > 1
+ mpl.axes.Axes._parse_scatter_color_args(
+ c, None, kwargs={}, xsize=2, get_next_color_func=get_next_color)
+
+
+# Warning message tested in the next two tests.
+WARN_MSG = (
+ "You passed both c and facecolor/facecolors for the markers. "
+ "c has precedence over facecolor/facecolors. This behavior may "
+ "change in the future."
+)
+# Test cases shared between direct and integration tests
+COLOR_TEST_CASES = [
+ ('red', 'blue'),
+ (['red', 'blue'], ['green', 'yellow']),
+ ([[1, 0, 0], [0, 1, 0]], [[0, 0, 1], [1, 1, 0]])
+]
+
+
+@pytest.mark.parametrize('c, facecolor', COLOR_TEST_CASES)
+def test_parse_c_facecolor_warning_direct(c, facecolor):
+ """Test the internal _parse_scatter_color_args method directly."""
+ def get_next_color(): # pragma: no cover
+ return 'blue' # currently unused
+
+ # Test with facecolors (plural)
+ with pytest.warns(UserWarning, match=WARN_MSG):
+ mpl.axes.Axes._parse_scatter_color_args(
+ c=c, edgecolors=None, kwargs={'facecolors': facecolor},
+ xsize=2, get_next_color_func=get_next_color)
+
+ # Test with facecolor (singular)
+ with pytest.warns(UserWarning, match=WARN_MSG):
+ mpl.axes.Axes._parse_scatter_color_args(
+ c=c, edgecolors=None, kwargs={'facecolor': facecolor},
+ xsize=2, get_next_color_func=get_next_color)
+
+
+@pytest.mark.parametrize('c, facecolor', COLOR_TEST_CASES)
+def test_scatter_c_facecolor_warning_integration(c, facecolor):
+ """Test the warning through the actual scatter plot creation."""
+ fig, ax = plt.subplots()
+ x = [0, 1] if isinstance(c, (list, tuple)) else [0]
+ y = x
+
+ # Test with facecolors (plural)
+ with pytest.warns(UserWarning, match=WARN_MSG):
+ ax.scatter(x, y, c=c, facecolors=facecolor)
+
+ # Test with facecolor (singular)
+ with pytest.warns(UserWarning, match=WARN_MSG):
+ ax.scatter(x, y, c=c, facecolor=facecolor)
+
+
+def test_as_mpl_axes_api():
+ # tests the _as_mpl_axes api
+ class Polar:
+ def __init__(self):
+ self.theta_offset = 0
+
+ def _as_mpl_axes(self):
+ # implement the matplotlib axes interface
+ return PolarAxes, {'theta_offset': self.theta_offset}
+
+ prj = Polar()
+ prj2 = Polar()
+ prj2.theta_offset = np.pi
+
+ # testing axes creation with plt.axes
+ ax = plt.axes((0, 0, 1, 1), projection=prj)
+ assert type(ax) is PolarAxes
+ plt.close()
+
+ # testing axes creation with subplot
+ ax = plt.subplot(121, projection=prj)
+ assert type(ax) is PolarAxes
+ plt.close()
+
+
+def test_pyplot_axes():
+ # test focusing of Axes in other Figure
+ fig1, ax1 = plt.subplots()
+ fig2, ax2 = plt.subplots()
+ plt.sca(ax1)
+ assert ax1 is plt.gca()
+ assert fig1 is plt.gcf()
+ plt.close(fig1)
+ plt.close(fig2)
+
+
+def test_log_scales():
+ fig, ax = plt.subplots()
+ ax.plot(np.log(np.linspace(0.1, 100)))
+ ax.set_yscale('log', base=5.5)
+ ax.invert_yaxis()
+ ax.set_xscale('log', base=9.0)
+ xticks, yticks = (
+ [(t.get_loc(), t.label1.get_text()) for t in axis._update_ticks()]
+ for axis in [ax.xaxis, ax.yaxis]
+ )
+ assert xticks == [
+ (1.0, '$\\mathdefault{9^{0}}$'),
+ (9.0, '$\\mathdefault{9^{1}}$'),
+ (81.0, '$\\mathdefault{9^{2}}$'),
+ (2.0, ''),
+ (3.0, ''),
+ (4.0, ''),
+ (5.0, ''),
+ (6.0, ''),
+ (7.0, ''),
+ (8.0, ''),
+ (18.0, ''),
+ (27.0, ''),
+ (36.0, ''),
+ (45.0, ''),
+ (54.0, ''),
+ (63.0, ''),
+ (72.0, ''),
+ ]
+ assert yticks == [
+ (0.18181818181818182, '$\\mathdefault{5.5^{-1}}$'),
+ (1.0, '$\\mathdefault{5.5^{0}}$'),
+ (5.5, '$\\mathdefault{5.5^{1}}$'),
+ (0.36363636363636365, ''),
+ (0.5454545454545454, ''),
+ (0.7272727272727273, ''),
+ (0.9090909090909092, ''),
+ (2.0, ''),
+ (3.0, ''),
+ (4.0, ''),
+ (5.0, ''),
+ ]
+
+
+def test_log_scales_no_data():
+ _, ax = plt.subplots()
+ ax.set(xscale="log", yscale="log")
+ ax.xaxis.set_major_locator(mticker.MultipleLocator(1))
+ assert ax.get_xlim() == ax.get_ylim() == (1, 10)
+
+
+def test_log_scales_invalid():
+ fig, ax = plt.subplots()
+ ax.set_xscale('log')
+ with pytest.warns(UserWarning, match='Attempt to set non-positive'):
+ ax.set_xlim(-1, 10)
+ ax.set_yscale('log')
+ with pytest.warns(UserWarning, match='Attempt to set non-positive'):
+ ax.set_ylim(-1, 10)
+
+
+@image_comparison(['stackplot_test_image.png', 'stackplot_test_image.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.031)
+def test_stackplot():
+ fig = plt.figure()
+ x = np.linspace(0, 10, 10)
+ y1 = 1.0 * x
+ y2 = 2.0 * x + 1
+ y3 = 3.0 * x + 2
+ ax = fig.add_subplot(1, 1, 1)
+ ax.stackplot(x, y1, y2, y3)
+ ax.set_xlim((0, 10))
+ ax.set_ylim((0, 70))
+
+ # Reuse testcase from above for a test with labeled data and with colours
+ # from the Axes property cycle.
+ data = {"x": x, "y1": y1, "y2": y2, "y3": y3}
+ fig, ax = plt.subplots()
+ ax.stackplot("x", "y1", "y2", "y3", data=data, colors=["C0", "C1", "C2"])
+ ax.set_xlim((0, 10))
+ ax.set_ylim((0, 70))
+
+
+@image_comparison(['stackplot_test_baseline.png'], remove_text=True)
+def test_stackplot_baseline():
+ np.random.seed(0)
+
+ def layers(n, m):
+ a = np.zeros((m, n))
+ for i in range(n):
+ for j in range(5):
+ x = 1 / (.1 + np.random.random())
+ y = 2 * np.random.random() - .5
+ z = 10 / (.1 + np.random.random())
+ a[:, i] += x * np.exp(-((np.arange(m) / m - y) * z) ** 2)
+ return a
+
+ d = layers(3, 100)
+ d[50, :] = 0 # test for fixed weighted wiggle (issue #6313)
+
+ fig, axs = plt.subplots(2, 2)
+
+ axs[0, 0].stackplot(range(100), d.T, baseline='zero')
+ axs[0, 1].stackplot(range(100), d.T, baseline='sym')
+ axs[1, 0].stackplot(range(100), d.T, baseline='wiggle')
+ axs[1, 1].stackplot(range(100), d.T, baseline='weighted_wiggle')
+
+
+@check_figures_equal(extensions=['png'])
+def test_stackplot_hatching(fig_ref, fig_test):
+ x = np.linspace(0, 10, 10)
+ y1 = 1.0 * x
+ y2 = 2.0 * x + 1
+ y3 = 3.0 * x + 2
+ # stackplot with different hatching styles (issue #27146)
+ ax_test = fig_test.subplots()
+ ax_test.stackplot(x, y1, y2, y3, hatch=["x", "//", "\\\\"], colors=["white"])
+ ax_test.set_xlim((0, 10))
+ ax_test.set_ylim((0, 70))
+ # compare with result from hatching each layer individually
+ stack_baseline = np.zeros(len(x))
+ ax_ref = fig_ref.subplots()
+ ax_ref.fill_between(x, stack_baseline, y1, hatch="x", facecolor="white")
+ ax_ref.fill_between(x, y1, y1+y2, hatch="//", facecolor="white")
+ ax_ref.fill_between(x, y1+y2, y1+y2+y3, hatch="\\\\", facecolor="white")
+ ax_ref.set_xlim((0, 10))
+ ax_ref.set_ylim((0, 70))
+
+
+def test_stackplot_subfig_legend():
+ # Smoke test for https://github.com/matplotlib/matplotlib/issues/30158
+
+ fig = plt.figure()
+ subfigs = fig.subfigures(nrows=1, ncols=2)
+
+ for _fig in subfigs:
+ ax = _fig.subplots(nrows=1, ncols=1)
+ ax.stackplot([3, 4], [[1, 2]], labels=['a'])
+
+ fig.legend()
+ fig.draw_without_rendering()
+
+
+def _bxp_test_helper(
+ stats_kwargs={}, transform_stats=lambda s: s, bxp_kwargs={}):
+ np.random.seed(937)
+ logstats = mpl.cbook.boxplot_stats(
+ np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)), **stats_kwargs)
+ fig, ax = plt.subplots()
+ if bxp_kwargs.get('orientation', 'vertical') == 'vertical':
+ ax.set_yscale('log')
+ else:
+ ax.set_xscale('log')
+ # Work around baseline images generate back when bxp did not respect the
+ # boxplot.boxprops.linewidth rcParam when patch_artist is False.
+ if not bxp_kwargs.get('patch_artist', False):
+ mpl.rcParams['boxplot.boxprops.linewidth'] = \
+ mpl.rcParams['lines.linewidth']
+ ax.bxp(transform_stats(logstats), **bxp_kwargs)
+
+
+@image_comparison(['bxp_baseline.png'],
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_baseline():
+ _bxp_test_helper()
+
+
+@image_comparison(['bxp_rangewhis.png'],
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_rangewhis():
+ _bxp_test_helper(stats_kwargs=dict(whis=[0, 100]))
+
+
+@image_comparison(['bxp_percentilewhis.png'],
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_percentilewhis():
+ _bxp_test_helper(stats_kwargs=dict(whis=[5, 95]))
+
+
+@image_comparison(['bxp_with_xlabels.png'],
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_with_xlabels():
+ def transform(stats):
+ for s, label in zip(stats, list('ABCD')):
+ s['label'] = label
+ return stats
+
+ _bxp_test_helper(transform_stats=transform)
+
+
+@image_comparison(['bxp_horizontal.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default',
+ tol=0.1)
+def test_bxp_horizontal():
+ _bxp_test_helper(bxp_kwargs=dict(orientation='horizontal'))
+
+
+@image_comparison(['bxp_with_ylabels.png'],
+ savefig_kwarg={'dpi': 40},
+ style='default',
+ tol=0.1)
+def test_bxp_with_ylabels():
+ def transform(stats):
+ for s, label in zip(stats, list('ABCD')):
+ s['label'] = label
+ return stats
+
+ _bxp_test_helper(transform_stats=transform,
+ bxp_kwargs=dict(orientation='horizontal'))
+
+
+@image_comparison(['bxp_patchartist.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_patchartist():
+ _bxp_test_helper(bxp_kwargs=dict(patch_artist=True))
+
+
+@image_comparison(['bxp_custompatchartist.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 100},
+ style='default')
+def test_bxp_custompatchartist():
+ _bxp_test_helper(bxp_kwargs=dict(
+ patch_artist=True,
+ boxprops=dict(facecolor='yellow', edgecolor='green', ls=':')))
+
+
+@image_comparison(['bxp_customoutlier.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_customoutlier():
+ _bxp_test_helper(bxp_kwargs=dict(
+ flierprops=dict(linestyle='none', marker='d', mfc='g')))
+
+
+@image_comparison(['bxp_withmean_custompoint.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_showcustommean():
+ _bxp_test_helper(bxp_kwargs=dict(
+ showmeans=True,
+ meanprops=dict(linestyle='none', marker='d', mfc='green'),
+ ))
+
+
+@image_comparison(['bxp_custombox.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_custombox():
+ _bxp_test_helper(bxp_kwargs=dict(
+ boxprops=dict(linestyle='--', color='b', lw=3)))
+
+
+@image_comparison(['bxp_custommedian.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_custommedian():
+ _bxp_test_helper(bxp_kwargs=dict(
+ medianprops=dict(linestyle='--', color='b', lw=3)))
+
+
+@image_comparison(['bxp_customcap.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_customcap():
+ _bxp_test_helper(bxp_kwargs=dict(
+ capprops=dict(linestyle='--', color='g', lw=3)))
+
+
+@image_comparison(['bxp_customwhisker.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_customwhisker():
+ _bxp_test_helper(bxp_kwargs=dict(
+ whiskerprops=dict(linestyle='-', color='m', lw=3)))
+
+
+@check_figures_equal(extensions=['png'])
+def test_boxplot_median_bound_by_box(fig_test, fig_ref):
+ data = np.arange(3)
+ medianprops_test = {"linewidth": 12}
+ medianprops_ref = {**medianprops_test, "solid_capstyle": "butt"}
+ fig_test.subplots().boxplot(data, medianprops=medianprops_test)
+ fig_ref.subplots().boxplot(data, medianprops=medianprops_ref)
+
+
+@image_comparison(['bxp_withnotch.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_shownotches():
+ _bxp_test_helper(bxp_kwargs=dict(shownotches=True))
+
+
+@image_comparison(['bxp_nocaps.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_nocaps():
+ _bxp_test_helper(bxp_kwargs=dict(showcaps=False))
+
+
+@image_comparison(['bxp_nobox.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_nobox():
+ _bxp_test_helper(bxp_kwargs=dict(showbox=False))
+
+
+@image_comparison(['bxp_no_flier_stats.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_no_flier_stats():
+ def transform(stats):
+ for s in stats:
+ s.pop('fliers', None)
+ return stats
+
+ _bxp_test_helper(transform_stats=transform,
+ bxp_kwargs=dict(showfliers=False))
+
+
+@image_comparison(['bxp_withmean_point.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_showmean():
+ _bxp_test_helper(bxp_kwargs=dict(showmeans=True, meanline=False))
+
+
+@image_comparison(['bxp_withmean_line.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_showmeanasline():
+ _bxp_test_helper(bxp_kwargs=dict(showmeans=True, meanline=True))
+
+
+@image_comparison(['bxp_scalarwidth.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_scalarwidth():
+ _bxp_test_helper(bxp_kwargs=dict(widths=.25))
+
+
+@image_comparison(['bxp_customwidths.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_customwidths():
+ _bxp_test_helper(bxp_kwargs=dict(widths=[0.10, 0.25, 0.65, 0.85]))
+
+
+@image_comparison(['bxp_custompositions.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_custompositions():
+ _bxp_test_helper(bxp_kwargs=dict(positions=[1, 5, 6, 7]))
+
+
+def test_bxp_bad_widths():
+ with pytest.raises(ValueError):
+ _bxp_test_helper(bxp_kwargs=dict(widths=[1]))
+
+
+def test_bxp_bad_positions():
+ with pytest.raises(ValueError):
+ _bxp_test_helper(bxp_kwargs=dict(positions=[2, 3]))
+
+
+@image_comparison(['bxp_custom_capwidths.png'],
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_custom_capwidths():
+ _bxp_test_helper(bxp_kwargs=dict(capwidths=[0.0, 0.1, 0.5, 1.0]))
+
+
+@image_comparison(['bxp_custom_capwidth.png'],
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_bxp_custom_capwidth():
+ _bxp_test_helper(bxp_kwargs=dict(capwidths=0.6))
+
+
+def test_bxp_bad_capwidths():
+ with pytest.raises(ValueError):
+ _bxp_test_helper(bxp_kwargs=dict(capwidths=[1]))
+
+
+@image_comparison(['boxplot.png', 'boxplot.png'], tol=1.28, style='default')
+def test_boxplot():
+ # Randomness used for bootstrapping.
+ np.random.seed(937)
+
+ x = np.linspace(-7, 7, 140)
+ x = np.hstack([-25, x, 25])
+ fig, ax = plt.subplots()
+
+ ax.boxplot([x, x], bootstrap=10000, notch=1)
+ ax.set_ylim((-30, 30))
+
+ # Reuse testcase from above for a labeled data test
+ data = {"x": [x, x]}
+ fig, ax = plt.subplots()
+ ax.boxplot("x", bootstrap=10000, notch=1, data=data)
+ ax.set_ylim((-30, 30))
+
+
+@check_figures_equal(extensions=["png"])
+def test_boxplot_masked(fig_test, fig_ref):
+ # Check that masked values are ignored when plotting a boxplot
+ x_orig = np.linspace(-1, 1, 200)
+
+ ax = fig_test.subplots()
+ x = x_orig[x_orig >= 0]
+ ax.boxplot(x)
+
+ x = np.ma.masked_less(x_orig, 0)
+ ax = fig_ref.subplots()
+ ax.boxplot(x)
+
+
+@image_comparison(['boxplot_custom_capwidths.png'],
+ savefig_kwarg={'dpi': 40}, style='default')
+def test_boxplot_custom_capwidths():
+
+ x = np.linspace(-7, 7, 140)
+ x = np.hstack([-25, x, 25])
+ fig, ax = plt.subplots()
+
+ ax.boxplot([x, x], notch=1, capwidths=[0.01, 0.2])
+
+
+@image_comparison(['boxplot_sym2.png'], remove_text=True, style='default')
+def test_boxplot_sym2():
+ # Randomness used for bootstrapping.
+ np.random.seed(937)
+
+ x = np.linspace(-7, 7, 140)
+ x = np.hstack([-25, x, 25])
+ fig, [ax1, ax2] = plt.subplots(1, 2)
+
+ ax1.boxplot([x, x], bootstrap=10000, sym='^')
+ ax1.set_ylim((-30, 30))
+
+ ax2.boxplot([x, x], bootstrap=10000, sym='g')
+ ax2.set_ylim((-30, 30))
+
+
+@image_comparison(['boxplot_sym.png'],
+ remove_text=True,
+ savefig_kwarg={'dpi': 40},
+ style='default')
+def test_boxplot_sym():
+ x = np.linspace(-7, 7, 140)
+ x = np.hstack([-25, x, 25])
+ fig, ax = plt.subplots()
+
+ ax.boxplot([x, x], sym='gs')
+ ax.set_ylim((-30, 30))
+
+
+@image_comparison(['boxplot_autorange_false_whiskers.png',
+ 'boxplot_autorange_true_whiskers.png'],
+ style='default')
+def test_boxplot_autorange_whiskers():
+ # Randomness used for bootstrapping.
+ np.random.seed(937)
+
+ x = np.ones(140)
+ x = np.hstack([0, x, 2])
+
+ fig1, ax1 = plt.subplots()
+ ax1.boxplot([x, x], bootstrap=10000, notch=1)
+ ax1.set_ylim((-5, 5))
+
+ fig2, ax2 = plt.subplots()
+ ax2.boxplot([x, x], bootstrap=10000, notch=1, autorange=True)
+ ax2.set_ylim((-5, 5))
+
+
+def _rc_test_bxp_helper(ax, rc_dict):
+ x = np.linspace(-7, 7, 140)
+ x = np.hstack([-25, x, 25])
+ with matplotlib.rc_context(rc_dict):
+ ax.boxplot([x, x])
+ return ax
+
+
+@image_comparison(['boxplot_rc_parameters.png'],
+ savefig_kwarg={'dpi': 100}, remove_text=True,
+ tol=1, style='default')
+def test_boxplot_rc_parameters():
+ # Randomness used for bootstrapping.
+ np.random.seed(937)
+
+ fig, ax = plt.subplots(3)
+
+ rc_axis0 = {
+ 'boxplot.notch': True,
+ 'boxplot.whiskers': [5, 95],
+ 'boxplot.bootstrap': 10000,
+
+ 'boxplot.flierprops.color': 'b',
+ 'boxplot.flierprops.marker': 'o',
+ 'boxplot.flierprops.markerfacecolor': 'g',
+ 'boxplot.flierprops.markeredgecolor': 'b',
+ 'boxplot.flierprops.markersize': 5,
+ 'boxplot.flierprops.linestyle': '--',
+ 'boxplot.flierprops.linewidth': 2.0,
+
+ 'boxplot.boxprops.color': 'r',
+ 'boxplot.boxprops.linewidth': 2.0,
+ 'boxplot.boxprops.linestyle': '--',
+
+ 'boxplot.capprops.color': 'c',
+ 'boxplot.capprops.linewidth': 2.0,
+ 'boxplot.capprops.linestyle': '--',
+
+ 'boxplot.medianprops.color': 'k',
+ 'boxplot.medianprops.linewidth': 2.0,
+ 'boxplot.medianprops.linestyle': '--',
+ }
+
+ rc_axis1 = {
+ 'boxplot.whiskers': [0, 100],
+ 'boxplot.patchartist': True,
+ }
+
+ rc_axis2 = {
+ 'boxplot.whiskers': 2.0,
+ 'boxplot.showcaps': False,
+ 'boxplot.showbox': False,
+ 'boxplot.showfliers': False,
+ 'boxplot.showmeans': True,
+ 'boxplot.meanline': True,
+
+ 'boxplot.meanprops.color': 'c',
+ 'boxplot.meanprops.linewidth': 2.0,
+ 'boxplot.meanprops.linestyle': '--',
+
+ 'boxplot.whiskerprops.color': 'r',
+ 'boxplot.whiskerprops.linewidth': 2.0,
+ 'boxplot.whiskerprops.linestyle': '-.',
+ }
+ dict_list = [rc_axis0, rc_axis1, rc_axis2]
+ for axis, rc_axis in zip(ax, dict_list):
+ _rc_test_bxp_helper(axis, rc_axis)
+
+ assert (matplotlib.patches.PathPatch in
+ [type(t) for t in ax[1].get_children()])
+
+
+@image_comparison(['boxplot_with_CIarray.png'],
+ remove_text=True, savefig_kwarg={'dpi': 40}, style='default')
+def test_boxplot_with_CIarray():
+ # Randomness used for bootstrapping.
+ np.random.seed(937)
+
+ x = np.linspace(-7, 7, 140)
+ x = np.hstack([-25, x, 25])
+ fig, ax = plt.subplots()
+ CIs = np.array([[-1.5, 3.], [-1., 3.5]])
+
+ # show a boxplot with Matplotlib medians and confidence intervals, and
+ # another with manual values
+ ax.boxplot([x, x], bootstrap=10000, usermedians=[None, 1.0],
+ conf_intervals=CIs, notch=1)
+ ax.set_ylim((-30, 30))
+
+
+@image_comparison(['boxplot_no_inverted_whisker.png'],
+ remove_text=True, savefig_kwarg={'dpi': 40}, style='default')
+def test_boxplot_no_weird_whisker():
+ x = np.array([3, 9000, 150, 88, 350, 200000, 1400, 960],
+ dtype=np.float64)
+ ax1 = plt.axes()
+ ax1.boxplot(x)
+ ax1.set_yscale('log')
+ ax1.yaxis.grid(False, which='minor')
+ ax1.xaxis.grid(False)
+
+
+def test_boxplot_bad_medians():
+ x = np.linspace(-7, 7, 140)
+ x = np.hstack([-25, x, 25])
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.boxplot(x, usermedians=[1, 2])
+ with pytest.raises(ValueError):
+ ax.boxplot([x, x], usermedians=[[1, 2], [1, 2]])
+
+
+def test_boxplot_bad_ci():
+ x = np.linspace(-7, 7, 140)
+ x = np.hstack([-25, x, 25])
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.boxplot([x, x], conf_intervals=[[1, 2]])
+ with pytest.raises(ValueError):
+ ax.boxplot([x, x], conf_intervals=[[1, 2], [1]])
+
+
+def test_boxplot_zorder():
+ x = np.arange(10)
+ fix, ax = plt.subplots()
+ assert ax.boxplot(x)['boxes'][0].get_zorder() == 2
+ assert ax.boxplot(x, zorder=10)['boxes'][0].get_zorder() == 10
+
+
+def test_boxplot_marker_behavior():
+ plt.rcParams['lines.marker'] = 's'
+ plt.rcParams['boxplot.flierprops.marker'] = 'o'
+ plt.rcParams['boxplot.meanprops.marker'] = '^'
+ fig, ax = plt.subplots()
+ test_data = np.arange(100)
+ test_data[-1] = 150 # a flier point
+ bxp_handle = ax.boxplot(test_data, showmeans=True)
+ for bxp_lines in ['whiskers', 'caps', 'boxes', 'medians']:
+ for each_line in bxp_handle[bxp_lines]:
+ # Ensure that the rcParams['lines.marker'] is overridden by ''
+ assert each_line.get_marker() == ''
+
+ # Ensure that markers for fliers and means aren't overridden with ''
+ assert bxp_handle['fliers'][0].get_marker() == 'o'
+ assert bxp_handle['means'][0].get_marker() == '^'
+
+
+@image_comparison(['boxplot_mod_artists_after_plotting.png'],
+ remove_text=True, savefig_kwarg={'dpi': 40}, style='default')
+def test_boxplot_mod_artist_after_plotting():
+ x = [0.15, 0.11, 0.06, 0.06, 0.12, 0.56, -0.56]
+ fig, ax = plt.subplots()
+ bp = ax.boxplot(x, sym="o")
+ for key in bp:
+ for obj in bp[key]:
+ obj.set_color('green')
+
+
+@image_comparison(['violinplot_vert_baseline.png',
+ 'violinplot_vert_baseline.png'])
+def test_vert_violinplot_baseline():
+ # First 9 digits of frac(sqrt(2))
+ np.random.seed(414213562)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax = plt.axes()
+ ax.violinplot(data, positions=range(4), showmeans=False, showextrema=False,
+ showmedians=False)
+
+ # Reuse testcase from above for a labeled data test
+ data = {"d": data}
+ fig, ax = plt.subplots()
+ ax.violinplot("d", positions=range(4), showmeans=False, showextrema=False,
+ showmedians=False, data=data)
+
+
+@image_comparison(['violinplot_vert_showmeans.png'])
+def test_vert_violinplot_showmeans():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(3))
+ np.random.seed(732050807)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), showmeans=True, showextrema=False,
+ showmedians=False)
+
+
+@image_comparison(['violinplot_vert_showextrema.png'])
+def test_vert_violinplot_showextrema():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(5))
+ np.random.seed(236067977)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), showmeans=False, showextrema=True,
+ showmedians=False)
+
+
+@image_comparison(['violinplot_vert_showmedians.png'])
+def test_vert_violinplot_showmedians():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(7))
+ np.random.seed(645751311)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), showmeans=False, showextrema=False,
+ showmedians=True)
+
+
+@image_comparison(['violinplot_vert_showall.png'])
+def test_vert_violinplot_showall():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(11))
+ np.random.seed(316624790)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), showmeans=True, showextrema=True,
+ showmedians=True,
+ quantiles=[[0.1, 0.9], [0.2, 0.8], [0.3, 0.7], [0.4, 0.6]])
+
+
+@image_comparison(['violinplot_vert_custompoints_10.png'])
+def test_vert_violinplot_custompoints_10():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(13))
+ np.random.seed(605551275)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), showmeans=False, showextrema=False,
+ showmedians=False, points=10)
+
+
+@image_comparison(['violinplot_vert_custompoints_200.png'])
+def test_vert_violinplot_custompoints_200():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(17))
+ np.random.seed(123105625)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), showmeans=False, showextrema=False,
+ showmedians=False, points=200)
+
+
+@image_comparison(['violinplot_horiz_baseline.png'])
+def test_horiz_violinplot_baseline():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(19))
+ np.random.seed(358898943)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
+ showextrema=False, showmedians=False)
+
+
+@image_comparison(['violinplot_horiz_showmedians.png'])
+def test_horiz_violinplot_showmedians():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(23))
+ np.random.seed(795831523)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
+ showextrema=False, showmedians=True)
+
+
+@image_comparison(['violinplot_horiz_showmeans.png'])
+def test_horiz_violinplot_showmeans():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(29))
+ np.random.seed(385164807)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=True,
+ showextrema=False, showmedians=False)
+
+
+@image_comparison(['violinplot_horiz_showextrema.png'])
+def test_horiz_violinplot_showextrema():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(31))
+ np.random.seed(567764362)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
+ showextrema=True, showmedians=False)
+
+
+@image_comparison(['violinplot_horiz_showall.png'])
+def test_horiz_violinplot_showall():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(37))
+ np.random.seed(82762530)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=True,
+ showextrema=True, showmedians=True,
+ quantiles=[[0.1, 0.9], [0.2, 0.8], [0.3, 0.7], [0.4, 0.6]])
+
+
+@image_comparison(['violinplot_horiz_custompoints_10.png'])
+def test_horiz_violinplot_custompoints_10():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(41))
+ np.random.seed(403124237)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
+ showextrema=False, showmedians=False, points=10)
+
+
+@image_comparison(['violinplot_horiz_custompoints_200.png'])
+def test_horiz_violinplot_custompoints_200():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(43))
+ np.random.seed(557438524)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
+ showextrema=False, showmedians=False, points=200)
+
+
+@image_comparison(['violinplot_sides.png'], remove_text=True, style='mpl20')
+def test_violinplot_sides():
+ ax = plt.axes()
+ np.random.seed(19680801)
+ data = [np.random.normal(size=100)]
+ # Check horizontal violinplot
+ for pos, side in zip([0, -0.5, 0.5], ['both', 'low', 'high']):
+ ax.violinplot(data, positions=[pos], orientation='horizontal', showmeans=False,
+ showextrema=True, showmedians=True, side=side)
+ # Check vertical violinplot
+ for pos, side in zip([4, 3.5, 4.5], ['both', 'low', 'high']):
+ ax.violinplot(data, positions=[pos], orientation='vertical', showmeans=False,
+ showextrema=True, showmedians=True, side=side)
+
+
+def test_violinplot_bad_positions():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(47))
+ np.random.seed(855654600)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ with pytest.raises(ValueError):
+ ax.violinplot(data, positions=range(5))
+
+
+def test_violinplot_bad_widths():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(53))
+ np.random.seed(280109889)
+ data = [np.random.normal(size=100) for _ in range(4)]
+ with pytest.raises(ValueError):
+ ax.violinplot(data, positions=range(4), widths=[1, 2, 3])
+
+
+def test_violinplot_bad_quantiles():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(73))
+ np.random.seed(544003745)
+ data = [np.random.normal(size=100)]
+
+ # Different size quantile list and plots
+ with pytest.raises(ValueError):
+ ax.violinplot(data, quantiles=[[0.1, 0.2], [0.5, 0.7]])
+
+
+def test_violinplot_outofrange_quantiles():
+ ax = plt.axes()
+ # First 9 digits of frac(sqrt(79))
+ np.random.seed(888194417)
+ data = [np.random.normal(size=100)]
+
+ # Quantile value above 100
+ with pytest.raises(ValueError):
+ ax.violinplot(data, quantiles=[[0.1, 0.2, 0.3, 1.05]])
+
+ # Quantile value below 0
+ with pytest.raises(ValueError):
+ ax.violinplot(data, quantiles=[[-0.05, 0.2, 0.3, 0.75]])
+
+
+@check_figures_equal(extensions=["png"])
+def test_violinplot_single_list_quantiles(fig_test, fig_ref):
+ # Ensures quantile list for 1D can be passed in as single list
+ # First 9 digits of frac(sqrt(83))
+ np.random.seed(110433579)
+ data = [np.random.normal(size=100)]
+
+ # Test image
+ ax = fig_test.subplots()
+ ax.violinplot(data, quantiles=[0.1, 0.3, 0.9])
+
+ # Reference image
+ ax = fig_ref.subplots()
+ ax.violinplot(data, quantiles=[[0.1, 0.3, 0.9]])
+
+
+@check_figures_equal(extensions=["png"])
+def test_violinplot_pandas_series(fig_test, fig_ref, pd):
+ np.random.seed(110433579)
+ s1 = pd.Series(np.random.normal(size=7), index=[9, 8, 7, 6, 5, 4, 3])
+ s2 = pd.Series(np.random.normal(size=9), index=list('ABCDEFGHI'))
+ s3 = pd.Series(np.random.normal(size=11))
+ fig_test.subplots().violinplot([s1, s2, s3])
+ fig_ref.subplots().violinplot([s1.values, s2.values, s3.values])
+
+
+def test_manage_xticks():
+ _, ax = plt.subplots()
+ ax.set_xlim(0, 4)
+ old_xlim = ax.get_xlim()
+ np.random.seed(0)
+ y1 = np.random.normal(10, 3, 20)
+ y2 = np.random.normal(3, 1, 20)
+ ax.boxplot([y1, y2], positions=[1, 2], manage_ticks=False)
+ new_xlim = ax.get_xlim()
+ assert_array_equal(old_xlim, new_xlim)
+
+
+def test_boxplot_not_single():
+ fig, ax = plt.subplots()
+ ax.boxplot(np.random.rand(100), positions=[3])
+ ax.boxplot(np.random.rand(100), positions=[5])
+ fig.canvas.draw()
+ assert ax.get_xlim() == (2.5, 5.5)
+ assert list(ax.get_xticks()) == [3, 5]
+ assert [t.get_text() for t in ax.get_xticklabels()] == ["3", "5"]
+
+
+def test_tick_space_size_0():
+ # allow font size to be zero, which affects ticks when there is
+ # no other text in the figure.
+ plt.plot([0, 1], [0, 1])
+ matplotlib.rcParams.update({'font.size': 0})
+ b = io.BytesIO()
+ plt.savefig(b, dpi=80, format='raw')
+
+
+@image_comparison(['errorbar_basic.png', 'errorbar_mixed.png', 'errorbar_basic.png'])
+def test_errorbar():
+ # longdouble due to floating point rounding issues with certain
+ # computer chipsets
+ x = np.arange(0.1, 4, 0.5, dtype=np.longdouble)
+ y = np.exp(-x)
+
+ yerr = 0.1 + 0.2*np.sqrt(x)
+ xerr = 0.1 + yerr
+
+ # First illustrate basic pyplot interface, using defaults where possible.
+ fig = plt.figure()
+ ax = fig.gca()
+ ax.errorbar(x, y, xerr=0.2, yerr=0.4)
+ ax.set_title("Simplest errorbars, 0.2 in x, 0.4 in y")
+
+ # Now switch to a more OO interface to exercise more features.
+ fig, axs = plt.subplots(nrows=2, ncols=2, sharex=True)
+ ax = axs[0, 0]
+ ax.errorbar(x, y, yerr=yerr, fmt='o')
+ ax.set_title('Vert. symmetric')
+
+ # With 4 subplots, reduce the number of axis ticks to avoid crowding.
+ ax.locator_params(nbins=4)
+
+ ax = axs[0, 1]
+ ax.errorbar(x, y, xerr=xerr, fmt='o', alpha=0.4)
+ ax.set_title('Hor. symmetric w/ alpha')
+
+ ax = axs[1, 0]
+ ax.errorbar(x, y, yerr=[yerr, 2*yerr], xerr=[xerr, 2*xerr], fmt='--o')
+ ax.set_title('H, V asymmetric')
+
+ ax = axs[1, 1]
+ ax.set_yscale('log')
+ # Here we have to be careful to keep all y values positive:
+ ylower = np.maximum(1e-2, y - yerr)
+ yerr_lower = y - ylower
+
+ ax.errorbar(x, y, yerr=[yerr_lower, 2*yerr], xerr=xerr,
+ fmt='o', ecolor='g', capthick=2)
+ ax.set_title('Mixed sym., log y')
+ # Force limits due to floating point slop potentially expanding the range
+ ax.set_ylim(1e-2, 1e1)
+
+ fig.suptitle('Variable errorbars')
+
+ # Reuse the first testcase from above for a labeled data test
+ data = {"x": x, "y": y}
+ fig = plt.figure()
+ ax = fig.gca()
+ ax.errorbar("x", "y", xerr=0.2, yerr=0.4, data=data)
+ ax.set_title("Simplest errorbars, 0.2 in x, 0.4 in y")
+
+
+@image_comparison(['mixed_errorbar_polar_caps.png'], remove_text=True)
+def test_mixed_errorbar_polar_caps():
+ """
+ Mix several polar errorbar use cases in a single test figure.
+
+ It is advisable to position individual points off the grid. If there are
+ problems with reproducibility of this test, consider removing grid.
+ """
+ fig = plt.figure()
+ ax = plt.subplot(111, projection='polar')
+
+ # symmetric errorbars
+ th_sym = [1, 2, 3]
+ r_sym = [0.9]*3
+ ax.errorbar(th_sym, r_sym, xerr=0.35, yerr=0.2, fmt="o")
+
+ # long errorbars
+ th_long = [np.pi/2 + .1, np.pi + .1]
+ r_long = [1.8, 2.2]
+ ax.errorbar(th_long, r_long, xerr=0.8 * np.pi, yerr=0.15, fmt="o")
+
+ # asymmetric errorbars
+ th_asym = [4*np.pi/3 + .1, 5*np.pi/3 + .1, 2*np.pi-0.1]
+ r_asym = [1.1]*3
+ xerr = [[.3, .3, .2], [.2, .3, .3]]
+ yerr = [[.35, .5, .5], [.5, .35, .5]]
+ ax.errorbar(th_asym, r_asym, xerr=xerr, yerr=yerr, fmt="o")
+
+ # overlapping errorbar
+ th_over = [2.1]
+ r_over = [3.1]
+ ax.errorbar(th_over, r_over, xerr=10, yerr=.2, fmt="o")
+
+
+def test_errorbar_colorcycle():
+
+ f, ax = plt.subplots()
+ x = np.arange(10)
+ y = 2*x
+
+ e1, _, _ = ax.errorbar(x, y, c=None)
+ e2, _, _ = ax.errorbar(x, 2*y, c=None)
+ ln1, = ax.plot(x, 4*y)
+
+ assert mcolors.to_rgba(e1.get_color()) == mcolors.to_rgba('C0')
+ assert mcolors.to_rgba(e2.get_color()) == mcolors.to_rgba('C1')
+ assert mcolors.to_rgba(ln1.get_color()) == mcolors.to_rgba('C2')
+
+
+@check_figures_equal(extensions=['png'])
+def test_errorbar_cycle_ecolor(fig_test, fig_ref):
+ x = np.arange(0.1, 4, 0.5)
+ y = [np.exp(-x+n) for n in range(4)]
+
+ axt = fig_test.subplots()
+ axr = fig_ref.subplots()
+
+ for yi, color in zip(y, ['C0', 'C1', 'C2', 'C3']):
+ axt.errorbar(x, yi, yerr=(yi * 0.25), linestyle='-',
+ marker='o', ecolor='black')
+ axr.errorbar(x, yi, yerr=(yi * 0.25), linestyle='-',
+ marker='o', color=color, ecolor='black')
+
+
+def test_errorbar_shape():
+ fig = plt.figure()
+ ax = fig.gca()
+
+ x = np.arange(0.1, 4, 0.5)
+ y = np.exp(-x)
+ yerr1 = 0.1 + 0.2*np.sqrt(x)
+ yerr = np.vstack((yerr1, 2*yerr1)).T
+ xerr = 0.1 + yerr
+
+ with pytest.raises(ValueError):
+ ax.errorbar(x, y, yerr=yerr, fmt='o')
+ with pytest.raises(ValueError):
+ ax.errorbar(x, y, xerr=xerr, fmt='o')
+ with pytest.raises(ValueError):
+ ax.errorbar(x, y, yerr=yerr, xerr=xerr, fmt='o')
+
+
+@image_comparison(['errorbar_limits.png'])
+def test_errorbar_limits():
+ x = np.arange(0.5, 5.5, 0.5)
+ y = np.exp(-x)
+ xerr = 0.1
+ yerr = 0.2
+ ls = 'dotted'
+
+ fig, ax = plt.subplots()
+
+ # standard error bars
+ ax.errorbar(x, y, xerr=xerr, yerr=yerr, ls=ls, color='blue')
+
+ # including upper limits
+ uplims = np.zeros_like(x)
+ uplims[[1, 5, 9]] = True
+ ax.errorbar(x, y+0.5, xerr=xerr, yerr=yerr, uplims=uplims, ls=ls,
+ color='green')
+
+ # including lower limits
+ lolims = np.zeros_like(x)
+ lolims[[2, 4, 8]] = True
+ ax.errorbar(x, y+1.0, xerr=xerr, yerr=yerr, lolims=lolims, ls=ls,
+ color='red')
+
+ # including upper and lower limits
+ ax.errorbar(x, y+1.5, marker='o', ms=8, xerr=xerr, yerr=yerr,
+ lolims=lolims, uplims=uplims, ls=ls, color='magenta')
+
+ # including xlower and xupper limits
+ xerr = 0.2
+ yerr = np.full_like(x, 0.2)
+ yerr[[3, 6]] = 0.3
+ xlolims = lolims
+ xuplims = uplims
+ lolims = np.zeros_like(x)
+ uplims = np.zeros_like(x)
+ lolims[[6]] = True
+ uplims[[3]] = True
+ ax.errorbar(x, y+2.1, marker='o', ms=8, xerr=xerr, yerr=yerr,
+ xlolims=xlolims, xuplims=xuplims, uplims=uplims,
+ lolims=lolims, ls='none', mec='blue', capsize=0,
+ color='cyan')
+ ax.set_xlim((0, 5.5))
+ ax.set_title('Errorbar upper and lower limits')
+
+
+def test_errorbar_nonefmt():
+ # Check that passing 'none' as a format still plots errorbars
+ x = np.arange(5)
+ y = np.arange(5)
+
+ plotline, _, barlines = plt.errorbar(x, y, xerr=1, yerr=1, fmt='none')
+ assert plotline is None
+ for errbar in barlines:
+ assert np.all(errbar.get_color() == mcolors.to_rgba('C0'))
+
+
+def test_errorbar_remove():
+ x = np.arange(5)
+ y = np.arange(5)
+
+ fig, ax = plt.subplots()
+ ec = ax.errorbar(x, y, xerr=1, yerr=1)
+
+ assert len(ax.containers) == 1
+ assert len(ax.lines) == 5
+ assert len(ax.collections) == 2
+
+ ec.remove()
+
+ assert not ax.containers
+ assert not ax.lines
+ assert not ax.collections
+
+
+def test_errorbar_line_specific_kwargs():
+ # Check that passing line-specific keyword arguments will not result in
+ # errors.
+ x = np.arange(5)
+ y = np.arange(5)
+
+ plotline, _, _ = plt.errorbar(x, y, xerr=1, yerr=1, ls='None',
+ marker='s', fillstyle='full',
+ drawstyle='steps-mid',
+ dash_capstyle='round',
+ dash_joinstyle='miter',
+ solid_capstyle='butt',
+ solid_joinstyle='bevel')
+ assert plotline.get_fillstyle() == 'full'
+ assert plotline.get_drawstyle() == 'steps-mid'
+
+
+@check_figures_equal(extensions=['png'])
+def test_errorbar_with_prop_cycle(fig_test, fig_ref):
+ ax = fig_ref.subplots()
+ ax.errorbar(x=[2, 4, 10], y=[0, 1, 2], yerr=0.5,
+ ls='--', marker='s', mfc='k')
+ ax.errorbar(x=[2, 4, 10], y=[2, 3, 4], yerr=0.5, color='tab:green',
+ ls=':', marker='s', mfc='y')
+ ax.errorbar(x=[2, 4, 10], y=[4, 5, 6], yerr=0.5, fmt='tab:blue',
+ ls='-.', marker='o', mfc='c')
+ ax.set_xlim(1, 11)
+
+ _cycle = cycler(ls=['--', ':', '-.'], marker=['s', 's', 'o'],
+ mfc=['k', 'y', 'c'], color=['b', 'g', 'r'])
+ plt.rc("axes", prop_cycle=_cycle)
+ ax = fig_test.subplots()
+ ax.errorbar(x=[2, 4, 10], y=[0, 1, 2], yerr=0.5)
+ ax.errorbar(x=[2, 4, 10], y=[2, 3, 4], yerr=0.5, color='tab:green')
+ ax.errorbar(x=[2, 4, 10], y=[4, 5, 6], yerr=0.5, fmt='tab:blue')
+ ax.set_xlim(1, 11)
+
+
+def test_errorbar_every_invalid():
+ x = np.linspace(0, 1, 15)
+ y = x * (1-x)
+ yerr = y/6
+
+ ax = plt.figure().subplots()
+
+ with pytest.raises(ValueError, match='not a tuple of two integers'):
+ ax.errorbar(x, y, yerr, errorevery=(1, 2, 3))
+ with pytest.raises(ValueError, match='not a tuple of two integers'):
+ ax.errorbar(x, y, yerr, errorevery=(1.3, 3))
+ with pytest.raises(ValueError, match='not a valid NumPy fancy index'):
+ ax.errorbar(x, y, yerr, errorevery=[False, True])
+ with pytest.raises(ValueError, match='not a recognized value'):
+ ax.errorbar(x, y, yerr, errorevery='foobar')
+
+
+def test_xerr_yerr_not_negative():
+ ax = plt.figure().subplots()
+
+ with pytest.raises(ValueError,
+ match="'xerr' must not contain negative values"):
+ ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]])
+ with pytest.raises(ValueError,
+ match="'xerr' must not contain negative values"):
+ ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]])
+ with pytest.raises(ValueError,
+ match="'yerr' must not contain negative values"):
+ ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]])
+ with pytest.raises(ValueError,
+ match="'yerr' must not contain negative values"):
+ x = np.arange(5)
+ y = [datetime.datetime(2021, 9, i * 2 + 1) for i in x]
+ ax.errorbar(x=x,
+ y=y,
+ yerr=datetime.timedelta(days=-10))
+
+
+def test_xerr_yerr_not_none():
+ ax = plt.figure().subplots()
+
+ with pytest.raises(ValueError,
+ match="'xerr' must not contain None"):
+ ax.errorbar(x=[0], y=[0], xerr=[[None], [1]], yerr=[[None], [1]])
+ with pytest.raises(ValueError,
+ match="'xerr' must not contain None"):
+ ax.errorbar(x=[0], y=[0], xerr=[[None], [1]])
+ with pytest.raises(ValueError,
+ match="'yerr' must not contain None"):
+ ax.errorbar(x=[0], y=[0], yerr=[[None], [1]])
+
+
+@check_figures_equal(extensions=['png'])
+def test_errorbar_every(fig_test, fig_ref):
+ x = np.linspace(0, 1, 15)
+ y = x * (1-x)
+ yerr = y/6
+
+ ax_ref = fig_ref.subplots()
+ ax_test = fig_test.subplots()
+
+ for color, shift in zip('rgbk', [0, 0, 2, 7]):
+ y += .02
+
+ # Check errorevery using an explicit offset and step.
+ ax_test.errorbar(x, y, yerr, errorevery=(shift, 4),
+ capsize=4, c=color)
+
+ # Using manual errorbars
+ # n.b. errorbar draws the main plot at z=2.1 by default
+ ax_ref.plot(x, y, c=color, zorder=2.1)
+ ax_ref.errorbar(x[shift::4], y[shift::4], yerr[shift::4],
+ capsize=4, c=color, fmt='none')
+
+ # Check that markevery is propagated to line, without affecting errorbars.
+ ax_test.errorbar(x, y + 0.1, yerr, markevery=(1, 4), capsize=4, fmt='o')
+ ax_ref.plot(x[1::4], y[1::4] + 0.1, 'o', zorder=2.1)
+ ax_ref.errorbar(x, y + 0.1, yerr, capsize=4, fmt='none')
+
+ # Check that passing a slice to markevery/errorevery works.
+ ax_test.errorbar(x, y + 0.2, yerr, errorevery=slice(2, None, 3),
+ markevery=slice(2, None, 3),
+ capsize=4, c='C0', fmt='o')
+ ax_ref.plot(x[2::3], y[2::3] + 0.2, 'o', c='C0', zorder=2.1)
+ ax_ref.errorbar(x[2::3], y[2::3] + 0.2, yerr[2::3],
+ capsize=4, c='C0', fmt='none')
+
+ # Check that passing an iterable to markevery/errorevery works.
+ ax_test.errorbar(x, y + 0.2, yerr, errorevery=[False, True, False] * 5,
+ markevery=[False, True, False] * 5,
+ capsize=4, c='C1', fmt='o')
+ ax_ref.plot(x[1::3], y[1::3] + 0.2, 'o', c='C1', zorder=2.1)
+ ax_ref.errorbar(x[1::3], y[1::3] + 0.2, yerr[1::3],
+ capsize=4, c='C1', fmt='none')
+
+
+@pytest.mark.parametrize('elinewidth', [[1, 2, 3],
+ np.array([1, 2, 3]),
+ 1])
+def test_errorbar_linewidth_type(elinewidth):
+ plt.errorbar([1, 2, 3], [1, 2, 3], yerr=[1, 2, 3], elinewidth=elinewidth)
+
+
+@check_figures_equal(extensions=["png"])
+def test_errorbar_nan(fig_test, fig_ref):
+ ax = fig_test.add_subplot()
+ xs = range(5)
+ ys = np.array([1, 2, np.nan, np.nan, 3])
+ es = np.array([4, 5, np.nan, np.nan, 6])
+ ax.errorbar(xs, ys, yerr=es)
+ ax = fig_ref.add_subplot()
+ ax.errorbar([0, 1], [1, 2], yerr=[4, 5])
+ ax.errorbar([4], [3], yerr=[6], fmt="C0")
+
+
+@check_figures_equal()
+def test_errorbar_masked_negative(fig_test, fig_ref):
+ ax = fig_test.add_subplot()
+ xs = range(5)
+ mask = np.array([False, False, True, True, False])
+ ys = np.ma.array([1, 2, 2, 2, 3], mask=mask)
+ es = np.ma.array([4, 5, -1, -10, 6], mask=mask)
+ ax.errorbar(xs, ys, yerr=es)
+ ax = fig_ref.add_subplot()
+ ax.errorbar([0, 1], [1, 2], yerr=[4, 5])
+ ax.errorbar([4], [3], yerr=[6], fmt="C0")
+
+
+@image_comparison(['hist_stacked_stepfilled.png', 'hist_stacked_stepfilled.png'])
+def test_hist_stacked_stepfilled():
+ # make some data
+ d1 = np.linspace(1, 3, 20)
+ d2 = np.linspace(0, 10, 50)
+ fig, ax = plt.subplots()
+ ax.hist((d1, d2), histtype="stepfilled", stacked=True)
+
+ # Reuse testcase from above for a labeled data test
+ data = {"x": (d1, d2)}
+ fig, ax = plt.subplots()
+ ax.hist("x", histtype="stepfilled", stacked=True, data=data)
+
+
+@image_comparison(['hist_offset.png'])
+def test_hist_offset():
+ # make some data
+ d1 = np.linspace(0, 10, 50)
+ d2 = np.linspace(1, 3, 20)
+ fig, ax = plt.subplots()
+ ax.hist(d1, bottom=5)
+ ax.hist(d2, bottom=15)
+
+
+@image_comparison(['hist_step.png'], remove_text=True)
+def test_hist_step():
+ # make some data
+ d1 = np.linspace(1, 3, 20)
+ fig, ax = plt.subplots()
+ ax.hist(d1, histtype="step")
+ ax.set_ylim(0, 10)
+ ax.set_xlim(-1, 5)
+
+
+@image_comparison(['hist_step_horiz.png'])
+def test_hist_step_horiz():
+ # make some data
+ d1 = np.linspace(0, 10, 50)
+ d2 = np.linspace(1, 3, 20)
+ fig, ax = plt.subplots()
+ ax.hist((d1, d2), histtype="step", orientation="horizontal")
+
+
+@image_comparison(['hist_stacked_weights.png'])
+def test_hist_stacked_weighted():
+ # make some data
+ d1 = np.linspace(0, 10, 50)
+ d2 = np.linspace(1, 3, 20)
+ w1 = np.linspace(0.01, 3.5, 50)
+ w2 = np.linspace(0.05, 2., 20)
+ fig, ax = plt.subplots()
+ ax.hist((d1, d2), weights=(w1, w2), histtype="stepfilled", stacked=True)
+
+
+@image_comparison(['stem.png'], style='mpl20', remove_text=True)
+def test_stem():
+ x = np.linspace(0.1, 2 * np.pi, 100)
+
+ fig, ax = plt.subplots()
+ # Label is a single space to force a legend to be drawn, but to avoid any
+ # text being drawn
+ ax.stem(x, np.cos(x),
+ linefmt='C2-.', markerfmt='k+', basefmt='C1-.', label=' ')
+ ax.legend()
+
+
+def test_stem_args():
+ """Test that stem() correctly identifies x and y values."""
+ def _assert_equal(stem_container, expected):
+ x, y = map(list, stem_container.markerline.get_data())
+ assert x == expected[0]
+ assert y == expected[1]
+
+ fig, ax = plt.subplots()
+
+ x = [1, 3, 5]
+ y = [9, 8, 7]
+
+ # Test the call signatures
+ _assert_equal(ax.stem(y), expected=([0, 1, 2], y))
+ _assert_equal(ax.stem(x, y), expected=(x, y))
+ _assert_equal(ax.stem(x, y, linefmt='r--'), expected=(x, y))
+ _assert_equal(ax.stem(x, y, 'r--'), expected=(x, y))
+ _assert_equal(ax.stem(x, y, linefmt='r--', basefmt='b--'), expected=(x, y))
+ _assert_equal(ax.stem(y, linefmt='r--'), expected=([0, 1, 2], y))
+ _assert_equal(ax.stem(y, 'r--'), expected=([0, 1, 2], y))
+
+ with pytest.raises(ValueError):
+ ax.stem([[y]])
+ with pytest.raises(ValueError):
+ ax.stem([[x]], y)
+
+
+def test_stem_markerfmt():
+ """Test that stem(..., markerfmt=...) produces the intended markers."""
+ def _assert_equal(stem_container, linecolor=None, markercolor=None,
+ marker=None):
+ """
+ Check that the given StemContainer has the properties listed as
+ keyword-arguments.
+ """
+ if linecolor is not None:
+ assert mcolors.same_color(
+ stem_container.stemlines.get_color(),
+ linecolor)
+ if markercolor is not None:
+ assert mcolors.same_color(
+ stem_container.markerline.get_color(),
+ markercolor)
+ if marker is not None:
+ assert stem_container.markerline.get_marker() == marker
+ assert stem_container.markerline.get_linestyle() == 'None'
+
+ fig, ax = plt.subplots()
+
+ x = [1, 3, 5]
+ y = [9, 8, 7]
+
+ # no linefmt
+ _assert_equal(ax.stem(x, y), markercolor='C0', marker='o')
+ _assert_equal(ax.stem(x, y, markerfmt='x'), markercolor='C0', marker='x')
+ _assert_equal(ax.stem(x, y, markerfmt='rx'), markercolor='r', marker='x')
+
+ # positional linefmt
+ _assert_equal(
+ ax.stem(x, y, 'r'), # marker color follows linefmt if not given
+ linecolor='r', markercolor='r', marker='o')
+ _assert_equal(
+ ax.stem(x, y, 'rx'), # the marker is currently not taken from linefmt
+ linecolor='r', markercolor='r', marker='o')
+ _assert_equal(
+ ax.stem(x, y, 'r', markerfmt='x'), # only marker type specified
+ linecolor='r', markercolor='r', marker='x')
+ _assert_equal(
+ ax.stem(x, y, 'r', markerfmt='g'), # only marker color specified
+ linecolor='r', markercolor='g', marker='o')
+ _assert_equal(
+ ax.stem(x, y, 'r', markerfmt='gx'), # marker type and color specified
+ linecolor='r', markercolor='g', marker='x')
+ _assert_equal(
+ ax.stem(x, y, 'r', markerfmt=' '), # markerfmt=' ' for no marker
+ linecolor='r', markercolor='r', marker='None')
+ _assert_equal(
+ ax.stem(x, y, 'r', markerfmt=''), # markerfmt='' for no marker
+ linecolor='r', markercolor='r', marker='None')
+
+ # with linefmt kwarg
+ _assert_equal(
+ ax.stem(x, y, linefmt='r'),
+ linecolor='r', markercolor='r', marker='o')
+ _assert_equal(
+ ax.stem(x, y, linefmt='r', markerfmt='x'),
+ linecolor='r', markercolor='r', marker='x')
+ _assert_equal(
+ ax.stem(x, y, linefmt='r', markerfmt='gx'),
+ linecolor='r', markercolor='g', marker='x')
+
+
+def test_stem_dates():
+ fig, ax = plt.subplots(1, 1)
+ xs = [dateutil.parser.parse("2013-9-28 11:00:00"),
+ dateutil.parser.parse("2013-9-28 12:00:00")]
+ ys = [100, 200]
+ ax.stem(xs, ys)
+
+
+@image_comparison(['stem_orientation.png'], style='mpl20', remove_text=True)
+def test_stem_orientation():
+ x = np.linspace(0.1, 2*np.pi, 50)
+
+ fig, ax = plt.subplots()
+ ax.stem(x, np.cos(x),
+ linefmt='C2-.', markerfmt='kx', basefmt='C1-.',
+ orientation='horizontal')
+
+
+@image_comparison(['hist_stacked_stepfilled_alpha.png'])
+def test_hist_stacked_stepfilled_alpha():
+ # make some data
+ d1 = np.linspace(1, 3, 20)
+ d2 = np.linspace(0, 10, 50)
+ fig, ax = plt.subplots()
+ ax.hist((d1, d2), histtype="stepfilled", stacked=True, alpha=0.5)
+
+
+@image_comparison(['hist_stacked_step.png'])
+def test_hist_stacked_step():
+ # make some data
+ d1 = np.linspace(1, 3, 20)
+ d2 = np.linspace(0, 10, 50)
+ fig, ax = plt.subplots()
+ ax.hist((d1, d2), histtype="step", stacked=True)
+
+
+@image_comparison(['hist_stacked_normed.png'])
+def test_hist_stacked_density():
+ # make some data
+ d1 = np.linspace(1, 3, 20)
+ d2 = np.linspace(0, 10, 50)
+ fig, ax = plt.subplots()
+ ax.hist((d1, d2), stacked=True, density=True)
+
+
+@image_comparison(['hist_step_bottom.png'], remove_text=True)
+def test_hist_step_bottom():
+ # make some data
+ d1 = np.linspace(1, 3, 20)
+ fig, ax = plt.subplots()
+ ax.hist(d1, bottom=np.arange(10), histtype="stepfilled")
+
+
+def test_hist_step_geometry():
+ bins = [0, 1, 2, 3]
+ data = [0, 0, 1, 1, 1, 2]
+ top = [[0, 0], [0, 2], [1, 2], [1, 3], [2, 3], [2, 1], [3, 1], [3, 0]]
+ bottom = [[2, 0], [2, 0], [1, 0], [1, 0], [0, 0]]
+
+ for histtype, xy in [('step', top), ('stepfilled', top + bottom)]:
+ _, _, (polygon, ) = plt.hist(data, bins=bins, histtype=histtype)
+ assert_array_equal(polygon.get_xy(), xy)
+
+
+def test_hist_step_bottom_geometry():
+ bins = [0, 1, 2, 3]
+ data = [0, 0, 1, 1, 1, 2]
+ top = [[0, 1], [0, 3], [1, 3], [1, 5], [2, 5], [2, 2.5], [3, 2.5], [3, 1.5]]
+ bottom = [[2, 1.5], [2, 2], [1, 2], [1, 1], [0, 1]]
+
+ for histtype, xy in [('step', top), ('stepfilled', top + bottom)]:
+ _, _, (polygon, ) = plt.hist(data, bins=bins, bottom=[1, 2, 1.5],
+ histtype=histtype)
+ assert_array_equal(polygon.get_xy(), xy)
+
+
+def test_hist_stacked_step_geometry():
+ bins = [0, 1, 2, 3]
+ data_1 = [0, 0, 1, 1, 1, 2]
+ data_2 = [0, 1, 2]
+ tops = [
+ [[0, 0], [0, 2], [1, 2], [1, 3], [2, 3], [2, 1], [3, 1], [3, 0]],
+ [[0, 2], [0, 3], [1, 3], [1, 4], [2, 4], [2, 2], [3, 2], [3, 1]],
+ ]
+ bottoms = [
+ [[2, 0], [2, 0], [1, 0], [1, 0], [0, 0]],
+ [[2, 1], [2, 3], [1, 3], [1, 2], [0, 2]],
+ ]
+ combined = [t + b for t, b in zip(tops, bottoms)]
+
+ for histtype, xy in [('step', tops), ('stepfilled', combined)]:
+ _, _, patches = plt.hist([data_1, data_2], bins=bins, stacked=True,
+ histtype=histtype)
+ assert len(patches) == 2
+ polygon, = patches[0]
+ assert_array_equal(polygon.get_xy(), xy[0])
+ polygon, = patches[1]
+ assert_array_equal(polygon.get_xy(), xy[1])
+
+
+def test_hist_stacked_step_bottom_geometry():
+ bins = [0, 1, 2, 3]
+ data_1 = [0, 0, 1, 1, 1, 2]
+ data_2 = [0, 1, 2]
+ tops = [
+ [[0, 1], [0, 3], [1, 3], [1, 5], [2, 5], [2, 2.5], [3, 2.5], [3, 1.5]],
+ [[0, 3], [0, 4], [1, 4], [1, 6], [2, 6], [2, 3.5], [3, 3.5], [3, 2.5]],
+ ]
+ bottoms = [
+ [[2, 1.5], [2, 2], [1, 2], [1, 1], [0, 1]],
+ [[2, 2.5], [2, 5], [1, 5], [1, 3], [0, 3]],
+ ]
+ combined = [t + b for t, b in zip(tops, bottoms)]
+
+ for histtype, xy in [('step', tops), ('stepfilled', combined)]:
+ _, _, patches = plt.hist([data_1, data_2], bins=bins, stacked=True,
+ bottom=[1, 2, 1.5], histtype=histtype)
+ assert len(patches) == 2
+ polygon, = patches[0]
+ assert_array_equal(polygon.get_xy(), xy[0])
+ polygon, = patches[1]
+ assert_array_equal(polygon.get_xy(), xy[1])
+
+
+@image_comparison(['hist_stacked_bar.png'])
+def test_hist_stacked_bar():
+ # make some data
+ d = [[100, 100, 100, 100, 200, 320, 450, 80, 20, 600, 310, 800],
+ [20, 23, 50, 11, 100, 420], [120, 120, 120, 140, 140, 150, 180],
+ [60, 60, 60, 60, 300, 300, 5, 5, 5, 5, 10, 300],
+ [555, 555, 555, 30, 30, 30, 30, 30, 100, 100, 100, 100, 30, 30],
+ [30, 30, 30, 30, 400, 400, 400, 400, 400, 400, 400, 400]]
+ colors = [(0.5759849696758961, 1.0, 0.0), (0.0, 1.0, 0.350624650815206),
+ (0.0, 1.0, 0.6549834156005998), (0.0, 0.6569064625276622, 1.0),
+ (0.28302699607823545, 0.0, 1.0), (0.6849123462299822, 0.0, 1.0)]
+ labels = ['green', 'orange', ' yellow', 'magenta', 'black']
+ fig, ax = plt.subplots()
+ ax.hist(d, bins=10, histtype='barstacked', align='mid', color=colors,
+ label=labels)
+ ax.legend(loc='upper right', bbox_to_anchor=(1.0, 1.0), ncols=1)
+
+
+@pytest.mark.parametrize('kwargs', ({'facecolor': ["b", "g", "r"]},
+ {'edgecolor': ["b", "g", "r"]},
+ {'hatch': ["/", "\\", "."]},
+ {'linestyle': ["-", "--", ":"]},
+ {'linewidth': [1, 1.5, 2]},
+ {'color': ["b", "g", "r"]}))
+@check_figures_equal(extensions=["png"])
+def test_hist_vectorized_params(fig_test, fig_ref, kwargs):
+ np.random.seed(19680801)
+ xs = [np.random.randn(n) for n in [20, 50, 100]]
+
+ (axt1, axt2) = fig_test.subplots(2)
+ (axr1, axr2) = fig_ref.subplots(2)
+
+ for histtype, axt, axr in [("stepfilled", axt1, axr1), ("step", axt2, axr2)]:
+ _, bins, _ = axt.hist(xs, bins=10, histtype=histtype, **kwargs)
+
+ kw, values = next(iter(kwargs.items()))
+ for i, (x, value) in enumerate(zip(xs, values)):
+ axr.hist(x, bins=bins, histtype=histtype, **{kw: value},
+ zorder=(len(xs)-i)/2)
+
+
+def test_hist_sequence_type_styles():
+ facecolor = ('r', 0.5)
+ edgecolor = [0.5, 0.5, 0.5]
+ linestyle = (0, (1, 1))
+
+ arr = np.random.uniform(size=50)
+ _, _, bars = plt.hist(arr, facecolor=facecolor, edgecolor=edgecolor,
+ linestyle=linestyle)
+ assert mcolors.same_color(bars[0].get_facecolor(), facecolor)
+ assert mcolors.same_color(bars[0].get_edgecolor(), edgecolor)
+ assert bars[0].get_linestyle() == linestyle
+
+
+def test_hist_color_none():
+ arr = np.random.uniform(size=50)
+ # No edgecolor is the default but check that it can be explicitly passed.
+ _, _, bars = plt.hist(arr, facecolor='none', edgecolor='none')
+ assert bars[0].get_facecolor(), (0, 0, 0, 0)
+ assert bars[0].get_edgecolor(), (0, 0, 0, 0)
+
+
+@pytest.mark.parametrize('kwargs, patch_face, patch_edge',
+ # 'C0'(blue) stands for the first color of the
+ # default color cycle as well as the patch.facecolor rcParam
+ # When the expected edgecolor is 'k'(black),
+ # it corresponds to the patch.edgecolor rcParam
+ [({'histtype': 'stepfilled', 'color': 'r',
+ 'facecolor': 'y', 'edgecolor': 'g'}, 'y', 'g'),
+ ({'histtype': 'step', 'color': 'r',
+ 'facecolor': 'y', 'edgecolor': 'g'}, ('y', 0), 'g'),
+ ({'histtype': 'stepfilled', 'color': 'r',
+ 'edgecolor': 'g'}, 'r', 'g'),
+ ({'histtype': 'step', 'color': 'r',
+ 'edgecolor': 'g'}, ('r', 0), 'g'),
+ ({'histtype': 'stepfilled', 'color': 'r',
+ 'facecolor': 'y'}, 'y', 'k'),
+ ({'histtype': 'step', 'color': 'r',
+ 'facecolor': 'y'}, ('y', 0), 'r'),
+ ({'histtype': 'stepfilled',
+ 'facecolor': 'y', 'edgecolor': 'g'}, 'y', 'g'),
+ ({'histtype': 'step', 'facecolor': 'y',
+ 'edgecolor': 'g'}, ('y', 0), 'g'),
+ ({'histtype': 'stepfilled', 'color': 'r'}, 'r', 'k'),
+ ({'histtype': 'step', 'color': 'r'}, ('r', 0), 'r'),
+ ({'histtype': 'stepfilled', 'facecolor': 'y'}, 'y', 'k'),
+ ({'histtype': 'step', 'facecolor': 'y'}, ('y', 0), 'C0'),
+ ({'histtype': 'stepfilled', 'edgecolor': 'g'}, 'C0', 'g'),
+ ({'histtype': 'step', 'edgecolor': 'g'}, ('C0', 0), 'g'),
+ ({'histtype': 'stepfilled'}, 'C0', 'k'),
+ ({'histtype': 'step'}, ('C0', 0), 'C0')])
+def test_hist_color_semantics(kwargs, patch_face, patch_edge):
+ _, _, patches = plt.figure().subplots().hist([1, 2, 3], **kwargs)
+ assert all(mcolors.same_color([p.get_facecolor(), p.get_edgecolor()],
+ [patch_face, patch_edge]) for p in patches)
+
+
+def test_hist_barstacked_bottom_unchanged():
+ b = np.array([10, 20])
+ plt.hist([[0, 1], [0, 1]], 2, histtype="barstacked", bottom=b)
+ assert b.tolist() == [10, 20]
+
+
+def test_hist_emptydata():
+ fig, ax = plt.subplots()
+ ax.hist([[], range(10), range(10)], histtype="step")
+
+
+def test_hist_unused_labels():
+ # When a list with one dataset and N elements is provided and N labels, ensure
+ # that the first label is used for the dataset and all other labels are ignored
+ fig, ax = plt.subplots()
+ ax.hist([[1, 2, 3]], label=["values", "unused", "also unused"])
+ _, labels = ax.get_legend_handles_labels()
+ assert labels == ["values"]
+
+
+def test_hist_labels():
+ # test singleton labels OK
+ fig, ax = plt.subplots()
+ _, _, bars = ax.hist([0, 1], label=0)
+ assert bars[0].get_label() == '0'
+ _, _, bars = ax.hist([0, 1], label=[0])
+ assert bars[0].get_label() == '0'
+ _, _, bars = ax.hist([0, 1], label=None)
+ assert bars[0].get_label() == '_nolegend_'
+ _, _, bars = ax.hist([0, 1], label='0')
+ assert bars[0].get_label() == '0'
+ _, _, bars = ax.hist([0, 1], label='00')
+ assert bars[0].get_label() == '00'
+
+
+@image_comparison(['transparent_markers'], remove_text=True)
+def test_transparent_markers():
+ np.random.seed(0)
+ data = np.random.random(50)
+
+ fig, ax = plt.subplots()
+ ax.plot(data, 'D', mfc='none', markersize=100)
+
+
+@image_comparison(['rgba_markers'], remove_text=True)
+def test_rgba_markers():
+ fig, axs = plt.subplots(ncols=2)
+ rcolors = [(1, 0, 0, 1), (1, 0, 0, 0.5)]
+ bcolors = [(0, 0, 1, 1), (0, 0, 1, 0.5)]
+ alphas = [None, 0.2]
+ kw = dict(ms=100, mew=20)
+ for i, alpha in enumerate(alphas):
+ for j, rcolor in enumerate(rcolors):
+ for k, bcolor in enumerate(bcolors):
+ axs[i].plot(j+1, k+1, 'o', mfc=bcolor, mec=rcolor,
+ alpha=alpha, **kw)
+ axs[i].plot(j+1, k+3, 'x', mec=rcolor, alpha=alpha, **kw)
+ for ax in axs:
+ ax.axis([-1, 4, 0, 5])
+
+
+@image_comparison(['mollweide_grid.png'], remove_text=True)
+def test_mollweide_grid():
+ # test that both horizontal and vertical gridlines appear on the Mollweide
+ # projection
+ fig = plt.figure()
+ ax = fig.add_subplot(projection='mollweide')
+ ax.grid()
+
+
+def test_mollweide_forward_inverse_closure():
+ # test that the round-trip Mollweide forward->inverse transformation is an
+ # approximate identity
+ fig = plt.figure()
+ ax = fig.add_subplot(projection='mollweide')
+
+ # set up 1-degree grid in longitude, latitude
+ lon = np.linspace(-np.pi, np.pi, 360)
+ # The poles are degenerate and thus sensitive to floating point precision errors
+ lat = np.linspace(-np.pi / 2.0, np.pi / 2.0, 180)[1:-1]
+ lon, lat = np.meshgrid(lon, lat)
+ ll = np.vstack((lon.flatten(), lat.flatten())).T
+
+ # perform forward transform
+ xy = ax.transProjection.transform(ll)
+
+ # perform inverse transform
+ ll2 = ax.transProjection.inverted().transform(xy)
+
+ # compare
+ np.testing.assert_array_almost_equal(ll, ll2, 3)
+
+
+def test_mollweide_inverse_forward_closure():
+ # test that the round-trip Mollweide inverse->forward transformation is an
+ # approximate identity
+ fig = plt.figure()
+ ax = fig.add_subplot(projection='mollweide')
+
+ # set up grid in x, y
+ x = np.linspace(0, 1, 500)
+ x, y = np.meshgrid(x, x)
+ xy = np.vstack((x.flatten(), y.flatten())).T
+
+ # perform inverse transform
+ ll = ax.transProjection.inverted().transform(xy)
+
+ # perform forward transform
+ xy2 = ax.transProjection.transform(ll)
+
+ # compare
+ np.testing.assert_array_almost_equal(xy, xy2, 3)
+
+
+@image_comparison(['test_alpha'], remove_text=True)
+def test_alpha():
+ np.random.seed(0)
+ data = np.random.random(50)
+
+ fig, ax = plt.subplots()
+
+ # alpha=.5 markers, solid line
+ ax.plot(data, '-D', color=[1, 0, 0], mfc=[1, 0, 0, .5],
+ markersize=20, lw=10)
+
+ # everything solid by kwarg
+ ax.plot(data + 2, '-D', color=[1, 0, 0, .5], mfc=[1, 0, 0, .5],
+ markersize=20, lw=10,
+ alpha=1)
+
+ # everything alpha=.5 by kwarg
+ ax.plot(data + 4, '-D', color=[1, 0, 0], mfc=[1, 0, 0],
+ markersize=20, lw=10,
+ alpha=.5)
+
+ # everything alpha=.5 by colors
+ ax.plot(data + 6, '-D', color=[1, 0, 0, .5], mfc=[1, 0, 0, .5],
+ markersize=20, lw=10)
+
+ # alpha=.5 line, solid markers
+ ax.plot(data + 8, '-D', color=[1, 0, 0, .5], mfc=[1, 0, 0],
+ markersize=20, lw=10)
+
+
+@image_comparison(['eventplot.png', 'eventplot.png'], remove_text=True)
+def test_eventplot():
+ np.random.seed(0)
+
+ data1 = np.random.random([32, 20]).tolist()
+ data2 = np.random.random([6, 20]).tolist()
+ data = data1 + data2
+ num_datasets = len(data)
+
+ colors1 = [[0, 1, .7]] * len(data1)
+ colors2 = [[1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1],
+ [1, .75, 0],
+ [1, 0, 1],
+ [0, 1, 1]]
+ colors = colors1 + colors2
+
+ lineoffsets1 = 12 + np.arange(0, len(data1)) * .33
+ lineoffsets2 = [-15, -3, 1, 1.5, 6, 10]
+ lineoffsets = lineoffsets1.tolist() + lineoffsets2
+
+ linelengths1 = [.33] * len(data1)
+ linelengths2 = [5, 2, 1, 1, 3, 1.5]
+ linelengths = linelengths1 + linelengths2
+
+ fig = plt.figure()
+ axobj = fig.add_subplot()
+ colls = axobj.eventplot(data, colors=colors, lineoffsets=lineoffsets,
+ linelengths=linelengths)
+
+ num_collections = len(colls)
+ assert num_collections == num_datasets
+
+ # Reuse testcase from above for a labeled data test
+ data = {"pos": data, "c": colors, "lo": lineoffsets, "ll": linelengths}
+ fig = plt.figure()
+ axobj = fig.add_subplot()
+ colls = axobj.eventplot("pos", colors="c", lineoffsets="lo",
+ linelengths="ll", data=data)
+ num_collections = len(colls)
+ assert num_collections == num_datasets
+
+
+@image_comparison(['test_eventplot_defaults.png'], remove_text=True)
+def test_eventplot_defaults():
+ """
+ test that eventplot produces the correct output given the default params
+ (see bug #3728)
+ """
+ np.random.seed(0)
+
+ data1 = np.random.random([32, 20]).tolist()
+ data2 = np.random.random([6, 20]).tolist()
+ data = data1 + data2
+
+ fig = plt.figure()
+ axobj = fig.add_subplot()
+ axobj.eventplot(data)
+
+
+@pytest.mark.parametrize(('colors'), [
+ ('0.5',), # string color with multiple characters: not OK before #8193 fix
+ ('tab:orange', 'tab:pink', 'tab:cyan', 'bLacK'), # case-insensitive
+ ('red', (0, 1, 0), None, (1, 0, 1, 0.5)), # a tricky case mixing types
+])
+def test_eventplot_colors(colors):
+ """Test the *colors* parameter of eventplot. Inspired by issue #8193."""
+ data = [[0], [1], [2], [3]] # 4 successive events of different nature
+
+ # Build the list of the expected colors
+ expected = [c if c is not None else 'C0' for c in colors]
+ # Convert the list into an array of RGBA values
+ # NB: ['rgbk'] is not a valid argument for to_rgba_array, while 'rgbk' is.
+ if len(expected) == 1:
+ expected = expected[0]
+ expected = np.broadcast_to(mcolors.to_rgba_array(expected), (len(data), 4))
+
+ fig, ax = plt.subplots()
+ if len(colors) == 1: # tuple with a single string (like '0.5' or 'rgbk')
+ colors = colors[0]
+ collections = ax.eventplot(data, colors=colors)
+
+ for coll, color in zip(collections, expected):
+ assert_allclose(coll.get_color(), color)
+
+
+def test_eventplot_alpha():
+ fig, ax = plt.subplots()
+
+ # one alpha for all
+ collections = ax.eventplot([[0, 2, 4], [1, 3, 5, 7]], alpha=0.7)
+ assert collections[0].get_alpha() == 0.7
+ assert collections[1].get_alpha() == 0.7
+
+ # one alpha per collection
+ collections = ax.eventplot([[0, 2, 4], [1, 3, 5, 7]], alpha=[0.5, 0.7])
+ assert collections[0].get_alpha() == 0.5
+ assert collections[1].get_alpha() == 0.7
+
+ with pytest.raises(ValueError, match="alpha and positions are unequal"):
+ ax.eventplot([[0, 2, 4], [1, 3, 5, 7]], alpha=[0.5, 0.7, 0.9])
+
+ with pytest.raises(ValueError, match="alpha and positions are unequal"):
+ ax.eventplot([0, 2, 4], alpha=[0.5, 0.7])
+
+
+@image_comparison(['test_eventplot_problem_kwargs.png'], remove_text=True)
+def test_eventplot_problem_kwargs(recwarn):
+ """
+ test that 'singular' versions of LineCollection props raise an
+ MatplotlibDeprecationWarning rather than overriding the 'plural' versions
+ (e.g., to prevent 'color' from overriding 'colors', see issue #4297)
+ """
+ np.random.seed(0)
+
+ data1 = np.random.random([20]).tolist()
+ data2 = np.random.random([10]).tolist()
+ data = [data1, data2]
+
+ fig = plt.figure()
+ axobj = fig.add_subplot()
+
+ axobj.eventplot(data,
+ colors=['r', 'b'],
+ color=['c', 'm'],
+ linewidths=[2, 1],
+ linewidth=[1, 2],
+ linestyles=['solid', 'dashed'],
+ linestyle=['dashdot', 'dotted'])
+
+ assert len(recwarn) == 3
+ assert all(issubclass(wi.category, mpl.MatplotlibDeprecationWarning)
+ for wi in recwarn)
+
+
+def test_empty_eventplot():
+ fig, ax = plt.subplots(1, 1)
+ ax.eventplot([[]], colors=[(0.0, 0.0, 0.0, 0.0)])
+ plt.draw()
+
+
+@pytest.mark.parametrize('data', [[[]], [[], [0, 1]], [[0, 1], []]])
+@pytest.mark.parametrize('orientation', [None, 'vertical', 'horizontal'])
+def test_eventplot_orientation(data, orientation):
+ """Introduced when fixing issue #6412."""
+ opts = {} if orientation is None else {'orientation': orientation}
+ fig, ax = plt.subplots(1, 1)
+ ax.eventplot(data, **opts)
+ plt.draw()
+
+
+@check_figures_equal(extensions=['png'])
+def test_eventplot_units_list(fig_test, fig_ref):
+ # test that list of lists converted properly:
+ ts_1 = [datetime.datetime(2021, 1, 1), datetime.datetime(2021, 1, 2),
+ datetime.datetime(2021, 1, 3)]
+ ts_2 = [datetime.datetime(2021, 1, 15), datetime.datetime(2021, 1, 16)]
+
+ ax = fig_ref.subplots()
+ ax.eventplot(ts_1, lineoffsets=0)
+ ax.eventplot(ts_2, lineoffsets=1)
+
+ ax = fig_test.subplots()
+ ax.eventplot([ts_1, ts_2])
+
+
+@image_comparison(['marker_styles.png'], remove_text=True)
+def test_marker_styles():
+ fig, ax = plt.subplots()
+ # Since generation of the test image, None was removed but 'none' was
+ # added. By moving 'none' to the front (=former sorted place of None)
+ # we can avoid regenerating the test image. This can be removed if the
+ # test image has to be regenerated for other reasons.
+ markers = sorted(matplotlib.markers.MarkerStyle.markers,
+ key=lambda x: str(type(x))+str(x))
+ markers.remove('none')
+ markers = ['none', *markers]
+ for y, marker in enumerate(markers):
+ ax.plot((y % 2)*5 + np.arange(10)*10, np.ones(10)*10*y, linestyle='',
+ marker=marker, markersize=10+y/5, label=marker)
+
+
+@image_comparison(['rc_markerfill.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.037)
+def test_markers_fillstyle_rcparams():
+ fig, ax = plt.subplots()
+ x = np.arange(7)
+ for idx, (style, marker) in enumerate(
+ [('top', 's'), ('bottom', 'o'), ('none', '^')]):
+ matplotlib.rcParams['markers.fillstyle'] = style
+ ax.plot(x+idx, marker=marker)
+
+
+@image_comparison(['vertex_markers.png'], remove_text=True)
+def test_vertex_markers():
+ data = list(range(10))
+ marker_as_tuple = ((-1, -1), (1, -1), (1, 1), (-1, 1))
+ marker_as_list = [(-1, -1), (1, -1), (1, 1), (-1, 1)]
+ fig, ax = plt.subplots()
+ ax.plot(data, linestyle='', marker=marker_as_tuple, mfc='k')
+ ax.plot(data[::-1], linestyle='', marker=marker_as_list, mfc='b')
+ ax.set_xlim([-1, 10])
+ ax.set_ylim([-1, 10])
+
+
+@image_comparison(['vline_hline_zorder.png', 'errorbar_zorder.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.026)
+def test_eb_line_zorder():
+ x = list(range(10))
+
+ # First illustrate basic pyplot interface, using defaults where possible.
+ fig = plt.figure()
+ ax = fig.gca()
+ ax.plot(x, lw=10, zorder=5)
+ ax.axhline(1, color='red', lw=10, zorder=1)
+ ax.axhline(5, color='green', lw=10, zorder=10)
+ ax.axvline(7, color='m', lw=10, zorder=7)
+ ax.axvline(2, color='k', lw=10, zorder=3)
+
+ ax.set_title("axvline and axhline zorder test")
+
+ # Now switch to a more OO interface to exercise more features.
+ fig = plt.figure()
+ ax = fig.gca()
+ x = list(range(10))
+ y = np.zeros(10)
+ yerr = list(range(10))
+ ax.errorbar(x, y, yerr=yerr, zorder=5, lw=5, color='r')
+ for j in range(10):
+ ax.axhline(j, lw=5, color='k', zorder=j)
+ ax.axhline(-j, lw=5, color='k', zorder=j)
+
+ ax.set_title("errorbar zorder test")
+
+
+@check_figures_equal(extensions=['png'])
+def test_axline_loglog(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ ax.set(xlim=(0.1, 10), ylim=(1e-3, 1))
+ ax.loglog([.3, .6], [.3, .6], ".-")
+ ax.axline((1, 1e-3), (10, 1e-2), c="k")
+
+ ax = fig_ref.subplots()
+ ax.set(xlim=(0.1, 10), ylim=(1e-3, 1))
+ ax.loglog([.3, .6], [.3, .6], ".-")
+ ax.loglog([1, 10], [1e-3, 1e-2], c="k")
+
+
+@check_figures_equal(extensions=['png'])
+def test_axline(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ ax.set(xlim=(-1, 1), ylim=(-1, 1))
+ ax.axline((0, 0), (1, 1))
+ ax.axline((0, 0), (1, 0), color='C1')
+ ax.axline((0, 0.5), (1, 0.5), color='C2')
+ # slopes
+ ax.axline((-0.7, -0.5), slope=0, color='C3')
+ ax.axline((1, -0.5), slope=-0.5, color='C4')
+ ax.axline((-0.5, 1), slope=float('inf'), color='C5')
+
+ ax = fig_ref.subplots()
+ ax.set(xlim=(-1, 1), ylim=(-1, 1))
+ ax.plot([-1, 1], [-1, 1])
+ ax.axhline(0, color='C1')
+ ax.axhline(0.5, color='C2')
+ # slopes
+ ax.axhline(-0.5, color='C3')
+ ax.plot([-1, 1], [0.5, -0.5], color='C4')
+ ax.axvline(-0.5, color='C5')
+
+
+@check_figures_equal(extensions=['png'])
+def test_axline_transaxes(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ ax.set(xlim=(-1, 1), ylim=(-1, 1))
+ ax.axline((0, 0), slope=1, transform=ax.transAxes)
+ ax.axline((1, 0.5), slope=1, color='C1', transform=ax.transAxes)
+ ax.axline((0.5, 0.5), slope=0, color='C2', transform=ax.transAxes)
+ ax.axline((0.5, 0), (0.5, 1), color='C3', transform=ax.transAxes)
+
+ ax = fig_ref.subplots()
+ ax.set(xlim=(-1, 1), ylim=(-1, 1))
+ ax.plot([-1, 1], [-1, 1])
+ ax.plot([0, 1], [-1, 0], color='C1')
+ ax.plot([-1, 1], [0, 0], color='C2')
+ ax.plot([0, 0], [-1, 1], color='C3')
+
+
+@check_figures_equal(extensions=['png'])
+def test_axline_transaxes_panzoom(fig_test, fig_ref):
+ # test that it is robust against pan/zoom and
+ # figure resize after plotting
+ ax = fig_test.subplots()
+ ax.set(xlim=(-1, 1), ylim=(-1, 1))
+ ax.axline((0, 0), slope=1, transform=ax.transAxes)
+ ax.axline((0.5, 0.5), slope=2, color='C1', transform=ax.transAxes)
+ ax.axline((0.5, 0.5), slope=0, color='C2', transform=ax.transAxes)
+ ax.set(xlim=(0, 5), ylim=(0, 10))
+ fig_test.set_size_inches(3, 3)
+
+ ax = fig_ref.subplots()
+ ax.set(xlim=(0, 5), ylim=(0, 10))
+ fig_ref.set_size_inches(3, 3)
+ ax.plot([0, 5], [0, 5])
+ ax.plot([0, 5], [0, 10], color='C1')
+ ax.plot([0, 5], [5, 5], color='C2')
+
+
+def test_axline_args():
+ """Exactly one of *xy2* and *slope* must be specified."""
+ fig, ax = plt.subplots()
+ with pytest.raises(TypeError):
+ ax.axline((0, 0)) # missing second parameter
+ with pytest.raises(TypeError):
+ ax.axline((0, 0), (1, 1), slope=1) # redundant parameters
+ ax.set_xscale('log')
+ with pytest.raises(TypeError):
+ ax.axline((0, 0), slope=1)
+ ax.set_xscale('linear')
+ ax.set_yscale('log')
+ with pytest.raises(TypeError):
+ ax.axline((0, 0), slope=1)
+ ax.set_yscale('linear')
+ with pytest.raises(ValueError):
+ ax.axline((0, 0), (0, 0)) # two identical points are not allowed
+ plt.draw()
+
+
+@image_comparison(['vlines_basic', 'vlines_with_nan', 'vlines_masked'],
+ extensions=['png'])
+def test_vlines():
+ # normal
+ x1 = [2, 3, 4, 5, 7]
+ y1 = [2, -6, 3, 8, 2]
+ fig1, ax1 = plt.subplots()
+ ax1.vlines(x1, 0, y1, colors='g', linewidth=5)
+
+ # GH #7406
+ x2 = [2, 3, 4, 5, 6, 7]
+ y2 = [2, -6, 3, 8, np.nan, 2]
+ fig2, (ax2, ax3, ax4) = plt.subplots(nrows=3, figsize=(4, 8))
+ ax2.vlines(x2, 0, y2, colors='g', linewidth=5)
+
+ x3 = [2, 3, 4, 5, 6, 7]
+ y3 = [np.nan, 2, -6, 3, 8, 2]
+ ax3.vlines(x3, 0, y3, colors='r', linewidth=3, linestyle='--')
+
+ x4 = [2, 3, 4, 5, 6, 7]
+ y4 = [np.nan, 2, -6, 3, 8, np.nan]
+ ax4.vlines(x4, 0, y4, colors='k', linewidth=2)
+
+ # tweak the x-axis so we can see the lines better
+ for ax in [ax1, ax2, ax3, ax4]:
+ ax.set_xlim(0, 10)
+
+ # check that the y-lims are all automatically the same
+ assert ax1.get_ylim() == ax2.get_ylim()
+ assert ax1.get_ylim() == ax3.get_ylim()
+ assert ax1.get_ylim() == ax4.get_ylim()
+
+ fig3, ax5 = plt.subplots()
+ x5 = np.ma.masked_equal([2, 4, 6, 8, 10, 12], 8)
+ ymin5 = np.ma.masked_equal([0, 1, -1, 0, 2, 1], 2)
+ ymax5 = np.ma.masked_equal([13, 14, 15, 16, 17, 18], 18)
+ ax5.vlines(x5, ymin5, ymax5, colors='k', linewidth=2)
+ ax5.set_xlim(0, 15)
+
+
+def test_vlines_default():
+ fig, ax = plt.subplots()
+ with mpl.rc_context({'lines.color': 'red'}):
+ lines = ax.vlines(0.5, 0, 1)
+ assert mpl.colors.same_color(lines.get_color(), 'red')
+
+
+@image_comparison(['hlines_basic', 'hlines_with_nan', 'hlines_masked'],
+ extensions=['png'])
+def test_hlines():
+ # normal
+ y1 = [2, 3, 4, 5, 7]
+ x1 = [2, -6, 3, 8, 2]
+ fig1, ax1 = plt.subplots()
+ ax1.hlines(y1, 0, x1, colors='g', linewidth=5)
+
+ # GH #7406
+ y2 = [2, 3, 4, 5, 6, 7]
+ x2 = [2, -6, 3, 8, np.nan, 2]
+ fig2, (ax2, ax3, ax4) = plt.subplots(nrows=3, figsize=(4, 8))
+ ax2.hlines(y2, 0, x2, colors='g', linewidth=5)
+
+ y3 = [2, 3, 4, 5, 6, 7]
+ x3 = [np.nan, 2, -6, 3, 8, 2]
+ ax3.hlines(y3, 0, x3, colors='r', linewidth=3, linestyle='--')
+
+ y4 = [2, 3, 4, 5, 6, 7]
+ x4 = [np.nan, 2, -6, 3, 8, np.nan]
+ ax4.hlines(y4, 0, x4, colors='k', linewidth=2)
+
+ # tweak the y-axis so we can see the lines better
+ for ax in [ax1, ax2, ax3, ax4]:
+ ax.set_ylim(0, 10)
+
+ # check that the x-lims are all automatically the same
+ assert ax1.get_xlim() == ax2.get_xlim()
+ assert ax1.get_xlim() == ax3.get_xlim()
+ assert ax1.get_xlim() == ax4.get_xlim()
+
+ fig3, ax5 = plt.subplots()
+ y5 = np.ma.masked_equal([2, 4, 6, 8, 10, 12], 8)
+ xmin5 = np.ma.masked_equal([0, 1, -1, 0, 2, 1], 2)
+ xmax5 = np.ma.masked_equal([13, 14, 15, 16, 17, 18], 18)
+ ax5.hlines(y5, xmin5, xmax5, colors='k', linewidth=2)
+ ax5.set_ylim(0, 15)
+
+
+def test_hlines_default():
+ fig, ax = plt.subplots()
+ with mpl.rc_context({'lines.color': 'red'}):
+ lines = ax.hlines(0.5, 0, 1)
+ assert mpl.colors.same_color(lines.get_color(), 'red')
+
+
+@pytest.mark.parametrize('data', [[1, 2, 3, np.nan, 5],
+ np.ma.masked_equal([1, 2, 3, 4, 5], 4)])
+@check_figures_equal(extensions=["png"])
+def test_lines_with_colors(fig_test, fig_ref, data):
+ test_colors = ['red', 'green', 'blue', 'purple', 'orange']
+ fig_test.add_subplot(2, 1, 1).vlines(data, 0, 1,
+ colors=test_colors, linewidth=5)
+ fig_test.add_subplot(2, 1, 2).hlines(data, 0, 1,
+ colors=test_colors, linewidth=5)
+
+ expect_xy = [1, 2, 3, 5]
+ expect_color = ['red', 'green', 'blue', 'orange']
+ fig_ref.add_subplot(2, 1, 1).vlines(expect_xy, 0, 1,
+ colors=expect_color, linewidth=5)
+ fig_ref.add_subplot(2, 1, 2).hlines(expect_xy, 0, 1,
+ colors=expect_color, linewidth=5)
+
+
+@image_comparison(['vlines_hlines_blended_transform'],
+ extensions=['png'], style='mpl20')
+def test_vlines_hlines_blended_transform():
+ t = np.arange(5.0, 10.0, 0.1)
+ s = np.exp(-t) + np.sin(2 * np.pi * t) + 10
+ fig, (hax, vax) = plt.subplots(2, 1, figsize=(6, 6))
+ hax.plot(t, s, '^')
+ hax.hlines([10, 9], xmin=0, xmax=0.5,
+ transform=hax.get_yaxis_transform(), colors='r')
+ vax.plot(t, s, '^')
+ vax.vlines([6, 7], ymin=0, ymax=0.15, transform=vax.get_xaxis_transform(),
+ colors='r')
+
+
+@image_comparison(['step_linestyle', 'step_linestyle'], remove_text=True,
+ tol=0.2)
+def test_step_linestyle():
+ # Tolerance caused by reordering of floating-point operations
+ # Remove when regenerating the images
+ x = y = np.arange(10)
+
+ # First illustrate basic pyplot interface, using defaults where possible.
+ fig, ax_lst = plt.subplots(2, 2)
+ ax_lst = ax_lst.flatten()
+
+ ln_styles = ['-', '--', '-.', ':']
+
+ for ax, ls in zip(ax_lst, ln_styles):
+ ax.step(x, y, lw=5, linestyle=ls, where='pre')
+ ax.step(x, y + 1, lw=5, linestyle=ls, where='mid')
+ ax.step(x, y + 2, lw=5, linestyle=ls, where='post')
+ ax.set_xlim([-1, 5])
+ ax.set_ylim([-1, 7])
+
+ # Reuse testcase from above for a labeled data test
+ data = {"X": x, "Y0": y, "Y1": y+1, "Y2": y+2}
+ fig, ax_lst = plt.subplots(2, 2)
+ ax_lst = ax_lst.flatten()
+ ln_styles = ['-', '--', '-.', ':']
+ for ax, ls in zip(ax_lst, ln_styles):
+ ax.step("X", "Y0", lw=5, linestyle=ls, where='pre', data=data)
+ ax.step("X", "Y1", lw=5, linestyle=ls, where='mid', data=data)
+ ax.step("X", "Y2", lw=5, linestyle=ls, where='post', data=data)
+ ax.set_xlim([-1, 5])
+ ax.set_ylim([-1, 7])
+
+
+@image_comparison(['mixed_collection'], remove_text=True)
+def test_mixed_collection():
+ # First illustrate basic pyplot interface, using defaults where possible.
+ fig, ax = plt.subplots()
+
+ c = mpatches.Circle((8, 8), radius=4, facecolor='none', edgecolor='green')
+
+ # PDF can optimize this one
+ p1 = mpl.collections.PatchCollection([c], match_original=True)
+ p1.set_offsets([[0, 0], [24, 24]])
+ p1.set_linewidths([1, 5])
+
+ # PDF can't optimize this one, because the alpha of the edge changes
+ p2 = mpl.collections.PatchCollection([c], match_original=True)
+ p2.set_offsets([[48, 0], [-32, -16]])
+ p2.set_linewidths([1, 5])
+ p2.set_edgecolors([[0, 0, 0.1, 1.0], [0, 0, 0.1, 0.5]])
+
+ ax.patch.set_color('0.5')
+ ax.add_collection(p1)
+ ax.add_collection(p2)
+
+ ax.set_xlim(0, 16)
+ ax.set_ylim(0, 16)
+
+
+def test_subplot_key_hash():
+ ax = plt.subplot(np.int32(5), np.int64(1), 1)
+ ax.twinx()
+ assert ax.get_subplotspec().get_geometry() == (5, 1, 0, 0)
+
+
+@image_comparison(
+ ["specgram_freqs.png", "specgram_freqs_linear.png",
+ "specgram_noise.png", "specgram_noise_linear.png"],
+ remove_text=True, tol=0.07, style="default")
+def test_specgram():
+ """Test axes.specgram in default (psd) mode."""
+
+ # use former defaults to match existing baseline image
+ matplotlib.rcParams['image.interpolation'] = 'nearest'
+
+ n = 1000
+ Fs = 10.
+
+ fstims = [[Fs/4, Fs/5, Fs/11], [Fs/4.7, Fs/5.6, Fs/11.9]]
+ NFFT_freqs = int(10 * Fs / np.min(fstims))
+ x = np.arange(0, n, 1/Fs)
+ y_freqs = np.concatenate(
+ np.sin(2 * np.pi * np.multiply.outer(fstims, x)).sum(axis=1))
+
+ NFFT_noise = int(10 * Fs / 11)
+ np.random.seed(0)
+ y_noise = np.concatenate([np.random.standard_normal(n), np.random.rand(n)])
+
+ all_sides = ["default", "onesided", "twosided"]
+ for y, NFFT in [(y_freqs, NFFT_freqs), (y_noise, NFFT_noise)]:
+ noverlap = NFFT // 2
+ pad_to = int(2 ** np.ceil(np.log2(NFFT)))
+ for ax, sides in zip(plt.figure().subplots(3), all_sides):
+ ax.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
+ pad_to=pad_to, sides=sides)
+ for ax, sides in zip(plt.figure().subplots(3), all_sides):
+ ax.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
+ pad_to=pad_to, sides=sides,
+ scale="linear", norm=matplotlib.colors.LogNorm())
+
+
+@image_comparison(
+ ["specgram_magnitude_freqs.png", "specgram_magnitude_freqs_linear.png",
+ "specgram_magnitude_noise.png", "specgram_magnitude_noise_linear.png"],
+ remove_text=True, tol=0.07, style="default")
+def test_specgram_magnitude():
+ """Test axes.specgram in magnitude mode."""
+
+ # use former defaults to match existing baseline image
+ matplotlib.rcParams['image.interpolation'] = 'nearest'
+
+ n = 1000
+ Fs = 10.
+
+ fstims = [[Fs/4, Fs/5, Fs/11], [Fs/4.7, Fs/5.6, Fs/11.9]]
+ NFFT_freqs = int(100 * Fs / np.min(fstims))
+ x = np.arange(0, n, 1/Fs)
+ y = np.sin(2 * np.pi * np.multiply.outer(fstims, x)).sum(axis=1)
+ y[:, -1] = 1
+ y_freqs = np.hstack(y)
+
+ NFFT_noise = int(10 * Fs / 11)
+ np.random.seed(0)
+ y_noise = np.concatenate([np.random.standard_normal(n), np.random.rand(n)])
+
+ all_sides = ["default", "onesided", "twosided"]
+ for y, NFFT in [(y_freqs, NFFT_freqs), (y_noise, NFFT_noise)]:
+ noverlap = NFFT // 2
+ pad_to = int(2 ** np.ceil(np.log2(NFFT)))
+ for ax, sides in zip(plt.figure().subplots(3), all_sides):
+ ax.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
+ pad_to=pad_to, sides=sides, mode="magnitude")
+ for ax, sides in zip(plt.figure().subplots(3), all_sides):
+ ax.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
+ pad_to=pad_to, sides=sides, mode="magnitude",
+ scale="linear", norm=matplotlib.colors.LogNorm())
+
+
+@image_comparison(
+ ["specgram_angle_freqs.png", "specgram_phase_freqs.png",
+ "specgram_angle_noise.png", "specgram_phase_noise.png"],
+ remove_text=True, tol=0.07, style="default")
+def test_specgram_angle():
+ """Test axes.specgram in angle and phase modes."""
+
+ # use former defaults to match existing baseline image
+ matplotlib.rcParams['image.interpolation'] = 'nearest'
+
+ n = 1000
+ Fs = 10.
+
+ fstims = [[Fs/4, Fs/5, Fs/11], [Fs/4.7, Fs/5.6, Fs/11.9]]
+ NFFT_freqs = int(10 * Fs / np.min(fstims))
+ x = np.arange(0, n, 1/Fs)
+ y = np.sin(2 * np.pi * np.multiply.outer(fstims, x)).sum(axis=1)
+ y[:, -1] = 1
+ y_freqs = np.hstack(y)
+
+ NFFT_noise = int(10 * Fs / 11)
+ np.random.seed(0)
+ y_noise = np.concatenate([np.random.standard_normal(n), np.random.rand(n)])
+
+ all_sides = ["default", "onesided", "twosided"]
+ for y, NFFT in [(y_freqs, NFFT_freqs), (y_noise, NFFT_noise)]:
+ noverlap = NFFT // 2
+ pad_to = int(2 ** np.ceil(np.log2(NFFT)))
+ for mode in ["angle", "phase"]:
+ for ax, sides in zip(plt.figure().subplots(3), all_sides):
+ ax.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
+ pad_to=pad_to, sides=sides, mode=mode)
+ with pytest.raises(ValueError):
+ ax.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
+ pad_to=pad_to, sides=sides, mode=mode,
+ scale="dB")
+
+
+def test_specgram_fs_none():
+ """Test axes.specgram when Fs is None, should not throw error."""
+ spec, freqs, t, im = plt.specgram(np.ones(300), Fs=None, scale='linear')
+ xmin, xmax, freq0, freq1 = im.get_extent()
+ assert xmin == 32 and xmax == 96
+
+
+@check_figures_equal(extensions=["png"])
+def test_specgram_origin_rcparam(fig_test, fig_ref):
+ """Test specgram ignores image.origin rcParam and uses origin 'upper'."""
+ t = np.arange(500)
+ signal = np.sin(t)
+
+ plt.rcParams["image.origin"] = 'upper'
+
+ # Reference: First graph using default origin in imshow (upper),
+ fig_ref.subplots().specgram(signal)
+
+ # Try to overwrite the setting trying to flip the specgram
+ plt.rcParams["image.origin"] = 'lower'
+
+ # Test: origin='lower' should be ignored
+ fig_test.subplots().specgram(signal)
+
+
+def test_specgram_origin_kwarg():
+ """Ensure passing origin as a kwarg raises a TypeError."""
+ t = np.arange(500)
+ signal = np.sin(t)
+
+ with pytest.raises(TypeError):
+ plt.specgram(signal, origin='lower')
+
+
+@image_comparison(
+ ["psd_freqs.png", "csd_freqs.png", "psd_noise.png", "csd_noise.png"],
+ remove_text=True, tol=0.002)
+def test_psd_csd():
+ n = 10000
+ Fs = 100.
+
+ fstims = [[Fs/4, Fs/5, Fs/11], [Fs/4.7, Fs/5.6, Fs/11.9]]
+ NFFT_freqs = int(1000 * Fs / np.min(fstims))
+ x = np.arange(0, n, 1/Fs)
+ ys_freqs = np.sin(2 * np.pi * np.multiply.outer(fstims, x)).sum(axis=1)
+
+ NFFT_noise = int(1000 * Fs / 11)
+ np.random.seed(0)
+ ys_noise = [np.random.standard_normal(n), np.random.rand(n)]
+
+ all_kwargs = [{"sides": "default"},
+ {"sides": "onesided", "return_line": False},
+ {"sides": "twosided", "return_line": True}]
+ for ys, NFFT in [(ys_freqs, NFFT_freqs), (ys_noise, NFFT_noise)]:
+ noverlap = NFFT // 2
+ pad_to = int(2 ** np.ceil(np.log2(NFFT)))
+ for ax, kwargs in zip(plt.figure().subplots(3), all_kwargs):
+ ret = ax.psd(np.concatenate(ys), NFFT=NFFT, Fs=Fs,
+ noverlap=noverlap, pad_to=pad_to, **kwargs)
+ assert len(ret) == 2 + kwargs.get("return_line", False)
+ ax.set(xlabel="", ylabel="")
+ for ax, kwargs in zip(plt.figure().subplots(3), all_kwargs):
+ ret = ax.csd(*ys, NFFT=NFFT, Fs=Fs,
+ noverlap=noverlap, pad_to=pad_to, **kwargs)
+ assert len(ret) == 2 + kwargs.get("return_line", False)
+ ax.set(xlabel="", ylabel="")
+
+
+@image_comparison(
+ ["magnitude_spectrum_freqs_linear.png",
+ "magnitude_spectrum_freqs_dB.png",
+ "angle_spectrum_freqs.png",
+ "phase_spectrum_freqs.png",
+ "magnitude_spectrum_noise_linear.png",
+ "magnitude_spectrum_noise_dB.png",
+ "angle_spectrum_noise.png",
+ "phase_spectrum_noise.png"],
+ remove_text=True)
+def test_spectrum():
+ n = 10000
+ Fs = 100.
+
+ fstims1 = [Fs/4, Fs/5, Fs/11]
+ NFFT = int(1000 * Fs / min(fstims1))
+ pad_to = int(2 ** np.ceil(np.log2(NFFT)))
+
+ x = np.arange(0, n, 1/Fs)
+ y_freqs = ((np.sin(2 * np.pi * np.outer(x, fstims1)) * 10**np.arange(3))
+ .sum(axis=1))
+ np.random.seed(0)
+ y_noise = np.hstack([np.random.standard_normal(n), np.random.rand(n)]) - .5
+
+ all_sides = ["default", "onesided", "twosided"]
+ kwargs = {"Fs": Fs, "pad_to": pad_to}
+ for y in [y_freqs, y_noise]:
+ for ax, sides in zip(plt.figure().subplots(3), all_sides):
+ spec, freqs, line = ax.magnitude_spectrum(y, sides=sides, **kwargs)
+ ax.set(xlabel="", ylabel="")
+ for ax, sides in zip(plt.figure().subplots(3), all_sides):
+ spec, freqs, line = ax.magnitude_spectrum(y, sides=sides, **kwargs,
+ scale="dB")
+ ax.set(xlabel="", ylabel="")
+ for ax, sides in zip(plt.figure().subplots(3), all_sides):
+ spec, freqs, line = ax.angle_spectrum(y, sides=sides, **kwargs)
+ ax.set(xlabel="", ylabel="")
+ for ax, sides in zip(plt.figure().subplots(3), all_sides):
+ spec, freqs, line = ax.phase_spectrum(y, sides=sides, **kwargs)
+ ax.set(xlabel="", ylabel="")
+
+
+def test_psd_csd_edge_cases():
+ # Inverted yaxis or fully zero inputs used to throw exceptions.
+ axs = plt.figure().subplots(2)
+ for ax in axs:
+ ax.yaxis.set(inverted=True)
+ with np.errstate(divide="ignore"):
+ axs[0].psd(np.zeros(5))
+ axs[1].csd(np.zeros(5), np.zeros(5))
+
+
+@check_figures_equal(extensions=['png'])
+def test_twin_remove(fig_test, fig_ref):
+ ax_test = fig_test.add_subplot()
+ ax_twinx = ax_test.twinx()
+ ax_twiny = ax_test.twiny()
+ ax_twinx.remove()
+ ax_twiny.remove()
+
+ ax_ref = fig_ref.add_subplot()
+ # Ideally we also undo tick changes when calling ``remove()``, but for now
+ # manually set the ticks of the reference image to match the test image
+ ax_ref.xaxis.tick_bottom()
+ ax_ref.yaxis.tick_left()
+
+
+@image_comparison(['twin_spines.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.022)
+def test_twin_spines():
+
+ def make_patch_spines_invisible(ax):
+ ax.set_frame_on(True)
+ ax.patch.set_visible(False)
+ ax.spines[:].set_visible(False)
+
+ fig = plt.figure(figsize=(4, 3))
+ fig.subplots_adjust(right=0.75)
+
+ host = fig.add_subplot()
+ par1 = host.twinx()
+ par2 = host.twinx()
+
+ # Offset the right spine of par2. The ticks and label have already been
+ # placed on the right by twinx above.
+ par2.spines.right.set_position(("axes", 1.2))
+ # Having been created by twinx, par2 has its frame off, so the line of
+ # its detached spine is invisible. First, activate the frame but make
+ # the patch and spines invisible.
+ make_patch_spines_invisible(par2)
+ # Second, show the right spine.
+ par2.spines.right.set_visible(True)
+
+ p1, = host.plot([0, 1, 2], [0, 1, 2], "b-")
+ p2, = par1.plot([0, 1, 2], [0, 3, 2], "r-")
+ p3, = par2.plot([0, 1, 2], [50, 30, 15], "g-")
+
+ host.set_xlim(0, 2)
+ host.set_ylim(0, 2)
+ par1.set_ylim(0, 4)
+ par2.set_ylim(1, 65)
+
+ host.yaxis.label.set_color(p1.get_color())
+ par1.yaxis.label.set_color(p2.get_color())
+ par2.yaxis.label.set_color(p3.get_color())
+
+ tkw = dict(size=4, width=1.5)
+ host.tick_params(axis='y', colors=p1.get_color(), **tkw)
+ par1.tick_params(axis='y', colors=p2.get_color(), **tkw)
+ par2.tick_params(axis='y', colors=p3.get_color(), **tkw)
+ host.tick_params(axis='x', **tkw)
+
+
+@image_comparison(['twin_spines_on_top.png', 'twin_spines_on_top.png'],
+ remove_text=True)
+def test_twin_spines_on_top():
+ matplotlib.rcParams['axes.linewidth'] = 48.0
+ matplotlib.rcParams['lines.linewidth'] = 48.0
+
+ fig = plt.figure()
+ ax1 = fig.add_subplot(1, 1, 1)
+
+ data = np.array([[1000, 1100, 1200, 1250],
+ [310, 301, 360, 400]])
+
+ ax2 = ax1.twinx()
+
+ ax1.plot(data[0], data[1]/1E3, color='#BEAED4')
+ ax1.fill_between(data[0], data[1]/1E3, color='#BEAED4', alpha=.8)
+
+ ax2.plot(data[0], data[1]/1E3, color='#7FC97F')
+ ax2.fill_between(data[0], data[1]/1E3, color='#7FC97F', alpha=.5)
+
+ # Reuse testcase from above for a labeled data test
+ data = {"i": data[0], "j": data[1]/1E3}
+ fig = plt.figure()
+ ax1 = fig.add_subplot(1, 1, 1)
+ ax2 = ax1.twinx()
+ ax1.plot("i", "j", color='#BEAED4', data=data)
+ ax1.fill_between("i", "j", color='#BEAED4', alpha=.8, data=data)
+ ax2.plot("i", "j", color='#7FC97F', data=data)
+ ax2.fill_between("i", "j", color='#7FC97F', alpha=.5, data=data)
+
+
+@pytest.mark.parametrize("grid_which, major_visible, minor_visible", [
+ ("both", True, True),
+ ("major", True, False),
+ ("minor", False, True),
+])
+def test_rcparam_grid_minor(grid_which, major_visible, minor_visible):
+ mpl.rcParams.update({"axes.grid": True, "axes.grid.which": grid_which})
+ fig, ax = plt.subplots()
+ fig.canvas.draw()
+ assert all(tick.gridline.get_visible() == major_visible
+ for tick in ax.xaxis.majorTicks)
+ assert all(tick.gridline.get_visible() == minor_visible
+ for tick in ax.xaxis.minorTicks)
+
+
+def test_grid():
+ fig, ax = plt.subplots()
+ ax.grid()
+ fig.canvas.draw()
+ assert ax.xaxis.majorTicks[0].gridline.get_visible()
+ ax.grid(visible=False)
+ fig.canvas.draw()
+ assert not ax.xaxis.majorTicks[0].gridline.get_visible()
+ ax.grid(visible=True)
+ fig.canvas.draw()
+ assert ax.xaxis.majorTicks[0].gridline.get_visible()
+ ax.grid()
+ fig.canvas.draw()
+ assert not ax.xaxis.majorTicks[0].gridline.get_visible()
+
+
+def test_reset_grid():
+ fig, ax = plt.subplots()
+ ax.tick_params(reset=True, which='major', labelsize=10)
+ assert not ax.xaxis.majorTicks[0].gridline.get_visible()
+ ax.grid(color='red') # enables grid
+ assert ax.xaxis.majorTicks[0].gridline.get_visible()
+
+ with plt.rc_context({'axes.grid': True}):
+ ax.clear()
+ ax.tick_params(reset=True, which='major', labelsize=10)
+ assert ax.xaxis.majorTicks[0].gridline.get_visible()
+
+
+@check_figures_equal(extensions=['png'])
+def test_reset_ticks(fig_test, fig_ref):
+ for fig in [fig_ref, fig_test]:
+ ax = fig.add_subplot()
+ ax.grid(True)
+ ax.tick_params(
+ direction='in', length=10, width=5, color='C0', pad=12,
+ labelsize=14, labelcolor='C1', labelrotation=45,
+ grid_color='C2', grid_alpha=0.8, grid_linewidth=3,
+ grid_linestyle='--')
+ fig.draw_without_rendering()
+
+ # After we've changed any setting on ticks, reset_ticks will mean
+ # re-creating them from scratch. This *should* appear the same as not
+ # resetting them.
+ for ax in fig_test.axes:
+ ax.xaxis.reset_ticks()
+ ax.yaxis.reset_ticks()
+
+
+@mpl.style.context('mpl20')
+def test_context_ticks():
+ with plt.rc_context({
+ 'xtick.direction': 'in', 'xtick.major.size': 30, 'xtick.major.width': 5,
+ 'xtick.color': 'C0', 'xtick.major.pad': 12,
+ 'xtick.bottom': True, 'xtick.top': True,
+ 'xtick.labelsize': 14, 'xtick.labelcolor': 'C1'}):
+ fig, ax = plt.subplots()
+ # Draw outside the context so that all-but-first tick are generated with the normal
+ # mpl20 style in place.
+ fig.draw_without_rendering()
+
+ first_tick = ax.xaxis.majorTicks[0]
+ for tick in ax.xaxis.majorTicks[1:]:
+ assert tick._size == first_tick._size
+ assert tick._width == first_tick._width
+ assert tick._base_pad == first_tick._base_pad
+ assert tick._labelrotation == first_tick._labelrotation
+ assert tick._zorder == first_tick._zorder
+ assert tick._tickdir == first_tick._tickdir
+
+
+def test_vline_limit():
+ fig = plt.figure()
+ ax = fig.gca()
+ ax.axvline(0.5)
+ ax.plot([-0.1, 0, 0.2, 0.1])
+ assert_allclose(ax.get_ylim(), (-.1, .2))
+
+
+@pytest.mark.parametrize('fv, fh, args', [[plt.axvline, plt.axhline, (1,)],
+ [plt.axvspan, plt.axhspan, (1, 1)]])
+def test_axline_minmax(fv, fh, args):
+ bad_lim = matplotlib.dates.num2date(1)
+ # Check vertical functions
+ with pytest.raises(ValueError, match='ymin must be a single scalar value'):
+ fv(*args, ymin=bad_lim, ymax=1)
+ with pytest.raises(ValueError, match='ymax must be a single scalar value'):
+ fv(*args, ymin=1, ymax=bad_lim)
+ # Check horizontal functions
+ with pytest.raises(ValueError, match='xmin must be a single scalar value'):
+ fh(*args, xmin=bad_lim, xmax=1)
+ with pytest.raises(ValueError, match='xmax must be a single scalar value'):
+ fh(*args, xmin=1, xmax=bad_lim)
+
+
+def test_empty_shared_subplots():
+ # empty plots with shared axes inherit limits from populated plots
+ fig, axs = plt.subplots(nrows=1, ncols=2, sharex=True, sharey=True)
+ axs[0].plot([1, 2, 3], [2, 4, 6])
+ x0, x1 = axs[1].get_xlim()
+ y0, y1 = axs[1].get_ylim()
+ assert x0 <= 1
+ assert x1 >= 3
+ assert y0 <= 2
+ assert y1 >= 6
+
+
+def test_shared_with_aspect_1():
+ # allow sharing one axis
+ for adjustable in ['box', 'datalim']:
+ fig, axs = plt.subplots(nrows=2, sharex=True)
+ axs[0].set_aspect(2, adjustable=adjustable, share=True)
+ assert axs[1].get_aspect() == 2
+ assert axs[1].get_adjustable() == adjustable
+
+ fig, axs = plt.subplots(nrows=2, sharex=True)
+ axs[0].set_aspect(2, adjustable=adjustable)
+ assert axs[1].get_aspect() == 'auto'
+
+
+def test_shared_with_aspect_2():
+ # Share 2 axes only with 'box':
+ fig, axs = plt.subplots(nrows=2, sharex=True, sharey=True)
+ axs[0].set_aspect(2, share=True)
+ axs[0].plot([1, 2], [3, 4])
+ axs[1].plot([3, 4], [1, 2])
+ plt.draw() # Trigger apply_aspect().
+ assert axs[0].get_xlim() == axs[1].get_xlim()
+ assert axs[0].get_ylim() == axs[1].get_ylim()
+
+
+def test_shared_with_aspect_3():
+ # Different aspect ratios:
+ for adjustable in ['box', 'datalim']:
+ fig, axs = plt.subplots(nrows=2, sharey=True)
+ axs[0].set_aspect(2, adjustable=adjustable)
+ axs[1].set_aspect(0.5, adjustable=adjustable)
+ axs[0].plot([1, 2], [3, 4])
+ axs[1].plot([3, 4], [1, 2])
+ plt.draw() # Trigger apply_aspect().
+ assert axs[0].get_xlim() != axs[1].get_xlim()
+ assert axs[0].get_ylim() == axs[1].get_ylim()
+ fig_aspect = fig.bbox_inches.height / fig.bbox_inches.width
+ for ax in axs:
+ p = ax.get_position()
+ box_aspect = p.height / p.width
+ lim_aspect = ax.viewLim.height / ax.viewLim.width
+ expected = fig_aspect * box_aspect / lim_aspect
+ assert round(expected, 4) == round(ax.get_aspect(), 4)
+
+
+def test_shared_aspect_error():
+ fig, axes = plt.subplots(1, 2, sharex=True, sharey=True)
+ axes[0].axis("equal")
+ with pytest.raises(RuntimeError, match=r"set_aspect\(..., adjustable="):
+ fig.draw_without_rendering()
+
+
+@pytest.mark.parametrize('err, args, kwargs, match',
+ ((TypeError, (1, 2), {},
+ r"axis\(\) takes from 0 to 1 positional arguments "
+ "but 2 were given"),
+ (ValueError, ('foo', ), {},
+ "Unrecognized string 'foo' to axis; try 'on' or "
+ "'off'"),
+ (TypeError, ([1, 2], ), {},
+ "The first argument to axis*"),
+ (TypeError, tuple(), {'foo': None},
+ r"axis\(\) got an unexpected keyword argument "
+ "'foo'"),
+ ))
+def test_axis_errors(err, args, kwargs, match):
+ with pytest.raises(err, match=match):
+ plt.axis(*args, **kwargs)
+
+
+def test_axis_method_errors():
+ ax = plt.gca()
+ with pytest.raises(ValueError, match="unknown value for which: 'foo'"):
+ ax.get_xaxis_transform('foo')
+ with pytest.raises(ValueError, match="unknown value for which: 'foo'"):
+ ax.get_yaxis_transform('foo')
+ with pytest.raises(TypeError, match="Cannot supply both positional and"):
+ ax.set_prop_cycle('foo', label='bar')
+ with pytest.raises(ValueError, match="argument must be among"):
+ ax.set_anchor('foo')
+ with pytest.raises(ValueError, match="scilimits must be a sequence"):
+ ax.ticklabel_format(scilimits=1)
+ with pytest.raises(TypeError, match="Specifying 'loc' is disallowed"):
+ ax.set_xlabel('foo', loc='left', x=1)
+ with pytest.raises(TypeError, match="Specifying 'loc' is disallowed"):
+ ax.set_ylabel('foo', loc='top', y=1)
+ with pytest.raises(TypeError, match="Cannot pass both 'left'"):
+ ax.set_xlim(left=0, xmin=1)
+ with pytest.raises(TypeError, match="Cannot pass both 'right'"):
+ ax.set_xlim(right=0, xmax=1)
+ with pytest.raises(TypeError, match="Cannot pass both 'bottom'"):
+ ax.set_ylim(bottom=0, ymin=1)
+ with pytest.raises(TypeError, match="Cannot pass both 'top'"):
+ ax.set_ylim(top=0, ymax=1)
+
+
+@pytest.mark.parametrize('twin', ('x', 'y'))
+def test_twin_with_aspect(twin):
+ fig, ax = plt.subplots()
+ # test twinx or twiny
+ ax_twin = getattr(ax, f'twin{twin}')()
+ ax.set_aspect(5)
+ ax_twin.set_aspect(2)
+ assert_array_equal(ax.bbox.extents,
+ ax_twin.bbox.extents)
+
+
+def test_relim_visible_only():
+ x1 = (0., 10.)
+ y1 = (0., 10.)
+ x2 = (-10., 20.)
+ y2 = (-10., 30.)
+
+ fig = matplotlib.figure.Figure()
+ ax = fig.add_subplot()
+ ax.plot(x1, y1)
+ assert ax.get_xlim() == x1
+ assert ax.get_ylim() == y1
+ line, = ax.plot(x2, y2)
+ assert ax.get_xlim() == x2
+ assert ax.get_ylim() == y2
+ line.set_visible(False)
+ assert ax.get_xlim() == x2
+ assert ax.get_ylim() == y2
+
+ ax.relim(visible_only=True)
+ ax.autoscale_view()
+
+ assert ax.get_xlim() == x1
+ assert ax.get_ylim() == y1
+
+
+def test_text_labelsize():
+ """
+ tests for issue #1172
+ """
+ fig = plt.figure()
+ ax = fig.gca()
+ ax.tick_params(labelsize='large')
+ ax.tick_params(direction='out')
+
+
+# Note: The `pie` image tests were affected by Numpy 2.0 changing promotions
+# (NEP 50). While the changes were only marginal, tolerances were introduced.
+# These tolerances could likely go away when numpy 2.0 is the minimum supported
+# numpy and the images are regenerated.
+
+@image_comparison(['pie_default.png'], tol=0.01)
+def test_pie_default():
+ # The slices will be ordered and plotted counter-clockwise.
+ labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
+ sizes = [15, 30, 45, 10]
+ colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
+ explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
+ fig1, ax1 = plt.subplots(figsize=(8, 6))
+ ax1.pie(sizes, explode=explode, labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90)
+
+
+@image_comparison(['pie_linewidth_0', 'pie_linewidth_0', 'pie_linewidth_0'],
+ extensions=['png'], style='mpl20', tol=0.01)
+def test_pie_linewidth_0():
+ # The slices will be ordered and plotted counter-clockwise.
+ labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
+ sizes = [15, 30, 45, 10]
+ colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
+ explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
+
+ plt.pie(sizes, explode=explode, labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ wedgeprops={'linewidth': 0})
+ # Set aspect ratio to be equal so that pie is drawn as a circle.
+ plt.axis('equal')
+
+ # Reuse testcase from above for a labeled data test
+ data = {"l": labels, "s": sizes, "c": colors, "ex": explode}
+ fig = plt.figure()
+ ax = fig.gca()
+ ax.pie("s", explode="ex", labels="l", colors="c",
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ wedgeprops={'linewidth': 0}, data=data)
+ ax.axis('equal')
+
+ # And again to test the pyplot functions which should also be able to be
+ # called with a data kwarg
+ plt.figure()
+ plt.pie("s", explode="ex", labels="l", colors="c",
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ wedgeprops={'linewidth': 0}, data=data)
+ plt.axis('equal')
+
+
+@image_comparison(['pie_center_radius.png'], style='mpl20', tol=0.01)
+def test_pie_center_radius():
+ # The slices will be ordered and plotted counter-clockwise.
+ labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
+ sizes = [15, 30, 45, 10]
+ colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
+ explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
+
+ plt.pie(sizes, explode=explode, labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ wedgeprops={'linewidth': 0}, center=(1, 2), radius=1.5)
+
+ plt.annotate("Center point", xy=(1, 2), xytext=(1, 1.3),
+ arrowprops=dict(arrowstyle="->",
+ connectionstyle="arc3"),
+ bbox=dict(boxstyle="square", facecolor="lightgrey"))
+ # Set aspect ratio to be equal so that pie is drawn as a circle.
+ plt.axis('equal')
+
+
+@image_comparison(['pie_linewidth_2.png'], style='mpl20', tol=0.01)
+def test_pie_linewidth_2():
+ # The slices will be ordered and plotted counter-clockwise.
+ labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
+ sizes = [15, 30, 45, 10]
+ colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
+ explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
+
+ plt.pie(sizes, explode=explode, labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ wedgeprops={'linewidth': 2})
+ # Set aspect ratio to be equal so that pie is drawn as a circle.
+ plt.axis('equal')
+
+
+@image_comparison(['pie_ccw_true.png'], style='mpl20', tol=0.01)
+def test_pie_ccw_true():
+ # The slices will be ordered and plotted counter-clockwise.
+ labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
+ sizes = [15, 30, 45, 10]
+ colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
+ explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
+
+ plt.pie(sizes, explode=explode, labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ counterclock=True)
+ # Set aspect ratio to be equal so that pie is drawn as a circle.
+ plt.axis('equal')
+
+
+@image_comparison(['pie_frame_grid.png'], style='mpl20', tol=0.002)
+def test_pie_frame_grid():
+ # The slices will be ordered and plotted counter-clockwise.
+ labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
+ sizes = [15, 30, 45, 10]
+ colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
+ # only "explode" the 2nd slice (i.e. 'Hogs')
+ explode = (0, 0.1, 0, 0)
+
+ plt.pie(sizes, explode=explode, labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ wedgeprops={'linewidth': 0},
+ frame=True, center=(2, 2))
+
+ plt.pie(sizes[::-1], explode=explode, labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ wedgeprops={'linewidth': 0},
+ frame=True, center=(5, 2))
+
+ plt.pie(sizes, explode=explode[::-1], labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ wedgeprops={'linewidth': 0},
+ frame=True, center=(3, 5))
+ # Set aspect ratio to be equal so that pie is drawn as a circle.
+ plt.axis('equal')
+
+
+@image_comparison(['pie_rotatelabels_true.png'], style='mpl20', tol=0.009)
+def test_pie_rotatelabels_true():
+ # The slices will be ordered and plotted counter-clockwise.
+ labels = 'Hogwarts', 'Frogs', 'Dogs', 'Logs'
+ sizes = [15, 30, 45, 10]
+ colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
+ explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
+
+ plt.pie(sizes, explode=explode, labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90,
+ rotatelabels=True)
+ # Set aspect ratio to be equal so that pie is drawn as a circle.
+ plt.axis('equal')
+
+
+@image_comparison(['pie_no_label.png'], tol=0.01)
+def test_pie_nolabel_but_legend():
+ labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
+ sizes = [15, 30, 45, 10]
+ colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
+ explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
+ plt.pie(sizes, explode=explode, labels=labels, colors=colors,
+ autopct='%1.1f%%', shadow=True, startangle=90, labeldistance=None,
+ rotatelabels=True)
+ plt.axis('equal')
+ plt.ylim(-1.2, 1.2)
+ plt.legend()
+
+
+@image_comparison(['pie_shadow.png'], style='mpl20', tol=0.002)
+def test_pie_shadow():
+ # Also acts as a test for the shade argument of Shadow
+ sizes = [15, 30, 45, 10]
+ colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
+ explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice
+ _, axes = plt.subplots(2, 2)
+ axes[0][0].pie(sizes, explode=explode, colors=colors,
+ shadow=True, startangle=90,
+ wedgeprops={'linewidth': 0})
+
+ axes[0][1].pie(sizes, explode=explode, colors=colors,
+ shadow=False, startangle=90,
+ wedgeprops={'linewidth': 0})
+
+ axes[1][0].pie(sizes, explode=explode, colors=colors,
+ shadow={'ox': -0.05, 'oy': -0.05, 'shade': 0.9, 'edgecolor': 'none'},
+ startangle=90, wedgeprops={'linewidth': 0})
+
+ axes[1][1].pie(sizes, explode=explode, colors=colors,
+ shadow={'ox': 0.05, 'linewidth': 2, 'shade': 0.2},
+ startangle=90, wedgeprops={'linewidth': 0})
+
+
+def test_pie_textprops():
+ data = [23, 34, 45]
+ labels = ["Long name 1", "Long name 2", "Long name 3"]
+
+ textprops = dict(horizontalalignment="center",
+ verticalalignment="top",
+ rotation=90,
+ rotation_mode="anchor",
+ size=12, color="red")
+
+ _, texts, autopct = plt.gca().pie(data, labels=labels, autopct='%.2f',
+ textprops=textprops)
+ for labels in [texts, autopct]:
+ for tx in labels:
+ assert tx.get_ha() == textprops["horizontalalignment"]
+ assert tx.get_va() == textprops["verticalalignment"]
+ assert tx.get_rotation() == textprops["rotation"]
+ assert tx.get_rotation_mode() == textprops["rotation_mode"]
+ assert tx.get_size() == textprops["size"]
+ assert tx.get_color() == textprops["color"]
+
+
+def test_pie_get_negative_values():
+ # Test the ValueError raised when feeding negative values into axes.pie
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.pie([5, 5, -3], explode=[0, .1, .2])
+
+
+def test_pie_invalid_explode():
+ # Test ValueError raised when feeding short explode list to axes.pie
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.pie([1, 2, 3], explode=[0.1, 0.1])
+
+
+def test_pie_invalid_labels():
+ # Test ValueError raised when feeding short labels list to axes.pie
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.pie([1, 2, 3], labels=["One", "Two"])
+
+
+def test_pie_invalid_radius():
+ # Test ValueError raised when feeding negative radius to axes.pie
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.pie([1, 2, 3], radius=-5)
+
+
+def test_normalize_kwarg_pie():
+ fig, ax = plt.subplots()
+ x = [0.3, 0.3, 0.1]
+ t1 = ax.pie(x=x, normalize=True)
+ assert abs(t1[0][-1].theta2 - 360.) < 1e-3
+ t2 = ax.pie(x=x, normalize=False)
+ assert abs(t2[0][-1].theta2 - 360.) > 1e-3
+
+
+@check_figures_equal(extensions=['png'])
+def test_pie_hatch_single(fig_test, fig_ref):
+ x = [0.3, 0.3, 0.1]
+ hatch = '+'
+ fig_test.subplots().pie(x, hatch=hatch)
+ wedges, _ = fig_ref.subplots().pie(x)
+ [w.set_hatch(hatch) for w in wedges]
+
+
+@check_figures_equal(extensions=['png'])
+def test_pie_hatch_multi(fig_test, fig_ref):
+ x = [0.3, 0.3, 0.1]
+ hatch = ['/', '+', '.']
+ fig_test.subplots().pie(x, hatch=hatch)
+ wedges, _ = fig_ref.subplots().pie(x)
+ [w.set_hatch(hp) for w, hp in zip(wedges, hatch)]
+
+
+@image_comparison(['set_get_ticklabels.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.025)
+def test_set_get_ticklabels():
+ # test issue 2246
+ fig, ax = plt.subplots(2)
+ ha = ['normal', 'set_x/yticklabels']
+
+ ax[0].plot(np.arange(10))
+ ax[0].set_title(ha[0])
+
+ ax[1].plot(np.arange(10))
+ ax[1].set_title(ha[1])
+
+ # set ticklabel to 1 plot in normal way
+ ax[0].set_xticks(range(10))
+ ax[0].set_yticks(range(10))
+ ax[0].set_xticklabels(['a', 'b', 'c', 'd'] + 6 * [''])
+ ax[0].set_yticklabels(['11', '12', '13', '14'] + 6 * [''])
+
+ # set ticklabel to the other plot, expect the 2 plots have same label
+ # setting pass get_ticklabels return value as ticklabels argument
+ ax[1].set_xticks(ax[0].get_xticks())
+ ax[1].set_yticks(ax[0].get_yticks())
+ ax[1].set_xticklabels(ax[0].get_xticklabels())
+ ax[1].set_yticklabels(ax[0].get_yticklabels())
+
+
+def test_set_ticks_kwargs_raise_error_without_labels():
+ """
+ When labels=None and any kwarg is passed, axis.set_ticks() raises a
+ ValueError.
+ """
+ fig, ax = plt.subplots()
+ ticks = [1, 2, 3]
+ with pytest.raises(ValueError, match="Incorrect use of keyword argument 'alpha'"):
+ ax.xaxis.set_ticks(ticks, alpha=0.5)
+
+
+@check_figures_equal(extensions=["png"])
+def test_set_ticks_with_labels(fig_test, fig_ref):
+ """
+ Test that these two are identical::
+
+ set_xticks(ticks); set_xticklabels(labels, **kwargs)
+ set_xticks(ticks, labels, **kwargs)
+
+ """
+ ax = fig_ref.subplots()
+ ax.set_xticks([1, 2, 4, 6])
+ ax.set_xticklabels(['a', 'b', 'c', 'd'], fontweight='bold')
+ ax.set_yticks([1, 3, 5])
+ ax.set_yticks([2, 4], minor=True)
+ ax.set_yticklabels(['A', 'B'], minor=True)
+
+ ax = fig_test.subplots()
+ ax.set_xticks([1, 2, 4, 6], ['a', 'b', 'c', 'd'], fontweight='bold')
+ ax.set_yticks([1, 3, 5])
+ ax.set_yticks([2, 4], ['A', 'B'], minor=True)
+
+
+def test_xticks_bad_args():
+ ax = plt.figure().add_subplot()
+ with pytest.raises(TypeError, match='must be a sequence'):
+ ax.set_xticks([2, 9], 3.1)
+ with pytest.raises(ValueError, match='must be 1D'):
+ plt.xticks(np.arange(4).reshape((-1, 1)))
+ with pytest.raises(ValueError, match='must be 1D'):
+ plt.xticks(np.arange(4).reshape((1, -1)))
+ with pytest.raises(ValueError, match='must be 1D'):
+ plt.xticks(np.arange(4).reshape((-1, 1)), labels=range(4))
+ with pytest.raises(ValueError, match='must be 1D'):
+ plt.xticks(np.arange(4).reshape((1, -1)), labels=range(4))
+
+
+def test_subsampled_ticklabels():
+ # test issue 11937
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10))
+ ax.xaxis.set_ticks(np.arange(10) + 0.1)
+ ax.locator_params(nbins=5)
+ ax.xaxis.set_ticklabels([c for c in "bcdefghijk"])
+ plt.draw()
+ labels = [t.get_text() for t in ax.xaxis.get_ticklabels()]
+ assert labels == ['b', 'd', 'f', 'h', 'j']
+
+
+def test_mismatched_ticklabels():
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10))
+ ax.xaxis.set_ticks([1.5, 2.5])
+ with pytest.raises(ValueError):
+ ax.xaxis.set_ticklabels(['a', 'b', 'c'])
+
+
+def test_empty_ticks_fixed_loc():
+ # Smoke test that [] can be used to unset all tick labels
+ fig, ax = plt.subplots()
+ ax.bar([1, 2], [1, 2])
+ ax.set_xticks([1, 2])
+ ax.set_xticklabels([])
+
+
+@image_comparison(['retain_tick_visibility.png'])
+def test_retain_tick_visibility():
+ fig, ax = plt.subplots()
+ plt.plot([0, 1, 2], [0, -1, 4])
+ plt.setp(ax.get_yticklabels(), visible=False)
+ ax.tick_params(axis="y", which="both", length=0)
+
+
+def test_warn_too_few_labels():
+ # note that the axis is still using an AutoLocator:
+ fig, ax = plt.subplots()
+ with pytest.warns(
+ UserWarning,
+ match=r'set_ticklabels\(\) should only be used with a fixed number'):
+ ax.set_xticklabels(['0', '0.1'])
+ # note that the axis is still using a FixedLocator:
+ fig, ax = plt.subplots()
+ ax.set_xticks([0, 0.5, 1])
+ with pytest.raises(ValueError,
+ match='The number of FixedLocator locations'):
+ ax.set_xticklabels(['0', '0.1'])
+
+
+def test_tick_label_update():
+ # test issue 9397
+
+ fig, ax = plt.subplots()
+
+ # Set up a dummy formatter
+ def formatter_func(x, pos):
+ return "unit value" if x == 1 else ""
+ ax.xaxis.set_major_formatter(plt.FuncFormatter(formatter_func))
+
+ # Force some of the x-axis ticks to be outside of the drawn range
+ ax.set_xticks([-1, 0, 1, 2, 3])
+ ax.set_xlim(-0.5, 2.5)
+
+ fig.canvas.draw()
+ tick_texts = [tick.get_text() for tick in ax.xaxis.get_ticklabels()]
+ assert tick_texts == ["", "", "unit value", "", ""]
+
+
+@image_comparison(['o_marker_path_snap.png'], savefig_kwarg={'dpi': 72})
+def test_o_marker_path_snap():
+ fig, ax = plt.subplots()
+ ax.margins(.1)
+ for ms in range(1, 15):
+ ax.plot([1, 2, ], np.ones(2) + ms, 'o', ms=ms)
+
+ for ms in np.linspace(1, 10, 25):
+ ax.plot([3, 4, ], np.ones(2) + ms, 'o', ms=ms)
+
+
+def test_margins():
+ # test all ways margins can be called
+ data = [1, 10]
+ xmin = 0.0
+ xmax = len(data) - 1.0
+ ymin = min(data)
+ ymax = max(data)
+
+ fig1, ax1 = plt.subplots(1, 1)
+ ax1.plot(data)
+ ax1.margins(1)
+ assert ax1.margins() == (1, 1)
+ assert ax1.get_xlim() == (xmin - (xmax - xmin) * 1,
+ xmax + (xmax - xmin) * 1)
+ assert ax1.get_ylim() == (ymin - (ymax - ymin) * 1,
+ ymax + (ymax - ymin) * 1)
+
+ fig2, ax2 = plt.subplots(1, 1)
+ ax2.plot(data)
+ ax2.margins(0.5, 2)
+ assert ax2.margins() == (0.5, 2)
+ assert ax2.get_xlim() == (xmin - (xmax - xmin) * 0.5,
+ xmax + (xmax - xmin) * 0.5)
+ assert ax2.get_ylim() == (ymin - (ymax - ymin) * 2,
+ ymax + (ymax - ymin) * 2)
+
+ fig3, ax3 = plt.subplots(1, 1)
+ ax3.plot(data)
+ ax3.margins(x=-0.2, y=0.5)
+ assert ax3.margins() == (-0.2, 0.5)
+ assert ax3.get_xlim() == (xmin - (xmax - xmin) * -0.2,
+ xmax + (xmax - xmin) * -0.2)
+ assert ax3.get_ylim() == (ymin - (ymax - ymin) * 0.5,
+ ymax + (ymax - ymin) * 0.5)
+
+
+def test_margin_getters():
+ fig = plt.figure()
+ ax = fig.add_subplot()
+ ax.margins(0.2, 0.3)
+ assert ax.get_xmargin() == 0.2
+ assert ax.get_ymargin() == 0.3
+
+
+def test_set_margin_updates_limits():
+ mpl.style.use("default")
+ fig, ax = plt.subplots()
+ ax.plot([1, 2], [1, 2])
+ ax.set(xscale="log", xmargin=0)
+ assert ax.get_xlim() == (1, 2)
+
+
+@pytest.mark.parametrize('err, args, kwargs, match', (
+ (ValueError, (-1,), {}, r'margin must be greater than -0\.5'),
+ (ValueError, (1, -1), {}, r'margin must be greater than -0\.5'),
+ (ValueError, tuple(), {'x': -1}, r'margin must be greater than -0\.5'),
+ (ValueError, tuple(), {'y': -1}, r'margin must be greater than -0\.5'),
+ (TypeError, (1, ), {'x': 1, 'y': 1},
+ 'Cannot pass both positional and keyword arguments for x and/or y'),
+ (TypeError, (1, ), {'x': 1},
+ 'Cannot pass both positional and keyword arguments for x and/or y'),
+ (TypeError, (1, 1, 1), {}, 'Must pass a single positional argument'),
+))
+def test_margins_errors(err, args, kwargs, match):
+ with pytest.raises(err, match=match):
+ fig = plt.figure()
+ ax = fig.add_subplot()
+ ax.margins(*args, **kwargs)
+
+
+def test_length_one_hist():
+ fig, ax = plt.subplots()
+ ax.hist(1)
+ ax.hist([1])
+
+
+def test_set_xy_bound():
+ fig = plt.figure()
+ ax = fig.add_subplot()
+ ax.set_xbound(2.0, 3.0)
+ assert ax.get_xbound() == (2.0, 3.0)
+ assert ax.get_xlim() == (2.0, 3.0)
+ ax.set_xbound(upper=4.0)
+ assert ax.get_xbound() == (2.0, 4.0)
+ assert ax.get_xlim() == (2.0, 4.0)
+ ax.set_xbound(lower=3.0)
+ assert ax.get_xbound() == (3.0, 4.0)
+ assert ax.get_xlim() == (3.0, 4.0)
+
+ ax.set_ybound(2.0, 3.0)
+ assert ax.get_ybound() == (2.0, 3.0)
+ assert ax.get_ylim() == (2.0, 3.0)
+ ax.set_ybound(upper=4.0)
+ assert ax.get_ybound() == (2.0, 4.0)
+ assert ax.get_ylim() == (2.0, 4.0)
+ ax.set_ybound(lower=3.0)
+ assert ax.get_ybound() == (3.0, 4.0)
+ assert ax.get_ylim() == (3.0, 4.0)
+
+
+def test_pathological_hexbin():
+ # issue #2863
+ mylist = [10] * 100
+ fig, ax = plt.subplots(1, 1)
+ ax.hexbin(mylist, mylist)
+ fig.savefig(io.BytesIO()) # Check that no warning is emitted.
+
+
+def test_color_None():
+ # issue 3855
+ fig, ax = plt.subplots()
+ ax.plot([1, 2], [1, 2], color=None)
+
+
+def test_color_alias():
+ # issues 4157 and 4162
+ fig, ax = plt.subplots()
+ line = ax.plot([0, 1], c='lime')[0]
+ assert 'lime' == line.get_color()
+
+
+def test_numerical_hist_label():
+ fig, ax = plt.subplots()
+ ax.hist([range(15)] * 5, label=range(5))
+ ax.legend()
+
+
+def test_unicode_hist_label():
+ fig, ax = plt.subplots()
+ a = (b'\xe5\xbe\x88\xe6\xbc\x82\xe4\xba\xae, ' +
+ b'r\xc3\xb6m\xc3\xa4n ch\xc3\xa4r\xc3\xa1ct\xc3\xa8rs')
+ b = b'\xd7\xa9\xd7\x9c\xd7\x95\xd7\x9d'
+ labels = [a.decode('utf-8'),
+ 'hi aardvark',
+ b.decode('utf-8'),
+ ]
+
+ ax.hist([range(15)] * 3, label=labels)
+ ax.legend()
+
+
+def test_move_offsetlabel():
+ data = np.random.random(10) * 1e-22
+
+ fig, ax = plt.subplots()
+ ax.plot(data)
+ fig.canvas.draw()
+ before = ax.yaxis.offsetText.get_position()
+ assert ax.yaxis.offsetText.get_horizontalalignment() == 'left'
+ ax.yaxis.tick_right()
+ fig.canvas.draw()
+ after = ax.yaxis.offsetText.get_position()
+ assert after[0] > before[0] and after[1] == before[1]
+ assert ax.yaxis.offsetText.get_horizontalalignment() == 'right'
+
+ fig, ax = plt.subplots()
+ ax.plot(data)
+ fig.canvas.draw()
+ before = ax.xaxis.offsetText.get_position()
+ assert ax.xaxis.offsetText.get_verticalalignment() == 'top'
+ ax.xaxis.tick_top()
+ fig.canvas.draw()
+ after = ax.xaxis.offsetText.get_position()
+ assert after[0] == before[0] and after[1] > before[1]
+ assert ax.xaxis.offsetText.get_verticalalignment() == 'bottom'
+
+
+@image_comparison(['rc_spines.png'], savefig_kwarg={'dpi': 40})
+def test_rc_spines():
+ rc_dict = {
+ 'axes.spines.left': False,
+ 'axes.spines.right': False,
+ 'axes.spines.top': False,
+ 'axes.spines.bottom': False}
+ with matplotlib.rc_context(rc_dict):
+ plt.subplots() # create a figure and axes with the spine properties
+
+
+@image_comparison(['rc_grid.png'], savefig_kwarg={'dpi': 40})
+def test_rc_grid():
+ fig = plt.figure()
+ rc_dict0 = {
+ 'axes.grid': True,
+ 'axes.grid.axis': 'both'
+ }
+ rc_dict1 = {
+ 'axes.grid': True,
+ 'axes.grid.axis': 'x'
+ }
+ rc_dict2 = {
+ 'axes.grid': True,
+ 'axes.grid.axis': 'y'
+ }
+ dict_list = [rc_dict0, rc_dict1, rc_dict2]
+
+ for i, rc_dict in enumerate(dict_list, 1):
+ with matplotlib.rc_context(rc_dict):
+ fig.add_subplot(3, 1, i)
+
+
+def test_rc_tick():
+ d = {'xtick.bottom': False, 'xtick.top': True,
+ 'ytick.left': True, 'ytick.right': False}
+ with plt.rc_context(rc=d):
+ fig = plt.figure()
+ ax1 = fig.add_subplot(1, 1, 1)
+ xax = ax1.xaxis
+ yax = ax1.yaxis
+ # tick1On bottom/left
+ assert not xax._major_tick_kw['tick1On']
+ assert xax._major_tick_kw['tick2On']
+ assert not xax._minor_tick_kw['tick1On']
+ assert xax._minor_tick_kw['tick2On']
+
+ assert yax._major_tick_kw['tick1On']
+ assert not yax._major_tick_kw['tick2On']
+ assert yax._minor_tick_kw['tick1On']
+ assert not yax._minor_tick_kw['tick2On']
+
+
+def test_rc_major_minor_tick():
+ d = {'xtick.top': True, 'ytick.right': True, # Enable all ticks
+ 'xtick.bottom': True, 'ytick.left': True,
+ # Selectively disable
+ 'xtick.minor.bottom': False, 'xtick.major.bottom': False,
+ 'ytick.major.left': False, 'ytick.minor.left': False}
+ with plt.rc_context(rc=d):
+ fig = plt.figure()
+ ax1 = fig.add_subplot(1, 1, 1)
+ xax = ax1.xaxis
+ yax = ax1.yaxis
+ # tick1On bottom/left
+ assert not xax._major_tick_kw['tick1On']
+ assert xax._major_tick_kw['tick2On']
+ assert not xax._minor_tick_kw['tick1On']
+ assert xax._minor_tick_kw['tick2On']
+
+ assert not yax._major_tick_kw['tick1On']
+ assert yax._major_tick_kw['tick2On']
+ assert not yax._minor_tick_kw['tick1On']
+ assert yax._minor_tick_kw['tick2On']
+
+
+def test_square_plot():
+ x = np.arange(4)
+ y = np.array([1., 3., 5., 7.])
+ fig, ax = plt.subplots()
+ ax.plot(x, y, 'mo')
+ ax.axis('square')
+ xlim, ylim = ax.get_xlim(), ax.get_ylim()
+ assert np.diff(xlim) == np.diff(ylim)
+ assert ax.get_aspect() == 1
+ assert_array_almost_equal(
+ ax.get_position(original=True).extents, (0.125, 0.1, 0.9, 0.9))
+ assert_array_almost_equal(
+ ax.get_position(original=False).extents, (0.2125, 0.1, 0.8125, 0.9))
+
+
+def test_bad_plot_args():
+ with pytest.raises(ValueError):
+ plt.plot(None)
+ with pytest.raises(ValueError):
+ plt.plot(None, None)
+ with pytest.raises(ValueError):
+ plt.plot(np.zeros((2, 2)), np.zeros((2, 3)))
+ with pytest.raises(ValueError):
+ plt.plot((np.arange(5).reshape((1, -1)), np.arange(5).reshape(-1, 1)))
+
+
+@pytest.mark.parametrize(
+ "xy, cls", [
+ ((), mpl.image.AxesImage), # (0, N)
+ (((3, 7), (2, 6)), mpl.image.AxesImage), # (xmin, xmax)
+ ((range(5), range(4)), mpl.image.AxesImage), # regular grid
+ (([1, 2, 4, 8, 16], [0, 1, 2, 3]), # irregular grid
+ mpl.image.PcolorImage),
+ ((np.random.random((4, 5)), np.random.random((4, 5))), # 2D coords
+ mpl.collections.QuadMesh),
+ ]
+)
+@pytest.mark.parametrize(
+ "data", [np.arange(12).reshape((3, 4)), np.random.rand(3, 4, 3)]
+)
+def test_pcolorfast(xy, data, cls):
+ fig, ax = plt.subplots()
+ assert type(ax.pcolorfast(*xy, data)) == cls
+
+
+def test_pcolorfast_bad_dims():
+ fig, ax = plt.subplots()
+ with pytest.raises(
+ TypeError, match=("the given X was 1D and the given Y was 2D")):
+ ax.pcolorfast(np.empty(6), np.empty((4, 7)), np.empty((8, 8)))
+
+
+def test_pcolorfast_regular_xy_incompatible_size():
+ """
+ Test that the sizes of X, Y, C are compatible for regularly spaced X, Y.
+
+ Note that after the regualar-spacing check, pcolorfast may go into the
+ fast "image" mode, where the individual X, Y positions are not used anymore.
+ Therefore, the algorithm had worked with any regularly number of regularly
+ spaced values, but discarded their values.
+ """
+ fig, ax = plt.subplots()
+ with pytest.raises(
+ ValueError, match=r"Length of X \(5\) must be one larger than the "
+ r"number of columns in C \(20\)"):
+ ax.pcolorfast(np.arange(5), np.arange(11), np.random.rand(10, 20))
+
+ with pytest.raises(
+ ValueError, match=r"Length of Y \(5\) must be one larger than the "
+ r"number of rows in C \(10\)"):
+ ax.pcolorfast(np.arange(21), np.arange(5), np.random.rand(10, 20))
+
+
+def test_shared_scale():
+ fig, axs = plt.subplots(2, 2, sharex=True, sharey=True)
+
+ axs[0, 0].set_xscale("log")
+ axs[0, 0].set_yscale("log")
+
+ for ax in axs.flat:
+ assert ax.get_yscale() == 'log'
+ assert ax.get_xscale() == 'log'
+
+ axs[1, 1].set_xscale("linear")
+ axs[1, 1].set_yscale("linear")
+
+ for ax in axs.flat:
+ assert ax.get_yscale() == 'linear'
+ assert ax.get_xscale() == 'linear'
+
+
+def test_shared_bool():
+ with pytest.raises(TypeError):
+ plt.subplot(sharex=True)
+ with pytest.raises(TypeError):
+ plt.subplot(sharey=True)
+
+
+def test_violin_point_mass():
+ """Violin plot should handle point mass pdf gracefully."""
+ plt.violinplot(np.array([0, 0]))
+
+
+def generate_errorbar_inputs():
+ base_xy = cycler('x', [np.arange(5)]) + cycler('y', [np.ones(5)])
+ err_cycler = cycler('err', [1,
+ [1, 1, 1, 1, 1],
+ [[1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1]],
+ np.ones(5),
+ np.ones((2, 5)),
+ None
+ ])
+ xerr_cy = cycler('xerr', err_cycler)
+ yerr_cy = cycler('yerr', err_cycler)
+
+ empty = ((cycler('x', [[]]) + cycler('y', [[]])) *
+ cycler('xerr', [[], None]) * cycler('yerr', [[], None]))
+ xerr_only = base_xy * xerr_cy
+ yerr_only = base_xy * yerr_cy
+ both_err = base_xy * yerr_cy * xerr_cy
+
+ return [*xerr_only, *yerr_only, *both_err, *empty]
+
+
+@pytest.mark.parametrize('kwargs', generate_errorbar_inputs())
+def test_errorbar_inputs_shotgun(kwargs):
+ ax = plt.gca()
+ eb = ax.errorbar(**kwargs)
+ eb.remove()
+
+
+@image_comparison(["dash_offset"], remove_text=True)
+def test_dash_offset():
+ fig, ax = plt.subplots()
+ x = np.linspace(0, 10)
+ y = np.ones_like(x)
+ for j in range(0, 100, 2):
+ ax.plot(x, j*y, ls=(j, (10, 10)), lw=5, color='k')
+
+
+def test_title_pad():
+ # check that title padding puts the title in the right
+ # place...
+ fig, ax = plt.subplots()
+ ax.set_title('aardvark', pad=30.)
+ m = ax.titleOffsetTrans.get_matrix()
+ assert m[1, -1] == (30. / 72. * fig.dpi)
+ ax.set_title('aardvark', pad=0.)
+ m = ax.titleOffsetTrans.get_matrix()
+ assert m[1, -1] == 0.
+ # check that it is reverted...
+ ax.set_title('aardvark', pad=None)
+ m = ax.titleOffsetTrans.get_matrix()
+ assert m[1, -1] == (matplotlib.rcParams['axes.titlepad'] / 72. * fig.dpi)
+
+
+def test_title_location_roundtrip():
+ fig, ax = plt.subplots()
+ # set default title location
+ plt.rcParams['axes.titlelocation'] = 'center'
+ ax.set_title('aardvark')
+ ax.set_title('left', loc='left')
+ ax.set_title('right', loc='right')
+
+ assert 'left' == ax.get_title(loc='left')
+ assert 'right' == ax.get_title(loc='right')
+ assert 'aardvark' == ax.get_title(loc='center')
+
+ with pytest.raises(ValueError):
+ ax.get_title(loc='foo')
+ with pytest.raises(ValueError):
+ ax.set_title('fail', loc='foo')
+
+
+@pytest.mark.parametrize('sharex', [True, False])
+def test_title_location_shared(sharex):
+ fig, axs = plt.subplots(2, 1, sharex=sharex)
+ axs[0].set_title('A', pad=-40)
+ axs[1].set_title('B', pad=-40)
+ fig.draw_without_rendering()
+ x, y1 = axs[0].title.get_position()
+ x, y2 = axs[1].title.get_position()
+ assert y1 == y2 == 1.0
+
+
+@image_comparison(["loglog.png"], remove_text=True, tol=0.02)
+def test_loglog():
+ fig, ax = plt.subplots()
+ x = np.arange(1, 11)
+ ax.loglog(x, x**3, lw=5)
+ ax.tick_params(length=25, width=2)
+ ax.tick_params(length=15, width=2, which='minor')
+
+
+@image_comparison(["test_loglog_nonpos.png"], remove_text=True, style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.029)
+def test_loglog_nonpos():
+ fig, axs = plt.subplots(3, 3)
+ x = np.arange(1, 11)
+ y = x**3
+ y[7] = -3.
+ x[4] = -10
+ for (mcy, mcx), ax in zip(product(['mask', 'clip', ''], repeat=2),
+ axs.flat):
+ if mcx == mcy:
+ if mcx:
+ ax.loglog(x, y**3, lw=2, nonpositive=mcx)
+ else:
+ ax.loglog(x, y**3, lw=2)
+ else:
+ ax.loglog(x, y**3, lw=2)
+ if mcx:
+ ax.set_xscale("log", nonpositive=mcx)
+ if mcy:
+ ax.set_yscale("log", nonpositive=mcy)
+
+
+@mpl.style.context('default')
+def test_axes_margins():
+ fig, ax = plt.subplots()
+ ax.plot([0, 1, 2, 3])
+ assert ax.get_ybound()[0] != 0
+
+ fig, ax = plt.subplots()
+ ax.bar([0, 1, 2, 3], [1, 1, 1, 1])
+ assert ax.get_ybound()[0] == 0
+
+ fig, ax = plt.subplots()
+ ax.barh([0, 1, 2, 3], [1, 1, 1, 1])
+ assert ax.get_xbound()[0] == 0
+
+ fig, ax = plt.subplots()
+ ax.pcolor(np.zeros((10, 10)))
+ assert ax.get_xbound() == (0, 10)
+ assert ax.get_ybound() == (0, 10)
+
+ fig, ax = plt.subplots()
+ ax.pcolorfast(np.zeros((10, 10)))
+ assert ax.get_xbound() == (0, 10)
+ assert ax.get_ybound() == (0, 10)
+
+ fig, ax = plt.subplots()
+ ax.hist(np.arange(10))
+ assert ax.get_ybound()[0] == 0
+
+ fig, ax = plt.subplots()
+ ax.imshow(np.zeros((10, 10)))
+ assert ax.get_xbound() == (-0.5, 9.5)
+ assert ax.get_ybound() == (-0.5, 9.5)
+
+
+@pytest.fixture(params=['x', 'y'])
+def shared_axis_remover(request):
+ def _helper_x(ax):
+ ax2 = ax.twinx()
+ ax2.remove()
+ ax.set_xlim(0, 15)
+ r = ax.xaxis.get_major_locator()()
+ assert r[-1] > 14
+
+ def _helper_y(ax):
+ ax2 = ax.twiny()
+ ax2.remove()
+ ax.set_ylim(0, 15)
+ r = ax.yaxis.get_major_locator()()
+ assert r[-1] > 14
+
+ return {"x": _helper_x, "y": _helper_y}[request.param]
+
+
+@pytest.fixture(params=['gca', 'subplots', 'subplots_shared', 'add_axes'])
+def shared_axes_generator(request):
+ # test all of the ways to get fig/ax sets
+ if request.param == 'gca':
+ fig = plt.figure()
+ ax = fig.gca()
+ elif request.param == 'subplots':
+ fig, ax = plt.subplots()
+ elif request.param == 'subplots_shared':
+ fig, ax_lst = plt.subplots(2, 2, sharex='all', sharey='all')
+ ax = ax_lst[0][0]
+ elif request.param == 'add_axes':
+ fig = plt.figure()
+ ax = fig.add_axes([.1, .1, .8, .8])
+ return fig, ax
+
+
+def test_remove_shared_axes(shared_axes_generator, shared_axis_remover):
+ # test all of the ways to get fig/ax sets
+ fig, ax = shared_axes_generator
+ shared_axis_remover(ax)
+
+
+def test_remove_shared_axes_relim():
+ fig, ax_lst = plt.subplots(2, 2, sharex='all', sharey='all')
+ ax = ax_lst[0][0]
+ orig_xlim = ax_lst[0][1].get_xlim()
+ ax.remove()
+ ax.set_xlim(0, 5)
+ assert_array_equal(ax_lst[0][1].get_xlim(), orig_xlim)
+
+
+def test_shared_axes_autoscale():
+ l = np.arange(-80, 90, 40)
+ t = np.random.random_sample((l.size, l.size))
+
+ fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, sharey=True)
+
+ ax1.set_xlim(-1000, 1000)
+ ax1.set_ylim(-1000, 1000)
+ ax1.contour(l, l, t)
+
+ ax2.contour(l, l, t)
+ assert not ax1.get_autoscalex_on() and not ax2.get_autoscalex_on()
+ assert not ax1.get_autoscaley_on() and not ax2.get_autoscaley_on()
+ assert ax1.get_xlim() == ax2.get_xlim() == (-1000, 1000)
+ assert ax1.get_ylim() == ax2.get_ylim() == (-1000, 1000)
+
+
+def test_adjust_numtick_aspect():
+ fig, ax = plt.subplots()
+ ax.yaxis.get_major_locator().set_params(nbins='auto')
+ ax.set_xlim(0, 1000)
+ ax.set_aspect('equal')
+ fig.canvas.draw()
+ assert len(ax.yaxis.get_major_locator()()) == 2
+ ax.set_ylim(0, 1000)
+ fig.canvas.draw()
+ assert len(ax.yaxis.get_major_locator()()) > 2
+
+
+@mpl.style.context("default")
+def test_auto_numticks():
+ axs = plt.figure().subplots(4, 4)
+ for ax in axs.flat: # Tiny, empty subplots have only 3 ticks.
+ assert [*ax.get_xticks()] == [*ax.get_yticks()] == [0, 0.5, 1]
+
+
+@mpl.style.context("default")
+def test_auto_numticks_log():
+ # Verify that there are not too many ticks with a large log range.
+ fig, ax = plt.subplots()
+ mpl.rcParams['axes.autolimit_mode'] = 'round_numbers'
+ ax.loglog([1e-20, 1e5], [1e-16, 10])
+ assert (np.log10(ax.get_xticks()) == np.arange(-26, 18, 4)).all()
+ assert (np.log10(ax.get_yticks()) == np.arange(-20, 10, 3)).all()
+
+
+def test_broken_barh_empty():
+ fig, ax = plt.subplots()
+ ax.broken_barh([], (.1, .5))
+
+
+def test_broken_barh_timedelta():
+ """Check that timedelta works as x, dx pair for this method."""
+ fig, ax = plt.subplots()
+ d0 = datetime.datetime(2018, 11, 9, 0, 0, 0)
+ pp = ax.broken_barh([(d0, datetime.timedelta(hours=1))], [1, 2])
+ assert pp.get_paths()[0].vertices[0, 0] == mdates.date2num(d0)
+ assert pp.get_paths()[0].vertices[2, 0] == mdates.date2num(d0) + 1 / 24
+
+
+def test_pandas_pcolormesh(pd):
+ time = pd.date_range('2000-01-01', periods=10)
+ depth = np.arange(20)
+ data = np.random.rand(19, 9)
+
+ fig, ax = plt.subplots()
+ ax.pcolormesh(time, depth, data)
+
+
+def test_pandas_indexing_dates(pd):
+ dates = np.arange('2005-02', '2005-03', dtype='datetime64[D]')
+ values = np.sin(range(len(dates)))
+ df = pd.DataFrame({'dates': dates, 'values': values})
+
+ ax = plt.gca()
+
+ without_zero_index = df[np.array(df.index) % 2 == 1].copy()
+ ax.plot('dates', 'values', data=without_zero_index)
+
+
+def test_pandas_errorbar_indexing(pd):
+ df = pd.DataFrame(np.random.uniform(size=(5, 4)),
+ columns=['x', 'y', 'xe', 'ye'],
+ index=[1, 2, 3, 4, 5])
+ fig, ax = plt.subplots()
+ ax.errorbar('x', 'y', xerr='xe', yerr='ye', data=df)
+
+
+def test_pandas_index_shape(pd):
+ df = pd.DataFrame({"XX": [4, 5, 6], "YY": [7, 1, 2]})
+ fig, ax = plt.subplots()
+ ax.plot(df.index, df['YY'])
+
+
+def test_pandas_indexing_hist(pd):
+ ser_1 = pd.Series(data=[1, 2, 2, 3, 3, 4, 4, 4, 4, 5])
+ ser_2 = ser_1.iloc[1:]
+ fig, ax = plt.subplots()
+ ax.hist(ser_2)
+
+
+def test_pandas_bar_align_center(pd):
+ # Tests fix for issue 8767
+ df = pd.DataFrame({'a': range(2), 'b': range(2)})
+
+ fig, ax = plt.subplots(1)
+
+ ax.bar(df.loc[df['a'] == 1, 'b'],
+ df.loc[df['a'] == 1, 'b'],
+ align='center')
+
+ fig.canvas.draw()
+
+
+def test_axis_get_tick_params():
+ axis = plt.subplot().yaxis
+ initial_major_style_translated = {**axis.get_tick_params(which='major')}
+ initial_minor_style_translated = {**axis.get_tick_params(which='minor')}
+
+ translated_major_kw = axis._translate_tick_params(
+ axis._major_tick_kw, reverse=True
+ )
+ translated_minor_kw = axis._translate_tick_params(
+ axis._minor_tick_kw, reverse=True
+ )
+
+ assert translated_major_kw == initial_major_style_translated
+ assert translated_minor_kw == initial_minor_style_translated
+ axis.set_tick_params(labelsize=30, labelcolor='red',
+ direction='out', which='both')
+
+ new_major_style_translated = {**axis.get_tick_params(which='major')}
+ new_minor_style_translated = {**axis.get_tick_params(which='minor')}
+ new_major_style = axis._translate_tick_params(new_major_style_translated)
+ new_minor_style = axis._translate_tick_params(new_minor_style_translated)
+ assert initial_major_style_translated != new_major_style_translated
+ assert axis._major_tick_kw == new_major_style
+ assert initial_minor_style_translated != new_minor_style_translated
+ assert axis._minor_tick_kw == new_minor_style
+
+
+def test_axis_set_tick_params_labelsize_labelcolor():
+ # Tests fix for issue 4346
+ axis_1 = plt.subplot()
+ axis_1.yaxis.set_tick_params(labelsize=30, labelcolor='red',
+ direction='out')
+
+ # Expected values after setting the ticks
+ assert axis_1.yaxis.majorTicks[0]._size == 4.0
+ assert axis_1.yaxis.majorTicks[0].tick1line.get_color() == 'k'
+ assert axis_1.yaxis.majorTicks[0].label1.get_size() == 30.0
+ assert axis_1.yaxis.majorTicks[0].label1.get_color() == 'red'
+
+
+def test_axes_tick_params_gridlines():
+ # Now treating grid params like other Tick params
+ ax = plt.subplot()
+ ax.tick_params(grid_color='b', grid_linewidth=5, grid_alpha=0.5,
+ grid_linestyle='dashdot')
+ for axis in ax.xaxis, ax.yaxis:
+ assert axis.majorTicks[0].gridline.get_color() == 'b'
+ assert axis.majorTicks[0].gridline.get_linewidth() == 5
+ assert axis.majorTicks[0].gridline.get_alpha() == 0.5
+ assert axis.majorTicks[0].gridline.get_linestyle() == '-.'
+
+
+def test_axes_tick_params_ylabelside():
+ # Tests fix for issue 10267
+ ax = plt.subplot()
+ ax.tick_params(labelleft=False, labelright=True,
+ which='major')
+ ax.tick_params(labelleft=False, labelright=True,
+ which='minor')
+ # expects left false, right true
+ assert ax.yaxis.majorTicks[0].label1.get_visible() is False
+ assert ax.yaxis.majorTicks[0].label2.get_visible() is True
+ assert ax.yaxis.minorTicks[0].label1.get_visible() is False
+ assert ax.yaxis.minorTicks[0].label2.get_visible() is True
+
+
+def test_axes_tick_params_xlabelside():
+ # Tests fix for issue 10267
+ ax = plt.subplot()
+ ax.tick_params(labeltop=True, labelbottom=False,
+ which='major')
+ ax.tick_params(labeltop=True, labelbottom=False,
+ which='minor')
+ # expects top True, bottom False
+ # label1.get_visible() mapped to labelbottom
+ # label2.get_visible() mapped to labeltop
+ assert ax.xaxis.majorTicks[0].label1.get_visible() is False
+ assert ax.xaxis.majorTicks[0].label2.get_visible() is True
+ assert ax.xaxis.minorTicks[0].label1.get_visible() is False
+ assert ax.xaxis.minorTicks[0].label2.get_visible() is True
+
+
+def test_none_kwargs():
+ ax = plt.figure().subplots()
+ ln, = ax.plot(range(32), linestyle=None)
+ assert ln.get_linestyle() == '-'
+
+
+def test_bar_uint8():
+ xs = [0, 1, 2, 3]
+ b = plt.bar(np.array(xs, dtype=np.uint8), [2, 3, 4, 5], align="edge")
+ for (patch, x) in zip(b.patches, xs):
+ assert patch.xy[0] == x
+
+
+@image_comparison(['date_timezone_x.png'], tol=1.0)
+def test_date_timezone_x():
+ # Tests issue 5575
+ time_index = [datetime.datetime(2016, 2, 22, hour=x,
+ tzinfo=dateutil.tz.gettz('Canada/Eastern'))
+ for x in range(3)]
+
+ # Same Timezone
+ plt.figure(figsize=(20, 12))
+ plt.subplot(2, 1, 1)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ plt.plot_date(time_index, [3] * 3, tz='Canada/Eastern')
+
+ # Different Timezone
+ plt.subplot(2, 1, 2)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ plt.plot_date(time_index, [3] * 3, tz='UTC')
+
+
+@image_comparison(['date_timezone_y.png'])
+def test_date_timezone_y():
+ # Tests issue 5575
+ time_index = [datetime.datetime(2016, 2, 22, hour=x,
+ tzinfo=dateutil.tz.gettz('Canada/Eastern'))
+ for x in range(3)]
+
+ # Same Timezone
+ plt.figure(figsize=(20, 12))
+ plt.subplot(2, 1, 1)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ plt.plot_date([3] * 3, time_index, tz='Canada/Eastern', xdate=False, ydate=True)
+
+ # Different Timezone
+ plt.subplot(2, 1, 2)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ plt.plot_date([3] * 3, time_index, tz='UTC', xdate=False, ydate=True)
+
+
+@image_comparison(['date_timezone_x_and_y.png'], tol=1.0)
+def test_date_timezone_x_and_y():
+ # Tests issue 5575
+ UTC = datetime.timezone.utc
+ time_index = [datetime.datetime(2016, 2, 22, hour=x, tzinfo=UTC)
+ for x in range(3)]
+
+ # Same Timezone
+ plt.figure(figsize=(20, 12))
+ plt.subplot(2, 1, 1)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ plt.plot_date(time_index, time_index, tz='UTC', ydate=True)
+
+ # Different Timezone
+ plt.subplot(2, 1, 2)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ plt.plot_date(time_index, time_index, tz='US/Eastern', ydate=True)
+
+
+@image_comparison(['axisbelow.png'], remove_text=True)
+def test_axisbelow():
+ # Test 'line' setting added in 6287.
+ # Show only grids, not frame or ticks, to make this test
+ # independent of future change to drawing order of those elements.
+ axs = plt.figure().subplots(ncols=3, sharex=True, sharey=True)
+ settings = (False, 'line', True)
+
+ for ax, setting in zip(axs, settings):
+ ax.plot((0, 10), (0, 10), lw=10, color='m')
+ circ = mpatches.Circle((3, 3), color='r')
+ ax.add_patch(circ)
+ ax.grid(color='c', linestyle='-', linewidth=3)
+ ax.tick_params(top=False, bottom=False,
+ left=False, right=False)
+ ax.spines[:].set_visible(False)
+ ax.set_axisbelow(setting)
+ assert ax.get_axisbelow() == setting
+
+
+def test_titletwiny():
+ plt.style.use('mpl20')
+ fig, ax = plt.subplots(dpi=72)
+ ax2 = ax.twiny()
+ xlabel2 = ax2.set_xlabel('Xlabel2')
+ title = ax.set_title('Title')
+ fig.canvas.draw()
+ renderer = fig.canvas.get_renderer()
+ # ------- Test that title is put above Xlabel2 (Xlabel2 at top) ----------
+ bbox_y0_title = title.get_window_extent(renderer).y0 # bottom of title
+ bbox_y1_xlabel2 = xlabel2.get_window_extent(renderer).y1 # top of xlabel2
+ y_diff = bbox_y0_title - bbox_y1_xlabel2
+ assert np.isclose(y_diff, 3)
+
+
+def test_titlesetpos():
+ # Test that title stays put if we set it manually
+ fig, ax = plt.subplots()
+ fig.subplots_adjust(top=0.8)
+ ax2 = ax.twiny()
+ ax.set_xlabel('Xlabel')
+ ax2.set_xlabel('Xlabel2')
+ ax.set_title('Title')
+ pos = (0.5, 1.11)
+ ax.title.set_position(pos)
+ renderer = fig.canvas.get_renderer()
+ ax._update_title_position(renderer)
+ assert ax.title.get_position() == pos
+
+
+def test_title_xticks_top():
+ # Test that title moves if xticks on top of axes.
+ mpl.rcParams['axes.titley'] = None
+ fig, ax = plt.subplots()
+ ax.xaxis.set_ticks_position('top')
+ ax.set_title('xlabel top')
+ fig.canvas.draw()
+ assert ax.title.get_position()[1] > 1.04
+
+
+def test_title_xticks_top_both():
+ # Test that title moves if xticks on top of axes.
+ mpl.rcParams['axes.titley'] = None
+ fig, ax = plt.subplots()
+ ax.tick_params(axis="x",
+ bottom=True, top=True, labelbottom=True, labeltop=True)
+ ax.set_title('xlabel top')
+ fig.canvas.draw()
+ assert ax.title.get_position()[1] > 1.04
+
+
+@pytest.mark.parametrize(
+ 'left, center', [
+ ('left', ''),
+ ('', 'center'),
+ ('left', 'center')
+ ], ids=[
+ 'left title moved',
+ 'center title kept',
+ 'both titles aligned'
+ ]
+)
+def test_title_above_offset(left, center):
+ # Test that title moves if overlaps with yaxis offset text.
+ mpl.rcParams['axes.titley'] = None
+ fig, ax = plt.subplots()
+ ax.set_ylim(1e11)
+ ax.set_title(left, loc='left')
+ ax.set_title(center)
+ fig.draw_without_rendering()
+ if left and not center:
+ assert ax._left_title.get_position()[1] > 1.0
+ elif not left and center:
+ assert ax.title.get_position()[1] == 1.0
+ else:
+ yleft = ax._left_title.get_position()[1]
+ ycenter = ax.title.get_position()[1]
+ assert yleft > 1.0
+ assert ycenter == yleft
+
+
+def test_title_no_move_off_page():
+ # If an Axes is off the figure (ie. if it is cropped during a save)
+ # make sure that the automatic title repositioning does not get done.
+ mpl.rcParams['axes.titley'] = None
+ fig = plt.figure()
+ ax = fig.add_axes([0.1, -0.5, 0.8, 0.2])
+ ax.tick_params(axis="x",
+ bottom=True, top=True, labelbottom=True, labeltop=True)
+ tt = ax.set_title('Boo')
+ fig.canvas.draw()
+ assert tt.get_position()[1] == 1.0
+
+
+def test_title_inset_ax():
+ # Title should be above any child axes
+ mpl.rcParams['axes.titley'] = None
+ fig, ax = plt.subplots()
+ ax.set_title('Title')
+ fig.draw_without_rendering()
+ assert ax.title.get_position()[1] == 1
+ ax.inset_axes([0, 1, 1, 0.1])
+ fig.draw_without_rendering()
+ assert ax.title.get_position()[1] == 1.1
+
+
+def test_offset_label_color():
+ # Tests issue 6440
+ fig, ax = plt.subplots()
+ ax.plot([1.01e9, 1.02e9, 1.03e9])
+ ax.yaxis.set_tick_params(labelcolor='red')
+ assert ax.yaxis.get_offset_text().get_color() == 'red'
+
+
+def test_offset_text_visible():
+ fig, ax = plt.subplots()
+ ax.plot([1.01e9, 1.02e9, 1.03e9])
+ ax.yaxis.set_tick_params(label1On=False, label2On=True)
+ assert ax.yaxis.get_offset_text().get_visible()
+ ax.yaxis.set_tick_params(label2On=False)
+ assert not ax.yaxis.get_offset_text().get_visible()
+
+
+def test_large_offset():
+ fig, ax = plt.subplots()
+ ax.plot((1 + np.array([0, 1.e-12])) * 1.e27)
+ fig.canvas.draw()
+
+
+def test_barb_units():
+ fig, ax = plt.subplots()
+ dates = [datetime.datetime(2017, 7, 15, 18, i) for i in range(0, 60, 10)]
+ y = np.linspace(0, 5, len(dates))
+ u = v = np.linspace(0, 50, len(dates))
+ ax.barbs(dates, y, u, v)
+
+
+def test_quiver_units():
+ fig, ax = plt.subplots()
+ dates = [datetime.datetime(2017, 7, 15, 18, i) for i in range(0, 60, 10)]
+ y = np.linspace(0, 5, len(dates))
+ u = v = np.linspace(0, 50, len(dates))
+ ax.quiver(dates, y, u, v)
+
+
+def test_bar_color_cycle():
+ to_rgb = mcolors.to_rgb
+ fig, ax = plt.subplots()
+ for j in range(5):
+ ln, = ax.plot(range(3))
+ brs = ax.bar(range(3), range(3))
+ for br in brs:
+ assert to_rgb(ln.get_color()) == to_rgb(br.get_facecolor())
+
+
+def test_tick_param_label_rotation():
+ fix, (ax, ax2) = plt.subplots(1, 2)
+ ax.plot([0, 1], [0, 1])
+ ax2.plot([0, 1], [0, 1])
+ ax.xaxis.set_tick_params(which='both', rotation=75)
+ ax.yaxis.set_tick_params(which='both', rotation=90)
+ for text in ax.get_xticklabels(which='both'):
+ assert text.get_rotation() == 75
+ for text in ax.get_yticklabels(which='both'):
+ assert text.get_rotation() == 90
+
+ ax2.tick_params(axis='x', labelrotation=53)
+ ax2.tick_params(axis='y', rotation=35)
+ for text in ax2.get_xticklabels(which='major'):
+ assert text.get_rotation() == 53
+ for text in ax2.get_yticklabels(which='major'):
+ assert text.get_rotation() == 35
+
+
+@mpl.style.context('default')
+def test_fillbetween_cycle():
+ fig, ax = plt.subplots()
+
+ for j in range(3):
+ cc = ax.fill_between(range(3), range(3))
+ target = mcolors.to_rgba(f'C{j}')
+ assert tuple(cc.get_facecolors().squeeze()) == tuple(target)
+
+ for j in range(3, 6):
+ cc = ax.fill_betweenx(range(3), range(3))
+ target = mcolors.to_rgba(f'C{j}')
+ assert tuple(cc.get_facecolors().squeeze()) == tuple(target)
+
+ target = mcolors.to_rgba('k')
+
+ for al in ['facecolor', 'facecolors', 'color']:
+ cc = ax.fill_between(range(3), range(3), **{al: 'k'})
+ assert tuple(cc.get_facecolors().squeeze()) == tuple(target)
+
+ edge_target = mcolors.to_rgba('k')
+ for j, el in enumerate(['edgecolor', 'edgecolors'], start=6):
+ cc = ax.fill_between(range(3), range(3), **{el: 'k'})
+ face_target = mcolors.to_rgba(f'C{j}')
+ assert tuple(cc.get_facecolors().squeeze()) == tuple(face_target)
+ assert tuple(cc.get_edgecolors().squeeze()) == tuple(edge_target)
+
+
+def test_log_margins():
+ plt.rcParams['axes.autolimit_mode'] = 'data'
+ fig, ax = plt.subplots()
+ margin = 0.05
+ ax.set_xmargin(margin)
+ ax.semilogx([10, 100], [10, 100])
+ xlim0, xlim1 = ax.get_xlim()
+ transform = ax.xaxis.get_transform()
+ xlim0t, xlim1t = transform.transform([xlim0, xlim1])
+ x0t, x1t = transform.transform([10, 100])
+ delta = (x1t - x0t) * margin
+ assert_allclose([xlim0t + delta, xlim1t - delta], [x0t, x1t])
+
+
+def test_color_length_mismatch():
+ N = 5
+ x, y = np.arange(N), np.arange(N)
+ colors = np.arange(N+1)
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.scatter(x, y, c=colors)
+ with pytest.warns(match="argument looks like a single numeric RGB"):
+ ax.scatter(x, y, c=(0.5, 0.5, 0.5))
+ ax.scatter(x, y, c=[(0.5, 0.5, 0.5)] * N)
+
+
+def test_eventplot_legend():
+ plt.eventplot([1.0], label='Label')
+ plt.legend()
+
+
+@pytest.mark.parametrize('err, args, kwargs, match', (
+ (ValueError, [[1]], {'lineoffsets': []}, 'lineoffsets cannot be empty'),
+ (ValueError, [[1]], {'linelengths': []}, 'linelengths cannot be empty'),
+ (ValueError, [[1]], {'linewidths': []}, 'linewidths cannot be empty'),
+ (ValueError, [[1]], {'linestyles': []}, 'linestyles cannot be empty'),
+ (ValueError, [[1]], {'alpha': []}, 'alpha cannot be empty'),
+ (ValueError, [1], {}, 'positions must be one-dimensional'),
+ (ValueError, [[1]], {'lineoffsets': [1, 2]},
+ 'lineoffsets and positions are unequal sized sequences'),
+ (ValueError, [[1]], {'linelengths': [1, 2]},
+ 'linelengths and positions are unequal sized sequences'),
+ (ValueError, [[1]], {'linewidths': [1, 2]},
+ 'linewidths and positions are unequal sized sequences'),
+ (ValueError, [[1]], {'linestyles': [1, 2]},
+ 'linestyles and positions are unequal sized sequences'),
+ (ValueError, [[1]], {'alpha': [1, 2]},
+ 'alpha and positions are unequal sized sequences'),
+ (ValueError, [[1]], {'colors': [1, 2]},
+ 'colors and positions are unequal sized sequences'),
+))
+def test_eventplot_errors(err, args, kwargs, match):
+ with pytest.raises(err, match=match):
+ plt.eventplot(*args, **kwargs)
+
+
+def test_bar_broadcast_args():
+ fig, ax = plt.subplots()
+ # Check that a bar chart with a single height for all bars works.
+ ax.bar(range(4), 1)
+ # Check that a horizontal chart with one width works.
+ ax.barh(0, 1, left=range(4), height=1)
+ # Check that edgecolor gets broadcast.
+ rect1, rect2 = ax.bar([0, 1], [0, 1], edgecolor=(.1, .2, .3, .4))
+ assert rect1.get_edgecolor() == rect2.get_edgecolor() == (.1, .2, .3, .4)
+
+
+def test_invalid_axis_limits():
+ plt.plot([0, 1], [0, 1])
+ with pytest.raises(ValueError):
+ plt.xlim(np.nan)
+ with pytest.raises(ValueError):
+ plt.xlim(np.inf)
+ with pytest.raises(ValueError):
+ plt.ylim(np.nan)
+ with pytest.raises(ValueError):
+ plt.ylim(np.inf)
+
+
+# Test all 4 combinations of logs/symlogs for minorticks_on()
+@pytest.mark.parametrize('xscale', ['symlog', 'log'])
+@pytest.mark.parametrize('yscale', ['symlog', 'log'])
+def test_minorticks_on(xscale, yscale):
+ ax = plt.subplot()
+ ax.plot([1, 2, 3, 4])
+ ax.set_xscale(xscale)
+ ax.set_yscale(yscale)
+ ax.minorticks_on()
+
+
+def test_twinx_knows_limits():
+ fig, ax = plt.subplots()
+
+ ax.axvspan(1, 2)
+ xtwin = ax.twinx()
+ xtwin.plot([0, 0.5], [1, 2])
+ # control axis
+ fig2, ax2 = plt.subplots()
+
+ ax2.axvspan(1, 2)
+ ax2.plot([0, 0.5], [1, 2])
+
+ assert_array_equal(xtwin.viewLim.intervalx, ax2.viewLim.intervalx)
+
+
+def test_zero_linewidth():
+ # Check that setting a zero linewidth doesn't error
+ plt.plot([0, 1], [0, 1], ls='--', lw=0)
+
+
+def test_empty_errorbar_legend():
+ fig, ax = plt.subplots()
+ ax.errorbar([], [], xerr=[], label='empty y')
+ ax.errorbar([], [], yerr=[], label='empty x')
+ ax.legend()
+
+
+@check_figures_equal(extensions=["png"])
+def test_plot_decimal(fig_test, fig_ref):
+ x0 = np.arange(-10, 10, 0.3)
+ y0 = [5.2 * x ** 3 - 2.1 * x ** 2 + 7.34 * x + 4.5 for x in x0]
+ x = [Decimal(i) for i in x0]
+ y = [Decimal(i) for i in y0]
+ # Test image - line plot with Decimal input
+ fig_test.subplots().plot(x, y)
+ # Reference image
+ fig_ref.subplots().plot(x0, y0)
+
+
+# pdf and svg tests fail using travis' old versions of gs and inkscape.
+@check_figures_equal(extensions=["png"])
+def test_markerfacecolor_none_alpha(fig_test, fig_ref):
+ fig_test.subplots().plot(0, "o", mfc="none", alpha=.5)
+ fig_ref.subplots().plot(0, "o", mfc="w", alpha=.5)
+
+
+def test_tick_padding_tightbbox():
+ """Test that tick padding gets turned off if axis is off"""
+ plt.rcParams["xtick.direction"] = "out"
+ plt.rcParams["ytick.direction"] = "out"
+ fig, ax = plt.subplots()
+ bb = ax.get_tightbbox(fig.canvas.get_renderer())
+ ax.axis('off')
+ bb2 = ax.get_tightbbox(fig.canvas.get_renderer())
+ assert bb.x0 < bb2.x0
+ assert bb.y0 < bb2.y0
+
+
+def test_inset():
+ """
+ Ensure that inset_ax argument is indeed optional
+ """
+ dx, dy = 0.05, 0.05
+ # generate 2 2d grids for the x & y bounds
+ y, x = np.mgrid[slice(1, 5 + dy, dy),
+ slice(1, 5 + dx, dx)]
+ z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
+
+ fig, ax = plt.subplots()
+ ax.pcolormesh(x, y, z[:-1, :-1])
+ ax.set_aspect(1.)
+ ax.apply_aspect()
+ # we need to apply_aspect to make the drawing below work.
+
+ xlim = [1.5, 2.15]
+ ylim = [2, 2.5]
+
+ rect = [xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0]]
+
+ inset = ax.indicate_inset(bounds=rect)
+ assert inset.connectors is None
+ fig.canvas.draw()
+ xx = np.array([[1.5, 2.],
+ [2.15, 2.5]])
+ assert np.all(inset.rectangle.get_bbox().get_points() == xx)
+
+
+def test_zoom_inset():
+ dx, dy = 0.05, 0.05
+ # generate 2 2d grids for the x & y bounds
+ y, x = np.mgrid[slice(1, 5 + dy, dy),
+ slice(1, 5 + dx, dx)]
+ z = np.sin(x)**10 + np.cos(10 + y*x) * np.cos(x)
+
+ fig, ax = plt.subplots()
+ ax.pcolormesh(x, y, z[:-1, :-1])
+ ax.set_aspect(1.)
+ ax.apply_aspect()
+ # we need to apply_aspect to make the drawing below work.
+
+ # Make the inset_axes... Position axes coordinates...
+ axin1 = ax.inset_axes([0.7, 0.7, 0.35, 0.35])
+ # redraw the data in the inset axes...
+ axin1.pcolormesh(x, y, z[:-1, :-1])
+ axin1.set_xlim([1.5, 2.15])
+ axin1.set_ylim([2, 2.5])
+ axin1.set_aspect(ax.get_aspect())
+
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ rec, connectors = ax.indicate_inset_zoom(axin1)
+ fig.canvas.draw()
+ assert len(connectors) == 4
+ xx = np.array([[1.5, 2.],
+ [2.15, 2.5]])
+ assert np.all(rec.get_bbox().get_points() == xx)
+ xx = np.array([[0.6325, 0.692308],
+ [0.8425, 0.907692]])
+ np.testing.assert_allclose(
+ axin1.get_position().get_points(), xx, rtol=1e-4)
+
+
+@image_comparison(['inset_polar.png'], remove_text=True, style='mpl20')
+def test_inset_polar():
+ _, ax = plt.subplots()
+ axins = ax.inset_axes([0.5, 0.1, 0.45, 0.45], polar=True)
+ assert isinstance(axins, PolarAxes)
+
+ r = np.arange(0, 2, 0.01)
+ theta = 2 * np.pi * r
+
+ ax.plot(theta, r)
+ axins.plot(theta, r)
+
+
+def test_inset_projection():
+ _, ax = plt.subplots()
+ axins = ax.inset_axes([0.2, 0.2, 0.3, 0.3], projection="hammer")
+ assert isinstance(axins, HammerAxes)
+
+
+def test_inset_subclass():
+ _, ax = plt.subplots()
+ axins = ax.inset_axes([0.2, 0.2, 0.3, 0.3], axes_class=AA.Axes)
+ assert isinstance(axins, AA.Axes)
+
+
+@pytest.mark.parametrize('x_inverted', [False, True])
+@pytest.mark.parametrize('y_inverted', [False, True])
+def test_indicate_inset_inverted(x_inverted, y_inverted):
+ """
+ Test that the inset lines are correctly located with inverted data axes.
+ """
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+
+ x = np.arange(10)
+ ax1.plot(x, x, 'o')
+ if x_inverted:
+ ax1.invert_xaxis()
+ if y_inverted:
+ ax1.invert_yaxis()
+
+ inset = ax1.indicate_inset([2, 2, 5, 4], ax2)
+ lower_left, upper_left, lower_right, upper_right = inset.connectors
+
+ sign_x = -1 if x_inverted else 1
+ sign_y = -1 if y_inverted else 1
+ assert sign_x * (lower_right.xy2[0] - lower_left.xy2[0]) > 0
+ assert sign_x * (upper_right.xy2[0] - upper_left.xy2[0]) > 0
+ assert sign_y * (upper_left.xy2[1] - lower_left.xy2[1]) > 0
+ assert sign_y * (upper_right.xy2[1] - lower_right.xy2[1]) > 0
+
+
+def test_set_position():
+ fig, ax = plt.subplots()
+ ax.set_aspect(3.)
+ ax.set_position([0.1, 0.1, 0.4, 0.4], which='both')
+ assert np.allclose(ax.get_position().width, 0.1)
+ ax.set_aspect(2.)
+ ax.set_position([0.1, 0.1, 0.4, 0.4], which='original')
+ assert np.allclose(ax.get_position().width, 0.15)
+ ax.set_aspect(3.)
+ ax.set_position([0.1, 0.1, 0.4, 0.4], which='active')
+ assert np.allclose(ax.get_position().width, 0.1)
+
+
+def test_spines_properbbox_after_zoom():
+ fig, ax = plt.subplots()
+ bb = ax.spines.bottom.get_window_extent(fig.canvas.get_renderer())
+ # this is what zoom calls:
+ ax._set_view_from_bbox((320, 320, 500, 500), 'in',
+ None, False, False)
+ bb2 = ax.spines.bottom.get_window_extent(fig.canvas.get_renderer())
+ np.testing.assert_allclose(bb.get_points(), bb2.get_points(), rtol=1e-6)
+
+
+def test_limits_after_scroll_zoom():
+ fig, ax = plt.subplots()
+ #
+ xlim = (-0.5, 0.5)
+ ylim = (-1, 2)
+ ax.set_xlim(xlim)
+ ax.set_ylim(ymin=ylim[0], ymax=ylim[1])
+ # This is what scroll zoom calls:
+ # Zoom with factor 1, small numerical change
+ ax._set_view_from_bbox((200, 200, 1.))
+ np.testing.assert_allclose(xlim, ax.get_xlim(), atol=1e-16)
+ np.testing.assert_allclose(ylim, ax.get_ylim(), atol=1e-16)
+
+ # Zoom in
+ ax._set_view_from_bbox((200, 200, 2.))
+ # Hard-coded values
+ new_xlim = (-0.3790322580645161, 0.12096774193548387)
+ new_ylim = (-0.40625, 1.09375)
+
+ res_xlim = ax.get_xlim()
+ res_ylim = ax.get_ylim()
+ np.testing.assert_allclose(res_xlim[1] - res_xlim[0], 0.5)
+ np.testing.assert_allclose(res_ylim[1] - res_ylim[0], 1.5)
+ np.testing.assert_allclose(new_xlim, res_xlim, atol=1e-16)
+ np.testing.assert_allclose(new_ylim, res_ylim)
+
+ # Zoom out, should be same as before, except for numerical issues
+ ax._set_view_from_bbox((200, 200, 0.5))
+ res_xlim = ax.get_xlim()
+ res_ylim = ax.get_ylim()
+ np.testing.assert_allclose(res_xlim[1] - res_xlim[0], 1)
+ np.testing.assert_allclose(res_ylim[1] - res_ylim[0], 3)
+ np.testing.assert_allclose(xlim, res_xlim, atol=1e-16)
+ np.testing.assert_allclose(ylim, res_ylim, atol=1e-16)
+
+
+def test_gettightbbox_ignore_nan():
+ fig, ax = plt.subplots()
+ remove_ticks_and_titles(fig)
+ ax.text(np.nan, 1, 'Boo')
+ renderer = fig.canvas.get_renderer()
+ np.testing.assert_allclose(ax.get_tightbbox(renderer).width, 496)
+
+
+def test_scatter_series_non_zero_index(pd):
+ # create non-zero index
+ ids = range(10, 18)
+ x = pd.Series(np.random.uniform(size=8), index=ids)
+ y = pd.Series(np.random.uniform(size=8), index=ids)
+ c = pd.Series([1, 1, 1, 1, 1, 0, 0, 0], index=ids)
+ plt.scatter(x, y, c)
+
+
+def test_scatter_empty_data():
+ # making sure this does not raise an exception
+ plt.scatter([], [])
+ plt.scatter([], [], s=[], c=[])
+
+
+@image_comparison(['annotate_across_transforms.png'], style='mpl20', remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.025)
+def test_annotate_across_transforms():
+ x = np.linspace(0, 10, 200)
+ y = np.exp(-x) * np.sin(x)
+
+ fig, ax = plt.subplots(figsize=(3.39, 3))
+ ax.plot(x, y)
+ axins = ax.inset_axes([0.4, 0.5, 0.3, 0.3])
+ axins.set_aspect(0.2)
+ axins.xaxis.set_visible(False)
+ axins.yaxis.set_visible(False)
+ ax.annotate("", xy=(x[150], y[150]), xycoords=ax.transData,
+ xytext=(1, 0), textcoords=axins.transAxes,
+ arrowprops=dict(arrowstyle="->"))
+
+
+class _Translation(mtransforms.Transform):
+ input_dims = 1
+ output_dims = 1
+
+ def __init__(self, dx):
+ self.dx = dx
+
+ def transform(self, values):
+ return values + self.dx
+
+ def inverted(self):
+ return _Translation(-self.dx)
+
+
+@image_comparison(['secondary_xy.png'], style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.027)
+def test_secondary_xy():
+ fig, axs = plt.subplots(1, 2, figsize=(10, 5), constrained_layout=True)
+
+ def invert(x):
+ with np.errstate(divide='ignore'):
+ return 1 / x
+
+ for nn, ax in enumerate(axs):
+ ax.plot(np.arange(2, 11), np.arange(2, 11))
+ if nn == 0:
+ secax = ax.secondary_xaxis
+ else:
+ secax = ax.secondary_yaxis
+
+ secax(0.2, functions=(invert, invert))
+ secax(0.4, functions=(lambda x: 2 * x, lambda x: x / 2))
+ secax(0.6, functions=(lambda x: x**2, lambda x: x**(1/2)))
+ secax(0.8)
+ secax("top" if nn == 0 else "right", functions=_Translation(2))
+ secax(6.25, transform=ax.transData)
+
+
+def test_secondary_fail():
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(2, 11), np.arange(2, 11))
+ with pytest.raises(ValueError):
+ ax.secondary_xaxis(0.2, functions=(lambda x: 1 / x))
+ with pytest.raises(ValueError):
+ ax.secondary_xaxis('right')
+ with pytest.raises(ValueError):
+ ax.secondary_yaxis('bottom')
+ with pytest.raises(TypeError):
+ ax.secondary_xaxis(0.2, transform='error')
+
+
+def test_secondary_resize():
+ fig, ax = plt.subplots(figsize=(10, 5))
+ ax.plot(np.arange(2, 11), np.arange(2, 11))
+
+ def invert(x):
+ with np.errstate(divide='ignore'):
+ return 1 / x
+
+ ax.secondary_xaxis('top', functions=(invert, invert))
+ fig.canvas.draw()
+ fig.set_size_inches((7, 4))
+ assert_allclose(ax.get_position().extents, [0.125, 0.1, 0.9, 0.9])
+
+
+def test_secondary_minorloc():
+ fig, ax = plt.subplots(figsize=(10, 5))
+ ax.plot(np.arange(2, 11), np.arange(2, 11))
+
+ def invert(x):
+ with np.errstate(divide='ignore'):
+ return 1 / x
+
+ secax = ax.secondary_xaxis('top', functions=(invert, invert))
+ assert isinstance(secax._axis.get_minor_locator(),
+ mticker.NullLocator)
+ secax.minorticks_on()
+ assert isinstance(secax._axis.get_minor_locator(),
+ mticker.AutoMinorLocator)
+ ax.set_xscale('log')
+ plt.draw()
+ assert isinstance(secax._axis.get_minor_locator(),
+ mticker.LogLocator)
+ ax.set_xscale('linear')
+ plt.draw()
+ assert isinstance(secax._axis.get_minor_locator(),
+ mticker.NullLocator)
+
+
+def test_secondary_formatter():
+ fig, ax = plt.subplots()
+ ax.set_xscale("log")
+ secax = ax.secondary_xaxis("top")
+ secax.xaxis.set_major_formatter(mticker.ScalarFormatter())
+ fig.canvas.draw()
+ assert isinstance(
+ secax.xaxis.get_major_formatter(), mticker.ScalarFormatter)
+
+
+def test_secondary_repr():
+ fig, ax = plt.subplots()
+ secax = ax.secondary_xaxis("top")
+ assert repr(secax) == ''
+
+
+@image_comparison(['axis_options.png'], remove_text=True, style='mpl20')
+def test_axis_options():
+ fig, axes = plt.subplots(2, 3)
+ for i, option in enumerate(('scaled', 'tight', 'image')):
+ # Draw a line and a circle fitting within the boundaries of the line
+ # The circle should look like a circle for 'scaled' and 'image'
+ # High/narrow aspect ratio
+ axes[0, i].plot((1, 2), (1, 3.2))
+ axes[0, i].axis(option)
+ axes[0, i].add_artist(mpatches.Circle((1.5, 1.5), radius=0.5,
+ facecolor='none', edgecolor='k'))
+ # Low/wide aspect ratio
+ axes[1, i].plot((1, 2.25), (1, 1.75))
+ axes[1, i].axis(option)
+ axes[1, i].add_artist(mpatches.Circle((1.5, 1.25), radius=0.25,
+ facecolor='none', edgecolor='k'))
+
+
+def color_boxes(fig, ax):
+ """
+ Helper for the tests below that test the extents of various Axes elements
+ """
+ fig.canvas.draw()
+
+ renderer = fig.canvas.get_renderer()
+ bbaxis = []
+ for nn, axx in enumerate([ax.xaxis, ax.yaxis]):
+ bb = axx.get_tightbbox(renderer)
+ if bb:
+ axisr = mpatches.Rectangle(
+ (bb.x0, bb.y0), width=bb.width, height=bb.height,
+ linewidth=0.7, edgecolor='y', facecolor="none", transform=None,
+ zorder=3)
+ fig.add_artist(axisr)
+ bbaxis += [bb]
+
+ bbspines = []
+ for nn, a in enumerate(['bottom', 'top', 'left', 'right']):
+ bb = ax.spines[a].get_window_extent(renderer)
+ spiner = mpatches.Rectangle(
+ (bb.x0, bb.y0), width=bb.width, height=bb.height,
+ linewidth=0.7, edgecolor="green", facecolor="none", transform=None,
+ zorder=3)
+ fig.add_artist(spiner)
+ bbspines += [bb]
+
+ bb = ax.get_window_extent()
+ rect2 = mpatches.Rectangle(
+ (bb.x0, bb.y0), width=bb.width, height=bb.height,
+ linewidth=1.5, edgecolor="magenta", facecolor="none", transform=None,
+ zorder=2)
+ fig.add_artist(rect2)
+ bbax = bb
+
+ bb2 = ax.get_tightbbox(renderer)
+ rect2 = mpatches.Rectangle(
+ (bb2.x0, bb2.y0), width=bb2.width, height=bb2.height,
+ linewidth=3, edgecolor="red", facecolor="none", transform=None,
+ zorder=1)
+ fig.add_artist(rect2)
+ bbtb = bb2
+ return bbaxis, bbspines, bbax, bbtb
+
+
+def test_normal_axes():
+ with rc_context({'_internal.classic_mode': False}):
+ fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
+ fig.canvas.draw()
+ plt.close(fig)
+ bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
+
+ # test the axis bboxes
+ target = [
+ [123.375, 75.88888888888886, 983.25, 33.0],
+ [85.51388888888889, 99.99999999999997, 53.375, 993.0]
+ ]
+ for nn, b in enumerate(bbaxis):
+ targetbb = mtransforms.Bbox.from_bounds(*target[nn])
+ assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=2)
+
+ target = [
+ [150.0, 119.999, 930.0, 11.111],
+ [150.0, 1080.0, 930.0, 0.0],
+ [150.0, 119.9999, 11.111, 960.0],
+ [1068.8888, 119.9999, 11.111, 960.0]
+ ]
+ for nn, b in enumerate(bbspines):
+ targetbb = mtransforms.Bbox.from_bounds(*target[nn])
+ assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=2)
+
+ target = [150.0, 119.99999999999997, 930.0, 960.0]
+ targetbb = mtransforms.Bbox.from_bounds(*target)
+ assert_array_almost_equal(bbax.bounds, targetbb.bounds, decimal=2)
+
+ target = [85.5138, 75.88888, 1021.11, 1017.11]
+ targetbb = mtransforms.Bbox.from_bounds(*target)
+ assert_array_almost_equal(bbtb.bounds, targetbb.bounds, decimal=2)
+
+ # test that get_position roundtrips to get_window_extent
+ axbb = ax.get_position().transformed(fig.transFigure).bounds
+ assert_array_almost_equal(axbb, ax.get_window_extent().bounds, decimal=2)
+
+
+def test_nodecorator():
+ with rc_context({'_internal.classic_mode': False}):
+ fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
+ fig.canvas.draw()
+ ax.set(xticklabels=[], yticklabels=[])
+ bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
+
+ # test the axis bboxes
+ for nn, b in enumerate(bbaxis):
+ assert b is None
+
+ target = [
+ [150.0, 119.999, 930.0, 11.111],
+ [150.0, 1080.0, 930.0, 0.0],
+ [150.0, 119.9999, 11.111, 960.0],
+ [1068.8888, 119.9999, 11.111, 960.0]
+ ]
+ for nn, b in enumerate(bbspines):
+ targetbb = mtransforms.Bbox.from_bounds(*target[nn])
+ assert_allclose(b.bounds, targetbb.bounds, atol=1e-2)
+
+ target = [150.0, 119.99999999999997, 930.0, 960.0]
+ targetbb = mtransforms.Bbox.from_bounds(*target)
+ assert_allclose(bbax.bounds, targetbb.bounds, atol=1e-2)
+
+ target = [150., 120., 930., 960.]
+ targetbb = mtransforms.Bbox.from_bounds(*target)
+ assert_allclose(bbtb.bounds, targetbb.bounds, atol=1e-2)
+
+
+def test_displaced_spine():
+ with rc_context({'_internal.classic_mode': False}):
+ fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
+ ax.set(xticklabels=[], yticklabels=[])
+ ax.spines.bottom.set_position(('axes', -0.1))
+ fig.canvas.draw()
+ bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
+
+ targets = [
+ [150., 24., 930., 11.111111],
+ [150.0, 1080.0, 930.0, 0.0],
+ [150.0, 119.9999, 11.111, 960.0],
+ [1068.8888, 119.9999, 11.111, 960.0]
+ ]
+ for target, bbspine in zip(targets, bbspines):
+ targetbb = mtransforms.Bbox.from_bounds(*target)
+ assert_allclose(bbspine.bounds, targetbb.bounds, atol=1e-2)
+
+ target = [150.0, 119.99999999999997, 930.0, 960.0]
+ targetbb = mtransforms.Bbox.from_bounds(*target)
+ assert_allclose(bbax.bounds, targetbb.bounds, atol=1e-2)
+
+ target = [150., 24., 930., 1056.]
+ targetbb = mtransforms.Bbox.from_bounds(*target)
+ assert_allclose(bbtb.bounds, targetbb.bounds, atol=1e-2)
+
+
+def test_tickdirs():
+ """
+ Switch the tickdirs and make sure the bboxes switch with them
+ """
+ targets = [[[150.0, 120.0, 930.0, 11.1111],
+ [150.0, 120.0, 11.111, 960.0]],
+ [[150.0, 108.8889, 930.0, 11.111111111111114],
+ [138.889, 120, 11.111, 960.0]],
+ [[150.0, 114.44444444444441, 930.0, 11.111111111111114],
+ [144.44444444444446, 119.999, 11.111, 960.0]]]
+ for dnum, dirs in enumerate(['in', 'out', 'inout']):
+ with rc_context({'_internal.classic_mode': False}):
+ fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
+ ax.tick_params(direction=dirs)
+ fig.canvas.draw()
+ bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
+ for nn, num in enumerate([0, 2]):
+ targetbb = mtransforms.Bbox.from_bounds(*targets[dnum][nn])
+ assert_allclose(
+ bbspines[num].bounds, targetbb.bounds, atol=1e-2)
+
+
+def test_minor_accountedfor():
+ with rc_context({'_internal.classic_mode': False}):
+ fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
+ fig.canvas.draw()
+ ax.tick_params(which='both', direction='out')
+
+ bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
+ bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
+ targets = [[150.0, 108.88888888888886, 930.0, 11.111111111111114],
+ [138.8889, 119.9999, 11.1111, 960.0]]
+ for n in range(2):
+ targetbb = mtransforms.Bbox.from_bounds(*targets[n])
+ assert_allclose(
+ bbspines[n * 2].bounds, targetbb.bounds, atol=1e-2)
+
+ fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
+ fig.canvas.draw()
+ ax.tick_params(which='both', direction='out')
+ ax.minorticks_on()
+ ax.tick_params(axis='both', which='minor', length=30)
+ fig.canvas.draw()
+ bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
+ targets = [[150.0, 36.66666666666663, 930.0, 83.33333333333334],
+ [66.6667, 120.0, 83.3333, 960.0]]
+
+ for n in range(2):
+ targetbb = mtransforms.Bbox.from_bounds(*targets[n])
+ assert_allclose(
+ bbspines[n * 2].bounds, targetbb.bounds, atol=1e-2)
+
+
+@check_figures_equal(extensions=["png"])
+def test_axis_bool_arguments(fig_test, fig_ref):
+ # Test if False and "off" give the same
+ fig_test.add_subplot(211).axis(False)
+ fig_ref.add_subplot(211).axis("off")
+ # Test if True after False gives the same as "on"
+ ax = fig_test.add_subplot(212)
+ ax.axis(False)
+ ax.axis(True)
+ fig_ref.add_subplot(212).axis("on")
+
+
+def test_axis_extent_arg():
+ fig, ax = plt.subplots()
+ xmin = 5
+ xmax = 10
+ ymin = 15
+ ymax = 20
+ extent = ax.axis([xmin, xmax, ymin, ymax])
+
+ # test that the docstring is correct
+ assert tuple(extent) == (xmin, xmax, ymin, ymax)
+
+ # test that limits were set per the docstring
+ assert (xmin, xmax) == ax.get_xlim()
+ assert (ymin, ymax) == ax.get_ylim()
+
+
+def test_axis_extent_arg2():
+ # Same as test_axis_extent_arg, but with keyword arguments
+ fig, ax = plt.subplots()
+ xmin = 5
+ xmax = 10
+ ymin = 15
+ ymax = 20
+ extent = ax.axis(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
+
+ # test that the docstring is correct
+ assert tuple(extent) == (xmin, xmax, ymin, ymax)
+
+ # test that limits were set per the docstring
+ assert (xmin, xmax) == ax.get_xlim()
+ assert (ymin, ymax) == ax.get_ylim()
+
+
+def test_hist_auto_bins():
+ _, bins, _ = plt.hist([[1, 2, 3], [3, 4, 5, 6]], bins='auto')
+ assert bins[0] <= 1
+ assert bins[-1] >= 6
+
+
+def test_hist_nan_data():
+ fig, (ax1, ax2) = plt.subplots(2)
+
+ data = [1, 2, 3]
+ nan_data = data + [np.nan]
+
+ bins, edges, _ = ax1.hist(data)
+ with np.errstate(invalid='ignore'):
+ nanbins, nanedges, _ = ax2.hist(nan_data)
+
+ np.testing.assert_allclose(bins, nanbins)
+ np.testing.assert_allclose(edges, nanedges)
+
+
+def test_hist_range_and_density():
+ _, bins, _ = plt.hist(np.random.rand(10), "auto",
+ range=(0, 1), density=True)
+ assert bins[0] == 0
+ assert bins[-1] == 1
+
+
+def test_bar_errbar_zorder():
+ # Check that the zorder of errorbars is always greater than the bar they
+ # are plotted on
+ fig, ax = plt.subplots()
+ x = [1, 2, 3]
+ barcont = ax.bar(x=x, height=x, yerr=x, capsize=5, zorder=3)
+
+ data_line, caplines, barlinecols = barcont.errorbar.lines
+ for bar in barcont.patches:
+ for capline in caplines:
+ assert capline.zorder > bar.zorder
+ for barlinecol in barlinecols:
+ assert barlinecol.zorder > bar.zorder
+
+
+def test_set_ticks_inverted():
+ fig, ax = plt.subplots()
+ ax.invert_xaxis()
+ ax.set_xticks([.3, .7])
+ assert ax.get_xlim() == (1, 0)
+ ax.set_xticks([-1])
+ assert ax.get_xlim() == (1, -1)
+
+
+def test_aspect_nonlinear_adjustable_box():
+ fig = plt.figure(figsize=(10, 10)) # Square.
+
+ ax = fig.add_subplot()
+ ax.plot([.4, .6], [.4, .6]) # Set minpos to keep logit happy.
+ ax.set(xscale="log", xlim=(1, 10),
+ yscale="logit", ylim=(1/11, 1/1001),
+ aspect=1, adjustable="box")
+ ax.margins(0)
+ pos = fig.transFigure.transform_bbox(ax.get_position())
+ assert pos.height / pos.width == pytest.approx(2)
+
+
+def test_aspect_nonlinear_adjustable_datalim():
+ fig = plt.figure(figsize=(10, 10)) # Square.
+
+ ax = fig.add_axes([.1, .1, .8, .8]) # Square.
+ ax.plot([.4, .6], [.4, .6]) # Set minpos to keep logit happy.
+ ax.set(xscale="log", xlim=(1, 100),
+ yscale="logit", ylim=(1 / 101, 1 / 11),
+ aspect=1, adjustable="datalim")
+ ax.margins(0)
+ ax.apply_aspect()
+
+ assert ax.get_xlim() == pytest.approx([1*10**(1/2), 100/10**(1/2)])
+ assert ax.get_ylim() == (1 / 101, 1 / 11)
+
+
+def test_box_aspect():
+ # Test if axes with box_aspect=1 has same dimensions
+ # as axes with aspect equal and adjustable="box"
+
+ fig1, ax1 = plt.subplots()
+ axtwin = ax1.twinx()
+ axtwin.plot([12, 344])
+
+ ax1.set_box_aspect(1)
+ assert ax1.get_box_aspect() == 1.0
+
+ fig2, ax2 = plt.subplots()
+ ax2.margins(0)
+ ax2.plot([0, 2], [6, 8])
+ ax2.set_aspect("equal", adjustable="box")
+
+ fig1.canvas.draw()
+ fig2.canvas.draw()
+
+ bb1 = ax1.get_position()
+ bbt = axtwin.get_position()
+ bb2 = ax2.get_position()
+
+ assert_array_equal(bb1.extents, bb2.extents)
+ assert_array_equal(bbt.extents, bb2.extents)
+
+
+def test_box_aspect_custom_position():
+ # Test if axes with custom position and box_aspect
+ # behaves the same independent of the order of setting those.
+
+ fig1, ax1 = plt.subplots()
+ ax1.set_position([0.1, 0.1, 0.9, 0.2])
+ fig1.canvas.draw()
+ ax1.set_box_aspect(1.)
+
+ fig2, ax2 = plt.subplots()
+ ax2.set_box_aspect(1.)
+ fig2.canvas.draw()
+ ax2.set_position([0.1, 0.1, 0.9, 0.2])
+
+ fig1.canvas.draw()
+ fig2.canvas.draw()
+
+ bb1 = ax1.get_position()
+ bb2 = ax2.get_position()
+
+ assert_array_equal(bb1.extents, bb2.extents)
+
+
+def test_bbox_aspect_axes_init():
+ # Test that box_aspect can be given to axes init and produces
+ # all equal square axes.
+ fig, axs = plt.subplots(2, 3, subplot_kw=dict(box_aspect=1),
+ constrained_layout=True)
+ fig.canvas.draw()
+ renderer = fig.canvas.get_renderer()
+ sizes = []
+ for ax in axs.flat:
+ bb = ax.get_window_extent(renderer)
+ sizes.extend([bb.width, bb.height])
+
+ assert_allclose(sizes, sizes[0])
+
+
+def test_set_aspect_negative():
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError, match="must be finite and positive"):
+ ax.set_aspect(-1)
+ with pytest.raises(ValueError, match="must be finite and positive"):
+ ax.set_aspect(0)
+ with pytest.raises(ValueError, match="must be finite and positive"):
+ ax.set_aspect(np.inf)
+ with pytest.raises(ValueError, match="must be finite and positive"):
+ ax.set_aspect(-np.inf)
+
+
+def test_redraw_in_frame():
+ fig, ax = plt.subplots(1, 1)
+ ax.plot([1, 2, 3])
+ fig.canvas.draw()
+ ax.redraw_in_frame()
+
+
+def test_invisible_axes_events():
+ # invisible axes should not respond to events...
+ fig, ax = plt.subplots()
+ assert fig.canvas.inaxes((200, 200)) is not None
+ ax.set_visible(False)
+ assert fig.canvas.inaxes((200, 200)) is None
+
+
+def test_xtickcolor_is_not_markercolor():
+ plt.rcParams['lines.markeredgecolor'] = 'white'
+ ax = plt.axes()
+ ticks = ax.xaxis.get_major_ticks()
+ for tick in ticks:
+ assert tick.tick1line.get_markeredgecolor() != 'white'
+
+
+def test_ytickcolor_is_not_markercolor():
+ plt.rcParams['lines.markeredgecolor'] = 'white'
+ ax = plt.axes()
+ ticks = ax.yaxis.get_major_ticks()
+ for tick in ticks:
+ assert tick.tick1line.get_markeredgecolor() != 'white'
+
+
+@pytest.mark.parametrize('axis', ('x', 'y'))
+@pytest.mark.parametrize('auto', (True, False, None))
+def test_unautoscale(axis, auto):
+ fig, ax = plt.subplots()
+ x = np.arange(100)
+ y = np.linspace(-.1, .1, 100)
+ ax.scatter(y, x)
+
+ get_autoscale_on = getattr(ax, f'get_autoscale{axis}_on')
+ set_lim = getattr(ax, f'set_{axis}lim')
+ get_lim = getattr(ax, f'get_{axis}lim')
+
+ post_auto = get_autoscale_on() if auto is None else auto
+
+ set_lim((-0.5, 0.5), auto=auto)
+ assert post_auto == get_autoscale_on()
+ fig.canvas.draw()
+ assert_array_equal(get_lim(), (-0.5, 0.5))
+
+
+@check_figures_equal(extensions=["png"])
+def test_polar_interpolation_steps_variable_r(fig_test, fig_ref):
+ l, = fig_test.add_subplot(projection="polar").plot([0, np.pi/2], [1, 2])
+ l.get_path()._interpolation_steps = 100
+ fig_ref.add_subplot(projection="polar").plot(
+ np.linspace(0, np.pi/2, 101), np.linspace(1, 2, 101))
+
+
+@mpl.style.context('default')
+def test_autoscale_tiny_sticky():
+ fig, ax = plt.subplots()
+ ax.bar(0, 1e-9)
+ fig.canvas.draw()
+ assert ax.get_ylim() == (0, 1.05e-9)
+
+
+def test_xtickcolor_is_not_xticklabelcolor():
+ plt.rcParams['xtick.color'] = 'yellow'
+ plt.rcParams['xtick.labelcolor'] = 'blue'
+ ax = plt.axes()
+ ticks = ax.xaxis.get_major_ticks()
+ for tick in ticks:
+ assert tick.tick1line.get_color() == 'yellow'
+ assert tick.label1.get_color() == 'blue'
+
+
+def test_ytickcolor_is_not_yticklabelcolor():
+ plt.rcParams['ytick.color'] = 'yellow'
+ plt.rcParams['ytick.labelcolor'] = 'blue'
+ ax = plt.axes()
+ ticks = ax.yaxis.get_major_ticks()
+ for tick in ticks:
+ assert tick.tick1line.get_color() == 'yellow'
+ assert tick.label1.get_color() == 'blue'
+
+
+def test_xaxis_offsetText_color():
+ plt.rcParams['xtick.labelcolor'] = 'blue'
+ ax = plt.axes()
+ assert ax.xaxis.offsetText.get_color() == 'blue'
+
+ plt.rcParams['xtick.color'] = 'yellow'
+ plt.rcParams['xtick.labelcolor'] = 'inherit'
+ ax = plt.axes()
+ assert ax.xaxis.offsetText.get_color() == 'yellow'
+
+
+def test_yaxis_offsetText_color():
+ plt.rcParams['ytick.labelcolor'] = 'green'
+ ax = plt.axes()
+ assert ax.yaxis.offsetText.get_color() == 'green'
+
+ plt.rcParams['ytick.color'] = 'red'
+ plt.rcParams['ytick.labelcolor'] = 'inherit'
+ ax = plt.axes()
+ assert ax.yaxis.offsetText.get_color() == 'red'
+
+
+@pytest.mark.parametrize('size', [size for size in mfont_manager.font_scalings
+ if size is not None] + [8, 10, 12])
+@mpl.style.context('default')
+def test_relative_ticklabel_sizes(size):
+ mpl.rcParams['xtick.labelsize'] = size
+ mpl.rcParams['ytick.labelsize'] = size
+ fig, ax = plt.subplots()
+ fig.canvas.draw()
+
+ for name, axis in zip(['x', 'y'], [ax.xaxis, ax.yaxis]):
+ for tick in axis.get_major_ticks():
+ assert tick.label1.get_size() == axis._get_tick_label_size(name)
+
+
+def test_multiplot_autoscale():
+ fig = plt.figure()
+ ax1, ax2 = fig.subplots(2, 1, sharex='all')
+ ax1.plot([18000, 18250, 18500, 18750], [2, 3, 2, 3])
+ ax2.axhspan(-5, 5)
+ xlim = ax1.get_xlim()
+ assert np.allclose(xlim, [18000, 18800])
+
+
+def test_sharing_does_not_link_positions():
+ fig = plt.figure()
+ ax0 = fig.add_subplot(221)
+ ax1 = fig.add_axes([.6, .6, .3, .3], sharex=ax0)
+ init_pos = ax1.get_position()
+ fig.subplots_adjust(left=0)
+ assert (ax1.get_position().get_points() == init_pos.get_points()).all()
+
+
+@check_figures_equal(extensions=["pdf"])
+def test_2dcolor_plot(fig_test, fig_ref):
+ color = np.array([0.1, 0.2, 0.3])
+ # plot with 1D-color:
+ axs = fig_test.subplots(5)
+ axs[0].plot([1, 2], [1, 2], c=color.reshape(-1))
+ with pytest.warns(match="argument looks like a single numeric RGB"):
+ axs[1].scatter([1, 2], [1, 2], c=color.reshape(-1))
+ axs[2].step([1, 2], [1, 2], c=color.reshape(-1))
+ axs[3].hist(np.arange(10), color=color.reshape(-1))
+ axs[4].bar(np.arange(10), np.arange(10), color=color.reshape(-1))
+ # plot with 2D-color:
+ axs = fig_ref.subplots(5)
+ axs[0].plot([1, 2], [1, 2], c=color.reshape((1, -1)))
+ axs[1].scatter([1, 2], [1, 2], c=color.reshape((1, -1)))
+ axs[2].step([1, 2], [1, 2], c=color.reshape((1, -1)))
+ axs[3].hist(np.arange(10), color=color.reshape((1, -1)))
+ axs[4].bar(np.arange(10), np.arange(10), color=color.reshape((1, -1)))
+
+
+@check_figures_equal(extensions=['png'])
+def test_shared_axes_clear(fig_test, fig_ref):
+ x = np.arange(0.0, 2*np.pi, 0.01)
+ y = np.sin(x)
+
+ axs = fig_ref.subplots(2, 2, sharex=True, sharey=True)
+ for ax in axs.flat:
+ ax.plot(x, y)
+
+ axs = fig_test.subplots(2, 2, sharex=True, sharey=True)
+ for ax in axs.flat:
+ ax.clear()
+ ax.plot(x, y)
+
+
+def test_shared_axes_retick():
+ fig, axs = plt.subplots(2, 2, sharex='all', sharey='all')
+
+ for ax in axs.flat:
+ ax.plot([0, 2], 'o-')
+
+ axs[0, 0].set_xticks([-0.5, 0, 1, 1.5]) # should affect all axes xlims
+ for ax in axs.flat:
+ assert ax.get_xlim() == axs[0, 0].get_xlim()
+
+ axs[0, 0].set_yticks([-0.5, 0, 2, 2.5]) # should affect all axes ylims
+ for ax in axs.flat:
+ assert ax.get_ylim() == axs[0, 0].get_ylim()
+
+
+@pytest.mark.parametrize('ha', ['left', 'center', 'right'])
+def test_ylabel_ha_with_position(ha):
+ fig = Figure()
+ ax = fig.subplots()
+ ax.set_ylabel("test", y=1, ha=ha)
+ ax.yaxis.set_label_position("right")
+ assert ax.yaxis.label.get_ha() == ha
+
+
+def test_bar_label_location_vertical():
+ ax = plt.gca()
+ xs, heights = [1, 2], [3, -4]
+ rects = ax.bar(xs, heights)
+ labels = ax.bar_label(rects)
+ assert labels[0].xy == (xs[0], heights[0])
+ assert labels[0].get_horizontalalignment() == 'center'
+ assert labels[0].get_verticalalignment() == 'bottom'
+ assert labels[1].xy == (xs[1], heights[1])
+ assert labels[1].get_horizontalalignment() == 'center'
+ assert labels[1].get_verticalalignment() == 'top'
+
+
+def test_bar_label_location_vertical_yinverted():
+ ax = plt.gca()
+ ax.invert_yaxis()
+ xs, heights = [1, 2], [3, -4]
+ rects = ax.bar(xs, heights)
+ labels = ax.bar_label(rects)
+ assert labels[0].xy == (xs[0], heights[0])
+ assert labels[0].get_horizontalalignment() == 'center'
+ assert labels[0].get_verticalalignment() == 'top'
+ assert labels[1].xy == (xs[1], heights[1])
+ assert labels[1].get_horizontalalignment() == 'center'
+ assert labels[1].get_verticalalignment() == 'bottom'
+
+
+def test_bar_label_location_horizontal():
+ ax = plt.gca()
+ ys, widths = [1, 2], [3, -4]
+ rects = ax.barh(ys, widths)
+ labels = ax.bar_label(rects)
+ assert labels[0].xy == (widths[0], ys[0])
+ assert labels[0].get_horizontalalignment() == 'left'
+ assert labels[0].get_verticalalignment() == 'center'
+ assert labels[1].xy == (widths[1], ys[1])
+ assert labels[1].get_horizontalalignment() == 'right'
+ assert labels[1].get_verticalalignment() == 'center'
+
+
+def test_bar_label_location_horizontal_yinverted():
+ ax = plt.gca()
+ ax.invert_yaxis()
+ ys, widths = [1, 2], [3, -4]
+ rects = ax.barh(ys, widths)
+ labels = ax.bar_label(rects)
+ assert labels[0].xy == (widths[0], ys[0])
+ assert labels[0].get_horizontalalignment() == 'left'
+ assert labels[0].get_verticalalignment() == 'center'
+ assert labels[1].xy == (widths[1], ys[1])
+ assert labels[1].get_horizontalalignment() == 'right'
+ assert labels[1].get_verticalalignment() == 'center'
+
+
+def test_bar_label_location_horizontal_xinverted():
+ ax = plt.gca()
+ ax.invert_xaxis()
+ ys, widths = [1, 2], [3, -4]
+ rects = ax.barh(ys, widths)
+ labels = ax.bar_label(rects)
+ assert labels[0].xy == (widths[0], ys[0])
+ assert labels[0].get_horizontalalignment() == 'right'
+ assert labels[0].get_verticalalignment() == 'center'
+ assert labels[1].xy == (widths[1], ys[1])
+ assert labels[1].get_horizontalalignment() == 'left'
+ assert labels[1].get_verticalalignment() == 'center'
+
+
+def test_bar_label_location_horizontal_xyinverted():
+ ax = plt.gca()
+ ax.invert_xaxis()
+ ax.invert_yaxis()
+ ys, widths = [1, 2], [3, -4]
+ rects = ax.barh(ys, widths)
+ labels = ax.bar_label(rects)
+ assert labels[0].xy == (widths[0], ys[0])
+ assert labels[0].get_horizontalalignment() == 'right'
+ assert labels[0].get_verticalalignment() == 'center'
+ assert labels[1].xy == (widths[1], ys[1])
+ assert labels[1].get_horizontalalignment() == 'left'
+ assert labels[1].get_verticalalignment() == 'center'
+
+
+def test_bar_label_location_center():
+ ax = plt.gca()
+ ys, widths = [1, 2], [3, -4]
+ rects = ax.barh(ys, widths)
+ labels = ax.bar_label(rects, label_type='center')
+ assert labels[0].xy == (0.5, 0.5)
+ assert labels[0].get_horizontalalignment() == 'center'
+ assert labels[0].get_verticalalignment() == 'center'
+ assert labels[1].xy == (0.5, 0.5)
+ assert labels[1].get_horizontalalignment() == 'center'
+ assert labels[1].get_verticalalignment() == 'center'
+
+
+@image_comparison(['test_centered_bar_label_nonlinear.svg'])
+def test_centered_bar_label_nonlinear():
+ _, ax = plt.subplots()
+ bar_container = ax.barh(['c', 'b', 'a'], [1_000, 5_000, 7_000])
+ ax.set_xscale('log')
+ ax.set_xlim(1, None)
+ ax.bar_label(bar_container, label_type='center')
+ ax.set_axis_off()
+
+
+def test_centered_bar_label_label_beyond_limits():
+ fig, ax = plt.subplots()
+
+ last = 0
+ for label, value in zip(['a', 'b', 'c'], [10, 20, 50]):
+ bar_container = ax.barh('col', value, label=label, left=last)
+ ax.bar_label(bar_container, label_type='center')
+ last += value
+ ax.set_xlim(None, 20)
+
+ fig.draw_without_rendering()
+
+
+def test_bar_label_location_errorbars():
+ ax = plt.gca()
+ xs, heights = [1, 2], [3, -4]
+ rects = ax.bar(xs, heights, yerr=1)
+ labels = ax.bar_label(rects)
+ assert labels[0].xy == (xs[0], heights[0] + 1)
+ assert labels[0].get_horizontalalignment() == 'center'
+ assert labels[0].get_verticalalignment() == 'bottom'
+ assert labels[1].xy == (xs[1], heights[1] - 1)
+ assert labels[1].get_horizontalalignment() == 'center'
+ assert labels[1].get_verticalalignment() == 'top'
+
+
+@pytest.mark.parametrize('fmt', [
+ '%.2f', '{:.2f}', '{:.2f}'.format
+])
+def test_bar_label_fmt(fmt):
+ ax = plt.gca()
+ rects = ax.bar([1, 2], [3, -4])
+ labels = ax.bar_label(rects, fmt=fmt)
+ assert labels[0].get_text() == '3.00'
+ assert labels[1].get_text() == '-4.00'
+
+
+def test_bar_label_fmt_error():
+ ax = plt.gca()
+ rects = ax.bar([1, 2], [3, -4])
+ with pytest.raises(TypeError, match='str or callable'):
+ _ = ax.bar_label(rects, fmt=10)
+
+
+def test_bar_label_labels():
+ ax = plt.gca()
+ rects = ax.bar([1, 2], [3, -4])
+ labels = ax.bar_label(rects, labels=['A', 'B'])
+ assert labels[0].get_text() == 'A'
+ assert labels[1].get_text() == 'B'
+
+
+def test_bar_label_nan_ydata():
+ ax = plt.gca()
+ bars = ax.bar([2, 3], [np.nan, 1])
+ labels = ax.bar_label(bars)
+ assert [l.get_text() for l in labels] == ['', '1']
+ assert labels[0].xy == (2, 0)
+ assert labels[0].get_verticalalignment() == 'bottom'
+
+
+def test_bar_label_nan_ydata_inverted():
+ ax = plt.gca()
+ ax.yaxis_inverted()
+ bars = ax.bar([2, 3], [np.nan, 1])
+ labels = ax.bar_label(bars)
+ assert [l.get_text() for l in labels] == ['', '1']
+ assert labels[0].xy == (2, 0)
+ assert labels[0].get_verticalalignment() == 'bottom'
+
+
+def test_nan_barlabels():
+ fig, ax = plt.subplots()
+ bars = ax.bar([1, 2, 3], [np.nan, 1, 2], yerr=[0.2, 0.4, 0.6])
+ labels = ax.bar_label(bars)
+ assert [l.get_text() for l in labels] == ['', '1', '2']
+ assert np.allclose(ax.get_ylim(), (0.0, 3.0))
+
+ fig, ax = plt.subplots()
+ bars = ax.bar([1, 2, 3], [0, 1, 2], yerr=[0.2, np.nan, 0.6])
+ labels = ax.bar_label(bars)
+ assert [l.get_text() for l in labels] == ['0', '1', '2']
+ assert np.allclose(ax.get_ylim(), (-0.5, 3.0))
+
+ fig, ax = plt.subplots()
+ bars = ax.bar([1, 2, 3], [np.nan, 1, 2], yerr=[np.nan, np.nan, 0.6])
+ labels = ax.bar_label(bars)
+ assert [l.get_text() for l in labels] == ['', '1', '2']
+ assert np.allclose(ax.get_ylim(), (0.0, 3.0))
+
+
+def test_patch_bounds(): # PR 19078
+ fig, ax = plt.subplots()
+ ax.add_patch(mpatches.Wedge((0, -1), 1.05, 60, 120, width=0.1))
+ bot = 1.9*np.sin(15*np.pi/180)**2
+ np.testing.assert_array_almost_equal_nulp(
+ np.array((-0.525, -(bot+0.05), 1.05, bot+0.1)), ax.dataLim.bounds, 16)
+
+
+@mpl.style.context('default')
+def test_warn_ignored_scatter_kwargs():
+ with pytest.warns(UserWarning,
+ match=r"You passed a edgecolor/edgecolors"):
+ plt.scatter([0], [0], marker="+", s=500, facecolor="r", edgecolor="b")
+
+
+def test_artist_sublists():
+ fig, ax = plt.subplots()
+ lines = [ax.plot(np.arange(i, i + 5))[0] for i in range(6)]
+ col = ax.scatter(np.arange(5), np.arange(5))
+ im = ax.imshow(np.zeros((5, 5)))
+ patch = ax.add_patch(mpatches.Rectangle((0, 0), 5, 5))
+ text = ax.text(0, 0, 'foo')
+
+ # Get items, which should not be mixed.
+ assert list(ax.collections) == [col]
+ assert list(ax.images) == [im]
+ assert list(ax.lines) == lines
+ assert list(ax.patches) == [patch]
+ assert not ax.tables
+ assert list(ax.texts) == [text]
+
+ # Get items should work like lists/tuple.
+ assert ax.lines[0] is lines[0]
+ assert ax.lines[-1] is lines[-1]
+ with pytest.raises(IndexError, match='out of range'):
+ ax.lines[len(lines) + 1]
+
+ # Adding to other lists should produce a regular list.
+ assert ax.lines + [1, 2, 3] == [*lines, 1, 2, 3]
+ assert [1, 2, 3] + ax.lines == [1, 2, 3, *lines]
+
+ # Adding to other tuples should produce a regular tuples.
+ assert ax.lines + (1, 2, 3) == (*lines, 1, 2, 3)
+ assert (1, 2, 3) + ax.lines == (1, 2, 3, *lines)
+
+ # Lists should be empty after removing items.
+ col.remove()
+ assert not ax.collections
+ im.remove()
+ assert not ax.images
+ patch.remove()
+ assert not ax.patches
+ assert not ax.tables
+ text.remove()
+ assert not ax.texts
+
+ for ln in ax.lines:
+ ln.remove()
+ assert len(ax.lines) == 0
+
+
+def test_empty_line_plots():
+ # Incompatible nr columns, plot "nothing"
+ x = np.ones(10)
+ y = np.ones((10, 0))
+ _, ax = plt.subplots()
+ line = ax.plot(x, y)
+ assert len(line) == 0
+
+ # Ensure plot([],[]) creates line
+ _, ax = plt.subplots()
+ line = ax.plot([], [])
+ assert len(line) == 1
+
+
+@pytest.mark.parametrize('fmt, match', (
+ ("f", r"'f' is not a valid format string \(unrecognized character 'f'\)"),
+ ("o+", r"'o\+' is not a valid format string \(two marker symbols\)"),
+ (":-", r"':-' is not a valid format string \(two linestyle symbols\)"),
+ ("rk", r"'rk' is not a valid format string \(two color symbols\)"),
+ (":o-r", r"':o-r' is not a valid format string \(two linestyle symbols\)"),
+ ("C", r"'C' is not a valid format string \('C' must be followed by a number\)"),
+ (".C", r"'.C' is not a valid format string \('C' must be followed by a number\)"),
+))
+@pytest.mark.parametrize("data", [None, {"string": range(3)}])
+def test_plot_format_errors(fmt, match, data):
+ fig, ax = plt.subplots()
+ if data is not None:
+ match = match.replace("not", "neither a data key nor")
+ with pytest.raises(ValueError, match=r"\A" + match + r"\Z"):
+ ax.plot("string", fmt, data=data)
+
+
+def test_plot_format():
+ fig, ax = plt.subplots()
+ line = ax.plot([1, 2, 3], '1.0')
+ assert line[0].get_color() == (1.0, 1.0, 1.0, 1.0)
+ assert line[0].get_marker() == 'None'
+ fig, ax = plt.subplots()
+ line = ax.plot([1, 2, 3], '1')
+ assert line[0].get_marker() == '1'
+ fig, ax = plt.subplots()
+ line = ax.plot([1, 2], [1, 2], '1.0', "1")
+ fig.canvas.draw()
+ assert line[0].get_color() == (1.0, 1.0, 1.0, 1.0)
+ assert ax.get_yticklabels()[0].get_text() == '1'
+ fig, ax = plt.subplots()
+ line = ax.plot([1, 2], [1, 2], '1', "1.0")
+ fig.canvas.draw()
+ assert line[0].get_marker() == '1'
+ assert ax.get_yticklabels()[0].get_text() == '1.0'
+ fig, ax = plt.subplots()
+ line = ax.plot([1, 2, 3], 'k3')
+ assert line[0].get_marker() == '3'
+ assert line[0].get_color() == 'k'
+ fig, ax = plt.subplots()
+ line = ax.plot([1, 2, 3], '.C12:')
+ assert line[0].get_marker() == '.'
+ assert line[0].get_color() == mcolors.to_rgba('C12')
+ assert line[0].get_linestyle() == ':'
+
+
+def test_automatic_legend():
+ fig, ax = plt.subplots()
+ ax.plot("a", "b", data={"d": 2})
+ leg = ax.legend()
+ fig.canvas.draw()
+ assert leg.get_texts()[0].get_text() == 'a'
+ assert ax.get_yticklabels()[0].get_text() == 'a'
+
+ fig, ax = plt.subplots()
+ ax.plot("a", "b", "c", data={"d": 2})
+ leg = ax.legend()
+ fig.canvas.draw()
+ assert leg.get_texts()[0].get_text() == 'b'
+ assert ax.get_xticklabels()[0].get_text() == 'a'
+ assert ax.get_yticklabels()[0].get_text() == 'b'
+
+
+def test_plot_errors():
+ with pytest.raises(TypeError, match=r"plot\(\) got an unexpected keyword"):
+ plt.plot([1, 2, 3], x=1)
+ with pytest.raises(ValueError, match=r"plot\(\) with multiple groups"):
+ plt.plot([1, 2, 3], [1, 2, 3], [2, 3, 4], [2, 3, 4], label=['1', '2'])
+ with pytest.raises(ValueError, match="x and y must have same first"):
+ plt.plot([1, 2, 3], [1])
+ with pytest.raises(ValueError, match="x and y can be no greater than"):
+ plt.plot(np.ones((2, 2, 2)))
+ with pytest.raises(ValueError, match="Using arbitrary long args with"):
+ plt.plot("a", "b", "c", "d", data={"a": 2})
+
+
+def test_clim():
+ ax = plt.figure().add_subplot()
+ for plot_method in [
+ partial(ax.scatter, range(3), range(3), c=range(3)),
+ partial(ax.imshow, [[0, 1], [2, 3]]),
+ partial(ax.pcolor, [[0, 1], [2, 3]]),
+ partial(ax.pcolormesh, [[0, 1], [2, 3]]),
+ partial(ax.pcolorfast, [[0, 1], [2, 3]]),
+ ]:
+ clim = (7, 8)
+ norm = plot_method(clim=clim).norm
+ assert (norm.vmin, norm.vmax) == clim
+
+
+def test_bezier_autoscale():
+ # Check that bezier curves autoscale to their curves, and not their
+ # control points
+ verts = [[-1, 0],
+ [0, -1],
+ [1, 0],
+ [1, 0]]
+ codes = [mpath.Path.MOVETO,
+ mpath.Path.CURVE3,
+ mpath.Path.CURVE3,
+ mpath.Path.CLOSEPOLY]
+ p = mpath.Path(verts, codes)
+
+ fig, ax = plt.subplots()
+ ax.add_patch(mpatches.PathPatch(p))
+ ax.autoscale()
+ # Bottom ylim should be at the edge of the curve (-0.5), and not include
+ # the control point (at -1)
+ assert ax.get_ylim()[0] == -0.5
+
+
+def test_small_autoscale():
+ # Check that paths with small values autoscale correctly #24097.
+ verts = np.array([
+ [-5.45, 0.00], [-5.45, 0.00], [-5.29, 0.00], [-5.29, 0.00],
+ [-5.13, 0.00], [-5.13, 0.00], [-4.97, 0.00], [-4.97, 0.00],
+ [-4.81, 0.00], [-4.81, 0.00], [-4.65, 0.00], [-4.65, 0.00],
+ [-4.49, 0.00], [-4.49, 0.00], [-4.33, 0.00], [-4.33, 0.00],
+ [-4.17, 0.00], [-4.17, 0.00], [-4.01, 0.00], [-4.01, 0.00],
+ [-3.85, 0.00], [-3.85, 0.00], [-3.69, 0.00], [-3.69, 0.00],
+ [-3.53, 0.00], [-3.53, 0.00], [-3.37, 0.00], [-3.37, 0.00],
+ [-3.21, 0.00], [-3.21, 0.01], [-3.05, 0.01], [-3.05, 0.01],
+ [-2.89, 0.01], [-2.89, 0.01], [-2.73, 0.01], [-2.73, 0.02],
+ [-2.57, 0.02], [-2.57, 0.04], [-2.41, 0.04], [-2.41, 0.04],
+ [-2.25, 0.04], [-2.25, 0.06], [-2.09, 0.06], [-2.09, 0.08],
+ [-1.93, 0.08], [-1.93, 0.10], [-1.77, 0.10], [-1.77, 0.12],
+ [-1.61, 0.12], [-1.61, 0.14], [-1.45, 0.14], [-1.45, 0.17],
+ [-1.30, 0.17], [-1.30, 0.19], [-1.14, 0.19], [-1.14, 0.22],
+ [-0.98, 0.22], [-0.98, 0.25], [-0.82, 0.25], [-0.82, 0.27],
+ [-0.66, 0.27], [-0.66, 0.29], [-0.50, 0.29], [-0.50, 0.30],
+ [-0.34, 0.30], [-0.34, 0.32], [-0.18, 0.32], [-0.18, 0.33],
+ [-0.02, 0.33], [-0.02, 0.32], [0.13, 0.32], [0.13, 0.33], [0.29, 0.33],
+ [0.29, 0.31], [0.45, 0.31], [0.45, 0.30], [0.61, 0.30], [0.61, 0.28],
+ [0.77, 0.28], [0.77, 0.25], [0.93, 0.25], [0.93, 0.22], [1.09, 0.22],
+ [1.09, 0.19], [1.25, 0.19], [1.25, 0.17], [1.41, 0.17], [1.41, 0.15],
+ [1.57, 0.15], [1.57, 0.12], [1.73, 0.12], [1.73, 0.10], [1.89, 0.10],
+ [1.89, 0.08], [2.05, 0.08], [2.05, 0.07], [2.21, 0.07], [2.21, 0.05],
+ [2.37, 0.05], [2.37, 0.04], [2.53, 0.04], [2.53, 0.02], [2.69, 0.02],
+ [2.69, 0.02], [2.85, 0.02], [2.85, 0.01], [3.01, 0.01], [3.01, 0.01],
+ [3.17, 0.01], [3.17, 0.00], [3.33, 0.00], [3.33, 0.00], [3.49, 0.00],
+ [3.49, 0.00], [3.65, 0.00], [3.65, 0.00], [3.81, 0.00], [3.81, 0.00],
+ [3.97, 0.00], [3.97, 0.00], [4.13, 0.00], [4.13, 0.00], [4.29, 0.00],
+ [4.29, 0.00], [4.45, 0.00], [4.45, 0.00], [4.61, 0.00], [4.61, 0.00],
+ [4.77, 0.00], [4.77, 0.00], [4.93, 0.00], [4.93, 0.00],
+ ])
+
+ minx = np.min(verts[:, 0])
+ miny = np.min(verts[:, 1])
+ maxx = np.max(verts[:, 0])
+ maxy = np.max(verts[:, 1])
+
+ p = mpath.Path(verts)
+
+ fig, ax = plt.subplots()
+ ax.add_patch(mpatches.PathPatch(p))
+ ax.autoscale()
+
+ assert ax.get_xlim()[0] <= minx
+ assert ax.get_xlim()[1] >= maxx
+ assert ax.get_ylim()[0] <= miny
+ assert ax.get_ylim()[1] >= maxy
+
+
+def test_get_xticklabel():
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10))
+ for ind in range(10):
+ assert ax.get_xticklabels()[ind].get_text() == f'{ind}'
+ assert ax.get_yticklabels()[ind].get_text() == f'{ind}'
+
+
+def test_bar_leading_nan():
+
+ barx = np.arange(3, dtype=float)
+ barheights = np.array([0.5, 1.5, 2.0])
+ barstarts = np.array([0.77]*3)
+
+ barx[0] = np.nan
+
+ fig, ax = plt.subplots()
+
+ bars = ax.bar(barx, barheights, bottom=barstarts)
+
+ hbars = ax.barh(barx, barheights, left=barstarts)
+
+ for bar_set in (bars, hbars):
+ # the first bar should have a nan in the location
+ nanful, *rest = bar_set
+ assert (~np.isfinite(nanful.xy)).any()
+ assert np.isfinite(nanful.get_width())
+ for b in rest:
+ assert np.isfinite(b.xy).all()
+ assert np.isfinite(b.get_width())
+
+
+@check_figures_equal(extensions=["png"])
+def test_bar_all_nan(fig_test, fig_ref):
+ mpl.style.use("mpl20")
+ ax_test = fig_test.subplots()
+ ax_ref = fig_ref.subplots()
+
+ ax_test.bar([np.nan], [np.nan])
+ ax_test.bar([1], [1])
+
+ ax_ref.bar([1], [1]).remove()
+ ax_ref.bar([1], [1])
+
+
+@image_comparison(["extent_units.png"], style="mpl20")
+def test_extent_units():
+ _, axs = plt.subplots(2, 2)
+ date_first = np.datetime64('2020-01-01', 'D')
+ date_last = np.datetime64('2020-01-11', 'D')
+ arr = [[i+j for i in range(10)] for j in range(10)]
+
+ axs[0, 0].set_title('Date extents on y axis')
+ im = axs[0, 0].imshow(arr, origin='lower',
+ extent=[1, 11, date_first, date_last],
+ cmap=mpl.colormaps["plasma"])
+
+ axs[0, 1].set_title('Date extents on x axis (Day of Jan 2020)')
+ im = axs[0, 1].imshow(arr, origin='lower',
+ extent=[date_first, date_last, 1, 11],
+ cmap=mpl.colormaps["plasma"])
+ axs[0, 1].xaxis.set_major_formatter(mdates.DateFormatter('%d'))
+
+ im = axs[1, 0].imshow(arr, origin='lower',
+ extent=[date_first, date_last,
+ date_first, date_last],
+ cmap=mpl.colormaps["plasma"])
+ axs[1, 0].xaxis.set_major_formatter(mdates.DateFormatter('%d'))
+ axs[1, 0].set(xlabel='Day of Jan 2020')
+
+ im = axs[1, 1].imshow(arr, origin='lower',
+ cmap=mpl.colormaps["plasma"])
+ im.set_extent([date_last, date_first, date_last, date_first])
+ axs[1, 1].xaxis.set_major_formatter(mdates.DateFormatter('%d'))
+ axs[1, 1].set(xlabel='Day of Jan 2020')
+
+ with pytest.raises(TypeError, match=r"set_extent\(\) got an unexpected"):
+ im.set_extent([2, 12, date_first, date_last], clip=False)
+
+
+def test_cla_clears_children_axes_and_fig():
+ fig, ax = plt.subplots()
+ lines = ax.plot([], [], [], [])
+ img = ax.imshow([[1]])
+ for art in lines + [img]:
+ assert art.axes is ax
+ assert art.get_figure() is fig
+ ax.clear()
+ for art in lines + [img]:
+ assert art.axes is None
+ assert art.get_figure() is None
+
+
+def test_child_axes_removal():
+ fig, ax = plt.subplots()
+ marginal = ax.inset_axes([1, 0, .1, 1], sharey=ax)
+ marginal_twin = marginal.twinx()
+ marginal.remove()
+ ax.set(xlim=(-1, 1), ylim=(10, 20))
+
+
+def test_scatter_color_repr_error():
+
+ def get_next_color(): # pragma: no cover
+ return 'blue' # currently unused
+ msg = (
+ r"'c' argument must be a color, a sequence of colors"
+ r", or a sequence of numbers, not 'red\\n'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ c = 'red\n'
+ mpl.axes.Axes._parse_scatter_color_args(
+ c, None, kwargs={}, xsize=2, get_next_color_func=get_next_color)
+
+
+def test_zorder_and_explicit_rasterization():
+ fig, ax = plt.subplots()
+ ax.set_rasterization_zorder(5)
+ ln, = ax.plot(range(5), rasterized=True, zorder=1)
+ with io.BytesIO() as b:
+ fig.savefig(b, format='pdf')
+
+
+@image_comparison(["preset_clip_paths.png"], remove_text=True, style="mpl20",
+ tol=0 if platform.machine() == 'x86_64' else 0.027)
+def test_preset_clip_paths():
+ fig, ax = plt.subplots()
+
+ poly = mpl.patches.Polygon(
+ [[1, 0], [0, 1], [-1, 0], [0, -1]], facecolor="#ddffdd",
+ edgecolor="#00ff00", linewidth=2, alpha=0.5)
+
+ ax.add_patch(poly)
+
+ line = mpl.lines.Line2D((-1, 1), (0.5, 0.5), clip_on=True, clip_path=poly)
+ line.set_path_effects([patheffects.withTickedStroke()])
+ ax.add_artist(line)
+
+ line = mpl.lines.Line2D((-1, 1), (-0.5, -0.5), color='r', clip_on=True,
+ clip_path=poly)
+ ax.add_artist(line)
+
+ poly2 = mpl.patches.Polygon(
+ [[-1, 1], [0, 1], [0, -0.25]], facecolor="#beefc0", alpha=0.3,
+ edgecolor="#faded0", linewidth=2, clip_on=True, clip_path=poly)
+ ax.add_artist(poly2)
+
+ # When text clipping works, the "Annotation" text should be clipped
+ ax.annotate('Annotation', (-0.75, -0.75), xytext=(0.1, 0.75),
+ arrowprops={'color': 'k'}, clip_on=True, clip_path=poly)
+
+ poly3 = mpl.patches.Polygon(
+ [[0, 0], [0, 0.5], [0.5, 0.5], [0.5, 0]], facecolor="g", edgecolor="y",
+ linewidth=2, alpha=0.3, clip_on=True, clip_path=poly)
+
+ fig.add_artist(poly3, clip=True)
+
+ ax.set_xlim(-1, 1)
+ ax.set_ylim(-1, 1)
+
+
+@mpl.style.context('default')
+def test_rc_axes_label_formatting():
+ mpl.rcParams['axes.labelcolor'] = 'red'
+ mpl.rcParams['axes.labelsize'] = 20
+ mpl.rcParams['axes.labelweight'] = 'bold'
+
+ ax = plt.axes()
+ assert ax.xaxis.label.get_color() == 'red'
+ assert ax.xaxis.label.get_fontsize() == 20
+ assert ax.xaxis.label.get_fontweight() == 'bold'
+
+
+@check_figures_equal(extensions=["png"])
+def test_ecdf(fig_test, fig_ref):
+ data = np.array([0, -np.inf, -np.inf, np.inf, 1, 1, 2])
+ weights = range(len(data))
+ axs_test = fig_test.subplots(1, 2)
+ for ax, orientation in zip(axs_test, ["vertical", "horizontal"]):
+ l0 = ax.ecdf(data, orientation=orientation)
+ l1 = ax.ecdf("d", "w", data={"d": np.ma.array(data), "w": weights},
+ orientation=orientation,
+ complementary=True, compress=True, ls=":")
+ assert len(l0.get_xdata()) == (~np.isnan(data)).sum() + 1
+ assert len(l1.get_xdata()) == len({*data[~np.isnan(data)]}) + 1
+ axs_ref = fig_ref.subplots(1, 2)
+ axs_ref[0].plot([-np.inf, -np.inf, -np.inf, 0, 1, 1, 2, np.inf],
+ np.arange(8) / 7, ds="steps-post")
+ axs_ref[0].plot([-np.inf, 0, 1, 2, np.inf, np.inf],
+ np.array([21, 20, 18, 14, 3, 0]) / 21,
+ ds="steps-pre", ls=":")
+ axs_ref[1].plot(np.arange(8) / 7,
+ [-np.inf, -np.inf, -np.inf, 0, 1, 1, 2, np.inf],
+ ds="steps-pre")
+ axs_ref[1].plot(np.array([21, 20, 18, 14, 3, 0]) / 21,
+ [-np.inf, 0, 1, 2, np.inf, np.inf],
+ ds="steps-post", ls=":")
+
+
+def test_ecdf_invalid():
+ with pytest.raises(ValueError):
+ plt.ecdf([1, np.nan])
+ with pytest.raises(ValueError):
+ plt.ecdf(np.ma.array([1, 2], mask=[True, False]))
+
+
+def test_fill_between_axes_limits():
+ fig, ax = plt.subplots()
+ x = np.arange(0, 4 * np.pi, 0.01)
+ y = 0.1*np.sin(x)
+ threshold = 0.075
+ ax.plot(x, y, color='black')
+
+ original_lims = (ax.get_xlim(), ax.get_ylim())
+
+ ax.axhline(threshold, color='green', lw=2, alpha=0.7)
+ ax.fill_between(x, 0, 1, where=y > threshold,
+ color='green', alpha=0.5, transform=ax.get_xaxis_transform())
+
+ assert (ax.get_xlim(), ax.get_ylim()) == original_lims
+
+
+def test_tick_param_labelfont():
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3, 4], [1, 2, 3, 4])
+ ax.set_xlabel('X label in Impact font', fontname='Impact')
+ ax.set_ylabel('Y label in xkcd script', fontname='xkcd script')
+ ax.tick_params(color='r', labelfontfamily='monospace')
+ plt.title('Title in sans-serif')
+ for text in ax.get_xticklabels():
+ assert text.get_fontfamily()[0] == 'monospace'
+
+
+def test_set_secondary_axis_color():
+ fig, ax = plt.subplots()
+ sax = ax.secondary_xaxis("top", color="red")
+ assert mcolors.same_color(sax.spines["bottom"].get_edgecolor(), "red")
+ assert mcolors.same_color(sax.spines["top"].get_edgecolor(), "red")
+ assert mcolors.same_color(sax.xaxis.get_tick_params()["color"], "red")
+ assert mcolors.same_color(sax.xaxis.get_tick_params()["labelcolor"], "red")
+ assert mcolors.same_color(sax.xaxis.label.get_color(), "red")
+
+
+def test_xylim_changed_shared():
+ fig, axs = plt.subplots(2, sharex=True, sharey=True)
+ events = []
+ axs[1].callbacks.connect("xlim_changed", events.append)
+ axs[1].callbacks.connect("ylim_changed", events.append)
+ axs[0].set(xlim=[1, 3], ylim=[2, 4])
+ assert events == [axs[1], axs[1]]
+
+
+@image_comparison(["axhvlinespan_interpolation.png"], style="default")
+def test_axhvlinespan_interpolation():
+ ax = plt.figure().add_subplot(projection="polar")
+ ax.set_axis_off()
+ ax.axvline(.1, c="C0")
+ ax.axvspan(.2, .3, fc="C1")
+ ax.axvspan(.4, .5, .1, .2, fc="C2")
+ ax.axhline(1, c="C0", alpha=.5)
+ ax.axhspan(.8, .9, fc="C1", alpha=.5)
+ ax.axhspan(.6, .7, .8, .9, fc="C2", alpha=.5)
+
+
+@check_figures_equal(extensions=["png"])
+@pytest.mark.parametrize("which", ("x", "y"))
+def test_axes_clear_behavior(fig_ref, fig_test, which):
+ """Test that the given tick params are not reset by ax.clear()."""
+ ax_test = fig_test.subplots()
+ ax_ref = fig_ref.subplots()
+ # the following tick params values are chosen to each create a visual difference
+ # from their defaults
+ target = {
+ "direction": "in",
+ "length": 10,
+ "width": 10,
+ "color": "xkcd:wine red",
+ "pad": 0,
+ "labelfontfamily": "serif",
+ "zorder": 7,
+ "labelrotation": 45,
+ "labelcolor": "xkcd:shocking pink",
+ # this overrides color + labelcolor, skip
+ # colors: ,
+ "grid_color": "xkcd:fluorescent green",
+ "grid_alpha": 0.5,
+ "grid_linewidth": 3,
+ "grid_linestyle": ":",
+ "bottom": False,
+ "top": True,
+ "left": False,
+ "right": True,
+ "labelbottom": True,
+ "labeltop": True,
+ "labelleft": True,
+ "labelright": True,
+ }
+
+ ax_ref.tick_params(axis=which, **target)
+
+ ax_test.tick_params(axis=which, **target)
+ ax_test.clear()
+
+ ax_ref.grid(True)
+ ax_test.grid(True)
+
+
+@pytest.mark.skipif(
+ sys.version_info[:3] == (3, 13, 0) and sys.version_info.releaselevel != "final",
+ reason="https://github.com/python/cpython/issues/124538",
+)
+def test_axes_clear_reference_cycle():
+ def assert_not_in_reference_cycle(start):
+ # Breadth first search. Return True if we encounter the starting node
+ to_visit = deque([start])
+ explored = set()
+ while len(to_visit) > 0:
+ parent = to_visit.popleft()
+ for child in gc.get_referents(parent):
+ if id(child) in explored:
+ continue
+ assert child is not start
+ explored.add(id(child))
+ to_visit.append(child)
+
+ fig = Figure()
+ ax = fig.add_subplot()
+ points = np.random.rand(1000)
+ ax.plot(points, points)
+ ax.scatter(points, points)
+ ax_children = ax.get_children()
+ fig.clear() # This should break the reference cycle
+
+ # Care most about the objects that scale with number of points
+ big_artists = [
+ a for a in ax_children
+ if isinstance(a, (Line2D, PathCollection))
+ ]
+ assert len(big_artists) > 0
+ for big_artist in big_artists:
+ assert_not_in_reference_cycle(big_artist)
+ assert len(ax_children) > 0
+ for child in ax_children:
+ # Make sure this doesn't raise because the child is already removed.
+ try:
+ child.remove()
+ except NotImplementedError:
+ pass # not implemented is expected for some artists
+
+
+def test_boxplot_tick_labels():
+ # Test the renamed `tick_labels` parameter.
+ # Test for deprecation of old name `labels`.
+ np.random.seed(19680801)
+ data = np.random.random((10, 3))
+
+ fig, axs = plt.subplots(nrows=1, ncols=2, sharey=True)
+ # Should get deprecation warning for `labels`
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match='has been renamed \'tick_labels\''):
+ axs[0].boxplot(data, labels=['A', 'B', 'C'])
+ assert [l.get_text() for l in axs[0].get_xticklabels()] == ['A', 'B', 'C']
+
+ # Test the new tick_labels parameter
+ axs[1].boxplot(data, tick_labels=['A', 'B', 'C'])
+ assert [l.get_text() for l in axs[1].get_xticklabels()] == ['A', 'B', 'C']
+
+
+@needs_usetex
+@check_figures_equal(extensions=['png'])
+def test_latex_pie_percent(fig_test, fig_ref):
+
+ data = [20, 10, 70]
+
+ ax = fig_test.subplots()
+ ax.pie(data, autopct="%1.0f%%", textprops={'usetex': True})
+
+ ax1 = fig_ref.subplots()
+ ax1.pie(data, autopct=r"%1.0f\%%", textprops={'usetex': True})
+
+
+@check_figures_equal(extensions=['png'])
+def test_violinplot_orientation(fig_test, fig_ref):
+ # Test the `orientation : {'vertical', 'horizontal'}`
+ # parameter and deprecation of `vert: bool`.
+ fig, axs = plt.subplots(nrows=1, ncols=3)
+ np.random.seed(19680801)
+ all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
+
+ axs[0].violinplot(all_data) # Default vertical plot.
+ # xticks and yticks should be at their default position.
+ assert all(axs[0].get_xticks() == np.array(
+ [0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5]))
+ assert all(axs[0].get_yticks() == np.array(
+ [-30., -20., -10., 0., 10., 20., 30.]))
+
+ # Horizontal plot using new `orientation` keyword.
+ axs[1].violinplot(all_data, orientation='horizontal')
+ # xticks and yticks should be swapped.
+ assert all(axs[1].get_xticks() == np.array(
+ [-30., -20., -10., 0., 10., 20., 30.]))
+ assert all(axs[1].get_yticks() == np.array(
+ [0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5]))
+
+ plt.close()
+
+ # Compare images between a figure that
+ # uses vert and one that uses orientation.
+ ax_ref = fig_ref.subplots()
+
+ with pytest.warns(PendingDeprecationWarning, match='vert: bool'):
+ ax_ref.violinplot(all_data, vert=False)
+
+ ax_test = fig_test.subplots()
+ ax_test.violinplot(all_data, orientation='horizontal')
+
+
+@check_figures_equal(extensions=['png'])
+def test_boxplot_orientation(fig_test, fig_ref):
+ # Test the `orientation : {'vertical', 'horizontal'}`
+ # parameter and deprecation of `vert: bool`.
+ fig, axs = plt.subplots(nrows=1, ncols=2)
+ np.random.seed(19680801)
+ all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
+
+ axs[0].boxplot(all_data) # Default vertical plot.
+ # xticks and yticks should be at their default position.
+ assert all(axs[0].get_xticks() == np.array(
+ [1, 2, 3, 4]))
+ assert all(axs[0].get_yticks() == np.array(
+ [-30., -20., -10., 0., 10., 20., 30.]))
+
+ # Horizontal plot using new `orientation` keyword.
+ axs[1].boxplot(all_data, orientation='horizontal')
+ # xticks and yticks should be swapped.
+ assert all(axs[1].get_xticks() == np.array(
+ [-30., -20., -10., 0., 10., 20., 30.]))
+ assert all(axs[1].get_yticks() == np.array(
+ [1, 2, 3, 4]))
+
+ plt.close()
+
+ # Deprecation of `vert: bool` keyword and
+ # 'boxplot.vertical' rcparam.
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match='was deprecated in Matplotlib 3.10'):
+ # Compare images between a figure that
+ # uses vert and one that uses orientation.
+ with mpl.rc_context({'boxplot.vertical': False}):
+ ax_ref = fig_ref.subplots()
+ ax_ref.boxplot(all_data)
+
+ ax_test = fig_test.subplots()
+ ax_test.boxplot(all_data, orientation='horizontal')
+
+
+@image_comparison(["use_colorizer_keyword.png"],
+ tol=0 if platform.machine() == 'x86_64' else 0.05)
+def test_use_colorizer_keyword():
+ # test using the colorizer keyword
+ np.random.seed(0)
+ rand_x = np.random.random(100)
+ rand_y = np.random.random(100)
+ c = np.arange(25, dtype='float32').reshape((5, 5))
+
+ fig, axes = plt.subplots(3, 4)
+ norm = mpl.colors.Normalize(4, 20)
+ cl = mpl.colorizer.Colorizer(norm=norm, cmap='RdBu')
+
+ axes[0, 0].scatter(c, c, c=c, colorizer=cl)
+ axes[0, 1].hexbin(rand_x, rand_y, colorizer=cl, gridsize=(2, 2))
+ axes[0, 2].imshow(c, colorizer=cl)
+ axes[0, 3].pcolor(c, colorizer=cl)
+ axes[1, 0].pcolormesh(c, colorizer=cl)
+ axes[1, 1].pcolorfast(c, colorizer=cl) # style = image
+ axes[1, 2].pcolorfast((0, 1, 2, 3, 4, 5), (0, 1, 2, 3, 5, 6), c,
+ colorizer=cl) # style = pcolorimage
+ axes[1, 3].pcolorfast(c.T, c, c[:4, :4], colorizer=cl) # style = quadmesh
+ axes[2, 0].contour(c, colorizer=cl)
+ axes[2, 1].contourf(c, colorizer=cl)
+ axes[2, 2].tricontour(c.T.ravel(), c.ravel(), c.ravel(), colorizer=cl)
+ axes[2, 3].tricontourf(c.T.ravel(), c.ravel(), c.ravel(), colorizer=cl)
+
+ fig.figimage(np.repeat(np.repeat(c, 15, axis=0), 15, axis=1), colorizer=cl)
+ remove_ticks_and_titles(fig)
+
+
+def test_wrong_use_colorizer():
+ # test using the colorizer keyword and norm or cmap
+ np.random.seed(0)
+ rand_x = np.random.random(100)
+ rand_y = np.random.random(100)
+ c = np.arange(25, dtype='float32').reshape((5, 5))
+
+ fig, axes = plt.subplots(3, 4)
+ norm = mpl.colors.Normalize(4, 20)
+ cl = mpl.colorizer.Colorizer(norm=norm, cmap='RdBu')
+
+ match_str = "The `colorizer` keyword cannot be used simultaneously"
+ kwrds = [{'vmin': 0}, {'vmax': 0}, {'norm': 'log'}, {'cmap': 'viridis'}]
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[0, 0].scatter(c, c, c=c, colorizer=cl, **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[0, 0].scatter(c, c, c=c, colorizer=cl, **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[0, 1].hexbin(rand_x, rand_y, colorizer=cl, gridsize=(2, 2), **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[0, 2].imshow(c, colorizer=cl, **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[0, 3].pcolor(c, colorizer=cl, **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[1, 0].pcolormesh(c, colorizer=cl, **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[1, 1].pcolorfast(c, colorizer=cl, **kwrd) # style = image
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[1, 2].pcolorfast((0, 1, 2, 3, 4, 5), (0, 1, 2, 3, 5, 6), c,
+ colorizer=cl, **kwrd) # style = pcolorimage
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[1, 3].pcolorfast(c.T, c, c[:4, :4], colorizer=cl, **kwrd) # quadmesh
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[2, 0].contour(c, colorizer=cl, **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[2, 1].contourf(c, colorizer=cl, **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[2, 2].tricontour(c.T.ravel(), c.ravel(), c.ravel(), colorizer=cl,
+ **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ axes[2, 3].tricontourf(c.T.ravel(), c.ravel(), c.ravel(), colorizer=cl,
+ **kwrd)
+ for kwrd in kwrds:
+ with pytest.raises(ValueError, match=match_str):
+ fig.figimage(c, colorizer=cl, **kwrd)
+
+
+def test_bar_color_precedence():
+ # Test the precedence of 'color' and 'facecolor' in bar plots
+ fig, ax = plt.subplots()
+
+ # case 1: no color specified
+ bars = ax.bar([1, 2, 3], [4, 5, 6])
+ for bar in bars:
+ assert mcolors.same_color(bar.get_facecolor(), 'blue')
+
+ # case 2: Only 'color'
+ bars = ax.bar([11, 12, 13], [4, 5, 6], color='red')
+ for bar in bars:
+ assert mcolors.same_color(bar.get_facecolor(), 'red')
+
+ # case 3: Only 'facecolor'
+ bars = ax.bar([21, 22, 23], [4, 5, 6], facecolor='yellow')
+ for bar in bars:
+ assert mcolors.same_color(bar.get_facecolor(), 'yellow')
+
+ # case 4: 'facecolor' and 'color'
+ bars = ax.bar([31, 32, 33], [4, 5, 6], color='red', facecolor='green')
+ for bar in bars:
+ assert mcolors.same_color(bar.get_facecolor(), 'green')
+
+
+@check_figures_equal(extensions=['png'])
+def test_axes_set_position_external_bbox_unchanged(fig_test, fig_ref):
+ # From #29410: Modifying Axes' position also alters the original Bbox
+ # object used for initialization
+ bbox = mtransforms.Bbox([[0.0, 0.0], [1.0, 1.0]])
+ ax_test = fig_test.add_axes(bbox)
+ ax_test.set_position([0.25, 0.25, 0.5, 0.5])
+ assert (bbox.x0, bbox.y0, bbox.width, bbox.height) == (0.0, 0.0, 1.0, 1.0)
+ ax_ref = fig_ref.add_axes([0.25, 0.25, 0.5, 0.5])
+
+
+def test_caps_color():
+ # Creates a simple plot with error bars and a specified ecolor
+ x = np.linspace(0, 10, 10)
+ mpl.rcParams['lines.markeredgecolor'] = 'green'
+ ecolor = 'red'
+
+ fig, ax = plt.subplots()
+ errorbars = ax.errorbar(x, np.sin(x), yerr=0.1, ecolor=ecolor)
+
+ # Tests if the caps have the specified color
+ for cap in errorbars[2]:
+ assert mcolors.same_color(cap.get_edgecolor(), ecolor)
+
+
+def test_caps_no_ecolor():
+ # Creates a simple plot with error bars without specifying ecolor
+ x = np.linspace(0, 10, 10)
+ mpl.rcParams['lines.markeredgecolor'] = 'green'
+ fig, ax = plt.subplots()
+ errorbars = ax.errorbar(x, np.sin(x), yerr=0.1)
+
+ # Tests if the caps have the default color (blue)
+ for cap in errorbars[2]:
+ assert mcolors.same_color(cap.get_edgecolor(), "blue")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_axis.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_axis.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1b09006607da83cb98ac9b51420fc5a929dab78
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_axis.py
@@ -0,0 +1,40 @@
+import numpy as np
+
+import matplotlib.pyplot as plt
+from matplotlib.axis import XTick
+
+
+def test_tick_labelcolor_array():
+ # Smoke test that we can instantiate a Tick with labelcolor as array.
+ ax = plt.axes()
+ XTick(ax, 0, labelcolor=np.array([1, 0, 0, 1]))
+
+
+def test_axis_not_in_layout():
+ fig1, (ax1_left, ax1_right) = plt.subplots(ncols=2, layout='constrained')
+ fig2, (ax2_left, ax2_right) = plt.subplots(ncols=2, layout='constrained')
+
+ # 100 label overlapping the end of the axis
+ ax1_left.set_xlim([0, 100])
+ # 100 label not overlapping the end of the axis
+ ax2_left.set_xlim([0, 120])
+
+ for ax in ax1_left, ax2_left:
+ ax.set_xticks([0, 100])
+ ax.xaxis.set_in_layout(False)
+
+ for fig in fig1, fig2:
+ fig.draw_without_rendering()
+
+ # Positions should not be affected by overlapping 100 label
+ assert ax1_left.get_position().bounds == ax2_left.get_position().bounds
+ assert ax1_right.get_position().bounds == ax2_right.get_position().bounds
+
+
+def test_translate_tick_params_reverse():
+ fig, ax = plt.subplots()
+ kw = {'label1On': 'a', 'label2On': 'b', 'tick1On': 'c', 'tick2On': 'd'}
+ assert (ax.xaxis._translate_tick_params(kw, reverse=True) ==
+ {'labelbottom': 'a', 'labeltop': 'b', 'bottom': 'c', 'top': 'd'})
+ assert (ax.yaxis._translate_tick_params(kw, reverse=True) ==
+ {'labelleft': 'a', 'labelright': 'b', 'left': 'c', 'right': 'd'})
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_bases.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_bases.py
new file mode 100644
index 0000000000000000000000000000000000000000..10108d11bd0a1081dd6abb71c0522d38bca36218
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_bases.py
@@ -0,0 +1,586 @@
+import importlib
+
+from matplotlib import path, transforms
+from matplotlib.backend_bases import (
+ FigureCanvasBase, KeyEvent, LocationEvent, MouseButton, MouseEvent,
+ NavigationToolbar2, RendererBase)
+from matplotlib.backend_tools import RubberbandBase
+from matplotlib.figure import Figure
+from matplotlib.testing._markers import needs_pgf_xelatex
+import matplotlib.pyplot as plt
+
+import numpy as np
+import pytest
+
+
+_EXPECTED_WARNING_TOOLMANAGER = (
+ r"Treat the new Tool classes introduced in "
+ r"v[0-9]*.[0-9]* as experimental for now; "
+ "the API and rcParam may change in future versions.")
+
+
+def test_uses_per_path():
+ id = transforms.Affine2D()
+ paths = [path.Path.unit_regular_polygon(i) for i in range(3, 7)]
+ tforms_matrices = [id.rotate(i).get_matrix().copy() for i in range(1, 5)]
+ offsets = np.arange(20).reshape((10, 2))
+ facecolors = ['red', 'green']
+ edgecolors = ['red', 'green']
+
+ def check(master_transform, paths, all_transforms,
+ offsets, facecolors, edgecolors):
+ rb = RendererBase()
+ raw_paths = list(rb._iter_collection_raw_paths(
+ master_transform, paths, all_transforms))
+ gc = rb.new_gc()
+ ids = [path_id for xo, yo, path_id, gc0, rgbFace in
+ rb._iter_collection(
+ gc, range(len(raw_paths)), offsets,
+ transforms.AffineDeltaTransform(master_transform),
+ facecolors, edgecolors, [], [], [False],
+ [], 'screen')]
+ uses = rb._iter_collection_uses_per_path(
+ paths, all_transforms, offsets, facecolors, edgecolors)
+ if raw_paths:
+ seen = np.bincount(ids, minlength=len(raw_paths))
+ assert set(seen).issubset([uses - 1, uses])
+
+ check(id, paths, tforms_matrices, offsets, facecolors, edgecolors)
+ check(id, paths[0:1], tforms_matrices, offsets, facecolors, edgecolors)
+ check(id, [], tforms_matrices, offsets, facecolors, edgecolors)
+ check(id, paths, tforms_matrices[0:1], offsets, facecolors, edgecolors)
+ check(id, paths, [], offsets, facecolors, edgecolors)
+ for n in range(0, offsets.shape[0]):
+ check(id, paths, tforms_matrices, offsets[0:n, :],
+ facecolors, edgecolors)
+ check(id, paths, tforms_matrices, offsets, [], edgecolors)
+ check(id, paths, tforms_matrices, offsets, facecolors, [])
+ check(id, paths, tforms_matrices, offsets, [], [])
+ check(id, paths, tforms_matrices, offsets, facecolors[0:1], edgecolors)
+
+
+def test_canvas_ctor():
+ assert isinstance(FigureCanvasBase().figure, Figure)
+
+
+def test_get_default_filename():
+ fig = plt.figure()
+ assert fig.canvas.get_default_filename() == "Figure_1.png"
+ fig.canvas.manager.set_window_title("0:1/2<3")
+ assert fig.canvas.get_default_filename() == "0_1_2_3.png"
+
+
+def test_canvas_change():
+ fig = plt.figure()
+ # Replaces fig.canvas
+ canvas = FigureCanvasBase(fig)
+ # Should still work.
+ plt.close(fig)
+ assert not plt.fignum_exists(fig.number)
+
+
+@pytest.mark.backend('pdf')
+def test_non_gui_warning(monkeypatch):
+ plt.subplots()
+
+ monkeypatch.setenv("DISPLAY", ":999")
+
+ with pytest.warns(UserWarning) as rec:
+ plt.show()
+ assert len(rec) == 1
+ assert ('FigureCanvasPdf is non-interactive, and thus cannot be shown'
+ in str(rec[0].message))
+
+ with pytest.warns(UserWarning) as rec:
+ plt.gcf().show()
+ assert len(rec) == 1
+ assert ('FigureCanvasPdf is non-interactive, and thus cannot be shown'
+ in str(rec[0].message))
+
+
+def test_grab_clear():
+ fig, ax = plt.subplots()
+
+ fig.canvas.grab_mouse(ax)
+ assert fig.canvas.mouse_grabber == ax
+
+ fig.clear()
+ assert fig.canvas.mouse_grabber is None
+
+
+@pytest.mark.parametrize(
+ "x, y", [(42, 24), (None, 42), (None, None), (200, 100.01), (205.75, 2.0)])
+def test_location_event_position(x, y):
+ # LocationEvent should cast its x and y arguments to int unless it is None.
+ fig, ax = plt.subplots()
+ canvas = FigureCanvasBase(fig)
+ event = LocationEvent("test_event", canvas, x, y)
+ if x is None:
+ assert event.x is None
+ else:
+ assert event.x == int(x)
+ assert isinstance(event.x, int)
+ if y is None:
+ assert event.y is None
+ else:
+ assert event.y == int(y)
+ assert isinstance(event.y, int)
+ if x is not None and y is not None:
+ assert (ax.format_coord(x, y)
+ == f"(x, y) = ({ax.format_xdata(x)}, {ax.format_ydata(y)})")
+ ax.fmt_xdata = ax.fmt_ydata = lambda x: "foo"
+ assert ax.format_coord(x, y) == "(x, y) = (foo, foo)"
+
+
+def test_location_event_position_twin():
+ fig, ax = plt.subplots()
+ ax.set(xlim=(0, 10), ylim=(0, 20))
+ assert ax.format_coord(5., 5.) == "(x, y) = (5.00, 5.00)"
+ ax.twinx().set(ylim=(0, 40))
+ assert ax.format_coord(5., 5.) == "(x, y) = (5.00, 5.00) | (5.00, 10.0)"
+ ax.twiny().set(xlim=(0, 5))
+ assert (ax.format_coord(5., 5.)
+ == "(x, y) = (5.00, 5.00) | (5.00, 10.0) | (2.50, 5.00)")
+
+
+def test_pick():
+ fig = plt.figure()
+ fig.text(.5, .5, "hello", ha="center", va="center", picker=True)
+ fig.canvas.draw()
+
+ picks = []
+ def handle_pick(event):
+ assert event.mouseevent.key == "a"
+ picks.append(event)
+ fig.canvas.mpl_connect("pick_event", handle_pick)
+
+ KeyEvent("key_press_event", fig.canvas, "a")._process()
+ MouseEvent("button_press_event", fig.canvas,
+ *fig.transFigure.transform((.5, .5)),
+ MouseButton.LEFT)._process()
+ KeyEvent("key_release_event", fig.canvas, "a")._process()
+ assert len(picks) == 1
+
+
+def test_interactive_zoom():
+ fig, ax = plt.subplots()
+ ax.set(xscale="logit")
+ assert ax.get_navigate_mode() is None
+
+ tb = NavigationToolbar2(fig.canvas)
+ tb.zoom()
+ assert ax.get_navigate_mode() == 'ZOOM'
+
+ xlim0 = ax.get_xlim()
+ ylim0 = ax.get_ylim()
+
+ # Zoom from x=1e-6, y=0.1 to x=1-1e-5, 0.8 (data coordinates, "d").
+ d0 = (1e-6, 0.1)
+ d1 = (1-1e-5, 0.8)
+ # Convert to screen coordinates ("s"). Events are defined only with pixel
+ # precision, so round the pixel values, and below, check against the
+ # corresponding xdata/ydata, which are close but not equal to d0/d1.
+ s0 = ax.transData.transform(d0).astype(int)
+ s1 = ax.transData.transform(d1).astype(int)
+
+ # Zoom in.
+ start_event = MouseEvent(
+ "button_press_event", fig.canvas, *s0, MouseButton.LEFT)
+ fig.canvas.callbacks.process(start_event.name, start_event)
+ stop_event = MouseEvent(
+ "button_release_event", fig.canvas, *s1, MouseButton.LEFT)
+ fig.canvas.callbacks.process(stop_event.name, stop_event)
+ assert ax.get_xlim() == (start_event.xdata, stop_event.xdata)
+ assert ax.get_ylim() == (start_event.ydata, stop_event.ydata)
+
+ # Zoom out.
+ start_event = MouseEvent(
+ "button_press_event", fig.canvas, *s1, MouseButton.RIGHT)
+ fig.canvas.callbacks.process(start_event.name, start_event)
+ stop_event = MouseEvent(
+ "button_release_event", fig.canvas, *s0, MouseButton.RIGHT)
+ fig.canvas.callbacks.process(stop_event.name, stop_event)
+ # Absolute tolerance much less than original xmin (1e-7).
+ assert ax.get_xlim() == pytest.approx(xlim0, rel=0, abs=1e-10)
+ assert ax.get_ylim() == pytest.approx(ylim0, rel=0, abs=1e-10)
+
+ tb.zoom()
+ assert ax.get_navigate_mode() is None
+
+ assert not ax.get_autoscalex_on() and not ax.get_autoscaley_on()
+
+
+def test_widgetlock_zoompan():
+ fig, ax = plt.subplots()
+ ax.plot([0, 1], [0, 1])
+ fig.canvas.widgetlock(ax)
+ tb = NavigationToolbar2(fig.canvas)
+ tb.zoom()
+ assert ax.get_navigate_mode() is None
+ tb.pan()
+ assert ax.get_navigate_mode() is None
+
+
+@pytest.mark.parametrize("plot_func", ["imshow", "contourf"])
+@pytest.mark.parametrize("orientation", ["vertical", "horizontal"])
+@pytest.mark.parametrize("tool,button,expected",
+ [("zoom", MouseButton.LEFT, (4, 6)), # zoom in
+ ("zoom", MouseButton.RIGHT, (-20, 30)), # zoom out
+ ("pan", MouseButton.LEFT, (-2, 8)),
+ ("pan", MouseButton.RIGHT, (1.47, 7.78))]) # zoom
+def test_interactive_colorbar(plot_func, orientation, tool, button, expected):
+ fig, ax = plt.subplots()
+ data = np.arange(12).reshape((4, 3))
+ vmin0, vmax0 = 0, 10
+ coll = getattr(ax, plot_func)(data, vmin=vmin0, vmax=vmax0)
+
+ cb = fig.colorbar(coll, ax=ax, orientation=orientation)
+ if plot_func == "contourf":
+ # Just determine we can't navigate and exit out of the test
+ assert not cb.ax.get_navigate()
+ return
+
+ assert cb.ax.get_navigate()
+
+ # Mouse from 4 to 6 (data coordinates, "d").
+ vmin, vmax = 4, 6
+ # The y coordinate doesn't matter, it just needs to be between 0 and 1
+ # However, we will set d0/d1 to the same y coordinate to test that small
+ # pixel changes in that coordinate doesn't cancel the zoom like a normal
+ # axes would.
+ d0 = (vmin, 0.5)
+ d1 = (vmax, 0.5)
+ # Swap them if the orientation is vertical
+ if orientation == "vertical":
+ d0 = d0[::-1]
+ d1 = d1[::-1]
+ # Convert to screen coordinates ("s"). Events are defined only with pixel
+ # precision, so round the pixel values, and below, check against the
+ # corresponding xdata/ydata, which are close but not equal to d0/d1.
+ s0 = cb.ax.transData.transform(d0).astype(int)
+ s1 = cb.ax.transData.transform(d1).astype(int)
+
+ # Set up the mouse movements
+ start_event = MouseEvent(
+ "button_press_event", fig.canvas, *s0, button)
+ stop_event = MouseEvent(
+ "button_release_event", fig.canvas, *s1, button)
+
+ tb = NavigationToolbar2(fig.canvas)
+ if tool == "zoom":
+ tb.zoom()
+ tb.press_zoom(start_event)
+ tb.drag_zoom(stop_event)
+ tb.release_zoom(stop_event)
+ else:
+ tb.pan()
+ tb.press_pan(start_event)
+ tb.drag_pan(stop_event)
+ tb.release_pan(stop_event)
+
+ # Should be close, but won't be exact due to screen integer resolution
+ assert (cb.vmin, cb.vmax) == pytest.approx(expected, abs=0.15)
+
+
+def test_toolbar_zoompan():
+ with pytest.warns(UserWarning, match=_EXPECTED_WARNING_TOOLMANAGER):
+ plt.rcParams['toolbar'] = 'toolmanager'
+ ax = plt.gca()
+ fig = ax.get_figure()
+ assert ax.get_navigate_mode() is None
+ fig.canvas.manager.toolmanager.trigger_tool('zoom')
+ assert ax.get_navigate_mode() == "ZOOM"
+ fig.canvas.manager.toolmanager.trigger_tool('pan')
+ assert ax.get_navigate_mode() == "PAN"
+
+
+def test_toolbar_home_restores_autoscale():
+ fig, ax = plt.subplots()
+ ax.plot(range(11), range(11))
+
+ tb = NavigationToolbar2(fig.canvas)
+ tb.zoom()
+
+ # Switch to log.
+ KeyEvent("key_press_event", fig.canvas, "k", 100, 100)._process()
+ KeyEvent("key_press_event", fig.canvas, "l", 100, 100)._process()
+ assert ax.get_xlim() == ax.get_ylim() == (1, 10) # Autolimits excluding 0.
+ # Switch back to linear.
+ KeyEvent("key_press_event", fig.canvas, "k", 100, 100)._process()
+ KeyEvent("key_press_event", fig.canvas, "l", 100, 100)._process()
+ assert ax.get_xlim() == ax.get_ylim() == (0, 10) # Autolimits.
+
+ # Zoom in from (x, y) = (2, 2) to (5, 5).
+ start, stop = ax.transData.transform([(2, 2), (5, 5)])
+ MouseEvent("button_press_event", fig.canvas, *start, MouseButton.LEFT)._process()
+ MouseEvent("button_release_event", fig.canvas, *stop, MouseButton.LEFT)._process()
+ # Go back to home.
+ KeyEvent("key_press_event", fig.canvas, "h")._process()
+
+ assert ax.get_xlim() == ax.get_ylim() == (0, 10)
+ # Switch to log.
+ KeyEvent("key_press_event", fig.canvas, "k", 100, 100)._process()
+ KeyEvent("key_press_event", fig.canvas, "l", 100, 100)._process()
+ assert ax.get_xlim() == ax.get_ylim() == (1, 10) # Autolimits excluding 0.
+
+
+@pytest.mark.parametrize(
+ "backend", ['svg', 'ps', 'pdf',
+ pytest.param('pgf', marks=needs_pgf_xelatex)]
+)
+def test_draw(backend):
+ from matplotlib.figure import Figure
+ from matplotlib.backends.backend_agg import FigureCanvas
+ test_backend = importlib.import_module(f'matplotlib.backends.backend_{backend}')
+ TestCanvas = test_backend.FigureCanvas
+ fig_test = Figure(constrained_layout=True)
+ TestCanvas(fig_test)
+ axes_test = fig_test.subplots(2, 2)
+
+ # defaults to FigureCanvasBase
+ fig_agg = Figure(constrained_layout=True)
+ # put a backends.backend_agg.FigureCanvas on it
+ FigureCanvas(fig_agg)
+ axes_agg = fig_agg.subplots(2, 2)
+
+ init_pos = [ax.get_position() for ax in axes_test.ravel()]
+
+ fig_test.canvas.draw()
+ fig_agg.canvas.draw()
+
+ layed_out_pos_test = [ax.get_position() for ax in axes_test.ravel()]
+ layed_out_pos_agg = [ax.get_position() for ax in axes_agg.ravel()]
+
+ for init, placed in zip(init_pos, layed_out_pos_test):
+ assert not np.allclose(init, placed, atol=0.005)
+
+ for ref, test in zip(layed_out_pos_agg, layed_out_pos_test):
+ np.testing.assert_allclose(ref, test, atol=0.005)
+
+
+@pytest.mark.parametrize(
+ "key,mouseend,expectedxlim,expectedylim",
+ [(None, (0.2, 0.2), (3.49, 12.49), (2.7, 11.7)),
+ (None, (0.2, 0.5), (3.49, 12.49), (0, 9)),
+ (None, (0.5, 0.2), (0, 9), (2.7, 11.7)),
+ (None, (0.5, 0.5), (0, 9), (0, 9)), # No move
+ (None, (0.8, 0.25), (-3.47, 5.53), (2.25, 11.25)),
+ (None, (0.2, 0.25), (3.49, 12.49), (2.25, 11.25)),
+ (None, (0.8, 0.85), (-3.47, 5.53), (-3.14, 5.86)),
+ (None, (0.2, 0.85), (3.49, 12.49), (-3.14, 5.86)),
+ ("shift", (0.2, 0.4), (3.49, 12.49), (0, 9)), # snap to x
+ ("shift", (0.4, 0.2), (0, 9), (2.7, 11.7)), # snap to y
+ ("shift", (0.2, 0.25), (3.49, 12.49), (3.49, 12.49)), # snap to diagonal
+ ("shift", (0.8, 0.25), (-3.47, 5.53), (3.47, 12.47)), # snap to diagonal
+ ("shift", (0.8, 0.9), (-3.58, 5.41), (-3.58, 5.41)), # snap to diagonal
+ ("shift", (0.2, 0.85), (3.49, 12.49), (-3.49, 5.51)), # snap to diagonal
+ ("x", (0.2, 0.1), (3.49, 12.49), (0, 9)), # only x
+ ("y", (0.1, 0.2), (0, 9), (2.7, 11.7)), # only y
+ ("control", (0.2, 0.2), (3.49, 12.49), (3.49, 12.49)), # diagonal
+ ("control", (0.4, 0.2), (2.72, 11.72), (2.72, 11.72)), # diagonal
+ ])
+def test_interactive_pan(key, mouseend, expectedxlim, expectedylim):
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10))
+ assert ax.get_navigate()
+ # Set equal aspect ratio to easier see diagonal snap
+ ax.set_aspect('equal')
+
+ # Mouse move starts from 0.5, 0.5
+ mousestart = (0.5, 0.5)
+ # Convert to screen coordinates ("s"). Events are defined only with pixel
+ # precision, so round the pixel values, and below, check against the
+ # corresponding xdata/ydata, which are close but not equal to d0/d1.
+ sstart = ax.transData.transform(mousestart).astype(int)
+ send = ax.transData.transform(mouseend).astype(int)
+
+ # Set up the mouse movements
+ start_event = MouseEvent(
+ "button_press_event", fig.canvas, *sstart, button=MouseButton.LEFT,
+ key=key)
+ stop_event = MouseEvent(
+ "button_release_event", fig.canvas, *send, button=MouseButton.LEFT,
+ key=key)
+
+ tb = NavigationToolbar2(fig.canvas)
+ tb.pan()
+ tb.press_pan(start_event)
+ tb.drag_pan(stop_event)
+ tb.release_pan(stop_event)
+ # Should be close, but won't be exact due to screen integer resolution
+ assert tuple(ax.get_xlim()) == pytest.approx(expectedxlim, abs=0.02)
+ assert tuple(ax.get_ylim()) == pytest.approx(expectedylim, abs=0.02)
+
+
+def test_toolmanager_remove():
+ with pytest.warns(UserWarning, match=_EXPECTED_WARNING_TOOLMANAGER):
+ plt.rcParams['toolbar'] = 'toolmanager'
+ fig = plt.gcf()
+ initial_len = len(fig.canvas.manager.toolmanager.tools)
+ assert 'forward' in fig.canvas.manager.toolmanager.tools
+ fig.canvas.manager.toolmanager.remove_tool('forward')
+ assert len(fig.canvas.manager.toolmanager.tools) == initial_len - 1
+ assert 'forward' not in fig.canvas.manager.toolmanager.tools
+
+
+def test_toolmanager_get_tool():
+ with pytest.warns(UserWarning, match=_EXPECTED_WARNING_TOOLMANAGER):
+ plt.rcParams['toolbar'] = 'toolmanager'
+ fig = plt.gcf()
+ rubberband = fig.canvas.manager.toolmanager.get_tool('rubberband')
+ assert isinstance(rubberband, RubberbandBase)
+ assert fig.canvas.manager.toolmanager.get_tool(rubberband) is rubberband
+ with pytest.warns(UserWarning,
+ match="ToolManager does not control tool 'foo'"):
+ assert fig.canvas.manager.toolmanager.get_tool('foo') is None
+ assert fig.canvas.manager.toolmanager.get_tool('foo', warn=False) is None
+
+ with pytest.warns(UserWarning,
+ match="ToolManager does not control tool 'foo'"):
+ assert fig.canvas.manager.toolmanager.trigger_tool('foo') is None
+
+
+def test_toolmanager_update_keymap():
+ with pytest.warns(UserWarning, match=_EXPECTED_WARNING_TOOLMANAGER):
+ plt.rcParams['toolbar'] = 'toolmanager'
+ fig = plt.gcf()
+ assert 'v' in fig.canvas.manager.toolmanager.get_tool_keymap('forward')
+ with pytest.warns(UserWarning,
+ match="Key c changed from back to forward"):
+ fig.canvas.manager.toolmanager.update_keymap('forward', 'c')
+ assert fig.canvas.manager.toolmanager.get_tool_keymap('forward') == ['c']
+ with pytest.raises(KeyError, match="'foo' not in Tools"):
+ fig.canvas.manager.toolmanager.update_keymap('foo', 'c')
+
+
+@pytest.mark.parametrize("tool", ["zoom", "pan"])
+@pytest.mark.parametrize("button", [MouseButton.LEFT, MouseButton.RIGHT])
+@pytest.mark.parametrize("patch_vis", [True, False])
+@pytest.mark.parametrize("forward_nav", [True, False, "auto"])
+@pytest.mark.parametrize("t_s", ["twin", "share"])
+def test_interactive_pan_zoom_events(tool, button, patch_vis, forward_nav, t_s):
+ # Bottom axes: ax_b Top axes: ax_t
+ fig, ax_b = plt.subplots()
+ ax_t = fig.add_subplot(221, zorder=99)
+ ax_t.set_forward_navigation_events(forward_nav)
+ ax_t.patch.set_visible(patch_vis)
+
+ # ----------------------------
+ if t_s == "share":
+ ax_t_twin = fig.add_subplot(222)
+ ax_t_twin.sharex(ax_t)
+ ax_t_twin.sharey(ax_t)
+
+ ax_b_twin = fig.add_subplot(223)
+ ax_b_twin.sharex(ax_b)
+ ax_b_twin.sharey(ax_b)
+ elif t_s == "twin":
+ ax_t_twin = ax_t.twinx()
+ ax_b_twin = ax_b.twinx()
+
+ # just some styling to simplify manual checks
+ ax_t.set_label("ax_t")
+ ax_t.patch.set_facecolor((1, 0, 0, 0.5))
+
+ ax_t_twin.set_label("ax_t_twin")
+ ax_t_twin.patch.set_facecolor("r")
+
+ ax_b.set_label("ax_b")
+ ax_b.patch.set_facecolor((0, 0, 1, 0.5))
+
+ ax_b_twin.set_label("ax_b_twin")
+ ax_b_twin.patch.set_facecolor("b")
+
+ # ----------------------------
+
+ # Set initial axis limits
+ init_xlim, init_ylim = (0, 10), (0, 10)
+ for ax in [ax_t, ax_b]:
+ ax.set_xlim(*init_xlim)
+ ax.set_ylim(*init_ylim)
+
+ # Mouse from 2 to 1 (in data-coordinates of ax_t).
+ xstart_t, xstop_t, ystart_t, ystop_t = 1, 2, 1, 2
+ # Convert to screen coordinates ("s"). Events are defined only with pixel
+ # precision, so round the pixel values, and below, check against the
+ # corresponding xdata/ydata, which are close but not equal to s0/s1.
+ s0 = ax_t.transData.transform((xstart_t, ystart_t)).astype(int)
+ s1 = ax_t.transData.transform((xstop_t, ystop_t)).astype(int)
+
+ # Calculate the mouse-distance in data-coordinates of the bottom-axes
+ xstart_b, ystart_b = ax_b.transData.inverted().transform(s0)
+ xstop_b, ystop_b = ax_b.transData.inverted().transform(s1)
+
+ # Set up the mouse movements
+ start_event = MouseEvent("button_press_event", fig.canvas, *s0, button)
+ stop_event = MouseEvent("button_release_event", fig.canvas, *s1, button)
+
+ tb = NavigationToolbar2(fig.canvas)
+
+ if tool == "zoom":
+ # Evaluate expected limits before executing the zoom-event
+ direction = ("in" if button == 1 else "out")
+
+ xlim_t, ylim_t = ax_t._prepare_view_from_bbox([*s0, *s1], direction)
+
+ if ax_t.get_forward_navigation_events() is True:
+ xlim_b, ylim_b = ax_b._prepare_view_from_bbox([*s0, *s1], direction)
+ elif ax_t.get_forward_navigation_events() is False:
+ xlim_b = init_xlim
+ ylim_b = init_ylim
+ else:
+ if not ax_t.patch.get_visible():
+ xlim_b, ylim_b = ax_b._prepare_view_from_bbox([*s0, *s1], direction)
+ else:
+ xlim_b = init_xlim
+ ylim_b = init_ylim
+
+ tb.zoom()
+ tb.press_zoom(start_event)
+ tb.drag_zoom(stop_event)
+ tb.release_zoom(stop_event)
+
+ assert ax_t.get_xlim() == pytest.approx(xlim_t, abs=0.15)
+ assert ax_t.get_ylim() == pytest.approx(ylim_t, abs=0.15)
+ assert ax_b.get_xlim() == pytest.approx(xlim_b, abs=0.15)
+ assert ax_b.get_ylim() == pytest.approx(ylim_b, abs=0.15)
+
+ # Check if twin-axes are properly triggered
+ assert ax_t.get_xlim() == pytest.approx(ax_t_twin.get_xlim(), abs=0.15)
+ assert ax_b.get_xlim() == pytest.approx(ax_b_twin.get_xlim(), abs=0.15)
+ else:
+ # Evaluate expected limits
+ # (call start_pan to make sure ax._pan_start is set)
+ ax_t.start_pan(*s0, button)
+ xlim_t, ylim_t = ax_t._get_pan_points(button, None, *s1).T.astype(float)
+ ax_t.end_pan()
+
+ if ax_t.get_forward_navigation_events() is True:
+ ax_b.start_pan(*s0, button)
+ xlim_b, ylim_b = ax_b._get_pan_points(button, None, *s1).T.astype(float)
+ ax_b.end_pan()
+ elif ax_t.get_forward_navigation_events() is False:
+ xlim_b = init_xlim
+ ylim_b = init_ylim
+ else:
+ if not ax_t.patch.get_visible():
+ ax_b.start_pan(*s0, button)
+ xlim_b, ylim_b = ax_b._get_pan_points(button, None, *s1).T.astype(float)
+ ax_b.end_pan()
+ else:
+ xlim_b = init_xlim
+ ylim_b = init_ylim
+
+ tb.pan()
+ tb.press_pan(start_event)
+ tb.drag_pan(stop_event)
+ tb.release_pan(stop_event)
+
+ assert ax_t.get_xlim() == pytest.approx(xlim_t, abs=0.15)
+ assert ax_t.get_ylim() == pytest.approx(ylim_t, abs=0.15)
+ assert ax_b.get_xlim() == pytest.approx(xlim_b, abs=0.15)
+ assert ax_b.get_ylim() == pytest.approx(ylim_b, abs=0.15)
+
+ # Check if twin-axes are properly triggered
+ assert ax_t.get_xlim() == pytest.approx(ax_t_twin.get_xlim(), abs=0.15)
+ assert ax_b.get_xlim() == pytest.approx(ax_b_twin.get_xlim(), abs=0.15)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_cairo.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_cairo.py
new file mode 100644
index 0000000000000000000000000000000000000000..8cc0b319b770ff7d32996ef344bf8a971f535177
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_cairo.py
@@ -0,0 +1,48 @@
+import numpy as np
+
+import pytest
+
+from matplotlib.testing.decorators import check_figures_equal
+from matplotlib import (
+ collections as mcollections, patches as mpatches, path as mpath)
+
+
+@pytest.mark.backend('cairo')
+@check_figures_equal(extensions=["png"])
+def test_patch_alpha_coloring(fig_test, fig_ref):
+ """
+ Test checks that the patch and collection are rendered with the specified
+ alpha values in their facecolor and edgecolor.
+ """
+ star = mpath.Path.unit_regular_star(6)
+ circle = mpath.Path.unit_circle()
+ # concatenate the star with an internal cutout of the circle
+ verts = np.concatenate([circle.vertices, star.vertices[::-1]])
+ codes = np.concatenate([circle.codes, star.codes])
+ cut_star1 = mpath.Path(verts, codes)
+ cut_star2 = mpath.Path(verts + 1, codes)
+
+ # Reference: two separate patches
+ ax = fig_ref.subplots()
+ ax.set_xlim([-1, 2])
+ ax.set_ylim([-1, 2])
+ patch = mpatches.PathPatch(cut_star1,
+ linewidth=5, linestyle='dashdot',
+ facecolor=(1, 0, 0, 0.5),
+ edgecolor=(0, 0, 1, 0.75))
+ ax.add_patch(patch)
+ patch = mpatches.PathPatch(cut_star2,
+ linewidth=5, linestyle='dashdot',
+ facecolor=(1, 0, 0, 0.5),
+ edgecolor=(0, 0, 1, 0.75))
+ ax.add_patch(patch)
+
+ # Test: path collection
+ ax = fig_test.subplots()
+ ax.set_xlim([-1, 2])
+ ax.set_ylim([-1, 2])
+ col = mcollections.PathCollection([cut_star1, cut_star2],
+ linewidth=5, linestyles='dashdot',
+ facecolor=(1, 0, 0, 0.5),
+ edgecolor=(0, 0, 1, 0.75))
+ ax.add_collection(col)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_gtk3.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_gtk3.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4c6e3d7fca841c1a1f0efa1d0a7ee9df85bde56
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_gtk3.py
@@ -0,0 +1,74 @@
+import os
+from matplotlib import pyplot as plt
+
+import pytest
+from unittest import mock
+
+
+@pytest.mark.backend("gtk3agg", skip_on_importerror=True)
+def test_correct_key():
+ pytest.xfail("test_widget_send_event is not triggering key_press_event")
+
+ from gi.repository import Gdk, Gtk # type: ignore[import]
+ fig = plt.figure()
+ buf = []
+
+ def send(event):
+ for key, mod in [
+ (Gdk.KEY_a, Gdk.ModifierType.SHIFT_MASK),
+ (Gdk.KEY_a, 0),
+ (Gdk.KEY_a, Gdk.ModifierType.CONTROL_MASK),
+ (Gdk.KEY_agrave, 0),
+ (Gdk.KEY_Control_L, Gdk.ModifierType.MOD1_MASK),
+ (Gdk.KEY_Alt_L, Gdk.ModifierType.CONTROL_MASK),
+ (Gdk.KEY_agrave,
+ Gdk.ModifierType.CONTROL_MASK
+ | Gdk.ModifierType.MOD1_MASK
+ | Gdk.ModifierType.MOD4_MASK),
+ (0xfd16, 0), # KEY_3270_Play.
+ (Gdk.KEY_BackSpace, 0),
+ (Gdk.KEY_BackSpace, Gdk.ModifierType.CONTROL_MASK),
+ ]:
+ # This is not actually really the right API: it depends on the
+ # actual keymap (e.g. on Azerty, shift+agrave -> 0).
+ Gtk.test_widget_send_key(fig.canvas, key, mod)
+
+ def receive(event):
+ buf.append(event.key)
+ if buf == [
+ "A", "a", "ctrl+a",
+ "\N{LATIN SMALL LETTER A WITH GRAVE}",
+ "alt+control", "ctrl+alt",
+ "ctrl+alt+super+\N{LATIN SMALL LETTER A WITH GRAVE}",
+ # (No entry for KEY_3270_Play.)
+ "backspace", "ctrl+backspace",
+ ]:
+ plt.close(fig)
+
+ fig.canvas.mpl_connect("draw_event", send)
+ fig.canvas.mpl_connect("key_press_event", receive)
+ plt.show()
+
+
+@pytest.mark.backend("gtk3agg", skip_on_importerror=True)
+def test_save_figure_return():
+ from gi.repository import Gtk
+ fig, ax = plt.subplots()
+ ax.imshow([[1]])
+ with mock.patch("gi.repository.Gtk.FileFilter") as fileFilter:
+ filt = fileFilter.return_value
+ filt.get_name.return_value = "Portable Network Graphics"
+ with mock.patch("gi.repository.Gtk.FileChooserDialog") as dialogChooser:
+ dialog = dialogChooser.return_value
+ dialog.get_filter.return_value = filt
+ dialog.get_filename.return_value = "foobar.png"
+ dialog.run.return_value = Gtk.ResponseType.OK
+ fname = fig.canvas.manager.toolbar.save_figure()
+ os.remove("foobar.png")
+ assert fname == "foobar.png"
+
+ with mock.patch("gi.repository.Gtk.MessageDialog"):
+ dialog.get_filename.return_value = None
+ dialog.run.return_value = Gtk.ResponseType.OK
+ fname = fig.canvas.manager.toolbar.save_figure()
+ assert fname is None
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_inline.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_inline.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f0d67d51756b8f280522e8f2a9f5d6482f5c9ea
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_inline.py
@@ -0,0 +1,46 @@
+import os
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import pytest
+
+from matplotlib.testing import subprocess_run_for_testing
+
+nbformat = pytest.importorskip('nbformat')
+pytest.importorskip('nbconvert')
+pytest.importorskip('ipykernel')
+pytest.importorskip('matplotlib_inline')
+
+
+def test_ipynb():
+ nb_path = Path(__file__).parent / 'test_inline_01.ipynb'
+
+ with TemporaryDirectory() as tmpdir:
+ out_path = Path(tmpdir, "out.ipynb")
+
+ subprocess_run_for_testing(
+ ["jupyter", "nbconvert", "--to", "notebook",
+ "--execute", "--ExecutePreprocessor.timeout=500",
+ "--output", str(out_path), str(nb_path)],
+ env={**os.environ, "IPYTHONDIR": tmpdir},
+ check=True)
+ with out_path.open() as out:
+ nb = nbformat.read(out, nbformat.current_nbformat)
+
+ errors = [output for cell in nb.cells for output in cell.get("outputs", [])
+ if output.output_type == "error"]
+ assert not errors
+
+ import IPython
+ if IPython.version_info[:2] >= (8, 24):
+ expected_backend = "inline"
+ else:
+ # This code can be removed when Python 3.12, the latest version supported by
+ # IPython < 8.24, reaches end-of-life in late 2028.
+ expected_backend = "module://matplotlib_inline.backend_inline"
+ backend_outputs = nb.cells[2]["outputs"]
+ assert backend_outputs[0]["data"]["text/plain"] == f"'{expected_backend}'"
+
+ image = nb.cells[1]["outputs"][1]["data"]
+ assert image["text/plain"] == ""
+ assert "image/png" in image
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_macosx.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_macosx.py
new file mode 100644
index 0000000000000000000000000000000000000000..0648e43cde948cc74b4132e983791b81d762ff56
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_macosx.py
@@ -0,0 +1,109 @@
+import os
+import threading
+from pathlib import Path
+
+import pytest
+from unittest import mock
+
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+from matplotlib.testing import subprocess_run_helper
+
+
+_test_timeout = 60
+
+
+def _test_cached_renderer():
+ # Make sure that figures have an associated renderer after
+ # a fig.canvas.draw() call
+ fig = plt.figure(1)
+ fig.canvas.draw()
+ assert fig.canvas.get_renderer()._renderer is not None
+
+ fig = plt.figure(2)
+ fig.draw_without_rendering()
+ assert fig.canvas.get_renderer()._renderer is not None
+
+
+@pytest.mark.backend('macosx', skip_on_importerror=True)
+def test_cached_renderer():
+ subprocess_run_helper(_test_cached_renderer, timeout=_test_timeout,
+ extra_env={"MPLBACKEND": "macosx"})
+
+
+def _test_savefig_rcparam():
+ tmp_path = Path(os.environ["TEST_SAVEFIG_PATH"])
+
+ def new_choose_save_file(title, directory, filename):
+ # Replacement function instead of opening a GUI window
+ # Make a new directory for testing the update of the rcParams
+ assert directory == str(tmp_path)
+ os.makedirs(f"{directory}/test")
+ return f"{directory}/test/{filename}"
+
+ fig = plt.figure()
+ with (mock.patch("matplotlib.backends._macosx.choose_save_file",
+ new_choose_save_file),
+ mpl.rc_context({"savefig.directory": tmp_path})):
+ fig.canvas.toolbar.save_figure()
+ # Check the saved location got created
+ save_file = f"{tmp_path}/test/{fig.canvas.get_default_filename()}"
+ assert os.path.exists(save_file)
+
+ # Check the savefig.directory rcParam got updated because
+ # we added a subdirectory "test"
+ assert mpl.rcParams["savefig.directory"] == f"{tmp_path}/test"
+
+
+@pytest.mark.backend('macosx', skip_on_importerror=True)
+def test_savefig_rcparam(tmp_path):
+ subprocess_run_helper(
+ _test_savefig_rcparam, timeout=_test_timeout,
+ extra_env={"MPLBACKEND": "macosx", "TEST_SAVEFIG_PATH": tmp_path})
+
+
+@pytest.mark.backend('macosx', skip_on_importerror=True)
+def test_ipython():
+ from matplotlib.testing import ipython_in_subprocess
+ ipython_in_subprocess("osx", {(8, 24): "macosx", (7, 0): "MacOSX"})
+
+
+def _test_save_figure_return():
+ fig, ax = plt.subplots()
+ ax.imshow([[1]])
+ prop = "matplotlib.backends._macosx.choose_save_file"
+ with mock.patch(prop, return_value="foobar.png"):
+ fname = fig.canvas.manager.toolbar.save_figure()
+ os.remove("foobar.png")
+ assert fname == "foobar.png"
+ with mock.patch(prop, return_value=None):
+ fname = fig.canvas.manager.toolbar.save_figure()
+ assert fname is None
+
+
+@pytest.mark.backend('macosx', skip_on_importerror=True)
+def test_save_figure_return():
+ subprocess_run_helper(_test_save_figure_return, timeout=_test_timeout,
+ extra_env={"MPLBACKEND": "macosx"})
+
+
+def _test_create_figure_on_worker_thread_fails():
+ def create_figure():
+ warn_msg = "Matplotlib GUI outside of the main thread will likely fail."
+ err_msg = "Cannot create a GUI FigureManager outside the main thread"
+ with pytest.warns(UserWarning, match=warn_msg):
+ with pytest.raises(RuntimeError, match=err_msg):
+ plt.gcf()
+
+ worker = threading.Thread(target=create_figure)
+ worker.start()
+ worker.join()
+
+
+@pytest.mark.backend('macosx', skip_on_importerror=True)
+def test_create_figure_on_worker_thread_fails():
+ subprocess_run_helper(
+ _test_create_figure_on_worker_thread_fails,
+ timeout=_test_timeout,
+ extra_env={"MPLBACKEND": "macosx"}
+ )
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_nbagg.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_nbagg.py
new file mode 100644
index 0000000000000000000000000000000000000000..23af88d9508677d799d6546b1b49fa10d34822b2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_nbagg.py
@@ -0,0 +1,42 @@
+import os
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import pytest
+
+from matplotlib.testing import subprocess_run_for_testing
+
+nbformat = pytest.importorskip('nbformat')
+pytest.importorskip('nbconvert')
+pytest.importorskip('ipykernel')
+
+# From https://blog.thedataincubator.com/2016/06/testing-jupyter-notebooks/
+
+
+def test_ipynb():
+ nb_path = Path(__file__).parent / 'test_nbagg_01.ipynb'
+
+ with TemporaryDirectory() as tmpdir:
+ out_path = Path(tmpdir, "out.ipynb")
+ subprocess_run_for_testing(
+ ["jupyter", "nbconvert", "--to", "notebook",
+ "--execute", "--ExecutePreprocessor.timeout=500",
+ "--output", str(out_path), str(nb_path)],
+ env={**os.environ, "IPYTHONDIR": tmpdir},
+ check=True)
+ with out_path.open() as out:
+ nb = nbformat.read(out, nbformat.current_nbformat)
+
+ errors = [output for cell in nb.cells for output in cell.get("outputs", [])
+ if output.output_type == "error"]
+ assert not errors
+
+ import IPython
+ if IPython.version_info[:2] >= (8, 24):
+ expected_backend = "notebook"
+ else:
+ # This code can be removed when Python 3.12, the latest version supported by
+ # IPython < 8.24, reaches end-of-life in late 2028.
+ expected_backend = "nbAgg"
+ backend_outputs = nb.cells[2]["outputs"]
+ assert backend_outputs[0]["data"]["text/plain"] == f"'{expected_backend}'"
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_pdf.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_pdf.py
new file mode 100644
index 0000000000000000000000000000000000000000..c816c4715ae2123eb792ef3278fe0ea87e6705a1
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_pdf.py
@@ -0,0 +1,448 @@
+import datetime
+import decimal
+import io
+import os
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+import matplotlib as mpl
+from matplotlib import (
+ pyplot as plt, rcParams, font_manager as fm
+)
+from matplotlib.cbook import _get_data_path
+from matplotlib.ft2font import FT2Font
+from matplotlib.font_manager import findfont, FontProperties
+from matplotlib.backends._backend_pdf_ps import get_glyphs_subset, font_as_file
+from matplotlib.backends.backend_pdf import PdfPages
+from matplotlib.patches import Rectangle
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+from matplotlib.testing._markers import needs_usetex
+
+
+@image_comparison(['pdf_use14corefonts.pdf'])
+def test_use14corefonts():
+ rcParams['pdf.use14corefonts'] = True
+ rcParams['font.family'] = 'sans-serif'
+ rcParams['font.size'] = 8
+ rcParams['font.sans-serif'] = ['Helvetica']
+ rcParams['pdf.compression'] = 0
+
+ text = '''A three-line text positioned just above a blue line
+and containing some French characters and the euro symbol:
+"Merci pépé pour les 10 €"'''
+
+ fig, ax = plt.subplots()
+ ax.set_title('Test PDF backend with option use14corefonts=True')
+ ax.text(0.5, 0.5, text, horizontalalignment='center',
+ verticalalignment='bottom',
+ fontsize=14)
+ ax.axhline(0.5, linewidth=0.5)
+
+
+@pytest.mark.parametrize('fontname, fontfile', [
+ ('DejaVu Sans', 'DejaVuSans.ttf'),
+ ('WenQuanYi Zen Hei', 'wqy-zenhei.ttc'),
+])
+@pytest.mark.parametrize('fonttype', [3, 42])
+def test_embed_fonts(fontname, fontfile, fonttype):
+ if Path(findfont(FontProperties(family=[fontname]))).name != fontfile:
+ pytest.skip(f'Font {fontname!r} may be missing')
+
+ rcParams['pdf.fonttype'] = fonttype
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3])
+ ax.set_title('Axes Title', font=fontname)
+ fig.savefig(io.BytesIO(), format='pdf')
+
+
+def test_multipage_pagecount():
+ with PdfPages(io.BytesIO()) as pdf:
+ assert pdf.get_pagecount() == 0
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3])
+ fig.savefig(pdf, format="pdf")
+ assert pdf.get_pagecount() == 1
+ pdf.savefig()
+ assert pdf.get_pagecount() == 2
+
+
+def test_multipage_properfinalize():
+ pdfio = io.BytesIO()
+ with PdfPages(pdfio) as pdf:
+ for i in range(10):
+ fig, ax = plt.subplots()
+ ax.set_title('This is a long title')
+ fig.savefig(pdf, format="pdf")
+ s = pdfio.getvalue()
+ assert s.count(b'startxref') == 1
+ assert len(s) < 40000
+
+
+def test_multipage_keep_empty(tmp_path):
+ # An empty pdf deletes itself afterwards.
+ fn = tmp_path / "a.pdf"
+ with PdfPages(fn) as pdf:
+ pass
+ assert not fn.exists()
+
+ # Test pdf files with content, they should never be deleted.
+ fn = tmp_path / "b.pdf"
+ with PdfPages(fn) as pdf:
+ pdf.savefig(plt.figure())
+ assert fn.exists()
+
+
+def test_composite_image():
+ # Test that figures can be saved with and without combining multiple images
+ # (on a single set of axes) into a single composite image.
+ X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
+ Z = np.sin(Y ** 2)
+ fig, ax = plt.subplots()
+ ax.set_xlim(0, 3)
+ ax.imshow(Z, extent=[0, 1, 0, 1])
+ ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
+ plt.rcParams['image.composite_image'] = True
+ with PdfPages(io.BytesIO()) as pdf:
+ fig.savefig(pdf, format="pdf")
+ assert len(pdf._file._images) == 1
+ plt.rcParams['image.composite_image'] = False
+ with PdfPages(io.BytesIO()) as pdf:
+ fig.savefig(pdf, format="pdf")
+ assert len(pdf._file._images) == 2
+
+
+def test_indexed_image():
+ # An image with low color count should compress to a palette-indexed format.
+ pikepdf = pytest.importorskip('pikepdf')
+
+ data = np.zeros((256, 1, 3), dtype=np.uint8)
+ data[:, 0, 0] = np.arange(256) # Maximum unique colours for an indexed image.
+
+ rcParams['pdf.compression'] = True
+ fig = plt.figure()
+ fig.figimage(data, resize=True)
+ buf = io.BytesIO()
+ fig.savefig(buf, format='pdf', dpi='figure')
+
+ with pikepdf.Pdf.open(buf) as pdf:
+ page, = pdf.pages
+ image, = page.images.values()
+ pdf_image = pikepdf.PdfImage(image)
+ assert pdf_image.indexed
+ pil_image = pdf_image.as_pil_image()
+ rgb = np.asarray(pil_image.convert('RGB'))
+
+ np.testing.assert_array_equal(data, rgb)
+
+
+def test_savefig_metadata(monkeypatch):
+ pikepdf = pytest.importorskip('pikepdf')
+ monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
+
+ fig, ax = plt.subplots()
+ ax.plot(range(5))
+
+ md = {
+ 'Author': 'me',
+ 'Title': 'Multipage PDF',
+ 'Subject': 'Test page',
+ 'Keywords': 'test,pdf,multipage',
+ 'ModDate': datetime.datetime(
+ 1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
+ 'Trapped': 'True'
+ }
+ buf = io.BytesIO()
+ fig.savefig(buf, metadata=md, format='pdf')
+
+ with pikepdf.Pdf.open(buf) as pdf:
+ info = {k: str(v) for k, v in pdf.docinfo.items()}
+
+ assert info == {
+ '/Author': 'me',
+ '/CreationDate': 'D:19700101000000Z',
+ '/Creator': f'Matplotlib v{mpl.__version__}, https://matplotlib.org',
+ '/Keywords': 'test,pdf,multipage',
+ '/ModDate': 'D:19680801000000Z',
+ '/Producer': f'Matplotlib pdf backend v{mpl.__version__}',
+ '/Subject': 'Test page',
+ '/Title': 'Multipage PDF',
+ '/Trapped': '/True',
+ }
+
+
+def test_invalid_metadata():
+ fig, ax = plt.subplots()
+
+ with pytest.warns(UserWarning,
+ match="Unknown infodict keyword: 'foobar'."):
+ fig.savefig(io.BytesIO(), format='pdf', metadata={'foobar': 'invalid'})
+
+ with pytest.warns(UserWarning,
+ match='not an instance of datetime.datetime.'):
+ fig.savefig(io.BytesIO(), format='pdf',
+ metadata={'ModDate': '1968-08-01'})
+
+ with pytest.warns(UserWarning,
+ match='not one of {"True", "False", "Unknown"}'):
+ fig.savefig(io.BytesIO(), format='pdf', metadata={'Trapped': 'foo'})
+
+ with pytest.warns(UserWarning, match='not an instance of str.'):
+ fig.savefig(io.BytesIO(), format='pdf', metadata={'Title': 1234})
+
+
+def test_multipage_metadata(monkeypatch):
+ pikepdf = pytest.importorskip('pikepdf')
+ monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
+
+ fig, ax = plt.subplots()
+ ax.plot(range(5))
+
+ md = {
+ 'Author': 'me',
+ 'Title': 'Multipage PDF',
+ 'Subject': 'Test page',
+ 'Keywords': 'test,pdf,multipage',
+ 'ModDate': datetime.datetime(
+ 1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
+ 'Trapped': 'True'
+ }
+ buf = io.BytesIO()
+ with PdfPages(buf, metadata=md) as pdf:
+ pdf.savefig(fig)
+ pdf.savefig(fig)
+
+ with pikepdf.Pdf.open(buf) as pdf:
+ info = {k: str(v) for k, v in pdf.docinfo.items()}
+
+ assert info == {
+ '/Author': 'me',
+ '/CreationDate': 'D:19700101000000Z',
+ '/Creator': f'Matplotlib v{mpl.__version__}, https://matplotlib.org',
+ '/Keywords': 'test,pdf,multipage',
+ '/ModDate': 'D:19680801000000Z',
+ '/Producer': f'Matplotlib pdf backend v{mpl.__version__}',
+ '/Subject': 'Test page',
+ '/Title': 'Multipage PDF',
+ '/Trapped': '/True',
+ }
+
+
+def test_text_urls():
+ pikepdf = pytest.importorskip('pikepdf')
+
+ test_url = 'https://test_text_urls.matplotlib.org/'
+
+ fig = plt.figure(figsize=(2, 1))
+ fig.text(0.1, 0.1, 'test plain 123', url=f'{test_url}plain')
+ fig.text(0.1, 0.4, 'test mathtext $123$', url=f'{test_url}mathtext')
+
+ with io.BytesIO() as fd:
+ fig.savefig(fd, format='pdf')
+
+ with pikepdf.Pdf.open(fd) as pdf:
+ annots = pdf.pages[0].Annots
+
+ # Iteration over Annots must occur within the context manager,
+ # otherwise it may fail depending on the pdf structure.
+ for y, fragment in [('0.1', 'plain'), ('0.4', 'mathtext')]:
+ annot = next(
+ (a for a in annots if a.A.URI == f'{test_url}{fragment}'),
+ None)
+ assert annot is not None
+ assert getattr(annot, 'QuadPoints', None) is None
+ # Positions in points (72 per inch.)
+ assert annot.Rect[1] == decimal.Decimal(y) * 72
+
+
+def test_text_rotated_urls():
+ pikepdf = pytest.importorskip('pikepdf')
+
+ test_url = 'https://test_text_urls.matplotlib.org/'
+
+ fig = plt.figure(figsize=(1, 1))
+ fig.text(0.1, 0.1, 'N', rotation=45, url=f'{test_url}')
+
+ with io.BytesIO() as fd:
+ fig.savefig(fd, format='pdf')
+
+ with pikepdf.Pdf.open(fd) as pdf:
+ annots = pdf.pages[0].Annots
+
+ # Iteration over Annots must occur within the context manager,
+ # otherwise it may fail depending on the pdf structure.
+ annot = next(
+ (a for a in annots if a.A.URI == f'{test_url}'),
+ None)
+ assert annot is not None
+ assert getattr(annot, 'QuadPoints', None) is not None
+ # Positions in points (72 per inch)
+ assert annot.Rect[0] == \
+ annot.QuadPoints[6] - decimal.Decimal('0.00001')
+
+
+@needs_usetex
+def test_text_urls_tex():
+ pikepdf = pytest.importorskip('pikepdf')
+
+ test_url = 'https://test_text_urls.matplotlib.org/'
+
+ fig = plt.figure(figsize=(2, 1))
+ fig.text(0.1, 0.7, 'test tex $123$', usetex=True, url=f'{test_url}tex')
+
+ with io.BytesIO() as fd:
+ fig.savefig(fd, format='pdf')
+
+ with pikepdf.Pdf.open(fd) as pdf:
+ annots = pdf.pages[0].Annots
+
+ # Iteration over Annots must occur within the context manager,
+ # otherwise it may fail depending on the pdf structure.
+ annot = next(
+ (a for a in annots if a.A.URI == f'{test_url}tex'),
+ None)
+ assert annot is not None
+ # Positions in points (72 per inch.)
+ assert annot.Rect[1] == decimal.Decimal('0.7') * 72
+
+
+def test_pdfpages_fspath():
+ with PdfPages(Path(os.devnull)) as pdf:
+ pdf.savefig(plt.figure())
+
+
+@image_comparison(['hatching_legend.pdf'])
+def test_hatching_legend():
+ """Test for correct hatching on patches in legend"""
+ fig = plt.figure(figsize=(1, 2))
+
+ a = Rectangle([0, 0], 0, 0, facecolor="green", hatch="XXXX")
+ b = Rectangle([0, 0], 0, 0, facecolor="blue", hatch="XXXX")
+
+ fig.legend([a, b, a, b], ["", "", "", ""])
+
+
+@image_comparison(['grayscale_alpha.pdf'])
+def test_grayscale_alpha():
+ """Masking images with NaN did not work for grayscale images"""
+ x, y = np.ogrid[-2:2:.1, -2:2:.1]
+ dd = np.exp(-(x**2 + y**2))
+ dd[dd < .1] = np.nan
+ fig, ax = plt.subplots()
+ ax.imshow(dd, interpolation='none', cmap='gray_r')
+ ax.set_xticks([])
+ ax.set_yticks([])
+
+
+@mpl.style.context('default')
+@check_figures_equal(extensions=["pdf", "eps"])
+def test_pdf_eps_savefig_when_color_is_none(fig_test, fig_ref):
+ ax_test = fig_test.add_subplot()
+ ax_test.set_axis_off()
+ ax_test.plot(np.sin(np.linspace(-5, 5, 100)), "v", c="none")
+ ax_ref = fig_ref.add_subplot()
+ ax_ref.set_axis_off()
+
+
+@needs_usetex
+def test_failing_latex():
+ """Test failing latex subprocess call"""
+ plt.xlabel("$22_2_2$", usetex=True) # This fails with "Double subscript"
+ with pytest.raises(RuntimeError):
+ plt.savefig(io.BytesIO(), format="pdf")
+
+
+def test_empty_rasterized():
+ # Check that empty figures that are rasterised save to pdf files fine
+ fig, ax = plt.subplots()
+ ax.plot([], [], rasterized=True)
+ fig.savefig(io.BytesIO(), format="pdf")
+
+
+@image_comparison(['kerning.pdf'])
+def test_kerning():
+ fig = plt.figure()
+ s = "AVAVAVAVAVAVAVAV€AAVV"
+ fig.text(0, .25, s, size=5)
+ fig.text(0, .75, s, size=20)
+
+
+def test_glyphs_subset():
+ fpath = str(_get_data_path("fonts/ttf/DejaVuSerif.ttf"))
+ chars = "these should be subsetted! 1234567890"
+
+ # non-subsetted FT2Font
+ nosubfont = FT2Font(fpath)
+ nosubfont.set_text(chars)
+
+ # subsetted FT2Font
+ with get_glyphs_subset(fpath, chars) as subset:
+ subfont = FT2Font(font_as_file(subset))
+ subfont.set_text(chars)
+
+ nosubcmap = nosubfont.get_charmap()
+ subcmap = subfont.get_charmap()
+
+ # all unique chars must be available in subsetted font
+ assert {*chars} == {chr(key) for key in subcmap}
+
+ # subsetted font's charmap should have less entries
+ assert len(subcmap) < len(nosubcmap)
+
+ # since both objects are assigned same characters
+ assert subfont.get_num_glyphs() == nosubfont.get_num_glyphs()
+
+
+@image_comparison(["multi_font_type3.pdf"], tol=4.6)
+def test_multi_font_type3():
+ fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
+ if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
+ pytest.skip("Font may be missing")
+
+ plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27)
+ plt.rc('pdf', fonttype=3)
+
+ fig = plt.figure()
+ fig.text(0.15, 0.475, "There are å‡ ä¸ªæ±‰å— in between!")
+
+
+@image_comparison(["multi_font_type42.pdf"], tol=2.2)
+def test_multi_font_type42():
+ fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
+ if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
+ pytest.skip("Font may be missing")
+
+ plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27)
+ plt.rc('pdf', fonttype=42)
+
+ fig = plt.figure()
+ fig.text(0.15, 0.475, "There are å‡ ä¸ªæ±‰å— in between!")
+
+
+@pytest.mark.parametrize('family_name, file_name',
+ [("Noto Sans", "NotoSans-Regular.otf"),
+ ("FreeMono", "FreeMono.otf")])
+def test_otf_font_smoke(family_name, file_name):
+ # checks that there's no segfault
+ fp = fm.FontProperties(family=[family_name])
+ if Path(fm.findfont(fp)).name != file_name:
+ pytest.skip(f"Font {family_name} may be missing")
+
+ plt.rc('font', family=[family_name], size=27)
+
+ fig = plt.figure()
+ fig.text(0.15, 0.475, "Привет мир!")
+ fig.savefig(io.BytesIO(), format="pdf")
+
+
+@image_comparison(["truetype-conversion.pdf"])
+# mpltest.ttf does not have "l"/"p" glyphs so we get a warning when trying to
+# get the font extents.
+def test_truetype_conversion(recwarn):
+ mpl.rcParams['pdf.fonttype'] = 3
+ fig, ax = plt.subplots()
+ ax.text(0, 0, "ABCDE",
+ font=Path(__file__).with_name("mpltest.ttf"), fontsize=80)
+ ax.set_xticks([])
+ ax.set_yticks([])
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_pgf.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_pgf.py
new file mode 100644
index 0000000000000000000000000000000000000000..e218a81cdceb9f6cdc4594467f5313f35e4592ab
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_pgf.py
@@ -0,0 +1,402 @@
+import datetime
+from io import BytesIO
+import os
+import shutil
+
+import numpy as np
+from packaging.version import parse as parse_version
+import pytest
+
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+from matplotlib.testing import _has_tex_package, _check_for_pgf
+from matplotlib.testing.exceptions import ImageComparisonFailure
+from matplotlib.testing.compare import compare_images
+from matplotlib.backends.backend_pgf import PdfPages
+from matplotlib.testing.decorators import (
+ _image_directories, check_figures_equal, image_comparison)
+from matplotlib.testing._markers import (
+ needs_ghostscript, needs_pgf_lualatex, needs_pgf_pdflatex,
+ needs_pgf_xelatex)
+
+
+baseline_dir, result_dir = _image_directories(lambda: 'dummy func')
+
+
+def compare_figure(fname, savefig_kwargs={}, tol=0):
+ actual = os.path.join(result_dir, fname)
+ plt.savefig(actual, **savefig_kwargs)
+
+ expected = os.path.join(result_dir, "expected_%s" % fname)
+ shutil.copyfile(os.path.join(baseline_dir, fname), expected)
+ err = compare_images(expected, actual, tol=tol)
+ if err:
+ raise ImageComparisonFailure(err)
+
+
+@needs_pgf_xelatex
+@needs_ghostscript
+@pytest.mark.backend('pgf')
+def test_tex_special_chars(tmp_path):
+ fig = plt.figure()
+ fig.text(.5, .5, "%_^ $a_b^c$")
+ buf = BytesIO()
+ fig.savefig(buf, format="png", backend="pgf")
+ buf.seek(0)
+ t = plt.imread(buf)
+ assert not (t == 1).all() # The leading "%" didn't eat up everything.
+
+
+def create_figure():
+ plt.figure()
+ x = np.linspace(0, 1, 15)
+
+ # line plot
+ plt.plot(x, x ** 2, "b-")
+
+ # marker
+ plt.plot(x, 1 - x**2, "g>")
+
+ # filled paths and patterns
+ plt.fill_between([0., .4], [.4, 0.], hatch='//', facecolor="lightgray",
+ edgecolor="red")
+ plt.fill([3, 3, .8, .8, 3], [2, -2, -2, 0, 2], "b")
+
+ # text and typesetting
+ plt.plot([0.9], [0.5], "ro", markersize=3)
+ plt.text(0.9, 0.5, 'unicode (ü, °, \N{Section Sign}) and math ($\\mu_i = x_i^2$)',
+ ha='right', fontsize=20)
+ plt.ylabel('sans-serif, blue, $\\frac{\\sqrt{x}}{y^2}$..',
+ family='sans-serif', color='blue')
+ plt.text(1, 1, 'should be clipped as default clip_box is Axes bbox',
+ fontsize=20, clip_on=True)
+
+ plt.xlim(0, 1)
+ plt.ylim(0, 1)
+
+
+# test compiling a figure to pdf with xelatex
+@needs_pgf_xelatex
+@pytest.mark.backend('pgf')
+@image_comparison(['pgf_xelatex.pdf'], style='default')
+def test_xelatex():
+ rc_xelatex = {'font.family': 'serif',
+ 'pgf.rcfonts': False}
+ mpl.rcParams.update(rc_xelatex)
+ create_figure()
+
+
+try:
+ _old_gs_version = \
+ mpl._get_executable_info('gs').version < parse_version('9.50')
+except mpl.ExecutableNotFoundError:
+ _old_gs_version = True
+
+
+# test compiling a figure to pdf with pdflatex
+@needs_pgf_pdflatex
+@pytest.mark.skipif(not _has_tex_package('type1ec'), reason='needs type1ec.sty')
+@pytest.mark.skipif(not _has_tex_package('ucs'), reason='needs ucs.sty')
+@pytest.mark.backend('pgf')
+@image_comparison(['pgf_pdflatex.pdf'], style='default',
+ tol=11.71 if _old_gs_version else 0)
+def test_pdflatex():
+ rc_pdflatex = {'font.family': 'serif',
+ 'pgf.rcfonts': False,
+ 'pgf.texsystem': 'pdflatex',
+ 'pgf.preamble': ('\\usepackage[utf8x]{inputenc}'
+ '\\usepackage[T1]{fontenc}')}
+ mpl.rcParams.update(rc_pdflatex)
+ create_figure()
+
+
+# test updating the rc parameters for each figure
+@needs_pgf_xelatex
+@needs_pgf_pdflatex
+@mpl.style.context('default')
+@pytest.mark.backend('pgf')
+def test_rcupdate():
+ rc_sets = [{'font.family': 'sans-serif',
+ 'font.size': 30,
+ 'figure.subplot.left': .2,
+ 'lines.markersize': 10,
+ 'pgf.rcfonts': False,
+ 'pgf.texsystem': 'xelatex'},
+ {'font.family': 'monospace',
+ 'font.size': 10,
+ 'figure.subplot.left': .1,
+ 'lines.markersize': 20,
+ 'pgf.rcfonts': False,
+ 'pgf.texsystem': 'pdflatex',
+ 'pgf.preamble': ('\\usepackage[utf8x]{inputenc}'
+ '\\usepackage[T1]{fontenc}'
+ '\\usepackage{sfmath}')}]
+ tol = [0, 13.2] if _old_gs_version else [0, 0]
+ for i, rc_set in enumerate(rc_sets):
+ with mpl.rc_context(rc_set):
+ for substring, pkg in [('sfmath', 'sfmath'), ('utf8x', 'ucs')]:
+ if (substring in mpl.rcParams['pgf.preamble']
+ and not _has_tex_package(pkg)):
+ pytest.skip(f'needs {pkg}.sty')
+ create_figure()
+ compare_figure(f'pgf_rcupdate{i + 1}.pdf', tol=tol[i])
+
+
+# test backend-side clipping, since large numbers are not supported by TeX
+@needs_pgf_xelatex
+@mpl.style.context('default')
+@pytest.mark.backend('pgf')
+def test_pathclip():
+ np.random.seed(19680801)
+ mpl.rcParams.update({'font.family': 'serif', 'pgf.rcfonts': False})
+ fig, axs = plt.subplots(1, 2)
+
+ axs[0].plot([0., 1e100], [0., 1e100])
+ axs[0].set_xlim(0, 1)
+ axs[0].set_ylim(0, 1)
+
+ axs[1].scatter([0, 1], [1, 1])
+ axs[1].hist(np.random.normal(size=1000), bins=20, range=[-10, 10])
+ axs[1].set_xscale('log')
+
+ fig.savefig(BytesIO(), format="pdf") # No image comparison.
+
+
+# test mixed mode rendering
+@needs_pgf_xelatex
+@pytest.mark.backend('pgf')
+@image_comparison(['pgf_mixedmode.pdf'], style='default')
+def test_mixedmode():
+ mpl.rcParams.update({'font.family': 'serif', 'pgf.rcfonts': False})
+ Y, X = np.ogrid[-1:1:40j, -1:1:40j]
+ plt.pcolor(X**2 + Y**2).set_rasterized(True)
+
+
+# test bbox_inches clipping
+@needs_pgf_xelatex
+@mpl.style.context('default')
+@pytest.mark.backend('pgf')
+def test_bbox_inches():
+ mpl.rcParams.update({'font.family': 'serif', 'pgf.rcfonts': False})
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+ ax1.plot(range(5))
+ ax2.plot(range(5))
+ plt.tight_layout()
+ bbox = ax1.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
+ compare_figure('pgf_bbox_inches.pdf', savefig_kwargs={'bbox_inches': bbox},
+ tol=0)
+
+
+@mpl.style.context('default')
+@pytest.mark.backend('pgf')
+@pytest.mark.parametrize('system', [
+ pytest.param('lualatex', marks=[needs_pgf_lualatex]),
+ pytest.param('pdflatex', marks=[needs_pgf_pdflatex]),
+ pytest.param('xelatex', marks=[needs_pgf_xelatex]),
+])
+def test_pdf_pages(system):
+ rc_pdflatex = {
+ 'font.family': 'serif',
+ 'pgf.rcfonts': False,
+ 'pgf.texsystem': system,
+ }
+ mpl.rcParams.update(rc_pdflatex)
+
+ fig1, ax1 = plt.subplots()
+ ax1.plot(range(5))
+ fig1.tight_layout()
+
+ fig2, ax2 = plt.subplots(figsize=(3, 2))
+ ax2.plot(range(5))
+ fig2.tight_layout()
+
+ path = os.path.join(result_dir, f'pdfpages_{system}.pdf')
+ md = {
+ 'Author': 'me',
+ 'Title': 'Multipage PDF with pgf',
+ 'Subject': 'Test page',
+ 'Keywords': 'test,pdf,multipage',
+ 'ModDate': datetime.datetime(
+ 1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
+ 'Trapped': 'Unknown'
+ }
+
+ with PdfPages(path, metadata=md) as pdf:
+ pdf.savefig(fig1)
+ pdf.savefig(fig2)
+ pdf.savefig(fig1)
+
+ assert pdf.get_pagecount() == 3
+
+
+@mpl.style.context('default')
+@pytest.mark.backend('pgf')
+@pytest.mark.parametrize('system', [
+ pytest.param('lualatex', marks=[needs_pgf_lualatex]),
+ pytest.param('pdflatex', marks=[needs_pgf_pdflatex]),
+ pytest.param('xelatex', marks=[needs_pgf_xelatex]),
+])
+def test_pdf_pages_metadata_check(monkeypatch, system):
+ # Basically the same as test_pdf_pages, but we keep it separate to leave
+ # pikepdf as an optional dependency.
+ pikepdf = pytest.importorskip('pikepdf')
+ monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
+
+ mpl.rcParams.update({'pgf.texsystem': system})
+
+ fig, ax = plt.subplots()
+ ax.plot(range(5))
+
+ md = {
+ 'Author': 'me',
+ 'Title': 'Multipage PDF with pgf',
+ 'Subject': 'Test page',
+ 'Keywords': 'test,pdf,multipage',
+ 'ModDate': datetime.datetime(
+ 1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
+ 'Trapped': 'True'
+ }
+ path = os.path.join(result_dir, f'pdfpages_meta_check_{system}.pdf')
+ with PdfPages(path, metadata=md) as pdf:
+ pdf.savefig(fig)
+
+ with pikepdf.Pdf.open(path) as pdf:
+ info = {k: str(v) for k, v in pdf.docinfo.items()}
+
+ # Not set by us, so don't bother checking.
+ if '/PTEX.FullBanner' in info:
+ del info['/PTEX.FullBanner']
+ if '/PTEX.Fullbanner' in info:
+ del info['/PTEX.Fullbanner']
+
+ # Some LaTeX engines ignore this setting, and state themselves as producer.
+ producer = info.pop('/Producer')
+ assert producer == f'Matplotlib pgf backend v{mpl.__version__}' or (
+ system == 'lualatex' and 'LuaTeX' in producer)
+
+ assert info == {
+ '/Author': 'me',
+ '/CreationDate': 'D:19700101000000Z',
+ '/Creator': f'Matplotlib v{mpl.__version__}, https://matplotlib.org',
+ '/Keywords': 'test,pdf,multipage',
+ '/ModDate': 'D:19680801000000Z',
+ '/Subject': 'Test page',
+ '/Title': 'Multipage PDF with pgf',
+ '/Trapped': '/True',
+ }
+
+
+@needs_pgf_xelatex
+def test_multipage_keep_empty(tmp_path):
+ # An empty pdf deletes itself afterwards.
+ fn = tmp_path / "a.pdf"
+ with PdfPages(fn) as pdf:
+ pass
+ assert not fn.exists()
+
+ # Test pdf files with content, they should never be deleted.
+ fn = tmp_path / "b.pdf"
+ with PdfPages(fn) as pdf:
+ pdf.savefig(plt.figure())
+ assert fn.exists()
+
+
+@needs_pgf_xelatex
+def test_tex_restart_after_error():
+ fig = plt.figure()
+ fig.suptitle(r"\oops")
+ with pytest.raises(ValueError):
+ fig.savefig(BytesIO(), format="pgf")
+
+ fig = plt.figure() # start from scratch
+ fig.suptitle(r"this is ok")
+ fig.savefig(BytesIO(), format="pgf")
+
+
+@needs_pgf_xelatex
+def test_bbox_inches_tight():
+ fig, ax = plt.subplots()
+ ax.imshow([[0, 1], [2, 3]])
+ fig.savefig(BytesIO(), format="pdf", backend="pgf", bbox_inches="tight")
+
+
+@needs_pgf_xelatex
+@needs_ghostscript
+def test_png_transparency(): # Actually, also just testing that png works.
+ buf = BytesIO()
+ plt.figure().savefig(buf, format="png", backend="pgf", transparent=True)
+ buf.seek(0)
+ t = plt.imread(buf)
+ assert (t[..., 3] == 0).all() # fully transparent.
+
+
+@needs_pgf_xelatex
+def test_unknown_font(caplog):
+ with caplog.at_level("WARNING"):
+ mpl.rcParams["font.family"] = "this-font-does-not-exist"
+ plt.figtext(.5, .5, "hello, world")
+ plt.savefig(BytesIO(), format="pgf")
+ assert "Ignoring unknown font: this-font-does-not-exist" in [
+ r.getMessage() for r in caplog.records]
+
+
+@check_figures_equal(extensions=["pdf"])
+@pytest.mark.parametrize("texsystem", ("pdflatex", "xelatex", "lualatex"))
+@pytest.mark.backend("pgf")
+def test_minus_signs_with_tex(fig_test, fig_ref, texsystem):
+ if not _check_for_pgf(texsystem):
+ pytest.skip(texsystem + ' + pgf is required')
+ mpl.rcParams["pgf.texsystem"] = texsystem
+ fig_test.text(.5, .5, "$-1$")
+ fig_ref.text(.5, .5, "$\N{MINUS SIGN}1$")
+
+
+@pytest.mark.backend("pgf")
+def test_sketch_params():
+ fig, ax = plt.subplots(figsize=(3, 3))
+ ax.set_xticks([])
+ ax.set_yticks([])
+ ax.set_frame_on(False)
+ handle, = ax.plot([0, 1])
+ handle.set_sketch_params(scale=5, length=30, randomness=42)
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format='pgf')
+ buf = fd.getvalue().decode()
+
+ baseline = r"""\pgfpathmoveto{\pgfqpoint{0.375000in}{0.300000in}}%
+\pgfpathlineto{\pgfqpoint{2.700000in}{2.700000in}}%
+\usepgfmodule{decorations}%
+\usepgflibrary{decorations.pathmorphing}%
+\pgfkeys{/pgf/decoration/.cd, """ \
+ r"""segment length = 0.150000in, amplitude = 0.100000in}%
+\pgfmathsetseed{42}%
+\pgfdecoratecurrentpath{random steps}%
+\pgfusepath{stroke}%"""
+ # \pgfdecoratecurrentpath must be after the path definition and before the
+ # path is used (\pgfusepath)
+ assert baseline in buf
+
+
+# test to make sure that the document font size is set consistently (see #26892)
+@needs_pgf_xelatex
+@pytest.mark.skipif(
+ not _has_tex_package('unicode-math'), reason='needs unicode-math.sty'
+)
+@pytest.mark.backend('pgf')
+@image_comparison(['pgf_document_font_size.pdf'], style='default', remove_text=True)
+def test_document_font_size():
+ mpl.rcParams.update({
+ 'pgf.texsystem': 'xelatex',
+ 'pgf.rcfonts': False,
+ 'pgf.preamble': r'\usepackage{unicode-math}',
+ })
+ plt.figure()
+ plt.plot([],
+ label=r'$this is a very very very long math label a \times b + 10^{-3}$ '
+ r'and some text'
+ )
+ plt.plot([],
+ label=r'\normalsize the document font size is \the\fontdimen6\font'
+ )
+ plt.legend()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_ps.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_ps.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc968795802e47e326dfdcfeda5d6133bcbf6149
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_ps.py
@@ -0,0 +1,380 @@
+from collections import Counter
+from pathlib import Path
+import io
+import re
+import tempfile
+
+import numpy as np
+import pytest
+
+from matplotlib import cbook, path, patheffects, font_manager as fm
+from matplotlib.figure import Figure
+from matplotlib.patches import Ellipse
+from matplotlib.testing._markers import needs_ghostscript, needs_usetex
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+import matplotlib as mpl
+import matplotlib.collections as mcollections
+import matplotlib.colors as mcolors
+import matplotlib.pyplot as plt
+
+
+# This tests tends to hit a TeX cache lock on AppVeyor.
+@pytest.mark.flaky(reruns=3)
+@pytest.mark.parametrize('papersize', ['letter', 'figure'])
+@pytest.mark.parametrize('orientation', ['portrait', 'landscape'])
+@pytest.mark.parametrize('format, use_log, rcParams', [
+ ('ps', False, {}),
+ ('ps', False, {'ps.usedistiller': 'ghostscript'}),
+ ('ps', False, {'ps.usedistiller': 'xpdf'}),
+ ('ps', False, {'text.usetex': True}),
+ ('eps', False, {}),
+ ('eps', True, {'ps.useafm': True}),
+ ('eps', False, {'text.usetex': True}),
+], ids=[
+ 'ps',
+ 'ps with distiller=ghostscript',
+ 'ps with distiller=xpdf',
+ 'ps with usetex',
+ 'eps',
+ 'eps afm',
+ 'eps with usetex'
+])
+def test_savefig_to_stringio(format, use_log, rcParams, orientation, papersize):
+ mpl.rcParams.update(rcParams)
+ if mpl.rcParams["ps.usedistiller"] == "ghostscript":
+ try:
+ mpl._get_executable_info("gs")
+ except mpl.ExecutableNotFoundError as exc:
+ pytest.skip(str(exc))
+ elif mpl.rcParams["ps.usedistiller"] == "xpdf":
+ try:
+ mpl._get_executable_info("gs") # Effectively checks for ps2pdf.
+ mpl._get_executable_info("pdftops")
+ except mpl.ExecutableNotFoundError as exc:
+ pytest.skip(str(exc))
+
+ fig, ax = plt.subplots()
+
+ with io.StringIO() as s_buf, io.BytesIO() as b_buf:
+
+ if use_log:
+ ax.set_yscale('log')
+
+ ax.plot([1, 2], [1, 2])
+ title = "Déjà vu"
+ if not mpl.rcParams["text.usetex"]:
+ title += " \N{MINUS SIGN}\N{EURO SIGN}"
+ ax.set_title(title)
+ allowable_exceptions = []
+ if mpl.rcParams["text.usetex"]:
+ allowable_exceptions.append(RuntimeError)
+ if mpl.rcParams["ps.useafm"]:
+ allowable_exceptions.append(mpl.MatplotlibDeprecationWarning)
+ try:
+ fig.savefig(s_buf, format=format, orientation=orientation,
+ papertype=papersize)
+ fig.savefig(b_buf, format=format, orientation=orientation,
+ papertype=papersize)
+ except tuple(allowable_exceptions) as exc:
+ pytest.skip(str(exc))
+
+ assert not s_buf.closed
+ assert not b_buf.closed
+ s_val = s_buf.getvalue().encode('ascii')
+ b_val = b_buf.getvalue()
+
+ if format == 'ps':
+ # Default figsize = (8, 6) inches = (576, 432) points = (203.2, 152.4) mm.
+ # Landscape orientation will swap dimensions.
+ if mpl.rcParams["ps.usedistiller"] == "xpdf":
+ # Some versions specifically show letter/203x152, but not all,
+ # so we can only use this simpler test.
+ if papersize == 'figure':
+ assert b'letter' not in s_val.lower()
+ else:
+ assert b'letter' in s_val.lower()
+ elif mpl.rcParams["ps.usedistiller"] or mpl.rcParams["text.usetex"]:
+ width = b'432.0' if orientation == 'landscape' else b'576.0'
+ wanted = (b'-dDEVICEWIDTHPOINTS=' + width if papersize == 'figure'
+ else b'-sPAPERSIZE')
+ assert wanted in s_val
+ else:
+ if papersize == 'figure':
+ assert b'%%DocumentPaperSizes' not in s_val
+ else:
+ assert b'%%DocumentPaperSizes' in s_val
+
+ # Strip out CreationDate: ghostscript and cairo don't obey
+ # SOURCE_DATE_EPOCH, and that environment variable is already tested in
+ # test_determinism.
+ s_val = re.sub(b"(?<=\n%%CreationDate: ).*", b"", s_val)
+ b_val = re.sub(b"(?<=\n%%CreationDate: ).*", b"", b_val)
+
+ assert s_val == b_val.replace(b'\r\n', b'\n')
+
+
+def test_patheffects():
+ mpl.rcParams['path.effects'] = [
+ patheffects.withStroke(linewidth=4, foreground='w')]
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3])
+ with io.BytesIO() as ps:
+ fig.savefig(ps, format='ps')
+
+
+@needs_usetex
+@needs_ghostscript
+def test_tilde_in_tempfilename(tmp_path):
+ # Tilde ~ in the tempdir path (e.g. TMPDIR, TMP or TEMP on windows
+ # when the username is very long and windows uses a short name) breaks
+ # latex before https://github.com/matplotlib/matplotlib/pull/5928
+ base_tempdir = tmp_path / "short-1"
+ base_tempdir.mkdir()
+ # Change the path for new tempdirs, which is used internally by the ps
+ # backend to write a file.
+ with cbook._setattr_cm(tempfile, tempdir=str(base_tempdir)):
+ # usetex results in the latex call, which does not like the ~
+ mpl.rcParams['text.usetex'] = True
+ plt.plot([1, 2, 3, 4])
+ plt.xlabel(r'\textbf{time} (s)')
+ # use the PS backend to write the file...
+ plt.savefig(base_tempdir / 'tex_demo.eps', format="ps")
+
+
+@image_comparison(["empty.eps"])
+def test_transparency():
+ fig, ax = plt.subplots()
+ ax.set_axis_off()
+ ax.plot([0, 1], color="r", alpha=0)
+ ax.text(.5, .5, "foo", color="r", alpha=0)
+
+
+@needs_usetex
+@image_comparison(["empty.eps"])
+def test_transparency_tex():
+ mpl.rcParams['text.usetex'] = True
+ fig, ax = plt.subplots()
+ ax.set_axis_off()
+ ax.plot([0, 1], color="r", alpha=0)
+ ax.text(.5, .5, "foo", color="r", alpha=0)
+
+
+def test_bbox():
+ fig, ax = plt.subplots()
+ with io.BytesIO() as buf:
+ fig.savefig(buf, format='eps')
+ buf = buf.getvalue()
+
+ bb = re.search(b'^%%BoundingBox: (.+) (.+) (.+) (.+)$', buf, re.MULTILINE)
+ assert bb
+ hibb = re.search(b'^%%HiResBoundingBox: (.+) (.+) (.+) (.+)$', buf,
+ re.MULTILINE)
+ assert hibb
+
+ for i in range(1, 5):
+ # BoundingBox must use integers, and be ceil/floor of the hi res.
+ assert b'.' not in bb.group(i)
+ assert int(bb.group(i)) == pytest.approx(float(hibb.group(i)), 1)
+
+
+@needs_usetex
+def test_failing_latex():
+ """Test failing latex subprocess call"""
+ mpl.rcParams['text.usetex'] = True
+ # This fails with "Double subscript"
+ plt.xlabel("$22_2_2$")
+ with pytest.raises(RuntimeError):
+ plt.savefig(io.BytesIO(), format="ps")
+
+
+@needs_usetex
+def test_partial_usetex(caplog):
+ caplog.set_level("WARNING")
+ plt.figtext(.1, .1, "foo", usetex=True)
+ plt.figtext(.2, .2, "bar", usetex=True)
+ plt.savefig(io.BytesIO(), format="ps")
+ record, = caplog.records # asserts there's a single record.
+ assert "as if usetex=False" in record.getMessage()
+
+
+@needs_usetex
+def test_usetex_preamble(caplog):
+ mpl.rcParams.update({
+ "text.usetex": True,
+ # Check that these don't conflict with the packages loaded by default.
+ "text.latex.preamble": r"\usepackage{color,graphicx,textcomp}",
+ })
+ plt.figtext(.5, .5, "foo")
+ plt.savefig(io.BytesIO(), format="ps")
+
+
+@image_comparison(["useafm.eps"])
+def test_useafm():
+ mpl.rcParams["ps.useafm"] = True
+ fig, ax = plt.subplots()
+ ax.set_axis_off()
+ ax.axhline(.5)
+ ax.text(.5, .5, "qk")
+
+
+@image_comparison(["type3.eps"])
+def test_type3_font():
+ plt.figtext(.5, .5, "I/J")
+
+
+@image_comparison(["coloredhatcheszerolw.eps"])
+def test_colored_hatch_zero_linewidth():
+ ax = plt.gca()
+ ax.add_patch(Ellipse((0, 0), 1, 1, hatch='/', facecolor='none',
+ edgecolor='r', linewidth=0))
+ ax.add_patch(Ellipse((0.5, 0.5), 0.5, 0.5, hatch='+', facecolor='none',
+ edgecolor='g', linewidth=0.2))
+ ax.add_patch(Ellipse((1, 1), 0.3, 0.8, hatch='\\', facecolor='none',
+ edgecolor='b', linewidth=0))
+ ax.set_axis_off()
+
+
+@check_figures_equal(extensions=["eps"])
+def test_text_clip(fig_test, fig_ref):
+ ax = fig_test.add_subplot()
+ # Fully clipped-out text should not appear.
+ ax.text(0, 0, "hello", transform=fig_test.transFigure, clip_on=True)
+ fig_ref.add_subplot()
+
+
+@needs_ghostscript
+def test_d_glyph(tmp_path):
+ # Ensure that we don't have a procedure defined as /d, which would be
+ # overwritten by the glyph definition for "d".
+ fig = plt.figure()
+ fig.text(.5, .5, "def")
+ out = tmp_path / "test.eps"
+ fig.savefig(out)
+ mpl.testing.compare.convert(out, cache=False) # Should not raise.
+
+
+@image_comparison(["type42_without_prep.eps"], style='mpl20')
+def test_type42_font_without_prep():
+ # Test whether Type 42 fonts without prep table are properly embedded
+ mpl.rcParams["ps.fonttype"] = 42
+ mpl.rcParams["mathtext.fontset"] = "stix"
+
+ plt.figtext(0.5, 0.5, "Mass $m$")
+
+
+@pytest.mark.parametrize('fonttype', ["3", "42"])
+def test_fonttype(fonttype):
+ mpl.rcParams["ps.fonttype"] = fonttype
+ fig, ax = plt.subplots()
+
+ ax.text(0.25, 0.5, "Forty-two is the answer to everything!")
+
+ buf = io.BytesIO()
+ fig.savefig(buf, format="ps")
+
+ test = b'/FontType ' + bytes(f"{fonttype}", encoding='utf-8') + b' def'
+
+ assert re.search(test, buf.getvalue(), re.MULTILINE)
+
+
+def test_linedash():
+ """Test that dashed lines do not break PS output"""
+ fig, ax = plt.subplots()
+
+ ax.plot([0, 1], linestyle="--")
+
+ buf = io.BytesIO()
+ fig.savefig(buf, format="ps")
+
+ assert buf.tell() > 0
+
+
+def test_empty_line():
+ # Smoke-test for gh#23954
+ figure = Figure()
+ figure.text(0.5, 0.5, "\nfoo\n\n")
+ buf = io.BytesIO()
+ figure.savefig(buf, format='eps')
+ figure.savefig(buf, format='ps')
+
+
+def test_no_duplicate_definition():
+
+ fig = Figure()
+ axs = fig.subplots(4, 4, subplot_kw=dict(projection="polar"))
+ for ax in axs.flat:
+ ax.set(xticks=[], yticks=[])
+ ax.plot([1, 2])
+ fig.suptitle("hello, world")
+
+ buf = io.StringIO()
+ fig.savefig(buf, format='eps')
+ buf.seek(0)
+
+ wds = [ln.partition(' ')[0] for
+ ln in buf.readlines()
+ if ln.startswith('/')]
+
+ assert max(Counter(wds).values()) == 1
+
+
+@image_comparison(["multi_font_type3.eps"], tol=0.51)
+def test_multi_font_type3():
+ fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
+ if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
+ pytest.skip("Font may be missing")
+
+ plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27)
+ plt.rc('ps', fonttype=3)
+
+ fig = plt.figure()
+ fig.text(0.15, 0.475, "There are å‡ ä¸ªæ±‰å— in between!")
+
+
+@image_comparison(["multi_font_type42.eps"], tol=1.6)
+def test_multi_font_type42():
+ fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
+ if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
+ pytest.skip("Font may be missing")
+
+ plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27)
+ plt.rc('ps', fonttype=42)
+
+ fig = plt.figure()
+ fig.text(0.15, 0.475, "There are å‡ ä¸ªæ±‰å— in between!")
+
+
+@image_comparison(["scatter.eps"])
+def test_path_collection():
+ rng = np.random.default_rng(19680801)
+ xvals = rng.uniform(0, 1, 10)
+ yvals = rng.uniform(0, 1, 10)
+ sizes = rng.uniform(30, 100, 10)
+ fig, ax = plt.subplots()
+ ax.scatter(xvals, yvals, sizes, edgecolor=[0.9, 0.2, 0.1], marker='<')
+ ax.set_axis_off()
+ paths = [path.Path.unit_regular_polygon(i) for i in range(3, 7)]
+ offsets = rng.uniform(0, 200, 20).reshape(10, 2)
+ sizes = [0.02, 0.04]
+ pc = mcollections.PathCollection(paths, sizes, zorder=-1,
+ facecolors='yellow', offsets=offsets)
+ ax.add_collection(pc)
+ ax.set_xlim(0, 1)
+
+
+@image_comparison(["colorbar_shift.eps"], savefig_kwarg={"bbox_inches": "tight"},
+ style="mpl20")
+def test_colorbar_shift(tmp_path):
+ cmap = mcolors.ListedColormap(["r", "g", "b"])
+ norm = mcolors.BoundaryNorm([-1, -0.5, 0.5, 1], cmap.N)
+ plt.scatter([0, 1], [1, 1], c=[0, 1], cmap=cmap, norm=norm)
+ plt.colorbar()
+
+
+def test_auto_papersize_removal():
+ fig = plt.figure()
+ with pytest.raises(ValueError, match="'auto' is not a valid value"):
+ fig.savefig(io.BytesIO(), format='eps', papertype='auto')
+
+ with pytest.raises(ValueError, match="'auto' is not a valid value"):
+ mpl.rcParams['ps.papersize'] = 'auto'
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_qt.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_qt.py
new file mode 100644
index 0000000000000000000000000000000000000000..1012b16f02127776e2c8b7590862372d01b279fc
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_qt.py
@@ -0,0 +1,388 @@
+import copy
+import importlib
+import os
+import signal
+import sys
+
+from datetime import date, datetime
+from unittest import mock
+
+import pytest
+
+import matplotlib
+from matplotlib import pyplot as plt
+from matplotlib._pylab_helpers import Gcf
+from matplotlib import _c_internal_utils
+
+try:
+ from matplotlib.backends.qt_compat import QtGui # type: ignore[attr-defined] # noqa: E501, F401
+ from matplotlib.backends.qt_compat import QtWidgets # type: ignore[attr-defined]
+ from matplotlib.backends.qt_editor import _formlayout
+except ImportError:
+ pytestmark = pytest.mark.skip('No usable Qt bindings')
+
+
+_test_timeout = 60 # A reasonably safe value for slower architectures.
+
+
+@pytest.fixture
+def qt_core(request):
+ from matplotlib.backends.qt_compat import QtCore
+ return QtCore
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_fig_close():
+
+ # save the state of Gcf.figs
+ init_figs = copy.copy(Gcf.figs)
+
+ # make a figure using pyplot interface
+ fig = plt.figure()
+
+ # simulate user clicking the close button by reaching in
+ # and calling close on the underlying Qt object
+ fig.canvas.manager.window.close()
+
+ # assert that we have removed the reference to the FigureManager
+ # that got added by plt.figure()
+ assert init_figs == Gcf.figs
+
+
+@pytest.mark.parametrize(
+ "qt_key, qt_mods, answer",
+ [
+ ("Key_A", ["ShiftModifier"], "A"),
+ ("Key_A", [], "a"),
+ ("Key_A", ["ControlModifier"], ("ctrl+a")),
+ (
+ "Key_Aacute",
+ ["ShiftModifier"],
+ "\N{LATIN CAPITAL LETTER A WITH ACUTE}",
+ ),
+ ("Key_Aacute", [], "\N{LATIN SMALL LETTER A WITH ACUTE}"),
+ ("Key_Control", ["AltModifier"], ("alt+control")),
+ ("Key_Alt", ["ControlModifier"], "ctrl+alt"),
+ (
+ "Key_Aacute",
+ ["ControlModifier", "AltModifier", "MetaModifier"],
+ ("ctrl+alt+meta+\N{LATIN SMALL LETTER A WITH ACUTE}"),
+ ),
+ # We do not currently map the media keys, this may change in the
+ # future. This means the callback will never fire
+ ("Key_Play", [], None),
+ ("Key_Backspace", [], "backspace"),
+ (
+ "Key_Backspace",
+ ["ControlModifier"],
+ "ctrl+backspace",
+ ),
+ ],
+ ids=[
+ 'shift',
+ 'lower',
+ 'control',
+ 'unicode_upper',
+ 'unicode_lower',
+ 'alt_control',
+ 'control_alt',
+ 'modifier_order',
+ 'non_unicode_key',
+ 'backspace',
+ 'backspace_mod',
+ ]
+)
+@pytest.mark.parametrize('backend', [
+ # Note: the value is irrelevant; the important part is the marker.
+ pytest.param(
+ 'Qt5Agg',
+ marks=pytest.mark.backend('Qt5Agg', skip_on_importerror=True)),
+ pytest.param(
+ 'QtAgg',
+ marks=pytest.mark.backend('QtAgg', skip_on_importerror=True)),
+])
+def test_correct_key(backend, qt_core, qt_key, qt_mods, answer, monkeypatch):
+ """
+ Make a figure.
+ Send a key_press_event event (using non-public, qtX backend specific api).
+ Catch the event.
+ Assert sent and caught keys are the same.
+ """
+ from matplotlib.backends.qt_compat import _to_int, QtCore
+
+ if sys.platform == "darwin" and answer is not None:
+ answer = answer.replace("ctrl", "cmd")
+ answer = answer.replace("control", "cmd")
+ answer = answer.replace("meta", "ctrl")
+ result = None
+ qt_mod = QtCore.Qt.KeyboardModifier.NoModifier
+ for mod in qt_mods:
+ qt_mod |= getattr(QtCore.Qt.KeyboardModifier, mod)
+
+ class _Event:
+ def isAutoRepeat(self): return False
+ def key(self): return _to_int(getattr(QtCore.Qt.Key, qt_key))
+
+ monkeypatch.setattr(QtWidgets.QApplication, "keyboardModifiers",
+ lambda self: qt_mod)
+
+ def on_key_press(event):
+ nonlocal result
+ result = event.key
+
+ qt_canvas = plt.figure().canvas
+ qt_canvas.mpl_connect('key_press_event', on_key_press)
+ qt_canvas.keyPressEvent(_Event())
+ assert result == answer
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_device_pixel_ratio_change(qt_core):
+ """
+ Make sure that if the pixel ratio changes, the figure dpi changes but the
+ widget remains the same logical size.
+ """
+
+ prop = 'matplotlib.backends.backend_qt.FigureCanvasQT.devicePixelRatioF'
+ with mock.patch(prop) as p:
+ p.return_value = 3
+
+ fig = plt.figure(figsize=(5, 2), dpi=120)
+ qt_canvas = fig.canvas
+ qt_canvas.show()
+
+ def set_device_pixel_ratio(ratio):
+ p.return_value = ratio
+
+ window = qt_canvas.window().windowHandle()
+ current_version = tuple(
+ int(x) for x in qt_core.qVersion().split('.', 2)[:2])
+ if current_version >= (6, 6):
+ qt_core.QCoreApplication.sendEvent(
+ window,
+ qt_core.QEvent(qt_core.QEvent.Type.DevicePixelRatioChange))
+ else:
+ # The value here doesn't matter, as we can't mock the C++ QScreen
+ # object, but can override the functional wrapper around it.
+ # Emitting this event is simply to trigger the DPI change handler
+ # in Matplotlib in the same manner that it would occur normally.
+ window.screen().logicalDotsPerInchChanged.emit(96)
+
+ qt_canvas.draw()
+ qt_canvas.flush_events()
+
+ # Make sure the mocking worked
+ assert qt_canvas.device_pixel_ratio == ratio
+
+ qt_canvas.manager.show()
+ qt_canvas.draw()
+ qt_canvas.flush_events()
+ size = qt_canvas.size()
+
+ options = [
+ (None, 360, 1800, 720), # Use ratio at startup time.
+ (3, 360, 1800, 720), # Change to same ratio.
+ (2, 240, 1200, 480), # Change to different ratio.
+ (1.5, 180, 900, 360), # Fractional ratio.
+ ]
+ for ratio, dpi, width, height in options:
+ if ratio is not None:
+ set_device_pixel_ratio(ratio)
+
+ # The DPI and the renderer width/height change
+ assert fig.dpi == dpi
+ assert qt_canvas.renderer.width == width
+ assert qt_canvas.renderer.height == height
+
+ # The actual widget size and figure logical size don't change.
+ assert size.width() == 600
+ assert size.height() == 240
+ assert qt_canvas.get_width_height() == (600, 240)
+ assert (fig.get_size_inches() == (5, 2)).all()
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_subplottool():
+ fig, ax = plt.subplots()
+ with mock.patch("matplotlib.backends.qt_compat._exec", lambda obj: None):
+ fig.canvas.manager.toolbar.configure_subplots()
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_figureoptions():
+ fig, ax = plt.subplots()
+ ax.plot([1, 2])
+ ax.imshow([[1]])
+ ax.scatter(range(3), range(3), c=range(3))
+ with mock.patch("matplotlib.backends.qt_compat._exec", lambda obj: None):
+ fig.canvas.manager.toolbar.edit_parameters()
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_save_figure_return(tmp_path):
+ fig, ax = plt.subplots()
+ ax.imshow([[1]])
+ expected = tmp_path / "foobar.png"
+ prop = "matplotlib.backends.qt_compat.QtWidgets.QFileDialog.getSaveFileName"
+ with mock.patch(prop, return_value=(str(expected), None)):
+ fname = fig.canvas.manager.toolbar.save_figure()
+ assert fname == str(expected)
+ assert expected.exists()
+ with mock.patch(prop, return_value=(None, None)):
+ fname = fig.canvas.manager.toolbar.save_figure()
+ assert fname is None
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_figureoptions_with_datetime_axes():
+ fig, ax = plt.subplots()
+ xydata = [
+ datetime(year=2021, month=1, day=1),
+ datetime(year=2021, month=2, day=1)
+ ]
+ ax.plot(xydata, xydata)
+ with mock.patch("matplotlib.backends.qt_compat._exec", lambda obj: None):
+ fig.canvas.manager.toolbar.edit_parameters()
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_double_resize():
+ # Check that resizing a figure twice keeps the same window size
+ fig, ax = plt.subplots()
+ fig.canvas.draw()
+ window = fig.canvas.manager.window
+
+ w, h = 3, 2
+ fig.set_size_inches(w, h)
+ assert fig.canvas.width() == w * matplotlib.rcParams['figure.dpi']
+ assert fig.canvas.height() == h * matplotlib.rcParams['figure.dpi']
+
+ old_width = window.width()
+ old_height = window.height()
+
+ fig.set_size_inches(w, h)
+ assert window.width() == old_width
+ assert window.height() == old_height
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_canvas_reinit():
+ from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
+
+ called = False
+
+ def crashing_callback(fig, stale):
+ nonlocal called
+ fig.canvas.draw_idle()
+ called = True
+
+ fig, ax = plt.subplots()
+ fig.stale_callback = crashing_callback
+ # this should not raise
+ canvas = FigureCanvasQTAgg(fig)
+ fig.stale = True
+ assert called
+
+
+@pytest.mark.backend('Qt5Agg', skip_on_importerror=True)
+def test_form_widget_get_with_datetime_and_date_fields():
+ from matplotlib.backends.backend_qt import _create_qApp
+ _create_qApp()
+
+ form = [
+ ("Datetime field", datetime(year=2021, month=3, day=11)),
+ ("Date field", date(year=2021, month=3, day=11))
+ ]
+ widget = _formlayout.FormWidget(form)
+ widget.setup()
+ values = widget.get()
+ assert values == [
+ datetime(year=2021, month=3, day=11),
+ date(year=2021, month=3, day=11)
+ ]
+
+
+def _get_testable_qt_backends():
+ envs = []
+ for deps, env in [
+ ([qt_api], {"MPLBACKEND": "qtagg", "QT_API": qt_api})
+ for qt_api in ["PyQt6", "PySide6", "PyQt5", "PySide2"]
+ ]:
+ reason = None
+ missing = [dep for dep in deps if not importlib.util.find_spec(dep)]
+ if (sys.platform == "linux" and
+ not _c_internal_utils.display_is_valid()):
+ reason = "$DISPLAY and $WAYLAND_DISPLAY are unset"
+ elif missing:
+ reason = "{} cannot be imported".format(", ".join(missing))
+ elif env["MPLBACKEND"] == 'macosx' and os.environ.get('TF_BUILD'):
+ reason = "macosx backend fails on Azure"
+ marks = []
+ if reason:
+ marks.append(pytest.mark.skip(
+ reason=f"Skipping {env} because {reason}"))
+ envs.append(pytest.param(env, marks=marks, id=str(env)))
+ return envs
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_fig_sigint_override(qt_core):
+ from matplotlib.backends.backend_qt5 import _BackendQT5
+ # Create a figure
+ plt.figure()
+
+ # Variable to access the handler from the inside of the event loop
+ event_loop_handler = None
+
+ # Callback to fire during event loop: save SIGINT handler, then exit
+ def fire_signal_and_quit():
+ # Save event loop signal
+ nonlocal event_loop_handler
+ event_loop_handler = signal.getsignal(signal.SIGINT)
+
+ # Request event loop exit
+ qt_core.QCoreApplication.exit()
+
+ # Timer to exit event loop
+ qt_core.QTimer.singleShot(0, fire_signal_and_quit)
+
+ # Save original SIGINT handler
+ original_handler = signal.getsignal(signal.SIGINT)
+
+ # Use our own SIGINT handler to be 100% sure this is working
+ def custom_handler(signum, frame):
+ pass
+
+ signal.signal(signal.SIGINT, custom_handler)
+
+ try:
+ # mainloop() sets SIGINT, starts Qt event loop (which triggers timer
+ # and exits) and then mainloop() resets SIGINT
+ matplotlib.backends.backend_qt._BackendQT.mainloop()
+
+ # Assert: signal handler during loop execution is changed
+ # (can't test equality with func)
+ assert event_loop_handler != custom_handler
+
+ # Assert: current signal handler is the same as the one we set before
+ assert signal.getsignal(signal.SIGINT) == custom_handler
+
+ # Repeat again to test that SIG_DFL and SIG_IGN will not be overridden
+ for custom_handler in (signal.SIG_DFL, signal.SIG_IGN):
+ qt_core.QTimer.singleShot(0, fire_signal_and_quit)
+ signal.signal(signal.SIGINT, custom_handler)
+
+ _BackendQT5.mainloop()
+
+ assert event_loop_handler == custom_handler
+ assert signal.getsignal(signal.SIGINT) == custom_handler
+
+ finally:
+ # Reset SIGINT handler to what it was before the test
+ signal.signal(signal.SIGINT, original_handler)
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_ipython():
+ from matplotlib.testing import ipython_in_subprocess
+ ipython_in_subprocess("qt", {(8, 24): "qtagg", (8, 15): "QtAgg", (7, 0): "Qt5Agg"})
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_registry.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..80c2ce4fc51a6869315a84a91fcced18a7a0443f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_registry.py
@@ -0,0 +1,180 @@
+from collections.abc import Sequence
+from typing import Any
+
+import pytest
+
+import matplotlib as mpl
+from matplotlib.backends import BackendFilter, backend_registry
+
+
+@pytest.fixture
+def clear_backend_registry():
+ # Fixture that clears the singleton backend_registry before and after use
+ # so that the test state remains isolated.
+ backend_registry._clear()
+ yield
+ backend_registry._clear()
+
+
+def has_duplicates(seq: Sequence[Any]) -> bool:
+ return len(seq) > len(set(seq))
+
+
+@pytest.mark.parametrize(
+ 'framework,expected',
+ [
+ ('qt', 'qtagg'),
+ ('gtk3', 'gtk3agg'),
+ ('gtk4', 'gtk4agg'),
+ ('wx', 'wxagg'),
+ ('tk', 'tkagg'),
+ ('macosx', 'macosx'),
+ ('headless', 'agg'),
+ ('does not exist', None),
+ ]
+)
+def test_backend_for_gui_framework(framework, expected):
+ assert backend_registry.backend_for_gui_framework(framework) == expected
+
+
+def test_list_builtin():
+ backends = backend_registry.list_builtin()
+ assert not has_duplicates(backends)
+ # Compare using sets as order is not important
+ assert {*backends} == {
+ 'gtk3agg', 'gtk3cairo', 'gtk4agg', 'gtk4cairo', 'macosx', 'nbagg', 'notebook',
+ 'qtagg', 'qtcairo', 'qt5agg', 'qt5cairo', 'tkagg',
+ 'tkcairo', 'webagg', 'wx', 'wxagg', 'wxcairo', 'agg', 'cairo', 'pdf', 'pgf',
+ 'ps', 'svg', 'template',
+ }
+
+
+@pytest.mark.parametrize(
+ 'filter,expected',
+ [
+ (BackendFilter.INTERACTIVE,
+ ['gtk3agg', 'gtk3cairo', 'gtk4agg', 'gtk4cairo', 'macosx', 'nbagg', 'notebook',
+ 'qtagg', 'qtcairo', 'qt5agg', 'qt5cairo', 'tkagg',
+ 'tkcairo', 'webagg', 'wx', 'wxagg', 'wxcairo']),
+ (BackendFilter.NON_INTERACTIVE,
+ ['agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template']),
+ ]
+)
+def test_list_builtin_with_filter(filter, expected):
+ backends = backend_registry.list_builtin(filter)
+ assert not has_duplicates(backends)
+ # Compare using sets as order is not important
+ assert {*backends} == {*expected}
+
+
+def test_list_gui_frameworks():
+ frameworks = backend_registry.list_gui_frameworks()
+ assert not has_duplicates(frameworks)
+ # Compare using sets as order is not important
+ assert {*frameworks} == {
+ "gtk3", "gtk4", "macosx", "qt", "qt5", "qt6", "tk", "wx",
+ }
+
+
+@pytest.mark.parametrize("backend, is_valid", [
+ ("agg", True),
+ ("QtAgg", True),
+ ("module://anything", True),
+ ("made-up-name", False),
+])
+def test_is_valid_backend(backend, is_valid):
+ assert backend_registry.is_valid_backend(backend) == is_valid
+
+
+@pytest.mark.parametrize("backend, normalized", [
+ ("agg", "matplotlib.backends.backend_agg"),
+ ("QtAgg", "matplotlib.backends.backend_qtagg"),
+ ("module://Anything", "Anything"),
+])
+def test_backend_normalization(backend, normalized):
+ assert backend_registry._backend_module_name(backend) == normalized
+
+
+def test_deprecated_rcsetup_attributes():
+ match = "was deprecated in Matplotlib 3.9"
+ with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):
+ mpl.rcsetup.interactive_bk
+ with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):
+ mpl.rcsetup.non_interactive_bk
+ with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):
+ mpl.rcsetup.all_backends
+
+
+def test_entry_points_inline():
+ pytest.importorskip('matplotlib_inline')
+ backends = backend_registry.list_all()
+ assert 'inline' in backends
+
+
+def test_entry_points_ipympl():
+ pytest.importorskip('ipympl')
+ backends = backend_registry.list_all()
+ assert 'ipympl' in backends
+ assert 'widget' in backends
+
+
+def test_entry_point_name_shadows_builtin(clear_backend_registry):
+ with pytest.raises(RuntimeError):
+ backend_registry._validate_and_store_entry_points(
+ [('qtagg', 'module1')])
+
+
+def test_entry_point_name_duplicate(clear_backend_registry):
+ with pytest.raises(RuntimeError):
+ backend_registry._validate_and_store_entry_points(
+ [('some_name', 'module1'), ('some_name', 'module2')])
+
+
+def test_entry_point_identical(clear_backend_registry):
+ # Issue https://github.com/matplotlib/matplotlib/issues/28367
+ # Multiple entry points with the same name and value (value is the module)
+ # are acceptable.
+ n = len(backend_registry._name_to_module)
+ backend_registry._validate_and_store_entry_points(
+ [('some_name', 'some.module'), ('some_name', 'some.module')])
+ assert len(backend_registry._name_to_module) == n+1
+ assert backend_registry._name_to_module['some_name'] == 'module://some.module'
+
+
+def test_entry_point_name_is_module(clear_backend_registry):
+ with pytest.raises(RuntimeError):
+ backend_registry._validate_and_store_entry_points(
+ [('module://backend.something', 'module1')])
+
+
+@pytest.mark.parametrize('backend', [
+ 'agg',
+ 'module://matplotlib.backends.backend_agg',
+])
+def test_load_entry_points_only_if_needed(clear_backend_registry, backend):
+ assert not backend_registry._loaded_entry_points
+ check = backend_registry.resolve_backend(backend)
+ assert check == (backend, None)
+ assert not backend_registry._loaded_entry_points
+ backend_registry.list_all() # Force load of entry points
+ assert backend_registry._loaded_entry_points
+
+
+@pytest.mark.parametrize(
+ 'gui_or_backend, expected_backend, expected_gui',
+ [
+ ('agg', 'agg', None),
+ ('qt', 'qtagg', 'qt'),
+ ('TkCairo', 'tkcairo', 'tk'),
+ ]
+)
+def test_resolve_gui_or_backend(gui_or_backend, expected_backend, expected_gui):
+ backend, gui = backend_registry.resolve_gui_or_backend(gui_or_backend)
+ assert backend == expected_backend
+ assert gui == expected_gui
+
+
+def test_resolve_gui_or_backend_invalid():
+ match = "is not a recognised GUI loop or backend name"
+ with pytest.raises(RuntimeError, match=match):
+ backend_registry.resolve_gui_or_backend('no-such-name')
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_svg.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_svg.py
new file mode 100644
index 0000000000000000000000000000000000000000..509136fe323e618c64c1b0ea6f1da4a37f730697
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_svg.py
@@ -0,0 +1,707 @@
+import datetime
+from io import BytesIO
+from pathlib import Path
+import xml.etree.ElementTree
+import xml.parsers.expat
+
+import pytest
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib.figure import Figure
+from matplotlib.patches import Circle
+from matplotlib.text import Text
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+from matplotlib.testing._markers import needs_usetex
+from matplotlib import font_manager as fm
+from matplotlib.offsetbox import (OffsetImage, AnnotationBbox)
+
+
+def test_visibility():
+ fig, ax = plt.subplots()
+
+ x = np.linspace(0, 4 * np.pi, 50)
+ y = np.sin(x)
+ yerr = np.ones_like(y)
+
+ a, b, c = ax.errorbar(x, y, yerr=yerr, fmt='ko')
+ for artist in b:
+ artist.set_visible(False)
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue()
+
+ parser = xml.parsers.expat.ParserCreate()
+ parser.Parse(buf) # this will raise ExpatError if the svg is invalid
+
+
+@image_comparison(['fill_black_with_alpha.svg'], remove_text=True)
+def test_fill_black_with_alpha():
+ fig, ax = plt.subplots()
+ ax.scatter(x=[0, 0.1, 1], y=[0, 0, 0], c='k', alpha=0.1, s=10000)
+
+
+@image_comparison(['noscale'], remove_text=True)
+def test_noscale():
+ X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
+ Z = np.sin(Y ** 2)
+
+ fig, ax = plt.subplots()
+ ax.imshow(Z, cmap='gray', interpolation='none')
+
+
+def test_text_urls():
+ fig = plt.figure()
+
+ test_url = "http://test_text_urls.matplotlib.org"
+ fig.suptitle("test_text_urls", url=test_url)
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue().decode()
+
+ expected = f''
+ assert expected in buf
+
+
+@image_comparison(['bold_font_output.svg'])
+def test_bold_font_output():
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10))
+ ax.set_xlabel('nonbold-xlabel')
+ ax.set_ylabel('bold-ylabel', fontweight='bold')
+ ax.set_title('bold-title', fontweight='bold')
+
+
+@image_comparison(['bold_font_output_with_none_fonttype.svg'])
+def test_bold_font_output_with_none_fonttype():
+ plt.rcParams['svg.fonttype'] = 'none'
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10))
+ ax.set_xlabel('nonbold-xlabel')
+ ax.set_ylabel('bold-ylabel', fontweight='bold')
+ ax.set_title('bold-title', fontweight='bold')
+
+
+@check_figures_equal(tol=20)
+def test_rasterized(fig_test, fig_ref):
+ t = np.arange(0, 100) * (2.3)
+ x = np.cos(t)
+ y = np.sin(t)
+
+ ax_ref = fig_ref.subplots()
+ ax_ref.plot(x, y, "-", c="r", lw=10)
+ ax_ref.plot(x+1, y, "-", c="b", lw=10)
+
+ ax_test = fig_test.subplots()
+ ax_test.plot(x, y, "-", c="r", lw=10, rasterized=True)
+ ax_test.plot(x+1, y, "-", c="b", lw=10, rasterized=True)
+
+
+@check_figures_equal(extensions=['svg'])
+def test_rasterized_ordering(fig_test, fig_ref):
+ t = np.arange(0, 100) * (2.3)
+ x = np.cos(t)
+ y = np.sin(t)
+
+ ax_ref = fig_ref.subplots()
+ ax_ref.set_xlim(0, 3)
+ ax_ref.set_ylim(-1.1, 1.1)
+ ax_ref.plot(x, y, "-", c="r", lw=10, rasterized=True)
+ ax_ref.plot(x+1, y, "-", c="b", lw=10, rasterized=False)
+ ax_ref.plot(x+2, y, "-", c="g", lw=10, rasterized=True)
+ ax_ref.plot(x+3, y, "-", c="m", lw=10, rasterized=True)
+
+ ax_test = fig_test.subplots()
+ ax_test.set_xlim(0, 3)
+ ax_test.set_ylim(-1.1, 1.1)
+ ax_test.plot(x, y, "-", c="r", lw=10, rasterized=True, zorder=1.1)
+ ax_test.plot(x+2, y, "-", c="g", lw=10, rasterized=True, zorder=1.3)
+ ax_test.plot(x+3, y, "-", c="m", lw=10, rasterized=True, zorder=1.4)
+ ax_test.plot(x+1, y, "-", c="b", lw=10, rasterized=False, zorder=1.2)
+
+
+@check_figures_equal(tol=5, extensions=['svg', 'pdf'])
+def test_prevent_rasterization(fig_test, fig_ref):
+ loc = [0.05, 0.05]
+
+ ax_ref = fig_ref.subplots()
+
+ ax_ref.plot([loc[0]], [loc[1]], marker="x", c="black", zorder=2)
+
+ b = mpl.offsetbox.TextArea("X")
+ abox = mpl.offsetbox.AnnotationBbox(b, loc, zorder=2.1)
+ ax_ref.add_artist(abox)
+
+ ax_test = fig_test.subplots()
+ ax_test.plot([loc[0]], [loc[1]], marker="x", c="black", zorder=2,
+ rasterized=True)
+
+ b = mpl.offsetbox.TextArea("X")
+ abox = mpl.offsetbox.AnnotationBbox(b, loc, zorder=2.1)
+ ax_test.add_artist(abox)
+
+
+def test_count_bitmaps():
+ def count_tag(fig, tag):
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue().decode()
+ return buf.count(f"<{tag}")
+
+ # No rasterized elements
+ fig1 = plt.figure()
+ ax1 = fig1.add_subplot(1, 1, 1)
+ ax1.set_axis_off()
+ for n in range(5):
+ ax1.plot([0, 20], [0, n], "b-", rasterized=False)
+ assert count_tag(fig1, "image") == 0
+ assert count_tag(fig1, "path") == 6 # axis patch plus lines
+
+ # rasterized can be merged
+ fig2 = plt.figure()
+ ax2 = fig2.add_subplot(1, 1, 1)
+ ax2.set_axis_off()
+ for n in range(5):
+ ax2.plot([0, 20], [0, n], "b-", rasterized=True)
+ assert count_tag(fig2, "image") == 1
+ assert count_tag(fig2, "path") == 1 # axis patch
+
+ # rasterized can't be merged without affecting draw order
+ fig3 = plt.figure()
+ ax3 = fig3.add_subplot(1, 1, 1)
+ ax3.set_axis_off()
+ for n in range(5):
+ ax3.plot([0, 20], [n, 0], "b-", rasterized=False)
+ ax3.plot([0, 20], [0, n], "b-", rasterized=True)
+ assert count_tag(fig3, "image") == 5
+ assert count_tag(fig3, "path") == 6
+
+ # rasterized whole axes
+ fig4 = plt.figure()
+ ax4 = fig4.add_subplot(1, 1, 1)
+ ax4.set_axis_off()
+ ax4.set_rasterized(True)
+ for n in range(5):
+ ax4.plot([0, 20], [n, 0], "b-", rasterized=False)
+ ax4.plot([0, 20], [0, n], "b-", rasterized=True)
+ assert count_tag(fig4, "image") == 1
+ assert count_tag(fig4, "path") == 1
+
+ # rasterized can be merged, but inhibited by suppressComposite
+ fig5 = plt.figure()
+ fig5.suppressComposite = True
+ ax5 = fig5.add_subplot(1, 1, 1)
+ ax5.set_axis_off()
+ for n in range(5):
+ ax5.plot([0, 20], [0, n], "b-", rasterized=True)
+ assert count_tag(fig5, "image") == 5
+ assert count_tag(fig5, "path") == 1 # axis patch
+
+
+# Use Computer Modern Sans Serif, not Helvetica (which has no \textwon).
+@mpl.style.context('default')
+@needs_usetex
+def test_unicode_won():
+ fig = Figure()
+ fig.text(.5, .5, r'\textwon', usetex=True)
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue()
+
+ tree = xml.etree.ElementTree.fromstring(buf)
+ ns = 'http://www.w3.org/2000/svg'
+ won_id = 'SFSS3583-8e'
+ assert len(tree.findall(f'.//{{{ns}}}path[@d][@id="{won_id}"]')) == 1
+ assert f'#{won_id}' in tree.find(f'.//{{{ns}}}use').attrib.values()
+
+
+def test_svgnone_with_data_coordinates():
+ plt.rcParams.update({'svg.fonttype': 'none', 'font.stretch': 'condensed'})
+ expected = 'Unlikely to appear by chance'
+
+ fig, ax = plt.subplots()
+ ax.text(np.datetime64('2019-06-30'), 1, expected)
+ ax.set_xlim(np.datetime64('2019-01-01'), np.datetime64('2019-12-31'))
+ ax.set_ylim(0, 2)
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ fd.seek(0)
+ buf = fd.read().decode()
+
+ assert expected in buf and "condensed" in buf
+
+
+def test_gid():
+ """Test that object gid appears in output svg."""
+ from matplotlib.offsetbox import OffsetBox
+ from matplotlib.axis import Tick
+
+ fig = plt.figure()
+
+ ax1 = fig.add_subplot(131)
+ ax1.imshow([[1., 2.], [2., 3.]], aspect="auto")
+ ax1.scatter([1, 2, 3], [1, 2, 3], label="myscatter")
+ ax1.plot([2, 3, 1], label="myplot")
+ ax1.legend()
+ ax1a = ax1.twinx()
+ ax1a.bar([1, 2, 3], [1, 2, 3])
+
+ ax2 = fig.add_subplot(132, projection="polar")
+ ax2.plot([0, 1.5, 3], [1, 2, 3])
+
+ ax3 = fig.add_subplot(133, projection="3d")
+ ax3.plot([1, 2], [1, 2], [1, 2])
+
+ fig.canvas.draw()
+
+ gdic = {}
+ for idx, obj in enumerate(fig.findobj(include_self=True)):
+ if obj.get_visible():
+ gid = f"test123{obj.__class__.__name__}_{idx}"
+ gdic[gid] = obj
+ obj.set_gid(gid)
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue().decode()
+
+ def include(gid, obj):
+ # we need to exclude certain objects which will not appear in the svg
+ if isinstance(obj, OffsetBox):
+ return False
+ if isinstance(obj, Text):
+ if obj.get_text() == "":
+ return False
+ elif obj.axes is None:
+ return False
+ if isinstance(obj, plt.Line2D):
+ xdata, ydata = obj.get_data()
+ if len(xdata) == len(ydata) == 1:
+ return False
+ elif not hasattr(obj, "axes") or obj.axes is None:
+ return False
+ if isinstance(obj, Tick):
+ loc = obj.get_loc()
+ if loc == 0:
+ return False
+ vi = obj.get_view_interval()
+ if loc < min(vi) or loc > max(vi):
+ return False
+ return True
+
+ for gid, obj in gdic.items():
+ if include(gid, obj):
+ assert gid in buf
+
+
+def test_clip_path_ids_reuse():
+ fig, circle = Figure(), Circle((0, 0), radius=10)
+ for i in range(5):
+ ax = fig.add_subplot()
+ aimg = ax.imshow([[i]])
+ aimg.set_clip_path(circle)
+
+ inner_circle = Circle((0, 0), radius=1)
+ ax = fig.add_subplot()
+ aimg = ax.imshow([[0]])
+ aimg.set_clip_path(inner_circle)
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue()
+
+ tree = xml.etree.ElementTree.fromstring(buf)
+ ns = 'http://www.w3.org/2000/svg'
+
+ clip_path_ids = set()
+ for node in tree.findall(f'.//{{{ns}}}clipPath[@id]'):
+ node_id = node.attrib['id']
+ assert node_id not in clip_path_ids # assert ID uniqueness
+ clip_path_ids.add(node_id)
+ assert len(clip_path_ids) == 2 # only two clipPaths despite reuse in multiple axes
+
+
+def test_savefig_tight():
+ # Check that the draw-disabled renderer correctly disables open/close_group
+ # as well.
+ plt.savefig(BytesIO(), format="svgz", bbox_inches="tight")
+
+
+def test_url():
+ # Test that object url appears in output svg.
+
+ fig, ax = plt.subplots()
+
+ # collections
+ s = ax.scatter([1, 2, 3], [4, 5, 6])
+ s.set_urls(['https://example.com/foo', 'https://example.com/bar', None])
+
+ # Line2D
+ p, = plt.plot([2, 3, 4], [4, 5, 6])
+ p.set_url('https://example.com/baz')
+
+ # Line2D markers-only
+ p, = plt.plot([3, 4, 5], [4, 5, 6], linestyle='none', marker='x')
+ p.set_url('https://example.com/quux')
+
+ b = BytesIO()
+ fig.savefig(b, format='svg')
+ b = b.getvalue()
+ for v in [b'foo', b'bar', b'baz', b'quux']:
+ assert b'https://example.com/' + v in b
+
+
+def test_url_tick(monkeypatch):
+ monkeypatch.setenv('SOURCE_DATE_EPOCH', '19680801')
+
+ fig1, ax = plt.subplots()
+ ax.scatter([1, 2, 3], [4, 5, 6])
+ for i, tick in enumerate(ax.yaxis.get_major_ticks()):
+ tick.set_url(f'https://example.com/{i}')
+
+ fig2, ax = plt.subplots()
+ ax.scatter([1, 2, 3], [4, 5, 6])
+ for i, tick in enumerate(ax.yaxis.get_major_ticks()):
+ tick.label1.set_url(f'https://example.com/{i}')
+ tick.label2.set_url(f'https://example.com/{i}')
+
+ b1 = BytesIO()
+ fig1.savefig(b1, format='svg')
+ b1 = b1.getvalue()
+
+ b2 = BytesIO()
+ fig2.savefig(b2, format='svg')
+ b2 = b2.getvalue()
+
+ for i in range(len(ax.yaxis.get_major_ticks())):
+ assert f'https://example.com/{i}'.encode('ascii') in b1
+ assert b1 == b2
+
+
+def test_svg_default_metadata(monkeypatch):
+ # Values have been predefined for 'Creator', 'Date', 'Format', and 'Type'.
+ monkeypatch.setenv('SOURCE_DATE_EPOCH', '19680801')
+
+ fig, ax = plt.subplots()
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue().decode()
+
+ # Creator
+ assert mpl.__version__ in buf
+ # Date
+ assert '1970-08-16' in buf
+ # Format
+ assert 'image/svg+xml' in buf
+ # Type
+ assert 'StillImage' in buf
+
+ # Now make sure all the default metadata can be cleared.
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg', metadata={'Date': None, 'Creator': None,
+ 'Format': None, 'Type': None})
+ buf = fd.getvalue().decode()
+
+ # Creator
+ assert mpl.__version__ not in buf
+ # Date
+ assert '1970-08-16' not in buf
+ # Format
+ assert 'image/svg+xml' not in buf
+ # Type
+ assert 'StillImage' not in buf
+
+
+def test_svg_clear_default_metadata(monkeypatch):
+ # Makes sure that setting a default metadata to `None`
+ # removes the corresponding tag from the metadata.
+ monkeypatch.setenv('SOURCE_DATE_EPOCH', '19680801')
+
+ metadata_contains = {'creator': mpl.__version__, 'date': '1970-08-16',
+ 'format': 'image/svg+xml', 'type': 'StillImage'}
+
+ SVGNS = '{http://www.w3.org/2000/svg}'
+ RDFNS = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
+ CCNS = '{http://creativecommons.org/ns#}'
+ DCNS = '{http://purl.org/dc/elements/1.1/}'
+
+ fig, ax = plt.subplots()
+ for name in metadata_contains:
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg', metadata={name.title(): None})
+ buf = fd.getvalue().decode()
+
+ root = xml.etree.ElementTree.fromstring(buf)
+ work, = root.findall(f'./{SVGNS}metadata/{RDFNS}RDF/{CCNS}Work')
+ for key in metadata_contains:
+ data = work.findall(f'./{DCNS}{key}')
+ if key == name:
+ # The one we cleared is not there
+ assert not data
+ continue
+ # Everything else should be there
+ data, = data
+ xmlstr = xml.etree.ElementTree.tostring(data, encoding="unicode")
+ assert metadata_contains[key] in xmlstr
+
+
+def test_svg_clear_all_metadata():
+ # Makes sure that setting all default metadata to `None`
+ # removes the metadata tag from the output.
+
+ fig, ax = plt.subplots()
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg', metadata={'Date': None, 'Creator': None,
+ 'Format': None, 'Type': None})
+ buf = fd.getvalue().decode()
+
+ SVGNS = '{http://www.w3.org/2000/svg}'
+
+ root = xml.etree.ElementTree.fromstring(buf)
+ assert not root.findall(f'./{SVGNS}metadata')
+
+
+def test_svg_metadata():
+ single_value = ['Coverage', 'Identifier', 'Language', 'Relation', 'Source',
+ 'Title', 'Type']
+ multi_value = ['Contributor', 'Creator', 'Keywords', 'Publisher', 'Rights']
+ metadata = {
+ 'Date': [datetime.date(1968, 8, 1),
+ datetime.datetime(1968, 8, 2, 1, 2, 3)],
+ 'Description': 'description\ntext',
+ **{k: f'{k} foo' for k in single_value},
+ **{k: [f'{k} bar', f'{k} baz'] for k in multi_value},
+ }
+
+ fig = plt.figure()
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg', metadata=metadata)
+ buf = fd.getvalue().decode()
+
+ SVGNS = '{http://www.w3.org/2000/svg}'
+ RDFNS = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
+ CCNS = '{http://creativecommons.org/ns#}'
+ DCNS = '{http://purl.org/dc/elements/1.1/}'
+
+ root = xml.etree.ElementTree.fromstring(buf)
+ rdf, = root.findall(f'./{SVGNS}metadata/{RDFNS}RDF')
+
+ # Check things that are single entries.
+ titles = [node.text for node in root.findall(f'./{SVGNS}title')]
+ assert titles == [metadata['Title']]
+ types = [node.attrib[f'{RDFNS}resource']
+ for node in rdf.findall(f'./{CCNS}Work/{DCNS}type')]
+ assert types == [metadata['Type']]
+ for k in ['Description', *single_value]:
+ if k == 'Type':
+ continue
+ values = [node.text
+ for node in rdf.findall(f'./{CCNS}Work/{DCNS}{k.lower()}')]
+ assert values == [metadata[k]]
+
+ # Check things that are multi-value entries.
+ for k in multi_value:
+ if k == 'Keywords':
+ continue
+ values = [
+ node.text
+ for node in rdf.findall(
+ f'./{CCNS}Work/{DCNS}{k.lower()}/{CCNS}Agent/{DCNS}title')]
+ assert values == metadata[k]
+
+ # Check special things.
+ dates = [node.text for node in rdf.findall(f'./{CCNS}Work/{DCNS}date')]
+ assert dates == ['1968-08-01/1968-08-02T01:02:03']
+
+ values = [node.text for node in
+ rdf.findall(f'./{CCNS}Work/{DCNS}subject/{RDFNS}Bag/{RDFNS}li')]
+ assert values == metadata['Keywords']
+
+
+@image_comparison(["multi_font_aspath.svg"], tol=1.8)
+def test_multi_font_type3():
+ fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
+ if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
+ pytest.skip("Font may be missing")
+
+ plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27)
+ plt.rc('svg', fonttype='path')
+
+ fig = plt.figure()
+ fig.text(0.15, 0.475, "There are å‡ ä¸ªæ±‰å— in between!")
+
+
+@image_comparison(["multi_font_astext.svg"])
+def test_multi_font_type42():
+ fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
+ if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
+ pytest.skip("Font may be missing")
+
+ fig = plt.figure()
+ plt.rc('svg', fonttype='none')
+
+ plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27)
+ fig.text(0.15, 0.475, "There are å‡ ä¸ªæ±‰å— in between!")
+
+
+@pytest.mark.parametrize('metadata,error,message', [
+ ({'Date': 1}, TypeError, "Invalid type for Date metadata. Expected str"),
+ ({'Date': [1]}, TypeError,
+ "Invalid type for Date metadata. Expected iterable"),
+ ({'Keywords': 1}, TypeError,
+ "Invalid type for Keywords metadata. Expected str"),
+ ({'Keywords': [1]}, TypeError,
+ "Invalid type for Keywords metadata. Expected iterable"),
+ ({'Creator': 1}, TypeError,
+ "Invalid type for Creator metadata. Expected str"),
+ ({'Creator': [1]}, TypeError,
+ "Invalid type for Creator metadata. Expected iterable"),
+ ({'Title': 1}, TypeError,
+ "Invalid type for Title metadata. Expected str"),
+ ({'Format': 1}, TypeError,
+ "Invalid type for Format metadata. Expected str"),
+ ({'Foo': 'Bar'}, ValueError, "Unknown metadata key"),
+ ])
+def test_svg_incorrect_metadata(metadata, error, message):
+ with pytest.raises(error, match=message), BytesIO() as fd:
+ fig = plt.figure()
+ fig.savefig(fd, format='svg', metadata=metadata)
+
+
+def test_svg_escape():
+ fig = plt.figure()
+ fig.text(0.5, 0.5, "<\'\"&>", gid="<\'\"&>")
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue().decode()
+ assert '<'"&>"' in buf
+
+
+@pytest.mark.parametrize("font_str", [
+ "'DejaVu Sans', 'WenQuanYi Zen Hei', 'Arial', sans-serif",
+ "'DejaVu Serif', 'WenQuanYi Zen Hei', 'Times New Roman', serif",
+ "'Arial', 'WenQuanYi Zen Hei', cursive",
+ "'Impact', 'WenQuanYi Zen Hei', fantasy",
+ "'DejaVu Sans Mono', 'WenQuanYi Zen Hei', 'Courier New', monospace",
+ # These do not work because the logic to get the font metrics will not find
+ # WenQuanYi as the fallback logic stops with the first fallback font:
+ # "'DejaVu Sans Mono', 'Courier New', 'WenQuanYi Zen Hei', monospace",
+ # "'DejaVu Sans', 'Arial', 'WenQuanYi Zen Hei', sans-serif",
+ # "'DejaVu Serif', 'Times New Roman', 'WenQuanYi Zen Hei', serif",
+])
+@pytest.mark.parametrize("include_generic", [True, False])
+def test_svg_font_string(font_str, include_generic):
+ fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
+ if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
+ pytest.skip("Font may be missing")
+
+ explicit, *rest, generic = map(
+ lambda x: x.strip("'"), font_str.split(", ")
+ )
+ size = len(generic)
+ if include_generic:
+ rest = rest + [generic]
+ plt.rcParams[f"font.{generic}"] = rest
+ plt.rcParams["font.size"] = size
+ plt.rcParams["svg.fonttype"] = "none"
+
+ fig, ax = plt.subplots()
+ if generic == "sans-serif":
+ generic_options = ["sans", "sans-serif", "sans serif"]
+ else:
+ generic_options = [generic]
+
+ for generic_name in generic_options:
+ # test that fallback works
+ ax.text(0.5, 0.5, "There are å‡ ä¸ªæ±‰å— in between!",
+ family=[explicit, generic_name], ha="center")
+ # test deduplication works
+ ax.text(0.5, 0.1, "There are å‡ ä¸ªæ±‰å— in between!",
+ family=[explicit, *rest, generic_name], ha="center")
+ ax.axis("off")
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format="svg")
+ buf = fd.getvalue()
+
+ tree = xml.etree.ElementTree.fromstring(buf)
+ ns = "http://www.w3.org/2000/svg"
+ text_count = 0
+ for text_element in tree.findall(f".//{{{ns}}}text"):
+ text_count += 1
+ font_style = dict(
+ map(lambda x: x.strip(), _.strip().split(":"))
+ for _ in dict(text_element.items())["style"].split(";")
+ )
+
+ assert font_style["font-size"] == f"{size}px"
+ assert font_style["font-family"] == font_str
+ assert text_count == len(ax.texts)
+
+
+def test_annotationbbox_gid():
+ # Test that object gid appears in the AnnotationBbox
+ # in output svg.
+ fig = plt.figure()
+ ax = fig.add_subplot()
+ arr_img = np.ones((32, 32))
+ xy = (0.3, 0.55)
+
+ imagebox = OffsetImage(arr_img, zoom=0.1)
+ imagebox.image.axes = ax
+
+ ab = AnnotationBbox(imagebox, xy,
+ xybox=(120., -80.),
+ xycoords='data',
+ boxcoords="offset points",
+ pad=0.5,
+ arrowprops=dict(
+ arrowstyle="->",
+ connectionstyle="angle,angleA=0,angleB=90,rad=3")
+ )
+ ab.set_gid("a test for issue 20044")
+ ax.add_artist(ab)
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue().decode('utf-8')
+
+ expected = ''
+ assert expected in buf
+
+
+def test_svgid():
+ """Test that `svg.id` rcparam appears in output svg if not None."""
+
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3], [3, 2, 1])
+ fig.canvas.draw()
+
+ # Default: svg.id = None
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue().decode()
+
+ tree = xml.etree.ElementTree.fromstring(buf)
+
+ assert plt.rcParams['svg.id'] is None
+ assert not tree.findall('.[@id]')
+
+ # String: svg.id = str
+ svg_id = 'a test for issue 28535'
+ plt.rc('svg', id=svg_id)
+
+ with BytesIO() as fd:
+ fig.savefig(fd, format='svg')
+ buf = fd.getvalue().decode()
+
+ tree = xml.etree.ElementTree.fromstring(buf)
+
+ assert plt.rcParams['svg.id'] == svg_id
+ assert tree.findall(f'.[@id="{svg_id}"]')
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_template.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_template.py
new file mode 100644
index 0000000000000000000000000000000000000000..964d15c1559a7f294a1b8e5eb04d8c74f22282c8
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_template.py
@@ -0,0 +1,62 @@
+"""
+Backend-loading machinery tests, using variations on the template backend.
+"""
+
+import sys
+from types import SimpleNamespace
+from unittest.mock import MagicMock
+
+import matplotlib as mpl
+from matplotlib import pyplot as plt
+from matplotlib.backends import backend_template
+from matplotlib.backends.backend_template import (
+ FigureCanvasTemplate, FigureManagerTemplate)
+
+
+def test_load_template():
+ mpl.use("template")
+ assert type(plt.figure().canvas) == FigureCanvasTemplate
+
+
+def test_load_old_api(monkeypatch):
+ mpl_test_backend = SimpleNamespace(**vars(backend_template))
+ mpl_test_backend.new_figure_manager = (
+ lambda num, *args, FigureClass=mpl.figure.Figure, **kwargs:
+ FigureManagerTemplate(
+ FigureCanvasTemplate(FigureClass(*args, **kwargs)), num))
+ monkeypatch.setitem(sys.modules, "mpl_test_backend", mpl_test_backend)
+ mpl.use("module://mpl_test_backend")
+ assert type(plt.figure().canvas) == FigureCanvasTemplate
+ plt.draw_if_interactive()
+
+
+def test_show(monkeypatch):
+ mpl_test_backend = SimpleNamespace(**vars(backend_template))
+ mock_show = MagicMock()
+ monkeypatch.setattr(
+ mpl_test_backend.FigureManagerTemplate, "pyplot_show", mock_show)
+ monkeypatch.setitem(sys.modules, "mpl_test_backend", mpl_test_backend)
+ mpl.use("module://mpl_test_backend")
+ plt.show()
+ mock_show.assert_called_with()
+
+
+def test_show_old_global_api(monkeypatch):
+ mpl_test_backend = SimpleNamespace(**vars(backend_template))
+ mock_show = MagicMock()
+ monkeypatch.setattr(mpl_test_backend, "show", mock_show, raising=False)
+ monkeypatch.setitem(sys.modules, "mpl_test_backend", mpl_test_backend)
+ mpl.use("module://mpl_test_backend")
+ plt.show()
+ mock_show.assert_called_with()
+
+
+def test_load_case_sensitive(monkeypatch):
+ mpl_test_backend = SimpleNamespace(**vars(backend_template))
+ mock_show = MagicMock()
+ monkeypatch.setattr(
+ mpl_test_backend.FigureManagerTemplate, "pyplot_show", mock_show)
+ monkeypatch.setitem(sys.modules, "mpl_Test_Backend", mpl_test_backend)
+ mpl.use("module://mpl_Test_Backend")
+ plt.show()
+ mock_show.assert_called_with()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_tk.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_tk.py
new file mode 100644
index 0000000000000000000000000000000000000000..1210c8c9993ed11f606fb9888520df2bab4d4da7
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_tk.py
@@ -0,0 +1,280 @@
+import functools
+import importlib
+import os
+import platform
+import subprocess
+import sys
+
+import pytest
+
+from matplotlib import _c_internal_utils
+from matplotlib.testing import subprocess_run_helper
+
+
+_test_timeout = 60 # A reasonably safe value for slower architectures.
+
+
+def _isolated_tk_test(success_count, func=None):
+ """
+ A decorator to run *func* in a subprocess and assert that it prints
+ "success" *success_count* times and nothing on stderr.
+
+ TkAgg tests seem to have interactions between tests, so isolate each test
+ in a subprocess. See GH#18261
+ """
+
+ if func is None:
+ return functools.partial(_isolated_tk_test, success_count)
+
+ if "MPL_TEST_ESCAPE_HATCH" in os.environ:
+ # set in subprocess_run_helper() below
+ return func
+
+ @pytest.mark.skipif(
+ not importlib.util.find_spec('tkinter'),
+ reason="missing tkinter"
+ )
+ @pytest.mark.skipif(
+ sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(),
+ reason="$DISPLAY is unset"
+ )
+ @pytest.mark.xfail( # https://github.com/actions/setup-python/issues/649
+ ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and
+ sys.platform == 'darwin' and sys.version_info[:2] < (3, 11),
+ reason='Tk version mismatch on Azure macOS CI'
+ )
+ @functools.wraps(func)
+ def test_func():
+ # even if the package exists, may not actually be importable this can
+ # be the case on some CI systems.
+ pytest.importorskip('tkinter')
+ try:
+ proc = subprocess_run_helper(
+ func, timeout=_test_timeout, extra_env=dict(
+ MPLBACKEND="TkAgg", MPL_TEST_ESCAPE_HATCH="1"))
+ except subprocess.TimeoutExpired:
+ pytest.fail("Subprocess timed out")
+ except subprocess.CalledProcessError as e:
+ pytest.fail("Subprocess failed to test intended behavior\n"
+ + str(e.stderr))
+ else:
+ # macOS may actually emit irrelevant errors about Accelerated
+ # OpenGL vs. software OpenGL, or some permission error on Azure, so
+ # suppress them.
+ # Asserting stderr first (and printing it on failure) should be
+ # more helpful for debugging that printing a failed success count.
+ ignored_lines = ["OpenGL", "CFMessagePort: bootstrap_register",
+ "/usr/include/servers/bootstrap_defs.h"]
+ assert not [line for line in proc.stderr.splitlines()
+ if all(msg not in line for msg in ignored_lines)]
+ assert proc.stdout.count("success") == success_count
+
+ return test_func
+
+
+@_isolated_tk_test(success_count=6) # len(bad_boxes)
+def test_blit():
+ import matplotlib.pyplot as plt
+ import numpy as np
+ import matplotlib.backends.backend_tkagg # noqa
+ from matplotlib.backends import _backend_tk, _tkagg
+
+ fig, ax = plt.subplots()
+ photoimage = fig.canvas._tkphoto
+ data = np.ones((4, 4, 4), dtype=np.uint8)
+ # Test out of bounds blitting.
+ bad_boxes = ((-1, 2, 0, 2),
+ (2, 0, 0, 2),
+ (1, 6, 0, 2),
+ (0, 2, -1, 2),
+ (0, 2, 2, 0),
+ (0, 2, 1, 6))
+ for bad_box in bad_boxes:
+ try:
+ _tkagg.blit(
+ photoimage.tk.interpaddr(), str(photoimage), data,
+ _tkagg.TK_PHOTO_COMPOSITE_OVERLAY, (0, 1, 2, 3), bad_box)
+ except ValueError:
+ print("success")
+
+ # Test blitting to a destroyed canvas.
+ plt.close(fig)
+ _backend_tk.blit(photoimage, data, (0, 1, 2, 3))
+
+
+@_isolated_tk_test(success_count=1)
+def test_figuremanager_preserves_host_mainloop():
+ import tkinter
+ import matplotlib.pyplot as plt
+ success = []
+
+ def do_plot():
+ plt.figure()
+ plt.plot([1, 2], [3, 5])
+ plt.close()
+ root.after(0, legitimate_quit)
+
+ def legitimate_quit():
+ root.quit()
+ success.append(True)
+
+ root = tkinter.Tk()
+ root.after(0, do_plot)
+ root.mainloop()
+
+ if success:
+ print("success")
+
+
+@pytest.mark.skipif(platform.python_implementation() != 'CPython',
+ reason='PyPy does not support Tkinter threading: '
+ 'https://foss.heptapod.net/pypy/pypy/-/issues/1929')
+@pytest.mark.flaky(reruns=3)
+@_isolated_tk_test(success_count=1)
+def test_figuremanager_cleans_own_mainloop():
+ import tkinter
+ import time
+ import matplotlib.pyplot as plt
+ import threading
+ from matplotlib.cbook import _get_running_interactive_framework
+
+ root = tkinter.Tk()
+ plt.plot([1, 2, 3], [1, 2, 5])
+
+ def target():
+ while not 'tk' == _get_running_interactive_framework():
+ time.sleep(.01)
+ plt.close()
+ if show_finished_event.wait():
+ print('success')
+
+ show_finished_event = threading.Event()
+ thread = threading.Thread(target=target, daemon=True)
+ thread.start()
+ plt.show(block=True) # Testing if this function hangs.
+ show_finished_event.set()
+ thread.join()
+
+
+@pytest.mark.flaky(reruns=3)
+@_isolated_tk_test(success_count=0)
+def test_never_update():
+ import tkinter
+ del tkinter.Misc.update
+ del tkinter.Misc.update_idletasks
+
+ import matplotlib.pyplot as plt
+ fig = plt.figure()
+ plt.show(block=False)
+
+ plt.draw() # Test FigureCanvasTkAgg.
+ fig.canvas.toolbar.configure_subplots() # Test NavigationToolbar2Tk.
+ # Test FigureCanvasTk filter_destroy callback
+ fig.canvas.get_tk_widget().after(100, plt.close, fig)
+
+ # Check for update() or update_idletasks() in the event queue, functionally
+ # equivalent to tkinter.Misc.update.
+ plt.show(block=True)
+
+ # Note that exceptions would be printed to stderr; _isolated_tk_test
+ # checks them.
+
+
+@_isolated_tk_test(success_count=2)
+def test_missing_back_button():
+ import matplotlib.pyplot as plt
+ from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
+
+ class Toolbar(NavigationToolbar2Tk):
+ # Only display the buttons we need.
+ toolitems = [t for t in NavigationToolbar2Tk.toolitems if
+ t[0] in ('Home', 'Pan', 'Zoom')]
+
+ fig = plt.figure()
+ print("success")
+ Toolbar(fig.canvas, fig.canvas.manager.window) # This should not raise.
+ print("success")
+
+
+@_isolated_tk_test(success_count=2)
+def test_save_figure_return():
+ import matplotlib.pyplot as plt
+ from unittest import mock
+ fig = plt.figure()
+ prop = "tkinter.filedialog.asksaveasfilename"
+ with mock.patch(prop, return_value="foobar.png"):
+ fname = fig.canvas.manager.toolbar.save_figure()
+ os.remove("foobar.png")
+ assert fname == "foobar.png"
+ print("success")
+ with mock.patch(prop, return_value=""):
+ fname = fig.canvas.manager.toolbar.save_figure()
+ assert fname is None
+ print("success")
+
+
+@_isolated_tk_test(success_count=1)
+def test_canvas_focus():
+ import tkinter as tk
+ import matplotlib.pyplot as plt
+ success = []
+
+ def check_focus():
+ tkcanvas = fig.canvas.get_tk_widget()
+ # Give the plot window time to appear
+ if not tkcanvas.winfo_viewable():
+ tkcanvas.wait_visibility()
+ # Make sure the canvas has the focus, so that it's able to receive
+ # keyboard events.
+ if tkcanvas.focus_lastfor() == tkcanvas:
+ success.append(True)
+ plt.close()
+ root.destroy()
+
+ root = tk.Tk()
+ fig = plt.figure()
+ plt.plot([1, 2, 3])
+ root.after(0, plt.show)
+ root.after(100, check_focus)
+ root.mainloop()
+
+ if success:
+ print("success")
+
+
+@_isolated_tk_test(success_count=2)
+def test_embedding():
+ import tkinter as tk
+ from matplotlib.backends.backend_tkagg import (
+ FigureCanvasTkAgg, NavigationToolbar2Tk)
+ from matplotlib.backend_bases import key_press_handler
+ from matplotlib.figure import Figure
+
+ root = tk.Tk()
+
+ def test_figure(master):
+ fig = Figure()
+ ax = fig.add_subplot()
+ ax.plot([1, 2, 3])
+
+ canvas = FigureCanvasTkAgg(fig, master=master)
+ canvas.draw()
+ canvas.mpl_connect("key_press_event", key_press_handler)
+ canvas.get_tk_widget().pack(expand=True, fill="both")
+
+ toolbar = NavigationToolbar2Tk(canvas, master, pack_toolbar=False)
+ toolbar.pack(expand=True, fill="x")
+
+ canvas.get_tk_widget().forget()
+ toolbar.forget()
+
+ test_figure(root)
+ print("success")
+
+ # Test with a dark button color. Doesn't actually check whether the icon
+ # color becomes lighter, just that the code doesn't break.
+
+ root.tk_setPalette(background="sky blue", selectColor="midnight blue",
+ foreground="white")
+ test_figure(root)
+ print("success")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_tools.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc05a1a98f78f6b7267cf3d861013b3cf4416624
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_tools.py
@@ -0,0 +1,20 @@
+import pytest
+
+from matplotlib.backend_tools import ToolHelpBase
+
+
+@pytest.mark.parametrize('rc_shortcut,expected', [
+ ('home', 'Home'),
+ ('backspace', 'Backspace'),
+ ('f1', 'F1'),
+ ('ctrl+a', 'Ctrl+A'),
+ ('ctrl+A', 'Ctrl+Shift+A'),
+ ('a', 'a'),
+ ('A', 'A'),
+ ('ctrl+shift+f1', 'Ctrl+Shift+F1'),
+ ('1', '1'),
+ ('cmd+p', 'Cmd+P'),
+ ('cmd+1', 'Cmd+1'),
+])
+def test_format_shortcut(rc_shortcut, expected):
+ assert ToolHelpBase.format_shortcut(rc_shortcut) == expected
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_webagg.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_webagg.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d6769494ef937edfd0116c612839a3fe08f3ec6
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backend_webagg.py
@@ -0,0 +1,32 @@
+import os
+import sys
+import pytest
+
+import matplotlib.backends.backend_webagg_core
+from matplotlib.testing import subprocess_run_for_testing
+
+
+@pytest.mark.parametrize("backend", ["webagg", "nbagg"])
+def test_webagg_fallback(backend):
+ pytest.importorskip("tornado")
+ if backend == "nbagg":
+ pytest.importorskip("IPython")
+ env = dict(os.environ)
+ if sys.platform != "win32":
+ env["DISPLAY"] = ""
+
+ env["MPLBACKEND"] = backend
+
+ test_code = (
+ "import os;"
+ + f"assert os.environ['MPLBACKEND'] == '{backend}';"
+ + "import matplotlib.pyplot as plt; "
+ + "print(plt.get_backend());"
+ f"assert '{backend}' == plt.get_backend().lower();"
+ )
+ subprocess_run_for_testing([sys.executable, "-c", test_code], env=env, check=True)
+
+
+def test_webagg_core_no_toolbar():
+ fm = matplotlib.backends.backend_webagg_core.FigureManagerWebAgg
+ assert fm._toolbar2_class is None
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backends_interactive.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backends_interactive.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e3c1bbc2bb5cdba4ec8ecfe8d1a394808eb74f8
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_backends_interactive.py
@@ -0,0 +1,791 @@
+import functools
+import importlib
+import importlib.util
+import inspect
+import json
+import os
+import platform
+import signal
+import subprocess
+import sys
+import tempfile
+import time
+import urllib.request
+
+from PIL import Image
+
+import pytest
+
+import matplotlib as mpl
+from matplotlib import _c_internal_utils
+from matplotlib.backend_tools import ToolToggleBase
+from matplotlib.testing import subprocess_run_helper as _run_helper, is_ci_environment
+
+
+class _WaitForStringPopen(subprocess.Popen):
+ """
+ A Popen that passes flags that allow triggering KeyboardInterrupt.
+ """
+
+ def __init__(self, *args, **kwargs):
+ if sys.platform == 'win32':
+ kwargs['creationflags'] = subprocess.CREATE_NEW_CONSOLE
+ super().__init__(
+ *args, **kwargs,
+ # Force Agg so that each test can switch to its desired backend.
+ env={**os.environ, "MPLBACKEND": "Agg", "SOURCE_DATE_EPOCH": "0"},
+ stdout=subprocess.PIPE, universal_newlines=True)
+
+ def wait_for(self, terminator):
+ """Read until the terminator is reached."""
+ buf = ''
+ while True:
+ c = self.stdout.read(1)
+ if not c:
+ raise RuntimeError(
+ f'Subprocess died before emitting expected {terminator!r}')
+ buf += c
+ if buf.endswith(terminator):
+ return
+
+
+# Minimal smoke-testing of the backends for which the dependencies are
+# PyPI-installable on CI. They are not available for all tested Python
+# versions so we don't fail on missing backends.
+
+@functools.lru_cache
+def _get_available_interactive_backends():
+ _is_linux_and_display_invalid = (sys.platform == "linux" and
+ not _c_internal_utils.display_is_valid())
+ _is_linux_and_xdisplay_invalid = (sys.platform == "linux" and
+ not _c_internal_utils.xdisplay_is_valid())
+ envs = []
+ for deps, env in [
+ *[([qt_api],
+ {"MPLBACKEND": "qtagg", "QT_API": qt_api})
+ for qt_api in ["PyQt6", "PySide6", "PyQt5", "PySide2"]],
+ *[([qt_api, "cairocffi"],
+ {"MPLBACKEND": "qtcairo", "QT_API": qt_api})
+ for qt_api in ["PyQt6", "PySide6", "PyQt5", "PySide2"]],
+ *[(["cairo", "gi"], {"MPLBACKEND": f"gtk{version}{renderer}"})
+ for version in [3, 4] for renderer in ["agg", "cairo"]],
+ (["tkinter"], {"MPLBACKEND": "tkagg"}),
+ (["wx"], {"MPLBACKEND": "wx"}),
+ (["wx"], {"MPLBACKEND": "wxagg"}),
+ (["matplotlib.backends._macosx"], {"MPLBACKEND": "macosx"}),
+ ]:
+ reason = None
+ missing = [dep for dep in deps if not importlib.util.find_spec(dep)]
+ if missing:
+ reason = "{} cannot be imported".format(", ".join(missing))
+ elif _is_linux_and_xdisplay_invalid and (
+ env["MPLBACKEND"] == "tkagg"
+ # Remove when https://github.com/wxWidgets/Phoenix/pull/2638 is out.
+ or env["MPLBACKEND"].startswith("wx")):
+ reason = "$DISPLAY is unset"
+ elif _is_linux_and_display_invalid:
+ reason = "$DISPLAY and $WAYLAND_DISPLAY are unset"
+ elif env["MPLBACKEND"] == 'macosx' and os.environ.get('TF_BUILD'):
+ reason = "macosx backend fails on Azure"
+ elif env["MPLBACKEND"].startswith('gtk'):
+ try:
+ import gi # type: ignore[import]
+ except ImportError:
+ # Though we check that `gi` exists above, it is possible that its
+ # C-level dependencies are not available, and then it still raises an
+ # `ImportError`, so guard against that.
+ available_gtk_versions = []
+ else:
+ gi_repo = gi.Repository.get_default()
+ available_gtk_versions = gi_repo.enumerate_versions('Gtk')
+ version = env["MPLBACKEND"][3]
+ if f'{version}.0' not in available_gtk_versions:
+ reason = "no usable GTK bindings"
+ marks = []
+ if reason:
+ marks.append(pytest.mark.skip(reason=f"Skipping {env} because {reason}"))
+ elif env["MPLBACKEND"].startswith('wx') and sys.platform == 'darwin':
+ # ignore on macosx because that's currently broken (github #16849)
+ marks.append(pytest.mark.xfail(reason='github #16849'))
+ elif (env['MPLBACKEND'] == 'tkagg' and
+ ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and
+ sys.platform == 'darwin' and
+ sys.version_info[:2] < (3, 11)
+ ):
+ marks.append( # https://github.com/actions/setup-python/issues/649
+ pytest.mark.xfail(reason='Tk version mismatch on Azure macOS CI'))
+ envs.append(({**env, 'BACKEND_DEPS': ','.join(deps)}, marks))
+ return envs
+
+
+def _get_testable_interactive_backends():
+ # We re-create this because some of the callers below might modify the markers.
+ return [pytest.param({**env}, marks=[*marks],
+ id='-'.join(f'{k}={v}' for k, v in env.items()))
+ for env, marks in _get_available_interactive_backends()]
+
+
+# Reasonable safe values for slower CI/Remote and local architectures.
+_test_timeout = 120 if is_ci_environment() else 20
+
+
+def _test_toolbar_button_la_mode_icon(fig):
+ # test a toolbar button icon using an image in LA mode (GH issue 25174)
+ # create an icon in LA mode
+ with tempfile.TemporaryDirectory() as tempdir:
+ img = Image.new("LA", (26, 26))
+ tmp_img_path = os.path.join(tempdir, "test_la_icon.png")
+ img.save(tmp_img_path)
+
+ class CustomTool(ToolToggleBase):
+ image = tmp_img_path
+ description = "" # gtk3 backend does not allow None
+
+ toolmanager = fig.canvas.manager.toolmanager
+ toolbar = fig.canvas.manager.toolbar
+ toolmanager.add_tool("test", CustomTool)
+ toolbar.add_tool("test", "group")
+
+
+# The source of this function gets extracted and run in another process, so it
+# must be fully self-contained.
+# Using a timer not only allows testing of timers (on other backends), but is
+# also necessary on gtk3 and wx, where directly processing a KeyEvent() for "q"
+# from draw_event causes breakage as the canvas widget gets deleted too early.
+def _test_interactive_impl():
+ import importlib.util
+ import io
+ import json
+ import sys
+
+ import pytest
+
+ import matplotlib as mpl
+ from matplotlib import pyplot as plt
+ from matplotlib.backend_bases import KeyEvent
+ mpl.rcParams.update({
+ "webagg.open_in_browser": False,
+ "webagg.port_retries": 1,
+ })
+
+ mpl.rcParams.update(json.loads(sys.argv[1]))
+ backend = plt.rcParams["backend"].lower()
+
+ if backend.endswith("agg") and not backend.startswith(("gtk", "web")):
+ # Force interactive framework setup.
+ fig = plt.figure()
+ plt.close(fig)
+
+ # Check that we cannot switch to a backend using another interactive
+ # framework, but can switch to a backend using cairo instead of agg,
+ # or a non-interactive backend. In the first case, we use tkagg as
+ # the "other" interactive backend as it is (essentially) guaranteed
+ # to be present. Moreover, don't test switching away from gtk3 (as
+ # Gtk.main_level() is not set up at this point yet) and webagg (which
+ # uses no interactive framework).
+
+ if backend != "tkagg":
+ with pytest.raises(ImportError):
+ mpl.use("tkagg", force=True)
+
+ def check_alt_backend(alt_backend):
+ mpl.use(alt_backend, force=True)
+ fig = plt.figure()
+ assert (type(fig.canvas).__module__ ==
+ f"matplotlib.backends.backend_{alt_backend}")
+ plt.close("all")
+
+ if importlib.util.find_spec("cairocffi"):
+ check_alt_backend(backend[:-3] + "cairo")
+ check_alt_backend("svg")
+ mpl.use(backend, force=True)
+
+ fig, ax = plt.subplots()
+ assert type(fig.canvas).__module__ == f"matplotlib.backends.backend_{backend}"
+
+ assert fig.canvas.manager.get_window_title() == "Figure 1"
+
+ if mpl.rcParams["toolbar"] == "toolmanager":
+ # test toolbar button icon LA mode see GH issue 25174
+ _test_toolbar_button_la_mode_icon(fig)
+
+ ax.plot([0, 1], [2, 3])
+ if fig.canvas.toolbar: # i.e toolbar2.
+ fig.canvas.toolbar.draw_rubberband(None, 1., 1, 2., 2)
+
+ timer = fig.canvas.new_timer(1.) # Test that floats are cast to int.
+ timer.add_callback(KeyEvent("key_press_event", fig.canvas, "q")._process)
+ # Trigger quitting upon draw.
+ fig.canvas.mpl_connect("draw_event", lambda event: timer.start())
+ fig.canvas.mpl_connect("close_event", print)
+
+ result = io.BytesIO()
+ fig.savefig(result, format='png')
+
+ plt.show()
+
+ # Ensure that the window is really closed.
+ plt.pause(0.5)
+
+ # Test that saving works after interactive window is closed, but the figure
+ # is not deleted.
+ result_after = io.BytesIO()
+ fig.savefig(result_after, format='png')
+
+ assert result.getvalue() == result_after.getvalue()
+
+
+@pytest.mark.parametrize("env", _get_testable_interactive_backends())
+@pytest.mark.parametrize("toolbar", ["toolbar2", "toolmanager"])
+@pytest.mark.flaky(reruns=3)
+def test_interactive_backend(env, toolbar):
+ if env["MPLBACKEND"] == "macosx":
+ if toolbar == "toolmanager":
+ pytest.skip("toolmanager is not implemented for macosx.")
+ if env["MPLBACKEND"] == "wx":
+ pytest.skip("wx backend is deprecated; tests failed on appveyor")
+ if env["MPLBACKEND"] == "wxagg" and toolbar == "toolmanager":
+ pytest.skip("Temporarily deactivated: show() changes figure height "
+ "and thus fails the test")
+ try:
+ proc = _run_helper(
+ _test_interactive_impl,
+ json.dumps({"toolbar": toolbar}),
+ timeout=_test_timeout,
+ extra_env=env,
+ )
+ except subprocess.CalledProcessError as err:
+ pytest.fail(
+ "Subprocess failed to test intended behavior\n"
+ + str(err.stderr))
+ assert proc.stdout.count("CloseEvent") == 1
+
+
+def _test_thread_impl():
+ from concurrent.futures import ThreadPoolExecutor
+
+ import matplotlib as mpl
+ from matplotlib import pyplot as plt
+
+ mpl.rcParams.update({
+ "webagg.open_in_browser": False,
+ "webagg.port_retries": 1,
+ })
+
+ # Test artist creation and drawing does not crash from thread
+ # No other guarantees!
+ fig, ax = plt.subplots()
+ # plt.pause needed vs plt.show(block=False) at least on toolbar2-tkagg
+ plt.pause(0.5)
+
+ future = ThreadPoolExecutor().submit(ax.plot, [1, 3, 6])
+ future.result() # Joins the thread; rethrows any exception.
+
+ fig.canvas.mpl_connect("close_event", print)
+ future = ThreadPoolExecutor().submit(fig.canvas.draw)
+ plt.pause(0.5) # flush_events fails here on at least Tkagg (bpo-41176)
+ future.result() # Joins the thread; rethrows any exception.
+ plt.close() # backend is responsible for flushing any events here
+ if plt.rcParams["backend"].lower().startswith("wx"):
+ # TODO: debug why WX needs this only on py >= 3.8
+ fig.canvas.flush_events()
+
+
+_thread_safe_backends = _get_testable_interactive_backends()
+# Known unsafe backends. Remove the xfails if they start to pass!
+for param in _thread_safe_backends:
+ backend = param.values[0]["MPLBACKEND"]
+ if "cairo" in backend:
+ # Cairo backends save a cairo_t on the graphics context, and sharing
+ # these is not threadsafe.
+ param.marks.append(
+ pytest.mark.xfail(raises=subprocess.CalledProcessError))
+ elif backend == "wx":
+ param.marks.append(
+ pytest.mark.xfail(raises=subprocess.CalledProcessError))
+ elif backend == "macosx":
+ from packaging.version import parse
+ mac_ver = platform.mac_ver()[0]
+ # Note, macOS Big Sur is both 11 and 10.16, depending on SDK that
+ # Python was compiled against.
+ if mac_ver and parse(mac_ver) < parse('10.16'):
+ param.marks.append(
+ pytest.mark.xfail(raises=subprocess.TimeoutExpired,
+ strict=True))
+ elif param.values[0].get("QT_API") == "PySide2":
+ param.marks.append(
+ pytest.mark.xfail(raises=subprocess.CalledProcessError))
+ elif backend == "tkagg" and platform.python_implementation() != 'CPython':
+ param.marks.append(
+ pytest.mark.xfail(
+ reason='PyPy does not support Tkinter threading: '
+ 'https://foss.heptapod.net/pypy/pypy/-/issues/1929',
+ strict=True))
+ elif (backend == 'tkagg' and
+ ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and
+ sys.platform == 'darwin' and sys.version_info[:2] < (3, 11)):
+ param.marks.append( # https://github.com/actions/setup-python/issues/649
+ pytest.mark.xfail('Tk version mismatch on Azure macOS CI'))
+
+
+@pytest.mark.parametrize("env", _thread_safe_backends)
+@pytest.mark.flaky(reruns=3)
+def test_interactive_thread_safety(env):
+ proc = _run_helper(_test_thread_impl, timeout=_test_timeout, extra_env=env)
+ assert proc.stdout.count("CloseEvent") == 1
+
+
+def _impl_test_lazy_auto_backend_selection():
+ import matplotlib
+ import matplotlib.pyplot as plt
+ # just importing pyplot should not be enough to trigger resolution
+ bk = matplotlib.rcParams._get('backend')
+ assert not isinstance(bk, str)
+ assert plt._backend_mod is None
+ # but actually plotting should
+ plt.plot(5)
+ assert plt._backend_mod is not None
+ bk = matplotlib.rcParams._get('backend')
+ assert isinstance(bk, str)
+
+
+def test_lazy_auto_backend_selection():
+ _run_helper(_impl_test_lazy_auto_backend_selection,
+ timeout=_test_timeout)
+
+
+def _implqt5agg():
+ import matplotlib.backends.backend_qt5agg # noqa
+ import sys
+
+ assert 'PyQt6' not in sys.modules
+ assert 'pyside6' not in sys.modules
+ assert 'PyQt5' in sys.modules or 'pyside2' in sys.modules
+
+
+def _implcairo():
+ import matplotlib.backends.backend_qt5cairo # noqa
+ import sys
+
+ assert 'PyQt6' not in sys.modules
+ assert 'pyside6' not in sys.modules
+ assert 'PyQt5' in sys.modules or 'pyside2' in sys.modules
+
+
+def _implcore():
+ import matplotlib.backends.backend_qt5 # noqa
+ import sys
+
+ assert 'PyQt6' not in sys.modules
+ assert 'pyside6' not in sys.modules
+ assert 'PyQt5' in sys.modules or 'pyside2' in sys.modules
+
+
+def test_qt5backends_uses_qt5():
+ qt5_bindings = [
+ dep for dep in ['PyQt5', 'pyside2']
+ if importlib.util.find_spec(dep) is not None
+ ]
+ qt6_bindings = [
+ dep for dep in ['PyQt6', 'pyside6']
+ if importlib.util.find_spec(dep) is not None
+ ]
+ if len(qt5_bindings) == 0 or len(qt6_bindings) == 0:
+ pytest.skip('need both QT6 and QT5 bindings')
+ _run_helper(_implqt5agg, timeout=_test_timeout)
+ if importlib.util.find_spec('pycairo') is not None:
+ _run_helper(_implcairo, timeout=_test_timeout)
+ _run_helper(_implcore, timeout=_test_timeout)
+
+
+def _impl_missing():
+ import sys
+ # Simulate uninstalled
+ sys.modules["PyQt6"] = None
+ sys.modules["PyQt5"] = None
+ sys.modules["PySide2"] = None
+ sys.modules["PySide6"] = None
+
+ import matplotlib.pyplot as plt
+ with pytest.raises(ImportError, match="Failed to import any of the following Qt"):
+ plt.switch_backend("qtagg")
+ # Specifically ensure that Pyside6/Pyqt6 are not in the error message for qt5agg
+ with pytest.raises(ImportError, match="^(?:(?!(PySide6|PyQt6)).)*$"):
+ plt.switch_backend("qt5agg")
+
+
+def test_qt_missing():
+ _run_helper(_impl_missing, timeout=_test_timeout)
+
+
+def _impl_test_cross_Qt_imports():
+ import importlib
+ import sys
+ import warnings
+
+ _, host_binding, mpl_binding = sys.argv
+ # import the mpl binding. This will force us to use that binding
+ importlib.import_module(f'{mpl_binding}.QtCore')
+ mpl_binding_qwidgets = importlib.import_module(f'{mpl_binding}.QtWidgets')
+ import matplotlib.backends.backend_qt
+ host_qwidgets = importlib.import_module(f'{host_binding}.QtWidgets')
+
+ host_app = host_qwidgets.QApplication(["mpl testing"])
+ warnings.filterwarnings("error", message=r".*Mixing Qt major.*",
+ category=UserWarning)
+ matplotlib.backends.backend_qt._create_qApp()
+
+
+def qt5_and_qt6_pairs():
+ qt5_bindings = [
+ dep for dep in ['PyQt5', 'PySide2']
+ if importlib.util.find_spec(dep) is not None
+ ]
+ qt6_bindings = [
+ dep for dep in ['PyQt6', 'PySide6']
+ if importlib.util.find_spec(dep) is not None
+ ]
+ if len(qt5_bindings) == 0 or len(qt6_bindings) == 0:
+ yield pytest.param(None, None,
+ marks=[pytest.mark.skip('need both QT6 and QT5 bindings')])
+ return
+
+ for qt5 in qt5_bindings:
+ for qt6 in qt6_bindings:
+ yield from ([qt5, qt6], [qt6, qt5])
+
+
+@pytest.mark.skipif(
+ sys.platform == "linux" and not _c_internal_utils.display_is_valid(),
+ reason="$DISPLAY and $WAYLAND_DISPLAY are unset")
+@pytest.mark.parametrize('host, mpl', [*qt5_and_qt6_pairs()])
+def test_cross_Qt_imports(host, mpl):
+ try:
+ proc = _run_helper(_impl_test_cross_Qt_imports, host, mpl,
+ timeout=_test_timeout)
+ except subprocess.CalledProcessError as ex:
+ # We do try to warn the user they are doing something that we do not
+ # expect to work, so we're going to ignore if the subprocess crashes or
+ # is killed, and just check that the warning is printed.
+ stderr = ex.stderr
+ else:
+ stderr = proc.stderr
+ assert "Mixing Qt major versions may not work as expected." in stderr
+
+
+@pytest.mark.skipif('TF_BUILD' in os.environ,
+ reason="this test fails an azure for unknown reasons")
+@pytest.mark.skipif(sys.platform == "win32", reason="Cannot send SIGINT on Windows.")
+def test_webagg():
+ pytest.importorskip("tornado")
+ proc = subprocess.Popen(
+ [sys.executable, "-c",
+ inspect.getsource(_test_interactive_impl)
+ + "\n_test_interactive_impl()", "{}"],
+ env={**os.environ, "MPLBACKEND": "webagg", "SOURCE_DATE_EPOCH": "0"})
+ url = f'http://{mpl.rcParams["webagg.address"]}:{mpl.rcParams["webagg.port"]}'
+ timeout = time.perf_counter() + _test_timeout
+ try:
+ while True:
+ try:
+ retcode = proc.poll()
+ # check that the subprocess for the server is not dead
+ assert retcode is None
+ conn = urllib.request.urlopen(url)
+ break
+ except urllib.error.URLError:
+ if time.perf_counter() > timeout:
+ pytest.fail("Failed to connect to the webagg server.")
+ else:
+ continue
+ conn.close()
+ proc.send_signal(signal.SIGINT)
+ assert proc.wait(timeout=_test_timeout) == 0
+ finally:
+ if proc.poll() is None:
+ proc.kill()
+
+
+def _lazy_headless():
+ import os
+ import sys
+
+ backend, deps = sys.argv[1:]
+ deps = deps.split(',')
+
+ # make it look headless
+ os.environ.pop('DISPLAY', None)
+ os.environ.pop('WAYLAND_DISPLAY', None)
+ for dep in deps:
+ assert dep not in sys.modules
+
+ # we should fast-track to Agg
+ import matplotlib.pyplot as plt
+ assert plt.get_backend() == 'agg'
+ for dep in deps:
+ assert dep not in sys.modules
+
+ # make sure we really have dependencies installed
+ for dep in deps:
+ importlib.import_module(dep)
+ assert dep in sys.modules
+
+ # try to switch and make sure we fail with ImportError
+ try:
+ plt.switch_backend(backend)
+ except ImportError:
+ pass
+ else:
+ sys.exit(1)
+
+
+@pytest.mark.skipif(sys.platform != "linux", reason="this a linux-only test")
+@pytest.mark.parametrize("env", _get_testable_interactive_backends())
+def test_lazy_linux_headless(env):
+ proc = _run_helper(
+ _lazy_headless,
+ env.pop('MPLBACKEND'), env.pop("BACKEND_DEPS"),
+ timeout=_test_timeout,
+ extra_env={**env, 'DISPLAY': '', 'WAYLAND_DISPLAY': ''}
+ )
+
+
+def _test_number_of_draws_script():
+ import matplotlib.pyplot as plt
+
+ fig, ax = plt.subplots()
+
+ # animated=True tells matplotlib to only draw the artist when we
+ # explicitly request it
+ ln, = ax.plot([0, 1], [1, 2], animated=True)
+
+ # make sure the window is raised, but the script keeps going
+ plt.show(block=False)
+ plt.pause(0.3)
+ # Connect to draw_event to count the occurrences
+ fig.canvas.mpl_connect('draw_event', print)
+
+ # get copy of entire figure (everything inside fig.bbox)
+ # sans animated artist
+ bg = fig.canvas.copy_from_bbox(fig.bbox)
+ # draw the animated artist, this uses a cached renderer
+ ax.draw_artist(ln)
+ # show the result to the screen
+ fig.canvas.blit(fig.bbox)
+
+ for j in range(10):
+ # reset the background back in the canvas state, screen unchanged
+ fig.canvas.restore_region(bg)
+ # Create a **new** artist here, this is poor usage of blitting
+ # but good for testing to make sure that this doesn't create
+ # excessive draws
+ ln, = ax.plot([0, 1], [1, 2])
+ # render the artist, updating the canvas state, but not the screen
+ ax.draw_artist(ln)
+ # copy the image to the GUI state, but screen might not changed yet
+ fig.canvas.blit(fig.bbox)
+ # flush any pending GUI events, re-painting the screen if needed
+ fig.canvas.flush_events()
+
+ # Let the event loop process everything before leaving
+ plt.pause(0.1)
+
+
+_blit_backends = _get_testable_interactive_backends()
+for param in _blit_backends:
+ backend = param.values[0]["MPLBACKEND"]
+ if backend == "gtk3cairo":
+ # copy_from_bbox only works when rendering to an ImageSurface
+ param.marks.append(
+ pytest.mark.skip("gtk3cairo does not support blitting"))
+ elif backend == "gtk4cairo":
+ # copy_from_bbox only works when rendering to an ImageSurface
+ param.marks.append(
+ pytest.mark.skip("gtk4cairo does not support blitting"))
+ elif backend == "wx":
+ param.marks.append(
+ pytest.mark.skip("wx does not support blitting"))
+ elif (backend == 'tkagg' and
+ ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and
+ sys.platform == 'darwin' and
+ sys.version_info[:2] < (3, 11)
+ ):
+ param.marks.append( # https://github.com/actions/setup-python/issues/649
+ pytest.mark.xfail('Tk version mismatch on Azure macOS CI')
+ )
+
+
+@pytest.mark.parametrize("env", _blit_backends)
+# subprocesses can struggle to get the display, so rerun a few times
+@pytest.mark.flaky(reruns=4)
+def test_blitting_events(env):
+ proc = _run_helper(
+ _test_number_of_draws_script, timeout=_test_timeout, extra_env=env)
+ # Count the number of draw_events we got. We could count some initial
+ # canvas draws (which vary in number by backend), but the critical
+ # check here is that it isn't 10 draws, which would be called if
+ # blitting is not properly implemented
+ ndraws = proc.stdout.count("DrawEvent")
+ assert 0 < ndraws < 5
+
+
+def _impl_test_interactive_timers():
+ # A timer with <1 millisecond gets converted to int and therefore 0
+ # milliseconds, which the mac framework interprets as singleshot.
+ # We only want singleshot if we specify that ourselves, otherwise we want
+ # a repeating timer
+ from unittest.mock import Mock
+ import matplotlib.pyplot as plt
+ pause_time = 0.5
+ fig = plt.figure()
+ plt.pause(pause_time)
+ timer = fig.canvas.new_timer(0.1)
+ mock = Mock()
+ timer.add_callback(mock)
+ timer.start()
+ plt.pause(pause_time)
+ timer.stop()
+ assert mock.call_count > 1
+
+ # Now turn it into a single shot timer and verify only one gets triggered
+ mock.call_count = 0
+ timer.single_shot = True
+ timer.start()
+ plt.pause(pause_time)
+ assert mock.call_count == 1
+
+ # Make sure we can start the timer a second time
+ timer.start()
+ plt.pause(pause_time)
+ assert mock.call_count == 2
+ plt.close("all")
+
+
+@pytest.mark.parametrize("env", _get_testable_interactive_backends())
+def test_interactive_timers(env):
+ if env["MPLBACKEND"] == "gtk3cairo" and os.getenv("CI"):
+ pytest.skip("gtk3cairo timers do not work in remote CI")
+ if env["MPLBACKEND"] == "wx":
+ pytest.skip("wx backend is deprecated; tests failed on appveyor")
+ _run_helper(_impl_test_interactive_timers,
+ timeout=_test_timeout, extra_env=env)
+
+
+def _test_sigint_impl(backend, target_name, kwargs):
+ import sys
+ import matplotlib.pyplot as plt
+ import os
+ import threading
+
+ plt.switch_backend(backend)
+
+ def interrupter():
+ if sys.platform == 'win32':
+ import win32api
+ win32api.GenerateConsoleCtrlEvent(0, 0)
+ else:
+ import signal
+ os.kill(os.getpid(), signal.SIGINT)
+
+ target = getattr(plt, target_name)
+ timer = threading.Timer(1, interrupter)
+ fig = plt.figure()
+ fig.canvas.mpl_connect(
+ 'draw_event',
+ lambda *args: print('DRAW', flush=True)
+ )
+ fig.canvas.mpl_connect(
+ 'draw_event',
+ lambda *args: timer.start()
+ )
+ try:
+ target(**kwargs)
+ except KeyboardInterrupt:
+ print('SUCCESS', flush=True)
+
+
+@pytest.mark.parametrize("env", _get_testable_interactive_backends())
+@pytest.mark.parametrize("target, kwargs", [
+ ('show', {'block': True}),
+ ('pause', {'interval': 10})
+])
+def test_sigint(env, target, kwargs):
+ backend = env.get("MPLBACKEND")
+ if not backend.startswith(("qt", "macosx")):
+ pytest.skip("SIGINT currently only tested on qt and macosx")
+ proc = _WaitForStringPopen(
+ [sys.executable, "-c",
+ inspect.getsource(_test_sigint_impl) +
+ f"\n_test_sigint_impl({backend!r}, {target!r}, {kwargs!r})"])
+ try:
+ proc.wait_for('DRAW')
+ stdout, _ = proc.communicate(timeout=_test_timeout)
+ except Exception:
+ proc.kill()
+ stdout, _ = proc.communicate()
+ raise
+ assert 'SUCCESS' in stdout
+
+
+def _test_other_signal_before_sigint_impl(backend, target_name, kwargs):
+ import signal
+ import matplotlib.pyplot as plt
+
+ plt.switch_backend(backend)
+
+ target = getattr(plt, target_name)
+
+ fig = plt.figure()
+ fig.canvas.mpl_connect('draw_event', lambda *args: print('DRAW', flush=True))
+
+ timer = fig.canvas.new_timer(interval=1)
+ timer.single_shot = True
+ timer.add_callback(print, 'SIGUSR1', flush=True)
+
+ def custom_signal_handler(signum, frame):
+ timer.start()
+ signal.signal(signal.SIGUSR1, custom_signal_handler)
+
+ try:
+ target(**kwargs)
+ except KeyboardInterrupt:
+ print('SUCCESS', flush=True)
+
+
+@pytest.mark.skipif(sys.platform == 'win32',
+ reason='No other signal available to send on Windows')
+@pytest.mark.parametrize("env", _get_testable_interactive_backends())
+@pytest.mark.parametrize("target, kwargs", [
+ ('show', {'block': True}),
+ ('pause', {'interval': 10})
+])
+def test_other_signal_before_sigint(env, target, kwargs, request):
+ backend = env.get("MPLBACKEND")
+ if not backend.startswith(("qt", "macosx")):
+ pytest.skip("SIGINT currently only tested on qt and macosx")
+ if backend == "macosx":
+ request.node.add_marker(pytest.mark.xfail(reason="macosx backend is buggy"))
+ if sys.platform == "darwin" and target == "show":
+ # We've not previously had these toolkits installed on CI, and so were never
+ # aware that this was crashing. However, we've had little luck reproducing it
+ # locally, so mark it xfail for now. For more information, see
+ # https://github.com/matplotlib/matplotlib/issues/27984
+ request.node.add_marker(
+ pytest.mark.xfail(reason="Qt backend is buggy on macOS"))
+ proc = _WaitForStringPopen(
+ [sys.executable, "-c",
+ inspect.getsource(_test_other_signal_before_sigint_impl) +
+ "\n_test_other_signal_before_sigint_impl("
+ f"{backend!r}, {target!r}, {kwargs!r})"])
+ try:
+ proc.wait_for('DRAW')
+ os.kill(proc.pid, signal.SIGUSR1)
+ proc.wait_for('SIGUSR1')
+ os.kill(proc.pid, signal.SIGINT)
+ stdout, _ = proc.communicate(timeout=_test_timeout)
+ except Exception:
+ proc.kill()
+ stdout, _ = proc.communicate()
+ raise
+ print(stdout)
+ assert 'SUCCESS' in stdout
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_basic.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6aa1e458555dbd313f556e19094e82062ba8ade
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_basic.py
@@ -0,0 +1,48 @@
+import builtins
+import os
+import sys
+import textwrap
+
+from matplotlib.testing import subprocess_run_for_testing
+
+
+def test_simple():
+ assert 1 + 1 == 2
+
+
+def test_override_builtins():
+ import pylab # type: ignore[import]
+ ok_to_override = {
+ '__name__',
+ '__doc__',
+ '__package__',
+ '__loader__',
+ '__spec__',
+ 'any',
+ 'all',
+ 'sum',
+ 'divmod'
+ }
+ overridden = {key for key in {*dir(pylab)} & {*dir(builtins)}
+ if getattr(pylab, key) != getattr(builtins, key)}
+ assert overridden <= ok_to_override
+
+
+def test_lazy_imports():
+ source = textwrap.dedent("""
+ import sys
+
+ import matplotlib.figure
+ import matplotlib.backend_bases
+ import matplotlib.pyplot
+
+ assert 'matplotlib._tri' not in sys.modules
+ assert 'matplotlib._qhull' not in sys.modules
+ assert 'matplotlib._contour' not in sys.modules
+ assert 'urllib.request' not in sys.modules
+ """)
+
+ subprocess_run_for_testing(
+ [sys.executable, '-c', source],
+ env={**os.environ, "MPLBACKEND": "", "MATPLOTLIBRC": os.devnull},
+ check=True)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_bbox_tight.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_bbox_tight.py
new file mode 100644
index 0000000000000000000000000000000000000000..9965c914daa4f590ae5a173217814396f124ae64
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_bbox_tight.py
@@ -0,0 +1,175 @@
+from io import BytesIO
+import platform
+
+import numpy as np
+
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.pyplot as plt
+import matplotlib.path as mpath
+import matplotlib.patches as mpatches
+from matplotlib.ticker import FuncFormatter
+
+
+@image_comparison(['bbox_inches_tight'], remove_text=True,
+ savefig_kwarg={'bbox_inches': 'tight'})
+def test_bbox_inches_tight():
+ #: Test that a figure saved using bbox_inches='tight' is clipped correctly
+ data = [[66386, 174296, 75131, 577908, 32015],
+ [58230, 381139, 78045, 99308, 160454],
+ [89135, 80552, 152558, 497981, 603535],
+ [78415, 81858, 150656, 193263, 69638],
+ [139361, 331509, 343164, 781380, 52269]]
+
+ col_labels = row_labels = [''] * 5
+
+ rows = len(data)
+ ind = np.arange(len(col_labels)) + 0.3 # the x locations for the groups
+ cell_text = []
+ width = 0.4 # the width of the bars
+ yoff = np.zeros(len(col_labels))
+ # the bottom values for stacked bar chart
+ fig, ax = plt.subplots(1, 1)
+ for row in range(rows):
+ ax.bar(ind, data[row], width, bottom=yoff, align='edge', color='b')
+ yoff = yoff + data[row]
+ cell_text.append([''])
+ plt.xticks([])
+ plt.xlim(0, 5)
+ plt.legend([''] * 5, loc=(1.2, 0.2))
+ fig.legend([''] * 5, bbox_to_anchor=(0, 0.2), loc='lower left')
+ # Add a table at the bottom of the axes
+ cell_text.reverse()
+ plt.table(cellText=cell_text, rowLabels=row_labels, colLabels=col_labels,
+ loc='bottom')
+
+
+@image_comparison(['bbox_inches_tight_suptile_legend'],
+ savefig_kwarg={'bbox_inches': 'tight'},
+ tol=0 if platform.machine() == 'x86_64' else 0.02)
+def test_bbox_inches_tight_suptile_legend():
+ plt.plot(np.arange(10), label='a straight line')
+ plt.legend(bbox_to_anchor=(0.9, 1), loc='upper left')
+ plt.title('Axis title')
+ plt.suptitle('Figure title')
+
+ # put an extra long y tick on to see that the bbox is accounted for
+ def y_formatter(y, pos):
+ if int(y) == 4:
+ return 'The number 4'
+ else:
+ return str(y)
+ plt.gca().yaxis.set_major_formatter(FuncFormatter(y_formatter))
+
+ plt.xlabel('X axis')
+
+
+@image_comparison(['bbox_inches_tight_suptile_non_default.png'],
+ savefig_kwarg={'bbox_inches': 'tight'},
+ tol=0.1) # large tolerance because only testing clipping.
+def test_bbox_inches_tight_suptitle_non_default():
+ fig, ax = plt.subplots()
+ fig.suptitle('Booo', x=0.5, y=1.1)
+
+
+@image_comparison(['bbox_inches_tight_layout.png'], remove_text=True,
+ style='mpl20',
+ savefig_kwarg=dict(bbox_inches='tight', pad_inches='layout'))
+def test_bbox_inches_tight_layout_constrained():
+ fig, ax = plt.subplots(layout='constrained')
+ fig.get_layout_engine().set(h_pad=0.5)
+ ax.set_aspect('equal')
+
+
+def test_bbox_inches_tight_layout_notconstrained(tmp_path):
+ # pad_inches='layout' should be ignored when not using constrained/
+ # compressed layout. Smoke test that savefig doesn't error in this case.
+ fig, ax = plt.subplots()
+ fig.savefig(tmp_path / 'foo.png', bbox_inches='tight', pad_inches='layout')
+
+
+@image_comparison(['bbox_inches_tight_clipping'],
+ remove_text=True, savefig_kwarg={'bbox_inches': 'tight'})
+def test_bbox_inches_tight_clipping():
+ # tests bbox clipping on scatter points, and path clipping on a patch
+ # to generate an appropriately tight bbox
+ plt.scatter(np.arange(10), np.arange(10))
+ ax = plt.gca()
+ ax.set_xlim(0, 5)
+ ax.set_ylim(0, 5)
+
+ # make a massive rectangle and clip it with a path
+ patch = mpatches.Rectangle([-50, -50], 100, 100,
+ transform=ax.transData,
+ facecolor='blue', alpha=0.5)
+
+ path = mpath.Path.unit_regular_star(5).deepcopy()
+ path.vertices *= 0.25
+ patch.set_clip_path(path, transform=ax.transAxes)
+ plt.gcf().artists.append(patch)
+
+
+@image_comparison(['bbox_inches_tight_raster'], tol=0.15, # For Ghostscript 10.06+.
+ remove_text=True, savefig_kwarg={'bbox_inches': 'tight'})
+def test_bbox_inches_tight_raster():
+ """Test rasterization with tight_layout"""
+ fig, ax = plt.subplots()
+ ax.plot([1.0, 2.0], rasterized=True)
+
+
+def test_only_on_non_finite_bbox():
+ fig, ax = plt.subplots()
+ ax.annotate("", xy=(0, float('nan')))
+ ax.set_axis_off()
+ # we only need to test that it does not error out on save
+ fig.savefig(BytesIO(), bbox_inches='tight', format='png')
+
+
+def test_tight_pcolorfast():
+ fig, ax = plt.subplots()
+ ax.pcolorfast(np.arange(4).reshape((2, 2)))
+ ax.set(ylim=(0, .1))
+ buf = BytesIO()
+ fig.savefig(buf, bbox_inches="tight")
+ buf.seek(0)
+ height, width, _ = plt.imread(buf).shape
+ # Previously, the bbox would include the area of the image clipped out by
+ # the axes, resulting in a very tall image given the y limits of (0, 0.1).
+ assert width > height
+
+
+def test_noop_tight_bbox():
+ from PIL import Image
+ x_size, y_size = (10, 7)
+ dpi = 100
+ # make the figure just the right size up front
+ fig = plt.figure(frameon=False, dpi=dpi, figsize=(x_size/dpi, y_size/dpi))
+ ax = fig.add_axes((0, 0, 1, 1))
+ ax.set_axis_off()
+ ax.xaxis.set_visible(False)
+ ax.yaxis.set_visible(False)
+
+ data = np.arange(x_size * y_size).reshape(y_size, x_size)
+ ax.imshow(data, rasterized=True)
+
+ # When a rasterized Artist is included, a mixed-mode renderer does
+ # additional bbox adjustment. It should also be a no-op, and not affect the
+ # next save.
+ fig.savefig(BytesIO(), bbox_inches='tight', pad_inches=0, format='pdf')
+
+ out = BytesIO()
+ fig.savefig(out, bbox_inches='tight', pad_inches=0)
+ out.seek(0)
+ im = np.asarray(Image.open(out))
+ assert (im[:, :, 3] == 255).all()
+ assert not (im[:, :, :3] == 255).all()
+ assert im.shape == (7, 10, 4)
+
+
+@image_comparison(['bbox_inches_fixed_aspect'], extensions=['png'],
+ remove_text=True, savefig_kwarg={'bbox_inches': 'tight'})
+def test_bbox_inches_fixed_aspect():
+ with plt.rc_context({'figure.constrained_layout.use': True}):
+ fig, ax = plt.subplots()
+ ax.plot([0, 1])
+ ax.set_xlim(0, 1)
+ ax.set_aspect('equal')
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_bezier.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_bezier.py
new file mode 100644
index 0000000000000000000000000000000000000000..65e2c616e7381ee7e0b611ac8feaac8ab411dac1
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_bezier.py
@@ -0,0 +1,17 @@
+"""
+Tests specific to the bezier module.
+"""
+
+from matplotlib.bezier import inside_circle, split_bezier_intersecting_with_closedpath
+
+
+def test_split_bezier_with_large_values():
+ # These numbers come from gh-27753
+ arrow_path = [(96950809781500.0, 804.7503795623779),
+ (96950809781500.0, 859.6242585800646),
+ (96950809781500.0, 914.4981375977513)]
+ in_f = inside_circle(96950809781500.0, 804.7503795623779, 0.06)
+ split_bezier_intersecting_with_closedpath(arrow_path, in_f)
+ # All we are testing is that this completes
+ # The failure case is an infinite loop resulting from floating point precision
+ # pytest will timeout if that occurs
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_category.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_category.py
new file mode 100644
index 0000000000000000000000000000000000000000..b724e5839c4dea1762f55c2a86e9130ce675588a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_category.py
@@ -0,0 +1,331 @@
+"""Catch all for categorical functions"""
+import warnings
+
+import pytest
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib.axes import Axes
+import matplotlib.pyplot as plt
+import matplotlib.category as cat
+from matplotlib.testing.decorators import check_figures_equal
+
+
+class TestUnitData:
+ test_cases = [('single', (["hello world"], [0])),
+ ('unicode', (["Здрав�твуйте мир"], [0])),
+ ('mixed', (['A', "np.nan", 'B', "3.14", "мир"],
+ [0, 1, 2, 3, 4]))]
+ ids, data = zip(*test_cases)
+
+ @pytest.mark.parametrize("data, locs", data, ids=ids)
+ def test_unit(self, data, locs):
+ unit = cat.UnitData(data)
+ assert list(unit._mapping.keys()) == data
+ assert list(unit._mapping.values()) == locs
+
+ def test_update(self):
+ data = ['a', 'd']
+ locs = [0, 1]
+
+ data_update = ['b', 'd', 'e']
+ unique_data = ['a', 'd', 'b', 'e']
+ updated_locs = [0, 1, 2, 3]
+
+ unit = cat.UnitData(data)
+ assert list(unit._mapping.keys()) == data
+ assert list(unit._mapping.values()) == locs
+
+ unit.update(data_update)
+ assert list(unit._mapping.keys()) == unique_data
+ assert list(unit._mapping.values()) == updated_locs
+
+ failing_test_cases = [("number", 3.14), ("nan", np.nan),
+ ("list", [3.14, 12]), ("mixed type", ["A", 2])]
+
+ fids, fdata = zip(*test_cases)
+
+ @pytest.mark.parametrize("fdata", fdata, ids=fids)
+ def test_non_string_fails(self, fdata):
+ with pytest.raises(TypeError):
+ cat.UnitData(fdata)
+
+ @pytest.mark.parametrize("fdata", fdata, ids=fids)
+ def test_non_string_update_fails(self, fdata):
+ unitdata = cat.UnitData()
+ with pytest.raises(TypeError):
+ unitdata.update(fdata)
+
+
+class FakeAxis:
+ def __init__(self, units):
+ self.units = units
+
+
+class TestStrCategoryConverter:
+ """
+ Based on the pandas conversion and factorization tests:
+
+ ref: /pandas/tseries/tests/test_converter.py
+ /pandas/tests/test_algos.py:TestFactorize
+ """
+ test_cases = [("unicode", ["Здрав�твуйте мир"]),
+ ("ascii", ["hello world"]),
+ ("single", ['a', 'b', 'c']),
+ ("integer string", ["1", "2"]),
+ ("single + values>10", ["A", "B", "C", "D", "E", "F", "G",
+ "H", "I", "J", "K", "L", "M", "N",
+ "O", "P", "Q", "R", "S", "T", "U",
+ "V", "W", "X", "Y", "Z"])]
+
+ ids, values = zip(*test_cases)
+
+ failing_test_cases = [("mixed", [3.14, 'A', np.inf]),
+ ("string integer", ['42', 42])]
+
+ fids, fvalues = zip(*failing_test_cases)
+
+ @pytest.fixture(autouse=True)
+ def mock_axis(self, request):
+ self.cc = cat.StrCategoryConverter()
+ # self.unit should be probably be replaced with real mock unit
+ self.unit = cat.UnitData()
+ self.ax = FakeAxis(self.unit)
+
+ @pytest.mark.parametrize("vals", values, ids=ids)
+ def test_convert(self, vals):
+ np.testing.assert_allclose(self.cc.convert(vals, self.ax.units,
+ self.ax),
+ range(len(vals)))
+
+ @pytest.mark.parametrize("value", ["hi", "мир"], ids=["ascii", "unicode"])
+ def test_convert_one_string(self, value):
+ assert self.cc.convert(value, self.unit, self.ax) == 0
+
+ @pytest.mark.parametrize("fvals", fvalues, ids=fids)
+ def test_convert_fail(self, fvals):
+ with pytest.raises(TypeError):
+ self.cc.convert(fvals, self.unit, self.ax)
+
+ def test_axisinfo(self):
+ axis = self.cc.axisinfo(self.unit, self.ax)
+ assert isinstance(axis.majloc, cat.StrCategoryLocator)
+ assert isinstance(axis.majfmt, cat.StrCategoryFormatter)
+
+ def test_default_units(self):
+ assert isinstance(self.cc.default_units(["a"], self.ax), cat.UnitData)
+
+
+PLOT_LIST = [Axes.scatter, Axes.plot, Axes.bar]
+PLOT_IDS = ["scatter", "plot", "bar"]
+
+
+class TestStrCategoryLocator:
+ def test_StrCategoryLocator(self):
+ locs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ unit = cat.UnitData([str(j) for j in locs])
+ ticks = cat.StrCategoryLocator(unit._mapping)
+ np.testing.assert_array_equal(ticks.tick_values(None, None), locs)
+
+ @pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
+ def test_StrCategoryLocatorPlot(self, plotter):
+ ax = plt.figure().subplots()
+ plotter(ax, [1, 2, 3], ["a", "b", "c"])
+ np.testing.assert_array_equal(ax.yaxis.major.locator(), range(3))
+
+
+class TestStrCategoryFormatter:
+ test_cases = [("ascii", ["hello", "world", "hi"]),
+ ("unicode", ["Здрав�твуйте", "привет"])]
+
+ ids, cases = zip(*test_cases)
+
+ @pytest.mark.parametrize("ydata", cases, ids=ids)
+ def test_StrCategoryFormatter(self, ydata):
+ unit = cat.UnitData(ydata)
+ labels = cat.StrCategoryFormatter(unit._mapping)
+ for i, d in enumerate(ydata):
+ assert labels(i, i) == d
+ assert labels(i, None) == d
+
+ @pytest.mark.parametrize("ydata", cases, ids=ids)
+ @pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
+ def test_StrCategoryFormatterPlot(self, ydata, plotter):
+ ax = plt.figure().subplots()
+ plotter(ax, range(len(ydata)), ydata)
+ for i, d in enumerate(ydata):
+ assert ax.yaxis.major.formatter(i) == d
+ assert ax.yaxis.major.formatter(i+1) == ""
+
+
+def axis_test(axis, labels):
+ ticks = list(range(len(labels)))
+ np.testing.assert_array_equal(axis.get_majorticklocs(), ticks)
+ graph_labels = [axis.major.formatter(i, i) for i in ticks]
+ # _text also decodes bytes as utf-8.
+ assert graph_labels == [cat.StrCategoryFormatter._text(l) for l in labels]
+ assert list(axis.units._mapping.keys()) == [l for l in labels]
+ assert list(axis.units._mapping.values()) == ticks
+
+
+class TestPlotBytes:
+ bytes_cases = [('string list', ['a', 'b', 'c']),
+ ('bytes list', [b'a', b'b', b'c']),
+ ('bytes ndarray', np.array([b'a', b'b', b'c']))]
+
+ bytes_ids, bytes_data = zip(*bytes_cases)
+
+ @pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
+ @pytest.mark.parametrize("bdata", bytes_data, ids=bytes_ids)
+ def test_plot_bytes(self, plotter, bdata):
+ ax = plt.figure().subplots()
+ counts = np.array([4, 6, 5])
+ plotter(ax, bdata, counts)
+ axis_test(ax.xaxis, bdata)
+
+
+class TestPlotNumlike:
+ numlike_cases = [('string list', ['1', '11', '3']),
+ ('string ndarray', np.array(['1', '11', '3'])),
+ ('bytes list', [b'1', b'11', b'3']),
+ ('bytes ndarray', np.array([b'1', b'11', b'3']))]
+ numlike_ids, numlike_data = zip(*numlike_cases)
+
+ @pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
+ @pytest.mark.parametrize("ndata", numlike_data, ids=numlike_ids)
+ def test_plot_numlike(self, plotter, ndata):
+ ax = plt.figure().subplots()
+ counts = np.array([4, 6, 5])
+ plotter(ax, ndata, counts)
+ axis_test(ax.xaxis, ndata)
+
+
+class TestPlotTypes:
+ @pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
+ def test_plot_unicode(self, plotter):
+ ax = plt.figure().subplots()
+ words = ['Здрав�твуйте', 'привет']
+ plotter(ax, words, [0, 1])
+ axis_test(ax.xaxis, words)
+
+ @pytest.fixture
+ def test_data(self):
+ self.x = ["hello", "happy", "world"]
+ self.xy = [2, 6, 3]
+ self.y = ["Python", "is", "fun"]
+ self.yx = [3, 4, 5]
+
+ @pytest.mark.usefixtures("test_data")
+ @pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
+ def test_plot_xaxis(self, test_data, plotter):
+ ax = plt.figure().subplots()
+ plotter(ax, self.x, self.xy)
+ axis_test(ax.xaxis, self.x)
+
+ @pytest.mark.usefixtures("test_data")
+ @pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
+ def test_plot_yaxis(self, test_data, plotter):
+ ax = plt.figure().subplots()
+ plotter(ax, self.yx, self.y)
+ axis_test(ax.yaxis, self.y)
+
+ @pytest.mark.usefixtures("test_data")
+ @pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
+ def test_plot_xyaxis(self, test_data, plotter):
+ ax = plt.figure().subplots()
+ plotter(ax, self.x, self.y)
+ axis_test(ax.xaxis, self.x)
+ axis_test(ax.yaxis, self.y)
+
+ @pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
+ def test_update_plot(self, plotter):
+ ax = plt.figure().subplots()
+ plotter(ax, ['a', 'b'], ['e', 'g'])
+ plotter(ax, ['a', 'b', 'd'], ['f', 'a', 'b'])
+ plotter(ax, ['b', 'c', 'd'], ['g', 'e', 'd'])
+ axis_test(ax.xaxis, ['a', 'b', 'd', 'c'])
+ axis_test(ax.yaxis, ['e', 'g', 'f', 'a', 'b', 'd'])
+
+ def test_update_plot_heterogenous_plotter(self):
+ ax = plt.figure().subplots()
+ ax.scatter(['a', 'b'], ['e', 'g'])
+ ax.plot(['a', 'b', 'd'], ['f', 'a', 'b'])
+ ax.bar(['b', 'c', 'd'], ['g', 'e', 'd'])
+ axis_test(ax.xaxis, ['a', 'b', 'd', 'c'])
+ axis_test(ax.yaxis, ['e', 'g', 'f', 'a', 'b', 'd'])
+
+ failing_test_cases = [("mixed", ['A', 3.14]),
+ ("number integer", ['1', 1]),
+ ("string integer", ['42', 42]),
+ ("missing", ['12', np.nan])]
+
+ fids, fvalues = zip(*failing_test_cases)
+
+ plotters = [Axes.scatter, Axes.bar,
+ pytest.param(Axes.plot, marks=pytest.mark.xfail)]
+
+ @pytest.mark.parametrize("plotter", plotters)
+ @pytest.mark.parametrize("xdata", fvalues, ids=fids)
+ def test_mixed_type_exception(self, plotter, xdata):
+ ax = plt.figure().subplots()
+ with pytest.raises(TypeError):
+ plotter(ax, xdata, [1, 2])
+
+ @pytest.mark.parametrize("plotter", plotters)
+ @pytest.mark.parametrize("xdata", fvalues, ids=fids)
+ def test_mixed_type_update_exception(self, plotter, xdata):
+ ax = plt.figure().subplots()
+ with pytest.raises(TypeError):
+ plotter(ax, [0, 3], [1, 3])
+ plotter(ax, xdata, [1, 2])
+
+
+@mpl.style.context('default')
+@check_figures_equal(extensions=["png"])
+def test_overriding_units_in_plot(fig_test, fig_ref):
+ from datetime import datetime
+
+ t0 = datetime(2018, 3, 1)
+ t1 = datetime(2018, 3, 2)
+ t2 = datetime(2018, 3, 3)
+ t3 = datetime(2018, 3, 4)
+
+ ax_test = fig_test.subplots()
+ ax_ref = fig_ref.subplots()
+ for ax, kwargs in zip([ax_test, ax_ref],
+ ({}, dict(xunits=None, yunits=None))):
+ # First call works
+ ax.plot([t0, t1], ["V1", "V2"], **kwargs)
+ x_units = ax.xaxis.units
+ y_units = ax.yaxis.units
+ # this should not raise
+ ax.plot([t2, t3], ["V1", "V2"], **kwargs)
+ # assert that we have not re-set the units attribute at all
+ assert x_units is ax.xaxis.units
+ assert y_units is ax.yaxis.units
+
+
+def test_no_deprecation_on_empty_data():
+ """
+ Smoke test to check that no deprecation warning is emitted. See #22640.
+ """
+ f, ax = plt.subplots()
+ ax.xaxis.update_units(["a", "b"])
+ ax.plot([], [])
+
+
+def test_hist():
+ fig, ax = plt.subplots()
+ n, bins, patches = ax.hist(['a', 'b', 'a', 'c', 'ff'])
+ assert n.shape == (10,)
+ np.testing.assert_allclose(n, [2., 0., 0., 1., 0., 0., 1., 0., 0., 1.])
+
+
+def test_set_lim():
+ # Numpy 1.25 deprecated casting [2.] to float, catch_warnings added to error
+ # with numpy 1.25 and prior to the change from gh-26597
+ # can be removed once the minimum numpy version has expired the warning
+ f, ax = plt.subplots()
+ ax.plot(["a", "b", "c", "d"], [1, 2, 3, 4])
+ with warnings.catch_warnings():
+ ax.set_xlim("b", "c")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_cbook.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_cbook.py
new file mode 100644
index 0000000000000000000000000000000000000000..639563afeb4d0ae942d08a2e8e21d0ff8bc4757c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_cbook.py
@@ -0,0 +1,1049 @@
+from __future__ import annotations
+
+import itertools
+import pathlib
+import pickle
+import sys
+
+from typing import Any
+from unittest.mock import patch, Mock
+
+from datetime import datetime, date, timedelta
+
+import numpy as np
+from numpy.testing import (assert_array_equal, assert_approx_equal,
+ assert_array_almost_equal)
+import pytest
+
+from matplotlib import _api, cbook
+import matplotlib.colors as mcolors
+from matplotlib.cbook import delete_masked_points, strip_math
+from types import ModuleType
+
+
+class Test_delete_masked_points:
+ def test_bad_first_arg(self):
+ with pytest.raises(ValueError):
+ delete_masked_points('a string', np.arange(1.0, 7.0))
+
+ def test_string_seq(self):
+ a1 = ['a', 'b', 'c', 'd', 'e', 'f']
+ a2 = [1, 2, 3, np.nan, np.nan, 6]
+ result1, result2 = delete_masked_points(a1, a2)
+ ind = [0, 1, 2, 5]
+ assert_array_equal(result1, np.array(a1)[ind])
+ assert_array_equal(result2, np.array(a2)[ind])
+
+ def test_datetime(self):
+ dates = [datetime(2008, 1, 1), datetime(2008, 1, 2),
+ datetime(2008, 1, 3), datetime(2008, 1, 4),
+ datetime(2008, 1, 5), datetime(2008, 1, 6)]
+ a_masked = np.ma.array([1, 2, 3, np.nan, np.nan, 6],
+ mask=[False, False, True, True, False, False])
+ actual = delete_masked_points(dates, a_masked)
+ ind = [0, 1, 5]
+ assert_array_equal(actual[0], np.array(dates)[ind])
+ assert_array_equal(actual[1], a_masked[ind].compressed())
+
+ def test_rgba(self):
+ a_masked = np.ma.array([1, 2, 3, np.nan, np.nan, 6],
+ mask=[False, False, True, True, False, False])
+ a_rgba = mcolors.to_rgba_array(['r', 'g', 'b', 'c', 'm', 'y'])
+ actual = delete_masked_points(a_masked, a_rgba)
+ ind = [0, 1, 5]
+ assert_array_equal(actual[0], a_masked[ind].compressed())
+ assert_array_equal(actual[1], a_rgba[ind])
+
+
+class Test_boxplot_stats:
+ def setup_method(self):
+ np.random.seed(937)
+ self.nrows = 37
+ self.ncols = 4
+ self.data = np.random.lognormal(size=(self.nrows, self.ncols),
+ mean=1.5, sigma=1.75)
+ self.known_keys = sorted([
+ 'mean', 'med', 'q1', 'q3', 'iqr',
+ 'cilo', 'cihi', 'whislo', 'whishi',
+ 'fliers', 'label'
+ ])
+ self.std_results = cbook.boxplot_stats(self.data)
+
+ self.known_nonbootstrapped_res = {
+ 'cihi': 6.8161283264444847,
+ 'cilo': -0.1489815330368689,
+ 'iqr': 13.492709959447094,
+ 'mean': 13.00447442387868,
+ 'med': 3.3335733967038079,
+ 'fliers': np.array([
+ 92.55467075, 87.03819018, 42.23204914, 39.29390996
+ ]),
+ 'q1': 1.3597529879465153,
+ 'q3': 14.85246294739361,
+ 'whishi': 27.899688243699629,
+ 'whislo': 0.042143774965502923
+ }
+
+ self.known_bootstrapped_ci = {
+ 'cihi': 8.939577523357828,
+ 'cilo': 1.8692703958676578,
+ }
+
+ self.known_whis3_res = {
+ 'whishi': 42.232049135969874,
+ 'whislo': 0.042143774965502923,
+ 'fliers': np.array([92.55467075, 87.03819018]),
+ }
+
+ self.known_res_percentiles = {
+ 'whislo': 0.1933685896907924,
+ 'whishi': 42.232049135969874
+ }
+
+ self.known_res_range = {
+ 'whislo': 0.042143774965502923,
+ 'whishi': 92.554670752188699
+
+ }
+
+ def test_form_main_list(self):
+ assert isinstance(self.std_results, list)
+
+ def test_form_each_dict(self):
+ for res in self.std_results:
+ assert isinstance(res, dict)
+
+ def test_form_dict_keys(self):
+ for res in self.std_results:
+ assert set(res) <= set(self.known_keys)
+
+ def test_results_baseline(self):
+ res = self.std_results[0]
+ for key, value in self.known_nonbootstrapped_res.items():
+ assert_array_almost_equal(res[key], value)
+
+ def test_results_bootstrapped(self):
+ results = cbook.boxplot_stats(self.data, bootstrap=10000)
+ res = results[0]
+ for key, value in self.known_bootstrapped_ci.items():
+ assert_approx_equal(res[key], value)
+
+ def test_results_whiskers_float(self):
+ results = cbook.boxplot_stats(self.data, whis=3)
+ res = results[0]
+ for key, value in self.known_whis3_res.items():
+ assert_array_almost_equal(res[key], value)
+
+ def test_results_whiskers_range(self):
+ results = cbook.boxplot_stats(self.data, whis=[0, 100])
+ res = results[0]
+ for key, value in self.known_res_range.items():
+ assert_array_almost_equal(res[key], value)
+
+ def test_results_whiskers_percentiles(self):
+ results = cbook.boxplot_stats(self.data, whis=[5, 95])
+ res = results[0]
+ for key, value in self.known_res_percentiles.items():
+ assert_array_almost_equal(res[key], value)
+
+ def test_results_withlabels(self):
+ labels = ['Test1', 2, 'Aardvark', 4]
+ results = cbook.boxplot_stats(self.data, labels=labels)
+ for lab, res in zip(labels, results):
+ assert res['label'] == lab
+
+ results = cbook.boxplot_stats(self.data)
+ for res in results:
+ assert 'label' not in res
+
+ def test_label_error(self):
+ labels = [1, 2]
+ with pytest.raises(ValueError):
+ cbook.boxplot_stats(self.data, labels=labels)
+
+ def test_bad_dims(self):
+ data = np.random.normal(size=(34, 34, 34))
+ with pytest.raises(ValueError):
+ cbook.boxplot_stats(data)
+
+ def test_boxplot_stats_autorange_false(self):
+ x = np.zeros(shape=140)
+ x = np.hstack([-25, x, 25])
+ bstats_false = cbook.boxplot_stats(x, autorange=False)
+ bstats_true = cbook.boxplot_stats(x, autorange=True)
+
+ assert bstats_false[0]['whislo'] == 0
+ assert bstats_false[0]['whishi'] == 0
+ assert_array_almost_equal(bstats_false[0]['fliers'], [-25, 25])
+
+ assert bstats_true[0]['whislo'] == -25
+ assert bstats_true[0]['whishi'] == 25
+ assert_array_almost_equal(bstats_true[0]['fliers'], [])
+
+
+class Hashable:
+ def dummy(self): pass
+
+
+class Unhashable:
+ __hash__ = None # type: ignore[assignment]
+ def dummy(self): pass
+
+
+class Test_callback_registry:
+ def setup_method(self):
+ self.signal = 'test'
+ self.callbacks = cbook.CallbackRegistry()
+
+ def connect(self, s, func, pickle):
+ if pickle:
+ return self.callbacks.connect(s, func)
+ else:
+ return self.callbacks._connect_picklable(s, func)
+
+ def disconnect(self, cid):
+ return self.callbacks.disconnect(cid)
+
+ def count(self):
+ count1 = sum(s == self.signal for s, p in self.callbacks._func_cid_map)
+ count2 = len(self.callbacks.callbacks.get(self.signal))
+ assert count1 == count2
+ return count1
+
+ def is_empty(self):
+ np.testing.break_cycles()
+ assert [*self.callbacks._func_cid_map] == []
+ assert self.callbacks.callbacks == {}
+ assert self.callbacks._pickled_cids == set()
+
+ def is_not_empty(self):
+ np.testing.break_cycles()
+ assert [*self.callbacks._func_cid_map] != []
+ assert self.callbacks.callbacks != {}
+
+ def test_cid_restore(self):
+ cb = cbook.CallbackRegistry()
+ cb.connect('a', lambda: None)
+ cb2 = pickle.loads(pickle.dumps(cb))
+ cid = cb2.connect('c', lambda: None)
+ assert cid == 1
+
+ @pytest.mark.parametrize('pickle', [True, False])
+ @pytest.mark.parametrize('cls', [Hashable, Unhashable])
+ def test_callback_complete(self, pickle, cls):
+ # ensure we start with an empty registry
+ self.is_empty()
+
+ # create a class for testing
+ mini_me = cls()
+
+ # test that we can add a callback
+ cid1 = self.connect(self.signal, mini_me.dummy, pickle)
+ assert type(cid1) is int
+ self.is_not_empty()
+
+ # test that we don't add a second callback
+ cid2 = self.connect(self.signal, mini_me.dummy, pickle)
+ assert cid1 == cid2
+ self.is_not_empty()
+ assert len([*self.callbacks._func_cid_map]) == 1
+ assert len(self.callbacks.callbacks) == 1
+
+ del mini_me
+
+ # check we now have no callbacks registered
+ self.is_empty()
+
+ @pytest.mark.parametrize('pickle', [True, False])
+ @pytest.mark.parametrize('cls', [Hashable, Unhashable])
+ def test_callback_disconnect(self, pickle, cls):
+ # ensure we start with an empty registry
+ self.is_empty()
+
+ # create a class for testing
+ mini_me = cls()
+
+ # test that we can add a callback
+ cid1 = self.connect(self.signal, mini_me.dummy, pickle)
+ assert type(cid1) is int
+ self.is_not_empty()
+
+ self.disconnect(cid1)
+
+ # check we now have no callbacks registered
+ self.is_empty()
+
+ @pytest.mark.parametrize('pickle', [True, False])
+ @pytest.mark.parametrize('cls', [Hashable, Unhashable])
+ def test_callback_wrong_disconnect(self, pickle, cls):
+ # ensure we start with an empty registry
+ self.is_empty()
+
+ # create a class for testing
+ mini_me = cls()
+
+ # test that we can add a callback
+ cid1 = self.connect(self.signal, mini_me.dummy, pickle)
+ assert type(cid1) is int
+ self.is_not_empty()
+
+ self.disconnect("foo")
+
+ # check we still have callbacks registered
+ self.is_not_empty()
+
+ @pytest.mark.parametrize('pickle', [True, False])
+ @pytest.mark.parametrize('cls', [Hashable, Unhashable])
+ def test_registration_on_non_empty_registry(self, pickle, cls):
+ # ensure we start with an empty registry
+ self.is_empty()
+
+ # setup the registry with a callback
+ mini_me = cls()
+ self.connect(self.signal, mini_me.dummy, pickle)
+
+ # Add another callback
+ mini_me2 = cls()
+ self.connect(self.signal, mini_me2.dummy, pickle)
+
+ # Remove and add the second callback
+ mini_me2 = cls()
+ self.connect(self.signal, mini_me2.dummy, pickle)
+
+ # We still have 2 references
+ self.is_not_empty()
+ assert self.count() == 2
+
+ # Removing the last 2 references
+ mini_me = None
+ mini_me2 = None
+ self.is_empty()
+
+ def test_pickling(self):
+ assert hasattr(pickle.loads(pickle.dumps(cbook.CallbackRegistry())),
+ "callbacks")
+
+
+def test_callbackregistry_default_exception_handler(capsys, monkeypatch):
+ cb = cbook.CallbackRegistry()
+ cb.connect("foo", lambda: None)
+
+ monkeypatch.setattr(
+ cbook, "_get_running_interactive_framework", lambda: None)
+ with pytest.raises(TypeError):
+ cb.process("foo", "argument mismatch")
+ outerr = capsys.readouterr()
+ assert outerr.out == outerr.err == ""
+
+ monkeypatch.setattr(
+ cbook, "_get_running_interactive_framework", lambda: "not-none")
+ cb.process("foo", "argument mismatch") # No error in that case.
+ outerr = capsys.readouterr()
+ assert outerr.out == ""
+ assert "takes 0 positional arguments but 1 was given" in outerr.err
+
+
+def raising_cb_reg(func):
+ class TestException(Exception):
+ pass
+
+ def raise_runtime_error():
+ raise RuntimeError
+
+ def raise_value_error():
+ raise ValueError
+
+ def transformer(excp):
+ if isinstance(excp, RuntimeError):
+ raise TestException
+ raise excp
+
+ # old default
+ cb_old = cbook.CallbackRegistry(exception_handler=None)
+ cb_old.connect('foo', raise_runtime_error)
+
+ # filter
+ cb_filt = cbook.CallbackRegistry(exception_handler=transformer)
+ cb_filt.connect('foo', raise_runtime_error)
+
+ # filter
+ cb_filt_pass = cbook.CallbackRegistry(exception_handler=transformer)
+ cb_filt_pass.connect('foo', raise_value_error)
+
+ return pytest.mark.parametrize('cb, excp',
+ [[cb_old, RuntimeError],
+ [cb_filt, TestException],
+ [cb_filt_pass, ValueError]])(func)
+
+
+@raising_cb_reg
+def test_callbackregistry_custom_exception_handler(monkeypatch, cb, excp):
+ monkeypatch.setattr(
+ cbook, "_get_running_interactive_framework", lambda: None)
+ with pytest.raises(excp):
+ cb.process('foo')
+
+
+def test_callbackregistry_signals():
+ cr = cbook.CallbackRegistry(signals=["foo"])
+ results = []
+ def cb(x): results.append(x)
+ cr.connect("foo", cb)
+ with pytest.raises(ValueError):
+ cr.connect("bar", cb)
+ cr.process("foo", 1)
+ with pytest.raises(ValueError):
+ cr.process("bar", 1)
+ assert results == [1]
+
+
+def test_callbackregistry_blocking():
+ # Needs an exception handler for interactive testing environments
+ # that would only print this out instead of raising the exception
+ def raise_handler(excp):
+ raise excp
+ cb = cbook.CallbackRegistry(exception_handler=raise_handler)
+ def test_func1():
+ raise ValueError("1 should be blocked")
+ def test_func2():
+ raise ValueError("2 should be blocked")
+ cb.connect("test1", test_func1)
+ cb.connect("test2", test_func2)
+
+ # block all of the callbacks to make sure they aren't processed
+ with cb.blocked():
+ cb.process("test1")
+ cb.process("test2")
+
+ # block individual callbacks to make sure the other is still processed
+ with cb.blocked(signal="test1"):
+ # Blocked
+ cb.process("test1")
+ # Should raise
+ with pytest.raises(ValueError, match="2 should be blocked"):
+ cb.process("test2")
+
+ # Make sure the original callback functions are there after blocking
+ with pytest.raises(ValueError, match="1 should be blocked"):
+ cb.process("test1")
+ with pytest.raises(ValueError, match="2 should be blocked"):
+ cb.process("test2")
+
+
+@pytest.mark.parametrize('line, result', [
+ ('a : no_comment', 'a : no_comment'),
+ ('a : "quoted str"', 'a : "quoted str"'),
+ ('a : "quoted str" # comment', 'a : "quoted str"'),
+ ('a : "#000000"', 'a : "#000000"'),
+ ('a : "#000000" # comment', 'a : "#000000"'),
+ ('a : ["#000000", "#FFFFFF"]', 'a : ["#000000", "#FFFFFF"]'),
+ ('a : ["#000000", "#FFFFFF"] # comment', 'a : ["#000000", "#FFFFFF"]'),
+ ('a : val # a comment "with quotes"', 'a : val'),
+ ('# only comment "with quotes" xx', ''),
+])
+def test_strip_comment(line, result):
+ """Strip everything from the first unquoted #."""
+ assert cbook._strip_comment(line) == result
+
+
+def test_strip_comment_invalid():
+ with pytest.raises(ValueError, match="Missing closing quote"):
+ cbook._strip_comment('grid.color: "aa')
+
+
+def test_sanitize_sequence():
+ d = {'a': 1, 'b': 2, 'c': 3}
+ k = ['a', 'b', 'c']
+ v = [1, 2, 3]
+ i = [('a', 1), ('b', 2), ('c', 3)]
+ assert k == sorted(cbook.sanitize_sequence(d.keys()))
+ assert v == sorted(cbook.sanitize_sequence(d.values()))
+ assert i == sorted(cbook.sanitize_sequence(d.items()))
+ assert i == cbook.sanitize_sequence(i)
+ assert k == cbook.sanitize_sequence(k)
+
+
+fail_mapping: tuple[tuple[dict, dict], ...] = (
+ ({'a': 1, 'b': 2}, {'alias_mapping': {'a': ['b']}}),
+ ({'a': 1, 'b': 2}, {'alias_mapping': {'a': ['a', 'b']}}),
+)
+
+pass_mapping: tuple[tuple[Any, dict, dict], ...] = (
+ (None, {}, {}),
+ ({'a': 1, 'b': 2}, {'a': 1, 'b': 2}, {}),
+ ({'b': 2}, {'a': 2}, {'alias_mapping': {'a': ['a', 'b']}}),
+)
+
+
+@pytest.mark.parametrize('inp, kwargs_to_norm', fail_mapping)
+def test_normalize_kwargs_fail(inp, kwargs_to_norm):
+ with pytest.raises(TypeError), _api.suppress_matplotlib_deprecation_warning():
+ cbook.normalize_kwargs(inp, **kwargs_to_norm)
+
+
+@pytest.mark.parametrize('inp, expected, kwargs_to_norm',
+ pass_mapping)
+def test_normalize_kwargs_pass(inp, expected, kwargs_to_norm):
+ with _api.suppress_matplotlib_deprecation_warning():
+ # No other warning should be emitted.
+ assert expected == cbook.normalize_kwargs(inp, **kwargs_to_norm)
+
+
+def test_warn_external(recwarn):
+ _api.warn_external("oops")
+ assert len(recwarn) == 1
+ if sys.version_info[:2] >= (3, 12):
+ # With Python 3.12, we let Python figure out the stacklevel using the
+ # `skip_file_prefixes` argument, which cannot exempt tests, so just confirm
+ # the filename is not in the package.
+ basedir = pathlib.Path(__file__).parents[2]
+ assert not recwarn[0].filename.startswith((str(basedir / 'matplotlib'),
+ str(basedir / 'mpl_toolkits')))
+ else:
+ # On older Python versions, we manually calculated the stacklevel, and had an
+ # exception for our own tests.
+ assert recwarn[0].filename == __file__
+
+
+def test_warn_external_frame_embedded_python():
+ with patch.object(cbook, "sys") as mock_sys:
+ mock_sys._getframe = Mock(return_value=None)
+ with pytest.warns(UserWarning, match=r"\Adummy\Z"):
+ _api.warn_external("dummy")
+
+
+def test_to_prestep():
+ x = np.arange(4)
+ y1 = np.arange(4)
+ y2 = np.arange(4)[::-1]
+
+ xs, y1s, y2s = cbook.pts_to_prestep(x, y1, y2)
+
+ x_target = np.asarray([0, 0, 1, 1, 2, 2, 3], dtype=float)
+ y1_target = np.asarray([0, 1, 1, 2, 2, 3, 3], dtype=float)
+ y2_target = np.asarray([3, 2, 2, 1, 1, 0, 0], dtype=float)
+
+ assert_array_equal(x_target, xs)
+ assert_array_equal(y1_target, y1s)
+ assert_array_equal(y2_target, y2s)
+
+ xs, y1s = cbook.pts_to_prestep(x, y1)
+ assert_array_equal(x_target, xs)
+ assert_array_equal(y1_target, y1s)
+
+
+def test_to_prestep_empty():
+ steps = cbook.pts_to_prestep([], [])
+ assert steps.shape == (2, 0)
+
+
+def test_to_poststep():
+ x = np.arange(4)
+ y1 = np.arange(4)
+ y2 = np.arange(4)[::-1]
+
+ xs, y1s, y2s = cbook.pts_to_poststep(x, y1, y2)
+
+ x_target = np.asarray([0, 1, 1, 2, 2, 3, 3], dtype=float)
+ y1_target = np.asarray([0, 0, 1, 1, 2, 2, 3], dtype=float)
+ y2_target = np.asarray([3, 3, 2, 2, 1, 1, 0], dtype=float)
+
+ assert_array_equal(x_target, xs)
+ assert_array_equal(y1_target, y1s)
+ assert_array_equal(y2_target, y2s)
+
+ xs, y1s = cbook.pts_to_poststep(x, y1)
+ assert_array_equal(x_target, xs)
+ assert_array_equal(y1_target, y1s)
+
+
+def test_to_poststep_empty():
+ steps = cbook.pts_to_poststep([], [])
+ assert steps.shape == (2, 0)
+
+
+def test_to_midstep():
+ x = np.arange(4)
+ y1 = np.arange(4)
+ y2 = np.arange(4)[::-1]
+
+ xs, y1s, y2s = cbook.pts_to_midstep(x, y1, y2)
+
+ x_target = np.asarray([0, .5, .5, 1.5, 1.5, 2.5, 2.5, 3], dtype=float)
+ y1_target = np.asarray([0, 0, 1, 1, 2, 2, 3, 3], dtype=float)
+ y2_target = np.asarray([3, 3, 2, 2, 1, 1, 0, 0], dtype=float)
+
+ assert_array_equal(x_target, xs)
+ assert_array_equal(y1_target, y1s)
+ assert_array_equal(y2_target, y2s)
+
+ xs, y1s = cbook.pts_to_midstep(x, y1)
+ assert_array_equal(x_target, xs)
+ assert_array_equal(y1_target, y1s)
+
+
+def test_to_midstep_empty():
+ steps = cbook.pts_to_midstep([], [])
+ assert steps.shape == (2, 0)
+
+
+@pytest.mark.parametrize(
+ "args",
+ [(np.arange(12).reshape(3, 4), 'a'),
+ (np.arange(12), 'a'),
+ (np.arange(12), np.arange(3))])
+def test_step_fails(args):
+ with pytest.raises(ValueError):
+ cbook.pts_to_prestep(*args)
+
+
+def test_grouper():
+ class Dummy:
+ pass
+ a, b, c, d, e = objs = [Dummy() for _ in range(5)]
+ g = cbook.Grouper()
+ g.join(*objs)
+ assert set(list(g)[0]) == set(objs)
+ assert set(g.get_siblings(a)) == set(objs)
+
+ for other in objs[1:]:
+ assert g.joined(a, other)
+
+ g.remove(a)
+ for other in objs[1:]:
+ assert not g.joined(a, other)
+
+ for A, B in itertools.product(objs[1:], objs[1:]):
+ assert g.joined(A, B)
+
+
+def test_grouper_private():
+ class Dummy:
+ pass
+ objs = [Dummy() for _ in range(5)]
+ g = cbook.Grouper()
+ g.join(*objs)
+ # reach in and touch the internals !
+ mapping = g._mapping
+
+ for o in objs:
+ assert o in mapping
+
+ base_set = mapping[objs[0]]
+ for o in objs[1:]:
+ assert mapping[o] is base_set
+
+
+def test_flatiter():
+ x = np.arange(5)
+ it = x.flat
+ assert 0 == next(it)
+ assert 1 == next(it)
+ ret = cbook._safe_first_finite(it)
+ assert ret == 0
+
+ assert 0 == next(it)
+ assert 1 == next(it)
+
+
+def test__safe_first_finite_all_nan():
+ arr = np.full(2, np.nan)
+ ret = cbook._safe_first_finite(arr)
+ assert np.isnan(ret)
+
+
+def test__safe_first_finite_all_inf():
+ arr = np.full(2, np.inf)
+ ret = cbook._safe_first_finite(arr)
+ assert np.isinf(ret)
+
+
+def test_reshape2d():
+
+ class Dummy:
+ pass
+
+ xnew = cbook._reshape_2D([], 'x')
+ assert np.shape(xnew) == (1, 0)
+
+ x = [Dummy() for _ in range(5)]
+
+ xnew = cbook._reshape_2D(x, 'x')
+ assert np.shape(xnew) == (1, 5)
+
+ x = np.arange(5)
+ xnew = cbook._reshape_2D(x, 'x')
+ assert np.shape(xnew) == (1, 5)
+
+ x = [[Dummy() for _ in range(5)] for _ in range(3)]
+ xnew = cbook._reshape_2D(x, 'x')
+ assert np.shape(xnew) == (3, 5)
+
+ # this is strange behaviour, but...
+ x = np.random.rand(3, 5)
+ xnew = cbook._reshape_2D(x, 'x')
+ assert np.shape(xnew) == (5, 3)
+
+ # Test a list of lists which are all of length 1
+ x = [[1], [2], [3]]
+ xnew = cbook._reshape_2D(x, 'x')
+ assert isinstance(xnew, list)
+ assert isinstance(xnew[0], np.ndarray) and xnew[0].shape == (1,)
+ assert isinstance(xnew[1], np.ndarray) and xnew[1].shape == (1,)
+ assert isinstance(xnew[2], np.ndarray) and xnew[2].shape == (1,)
+
+ # Test a list of zero-dimensional arrays
+ x = [np.array(0), np.array(1), np.array(2)]
+ xnew = cbook._reshape_2D(x, 'x')
+ assert isinstance(xnew, list)
+ assert len(xnew) == 1
+ assert isinstance(xnew[0], np.ndarray) and xnew[0].shape == (3,)
+
+ # Now test with a list of lists with different lengths, which means the
+ # array will internally be converted to a 1D object array of lists
+ x = [[1, 2, 3], [3, 4], [2]]
+ xnew = cbook._reshape_2D(x, 'x')
+ assert isinstance(xnew, list)
+ assert isinstance(xnew[0], np.ndarray) and xnew[0].shape == (3,)
+ assert isinstance(xnew[1], np.ndarray) and xnew[1].shape == (2,)
+ assert isinstance(xnew[2], np.ndarray) and xnew[2].shape == (1,)
+
+ # We now need to make sure that this works correctly for Numpy subclasses
+ # where iterating over items can return subclasses too, which may be
+ # iterable even if they are scalars. To emulate this, we make a Numpy
+ # array subclass that returns Numpy 'scalars' when iterating or accessing
+ # values, and these are technically iterable if checking for example
+ # isinstance(x, collections.abc.Iterable).
+
+ class ArraySubclass(np.ndarray):
+
+ def __iter__(self):
+ for value in super().__iter__():
+ yield np.array(value)
+
+ def __getitem__(self, item):
+ return np.array(super().__getitem__(item))
+
+ v = np.arange(10, dtype=float)
+ x = ArraySubclass((10,), dtype=float, buffer=v.data)
+
+ xnew = cbook._reshape_2D(x, 'x')
+
+ # We check here that the array wasn't split up into many individual
+ # ArraySubclass, which is what used to happen due to a bug in _reshape_2D
+ assert len(xnew) == 1
+ assert isinstance(xnew[0], ArraySubclass)
+
+ # check list of strings:
+ x = ['a', 'b', 'c', 'c', 'dd', 'e', 'f', 'ff', 'f']
+ xnew = cbook._reshape_2D(x, 'x')
+ assert len(xnew[0]) == len(x)
+ assert isinstance(xnew[0], np.ndarray)
+
+
+def test_reshape2d_pandas(pd):
+ # separate to allow the rest of the tests to run if no pandas...
+ X = np.arange(30).reshape(10, 3)
+ x = pd.DataFrame(X, columns=["a", "b", "c"])
+ Xnew = cbook._reshape_2D(x, 'x')
+ # Need to check each row because _reshape_2D returns a list of arrays:
+ for x, xnew in zip(X.T, Xnew):
+ np.testing.assert_array_equal(x, xnew)
+
+
+def test_reshape2d_xarray(xr):
+ # separate to allow the rest of the tests to run if no xarray...
+ X = np.arange(30).reshape(10, 3)
+ x = xr.DataArray(X, dims=["x", "y"])
+ Xnew = cbook._reshape_2D(x, 'x')
+ # Need to check each row because _reshape_2D returns a list of arrays:
+ for x, xnew in zip(X.T, Xnew):
+ np.testing.assert_array_equal(x, xnew)
+
+
+def test_index_of_pandas(pd):
+ # separate to allow the rest of the tests to run if no pandas...
+ X = np.arange(30).reshape(10, 3)
+ x = pd.DataFrame(X, columns=["a", "b", "c"])
+ Idx, Xnew = cbook.index_of(x)
+ np.testing.assert_array_equal(X, Xnew)
+ IdxRef = np.arange(10)
+ np.testing.assert_array_equal(Idx, IdxRef)
+
+
+def test_index_of_xarray(xr):
+ # separate to allow the rest of the tests to run if no xarray...
+ X = np.arange(30).reshape(10, 3)
+ x = xr.DataArray(X, dims=["x", "y"])
+ Idx, Xnew = cbook.index_of(x)
+ np.testing.assert_array_equal(X, Xnew)
+ IdxRef = np.arange(10)
+ np.testing.assert_array_equal(Idx, IdxRef)
+
+
+def test_contiguous_regions():
+ a, b, c = 3, 4, 5
+ # Starts and ends with True
+ mask = [True]*a + [False]*b + [True]*c
+ expected = [(0, a), (a+b, a+b+c)]
+ assert cbook.contiguous_regions(mask) == expected
+ d, e = 6, 7
+ # Starts with True ends with False
+ mask = mask + [False]*e
+ assert cbook.contiguous_regions(mask) == expected
+ # Starts with False ends with True
+ mask = [False]*d + mask[:-e]
+ expected = [(d, d+a), (d+a+b, d+a+b+c)]
+ assert cbook.contiguous_regions(mask) == expected
+ # Starts and ends with False
+ mask = mask + [False]*e
+ assert cbook.contiguous_regions(mask) == expected
+ # No True in mask
+ assert cbook.contiguous_regions([False]*5) == []
+ # Empty mask
+ assert cbook.contiguous_regions([]) == []
+
+
+def test_safe_first_element_pandas_series(pd):
+ # deliberately create a pandas series with index not starting from 0
+ s = pd.Series(range(5), index=range(10, 15))
+ actual = cbook._safe_first_finite(s)
+ assert actual == 0
+
+
+def test_array_patch_perimeters():
+ # This compares the old implementation as a reference for the
+ # vectorized one.
+ def check(x, rstride, cstride):
+ rows, cols = x.shape
+ row_inds = [*range(0, rows-1, rstride), rows-1]
+ col_inds = [*range(0, cols-1, cstride), cols-1]
+ polys = []
+ for rs, rs_next in itertools.pairwise(row_inds):
+ for cs, cs_next in itertools.pairwise(col_inds):
+ # +1 ensures we share edges between polygons
+ ps = cbook._array_perimeter(x[rs:rs_next+1, cs:cs_next+1]).T
+ polys.append(ps)
+ polys = np.asarray(polys)
+ assert np.array_equal(polys,
+ cbook._array_patch_perimeters(
+ x, rstride=rstride, cstride=cstride))
+
+ def divisors(n):
+ return [i for i in range(1, n + 1) if n % i == 0]
+
+ for rows, cols in [(5, 5), (7, 14), (13, 9)]:
+ x = np.arange(rows * cols).reshape(rows, cols)
+ for rstride, cstride in itertools.product(divisors(rows - 1),
+ divisors(cols - 1)):
+ check(x, rstride=rstride, cstride=cstride)
+
+
+def test_setattr_cm():
+ class A:
+ cls_level = object()
+ override = object()
+
+ def __init__(self):
+ self.aardvark = 'aardvark'
+ self.override = 'override'
+ self._p = 'p'
+
+ def meth(self):
+ ...
+
+ @classmethod
+ def classy(cls):
+ ...
+
+ @staticmethod
+ def static():
+ ...
+
+ @property
+ def prop(self):
+ return self._p
+
+ @prop.setter
+ def prop(self, val):
+ self._p = val
+
+ class B(A):
+ ...
+
+ other = A()
+
+ def verify_pre_post_state(obj):
+ # When you access a Python method the function is bound
+ # to the object at access time so you get a new instance
+ # of MethodType every time.
+ #
+ # https://docs.python.org/3/howto/descriptor.html#functions-and-methods
+ assert obj.meth is not obj.meth
+ # normal attribute should give you back the same instance every time
+ assert obj.aardvark is obj.aardvark
+ assert a.aardvark == 'aardvark'
+ # and our property happens to give the same instance every time
+ assert obj.prop is obj.prop
+ assert obj.cls_level is A.cls_level
+ assert obj.override == 'override'
+ assert not hasattr(obj, 'extra')
+ assert obj.prop == 'p'
+ assert obj.monkey == other.meth
+ assert obj.cls_level is A.cls_level
+ assert 'cls_level' not in obj.__dict__
+ assert 'classy' not in obj.__dict__
+ assert 'static' not in obj.__dict__
+
+ a = B()
+
+ a.monkey = other.meth
+ verify_pre_post_state(a)
+ with cbook._setattr_cm(
+ a, prop='squirrel',
+ aardvark='moose', meth=lambda: None,
+ override='boo', extra='extra',
+ monkey=lambda: None, cls_level='bob',
+ classy='classy', static='static'):
+ # because we have set a lambda, it is normal attribute access
+ # and the same every time
+ assert a.meth is a.meth
+ assert a.aardvark is a.aardvark
+ assert a.aardvark == 'moose'
+ assert a.override == 'boo'
+ assert a.extra == 'extra'
+ assert a.prop == 'squirrel'
+ assert a.monkey != other.meth
+ assert a.cls_level == 'bob'
+ assert a.classy == 'classy'
+ assert a.static == 'static'
+
+ verify_pre_post_state(a)
+
+
+def test_format_approx():
+ f = cbook._format_approx
+ assert f(0, 1) == '0'
+ assert f(0, 2) == '0'
+ assert f(0, 3) == '0'
+ assert f(-0.0123, 1) == '-0'
+ assert f(1e-7, 5) == '0'
+ assert f(0.0012345600001, 5) == '0.00123'
+ assert f(-0.0012345600001, 5) == '-0.00123'
+ assert f(0.0012345600001, 8) == f(0.0012345600001, 10) == '0.00123456'
+
+
+def test_safe_first_element_with_none():
+ datetime_lst = [date.today() + timedelta(days=i) for i in range(10)]
+ datetime_lst[0] = None
+ actual = cbook._safe_first_finite(datetime_lst)
+ assert actual is not None and actual == datetime_lst[1]
+
+
+def test_strip_math():
+ assert strip_math(r'1 \times 2') == r'1 \times 2'
+ assert strip_math(r'$1 \times 2$') == '1 x 2'
+ assert strip_math(r'$\rm{hi}$') == 'hi'
+
+
+@pytest.mark.parametrize('fmt, value, result', [
+ ('%.2f m', 0.2, '0.20 m'),
+ ('{:.2f} m', 0.2, '0.20 m'),
+ ('{} m', 0.2, '0.2 m'),
+ ('const', 0.2, 'const'),
+ ('%d or {}', 0.2, '0 or {}'),
+ ('{{{:,.0f}}}', 2e5, '{200,000}'),
+ ('{:.2%}', 2/3, '66.67%'),
+ ('$%g', 2.54, '$2.54'),
+])
+def test_auto_format_str(fmt, value, result):
+ """Apply *value* to the format string *fmt*."""
+ assert cbook._auto_format_str(fmt, value) == result
+ assert cbook._auto_format_str(fmt, np.float64(value)) == result
+
+
+def test_unpack_to_numpy_from_torch():
+ """
+ Test that torch tensors are converted to NumPy arrays.
+
+ We don't want to create a dependency on torch in the test suite, so we mock it.
+ """
+ class Tensor:
+ def __init__(self, data):
+ self.data = data
+
+ def __array__(self):
+ return self.data
+
+ torch = ModuleType('torch')
+ torch.Tensor = Tensor
+ sys.modules['torch'] = torch
+
+ data = np.arange(10)
+ torch_tensor = torch.Tensor(data)
+
+ result = cbook._unpack_to_numpy(torch_tensor)
+ assert isinstance(result, np.ndarray)
+ # compare results, do not check for identity: the latter would fail
+ # if not mocked, and the implementation does not guarantee it
+ # is the same Python object, just the same values.
+ assert_array_equal(result, data)
+
+
+def test_unpack_to_numpy_from_jax():
+ """
+ Test that jax arrays are converted to NumPy arrays.
+
+ We don't want to create a dependency on jax in the test suite, so we mock it.
+ """
+ class Array:
+ def __init__(self, data):
+ self.data = data
+
+ def __array__(self):
+ return self.data
+
+ jax = ModuleType('jax')
+ jax.Array = Array
+
+ sys.modules['jax'] = jax
+
+ data = np.arange(10)
+ jax_array = jax.Array(data)
+
+ result = cbook._unpack_to_numpy(jax_array)
+ assert isinstance(result, np.ndarray)
+ # compare results, do not check for identity: the latter would fail
+ # if not mocked, and the implementation does not guarantee it
+ # is the same Python object, just the same values.
+ assert_array_equal(result, data)
+
+
+def test_unpack_to_numpy_from_tensorflow():
+ """
+ Test that tensorflow arrays are converted to NumPy arrays.
+
+ We don't want to create a dependency on tensorflow in the test suite, so we mock it.
+ """
+ class Tensor:
+ def __init__(self, data):
+ self.data = data
+
+ def __array__(self):
+ return self.data
+
+ tensorflow = ModuleType('tensorflow')
+ tensorflow.is_tensor = lambda x: isinstance(x, Tensor)
+ tensorflow.Tensor = Tensor
+
+ sys.modules['tensorflow'] = tensorflow
+
+ data = np.arange(10)
+ tf_tensor = tensorflow.Tensor(data)
+
+ result = cbook._unpack_to_numpy(tf_tensor)
+ assert isinstance(result, np.ndarray)
+ # compare results, do not check for identity: the latter would fail
+ # if not mocked, and the implementation does not guarantee it
+ # is the same Python object, just the same values.
+ assert_array_equal(result, data)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_collections.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_collections.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c2f66a7a5cca8dbfe85b089de3a17ded0301009
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_collections.py
@@ -0,0 +1,1393 @@
+from datetime import datetime
+import io
+import itertools
+import platform
+import re
+from types import SimpleNamespace
+
+import numpy as np
+from numpy.testing import assert_array_equal, assert_array_almost_equal
+import pytest
+
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+import matplotlib.collections as mcollections
+import matplotlib.colors as mcolors
+import matplotlib.path as mpath
+import matplotlib.transforms as mtransforms
+from matplotlib.collections import (Collection, LineCollection,
+ EventCollection, PolyCollection)
+from matplotlib.collections import FillBetweenPolyCollection
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+
+
+@pytest.fixture(params=["pcolormesh", "pcolor"])
+def pcfunc(request):
+ return request.param
+
+
+def generate_EventCollection_plot():
+ """Generate the initial collection and plot it."""
+ positions = np.array([0., 1., 2., 3., 5., 8., 13., 21.])
+ extra_positions = np.array([34., 55., 89.])
+ orientation = 'horizontal'
+ lineoffset = 1
+ linelength = .5
+ linewidth = 2
+ color = [1, 0, 0, 1]
+ linestyle = 'solid'
+ antialiased = True
+
+ coll = EventCollection(positions,
+ orientation=orientation,
+ lineoffset=lineoffset,
+ linelength=linelength,
+ linewidth=linewidth,
+ color=color,
+ linestyle=linestyle,
+ antialiased=antialiased
+ )
+
+ fig, ax = plt.subplots()
+ ax.add_collection(coll)
+ ax.set_title('EventCollection: default')
+ props = {'positions': positions,
+ 'extra_positions': extra_positions,
+ 'orientation': orientation,
+ 'lineoffset': lineoffset,
+ 'linelength': linelength,
+ 'linewidth': linewidth,
+ 'color': color,
+ 'linestyle': linestyle,
+ 'antialiased': antialiased
+ }
+ ax.set_xlim(-1, 22)
+ ax.set_ylim(0, 2)
+ return ax, coll, props
+
+
+@image_comparison(['EventCollection_plot__default.png'])
+def test__EventCollection__get_props():
+ _, coll, props = generate_EventCollection_plot()
+ # check that the default segments have the correct coordinates
+ check_segments(coll,
+ props['positions'],
+ props['linelength'],
+ props['lineoffset'],
+ props['orientation'])
+ # check that the default positions match the input positions
+ np.testing.assert_array_equal(props['positions'], coll.get_positions())
+ # check that the default orientation matches the input orientation
+ assert props['orientation'] == coll.get_orientation()
+ # check that the default orientation matches the input orientation
+ assert coll.is_horizontal()
+ # check that the default linelength matches the input linelength
+ assert props['linelength'] == coll.get_linelength()
+ # check that the default lineoffset matches the input lineoffset
+ assert props['lineoffset'] == coll.get_lineoffset()
+ # check that the default linestyle matches the input linestyle
+ assert coll.get_linestyle() == [(0, None)]
+ # check that the default color matches the input color
+ for color in [coll.get_color(), *coll.get_colors()]:
+ np.testing.assert_array_equal(color, props['color'])
+
+
+@image_comparison(['EventCollection_plot__set_positions.png'])
+def test__EventCollection__set_positions():
+ splt, coll, props = generate_EventCollection_plot()
+ new_positions = np.hstack([props['positions'], props['extra_positions']])
+ coll.set_positions(new_positions)
+ np.testing.assert_array_equal(new_positions, coll.get_positions())
+ check_segments(coll, new_positions,
+ props['linelength'],
+ props['lineoffset'],
+ props['orientation'])
+ splt.set_title('EventCollection: set_positions')
+ splt.set_xlim(-1, 90)
+
+
+@image_comparison(['EventCollection_plot__add_positions.png'])
+def test__EventCollection__add_positions():
+ splt, coll, props = generate_EventCollection_plot()
+ new_positions = np.hstack([props['positions'],
+ props['extra_positions'][0]])
+ coll.switch_orientation() # Test adding in the vertical orientation, too.
+ coll.add_positions(props['extra_positions'][0])
+ coll.switch_orientation()
+ np.testing.assert_array_equal(new_positions, coll.get_positions())
+ check_segments(coll,
+ new_positions,
+ props['linelength'],
+ props['lineoffset'],
+ props['orientation'])
+ splt.set_title('EventCollection: add_positions')
+ splt.set_xlim(-1, 35)
+
+
+@image_comparison(['EventCollection_plot__append_positions.png'])
+def test__EventCollection__append_positions():
+ splt, coll, props = generate_EventCollection_plot()
+ new_positions = np.hstack([props['positions'],
+ props['extra_positions'][2]])
+ coll.append_positions(props['extra_positions'][2])
+ np.testing.assert_array_equal(new_positions, coll.get_positions())
+ check_segments(coll,
+ new_positions,
+ props['linelength'],
+ props['lineoffset'],
+ props['orientation'])
+ splt.set_title('EventCollection: append_positions')
+ splt.set_xlim(-1, 90)
+
+
+@image_comparison(['EventCollection_plot__extend_positions.png'])
+def test__EventCollection__extend_positions():
+ splt, coll, props = generate_EventCollection_plot()
+ new_positions = np.hstack([props['positions'],
+ props['extra_positions'][1:]])
+ coll.extend_positions(props['extra_positions'][1:])
+ np.testing.assert_array_equal(new_positions, coll.get_positions())
+ check_segments(coll,
+ new_positions,
+ props['linelength'],
+ props['lineoffset'],
+ props['orientation'])
+ splt.set_title('EventCollection: extend_positions')
+ splt.set_xlim(-1, 90)
+
+
+@image_comparison(['EventCollection_plot__switch_orientation.png'])
+def test__EventCollection__switch_orientation():
+ splt, coll, props = generate_EventCollection_plot()
+ new_orientation = 'vertical'
+ coll.switch_orientation()
+ assert new_orientation == coll.get_orientation()
+ assert not coll.is_horizontal()
+ new_positions = coll.get_positions()
+ check_segments(coll,
+ new_positions,
+ props['linelength'],
+ props['lineoffset'], new_orientation)
+ splt.set_title('EventCollection: switch_orientation')
+ splt.set_ylim(-1, 22)
+ splt.set_xlim(0, 2)
+
+
+@image_comparison(['EventCollection_plot__switch_orientation__2x.png'])
+def test__EventCollection__switch_orientation_2x():
+ """
+ Check that calling switch_orientation twice sets the orientation back to
+ the default.
+ """
+ splt, coll, props = generate_EventCollection_plot()
+ coll.switch_orientation()
+ coll.switch_orientation()
+ new_positions = coll.get_positions()
+ assert props['orientation'] == coll.get_orientation()
+ assert coll.is_horizontal()
+ np.testing.assert_array_equal(props['positions'], new_positions)
+ check_segments(coll,
+ new_positions,
+ props['linelength'],
+ props['lineoffset'],
+ props['orientation'])
+ splt.set_title('EventCollection: switch_orientation 2x')
+
+
+@image_comparison(['EventCollection_plot__set_orientation.png'])
+def test__EventCollection__set_orientation():
+ splt, coll, props = generate_EventCollection_plot()
+ new_orientation = 'vertical'
+ coll.set_orientation(new_orientation)
+ assert new_orientation == coll.get_orientation()
+ assert not coll.is_horizontal()
+ check_segments(coll,
+ props['positions'],
+ props['linelength'],
+ props['lineoffset'],
+ new_orientation)
+ splt.set_title('EventCollection: set_orientation')
+ splt.set_ylim(-1, 22)
+ splt.set_xlim(0, 2)
+
+
+@image_comparison(['EventCollection_plot__set_linelength.png'])
+def test__EventCollection__set_linelength():
+ splt, coll, props = generate_EventCollection_plot()
+ new_linelength = 15
+ coll.set_linelength(new_linelength)
+ assert new_linelength == coll.get_linelength()
+ check_segments(coll,
+ props['positions'],
+ new_linelength,
+ props['lineoffset'],
+ props['orientation'])
+ splt.set_title('EventCollection: set_linelength')
+ splt.set_ylim(-20, 20)
+
+
+@image_comparison(['EventCollection_plot__set_lineoffset.png'])
+def test__EventCollection__set_lineoffset():
+ splt, coll, props = generate_EventCollection_plot()
+ new_lineoffset = -5.
+ coll.set_lineoffset(new_lineoffset)
+ assert new_lineoffset == coll.get_lineoffset()
+ check_segments(coll,
+ props['positions'],
+ props['linelength'],
+ new_lineoffset,
+ props['orientation'])
+ splt.set_title('EventCollection: set_lineoffset')
+ splt.set_ylim(-6, -4)
+
+
+@image_comparison([
+ 'EventCollection_plot__set_linestyle.png',
+ 'EventCollection_plot__set_linestyle.png',
+ 'EventCollection_plot__set_linewidth.png',
+])
+def test__EventCollection__set_prop():
+ for prop, value, expected in [
+ ('linestyle', 'dashed', [(0, (6.0, 6.0))]),
+ ('linestyle', (0, (6., 6.)), [(0, (6.0, 6.0))]),
+ ('linewidth', 5, 5),
+ ]:
+ splt, coll, _ = generate_EventCollection_plot()
+ coll.set(**{prop: value})
+ assert plt.getp(coll, prop) == expected
+ splt.set_title(f'EventCollection: set_{prop}')
+
+
+@image_comparison(['EventCollection_plot__set_color.png'])
+def test__EventCollection__set_color():
+ splt, coll, _ = generate_EventCollection_plot()
+ new_color = np.array([0, 1, 1, 1])
+ coll.set_color(new_color)
+ for color in [coll.get_color(), *coll.get_colors()]:
+ np.testing.assert_array_equal(color, new_color)
+ splt.set_title('EventCollection: set_color')
+
+
+def check_segments(coll, positions, linelength, lineoffset, orientation):
+ """
+ Test helper checking that all values in the segment are correct, given a
+ particular set of inputs.
+ """
+ segments = coll.get_segments()
+ if (orientation.lower() == 'horizontal'
+ or orientation.lower() == 'none' or orientation is None):
+ # if horizontal, the position in is in the y-axis
+ pos1 = 1
+ pos2 = 0
+ elif orientation.lower() == 'vertical':
+ # if vertical, the position in is in the x-axis
+ pos1 = 0
+ pos2 = 1
+ else:
+ raise ValueError("orientation must be 'horizontal' or 'vertical'")
+
+ # test to make sure each segment is correct
+ for i, segment in enumerate(segments):
+ assert segment[0, pos1] == lineoffset + linelength / 2
+ assert segment[1, pos1] == lineoffset - linelength / 2
+ assert segment[0, pos2] == positions[i]
+ assert segment[1, pos2] == positions[i]
+
+
+def test_collection_norm_autoscale():
+ # norm should be autoscaled when array is set, not deferred to draw time
+ lines = np.arange(24).reshape((4, 3, 2))
+ coll = mcollections.LineCollection(lines, array=np.arange(4))
+ assert coll.norm(2) == 2 / 3
+ # setting a new array shouldn't update the already scaled limits
+ coll.set_array(np.arange(4) + 5)
+ assert coll.norm(2) == 2 / 3
+
+
+def test_null_collection_datalim():
+ col = mcollections.PathCollection([])
+ col_data_lim = col.get_datalim(mtransforms.IdentityTransform())
+ assert_array_equal(col_data_lim.get_points(),
+ mtransforms.Bbox.null().get_points())
+
+
+def test_no_offsets_datalim():
+ # A collection with no offsets and a non transData
+ # transform should return a null bbox
+ ax = plt.axes()
+ coll = mcollections.PathCollection([mpath.Path([(0, 0), (1, 0)])])
+ ax.add_collection(coll)
+ coll_data_lim = coll.get_datalim(mtransforms.IdentityTransform())
+ assert_array_equal(coll_data_lim.get_points(),
+ mtransforms.Bbox.null().get_points())
+
+
+def test_add_collection():
+ # Test if data limits are unchanged by adding an empty collection.
+ # GitHub issue #1490, pull #1497.
+ plt.figure()
+ ax = plt.axes()
+ ax.scatter([0, 1], [0, 1])
+ bounds = ax.dataLim.bounds
+ ax.scatter([], [])
+ assert ax.dataLim.bounds == bounds
+
+
+@mpl.style.context('mpl20')
+@check_figures_equal(extensions=['png'])
+def test_collection_log_datalim(fig_test, fig_ref):
+ # Data limits should respect the minimum x/y when using log scale.
+ x_vals = [4.38462e-6, 5.54929e-6, 7.02332e-6, 8.88889e-6, 1.12500e-5,
+ 1.42383e-5, 1.80203e-5, 2.28070e-5, 2.88651e-5, 3.65324e-5,
+ 4.62363e-5, 5.85178e-5, 7.40616e-5, 9.37342e-5, 1.18632e-4]
+ y_vals = [0.0, 0.1, 0.182, 0.332, 0.604, 1.1, 2.0, 3.64, 6.64, 12.1, 22.0,
+ 39.6, 71.3]
+
+ x, y = np.meshgrid(x_vals, y_vals)
+ x = x.flatten()
+ y = y.flatten()
+
+ ax_test = fig_test.subplots()
+ ax_test.set_xscale('log')
+ ax_test.set_yscale('log')
+ ax_test.margins = 0
+ ax_test.scatter(x, y)
+
+ ax_ref = fig_ref.subplots()
+ ax_ref.set_xscale('log')
+ ax_ref.set_yscale('log')
+ ax_ref.plot(x, y, marker="o", ls="")
+
+
+def test_quiver_limits():
+ ax = plt.axes()
+ x, y = np.arange(8), np.arange(10)
+ u = v = np.linspace(0, 10, 80).reshape(10, 8)
+ q = plt.quiver(x, y, u, v)
+ assert q.get_datalim(ax.transData).bounds == (0., 0., 7., 9.)
+
+ plt.figure()
+ ax = plt.axes()
+ x = np.linspace(-5, 10, 20)
+ y = np.linspace(-2, 4, 10)
+ y, x = np.meshgrid(y, x)
+ trans = mtransforms.Affine2D().translate(25, 32) + ax.transData
+ plt.quiver(x, y, np.sin(x), np.cos(y), transform=trans)
+ assert ax.dataLim.bounds == (20.0, 30.0, 15.0, 6.0)
+
+
+def test_barb_limits():
+ ax = plt.axes()
+ x = np.linspace(-5, 10, 20)
+ y = np.linspace(-2, 4, 10)
+ y, x = np.meshgrid(y, x)
+ trans = mtransforms.Affine2D().translate(25, 32) + ax.transData
+ plt.barbs(x, y, np.sin(x), np.cos(y), transform=trans)
+ # The calculated bounds are approximately the bounds of the original data,
+ # this is because the entire path is taken into account when updating the
+ # datalim.
+ assert_array_almost_equal(ax.dataLim.bounds, (20, 30, 15, 6),
+ decimal=1)
+
+
+@image_comparison(['EllipseCollection_test_image.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.021)
+def test_EllipseCollection():
+ # Test basic functionality
+ fig, ax = plt.subplots()
+ x = np.arange(4)
+ y = np.arange(3)
+ X, Y = np.meshgrid(x, y)
+ XY = np.vstack((X.ravel(), Y.ravel())).T
+
+ ww = X / x[-1]
+ hh = Y / y[-1]
+ aa = np.ones_like(ww) * 20 # first axis is 20 degrees CCW from x axis
+
+ ec = mcollections.EllipseCollection(
+ ww, hh, aa, units='x', offsets=XY, offset_transform=ax.transData,
+ facecolors='none')
+ ax.add_collection(ec)
+ ax.autoscale_view()
+
+
+def test_EllipseCollection_setter_getter():
+ # Test widths, heights and angle setter
+ rng = np.random.default_rng(0)
+
+ widths = (2, )
+ heights = (3, )
+ angles = (45, )
+ offsets = rng.random((10, 2)) * 10
+
+ fig, ax = plt.subplots()
+
+ ec = mcollections.EllipseCollection(
+ widths=widths,
+ heights=heights,
+ angles=angles,
+ offsets=offsets,
+ units='x',
+ offset_transform=ax.transData,
+ )
+
+ assert_array_almost_equal(ec._widths, np.array(widths).ravel() * 0.5)
+ assert_array_almost_equal(ec._heights, np.array(heights).ravel() * 0.5)
+ assert_array_almost_equal(ec._angles, np.deg2rad(angles).ravel())
+
+ assert_array_almost_equal(ec.get_widths(), widths)
+ assert_array_almost_equal(ec.get_heights(), heights)
+ assert_array_almost_equal(ec.get_angles(), angles)
+
+ ax.add_collection(ec)
+ ax.set_xlim(-2, 12)
+ ax.set_ylim(-2, 12)
+
+ new_widths = rng.random((10, 2)) * 2
+ new_heights = rng.random((10, 2)) * 3
+ new_angles = rng.random((10, 2)) * 180
+
+ ec.set(widths=new_widths, heights=new_heights, angles=new_angles)
+
+ assert_array_almost_equal(ec.get_widths(), new_widths.ravel())
+ assert_array_almost_equal(ec.get_heights(), new_heights.ravel())
+ assert_array_almost_equal(ec.get_angles(), new_angles.ravel())
+
+
+@image_comparison(['polycollection_close.png'], remove_text=True, style='mpl20')
+def test_polycollection_close():
+ from mpl_toolkits.mplot3d import Axes3D # type: ignore[import]
+ plt.rcParams['axes3d.automargin'] = True
+
+ vertsQuad = [
+ [[0., 0.], [0., 1.], [1., 1.], [1., 0.]],
+ [[0., 1.], [2., 3.], [2., 2.], [1., 1.]],
+ [[2., 2.], [2., 3.], [4., 1.], [3., 1.]],
+ [[3., 0.], [3., 1.], [4., 1.], [4., 0.]]]
+
+ fig = plt.figure()
+ ax = fig.add_axes(Axes3D(fig))
+
+ colors = ['r', 'g', 'b', 'y', 'k']
+ zpos = list(range(5))
+
+ poly = mcollections.PolyCollection(
+ vertsQuad * len(zpos), linewidth=0.25)
+ poly.set_alpha(0.7)
+
+ # need to have a z-value for *each* polygon = element!
+ zs = []
+ cs = []
+ for z, c in zip(zpos, colors):
+ zs.extend([z] * len(vertsQuad))
+ cs.extend([c] * len(vertsQuad))
+
+ poly.set_color(cs)
+
+ ax.add_collection3d(poly, zs=zs, zdir='y')
+
+ # axis limit settings:
+ ax.set_xlim3d(0, 4)
+ ax.set_zlim3d(0, 3)
+ ax.set_ylim3d(0, 4)
+
+
+@image_comparison(['regularpolycollection_rotate.png'], remove_text=True)
+def test_regularpolycollection_rotate():
+ xx, yy = np.mgrid[:10, :10]
+ xy_points = np.transpose([xx.flatten(), yy.flatten()])
+ rotations = np.linspace(0, 2*np.pi, len(xy_points))
+
+ fig, ax = plt.subplots()
+ for xy, alpha in zip(xy_points, rotations):
+ col = mcollections.RegularPolyCollection(
+ 4, sizes=(100,), rotation=alpha,
+ offsets=[xy], offset_transform=ax.transData)
+ ax.add_collection(col, autolim=True)
+ ax.autoscale_view()
+
+
+@image_comparison(['regularpolycollection_scale.png'], remove_text=True)
+def test_regularpolycollection_scale():
+ # See issue #3860
+
+ class SquareCollection(mcollections.RegularPolyCollection):
+ def __init__(self, **kwargs):
+ super().__init__(4, rotation=np.pi/4., **kwargs)
+
+ def get_transform(self):
+ """Return transform scaling circle areas to data space."""
+ ax = self.axes
+
+ pts2pixels = 72.0 / ax.get_figure(root=True).dpi
+
+ scale_x = pts2pixels * ax.bbox.width / ax.viewLim.width
+ scale_y = pts2pixels * ax.bbox.height / ax.viewLim.height
+ return mtransforms.Affine2D().scale(scale_x, scale_y)
+
+ fig, ax = plt.subplots()
+
+ xy = [(0, 0)]
+ # Unit square has a half-diagonal of `1/sqrt(2)`, so `pi * r**2` equals...
+ circle_areas = [np.pi / 2]
+ squares = SquareCollection(
+ sizes=circle_areas, offsets=xy, offset_transform=ax.transData)
+ ax.add_collection(squares, autolim=True)
+ ax.axis([-1, 1, -1, 1])
+
+
+def test_picking():
+ fig, ax = plt.subplots()
+ col = ax.scatter([0], [0], [1000], picker=True)
+ fig.savefig(io.BytesIO(), dpi=fig.dpi)
+ mouse_event = SimpleNamespace(x=325, y=240)
+ found, indices = col.contains(mouse_event)
+ assert found
+ assert_array_equal(indices['ind'], [0])
+
+
+def test_quadmesh_contains():
+ x = np.arange(4)
+ X = x[:, None] * x[None, :]
+
+ fig, ax = plt.subplots()
+ mesh = ax.pcolormesh(X)
+ fig.draw_without_rendering()
+ xdata, ydata = 0.5, 0.5
+ x, y = mesh.get_transform().transform((xdata, ydata))
+ mouse_event = SimpleNamespace(xdata=xdata, ydata=ydata, x=x, y=y)
+ found, indices = mesh.contains(mouse_event)
+ assert found
+ assert_array_equal(indices['ind'], [0])
+
+ xdata, ydata = 1.5, 1.5
+ x, y = mesh.get_transform().transform((xdata, ydata))
+ mouse_event = SimpleNamespace(xdata=xdata, ydata=ydata, x=x, y=y)
+ found, indices = mesh.contains(mouse_event)
+ assert found
+ assert_array_equal(indices['ind'], [5])
+
+
+def test_quadmesh_contains_concave():
+ # Test a concave polygon, V-like shape
+ x = [[0, -1], [1, 0]]
+ y = [[0, 1], [1, -1]]
+ fig, ax = plt.subplots()
+ mesh = ax.pcolormesh(x, y, [[0]])
+ fig.draw_without_rendering()
+ # xdata, ydata, expected
+ points = [(-0.5, 0.25, True), # left wing
+ (0, 0.25, False), # between the two wings
+ (0.5, 0.25, True), # right wing
+ (0, -0.25, True), # main body
+ ]
+ for point in points:
+ xdata, ydata, expected = point
+ x, y = mesh.get_transform().transform((xdata, ydata))
+ mouse_event = SimpleNamespace(xdata=xdata, ydata=ydata, x=x, y=y)
+ found, indices = mesh.contains(mouse_event)
+ assert found is expected
+
+
+def test_quadmesh_cursor_data():
+ x = np.arange(4)
+ X = x[:, None] * x[None, :]
+
+ fig, ax = plt.subplots()
+ mesh = ax.pcolormesh(X)
+ # Empty array data
+ mesh._A = None
+ fig.draw_without_rendering()
+ xdata, ydata = 0.5, 0.5
+ x, y = mesh.get_transform().transform((xdata, ydata))
+ mouse_event = SimpleNamespace(xdata=xdata, ydata=ydata, x=x, y=y)
+ # Empty collection should return None
+ assert mesh.get_cursor_data(mouse_event) is None
+
+ # Now test adding the array data, to make sure we do get a value
+ mesh.set_array(np.ones(X.shape))
+ assert_array_equal(mesh.get_cursor_data(mouse_event), [1])
+
+
+def test_quadmesh_cursor_data_multiple_points():
+ x = [1, 2, 1, 2]
+ fig, ax = plt.subplots()
+ mesh = ax.pcolormesh(x, x, np.ones((3, 3)))
+ fig.draw_without_rendering()
+ xdata, ydata = 1.5, 1.5
+ x, y = mesh.get_transform().transform((xdata, ydata))
+ mouse_event = SimpleNamespace(xdata=xdata, ydata=ydata, x=x, y=y)
+ # All quads are covering the same square
+ assert_array_equal(mesh.get_cursor_data(mouse_event), np.ones(9))
+
+
+def test_linestyle_single_dashes():
+ plt.scatter([0, 1, 2], [0, 1, 2], linestyle=(0., [2., 2.]))
+ plt.draw()
+
+
+@image_comparison(['size_in_xy.png'], remove_text=True)
+def test_size_in_xy():
+ fig, ax = plt.subplots()
+
+ widths, heights, angles = (10, 10), 10, 0
+ widths = 10, 10
+ coords = [(10, 10), (15, 15)]
+ e = mcollections.EllipseCollection(
+ widths, heights, angles, units='xy',
+ offsets=coords, offset_transform=ax.transData)
+
+ ax.add_collection(e)
+
+ ax.set_xlim(0, 30)
+ ax.set_ylim(0, 30)
+
+
+def test_pandas_indexing(pd):
+
+ # Should not fail break when faced with a
+ # non-zero indexed series
+ index = [11, 12, 13]
+ ec = fc = pd.Series(['red', 'blue', 'green'], index=index)
+ lw = pd.Series([1, 2, 3], index=index)
+ ls = pd.Series(['solid', 'dashed', 'dashdot'], index=index)
+ aa = pd.Series([True, False, True], index=index)
+
+ Collection(edgecolors=ec)
+ Collection(facecolors=fc)
+ Collection(linewidths=lw)
+ Collection(linestyles=ls)
+ Collection(antialiaseds=aa)
+
+
+@mpl.style.context('default')
+def test_lslw_bcast():
+ col = mcollections.PathCollection([])
+ col.set_linestyles(['-', '-'])
+ col.set_linewidths([1, 2, 3])
+
+ assert col.get_linestyles() == [(0, None)] * 6
+ assert col.get_linewidths() == [1, 2, 3] * 2
+
+ col.set_linestyles(['-', '-', '-'])
+ assert col.get_linestyles() == [(0, None)] * 3
+ assert (col.get_linewidths() == [1, 2, 3]).all()
+
+
+def test_set_wrong_linestyle():
+ c = Collection()
+ with pytest.raises(ValueError, match="Do not know how to convert 'fuzzy'"):
+ c.set_linestyle('fuzzy')
+
+
+@mpl.style.context('default')
+def test_capstyle():
+ col = mcollections.PathCollection([])
+ assert col.get_capstyle() is None
+ col = mcollections.PathCollection([], capstyle='round')
+ assert col.get_capstyle() == 'round'
+ col.set_capstyle('butt')
+ assert col.get_capstyle() == 'butt'
+
+
+@mpl.style.context('default')
+def test_joinstyle():
+ col = mcollections.PathCollection([])
+ assert col.get_joinstyle() is None
+ col = mcollections.PathCollection([], joinstyle='round')
+ assert col.get_joinstyle() == 'round'
+ col.set_joinstyle('miter')
+ assert col.get_joinstyle() == 'miter'
+
+
+@image_comparison(['cap_and_joinstyle.png'])
+def test_cap_and_joinstyle_image():
+ fig, ax = plt.subplots()
+ ax.set_xlim([-0.5, 1.5])
+ ax.set_ylim([-0.5, 2.5])
+
+ x = np.array([0.0, 1.0, 0.5])
+ ys = np.array([[0.0], [0.5], [1.0]]) + np.array([[0.0, 0.0, 1.0]])
+
+ segs = np.zeros((3, 3, 2))
+ segs[:, :, 0] = x
+ segs[:, :, 1] = ys
+ line_segments = LineCollection(segs, linewidth=[10, 15, 20])
+ line_segments.set_capstyle("round")
+ line_segments.set_joinstyle("miter")
+
+ ax.add_collection(line_segments)
+ ax.set_title('Line collection with customized caps and joinstyle')
+
+
+@image_comparison(['scatter_post_alpha.png'],
+ remove_text=True, style='default')
+def test_scatter_post_alpha():
+ fig, ax = plt.subplots()
+ sc = ax.scatter(range(5), range(5), c=range(5))
+ sc.set_alpha(.1)
+
+
+def test_scatter_alpha_array():
+ x = np.arange(5)
+ alpha = x / 5
+ # With colormapping.
+ fig, (ax0, ax1) = plt.subplots(2)
+ sc0 = ax0.scatter(x, x, c=x, alpha=alpha)
+ sc1 = ax1.scatter(x, x, c=x)
+ sc1.set_alpha(alpha)
+ plt.draw()
+ assert_array_equal(sc0.get_facecolors()[:, -1], alpha)
+ assert_array_equal(sc1.get_facecolors()[:, -1], alpha)
+ # Without colormapping.
+ fig, (ax0, ax1) = plt.subplots(2)
+ sc0 = ax0.scatter(x, x, color=['r', 'g', 'b', 'c', 'm'], alpha=alpha)
+ sc1 = ax1.scatter(x, x, color='r', alpha=alpha)
+ plt.draw()
+ assert_array_equal(sc0.get_facecolors()[:, -1], alpha)
+ assert_array_equal(sc1.get_facecolors()[:, -1], alpha)
+ # Without colormapping, and set alpha afterward.
+ fig, (ax0, ax1) = plt.subplots(2)
+ sc0 = ax0.scatter(x, x, color=['r', 'g', 'b', 'c', 'm'])
+ sc0.set_alpha(alpha)
+ sc1 = ax1.scatter(x, x, color='r')
+ sc1.set_alpha(alpha)
+ plt.draw()
+ assert_array_equal(sc0.get_facecolors()[:, -1], alpha)
+ assert_array_equal(sc1.get_facecolors()[:, -1], alpha)
+
+
+def test_pathcollection_legend_elements():
+ np.random.seed(19680801)
+ x, y = np.random.rand(2, 10)
+ y = np.random.rand(10)
+ c = np.random.randint(0, 5, size=10)
+ s = np.random.randint(10, 300, size=10)
+
+ fig, ax = plt.subplots()
+ sc = ax.scatter(x, y, c=c, s=s, cmap="jet", marker="o", linewidths=0)
+
+ h, l = sc.legend_elements(fmt="{x:g}")
+ assert len(h) == 5
+ assert l == ["0", "1", "2", "3", "4"]
+ colors = np.array([line.get_color() for line in h])
+ colors2 = sc.cmap(np.arange(5)/4)
+ assert_array_equal(colors, colors2)
+ l1 = ax.legend(h, l, loc=1)
+
+ h2, lab2 = sc.legend_elements(num=9)
+ assert len(h2) == 9
+ l2 = ax.legend(h2, lab2, loc=2)
+
+ h, l = sc.legend_elements(prop="sizes", alpha=0.5, color="red")
+ assert all(line.get_alpha() == 0.5 for line in h)
+ assert all(line.get_markerfacecolor() == "red" for line in h)
+ l3 = ax.legend(h, l, loc=4)
+
+ h, l = sc.legend_elements(prop="sizes", num=4, fmt="{x:.2f}",
+ func=lambda x: 2*x)
+ actsizes = [line.get_markersize() for line in h]
+ labeledsizes = np.sqrt(np.array(l, float) / 2)
+ assert_array_almost_equal(actsizes, labeledsizes)
+ l4 = ax.legend(h, l, loc=3)
+
+ loc = mpl.ticker.MaxNLocator(nbins=9, min_n_ticks=9-1,
+ steps=[1, 2, 2.5, 3, 5, 6, 8, 10])
+ h5, lab5 = sc.legend_elements(num=loc)
+ assert len(h2) == len(h5)
+
+ levels = [-1, 0, 55.4, 260]
+ h6, lab6 = sc.legend_elements(num=levels, prop="sizes", fmt="{x:g}")
+ assert [float(l) for l in lab6] == levels[2:]
+
+ for l in [l1, l2, l3, l4]:
+ ax.add_artist(l)
+
+ fig.canvas.draw()
+
+
+def test_EventCollection_nosort():
+ # Check that EventCollection doesn't modify input in place
+ arr = np.array([3, 2, 1, 10])
+ coll = EventCollection(arr)
+ np.testing.assert_array_equal(arr, np.array([3, 2, 1, 10]))
+
+
+def test_collection_set_verts_array():
+ verts = np.arange(80, dtype=np.double).reshape(10, 4, 2)
+ col_arr = PolyCollection(verts)
+ col_list = PolyCollection(list(verts))
+ assert len(col_arr._paths) == len(col_list._paths)
+ for ap, lp in zip(col_arr._paths, col_list._paths):
+ assert np.array_equal(ap._vertices, lp._vertices)
+ assert np.array_equal(ap._codes, lp._codes)
+
+ verts_tuple = np.empty(10, dtype=object)
+ verts_tuple[:] = [tuple(tuple(y) for y in x) for x in verts]
+ col_arr_tuple = PolyCollection(verts_tuple)
+ assert len(col_arr._paths) == len(col_arr_tuple._paths)
+ for ap, atp in zip(col_arr._paths, col_arr_tuple._paths):
+ assert np.array_equal(ap._vertices, atp._vertices)
+ assert np.array_equal(ap._codes, atp._codes)
+
+
+@check_figures_equal(extensions=["png"])
+@pytest.mark.parametrize("kwargs", [{}, {"step": "pre"}])
+def test_fill_between_poly_collection_set_data(fig_test, fig_ref, kwargs):
+ t = np.linspace(0, 16)
+ f1 = np.sin(t)
+ f2 = f1 + 0.2
+
+ fig_ref.subplots().fill_between(t, f1, f2, **kwargs)
+
+ coll = fig_test.subplots().fill_between(t, -1, 1.2, **kwargs)
+ coll.set_data(t, f1, f2)
+
+
+@pytest.mark.parametrize(("t_direction", "f1", "shape", "where", "msg"), [
+ ("z", None, None, None, r"t_direction must be 'x' or 'y', got 'z'"),
+ ("x", None, (-1, 1), None, r"'x' is not 1-dimensional"),
+ ("x", None, None, [False] * 3, r"where size \(3\) does not match 'x' size \(\d+\)"),
+ ("y", [1, 2], None, None, r"'y' has size \d+, but 'x1' has an unequal size of \d+"),
+])
+def test_fill_between_poly_collection_raise(t_direction, f1, shape, where, msg):
+ t = np.linspace(0, 16)
+ f1 = np.sin(t) if f1 is None else np.asarray(f1)
+ f2 = f1 + 0.2
+ if shape:
+ t = t.reshape(*shape)
+ with pytest.raises(ValueError, match=msg):
+ FillBetweenPolyCollection(t_direction, t, f1, f2, where=where)
+
+
+def test_collection_set_array():
+ vals = [*range(10)]
+
+ # Test set_array with list
+ c = Collection()
+ c.set_array(vals)
+
+ # Test set_array with wrong dtype
+ with pytest.raises(TypeError, match="^Image data of dtype"):
+ c.set_array("wrong_input")
+
+ # Test if array kwarg is copied
+ vals[5] = 45
+ assert np.not_equal(vals, c.get_array()).any()
+
+
+def test_blended_collection_autolim():
+ f, ax = plt.subplots()
+
+ # sample data to give initial data limits
+ ax.plot([2, 3, 4], [0.4, 0.6, 0.5])
+ np.testing.assert_allclose((ax.dataLim.xmin, ax.dataLim.xmax), (2, 4))
+ data_ymin, data_ymax = ax.dataLim.ymin, ax.dataLim.ymax
+
+ # LineCollection with vertical lines spanning the Axes vertical, using transAxes
+ x = [1, 2, 3, 4, 5]
+ vertical_lines = [np.array([[xi, 0], [xi, 1]]) for xi in x]
+ trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
+ ax.add_collection(LineCollection(vertical_lines, transform=trans))
+
+ # check that the x data limits are updated to include the LineCollection
+ np.testing.assert_allclose((ax.dataLim.xmin, ax.dataLim.xmax), (1, 5))
+ # check that the y data limits are not updated (because they are not transData)
+ np.testing.assert_allclose((ax.dataLim.ymin, ax.dataLim.ymax),
+ (data_ymin, data_ymax))
+
+
+def test_singleton_autolim():
+ fig, ax = plt.subplots()
+ ax.scatter(0, 0)
+ np.testing.assert_allclose(ax.get_ylim(), [-0.06, 0.06])
+ np.testing.assert_allclose(ax.get_xlim(), [-0.06, 0.06])
+
+
+@pytest.mark.parametrize("transform, expected", [
+ ("transData", (-0.5, 3.5)),
+ ("transAxes", (2.8, 3.2)),
+])
+def test_autolim_with_zeros(transform, expected):
+ # 1) Test that a scatter at (0, 0) data coordinates contributes to
+ # autoscaling even though any(offsets) would be False in that situation.
+ # 2) Test that specifying transAxes for the transform does not contribute
+ # to the autoscaling.
+ fig, ax = plt.subplots()
+ ax.scatter(0, 0, transform=getattr(ax, transform))
+ ax.scatter(3, 3)
+ np.testing.assert_allclose(ax.get_ylim(), expected)
+ np.testing.assert_allclose(ax.get_xlim(), expected)
+
+
+def test_quadmesh_set_array_validation(pcfunc):
+ x = np.arange(11)
+ y = np.arange(8)
+ z = np.random.random((7, 10))
+ fig, ax = plt.subplots()
+ coll = getattr(ax, pcfunc)(x, y, z)
+
+ with pytest.raises(ValueError, match=re.escape(
+ "For X (11) and Y (8) with flat shading, A should have shape "
+ "(7, 10, 3) or (7, 10, 4) or (7, 10) or (70,), not (10, 7)")):
+ coll.set_array(z.reshape(10, 7))
+
+ z = np.arange(54).reshape((6, 9))
+ with pytest.raises(ValueError, match=re.escape(
+ "For X (11) and Y (8) with flat shading, A should have shape "
+ "(7, 10, 3) or (7, 10, 4) or (7, 10) or (70,), not (6, 9)")):
+ coll.set_array(z)
+ with pytest.raises(ValueError, match=re.escape(
+ "For X (11) and Y (8) with flat shading, A should have shape "
+ "(7, 10, 3) or (7, 10, 4) or (7, 10) or (70,), not (54,)")):
+ coll.set_array(z.ravel())
+
+ # RGB(A) tests
+ z = np.ones((9, 6, 3)) # RGB with wrong X/Y dims
+ with pytest.raises(ValueError, match=re.escape(
+ "For X (11) and Y (8) with flat shading, A should have shape "
+ "(7, 10, 3) or (7, 10, 4) or (7, 10) or (70,), not (9, 6, 3)")):
+ coll.set_array(z)
+
+ z = np.ones((9, 6, 4)) # RGBA with wrong X/Y dims
+ with pytest.raises(ValueError, match=re.escape(
+ "For X (11) and Y (8) with flat shading, A should have shape "
+ "(7, 10, 3) or (7, 10, 4) or (7, 10) or (70,), not (9, 6, 4)")):
+ coll.set_array(z)
+
+ z = np.ones((7, 10, 2)) # Right X/Y dims, bad 3rd dim
+ with pytest.raises(ValueError, match=re.escape(
+ "For X (11) and Y (8) with flat shading, A should have shape "
+ "(7, 10, 3) or (7, 10, 4) or (7, 10) or (70,), not (7, 10, 2)")):
+ coll.set_array(z)
+
+ x = np.arange(10)
+ y = np.arange(7)
+ z = np.random.random((7, 10))
+ fig, ax = plt.subplots()
+ coll = ax.pcolormesh(x, y, z, shading='gouraud')
+
+
+def test_polyquadmesh_masked_vertices_array():
+ xx, yy = np.meshgrid([0, 1, 2], [0, 1, 2, 3])
+ # 2 x 3 mesh data
+ zz = (xx*yy)[:-1, :-1]
+ quadmesh = plt.pcolormesh(xx, yy, zz)
+ quadmesh.update_scalarmappable()
+ quadmesh_fc = quadmesh.get_facecolor()[1:, :]
+ # Mask the origin vertex in x
+ xx = np.ma.masked_where((xx == 0) & (yy == 0), xx)
+ polymesh = plt.pcolor(xx, yy, zz)
+ polymesh.update_scalarmappable()
+ # One cell should be left out
+ assert len(polymesh.get_paths()) == 5
+ # Poly version should have the same facecolors as the end of the quadmesh
+ assert_array_equal(quadmesh_fc, polymesh.get_facecolor())
+
+ # Mask the origin vertex in y
+ yy = np.ma.masked_where((xx == 0) & (yy == 0), yy)
+ polymesh = plt.pcolor(xx, yy, zz)
+ polymesh.update_scalarmappable()
+ # One cell should be left out
+ assert len(polymesh.get_paths()) == 5
+ # Poly version should have the same facecolors as the end of the quadmesh
+ assert_array_equal(quadmesh_fc, polymesh.get_facecolor())
+
+ # Mask the origin cell data
+ zz = np.ma.masked_where((xx[:-1, :-1] == 0) & (yy[:-1, :-1] == 0), zz)
+ polymesh = plt.pcolor(zz)
+ polymesh.update_scalarmappable()
+ # One cell should be left out
+ assert len(polymesh.get_paths()) == 5
+ # Poly version should have the same facecolors as the end of the quadmesh
+ assert_array_equal(quadmesh_fc, polymesh.get_facecolor())
+
+ # We should also be able to call set_array with a new mask and get
+ # updated polys
+ # Remove mask, should add all polys back
+ zz = np.arange(6).reshape((3, 2))
+ polymesh.set_array(zz)
+ polymesh.update_scalarmappable()
+ assert len(polymesh.get_paths()) == 6
+ # Add mask should remove polys
+ zz = np.ma.masked_less(zz, 2)
+ polymesh.set_array(zz)
+ polymesh.update_scalarmappable()
+ assert len(polymesh.get_paths()) == 4
+
+
+def test_quadmesh_get_coordinates(pcfunc):
+ x = [0, 1, 2]
+ y = [2, 4, 6]
+ z = np.ones(shape=(2, 2))
+ xx, yy = np.meshgrid(x, y)
+ coll = getattr(plt, pcfunc)(xx, yy, z)
+
+ # shape (3, 3, 2)
+ coords = np.stack([xx.T, yy.T]).T
+ assert_array_equal(coll.get_coordinates(), coords)
+
+
+def test_quadmesh_set_array():
+ x = np.arange(4)
+ y = np.arange(4)
+ z = np.arange(9).reshape((3, 3))
+ fig, ax = plt.subplots()
+ coll = ax.pcolormesh(x, y, np.ones(z.shape))
+ # Test that the collection is able to update with a 2d array
+ coll.set_array(z)
+ fig.canvas.draw()
+ assert np.array_equal(coll.get_array(), z)
+
+ # Check that pre-flattened arrays work too
+ coll.set_array(np.ones(9))
+ fig.canvas.draw()
+ assert np.array_equal(coll.get_array(), np.ones(9))
+
+ z = np.arange(16).reshape((4, 4))
+ fig, ax = plt.subplots()
+ coll = ax.pcolormesh(x, y, np.ones(z.shape), shading='gouraud')
+ # Test that the collection is able to update with a 2d array
+ coll.set_array(z)
+ fig.canvas.draw()
+ assert np.array_equal(coll.get_array(), z)
+
+ # Check that pre-flattened arrays work too
+ coll.set_array(np.ones(16))
+ fig.canvas.draw()
+ assert np.array_equal(coll.get_array(), np.ones(16))
+
+
+def test_quadmesh_vmin_vmax(pcfunc):
+ # test when vmin/vmax on the norm changes, the quadmesh gets updated
+ fig, ax = plt.subplots()
+ cmap = mpl.colormaps['plasma']
+ norm = mpl.colors.Normalize(vmin=0, vmax=1)
+ coll = getattr(ax, pcfunc)([[1]], cmap=cmap, norm=norm)
+ fig.canvas.draw()
+ assert np.array_equal(coll.get_facecolors()[0, :], cmap(norm(1)))
+
+ # Change the vmin/vmax of the norm so that the color is from
+ # the bottom of the colormap now
+ norm.vmin, norm.vmax = 1, 2
+ fig.canvas.draw()
+ assert np.array_equal(coll.get_facecolors()[0, :], cmap(norm(1)))
+
+
+def test_quadmesh_alpha_array(pcfunc):
+ x = np.arange(4)
+ y = np.arange(4)
+ z = np.arange(9).reshape((3, 3))
+ alpha = z / z.max()
+ alpha_flat = alpha.ravel()
+ # Provide 2-D alpha:
+ fig, (ax0, ax1) = plt.subplots(2)
+ coll1 = getattr(ax0, pcfunc)(x, y, z, alpha=alpha)
+ coll2 = getattr(ax0, pcfunc)(x, y, z)
+ coll2.set_alpha(alpha)
+ plt.draw()
+ assert_array_equal(coll1.get_facecolors()[:, -1], alpha_flat)
+ assert_array_equal(coll2.get_facecolors()[:, -1], alpha_flat)
+ # Or provide 1-D alpha:
+ fig, (ax0, ax1) = plt.subplots(2)
+ coll1 = getattr(ax0, pcfunc)(x, y, z, alpha=alpha)
+ coll2 = getattr(ax1, pcfunc)(x, y, z)
+ coll2.set_alpha(alpha)
+ plt.draw()
+ assert_array_equal(coll1.get_facecolors()[:, -1], alpha_flat)
+ assert_array_equal(coll2.get_facecolors()[:, -1], alpha_flat)
+
+
+def test_alpha_validation(pcfunc):
+ # Most of the relevant testing is in test_artist and test_colors.
+ fig, ax = plt.subplots()
+ pc = getattr(ax, pcfunc)(np.arange(12).reshape((3, 4)))
+ with pytest.raises(ValueError, match="^Data array shape"):
+ pc.set_alpha([0.5, 0.6])
+ pc.update_scalarmappable()
+
+
+def test_legend_inverse_size_label_relationship():
+ """
+ Ensure legend markers scale appropriately when label and size are
+ inversely related.
+ Here label = 5 / size
+ """
+
+ np.random.seed(19680801)
+ X = np.random.random(50)
+ Y = np.random.random(50)
+ C = 1 - np.random.random(50)
+ S = 5 / C
+
+ legend_sizes = [0.2, 0.4, 0.6, 0.8]
+ fig, ax = plt.subplots()
+ sc = ax.scatter(X, Y, s=S)
+ handles, labels = sc.legend_elements(
+ prop='sizes', num=legend_sizes, func=lambda s: 5 / s
+ )
+
+ # Convert markersize scale to 's' scale
+ handle_sizes = [x.get_markersize() for x in handles]
+ handle_sizes = [5 / x**2 for x in handle_sizes]
+
+ assert_array_almost_equal(handle_sizes, legend_sizes, decimal=1)
+
+
+@mpl.style.context('default')
+def test_color_logic(pcfunc):
+ pcfunc = getattr(plt, pcfunc)
+ z = np.arange(12).reshape(3, 4)
+ # Explicitly set an edgecolor.
+ pc = pcfunc(z, edgecolors='red', facecolors='none')
+ pc.update_scalarmappable() # This is called in draw().
+ # Define 2 reference "colors" here for multiple use.
+ face_default = mcolors.to_rgba_array(pc._get_default_facecolor())
+ mapped = pc.get_cmap()(pc.norm(z.ravel()))
+ # GitHub issue #1302:
+ assert mcolors.same_color(pc.get_edgecolor(), 'red')
+ # Check setting attributes after initialization:
+ pc = pcfunc(z)
+ pc.set_facecolor('none')
+ pc.set_edgecolor('red')
+ pc.update_scalarmappable()
+ assert mcolors.same_color(pc.get_facecolor(), 'none')
+ assert mcolors.same_color(pc.get_edgecolor(), [[1, 0, 0, 1]])
+ pc.set_alpha(0.5)
+ pc.update_scalarmappable()
+ assert mcolors.same_color(pc.get_edgecolor(), [[1, 0, 0, 0.5]])
+ pc.set_alpha(None) # restore default alpha
+ pc.update_scalarmappable()
+ assert mcolors.same_color(pc.get_edgecolor(), [[1, 0, 0, 1]])
+ # Reset edgecolor to default.
+ pc.set_edgecolor(None)
+ pc.update_scalarmappable()
+ assert np.array_equal(pc.get_edgecolor(), mapped)
+ pc.set_facecolor(None) # restore default for facecolor
+ pc.update_scalarmappable()
+ assert np.array_equal(pc.get_facecolor(), mapped)
+ assert mcolors.same_color(pc.get_edgecolor(), 'none')
+ # Turn off colormapping entirely:
+ pc.set_array(None)
+ pc.update_scalarmappable()
+ assert mcolors.same_color(pc.get_edgecolor(), 'none')
+ assert mcolors.same_color(pc.get_facecolor(), face_default) # not mapped
+ # Turn it back on by restoring the array (must be 1D!):
+ pc.set_array(z)
+ pc.update_scalarmappable()
+ assert np.array_equal(pc.get_facecolor(), mapped)
+ assert mcolors.same_color(pc.get_edgecolor(), 'none')
+ # Give color via tuple rather than string.
+ pc = pcfunc(z, edgecolors=(1, 0, 0), facecolors=(0, 1, 0))
+ pc.update_scalarmappable()
+ assert np.array_equal(pc.get_facecolor(), mapped)
+ assert mcolors.same_color(pc.get_edgecolor(), [[1, 0, 0, 1]])
+ # Provide an RGB array; mapping overrides it.
+ pc = pcfunc(z, edgecolors=(1, 0, 0), facecolors=np.ones((12, 3)))
+ pc.update_scalarmappable()
+ assert np.array_equal(pc.get_facecolor(), mapped)
+ assert mcolors.same_color(pc.get_edgecolor(), [[1, 0, 0, 1]])
+ # Turn off the mapping.
+ pc.set_array(None)
+ pc.update_scalarmappable()
+ assert mcolors.same_color(pc.get_facecolor(), np.ones((12, 3)))
+ assert mcolors.same_color(pc.get_edgecolor(), [[1, 0, 0, 1]])
+ # And an RGBA array.
+ pc = pcfunc(z, edgecolors=(1, 0, 0), facecolors=np.ones((12, 4)))
+ pc.update_scalarmappable()
+ assert np.array_equal(pc.get_facecolor(), mapped)
+ assert mcolors.same_color(pc.get_edgecolor(), [[1, 0, 0, 1]])
+ # Turn off the mapping.
+ pc.set_array(None)
+ pc.update_scalarmappable()
+ assert mcolors.same_color(pc.get_facecolor(), np.ones((12, 4)))
+ assert mcolors.same_color(pc.get_edgecolor(), [[1, 0, 0, 1]])
+
+
+def test_LineCollection_args():
+ lc = LineCollection(None, linewidth=2.2, edgecolor='r',
+ zorder=3, facecolors=[0, 1, 0, 1])
+ assert lc.get_linewidth()[0] == 2.2
+ assert mcolors.same_color(lc.get_edgecolor(), 'r')
+ assert lc.get_zorder() == 3
+ assert mcolors.same_color(lc.get_facecolor(), [[0, 1, 0, 1]])
+ # To avoid breaking mplot3d, LineCollection internally sets the facecolor
+ # kwarg if it has not been specified. Hence we need the following test
+ # for LineCollection._set_default().
+ lc = LineCollection(None, facecolor=None)
+ assert mcolors.same_color(lc.get_facecolor(), 'none')
+
+
+def test_array_dimensions(pcfunc):
+ # Make sure we can set the 1D, 2D, and 3D array shapes
+ z = np.arange(12).reshape(3, 4)
+ pc = getattr(plt, pcfunc)(z)
+ # 1D
+ pc.set_array(z.ravel())
+ pc.update_scalarmappable()
+ # 2D
+ pc.set_array(z)
+ pc.update_scalarmappable()
+ # 3D RGB is OK as well
+ z = np.arange(36, dtype=np.uint8).reshape(3, 4, 3)
+ pc.set_array(z)
+ pc.update_scalarmappable()
+
+
+def test_get_segments():
+ segments = np.tile(np.linspace(0, 1, 256), (2, 1)).T
+ lc = LineCollection([segments])
+
+ readback, = lc.get_segments()
+ # these should comeback un-changed!
+ assert np.all(segments == readback)
+
+
+def test_set_offsets_late():
+ identity = mtransforms.IdentityTransform()
+ sizes = [2]
+
+ null = mcollections.CircleCollection(sizes=sizes)
+
+ init = mcollections.CircleCollection(sizes=sizes, offsets=(10, 10))
+
+ late = mcollections.CircleCollection(sizes=sizes)
+ late.set_offsets((10, 10))
+
+ # Bbox.__eq__ doesn't compare bounds
+ null_bounds = null.get_datalim(identity).bounds
+ init_bounds = init.get_datalim(identity).bounds
+ late_bounds = late.get_datalim(identity).bounds
+
+ # offsets and transform are applied when set after initialization
+ assert null_bounds != init_bounds
+ assert init_bounds == late_bounds
+
+
+def test_set_offset_transform():
+ skew = mtransforms.Affine2D().skew(2, 2)
+ init = mcollections.Collection(offset_transform=skew)
+
+ late = mcollections.Collection()
+ late.set_offset_transform(skew)
+
+ assert skew == init.get_offset_transform() == late.get_offset_transform()
+
+
+def test_set_offset_units():
+ # passing the offsets in initially (i.e. via scatter)
+ # should yield the same results as `set_offsets`
+ x = np.linspace(0, 10, 5)
+ y = np.sin(x)
+ d = x * np.timedelta64(24, 'h') + np.datetime64('2021-11-29')
+
+ sc = plt.scatter(d, y)
+ off0 = sc.get_offsets()
+ sc.set_offsets(list(zip(d, y)))
+ np.testing.assert_allclose(off0, sc.get_offsets())
+
+ # try the other way around
+ fig, ax = plt.subplots()
+ sc = ax.scatter(y, d)
+ off0 = sc.get_offsets()
+ sc.set_offsets(list(zip(y, d)))
+ np.testing.assert_allclose(off0, sc.get_offsets())
+
+
+@image_comparison(baseline_images=["test_check_masked_offsets"],
+ extensions=["png"], remove_text=True, style="mpl20")
+def test_check_masked_offsets():
+ # Check if masked data is respected by scatter
+ # Ref: Issue #24545
+ unmasked_x = [
+ datetime(2022, 12, 15, 4, 49, 52),
+ datetime(2022, 12, 15, 4, 49, 53),
+ datetime(2022, 12, 15, 4, 49, 54),
+ datetime(2022, 12, 15, 4, 49, 55),
+ datetime(2022, 12, 15, 4, 49, 56),
+ ]
+
+ masked_y = np.ma.array([1, 2, 3, 4, 5], mask=[0, 1, 1, 0, 0])
+
+ fig, ax = plt.subplots()
+ ax.scatter(unmasked_x, masked_y)
+
+
+@check_figures_equal(extensions=["png"])
+def test_masked_set_offsets(fig_ref, fig_test):
+ x = np.ma.array([1, 2, 3, 4, 5], mask=[0, 0, 1, 1, 0])
+ y = np.arange(1, 6)
+
+ ax_test = fig_test.add_subplot()
+ scat = ax_test.scatter(x, y)
+ scat.set_offsets(np.ma.column_stack([x, y]))
+ ax_test.set_xticks([])
+ ax_test.set_yticks([])
+
+ ax_ref = fig_ref.add_subplot()
+ ax_ref.scatter([1, 2, 5], [1, 2, 5])
+ ax_ref.set_xticks([])
+ ax_ref.set_yticks([])
+
+
+def test_check_offsets_dtype():
+ # Check that setting offsets doesn't change dtype
+ x = np.ma.array([1, 2, 3, 4, 5], mask=[0, 0, 1, 1, 0])
+ y = np.arange(1, 6)
+
+ fig, ax = plt.subplots()
+ scat = ax.scatter(x, y)
+ masked_offsets = np.ma.column_stack([x, y])
+ scat.set_offsets(masked_offsets)
+ assert isinstance(scat.get_offsets(), type(masked_offsets))
+
+ unmasked_offsets = np.column_stack([x, y])
+ scat.set_offsets(unmasked_offsets)
+ assert isinstance(scat.get_offsets(), type(unmasked_offsets))
+
+
+@pytest.mark.parametrize('gapcolor', ['orange', ['r', 'k']])
+@check_figures_equal(extensions=['png'])
+def test_striped_lines(fig_test, fig_ref, gapcolor):
+ ax_test = fig_test.add_subplot(111)
+ ax_ref = fig_ref.add_subplot(111)
+
+ for ax in [ax_test, ax_ref]:
+ ax.set_xlim(0, 6)
+ ax.set_ylim(0, 1)
+
+ x = range(1, 6)
+ linestyles = [':', '-', '--']
+
+ ax_test.vlines(x, 0, 1, linewidth=20, linestyle=linestyles, gapcolor=gapcolor,
+ alpha=0.5)
+
+ if isinstance(gapcolor, str):
+ gapcolor = [gapcolor]
+
+ for x, gcol, ls in zip(x, itertools.cycle(gapcolor),
+ itertools.cycle(linestyles)):
+ ax_ref.axvline(x, 0, 1, linewidth=20, linestyle=ls, gapcolor=gcol, alpha=0.5)
+
+
+@check_figures_equal(extensions=['png', 'pdf', 'svg', 'eps'])
+def test_hatch_linewidth(fig_test, fig_ref):
+ ax_test = fig_test.add_subplot()
+ ax_ref = fig_ref.add_subplot()
+
+ lw = 2.0
+
+ polygons = [
+ [(0.1, 0.1), (0.1, 0.4), (0.4, 0.4), (0.4, 0.1)],
+ [(0.6, 0.6), (0.6, 0.9), (0.9, 0.9), (0.9, 0.6)],
+ ]
+ ref = PolyCollection(polygons, hatch="x")
+ ref.set_hatch_linewidth(lw)
+
+ with mpl.rc_context({"hatch.linewidth": lw}):
+ test = PolyCollection(polygons, hatch="x")
+
+ ax_ref.add_collection(ref)
+ ax_test.add_collection(test)
+
+ assert test.get_hatch_linewidth() == ref.get_hatch_linewidth() == lw
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_colorbar.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_colorbar.py
new file mode 100644
index 0000000000000000000000000000000000000000..24eeab689424983c0047e95ae465a36b7a999edd
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_colorbar.py
@@ -0,0 +1,1240 @@
+import platform
+
+import numpy as np
+import pytest
+
+from matplotlib import cm
+import matplotlib.colors as mcolors
+import matplotlib as mpl
+
+
+from matplotlib import rc_context
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.pyplot as plt
+from matplotlib.colors import (
+ BoundaryNorm, LogNorm, PowerNorm, Normalize, NoNorm
+)
+from matplotlib.colorbar import Colorbar
+from matplotlib.ticker import FixedLocator, LogFormatter, StrMethodFormatter
+from matplotlib.testing.decorators import check_figures_equal
+
+
+def _get_cmap_norms():
+ """
+ Define a colormap and appropriate norms for each of the four
+ possible settings of the extend keyword.
+
+ Helper function for _colorbar_extension_shape and
+ colorbar_extension_length.
+ """
+ # Create a colormap and specify the levels it represents.
+ cmap = mpl.colormaps["RdBu"].resampled(5)
+ clevs = [-5., -2.5, -.5, .5, 1.5, 3.5]
+ # Define norms for the colormaps.
+ norms = dict()
+ norms['neither'] = BoundaryNorm(clevs, len(clevs) - 1)
+ norms['min'] = BoundaryNorm([-10] + clevs[1:], len(clevs) - 1)
+ norms['max'] = BoundaryNorm(clevs[:-1] + [10], len(clevs) - 1)
+ norms['both'] = BoundaryNorm([-10] + clevs[1:-1] + [10], len(clevs) - 1)
+ return cmap, norms
+
+
+def _colorbar_extension_shape(spacing):
+ """
+ Produce 4 colorbars with rectangular extensions for either uniform
+ or proportional spacing.
+
+ Helper function for test_colorbar_extension_shape.
+ """
+ # Get a colormap and appropriate norms for each extension type.
+ cmap, norms = _get_cmap_norms()
+ # Create a figure and adjust whitespace for subplots.
+ fig = plt.figure()
+ fig.subplots_adjust(hspace=4)
+ for i, extension_type in enumerate(('neither', 'min', 'max', 'both')):
+ # Get the appropriate norm and use it to get colorbar boundaries.
+ norm = norms[extension_type]
+ boundaries = values = norm.boundaries
+ # note that the last value was silently dropped pre 3.3:
+ values = values[:-1]
+ # Create a subplot.
+ cax = fig.add_subplot(4, 1, i + 1)
+ # Generate the colorbar.
+ Colorbar(cax, cmap=cmap, norm=norm,
+ boundaries=boundaries, values=values,
+ extend=extension_type, extendrect=True,
+ orientation='horizontal', spacing=spacing)
+ # Turn off text and ticks.
+ cax.tick_params(left=False, labelleft=False,
+ bottom=False, labelbottom=False)
+ # Return the figure to the caller.
+ return fig
+
+
+def _colorbar_extension_length(spacing):
+ """
+ Produce 12 colorbars with variable length extensions for either
+ uniform or proportional spacing.
+
+ Helper function for test_colorbar_extension_length.
+ """
+ # Get a colormap and appropriate norms for each extension type.
+ cmap, norms = _get_cmap_norms()
+ # Create a figure and adjust whitespace for subplots.
+ fig = plt.figure()
+ fig.subplots_adjust(hspace=.6)
+ for i, extension_type in enumerate(('neither', 'min', 'max', 'both')):
+ # Get the appropriate norm and use it to get colorbar boundaries.
+ norm = norms[extension_type]
+ boundaries = values = norm.boundaries
+ values = values[:-1]
+ for j, extendfrac in enumerate((None, 'auto', 0.1)):
+ # Create a subplot.
+ cax = fig.add_subplot(12, 1, i*3 + j + 1)
+ # Generate the colorbar.
+ Colorbar(cax, cmap=cmap, norm=norm,
+ boundaries=boundaries, values=values,
+ extend=extension_type, extendfrac=extendfrac,
+ orientation='horizontal', spacing=spacing)
+ # Turn off text and ticks.
+ cax.tick_params(left=False, labelleft=False,
+ bottom=False, labelbottom=False)
+ # Return the figure to the caller.
+ return fig
+
+
+@image_comparison(['colorbar_extensions_shape_uniform.png',
+ 'colorbar_extensions_shape_proportional.png'])
+def test_colorbar_extension_shape():
+ """Test rectangular colorbar extensions."""
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ # Create figures for uniform and proportionally spaced colorbars.
+ _colorbar_extension_shape('uniform')
+ _colorbar_extension_shape('proportional')
+
+
+@image_comparison(['colorbar_extensions_uniform.png',
+ 'colorbar_extensions_proportional.png'],
+ tol=1.0)
+def test_colorbar_extension_length():
+ """Test variable length colorbar extensions."""
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ # Create figures for uniform and proportionally spaced colorbars.
+ _colorbar_extension_length('uniform')
+ _colorbar_extension_length('proportional')
+
+
+@pytest.mark.parametrize("orientation", ["horizontal", "vertical"])
+@pytest.mark.parametrize("extend,expected", [("min", (0, 0, 0, 1)),
+ ("max", (1, 1, 1, 1)),
+ ("both", (1, 1, 1, 1))])
+def test_colorbar_extension_inverted_axis(orientation, extend, expected):
+ """Test extension color with an inverted axis"""
+ data = np.arange(12).reshape(3, 4)
+ fig, ax = plt.subplots()
+ cmap = mpl.colormaps["viridis"].with_extremes(under=(0, 0, 0, 1),
+ over=(1, 1, 1, 1))
+ im = ax.imshow(data, cmap=cmap)
+ cbar = fig.colorbar(im, orientation=orientation, extend=extend)
+ if orientation == "horizontal":
+ cbar.ax.invert_xaxis()
+ else:
+ cbar.ax.invert_yaxis()
+ assert cbar._extend_patches[0].get_facecolor() == expected
+ if extend == "both":
+ assert len(cbar._extend_patches) == 2
+ assert cbar._extend_patches[1].get_facecolor() == (0, 0, 0, 1)
+ else:
+ assert len(cbar._extend_patches) == 1
+
+
+@pytest.mark.parametrize('use_gridspec', [True, False])
+@image_comparison(['cbar_with_orientation',
+ 'cbar_locationing',
+ 'double_cbar',
+ 'cbar_sharing',
+ ],
+ extensions=['png'], remove_text=True,
+ savefig_kwarg={'dpi': 40})
+def test_colorbar_positioning(use_gridspec):
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ data = np.arange(1200).reshape(30, 40)
+ levels = [0, 200, 400, 600, 800, 1000, 1200]
+
+ # -------------------
+ plt.figure()
+ plt.contourf(data, levels=levels)
+ plt.colorbar(orientation='horizontal', use_gridspec=use_gridspec)
+
+ locations = ['left', 'right', 'top', 'bottom']
+ plt.figure()
+ for i, location in enumerate(locations):
+ plt.subplot(2, 2, i + 1)
+ plt.contourf(data, levels=levels)
+ plt.colorbar(location=location, use_gridspec=use_gridspec)
+
+ # -------------------
+ plt.figure()
+ # make some other data (random integers)
+ data_2nd = np.array([[2, 3, 2, 3], [1.5, 2, 2, 3], [2, 3, 3, 4]])
+ # make the random data expand to the shape of the main data
+ data_2nd = np.repeat(np.repeat(data_2nd, 10, axis=1), 10, axis=0)
+
+ color_mappable = plt.contourf(data, levels=levels, extend='both')
+ # test extend frac here
+ hatch_mappable = plt.contourf(data_2nd, levels=[1, 2, 3], colors='none',
+ hatches=['/', 'o', '+'], extend='max')
+ plt.contour(hatch_mappable, colors='black')
+
+ plt.colorbar(color_mappable, location='left', label='variable 1',
+ use_gridspec=use_gridspec)
+ plt.colorbar(hatch_mappable, location='right', label='variable 2',
+ use_gridspec=use_gridspec)
+
+ # -------------------
+ plt.figure()
+ ax1 = plt.subplot(211, anchor='NE', aspect='equal')
+ plt.contourf(data, levels=levels)
+ ax2 = plt.subplot(223)
+ plt.contourf(data, levels=levels)
+ ax3 = plt.subplot(224)
+ plt.contourf(data, levels=levels)
+
+ plt.colorbar(ax=[ax2, ax3, ax1], location='right', pad=0.0, shrink=0.5,
+ panchor=False, use_gridspec=use_gridspec)
+ plt.colorbar(ax=[ax2, ax3, ax1], location='left', shrink=0.5,
+ panchor=False, use_gridspec=use_gridspec)
+ plt.colorbar(ax=[ax1], location='bottom', panchor=False,
+ anchor=(0.8, 0.5), shrink=0.6, use_gridspec=use_gridspec)
+
+
+def test_colorbar_single_ax_panchor_false():
+ # Note that this differs from the tests above with panchor=False because
+ # there use_gridspec is actually ineffective: passing *ax* as lists always
+ # disables use_gridspec.
+ ax = plt.subplot(111, anchor='N')
+ plt.imshow([[0, 1]])
+ plt.colorbar(panchor=False)
+ assert ax.get_anchor() == 'N'
+
+
+@pytest.mark.parametrize('constrained', [False, True],
+ ids=['standard', 'constrained'])
+def test_colorbar_single_ax_panchor_east(constrained):
+ fig = plt.figure(constrained_layout=constrained)
+ ax = fig.add_subplot(111, anchor='N')
+ plt.imshow([[0, 1]])
+ plt.colorbar(panchor='E')
+ assert ax.get_anchor() == 'E'
+
+
+@image_comparison(['contour_colorbar.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.054)
+def test_contour_colorbar():
+ fig, ax = plt.subplots(figsize=(4, 2))
+ data = np.arange(1200).reshape(30, 40) - 500
+ levels = np.array([0, 200, 400, 600, 800, 1000, 1200]) - 500
+
+ CS = ax.contour(data, levels=levels, extend='both')
+ fig.colorbar(CS, orientation='horizontal', extend='both')
+ fig.colorbar(CS, orientation='vertical')
+
+
+@image_comparison(['cbar_with_subplots_adjust.png'], remove_text=True,
+ savefig_kwarg={'dpi': 40})
+def test_gridspec_make_colorbar():
+ plt.figure()
+ data = np.arange(1200).reshape(30, 40)
+ levels = [0, 200, 400, 600, 800, 1000, 1200]
+
+ plt.subplot(121)
+ plt.contourf(data, levels=levels)
+ plt.colorbar(use_gridspec=True, orientation='vertical')
+
+ plt.subplot(122)
+ plt.contourf(data, levels=levels)
+ plt.colorbar(use_gridspec=True, orientation='horizontal')
+
+ plt.subplots_adjust(top=0.95, right=0.95, bottom=0.2, hspace=0.25)
+
+
+@image_comparison(['colorbar_single_scatter.png'], remove_text=True,
+ savefig_kwarg={'dpi': 40})
+def test_colorbar_single_scatter():
+ # Issue #2642: if a path collection has only one entry,
+ # the norm scaling within the colorbar must ensure a
+ # finite range, otherwise a zero denominator will occur in _locate.
+ plt.figure()
+ x = y = [0]
+ z = [50]
+ cmap = mpl.colormaps['jet'].resampled(16)
+ cs = plt.scatter(x, y, z, c=z, cmap=cmap)
+ plt.colorbar(cs)
+
+
+@pytest.mark.parametrize('use_gridspec', [True, False])
+@pytest.mark.parametrize('nested_gridspecs', [True, False])
+def test_remove_from_figure(nested_gridspecs, use_gridspec):
+ """Test `remove` with the specified ``use_gridspec`` setting."""
+ fig = plt.figure()
+ if nested_gridspecs:
+ gs = fig.add_gridspec(2, 2)[1, 1].subgridspec(2, 2)
+ ax = fig.add_subplot(gs[1, 1])
+ else:
+ ax = fig.add_subplot()
+ sc = ax.scatter([1, 2], [3, 4])
+ sc.set_array(np.array([5, 6]))
+ pre_position = ax.get_position()
+ cb = fig.colorbar(sc, use_gridspec=use_gridspec)
+ fig.subplots_adjust()
+ cb.remove()
+ fig.subplots_adjust()
+ post_position = ax.get_position()
+ assert (pre_position.get_points() == post_position.get_points()).all()
+
+
+def test_remove_from_figure_cl():
+ """Test `remove` with constrained_layout."""
+ fig, ax = plt.subplots(constrained_layout=True)
+ sc = ax.scatter([1, 2], [3, 4])
+ sc.set_array(np.array([5, 6]))
+ fig.draw_without_rendering()
+ pre_position = ax.get_position()
+ cb = fig.colorbar(sc)
+ cb.remove()
+ fig.draw_without_rendering()
+ post_position = ax.get_position()
+ np.testing.assert_allclose(pre_position.get_points(),
+ post_position.get_points())
+
+
+def test_colorbarbase():
+ # smoke test from #3805
+ ax = plt.gca()
+ Colorbar(ax, cmap=plt.cm.bone)
+
+
+def test_parentless_mappable():
+ pc = mpl.collections.PatchCollection([], cmap=plt.get_cmap('viridis'), array=[])
+ with pytest.raises(ValueError, match='Unable to determine Axes to steal'):
+ plt.colorbar(pc)
+
+
+@image_comparison(['colorbar_closed_patch.png'], remove_text=True)
+def test_colorbar_closed_patch():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ fig = plt.figure(figsize=(8, 6))
+ ax1 = fig.add_axes([0.05, 0.85, 0.9, 0.1])
+ ax2 = fig.add_axes([0.1, 0.65, 0.75, 0.1])
+ ax3 = fig.add_axes([0.05, 0.45, 0.9, 0.1])
+ ax4 = fig.add_axes([0.05, 0.25, 0.9, 0.1])
+ ax5 = fig.add_axes([0.05, 0.05, 0.9, 0.1])
+
+ cmap = mpl.colormaps["RdBu"].resampled(5)
+
+ im = ax1.pcolormesh(np.linspace(0, 10, 16).reshape((4, 4)), cmap=cmap)
+
+ # The use of a "values" kwarg here is unusual. It works only
+ # because it is matched to the data range in the image and to
+ # the number of colors in the LUT.
+ values = np.linspace(0, 10, 5)
+ cbar_kw = dict(orientation='horizontal', values=values, ticks=[])
+
+ # The wide line is to show that the closed path is being handled
+ # correctly. See PR #4186.
+ with rc_context({'axes.linewidth': 16}):
+ plt.colorbar(im, cax=ax2, extend='both', extendfrac=0.5, **cbar_kw)
+ plt.colorbar(im, cax=ax3, extend='both', **cbar_kw)
+ plt.colorbar(im, cax=ax4, extend='both', extendrect=True, **cbar_kw)
+ plt.colorbar(im, cax=ax5, extend='neither', **cbar_kw)
+
+
+def test_colorbar_ticks():
+ # test fix for #5673
+ fig, ax = plt.subplots()
+ x = np.arange(-3.0, 4.001)
+ y = np.arange(-4.0, 3.001)
+ X, Y = np.meshgrid(x, y)
+ Z = X * Y
+ clevs = np.array([-12, -5, 0, 5, 12], dtype=float)
+ colors = ['r', 'g', 'b', 'c']
+ cs = ax.contourf(X, Y, Z, clevs, colors=colors, extend='neither')
+ cbar = fig.colorbar(cs, ax=ax, orientation='horizontal', ticks=clevs)
+ assert len(cbar.ax.xaxis.get_ticklocs()) == len(clevs)
+
+
+def test_colorbar_minorticks_on_off():
+ # test for github issue #11510 and PR #11584
+ np.random.seed(seed=12345)
+ data = np.random.randn(20, 20)
+ with rc_context({'_internal.classic_mode': False}):
+ fig, ax = plt.subplots()
+ # purposefully setting vmin and vmax to odd fractions
+ # so as to check for the correct locations of the minor ticks
+ im = ax.pcolormesh(data, vmin=-2.3, vmax=3.3)
+
+ cbar = fig.colorbar(im, extend='both')
+ # testing after minorticks_on()
+ cbar.minorticks_on()
+ np.testing.assert_almost_equal(
+ cbar.ax.yaxis.get_minorticklocs(),
+ [-2.2, -1.8, -1.6, -1.4, -1.2, -0.8, -0.6, -0.4, -0.2,
+ 0.2, 0.4, 0.6, 0.8, 1.2, 1.4, 1.6, 1.8, 2.2, 2.4, 2.6, 2.8, 3.2])
+ # testing after minorticks_off()
+ cbar.minorticks_off()
+ np.testing.assert_almost_equal(cbar.ax.yaxis.get_minorticklocs(), [])
+
+ im.set_clim(vmin=-1.2, vmax=1.2)
+ cbar.minorticks_on()
+ np.testing.assert_almost_equal(
+ cbar.ax.yaxis.get_minorticklocs(),
+ [-1.2, -1.1, -0.9, -0.8, -0.7, -0.6, -0.4, -0.3, -0.2, -0.1,
+ 0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, 1.1, 1.2])
+
+ # tests for github issue #13257 and PR #13265
+ data = np.random.uniform(low=1, high=10, size=(20, 20))
+
+ fig, ax = plt.subplots()
+ im = ax.pcolormesh(data, norm=LogNorm())
+ cbar = fig.colorbar(im)
+ fig.canvas.draw()
+ default_minorticklocks = cbar.ax.yaxis.get_minorticklocs()
+ # test that minorticks turn off for LogNorm
+ cbar.minorticks_off()
+ np.testing.assert_equal(cbar.ax.yaxis.get_minorticklocs(), [])
+
+ # test that minorticks turn back on for LogNorm
+ cbar.minorticks_on()
+ np.testing.assert_equal(cbar.ax.yaxis.get_minorticklocs(),
+ default_minorticklocks)
+
+ # test issue #13339: minorticks for LogNorm should stay off
+ cbar.minorticks_off()
+ cbar.set_ticks([3, 5, 7, 9])
+ np.testing.assert_equal(cbar.ax.yaxis.get_minorticklocs(), [])
+
+
+def test_cbar_minorticks_for_rc_xyminortickvisible():
+ """
+ issue gh-16468.
+
+ Making sure that minor ticks on the colorbar are turned on
+ (internally) using the cbar.minorticks_on() method when
+ rcParams['xtick.minor.visible'] = True (for horizontal cbar)
+ rcParams['ytick.minor.visible'] = True (for vertical cbar).
+ Using cbar.minorticks_on() ensures that the minor ticks
+ don't overflow into the extend regions of the colorbar.
+ """
+
+ plt.rcParams['ytick.minor.visible'] = True
+ plt.rcParams['xtick.minor.visible'] = True
+
+ vmin, vmax = 0.4, 2.6
+ fig, ax = plt.subplots()
+ im = ax.pcolormesh([[1, 2]], vmin=vmin, vmax=vmax)
+
+ cbar = fig.colorbar(im, extend='both', orientation='vertical')
+ assert cbar.ax.yaxis.get_minorticklocs()[0] >= vmin
+ assert cbar.ax.yaxis.get_minorticklocs()[-1] <= vmax
+
+ cbar = fig.colorbar(im, extend='both', orientation='horizontal')
+ assert cbar.ax.xaxis.get_minorticklocs()[0] >= vmin
+ assert cbar.ax.xaxis.get_minorticklocs()[-1] <= vmax
+
+
+def test_colorbar_autoticks():
+ # Test new autotick modes. Needs to be classic because
+ # non-classic doesn't go this route.
+ with rc_context({'_internal.classic_mode': False}):
+ fig, ax = plt.subplots(2, 1)
+ x = np.arange(-3.0, 4.001)
+ y = np.arange(-4.0, 3.001)
+ X, Y = np.meshgrid(x, y)
+ Z = X * Y
+ Z = Z[:-1, :-1]
+ pcm = ax[0].pcolormesh(X, Y, Z)
+ cbar = fig.colorbar(pcm, ax=ax[0], extend='both',
+ orientation='vertical')
+
+ pcm = ax[1].pcolormesh(X, Y, Z)
+ cbar2 = fig.colorbar(pcm, ax=ax[1], extend='both',
+ orientation='vertical', shrink=0.4)
+ # note only -10 to 10 are visible,
+ np.testing.assert_almost_equal(cbar.ax.yaxis.get_ticklocs(),
+ np.arange(-15, 16, 5))
+ # note only -10 to 10 are visible
+ np.testing.assert_almost_equal(cbar2.ax.yaxis.get_ticklocs(),
+ np.arange(-20, 21, 10))
+
+
+def test_colorbar_autotickslog():
+ # Test new autotick modes...
+ with rc_context({'_internal.classic_mode': False}):
+ fig, ax = plt.subplots(2, 1)
+ x = np.arange(-3.0, 4.001)
+ y = np.arange(-4.0, 3.001)
+ X, Y = np.meshgrid(x, y)
+ Z = X * Y
+ Z = Z[:-1, :-1]
+ pcm = ax[0].pcolormesh(X, Y, 10**Z, norm=LogNorm())
+ cbar = fig.colorbar(pcm, ax=ax[0], extend='both',
+ orientation='vertical')
+
+ pcm = ax[1].pcolormesh(X, Y, 10**Z, norm=LogNorm())
+ cbar2 = fig.colorbar(pcm, ax=ax[1], extend='both',
+ orientation='vertical', shrink=0.4)
+ # note only -12 to +12 are visible
+ np.testing.assert_almost_equal(cbar.ax.yaxis.get_ticklocs(),
+ 10**np.arange(-16., 16.2, 4.))
+ # note only -24 to +24 are visible
+ np.testing.assert_almost_equal(cbar2.ax.yaxis.get_ticklocs(),
+ 10**np.arange(-24., 25., 12.))
+
+
+def test_colorbar_get_ticks():
+ # test feature for #5792
+ plt.figure()
+ data = np.arange(1200).reshape(30, 40)
+ levels = [0, 200, 400, 600, 800, 1000, 1200]
+
+ plt.contourf(data, levels=levels)
+
+ # testing getter for user set ticks
+ userTicks = plt.colorbar(ticks=[0, 600, 1200])
+ assert userTicks.get_ticks().tolist() == [0, 600, 1200]
+
+ # testing for getter after calling set_ticks
+ userTicks.set_ticks([600, 700, 800])
+ assert userTicks.get_ticks().tolist() == [600, 700, 800]
+
+ # testing for getter after calling set_ticks with some ticks out of bounds
+ # removed #20054: other axes don't trim fixed lists, so colorbars
+ # should not either:
+ # userTicks.set_ticks([600, 1300, 1400, 1500])
+ # assert userTicks.get_ticks().tolist() == [600]
+
+ # testing getter when no ticks are assigned
+ defTicks = plt.colorbar(orientation='horizontal')
+ np.testing.assert_allclose(defTicks.get_ticks().tolist(), levels)
+
+ # test normal ticks and minor ticks
+ fig, ax = plt.subplots()
+ x = np.arange(-3.0, 4.001)
+ y = np.arange(-4.0, 3.001)
+ X, Y = np.meshgrid(x, y)
+ Z = X * Y
+ Z = Z[:-1, :-1]
+ pcm = ax.pcolormesh(X, Y, Z)
+ cbar = fig.colorbar(pcm, ax=ax, extend='both',
+ orientation='vertical')
+ ticks = cbar.get_ticks()
+ np.testing.assert_allclose(ticks, np.arange(-15, 16, 5))
+ assert len(cbar.get_ticks(minor=True)) == 0
+
+
+@pytest.mark.parametrize("extend", ['both', 'min', 'max'])
+def test_colorbar_lognorm_extension(extend):
+ # Test that colorbar with lognorm is extended correctly
+ f, ax = plt.subplots()
+ cb = Colorbar(ax, norm=LogNorm(vmin=0.1, vmax=1000.0),
+ orientation='vertical', extend=extend)
+ assert cb._values[0] >= 0.0
+
+
+def test_colorbar_powernorm_extension():
+ # Test that colorbar with powernorm is extended correctly
+ # Just a smoke test that adding the colorbar doesn't raise an error or warning
+ fig, ax = plt.subplots()
+ Colorbar(ax, norm=PowerNorm(gamma=0.5, vmin=0.0, vmax=1.0),
+ orientation='vertical', extend='both')
+
+
+def test_colorbar_axes_kw():
+ # test fix for #8493: This does only test, that axes-related keywords pass
+ # and do not raise an exception.
+ plt.figure()
+ plt.imshow([[1, 2], [3, 4]])
+ plt.colorbar(orientation='horizontal', fraction=0.2, pad=0.2, shrink=0.5,
+ aspect=10, anchor=(0., 0.), panchor=(0., 1.))
+
+
+def test_colorbar_log_minortick_labels():
+ with rc_context({'_internal.classic_mode': False}):
+ fig, ax = plt.subplots()
+ pcm = ax.imshow([[10000, 50000]], norm=LogNorm())
+ cb = fig.colorbar(pcm)
+ fig.canvas.draw()
+ lb = [l.get_text() for l in cb.ax.yaxis.get_ticklabels(which='both')]
+ expected = [r'$\mathdefault{10^{4}}$',
+ r'$\mathdefault{2\times10^{4}}$',
+ r'$\mathdefault{3\times10^{4}}$',
+ r'$\mathdefault{4\times10^{4}}$']
+ for exp in expected:
+ assert exp in lb
+
+
+def test_colorbar_renorm():
+ x, y = np.ogrid[-4:4:31j, -4:4:31j]
+ z = 120000*np.exp(-x**2 - y**2)
+
+ fig, ax = plt.subplots()
+ im = ax.imshow(z)
+ cbar = fig.colorbar(im)
+ np.testing.assert_allclose(cbar.ax.yaxis.get_majorticklocs(),
+ np.arange(0, 120000.1, 20000))
+
+ cbar.set_ticks([1, 2, 3])
+ assert isinstance(cbar.locator, FixedLocator)
+
+ norm = LogNorm(z.min(), z.max())
+ im.set_norm(norm)
+ np.testing.assert_allclose(cbar.ax.yaxis.get_majorticklocs(),
+ np.logspace(-10, 7, 18))
+ # note that set_norm removes the FixedLocator...
+ assert np.isclose(cbar.vmin, z.min())
+ cbar.set_ticks([1, 2, 3])
+ assert isinstance(cbar.locator, FixedLocator)
+ np.testing.assert_allclose(cbar.ax.yaxis.get_majorticklocs(),
+ [1.0, 2.0, 3.0])
+
+ norm = LogNorm(z.min() * 1000, z.max() * 1000)
+ im.set_norm(norm)
+ assert np.isclose(cbar.vmin, z.min() * 1000)
+ assert np.isclose(cbar.vmax, z.max() * 1000)
+
+
+@pytest.mark.parametrize('fmt', ['%4.2e', '{x:.2e}'])
+def test_colorbar_format(fmt):
+ # make sure that format is passed properly
+ x, y = np.ogrid[-4:4:31j, -4:4:31j]
+ z = 120000*np.exp(-x**2 - y**2)
+
+ fig, ax = plt.subplots()
+ im = ax.imshow(z)
+ cbar = fig.colorbar(im, format=fmt)
+ fig.canvas.draw()
+ assert cbar.ax.yaxis.get_ticklabels()[4].get_text() == '8.00e+04'
+
+ # make sure that if we change the clim of the mappable that the
+ # formatting is *not* lost:
+ im.set_clim([4, 200])
+ fig.canvas.draw()
+ assert cbar.ax.yaxis.get_ticklabels()[4].get_text() == '2.00e+02'
+
+ # but if we change the norm:
+ im.set_norm(LogNorm(vmin=0.1, vmax=10))
+ fig.canvas.draw()
+ assert (cbar.ax.yaxis.get_ticklabels()[0].get_text() ==
+ '$\\mathdefault{10^{-2}}$')
+
+
+def test_colorbar_scale_reset():
+ x, y = np.ogrid[-4:4:31j, -4:4:31j]
+ z = 120000*np.exp(-x**2 - y**2)
+
+ fig, ax = plt.subplots()
+ pcm = ax.pcolormesh(z, cmap='RdBu_r', rasterized=True)
+ cbar = fig.colorbar(pcm, ax=ax)
+ cbar.outline.set_edgecolor('red')
+ assert cbar.ax.yaxis.get_scale() == 'linear'
+
+ pcm.set_norm(LogNorm(vmin=1, vmax=100))
+ assert cbar.ax.yaxis.get_scale() == 'log'
+ pcm.set_norm(Normalize(vmin=-20, vmax=20))
+ assert cbar.ax.yaxis.get_scale() == 'linear'
+
+ assert cbar.outline.get_edgecolor() == mcolors.to_rgba('red')
+
+ # log scale with no vmin/vmax set should scale to the data if there
+ # is a mappable already associated with the colorbar, not (0, 1)
+ pcm.norm = LogNorm()
+ assert pcm.norm.vmin == z.min()
+ assert pcm.norm.vmax == z.max()
+
+
+def test_colorbar_get_ticks_2():
+ plt.rcParams['_internal.classic_mode'] = False
+ fig, ax = plt.subplots()
+ pc = ax.pcolormesh([[.05, .95]])
+ cb = fig.colorbar(pc)
+ np.testing.assert_allclose(cb.get_ticks(), [0., 0.2, 0.4, 0.6, 0.8, 1.0])
+
+
+def test_colorbar_inverted_ticks():
+ fig, axs = plt.subplots(2)
+ ax = axs[0]
+ pc = ax.pcolormesh(10**np.arange(1, 5).reshape(2, 2), norm=LogNorm())
+ cbar = fig.colorbar(pc, ax=ax, extend='both')
+ ticks = cbar.get_ticks()
+ cbar.ax.invert_yaxis()
+ np.testing.assert_allclose(ticks, cbar.get_ticks())
+
+ ax = axs[1]
+ pc = ax.pcolormesh(np.arange(1, 5).reshape(2, 2))
+ cbar = fig.colorbar(pc, ax=ax, extend='both')
+ cbar.minorticks_on()
+ ticks = cbar.get_ticks()
+ minorticks = cbar.get_ticks(minor=True)
+ assert isinstance(minorticks, np.ndarray)
+ cbar.ax.invert_yaxis()
+ np.testing.assert_allclose(ticks, cbar.get_ticks())
+ np.testing.assert_allclose(minorticks, cbar.get_ticks(minor=True))
+
+
+def test_mappable_no_alpha():
+ fig, ax = plt.subplots()
+ sm = cm.ScalarMappable(norm=mcolors.Normalize(), cmap='viridis')
+ fig.colorbar(sm, ax=ax)
+ sm.set_cmap('plasma')
+ plt.draw()
+
+
+def test_mappable_2d_alpha():
+ fig, ax = plt.subplots()
+ x = np.arange(1, 5).reshape(2, 2)/4
+ pc = ax.pcolormesh(x, alpha=x)
+ cb = fig.colorbar(pc, ax=ax)
+ # The colorbar's alpha should be None and the mappable should still have
+ # the original alpha array
+ assert cb.alpha is None
+ assert pc.get_alpha() is x
+ fig.draw_without_rendering()
+
+
+def test_colorbar_label():
+ """
+ Test the label parameter. It should just be mapped to the xlabel/ylabel of
+ the axes, depending on the orientation.
+ """
+ fig, ax = plt.subplots()
+ im = ax.imshow([[1, 2], [3, 4]])
+ cbar = fig.colorbar(im, label='cbar')
+ assert cbar.ax.get_ylabel() == 'cbar'
+ cbar.set_label(None)
+ assert cbar.ax.get_ylabel() == ''
+ cbar.set_label('cbar 2')
+ assert cbar.ax.get_ylabel() == 'cbar 2'
+
+ cbar2 = fig.colorbar(im, label=None)
+ assert cbar2.ax.get_ylabel() == ''
+
+ cbar3 = fig.colorbar(im, orientation='horizontal', label='horizontal cbar')
+ assert cbar3.ax.get_xlabel() == 'horizontal cbar'
+
+
+@image_comparison(['colorbar_keeping_xlabel.png'], style='mpl20')
+def test_keeping_xlabel():
+ # github issue #23398 - xlabels being ignored in colorbar axis
+ arr = np.arange(25).reshape((5, 5))
+ fig, ax = plt.subplots()
+ im = ax.imshow(arr)
+ cbar = plt.colorbar(im)
+ cbar.ax.set_xlabel('Visible Xlabel')
+ cbar.set_label('YLabel')
+
+
+@pytest.mark.parametrize("clim", [(-20000, 20000), (-32768, 0)])
+def test_colorbar_int(clim):
+ # Check that we cast to float early enough to not
+ # overflow ``int16(20000) - int16(-20000)`` or
+ # run into ``abs(int16(-32768)) == -32768``.
+ fig, ax = plt.subplots()
+ im = ax.imshow([[*map(np.int16, clim)]])
+ fig.colorbar(im)
+ assert (im.norm.vmin, im.norm.vmax) == clim
+
+
+def test_anchored_cbar_position_using_specgrid():
+ data = np.arange(1200).reshape(30, 40)
+ levels = [0, 200, 400, 600, 800, 1000, 1200]
+ shrink = 0.5
+ anchor_y = 0.3
+ # right
+ fig, ax = plt.subplots()
+ cs = ax.contourf(data, levels=levels)
+ cbar = plt.colorbar(
+ cs, ax=ax, use_gridspec=True,
+ location='right', anchor=(1, anchor_y), shrink=shrink)
+
+ # the bottom left corner of one ax is (x0, y0)
+ # the top right corner of one ax is (x1, y1)
+ # p0: the vertical / horizontal position of anchor
+ x0, y0, x1, y1 = ax.get_position().extents
+ cx0, cy0, cx1, cy1 = cbar.ax.get_position().extents
+ p0 = (y1 - y0) * anchor_y + y0
+
+ np.testing.assert_allclose(
+ [cy1, cy0],
+ [y1 * shrink + (1 - shrink) * p0, p0 * (1 - shrink) + y0 * shrink])
+
+ # left
+ fig, ax = plt.subplots()
+ cs = ax.contourf(data, levels=levels)
+ cbar = plt.colorbar(
+ cs, ax=ax, use_gridspec=True,
+ location='left', anchor=(1, anchor_y), shrink=shrink)
+
+ # the bottom left corner of one ax is (x0, y0)
+ # the top right corner of one ax is (x1, y1)
+ # p0: the vertical / horizontal position of anchor
+ x0, y0, x1, y1 = ax.get_position().extents
+ cx0, cy0, cx1, cy1 = cbar.ax.get_position().extents
+ p0 = (y1 - y0) * anchor_y + y0
+
+ np.testing.assert_allclose(
+ [cy1, cy0],
+ [y1 * shrink + (1 - shrink) * p0, p0 * (1 - shrink) + y0 * shrink])
+
+ # top
+ shrink = 0.5
+ anchor_x = 0.3
+ fig, ax = plt.subplots()
+ cs = ax.contourf(data, levels=levels)
+ cbar = plt.colorbar(
+ cs, ax=ax, use_gridspec=True,
+ location='top', anchor=(anchor_x, 1), shrink=shrink)
+
+ # the bottom left corner of one ax is (x0, y0)
+ # the top right corner of one ax is (x1, y1)
+ # p0: the vertical / horizontal position of anchor
+ x0, y0, x1, y1 = ax.get_position().extents
+ cx0, cy0, cx1, cy1 = cbar.ax.get_position().extents
+ p0 = (x1 - x0) * anchor_x + x0
+
+ np.testing.assert_allclose(
+ [cx1, cx0],
+ [x1 * shrink + (1 - shrink) * p0, p0 * (1 - shrink) + x0 * shrink])
+
+ # bottom
+ shrink = 0.5
+ anchor_x = 0.3
+ fig, ax = plt.subplots()
+ cs = ax.contourf(data, levels=levels)
+ cbar = plt.colorbar(
+ cs, ax=ax, use_gridspec=True,
+ location='bottom', anchor=(anchor_x, 1), shrink=shrink)
+
+ # the bottom left corner of one ax is (x0, y0)
+ # the top right corner of one ax is (x1, y1)
+ # p0: the vertical / horizontal position of anchor
+ x0, y0, x1, y1 = ax.get_position().extents
+ cx0, cy0, cx1, cy1 = cbar.ax.get_position().extents
+ p0 = (x1 - x0) * anchor_x + x0
+
+ np.testing.assert_allclose(
+ [cx1, cx0],
+ [x1 * shrink + (1 - shrink) * p0, p0 * (1 - shrink) + x0 * shrink])
+
+
+@image_comparison(['colorbar_change_lim_scale.png'], remove_text=True,
+ style='mpl20')
+def test_colorbar_change_lim_scale():
+ fig, ax = plt.subplots(1, 2, constrained_layout=True)
+ pc = ax[0].pcolormesh(np.arange(100).reshape(10, 10)+1)
+ cb = fig.colorbar(pc, ax=ax[0], extend='both')
+ cb.ax.set_yscale('log')
+
+ pc = ax[1].pcolormesh(np.arange(100).reshape(10, 10)+1)
+ cb = fig.colorbar(pc, ax=ax[1], extend='both')
+ cb.ax.set_ylim([20, 90])
+
+
+@check_figures_equal(extensions=["png"])
+def test_axes_handles_same_functions(fig_ref, fig_test):
+ # prove that cax and cb.ax are functionally the same
+ for nn, fig in enumerate([fig_ref, fig_test]):
+ ax = fig.add_subplot()
+ pc = ax.pcolormesh(np.ones(300).reshape(10, 30))
+ cax = fig.add_axes([0.9, 0.1, 0.03, 0.8])
+ cb = fig.colorbar(pc, cax=cax)
+ if nn == 0:
+ caxx = cax
+ else:
+ caxx = cb.ax
+ caxx.set_yticks(np.arange(0, 20))
+ caxx.set_yscale('log')
+ caxx.set_position([0.92, 0.1, 0.02, 0.7])
+
+
+def test_inset_colorbar_layout():
+ fig, ax = plt.subplots(constrained_layout=True, figsize=(3, 6))
+ pc = ax.imshow(np.arange(100).reshape(10, 10))
+ cax = ax.inset_axes([1.02, 0.1, 0.03, 0.8])
+ cb = fig.colorbar(pc, cax=cax)
+
+ fig.draw_without_rendering()
+ # make sure this is in the figure. In the colorbar swapping
+ # it was being dropped from the list of children...
+ np.testing.assert_allclose(cb.ax.get_position().bounds,
+ [0.87, 0.342, 0.0237, 0.315], atol=0.01)
+ assert cb.ax in ax.child_axes
+
+
+@image_comparison(['colorbar_twoslope.png'], remove_text=True,
+ style='mpl20')
+def test_twoslope_colorbar():
+ # Note that the second tick = 20, and should be in the middle
+ # of the colorbar (white)
+ # There should be no tick right at the bottom, nor at the top.
+ fig, ax = plt.subplots()
+
+ norm = mcolors.TwoSlopeNorm(20, 5, 95)
+ pc = ax.pcolormesh(np.arange(1, 11), np.arange(1, 11),
+ np.arange(100).reshape(10, 10),
+ norm=norm, cmap='RdBu_r')
+ fig.colorbar(pc)
+
+
+@check_figures_equal(extensions=["png"])
+def test_remove_cb_whose_mappable_has_no_figure(fig_ref, fig_test):
+ ax = fig_test.add_subplot()
+ cb = fig_test.colorbar(cm.ScalarMappable(), cax=ax)
+ cb.remove()
+
+
+def test_aspects():
+ fig, ax = plt.subplots(3, 2, figsize=(8, 8))
+ aspects = [20, 20, 10]
+ extends = ['neither', 'both', 'both']
+ cb = [[None, None, None], [None, None, None]]
+ for nn, orient in enumerate(['vertical', 'horizontal']):
+ for mm, (aspect, extend) in enumerate(zip(aspects, extends)):
+ pc = ax[mm, nn].pcolormesh(np.arange(100).reshape(10, 10))
+ cb[nn][mm] = fig.colorbar(pc, ax=ax[mm, nn], orientation=orient,
+ aspect=aspect, extend=extend)
+ fig.draw_without_rendering()
+ # check the extends are right ratio:
+ np.testing.assert_almost_equal(cb[0][1].ax.get_position().height,
+ cb[0][0].ax.get_position().height * 0.9,
+ decimal=2)
+ # horizontal
+ np.testing.assert_almost_equal(cb[1][1].ax.get_position().width,
+ cb[1][0].ax.get_position().width * 0.9,
+ decimal=2)
+ # check correct aspect:
+ pos = cb[0][0].ax.get_position(original=False)
+ np.testing.assert_almost_equal(pos.height, pos.width * 20, decimal=2)
+ pos = cb[1][0].ax.get_position(original=False)
+ np.testing.assert_almost_equal(pos.height * 20, pos.width, decimal=2)
+ # check twice as wide if aspect is 10 instead of 20
+ np.testing.assert_almost_equal(
+ cb[0][0].ax.get_position(original=False).width * 2,
+ cb[0][2].ax.get_position(original=False).width, decimal=2)
+ np.testing.assert_almost_equal(
+ cb[1][0].ax.get_position(original=False).height * 2,
+ cb[1][2].ax.get_position(original=False).height, decimal=2)
+
+
+@image_comparison(['proportional_colorbars.png'], remove_text=True,
+ style='mpl20')
+def test_proportional_colorbars():
+
+ x = y = np.arange(-3.0, 3.01, 0.025)
+ X, Y = np.meshgrid(x, y)
+ Z1 = np.exp(-X**2 - Y**2)
+ Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
+ Z = (Z1 - Z2) * 2
+
+ levels = [-1.25, -0.5, -0.125, 0.125, 0.5, 1.25]
+ cmap = mcolors.ListedColormap(
+ ['0.3', '0.5', 'white', 'lightblue', 'steelblue'])
+ cmap.set_under('darkred')
+ cmap.set_over('crimson')
+ norm = mcolors.BoundaryNorm(levels, cmap.N)
+
+ extends = ['neither', 'both']
+ spacings = ['uniform', 'proportional']
+ fig, axs = plt.subplots(2, 2)
+ for i in range(2):
+ for j in range(2):
+ CS3 = axs[i, j].contourf(X, Y, Z, levels, cmap=cmap, norm=norm,
+ extend=extends[i])
+ fig.colorbar(CS3, spacing=spacings[j], ax=axs[i, j])
+
+
+@image_comparison(['extend_drawedges.png'], remove_text=True, style='mpl20')
+def test_colorbar_extend_drawedges():
+ params = [
+ ('both', 1, [[[1.1, 0], [1.1, 1]],
+ [[2, 0], [2, 1]],
+ [[2.9, 0], [2.9, 1]]]),
+ ('min', 0, [[[1.1, 0], [1.1, 1]],
+ [[2, 0], [2, 1]]]),
+ ('max', 0, [[[2, 0], [2, 1]],
+ [[2.9, 0], [2.9, 1]]]),
+ ('neither', -1, [[[2, 0], [2, 1]]]),
+ ]
+
+ plt.rcParams['axes.linewidth'] = 2
+
+ fig = plt.figure(figsize=(10, 4))
+ subfigs = fig.subfigures(1, 2)
+
+ for orientation, subfig in zip(['horizontal', 'vertical'], subfigs):
+ if orientation == 'horizontal':
+ axs = subfig.subplots(4, 1)
+ else:
+ axs = subfig.subplots(1, 4)
+ fig.subplots_adjust(left=0.05, bottom=0.05, right=0.95, top=0.95)
+
+ for ax, (extend, coloroffset, res) in zip(axs, params):
+ cmap = mpl.colormaps["viridis"]
+ bounds = np.arange(5)
+ nb_colors = len(bounds) + coloroffset
+ colors = cmap(np.linspace(100, 255, nb_colors).astype(int))
+ cmap, norm = mcolors.from_levels_and_colors(bounds, colors,
+ extend=extend)
+
+ cbar = Colorbar(ax, cmap=cmap, norm=norm, orientation=orientation,
+ drawedges=True)
+ # Set limits such that only two colours are visible, and the
+ # dividers would be outside the Axes, to ensure that a) they are
+ # not drawn outside, and b) a divider still appears between the
+ # main colour and the extension.
+ if orientation == 'horizontal':
+ ax.set_xlim(1.1, 2.9)
+ else:
+ ax.set_ylim(1.1, 2.9)
+ res = np.array(res)[:, :, [1, 0]]
+ np.testing.assert_array_equal(cbar.dividers.get_segments(), res)
+
+
+@image_comparison(['contourf_extend_patches.png'], remove_text=True,
+ style='mpl20')
+def test_colorbar_contourf_extend_patches():
+ params = [
+ ('both', 5, ['\\', '//']),
+ ('min', 7, ['+']),
+ ('max', 2, ['|', '-', '/', '\\', '//']),
+ ('neither', 10, ['//', '\\', '||']),
+ ]
+
+ plt.rcParams['axes.linewidth'] = 2
+
+ fig = plt.figure(figsize=(10, 4))
+ subfigs = fig.subfigures(1, 2)
+ fig.subplots_adjust(left=0.05, bottom=0.05, right=0.95, top=0.95)
+
+ x = np.linspace(-2, 3, 50)
+ y = np.linspace(-2, 3, 30)
+ z = np.cos(x[np.newaxis, :]) + np.sin(y[:, np.newaxis])
+
+ cmap = mpl.colormaps["viridis"]
+ for orientation, subfig in zip(['horizontal', 'vertical'], subfigs):
+ axs = subfig.subplots(2, 2).ravel()
+ for ax, (extend, levels, hatches) in zip(axs, params):
+ cs = ax.contourf(x, y, z, levels, hatches=hatches,
+ cmap=cmap, extend=extend)
+ subfig.colorbar(cs, ax=ax, orientation=orientation, fraction=0.4,
+ extendfrac=0.2, aspect=5)
+
+
+def test_negative_boundarynorm():
+ fig, ax = plt.subplots(figsize=(1, 3))
+ cmap = mpl.colormaps["viridis"]
+
+ clevs = np.arange(-94, -85)
+ norm = BoundaryNorm(clevs, cmap.N)
+ cb = fig.colorbar(cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax)
+ np.testing.assert_allclose(cb.ax.get_ylim(), [clevs[0], clevs[-1]])
+ np.testing.assert_allclose(cb.ax.get_yticks(), clevs)
+
+ clevs = np.arange(85, 94)
+ norm = BoundaryNorm(clevs, cmap.N)
+ cb = fig.colorbar(cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax)
+ np.testing.assert_allclose(cb.ax.get_ylim(), [clevs[0], clevs[-1]])
+ np.testing.assert_allclose(cb.ax.get_yticks(), clevs)
+
+ clevs = np.arange(-3, 3)
+ norm = BoundaryNorm(clevs, cmap.N)
+ cb = fig.colorbar(cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax)
+ np.testing.assert_allclose(cb.ax.get_ylim(), [clevs[0], clevs[-1]])
+ np.testing.assert_allclose(cb.ax.get_yticks(), clevs)
+
+ clevs = np.arange(-8, 1)
+ norm = BoundaryNorm(clevs, cmap.N)
+ cb = fig.colorbar(cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax)
+ np.testing.assert_allclose(cb.ax.get_ylim(), [clevs[0], clevs[-1]])
+ np.testing.assert_allclose(cb.ax.get_yticks(), clevs)
+
+
+def test_centerednorm():
+ # Test default centered norm gets expanded with non-singular limits
+ # when plot data is all equal (autoscale halfrange == 0)
+ fig, ax = plt.subplots(figsize=(1, 3))
+
+ norm = mcolors.CenteredNorm()
+ mappable = ax.pcolormesh(np.zeros((3, 3)), norm=norm)
+ fig.colorbar(mappable)
+ assert (norm.vmin, norm.vmax) == (-0.1, 0.1)
+
+
+@image_comparison(['nonorm_colorbars.svg'], style='mpl20')
+def test_nonorm():
+ plt.rcParams['svg.fonttype'] = 'none'
+ data = [1, 2, 3, 4, 5]
+
+ fig, ax = plt.subplots(figsize=(6, 1))
+ fig.subplots_adjust(bottom=0.5)
+
+ norm = NoNorm(vmin=min(data), vmax=max(data))
+ cmap = mpl.colormaps["viridis"].resampled(len(data))
+ mappable = cm.ScalarMappable(norm=norm, cmap=cmap)
+ cbar = fig.colorbar(mappable, cax=ax, orientation="horizontal")
+
+
+@image_comparison(['test_boundaries.png'], remove_text=True,
+ style='mpl20')
+def test_boundaries():
+ np.random.seed(seed=19680808)
+ fig, ax = plt.subplots(figsize=(2, 2))
+ pc = ax.pcolormesh(np.random.randn(10, 10), cmap='RdBu_r')
+ cb = fig.colorbar(pc, ax=ax, boundaries=np.linspace(-3, 3, 7))
+
+
+def test_colorbar_no_warning_rcparams_grid_true():
+ # github issue #21723 - If mpl style has 'axes.grid' = True,
+ # fig.colorbar raises a warning about Auto-removal of grids
+ # by pcolor() and pcolormesh(). This is fixed by PR #22216.
+ plt.rcParams['axes.grid'] = True
+ fig, ax = plt.subplots()
+ ax.grid(False)
+ im = ax.pcolormesh([0, 1], [0, 1], [[1]])
+ # make sure that no warning is raised by fig.colorbar
+ fig.colorbar(im)
+
+
+def test_colorbar_set_formatter_locator():
+ # check that the locator properties echo what is on the axis:
+ fig, ax = plt.subplots()
+ pc = ax.pcolormesh(np.random.randn(10, 10))
+ cb = fig.colorbar(pc)
+ cb.ax.yaxis.set_major_locator(FixedLocator(np.arange(10)))
+ cb.ax.yaxis.set_minor_locator(FixedLocator(np.arange(0, 10, 0.2)))
+ assert cb.locator is cb.ax.yaxis.get_major_locator()
+ assert cb.minorlocator is cb.ax.yaxis.get_minor_locator()
+ cb.ax.yaxis.set_major_formatter(LogFormatter())
+ cb.ax.yaxis.set_minor_formatter(LogFormatter())
+ assert cb.formatter is cb.ax.yaxis.get_major_formatter()
+ assert cb.minorformatter is cb.ax.yaxis.get_minor_formatter()
+
+ # check that the setter works as expected:
+ loc = FixedLocator(np.arange(7))
+ cb.locator = loc
+ assert cb.ax.yaxis.get_major_locator() is loc
+ loc = FixedLocator(np.arange(0, 7, 0.1))
+ cb.minorlocator = loc
+ assert cb.ax.yaxis.get_minor_locator() is loc
+ fmt = LogFormatter()
+ cb.formatter = fmt
+ assert cb.ax.yaxis.get_major_formatter() is fmt
+ fmt = LogFormatter()
+ cb.minorformatter = fmt
+ assert cb.ax.yaxis.get_minor_formatter() is fmt
+ assert cb.long_axis is cb.ax.yaxis
+
+
+@image_comparison(['colorbar_extend_alpha.png'], remove_text=True,
+ savefig_kwarg={'dpi': 40})
+def test_colorbar_extend_alpha():
+ fig, ax = plt.subplots()
+ im = ax.imshow([[0, 1], [2, 3]], alpha=0.3, interpolation="none")
+ fig.colorbar(im, extend='both', boundaries=[0.5, 1.5, 2.5])
+
+
+def test_offset_text_loc():
+ plt.style.use('mpl20')
+ fig, ax = plt.subplots()
+ np.random.seed(seed=19680808)
+ pc = ax.pcolormesh(np.random.randn(10, 10)*1e6)
+ cb = fig.colorbar(pc, location='right', extend='max')
+ fig.draw_without_rendering()
+ # check that the offsetText is in the proper place above the
+ # colorbar axes. In this case the colorbar axes is the same
+ # height as the parent, so use the parents bbox.
+ assert cb.ax.yaxis.offsetText.get_position()[1] > ax.bbox.y1
+
+
+def test_title_text_loc():
+ plt.style.use('mpl20')
+ fig, ax = plt.subplots()
+ np.random.seed(seed=19680808)
+ pc = ax.pcolormesh(np.random.randn(10, 10))
+ cb = fig.colorbar(pc, location='right', extend='max')
+ cb.ax.set_title('Aardvark')
+ fig.draw_without_rendering()
+ # check that the title is in the proper place above the
+ # colorbar axes, including its extend triangles....
+ assert (cb.ax.title.get_window_extent(fig.canvas.get_renderer()).ymax >
+ cb.ax.spines['outline'].get_window_extent().ymax)
+
+
+@check_figures_equal(extensions=["png"])
+def test_passing_location(fig_ref, fig_test):
+ ax_ref = fig_ref.add_subplot()
+ im = ax_ref.imshow([[0, 1], [2, 3]])
+ ax_ref.get_figure().colorbar(im, cax=ax_ref.inset_axes([0, 1.05, 1, 0.05]),
+ orientation="horizontal", ticklocation="top")
+ ax_test = fig_test.add_subplot()
+ im = ax_test.imshow([[0, 1], [2, 3]])
+ ax_test.get_figure().colorbar(im, cax=ax_test.inset_axes([0, 1.05, 1, 0.05]),
+ location="top")
+
+
+@pytest.mark.parametrize("kwargs,error,message", [
+ ({'location': 'top', 'orientation': 'vertical'}, TypeError,
+ "location and orientation are mutually exclusive"),
+ ({'location': 'top', 'orientation': 'vertical', 'cax': True}, TypeError,
+ "location and orientation are mutually exclusive"), # Different to above
+ ({'ticklocation': 'top', 'orientation': 'vertical', 'cax': True},
+ ValueError, "'top' is not a valid value for position"),
+ ({'location': 'top', 'extendfrac': (0, None)}, ValueError,
+ "invalid value for extendfrac"),
+ ])
+def test_colorbar_errors(kwargs, error, message):
+ fig, ax = plt.subplots()
+ im = ax.imshow([[0, 1], [2, 3]])
+ if kwargs.get('cax', None) is True:
+ kwargs['cax'] = ax.inset_axes([0, 1.05, 1, 0.05])
+ with pytest.raises(error, match=message):
+ fig.colorbar(im, **kwargs)
+
+
+def test_colorbar_axes_parmeters():
+ fig, ax = plt.subplots(2)
+ im = ax[0].imshow([[0, 1], [2, 3]])
+ # colorbar should accept any form of axes sequence:
+ fig.colorbar(im, ax=ax)
+ fig.colorbar(im, ax=ax[0])
+ fig.colorbar(im, ax=[_ax for _ax in ax])
+ fig.colorbar(im, ax=(ax[0], ax[1]))
+ fig.colorbar(im, ax={i: _ax for i, _ax in enumerate(ax)}.values())
+ fig.draw_without_rendering()
+
+
+def test_colorbar_wrong_figure():
+ # If we decide in the future to disallow calling colorbar() on the "wrong" figure,
+ # just delete this test.
+ fig_tl = plt.figure(layout="tight")
+ fig_cl = plt.figure(layout="constrained")
+ im = fig_cl.add_subplot().imshow([[0, 1]])
+ # Make sure this doesn't try to setup a gridspec-controlled colorbar on fig_cl,
+ # which would crash CL.
+ with pytest.warns(UserWarning, match="different Figure"):
+ fig_tl.colorbar(im)
+ fig_tl.draw_without_rendering()
+ fig_cl.draw_without_rendering()
+
+
+def test_colorbar_format_string_and_old():
+ plt.imshow([[0, 1]])
+ cb = plt.colorbar(format="{x}%")
+ assert isinstance(cb._formatter, StrMethodFormatter)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_colors.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_colors.py
new file mode 100644
index 0000000000000000000000000000000000000000..7da4bab69c159a95eb36c784374b2951e7d1ab09
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_colors.py
@@ -0,0 +1,1747 @@
+import copy
+import itertools
+import unittest.mock
+
+from io import BytesIO
+import numpy as np
+from PIL import Image
+import pytest
+import base64
+
+from numpy.testing import assert_array_equal, assert_array_almost_equal
+
+from matplotlib import cbook, cm
+import matplotlib
+import matplotlib as mpl
+import matplotlib.colors as mcolors
+import matplotlib.colorbar as mcolorbar
+import matplotlib.colorizer as mcolorizer
+import matplotlib.pyplot as plt
+import matplotlib.scale as mscale
+from matplotlib.rcsetup import cycler
+from matplotlib.testing.decorators import image_comparison, check_figures_equal
+from matplotlib.colors import is_color_like, to_rgba_array
+
+
+@pytest.mark.parametrize('N, result', [
+ (5, [1, .6, .2, .1, 0]),
+ (2, [1, 0]),
+ (1, [0]),
+])
+def test_create_lookup_table(N, result):
+ data = [(0.0, 1.0, 1.0), (0.5, 0.2, 0.2), (1.0, 0.0, 0.0)]
+ assert_array_almost_equal(mcolors._create_lookup_table(N, data), result)
+
+
+@pytest.mark.parametrize("dtype", [np.uint8, int, np.float16, float])
+def test_index_dtype(dtype):
+ # We use subtraction in the indexing, so need to verify that uint8 works
+ cm = mpl.colormaps["viridis"]
+ assert_array_equal(cm(dtype(0)), cm(0))
+
+
+def test_resampled():
+ """
+ GitHub issue #6025 pointed to incorrect ListedColormap.resampled;
+ here we test the method for LinearSegmentedColormap as well.
+ """
+ n = 101
+ colorlist = np.empty((n, 4), float)
+ colorlist[:, 0] = np.linspace(0, 1, n)
+ colorlist[:, 1] = 0.2
+ colorlist[:, 2] = np.linspace(1, 0, n)
+ colorlist[:, 3] = 0.7
+ lsc = mcolors.LinearSegmentedColormap.from_list('lsc', colorlist)
+ lc = mcolors.ListedColormap(colorlist)
+ # Set some bad values for testing too
+ for cmap in [lsc, lc]:
+ cmap.set_under('r')
+ cmap.set_over('g')
+ cmap.set_bad('b')
+ lsc3 = lsc.resampled(3)
+ lc3 = lc.resampled(3)
+ expected = np.array([[0.0, 0.2, 1.0, 0.7],
+ [0.5, 0.2, 0.5, 0.7],
+ [1.0, 0.2, 0.0, 0.7]], float)
+ assert_array_almost_equal(lsc3([0, 0.5, 1]), expected)
+ assert_array_almost_equal(lc3([0, 0.5, 1]), expected)
+ # Test over/under was copied properly
+ assert_array_almost_equal(lsc(np.inf), lsc3(np.inf))
+ assert_array_almost_equal(lsc(-np.inf), lsc3(-np.inf))
+ assert_array_almost_equal(lsc(np.nan), lsc3(np.nan))
+ assert_array_almost_equal(lc(np.inf), lc3(np.inf))
+ assert_array_almost_equal(lc(-np.inf), lc3(-np.inf))
+ assert_array_almost_equal(lc(np.nan), lc3(np.nan))
+
+
+def test_colormaps_get_cmap():
+ cr = mpl.colormaps
+
+ # check str, and Colormap pass
+ assert cr.get_cmap('plasma') == cr["plasma"]
+ assert cr.get_cmap(cr["magma"]) == cr["magma"]
+
+ # check default
+ assert cr.get_cmap(None) == cr[mpl.rcParams['image.cmap']]
+
+ # check ValueError on bad name
+ bad_cmap = 'AardvarksAreAwkward'
+ with pytest.raises(ValueError, match=bad_cmap):
+ cr.get_cmap(bad_cmap)
+
+ # check TypeError on bad type
+ with pytest.raises(TypeError, match='object'):
+ cr.get_cmap(object())
+
+
+def test_double_register_builtin_cmap():
+ name = "viridis"
+ match = f"Re-registering the builtin cmap {name!r}."
+ with pytest.raises(ValueError, match=match):
+ matplotlib.colormaps.register(mpl.colormaps[name], name=name, force=True)
+
+
+def test_colormap_copy():
+ cmap = plt.cm.Reds
+ copied_cmap = copy.copy(cmap)
+ with np.errstate(invalid='ignore'):
+ ret1 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf])
+ cmap2 = copy.copy(copied_cmap)
+ cmap2.set_bad('g')
+ with np.errstate(invalid='ignore'):
+ ret2 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf])
+ assert_array_equal(ret1, ret2)
+ # again with the .copy method:
+ cmap = plt.cm.Reds
+ copied_cmap = cmap.copy()
+ with np.errstate(invalid='ignore'):
+ ret1 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf])
+ cmap2 = copy.copy(copied_cmap)
+ cmap2.set_bad('g')
+ with np.errstate(invalid='ignore'):
+ ret2 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf])
+ assert_array_equal(ret1, ret2)
+
+
+def test_colormap_equals():
+ cmap = mpl.colormaps["plasma"]
+ cm_copy = cmap.copy()
+ # different object id's
+ assert cm_copy is not cmap
+ # But the same data should be equal
+ assert cm_copy == cmap
+ # Change the copy
+ cm_copy.set_bad('y')
+ assert cm_copy != cmap
+ # Make sure we can compare different sizes without failure
+ cm_copy._lut = cm_copy._lut[:10, :]
+ assert cm_copy != cmap
+ # Test different names are equal if the lookup table is the same
+ cm_copy = cmap.copy()
+ cm_copy.name = "Test"
+ assert cm_copy == cmap
+ # Test colorbar extends
+ cm_copy = cmap.copy()
+ cm_copy.colorbar_extend = not cmap.colorbar_extend
+ assert cm_copy != cmap
+
+
+def test_colormap_endian():
+ """
+ GitHub issue #1005: a bug in putmask caused erroneous
+ mapping of 1.0 when input from a non-native-byteorder
+ array.
+ """
+ cmap = mpl.colormaps["jet"]
+ # Test under, over, and invalid along with values 0 and 1.
+ a = [-0.5, 0, 0.5, 1, 1.5, np.nan]
+ for dt in ["f2", "f4", "f8"]:
+ anative = np.ma.masked_invalid(np.array(a, dtype=dt))
+ aforeign = anative.byteswap().view(anative.dtype.newbyteorder())
+ assert_array_equal(cmap(anative), cmap(aforeign))
+
+
+def test_colormap_invalid():
+ """
+ GitHub issue #9892: Handling of nan's were getting mapped to under
+ rather than bad. This tests to make sure all invalid values
+ (-inf, nan, inf) are mapped respectively to (under, bad, over).
+ """
+ cmap = mpl.colormaps["plasma"]
+ x = np.array([-np.inf, -1, 0, np.nan, .7, 2, np.inf])
+
+ expected = np.array([[0.050383, 0.029803, 0.527975, 1.],
+ [0.050383, 0.029803, 0.527975, 1.],
+ [0.050383, 0.029803, 0.527975, 1.],
+ [0., 0., 0., 0.],
+ [0.949217, 0.517763, 0.295662, 1.],
+ [0.940015, 0.975158, 0.131326, 1.],
+ [0.940015, 0.975158, 0.131326, 1.]])
+ assert_array_equal(cmap(x), expected)
+
+ # Test masked representation (-inf, inf) are now masked
+ expected = np.array([[0., 0., 0., 0.],
+ [0.050383, 0.029803, 0.527975, 1.],
+ [0.050383, 0.029803, 0.527975, 1.],
+ [0., 0., 0., 0.],
+ [0.949217, 0.517763, 0.295662, 1.],
+ [0.940015, 0.975158, 0.131326, 1.],
+ [0., 0., 0., 0.]])
+ assert_array_equal(cmap(np.ma.masked_invalid(x)), expected)
+
+ # Test scalar representations
+ assert_array_equal(cmap(-np.inf), cmap(0))
+ assert_array_equal(cmap(np.inf), cmap(1.0))
+ assert_array_equal(cmap(np.nan), [0., 0., 0., 0.])
+
+
+def test_colormap_return_types():
+ """
+ Make sure that tuples are returned for scalar input and
+ that the proper shapes are returned for ndarrays.
+ """
+ cmap = mpl.colormaps["plasma"]
+ # Test return types and shapes
+ # scalar input needs to return a tuple of length 4
+ assert isinstance(cmap(0.5), tuple)
+ assert len(cmap(0.5)) == 4
+
+ # input array returns an ndarray of shape x.shape + (4,)
+ x = np.ones(4)
+ assert cmap(x).shape == x.shape + (4,)
+
+ # multi-dimensional array input
+ x2d = np.zeros((2, 2))
+ assert cmap(x2d).shape == x2d.shape + (4,)
+
+
+def test_BoundaryNorm():
+ """
+ GitHub issue #1258: interpolation was failing with numpy
+ 1.7 pre-release.
+ """
+
+ boundaries = [0, 1.1, 2.2]
+ vals = [-1, 0, 1, 2, 2.2, 4]
+
+ # Without interpolation
+ expected = [-1, 0, 0, 1, 2, 2]
+ ncolors = len(boundaries) - 1
+ bn = mcolors.BoundaryNorm(boundaries, ncolors)
+ assert_array_equal(bn(vals), expected)
+
+ # ncolors != len(boundaries) - 1 triggers interpolation
+ expected = [-1, 0, 0, 2, 3, 3]
+ ncolors = len(boundaries)
+ bn = mcolors.BoundaryNorm(boundaries, ncolors)
+ assert_array_equal(bn(vals), expected)
+
+ # with a single region and interpolation
+ expected = [-1, 1, 1, 1, 3, 3]
+ bn = mcolors.BoundaryNorm([0, 2.2], ncolors)
+ assert_array_equal(bn(vals), expected)
+
+ # more boundaries for a third color
+ boundaries = [0, 1, 2, 3]
+ vals = [-1, 0.1, 1.1, 2.2, 4]
+ ncolors = 5
+ expected = [-1, 0, 2, 4, 5]
+ bn = mcolors.BoundaryNorm(boundaries, ncolors)
+ assert_array_equal(bn(vals), expected)
+
+ # a scalar as input should not trigger an error and should return a scalar
+ boundaries = [0, 1, 2]
+ vals = [-1, 0.1, 1.1, 2.2]
+ bn = mcolors.BoundaryNorm(boundaries, 2)
+ expected = [-1, 0, 1, 2]
+ for v, ex in zip(vals, expected):
+ ret = bn(v)
+ assert isinstance(ret, int)
+ assert_array_equal(ret, ex)
+ assert_array_equal(bn([v]), ex)
+
+ # same with interp
+ bn = mcolors.BoundaryNorm(boundaries, 3)
+ expected = [-1, 0, 2, 3]
+ for v, ex in zip(vals, expected):
+ ret = bn(v)
+ assert isinstance(ret, int)
+ assert_array_equal(ret, ex)
+ assert_array_equal(bn([v]), ex)
+
+ # Clipping
+ bn = mcolors.BoundaryNorm(boundaries, 3, clip=True)
+ expected = [0, 0, 2, 2]
+ for v, ex in zip(vals, expected):
+ ret = bn(v)
+ assert isinstance(ret, int)
+ assert_array_equal(ret, ex)
+ assert_array_equal(bn([v]), ex)
+
+ # Masked arrays
+ boundaries = [0, 1.1, 2.2]
+ vals = np.ma.masked_invalid([-1., np.nan, 0, 1.4, 9])
+
+ # Without interpolation
+ ncolors = len(boundaries) - 1
+ bn = mcolors.BoundaryNorm(boundaries, ncolors)
+ expected = np.ma.masked_array([-1, -99, 0, 1, 2], mask=[0, 1, 0, 0, 0])
+ assert_array_equal(bn(vals), expected)
+
+ # With interpolation
+ bn = mcolors.BoundaryNorm(boundaries, len(boundaries))
+ expected = np.ma.masked_array([-1, -99, 0, 2, 3], mask=[0, 1, 0, 0, 0])
+ assert_array_equal(bn(vals), expected)
+
+ # Non-trivial masked arrays
+ vals = np.ma.masked_invalid([np.inf, np.nan])
+ assert np.all(bn(vals).mask)
+ vals = np.ma.masked_invalid([np.inf])
+ assert np.all(bn(vals).mask)
+
+ # Incompatible extend and clip
+ with pytest.raises(ValueError, match="not compatible"):
+ mcolors.BoundaryNorm(np.arange(4), 5, extend='both', clip=True)
+
+ # Too small ncolors argument
+ with pytest.raises(ValueError, match="ncolors must equal or exceed"):
+ mcolors.BoundaryNorm(np.arange(4), 2)
+
+ with pytest.raises(ValueError, match="ncolors must equal or exceed"):
+ mcolors.BoundaryNorm(np.arange(4), 3, extend='min')
+
+ with pytest.raises(ValueError, match="ncolors must equal or exceed"):
+ mcolors.BoundaryNorm(np.arange(4), 4, extend='both')
+
+ # Testing extend keyword, with interpolation (large cmap)
+ bounds = [1, 2, 3]
+ cmap = mpl.colormaps['viridis']
+ mynorm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both')
+ refnorm = mcolors.BoundaryNorm([0] + bounds + [4], cmap.N)
+ x = np.random.randn(100) * 10 + 2
+ ref = refnorm(x)
+ ref[ref == 0] = -1
+ ref[ref == cmap.N - 1] = cmap.N
+ assert_array_equal(mynorm(x), ref)
+
+ # Without interpolation
+ cmref = mcolors.ListedColormap(['blue', 'red'])
+ cmref.set_over('black')
+ cmref.set_under('white')
+ cmshould = mcolors.ListedColormap(['white', 'blue', 'red', 'black'])
+
+ assert mcolors.same_color(cmref.get_over(), 'black')
+ assert mcolors.same_color(cmref.get_under(), 'white')
+
+ refnorm = mcolors.BoundaryNorm(bounds, cmref.N)
+ mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='both')
+ assert mynorm.vmin == refnorm.vmin
+ assert mynorm.vmax == refnorm.vmax
+
+ assert mynorm(bounds[0] - 0.1) == -1 # under
+ assert mynorm(bounds[0] + 0.1) == 1 # first bin -> second color
+ assert mynorm(bounds[-1] - 0.1) == cmshould.N - 2 # next-to-last color
+ assert mynorm(bounds[-1] + 0.1) == cmshould.N # over
+
+ x = [-1, 1.2, 2.3, 9.6]
+ assert_array_equal(cmshould(mynorm(x)), cmshould([0, 1, 2, 3]))
+ x = np.random.randn(100) * 10 + 2
+ assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x)))
+
+ # Just min
+ cmref = mcolors.ListedColormap(['blue', 'red'])
+ cmref.set_under('white')
+ cmshould = mcolors.ListedColormap(['white', 'blue', 'red'])
+
+ assert mcolors.same_color(cmref.get_under(), 'white')
+
+ assert cmref.N == 2
+ assert cmshould.N == 3
+ refnorm = mcolors.BoundaryNorm(bounds, cmref.N)
+ mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='min')
+ assert mynorm.vmin == refnorm.vmin
+ assert mynorm.vmax == refnorm.vmax
+ x = [-1, 1.2, 2.3]
+ assert_array_equal(cmshould(mynorm(x)), cmshould([0, 1, 2]))
+ x = np.random.randn(100) * 10 + 2
+ assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x)))
+
+ # Just max
+ cmref = mcolors.ListedColormap(['blue', 'red'])
+ cmref.set_over('black')
+ cmshould = mcolors.ListedColormap(['blue', 'red', 'black'])
+
+ assert mcolors.same_color(cmref.get_over(), 'black')
+
+ assert cmref.N == 2
+ assert cmshould.N == 3
+ refnorm = mcolors.BoundaryNorm(bounds, cmref.N)
+ mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='max')
+ assert mynorm.vmin == refnorm.vmin
+ assert mynorm.vmax == refnorm.vmax
+ x = [1.2, 2.3, 4]
+ assert_array_equal(cmshould(mynorm(x)), cmshould([0, 1, 2]))
+ x = np.random.randn(100) * 10 + 2
+ assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x)))
+
+
+def test_CenteredNorm():
+ np.random.seed(0)
+
+ # Assert equivalence to symmetrical Normalize.
+ x = np.random.normal(size=100)
+ x_maxabs = np.max(np.abs(x))
+ norm_ref = mcolors.Normalize(vmin=-x_maxabs, vmax=x_maxabs)
+ norm = mcolors.CenteredNorm()
+ assert_array_almost_equal(norm_ref(x), norm(x))
+
+ # Check that vcenter is in the center of vmin and vmax
+ # when vcenter is set.
+ vcenter = int(np.random.normal(scale=50))
+ norm = mcolors.CenteredNorm(vcenter=vcenter)
+ norm.autoscale_None([1, 2])
+ assert norm.vmax + norm.vmin == 2 * vcenter
+
+ # Check that halfrange can be set without setting vcenter and that it is
+ # not reset through autoscale_None.
+ norm = mcolors.CenteredNorm(halfrange=1.0)
+ norm.autoscale_None([1, 3000])
+ assert norm.halfrange == 1.0
+
+ # Check that halfrange input works correctly.
+ x = np.random.normal(size=10)
+ norm = mcolors.CenteredNorm(vcenter=0.5, halfrange=0.5)
+ assert_array_almost_equal(x, norm(x))
+ norm = mcolors.CenteredNorm(vcenter=1, halfrange=1)
+ assert_array_almost_equal(x, 2 * norm(x))
+
+ # Check that halfrange input works correctly and use setters.
+ norm = mcolors.CenteredNorm()
+ norm.vcenter = 2
+ norm.halfrange = 2
+ assert_array_almost_equal(x, 4 * norm(x))
+
+ # Check that prior to adding data, setting halfrange first has same effect.
+ norm = mcolors.CenteredNorm()
+ norm.halfrange = 2
+ norm.vcenter = 2
+ assert_array_almost_equal(x, 4 * norm(x))
+
+ # Check that manual change of vcenter adjusts halfrange accordingly.
+ norm = mcolors.CenteredNorm()
+ assert norm.vcenter == 0
+ # add data
+ norm(np.linspace(-1.0, 0.0, 10))
+ assert norm.vmax == 1.0
+ assert norm.halfrange == 1.0
+ # set vcenter to 1, which should move the center but leave the
+ # halfrange unchanged
+ norm.vcenter = 1
+ assert norm.vmin == 0
+ assert norm.vmax == 2
+ assert norm.halfrange == 1
+
+ # Check setting vmin directly updates the halfrange and vmax, but
+ # leaves vcenter alone
+ norm.vmin = -1
+ assert norm.halfrange == 2
+ assert norm.vmax == 3
+ assert norm.vcenter == 1
+
+ # also check vmax updates
+ norm.vmax = 2
+ assert norm.halfrange == 1
+ assert norm.vmin == 0
+ assert norm.vcenter == 1
+
+
+@pytest.mark.parametrize("vmin,vmax", [[-1, 2], [3, 1]])
+def test_lognorm_invalid(vmin, vmax):
+ # Check that invalid limits in LogNorm error
+ norm = mcolors.LogNorm(vmin=vmin, vmax=vmax)
+ with pytest.raises(ValueError):
+ norm(1)
+ with pytest.raises(ValueError):
+ norm.inverse(1)
+
+
+def test_LogNorm():
+ """
+ LogNorm ignored clip, now it has the same
+ behavior as Normalize, e.g., values > vmax are bigger than 1
+ without clip, with clip they are 1.
+ """
+ ln = mcolors.LogNorm(clip=True, vmax=5)
+ assert_array_equal(ln([1, 6]), [0, 1.0])
+
+
+def test_LogNorm_inverse():
+ """
+ Test that lists work, and that the inverse works
+ """
+ norm = mcolors.LogNorm(vmin=0.1, vmax=10)
+ assert_array_almost_equal(norm([0.5, 0.4]), [0.349485, 0.30103])
+ assert_array_almost_equal([0.5, 0.4], norm.inverse([0.349485, 0.30103]))
+ assert_array_almost_equal(norm(0.4), [0.30103])
+ assert_array_almost_equal([0.4], norm.inverse([0.30103]))
+
+
+def test_PowerNorm():
+ # Check an exponent of 1 gives same results as a normal linear
+ # normalization. Also implicitly checks that vmin/vmax are
+ # automatically initialized from first array input.
+ a = np.array([0, 0.5, 1, 1.5], dtype=float)
+ pnorm = mcolors.PowerNorm(1)
+ norm = mcolors.Normalize()
+ assert_array_almost_equal(norm(a), pnorm(a))
+
+ a = np.array([-0.5, 0, 2, 4, 8], dtype=float)
+ expected = [-1/16, 0, 1/16, 1/4, 1]
+ pnorm = mcolors.PowerNorm(2, vmin=0, vmax=8)
+ assert_array_almost_equal(pnorm(a), expected)
+ assert pnorm(a[0]) == expected[0]
+ assert pnorm(a[2]) == expected[2]
+ # Check inverse
+ a_roundtrip = pnorm.inverse(pnorm(a))
+ assert_array_almost_equal(a, a_roundtrip)
+ # PowerNorm inverse adds a mask, so check that is correct too
+ assert_array_equal(a_roundtrip.mask, np.zeros(a.shape, dtype=bool))
+
+ # Clip = True
+ a = np.array([-0.5, 0, 1, 8, 16], dtype=float)
+ expected = [0, 0, 0, 1, 1]
+ # Clip = True when creating the norm
+ pnorm = mcolors.PowerNorm(2, vmin=2, vmax=8, clip=True)
+ assert_array_almost_equal(pnorm(a), expected)
+ assert pnorm(a[0]) == expected[0]
+ assert pnorm(a[-1]) == expected[-1]
+ # Clip = True at call time
+ pnorm = mcolors.PowerNorm(2, vmin=2, vmax=8, clip=False)
+ assert_array_almost_equal(pnorm(a, clip=True), expected)
+ assert pnorm(a[0], clip=True) == expected[0]
+ assert pnorm(a[-1], clip=True) == expected[-1]
+
+ # Check clip=True preserves masked values
+ a = np.ma.array([5, 2], mask=[True, False])
+ out = pnorm(a, clip=True)
+ assert_array_equal(out.mask, [True, False])
+
+
+def test_PowerNorm_translation_invariance():
+ a = np.array([0, 1/2, 1], dtype=float)
+ expected = [0, 1/8, 1]
+ pnorm = mcolors.PowerNorm(vmin=0, vmax=1, gamma=3)
+ assert_array_almost_equal(pnorm(a), expected)
+ pnorm = mcolors.PowerNorm(vmin=-2, vmax=-1, gamma=3)
+ assert_array_almost_equal(pnorm(a - 2), expected)
+
+
+def test_powernorm_cbar_limits():
+ fig, ax = plt.subplots()
+ vmin, vmax = 300, 1000
+ data = np.arange(10*10).reshape(10, 10) + vmin
+ im = ax.imshow(data, norm=mcolors.PowerNorm(gamma=0.2, vmin=vmin, vmax=vmax))
+ cbar = fig.colorbar(im)
+ assert cbar.ax.get_ylim() == (vmin, vmax)
+
+
+def test_Normalize():
+ norm = mcolors.Normalize()
+ vals = np.arange(-10, 10, 1, dtype=float)
+ _inverse_tester(norm, vals)
+ _scalar_tester(norm, vals)
+ _mask_tester(norm, vals)
+
+ # Handle integer input correctly (don't overflow when computing max-min,
+ # i.e. 127-(-128) here).
+ vals = np.array([-128, 127], dtype=np.int8)
+ norm = mcolors.Normalize(vals.min(), vals.max())
+ assert_array_equal(norm(vals), [0, 1])
+
+ # Don't lose precision on longdoubles (float128 on Linux):
+ # for array inputs...
+ vals = np.array([1.2345678901, 9.8765432109], dtype=np.longdouble)
+ norm = mcolors.Normalize(vals[0], vals[1])
+ assert norm(vals).dtype == np.longdouble
+ assert_array_equal(norm(vals), [0, 1])
+ # and for scalar ones.
+ eps = np.finfo(np.longdouble).resolution
+ norm = plt.Normalize(1, 1 + 100 * eps)
+ # This returns exactly 0.5 when longdouble is extended precision (80-bit),
+ # but only a value close to it when it is quadruple precision (128-bit).
+ assert_array_almost_equal(norm(1 + 50 * eps), 0.5, decimal=3)
+
+
+def test_FuncNorm():
+ def forward(x):
+ return (x**2)
+ def inverse(x):
+ return np.sqrt(x)
+
+ norm = mcolors.FuncNorm((forward, inverse), vmin=0, vmax=10)
+ expected = np.array([0, 0.25, 1])
+ input = np.array([0, 5, 10])
+ assert_array_almost_equal(norm(input), expected)
+ assert_array_almost_equal(norm.inverse(expected), input)
+
+ def forward(x):
+ return np.log10(x)
+ def inverse(x):
+ return 10**x
+ norm = mcolors.FuncNorm((forward, inverse), vmin=0.1, vmax=10)
+ lognorm = mcolors.LogNorm(vmin=0.1, vmax=10)
+ assert_array_almost_equal(norm([0.2, 5, 10]), lognorm([0.2, 5, 10]))
+ assert_array_almost_equal(norm.inverse([0.2, 5, 10]),
+ lognorm.inverse([0.2, 5, 10]))
+
+
+def test_TwoSlopeNorm_autoscale():
+ norm = mcolors.TwoSlopeNorm(vcenter=20)
+ norm.autoscale([10, 20, 30, 40])
+ assert norm.vmin == 10.
+ assert norm.vmax == 40.
+
+
+def test_TwoSlopeNorm_autoscale_None_vmin():
+ norm = mcolors.TwoSlopeNorm(2, vmin=0, vmax=None)
+ norm.autoscale_None([1, 2, 3, 4, 5])
+ assert norm(5) == 1
+ assert norm.vmax == 5
+
+
+def test_TwoSlopeNorm_autoscale_None_vmax():
+ norm = mcolors.TwoSlopeNorm(2, vmin=None, vmax=10)
+ norm.autoscale_None([1, 2, 3, 4, 5])
+ assert norm(1) == 0
+ assert norm.vmin == 1
+
+
+def test_TwoSlopeNorm_scale():
+ norm = mcolors.TwoSlopeNorm(2)
+ assert norm.scaled() is False
+ norm([1, 2, 3, 4])
+ assert norm.scaled() is True
+
+
+def test_TwoSlopeNorm_scaleout_center():
+ # test the vmin never goes above vcenter
+ norm = mcolors.TwoSlopeNorm(vcenter=0)
+ norm([0, 1, 2, 3, 5])
+ assert norm.vmin == -5
+ assert norm.vmax == 5
+
+
+def test_TwoSlopeNorm_scaleout_center_max():
+ # test the vmax never goes below vcenter
+ norm = mcolors.TwoSlopeNorm(vcenter=0)
+ norm([0, -1, -2, -3, -5])
+ assert norm.vmax == 5
+ assert norm.vmin == -5
+
+
+def test_TwoSlopeNorm_Even():
+ norm = mcolors.TwoSlopeNorm(vmin=-1, vcenter=0, vmax=4)
+ vals = np.array([-1.0, -0.5, 0.0, 1.0, 2.0, 3.0, 4.0])
+ expected = np.array([0.0, 0.25, 0.5, 0.625, 0.75, 0.875, 1.0])
+ assert_array_equal(norm(vals), expected)
+
+
+def test_TwoSlopeNorm_Odd():
+ norm = mcolors.TwoSlopeNorm(vmin=-2, vcenter=0, vmax=5)
+ vals = np.array([-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
+ expected = np.array([0.0, 0.25, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
+ assert_array_equal(norm(vals), expected)
+
+
+def test_TwoSlopeNorm_VminEqualsVcenter():
+ with pytest.raises(ValueError):
+ mcolors.TwoSlopeNorm(vmin=-2, vcenter=-2, vmax=2)
+
+
+def test_TwoSlopeNorm_VmaxEqualsVcenter():
+ with pytest.raises(ValueError):
+ mcolors.TwoSlopeNorm(vmin=-2, vcenter=2, vmax=2)
+
+
+def test_TwoSlopeNorm_VminGTVcenter():
+ with pytest.raises(ValueError):
+ mcolors.TwoSlopeNorm(vmin=10, vcenter=0, vmax=20)
+
+
+def test_TwoSlopeNorm_TwoSlopeNorm_VminGTVmax():
+ with pytest.raises(ValueError):
+ mcolors.TwoSlopeNorm(vmin=10, vcenter=0, vmax=5)
+
+
+def test_TwoSlopeNorm_VcenterGTVmax():
+ with pytest.raises(ValueError):
+ mcolors.TwoSlopeNorm(vmin=10, vcenter=25, vmax=20)
+
+
+def test_TwoSlopeNorm_premature_scaling():
+ norm = mcolors.TwoSlopeNorm(vcenter=2)
+ with pytest.raises(ValueError):
+ norm.inverse(np.array([0.1, 0.5, 0.9]))
+
+
+def test_SymLogNorm():
+ """
+ Test SymLogNorm behavior
+ """
+ norm = mcolors.SymLogNorm(3, vmax=5, linscale=1.2, base=np.e)
+ vals = np.array([-30, -1, 2, 6], dtype=float)
+ normed_vals = norm(vals)
+ expected = [0., 0.53980074, 0.826991, 1.02758204]
+ assert_array_almost_equal(normed_vals, expected)
+ _inverse_tester(norm, vals)
+ _scalar_tester(norm, vals)
+ _mask_tester(norm, vals)
+
+ # Ensure that specifying vmin returns the same result as above
+ norm = mcolors.SymLogNorm(3, vmin=-30, vmax=5, linscale=1.2, base=np.e)
+ normed_vals = norm(vals)
+ assert_array_almost_equal(normed_vals, expected)
+
+ # test something more easily checked.
+ norm = mcolors.SymLogNorm(1, vmin=-np.e**3, vmax=np.e**3, base=np.e)
+ nn = norm([-np.e**3, -np.e**2, -np.e**1, -1,
+ 0, 1, np.e**1, np.e**2, np.e**3])
+ xx = np.array([0., 0.109123, 0.218246, 0.32737, 0.5, 0.67263,
+ 0.781754, 0.890877, 1.])
+ assert_array_almost_equal(nn, xx)
+ norm = mcolors.SymLogNorm(1, vmin=-10**3, vmax=10**3, base=10)
+ nn = norm([-10**3, -10**2, -10**1, -1,
+ 0, 1, 10**1, 10**2, 10**3])
+ xx = np.array([0., 0.121622, 0.243243, 0.364865, 0.5, 0.635135,
+ 0.756757, 0.878378, 1.])
+ assert_array_almost_equal(nn, xx)
+
+
+def test_SymLogNorm_colorbar():
+ """
+ Test un-called SymLogNorm in a colorbar.
+ """
+ norm = mcolors.SymLogNorm(0.1, vmin=-1, vmax=1, linscale=1, base=np.e)
+ fig = plt.figure()
+ mcolorbar.ColorbarBase(fig.add_subplot(), norm=norm)
+ plt.close(fig)
+
+
+def test_SymLogNorm_single_zero():
+ """
+ Test SymLogNorm to ensure it is not adding sub-ticks to zero label
+ """
+ fig = plt.figure()
+ norm = mcolors.SymLogNorm(1e-5, vmin=-1, vmax=1, base=np.e)
+ cbar = mcolorbar.ColorbarBase(fig.add_subplot(), norm=norm)
+ ticks = cbar.get_ticks()
+ assert np.count_nonzero(ticks == 0) <= 1
+ plt.close(fig)
+
+
+class TestAsinhNorm:
+ """
+ Tests for `~.colors.AsinhNorm`
+ """
+
+ def test_init(self):
+ norm0 = mcolors.AsinhNorm()
+ assert norm0.linear_width == 1
+
+ norm5 = mcolors.AsinhNorm(linear_width=5)
+ assert norm5.linear_width == 5
+
+ def test_norm(self):
+ norm = mcolors.AsinhNorm(2, vmin=-4, vmax=4)
+ vals = np.arange(-3.5, 3.5, 10)
+ normed_vals = norm(vals)
+ asinh2 = np.arcsinh(2)
+
+ expected = (2 * np.arcsinh(vals / 2) + 2 * asinh2) / (4 * asinh2)
+ assert_array_almost_equal(normed_vals, expected)
+
+
+def _inverse_tester(norm_instance, vals):
+ """
+ Checks if the inverse of the given normalization is working.
+ """
+ assert_array_almost_equal(norm_instance.inverse(norm_instance(vals)), vals)
+
+
+def _scalar_tester(norm_instance, vals):
+ """
+ Checks if scalars and arrays are handled the same way.
+ Tests only for float.
+ """
+ scalar_result = [norm_instance(float(v)) for v in vals]
+ assert_array_almost_equal(scalar_result, norm_instance(vals))
+
+
+def _mask_tester(norm_instance, vals):
+ """
+ Checks mask handling
+ """
+ masked_array = np.ma.array(vals)
+ masked_array[0] = np.ma.masked
+ assert_array_equal(masked_array.mask, norm_instance(masked_array).mask)
+
+
+@image_comparison(['levels_and_colors.png'])
+def test_cmap_and_norm_from_levels_and_colors():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ data = np.linspace(-2, 4, 49).reshape(7, 7)
+ levels = [-1, 2, 2.5, 3]
+ colors = ['red', 'green', 'blue', 'yellow', 'black']
+ extend = 'both'
+ cmap, norm = mcolors.from_levels_and_colors(levels, colors, extend=extend)
+
+ ax = plt.axes()
+ m = plt.pcolormesh(data, cmap=cmap, norm=norm)
+ plt.colorbar(m)
+
+ # Hide the axes labels (but not the colorbar ones, as they are useful)
+ ax.tick_params(labelleft=False, labelbottom=False)
+
+
+@image_comparison(baseline_images=['boundarynorm_and_colorbar'],
+ extensions=['png'], tol=1.0)
+def test_boundarynorm_and_colorbarbase():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ # Make a figure and axes with dimensions as desired.
+ fig = plt.figure()
+ ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
+ ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
+ ax3 = fig.add_axes([0.05, 0.15, 0.9, 0.15])
+
+ # Set the colormap and bounds
+ bounds = [-1, 2, 5, 7, 12, 15]
+ cmap = mpl.colormaps['viridis']
+
+ # Default behavior
+ norm = mcolors.BoundaryNorm(bounds, cmap.N)
+ cb1 = mcolorbar.ColorbarBase(ax1, cmap=cmap, norm=norm, extend='both',
+ orientation='horizontal', spacing='uniform')
+ # New behavior
+ norm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both')
+ cb2 = mcolorbar.ColorbarBase(ax2, cmap=cmap, norm=norm,
+ orientation='horizontal')
+
+ # User can still force to any extend='' if really needed
+ norm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both')
+ cb3 = mcolorbar.ColorbarBase(ax3, cmap=cmap, norm=norm,
+ extend='neither', orientation='horizontal')
+
+
+def test_cmap_and_norm_from_levels_and_colors2():
+ levels = [-1, 2, 2.5, 3]
+ colors = ['red', (0, 1, 0), 'blue', (0.5, 0.5, 0.5), (0.0, 0.0, 0.0, 1.0)]
+ clr = mcolors.to_rgba_array(colors)
+ bad = (0.1, 0.1, 0.1, 0.1)
+ no_color = (0.0, 0.0, 0.0, 0.0)
+ masked_value = 'masked_value'
+
+ # Define the test values which are of interest.
+ # Note: levels are lev[i] <= v < lev[i+1]
+ tests = [('both', None, {-2: clr[0],
+ -1: clr[1],
+ 2: clr[2],
+ 2.25: clr[2],
+ 3: clr[4],
+ 3.5: clr[4],
+ masked_value: bad}),
+
+ ('min', -1, {-2: clr[0],
+ -1: clr[1],
+ 2: clr[2],
+ 2.25: clr[2],
+ 3: no_color,
+ 3.5: no_color,
+ masked_value: bad}),
+
+ ('max', -1, {-2: no_color,
+ -1: clr[0],
+ 2: clr[1],
+ 2.25: clr[1],
+ 3: clr[3],
+ 3.5: clr[3],
+ masked_value: bad}),
+
+ ('neither', -2, {-2: no_color,
+ -1: clr[0],
+ 2: clr[1],
+ 2.25: clr[1],
+ 3: no_color,
+ 3.5: no_color,
+ masked_value: bad}),
+ ]
+
+ for extend, i1, cases in tests:
+ cmap, norm = mcolors.from_levels_and_colors(levels, colors[0:i1],
+ extend=extend)
+ cmap.set_bad(bad)
+ for d_val, expected_color in cases.items():
+ if d_val == masked_value:
+ d_val = np.ma.array([1], mask=True)
+ else:
+ d_val = [d_val]
+ assert_array_equal(expected_color, cmap(norm(d_val))[0],
+ f'With extend={extend!r} and data '
+ f'value={d_val!r}')
+
+ with pytest.raises(ValueError):
+ mcolors.from_levels_and_colors(levels, colors)
+
+
+def test_rgb_hsv_round_trip():
+ for a_shape in [(500, 500, 3), (500, 3), (1, 3), (3,)]:
+ np.random.seed(0)
+ tt = np.random.random(a_shape)
+ assert_array_almost_equal(
+ tt, mcolors.hsv_to_rgb(mcolors.rgb_to_hsv(tt)))
+ assert_array_almost_equal(
+ tt, mcolors.rgb_to_hsv(mcolors.hsv_to_rgb(tt)))
+
+
+def test_autoscale_masked():
+ # Test for #2336. Previously fully masked data would trigger a ValueError.
+ data = np.ma.masked_all((12, 20))
+ plt.pcolor(data)
+ plt.draw()
+
+
+@image_comparison(['light_source_shading_topo.png'])
+def test_light_source_topo_surface():
+ """Shades a DEM using different v.e.'s and blend modes."""
+ dem = cbook.get_sample_data('jacksboro_fault_dem.npz')
+ elev = dem['elevation']
+ dx, dy = dem['dx'], dem['dy']
+ # Get the true cellsize in meters for accurate vertical exaggeration
+ # Convert from decimal degrees to meters
+ dx = 111320.0 * dx * np.cos(dem['ymin'])
+ dy = 111320.0 * dy
+
+ ls = mcolors.LightSource(315, 45)
+ cmap = cm.gist_earth
+
+ fig, axs = plt.subplots(nrows=3, ncols=3)
+ for row, mode in zip(axs, ['hsv', 'overlay', 'soft']):
+ for ax, ve in zip(row, [0.1, 1, 10]):
+ rgb = ls.shade(elev, cmap, vert_exag=ve, dx=dx, dy=dy,
+ blend_mode=mode)
+ ax.imshow(rgb)
+ ax.set(xticks=[], yticks=[])
+
+
+def test_light_source_shading_default():
+ """
+ Array comparison test for the default "hsv" blend mode. Ensure the
+ default result doesn't change without warning.
+ """
+ y, x = np.mgrid[-1.2:1.2:8j, -1.2:1.2:8j]
+ z = 10 * np.cos(x**2 + y**2)
+
+ cmap = plt.cm.copper
+ ls = mcolors.LightSource(315, 45)
+ rgb = ls.shade(z, cmap)
+
+ # Result stored transposed and rounded for more compact display...
+ expect = np.array(
+ [[[0.00, 0.45, 0.90, 0.90, 0.82, 0.62, 0.28, 0.00],
+ [0.45, 0.94, 0.99, 1.00, 1.00, 0.96, 0.65, 0.17],
+ [0.90, 0.99, 1.00, 1.00, 1.00, 1.00, 0.94, 0.35],
+ [0.90, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.49],
+ [0.82, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.41],
+ [0.62, 0.96, 1.00, 1.00, 1.00, 1.00, 0.90, 0.07],
+ [0.28, 0.65, 0.94, 1.00, 1.00, 0.90, 0.35, 0.01],
+ [0.00, 0.17, 0.35, 0.49, 0.41, 0.07, 0.01, 0.00]],
+
+ [[0.00, 0.28, 0.59, 0.72, 0.62, 0.40, 0.18, 0.00],
+ [0.28, 0.78, 0.93, 0.92, 0.83, 0.66, 0.39, 0.11],
+ [0.59, 0.93, 0.99, 1.00, 0.92, 0.75, 0.50, 0.21],
+ [0.72, 0.92, 1.00, 0.99, 0.93, 0.76, 0.51, 0.18],
+ [0.62, 0.83, 0.92, 0.93, 0.87, 0.68, 0.42, 0.08],
+ [0.40, 0.66, 0.75, 0.76, 0.68, 0.52, 0.23, 0.02],
+ [0.18, 0.39, 0.50, 0.51, 0.42, 0.23, 0.00, 0.00],
+ [0.00, 0.11, 0.21, 0.18, 0.08, 0.02, 0.00, 0.00]],
+
+ [[0.00, 0.18, 0.38, 0.46, 0.39, 0.26, 0.11, 0.00],
+ [0.18, 0.50, 0.70, 0.75, 0.64, 0.44, 0.25, 0.07],
+ [0.38, 0.70, 0.91, 0.98, 0.81, 0.51, 0.29, 0.13],
+ [0.46, 0.75, 0.98, 0.96, 0.84, 0.48, 0.22, 0.12],
+ [0.39, 0.64, 0.81, 0.84, 0.71, 0.31, 0.11, 0.05],
+ [0.26, 0.44, 0.51, 0.48, 0.31, 0.10, 0.03, 0.01],
+ [0.11, 0.25, 0.29, 0.22, 0.11, 0.03, 0.00, 0.00],
+ [0.00, 0.07, 0.13, 0.12, 0.05, 0.01, 0.00, 0.00]],
+
+ [[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00]]
+ ]).T
+
+ assert_array_almost_equal(rgb, expect, decimal=2)
+
+
+def test_light_source_shading_empty_mask():
+ y, x = np.mgrid[-1.2:1.2:8j, -1.2:1.2:8j]
+ z0 = 10 * np.cos(x**2 + y**2)
+ z1 = np.ma.array(z0)
+
+ cmap = plt.cm.copper
+ ls = mcolors.LightSource(315, 45)
+ rgb0 = ls.shade(z0, cmap)
+ rgb1 = ls.shade(z1, cmap)
+
+ assert_array_almost_equal(rgb0, rgb1)
+
+
+# Numpy 1.9.1 fixed a bug in masked arrays which resulted in
+# additional elements being masked when calculating the gradient thus
+# the output is different with earlier numpy versions.
+def test_light_source_masked_shading():
+ """
+ Array comparison test for a surface with a masked portion. Ensures that
+ we don't wind up with "fringes" of odd colors around masked regions.
+ """
+ y, x = np.mgrid[-1.2:1.2:8j, -1.2:1.2:8j]
+ z = 10 * np.cos(x**2 + y**2)
+
+ z = np.ma.masked_greater(z, 9.9)
+
+ cmap = plt.cm.copper
+ ls = mcolors.LightSource(315, 45)
+ rgb = ls.shade(z, cmap)
+
+ # Result stored transposed and rounded for more compact display...
+ expect = np.array(
+ [[[0.00, 0.46, 0.91, 0.91, 0.84, 0.64, 0.29, 0.00],
+ [0.46, 0.96, 1.00, 1.00, 1.00, 0.97, 0.67, 0.18],
+ [0.91, 1.00, 1.00, 1.00, 1.00, 1.00, 0.96, 0.36],
+ [0.91, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 0.51],
+ [0.84, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 0.44],
+ [0.64, 0.97, 1.00, 1.00, 1.00, 1.00, 0.94, 0.09],
+ [0.29, 0.67, 0.96, 1.00, 1.00, 0.94, 0.38, 0.01],
+ [0.00, 0.18, 0.36, 0.51, 0.44, 0.09, 0.01, 0.00]],
+
+ [[0.00, 0.29, 0.61, 0.75, 0.64, 0.41, 0.18, 0.00],
+ [0.29, 0.81, 0.95, 0.93, 0.85, 0.68, 0.40, 0.11],
+ [0.61, 0.95, 1.00, 0.78, 0.78, 0.77, 0.52, 0.22],
+ [0.75, 0.93, 0.78, 0.00, 0.00, 0.78, 0.54, 0.19],
+ [0.64, 0.85, 0.78, 0.00, 0.00, 0.78, 0.45, 0.08],
+ [0.41, 0.68, 0.77, 0.78, 0.78, 0.55, 0.25, 0.02],
+ [0.18, 0.40, 0.52, 0.54, 0.45, 0.25, 0.00, 0.00],
+ [0.00, 0.11, 0.22, 0.19, 0.08, 0.02, 0.00, 0.00]],
+
+ [[0.00, 0.19, 0.39, 0.48, 0.41, 0.26, 0.12, 0.00],
+ [0.19, 0.52, 0.73, 0.78, 0.66, 0.46, 0.26, 0.07],
+ [0.39, 0.73, 0.95, 0.50, 0.50, 0.53, 0.30, 0.14],
+ [0.48, 0.78, 0.50, 0.00, 0.00, 0.50, 0.23, 0.12],
+ [0.41, 0.66, 0.50, 0.00, 0.00, 0.50, 0.11, 0.05],
+ [0.26, 0.46, 0.53, 0.50, 0.50, 0.11, 0.03, 0.01],
+ [0.12, 0.26, 0.30, 0.23, 0.11, 0.03, 0.00, 0.00],
+ [0.00, 0.07, 0.14, 0.12, 0.05, 0.01, 0.00, 0.00]],
+
+ [[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
+ [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00]],
+ ]).T
+
+ assert_array_almost_equal(rgb, expect, decimal=2)
+
+
+def test_light_source_hillshading():
+ """
+ Compare the current hillshading method against one that should be
+ mathematically equivalent. Illuminates a cone from a range of angles.
+ """
+
+ def alternative_hillshade(azimuth, elev, z):
+ illum = _sph2cart(*_azimuth2math(azimuth, elev))
+ illum = np.array(illum)
+
+ dy, dx = np.gradient(-z)
+ dy = -dy
+ dz = np.ones_like(dy)
+ normals = np.dstack([dx, dy, dz])
+ normals /= np.linalg.norm(normals, axis=2)[..., None]
+
+ intensity = np.tensordot(normals, illum, axes=(2, 0))
+ intensity -= intensity.min()
+ intensity /= np.ptp(intensity)
+ return intensity
+
+ y, x = np.mgrid[5:0:-1, :5]
+ z = -np.hypot(x - x.mean(), y - y.mean())
+
+ for az, elev in itertools.product(range(0, 390, 30), range(0, 105, 15)):
+ ls = mcolors.LightSource(az, elev)
+ h1 = ls.hillshade(z)
+ h2 = alternative_hillshade(az, elev, z)
+ assert_array_almost_equal(h1, h2)
+
+
+def test_light_source_planar_hillshading():
+ """
+ Ensure that the illumination intensity is correct for planar surfaces.
+ """
+
+ def plane(azimuth, elevation, x, y):
+ """
+ Create a plane whose normal vector is at the given azimuth and
+ elevation.
+ """
+ theta, phi = _azimuth2math(azimuth, elevation)
+ a, b, c = _sph2cart(theta, phi)
+ z = -(a*x + b*y) / c
+ return z
+
+ def angled_plane(azimuth, elevation, angle, x, y):
+ """
+ Create a plane whose normal vector is at an angle from the given
+ azimuth and elevation.
+ """
+ elevation = elevation + angle
+ if elevation > 90:
+ azimuth = (azimuth + 180) % 360
+ elevation = (90 - elevation) % 90
+ return plane(azimuth, elevation, x, y)
+
+ y, x = np.mgrid[5:0:-1, :5]
+ for az, elev in itertools.product(range(0, 390, 30), range(0, 105, 15)):
+ ls = mcolors.LightSource(az, elev)
+
+ # Make a plane at a range of angles to the illumination
+ for angle in range(0, 105, 15):
+ z = angled_plane(az, elev, angle, x, y)
+ h = ls.hillshade(z)
+ assert_array_almost_equal(h, np.cos(np.radians(angle)))
+
+
+def test_color_names():
+ assert mcolors.to_hex("blue") == "#0000ff"
+ assert mcolors.to_hex("xkcd:blue") == "#0343df"
+ assert mcolors.to_hex("tab:blue") == "#1f77b4"
+
+
+def _sph2cart(theta, phi):
+ x = np.cos(theta) * np.sin(phi)
+ y = np.sin(theta) * np.sin(phi)
+ z = np.cos(phi)
+ return x, y, z
+
+
+def _azimuth2math(azimuth, elevation):
+ """
+ Convert from clockwise-from-north and up-from-horizontal to mathematical
+ conventions.
+ """
+ theta = np.radians((90 - azimuth) % 360)
+ phi = np.radians(90 - elevation)
+ return theta, phi
+
+
+def test_pandas_iterable(pd):
+ # Using a list or series yields equivalent
+ # colormaps, i.e the series isn't seen as
+ # a single color
+ lst = ['red', 'blue', 'green']
+ s = pd.Series(lst)
+ cm1 = mcolors.ListedColormap(lst, N=5)
+ cm2 = mcolors.ListedColormap(s, N=5)
+ assert_array_equal(cm1.colors, cm2.colors)
+
+
+@pytest.mark.parametrize('name', sorted(mpl.colormaps()))
+def test_colormap_reversing(name):
+ """
+ Check the generated _lut data of a colormap and corresponding reversed
+ colormap if they are almost the same.
+ """
+ cmap = mpl.colormaps[name]
+ cmap_r = cmap.reversed()
+ if not cmap_r._isinit:
+ cmap._init()
+ cmap_r._init()
+ assert_array_almost_equal(cmap._lut[:-3], cmap_r._lut[-4::-1])
+ # Test the bad, over, under values too
+ assert_array_almost_equal(cmap(-np.inf), cmap_r(np.inf))
+ assert_array_almost_equal(cmap(np.inf), cmap_r(-np.inf))
+ assert_array_almost_equal(cmap(np.nan), cmap_r(np.nan))
+
+
+def test_has_alpha_channel():
+ assert mcolors._has_alpha_channel((0, 0, 0, 0))
+ assert mcolors._has_alpha_channel([1, 1, 1, 1])
+ assert not mcolors._has_alpha_channel('blue') # 4-char string!
+ assert not mcolors._has_alpha_channel('0.25')
+ assert not mcolors._has_alpha_channel('r')
+ assert not mcolors._has_alpha_channel((1, 0, 0))
+
+
+def test_cn():
+ matplotlib.rcParams['axes.prop_cycle'] = cycler('color',
+ ['blue', 'r'])
+ assert mcolors.to_hex("C0") == '#0000ff'
+ assert mcolors.to_hex("C1") == '#ff0000'
+
+ matplotlib.rcParams['axes.prop_cycle'] = cycler('color',
+ ['xkcd:blue', 'r'])
+ assert mcolors.to_hex("C0") == '#0343df'
+ assert mcolors.to_hex("C1") == '#ff0000'
+ assert mcolors.to_hex("C10") == '#0343df'
+ assert mcolors.to_hex("C11") == '#ff0000'
+
+ matplotlib.rcParams['axes.prop_cycle'] = cycler('color', ['8e4585', 'r'])
+
+ assert mcolors.to_hex("C0") == '#8e4585'
+ # if '8e4585' gets parsed as a float before it gets detected as a hex
+ # colour it will be interpreted as a very large number.
+ # this mustn't happen.
+ assert mcolors.to_rgb("C0")[0] != np.inf
+
+
+def test_conversions():
+ # to_rgba_array("none") returns a (0, 4) array.
+ assert_array_equal(mcolors.to_rgba_array("none"), np.zeros((0, 4)))
+ assert_array_equal(mcolors.to_rgba_array([]), np.zeros((0, 4)))
+ # a list of grayscale levels, not a single color.
+ assert_array_equal(
+ mcolors.to_rgba_array([".2", ".5", ".8"]),
+ np.vstack([mcolors.to_rgba(c) for c in [".2", ".5", ".8"]]))
+ # alpha is properly set.
+ assert mcolors.to_rgba((1, 1, 1), .5) == (1, 1, 1, .5)
+ assert mcolors.to_rgba(".1", .5) == (.1, .1, .1, .5)
+ # builtin round differs between py2 and py3.
+ assert mcolors.to_hex((.7, .7, .7)) == "#b2b2b2"
+ # hex roundtrip.
+ hex_color = "#1234abcd"
+ assert mcolors.to_hex(mcolors.to_rgba(hex_color), keep_alpha=True) == \
+ hex_color
+
+
+def test_conversions_masked():
+ x1 = np.ma.array(['k', 'b'], mask=[True, False])
+ x2 = np.ma.array([[0, 0, 0, 1], [0, 0, 1, 1]])
+ x2[0] = np.ma.masked
+ assert mcolors.to_rgba(x1[0]) == (0, 0, 0, 0)
+ assert_array_equal(mcolors.to_rgba_array(x1),
+ [[0, 0, 0, 0], [0, 0, 1, 1]])
+ assert_array_equal(mcolors.to_rgba_array(x2), mcolors.to_rgba_array(x1))
+
+
+def test_to_rgba_array_single_str():
+ # single color name is valid
+ assert_array_equal(mcolors.to_rgba_array("red"), [(1, 0, 0, 1)])
+
+ # single char color sequence is invalid
+ with pytest.raises(ValueError,
+ match="'rgb' is not a valid color value."):
+ array = mcolors.to_rgba_array("rgb")
+
+
+def test_to_rgba_array_2tuple_str():
+ expected = np.array([[0, 0, 0, 1], [1, 1, 1, 1]])
+ assert_array_equal(mcolors.to_rgba_array(("k", "w")), expected)
+
+
+def test_to_rgba_array_alpha_array():
+ with pytest.raises(ValueError, match="The number of colors must match"):
+ mcolors.to_rgba_array(np.ones((5, 3), float), alpha=np.ones((2,)))
+ alpha = [0.5, 0.6]
+ c = mcolors.to_rgba_array(np.ones((2, 3), float), alpha=alpha)
+ assert_array_equal(c[:, 3], alpha)
+ c = mcolors.to_rgba_array(['r', 'g'], alpha=alpha)
+ assert_array_equal(c[:, 3], alpha)
+
+
+def test_to_rgba_array_accepts_color_alpha_tuple():
+ assert_array_equal(
+ mcolors.to_rgba_array(('black', 0.9)),
+ [[0, 0, 0, 0.9]])
+
+
+def test_to_rgba_array_explicit_alpha_overrides_tuple_alpha():
+ assert_array_equal(
+ mcolors.to_rgba_array(('black', 0.9), alpha=0.5),
+ [[0, 0, 0, 0.5]])
+
+
+def test_to_rgba_array_accepts_color_alpha_tuple_with_multiple_colors():
+ color_array = np.array([[1., 1., 1., 1.], [0., 0., 1., 0.]])
+ assert_array_equal(
+ mcolors.to_rgba_array((color_array, 0.2)),
+ [[1., 1., 1., 0.2], [0., 0., 1., 0.2]])
+
+ color_sequence = [[1., 1., 1., 1.], [0., 0., 1., 0.]]
+ assert_array_equal(
+ mcolors.to_rgba_array((color_sequence, 0.4)),
+ [[1., 1., 1., 0.4], [0., 0., 1., 0.4]])
+
+
+def test_to_rgba_array_error_with_color_invalid_alpha_tuple():
+ with pytest.raises(ValueError, match="'alpha' must be between 0 and 1,"):
+ mcolors.to_rgba_array(('black', 2.0))
+
+
+@pytest.mark.parametrize('rgba_alpha',
+ [('white', 0.5), ('#ffffff', 0.5), ('#ffffff00', 0.5),
+ ((1.0, 1.0, 1.0, 1.0), 0.5)])
+def test_to_rgba_accepts_color_alpha_tuple(rgba_alpha):
+ assert mcolors.to_rgba(rgba_alpha) == (1, 1, 1, 0.5)
+
+
+def test_to_rgba_explicit_alpha_overrides_tuple_alpha():
+ assert mcolors.to_rgba(('red', 0.1), alpha=0.9) == (1, 0, 0, 0.9)
+
+
+def test_to_rgba_error_with_color_invalid_alpha_tuple():
+ with pytest.raises(ValueError, match="'alpha' must be between 0 and 1"):
+ mcolors.to_rgba(('blue', 2.0))
+
+
+@pytest.mark.parametrize("bytes", (True, False))
+def test_scalarmappable_to_rgba(bytes):
+ sm = cm.ScalarMappable()
+ alpha_1 = 255 if bytes else 1
+
+ # uint8 RGBA
+ x = np.ones((2, 3, 4), dtype=np.uint8)
+ expected = x.copy() if bytes else x.astype(np.float32)/255
+ np.testing.assert_almost_equal(sm.to_rgba(x, bytes=bytes), expected)
+ # uint8 RGB
+ expected[..., 3] = alpha_1
+ np.testing.assert_almost_equal(sm.to_rgba(x[..., :3], bytes=bytes), expected)
+ # uint8 masked RGBA
+ xm = np.ma.masked_array(x, mask=np.zeros_like(x))
+ xm.mask[0, 0, 0] = True
+ expected = x.copy() if bytes else x.astype(np.float32)/255
+ expected[0, 0, 3] = 0
+ np.testing.assert_almost_equal(sm.to_rgba(xm, bytes=bytes), expected)
+ # uint8 masked RGB
+ expected[..., 3] = alpha_1
+ expected[0, 0, 3] = 0
+ np.testing.assert_almost_equal(sm.to_rgba(xm[..., :3], bytes=bytes), expected)
+
+ # float RGBA
+ x = np.ones((2, 3, 4), dtype=float) * 0.5
+ expected = (x * 255).astype(np.uint8) if bytes else x.copy()
+ np.testing.assert_almost_equal(sm.to_rgba(x, bytes=bytes), expected)
+ # float RGB
+ expected[..., 3] = alpha_1
+ np.testing.assert_almost_equal(sm.to_rgba(x[..., :3], bytes=bytes), expected)
+ # float masked RGBA
+ xm = np.ma.masked_array(x, mask=np.zeros_like(x))
+ xm.mask[0, 0, 0] = True
+ expected = (x * 255).astype(np.uint8) if bytes else x.copy()
+ expected[0, 0, 3] = 0
+ np.testing.assert_almost_equal(sm.to_rgba(xm, bytes=bytes), expected)
+ # float masked RGB
+ expected[..., 3] = alpha_1
+ expected[0, 0, 3] = 0
+ np.testing.assert_almost_equal(sm.to_rgba(xm[..., :3], bytes=bytes), expected)
+
+
+@pytest.mark.parametrize("bytes", (True, False))
+def test_scalarmappable_nan_to_rgba(bytes):
+ sm = cm.ScalarMappable()
+
+ # RGBA
+ x = np.ones((2, 3, 4), dtype=float) * 0.5
+ x[0, 0, 0] = np.nan
+ expected = x.copy()
+ expected[0, 0, :] = 0
+ if bytes:
+ expected = (expected * 255).astype(np.uint8)
+ np.testing.assert_almost_equal(sm.to_rgba(x, bytes=bytes), expected)
+ assert np.any(np.isnan(x)) # Input array should not be changed
+
+ # RGB
+ expected[..., 3] = 255 if bytes else 1
+ expected[0, 0, 3] = 0
+ np.testing.assert_almost_equal(sm.to_rgba(x[..., :3], bytes=bytes), expected)
+ assert np.any(np.isnan(x)) # Input array should not be changed
+
+ # Out-of-range fail
+ x[1, 0, 0] = 42
+ with pytest.raises(ValueError, match='0..1 range'):
+ sm.to_rgba(x[..., :3], bytes=bytes)
+
+
+def test_failed_conversions():
+ with pytest.raises(ValueError):
+ mcolors.to_rgba('5')
+ with pytest.raises(ValueError):
+ mcolors.to_rgba('-1')
+ with pytest.raises(ValueError):
+ mcolors.to_rgba('nan')
+ with pytest.raises(ValueError):
+ mcolors.to_rgba('unknown_color')
+ with pytest.raises(ValueError):
+ # Gray must be a string to distinguish 3-4 grays from RGB or RGBA.
+ mcolors.to_rgba(0.4)
+
+
+def test_grey_gray():
+ color_mapping = mcolors._colors_full_map
+ for k in color_mapping.keys():
+ if 'grey' in k:
+ assert color_mapping[k] == color_mapping[k.replace('grey', 'gray')]
+ if 'gray' in k:
+ assert color_mapping[k] == color_mapping[k.replace('gray', 'grey')]
+
+
+def test_tableau_order():
+ dflt_cycle = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728',
+ '#9467bd', '#8c564b', '#e377c2', '#7f7f7f',
+ '#bcbd22', '#17becf']
+
+ assert list(mcolors.TABLEAU_COLORS.values()) == dflt_cycle
+
+
+def test_ndarray_subclass_norm():
+ # Emulate an ndarray subclass that handles units
+ # which objects when adding or subtracting with other
+ # arrays. See #6622 and #8696
+ class MyArray(np.ndarray):
+ def __isub__(self, other): # type: ignore[misc]
+ raise RuntimeError
+
+ def __add__(self, other):
+ raise RuntimeError
+
+ data = np.arange(-10, 10, 1, dtype=float).reshape((10, 2))
+ mydata = data.view(MyArray)
+
+ for norm in [mcolors.Normalize(), mcolors.LogNorm(),
+ mcolors.SymLogNorm(3, vmax=5, linscale=1, base=np.e),
+ mcolors.Normalize(vmin=mydata.min(), vmax=mydata.max()),
+ mcolors.SymLogNorm(3, vmin=mydata.min(), vmax=mydata.max(),
+ base=np.e),
+ mcolors.PowerNorm(1)]:
+ assert_array_equal(norm(mydata), norm(data))
+ fig, ax = plt.subplots()
+ ax.imshow(mydata, norm=norm)
+ fig.canvas.draw() # Check that no warning is emitted.
+
+
+def test_same_color():
+ assert mcolors.same_color('k', (0, 0, 0))
+ assert not mcolors.same_color('w', (1, 1, 0))
+ assert mcolors.same_color(['red', 'blue'], ['r', 'b'])
+ assert mcolors.same_color('none', 'none')
+ assert not mcolors.same_color('none', 'red')
+ with pytest.raises(ValueError):
+ mcolors.same_color(['r', 'g', 'b'], ['r'])
+ with pytest.raises(ValueError):
+ mcolors.same_color(['red', 'green'], 'none')
+
+
+def test_hex_shorthand_notation():
+ assert mcolors.same_color("#123", "#112233")
+ assert mcolors.same_color("#123a", "#112233aa")
+
+
+def test_repr_png():
+ cmap = mpl.colormaps['viridis']
+ png = cmap._repr_png_()
+ assert len(png) > 0
+ img = Image.open(BytesIO(png))
+ assert img.width > 0
+ assert img.height > 0
+ assert 'Title' in img.text
+ assert 'Description' in img.text
+ assert 'Author' in img.text
+ assert 'Software' in img.text
+
+
+def test_repr_html():
+ cmap = mpl.colormaps['viridis']
+ html = cmap._repr_html_()
+ assert len(html) > 0
+ png = cmap._repr_png_()
+ assert base64.b64encode(png).decode('ascii') in html
+ assert cmap.name in html
+ assert html.startswith('')
+
+
+def test_get_under_over_bad():
+ cmap = mpl.colormaps['viridis']
+ assert_array_equal(cmap.get_under(), cmap(-np.inf))
+ assert_array_equal(cmap.get_over(), cmap(np.inf))
+ assert_array_equal(cmap.get_bad(), cmap(np.nan))
+
+
+@pytest.mark.parametrize('kind', ('over', 'under', 'bad'))
+def test_non_mutable_get_values(kind):
+ cmap = copy.copy(mpl.colormaps['viridis'])
+ init_value = getattr(cmap, f'get_{kind}')()
+ getattr(cmap, f'set_{kind}')('k')
+ black_value = getattr(cmap, f'get_{kind}')()
+ assert np.all(black_value == [0, 0, 0, 1])
+ assert not np.all(init_value == black_value)
+
+
+def test_colormap_alpha_array():
+ cmap = mpl.colormaps['viridis']
+ vals = [-1, 0.5, 2] # under, valid, over
+ with pytest.raises(ValueError, match="alpha is array-like but"):
+ cmap(vals, alpha=[1, 1, 1, 1])
+ alpha = np.array([0.1, 0.2, 0.3])
+ c = cmap(vals, alpha=alpha)
+ assert_array_equal(c[:, -1], alpha)
+ c = cmap(vals, alpha=alpha, bytes=True)
+ assert_array_equal(c[:, -1], (alpha * 255).astype(np.uint8))
+
+
+def test_colormap_bad_data_with_alpha():
+ cmap = mpl.colormaps['viridis']
+ c = cmap(np.nan, alpha=0.5)
+ assert c == (0, 0, 0, 0)
+ c = cmap([0.5, np.nan], alpha=0.5)
+ assert_array_equal(c[1], (0, 0, 0, 0))
+ c = cmap([0.5, np.nan], alpha=[0.1, 0.2])
+ assert_array_equal(c[1], (0, 0, 0, 0))
+ c = cmap([[np.nan, 0.5], [0, 0]], alpha=0.5)
+ assert_array_equal(c[0, 0], (0, 0, 0, 0))
+ c = cmap([[np.nan, 0.5], [0, 0]], alpha=np.full((2, 2), 0.5))
+ assert_array_equal(c[0, 0], (0, 0, 0, 0))
+
+
+def test_2d_to_rgba():
+ color = np.array([0.1, 0.2, 0.3])
+ rgba_1d = mcolors.to_rgba(color.reshape(-1))
+ rgba_2d = mcolors.to_rgba(color.reshape((1, -1)))
+ assert rgba_1d == rgba_2d
+
+
+def test_set_dict_to_rgba():
+ # downstream libraries do this...
+ # note we can't test this because it is not well-ordered
+ # so just smoketest:
+ colors = {(0, .5, 1), (1, .2, .5), (.4, 1, .2)}
+ res = mcolors.to_rgba_array(colors)
+ palette = {"red": (1, 0, 0), "green": (0, 1, 0), "blue": (0, 0, 1)}
+ res = mcolors.to_rgba_array(palette.values())
+ exp = np.eye(3)
+ np.testing.assert_array_almost_equal(res[:, :-1], exp)
+
+
+def test_norm_deepcopy():
+ norm = mcolors.LogNorm()
+ norm.vmin = 0.0002
+ norm2 = copy.deepcopy(norm)
+ assert norm2.vmin == norm.vmin
+ assert isinstance(norm2._scale, mscale.LogScale)
+ norm = mcolors.Normalize()
+ norm.vmin = 0.0002
+ norm2 = copy.deepcopy(norm)
+ assert norm2._scale is None
+ assert norm2.vmin == norm.vmin
+
+
+def test_set_clim_emits_single_callback():
+ data = np.array([[1, 2], [3, 4]])
+ fig, ax = plt.subplots()
+ image = ax.imshow(data, cmap='viridis')
+
+ callback = unittest.mock.Mock()
+ image.norm.callbacks.connect('changed', callback)
+
+ callback.assert_not_called()
+
+ # Call set_clim() to update the limits
+ image.set_clim(1, 5)
+
+ # Assert that only one "changed" callback is sent after calling set_clim()
+ callback.assert_called_once()
+
+
+def test_norm_callback():
+ increment = unittest.mock.Mock(return_value=None)
+
+ norm = mcolors.Normalize()
+ norm.callbacks.connect('changed', increment)
+ # Haven't updated anything, so call count should be 0
+ assert increment.call_count == 0
+
+ # Now change vmin and vmax to test callbacks
+ norm.vmin = 1
+ assert increment.call_count == 1
+ norm.vmax = 5
+ assert increment.call_count == 2
+ # callback shouldn't be called if setting to the same value
+ norm.vmin = 1
+ assert increment.call_count == 2
+ norm.vmax = 5
+ assert increment.call_count == 2
+
+ # We only want autoscale() calls to send out one update signal
+ increment.call_count = 0
+ norm.autoscale([0, 1, 2])
+ assert increment.call_count == 1
+
+
+def test_scalarmappable_norm_update():
+ norm = mcolors.Normalize()
+ sm = matplotlib.cm.ScalarMappable(norm=norm, cmap='plasma')
+ # sm doesn't have a stale attribute at first, set it to False
+ sm.stale = False
+ # The mappable should be stale after updating vmin/vmax
+ norm.vmin = 5
+ assert sm.stale
+ sm.stale = False
+ norm.vmax = 5
+ assert sm.stale
+ sm.stale = False
+ norm.clip = True
+ assert sm.stale
+ # change to the CenteredNorm and TwoSlopeNorm to test those
+ # Also make sure that updating the norm directly and with
+ # set_norm both update the Norm callback
+ norm = mcolors.CenteredNorm()
+ sm.norm = norm
+ sm.stale = False
+ norm.vcenter = 1
+ assert sm.stale
+ norm = mcolors.TwoSlopeNorm(vcenter=0, vmin=-1, vmax=1)
+ sm.set_norm(norm)
+ sm.stale = False
+ norm.vcenter = 1
+ assert sm.stale
+
+
+@check_figures_equal(extensions=['png'])
+def test_norm_update_figs(fig_test, fig_ref):
+ ax_ref = fig_ref.add_subplot()
+ ax_test = fig_test.add_subplot()
+
+ z = np.arange(100).reshape((10, 10))
+ ax_ref.imshow(z, norm=mcolors.Normalize(10, 90))
+
+ # Create the norm beforehand with different limits and then update
+ # after adding to the plot
+ norm = mcolors.Normalize(0, 1)
+ ax_test.imshow(z, norm=norm)
+ # Force initial draw to make sure it isn't already stale
+ fig_test.canvas.draw()
+ norm.vmin, norm.vmax = 10, 90
+
+
+def test_make_norm_from_scale_name():
+ logitnorm = mcolors.make_norm_from_scale(
+ mscale.LogitScale, mcolors.Normalize)
+ assert logitnorm.__name__ == logitnorm.__qualname__ == "LogitScaleNorm"
+
+
+def test_color_sequences():
+ # basic access
+ assert plt.color_sequences is matplotlib.color_sequences # same registry
+ assert list(plt.color_sequences) == [
+ 'tab10', 'tab20', 'tab20b', 'tab20c', 'Pastel1', 'Pastel2', 'Paired',
+ 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'petroff10']
+ assert len(plt.color_sequences['tab10']) == 10
+ assert len(plt.color_sequences['tab20']) == 20
+
+ tab_colors = [
+ 'tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple',
+ 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan']
+ for seq_color, tab_color in zip(plt.color_sequences['tab10'], tab_colors):
+ assert mcolors.same_color(seq_color, tab_color)
+
+ # registering
+ with pytest.raises(ValueError, match="reserved name"):
+ plt.color_sequences.register('tab10', ['r', 'g', 'b'])
+ with pytest.raises(ValueError, match="not a valid color specification"):
+ plt.color_sequences.register('invalid', ['not a color'])
+
+ rgb_colors = ['r', 'g', 'b']
+ plt.color_sequences.register('rgb', rgb_colors)
+ assert plt.color_sequences['rgb'] == ['r', 'g', 'b']
+ # should not affect the registered sequence because input is copied
+ rgb_colors.append('c')
+ assert plt.color_sequences['rgb'] == ['r', 'g', 'b']
+ # should not affect the registered sequence because returned list is a copy
+ plt.color_sequences['rgb'].append('c')
+ assert plt.color_sequences['rgb'] == ['r', 'g', 'b']
+
+ # unregister
+ plt.color_sequences.unregister('rgb')
+ with pytest.raises(KeyError):
+ plt.color_sequences['rgb'] # rgb is gone
+ plt.color_sequences.unregister('rgb') # multiple unregisters are ok
+ with pytest.raises(ValueError, match="Cannot unregister builtin"):
+ plt.color_sequences.unregister('tab10')
+
+
+def test_cm_set_cmap_error():
+ sm = cm.ScalarMappable()
+ # Pick a name we are pretty sure will never be a colormap name
+ bad_cmap = 'AardvarksAreAwkward'
+ with pytest.raises(ValueError, match=bad_cmap):
+ sm.set_cmap(bad_cmap)
+
+
+def test_set_cmap_mismatched_name():
+ cmap = matplotlib.colormaps["viridis"].with_extremes(over='r')
+ # register it with different names
+ cmap.name = "test-cmap"
+ matplotlib.colormaps.register(name='wrong-cmap', cmap=cmap)
+
+ plt.set_cmap("wrong-cmap")
+ cmap_returned = plt.get_cmap("wrong-cmap")
+ assert cmap_returned == cmap
+ assert cmap_returned.name == "wrong-cmap"
+
+
+def test_cmap_alias_names():
+ assert matplotlib.colormaps["gray"].name == "gray" # original
+ assert matplotlib.colormaps["grey"].name == "grey" # alias
+
+
+def test_to_rgba_array_none_color_with_alpha_param():
+ # effective alpha for color "none" must always be 0 to achieve a vanishing color
+ # even explicit alpha must be ignored
+ c = ["blue", "none"]
+ alpha = [1, 1]
+ assert_array_equal(
+ to_rgba_array(c, alpha), [[0., 0., 1., 1.], [0., 0., 0., 0.]]
+ )
+
+
+@pytest.mark.parametrize('input, expected',
+ [('red', True),
+ (('red', 0.5), True),
+ (('red', 2), False),
+ (['red', 0.5], False),
+ (('red', 'blue'), False),
+ (['red', 'blue'], False),
+ ('C3', True),
+ (('C3', 0.5), True)])
+def test_is_color_like(input, expected):
+ assert is_color_like(input) is expected
+
+
+def test_colorizer_vmin_vmax():
+ ca = mcolorizer.Colorizer()
+ assert ca.vmin is None
+ assert ca.vmax is None
+ ca.vmin = 1
+ ca.vmax = 3
+ assert ca.vmin == 1.0
+ assert ca.vmax == 3.0
+ assert ca.norm.vmin == 1.0
+ assert ca.norm.vmax == 3.0
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_compare_images.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_compare_images.py
new file mode 100644
index 0000000000000000000000000000000000000000..6023f3d054682568c52256464c7ff7f4e525d7ba
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_compare_images.py
@@ -0,0 +1,73 @@
+from pathlib import Path
+import shutil
+
+import pytest
+from pytest import approx
+
+from matplotlib.testing.compare import compare_images
+from matplotlib.testing.decorators import _image_directories
+
+
+# Tests of the image comparison algorithm.
+@pytest.mark.parametrize(
+ 'im1, im2, tol, expect_rms',
+ [
+ # Comparison of an image and the same image with minor differences.
+ # This expects the images to compare equal under normal tolerance, and
+ # have a small RMS.
+ ('basn3p02.png', 'basn3p02-minorchange.png', 10, None),
+ # Now test with no tolerance.
+ ('basn3p02.png', 'basn3p02-minorchange.png', 0, 6.50646),
+ # Comparison with an image that is shifted by 1px in the X axis.
+ ('basn3p02.png', 'basn3p02-1px-offset.png', 0, 90.15611),
+ # Comparison with an image with half the pixels shifted by 1px in the X
+ # axis.
+ ('basn3p02.png', 'basn3p02-half-1px-offset.png', 0, 63.75),
+ # Comparison of an image and the same image scrambled.
+ # This expects the images to compare completely different, with a very
+ # large RMS.
+ # Note: The image has been scrambled in a specific way, by having
+ # each color component of each pixel randomly placed somewhere in the
+ # image. It contains exactly the same number of pixels of each color
+ # value of R, G and B, but in a totally different position.
+ # Test with no tolerance to make sure that we pick up even a very small
+ # RMS error.
+ ('basn3p02.png', 'basn3p02-scrambled.png', 0, 172.63582),
+ # Comparison of an image and a slightly brighter image.
+ # The two images are solid color, with the second image being exactly 1
+ # color value brighter.
+ # This expects the images to compare equal under normal tolerance, and
+ # have an RMS of exactly 1.
+ ('all127.png', 'all128.png', 0, 1),
+ # Now test the reverse comparison.
+ ('all128.png', 'all127.png', 0, 1),
+ ])
+def test_image_comparison_expect_rms(im1, im2, tol, expect_rms, tmp_path,
+ monkeypatch):
+ """
+ Compare two images, expecting a particular RMS error.
+
+ im1 and im2 are filenames relative to the baseline_dir directory.
+
+ tol is the tolerance to pass to compare_images.
+
+ expect_rms is the expected RMS value, or None. If None, the test will
+ succeed if compare_images succeeds. Otherwise, the test will succeed if
+ compare_images fails and returns an RMS error almost equal to this value.
+ """
+ # Change the working directory using monkeypatch to use a temporary
+ # test specific directory
+ monkeypatch.chdir(tmp_path)
+ baseline_dir, result_dir = map(Path, _image_directories(lambda: "dummy"))
+ # Copy "test" image to result_dir, so that compare_images writes
+ # the diff to result_dir, rather than to the source tree
+ result_im2 = result_dir / im1
+ shutil.copyfile(baseline_dir / im2, result_im2)
+ results = compare_images(
+ baseline_dir / im1, result_im2, tol=tol, in_decorator=True)
+
+ if expect_rms is None:
+ assert results is None
+ else:
+ assert results is not None
+ assert results['rms'] == approx(expect_rms, abs=1e-4)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_constrainedlayout.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_constrainedlayout.py
new file mode 100644
index 0000000000000000000000000000000000000000..df2dbd6f43bdb52879893293a222d9e98fcb38b8
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_constrainedlayout.py
@@ -0,0 +1,743 @@
+import gc
+import platform
+
+import numpy as np
+import pytest
+
+import matplotlib as mpl
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.pyplot as plt
+import matplotlib.transforms as mtransforms
+from matplotlib import gridspec, ticker
+
+
+pytestmark = [
+ pytest.mark.usefixtures('text_placeholders')
+]
+
+
+def example_plot(ax, fontsize=12, nodec=False):
+ ax.plot([1, 2])
+ ax.locator_params(nbins=3)
+ if not nodec:
+ ax.set_xlabel('x-label', fontsize=fontsize)
+ ax.set_ylabel('y-label', fontsize=fontsize)
+ ax.set_title('Title', fontsize=fontsize)
+ else:
+ ax.set_xticklabels([])
+ ax.set_yticklabels([])
+
+
+def example_pcolor(ax, fontsize=12):
+ dx, dy = 0.6, 0.6
+ y, x = np.mgrid[slice(-3, 3 + dy, dy),
+ slice(-3, 3 + dx, dx)]
+ z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
+ pcm = ax.pcolormesh(x, y, z[:-1, :-1], cmap='RdBu_r', vmin=-1., vmax=1.,
+ rasterized=True)
+ ax.set_xlabel('x-label', fontsize=fontsize)
+ ax.set_ylabel('y-label', fontsize=fontsize)
+ ax.set_title('Title', fontsize=fontsize)
+ return pcm
+
+
+@image_comparison(['constrained_layout1.png'], style='mpl20')
+def test_constrained_layout1():
+ """Test constrained_layout for a single subplot"""
+ fig = plt.figure(layout="constrained")
+ ax = fig.add_subplot()
+ example_plot(ax, fontsize=24)
+
+
+@image_comparison(['constrained_layout2.png'], style='mpl20')
+def test_constrained_layout2():
+ """Test constrained_layout for 2x2 subplots"""
+ fig, axs = plt.subplots(2, 2, layout="constrained")
+ for ax in axs.flat:
+ example_plot(ax, fontsize=24)
+
+
+@image_comparison(['constrained_layout3.png'], style='mpl20')
+def test_constrained_layout3():
+ """Test constrained_layout for colorbars with subplots"""
+
+ fig, axs = plt.subplots(2, 2, layout="constrained")
+ for nn, ax in enumerate(axs.flat):
+ pcm = example_pcolor(ax, fontsize=24)
+ if nn == 3:
+ pad = 0.08
+ else:
+ pad = 0.02 # default
+ fig.colorbar(pcm, ax=ax, pad=pad)
+
+
+@image_comparison(['constrained_layout4.png'], style='mpl20')
+def test_constrained_layout4():
+ """Test constrained_layout for a single colorbar with subplots"""
+
+ fig, axs = plt.subplots(2, 2, layout="constrained")
+ for ax in axs.flat:
+ pcm = example_pcolor(ax, fontsize=24)
+ fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
+
+
+@image_comparison(['constrained_layout5.png'], style='mpl20')
+def test_constrained_layout5():
+ """
+ Test constrained_layout for a single colorbar with subplots,
+ colorbar bottom
+ """
+
+ fig, axs = plt.subplots(2, 2, layout="constrained")
+ for ax in axs.flat:
+ pcm = example_pcolor(ax, fontsize=24)
+ fig.colorbar(pcm, ax=axs,
+ use_gridspec=False, pad=0.01, shrink=0.6,
+ location='bottom')
+
+
+@image_comparison(['constrained_layout6.png'], style='mpl20')
+def test_constrained_layout6():
+ """Test constrained_layout for nested gridspecs"""
+ fig = plt.figure(layout="constrained")
+ gs = fig.add_gridspec(1, 2, figure=fig)
+ gsl = gs[0].subgridspec(2, 2)
+ gsr = gs[1].subgridspec(1, 2)
+ axsl = []
+ for gs in gsl:
+ ax = fig.add_subplot(gs)
+ axsl += [ax]
+ example_plot(ax, fontsize=12)
+ ax.set_xlabel('x-label\nMultiLine')
+ axsr = []
+ for gs in gsr:
+ ax = fig.add_subplot(gs)
+ axsr += [ax]
+ pcm = example_pcolor(ax, fontsize=12)
+
+ fig.colorbar(pcm, ax=axsr,
+ pad=0.01, shrink=0.99, location='bottom',
+ ticks=ticker.MaxNLocator(nbins=5))
+
+
+def test_identical_subgridspec():
+
+ fig = plt.figure(constrained_layout=True)
+
+ GS = fig.add_gridspec(2, 1)
+
+ GSA = GS[0].subgridspec(1, 3)
+ GSB = GS[1].subgridspec(1, 3)
+
+ axa = []
+ axb = []
+ for i in range(3):
+ axa += [fig.add_subplot(GSA[i])]
+ axb += [fig.add_subplot(GSB[i])]
+
+ fig.draw_without_rendering()
+ # check first row above second
+ assert axa[0].get_position().y0 > axb[0].get_position().y1
+
+
+def test_constrained_layout7():
+ """Test for proper warning if fig not set in GridSpec"""
+ with pytest.warns(
+ UserWarning, match=('There are no gridspecs with layoutgrids. '
+ 'Possibly did not call parent GridSpec with '
+ 'the "figure" keyword')):
+ fig = plt.figure(layout="constrained")
+ gs = gridspec.GridSpec(1, 2)
+ gsl = gridspec.GridSpecFromSubplotSpec(2, 2, gs[0])
+ gsr = gridspec.GridSpecFromSubplotSpec(1, 2, gs[1])
+ for gs in gsl:
+ fig.add_subplot(gs)
+ # need to trigger a draw to get warning
+ fig.draw_without_rendering()
+
+
+@image_comparison(['constrained_layout8.png'], style='mpl20')
+def test_constrained_layout8():
+ """Test for gridspecs that are not completely full"""
+
+ fig = plt.figure(figsize=(10, 5), layout="constrained")
+ gs = gridspec.GridSpec(3, 5, figure=fig)
+ axs = []
+ for j in [0, 1]:
+ if j == 0:
+ ilist = [1]
+ else:
+ ilist = [0, 4]
+ for i in ilist:
+ ax = fig.add_subplot(gs[j, i])
+ axs += [ax]
+ example_pcolor(ax, fontsize=9)
+ if i > 0:
+ ax.set_ylabel('')
+ if j < 1:
+ ax.set_xlabel('')
+ ax.set_title('')
+ ax = fig.add_subplot(gs[2, :])
+ axs += [ax]
+ pcm = example_pcolor(ax, fontsize=9)
+
+ fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
+
+
+@image_comparison(['constrained_layout9.png'], style='mpl20')
+def test_constrained_layout9():
+ """Test for handling suptitle and for sharex and sharey"""
+
+ fig, axs = plt.subplots(2, 2, layout="constrained",
+ sharex=False, sharey=False)
+ for ax in axs.flat:
+ pcm = example_pcolor(ax, fontsize=24)
+ ax.set_xlabel('')
+ ax.set_ylabel('')
+ ax.set_aspect(2.)
+ fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
+ fig.suptitle('Test Suptitle', fontsize=28)
+
+
+@image_comparison(['constrained_layout10.png'], style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.032)
+def test_constrained_layout10():
+ """Test for handling legend outside axis"""
+ fig, axs = plt.subplots(2, 2, layout="constrained")
+ for ax in axs.flat:
+ ax.plot(np.arange(12), label='This is a label')
+ ax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
+
+
+@image_comparison(['constrained_layout11.png'], style='mpl20')
+def test_constrained_layout11():
+ """Test for multiple nested gridspecs"""
+
+ fig = plt.figure(layout="constrained", figsize=(13, 3))
+ gs0 = gridspec.GridSpec(1, 2, figure=fig)
+ gsl = gridspec.GridSpecFromSubplotSpec(1, 2, gs0[0])
+ gsl0 = gridspec.GridSpecFromSubplotSpec(2, 2, gsl[1])
+ ax = fig.add_subplot(gs0[1])
+ example_plot(ax, fontsize=9)
+ axs = []
+ for gs in gsl0:
+ ax = fig.add_subplot(gs)
+ axs += [ax]
+ pcm = example_pcolor(ax, fontsize=9)
+ fig.colorbar(pcm, ax=axs, shrink=0.6, aspect=70.)
+ ax = fig.add_subplot(gsl[0])
+ example_plot(ax, fontsize=9)
+
+
+@image_comparison(['constrained_layout11rat.png'], style='mpl20')
+def test_constrained_layout11rat():
+ """Test for multiple nested gridspecs with width_ratios"""
+
+ fig = plt.figure(layout="constrained", figsize=(10, 3))
+ gs0 = gridspec.GridSpec(1, 2, figure=fig, width_ratios=[6, 1])
+ gsl = gridspec.GridSpecFromSubplotSpec(1, 2, gs0[0])
+ gsl0 = gridspec.GridSpecFromSubplotSpec(2, 2, gsl[1], height_ratios=[2, 1])
+ ax = fig.add_subplot(gs0[1])
+ example_plot(ax, fontsize=9)
+ axs = []
+ for gs in gsl0:
+ ax = fig.add_subplot(gs)
+ axs += [ax]
+ pcm = example_pcolor(ax, fontsize=9)
+ fig.colorbar(pcm, ax=axs, shrink=0.6, aspect=70.)
+ ax = fig.add_subplot(gsl[0])
+ example_plot(ax, fontsize=9)
+
+
+@image_comparison(['constrained_layout12.png'], style='mpl20')
+def test_constrained_layout12():
+ """Test that very unbalanced labeling still works."""
+ fig = plt.figure(layout="constrained", figsize=(6, 8))
+
+ gs0 = gridspec.GridSpec(6, 2, figure=fig)
+
+ ax1 = fig.add_subplot(gs0[:3, 1])
+ ax2 = fig.add_subplot(gs0[3:, 1])
+
+ example_plot(ax1, fontsize=18)
+ example_plot(ax2, fontsize=18)
+
+ ax = fig.add_subplot(gs0[0:2, 0])
+ example_plot(ax, nodec=True)
+ ax = fig.add_subplot(gs0[2:4, 0])
+ example_plot(ax, nodec=True)
+ ax = fig.add_subplot(gs0[4:, 0])
+ example_plot(ax, nodec=True)
+ ax.set_xlabel('x-label')
+
+
+@image_comparison(['constrained_layout13.png'], style='mpl20')
+def test_constrained_layout13():
+ """Test that padding works."""
+ fig, axs = plt.subplots(2, 2, layout="constrained")
+ for ax in axs.flat:
+ pcm = example_pcolor(ax, fontsize=12)
+ fig.colorbar(pcm, ax=ax, shrink=0.6, aspect=20., pad=0.02)
+ with pytest.raises(TypeError):
+ fig.get_layout_engine().set(wpad=1, hpad=2)
+ fig.get_layout_engine().set(w_pad=24./72., h_pad=24./72.)
+
+
+@image_comparison(['constrained_layout14.png'], style='mpl20')
+def test_constrained_layout14():
+ """Test that padding works."""
+ fig, axs = plt.subplots(2, 2, layout="constrained")
+ for ax in axs.flat:
+ pcm = example_pcolor(ax, fontsize=12)
+ fig.colorbar(pcm, ax=ax, shrink=0.6, aspect=20., pad=0.02)
+ fig.get_layout_engine().set(
+ w_pad=3./72., h_pad=3./72.,
+ hspace=0.2, wspace=0.2)
+
+
+@image_comparison(['constrained_layout15.png'], style='mpl20')
+def test_constrained_layout15():
+ """Test that rcparams work."""
+ mpl.rcParams['figure.constrained_layout.use'] = True
+ fig, axs = plt.subplots(2, 2)
+ for ax in axs.flat:
+ example_plot(ax, fontsize=12)
+
+
+@image_comparison(['constrained_layout16.png'], style='mpl20')
+def test_constrained_layout16():
+ """Test ax.set_position."""
+ fig, ax = plt.subplots(layout="constrained")
+ example_plot(ax, fontsize=12)
+ ax2 = fig.add_axes([0.2, 0.2, 0.4, 0.4])
+
+
+@image_comparison(['constrained_layout17.png'], style='mpl20')
+def test_constrained_layout17():
+ """Test uneven gridspecs"""
+ fig = plt.figure(layout="constrained")
+ gs = gridspec.GridSpec(3, 3, figure=fig)
+
+ ax1 = fig.add_subplot(gs[0, 0])
+ ax2 = fig.add_subplot(gs[0, 1:])
+ ax3 = fig.add_subplot(gs[1:, 0:2])
+ ax4 = fig.add_subplot(gs[1:, -1])
+
+ example_plot(ax1)
+ example_plot(ax2)
+ example_plot(ax3)
+ example_plot(ax4)
+
+
+def test_constrained_layout18():
+ """Test twinx"""
+ fig, ax = plt.subplots(layout="constrained")
+ ax2 = ax.twinx()
+ example_plot(ax)
+ example_plot(ax2, fontsize=24)
+ fig.draw_without_rendering()
+ assert all(ax.get_position().extents == ax2.get_position().extents)
+
+
+def test_constrained_layout19():
+ """Test twiny"""
+ fig, ax = plt.subplots(layout="constrained")
+ ax2 = ax.twiny()
+ example_plot(ax)
+ example_plot(ax2, fontsize=24)
+ ax2.set_title('')
+ ax.set_title('')
+ fig.draw_without_rendering()
+ assert all(ax.get_position().extents == ax2.get_position().extents)
+
+
+def test_constrained_layout20():
+ """Smoke test cl does not mess up added Axes"""
+ gx = np.linspace(-5, 5, 4)
+ img = np.hypot(gx, gx[:, None])
+
+ fig = plt.figure()
+ ax = fig.add_axes([0, 0, 1, 1])
+ mesh = ax.pcolormesh(gx, gx, img[:-1, :-1])
+ fig.colorbar(mesh)
+
+
+def test_constrained_layout21():
+ """#11035: repeated calls to suptitle should not alter the layout"""
+ fig, ax = plt.subplots(layout="constrained")
+
+ fig.suptitle("Suptitle0")
+ fig.draw_without_rendering()
+ extents0 = np.copy(ax.get_position().extents)
+
+ fig.suptitle("Suptitle1")
+ fig.draw_without_rendering()
+ extents1 = np.copy(ax.get_position().extents)
+
+ np.testing.assert_allclose(extents0, extents1)
+
+
+def test_constrained_layout22():
+ """#11035: suptitle should not be include in CL if manually positioned"""
+ fig, ax = plt.subplots(layout="constrained")
+
+ fig.draw_without_rendering()
+ extents0 = np.copy(ax.get_position().extents)
+
+ fig.suptitle("Suptitle", y=0.5)
+ fig.draw_without_rendering()
+ extents1 = np.copy(ax.get_position().extents)
+
+ np.testing.assert_allclose(extents0, extents1)
+
+
+def test_constrained_layout23():
+ """
+ Comment in #11035: suptitle used to cause an exception when
+ reusing a figure w/ CL with ``clear=True``.
+ """
+
+ for i in range(2):
+ fig = plt.figure(layout="constrained", clear=True, num="123")
+ gs = fig.add_gridspec(1, 2)
+ sub = gs[0].subgridspec(2, 2)
+ fig.suptitle(f"Suptitle{i}")
+
+
+@image_comparison(['test_colorbar_location.png'],
+ remove_text=True, style='mpl20')
+def test_colorbar_location():
+ """
+ Test that colorbar handling is as expected for various complicated
+ cases...
+ """
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ fig, axs = plt.subplots(4, 5, layout="constrained")
+ for ax in axs.flat:
+ pcm = example_pcolor(ax)
+ ax.set_xlabel('')
+ ax.set_ylabel('')
+ fig.colorbar(pcm, ax=axs[:, 1], shrink=0.4)
+ fig.colorbar(pcm, ax=axs[-1, :2], shrink=0.5, location='bottom')
+ fig.colorbar(pcm, ax=axs[0, 2:], shrink=0.5, location='bottom', pad=0.05)
+ fig.colorbar(pcm, ax=axs[-2, 3:], shrink=0.5, location='top')
+ fig.colorbar(pcm, ax=axs[0, 0], shrink=0.5, location='left')
+ fig.colorbar(pcm, ax=axs[1:3, 2], shrink=0.5, location='right')
+
+
+def test_hidden_axes():
+ # test that if we make an Axes not visible that constrained_layout
+ # still works. Note the axes still takes space in the layout
+ # (as does a gridspec slot that is empty)
+ fig, axs = plt.subplots(2, 2, layout="constrained")
+ axs[0, 1].set_visible(False)
+ fig.draw_without_rendering()
+ extents1 = np.copy(axs[0, 0].get_position().extents)
+
+ np.testing.assert_allclose(
+ extents1, [0.046918, 0.541204, 0.477409, 0.980555], rtol=1e-5)
+
+
+def test_colorbar_align():
+ for location in ['right', 'left', 'top', 'bottom']:
+ fig, axs = plt.subplots(2, 2, layout="constrained")
+ cbs = []
+ for nn, ax in enumerate(axs.flat):
+ ax.tick_params(direction='in')
+ pc = example_pcolor(ax)
+ cb = fig.colorbar(pc, ax=ax, location=location, shrink=0.6,
+ pad=0.04)
+ cbs += [cb]
+ cb.ax.tick_params(direction='in')
+ if nn != 1:
+ cb.ax.xaxis.set_ticks([])
+ cb.ax.yaxis.set_ticks([])
+ ax.set_xticklabels([])
+ ax.set_yticklabels([])
+ fig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72,
+ hspace=0.1, wspace=0.1)
+
+ fig.draw_without_rendering()
+ if location in ['left', 'right']:
+ np.testing.assert_allclose(cbs[0].ax.get_position().x0,
+ cbs[2].ax.get_position().x0)
+ np.testing.assert_allclose(cbs[1].ax.get_position().x0,
+ cbs[3].ax.get_position().x0)
+ else:
+ np.testing.assert_allclose(cbs[0].ax.get_position().y0,
+ cbs[1].ax.get_position().y0)
+ np.testing.assert_allclose(cbs[2].ax.get_position().y0,
+ cbs[3].ax.get_position().y0)
+
+
+@image_comparison(['test_colorbars_no_overlapV.png'], style='mpl20')
+def test_colorbars_no_overlapV():
+ fig = plt.figure(figsize=(2, 4), layout="constrained")
+ axs = fig.subplots(2, 1, sharex=True, sharey=True)
+ for ax in axs:
+ ax.yaxis.set_major_formatter(ticker.NullFormatter())
+ ax.tick_params(axis='both', direction='in')
+ im = ax.imshow([[1, 2], [3, 4]])
+ fig.colorbar(im, ax=ax, orientation="vertical")
+ fig.suptitle("foo")
+
+
+@image_comparison(['test_colorbars_no_overlapH.png'], style='mpl20')
+def test_colorbars_no_overlapH():
+ fig = plt.figure(figsize=(4, 2), layout="constrained")
+ fig.suptitle("foo")
+ axs = fig.subplots(1, 2, sharex=True, sharey=True)
+ for ax in axs:
+ ax.yaxis.set_major_formatter(ticker.NullFormatter())
+ ax.tick_params(axis='both', direction='in')
+ im = ax.imshow([[1, 2], [3, 4]])
+ fig.colorbar(im, ax=ax, orientation="horizontal")
+
+
+def test_manually_set_position():
+ fig, axs = plt.subplots(1, 2, layout="constrained")
+ axs[0].set_position([0.2, 0.2, 0.3, 0.3])
+ fig.draw_without_rendering()
+ pp = axs[0].get_position()
+ np.testing.assert_allclose(pp, [[0.2, 0.2], [0.5, 0.5]])
+
+ fig, axs = plt.subplots(1, 2, layout="constrained")
+ axs[0].set_position([0.2, 0.2, 0.3, 0.3])
+ pc = axs[0].pcolormesh(np.random.rand(20, 20))
+ fig.colorbar(pc, ax=axs[0])
+ fig.draw_without_rendering()
+ pp = axs[0].get_position()
+ np.testing.assert_allclose(pp, [[0.2, 0.2], [0.44, 0.5]])
+
+
+@image_comparison(['test_bboxtight.png'],
+ remove_text=True, style='mpl20',
+ savefig_kwarg={'bbox_inches': 'tight'})
+def test_bboxtight():
+ fig, ax = plt.subplots(layout="constrained")
+ ax.set_aspect(1.)
+
+
+@image_comparison(['test_bbox.png'],
+ remove_text=True, style='mpl20',
+ savefig_kwarg={'bbox_inches':
+ mtransforms.Bbox([[0.5, 0], [2.5, 2]])})
+def test_bbox():
+ fig, ax = plt.subplots(layout="constrained")
+ ax.set_aspect(1.)
+
+
+def test_align_labels():
+ """
+ Tests for a bug in which constrained layout and align_ylabels on
+ three unevenly sized subplots, one of whose y tick labels include
+ negative numbers, drives the non-negative subplots' y labels off
+ the edge of the plot
+ """
+ fig, (ax3, ax1, ax2) = plt.subplots(3, 1, layout="constrained",
+ figsize=(6.4, 8),
+ gridspec_kw={"height_ratios": (1, 1,
+ 0.7)})
+
+ ax1.set_ylim(0, 1)
+ ax1.set_ylabel("Label")
+
+ ax2.set_ylim(-1.5, 1.5)
+ ax2.set_ylabel("Label")
+
+ ax3.set_ylim(0, 1)
+ ax3.set_ylabel("Label")
+
+ fig.align_ylabels(axs=(ax3, ax1, ax2))
+
+ fig.draw_without_rendering()
+ after_align = [ax1.yaxis.label.get_window_extent(),
+ ax2.yaxis.label.get_window_extent(),
+ ax3.yaxis.label.get_window_extent()]
+ # ensure labels are approximately aligned
+ np.testing.assert_allclose([after_align[0].x0, after_align[2].x0],
+ after_align[1].x0, rtol=0, atol=1e-05)
+ # ensure labels do not go off the edge
+ assert after_align[0].x0 >= 1
+
+
+def test_suplabels():
+ fig, ax = plt.subplots(layout="constrained")
+ fig.draw_without_rendering()
+ pos0 = ax.get_tightbbox(fig.canvas.get_renderer())
+ fig.supxlabel('Boo')
+ fig.supylabel('Booy')
+ fig.draw_without_rendering()
+ pos = ax.get_tightbbox(fig.canvas.get_renderer())
+ assert pos.y0 > pos0.y0 + 10.0
+ assert pos.x0 > pos0.x0 + 10.0
+
+ fig, ax = plt.subplots(layout="constrained")
+ fig.draw_without_rendering()
+ pos0 = ax.get_tightbbox(fig.canvas.get_renderer())
+ # check that specifying x (y) doesn't ruin the layout
+ fig.supxlabel('Boo', x=0.5)
+ fig.supylabel('Boo', y=0.5)
+ fig.draw_without_rendering()
+ pos = ax.get_tightbbox(fig.canvas.get_renderer())
+ assert pos.y0 > pos0.y0 + 10.0
+ assert pos.x0 > pos0.x0 + 10.0
+
+
+def test_gridspec_addressing():
+ fig = plt.figure()
+ gs = fig.add_gridspec(3, 3)
+ sp = fig.add_subplot(gs[0:, 1:])
+ fig.draw_without_rendering()
+
+
+def test_discouraged_api():
+ fig, ax = plt.subplots(constrained_layout=True)
+ fig.draw_without_rendering()
+
+ with pytest.warns(PendingDeprecationWarning,
+ match="will be deprecated"):
+ fig, ax = plt.subplots()
+ fig.set_constrained_layout(True)
+ fig.draw_without_rendering()
+
+ with pytest.warns(PendingDeprecationWarning,
+ match="will be deprecated"):
+ fig, ax = plt.subplots()
+ fig.set_constrained_layout({'w_pad': 0.02, 'h_pad': 0.02})
+ fig.draw_without_rendering()
+
+
+def test_kwargs():
+ fig, ax = plt.subplots(constrained_layout={'h_pad': 0.02})
+ fig.draw_without_rendering()
+
+
+def test_rect():
+ fig, ax = plt.subplots(layout='constrained')
+ fig.get_layout_engine().set(rect=[0, 0, 0.5, 0.5])
+ fig.draw_without_rendering()
+ ppos = ax.get_position()
+ assert ppos.x1 < 0.5
+ assert ppos.y1 < 0.5
+
+ fig, ax = plt.subplots(layout='constrained')
+ fig.get_layout_engine().set(rect=[0.2, 0.2, 0.3, 0.3])
+ fig.draw_without_rendering()
+ ppos = ax.get_position()
+ assert ppos.x1 < 0.5
+ assert ppos.y1 < 0.5
+ assert ppos.x0 > 0.2
+ assert ppos.y0 > 0.2
+
+
+def test_compressed1():
+ fig, axs = plt.subplots(3, 2, layout='compressed',
+ sharex=True, sharey=True)
+ for ax in axs.flat:
+ pc = ax.imshow(np.random.randn(20, 20))
+
+ fig.colorbar(pc, ax=axs)
+ fig.draw_without_rendering()
+
+ pos = axs[0, 0].get_position()
+ np.testing.assert_allclose(pos.x0, 0.2381, atol=1e-2)
+ pos = axs[0, 1].get_position()
+ np.testing.assert_allclose(pos.x1, 0.7024, atol=1e-3)
+
+ # wider than tall
+ fig, axs = plt.subplots(2, 3, layout='compressed',
+ sharex=True, sharey=True, figsize=(5, 4))
+ for ax in axs.flat:
+ pc = ax.imshow(np.random.randn(20, 20))
+
+ fig.colorbar(pc, ax=axs)
+ fig.draw_without_rendering()
+
+ pos = axs[0, 0].get_position()
+ np.testing.assert_allclose(pos.x0, 0.05653, atol=1e-3)
+ np.testing.assert_allclose(pos.y1, 0.8603, atol=1e-2)
+ pos = axs[1, 2].get_position()
+ np.testing.assert_allclose(pos.x1, 0.8728, atol=1e-3)
+ np.testing.assert_allclose(pos.y0, 0.1808, atol=1e-2)
+
+
+def test_compressed_suptitle():
+ fig, (ax0, ax1) = plt.subplots(
+ nrows=2, figsize=(4, 10), layout="compressed",
+ gridspec_kw={"height_ratios": (1 / 4, 3 / 4), "hspace": 0})
+
+ ax0.axis("equal")
+ ax0.set_box_aspect(1/3)
+
+ ax1.axis("equal")
+ ax1.set_box_aspect(1)
+
+ title = fig.suptitle("Title")
+ fig.draw_without_rendering()
+ assert title.get_position()[1] == pytest.approx(0.7491, abs=1e-3)
+
+ title = fig.suptitle("Title", y=0.98)
+ fig.draw_without_rendering()
+ assert title.get_position()[1] == 0.98
+
+ title = fig.suptitle("Title", in_layout=False)
+ fig.draw_without_rendering()
+ assert title.get_position()[1] == 0.98
+
+
+@pytest.mark.parametrize('arg, state', [
+ (True, True),
+ (False, False),
+ ({}, True),
+ ({'rect': None}, True)
+])
+def test_set_constrained_layout(arg, state):
+ fig, ax = plt.subplots(constrained_layout=arg)
+ assert fig.get_constrained_layout() is state
+
+
+def test_constrained_toggle():
+ fig, ax = plt.subplots()
+ with pytest.warns(PendingDeprecationWarning):
+ fig.set_constrained_layout(True)
+ assert fig.get_constrained_layout()
+ fig.set_constrained_layout(False)
+ assert not fig.get_constrained_layout()
+ fig.set_constrained_layout(True)
+ assert fig.get_constrained_layout()
+
+
+def test_layout_leak():
+ # Make sure there aren't any cyclic references when using LayoutGrid
+ # GH #25853
+ fig = plt.figure(constrained_layout=True, figsize=(10, 10))
+ fig.add_subplot()
+ fig.draw_without_rendering()
+ plt.close("all")
+ del fig
+ gc.collect()
+ assert not any(isinstance(obj, mpl._layoutgrid.LayoutGrid)
+ for obj in gc.get_objects())
+
+
+def test_submerged_subfig():
+ """
+ Test that the submerged margin logic does not get called multiple times
+ on same axes if it is already in a subfigure
+ """
+ fig = plt.figure(figsize=(4, 5), layout='constrained')
+ figures = fig.subfigures(3, 1)
+ axs = []
+ for f in figures.flatten():
+ gs = f.add_gridspec(2, 2)
+ for i in range(2):
+ axs += [f.add_subplot(gs[i, 0])]
+ axs[-1].plot()
+ f.add_subplot(gs[:, 1]).plot()
+ fig.draw_without_rendering()
+ for ax in axs[1:]:
+ assert np.allclose(ax.get_position().bounds[-1],
+ axs[0].get_position().bounds[-1], atol=1e-6)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_container.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_container.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e4577c518aeb5982e6609d59ddd1eab18534878
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_container.py
@@ -0,0 +1,37 @@
+import numpy as np
+import matplotlib.pyplot as plt
+
+
+def test_stem_remove():
+ ax = plt.gca()
+ st = ax.stem([1, 2], [1, 2])
+ st.remove()
+
+
+def test_errorbar_remove():
+
+ # Regression test for a bug that caused remove to fail when using
+ # fmt='none'
+
+ ax = plt.gca()
+
+ eb = ax.errorbar([1], [1])
+ eb.remove()
+
+ eb = ax.errorbar([1], [1], xerr=1)
+ eb.remove()
+
+ eb = ax.errorbar([1], [1], yerr=2)
+ eb.remove()
+
+ eb = ax.errorbar([1], [1], xerr=[2], yerr=2)
+ eb.remove()
+
+ eb = ax.errorbar([1], [1], fmt='none')
+ eb.remove()
+
+
+def test_nonstring_label():
+ # Test for #26824
+ plt.bar(np.arange(10), np.random.rand(10), label=1)
+ plt.legend()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_contour.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_contour.py
new file mode 100644
index 0000000000000000000000000000000000000000..543cff18c6979529401b705978e34921fc2189b0
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_contour.py
@@ -0,0 +1,839 @@
+import datetime
+import platform
+import re
+from unittest import mock
+
+import contourpy
+import numpy as np
+from numpy.testing import assert_array_almost_equal, assert_array_almost_equal_nulp
+import matplotlib as mpl
+from matplotlib import pyplot as plt, rc_context, ticker
+from matplotlib.colors import LogNorm, same_color
+import matplotlib.patches as mpatches
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+import pytest
+
+
+def test_contour_shape_1d_valid():
+
+ x = np.arange(10)
+ y = np.arange(9)
+ z = np.random.random((9, 10))
+
+ fig, ax = plt.subplots()
+ ax.contour(x, y, z)
+
+
+def test_contour_shape_2d_valid():
+
+ x = np.arange(10)
+ y = np.arange(9)
+ xg, yg = np.meshgrid(x, y)
+ z = np.random.random((9, 10))
+
+ fig, ax = plt.subplots()
+ ax.contour(xg, yg, z)
+
+
+@pytest.mark.parametrize("args, message", [
+ ((np.arange(9), np.arange(9), np.empty((9, 10))),
+ 'Length of x (9) must match number of columns in z (10)'),
+ ((np.arange(10), np.arange(10), np.empty((9, 10))),
+ 'Length of y (10) must match number of rows in z (9)'),
+ ((np.empty((10, 10)), np.arange(10), np.empty((9, 10))),
+ 'Number of dimensions of x (2) and y (1) do not match'),
+ ((np.arange(10), np.empty((10, 10)), np.empty((9, 10))),
+ 'Number of dimensions of x (1) and y (2) do not match'),
+ ((np.empty((9, 9)), np.empty((9, 10)), np.empty((9, 10))),
+ 'Shapes of x (9, 9) and z (9, 10) do not match'),
+ ((np.empty((9, 10)), np.empty((9, 9)), np.empty((9, 10))),
+ 'Shapes of y (9, 9) and z (9, 10) do not match'),
+ ((np.empty((3, 3, 3)), np.empty((3, 3, 3)), np.empty((9, 10))),
+ 'Inputs x and y must be 1D or 2D, not 3D'),
+ ((np.empty((3, 3, 3)), np.empty((3, 3, 3)), np.empty((3, 3, 3))),
+ 'Input z must be 2D, not 3D'),
+ (([[0]],), # github issue 8197
+ 'Input z must be at least a (2, 2) shaped array, but has shape (1, 1)'),
+ (([0], [0], [[0]]),
+ 'Input z must be at least a (2, 2) shaped array, but has shape (1, 1)'),
+])
+def test_contour_shape_error(args, message):
+ fig, ax = plt.subplots()
+ with pytest.raises(TypeError, match=re.escape(message)):
+ ax.contour(*args)
+
+
+def test_contour_no_valid_levels():
+ fig, ax = plt.subplots()
+ # no warning for empty levels.
+ ax.contour(np.random.rand(9, 9), levels=[])
+ # no warning if levels is given and is not within the range of z.
+ cs = ax.contour(np.arange(81).reshape((9, 9)), levels=[100])
+ # ... and if fmt is given.
+ ax.clabel(cs, fmt={100: '%1.2f'})
+ # no warning if z is uniform.
+ ax.contour(np.ones((9, 9)))
+
+
+def test_contour_Nlevels():
+ # A scalar levels arg or kwarg should trigger auto level generation.
+ # https://github.com/matplotlib/matplotlib/issues/11913
+ z = np.arange(12).reshape((3, 4))
+ fig, ax = plt.subplots()
+ cs1 = ax.contour(z, 5)
+ assert len(cs1.levels) > 1
+ cs2 = ax.contour(z, levels=5)
+ assert (cs1.levels == cs2.levels).all()
+
+
+@check_figures_equal(extensions=['png'])
+def test_contour_set_paths(fig_test, fig_ref):
+ cs_test = fig_test.subplots().contour([[0, 1], [1, 2]])
+ cs_ref = fig_ref.subplots().contour([[1, 0], [2, 1]])
+
+ cs_test.set_paths(cs_ref.get_paths())
+
+
+@image_comparison(['contour_manual_labels'], remove_text=True, style='mpl20', tol=0.26)
+def test_contour_manual_labels():
+ x, y = np.meshgrid(np.arange(0, 10), np.arange(0, 10))
+ z = np.max(np.dstack([abs(x), abs(y)]), 2)
+
+ plt.figure(figsize=(6, 2), dpi=200)
+ cs = plt.contour(x, y, z)
+
+ pts = np.array([(1.0, 3.0), (1.0, 4.4), (1.0, 6.0)])
+ plt.clabel(cs, manual=pts)
+ pts = np.array([(2.0, 3.0), (2.0, 4.4), (2.0, 6.0)])
+ plt.clabel(cs, manual=pts, fontsize='small', colors=('r', 'g'))
+
+
+def test_contour_manual_moveto():
+ x = np.linspace(-10, 10)
+ y = np.linspace(-10, 10)
+
+ X, Y = np.meshgrid(x, y)
+
+ Z = X**2 * 1 / Y**2 - 1
+
+ contours = plt.contour(X, Y, Z, levels=[0, 100])
+
+ # This point lies on the `MOVETO` line for the 100 contour
+ # but is actually closest to the 0 contour
+ point = (1.3, 1)
+ clabels = plt.clabel(contours, manual=[point])
+
+ # Ensure that the 0 contour was chosen, not the 100 contour
+ assert clabels[0].get_text() == "0"
+
+
+@image_comparison(['contour_disconnected_segments'],
+ remove_text=True, style='mpl20', extensions=['png'])
+def test_contour_label_with_disconnected_segments():
+ x, y = np.mgrid[-1:1:21j, -1:1:21j]
+ z = 1 / np.sqrt(0.01 + (x + 0.3) ** 2 + y ** 2)
+ z += 1 / np.sqrt(0.01 + (x - 0.3) ** 2 + y ** 2)
+
+ plt.figure()
+ cs = plt.contour(x, y, z, levels=[7])
+ cs.clabel(manual=[(0.2, 0.1)])
+
+
+@image_comparison(['contour_manual_colors_and_levels.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.018)
+def test_given_colors_levels_and_extends():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ _, axs = plt.subplots(2, 4)
+
+ data = np.arange(12).reshape(3, 4)
+
+ colors = ['red', 'yellow', 'pink', 'blue', 'black']
+ levels = [2, 4, 8, 10]
+
+ for i, ax in enumerate(axs.flat):
+ filled = i % 2 == 0.
+ extend = ['neither', 'min', 'max', 'both'][i // 2]
+
+ if filled:
+ # If filled, we have 3 colors with no extension,
+ # 4 colors with one extension, and 5 colors with both extensions
+ first_color = 1 if extend in ['max', 'neither'] else None
+ last_color = -1 if extend in ['min', 'neither'] else None
+ c = ax.contourf(data, colors=colors[first_color:last_color],
+ levels=levels, extend=extend)
+ else:
+ # If not filled, we have 4 levels and 4 colors
+ c = ax.contour(data, colors=colors[:-1],
+ levels=levels, extend=extend)
+
+ plt.colorbar(c, ax=ax)
+
+
+@image_comparison(['contourf_hatch_colors'],
+ remove_text=True, style='mpl20', extensions=['png'])
+def test_hatch_colors():
+ fig, ax = plt.subplots()
+ cf = ax.contourf([[0, 1], [1, 2]], hatches=['-', '/', '\\', '//'], cmap='gray')
+ cf.set_edgecolors(["blue", "grey", "yellow", "red"])
+
+
+@pytest.mark.parametrize('color, extend', [('darkred', 'neither'),
+ ('darkred', 'both'),
+ (('r', 0.5), 'neither'),
+ ((0.1, 0.2, 0.5, 0.3), 'neither')])
+def test_single_color_and_extend(color, extend):
+ z = [[0, 1], [1, 2]]
+
+ _, ax = plt.subplots()
+ levels = [0.5, 0.75, 1, 1.25, 1.5]
+ cs = ax.contour(z, levels=levels, colors=color, extend=extend)
+ for c in cs.get_edgecolors():
+ assert same_color(c, color)
+
+
+@image_comparison(['contour_log_locator.svg'], style='mpl20', remove_text=False)
+def test_log_locator_levels():
+
+ fig, ax = plt.subplots()
+
+ N = 100
+ x = np.linspace(-3.0, 3.0, N)
+ y = np.linspace(-2.0, 2.0, N)
+
+ X, Y = np.meshgrid(x, y)
+
+ Z1 = np.exp(-X**2 - Y**2)
+ Z2 = np.exp(-(X * 10)**2 - (Y * 10)**2)
+ data = Z1 + 50 * Z2
+
+ c = ax.contourf(data, locator=ticker.LogLocator())
+ assert_array_almost_equal(c.levels, np.power(10.0, np.arange(-6, 3)))
+ cb = fig.colorbar(c, ax=ax)
+ assert_array_almost_equal(cb.ax.get_yticks(), c.levels)
+
+
+@image_comparison(['contour_datetime_axis.png'], style='mpl20')
+def test_contour_datetime_axis():
+ fig = plt.figure()
+ fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15)
+ base = datetime.datetime(2013, 1, 1)
+ x = np.array([base + datetime.timedelta(days=d) for d in range(20)])
+ y = np.arange(20)
+ z1, z2 = np.meshgrid(np.arange(20), np.arange(20))
+ z = z1 * z2
+ plt.subplot(221)
+ plt.contour(x, y, z)
+ plt.subplot(222)
+ plt.contourf(x, y, z)
+ x = np.repeat(x[np.newaxis], 20, axis=0)
+ y = np.repeat(y[:, np.newaxis], 20, axis=1)
+ plt.subplot(223)
+ plt.contour(x, y, z)
+ plt.subplot(224)
+ plt.contourf(x, y, z)
+ for ax in fig.get_axes():
+ for label in ax.get_xticklabels():
+ label.set_ha('right')
+ label.set_rotation(30)
+
+
+@image_comparison(['contour_test_label_transforms.png'],
+ remove_text=True, style='mpl20', tol=1.1)
+def test_labels():
+ # Adapted from pylab_examples example code: contour_demo.py
+ # see issues #2475, #2843, and #2818 for explanation
+ delta = 0.025
+ x = np.arange(-3.0, 3.0, delta)
+ y = np.arange(-2.0, 2.0, delta)
+ X, Y = np.meshgrid(x, y)
+ Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
+ Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
+ (2 * np.pi * 0.5 * 1.5))
+
+ # difference of Gaussians
+ Z = 10.0 * (Z2 - Z1)
+
+ fig, ax = plt.subplots(1, 1)
+ CS = ax.contour(X, Y, Z)
+ disp_units = [(216, 177), (359, 290), (521, 406)]
+ data_units = [(-2, .5), (0, -1.5), (2.8, 1)]
+
+ CS.clabel()
+
+ for x, y in data_units:
+ CS.add_label_near(x, y, inline=True, transform=None)
+
+ for x, y in disp_units:
+ CS.add_label_near(x, y, inline=True, transform=False)
+
+
+def test_label_contour_start():
+ # Set up data and figure/axes that result in automatic labelling adding the
+ # label to the start of a contour
+
+ _, ax = plt.subplots(dpi=100)
+ lats = lons = np.linspace(-np.pi / 2, np.pi / 2, 50)
+ lons, lats = np.meshgrid(lons, lats)
+ wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons)
+ mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2)
+ data = wave + mean
+
+ cs = ax.contour(lons, lats, data)
+
+ with mock.patch.object(
+ cs, '_split_path_and_get_label_rotation',
+ wraps=cs._split_path_and_get_label_rotation) as mocked_splitter:
+ # Smoke test that we can add the labels
+ cs.clabel(fontsize=9)
+
+ # Verify at least one label was added to the start of a contour. I.e. the
+ # splitting method was called with idx=0 at least once.
+ idxs = [cargs[0][1] for cargs in mocked_splitter.call_args_list]
+ assert 0 in idxs
+
+
+@image_comparison(['contour_corner_mask_False.png', 'contour_corner_mask_True.png'],
+ remove_text=True, tol=1.88)
+def test_corner_mask():
+ n = 60
+ mask_level = 0.95
+ noise_amp = 1.0
+ np.random.seed([1])
+ x, y = np.meshgrid(np.linspace(0, 2.0, n), np.linspace(0, 2.0, n))
+ z = np.cos(7*x)*np.sin(8*y) + noise_amp*np.random.rand(n, n)
+ mask = np.random.rand(n, n) >= mask_level
+ z = np.ma.array(z, mask=mask)
+
+ for corner_mask in [False, True]:
+ plt.figure()
+ plt.contourf(z, corner_mask=corner_mask)
+
+
+def test_contourf_decreasing_levels():
+ # github issue 5477.
+ z = [[0.1, 0.3], [0.5, 0.7]]
+ plt.figure()
+ with pytest.raises(ValueError):
+ plt.contourf(z, [1.0, 0.0])
+
+
+def test_contourf_symmetric_locator():
+ # github issue 7271
+ z = np.arange(12).reshape((3, 4))
+ locator = plt.MaxNLocator(nbins=4, symmetric=True)
+ cs = plt.contourf(z, locator=locator)
+ assert_array_almost_equal(cs.levels, np.linspace(-12, 12, 5))
+
+
+def test_circular_contour_warning():
+ # Check that almost circular contours don't throw a warning
+ x, y = np.meshgrid(np.linspace(-2, 2, 4), np.linspace(-2, 2, 4))
+ r = np.hypot(x, y)
+ plt.figure()
+ cs = plt.contour(x, y, r)
+ plt.clabel(cs)
+
+
+@pytest.mark.parametrize("use_clabeltext, contour_zorder, clabel_zorder",
+ [(True, 123, 1234), (False, 123, 1234),
+ (True, 123, None), (False, 123, None)])
+def test_clabel_zorder(use_clabeltext, contour_zorder, clabel_zorder):
+ x, y = np.meshgrid(np.arange(0, 10), np.arange(0, 10))
+ z = np.max(np.dstack([abs(x), abs(y)]), 2)
+
+ fig, (ax1, ax2) = plt.subplots(ncols=2)
+ cs = ax1.contour(x, y, z, zorder=contour_zorder)
+ cs_filled = ax2.contourf(x, y, z, zorder=contour_zorder)
+ clabels1 = cs.clabel(zorder=clabel_zorder, use_clabeltext=use_clabeltext)
+ clabels2 = cs_filled.clabel(zorder=clabel_zorder,
+ use_clabeltext=use_clabeltext)
+
+ if clabel_zorder is None:
+ expected_clabel_zorder = 2+contour_zorder
+ else:
+ expected_clabel_zorder = clabel_zorder
+
+ for clabel in clabels1:
+ assert clabel.get_zorder() == expected_clabel_zorder
+ for clabel in clabels2:
+ assert clabel.get_zorder() == expected_clabel_zorder
+
+
+def test_clabel_with_large_spacing():
+ # When the inline spacing is large relative to the contour, it may cause the
+ # entire contour to be removed. In current implementation, one line segment is
+ # retained between the identified points.
+ # This behavior may be worth reconsidering, but check to be sure we do not produce
+ # an invalid path, which results in an error at clabel call time.
+ # see gh-27045 for more information
+ x = y = np.arange(-3.0, 3.01, 0.05)
+ X, Y = np.meshgrid(x, y)
+ Z = np.exp(-X**2 - Y**2)
+
+ fig, ax = plt.subplots()
+ contourset = ax.contour(X, Y, Z, levels=[0.01, 0.2, .5, .8])
+ ax.clabel(contourset, inline_spacing=100)
+
+
+# tol because ticks happen to fall on pixel boundaries so small
+# floating point changes in tick location flip which pixel gets
+# the tick.
+@image_comparison(['contour_log_extension.png'],
+ remove_text=True, style='mpl20',
+ tol=1.444)
+def test_contourf_log_extension():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ # Test that contourf with lognorm is extended correctly
+ fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(10, 5))
+ fig.subplots_adjust(left=0.05, right=0.95)
+
+ # make data set with large range e.g. between 1e-8 and 1e10
+ data_exp = np.linspace(-7.5, 9.5, 1200)
+ data = np.power(10, data_exp).reshape(30, 40)
+ # make manual levels e.g. between 1e-4 and 1e-6
+ levels_exp = np.arange(-4., 7.)
+ levels = np.power(10., levels_exp)
+
+ # original data
+ c1 = ax1.contourf(data,
+ norm=LogNorm(vmin=data.min(), vmax=data.max()))
+ # just show data in levels
+ c2 = ax2.contourf(data, levels=levels,
+ norm=LogNorm(vmin=levels.min(), vmax=levels.max()),
+ extend='neither')
+ # extend data from levels
+ c3 = ax3.contourf(data, levels=levels,
+ norm=LogNorm(vmin=levels.min(), vmax=levels.max()),
+ extend='both')
+ cb = plt.colorbar(c1, ax=ax1)
+ assert cb.ax.get_ylim() == (1e-8, 1e10)
+ cb = plt.colorbar(c2, ax=ax2)
+ assert_array_almost_equal_nulp(cb.ax.get_ylim(), np.array((1e-4, 1e6)))
+ cb = plt.colorbar(c3, ax=ax3)
+
+
+@image_comparison(['contour_addlines.png'], remove_text=True, style='mpl20',
+ tol=0.03 if platform.machine() == 'x86_64' else 0.15)
+# tolerance is because image changed minutely when tick finding on
+# colorbars was cleaned up...
+def test_contour_addlines():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ fig, ax = plt.subplots()
+ np.random.seed(19680812)
+ X = np.random.rand(10, 10)*10000
+ pcm = ax.pcolormesh(X)
+ # add 1000 to make colors visible...
+ cont = ax.contour(X+1000)
+ cb = fig.colorbar(pcm)
+ cb.add_lines(cont)
+ assert_array_almost_equal(cb.ax.get_ylim(), [114.3091, 9972.30735], 3)
+
+
+@image_comparison(baseline_images=['contour_uneven'],
+ extensions=['png'], remove_text=True, style='mpl20')
+def test_contour_uneven():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['pcolormesh.snap'] = False
+
+ z = np.arange(24).reshape(4, 6)
+ fig, axs = plt.subplots(1, 2)
+ ax = axs[0]
+ cs = ax.contourf(z, levels=[2, 4, 6, 10, 20])
+ fig.colorbar(cs, ax=ax, spacing='proportional')
+ ax = axs[1]
+ cs = ax.contourf(z, levels=[2, 4, 6, 10, 20])
+ fig.colorbar(cs, ax=ax, spacing='uniform')
+
+
+@pytest.mark.parametrize(
+ "rc_lines_linewidth, rc_contour_linewidth, call_linewidths, expected", [
+ (1.23, None, None, 1.23),
+ (1.23, 4.24, None, 4.24),
+ (1.23, 4.24, 5.02, 5.02)
+ ])
+def test_contour_linewidth(
+ rc_lines_linewidth, rc_contour_linewidth, call_linewidths, expected):
+
+ with rc_context(rc={"lines.linewidth": rc_lines_linewidth,
+ "contour.linewidth": rc_contour_linewidth}):
+ fig, ax = plt.subplots()
+ X = np.arange(4*3).reshape(4, 3)
+ cs = ax.contour(X, linewidths=call_linewidths)
+ assert cs.get_linewidths()[0] == expected
+
+
+@pytest.mark.backend("pdf")
+def test_label_nonagg():
+ # This should not crash even if the canvas doesn't have a get_renderer().
+ plt.clabel(plt.contour([[1, 2], [3, 4]]))
+
+
+@image_comparison(baseline_images=['contour_closed_line_loop'],
+ extensions=['png'], remove_text=True)
+def test_contour_closed_line_loop():
+ # github issue 19568.
+ z = [[0, 0, 0], [0, 2, 0], [0, 0, 0], [2, 1, 2]]
+
+ fig, ax = plt.subplots(figsize=(2, 2))
+ ax.contour(z, [0.5], linewidths=[20], alpha=0.7)
+ ax.set_xlim(-0.1, 2.1)
+ ax.set_ylim(-0.1, 3.1)
+
+
+def test_quadcontourset_reuse():
+ # If QuadContourSet returned from one contour(f) call is passed as first
+ # argument to another the underlying C++ contour generator will be reused.
+ x, y = np.meshgrid([0.0, 1.0], [0.0, 1.0])
+ z = x + y
+ fig, ax = plt.subplots()
+ qcs1 = ax.contourf(x, y, z)
+ qcs2 = ax.contour(x, y, z)
+ assert qcs2._contour_generator != qcs1._contour_generator
+ qcs3 = ax.contour(qcs1, z)
+ assert qcs3._contour_generator == qcs1._contour_generator
+
+
+@image_comparison(baseline_images=['contour_manual'],
+ extensions=['png'], remove_text=True, tol=0.89)
+def test_contour_manual():
+ # Manually specifying contour lines/polygons to plot.
+ from matplotlib.contour import ContourSet
+
+ fig, ax = plt.subplots(figsize=(4, 4))
+ cmap = 'viridis'
+
+ # Segments only (no 'kind' codes).
+ lines0 = [[[2, 0], [1, 2], [1, 3]]] # Single line.
+ lines1 = [[[3, 0], [3, 2]], [[3, 3], [3, 4]]] # Two lines.
+ filled01 = [[[0, 0], [0, 4], [1, 3], [1, 2], [2, 0]]]
+ filled12 = [[[2, 0], [3, 0], [3, 2], [1, 3], [1, 2]], # Two polygons.
+ [[1, 4], [3, 4], [3, 3]]]
+ ContourSet(ax, [0, 1, 2], [filled01, filled12], filled=True, cmap=cmap)
+ ContourSet(ax, [1, 2], [lines0, lines1], linewidths=3, colors=['r', 'k'])
+
+ # Segments and kind codes (1 = MOVETO, 2 = LINETO, 79 = CLOSEPOLY).
+ segs = [[[4, 0], [7, 0], [7, 3], [4, 3], [4, 0],
+ [5, 1], [5, 2], [6, 2], [6, 1], [5, 1]]]
+ kinds = [[1, 2, 2, 2, 79, 1, 2, 2, 2, 79]] # Polygon containing hole.
+ ContourSet(ax, [2, 3], [segs], [kinds], filled=True, cmap=cmap)
+ ContourSet(ax, [2], [segs], [kinds], colors='k', linewidths=3)
+
+
+@image_comparison(baseline_images=['contour_line_start_on_corner_edge'],
+ extensions=['png'], remove_text=True)
+def test_contour_line_start_on_corner_edge():
+ fig, ax = plt.subplots(figsize=(6, 5))
+
+ x, y = np.meshgrid([0, 1, 2, 3, 4], [0, 1, 2])
+ z = 1.2 - (x - 2)**2 + (y - 1)**2
+ mask = np.zeros_like(z, dtype=bool)
+ mask[1, 1] = mask[1, 3] = True
+ z = np.ma.array(z, mask=mask)
+
+ filled = ax.contourf(x, y, z, corner_mask=True)
+ cbar = fig.colorbar(filled)
+ lines = ax.contour(x, y, z, corner_mask=True, colors='k')
+ cbar.add_lines(lines)
+
+
+def test_find_nearest_contour():
+ xy = np.indices((15, 15))
+ img = np.exp(-np.pi * (np.sum((xy - 5)**2, 0)/5.**2))
+ cs = plt.contour(img, 10)
+
+ nearest_contour = cs.find_nearest_contour(1, 1, pixel=False)
+ expected_nearest = (1, 0, 33, 1.965966, 1.965966, 1.866183)
+ assert_array_almost_equal(nearest_contour, expected_nearest)
+
+ nearest_contour = cs.find_nearest_contour(8, 1, pixel=False)
+ expected_nearest = (1, 0, 5, 7.550173, 1.587542, 0.547550)
+ assert_array_almost_equal(nearest_contour, expected_nearest)
+
+ nearest_contour = cs.find_nearest_contour(2, 5, pixel=False)
+ expected_nearest = (3, 0, 21, 1.884384, 5.023335, 0.013911)
+ assert_array_almost_equal(nearest_contour, expected_nearest)
+
+ nearest_contour = cs.find_nearest_contour(2, 5, indices=(5, 7), pixel=False)
+ expected_nearest = (5, 0, 16, 2.628202, 5.0, 0.394638)
+ assert_array_almost_equal(nearest_contour, expected_nearest)
+
+
+def test_find_nearest_contour_no_filled():
+ xy = np.indices((15, 15))
+ img = np.exp(-np.pi * (np.sum((xy - 5)**2, 0)/5.**2))
+ cs = plt.contourf(img, 10)
+
+ with pytest.raises(ValueError, match="Method does not support filled contours"):
+ cs.find_nearest_contour(1, 1, pixel=False)
+
+ with pytest.raises(ValueError, match="Method does not support filled contours"):
+ cs.find_nearest_contour(1, 10, indices=(5, 7), pixel=False)
+
+ with pytest.raises(ValueError, match="Method does not support filled contours"):
+ cs.find_nearest_contour(2, 5, indices=(2, 7), pixel=True)
+
+
+@mpl.style.context("default")
+def test_contour_autolabel_beyond_powerlimits():
+ ax = plt.figure().add_subplot()
+ cs = plt.contour(np.geomspace(1e-6, 1e-4, 100).reshape(10, 10),
+ levels=[.25e-5, 1e-5, 4e-5])
+ ax.clabel(cs)
+ # Currently, the exponent is missing, but that may be fixed in the future.
+ assert {text.get_text() for text in ax.texts} == {"0.25", "1.00", "4.00"}
+
+
+def test_contourf_legend_elements():
+ from matplotlib.patches import Rectangle
+ x = np.arange(1, 10)
+ y = x.reshape(-1, 1)
+ h = x * y
+
+ cs = plt.contourf(h, levels=[10, 30, 50],
+ colors=['#FFFF00', '#FF00FF', '#00FFFF'],
+ extend='both')
+ cs.cmap.set_over('red')
+ cs.cmap.set_under('blue')
+ cs.changed()
+ artists, labels = cs.legend_elements()
+ assert labels == ['$x \\leq -1e+250s$',
+ '$10.0 < x \\leq 30.0$',
+ '$30.0 < x \\leq 50.0$',
+ '$x > 1e+250s$']
+ expected_colors = ('blue', '#FFFF00', '#FF00FF', 'red')
+ assert all(isinstance(a, Rectangle) for a in artists)
+ assert all(same_color(a.get_facecolor(), c)
+ for a, c in zip(artists, expected_colors))
+
+
+def test_contour_legend_elements():
+ x = np.arange(1, 10)
+ y = x.reshape(-1, 1)
+ h = x * y
+
+ colors = ['blue', '#00FF00', 'red']
+ cs = plt.contour(h, levels=[10, 30, 50],
+ colors=colors,
+ extend='both')
+ artists, labels = cs.legend_elements()
+ assert labels == ['$x = 10.0$', '$x = 30.0$', '$x = 50.0$']
+ assert all(isinstance(a, mpl.lines.Line2D) for a in artists)
+ assert all(same_color(a.get_color(), c)
+ for a, c in zip(artists, colors))
+
+
+@pytest.mark.parametrize(
+ "algorithm, klass",
+ [('mpl2005', contourpy.Mpl2005ContourGenerator),
+ ('mpl2014', contourpy.Mpl2014ContourGenerator),
+ ('serial', contourpy.SerialContourGenerator),
+ ('threaded', contourpy.ThreadedContourGenerator),
+ ('invalid', None)])
+def test_algorithm_name(algorithm, klass):
+ z = np.array([[1.0, 2.0], [3.0, 4.0]])
+ if klass is not None:
+ cs = plt.contourf(z, algorithm=algorithm)
+ assert isinstance(cs._contour_generator, klass)
+ else:
+ with pytest.raises(ValueError):
+ plt.contourf(z, algorithm=algorithm)
+
+
+@pytest.mark.parametrize(
+ "algorithm", ['mpl2005', 'mpl2014', 'serial', 'threaded'])
+def test_algorithm_supports_corner_mask(algorithm):
+ z = np.array([[1.0, 2.0], [3.0, 4.0]])
+
+ # All algorithms support corner_mask=False
+ plt.contourf(z, algorithm=algorithm, corner_mask=False)
+
+ # Only some algorithms support corner_mask=True
+ if algorithm != 'mpl2005':
+ plt.contourf(z, algorithm=algorithm, corner_mask=True)
+ else:
+ with pytest.raises(ValueError):
+ plt.contourf(z, algorithm=algorithm, corner_mask=True)
+
+
+@image_comparison(baseline_images=['contour_all_algorithms'],
+ extensions=['png'], remove_text=True, tol=0.06)
+def test_all_algorithms():
+ algorithms = ['mpl2005', 'mpl2014', 'serial', 'threaded']
+
+ rng = np.random.default_rng(2981)
+ x, y = np.meshgrid(np.linspace(0.0, 1.0, 10), np.linspace(0.0, 1.0, 6))
+ z = np.sin(15*x)*np.cos(10*y) + rng.normal(scale=0.5, size=(6, 10))
+ mask = np.zeros_like(z, dtype=bool)
+ mask[3, 7] = True
+ z = np.ma.array(z, mask=mask)
+
+ _, axs = plt.subplots(2, 2)
+ for ax, algorithm in zip(axs.ravel(), algorithms):
+ ax.contourf(x, y, z, algorithm=algorithm)
+ ax.contour(x, y, z, algorithm=algorithm, colors='k')
+ ax.set_title(algorithm)
+
+
+def test_subfigure_clabel():
+ # Smoke test for gh#23173
+ delta = 0.025
+ x = np.arange(-3.0, 3.0, delta)
+ y = np.arange(-2.0, 2.0, delta)
+ X, Y = np.meshgrid(x, y)
+ Z1 = np.exp(-(X**2) - Y**2)
+ Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)
+ Z = (Z1 - Z2) * 2
+
+ fig = plt.figure()
+ figs = fig.subfigures(nrows=1, ncols=2)
+
+ for f in figs:
+ ax = f.subplots()
+ CS = ax.contour(X, Y, Z)
+ ax.clabel(CS, inline=True, fontsize=10)
+ ax.set_title("Simplest default with labels")
+
+
+@pytest.mark.parametrize(
+ "style", ['solid', 'dashed', 'dashdot', 'dotted'])
+def test_linestyles(style):
+ delta = 0.025
+ x = np.arange(-3.0, 3.0, delta)
+ y = np.arange(-2.0, 2.0, delta)
+ X, Y = np.meshgrid(x, y)
+ Z1 = np.exp(-X**2 - Y**2)
+ Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
+ Z = (Z1 - Z2) * 2
+
+ # Positive contour defaults to solid
+ fig1, ax1 = plt.subplots()
+ CS1 = ax1.contour(X, Y, Z, 6, colors='k')
+ ax1.clabel(CS1, fontsize=9, inline=True)
+ ax1.set_title('Single color - positive contours solid (default)')
+ assert CS1.linestyles is None # default
+
+ # Change linestyles using linestyles kwarg
+ fig2, ax2 = plt.subplots()
+ CS2 = ax2.contour(X, Y, Z, 6, colors='k', linestyles=style)
+ ax2.clabel(CS2, fontsize=9, inline=True)
+ ax2.set_title(f'Single color - positive contours {style}')
+ assert CS2.linestyles == style
+
+ # Ensure linestyles do not change when negative_linestyles is defined
+ fig3, ax3 = plt.subplots()
+ CS3 = ax3.contour(X, Y, Z, 6, colors='k', linestyles=style,
+ negative_linestyles='dashdot')
+ ax3.clabel(CS3, fontsize=9, inline=True)
+ ax3.set_title(f'Single color - positive contours {style}')
+ assert CS3.linestyles == style
+
+
+@pytest.mark.parametrize(
+ "style", ['solid', 'dashed', 'dashdot', 'dotted'])
+def test_negative_linestyles(style):
+ delta = 0.025
+ x = np.arange(-3.0, 3.0, delta)
+ y = np.arange(-2.0, 2.0, delta)
+ X, Y = np.meshgrid(x, y)
+ Z1 = np.exp(-X**2 - Y**2)
+ Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
+ Z = (Z1 - Z2) * 2
+
+ # Negative contour defaults to dashed
+ fig1, ax1 = plt.subplots()
+ CS1 = ax1.contour(X, Y, Z, 6, colors='k')
+ ax1.clabel(CS1, fontsize=9, inline=True)
+ ax1.set_title('Single color - negative contours dashed (default)')
+ assert CS1.negative_linestyles == 'dashed' # default
+
+ # Change negative_linestyles using rcParams
+ plt.rcParams['contour.negative_linestyle'] = style
+ fig2, ax2 = plt.subplots()
+ CS2 = ax2.contour(X, Y, Z, 6, colors='k')
+ ax2.clabel(CS2, fontsize=9, inline=True)
+ ax2.set_title(f'Single color - negative contours {style}'
+ '(using rcParams)')
+ assert CS2.negative_linestyles == style
+
+ # Change negative_linestyles using negative_linestyles kwarg
+ fig3, ax3 = plt.subplots()
+ CS3 = ax3.contour(X, Y, Z, 6, colors='k', negative_linestyles=style)
+ ax3.clabel(CS3, fontsize=9, inline=True)
+ ax3.set_title(f'Single color - negative contours {style}')
+ assert CS3.negative_linestyles == style
+
+ # Ensure negative_linestyles do not change when linestyles is defined
+ fig4, ax4 = plt.subplots()
+ CS4 = ax4.contour(X, Y, Z, 6, colors='k', linestyles='dashdot',
+ negative_linestyles=style)
+ ax4.clabel(CS4, fontsize=9, inline=True)
+ ax4.set_title(f'Single color - negative contours {style}')
+ assert CS4.negative_linestyles == style
+
+
+def test_contour_remove():
+ ax = plt.figure().add_subplot()
+ orig_children = ax.get_children()
+ cs = ax.contour(np.arange(16).reshape((4, 4)))
+ cs.clabel()
+ assert ax.get_children() != orig_children
+ cs.remove()
+ assert ax.get_children() == orig_children
+
+
+def test_contour_no_args():
+ fig, ax = plt.subplots()
+ data = [[0, 1], [1, 0]]
+ with pytest.raises(TypeError, match=r"contour\(\) takes from 1 to 4"):
+ ax.contour(Z=data)
+
+
+def test_contour_clip_path():
+ fig, ax = plt.subplots()
+ data = [[0, 1], [1, 0]]
+ circle = mpatches.Circle([0.5, 0.5], 0.5, transform=ax.transAxes)
+ cs = ax.contour(data, clip_path=circle)
+ assert cs.get_clip_path() is not None
+
+
+def test_bool_autolevel():
+ x, y = np.random.rand(2, 9)
+ z = (np.arange(9) % 2).reshape((3, 3)).astype(bool)
+ m = [[False, False, False], [False, True, False], [False, False, False]]
+ assert plt.contour(z.tolist()).levels.tolist() == [.5]
+ assert plt.contour(z).levels.tolist() == [.5]
+ assert plt.contour(np.ma.array(z, mask=m)).levels.tolist() == [.5]
+ assert plt.contourf(z.tolist()).levels.tolist() == [0, .5, 1]
+ assert plt.contourf(z).levels.tolist() == [0, .5, 1]
+ assert plt.contourf(np.ma.array(z, mask=m)).levels.tolist() == [0, .5, 1]
+ z = z.ravel()
+ assert plt.tricontour(x, y, z.tolist()).levels.tolist() == [.5]
+ assert plt.tricontour(x, y, z).levels.tolist() == [.5]
+ assert plt.tricontourf(x, y, z.tolist()).levels.tolist() == [0, .5, 1]
+ assert plt.tricontourf(x, y, z).levels.tolist() == [0, .5, 1]
+
+
+def test_all_nan():
+ x = np.array([[np.nan, np.nan], [np.nan, np.nan]])
+ assert_array_almost_equal(plt.contour(x).levels,
+ [-1e-13, -7.5e-14, -5e-14, -2.4e-14, 0.0,
+ 2.4e-14, 5e-14, 7.5e-14, 1e-13])
+
+
+def test_allsegs_allkinds():
+ x, y = np.meshgrid(np.arange(0, 10, 2), np.arange(0, 10, 2))
+ z = np.sin(x) * np.cos(y)
+
+ cs = plt.contour(x, y, z, levels=[0, 0.5])
+
+ # Expect two levels, the first with 5 segments and the second with 4.
+ for result in [cs.allsegs, cs.allkinds]:
+ assert len(result) == 2
+ assert len(result[0]) == 5
+ assert len(result[1]) == 4
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_cycles.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_cycles.py
new file mode 100644
index 0000000000000000000000000000000000000000..4fa261619490ba73cb08bf953f3e385c2d99fc5c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_cycles.py
@@ -0,0 +1,175 @@
+import contextlib
+from io import StringIO
+
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+import numpy as np
+import pytest
+
+from cycler import cycler
+
+
+def test_colorcycle_basic():
+ fig, ax = plt.subplots()
+ ax.set_prop_cycle(cycler('color', ['r', 'g', 'y']))
+ for _ in range(4):
+ ax.plot(range(10), range(10))
+ assert [l.get_color() for l in ax.lines] == ['r', 'g', 'y', 'r']
+
+
+def test_marker_cycle():
+ fig, ax = plt.subplots()
+ ax.set_prop_cycle(cycler('c', ['r', 'g', 'y']) +
+ cycler('marker', ['.', '*', 'x']))
+ for _ in range(4):
+ ax.plot(range(10), range(10))
+ assert [l.get_color() for l in ax.lines] == ['r', 'g', 'y', 'r']
+ assert [l.get_marker() for l in ax.lines] == ['.', '*', 'x', '.']
+
+
+def test_valid_marker_cycles():
+ fig, ax = plt.subplots()
+ ax.set_prop_cycle(cycler(marker=[1, "+", ".", 4]))
+
+
+def test_marker_cycle_kwargs_arrays_iterators():
+ fig, ax = plt.subplots()
+ ax.set_prop_cycle(c=np.array(['r', 'g', 'y']),
+ marker=iter(['.', '*', 'x']))
+ for _ in range(4):
+ ax.plot(range(10), range(10))
+ assert [l.get_color() for l in ax.lines] == ['r', 'g', 'y', 'r']
+ assert [l.get_marker() for l in ax.lines] == ['.', '*', 'x', '.']
+
+
+def test_linestylecycle_basic():
+ fig, ax = plt.subplots()
+ ax.set_prop_cycle(cycler('ls', ['-', '--', ':']))
+ for _ in range(4):
+ ax.plot(range(10), range(10))
+ assert [l.get_linestyle() for l in ax.lines] == ['-', '--', ':', '-']
+
+
+def test_fillcycle_basic():
+ fig, ax = plt.subplots()
+ ax.set_prop_cycle(cycler('c', ['r', 'g', 'y']) +
+ cycler('hatch', ['xx', 'O', '|-']) +
+ cycler('linestyle', ['-', '--', ':']))
+ for _ in range(4):
+ ax.fill(range(10), range(10))
+ assert ([p.get_facecolor() for p in ax.patches]
+ == [mpl.colors.to_rgba(c) for c in ['r', 'g', 'y', 'r']])
+ assert [p.get_hatch() for p in ax.patches] == ['xx', 'O', '|-', 'xx']
+ assert [p.get_linestyle() for p in ax.patches] == ['-', '--', ':', '-']
+
+
+def test_fillcycle_ignore():
+ fig, ax = plt.subplots()
+ ax.set_prop_cycle(cycler('color', ['r', 'g', 'y']) +
+ cycler('hatch', ['xx', 'O', '|-']) +
+ cycler('marker', ['.', '*', 'D']))
+ t = range(10)
+ # Should not advance the cycler, even though there is an
+ # unspecified property in the cycler "marker".
+ # "marker" is not a Polygon property, and should be ignored.
+ ax.fill(t, t, 'r', hatch='xx')
+ # Allow the cycler to advance, but specify some properties
+ ax.fill(t, t, hatch='O')
+ ax.fill(t, t)
+ ax.fill(t, t)
+ assert ([p.get_facecolor() for p in ax.patches]
+ == [mpl.colors.to_rgba(c) for c in ['r', 'r', 'g', 'y']])
+ assert [p.get_hatch() for p in ax.patches] == ['xx', 'O', 'O', '|-']
+
+
+def test_property_collision_plot():
+ fig, ax = plt.subplots()
+ ax.set_prop_cycle('linewidth', [2, 4])
+ t = range(10)
+ for c in range(1, 4):
+ ax.plot(t, t, lw=0.1)
+ ax.plot(t, t)
+ ax.plot(t, t)
+ assert [l.get_linewidth() for l in ax.lines] == [0.1, 0.1, 0.1, 2, 4]
+
+
+def test_property_collision_fill():
+ fig, ax = plt.subplots()
+ ax.set_prop_cycle(linewidth=[2, 3, 4, 5, 6], facecolor='bgcmy')
+ t = range(10)
+ for c in range(1, 4):
+ ax.fill(t, t, lw=0.1)
+ ax.fill(t, t)
+ ax.fill(t, t)
+ assert ([p.get_facecolor() for p in ax.patches]
+ == [mpl.colors.to_rgba(c) for c in 'bgcmy'])
+ assert [p.get_linewidth() for p in ax.patches] == [0.1, 0.1, 0.1, 5, 6]
+
+
+def test_valid_input_forms():
+ fig, ax = plt.subplots()
+ # These should not raise an error.
+ ax.set_prop_cycle(None)
+ ax.set_prop_cycle(cycler('linewidth', [1, 2]))
+ ax.set_prop_cycle('color', 'rgywkbcm')
+ ax.set_prop_cycle('lw', (1, 2))
+ ax.set_prop_cycle('linewidth', [1, 2])
+ ax.set_prop_cycle('linewidth', iter([1, 2]))
+ ax.set_prop_cycle('linewidth', np.array([1, 2]))
+ ax.set_prop_cycle('color', np.array([[1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1]]))
+ ax.set_prop_cycle('dashes', [[], [13, 2], [8, 3, 1, 3]])
+ ax.set_prop_cycle(lw=[1, 2], color=['k', 'w'], ls=['-', '--'])
+ ax.set_prop_cycle(lw=np.array([1, 2]),
+ color=np.array(['k', 'w']),
+ ls=np.array(['-', '--']))
+
+
+def test_cycle_reset():
+ fig, ax = plt.subplots()
+ prop0 = StringIO()
+ prop1 = StringIO()
+ prop2 = StringIO()
+
+ with contextlib.redirect_stdout(prop0):
+ plt.getp(ax.plot([1, 2], label="label")[0])
+
+ ax.set_prop_cycle(linewidth=[10, 9, 4])
+ with contextlib.redirect_stdout(prop1):
+ plt.getp(ax.plot([1, 2], label="label")[0])
+ assert prop1.getvalue() != prop0.getvalue()
+
+ ax.set_prop_cycle(None)
+ with contextlib.redirect_stdout(prop2):
+ plt.getp(ax.plot([1, 2], label="label")[0])
+ assert prop2.getvalue() == prop0.getvalue()
+
+
+def test_invalid_input_forms():
+ fig, ax = plt.subplots()
+
+ with pytest.raises((TypeError, ValueError)):
+ ax.set_prop_cycle(1)
+ with pytest.raises((TypeError, ValueError)):
+ ax.set_prop_cycle([1, 2])
+
+ with pytest.raises((TypeError, ValueError)):
+ ax.set_prop_cycle('color', 'fish')
+
+ with pytest.raises((TypeError, ValueError)):
+ ax.set_prop_cycle('linewidth', 1)
+ with pytest.raises((TypeError, ValueError)):
+ ax.set_prop_cycle('linewidth', {1, 2})
+ with pytest.raises((TypeError, ValueError)):
+ ax.set_prop_cycle(linewidth=1, color='r')
+
+ with pytest.raises((TypeError, ValueError)):
+ ax.set_prop_cycle('foobar', [1, 2])
+ with pytest.raises((TypeError, ValueError)):
+ ax.set_prop_cycle(foobar=[1, 2])
+
+ with pytest.raises((TypeError, ValueError)):
+ ax.set_prop_cycle(cycler(foobar=[1, 2]))
+ with pytest.raises(ValueError):
+ ax.set_prop_cycle(cycler(color='rgb', c='cmy'))
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_dates.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_dates.py
new file mode 100644
index 0000000000000000000000000000000000000000..73f10cec52aac86f20d7bd1de1938b970ad9a9bc
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_dates.py
@@ -0,0 +1,1413 @@
+import datetime
+
+import dateutil.tz
+import dateutil.rrule
+import functools
+import numpy as np
+import pytest
+
+from matplotlib import rc_context, style
+import matplotlib.dates as mdates
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.ticker as mticker
+
+
+def test_date_numpyx():
+ # test that numpy dates work properly...
+ base = datetime.datetime(2017, 1, 1)
+ time = [base + datetime.timedelta(days=x) for x in range(0, 3)]
+ timenp = np.array(time, dtype='datetime64[ns]')
+ data = np.array([0., 2., 1.])
+ fig = plt.figure(figsize=(10, 2))
+ ax = fig.add_subplot(1, 1, 1)
+ h, = ax.plot(time, data)
+ hnp, = ax.plot(timenp, data)
+ np.testing.assert_equal(h.get_xdata(orig=False), hnp.get_xdata(orig=False))
+ fig = plt.figure(figsize=(10, 2))
+ ax = fig.add_subplot(1, 1, 1)
+ h, = ax.plot(data, time)
+ hnp, = ax.plot(data, timenp)
+ np.testing.assert_equal(h.get_ydata(orig=False), hnp.get_ydata(orig=False))
+
+
+@pytest.mark.parametrize('t0', [datetime.datetime(2017, 1, 1, 0, 1, 1),
+
+ [datetime.datetime(2017, 1, 1, 0, 1, 1),
+ datetime.datetime(2017, 1, 1, 1, 1, 1)],
+
+ [[datetime.datetime(2017, 1, 1, 0, 1, 1),
+ datetime.datetime(2017, 1, 1, 1, 1, 1)],
+ [datetime.datetime(2017, 1, 1, 2, 1, 1),
+ datetime.datetime(2017, 1, 1, 3, 1, 1)]]])
+@pytest.mark.parametrize('dtype', ['datetime64[s]',
+ 'datetime64[us]',
+ 'datetime64[ms]',
+ 'datetime64[ns]'])
+def test_date_date2num_numpy(t0, dtype):
+ time = mdates.date2num(t0)
+ tnp = np.array(t0, dtype=dtype)
+ nptime = mdates.date2num(tnp)
+ np.testing.assert_equal(time, nptime)
+
+
+@pytest.mark.parametrize('dtype', ['datetime64[s]',
+ 'datetime64[us]',
+ 'datetime64[ms]',
+ 'datetime64[ns]'])
+def test_date2num_NaT(dtype):
+ t0 = datetime.datetime(2017, 1, 1, 0, 1, 1)
+ tmpl = [mdates.date2num(t0), np.nan]
+ tnp = np.array([t0, 'NaT'], dtype=dtype)
+ nptime = mdates.date2num(tnp)
+ np.testing.assert_array_equal(tmpl, nptime)
+
+
+@pytest.mark.parametrize('units', ['s', 'ms', 'us', 'ns'])
+def test_date2num_NaT_scalar(units):
+ tmpl = mdates.date2num(np.datetime64('NaT', units))
+ assert np.isnan(tmpl)
+
+
+def test_date2num_masked():
+ # Without tzinfo
+ base = datetime.datetime(2022, 12, 15)
+ dates = np.ma.array([base + datetime.timedelta(days=(2 * i))
+ for i in range(7)], mask=[0, 1, 1, 0, 0, 0, 1])
+ npdates = mdates.date2num(dates)
+ np.testing.assert_array_equal(np.ma.getmask(npdates),
+ (False, True, True, False, False, False,
+ True))
+
+ # With tzinfo
+ base = datetime.datetime(2022, 12, 15, tzinfo=mdates.UTC)
+ dates = np.ma.array([base + datetime.timedelta(days=(2 * i))
+ for i in range(7)], mask=[0, 1, 1, 0, 0, 0, 1])
+ npdates = mdates.date2num(dates)
+ np.testing.assert_array_equal(np.ma.getmask(npdates),
+ (False, True, True, False, False, False,
+ True))
+
+
+def test_date_empty():
+ # make sure we do the right thing when told to plot dates even
+ # if no date data has been presented, cf
+ # http://sourceforge.net/tracker/?func=detail&aid=2850075&group_id=80706&atid=560720
+ fig, ax = plt.subplots()
+ ax.xaxis_date()
+ fig.draw_without_rendering()
+ np.testing.assert_allclose(ax.get_xlim(),
+ [mdates.date2num(np.datetime64('1970-01-01')),
+ mdates.date2num(np.datetime64('1970-01-02'))])
+
+ mdates._reset_epoch_test_example()
+ mdates.set_epoch('0000-12-31')
+ fig, ax = plt.subplots()
+ ax.xaxis_date()
+ fig.draw_without_rendering()
+ np.testing.assert_allclose(ax.get_xlim(),
+ [mdates.date2num(np.datetime64('1970-01-01')),
+ mdates.date2num(np.datetime64('1970-01-02'))])
+ mdates._reset_epoch_test_example()
+
+
+def test_date_not_empty():
+ fig = plt.figure()
+ ax = fig.add_subplot()
+
+ ax.plot([50, 70], [1, 2])
+ ax.xaxis.axis_date()
+ np.testing.assert_allclose(ax.get_xlim(), [50, 70])
+
+
+def test_axhline():
+ # make sure that axhline doesn't set the xlimits...
+ fig, ax = plt.subplots()
+ ax.axhline(1.5)
+ ax.plot([np.datetime64('2016-01-01'), np.datetime64('2016-01-02')], [1, 2])
+ np.testing.assert_allclose(ax.get_xlim(),
+ [mdates.date2num(np.datetime64('2016-01-01')),
+ mdates.date2num(np.datetime64('2016-01-02'))])
+
+ mdates._reset_epoch_test_example()
+ mdates.set_epoch('0000-12-31')
+ fig, ax = plt.subplots()
+ ax.axhline(1.5)
+ ax.plot([np.datetime64('2016-01-01'), np.datetime64('2016-01-02')], [1, 2])
+ np.testing.assert_allclose(ax.get_xlim(),
+ [mdates.date2num(np.datetime64('2016-01-01')),
+ mdates.date2num(np.datetime64('2016-01-02'))])
+ mdates._reset_epoch_test_example()
+
+
+@image_comparison(['date_axhspan.png'])
+def test_date_axhspan():
+ # test axhspan with date inputs
+ t0 = datetime.datetime(2009, 1, 20)
+ tf = datetime.datetime(2009, 1, 21)
+ fig, ax = plt.subplots()
+ ax.axhspan(t0, tf, facecolor="blue", alpha=0.25)
+ ax.set_ylim(t0 - datetime.timedelta(days=5),
+ tf + datetime.timedelta(days=5))
+ fig.subplots_adjust(left=0.25)
+
+
+@image_comparison(['date_axvspan.png'])
+def test_date_axvspan():
+ # test axvspan with date inputs
+ t0 = datetime.datetime(2000, 1, 20)
+ tf = datetime.datetime(2010, 1, 21)
+ fig, ax = plt.subplots()
+ ax.axvspan(t0, tf, facecolor="blue", alpha=0.25)
+ ax.set_xlim(t0 - datetime.timedelta(days=720),
+ tf + datetime.timedelta(days=720))
+ fig.autofmt_xdate()
+
+
+@image_comparison(['date_axhline.png'])
+def test_date_axhline():
+ # test axhline with date inputs
+ t0 = datetime.datetime(2009, 1, 20)
+ tf = datetime.datetime(2009, 1, 31)
+ fig, ax = plt.subplots()
+ ax.axhline(t0, color="blue", lw=3)
+ ax.set_ylim(t0 - datetime.timedelta(days=5),
+ tf + datetime.timedelta(days=5))
+ fig.subplots_adjust(left=0.25)
+
+
+@image_comparison(['date_axvline.png'])
+def test_date_axvline():
+ # test axvline with date inputs
+ t0 = datetime.datetime(2000, 1, 20)
+ tf = datetime.datetime(2000, 1, 21)
+ fig, ax = plt.subplots()
+ ax.axvline(t0, color="red", lw=3)
+ ax.set_xlim(t0 - datetime.timedelta(days=5),
+ tf + datetime.timedelta(days=5))
+ fig.autofmt_xdate()
+
+
+def test_too_many_date_ticks(caplog):
+ # Attempt to test SF 2715172, see
+ # https://sourceforge.net/tracker/?func=detail&aid=2715172&group_id=80706&atid=560720
+ # setting equal datetimes triggers and expander call in
+ # transforms.nonsingular which results in too many ticks in the
+ # DayLocator. This should emit a log at WARNING level.
+ caplog.set_level("WARNING")
+ t0 = datetime.datetime(2000, 1, 20)
+ tf = datetime.datetime(2000, 1, 20)
+ fig, ax = plt.subplots()
+ with pytest.warns(UserWarning) as rec:
+ ax.set_xlim((t0, tf), auto=True)
+ assert len(rec) == 1
+ assert ('Attempting to set identical low and high xlims'
+ in str(rec[0].message))
+ ax.plot([], [])
+ ax.xaxis.set_major_locator(mdates.DayLocator())
+ v = ax.xaxis.get_major_locator()()
+ assert len(v) > 1000
+ # The warning is emitted multiple times because the major locator is also
+ # called both when placing the minor ticks (for overstriking detection) and
+ # during tick label positioning.
+ assert caplog.records and all(
+ record.name == "matplotlib.ticker" and record.levelname == "WARNING"
+ for record in caplog.records)
+ assert len(caplog.records) > 0
+
+
+def _new_epoch_decorator(thefunc):
+ @functools.wraps(thefunc)
+ def wrapper():
+ mdates._reset_epoch_test_example()
+ mdates.set_epoch('2000-01-01')
+ thefunc()
+ mdates._reset_epoch_test_example()
+ return wrapper
+
+
+@image_comparison(['RRuleLocator_bounds.png'])
+def test_RRuleLocator():
+ import matplotlib.testing.jpl_units as units
+ units.register()
+ # This will cause the RRuleLocator to go out of bounds when it tries
+ # to add padding to the limits, so we make sure it caps at the correct
+ # boundary values.
+ t0 = datetime.datetime(1000, 1, 1)
+ tf = datetime.datetime(6000, 1, 1)
+
+ fig = plt.figure()
+ ax = plt.subplot()
+ ax.set_autoscale_on(True)
+ ax.plot([t0, tf], [0.0, 1.0], marker='o')
+
+ rrule = mdates.rrulewrapper(dateutil.rrule.YEARLY, interval=500)
+ locator = mdates.RRuleLocator(rrule)
+ ax.xaxis.set_major_locator(locator)
+ ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(locator))
+
+ ax.autoscale_view()
+ fig.autofmt_xdate()
+
+
+def test_RRuleLocator_dayrange():
+ loc = mdates.DayLocator()
+ x1 = datetime.datetime(year=1, month=1, day=1, tzinfo=mdates.UTC)
+ y1 = datetime.datetime(year=1, month=1, day=16, tzinfo=mdates.UTC)
+ loc.tick_values(x1, y1)
+ # On success, no overflow error shall be thrown
+
+
+def test_RRuleLocator_close_minmax():
+ # if d1 and d2 are very close together, rrule cannot create
+ # reasonable tick intervals; ensure that this is handled properly
+ rrule = mdates.rrulewrapper(dateutil.rrule.SECONDLY, interval=5)
+ loc = mdates.RRuleLocator(rrule)
+ d1 = datetime.datetime(year=2020, month=1, day=1)
+ d2 = datetime.datetime(year=2020, month=1, day=1, microsecond=1)
+ expected = ['2020-01-01 00:00:00+00:00',
+ '2020-01-01 00:00:00.000001+00:00']
+ assert list(map(str, mdates.num2date(loc.tick_values(d1, d2)))) == expected
+
+
+@image_comparison(['DateFormatter_fractionalSeconds.png'])
+def test_DateFormatter():
+ import matplotlib.testing.jpl_units as units
+ units.register()
+
+ # Lets make sure that DateFormatter will allow us to have tick marks
+ # at intervals of fractional seconds.
+
+ t0 = datetime.datetime(2001, 1, 1, 0, 0, 0)
+ tf = datetime.datetime(2001, 1, 1, 0, 0, 1)
+
+ fig = plt.figure()
+ ax = plt.subplot()
+ ax.set_autoscale_on(True)
+ ax.plot([t0, tf], [0.0, 1.0], marker='o')
+
+ # rrule = mpldates.rrulewrapper( dateutil.rrule.YEARLY, interval=500 )
+ # locator = mpldates.RRuleLocator( rrule )
+ # ax.xaxis.set_major_locator( locator )
+ # ax.xaxis.set_major_formatter( mpldates.AutoDateFormatter(locator) )
+
+ ax.autoscale_view()
+ fig.autofmt_xdate()
+
+
+def test_locator_set_formatter():
+ """
+ Test if setting the locator only will update the AutoDateFormatter to use
+ the new locator.
+ """
+ plt.rcParams["date.autoformatter.minute"] = "%d %H:%M"
+ t = [datetime.datetime(2018, 9, 30, 8, 0),
+ datetime.datetime(2018, 9, 30, 8, 59),
+ datetime.datetime(2018, 9, 30, 10, 30)]
+ x = [2, 3, 1]
+
+ fig, ax = plt.subplots()
+ ax.plot(t, x)
+ ax.xaxis.set_major_locator(mdates.MinuteLocator((0, 30)))
+ fig.canvas.draw()
+ ticklabels = [tl.get_text() for tl in ax.get_xticklabels()]
+ expected = ['30 08:00', '30 08:30', '30 09:00',
+ '30 09:30', '30 10:00', '30 10:30']
+ assert ticklabels == expected
+
+ ax.xaxis.set_major_locator(mticker.NullLocator())
+ ax.xaxis.set_minor_locator(mdates.MinuteLocator((5, 55)))
+ decoy_loc = mdates.MinuteLocator((12, 27))
+ ax.xaxis.set_minor_formatter(mdates.AutoDateFormatter(decoy_loc))
+
+ ax.xaxis.set_minor_locator(mdates.MinuteLocator((15, 45)))
+ fig.canvas.draw()
+ ticklabels = [tl.get_text() for tl in ax.get_xticklabels(which="minor")]
+ expected = ['30 08:15', '30 08:45', '30 09:15', '30 09:45', '30 10:15']
+ assert ticklabels == expected
+
+
+def test_date_formatter_callable():
+
+ class _Locator:
+ def _get_unit(self): return -11
+
+ def callable_formatting_function(dates, _):
+ return [dt.strftime('%d-%m//%Y') for dt in dates]
+
+ formatter = mdates.AutoDateFormatter(_Locator())
+ formatter.scaled[-10] = callable_formatting_function
+ assert formatter([datetime.datetime(2014, 12, 25)]) == ['25-12//2014']
+
+
+@pytest.mark.parametrize('delta, expected', [
+ (datetime.timedelta(weeks=52 * 200),
+ [r'$\mathdefault{%d}$' % year for year in range(1990, 2171, 20)]),
+ (datetime.timedelta(days=30),
+ [r'$\mathdefault{1990{-}01{-}%02d}$' % day for day in range(1, 32, 3)]),
+ (datetime.timedelta(hours=20),
+ [r'$\mathdefault{01{-}01\;%02d}$' % hour for hour in range(0, 21, 2)]),
+ (datetime.timedelta(minutes=10),
+ [r'$\mathdefault{01\;00{:}%02d}$' % minu for minu in range(0, 11)]),
+])
+def test_date_formatter_usetex(delta, expected):
+ style.use("default")
+
+ d1 = datetime.datetime(1990, 1, 1)
+ d2 = d1 + delta
+
+ locator = mdates.AutoDateLocator(interval_multiples=False)
+ locator.create_dummy_axis()
+ locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2))
+
+ formatter = mdates.AutoDateFormatter(locator, usetex=True)
+ assert [formatter(loc) for loc in locator()] == expected
+
+
+def test_drange():
+ """
+ This test should check if drange works as expected, and if all the
+ rounding errors are fixed
+ """
+ start = datetime.datetime(2011, 1, 1, tzinfo=mdates.UTC)
+ end = datetime.datetime(2011, 1, 2, tzinfo=mdates.UTC)
+ delta = datetime.timedelta(hours=1)
+ # We expect 24 values in drange(start, end, delta), because drange returns
+ # dates from an half open interval [start, end)
+ assert len(mdates.drange(start, end, delta)) == 24
+
+ # Same if interval ends slightly earlier
+ end = end - datetime.timedelta(microseconds=1)
+ assert len(mdates.drange(start, end, delta)) == 24
+
+ # if end is a little bit later, we expect the range to contain one element
+ # more
+ end = end + datetime.timedelta(microseconds=2)
+ assert len(mdates.drange(start, end, delta)) == 25
+
+ # reset end
+ end = datetime.datetime(2011, 1, 2, tzinfo=mdates.UTC)
+
+ # and tst drange with "complicated" floats:
+ # 4 hours = 1/6 day, this is an "dangerous" float
+ delta = datetime.timedelta(hours=4)
+ daterange = mdates.drange(start, end, delta)
+ assert len(daterange) == 6
+ assert mdates.num2date(daterange[-1]) == (end - delta)
+
+
+@_new_epoch_decorator
+def test_auto_date_locator():
+ def _create_auto_date_locator(date1, date2):
+ locator = mdates.AutoDateLocator(interval_multiples=False)
+ locator.create_dummy_axis()
+ locator.axis.set_view_interval(*mdates.date2num([date1, date2]))
+ return locator
+
+ d1 = datetime.datetime(1990, 1, 1)
+ results = ([datetime.timedelta(weeks=52 * 200),
+ ['1990-01-01 00:00:00+00:00', '2010-01-01 00:00:00+00:00',
+ '2030-01-01 00:00:00+00:00', '2050-01-01 00:00:00+00:00',
+ '2070-01-01 00:00:00+00:00', '2090-01-01 00:00:00+00:00',
+ '2110-01-01 00:00:00+00:00', '2130-01-01 00:00:00+00:00',
+ '2150-01-01 00:00:00+00:00', '2170-01-01 00:00:00+00:00']
+ ],
+ [datetime.timedelta(weeks=52),
+ ['1990-01-01 00:00:00+00:00', '1990-02-01 00:00:00+00:00',
+ '1990-03-01 00:00:00+00:00', '1990-04-01 00:00:00+00:00',
+ '1990-05-01 00:00:00+00:00', '1990-06-01 00:00:00+00:00',
+ '1990-07-01 00:00:00+00:00', '1990-08-01 00:00:00+00:00',
+ '1990-09-01 00:00:00+00:00', '1990-10-01 00:00:00+00:00',
+ '1990-11-01 00:00:00+00:00', '1990-12-01 00:00:00+00:00']
+ ],
+ [datetime.timedelta(days=141),
+ ['1990-01-05 00:00:00+00:00', '1990-01-26 00:00:00+00:00',
+ '1990-02-16 00:00:00+00:00', '1990-03-09 00:00:00+00:00',
+ '1990-03-30 00:00:00+00:00', '1990-04-20 00:00:00+00:00',
+ '1990-05-11 00:00:00+00:00']
+ ],
+ [datetime.timedelta(days=40),
+ ['1990-01-03 00:00:00+00:00', '1990-01-10 00:00:00+00:00',
+ '1990-01-17 00:00:00+00:00', '1990-01-24 00:00:00+00:00',
+ '1990-01-31 00:00:00+00:00', '1990-02-07 00:00:00+00:00']
+ ],
+ [datetime.timedelta(hours=40),
+ ['1990-01-01 00:00:00+00:00', '1990-01-01 04:00:00+00:00',
+ '1990-01-01 08:00:00+00:00', '1990-01-01 12:00:00+00:00',
+ '1990-01-01 16:00:00+00:00', '1990-01-01 20:00:00+00:00',
+ '1990-01-02 00:00:00+00:00', '1990-01-02 04:00:00+00:00',
+ '1990-01-02 08:00:00+00:00', '1990-01-02 12:00:00+00:00',
+ '1990-01-02 16:00:00+00:00']
+ ],
+ [datetime.timedelta(minutes=20),
+ ['1990-01-01 00:00:00+00:00', '1990-01-01 00:05:00+00:00',
+ '1990-01-01 00:10:00+00:00', '1990-01-01 00:15:00+00:00',
+ '1990-01-01 00:20:00+00:00']
+ ],
+ [datetime.timedelta(seconds=40),
+ ['1990-01-01 00:00:00+00:00', '1990-01-01 00:00:05+00:00',
+ '1990-01-01 00:00:10+00:00', '1990-01-01 00:00:15+00:00',
+ '1990-01-01 00:00:20+00:00', '1990-01-01 00:00:25+00:00',
+ '1990-01-01 00:00:30+00:00', '1990-01-01 00:00:35+00:00',
+ '1990-01-01 00:00:40+00:00']
+ ],
+ [datetime.timedelta(microseconds=1500),
+ ['1989-12-31 23:59:59.999500+00:00',
+ '1990-01-01 00:00:00+00:00',
+ '1990-01-01 00:00:00.000500+00:00',
+ '1990-01-01 00:00:00.001000+00:00',
+ '1990-01-01 00:00:00.001500+00:00',
+ '1990-01-01 00:00:00.002000+00:00']
+ ],
+ )
+
+ for t_delta, expected in results:
+ d2 = d1 + t_delta
+ locator = _create_auto_date_locator(d1, d2)
+ assert list(map(str, mdates.num2date(locator()))) == expected
+
+ locator = mdates.AutoDateLocator(interval_multiples=False)
+ assert locator.maxticks == {0: 11, 1: 12, 3: 11, 4: 12, 5: 11, 6: 11, 7: 8}
+
+ locator = mdates.AutoDateLocator(maxticks={dateutil.rrule.MONTHLY: 5})
+ assert locator.maxticks == {0: 11, 1: 5, 3: 11, 4: 12, 5: 11, 6: 11, 7: 8}
+
+ locator = mdates.AutoDateLocator(maxticks=5)
+ assert locator.maxticks == {0: 5, 1: 5, 3: 5, 4: 5, 5: 5, 6: 5, 7: 5}
+
+
+@_new_epoch_decorator
+def test_auto_date_locator_intmult():
+ def _create_auto_date_locator(date1, date2):
+ locator = mdates.AutoDateLocator(interval_multiples=True)
+ locator.create_dummy_axis()
+ locator.axis.set_view_interval(*mdates.date2num([date1, date2]))
+ return locator
+
+ results = ([datetime.timedelta(weeks=52 * 200),
+ ['1980-01-01 00:00:00+00:00', '2000-01-01 00:00:00+00:00',
+ '2020-01-01 00:00:00+00:00', '2040-01-01 00:00:00+00:00',
+ '2060-01-01 00:00:00+00:00', '2080-01-01 00:00:00+00:00',
+ '2100-01-01 00:00:00+00:00', '2120-01-01 00:00:00+00:00',
+ '2140-01-01 00:00:00+00:00', '2160-01-01 00:00:00+00:00',
+ '2180-01-01 00:00:00+00:00', '2200-01-01 00:00:00+00:00']
+ ],
+ [datetime.timedelta(weeks=52),
+ ['1997-01-01 00:00:00+00:00', '1997-02-01 00:00:00+00:00',
+ '1997-03-01 00:00:00+00:00', '1997-04-01 00:00:00+00:00',
+ '1997-05-01 00:00:00+00:00', '1997-06-01 00:00:00+00:00',
+ '1997-07-01 00:00:00+00:00', '1997-08-01 00:00:00+00:00',
+ '1997-09-01 00:00:00+00:00', '1997-10-01 00:00:00+00:00',
+ '1997-11-01 00:00:00+00:00', '1997-12-01 00:00:00+00:00']
+ ],
+ [datetime.timedelta(days=141),
+ ['1997-01-01 00:00:00+00:00', '1997-01-15 00:00:00+00:00',
+ '1997-02-01 00:00:00+00:00', '1997-02-15 00:00:00+00:00',
+ '1997-03-01 00:00:00+00:00', '1997-03-15 00:00:00+00:00',
+ '1997-04-01 00:00:00+00:00', '1997-04-15 00:00:00+00:00',
+ '1997-05-01 00:00:00+00:00', '1997-05-15 00:00:00+00:00']
+ ],
+ [datetime.timedelta(days=40),
+ ['1997-01-01 00:00:00+00:00', '1997-01-05 00:00:00+00:00',
+ '1997-01-09 00:00:00+00:00', '1997-01-13 00:00:00+00:00',
+ '1997-01-17 00:00:00+00:00', '1997-01-21 00:00:00+00:00',
+ '1997-01-25 00:00:00+00:00', '1997-01-29 00:00:00+00:00',
+ '1997-02-01 00:00:00+00:00', '1997-02-05 00:00:00+00:00',
+ '1997-02-09 00:00:00+00:00']
+ ],
+ [datetime.timedelta(hours=40),
+ ['1997-01-01 00:00:00+00:00', '1997-01-01 04:00:00+00:00',
+ '1997-01-01 08:00:00+00:00', '1997-01-01 12:00:00+00:00',
+ '1997-01-01 16:00:00+00:00', '1997-01-01 20:00:00+00:00',
+ '1997-01-02 00:00:00+00:00', '1997-01-02 04:00:00+00:00',
+ '1997-01-02 08:00:00+00:00', '1997-01-02 12:00:00+00:00',
+ '1997-01-02 16:00:00+00:00']
+ ],
+ [datetime.timedelta(minutes=20),
+ ['1997-01-01 00:00:00+00:00', '1997-01-01 00:05:00+00:00',
+ '1997-01-01 00:10:00+00:00', '1997-01-01 00:15:00+00:00',
+ '1997-01-01 00:20:00+00:00']
+ ],
+ [datetime.timedelta(seconds=40),
+ ['1997-01-01 00:00:00+00:00', '1997-01-01 00:00:05+00:00',
+ '1997-01-01 00:00:10+00:00', '1997-01-01 00:00:15+00:00',
+ '1997-01-01 00:00:20+00:00', '1997-01-01 00:00:25+00:00',
+ '1997-01-01 00:00:30+00:00', '1997-01-01 00:00:35+00:00',
+ '1997-01-01 00:00:40+00:00']
+ ],
+ [datetime.timedelta(microseconds=1500),
+ ['1996-12-31 23:59:59.999500+00:00',
+ '1997-01-01 00:00:00+00:00',
+ '1997-01-01 00:00:00.000500+00:00',
+ '1997-01-01 00:00:00.001000+00:00',
+ '1997-01-01 00:00:00.001500+00:00',
+ '1997-01-01 00:00:00.002000+00:00']
+ ],
+ )
+
+ d1 = datetime.datetime(1997, 1, 1)
+ for t_delta, expected in results:
+ d2 = d1 + t_delta
+ locator = _create_auto_date_locator(d1, d2)
+ assert list(map(str, mdates.num2date(locator()))) == expected
+
+
+def test_concise_formatter_subsecond():
+ locator = mdates.AutoDateLocator(interval_multiples=True)
+ formatter = mdates.ConciseDateFormatter(locator)
+ year_1996 = 9861.0
+ strings = formatter.format_ticks([
+ year_1996,
+ year_1996 + 500 / mdates.MUSECONDS_PER_DAY,
+ year_1996 + 900 / mdates.MUSECONDS_PER_DAY])
+ assert strings == ['00:00', '00.0005', '00.0009']
+
+
+def test_concise_formatter():
+ def _create_auto_date_locator(date1, date2):
+ fig, ax = plt.subplots()
+
+ locator = mdates.AutoDateLocator(interval_multiples=True)
+ formatter = mdates.ConciseDateFormatter(locator)
+ ax.yaxis.set_major_locator(locator)
+ ax.yaxis.set_major_formatter(formatter)
+ ax.set_ylim(date1, date2)
+ fig.canvas.draw()
+ sts = [st.get_text() for st in ax.get_yticklabels()]
+ return sts
+
+ d1 = datetime.datetime(1997, 1, 1)
+ results = ([datetime.timedelta(weeks=52 * 200),
+ [str(t) for t in range(1980, 2201, 20)]
+ ],
+ [datetime.timedelta(weeks=52),
+ ['1997', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
+ 'Sep', 'Oct', 'Nov', 'Dec']
+ ],
+ [datetime.timedelta(days=141),
+ ['Jan', '15', 'Feb', '15', 'Mar', '15', 'Apr', '15',
+ 'May', '15']
+ ],
+ [datetime.timedelta(days=40),
+ ['Jan', '05', '09', '13', '17', '21', '25', '29', 'Feb',
+ '05', '09']
+ ],
+ [datetime.timedelta(hours=40),
+ ['Jan-01', '04:00', '08:00', '12:00', '16:00', '20:00',
+ 'Jan-02', '04:00', '08:00', '12:00', '16:00']
+ ],
+ [datetime.timedelta(minutes=20),
+ ['00:00', '00:05', '00:10', '00:15', '00:20']
+ ],
+ [datetime.timedelta(seconds=40),
+ ['00:00', '05', '10', '15', '20', '25', '30', '35', '40']
+ ],
+ [datetime.timedelta(seconds=2),
+ ['59.5', '00:00', '00.5', '01.0', '01.5', '02.0', '02.5']
+ ],
+ )
+ for t_delta, expected in results:
+ d2 = d1 + t_delta
+ strings = _create_auto_date_locator(d1, d2)
+ assert strings == expected
+
+
+@pytest.mark.parametrize('t_delta, expected', [
+ (datetime.timedelta(seconds=0.01), '1997-Jan-01 00:00'),
+ (datetime.timedelta(minutes=1), '1997-Jan-01 00:01'),
+ (datetime.timedelta(hours=1), '1997-Jan-01'),
+ (datetime.timedelta(days=1), '1997-Jan-02'),
+ (datetime.timedelta(weeks=1), '1997-Jan'),
+ (datetime.timedelta(weeks=26), ''),
+ (datetime.timedelta(weeks=520), '')
+])
+def test_concise_formatter_show_offset(t_delta, expected):
+ d1 = datetime.datetime(1997, 1, 1)
+ d2 = d1 + t_delta
+
+ fig, ax = plt.subplots()
+ locator = mdates.AutoDateLocator()
+ formatter = mdates.ConciseDateFormatter(locator)
+ ax.xaxis.set_major_locator(locator)
+ ax.xaxis.set_major_formatter(formatter)
+
+ ax.plot([d1, d2], [0, 0])
+ fig.canvas.draw()
+ assert formatter.get_offset() == expected
+
+
+def test_concise_formatter_show_offset_inverted():
+ # Test for github issue #28481
+ d1 = datetime.datetime(1997, 1, 1)
+ d2 = d1 + datetime.timedelta(days=60)
+
+ fig, ax = plt.subplots()
+ locator = mdates.AutoDateLocator()
+ formatter = mdates.ConciseDateFormatter(locator)
+ ax.xaxis.set_major_locator(locator)
+ ax.xaxis.set_major_formatter(formatter)
+ ax.invert_xaxis()
+
+ ax.plot([d1, d2], [0, 0])
+ fig.canvas.draw()
+ assert formatter.get_offset() == '1997-Jan'
+
+
+def test_concise_converter_stays():
+ # This test demonstrates problems introduced by gh-23417 (reverted in gh-25278)
+ # In particular, downstream libraries like Pandas had their designated converters
+ # overridden by actions like setting xlim (or plotting additional points using
+ # stdlib/numpy dates and string date representation, which otherwise work fine with
+ # their date converters)
+ # While this is a bit of a toy example that would be unusual to see it demonstrates
+ # the same ideas (namely having a valid converter already applied that is desired)
+ # without introducing additional subclasses.
+ # See also discussion at gh-25219 for how Pandas was affected
+ x = [datetime.datetime(2000, 1, 1), datetime.datetime(2020, 2, 20)]
+ y = [0, 1]
+ fig, ax = plt.subplots()
+ ax.plot(x, y)
+ # Bypass Switchable date converter
+ conv = mdates.ConciseDateConverter()
+ with pytest.warns(UserWarning, match="already has a converter"):
+ ax.xaxis.set_converter(conv)
+ assert ax.xaxis.units is None
+ ax.set_xlim(*x)
+ assert ax.xaxis.get_converter() == conv
+
+
+def test_offset_changes():
+ fig, ax = plt.subplots()
+
+ d1 = datetime.datetime(1997, 1, 1)
+ d2 = d1 + datetime.timedelta(weeks=520)
+
+ locator = mdates.AutoDateLocator()
+ formatter = mdates.ConciseDateFormatter(locator)
+ ax.xaxis.set_major_locator(locator)
+ ax.xaxis.set_major_formatter(formatter)
+
+ ax.plot([d1, d2], [0, 0])
+ fig.draw_without_rendering()
+ assert formatter.get_offset() == ''
+ ax.set_xlim(d1, d1 + datetime.timedelta(weeks=3))
+ fig.draw_without_rendering()
+ assert formatter.get_offset() == '1997-Jan'
+ ax.set_xlim(d1 + datetime.timedelta(weeks=7),
+ d1 + datetime.timedelta(weeks=30))
+ fig.draw_without_rendering()
+ assert formatter.get_offset() == '1997'
+ ax.set_xlim(d1, d1 + datetime.timedelta(weeks=520))
+ fig.draw_without_rendering()
+ assert formatter.get_offset() == ''
+
+
+@pytest.mark.parametrize('t_delta, expected', [
+ (datetime.timedelta(weeks=52 * 200),
+ ['$\\mathdefault{%d}$' % (t, ) for t in range(1980, 2201, 20)]),
+ (datetime.timedelta(days=40),
+ ['Jan', '$\\mathdefault{05}$', '$\\mathdefault{09}$',
+ '$\\mathdefault{13}$', '$\\mathdefault{17}$', '$\\mathdefault{21}$',
+ '$\\mathdefault{25}$', '$\\mathdefault{29}$', 'Feb',
+ '$\\mathdefault{05}$', '$\\mathdefault{09}$']),
+ (datetime.timedelta(hours=40),
+ ['Jan$\\mathdefault{{-}01}$', '$\\mathdefault{04{:}00}$',
+ '$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$',
+ '$\\mathdefault{16{:}00}$', '$\\mathdefault{20{:}00}$',
+ 'Jan$\\mathdefault{{-}02}$', '$\\mathdefault{04{:}00}$',
+ '$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$',
+ '$\\mathdefault{16{:}00}$']),
+ (datetime.timedelta(seconds=2),
+ ['$\\mathdefault{59.5}$', '$\\mathdefault{00{:}00}$',
+ '$\\mathdefault{00.5}$', '$\\mathdefault{01.0}$',
+ '$\\mathdefault{01.5}$', '$\\mathdefault{02.0}$',
+ '$\\mathdefault{02.5}$']),
+])
+def test_concise_formatter_usetex(t_delta, expected):
+ d1 = datetime.datetime(1997, 1, 1)
+ d2 = d1 + t_delta
+
+ locator = mdates.AutoDateLocator(interval_multiples=True)
+ locator.create_dummy_axis()
+ locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2))
+
+ formatter = mdates.ConciseDateFormatter(locator, usetex=True)
+ assert formatter.format_ticks(locator()) == expected
+
+
+def test_concise_formatter_formats():
+ formats = ['%Y', '%m/%Y', 'day: %d',
+ '%H hr %M min', '%H hr %M min', '%S.%f sec']
+
+ def _create_auto_date_locator(date1, date2):
+ fig, ax = plt.subplots()
+
+ locator = mdates.AutoDateLocator(interval_multiples=True)
+ formatter = mdates.ConciseDateFormatter(locator, formats=formats)
+ ax.yaxis.set_major_locator(locator)
+ ax.yaxis.set_major_formatter(formatter)
+ ax.set_ylim(date1, date2)
+ fig.canvas.draw()
+ sts = [st.get_text() for st in ax.get_yticklabels()]
+ return sts
+
+ d1 = datetime.datetime(1997, 1, 1)
+ results = (
+ [datetime.timedelta(weeks=52 * 200), [str(t) for t in range(1980,
+ 2201, 20)]],
+ [datetime.timedelta(weeks=52), [
+ '1997', '02/1997', '03/1997', '04/1997', '05/1997', '06/1997',
+ '07/1997', '08/1997', '09/1997', '10/1997', '11/1997', '12/1997',
+ ]],
+ [datetime.timedelta(days=141), [
+ '01/1997', 'day: 15', '02/1997', 'day: 15', '03/1997', 'day: 15',
+ '04/1997', 'day: 15', '05/1997', 'day: 15',
+ ]],
+ [datetime.timedelta(days=40), [
+ '01/1997', 'day: 05', 'day: 09', 'day: 13', 'day: 17', 'day: 21',
+ 'day: 25', 'day: 29', '02/1997', 'day: 05', 'day: 09',
+ ]],
+ [datetime.timedelta(hours=40), [
+ 'day: 01', '04 hr 00 min', '08 hr 00 min', '12 hr 00 min',
+ '16 hr 00 min', '20 hr 00 min', 'day: 02', '04 hr 00 min',
+ '08 hr 00 min', '12 hr 00 min', '16 hr 00 min',
+ ]],
+ [datetime.timedelta(minutes=20), ['00 hr 00 min', '00 hr 05 min',
+ '00 hr 10 min', '00 hr 15 min', '00 hr 20 min']],
+ [datetime.timedelta(seconds=40), [
+ '00 hr 00 min', '05.000000 sec', '10.000000 sec',
+ '15.000000 sec', '20.000000 sec', '25.000000 sec',
+ '30.000000 sec', '35.000000 sec', '40.000000 sec',
+ ]],
+ [datetime.timedelta(seconds=2), [
+ '59.500000 sec', '00 hr 00 min', '00.500000 sec', '01.000000 sec',
+ '01.500000 sec', '02.000000 sec', '02.500000 sec',
+ ]],
+ )
+ for t_delta, expected in results:
+ d2 = d1 + t_delta
+ strings = _create_auto_date_locator(d1, d2)
+ assert strings == expected
+
+
+def test_concise_formatter_zformats():
+ zero_formats = ['', "'%y", '%B', '%m-%d', '%S', '%S.%f']
+
+ def _create_auto_date_locator(date1, date2):
+ fig, ax = plt.subplots()
+
+ locator = mdates.AutoDateLocator(interval_multiples=True)
+ formatter = mdates.ConciseDateFormatter(
+ locator, zero_formats=zero_formats)
+ ax.yaxis.set_major_locator(locator)
+ ax.yaxis.set_major_formatter(formatter)
+ ax.set_ylim(date1, date2)
+ fig.canvas.draw()
+ sts = [st.get_text() for st in ax.get_yticklabels()]
+ return sts
+
+ d1 = datetime.datetime(1997, 1, 1)
+ results = ([datetime.timedelta(weeks=52 * 200),
+ [str(t) for t in range(1980, 2201, 20)]
+ ],
+ [datetime.timedelta(weeks=52),
+ ["'97", 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+ ],
+ [datetime.timedelta(days=141),
+ ['January', '15', 'February', '15', 'March',
+ '15', 'April', '15', 'May', '15']
+ ],
+ [datetime.timedelta(days=40),
+ ['January', '05', '09', '13', '17', '21',
+ '25', '29', 'February', '05', '09']
+ ],
+ [datetime.timedelta(hours=40),
+ ['01-01', '04:00', '08:00', '12:00', '16:00', '20:00',
+ '01-02', '04:00', '08:00', '12:00', '16:00']
+ ],
+ [datetime.timedelta(minutes=20),
+ ['00', '00:05', '00:10', '00:15', '00:20']
+ ],
+ [datetime.timedelta(seconds=40),
+ ['00', '05', '10', '15', '20', '25', '30', '35', '40']
+ ],
+ [datetime.timedelta(seconds=2),
+ ['59.5', '00.0', '00.5', '01.0', '01.5', '02.0', '02.5']
+ ],
+ )
+ for t_delta, expected in results:
+ d2 = d1 + t_delta
+ strings = _create_auto_date_locator(d1, d2)
+ assert strings == expected
+
+
+def test_concise_formatter_tz():
+ def _create_auto_date_locator(date1, date2, tz):
+ fig, ax = plt.subplots()
+
+ locator = mdates.AutoDateLocator(interval_multiples=True)
+ formatter = mdates.ConciseDateFormatter(locator, tz=tz)
+ ax.yaxis.set_major_locator(locator)
+ ax.yaxis.set_major_formatter(formatter)
+ ax.set_ylim(date1, date2)
+ fig.canvas.draw()
+ sts = [st.get_text() for st in ax.get_yticklabels()]
+ return sts, ax.yaxis.get_offset_text().get_text()
+
+ d1 = datetime.datetime(1997, 1, 1).replace(tzinfo=datetime.timezone.utc)
+ results = ([datetime.timedelta(hours=40),
+ ['03:00', '07:00', '11:00', '15:00', '19:00', '23:00',
+ '03:00', '07:00', '11:00', '15:00', '19:00'],
+ "1997-Jan-02"
+ ],
+ [datetime.timedelta(minutes=20),
+ ['03:00', '03:05', '03:10', '03:15', '03:20'],
+ "1997-Jan-01"
+ ],
+ [datetime.timedelta(seconds=40),
+ ['03:00', '05', '10', '15', '20', '25', '30', '35', '40'],
+ "1997-Jan-01 03:00"
+ ],
+ [datetime.timedelta(seconds=2),
+ ['59.5', '03:00', '00.5', '01.0', '01.5', '02.0', '02.5'],
+ "1997-Jan-01 03:00"
+ ],
+ )
+
+ new_tz = datetime.timezone(datetime.timedelta(hours=3))
+ for t_delta, expected_strings, expected_offset in results:
+ d2 = d1 + t_delta
+ strings, offset = _create_auto_date_locator(d1, d2, new_tz)
+ assert strings == expected_strings
+ assert offset == expected_offset
+
+
+def test_auto_date_locator_intmult_tz():
+ def _create_auto_date_locator(date1, date2, tz):
+ locator = mdates.AutoDateLocator(interval_multiples=True, tz=tz)
+ locator.create_dummy_axis()
+ locator.axis.set_view_interval(*mdates.date2num([date1, date2]))
+ return locator
+
+ results = ([datetime.timedelta(weeks=52*200),
+ ['1980-01-01 00:00:00-08:00', '2000-01-01 00:00:00-08:00',
+ '2020-01-01 00:00:00-08:00', '2040-01-01 00:00:00-08:00',
+ '2060-01-01 00:00:00-08:00', '2080-01-01 00:00:00-08:00',
+ '2100-01-01 00:00:00-08:00', '2120-01-01 00:00:00-08:00',
+ '2140-01-01 00:00:00-08:00', '2160-01-01 00:00:00-08:00',
+ '2180-01-01 00:00:00-08:00', '2200-01-01 00:00:00-08:00']
+ ],
+ [datetime.timedelta(weeks=52),
+ ['1997-01-01 00:00:00-08:00', '1997-02-01 00:00:00-08:00',
+ '1997-03-01 00:00:00-08:00', '1997-04-01 00:00:00-08:00',
+ '1997-05-01 00:00:00-07:00', '1997-06-01 00:00:00-07:00',
+ '1997-07-01 00:00:00-07:00', '1997-08-01 00:00:00-07:00',
+ '1997-09-01 00:00:00-07:00', '1997-10-01 00:00:00-07:00',
+ '1997-11-01 00:00:00-08:00', '1997-12-01 00:00:00-08:00']
+ ],
+ [datetime.timedelta(days=141),
+ ['1997-01-01 00:00:00-08:00', '1997-01-15 00:00:00-08:00',
+ '1997-02-01 00:00:00-08:00', '1997-02-15 00:00:00-08:00',
+ '1997-03-01 00:00:00-08:00', '1997-03-15 00:00:00-08:00',
+ '1997-04-01 00:00:00-08:00', '1997-04-15 00:00:00-07:00',
+ '1997-05-01 00:00:00-07:00', '1997-05-15 00:00:00-07:00']
+ ],
+ [datetime.timedelta(days=40),
+ ['1997-01-01 00:00:00-08:00', '1997-01-05 00:00:00-08:00',
+ '1997-01-09 00:00:00-08:00', '1997-01-13 00:00:00-08:00',
+ '1997-01-17 00:00:00-08:00', '1997-01-21 00:00:00-08:00',
+ '1997-01-25 00:00:00-08:00', '1997-01-29 00:00:00-08:00',
+ '1997-02-01 00:00:00-08:00', '1997-02-05 00:00:00-08:00',
+ '1997-02-09 00:00:00-08:00']
+ ],
+ [datetime.timedelta(hours=40),
+ ['1997-01-01 00:00:00-08:00', '1997-01-01 04:00:00-08:00',
+ '1997-01-01 08:00:00-08:00', '1997-01-01 12:00:00-08:00',
+ '1997-01-01 16:00:00-08:00', '1997-01-01 20:00:00-08:00',
+ '1997-01-02 00:00:00-08:00', '1997-01-02 04:00:00-08:00',
+ '1997-01-02 08:00:00-08:00', '1997-01-02 12:00:00-08:00',
+ '1997-01-02 16:00:00-08:00']
+ ],
+ [datetime.timedelta(minutes=20),
+ ['1997-01-01 00:00:00-08:00', '1997-01-01 00:05:00-08:00',
+ '1997-01-01 00:10:00-08:00', '1997-01-01 00:15:00-08:00',
+ '1997-01-01 00:20:00-08:00']
+ ],
+ [datetime.timedelta(seconds=40),
+ ['1997-01-01 00:00:00-08:00', '1997-01-01 00:00:05-08:00',
+ '1997-01-01 00:00:10-08:00', '1997-01-01 00:00:15-08:00',
+ '1997-01-01 00:00:20-08:00', '1997-01-01 00:00:25-08:00',
+ '1997-01-01 00:00:30-08:00', '1997-01-01 00:00:35-08:00',
+ '1997-01-01 00:00:40-08:00']
+ ]
+ )
+
+ tz = dateutil.tz.gettz('Canada/Pacific')
+ d1 = datetime.datetime(1997, 1, 1, tzinfo=tz)
+ for t_delta, expected in results:
+ with rc_context({'_internal.classic_mode': False}):
+ d2 = d1 + t_delta
+ locator = _create_auto_date_locator(d1, d2, tz)
+ st = list(map(str, mdates.num2date(locator(), tz=tz)))
+ assert st == expected
+
+
+@image_comparison(['date_inverted_limit.png'])
+def test_date_inverted_limit():
+ # test ax hline with date inputs
+ t0 = datetime.datetime(2009, 1, 20)
+ tf = datetime.datetime(2009, 1, 31)
+ fig, ax = plt.subplots()
+ ax.axhline(t0, color="blue", lw=3)
+ ax.set_ylim(t0 - datetime.timedelta(days=5),
+ tf + datetime.timedelta(days=5))
+ ax.invert_yaxis()
+ fig.subplots_adjust(left=0.25)
+
+
+def _test_date2num_dst(date_range, tz_convert):
+ # Timezones
+
+ BRUSSELS = dateutil.tz.gettz('Europe/Brussels')
+ UTC = mdates.UTC
+
+ # Create a list of timezone-aware datetime objects in UTC
+ # Interval is 0b0.0000011 days, to prevent float rounding issues
+ dtstart = datetime.datetime(2014, 3, 30, 0, 0, tzinfo=UTC)
+ interval = datetime.timedelta(minutes=33, seconds=45)
+ interval_days = interval.seconds / 86400
+ N = 8
+
+ dt_utc = date_range(start=dtstart, freq=interval, periods=N)
+ dt_bxl = tz_convert(dt_utc, BRUSSELS)
+ t0 = 735322.0 + mdates.date2num(np.datetime64('0000-12-31'))
+ expected_ordinalf = [t0 + (i * interval_days) for i in range(N)]
+ actual_ordinalf = list(mdates.date2num(dt_bxl))
+
+ assert actual_ordinalf == expected_ordinalf
+
+
+def test_date2num_dst():
+ # Test for github issue #3896, but in date2num around DST transitions
+ # with a timezone-aware pandas date_range object.
+
+ class dt_tzaware(datetime.datetime):
+ """
+ This bug specifically occurs because of the normalization behavior of
+ pandas Timestamp objects, so in order to replicate it, we need a
+ datetime-like object that applies timezone normalization after
+ subtraction.
+ """
+
+ def __sub__(self, other):
+ r = super().__sub__(other)
+ tzinfo = getattr(r, 'tzinfo', None)
+
+ if tzinfo is not None:
+ localizer = getattr(tzinfo, 'normalize', None)
+ if localizer is not None:
+ r = tzinfo.normalize(r)
+
+ if isinstance(r, datetime.datetime):
+ r = self.mk_tzaware(r)
+
+ return r
+
+ def __add__(self, other):
+ return self.mk_tzaware(super().__add__(other))
+
+ def astimezone(self, tzinfo):
+ dt = super().astimezone(tzinfo)
+ return self.mk_tzaware(dt)
+
+ @classmethod
+ def mk_tzaware(cls, datetime_obj):
+ kwargs = {}
+ attrs = ('year',
+ 'month',
+ 'day',
+ 'hour',
+ 'minute',
+ 'second',
+ 'microsecond',
+ 'tzinfo')
+
+ for attr in attrs:
+ val = getattr(datetime_obj, attr, None)
+ if val is not None:
+ kwargs[attr] = val
+
+ return cls(**kwargs)
+
+ # Define a date_range function similar to pandas.date_range
+ def date_range(start, freq, periods):
+ dtstart = dt_tzaware.mk_tzaware(start)
+
+ return [dtstart + (i * freq) for i in range(periods)]
+
+ # Define a tz_convert function that converts a list to a new timezone.
+ def tz_convert(dt_list, tzinfo):
+ return [d.astimezone(tzinfo) for d in dt_list]
+
+ _test_date2num_dst(date_range, tz_convert)
+
+
+def test_date2num_dst_pandas(pd):
+ # Test for github issue #3896, but in date2num around DST transitions
+ # with a timezone-aware pandas date_range object.
+
+ def tz_convert(*args):
+ return pd.DatetimeIndex.tz_convert(*args).astype(object)
+
+ _test_date2num_dst(pd.date_range, tz_convert)
+
+
+def _test_rrulewrapper(attach_tz, get_tz):
+ SYD = get_tz('Australia/Sydney')
+
+ dtstart = attach_tz(datetime.datetime(2017, 4, 1, 0), SYD)
+ dtend = attach_tz(datetime.datetime(2017, 4, 4, 0), SYD)
+
+ rule = mdates.rrulewrapper(freq=dateutil.rrule.DAILY, dtstart=dtstart)
+
+ act = rule.between(dtstart, dtend)
+ exp = [datetime.datetime(2017, 4, 1, 13, tzinfo=dateutil.tz.tzutc()),
+ datetime.datetime(2017, 4, 2, 14, tzinfo=dateutil.tz.tzutc())]
+
+ assert act == exp
+
+
+def test_rrulewrapper():
+ def attach_tz(dt, zi):
+ return dt.replace(tzinfo=zi)
+
+ _test_rrulewrapper(attach_tz, dateutil.tz.gettz)
+
+ SYD = dateutil.tz.gettz('Australia/Sydney')
+ dtstart = datetime.datetime(2017, 4, 1, 0)
+ dtend = datetime.datetime(2017, 4, 4, 0)
+ rule = mdates.rrulewrapper(freq=dateutil.rrule.DAILY, dtstart=dtstart,
+ tzinfo=SYD, until=dtend)
+ assert rule.after(dtstart) == datetime.datetime(2017, 4, 2, 0, 0,
+ tzinfo=SYD)
+ assert rule.before(dtend) == datetime.datetime(2017, 4, 3, 0, 0,
+ tzinfo=SYD)
+
+ # Test parts of __getattr__
+ assert rule._base_tzinfo == SYD
+ assert rule._interval == 1
+
+
+@pytest.mark.pytz
+def test_rrulewrapper_pytz():
+ # Test to make sure pytz zones are supported in rrules
+ pytz = pytest.importorskip("pytz")
+
+ def attach_tz(dt, zi):
+ return zi.localize(dt)
+
+ _test_rrulewrapper(attach_tz, pytz.timezone)
+
+
+@pytest.mark.pytz
+def test_yearlocator_pytz():
+ pytz = pytest.importorskip("pytz")
+
+ tz = pytz.timezone('America/New_York')
+ x = [tz.localize(datetime.datetime(2010, 1, 1))
+ + datetime.timedelta(i) for i in range(2000)]
+ locator = mdates.AutoDateLocator(interval_multiples=True, tz=tz)
+ locator.create_dummy_axis()
+ locator.axis.set_view_interval(mdates.date2num(x[0])-1.0,
+ mdates.date2num(x[-1])+1.0)
+ t = np.array([733408.208333, 733773.208333, 734138.208333,
+ 734503.208333, 734869.208333, 735234.208333, 735599.208333])
+ # convert to new epoch from old...
+ t = t + mdates.date2num(np.datetime64('0000-12-31'))
+ np.testing.assert_allclose(t, locator())
+ expected = ['2009-01-01 00:00:00-05:00',
+ '2010-01-01 00:00:00-05:00', '2011-01-01 00:00:00-05:00',
+ '2012-01-01 00:00:00-05:00', '2013-01-01 00:00:00-05:00',
+ '2014-01-01 00:00:00-05:00', '2015-01-01 00:00:00-05:00']
+ st = list(map(str, mdates.num2date(locator(), tz=tz)))
+ assert st == expected
+ assert np.allclose(locator.tick_values(x[0], x[1]), np.array(
+ [14610.20833333, 14610.33333333, 14610.45833333, 14610.58333333,
+ 14610.70833333, 14610.83333333, 14610.95833333, 14611.08333333,
+ 14611.20833333]))
+ assert np.allclose(locator.get_locator(x[1], x[0]).tick_values(x[0], x[1]),
+ np.array(
+ [14610.20833333, 14610.33333333, 14610.45833333, 14610.58333333,
+ 14610.70833333, 14610.83333333, 14610.95833333, 14611.08333333,
+ 14611.20833333]))
+
+
+def test_YearLocator():
+ def _create_year_locator(date1, date2, **kwargs):
+ locator = mdates.YearLocator(**kwargs)
+ locator.create_dummy_axis()
+ locator.axis.set_view_interval(mdates.date2num(date1),
+ mdates.date2num(date2))
+ return locator
+
+ d1 = datetime.datetime(1990, 1, 1)
+ results = ([datetime.timedelta(weeks=52 * 200),
+ {'base': 20, 'month': 1, 'day': 1},
+ ['1980-01-01 00:00:00+00:00', '2000-01-01 00:00:00+00:00',
+ '2020-01-01 00:00:00+00:00', '2040-01-01 00:00:00+00:00',
+ '2060-01-01 00:00:00+00:00', '2080-01-01 00:00:00+00:00',
+ '2100-01-01 00:00:00+00:00', '2120-01-01 00:00:00+00:00',
+ '2140-01-01 00:00:00+00:00', '2160-01-01 00:00:00+00:00',
+ '2180-01-01 00:00:00+00:00', '2200-01-01 00:00:00+00:00']
+ ],
+ [datetime.timedelta(weeks=52 * 200),
+ {'base': 20, 'month': 5, 'day': 16},
+ ['1980-05-16 00:00:00+00:00', '2000-05-16 00:00:00+00:00',
+ '2020-05-16 00:00:00+00:00', '2040-05-16 00:00:00+00:00',
+ '2060-05-16 00:00:00+00:00', '2080-05-16 00:00:00+00:00',
+ '2100-05-16 00:00:00+00:00', '2120-05-16 00:00:00+00:00',
+ '2140-05-16 00:00:00+00:00', '2160-05-16 00:00:00+00:00',
+ '2180-05-16 00:00:00+00:00', '2200-05-16 00:00:00+00:00']
+ ],
+ [datetime.timedelta(weeks=52 * 5),
+ {'base': 20, 'month': 9, 'day': 25},
+ ['1980-09-25 00:00:00+00:00', '2000-09-25 00:00:00+00:00']
+ ],
+ )
+
+ for delta, arguments, expected in results:
+ d2 = d1 + delta
+ locator = _create_year_locator(d1, d2, **arguments)
+ assert list(map(str, mdates.num2date(locator()))) == expected
+
+
+def test_DayLocator():
+ with pytest.raises(ValueError):
+ mdates.DayLocator(interval=-1)
+ with pytest.raises(ValueError):
+ mdates.DayLocator(interval=-1.5)
+ with pytest.raises(ValueError):
+ mdates.DayLocator(interval=0)
+ with pytest.raises(ValueError):
+ mdates.DayLocator(interval=1.3)
+ mdates.DayLocator(interval=1.0)
+
+
+def test_tz_utc():
+ dt = datetime.datetime(1970, 1, 1, tzinfo=mdates.UTC)
+ assert dt.tzname() == 'UTC'
+
+
+@pytest.mark.parametrize("x, tdelta",
+ [(1, datetime.timedelta(days=1)),
+ ([1, 1.5], [datetime.timedelta(days=1),
+ datetime.timedelta(days=1.5)])])
+def test_num2timedelta(x, tdelta):
+ dt = mdates.num2timedelta(x)
+ assert dt == tdelta
+
+
+def test_datetime64_in_list():
+ dt = [np.datetime64('2000-01-01'), np.datetime64('2001-01-01')]
+ dn = mdates.date2num(dt)
+ # convert fixed values from old to new epoch
+ t = (np.array([730120., 730486.]) +
+ mdates.date2num(np.datetime64('0000-12-31')))
+ np.testing.assert_equal(dn, t)
+
+
+def test_change_epoch():
+ date = np.datetime64('2000-01-01')
+
+ # use private method to clear the epoch and allow it to be set...
+ mdates._reset_epoch_test_example()
+ mdates.get_epoch() # Set default.
+
+ with pytest.raises(RuntimeError):
+ # this should fail here because there is a sentinel on the epoch
+ # if the epoch has been used then it cannot be set.
+ mdates.set_epoch('0000-01-01')
+
+ mdates._reset_epoch_test_example()
+ mdates.set_epoch('1970-01-01')
+ dt = (date - np.datetime64('1970-01-01')).astype('datetime64[D]')
+ dt = dt.astype('int')
+ np.testing.assert_equal(mdates.date2num(date), float(dt))
+
+ mdates._reset_epoch_test_example()
+ mdates.set_epoch('0000-12-31')
+ np.testing.assert_equal(mdates.date2num(date), 730120.0)
+
+ mdates._reset_epoch_test_example()
+ mdates.set_epoch('1970-01-01T01:00:00')
+ np.testing.assert_allclose(mdates.date2num(date), dt - 1./24.)
+ mdates._reset_epoch_test_example()
+ mdates.set_epoch('1970-01-01T00:00:00')
+ np.testing.assert_allclose(
+ mdates.date2num(np.datetime64('1970-01-01T12:00:00')),
+ 0.5)
+
+
+def test_warn_notintervals():
+ dates = np.arange('2001-01-10', '2001-03-04', dtype='datetime64[D]')
+ locator = mdates.AutoDateLocator(interval_multiples=False)
+ locator.intervald[3] = [2]
+ locator.create_dummy_axis()
+ locator.axis.set_view_interval(mdates.date2num(dates[0]),
+ mdates.date2num(dates[-1]))
+ with pytest.warns(UserWarning, match="AutoDateLocator was unable"):
+ locs = locator()
+
+
+def test_change_converter():
+ plt.rcParams['date.converter'] = 'concise'
+ dates = np.arange('2020-01-01', '2020-05-01', dtype='datetime64[D]')
+ fig, ax = plt.subplots()
+
+ ax.plot(dates, np.arange(len(dates)))
+ fig.canvas.draw()
+ assert ax.get_xticklabels()[0].get_text() == 'Jan'
+ assert ax.get_xticklabels()[1].get_text() == '15'
+
+ plt.rcParams['date.converter'] = 'auto'
+ fig, ax = plt.subplots()
+
+ ax.plot(dates, np.arange(len(dates)))
+ fig.canvas.draw()
+ assert ax.get_xticklabels()[0].get_text() == 'Jan 01 2020'
+ assert ax.get_xticklabels()[1].get_text() == 'Jan 15 2020'
+ with pytest.raises(ValueError):
+ plt.rcParams['date.converter'] = 'boo'
+
+
+def test_change_interval_multiples():
+ plt.rcParams['date.interval_multiples'] = False
+ dates = np.arange('2020-01-10', '2020-05-01', dtype='datetime64[D]')
+ fig, ax = plt.subplots()
+
+ ax.plot(dates, np.arange(len(dates)))
+ fig.canvas.draw()
+ assert ax.get_xticklabels()[0].get_text() == 'Jan 10 2020'
+ assert ax.get_xticklabels()[1].get_text() == 'Jan 24 2020'
+
+ plt.rcParams['date.interval_multiples'] = 'True'
+ fig, ax = plt.subplots()
+
+ ax.plot(dates, np.arange(len(dates)))
+ fig.canvas.draw()
+ assert ax.get_xticklabels()[0].get_text() == 'Jan 15 2020'
+ assert ax.get_xticklabels()[1].get_text() == 'Feb 01 2020'
+
+
+def test_DateLocator():
+ locator = mdates.DateLocator()
+ # Test nonsingular
+ assert locator.nonsingular(0, np.inf) == (0, 1)
+ assert locator.nonsingular(0, 1) == (0, 1)
+ assert locator.nonsingular(1, 0) == (0, 1)
+ assert locator.nonsingular(0, 0) == (-2, 2)
+ locator.create_dummy_axis()
+ # default values
+ assert locator.datalim_to_dt() == (
+ datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc),
+ datetime.datetime(1970, 1, 2, 0, 0, tzinfo=datetime.timezone.utc))
+
+ # Check default is UTC
+ assert locator.tz == mdates.UTC
+ tz_str = 'Iceland'
+ iceland_tz = dateutil.tz.gettz(tz_str)
+ # Check not Iceland
+ assert locator.tz != iceland_tz
+ # Set it to Iceland
+ locator.set_tzinfo('Iceland')
+ # Check now it is Iceland
+ assert locator.tz == iceland_tz
+ locator.create_dummy_axis()
+ locator.axis.set_data_interval(*mdates.date2num(["2022-01-10",
+ "2022-01-08"]))
+ assert locator.datalim_to_dt() == (
+ datetime.datetime(2022, 1, 8, 0, 0, tzinfo=iceland_tz),
+ datetime.datetime(2022, 1, 10, 0, 0, tzinfo=iceland_tz))
+
+ # Set rcParam
+ plt.rcParams['timezone'] = tz_str
+
+ # Create a new one in a similar way
+ locator = mdates.DateLocator()
+ # Check now it is Iceland
+ assert locator.tz == iceland_tz
+
+ # Test invalid tz values
+ with pytest.raises(ValueError, match="Aiceland is not a valid timezone"):
+ mdates.DateLocator(tz="Aiceland")
+ with pytest.raises(TypeError,
+ match="tz must be string or tzinfo subclass."):
+ mdates.DateLocator(tz=1)
+
+
+def test_datestr2num():
+ assert mdates.datestr2num('2022-01-10') == 19002.0
+ dt = datetime.date(year=2022, month=1, day=10)
+ assert mdates.datestr2num('2022-01', default=dt) == 19002.0
+ assert np.all(mdates.datestr2num(
+ ['2022-01', '2022-02'], default=dt
+ ) == np.array([19002., 19033.]))
+ assert mdates.datestr2num([]).size == 0
+ assert mdates.datestr2num([], datetime.date(year=2022,
+ month=1, day=10)).size == 0
+
+
+@pytest.mark.parametrize('kwarg',
+ ('formats', 'zero_formats', 'offset_formats'))
+def test_concise_formatter_exceptions(kwarg):
+ locator = mdates.AutoDateLocator()
+ kwargs = {kwarg: ['', '%Y']}
+ match = f"{kwarg} argument must be a list"
+ with pytest.raises(ValueError, match=match):
+ mdates.ConciseDateFormatter(locator, **kwargs)
+
+
+def test_concise_formatter_call():
+ locator = mdates.AutoDateLocator()
+ formatter = mdates.ConciseDateFormatter(locator)
+ assert formatter(19002.0) == '2022'
+ assert formatter.format_data_short(19002.0) == '2022-01-10 00:00:00'
+
+
+def test_datetime_masked():
+ # make sure that all-masked data falls back to the viewlim
+ # set in convert.axisinfo....
+ x = np.array([datetime.datetime(2017, 1, n) for n in range(1, 6)])
+ y = np.array([1, 2, 3, 4, 5])
+ m = np.ma.masked_greater(y, 0)
+
+ fig, ax = plt.subplots()
+ ax.plot(x, m)
+ assert ax.get_xlim() == (0, 1)
+
+
+@pytest.mark.parametrize('val', (-1000000, 10000000))
+def test_num2date_error(val):
+ with pytest.raises(ValueError, match=f"Date ordinal {val} converts"):
+ mdates.num2date(val)
+
+
+def test_num2date_roundoff():
+ assert mdates.num2date(100000.0000578702) == datetime.datetime(
+ 2243, 10, 17, 0, 0, 4, 999980, tzinfo=datetime.timezone.utc)
+ # Slightly larger, steps of 20 microseconds
+ assert mdates.num2date(100000.0000578703) == datetime.datetime(
+ 2243, 10, 17, 0, 0, 5, tzinfo=datetime.timezone.utc)
+
+
+def test_DateFormatter_settz():
+ time = mdates.date2num(datetime.datetime(2011, 1, 1, 0, 0,
+ tzinfo=mdates.UTC))
+ formatter = mdates.DateFormatter('%Y-%b-%d %H:%M')
+ # Default UTC
+ assert formatter(time) == '2011-Jan-01 00:00'
+
+ # Set tzinfo
+ formatter.set_tzinfo('Pacific/Kiritimati')
+ assert formatter(time) == '2011-Jan-01 14:00'
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_datetime.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..276056d044ae3e114f506a26a2806d20135ecc4d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_datetime.py
@@ -0,0 +1,865 @@
+import datetime
+import numpy as np
+
+import pytest
+
+import matplotlib.pyplot as plt
+import matplotlib as mpl
+
+
+class TestDatetimePlotting:
+ @mpl.style.context("default")
+ def test_annotate(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout="constrained")
+
+ start_date = datetime.datetime(2023, 10, 1)
+ dates = [start_date + datetime.timedelta(days=i) for i in range(31)]
+ data = list(range(1, 32))
+ test_text = "Test Text"
+
+ ax1.plot(dates, data)
+ ax1.annotate(text=test_text, xy=(dates[15], data[15]))
+ ax2.plot(data, dates)
+ ax2.annotate(text=test_text, xy=(data[5], dates[26]))
+ ax3.plot(dates, dates)
+ ax3.annotate(text=test_text, xy=(dates[15], dates[3]))
+ ax4.plot(dates, dates)
+ ax4.annotate(text=test_text, xy=(dates[5], dates[30]),
+ xytext=(dates[1], dates[7]), arrowprops=dict(facecolor='red'))
+
+ @pytest.mark.xfail(reason="Test for arrow not written yet")
+ @mpl.style.context("default")
+ def test_arrow(self):
+ fig, ax = plt.subplots()
+ ax.arrow(...)
+
+ @mpl.style.context("default")
+ def test_axhline(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained')
+ ax1.set_ylim(bottom=datetime.datetime(2020, 4, 1),
+ top=datetime.datetime(2020, 8, 1))
+ ax2.set_ylim(bottom=np.datetime64('2005-01-01'),
+ top=np.datetime64('2005-04-01'))
+ ax3.set_ylim(bottom=datetime.datetime(2023, 9, 1),
+ top=datetime.datetime(2023, 11, 1))
+ ax1.axhline(y=datetime.datetime(2020, 6, 3), xmin=0.5, xmax=0.7)
+ ax2.axhline(np.datetime64('2005-02-25T03:30'), xmin=0.1, xmax=0.9)
+ ax3.axhline(y=datetime.datetime(2023, 10, 24), xmin=0.4, xmax=0.7)
+
+ @mpl.style.context("default")
+ def test_axhspan(self):
+ mpl.rcParams["date.converter"] = 'concise'
+
+ start_date = datetime.datetime(2023, 1, 1)
+ dates = [start_date + datetime.timedelta(days=i) for i in range(31)]
+ numbers = list(range(1, 32))
+
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1,
+ constrained_layout=True,
+ figsize=(10, 12))
+
+ ax1.plot(dates, numbers, marker='o', color='blue')
+ for i in range(0, 31, 2):
+ ax1.axhspan(ymin=i+1, ymax=i+2, facecolor='green', alpha=0.5)
+ ax1.set_title('Datetime vs. Number')
+ ax1.set_xlabel('Date')
+ ax1.set_ylabel('Number')
+
+ ax2.plot(numbers, dates, marker='o', color='blue')
+ for i in range(0, 31, 2):
+ ymin = start_date + datetime.timedelta(days=i)
+ ymax = ymin + datetime.timedelta(days=1)
+ ax2.axhspan(ymin=ymin, ymax=ymax, facecolor='green', alpha=0.5)
+ ax2.set_title('Number vs. Datetime')
+ ax2.set_xlabel('Number')
+ ax2.set_ylabel('Date')
+
+ ax3.plot(dates, dates, marker='o', color='blue')
+ for i in range(0, 31, 2):
+ ymin = start_date + datetime.timedelta(days=i)
+ ymax = ymin + datetime.timedelta(days=1)
+ ax3.axhspan(ymin=ymin, ymax=ymax, facecolor='green', alpha=0.5)
+ ax3.set_title('Datetime vs. Datetime')
+ ax3.set_xlabel('Date')
+ ax3.set_ylabel('Date')
+
+ @pytest.mark.xfail(reason="Test for axline not written yet")
+ @mpl.style.context("default")
+ def test_axline(self):
+ fig, ax = plt.subplots()
+ ax.axline(...)
+
+ @mpl.style.context("default")
+ def test_axvline(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained')
+ ax1.set_xlim(left=datetime.datetime(2020, 4, 1),
+ right=datetime.datetime(2020, 8, 1))
+ ax2.set_xlim(left=np.datetime64('2005-01-01'),
+ right=np.datetime64('2005-04-01'))
+ ax3.set_xlim(left=datetime.datetime(2023, 9, 1),
+ right=datetime.datetime(2023, 11, 1))
+ ax1.axvline(x=datetime.datetime(2020, 6, 3), ymin=0.5, ymax=0.7)
+ ax2.axvline(np.datetime64('2005-02-25T03:30'), ymin=0.1, ymax=0.9)
+ ax3.axvline(x=datetime.datetime(2023, 10, 24), ymin=0.4, ymax=0.7)
+
+ @mpl.style.context("default")
+ def test_axvspan(self):
+ mpl.rcParams["date.converter"] = 'concise'
+
+ start_date = datetime.datetime(2023, 1, 1)
+ dates = [start_date + datetime.timedelta(days=i) for i in range(31)]
+ numbers = list(range(1, 32))
+
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1,
+ constrained_layout=True,
+ figsize=(10, 12))
+
+ ax1.plot(dates, numbers, marker='o', color='blue')
+ for i in range(0, 31, 2):
+ xmin = start_date + datetime.timedelta(days=i)
+ xmax = xmin + datetime.timedelta(days=1)
+ ax1.axvspan(xmin=xmin, xmax=xmax, facecolor='red', alpha=0.5)
+ ax1.set_title('Datetime vs. Number')
+ ax1.set_xlabel('Date')
+ ax1.set_ylabel('Number')
+
+ ax2.plot(numbers, dates, marker='o', color='blue')
+ for i in range(0, 31, 2):
+ ax2.axvspan(xmin=i+1, xmax=i+2, facecolor='red', alpha=0.5)
+ ax2.set_title('Number vs. Datetime')
+ ax2.set_xlabel('Number')
+ ax2.set_ylabel('Date')
+
+ ax3.plot(dates, dates, marker='o', color='blue')
+ for i in range(0, 31, 2):
+ xmin = start_date + datetime.timedelta(days=i)
+ xmax = xmin + datetime.timedelta(days=1)
+ ax3.axvspan(xmin=xmin, xmax=xmax, facecolor='red', alpha=0.5)
+ ax3.set_title('Datetime vs. Datetime')
+ ax3.set_xlabel('Date')
+ ax3.set_ylabel('Date')
+
+ @mpl.style.context("default")
+ def test_bar(self):
+ mpl.rcParams["date.converter"] = "concise"
+
+ fig, (ax1, ax2) = plt.subplots(2, 1, layout="constrained")
+
+ x_dates = np.array(
+ [
+ datetime.datetime(2020, 6, 30),
+ datetime.datetime(2020, 7, 22),
+ datetime.datetime(2020, 8, 3),
+ datetime.datetime(2020, 9, 14),
+ ],
+ dtype=np.datetime64,
+ )
+ x_ranges = [8800, 2600, 8500, 7400]
+
+ x = np.datetime64(datetime.datetime(2020, 6, 1))
+ ax1.bar(x_dates, x_ranges, width=np.timedelta64(4, "D"))
+ ax2.bar(np.arange(4), x_dates - x, bottom=x)
+
+ @mpl.style.context("default")
+ def test_bar_label(self):
+ # Generate some example data with dateTime inputs
+ date_list = [datetime.datetime(2023, 1, 1) +
+ datetime.timedelta(days=i) for i in range(5)]
+ values = [10, 20, 15, 25, 30]
+
+ # Creating the plot
+ fig, ax = plt.subplots(1, 1, figsize=(10, 8), layout='constrained')
+ bars = ax.bar(date_list, values)
+
+ # Add labels to the bars using bar_label
+ ax.bar_label(bars, labels=[f'{val}%' for val in values],
+ label_type='edge', color='black')
+
+ @mpl.style.context("default")
+ def test_barbs(self):
+ plt.rcParams["date.converter"] = 'concise'
+
+ start_date = datetime.datetime(2022, 2, 8, 22)
+ dates = [start_date + datetime.timedelta(hours=i) for i in range(12)]
+
+ numbers = np.sin(np.linspace(0, 2 * np.pi, 12))
+
+ u = np.ones(12) * 10
+ v = np.arange(0, 120, 10)
+
+ fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(12, 6))
+
+ axes[0].barbs(dates, numbers, u, v, length=7)
+ axes[0].set_title('Datetime vs. Numeric Data')
+ axes[0].set_xlabel('Datetime')
+ axes[0].set_ylabel('Numeric Data')
+
+ axes[1].barbs(numbers, dates, u, v, length=7)
+ axes[1].set_title('Numeric vs. Datetime Data')
+ axes[1].set_xlabel('Numeric Data')
+ axes[1].set_ylabel('Datetime')
+
+ @mpl.style.context("default")
+ def test_barh(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ fig, (ax1, ax2) = plt.subplots(2, 1, layout='constrained')
+ birth_date = np.array([datetime.datetime(2020, 4, 10),
+ datetime.datetime(2020, 5, 30),
+ datetime.datetime(2020, 10, 12),
+ datetime.datetime(2020, 11, 15)])
+ year_start = datetime.datetime(2020, 1, 1)
+ year_end = datetime.datetime(2020, 12, 31)
+ age = [21, 53, 20, 24]
+ ax1.set_xlabel('Age')
+ ax1.set_ylabel('Birth Date')
+ ax1.barh(birth_date, width=age, height=datetime.timedelta(days=10))
+ ax2.set_xlim(left=year_start, right=year_end)
+ ax2.set_xlabel('Birth Date')
+ ax2.set_ylabel('Order of Birth Dates')
+ ax2.barh(np.arange(4), birth_date-year_start, left=year_start)
+
+ @pytest.mark.xfail(reason="Test for boxplot not written yet")
+ @mpl.style.context("default")
+ def test_boxplot(self):
+ fig, ax = plt.subplots()
+ ax.boxplot(...)
+
+ @mpl.style.context("default")
+ def test_broken_barh(self):
+ # Horizontal bar plot with gaps
+ mpl.rcParams["date.converter"] = 'concise'
+ fig, ax = plt.subplots()
+
+ ax.broken_barh([(datetime.datetime(2023, 1, 4), datetime.timedelta(days=2)),
+ (datetime.datetime(2023, 1, 8), datetime.timedelta(days=3))],
+ (10, 9), facecolors='tab:blue')
+ ax.broken_barh([(datetime.datetime(2023, 1, 2), datetime.timedelta(days=1)),
+ (datetime.datetime(2023, 1, 4), datetime.timedelta(days=4))],
+ (20, 9), facecolors=('tab:red'))
+
+ @mpl.style.context("default")
+ def test_bxp(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ fig, ax = plt.subplots()
+ data = [{
+ "med": datetime.datetime(2020, 1, 15),
+ "q1": datetime.datetime(2020, 1, 10),
+ "q3": datetime.datetime(2020, 1, 20),
+ "whislo": datetime.datetime(2020, 1, 5),
+ "whishi": datetime.datetime(2020, 1, 25),
+ "fliers": [
+ datetime.datetime(2020, 1, 3),
+ datetime.datetime(2020, 1, 27)
+ ]
+ }]
+ ax.bxp(data, orientation='horizontal')
+ ax.xaxis.set_major_formatter(mpl.dates.DateFormatter("%Y-%m-%d"))
+ ax.set_title('Box plot with datetime data')
+
+ @pytest.mark.xfail(reason="Test for clabel not written yet")
+ @mpl.style.context("default")
+ def test_clabel(self):
+ fig, ax = plt.subplots()
+ ax.clabel(...)
+
+ @mpl.style.context("default")
+ def test_contour(self):
+ mpl.rcParams["date.converter"] = "concise"
+ range_threshold = 10
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained")
+
+ x_dates = np.array(
+ [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)]
+ )
+ y_dates = np.array(
+ [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)]
+ )
+ x_ranges = np.array(range(1, range_threshold))
+ y_ranges = np.array(range(1, range_threshold))
+
+ X_dates, Y_dates = np.meshgrid(x_dates, y_dates)
+ X_ranges, Y_ranges = np.meshgrid(x_ranges, y_ranges)
+
+ Z_ranges = np.cos(X_ranges / 4) + np.sin(Y_ranges / 4)
+
+ ax1.contour(X_dates, Y_dates, Z_ranges)
+ ax2.contour(X_dates, Y_ranges, Z_ranges)
+ ax3.contour(X_ranges, Y_dates, Z_ranges)
+
+ @mpl.style.context("default")
+ def test_contourf(self):
+ mpl.rcParams["date.converter"] = "concise"
+ range_threshold = 10
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained")
+
+ x_dates = np.array(
+ [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)]
+ )
+ y_dates = np.array(
+ [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)]
+ )
+ x_ranges = np.array(range(1, range_threshold))
+ y_ranges = np.array(range(1, range_threshold))
+
+ X_dates, Y_dates = np.meshgrid(x_dates, y_dates)
+ X_ranges, Y_ranges = np.meshgrid(x_ranges, y_ranges)
+
+ Z_ranges = np.cos(X_ranges / 4) + np.sin(Y_ranges / 4)
+
+ ax1.contourf(X_dates, Y_dates, Z_ranges)
+ ax2.contourf(X_dates, Y_ranges, Z_ranges)
+ ax3.contourf(X_ranges, Y_dates, Z_ranges)
+
+ @mpl.style.context("default")
+ def test_errorbar(self):
+ mpl.rcParams["date.converter"] = "concise"
+ fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout="constrained")
+ limit = 7
+ start_date = datetime.datetime(2023, 1, 1)
+
+ x_dates = np.array([datetime.datetime(2023, 10, d) for d in range(1, limit)])
+ y_dates = np.array([datetime.datetime(2023, 10, d) for d in range(1, limit)])
+ x_date_error = datetime.timedelta(days=1)
+ y_date_error = datetime.timedelta(days=1)
+
+ x_values = list(range(1, limit))
+ y_values = list(range(1, limit))
+ x_value_error = 0.5
+ y_value_error = 0.5
+
+ ax1.errorbar(x_dates, y_values,
+ yerr=y_value_error,
+ capsize=10,
+ barsabove=True,
+ label='Data')
+ ax2.errorbar(x_values, y_dates,
+ xerr=x_value_error, yerr=y_date_error,
+ errorevery=(1, 2),
+ fmt='-o', label='Data')
+ ax3.errorbar(x_dates, y_dates,
+ xerr=x_date_error, yerr=y_date_error,
+ lolims=True, xlolims=True,
+ label='Data')
+ ax4.errorbar(x_dates, y_values,
+ xerr=x_date_error, yerr=y_value_error,
+ uplims=True, xuplims=True,
+ label='Data')
+
+ @mpl.style.context("default")
+ def test_eventplot(self):
+ mpl.rcParams["date.converter"] = "concise"
+
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained")
+
+ x_dates1 = np.array([datetime.datetime(2020, 6, 30),
+ datetime.datetime(2020, 7, 22),
+ datetime.datetime(2020, 8, 3),
+ datetime.datetime(2020, 9, 14),],
+ dtype=np.datetime64,
+ )
+
+ ax1.eventplot(x_dates1)
+
+ np.random.seed(19680801)
+
+ start_date = datetime.datetime(2020, 7, 1)
+ end_date = datetime.datetime(2020, 10, 15)
+ date_range = end_date - start_date
+
+ dates1 = start_date + np.random.rand(30) * date_range
+ dates2 = start_date + np.random.rand(10) * date_range
+ dates3 = start_date + np.random.rand(50) * date_range
+
+ colors1 = ['C1', 'C2', 'C3']
+ lineoffsets1 = np.array([1, 6, 8])
+ linelengths1 = [5, 2, 3]
+
+ ax2.eventplot([dates1, dates2, dates3],
+ colors=colors1,
+ lineoffsets=lineoffsets1,
+ linelengths=linelengths1)
+
+ lineoffsets2 = np.array([
+ datetime.datetime(2020, 7, 1),
+ datetime.datetime(2020, 7, 15),
+ datetime.datetime(2020, 8, 1)
+ ], dtype=np.datetime64)
+
+ ax3.eventplot([dates1, dates2, dates3],
+ colors=colors1,
+ lineoffsets=lineoffsets2,
+ linelengths=linelengths1)
+
+ @mpl.style.context("default")
+ def test_fill(self):
+ mpl.rcParams["date.converter"] = "concise"
+ fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout="constrained")
+
+ np.random.seed(19680801)
+
+ x_base_date = datetime.datetime(2023, 1, 1)
+ x_dates = [x_base_date]
+ for _ in range(1, 5):
+ x_base_date += datetime.timedelta(days=np.random.randint(1, 5))
+ x_dates.append(x_base_date)
+
+ y_base_date = datetime.datetime(2023, 1, 1)
+ y_dates = [y_base_date]
+ for _ in range(1, 5):
+ y_base_date += datetime.timedelta(days=np.random.randint(1, 5))
+ y_dates.append(y_base_date)
+
+ x_values = np.random.rand(5) * 5
+ y_values = np.random.rand(5) * 5 - 2
+
+ ax1.fill(x_dates, y_values)
+ ax2.fill(x_values, y_dates)
+ ax3.fill(x_values, y_values)
+ ax4.fill(x_dates, y_dates)
+
+ @mpl.style.context("default")
+ def test_fill_between(self):
+ mpl.rcParams["date.converter"] = "concise"
+ np.random.seed(19680801)
+
+ y_base_date = datetime.datetime(2023, 1, 1)
+ y_dates1 = [y_base_date]
+ for i in range(1, 10):
+ y_base_date += datetime.timedelta(days=np.random.randint(1, 5))
+ y_dates1.append(y_base_date)
+
+ y_dates2 = [y_base_date]
+ for i in range(1, 10):
+ y_base_date += datetime.timedelta(days=np.random.randint(1, 5))
+ y_dates2.append(y_base_date)
+ x_values = np.random.rand(10) * 10
+ x_values.sort()
+
+ y_values1 = np.random.rand(10) * 10
+ y_values2 = y_values1 + np.random.rand(10) * 10
+ y_values1.sort()
+ y_values2.sort()
+
+ x_base_date = datetime.datetime(2023, 1, 1)
+ x_dates = [x_base_date]
+ for i in range(1, 10):
+ x_base_date += datetime.timedelta(days=np.random.randint(1, 10))
+ x_dates.append(x_base_date)
+
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained")
+
+ ax1.fill_between(x_values, y_dates1, y_dates2)
+ ax2.fill_between(x_dates, y_values1, y_values2)
+ ax3.fill_between(x_dates, y_dates1, y_dates2)
+
+ @mpl.style.context("default")
+ def test_fill_betweenx(self):
+ mpl.rcParams["date.converter"] = "concise"
+ np.random.seed(19680801)
+
+ x_base_date = datetime.datetime(2023, 1, 1)
+ x_dates1 = [x_base_date]
+ for i in range(1, 10):
+ x_base_date += datetime.timedelta(days=np.random.randint(1, 5))
+ x_dates1.append(x_base_date)
+
+ x_dates2 = [x_base_date]
+ for i in range(1, 10):
+ x_base_date += datetime.timedelta(days=np.random.randint(1, 5))
+ x_dates2.append(x_base_date)
+ y_values = np.random.rand(10) * 10
+ y_values.sort()
+
+ x_values1 = np.random.rand(10) * 10
+ x_values2 = x_values1 + np.random.rand(10) * 10
+ x_values1.sort()
+ x_values2.sort()
+
+ y_base_date = datetime.datetime(2023, 1, 1)
+ y_dates = [y_base_date]
+ for i in range(1, 10):
+ y_base_date += datetime.timedelta(days=np.random.randint(1, 10))
+ y_dates.append(y_base_date)
+
+ fig, (ax1, ax2, ax3) = plt.subplots(1, 3, layout="constrained")
+
+ ax1.fill_betweenx(y_values, x_dates1, x_dates2)
+ ax2.fill_betweenx(y_dates, x_values1, x_values2)
+ ax3.fill_betweenx(y_dates, x_dates1, x_dates2)
+
+ @pytest.mark.xfail(reason="Test for hexbin not written yet")
+ @mpl.style.context("default")
+ def test_hexbin(self):
+ fig, ax = plt.subplots()
+ ax.hexbin(...)
+
+ @mpl.style.context("default")
+ def test_hist(self):
+ mpl.rcParams["date.converter"] = 'concise'
+
+ start_date = datetime.datetime(2023, 10, 1)
+ time_delta = datetime.timedelta(days=1)
+
+ values1 = np.random.randint(1, 10, 30)
+ values2 = np.random.randint(1, 10, 30)
+ values3 = np.random.randint(1, 10, 30)
+
+ bin_edges = [start_date + i * time_delta for i in range(31)]
+
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, constrained_layout=True)
+ ax1.hist(
+ [start_date + i * time_delta for i in range(30)],
+ bins=10,
+ weights=values1
+ )
+ ax2.hist(
+ [start_date + i * time_delta for i in range(30)],
+ bins=10,
+ weights=values2
+ )
+ ax3.hist(
+ [start_date + i * time_delta for i in range(30)],
+ bins=10,
+ weights=values3
+ )
+
+ fig, (ax4, ax5, ax6) = plt.subplots(3, 1, constrained_layout=True)
+ ax4.hist(
+ [start_date + i * time_delta for i in range(30)],
+ bins=bin_edges,
+ weights=values1
+ )
+ ax5.hist(
+ [start_date + i * time_delta for i in range(30)],
+ bins=bin_edges,
+ weights=values2
+ )
+ ax6.hist(
+ [start_date + i * time_delta for i in range(30)],
+ bins=bin_edges,
+ weights=values3
+ )
+
+ @pytest.mark.xfail(reason="Test for hist2d not written yet")
+ @mpl.style.context("default")
+ def test_hist2d(self):
+ fig, ax = plt.subplots()
+ ax.hist2d(...)
+
+ @mpl.style.context("default")
+ def test_hlines(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ fig, axs = plt.subplots(2, 4, layout='constrained')
+ dateStrs = ['2023-03-08',
+ '2023-04-09',
+ '2023-05-13',
+ '2023-07-28',
+ '2023-12-24']
+ dates = [datetime.datetime(2023, m*2, 10) for m in range(1, 6)]
+ date_start = [datetime.datetime(2023, 6, d) for d in range(5, 30, 5)]
+ date_end = [datetime.datetime(2023, 7, d) for d in range(5, 30, 5)]
+ npDates = [np.datetime64(s) for s in dateStrs]
+ axs[0, 0].hlines(y=dates,
+ xmin=[0.1, 0.2, 0.3, 0.4, 0.5],
+ xmax=[0.5, 0.6, 0.7, 0.8, 0.9])
+ axs[0, 1].hlines(dates,
+ xmin=datetime.datetime(2020, 5, 10),
+ xmax=datetime.datetime(2020, 5, 31))
+ axs[0, 2].hlines(dates,
+ xmin=date_start,
+ xmax=date_end)
+ axs[0, 3].hlines(dates,
+ xmin=0.45,
+ xmax=0.65)
+ axs[1, 0].hlines(y=npDates,
+ xmin=[0.5, 0.6, 0.7, 0.8, 0.9],
+ xmax=[0.1, 0.2, 0.3, 0.4, 0.5])
+ axs[1, 2].hlines(y=npDates,
+ xmin=date_start,
+ xmax=date_end)
+ axs[1, 1].hlines(npDates,
+ xmin=datetime.datetime(2020, 5, 10),
+ xmax=datetime.datetime(2020, 5, 31))
+ axs[1, 3].hlines(npDates,
+ xmin=0.45,
+ xmax=0.65)
+
+ @mpl.style.context("default")
+ def test_imshow(self):
+ fig, ax = plt.subplots()
+ a = np.diag(range(5))
+ dt_start = datetime.datetime(2010, 11, 1)
+ dt_end = datetime.datetime(2010, 11, 11)
+ extent = (dt_start, dt_end, dt_start, dt_end)
+ ax.imshow(a, extent=extent)
+ ax.tick_params(axis="x", labelrotation=90)
+
+ @pytest.mark.xfail(reason="Test for loglog not written yet")
+ @mpl.style.context("default")
+ def test_loglog(self):
+ fig, ax = plt.subplots()
+ ax.loglog(...)
+
+ @mpl.style.context("default")
+ def test_matshow(self):
+ a = np.diag(range(5))
+ dt_start = datetime.datetime(1980, 4, 15)
+ dt_end = datetime.datetime(2020, 11, 11)
+ extent = (dt_start, dt_end, dt_start, dt_end)
+ fig, ax = plt.subplots()
+ ax.matshow(a, extent=extent)
+ for label in ax.get_xticklabels():
+ label.set_rotation(90)
+
+ @pytest.mark.xfail(reason="Test for pcolor not written yet")
+ @mpl.style.context("default")
+ def test_pcolor(self):
+ fig, ax = plt.subplots()
+ ax.pcolor(...)
+
+ @pytest.mark.xfail(reason="Test for pcolorfast not written yet")
+ @mpl.style.context("default")
+ def test_pcolorfast(self):
+ fig, ax = plt.subplots()
+ ax.pcolorfast(...)
+
+ @pytest.mark.xfail(reason="Test for pcolormesh not written yet")
+ @mpl.style.context("default")
+ def test_pcolormesh(self):
+ fig, ax = plt.subplots()
+ ax.pcolormesh(...)
+
+ @mpl.style.context("default")
+ def test_plot(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ N = 6
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained')
+ x = np.array([datetime.datetime(2023, 9, n) for n in range(1, N)])
+ ax1.plot(x, range(1, N))
+ ax2.plot(range(1, N), x)
+ ax3.plot(x, x)
+
+ @mpl.style.context("default")
+ def test_plot_date(self):
+ mpl.rcParams["date.converter"] = "concise"
+ range_threshold = 10
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained")
+
+ x_dates = np.array(
+ [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)]
+ )
+ y_dates = np.array(
+ [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)]
+ )
+ x_ranges = np.array(range(1, range_threshold))
+ y_ranges = np.array(range(1, range_threshold))
+
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ ax1.plot_date(x_dates, y_dates)
+ ax2.plot_date(x_dates, y_ranges)
+ ax3.plot_date(x_ranges, y_dates)
+
+ @pytest.mark.xfail(reason="Test for quiver not written yet")
+ @mpl.style.context("default")
+ def test_quiver(self):
+ fig, ax = plt.subplots()
+ ax.quiver(...)
+
+ @mpl.style.context("default")
+ def test_scatter(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ base = datetime.datetime(2005, 2, 1)
+ dates = [base + datetime.timedelta(hours=(2 * i)) for i in range(10)]
+ N = len(dates)
+ np.random.seed(19680801)
+ y = np.cumsum(np.random.randn(N))
+ fig, axs = plt.subplots(3, 1, layout='constrained', figsize=(6, 6))
+ # datetime array on x axis
+ axs[0].scatter(dates, y)
+ for label in axs[0].get_xticklabels():
+ label.set_rotation(40)
+ label.set_horizontalalignment('right')
+ # datetime on y axis
+ axs[1].scatter(y, dates)
+ # datetime on both x, y axes
+ axs[2].scatter(dates, dates)
+ for label in axs[2].get_xticklabels():
+ label.set_rotation(40)
+ label.set_horizontalalignment('right')
+
+ @pytest.mark.xfail(reason="Test for semilogx not written yet")
+ @mpl.style.context("default")
+ def test_semilogx(self):
+ fig, ax = plt.subplots()
+ ax.semilogx(...)
+
+ @pytest.mark.xfail(reason="Test for semilogy not written yet")
+ @mpl.style.context("default")
+ def test_semilogy(self):
+ fig, ax = plt.subplots()
+ ax.semilogy(...)
+
+ @mpl.style.context("default")
+ def test_stackplot(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ N = 10
+ stacked_nums = np.tile(np.arange(1, N), (4, 1))
+ dates = np.array([datetime.datetime(2020 + i, 1, 1) for i in range(N - 1)])
+
+ fig, ax = plt.subplots(layout='constrained')
+ ax.stackplot(dates, stacked_nums)
+
+ @mpl.style.context("default")
+ def test_stairs(self):
+ mpl.rcParams["date.converter"] = 'concise'
+
+ start_date = datetime.datetime(2023, 12, 1)
+ time_delta = datetime.timedelta(days=1)
+ baseline_date = datetime.datetime(1980, 1, 1)
+
+ bin_edges = [start_date + i * time_delta for i in range(31)]
+ edge_int = np.arange(31)
+ np.random.seed(123456)
+ values1 = np.random.randint(1, 100, 30)
+ values2 = [start_date + datetime.timedelta(days=int(i))
+ for i in np.random.randint(1, 10000, 30)]
+ values3 = [start_date + datetime.timedelta(days=int(i))
+ for i in np.random.randint(-10000, 10000, 30)]
+
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, constrained_layout=True)
+ ax1.stairs(values1, edges=bin_edges)
+ ax2.stairs(values2, edges=edge_int, baseline=baseline_date)
+ ax3.stairs(values3, edges=bin_edges, baseline=baseline_date)
+
+ @mpl.style.context("default")
+ def test_stem(self):
+ mpl.rcParams["date.converter"] = "concise"
+
+ fig, (ax1, ax2, ax3, ax4, ax5, ax6) = plt.subplots(6, 1, layout="constrained")
+
+ limit_value = 10
+ above = datetime.datetime(2023, 9, 18)
+ below = datetime.datetime(2023, 11, 18)
+
+ x_ranges = np.arange(1, limit_value)
+ y_ranges = np.arange(1, limit_value)
+
+ x_dates = np.array(
+ [datetime.datetime(2023, 10, n) for n in range(1, limit_value)]
+ )
+ y_dates = np.array(
+ [datetime.datetime(2023, 10, n) for n in range(1, limit_value)]
+ )
+
+ ax1.stem(x_dates, y_dates, bottom=above)
+ ax2.stem(x_dates, y_ranges, bottom=5)
+ ax3.stem(x_ranges, y_dates, bottom=below)
+
+ ax4.stem(x_ranges, y_dates, orientation="horizontal", bottom=above)
+ ax5.stem(x_dates, y_ranges, orientation="horizontal", bottom=5)
+ ax6.stem(x_ranges, y_dates, orientation="horizontal", bottom=below)
+
+ @mpl.style.context("default")
+ def test_step(self):
+ mpl.rcParams["date.converter"] = "concise"
+ N = 6
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained')
+ x = np.array([datetime.datetime(2023, 9, n) for n in range(1, N)])
+ ax1.step(x, range(1, N))
+ ax2.step(range(1, N), x)
+ ax3.step(x, x)
+
+ @pytest.mark.xfail(reason="Test for streamplot not written yet")
+ @mpl.style.context("default")
+ def test_streamplot(self):
+ fig, ax = plt.subplots()
+ ax.streamplot(...)
+
+ @mpl.style.context("default")
+ def test_text(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained")
+
+ limit_value = 10
+ font_properties = {'family': 'serif', 'size': 12, 'weight': 'bold'}
+ test_date = datetime.datetime(2023, 10, 1)
+
+ x_data = np.array(range(1, limit_value))
+ y_data = np.array(range(1, limit_value))
+
+ x_dates = np.array(
+ [datetime.datetime(2023, 10, n) for n in range(1, limit_value)]
+ )
+ y_dates = np.array(
+ [datetime.datetime(2023, 10, n) for n in range(1, limit_value)]
+ )
+
+ ax1.plot(x_dates, y_data)
+ ax1.text(test_date, 5, "Inserted Text", **font_properties)
+
+ ax2.plot(x_data, y_dates)
+ ax2.text(7, test_date, "Inserted Text", **font_properties)
+
+ ax3.plot(x_dates, y_dates)
+ ax3.text(test_date, test_date, "Inserted Text", **font_properties)
+
+ @pytest.mark.xfail(reason="Test for tricontour not written yet")
+ @mpl.style.context("default")
+ def test_tricontour(self):
+ fig, ax = plt.subplots()
+ ax.tricontour(...)
+
+ @pytest.mark.xfail(reason="Test for tricontourf not written yet")
+ @mpl.style.context("default")
+ def test_tricontourf(self):
+ fig, ax = plt.subplots()
+ ax.tricontourf(...)
+
+ @pytest.mark.xfail(reason="Test for tripcolor not written yet")
+ @mpl.style.context("default")
+ def test_tripcolor(self):
+ fig, ax = plt.subplots()
+ ax.tripcolor(...)
+
+ @pytest.mark.xfail(reason="Test for triplot not written yet")
+ @mpl.style.context("default")
+ def test_triplot(self):
+ fig, ax = plt.subplots()
+ ax.triplot(...)
+
+ @pytest.mark.xfail(reason="Test for violin not written yet")
+ @mpl.style.context("default")
+ def test_violin(self):
+ fig, ax = plt.subplots()
+ ax.violin(...)
+
+ @pytest.mark.xfail(reason="Test for violinplot not written yet")
+ @mpl.style.context("default")
+ def test_violinplot(self):
+ fig, ax = plt.subplots()
+ ax.violinplot(...)
+
+ @mpl.style.context("default")
+ def test_vlines(self):
+ mpl.rcParams["date.converter"] = 'concise'
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained')
+ ax1.set_xlim(left=datetime.datetime(2023, 1, 1),
+ right=datetime.datetime(2023, 6, 30))
+ ax1.vlines(x=[datetime.datetime(2023, 2, 10),
+ datetime.datetime(2023, 5, 18),
+ datetime.datetime(2023, 6, 6)],
+ ymin=[0, 0.25, 0.5],
+ ymax=[0.25, 0.5, 0.75])
+ ax2.set_xlim(left=0,
+ right=0.5)
+ ax2.vlines(x=[0.3, 0.35],
+ ymin=[np.datetime64('2023-03-20'), np.datetime64('2023-03-31')],
+ ymax=[np.datetime64('2023-05-01'), np.datetime64('2023-05-16')])
+ ax3.set_xlim(left=datetime.datetime(2023, 7, 1),
+ right=datetime.datetime(2023, 12, 31))
+ ax3.vlines(x=[datetime.datetime(2023, 9, 1), datetime.datetime(2023, 12, 10)],
+ ymin=datetime.datetime(2023, 1, 15),
+ ymax=datetime.datetime(2023, 1, 30))
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_determinism.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_determinism.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ecc40dbd3c0943c7bd95fa110054c7e9e6c0ad4
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_determinism.py
@@ -0,0 +1,218 @@
+"""
+Test output reproducibility.
+"""
+
+import os
+import sys
+
+import pytest
+
+import matplotlib as mpl
+from matplotlib import pyplot as plt
+from matplotlib.cbook import get_sample_data
+from matplotlib.collections import PathCollection
+from matplotlib.image import BboxImage
+from matplotlib.offsetbox import AnchoredOffsetbox, AuxTransformBox
+from matplotlib.patches import Circle, PathPatch
+from matplotlib.path import Path
+from matplotlib.testing import subprocess_run_for_testing
+from matplotlib.testing._markers import needs_ghostscript, needs_usetex
+import matplotlib.testing.compare
+from matplotlib.text import TextPath
+from matplotlib.transforms import IdentityTransform
+
+
+def _save_figure(objects='mhip', fmt="pdf", usetex=False):
+ mpl.use(fmt)
+ mpl.rcParams.update({'svg.hashsalt': 'asdf', 'text.usetex': usetex})
+
+ fig = plt.figure()
+
+ if 'm' in objects:
+ # use different markers...
+ ax1 = fig.add_subplot(1, 6, 1)
+ x = range(10)
+ ax1.plot(x, [1] * 10, marker='D')
+ ax1.plot(x, [2] * 10, marker='x')
+ ax1.plot(x, [3] * 10, marker='^')
+ ax1.plot(x, [4] * 10, marker='H')
+ ax1.plot(x, [5] * 10, marker='v')
+
+ if 'h' in objects:
+ # also use different hatch patterns
+ ax2 = fig.add_subplot(1, 6, 2)
+ bars = (ax2.bar(range(1, 5), range(1, 5)) +
+ ax2.bar(range(1, 5), [6] * 4, bottom=range(1, 5)))
+ ax2.set_xticks([1.5, 2.5, 3.5, 4.5])
+
+ patterns = ('-', '+', 'x', '\\', '*', 'o', 'O', '.')
+ for bar, pattern in zip(bars, patterns):
+ bar.set_hatch(pattern)
+
+ if 'i' in objects:
+ # also use different images
+ A = [[1, 2, 3], [2, 3, 1], [3, 1, 2]]
+ fig.add_subplot(1, 6, 3).imshow(A, interpolation='nearest')
+ A = [[1, 3, 2], [1, 2, 3], [3, 1, 2]]
+ fig.add_subplot(1, 6, 4).imshow(A, interpolation='bilinear')
+ A = [[2, 3, 1], [1, 2, 3], [2, 1, 3]]
+ fig.add_subplot(1, 6, 5).imshow(A, interpolation='bicubic')
+
+ if 'p' in objects:
+
+ # clipping support class, copied from demo_text_path.py gallery example
+ class PathClippedImagePatch(PathPatch):
+ """
+ The given image is used to draw the face of the patch. Internally,
+ it uses BboxImage whose clippath set to the path of the patch.
+
+ FIXME : The result is currently dpi dependent.
+ """
+
+ def __init__(self, path, bbox_image, **kwargs):
+ super().__init__(path, **kwargs)
+ self.bbox_image = BboxImage(
+ self.get_window_extent, norm=None, origin=None)
+ self.bbox_image.set_data(bbox_image)
+
+ def set_facecolor(self, color):
+ """Simply ignore facecolor."""
+ super().set_facecolor("none")
+
+ def draw(self, renderer=None):
+ # the clip path must be updated every draw. any solution? -JJ
+ self.bbox_image.set_clip_path(self._path, self.get_transform())
+ self.bbox_image.draw(renderer)
+ super().draw(renderer)
+
+ # add a polar projection
+ px = fig.add_subplot(projection="polar")
+ pimg = px.imshow([[2]])
+ pimg.set_clip_path(Circle((0, 1), radius=0.3333))
+
+ # add a text-based clipping path (origin: demo_text_path.py)
+ (ax1, ax2) = fig.subplots(2)
+ arr = plt.imread(get_sample_data("grace_hopper.jpg"))
+ text_path = TextPath((0, 0), "!?", size=150)
+ p = PathClippedImagePatch(text_path, arr, ec="k")
+ offsetbox = AuxTransformBox(IdentityTransform())
+ offsetbox.add_artist(p)
+ ao = AnchoredOffsetbox(loc='upper left', child=offsetbox, frameon=True,
+ borderpad=0.2)
+ ax1.add_artist(ao)
+
+ # add a 2x2 grid of path-clipped axes (origin: test_artist.py)
+ exterior = Path.unit_rectangle().deepcopy()
+ exterior.vertices *= 4
+ exterior.vertices -= 2
+ interior = Path.unit_circle().deepcopy()
+ interior.vertices = interior.vertices[::-1]
+ clip_path = Path.make_compound_path(exterior, interior)
+
+ star = Path.unit_regular_star(6).deepcopy()
+ star.vertices *= 2.6
+
+ (row1, row2) = fig.subplots(2, 2, sharex=True, sharey=True)
+ for row in (row1, row2):
+ ax1, ax2 = row
+ collection = PathCollection([star], lw=5, edgecolor='blue',
+ facecolor='red', alpha=0.7, hatch='*')
+ collection.set_clip_path(clip_path, ax1.transData)
+ ax1.add_collection(collection)
+
+ patch = PathPatch(star, lw=5, edgecolor='blue', facecolor='red',
+ alpha=0.7, hatch='*')
+ patch.set_clip_path(clip_path, ax2.transData)
+ ax2.add_patch(patch)
+
+ ax1.set_xlim([-3, 3])
+ ax1.set_ylim([-3, 3])
+
+ x = range(5)
+ ax = fig.add_subplot(1, 6, 6)
+ ax.plot(x, x)
+ ax.set_title('A string $1+2+\\sigma$')
+ ax.set_xlabel('A string $1+2+\\sigma$')
+ ax.set_ylabel('A string $1+2+\\sigma$')
+
+ stdout = getattr(sys.stdout, 'buffer', sys.stdout)
+ fig.savefig(stdout, format=fmt)
+
+
+@pytest.mark.parametrize(
+ "objects, fmt, usetex", [
+ ("", "pdf", False),
+ ("m", "pdf", False),
+ ("h", "pdf", False),
+ ("i", "pdf", False),
+ ("mhip", "pdf", False),
+ ("mhip", "ps", False),
+ pytest.param(
+ "mhip", "ps", True, marks=[needs_usetex, needs_ghostscript]),
+ ("p", "svg", False),
+ ("mhip", "svg", False),
+ pytest.param("mhip", "svg", True, marks=needs_usetex),
+ ]
+)
+def test_determinism_check(objects, fmt, usetex):
+ """
+ Output three times the same graphs and checks that the outputs are exactly
+ the same.
+
+ Parameters
+ ----------
+ objects : str
+ Objects to be included in the test document: 'm' for markers, 'h' for
+ hatch patterns, 'i' for images, and 'p' for paths.
+ fmt : {"pdf", "ps", "svg"}
+ Output format.
+ """
+ plots = [
+ subprocess_run_for_testing(
+ [sys.executable, "-R", "-c",
+ f"from matplotlib.tests.test_determinism import _save_figure;"
+ f"_save_figure({objects!r}, {fmt!r}, {usetex})"],
+ env={**os.environ, "SOURCE_DATE_EPOCH": "946684800",
+ "MPLBACKEND": "Agg"},
+ text=False, capture_output=True, check=True).stdout
+ for _ in range(3)
+ ]
+ for p in plots[1:]:
+ if fmt == "ps" and usetex:
+ if p != plots[0]:
+ pytest.skip("failed, maybe due to ghostscript timestamps")
+ else:
+ assert p == plots[0]
+
+
+@pytest.mark.parametrize(
+ "fmt, string", [
+ ("pdf", b"/CreationDate (D:20000101000000Z)"),
+ # SOURCE_DATE_EPOCH support is not tested with text.usetex,
+ # because the produced timestamp comes from ghostscript:
+ # %%CreationDate: D:20000101000000Z00\'00\', and this could change
+ # with another ghostscript version.
+ ("ps", b"%%CreationDate: Sat Jan 01 00:00:00 2000"),
+ ]
+)
+def test_determinism_source_date_epoch(fmt, string):
+ """
+ Test SOURCE_DATE_EPOCH support. Output a document with the environment
+ variable SOURCE_DATE_EPOCH set to 2000-01-01 00:00 UTC and check that the
+ document contains the timestamp that corresponds to this date (given as an
+ argument).
+
+ Parameters
+ ----------
+ fmt : {"pdf", "ps", "svg"}
+ Output format.
+ string : bytes
+ Timestamp string for 2000-01-01 00:00 UTC.
+ """
+ buf = subprocess_run_for_testing(
+ [sys.executable, "-R", "-c",
+ f"from matplotlib.tests.test_determinism import _save_figure; "
+ f"_save_figure('', {fmt!r})"],
+ env={**os.environ, "SOURCE_DATE_EPOCH": "946684800",
+ "MPLBACKEND": "Agg"}, capture_output=True, text=False, check=True).stdout
+ assert string in buf
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_doc.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_doc.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e28fd1b8eb785becb2613be4e0f456f810c68c1
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_doc.py
@@ -0,0 +1,35 @@
+import pytest
+
+
+def test_sphinx_gallery_example_header():
+ """
+ We have copied EXAMPLE_HEADER and modified it to include meta keywords.
+ This test monitors that the version we have copied is still the same as
+ the EXAMPLE_HEADER in sphinx-gallery. If sphinx-gallery changes its
+ EXAMPLE_HEADER, this test will start to fail. In that case, please update
+ the monkey-patching of EXAMPLE_HEADER in conf.py.
+ """
+ pytest.importorskip('sphinx_gallery', minversion='0.16.0')
+ from sphinx_gallery import gen_rst
+
+ EXAMPLE_HEADER = """
+.. DO NOT EDIT.
+.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
+.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
+.. "{0}"
+.. LINE NUMBERS ARE GIVEN BELOW.
+
+.. only:: html
+
+ .. note::
+ :class: sphx-glr-download-link-note
+
+ :ref:`Go to the end
`
+ to download the full example code.{2}
+
+.. rst-class:: sphx-glr-example-title
+
+.. _sphx_glr_{1}:
+
+"""
+ assert gen_rst.EXAMPLE_HEADER == EXAMPLE_HEADER
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_dviread.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_dviread.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b7ff151be1847671e2fe3be0759dedb31b863d4
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_dviread.py
@@ -0,0 +1,77 @@
+import json
+from pathlib import Path
+import shutil
+
+import matplotlib.dviread as dr
+import pytest
+
+
+def test_PsfontsMap(monkeypatch):
+ monkeypatch.setattr(dr, 'find_tex_file', lambda x: x.decode())
+
+ filename = str(Path(__file__).parent / 'baseline_images/dviread/test.map')
+ fontmap = dr.PsfontsMap(filename)
+ # Check all properties of a few fonts
+ for n in [1, 2, 3, 4, 5]:
+ key = b'TeXfont%d' % n
+ entry = fontmap[key]
+ assert entry.texname == key
+ assert entry.psname == b'PSfont%d' % n
+ if n not in [3, 5]:
+ assert entry.encoding == 'font%d.enc' % n
+ elif n == 3:
+ assert entry.encoding == 'enc3.foo'
+ # We don't care about the encoding of TeXfont5, which specifies
+ # multiple encodings.
+ if n not in [1, 5]:
+ assert entry.filename == 'font%d.pfa' % n
+ else:
+ assert entry.filename == 'font%d.pfb' % n
+ if n == 4:
+ assert entry.effects == {'slant': -0.1, 'extend': 1.2}
+ else:
+ assert entry.effects == {}
+ # Some special cases
+ entry = fontmap[b'TeXfont6']
+ assert entry.filename is None
+ assert entry.encoding is None
+ entry = fontmap[b'TeXfont7']
+ assert entry.filename is None
+ assert entry.encoding == 'font7.enc'
+ entry = fontmap[b'TeXfont8']
+ assert entry.filename == 'font8.pfb'
+ assert entry.encoding is None
+ entry = fontmap[b'TeXfont9']
+ assert entry.psname == b'TeXfont9'
+ assert entry.filename == '/absolute/font9.pfb'
+ # First of duplicates only.
+ entry = fontmap[b'TeXfontA']
+ assert entry.psname == b'PSfontA1'
+ # Slant/Extend only works for T1 fonts.
+ entry = fontmap[b'TeXfontB']
+ assert entry.psname == b'PSfontB6'
+ # Subsetted TrueType must have encoding.
+ entry = fontmap[b'TeXfontC']
+ assert entry.psname == b'PSfontC3'
+ # Missing font
+ with pytest.raises(LookupError, match='no-such-font'):
+ fontmap[b'no-such-font']
+ with pytest.raises(LookupError, match='%'):
+ fontmap[b'%']
+
+
+@pytest.mark.skipif(shutil.which("kpsewhich") is None,
+ reason="kpsewhich is not available")
+def test_dviread():
+ dirpath = Path(__file__).parent / 'baseline_images/dviread'
+ with (dirpath / 'test.json').open() as f:
+ correct = json.load(f)
+ with dr.Dvi(str(dirpath / 'test.dvi'), None) as dvi:
+ data = [{'text': [[t.x, t.y,
+ chr(t.glyph),
+ t.font.texname.decode('ascii'),
+ round(t.font.size, 2)]
+ for t in page.text],
+ 'boxes': [[b.x, b.y, b.height, b.width] for b in page.boxes]}
+ for page in dvi]
+ assert data == correct
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_figure.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_figure.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a4ced254091ed8e02f0dce1f90d3f5a68b173db
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_figure.py
@@ -0,0 +1,1821 @@
+import copy
+from datetime import datetime
+import io
+import pickle
+import platform
+from threading import Timer
+from types import SimpleNamespace
+import warnings
+
+import numpy as np
+import pytest
+from PIL import Image
+
+import matplotlib as mpl
+from matplotlib import gridspec
+from matplotlib.testing.decorators import image_comparison, check_figures_equal
+from matplotlib.axes import Axes
+from matplotlib.backend_bases import KeyEvent, MouseEvent
+from matplotlib.figure import Figure, FigureBase
+from matplotlib.layout_engine import (ConstrainedLayoutEngine,
+ TightLayoutEngine,
+ PlaceHolderLayoutEngine)
+from matplotlib.ticker import AutoMinorLocator, FixedFormatter, ScalarFormatter
+import matplotlib.pyplot as plt
+import matplotlib.dates as mdates
+
+
+@image_comparison(['figure_align_labels'], extensions=['png', 'svg'],
+ tol=0 if platform.machine() == 'x86_64' else 0.01)
+def test_align_labels():
+ fig = plt.figure(layout='tight')
+ gs = gridspec.GridSpec(3, 3)
+
+ ax = fig.add_subplot(gs[0, :2])
+ ax.plot(np.arange(0, 1e6, 1000))
+ ax.set_ylabel('Ylabel0 0')
+ ax = fig.add_subplot(gs[0, -1])
+ ax.plot(np.arange(0, 1e4, 100))
+
+ for i in range(3):
+ ax = fig.add_subplot(gs[1, i])
+ ax.set_ylabel('YLabel1 %d' % i)
+ ax.set_xlabel('XLabel1 %d' % i)
+ if i in [0, 2]:
+ ax.xaxis.set_label_position("top")
+ ax.xaxis.tick_top()
+ if i == 0:
+ for tick in ax.get_xticklabels():
+ tick.set_rotation(90)
+ if i == 2:
+ ax.yaxis.set_label_position("right")
+ ax.yaxis.tick_right()
+
+ for i in range(3):
+ ax = fig.add_subplot(gs[2, i])
+ ax.set_xlabel(f'XLabel2 {i}')
+ ax.set_ylabel(f'YLabel2 {i}')
+
+ if i == 2:
+ ax.plot(np.arange(0, 1e4, 10))
+ ax.yaxis.set_label_position("right")
+ ax.yaxis.tick_right()
+ for tick in ax.get_xticklabels():
+ tick.set_rotation(90)
+
+ fig.align_labels()
+
+
+@image_comparison(['figure_align_titles_tight.png',
+ 'figure_align_titles_constrained.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.022,
+ style='mpl20')
+def test_align_titles():
+ for layout in ['tight', 'constrained']:
+ fig, axs = plt.subplots(1, 2, layout=layout, width_ratios=[2, 1])
+
+ ax = axs[0]
+ ax.plot(np.arange(0, 1e6, 1000))
+ ax.set_title('Title0 left', loc='left')
+ ax.set_title('Title0 center', loc='center')
+ ax.set_title('Title0 right', loc='right')
+
+ ax = axs[1]
+ ax.plot(np.arange(0, 1e4, 100))
+ ax.set_title('Title1')
+ ax.set_xlabel('Xlabel0')
+ ax.xaxis.set_label_position("top")
+ ax.xaxis.tick_top()
+ for tick in ax.get_xticklabels():
+ tick.set_rotation(90)
+
+ fig.align_titles()
+
+
+def test_align_labels_stray_axes():
+ fig, axs = plt.subplots(2, 2)
+ for nn, ax in enumerate(axs.flat):
+ ax.set_xlabel('Boo')
+ ax.set_xlabel('Who')
+ ax.plot(np.arange(4)**nn, np.arange(4)**nn)
+ fig.align_ylabels()
+ fig.align_xlabels()
+ fig.draw_without_rendering()
+ xn = np.zeros(4)
+ yn = np.zeros(4)
+ for nn, ax in enumerate(axs.flat):
+ yn[nn] = ax.xaxis.label.get_position()[1]
+ xn[nn] = ax.yaxis.label.get_position()[0]
+ np.testing.assert_allclose(xn[:2], xn[2:])
+ np.testing.assert_allclose(yn[::2], yn[1::2])
+
+ fig, axs = plt.subplots(2, 2, constrained_layout=True)
+ for nn, ax in enumerate(axs.flat):
+ ax.set_xlabel('Boo')
+ ax.set_xlabel('Who')
+ pc = ax.pcolormesh(np.random.randn(10, 10))
+ fig.colorbar(pc, ax=ax)
+ fig.align_ylabels()
+ fig.align_xlabels()
+ fig.draw_without_rendering()
+ xn = np.zeros(4)
+ yn = np.zeros(4)
+ for nn, ax in enumerate(axs.flat):
+ yn[nn] = ax.xaxis.label.get_position()[1]
+ xn[nn] = ax.yaxis.label.get_position()[0]
+ np.testing.assert_allclose(xn[:2], xn[2:])
+ np.testing.assert_allclose(yn[::2], yn[1::2])
+
+
+def test_figure_label():
+ # pyplot figure creation, selection, and closing with label/number/instance
+ plt.close('all')
+ fig_today = plt.figure('today')
+ plt.figure(3)
+ plt.figure('tomorrow')
+ plt.figure()
+ plt.figure(0)
+ plt.figure(1)
+ plt.figure(3)
+ assert plt.get_fignums() == [0, 1, 3, 4, 5]
+ assert plt.get_figlabels() == ['', 'today', '', 'tomorrow', '']
+ plt.close(10)
+ plt.close()
+ plt.close(5)
+ plt.close('tomorrow')
+ assert plt.get_fignums() == [0, 1]
+ assert plt.get_figlabels() == ['', 'today']
+ plt.figure(fig_today)
+ assert plt.gcf() == fig_today
+ with pytest.raises(ValueError):
+ plt.figure(Figure())
+
+
+def test_figure_label_replaced():
+ plt.close('all')
+ fig = plt.figure(1)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match="Changing 'Figure.number' is deprecated"):
+ fig.number = 2
+ assert fig.number == 2
+
+
+def test_figure_no_label():
+ # standalone figures do not have a figure attribute
+ fig = Figure()
+ with pytest.raises(AttributeError):
+ fig.number
+ # but one can set one
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match="Changing 'Figure.number' is deprecated"):
+ fig.number = 5
+ assert fig.number == 5
+ # even though it's not known by pyplot
+ assert not plt.fignum_exists(fig.number)
+
+
+def test_fignum_exists():
+ # pyplot figure creation, selection and closing with fignum_exists
+ plt.figure('one')
+ plt.figure(2)
+ plt.figure('three')
+ plt.figure()
+ assert plt.fignum_exists('one')
+ assert plt.fignum_exists(2)
+ assert plt.fignum_exists('three')
+ assert plt.fignum_exists(4)
+ plt.close('one')
+ plt.close(4)
+ assert not plt.fignum_exists('one')
+ assert not plt.fignum_exists(4)
+
+
+def test_clf_keyword():
+ # test if existing figure is cleared with figure() and subplots()
+ text1 = 'A fancy plot'
+ text2 = 'Really fancy!'
+
+ fig0 = plt.figure(num=1)
+ fig0.suptitle(text1)
+ assert [t.get_text() for t in fig0.texts] == [text1]
+
+ fig1 = plt.figure(num=1, clear=False)
+ fig1.text(0.5, 0.5, text2)
+ assert fig0 is fig1
+ assert [t.get_text() for t in fig1.texts] == [text1, text2]
+
+ fig2, ax2 = plt.subplots(2, 1, num=1, clear=True)
+ assert fig0 is fig2
+ assert [t.get_text() for t in fig2.texts] == []
+
+
+@image_comparison(['figure_today.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.015)
+def test_figure():
+ # named figure support
+ fig = plt.figure('today')
+ ax = fig.add_subplot()
+ ax.set_title(fig.get_label())
+ ax.plot(np.arange(5))
+ # plot red line in a different figure.
+ plt.figure('tomorrow')
+ plt.plot([0, 1], [1, 0], 'r')
+ # Return to the original; make sure the red line is not there.
+ plt.figure('today')
+ plt.close('tomorrow')
+
+
+@image_comparison(['figure_legend.png'])
+def test_figure_legend():
+ fig, axs = plt.subplots(2)
+ axs[0].plot([0, 1], [1, 0], label='x', color='g')
+ axs[0].plot([0, 1], [0, 1], label='y', color='r')
+ axs[0].plot([0, 1], [0.5, 0.5], label='y', color='k')
+
+ axs[1].plot([0, 1], [1, 0], label='_y', color='r')
+ axs[1].plot([0, 1], [0, 1], label='z', color='b')
+ fig.legend()
+
+
+def test_gca():
+ fig = plt.figure()
+
+ # test that gca() picks up Axes created via add_axes()
+ ax0 = fig.add_axes([0, 0, 1, 1])
+ assert fig.gca() is ax0
+
+ # test that gca() picks up Axes created via add_subplot()
+ ax1 = fig.add_subplot(111)
+ assert fig.gca() is ax1
+
+ # add_axes on an existing Axes should not change stored order, but will
+ # make it current.
+ fig.add_axes(ax0)
+ assert fig.axes == [ax0, ax1]
+ assert fig.gca() is ax0
+
+ # sca() should not change stored order of Axes, which is order added.
+ fig.sca(ax0)
+ assert fig.axes == [ax0, ax1]
+
+ # add_subplot on an existing Axes should not change stored order, but will
+ # make it current.
+ fig.add_subplot(ax1)
+ assert fig.axes == [ax0, ax1]
+ assert fig.gca() is ax1
+
+
+def test_add_subplot_subclass():
+ fig = plt.figure()
+ fig.add_subplot(axes_class=Axes)
+ with pytest.raises(ValueError):
+ fig.add_subplot(axes_class=Axes, projection="3d")
+ with pytest.raises(ValueError):
+ fig.add_subplot(axes_class=Axes, polar=True)
+ with pytest.raises(ValueError):
+ fig.add_subplot(projection="3d", polar=True)
+ with pytest.raises(TypeError):
+ fig.add_subplot(projection=42)
+
+
+def test_add_subplot_invalid():
+ fig = plt.figure()
+ with pytest.raises(ValueError,
+ match='Number of columns must be a positive integer'):
+ fig.add_subplot(2, 0, 1)
+ with pytest.raises(ValueError,
+ match='Number of rows must be a positive integer'):
+ fig.add_subplot(0, 2, 1)
+ with pytest.raises(ValueError, match='num must be an integer with '
+ '1 <= num <= 4'):
+ fig.add_subplot(2, 2, 0)
+ with pytest.raises(ValueError, match='num must be an integer with '
+ '1 <= num <= 4'):
+ fig.add_subplot(2, 2, 5)
+ with pytest.raises(ValueError, match='num must be an integer with '
+ '1 <= num <= 4'):
+ fig.add_subplot(2, 2, 0.5)
+
+ with pytest.raises(ValueError, match='must be a three-digit integer'):
+ fig.add_subplot(42)
+ with pytest.raises(ValueError, match='must be a three-digit integer'):
+ fig.add_subplot(1000)
+
+ with pytest.raises(TypeError, match='takes 1 or 3 positional arguments '
+ 'but 2 were given'):
+ fig.add_subplot(2, 2)
+ with pytest.raises(TypeError, match='takes 1 or 3 positional arguments '
+ 'but 4 were given'):
+ fig.add_subplot(1, 2, 3, 4)
+ with pytest.raises(ValueError,
+ match="Number of rows must be a positive integer, "
+ "not '2'"):
+ fig.add_subplot('2', 2, 1)
+ with pytest.raises(ValueError,
+ match='Number of columns must be a positive integer, '
+ 'not 2.0'):
+ fig.add_subplot(2, 2.0, 1)
+ _, ax = plt.subplots()
+ with pytest.raises(ValueError,
+ match='The Axes must have been created in the '
+ 'present figure'):
+ fig.add_subplot(ax)
+
+
+@image_comparison(['figure_suptitle.png'])
+def test_suptitle():
+ fig, _ = plt.subplots()
+ fig.suptitle('hello', color='r')
+ fig.suptitle('title', color='g', rotation=30)
+
+
+def test_suptitle_fontproperties():
+ fig, ax = plt.subplots()
+ fps = mpl.font_manager.FontProperties(size='large', weight='bold')
+ txt = fig.suptitle('fontprops title', fontproperties=fps)
+ assert txt.get_fontsize() == fps.get_size_in_points()
+ assert txt.get_weight() == fps.get_weight()
+
+
+def test_suptitle_subfigures():
+ fig = plt.figure(figsize=(4, 3))
+ sf1, sf2 = fig.subfigures(1, 2)
+ sf2.set_facecolor('white')
+ sf1.subplots()
+ sf2.subplots()
+ fig.suptitle("This is a visible suptitle.")
+
+ # verify the first subfigure facecolor is the default transparent
+ assert sf1.get_facecolor() == (0.0, 0.0, 0.0, 0.0)
+ # verify the second subfigure facecolor is white
+ assert sf2.get_facecolor() == (1.0, 1.0, 1.0, 1.0)
+
+
+def test_get_suptitle_supxlabel_supylabel():
+ fig, ax = plt.subplots()
+ assert fig.get_suptitle() == ""
+ assert fig.get_supxlabel() == ""
+ assert fig.get_supylabel() == ""
+ fig.suptitle('suptitle')
+ assert fig.get_suptitle() == 'suptitle'
+ fig.supxlabel('supxlabel')
+ assert fig.get_supxlabel() == 'supxlabel'
+ fig.supylabel('supylabel')
+ assert fig.get_supylabel() == 'supylabel'
+
+
+@image_comparison(['alpha_background'],
+ # only test png and svg. The PDF output appears correct,
+ # but Ghostscript does not preserve the background color.
+ extensions=['png', 'svg'],
+ savefig_kwarg={'facecolor': (0, 1, 0.4),
+ 'edgecolor': 'none'})
+def test_alpha():
+ # We want an image which has a background color and an alpha of 0.4.
+ fig = plt.figure(figsize=[2, 1])
+ fig.set_facecolor((0, 1, 0.4))
+ fig.patch.set_alpha(0.4)
+ fig.patches.append(mpl.patches.CirclePolygon(
+ [20, 20], radius=15, alpha=0.6, facecolor='red'))
+
+
+def test_too_many_figures():
+ with pytest.warns(RuntimeWarning):
+ for i in range(mpl.rcParams['figure.max_open_warning'] + 1):
+ plt.figure()
+
+
+def test_iterability_axes_argument():
+
+ # This is a regression test for matplotlib/matplotlib#3196. If one of the
+ # arguments returned by _as_mpl_axes defines __getitem__ but is not
+ # iterable, this would raise an exception. This is because we check
+ # whether the arguments are iterable, and if so we try and convert them
+ # to a tuple. However, the ``iterable`` function returns True if
+ # __getitem__ is present, but some classes can define __getitem__ without
+ # being iterable. The tuple conversion is now done in a try...except in
+ # case it fails.
+
+ class MyAxes(Axes):
+ def __init__(self, *args, myclass=None, **kwargs):
+ Axes.__init__(self, *args, **kwargs)
+
+ class MyClass:
+
+ def __getitem__(self, item):
+ if item != 'a':
+ raise ValueError("item should be a")
+
+ def _as_mpl_axes(self):
+ return MyAxes, {'myclass': self}
+
+ fig = plt.figure()
+ fig.add_subplot(1, 1, 1, projection=MyClass())
+ plt.close(fig)
+
+
+def test_set_fig_size():
+ fig = plt.figure()
+
+ # check figwidth
+ fig.set_figwidth(5)
+ assert fig.get_figwidth() == 5
+
+ # check figheight
+ fig.set_figheight(1)
+ assert fig.get_figheight() == 1
+
+ # check using set_size_inches
+ fig.set_size_inches(2, 4)
+ assert fig.get_figwidth() == 2
+ assert fig.get_figheight() == 4
+
+ # check using tuple to first argument
+ fig.set_size_inches((1, 3))
+ assert fig.get_figwidth() == 1
+ assert fig.get_figheight() == 3
+
+
+def test_axes_remove():
+ fig, axs = plt.subplots(2, 2)
+ axs[-1, -1].remove()
+ for ax in axs.ravel()[:-1]:
+ assert ax in fig.axes
+ assert axs[-1, -1] not in fig.axes
+ assert len(fig.axes) == 3
+
+
+def test_figaspect():
+ w, h = plt.figaspect(np.float64(2) / np.float64(1))
+ assert h / w == 2
+ w, h = plt.figaspect(2)
+ assert h / w == 2
+ w, h = plt.figaspect(np.zeros((1, 2)))
+ assert h / w == 0.5
+ w, h = plt.figaspect(np.zeros((2, 2)))
+ assert h / w == 1
+
+
+@pytest.mark.parametrize('which', ['both', 'major', 'minor'])
+def test_autofmt_xdate(which):
+ date = ['3 Jan 2013', '4 Jan 2013', '5 Jan 2013', '6 Jan 2013',
+ '7 Jan 2013', '8 Jan 2013', '9 Jan 2013', '10 Jan 2013',
+ '11 Jan 2013', '12 Jan 2013', '13 Jan 2013', '14 Jan 2013']
+
+ time = ['16:44:00', '16:45:00', '16:46:00', '16:47:00', '16:48:00',
+ '16:49:00', '16:51:00', '16:52:00', '16:53:00', '16:55:00',
+ '16:56:00', '16:57:00']
+
+ angle = 60
+ minors = [1, 2, 3, 4, 5, 6, 7]
+
+ x = mdates.datestr2num(date)
+ y = mdates.datestr2num(time)
+
+ fig, ax = plt.subplots()
+
+ ax.plot(x, y)
+ ax.yaxis_date()
+ ax.xaxis_date()
+
+ ax.xaxis.set_minor_locator(AutoMinorLocator(2))
+ with warnings.catch_warnings():
+ warnings.filterwarnings(
+ 'ignore',
+ 'FixedFormatter should only be used together with FixedLocator')
+ ax.xaxis.set_minor_formatter(FixedFormatter(minors))
+
+ fig.autofmt_xdate(0.2, angle, 'right', which)
+
+ if which in ('both', 'major'):
+ for label in fig.axes[0].get_xticklabels(False, 'major'):
+ assert int(label.get_rotation()) == angle
+
+ if which in ('both', 'minor'):
+ for label in fig.axes[0].get_xticklabels(True, 'minor'):
+ assert int(label.get_rotation()) == angle
+
+
+def test_autofmt_xdate_colorbar_constrained():
+ # check works with a colorbar.
+ # with constrained layout, colorbars do not have a gridspec,
+ # but autofmt_xdate checks if all axes have a gridspec before being
+ # applied.
+ fig, ax = plt.subplots(layout="constrained")
+ im = ax.imshow([[1, 4, 6], [2, 3, 5]])
+ plt.colorbar(im)
+ fig.autofmt_xdate()
+ fig.draw_without_rendering()
+ label = ax.get_xticklabels(which='major')[1]
+ assert label.get_rotation() == 30.0
+
+
+@mpl.style.context('default')
+def test_change_dpi():
+ fig = plt.figure(figsize=(4, 4))
+ fig.draw_without_rendering()
+ assert fig.canvas.renderer.height == 400
+ assert fig.canvas.renderer.width == 400
+ fig.dpi = 50
+ fig.draw_without_rendering()
+ assert fig.canvas.renderer.height == 200
+ assert fig.canvas.renderer.width == 200
+
+
+@pytest.mark.parametrize('width, height', [
+ (1, np.nan),
+ (-1, 1),
+ (np.inf, 1)
+])
+def test_invalid_figure_size(width, height):
+ with pytest.raises(ValueError):
+ plt.figure(figsize=(width, height))
+
+ fig = plt.figure()
+ with pytest.raises(ValueError):
+ fig.set_size_inches(width, height)
+
+
+def test_invalid_figure_add_axes():
+ fig = plt.figure()
+ with pytest.raises(TypeError,
+ match="missing 1 required positional argument: 'rect'"):
+ fig.add_axes()
+
+ with pytest.raises(ValueError):
+ fig.add_axes((.1, .1, .5, np.nan))
+
+ with pytest.raises(TypeError, match="multiple values for argument 'rect'"):
+ fig.add_axes([0, 0, 1, 1], rect=[0, 0, 1, 1])
+
+ fig2, ax = plt.subplots()
+ with pytest.raises(ValueError,
+ match="The Axes must have been created in the present "
+ "figure"):
+ fig.add_axes(ax)
+
+ fig2.delaxes(ax)
+ with pytest.raises(TypeError, match=r"add_axes\(\) takes 1 positional arguments"):
+ fig2.add_axes(ax, "extra positional argument")
+
+ with pytest.raises(TypeError, match=r"add_axes\(\) takes 1 positional arguments"):
+ fig.add_axes([0, 0, 1, 1], "extra positional argument")
+
+
+def test_subplots_shareax_loglabels():
+ fig, axs = plt.subplots(2, 2, sharex=True, sharey=True, squeeze=False)
+ for ax in axs.flat:
+ ax.plot([10, 20, 30], [10, 20, 30])
+
+ ax.set_yscale("log")
+ ax.set_xscale("log")
+
+ for ax in axs[0, :]:
+ assert 0 == len(ax.xaxis.get_ticklabels(which='both'))
+
+ for ax in axs[1, :]:
+ assert 0 < len(ax.xaxis.get_ticklabels(which='both'))
+
+ for ax in axs[:, 1]:
+ assert 0 == len(ax.yaxis.get_ticklabels(which='both'))
+
+ for ax in axs[:, 0]:
+ assert 0 < len(ax.yaxis.get_ticklabels(which='both'))
+
+
+def test_savefig():
+ fig = plt.figure()
+ msg = r"savefig\(\) takes 2 positional arguments but 3 were given"
+ with pytest.raises(TypeError, match=msg):
+ fig.savefig("fname1.png", "fname2.png")
+
+
+def test_savefig_warns():
+ fig = plt.figure()
+ for format in ['png', 'pdf', 'svg', 'tif', 'jpg']:
+ with pytest.raises(TypeError):
+ fig.savefig(io.BytesIO(), format=format, non_existent_kwarg=True)
+
+
+def test_savefig_backend():
+ fig = plt.figure()
+ # Intentionally use an invalid module name.
+ with pytest.raises(ModuleNotFoundError, match="No module named '@absent'"):
+ fig.savefig("test", backend="module://@absent")
+ with pytest.raises(ValueError,
+ match="The 'pdf' backend does not support png output"):
+ fig.savefig("test.png", backend="pdf")
+
+
+@pytest.mark.parametrize('backend', [
+ pytest.param('Agg', marks=[pytest.mark.backend('Agg')]),
+ pytest.param('Cairo', marks=[pytest.mark.backend('Cairo')]),
+])
+def test_savefig_pixel_ratio(backend):
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3])
+ with io.BytesIO() as buf:
+ fig.savefig(buf, format='png')
+ ratio1 = Image.open(buf)
+ ratio1.load()
+
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3])
+ fig.canvas._set_device_pixel_ratio(2)
+ with io.BytesIO() as buf:
+ fig.savefig(buf, format='png')
+ ratio2 = Image.open(buf)
+ ratio2.load()
+
+ assert ratio1 == ratio2
+
+
+def test_savefig_preserve_layout_engine():
+ fig = plt.figure(layout='compressed')
+ fig.savefig(io.BytesIO(), bbox_inches='tight')
+
+ assert fig.get_layout_engine()._compress
+
+
+def test_savefig_locate_colorbar():
+ fig, ax = plt.subplots()
+ pc = ax.pcolormesh(np.random.randn(2, 2))
+ cbar = fig.colorbar(pc, aspect=40)
+ fig.savefig(io.BytesIO(), bbox_inches=mpl.transforms.Bbox([[0, 0], [4, 4]]))
+
+ # Check that an aspect ratio has been applied.
+ assert (cbar.ax.get_position(original=True).bounds !=
+ cbar.ax.get_position(original=False).bounds)
+
+
+@mpl.rc_context({"savefig.transparent": True})
+@check_figures_equal(extensions=["png"])
+def test_savefig_transparent(fig_test, fig_ref):
+ # create two transparent subfigures with corresponding transparent inset
+ # axes. the entire background of the image should be transparent.
+ gs1 = fig_test.add_gridspec(3, 3, left=0.05, wspace=0.05)
+ f1 = fig_test.add_subfigure(gs1[:, :])
+ f2 = f1.add_subfigure(gs1[0, 0])
+
+ ax12 = f2.add_subplot(gs1[:, :])
+
+ ax1 = f1.add_subplot(gs1[:-1, :])
+ iax1 = ax1.inset_axes([.1, .2, .3, .4])
+ iax2 = iax1.inset_axes([.1, .2, .3, .4])
+
+ ax2 = fig_test.add_subplot(gs1[-1, :-1])
+ ax3 = fig_test.add_subplot(gs1[-1, -1])
+
+ for ax in [ax12, ax1, iax1, iax2, ax2, ax3]:
+ ax.set(xticks=[], yticks=[])
+ ax.spines[:].set_visible(False)
+
+
+def test_figure_repr():
+ fig = plt.figure(figsize=(10, 20), dpi=10)
+ assert repr(fig) == ""
+
+
+def test_valid_layouts():
+ fig = Figure(layout=None)
+ assert not fig.get_tight_layout()
+ assert not fig.get_constrained_layout()
+
+ fig = Figure(layout='tight')
+ assert fig.get_tight_layout()
+ assert not fig.get_constrained_layout()
+
+ fig = Figure(layout='constrained')
+ assert not fig.get_tight_layout()
+ assert fig.get_constrained_layout()
+
+
+def test_invalid_layouts():
+ fig, ax = plt.subplots(layout="constrained")
+ with pytest.warns(UserWarning):
+ # this should warn,
+ fig.subplots_adjust(top=0.8)
+ assert isinstance(fig.get_layout_engine(), ConstrainedLayoutEngine)
+
+ # Using layout + (tight|constrained)_layout warns, but the former takes
+ # precedence.
+ wst = "The Figure parameters 'layout' and 'tight_layout'"
+ with pytest.warns(UserWarning, match=wst):
+ fig = Figure(layout='tight', tight_layout=False)
+ assert isinstance(fig.get_layout_engine(), TightLayoutEngine)
+ wst = "The Figure parameters 'layout' and 'constrained_layout'"
+ with pytest.warns(UserWarning, match=wst):
+ fig = Figure(layout='constrained', constrained_layout=False)
+ assert not isinstance(fig.get_layout_engine(), TightLayoutEngine)
+ assert isinstance(fig.get_layout_engine(), ConstrainedLayoutEngine)
+
+ with pytest.raises(ValueError,
+ match="Invalid value for 'layout'"):
+ Figure(layout='foobar')
+
+ # test that layouts can be swapped if no colorbar:
+ fig, ax = plt.subplots(layout="constrained")
+ fig.set_layout_engine("tight")
+ assert isinstance(fig.get_layout_engine(), TightLayoutEngine)
+ fig.set_layout_engine("constrained")
+ assert isinstance(fig.get_layout_engine(), ConstrainedLayoutEngine)
+
+ # test that layouts cannot be swapped if there is a colorbar:
+ fig, ax = plt.subplots(layout="constrained")
+ pc = ax.pcolormesh(np.random.randn(2, 2))
+ fig.colorbar(pc)
+ with pytest.raises(RuntimeError, match='Colorbar layout of new layout'):
+ fig.set_layout_engine("tight")
+ fig.set_layout_engine("none")
+ with pytest.raises(RuntimeError, match='Colorbar layout of new layout'):
+ fig.set_layout_engine("tight")
+
+ fig, ax = plt.subplots(layout="tight")
+ pc = ax.pcolormesh(np.random.randn(2, 2))
+ fig.colorbar(pc)
+ with pytest.raises(RuntimeError, match='Colorbar layout of new layout'):
+ fig.set_layout_engine("constrained")
+ fig.set_layout_engine("none")
+ assert isinstance(fig.get_layout_engine(), PlaceHolderLayoutEngine)
+
+ with pytest.raises(RuntimeError, match='Colorbar layout of new layout'):
+ fig.set_layout_engine("constrained")
+
+
+@check_figures_equal(extensions=["png"])
+def test_tightlayout_autolayout_deconflict(fig_test, fig_ref):
+ for fig, autolayout in zip([fig_ref, fig_test], [False, True]):
+ with mpl.rc_context({'figure.autolayout': autolayout}):
+ axes = fig.subplots(ncols=2)
+ fig.tight_layout(w_pad=10)
+ assert isinstance(fig.get_layout_engine(), PlaceHolderLayoutEngine)
+
+
+@pytest.mark.parametrize('layout', ['constrained', 'compressed'])
+def test_layout_change_warning(layout):
+ """
+ Raise a warning when a previously assigned layout changes to tight using
+ plt.tight_layout().
+ """
+ fig, ax = plt.subplots(layout=layout)
+ with pytest.warns(UserWarning, match='The figure layout has changed to'):
+ plt.tight_layout()
+
+
+def test_repeated_tightlayout():
+ fig = Figure()
+ fig.tight_layout()
+ # subsequent calls should not warn
+ fig.tight_layout()
+ fig.tight_layout()
+
+
+@check_figures_equal(extensions=["png", "pdf"])
+def test_add_artist(fig_test, fig_ref):
+ fig_test.dpi = 100
+ fig_ref.dpi = 100
+
+ fig_test.subplots()
+ l1 = plt.Line2D([.2, .7], [.7, .7], gid='l1')
+ l2 = plt.Line2D([.2, .7], [.8, .8], gid='l2')
+ r1 = plt.Circle((20, 20), 100, transform=None, gid='C1')
+ r2 = plt.Circle((.7, .5), .05, gid='C2')
+ r3 = plt.Circle((4.5, .8), .55, transform=fig_test.dpi_scale_trans,
+ facecolor='crimson', gid='C3')
+ for a in [l1, l2, r1, r2, r3]:
+ fig_test.add_artist(a)
+ l2.remove()
+
+ ax2 = fig_ref.subplots()
+ l1 = plt.Line2D([.2, .7], [.7, .7], transform=fig_ref.transFigure,
+ gid='l1', zorder=21)
+ r1 = plt.Circle((20, 20), 100, transform=None, clip_on=False, zorder=20,
+ gid='C1')
+ r2 = plt.Circle((.7, .5), .05, transform=fig_ref.transFigure, gid='C2',
+ zorder=20)
+ r3 = plt.Circle((4.5, .8), .55, transform=fig_ref.dpi_scale_trans,
+ facecolor='crimson', clip_on=False, zorder=20, gid='C3')
+ for a in [l1, r1, r2, r3]:
+ ax2.add_artist(a)
+
+
+@pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"])
+def test_fspath(fmt, tmp_path):
+ out = tmp_path / f"test.{fmt}"
+ plt.savefig(out)
+ with out.open("rb") as file:
+ # All the supported formats include the format name (case-insensitive)
+ # in the first 100 bytes.
+ assert fmt.encode("ascii") in file.read(100).lower()
+
+
+def test_tightbbox():
+ fig, ax = plt.subplots()
+ ax.set_xlim(0, 1)
+ t = ax.text(1., 0.5, 'This dangles over end')
+ renderer = fig.canvas.get_renderer()
+ x1Nom0 = 9.035 # inches
+ assert abs(t.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2
+ assert abs(ax.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2
+ assert abs(fig.get_tightbbox(renderer).x1 - x1Nom0) < 0.05
+ assert abs(fig.get_tightbbox(renderer).x0 - 0.679) < 0.05
+ # now exclude t from the tight bbox so now the bbox is quite a bit
+ # smaller
+ t.set_in_layout(False)
+ x1Nom = 7.333
+ assert abs(ax.get_tightbbox(renderer).x1 - x1Nom * fig.dpi) < 2
+ assert abs(fig.get_tightbbox(renderer).x1 - x1Nom) < 0.05
+
+ t.set_in_layout(True)
+ x1Nom = 7.333
+ assert abs(ax.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2
+ # test bbox_extra_artists method...
+ assert abs(ax.get_tightbbox(renderer, bbox_extra_artists=[]).x1
+ - x1Nom * fig.dpi) < 2
+
+
+def test_axes_removal():
+ # Check that units can set the formatter after an Axes removal
+ fig, axs = plt.subplots(1, 2, sharex=True)
+ axs[1].remove()
+ axs[0].plot([datetime(2000, 1, 1), datetime(2000, 2, 1)], [0, 1])
+ assert isinstance(axs[0].xaxis.get_major_formatter(),
+ mdates.AutoDateFormatter)
+
+ # Check that manually setting the formatter, then removing Axes keeps
+ # the set formatter.
+ fig, axs = plt.subplots(1, 2, sharex=True)
+ axs[1].xaxis.set_major_formatter(ScalarFormatter())
+ axs[1].remove()
+ axs[0].plot([datetime(2000, 1, 1), datetime(2000, 2, 1)], [0, 1])
+ assert isinstance(axs[0].xaxis.get_major_formatter(),
+ ScalarFormatter)
+
+
+def test_removed_axis():
+ # Simple smoke test to make sure removing a shared axis works
+ fig, axs = plt.subplots(2, sharex=True)
+ axs[0].remove()
+ fig.canvas.draw()
+
+
+@pytest.mark.parametrize('clear_meth', ['clear', 'clf'])
+def test_figure_clear(clear_meth):
+ # we test the following figure clearing scenarios:
+ fig = plt.figure()
+
+ # a) an empty figure
+ fig.clear()
+ assert fig.axes == []
+
+ # b) a figure with a single unnested axes
+ ax = fig.add_subplot(111)
+ getattr(fig, clear_meth)()
+ assert fig.axes == []
+
+ # c) a figure multiple unnested axes
+ axes = [fig.add_subplot(2, 1, i+1) for i in range(2)]
+ getattr(fig, clear_meth)()
+ assert fig.axes == []
+
+ # d) a figure with a subfigure
+ gs = fig.add_gridspec(ncols=2, nrows=1)
+ subfig = fig.add_subfigure(gs[0])
+ subaxes = subfig.add_subplot(111)
+ getattr(fig, clear_meth)()
+ assert subfig not in fig.subfigs
+ assert fig.axes == []
+
+ # e) a figure with a subfigure and a subplot
+ subfig = fig.add_subfigure(gs[0])
+ subaxes = subfig.add_subplot(111)
+ mainaxes = fig.add_subplot(gs[1])
+
+ # e.1) removing just the axes leaves the subplot
+ mainaxes.remove()
+ assert fig.axes == [subaxes]
+
+ # e.2) removing just the subaxes leaves the subplot
+ # and subfigure
+ mainaxes = fig.add_subplot(gs[1])
+ subaxes.remove()
+ assert fig.axes == [mainaxes]
+ assert subfig in fig.subfigs
+
+ # e.3) clearing the subfigure leaves the subplot
+ subaxes = subfig.add_subplot(111)
+ assert mainaxes in fig.axes
+ assert subaxes in fig.axes
+ getattr(subfig, clear_meth)()
+ assert subfig in fig.subfigs
+ assert subaxes not in subfig.axes
+ assert subaxes not in fig.axes
+ assert mainaxes in fig.axes
+
+ # e.4) clearing the whole thing
+ subaxes = subfig.add_subplot(111)
+ getattr(fig, clear_meth)()
+ assert fig.axes == []
+ assert fig.subfigs == []
+
+ # f) multiple subfigures
+ subfigs = [fig.add_subfigure(gs[i]) for i in [0, 1]]
+ subaxes = [sfig.add_subplot(111) for sfig in subfigs]
+ assert all(ax in fig.axes for ax in subaxes)
+ assert all(sfig in fig.subfigs for sfig in subfigs)
+
+ # f.1) clearing only one subfigure
+ getattr(subfigs[0], clear_meth)()
+ assert subaxes[0] not in fig.axes
+ assert subaxes[1] in fig.axes
+ assert subfigs[1] in fig.subfigs
+
+ # f.2) clearing the whole thing
+ getattr(subfigs[1], clear_meth)()
+ subfigs = [fig.add_subfigure(gs[i]) for i in [0, 1]]
+ subaxes = [sfig.add_subplot(111) for sfig in subfigs]
+ assert all(ax in fig.axes for ax in subaxes)
+ assert all(sfig in fig.subfigs for sfig in subfigs)
+ getattr(fig, clear_meth)()
+ assert fig.subfigs == []
+ assert fig.axes == []
+
+
+def test_clf_not_redefined():
+ for klass in FigureBase.__subclasses__():
+ # check that subclasses do not get redefined in our Figure subclasses
+ assert 'clf' not in klass.__dict__
+
+
+@mpl.style.context('mpl20')
+def test_picking_does_not_stale():
+ fig, ax = plt.subplots()
+ ax.scatter([0], [0], [1000], picker=True)
+ fig.canvas.draw()
+ assert not fig.stale
+
+ mouse_event = SimpleNamespace(x=ax.bbox.x0 + ax.bbox.width / 2,
+ y=ax.bbox.y0 + ax.bbox.height / 2,
+ inaxes=ax, guiEvent=None)
+ fig.pick(mouse_event)
+ assert not fig.stale
+
+
+def test_add_subplot_twotuple():
+ fig = plt.figure()
+ ax1 = fig.add_subplot(3, 2, (3, 5))
+ assert ax1.get_subplotspec().rowspan == range(1, 3)
+ assert ax1.get_subplotspec().colspan == range(0, 1)
+ ax2 = fig.add_subplot(3, 2, (4, 6))
+ assert ax2.get_subplotspec().rowspan == range(1, 3)
+ assert ax2.get_subplotspec().colspan == range(1, 2)
+ ax3 = fig.add_subplot(3, 2, (3, 6))
+ assert ax3.get_subplotspec().rowspan == range(1, 3)
+ assert ax3.get_subplotspec().colspan == range(0, 2)
+ ax4 = fig.add_subplot(3, 2, (4, 5))
+ assert ax4.get_subplotspec().rowspan == range(1, 3)
+ assert ax4.get_subplotspec().colspan == range(0, 2)
+ with pytest.raises(IndexError):
+ fig.add_subplot(3, 2, (6, 3))
+
+
+@image_comparison(['tightbbox_box_aspect.svg'], style='mpl20',
+ savefig_kwarg={'bbox_inches': 'tight',
+ 'facecolor': 'teal'},
+ remove_text=True)
+def test_tightbbox_box_aspect():
+ fig = plt.figure()
+ gs = fig.add_gridspec(1, 2)
+ ax1 = fig.add_subplot(gs[0, 0])
+ ax2 = fig.add_subplot(gs[0, 1], projection='3d')
+ ax1.set_box_aspect(.5)
+ ax2.set_box_aspect((2, 1, 1))
+
+
+@check_figures_equal(extensions=["svg", "pdf", "eps", "png"])
+def test_animated_with_canvas_change(fig_test, fig_ref):
+ ax_ref = fig_ref.subplots()
+ ax_ref.plot(range(5))
+
+ ax_test = fig_test.subplots()
+ ax_test.plot(range(5), animated=True)
+
+
+class TestSubplotMosaic:
+ @check_figures_equal(extensions=["png"])
+ @pytest.mark.parametrize(
+ "x", [
+ [["A", "A", "B"], ["C", "D", "B"]],
+ [[1, 1, 2], [3, 4, 2]],
+ (("A", "A", "B"), ("C", "D", "B")),
+ ((1, 1, 2), (3, 4, 2))
+ ]
+ )
+ def test_basic(self, fig_test, fig_ref, x):
+ grid_axes = fig_test.subplot_mosaic(x)
+
+ for k, ax in grid_axes.items():
+ ax.set_title(k)
+
+ labels = sorted(np.unique(x))
+
+ assert len(labels) == len(grid_axes)
+
+ gs = fig_ref.add_gridspec(2, 3)
+ axA = fig_ref.add_subplot(gs[:1, :2])
+ axA.set_title(labels[0])
+
+ axB = fig_ref.add_subplot(gs[:, 2])
+ axB.set_title(labels[1])
+
+ axC = fig_ref.add_subplot(gs[1, 0])
+ axC.set_title(labels[2])
+
+ axD = fig_ref.add_subplot(gs[1, 1])
+ axD.set_title(labels[3])
+
+ @check_figures_equal(extensions=["png"])
+ def test_all_nested(self, fig_test, fig_ref):
+ x = [["A", "B"], ["C", "D"]]
+ y = [["E", "F"], ["G", "H"]]
+
+ fig_ref.set_layout_engine("constrained")
+ fig_test.set_layout_engine("constrained")
+
+ grid_axes = fig_test.subplot_mosaic([[x, y]])
+ for ax in grid_axes.values():
+ ax.set_title(ax.get_label())
+
+ gs = fig_ref.add_gridspec(1, 2)
+ gs_left = gs[0, 0].subgridspec(2, 2)
+ for j, r in enumerate(x):
+ for k, label in enumerate(r):
+ fig_ref.add_subplot(gs_left[j, k]).set_title(label)
+
+ gs_right = gs[0, 1].subgridspec(2, 2)
+ for j, r in enumerate(y):
+ for k, label in enumerate(r):
+ fig_ref.add_subplot(gs_right[j, k]).set_title(label)
+
+ @check_figures_equal(extensions=["png"])
+ def test_nested(self, fig_test, fig_ref):
+
+ fig_ref.set_layout_engine("constrained")
+ fig_test.set_layout_engine("constrained")
+
+ x = [["A", "B"], ["C", "D"]]
+
+ y = [["F"], [x]]
+
+ grid_axes = fig_test.subplot_mosaic(y)
+
+ for k, ax in grid_axes.items():
+ ax.set_title(k)
+
+ gs = fig_ref.add_gridspec(2, 1)
+
+ gs_n = gs[1, 0].subgridspec(2, 2)
+
+ axA = fig_ref.add_subplot(gs_n[0, 0])
+ axA.set_title("A")
+
+ axB = fig_ref.add_subplot(gs_n[0, 1])
+ axB.set_title("B")
+
+ axC = fig_ref.add_subplot(gs_n[1, 0])
+ axC.set_title("C")
+
+ axD = fig_ref.add_subplot(gs_n[1, 1])
+ axD.set_title("D")
+
+ axF = fig_ref.add_subplot(gs[0, 0])
+ axF.set_title("F")
+
+ @check_figures_equal(extensions=["png"])
+ def test_nested_tuple(self, fig_test, fig_ref):
+ x = [["A", "B", "B"], ["C", "C", "D"]]
+ xt = (("A", "B", "B"), ("C", "C", "D"))
+
+ fig_ref.subplot_mosaic([["F"], [x]])
+ fig_test.subplot_mosaic([["F"], [xt]])
+
+ def test_nested_width_ratios(self):
+ x = [["A", [["B"],
+ ["C"]]]]
+ width_ratios = [2, 1]
+
+ fig, axd = plt.subplot_mosaic(x, width_ratios=width_ratios)
+
+ assert axd["A"].get_gridspec().get_width_ratios() == width_ratios
+ assert axd["B"].get_gridspec().get_width_ratios() != width_ratios
+
+ def test_nested_height_ratios(self):
+ x = [["A", [["B"],
+ ["C"]]], ["D", "D"]]
+ height_ratios = [1, 2]
+
+ fig, axd = plt.subplot_mosaic(x, height_ratios=height_ratios)
+
+ assert axd["D"].get_gridspec().get_height_ratios() == height_ratios
+ assert axd["B"].get_gridspec().get_height_ratios() != height_ratios
+
+ @check_figures_equal(extensions=["png"])
+ @pytest.mark.parametrize(
+ "x, empty_sentinel",
+ [
+ ([["A", None], [None, "B"]], None),
+ ([["A", "."], [".", "B"]], "SKIP"),
+ ([["A", 0], [0, "B"]], 0),
+ ([[1, None], [None, 2]], None),
+ ([[1, "."], [".", 2]], "SKIP"),
+ ([[1, 0], [0, 2]], 0),
+ ],
+ )
+ def test_empty(self, fig_test, fig_ref, x, empty_sentinel):
+ if empty_sentinel != "SKIP":
+ kwargs = {"empty_sentinel": empty_sentinel}
+ else:
+ kwargs = {}
+ grid_axes = fig_test.subplot_mosaic(x, **kwargs)
+
+ for k, ax in grid_axes.items():
+ ax.set_title(k)
+
+ labels = sorted(
+ {name for row in x for name in row} - {empty_sentinel, "."}
+ )
+
+ assert len(labels) == len(grid_axes)
+
+ gs = fig_ref.add_gridspec(2, 2)
+ axA = fig_ref.add_subplot(gs[0, 0])
+ axA.set_title(labels[0])
+
+ axB = fig_ref.add_subplot(gs[1, 1])
+ axB.set_title(labels[1])
+
+ def test_fail_list_of_str(self):
+ with pytest.raises(ValueError, match='must be 2D'):
+ plt.subplot_mosaic(['foo', 'bar'])
+ with pytest.raises(ValueError, match='must be 2D'):
+ plt.subplot_mosaic(['foo'])
+ with pytest.raises(ValueError, match='must be 2D'):
+ plt.subplot_mosaic([['foo', ('bar',)]])
+ with pytest.raises(ValueError, match='must be 2D'):
+ plt.subplot_mosaic([['a', 'b'], [('a', 'b'), 'c']])
+
+ @check_figures_equal(extensions=["png"])
+ @pytest.mark.parametrize("subplot_kw", [{}, {"projection": "polar"}, None])
+ def test_subplot_kw(self, fig_test, fig_ref, subplot_kw):
+ x = [[1, 2]]
+ grid_axes = fig_test.subplot_mosaic(x, subplot_kw=subplot_kw)
+ subplot_kw = subplot_kw or {}
+
+ gs = fig_ref.add_gridspec(1, 2)
+ axA = fig_ref.add_subplot(gs[0, 0], **subplot_kw)
+
+ axB = fig_ref.add_subplot(gs[0, 1], **subplot_kw)
+
+ @check_figures_equal(extensions=["png"])
+ @pytest.mark.parametrize("multi_value", ['BC', tuple('BC')])
+ def test_per_subplot_kw(self, fig_test, fig_ref, multi_value):
+ x = 'AB;CD'
+ grid_axes = fig_test.subplot_mosaic(
+ x,
+ subplot_kw={'facecolor': 'red'},
+ per_subplot_kw={
+ 'D': {'facecolor': 'blue'},
+ multi_value: {'facecolor': 'green'},
+ }
+ )
+
+ gs = fig_ref.add_gridspec(2, 2)
+ for color, spec in zip(['red', 'green', 'green', 'blue'], gs):
+ fig_ref.add_subplot(spec, facecolor=color)
+
+ def test_string_parser(self):
+ normalize = Figure._normalize_grid_string
+
+ assert normalize('ABC') == [['A', 'B', 'C']]
+ assert normalize('AB;CC') == [['A', 'B'], ['C', 'C']]
+ assert normalize('AB;CC;DE') == [['A', 'B'], ['C', 'C'], ['D', 'E']]
+ assert normalize("""
+ ABC
+ """) == [['A', 'B', 'C']]
+ assert normalize("""
+ AB
+ CC
+ """) == [['A', 'B'], ['C', 'C']]
+ assert normalize("""
+ AB
+ CC
+ DE
+ """) == [['A', 'B'], ['C', 'C'], ['D', 'E']]
+
+ def test_per_subplot_kw_expander(self):
+ normalize = Figure._norm_per_subplot_kw
+ assert normalize({"A": {}, "B": {}}) == {"A": {}, "B": {}}
+ assert normalize({("A", "B"): {}}) == {"A": {}, "B": {}}
+ with pytest.raises(
+ ValueError, match=f'The key {"B"!r} appears multiple times'
+ ):
+ normalize({("A", "B"): {}, "B": {}})
+ with pytest.raises(
+ ValueError, match=f'The key {"B"!r} appears multiple times'
+ ):
+ normalize({"B": {}, ("A", "B"): {}})
+
+ def test_extra_per_subplot_kw(self):
+ with pytest.raises(
+ ValueError, match=f'The keys {set("B")!r} are in'
+ ):
+ Figure().subplot_mosaic("A", per_subplot_kw={"B": {}})
+
+ @check_figures_equal(extensions=["png"])
+ @pytest.mark.parametrize("str_pattern",
+ ["AAA\nBBB", "\nAAA\nBBB\n", "ABC\nDEF"]
+ )
+ def test_single_str_input(self, fig_test, fig_ref, str_pattern):
+ grid_axes = fig_test.subplot_mosaic(str_pattern)
+
+ grid_axes = fig_ref.subplot_mosaic(
+ [list(ln) for ln in str_pattern.strip().split("\n")]
+ )
+
+ @pytest.mark.parametrize(
+ "x,match",
+ [
+ (
+ [["A", "."], [".", "A"]],
+ (
+ "(?m)we found that the label .A. specifies a "
+ + "non-rectangular or non-contiguous area."
+ ),
+ ),
+ (
+ [["A", "B"], [None, [["A", "B"], ["C", "D"]]]],
+ "There are duplicate keys .* between the outer layout",
+ ),
+ ("AAA\nc\nBBB", "All of the rows must be the same length"),
+ (
+ [["A", [["B", "C"], ["D"]]], ["E", "E"]],
+ "All of the rows must be the same length",
+ ),
+ ],
+ )
+ def test_fail(self, x, match):
+ fig = plt.figure()
+ with pytest.raises(ValueError, match=match):
+ fig.subplot_mosaic(x)
+
+ @check_figures_equal(extensions=["png"])
+ def test_hashable_keys(self, fig_test, fig_ref):
+ fig_test.subplot_mosaic([[object(), object()]])
+ fig_ref.subplot_mosaic([["A", "B"]])
+
+ @pytest.mark.parametrize('str_pattern',
+ ['abc', 'cab', 'bca', 'cba', 'acb', 'bac'])
+ def test_user_order(self, str_pattern):
+ fig = plt.figure()
+ ax_dict = fig.subplot_mosaic(str_pattern)
+ assert list(str_pattern) == list(ax_dict)
+ assert list(fig.axes) == list(ax_dict.values())
+
+ def test_nested_user_order(self):
+ layout = [
+ ["A", [["B", "C"],
+ ["D", "E"]]],
+ ["F", "G"],
+ [".", [["H", [["I"],
+ ["."]]]]]
+ ]
+
+ fig = plt.figure()
+ ax_dict = fig.subplot_mosaic(layout)
+ assert list(ax_dict) == list("ABCDEFGHI")
+ assert list(fig.axes) == list(ax_dict.values())
+
+ def test_share_all(self):
+ layout = [
+ ["A", [["B", "C"],
+ ["D", "E"]]],
+ ["F", "G"],
+ [".", [["H", [["I"],
+ ["."]]]]]
+ ]
+ fig = plt.figure()
+ ax_dict = fig.subplot_mosaic(layout, sharex=True, sharey=True)
+ ax_dict["A"].set(xscale="log", yscale="logit")
+ assert all(ax.get_xscale() == "log" and ax.get_yscale() == "logit"
+ for ax in ax_dict.values())
+
+
+def test_reused_gridspec():
+ """Test that these all use the same gridspec"""
+ fig = plt.figure()
+ ax1 = fig.add_subplot(3, 2, (3, 5))
+ ax2 = fig.add_subplot(3, 2, 4)
+ ax3 = plt.subplot2grid((3, 2), (2, 1), colspan=2, fig=fig)
+
+ gs1 = ax1.get_subplotspec().get_gridspec()
+ gs2 = ax2.get_subplotspec().get_gridspec()
+ gs3 = ax3.get_subplotspec().get_gridspec()
+
+ assert gs1 == gs2
+ assert gs1 == gs3
+
+
+@image_comparison(['test_subfigure.png'], style='mpl20',
+ savefig_kwarg={'facecolor': 'teal'})
+def test_subfigure():
+ np.random.seed(19680801)
+ fig = plt.figure(layout='constrained')
+ sub = fig.subfigures(1, 2)
+
+ axs = sub[0].subplots(2, 2)
+ for ax in axs.flat:
+ pc = ax.pcolormesh(np.random.randn(30, 30), vmin=-2, vmax=2)
+ sub[0].colorbar(pc, ax=axs)
+ sub[0].suptitle('Left Side')
+ sub[0].set_facecolor('white')
+
+ axs = sub[1].subplots(1, 3)
+ for ax in axs.flat:
+ pc = ax.pcolormesh(np.random.randn(30, 30), vmin=-2, vmax=2)
+ sub[1].colorbar(pc, ax=axs, location='bottom')
+ sub[1].suptitle('Right Side')
+ sub[1].set_facecolor('white')
+
+ fig.suptitle('Figure suptitle', fontsize='xx-large')
+
+ # below tests for the draw zorder of subfigures.
+ leg = fig.legend(handles=[plt.Line2D([0], [0], label='Line{}'.format(i))
+ for i in range(5)], loc='center')
+ sub[0].set_zorder(leg.get_zorder() - 1)
+ sub[1].set_zorder(leg.get_zorder() + 1)
+
+
+def test_subfigure_tightbbox():
+ # test that we can get the tightbbox with a subfigure...
+ fig = plt.figure(layout='constrained')
+ sub = fig.subfigures(1, 2)
+
+ np.testing.assert_allclose(
+ fig.get_tightbbox(fig.canvas.get_renderer()).width,
+ 8.0)
+
+
+def test_subfigure_dpi():
+ fig = plt.figure(dpi=100)
+ sub_fig = fig.subfigures()
+ assert sub_fig.get_dpi() == fig.get_dpi()
+
+ sub_fig.set_dpi(200)
+ assert sub_fig.get_dpi() == 200
+ assert fig.get_dpi() == 200
+
+
+@image_comparison(['test_subfigure_ss.png'], style='mpl20',
+ savefig_kwarg={'facecolor': 'teal'}, tol=0.02)
+def test_subfigure_ss():
+ # test assigning the subfigure via subplotspec
+ np.random.seed(19680801)
+ fig = plt.figure(layout='constrained')
+ gs = fig.add_gridspec(1, 2)
+
+ sub = fig.add_subfigure(gs[0], facecolor='pink')
+
+ axs = sub.subplots(2, 2)
+ for ax in axs.flat:
+ pc = ax.pcolormesh(np.random.randn(30, 30), vmin=-2, vmax=2)
+ sub.colorbar(pc, ax=axs)
+ sub.suptitle('Left Side')
+
+ ax = fig.add_subplot(gs[1])
+ ax.plot(np.arange(20))
+ ax.set_title('Axes')
+
+ fig.suptitle('Figure suptitle', fontsize='xx-large')
+
+
+@image_comparison(['test_subfigure_double.png'], style='mpl20',
+ savefig_kwarg={'facecolor': 'teal'})
+def test_subfigure_double():
+ # test assigning the subfigure via subplotspec
+ np.random.seed(19680801)
+
+ fig = plt.figure(layout='constrained', figsize=(10, 8))
+
+ fig.suptitle('fig')
+
+ subfigs = fig.subfigures(1, 2, wspace=0.07)
+
+ subfigs[0].set_facecolor('coral')
+ subfigs[0].suptitle('subfigs[0]')
+
+ subfigs[1].set_facecolor('coral')
+ subfigs[1].suptitle('subfigs[1]')
+
+ subfigsnest = subfigs[0].subfigures(2, 1, height_ratios=[1, 1.4])
+ subfigsnest[0].suptitle('subfigsnest[0]')
+ subfigsnest[0].set_facecolor('r')
+ axsnest0 = subfigsnest[0].subplots(1, 2, sharey=True)
+ for ax in axsnest0:
+ fontsize = 12
+ pc = ax.pcolormesh(np.random.randn(30, 30), vmin=-2.5, vmax=2.5)
+ ax.set_xlabel('x-label', fontsize=fontsize)
+ ax.set_ylabel('y-label', fontsize=fontsize)
+ ax.set_title('Title', fontsize=fontsize)
+ subfigsnest[0].colorbar(pc, ax=axsnest0)
+
+ subfigsnest[1].suptitle('subfigsnest[1]')
+ subfigsnest[1].set_facecolor('g')
+ axsnest1 = subfigsnest[1].subplots(3, 1, sharex=True)
+ for nn, ax in enumerate(axsnest1):
+ ax.set_ylabel(f'ylabel{nn}')
+ subfigsnest[1].supxlabel('supxlabel')
+ subfigsnest[1].supylabel('supylabel')
+
+ axsRight = subfigs[1].subplots(2, 2)
+
+
+def test_subfigure_spanning():
+ # test that subfigures get laid out properly...
+ fig = plt.figure(constrained_layout=True)
+ gs = fig.add_gridspec(3, 3)
+ sub_figs = [
+ fig.add_subfigure(gs[0, 0]),
+ fig.add_subfigure(gs[0:2, 1]),
+ fig.add_subfigure(gs[2, 1:3]),
+ fig.add_subfigure(gs[0:, 1:])
+ ]
+
+ w = 640
+ h = 480
+ np.testing.assert_allclose(sub_figs[0].bbox.min, [0., h * 2/3])
+ np.testing.assert_allclose(sub_figs[0].bbox.max, [w / 3, h])
+
+ np.testing.assert_allclose(sub_figs[1].bbox.min, [w / 3, h / 3])
+ np.testing.assert_allclose(sub_figs[1].bbox.max, [w * 2/3, h])
+
+ np.testing.assert_allclose(sub_figs[2].bbox.min, [w / 3, 0])
+ np.testing.assert_allclose(sub_figs[2].bbox.max, [w, h / 3])
+
+ # check here that slicing actually works. Last sub_fig
+ # with open slices failed, but only on draw...
+ for i in range(4):
+ sub_figs[i].add_subplot()
+ fig.draw_without_rendering()
+
+
+@mpl.style.context('mpl20')
+def test_subfigure_ticks():
+ # This tests a tick-spacing error that only seems applicable
+ # when the subfigures are saved to file. It is very hard to replicate
+ fig = plt.figure(constrained_layout=True, figsize=(10, 3))
+ # create left/right subfigs nested in bottom subfig
+ (subfig_bl, subfig_br) = fig.subfigures(1, 2, wspace=0.01,
+ width_ratios=[7, 2])
+
+ # put ax1-ax3 in gridspec of bottom-left subfig
+ gs = subfig_bl.add_gridspec(nrows=1, ncols=14)
+
+ ax1 = subfig_bl.add_subplot(gs[0, :1])
+ ax1.scatter(x=[-56.46881504821776, 24.179891162109396], y=[1500, 3600])
+
+ ax2 = subfig_bl.add_subplot(gs[0, 1:3], sharey=ax1)
+ ax2.scatter(x=[-126.5357270050049, 94.68456736755368], y=[1500, 3600])
+ ax3 = subfig_bl.add_subplot(gs[0, 3:14], sharey=ax1)
+
+ fig.dpi = 120
+ fig.draw_without_rendering()
+ ticks120 = ax2.get_xticks()
+ fig.dpi = 300
+ fig.draw_without_rendering()
+ ticks300 = ax2.get_xticks()
+ np.testing.assert_allclose(ticks120, ticks300)
+
+
+@image_comparison(['test_subfigure_scatter_size.png'], style='mpl20',
+ remove_text=True)
+def test_subfigure_scatter_size():
+ # markers in the left- and right-most subplots should be the same
+ fig = plt.figure()
+ gs = fig.add_gridspec(1, 2)
+ ax0 = fig.add_subplot(gs[1])
+ ax0.scatter([1, 2, 3], [1, 2, 3], s=30, marker='s')
+ ax0.scatter([3, 4, 5], [1, 2, 3], s=[20, 30, 40], marker='s')
+
+ sfig = fig.add_subfigure(gs[0])
+ axs = sfig.subplots(1, 2)
+ for ax in [ax0, axs[0]]:
+ ax.scatter([1, 2, 3], [1, 2, 3], s=30, marker='s', color='r')
+ ax.scatter([3, 4, 5], [1, 2, 3], s=[20, 30, 40], marker='s', color='g')
+
+
+def test_subfigure_pdf():
+ fig = plt.figure(layout='constrained')
+ sub_fig = fig.subfigures()
+ ax = sub_fig.add_subplot(111)
+ b = ax.bar(1, 1)
+ ax.bar_label(b)
+ buffer = io.BytesIO()
+ fig.savefig(buffer, format='pdf')
+
+
+def test_subfigures_wspace_hspace():
+ sub_figs = plt.figure().subfigures(2, 3, hspace=0.5, wspace=1/6.)
+
+ w = 640
+ h = 480
+
+ np.testing.assert_allclose(sub_figs[0, 0].bbox.min, [0., h * 0.6])
+ np.testing.assert_allclose(sub_figs[0, 0].bbox.max, [w * 0.3, h])
+
+ np.testing.assert_allclose(sub_figs[0, 1].bbox.min, [w * 0.35, h * 0.6])
+ np.testing.assert_allclose(sub_figs[0, 1].bbox.max, [w * 0.65, h])
+
+ np.testing.assert_allclose(sub_figs[0, 2].bbox.min, [w * 0.7, h * 0.6])
+ np.testing.assert_allclose(sub_figs[0, 2].bbox.max, [w, h])
+
+ np.testing.assert_allclose(sub_figs[1, 0].bbox.min, [0, 0])
+ np.testing.assert_allclose(sub_figs[1, 0].bbox.max, [w * 0.3, h * 0.4])
+
+ np.testing.assert_allclose(sub_figs[1, 1].bbox.min, [w * 0.35, 0])
+ np.testing.assert_allclose(sub_figs[1, 1].bbox.max, [w * 0.65, h * 0.4])
+
+ np.testing.assert_allclose(sub_figs[1, 2].bbox.min, [w * 0.7, 0])
+ np.testing.assert_allclose(sub_figs[1, 2].bbox.max, [w, h * 0.4])
+
+
+def test_subfigure_remove():
+ fig = plt.figure()
+ sfs = fig.subfigures(2, 2)
+ sfs[1, 1].remove()
+ assert len(fig.subfigs) == 3
+
+
+def test_add_subplot_kwargs():
+ # fig.add_subplot() always creates new axes, even if axes kwargs differ.
+ fig = plt.figure()
+ ax = fig.add_subplot(1, 1, 1)
+ ax1 = fig.add_subplot(1, 1, 1)
+ assert ax is not None
+ assert ax1 is not ax
+ plt.close()
+
+ fig = plt.figure()
+ ax = fig.add_subplot(1, 1, 1, projection='polar')
+ ax1 = fig.add_subplot(1, 1, 1, projection='polar')
+ assert ax is not None
+ assert ax1 is not ax
+ plt.close()
+
+ fig = plt.figure()
+ ax = fig.add_subplot(1, 1, 1, projection='polar')
+ ax1 = fig.add_subplot(1, 1, 1)
+ assert ax is not None
+ assert ax1.name == 'rectilinear'
+ assert ax1 is not ax
+ plt.close()
+
+
+def test_add_axes_kwargs():
+ # fig.add_axes() always creates new axes, even if axes kwargs differ.
+ fig = plt.figure()
+ ax = fig.add_axes([0, 0, 1, 1])
+ ax1 = fig.add_axes([0, 0, 1, 1])
+ assert ax is not None
+ assert ax1 is not ax
+ plt.close()
+
+ fig = plt.figure()
+ ax = fig.add_axes([0, 0, 1, 1], projection='polar')
+ ax1 = fig.add_axes([0, 0, 1, 1], projection='polar')
+ assert ax is not None
+ assert ax1 is not ax
+ plt.close()
+
+ fig = plt.figure()
+ ax = fig.add_axes([0, 0, 1, 1], projection='polar')
+ ax1 = fig.add_axes([0, 0, 1, 1])
+ assert ax is not None
+ assert ax1.name == 'rectilinear'
+ assert ax1 is not ax
+ plt.close()
+
+
+def test_ginput(recwarn): # recwarn undoes warn filters at exit.
+ warnings.filterwarnings("ignore", "cannot show the figure")
+ fig, ax = plt.subplots()
+ trans = ax.transData.transform
+
+ def single_press():
+ MouseEvent("button_press_event", fig.canvas, *trans((.1, .2)), 1)._process()
+
+ Timer(.1, single_press).start()
+ assert fig.ginput() == [(.1, .2)]
+
+ def multi_presses():
+ MouseEvent("button_press_event", fig.canvas, *trans((.1, .2)), 1)._process()
+ KeyEvent("key_press_event", fig.canvas, "backspace")._process()
+ MouseEvent("button_press_event", fig.canvas, *trans((.3, .4)), 1)._process()
+ MouseEvent("button_press_event", fig.canvas, *trans((.5, .6)), 1)._process()
+ MouseEvent("button_press_event", fig.canvas, *trans((0, 0)), 2)._process()
+
+ Timer(.1, multi_presses).start()
+ np.testing.assert_allclose(fig.ginput(3), [(.3, .4), (.5, .6)])
+
+
+def test_waitforbuttonpress(recwarn): # recwarn undoes warn filters at exit.
+ warnings.filterwarnings("ignore", "cannot show the figure")
+ fig = plt.figure()
+ assert fig.waitforbuttonpress(timeout=.1) is None
+ Timer(.1, KeyEvent("key_press_event", fig.canvas, "z")._process).start()
+ assert fig.waitforbuttonpress() is True
+ Timer(.1, MouseEvent("button_press_event", fig.canvas, 0, 0, 1)._process).start()
+ assert fig.waitforbuttonpress() is False
+
+
+def test_kwargs_pass():
+ fig = Figure(label='whole Figure')
+ sub_fig = fig.subfigures(1, 1, label='sub figure')
+
+ assert fig.get_label() == 'whole Figure'
+ assert sub_fig.get_label() == 'sub figure'
+
+
+@check_figures_equal(extensions=["png"])
+def test_rcparams(fig_test, fig_ref):
+ fig_ref.supxlabel("xlabel", weight='bold', size=15)
+ fig_ref.supylabel("ylabel", weight='bold', size=15)
+ fig_ref.suptitle("Title", weight='light', size=20)
+ with mpl.rc_context({'figure.labelweight': 'bold',
+ 'figure.labelsize': 15,
+ 'figure.titleweight': 'light',
+ 'figure.titlesize': 20}):
+ fig_test.supxlabel("xlabel")
+ fig_test.supylabel("ylabel")
+ fig_test.suptitle("Title")
+
+
+def test_deepcopy():
+ fig1, ax = plt.subplots()
+ ax.plot([0, 1], [2, 3])
+ ax.set_yscale('log')
+
+ fig2 = copy.deepcopy(fig1)
+
+ # Make sure it is a new object
+ assert fig2.axes[0] is not ax
+ # And that the axis scale got propagated
+ assert fig2.axes[0].get_yscale() == 'log'
+ # Update the deepcopy and check the original isn't modified
+ fig2.axes[0].set_yscale('linear')
+ assert ax.get_yscale() == 'log'
+
+ # And test the limits of the axes don't get propagated
+ ax.set_xlim(1e-1, 1e2)
+ # Draw these to make sure limits are updated
+ fig1.draw_without_rendering()
+ fig2.draw_without_rendering()
+
+ assert ax.get_xlim() == (1e-1, 1e2)
+ assert fig2.axes[0].get_xlim() == (0, 1)
+
+
+def test_unpickle_with_device_pixel_ratio():
+ fig = Figure(dpi=42)
+ fig.canvas._set_device_pixel_ratio(7)
+ assert fig.dpi == 42*7
+ fig2 = pickle.loads(pickle.dumps(fig))
+ assert fig2.dpi == 42
+
+
+def test_gridspec_no_mutate_input():
+ gs = {'left': .1}
+ gs_orig = dict(gs)
+ plt.subplots(1, 2, width_ratios=[1, 2], gridspec_kw=gs)
+ assert gs == gs_orig
+ plt.subplot_mosaic('AB', width_ratios=[1, 2], gridspec_kw=gs)
+
+
+@pytest.mark.parametrize('fmt', ['eps', 'pdf', 'png', 'ps', 'svg', 'svgz'])
+def test_savefig_metadata(fmt):
+ Figure().savefig(io.BytesIO(), format=fmt, metadata={})
+
+
+@pytest.mark.parametrize('fmt', ['jpeg', 'jpg', 'tif', 'tiff', 'webp', "raw", "rgba"])
+def test_savefig_metadata_error(fmt):
+ with pytest.raises(ValueError, match="metadata not supported"):
+ Figure().savefig(io.BytesIO(), format=fmt, metadata={})
+
+
+def test_get_constrained_layout_pads():
+ params = {'w_pad': 0.01, 'h_pad': 0.02, 'wspace': 0.03, 'hspace': 0.04}
+ expected = tuple([*params.values()])
+ fig = plt.figure(layout=mpl.layout_engine.ConstrainedLayoutEngine(**params))
+ with pytest.warns(PendingDeprecationWarning, match="will be deprecated"):
+ assert fig.get_constrained_layout_pads() == expected
+
+
+def test_not_visible_figure():
+ fig = Figure()
+
+ buf = io.StringIO()
+ fig.savefig(buf, format='svg')
+ buf.seek(0)
+ assert ' 1
+
+
+@pytest.mark.parametrize('factor', [2, 4, 6, 8])
+def test_hinting_factor(factor):
+ font = findfont(FontProperties(family=["sans-serif"]))
+
+ font1 = get_font(font, hinting_factor=1)
+ font1.clear()
+ font1.set_size(12, 100)
+ font1.set_text('abc')
+ expected = font1.get_width_height()
+
+ hinted_font = get_font(font, hinting_factor=factor)
+ hinted_font.clear()
+ hinted_font.set_size(12, 100)
+ hinted_font.set_text('abc')
+ # Check that hinting only changes text layout by a small (10%) amount.
+ np.testing.assert_allclose(hinted_font.get_width_height(), expected,
+ rtol=0.1)
+
+
+def test_utf16m_sfnt():
+ try:
+ # seguisbi = Microsoft Segoe UI Semibold
+ entry = next(entry for entry in fontManager.ttflist
+ if Path(entry.fname).name == "seguisbi.ttf")
+ except StopIteration:
+ pytest.skip("Couldn't find seguisbi.ttf font to test against.")
+ else:
+ # Check that we successfully read "semibold" from the font's sfnt table
+ # and set its weight accordingly.
+ assert entry.weight == 600
+
+
+def test_find_ttc():
+ fp = FontProperties(family=["WenQuanYi Zen Hei"])
+ if Path(findfont(fp)).name != "wqy-zenhei.ttc":
+ pytest.skip("Font wqy-zenhei.ttc may be missing")
+ fig, ax = plt.subplots()
+ ax.text(.5, .5, "\N{KANGXI RADICAL DRAGON}", fontproperties=fp)
+ for fmt in ["raw", "svg", "pdf", "ps"]:
+ fig.savefig(BytesIO(), format=fmt)
+
+
+def test_find_noto():
+ fp = FontProperties(family=["Noto Sans CJK SC", "Noto Sans CJK JP"])
+ name = Path(findfont(fp)).name
+ if name not in ("NotoSansCJKsc-Regular.otf", "NotoSansCJK-Regular.ttc"):
+ pytest.skip(f"Noto Sans CJK SC font may be missing (found {name})")
+
+ fig, ax = plt.subplots()
+ ax.text(0.5, 0.5, 'Hello, ä½ å¥½', fontproperties=fp)
+ for fmt in ["raw", "svg", "pdf", "ps"]:
+ fig.savefig(BytesIO(), format=fmt)
+
+
+def test_find_invalid(tmp_path):
+
+ with pytest.raises(FileNotFoundError):
+ get_font(tmp_path / 'non-existent-font-name.ttf')
+
+ with pytest.raises(FileNotFoundError):
+ get_font(str(tmp_path / 'non-existent-font-name.ttf'))
+
+ with pytest.raises(FileNotFoundError):
+ get_font(bytes(tmp_path / 'non-existent-font-name.ttf'))
+
+ # Not really public, but get_font doesn't expose non-filename constructor.
+ from matplotlib.ft2font import FT2Font
+ with pytest.raises(TypeError, match='font file or a binary-mode file'):
+ FT2Font(StringIO()) # type: ignore[arg-type]
+
+
+@pytest.mark.skipif(sys.platform != 'linux' or not has_fclist,
+ reason='only Linux with fontconfig installed')
+def test_user_fonts_linux(tmpdir, monkeypatch):
+ font_test_file = 'mpltest.ttf'
+
+ # Precondition: the test font should not be available
+ fonts = findSystemFonts()
+ if any(font_test_file in font for font in fonts):
+ pytest.skip(f'{font_test_file} already exists in system fonts')
+
+ # Prepare a temporary user font directory
+ user_fonts_dir = tmpdir.join('fonts')
+ user_fonts_dir.ensure(dir=True)
+ shutil.copyfile(Path(__file__).parent / font_test_file,
+ user_fonts_dir.join(font_test_file))
+
+ with monkeypatch.context() as m:
+ m.setenv('XDG_DATA_HOME', str(tmpdir))
+ _get_fontconfig_fonts.cache_clear()
+ # Now, the font should be available
+ fonts = findSystemFonts()
+ assert any(font_test_file in font for font in fonts)
+
+ # Make sure the temporary directory is no longer cached.
+ _get_fontconfig_fonts.cache_clear()
+
+
+def test_addfont_as_path():
+ """Smoke test that addfont() accepts pathlib.Path."""
+ font_test_file = 'mpltest.ttf'
+ path = Path(__file__).parent / font_test_file
+ try:
+ fontManager.addfont(path)
+ added, = (font for font in fontManager.ttflist
+ if font.fname.endswith(font_test_file))
+ fontManager.ttflist.remove(added)
+ finally:
+ to_remove = [font for font in fontManager.ttflist
+ if font.fname.endswith(font_test_file)]
+ for font in to_remove:
+ fontManager.ttflist.remove(font)
+
+
+@pytest.mark.skipif(sys.platform != 'win32', reason='Windows only')
+def test_user_fonts_win32():
+ if not (os.environ.get('APPVEYOR') or os.environ.get('TF_BUILD')):
+ pytest.xfail("This test should only run on CI (appveyor or azure) "
+ "as the developer's font directory should remain "
+ "unchanged.")
+ pytest.xfail("We need to update the registry for this test to work")
+ font_test_file = 'mpltest.ttf'
+
+ # Precondition: the test font should not be available
+ fonts = findSystemFonts()
+ if any(font_test_file in font for font in fonts):
+ pytest.skip(f'{font_test_file} already exists in system fonts')
+
+ user_fonts_dir = MSUserFontDirectories[0]
+
+ # Make sure that the user font directory exists (this is probably not the
+ # case on Windows versions < 1809)
+ os.makedirs(user_fonts_dir)
+
+ # Copy the test font to the user font directory
+ shutil.copy(Path(__file__).parent / font_test_file, user_fonts_dir)
+
+ # Now, the font should be available
+ fonts = findSystemFonts()
+ assert any(font_test_file in font for font in fonts)
+
+
+def _model_handler(_):
+ fig, ax = plt.subplots()
+ fig.savefig(BytesIO(), format="pdf")
+ plt.close()
+
+
+@pytest.mark.skipif(not hasattr(os, "register_at_fork"),
+ reason="Cannot register at_fork handlers")
+def test_fork():
+ _model_handler(0) # Make sure the font cache is filled.
+ ctx = multiprocessing.get_context("fork")
+ with ctx.Pool(processes=2) as pool:
+ pool.map(_model_handler, range(2))
+
+
+def test_missing_family(caplog):
+ plt.rcParams["font.sans-serif"] = ["this-font-does-not-exist"]
+ with caplog.at_level("WARNING"):
+ findfont("sans")
+ assert [rec.getMessage() for rec in caplog.records] == [
+ "findfont: Font family ['sans'] not found. "
+ "Falling back to DejaVu Sans.",
+ "findfont: Generic family 'sans' not found because none of the "
+ "following families were found: this-font-does-not-exist",
+ ]
+
+
+def _test_threading():
+ import threading
+ from matplotlib.ft2font import LoadFlags
+ import matplotlib.font_manager as fm
+
+ def loud_excepthook(args):
+ raise RuntimeError("error in thread!")
+
+ threading.excepthook = loud_excepthook
+
+ N = 10
+ b = threading.Barrier(N)
+
+ def bad_idea(n):
+ b.wait(timeout=5)
+ for j in range(100):
+ font = fm.get_font(fm.findfont("DejaVu Sans"))
+ font.set_text(str(n), 0.0, flags=LoadFlags.NO_HINTING)
+
+ threads = [
+ threading.Thread(target=bad_idea, name=f"bad_thread_{j}", args=(j,))
+ for j in range(N)
+ ]
+
+ for t in threads:
+ t.start()
+
+ for t in threads:
+ t.join(timeout=9)
+ if t.is_alive():
+ raise RuntimeError("thread failed to join")
+
+
+def test_fontcache_thread_safe():
+ pytest.importorskip('threading')
+
+ subprocess_run_helper(_test_threading, timeout=10)
+
+
+def test_lockfilefailure(tmp_path):
+ # The logic here:
+ # 1. get a temp directory from pytest
+ # 2. import matplotlib which makes sure it exists
+ # 3. get the cache dir (where we check it is writable)
+ # 4. make it not writable
+ # 5. try to write into it via font manager
+ proc = subprocess_run_for_testing(
+ [
+ sys.executable,
+ "-c",
+ "import matplotlib;"
+ "import os;"
+ "p = matplotlib.get_cachedir();"
+ "os.chmod(p, 0o555);"
+ "import matplotlib.font_manager;"
+ ],
+ env={**os.environ, 'MPLCONFIGDIR': str(tmp_path)},
+ check=True
+ )
+
+
+def test_fontentry_dataclass():
+ fontent = FontEntry(name='font-name')
+
+ png = fontent._repr_png_()
+ img = Image.open(BytesIO(png))
+ assert img.width > 0
+ assert img.height > 0
+
+ html = fontent._repr_html_()
+ assert html.startswith(" . The '
+ 'SIL License is a free and open source license specifically designed for '
+ 'fonts and related software. The basic terms are that the recipient will '
+ 'not remove the copyright and trademark statements from the fonts and '
+ 'that, if the person decides to create a derivative work based on the STIX '
+ 'Fonts but incorporating some changes or enhancements, the derivative work '
+ '("Modified Version") will carry a different name. The copyright and '
+ 'trademark restrictions are part of the agreement between the STI Pub '
+ 'companies and the typeface designer. The "renaming" restriction results '
+ 'from the desire of the STI Pub companies to assure that the STIX Fonts '
+ 'will continue to function in a predictable fashion for all that use them. '
+ 'No copy of one or more of the individual Font typefaces that form the '
+ 'STIX Fonts(TM) set may be sold by itself, but other than this one '
+ 'restriction, licensees are free to sell the fonts either separately or as '
+ 'part of a package that combines other software or fonts with this font '
+ 'set.',
+ 14: 'http://www.stixfonts.org/user_license.html',
+ },
+}
+
+
+@pytest.mark.parametrize('font_name, expected', _expected_sfnt_names.items(),
+ ids=_expected_sfnt_names.keys())
+def test_ft2font_get_sfnt(font_name, expected):
+ file = fm.findfont(font_name)
+ font = ft2font.FT2Font(file)
+ sfnt = font.get_sfnt()
+ for name, value in expected.items():
+ # Macintosh, Unicode 1.0, English, name.
+ assert sfnt.pop((1, 0, 0, name)) == value.encode('ascii')
+ # Microsoft, Unicode, English United States, name.
+ assert sfnt.pop((3, 1, 1033, name)) == value.encode('utf-16be')
+ assert sfnt == {}
+
+
+_expected_sfnt_tables = {
+ 'DejaVu Sans': {
+ 'invalid': None,
+ 'head': {
+ 'version': (1, 0),
+ 'fontRevision': (2, 22937),
+ 'checkSumAdjustment': -175678572,
+ 'magicNumber': 0x5F0F3CF5,
+ 'flags': 31,
+ 'unitsPerEm': 2048,
+ 'created': (0, 3514699492), 'modified': (0, 3514699492),
+ 'xMin': -2090, 'yMin': -948, 'xMax': 3673, 'yMax': 2524,
+ 'macStyle': 0,
+ 'lowestRecPPEM': 8,
+ 'fontDirectionHint': 0,
+ 'indexToLocFormat': 1,
+ 'glyphDataFormat': 0,
+ },
+ 'maxp': {
+ 'version': (1, 0),
+ 'numGlyphs': 6241,
+ 'maxPoints': 852, 'maxComponentPoints': 104, 'maxTwilightPoints': 16,
+ 'maxContours': 43, 'maxComponentContours': 12,
+ 'maxZones': 2,
+ 'maxStorage': 153,
+ 'maxFunctionDefs': 64,
+ 'maxInstructionDefs': 0,
+ 'maxStackElements': 1045,
+ 'maxSizeOfInstructions': 534,
+ 'maxComponentElements': 8,
+ 'maxComponentDepth': 4,
+ },
+ 'OS/2': {
+ 'version': 1,
+ 'xAvgCharWidth': 1038,
+ 'usWeightClass': 400, 'usWidthClass': 5,
+ 'fsType': 0,
+ 'ySubscriptXSize': 1331, 'ySubscriptYSize': 1433,
+ 'ySubscriptXOffset': 0, 'ySubscriptYOffset': 286,
+ 'ySuperscriptXSize': 1331, 'ySuperscriptYSize': 1433,
+ 'ySuperscriptXOffset': 0, 'ySuperscriptYOffset': 983,
+ 'yStrikeoutSize': 102, 'yStrikeoutPosition': 530,
+ 'sFamilyClass': 0,
+ 'panose': b'\x02\x0b\x06\x03\x03\x08\x04\x02\x02\x04',
+ 'ulCharRange': (3875565311, 3523280383, 170156073, 67117068),
+ 'achVendID': b'PfEd',
+ 'fsSelection': 64, 'fsFirstCharIndex': 32, 'fsLastCharIndex': 65535,
+ },
+ 'hhea': {
+ 'version': (1, 0),
+ 'ascent': 1901, 'descent': -483, 'lineGap': 0,
+ 'advanceWidthMax': 3838,
+ 'minLeftBearing': -2090, 'minRightBearing': -1455,
+ 'xMaxExtent': 3673,
+ 'caretSlopeRise': 1, 'caretSlopeRun': 0, 'caretOffset': 0,
+ 'metricDataFormat': 0, 'numOfLongHorMetrics': 6226,
+ },
+ 'vhea': None,
+ 'post': {
+ 'format': (2, 0),
+ 'isFixedPitch': 0, 'italicAngle': (0, 0),
+ 'underlinePosition': -130, 'underlineThickness': 90,
+ 'minMemType42': 0, 'maxMemType42': 0,
+ 'minMemType1': 0, 'maxMemType1': 0,
+ },
+ 'pclt': None,
+ },
+ 'cmtt10': {
+ 'invalid': None,
+ 'head': {
+ 'version': (1, 0),
+ 'fontRevision': (1, 0),
+ 'checkSumAdjustment': 555110277,
+ 'magicNumber': 0x5F0F3CF5,
+ 'flags': 3,
+ 'unitsPerEm': 2048,
+ 'created': (0, 0), 'modified': (0, 0),
+ 'xMin': -12, 'yMin': -477, 'xMax': 1280, 'yMax': 1430,
+ 'macStyle': 0,
+ 'lowestRecPPEM': 6,
+ 'fontDirectionHint': 2,
+ 'indexToLocFormat': 1,
+ 'glyphDataFormat': 0,
+ },
+ 'maxp': {
+ 'version': (1, 0),
+ 'numGlyphs': 133,
+ 'maxPoints': 94, 'maxComponentPoints': 0, 'maxTwilightPoints': 12,
+ 'maxContours': 5, 'maxComponentContours': 0,
+ 'maxZones': 2,
+ 'maxStorage': 6,
+ 'maxFunctionDefs': 64,
+ 'maxInstructionDefs': 0,
+ 'maxStackElements': 200,
+ 'maxSizeOfInstructions': 100,
+ 'maxComponentElements': 4,
+ 'maxComponentDepth': 1,
+ },
+ 'OS/2': {
+ 'version': 0,
+ 'xAvgCharWidth': 1075,
+ 'usWeightClass': 400, 'usWidthClass': 5,
+ 'fsType': 0,
+ 'ySubscriptXSize': 410, 'ySubscriptYSize': 369,
+ 'ySubscriptXOffset': 0, 'ySubscriptYOffset': -469,
+ 'ySuperscriptXSize': 410, 'ySuperscriptYSize': 369,
+ 'ySuperscriptXOffset': 0, 'ySuperscriptYOffset': 1090,
+ 'yStrikeoutSize': 102, 'yStrikeoutPosition': 530,
+ 'sFamilyClass': 0,
+ 'panose': b'\x02\x0b\x05\x00\x00\x00\x00\x00\x00\x00',
+ 'ulCharRange': (0, 0, 0, 0),
+ 'achVendID': b'\x00\x00\x00\x00',
+ 'fsSelection': 64, 'fsFirstCharIndex': 32, 'fsLastCharIndex': 9835,
+ },
+ 'hhea': {
+ 'version': (1, 0),
+ 'ascent': 1276, 'descent': -489, 'lineGap': 0,
+ 'advanceWidthMax': 1536,
+ 'minLeftBearing': -12, 'minRightBearing': -29,
+ 'xMaxExtent': 1280,
+ 'caretSlopeRise': 1, 'caretSlopeRun': 0, 'caretOffset': 0,
+ 'metricDataFormat': 0, 'numOfLongHorMetrics': 133,
+ },
+ 'vhea': None,
+ 'post': {
+ 'format': (2, 0),
+ 'isFixedPitch': 0, 'italicAngle': (0, 0),
+ 'underlinePosition': -133, 'underlineThickness': 20,
+ 'minMemType42': 0, 'maxMemType42': 0,
+ 'minMemType1': 0, 'maxMemType1': 0,
+ },
+ 'pclt': {
+ 'version': (1, 0),
+ 'fontNumber': 2147483648,
+ 'pitch': 1075,
+ 'xHeight': 905,
+ 'style': 0,
+ 'typeFamily': 0,
+ 'capHeight': 1276,
+ 'symbolSet': 0,
+ 'typeFace': b'cmtt10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ 'characterComplement': b'\xff\xff\xff\xff7\xff\xff\xfe',
+ 'strokeWeight': 0,
+ 'widthType': -5,
+ 'serifStyle': 64,
+ },
+ },
+ 'STIXSizeTwoSym:bold': {
+ 'invalid': None,
+ 'head': {
+ 'version': (1, 0),
+ 'fontRevision': (1, 0),
+ 'checkSumAdjustment': 1803408080,
+ 'magicNumber': 0x5F0F3CF5,
+ 'flags': 11,
+ 'unitsPerEm': 1000,
+ 'created': (0, 3359035786), 'modified': (0, 3359035786),
+ 'xMin': 4, 'yMin': -355, 'xMax': 1185, 'yMax': 2095,
+ 'macStyle': 1,
+ 'lowestRecPPEM': 8,
+ 'fontDirectionHint': 2,
+ 'indexToLocFormat': 0,
+ 'glyphDataFormat': 0,
+ },
+ 'maxp': {
+ 'version': (1, 0),
+ 'numGlyphs': 20,
+ 'maxPoints': 37, 'maxComponentPoints': 0, 'maxTwilightPoints': 0,
+ 'maxContours': 1, 'maxComponentContours': 0,
+ 'maxZones': 2,
+ 'maxStorage': 1,
+ 'maxFunctionDefs': 64,
+ 'maxInstructionDefs': 0,
+ 'maxStackElements': 64,
+ 'maxSizeOfInstructions': 0,
+ 'maxComponentElements': 0,
+ 'maxComponentDepth': 0,
+ },
+ 'OS/2': {
+ 'version': 2,
+ 'xAvgCharWidth': 598,
+ 'usWeightClass': 700, 'usWidthClass': 5,
+ 'fsType': 0,
+ 'ySubscriptXSize': 500, 'ySubscriptYSize': 500,
+ 'ySubscriptXOffset': 0, 'ySubscriptYOffset': 250,
+ 'ySuperscriptXSize': 500, 'ySuperscriptYSize': 500,
+ 'ySuperscriptXOffset': 0, 'ySuperscriptYOffset': 500,
+ 'yStrikeoutSize': 20, 'yStrikeoutPosition': 1037,
+ 'sFamilyClass': 0,
+ 'panose': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ 'ulCharRange': (3, 192, 0, 0),
+ 'achVendID': b'STIX',
+ 'fsSelection': 32, 'fsFirstCharIndex': 32, 'fsLastCharIndex': 10217,
+ },
+ 'hhea': {
+ 'version': (1, 0),
+ 'ascent': 2095, 'descent': -404, 'lineGap': 0,
+ 'advanceWidthMax': 1130,
+ 'minLeftBearing': 0, 'minRightBearing': -55,
+ 'xMaxExtent': 1185,
+ 'caretSlopeRise': 1, 'caretSlopeRun': 0, 'caretOffset': 0,
+ 'metricDataFormat': 0, 'numOfLongHorMetrics': 19,
+ },
+ 'vhea': None,
+ 'post': {
+ 'format': (2, 0),
+ 'isFixedPitch': 0, 'italicAngle': (0, 0),
+ 'underlinePosition': -123, 'underlineThickness': 20,
+ 'minMemType42': 0, 'maxMemType42': 0,
+ 'minMemType1': 0, 'maxMemType1': 0,
+ },
+ 'pclt': None,
+ },
+}
+
+
+@pytest.mark.parametrize('font_name', _expected_sfnt_tables.keys())
+@pytest.mark.parametrize('header', _expected_sfnt_tables['DejaVu Sans'].keys())
+def test_ft2font_get_sfnt_table(font_name, header):
+ file = fm.findfont(font_name)
+ font = ft2font.FT2Font(file)
+ assert font.get_sfnt_table(header) == _expected_sfnt_tables[font_name][header]
+
+
+@pytest.mark.parametrize('left, right, unscaled, unfitted, default', [
+ # These are all the same class.
+ ('A', 'A', 57, 248, 256), ('A', 'À', 57, 248, 256), ('A', '�', 57, 248, 256),
+ ('A', 'Â', 57, 248, 256), ('A', 'Ã', 57, 248, 256), ('A', 'Ä', 57, 248, 256),
+ # And a few other random ones.
+ ('D', 'A', -36, -156, -128), ('T', '.', -243, -1056, -1024),
+ ('X', 'C', -149, -647, -640), ('-', 'J', 114, 495, 512),
+])
+def test_ft2font_get_kerning(left, right, unscaled, unfitted, default):
+ file = fm.findfont('DejaVu Sans')
+ # With unscaled, these settings should produce exact values found in FontForge.
+ font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
+ font.set_size(100, 100)
+ assert font.get_kerning(font.get_char_index(ord(left)),
+ font.get_char_index(ord(right)),
+ ft2font.Kerning.UNSCALED) == unscaled
+ assert font.get_kerning(font.get_char_index(ord(left)),
+ font.get_char_index(ord(right)),
+ ft2font.Kerning.UNFITTED) == unfitted
+ assert font.get_kerning(font.get_char_index(ord(left)),
+ font.get_char_index(ord(right)),
+ ft2font.Kerning.DEFAULT) == default
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match='Use Kerning.UNSCALED instead'):
+ k = ft2font.KERNING_UNSCALED
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match='Use Kerning enum values instead'):
+ assert font.get_kerning(font.get_char_index(ord(left)),
+ font.get_char_index(ord(right)),
+ int(k)) == unscaled
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match='Use Kerning.UNFITTED instead'):
+ k = ft2font.KERNING_UNFITTED
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match='Use Kerning enum values instead'):
+ assert font.get_kerning(font.get_char_index(ord(left)),
+ font.get_char_index(ord(right)),
+ int(k)) == unfitted
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match='Use Kerning.DEFAULT instead'):
+ k = ft2font.KERNING_DEFAULT
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match='Use Kerning enum values instead'):
+ assert font.get_kerning(font.get_char_index(ord(left)),
+ font.get_char_index(ord(right)),
+ int(k)) == default
+
+
+def test_ft2font_set_text():
+ file = fm.findfont('DejaVu Sans')
+ font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
+ xys = font.set_text('')
+ np.testing.assert_array_equal(xys, np.empty((0, 2)))
+ assert font.get_width_height() == (0, 0)
+ assert font.get_num_glyphs() == 0
+ assert font.get_descent() == 0
+ assert font.get_bitmap_offset() == (0, 0)
+ # This string uses all the kerning pairs defined for test_ft2font_get_kerning.
+ xys = font.set_text('AADAT.XC-J')
+ np.testing.assert_array_equal(
+ xys,
+ [(0, 0), (512, 0), (1024, 0), (1600, 0), (2112, 0), (2496, 0), (2688, 0),
+ (3200, 0), (3712, 0), (4032, 0)])
+ assert font.get_width_height() == (4288, 768)
+ assert font.get_num_glyphs() == 10
+ assert font.get_descent() == 192
+ assert font.get_bitmap_offset() == (6, 0)
+
+
+def test_ft2font_loading():
+ file = fm.findfont('DejaVu Sans')
+ font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
+ for glyph in [font.load_char(ord('M')),
+ font.load_glyph(font.get_char_index(ord('M')))]:
+ assert glyph is not None
+ assert glyph.width == 576
+ assert glyph.height == 576
+ assert glyph.horiBearingX == 0
+ assert glyph.horiBearingY == 576
+ assert glyph.horiAdvance == 640
+ assert glyph.linearHoriAdvance == 678528
+ assert glyph.vertBearingX == -384
+ assert glyph.vertBearingY == 64
+ assert glyph.vertAdvance == 832
+ assert glyph.bbox == (54, 0, 574, 576)
+ assert font.get_num_glyphs() == 2 # Both count as loaded.
+ # But neither has been placed anywhere.
+ assert font.get_width_height() == (0, 0)
+ assert font.get_descent() == 0
+ assert font.get_bitmap_offset() == (0, 0)
+
+
+def test_ft2font_drawing():
+ expected_str = (
+ ' ',
+ '11 11 ',
+ '11 11 ',
+ '1 1 1 1 ',
+ '1 1 1 1 ',
+ '1 1 1 1 ',
+ '1 11 1 ',
+ '1 11 1 ',
+ '1 1 ',
+ '1 1 ',
+ ' ',
+ )
+ expected = np.array([
+ [int(c) for c in line.replace(' ', '0')] for line in expected_str
+ ])
+ expected *= 255
+ file = fm.findfont('DejaVu Sans')
+ font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
+ font.set_text('M')
+ font.draw_glyphs_to_bitmap(antialiased=False)
+ image = font.get_image()
+ np.testing.assert_array_equal(image, expected)
+ font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
+ glyph = font.load_char(ord('M'))
+ image = ft2font.FT2Image(expected.shape[1], expected.shape[0])
+ font.draw_glyph_to_bitmap(image, -1, 1, glyph, antialiased=False)
+ np.testing.assert_array_equal(image, expected)
+
+
+def test_ft2font_get_path():
+ file = fm.findfont('DejaVu Sans')
+ font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
+ vertices, codes = font.get_path()
+ assert vertices.shape == (0, 2)
+ assert codes.shape == (0, )
+ font.load_char(ord('M'))
+ vertices, codes = font.get_path()
+ expected_vertices = np.array([
+ (0.843750, 9.000000), (2.609375, 9.000000), # Top left.
+ (4.906250, 2.875000), # Top of midpoint.
+ (7.218750, 9.000000), (8.968750, 9.000000), # Top right.
+ (8.968750, 0.000000), (7.843750, 0.000000), # Bottom right.
+ (7.843750, 7.906250), # Point under top right.
+ (5.531250, 1.734375), (4.296875, 1.734375), # Bar under midpoint.
+ (1.984375, 7.906250), # Point under top left.
+ (1.984375, 0.000000), (0.843750, 0.000000), # Bottom left.
+ (0.843750, 9.000000), # Back to top left corner.
+ (0.000000, 0.000000),
+ ])
+ np.testing.assert_array_equal(vertices, expected_vertices)
+ expected_codes = np.full(expected_vertices.shape[0], mpath.Path.LINETO,
+ dtype=mpath.Path.code_type)
+ expected_codes[0] = mpath.Path.MOVETO
+ expected_codes[-1] = mpath.Path.CLOSEPOLY
+ np.testing.assert_array_equal(codes, expected_codes)
+
+
+@pytest.mark.parametrize('family_name, file_name',
+ [("WenQuanYi Zen Hei", "wqy-zenhei.ttc"),
+ ("Noto Sans CJK JP", "NotoSansCJK.ttc"),
+ ("Noto Sans TC", "NotoSansTC-Regular.otf")]
+ )
+def test_fallback_smoke(family_name, file_name):
+ fp = fm.FontProperties(family=[family_name])
+ if Path(fm.findfont(fp)).name != file_name:
+ pytest.skip(f"Font {family_name} ({file_name}) is missing")
+ plt.rcParams['font.size'] = 20
+ fig = plt.figure(figsize=(4.75, 1.85))
+ fig.text(0.05, 0.45, "There are å‡ ä¸ªæ±‰å— in between!",
+ family=['DejaVu Sans', family_name])
+ fig.text(0.05, 0.85, "There are å‡ ä¸ªæ±‰å— in between!",
+ family=[family_name])
+
+ # TODO enable fallback for other backends!
+ for fmt in ['png', 'raw']: # ["svg", "pdf", "ps"]:
+ fig.savefig(io.BytesIO(), format=fmt)
+
+
+@pytest.mark.parametrize('family_name, file_name',
+ [("WenQuanYi Zen Hei", "wqy-zenhei"),
+ ("Noto Sans CJK JP", "NotoSansCJK"),
+ ("Noto Sans TC", "NotoSansTC-Regular.otf")]
+ )
+@check_figures_equal(extensions=["png", "pdf", "eps", "svg"])
+def test_font_fallback_chinese(fig_test, fig_ref, family_name, file_name):
+ fp = fm.FontProperties(family=[family_name])
+ if file_name not in Path(fm.findfont(fp)).name:
+ pytest.skip(f"Font {family_name} ({file_name}) is missing")
+
+ text = ["There are", "å‡ ä¸ªæ±‰å—", "in between!"]
+
+ plt.rcParams["font.size"] = 20
+ test_fonts = [["DejaVu Sans", family_name]] * 3
+ ref_fonts = [["DejaVu Sans"], [family_name], ["DejaVu Sans"]]
+
+ for j, (txt, test_font, ref_font) in enumerate(
+ zip(text, test_fonts, ref_fonts)
+ ):
+ fig_ref.text(0.05, .85 - 0.15*j, txt, family=ref_font)
+ fig_test.text(0.05, .85 - 0.15*j, txt, family=test_font)
+
+
+@pytest.mark.parametrize("font_list",
+ [['DejaVu Serif', 'DejaVu Sans'],
+ ['DejaVu Sans Mono']],
+ ids=["two fonts", "one font"])
+def test_fallback_missing(recwarn, font_list):
+ fig = plt.figure()
+ fig.text(.5, .5, "Hello 🙃 World!", family=font_list)
+ fig.canvas.draw()
+ assert all(isinstance(warn.message, UserWarning) for warn in recwarn)
+ # not sure order is guaranteed on the font listing so
+ assert recwarn[0].message.args[0].startswith(
+ "Glyph 128579 (\\N{UPSIDE-DOWN FACE}) missing from font(s)")
+ assert all([font in recwarn[0].message.args[0] for font in font_list])
+
+
+@pytest.mark.parametrize(
+ "family_name, file_name",
+ [
+ ("WenQuanYi Zen Hei", "wqy-zenhei"),
+ ("Noto Sans CJK JP", "NotoSansCJK"),
+ ("Noto Sans TC", "NotoSansTC-Regular.otf")
+ ],
+)
+def test__get_fontmap(family_name, file_name):
+ fp = fm.FontProperties(family=[family_name])
+ found_file_name = Path(fm.findfont(fp)).name
+ if file_name not in found_file_name:
+ pytest.skip(f"Font {family_name} ({file_name}) is missing")
+
+ text = "There are å‡ ä¸ªæ±‰å— in between!"
+ ft = fm.get_font(
+ fm.fontManager._find_fonts_by_props(
+ fm.FontProperties(family=["DejaVu Sans", family_name])
+ )
+ )
+ fontmap = ft._get_fontmap(text)
+ for char, font in fontmap.items():
+ if ord(char) > 127:
+ assert Path(font.fname).name == found_file_name
+ else:
+ assert Path(font.fname).name == "DejaVuSans.ttf"
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_getattr.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_getattr.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0f5823600ca0378efe611c989ff293ea7740581
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_getattr.py
@@ -0,0 +1,35 @@
+from importlib import import_module
+from pkgutil import walk_packages
+
+import matplotlib
+import pytest
+
+# Get the names of all matplotlib submodules,
+# except for the unit tests and private modules.
+module_names = [
+ m.name
+ for m in walk_packages(
+ path=matplotlib.__path__, prefix=f'{matplotlib.__name__}.'
+ )
+ if not m.name.startswith(__package__)
+ and not any(x.startswith('_') for x in m.name.split('.'))
+]
+
+
+@pytest.mark.parametrize('module_name', module_names)
+@pytest.mark.filterwarnings('ignore::DeprecationWarning')
+@pytest.mark.filterwarnings('ignore::ImportWarning')
+def test_getattr(module_name):
+ """
+ Test that __getattr__ methods raise AttributeError for unknown keys.
+ See #20822, #20855.
+ """
+ try:
+ module = import_module(module_name)
+ except (ImportError, RuntimeError, OSError) as e:
+ # Skip modules that cannot be imported due to missing dependencies
+ pytest.skip(f'Cannot import {module_name} due to {e}')
+
+ key = 'THIS_SYMBOL_SHOULD_NOT_EXIST'
+ if hasattr(module, key):
+ delattr(module, key)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_gridspec.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_gridspec.py
new file mode 100644
index 0000000000000000000000000000000000000000..deda73c3b6abfc08fb921b6cbe395aca7efd3613
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_gridspec.py
@@ -0,0 +1,50 @@
+import matplotlib.gridspec as gridspec
+import matplotlib.pyplot as plt
+import pytest
+
+
+def test_equal():
+ gs = gridspec.GridSpec(2, 1)
+ assert gs[0, 0] == gs[0, 0]
+ assert gs[:, 0] == gs[:, 0]
+
+
+def test_width_ratios():
+ """
+ Addresses issue #5835.
+ See at https://github.com/matplotlib/matplotlib/issues/5835.
+ """
+ with pytest.raises(ValueError):
+ gridspec.GridSpec(1, 1, width_ratios=[2, 1, 3])
+
+
+def test_height_ratios():
+ """
+ Addresses issue #5835.
+ See at https://github.com/matplotlib/matplotlib/issues/5835.
+ """
+ with pytest.raises(ValueError):
+ gridspec.GridSpec(1, 1, height_ratios=[2, 1, 3])
+
+
+def test_repr():
+ ss = gridspec.GridSpec(3, 3)[2, 1:3]
+ assert repr(ss) == "GridSpec(3, 3)[2:3, 1:3]"
+
+ ss = gridspec.GridSpec(2, 2,
+ height_ratios=(3, 1),
+ width_ratios=(1, 3))
+ assert repr(ss) == \
+ "GridSpec(2, 2, height_ratios=(3, 1), width_ratios=(1, 3))"
+
+
+def test_subplotspec_args():
+ fig, axs = plt.subplots(1, 2)
+ # should work:
+ gs = gridspec.GridSpecFromSubplotSpec(2, 1,
+ subplot_spec=axs[0].get_subplotspec())
+ assert gs.get_topmost_subplotspec() == axs[0].get_subplotspec()
+ with pytest.raises(TypeError, match="subplot_spec must be type SubplotSpec"):
+ gs = gridspec.GridSpecFromSubplotSpec(2, 1, subplot_spec=axs[0])
+ with pytest.raises(TypeError, match="subplot_spec must be type SubplotSpec"):
+ gs = gridspec.GridSpecFromSubplotSpec(2, 1, subplot_spec=axs)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_image.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_image.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d1d302913281a34138e4b598a02ebce303438df
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_image.py
@@ -0,0 +1,1781 @@
+from contextlib import ExitStack
+from copy import copy
+import functools
+import io
+import os
+from pathlib import Path
+import platform
+import sys
+import urllib.request
+
+import numpy as np
+from numpy.testing import assert_array_equal
+from PIL import Image
+
+import matplotlib as mpl
+from matplotlib import (
+ colors, image as mimage, patches, pyplot as plt, style, rcParams)
+from matplotlib.image import (AxesImage, BboxImage, FigureImage,
+ NonUniformImage, PcolorImage)
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+from matplotlib.transforms import Bbox, Affine2D, TransformedBbox
+import matplotlib.ticker as mticker
+
+import pytest
+
+
+@image_comparison(['interp_alpha.png'], remove_text=True)
+def test_alpha_interp():
+ """Test the interpolation of the alpha channel on RGBA images"""
+ fig, (axl, axr) = plt.subplots(1, 2)
+ # full green image
+ img = np.zeros((5, 5, 4))
+ img[..., 1] = np.ones((5, 5))
+ # transparent under main diagonal
+ img[..., 3] = np.tril(np.ones((5, 5), dtype=np.uint8))
+ axl.imshow(img, interpolation="none")
+ axr.imshow(img, interpolation="bilinear")
+
+
+@image_comparison(['interp_nearest_vs_none'], tol=3.7, # For Ghostscript 10.06+.
+ extensions=['pdf', 'svg'], remove_text=True)
+def test_interp_nearest_vs_none():
+ """Test the effect of "nearest" and "none" interpolation"""
+ # Setting dpi to something really small makes the difference very
+ # visible. This works fine with pdf, since the dpi setting doesn't
+ # affect anything but images, but the agg output becomes unusably
+ # small.
+ rcParams['savefig.dpi'] = 3
+ X = np.array([[[218, 165, 32], [122, 103, 238]],
+ [[127, 255, 0], [255, 99, 71]]], dtype=np.uint8)
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+ ax1.imshow(X, interpolation='none')
+ ax1.set_title('interpolation none')
+ ax2.imshow(X, interpolation='nearest')
+ ax2.set_title('interpolation nearest')
+
+
+@pytest.mark.parametrize('suppressComposite', [False, True])
+@image_comparison(['figimage'], extensions=['png', 'pdf'])
+def test_figimage(suppressComposite):
+ fig = plt.figure(figsize=(2, 2), dpi=100)
+ fig.suppressComposite = suppressComposite
+ x, y = np.ix_(np.arange(100) / 100.0, np.arange(100) / 100)
+ z = np.sin(x**2 + y**2 - x*y)
+ c = np.sin(20*x**2 + 50*y**2)
+ img = z + c/5
+
+ fig.figimage(img, xo=0, yo=0, origin='lower')
+ fig.figimage(img[::-1, :], xo=0, yo=100, origin='lower')
+ fig.figimage(img[:, ::-1], xo=100, yo=0, origin='lower')
+ fig.figimage(img[::-1, ::-1], xo=100, yo=100, origin='lower')
+
+
+def test_image_python_io():
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3])
+ buffer = io.BytesIO()
+ fig.savefig(buffer)
+ buffer.seek(0)
+ plt.imread(buffer)
+
+
+@pytest.mark.parametrize(
+ "img_size, fig_size, interpolation",
+ [(5, 2, "hanning"), # data larger than figure.
+ (5, 5, "nearest"), # exact resample.
+ (5, 10, "nearest"), # double sample.
+ (3, 2.9, "hanning"), # <3 upsample.
+ (3, 9.1, "nearest"), # >3 upsample.
+ ])
+@check_figures_equal(extensions=['png'])
+def test_imshow_antialiased(fig_test, fig_ref,
+ img_size, fig_size, interpolation):
+ np.random.seed(19680801)
+ dpi = plt.rcParams["savefig.dpi"]
+ A = np.random.rand(int(dpi * img_size), int(dpi * img_size))
+ for fig in [fig_test, fig_ref]:
+ fig.set_size_inches(fig_size, fig_size)
+ ax = fig_test.subplots()
+ ax.set_position([0, 0, 1, 1])
+ ax.imshow(A, interpolation='auto')
+ ax = fig_ref.subplots()
+ ax.set_position([0, 0, 1, 1])
+ ax.imshow(A, interpolation=interpolation)
+
+
+@check_figures_equal(extensions=['png'])
+def test_imshow_zoom(fig_test, fig_ref):
+ # should be less than 3 upsample, so should be nearest...
+ np.random.seed(19680801)
+ dpi = plt.rcParams["savefig.dpi"]
+ A = np.random.rand(int(dpi * 3), int(dpi * 3))
+ for fig in [fig_test, fig_ref]:
+ fig.set_size_inches(2.9, 2.9)
+ ax = fig_test.subplots()
+ ax.imshow(A, interpolation='auto')
+ ax.set_xlim([10, 20])
+ ax.set_ylim([10, 20])
+ ax = fig_ref.subplots()
+ ax.imshow(A, interpolation='nearest')
+ ax.set_xlim([10, 20])
+ ax.set_ylim([10, 20])
+
+
+@check_figures_equal(extensions=['png'])
+def test_imshow_pil(fig_test, fig_ref):
+ style.use("default")
+ png_path = Path(__file__).parent / "baseline_images/pngsuite/basn3p04.png"
+ tiff_path = Path(__file__).parent / "baseline_images/test_image/uint16.tif"
+ axs = fig_test.subplots(2)
+ axs[0].imshow(Image.open(png_path))
+ axs[1].imshow(Image.open(tiff_path))
+ axs = fig_ref.subplots(2)
+ axs[0].imshow(plt.imread(png_path))
+ axs[1].imshow(plt.imread(tiff_path))
+
+
+def test_imread_pil_uint16():
+ img = plt.imread(os.path.join(os.path.dirname(__file__),
+ 'baseline_images', 'test_image', 'uint16.tif'))
+ assert img.dtype == np.uint16
+ assert np.sum(img) == 134184960
+
+
+def test_imread_fspath():
+ img = plt.imread(
+ Path(__file__).parent / 'baseline_images/test_image/uint16.tif')
+ assert img.dtype == np.uint16
+ assert np.sum(img) == 134184960
+
+
+@pytest.mark.parametrize("fmt", ["png", "jpg", "jpeg", "tiff"])
+def test_imsave(fmt):
+ has_alpha = fmt not in ["jpg", "jpeg"]
+
+ # The goal here is that the user can specify an output logical DPI
+ # for the image, but this will not actually add any extra pixels
+ # to the image, it will merely be used for metadata purposes.
+
+ # So we do the traditional case (dpi == 1), and the new case (dpi
+ # == 100) and read the resulting PNG files back in and make sure
+ # the data is 100% identical.
+ np.random.seed(1)
+ # The height of 1856 pixels was selected because going through creating an
+ # actual dpi=100 figure to save the image to a Pillow-provided format would
+ # cause a rounding error resulting in a final image of shape 1855.
+ data = np.random.rand(1856, 2)
+
+ buff_dpi1 = io.BytesIO()
+ plt.imsave(buff_dpi1, data, format=fmt, dpi=1)
+
+ buff_dpi100 = io.BytesIO()
+ plt.imsave(buff_dpi100, data, format=fmt, dpi=100)
+
+ buff_dpi1.seek(0)
+ arr_dpi1 = plt.imread(buff_dpi1, format=fmt)
+
+ buff_dpi100.seek(0)
+ arr_dpi100 = plt.imread(buff_dpi100, format=fmt)
+
+ assert arr_dpi1.shape == (1856, 2, 3 + has_alpha)
+ assert arr_dpi100.shape == (1856, 2, 3 + has_alpha)
+
+ assert_array_equal(arr_dpi1, arr_dpi100)
+
+
+def test_imsave_python_sequences():
+ # Tests saving an image with data passed using Python sequence types
+ # such as lists or tuples.
+
+ # RGB image: 3 rows × 2 columns, with float values in [0.0, 1.0]
+ img_data = [
+ [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0)],
+ [(0.0, 0.0, 1.0), (1.0, 1.0, 0.0)],
+ [(0.0, 1.0, 1.0), (1.0, 0.0, 1.0)],
+ ]
+
+ buff = io.BytesIO()
+ plt.imsave(buff, img_data, format="png")
+ buff.seek(0)
+ read_img = plt.imread(buff)
+
+ assert_array_equal(
+ np.array(img_data),
+ read_img[:, :, :3] # Drop alpha if present
+ )
+
+
+@pytest.mark.parametrize("origin", ["upper", "lower"])
+def test_imsave_rgba_origin(origin):
+ # test that imsave always passes c-contiguous arrays down to pillow
+ buf = io.BytesIO()
+ result = np.zeros((10, 10, 4), dtype='uint8')
+ mimage.imsave(buf, arr=result, format="png", origin=origin)
+
+
+@pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"])
+def test_imsave_fspath(fmt):
+ plt.imsave(Path(os.devnull), np.array([[0, 1]]), format=fmt)
+
+
+def test_imsave_color_alpha():
+ # Test that imsave accept arrays with ndim=3 where the third dimension is
+ # color and alpha without raising any exceptions, and that the data is
+ # acceptably preserved through a save/read roundtrip.
+ np.random.seed(1)
+
+ for origin in ['lower', 'upper']:
+ data = np.random.rand(16, 16, 4)
+ buff = io.BytesIO()
+ plt.imsave(buff, data, origin=origin, format="png")
+
+ buff.seek(0)
+ arr_buf = plt.imread(buff)
+
+ # Recreate the float -> uint8 conversion of the data
+ # We can only expect to be the same with 8 bits of precision,
+ # since that's what the PNG file used.
+ data = (255*data).astype('uint8')
+ if origin == 'lower':
+ data = data[::-1]
+ arr_buf = (255*arr_buf).astype('uint8')
+
+ assert_array_equal(data, arr_buf)
+
+
+def test_imsave_pil_kwargs_png():
+ from PIL.PngImagePlugin import PngInfo
+ buf = io.BytesIO()
+ pnginfo = PngInfo()
+ pnginfo.add_text("Software", "test")
+ plt.imsave(buf, [[0, 1], [2, 3]],
+ format="png", pil_kwargs={"pnginfo": pnginfo})
+ im = Image.open(buf)
+ assert im.info["Software"] == "test"
+
+
+def test_imsave_pil_kwargs_tiff():
+ from PIL.TiffTags import TAGS_V2 as TAGS
+ buf = io.BytesIO()
+ pil_kwargs = {"description": "test image"}
+ plt.imsave(buf, [[0, 1], [2, 3]], format="tiff", pil_kwargs=pil_kwargs)
+ assert len(pil_kwargs) == 1
+ im = Image.open(buf)
+ tags = {TAGS[k].name: v for k, v in im.tag_v2.items()}
+ assert tags["ImageDescription"] == "test image"
+
+
+@image_comparison(['image_alpha'], remove_text=True)
+def test_image_alpha():
+ np.random.seed(0)
+ Z = np.random.rand(6, 6)
+
+ fig, (ax1, ax2, ax3) = plt.subplots(1, 3)
+ ax1.imshow(Z, alpha=1.0, interpolation='none')
+ ax2.imshow(Z, alpha=0.5, interpolation='none')
+ ax3.imshow(Z, alpha=0.5, interpolation='nearest')
+
+
+@mpl.style.context('mpl20')
+@check_figures_equal(extensions=['png'])
+def test_imshow_alpha(fig_test, fig_ref):
+ np.random.seed(19680801)
+
+ rgbf = np.random.rand(6, 6, 3)
+ rgbu = np.uint8(rgbf * 255)
+ ((ax0, ax1), (ax2, ax3)) = fig_test.subplots(2, 2)
+ ax0.imshow(rgbf, alpha=0.5)
+ ax1.imshow(rgbf, alpha=0.75)
+ ax2.imshow(rgbu, alpha=0.5)
+ ax3.imshow(rgbu, alpha=0.75)
+
+ rgbaf = np.concatenate((rgbf, np.ones((6, 6, 1))), axis=2)
+ rgbau = np.concatenate((rgbu, np.full((6, 6, 1), 255, np.uint8)), axis=2)
+ ((ax0, ax1), (ax2, ax3)) = fig_ref.subplots(2, 2)
+ rgbaf[:, :, 3] = 0.5
+ ax0.imshow(rgbaf)
+ rgbaf[:, :, 3] = 0.75
+ ax1.imshow(rgbaf)
+ rgbau[:, :, 3] = 127
+ ax2.imshow(rgbau)
+ rgbau[:, :, 3] = 191
+ ax3.imshow(rgbau)
+
+
+def test_cursor_data():
+ from matplotlib.backend_bases import MouseEvent
+
+ fig, ax = plt.subplots()
+ im = ax.imshow(np.arange(100).reshape(10, 10), origin='upper')
+
+ x, y = 4, 4
+ xdisp, ydisp = ax.transData.transform([x, y])
+
+ event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ assert im.get_cursor_data(event) == 44
+
+ # Now try for a point outside the image
+ # Tests issue #4957
+ x, y = 10.1, 4
+ xdisp, ydisp = ax.transData.transform([x, y])
+
+ event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ assert im.get_cursor_data(event) is None
+
+ # Hmm, something is wrong here... I get 0, not None...
+ # But, this works further down in the tests with extents flipped
+ # x, y = 0.1, -0.1
+ # xdisp, ydisp = ax.transData.transform([x, y])
+ # event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ # z = im.get_cursor_data(event)
+ # assert z is None, "Did not get None, got %d" % z
+
+ ax.clear()
+ # Now try with the extents flipped.
+ im = ax.imshow(np.arange(100).reshape(10, 10), origin='lower')
+
+ x, y = 4, 4
+ xdisp, ydisp = ax.transData.transform([x, y])
+
+ event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ assert im.get_cursor_data(event) == 44
+
+ fig, ax = plt.subplots()
+ im = ax.imshow(np.arange(100).reshape(10, 10), extent=[0, 0.5, 0, 0.5])
+
+ x, y = 0.25, 0.25
+ xdisp, ydisp = ax.transData.transform([x, y])
+
+ event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ assert im.get_cursor_data(event) == 55
+
+ # Now try for a point outside the image
+ # Tests issue #4957
+ x, y = 0.75, 0.25
+ xdisp, ydisp = ax.transData.transform([x, y])
+
+ event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ assert im.get_cursor_data(event) is None
+
+ x, y = 0.01, -0.01
+ xdisp, ydisp = ax.transData.transform([x, y])
+
+ event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ assert im.get_cursor_data(event) is None
+
+ # Now try with additional transform applied to the image artist
+ trans = Affine2D().scale(2).rotate(0.5)
+ im = ax.imshow(np.arange(100).reshape(10, 10),
+ transform=trans + ax.transData)
+ x, y = 3, 10
+ xdisp, ydisp = ax.transData.transform([x, y])
+ event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ assert im.get_cursor_data(event) == 44
+
+
+@pytest.mark.parametrize("xy, data", [
+ # x/y coords chosen to be 0.5 above boundaries so they lie within image pixels
+ [[0.5, 0.5], 0 + 0],
+ [[0.5, 1.5], 0 + 1],
+ [[4.5, 0.5], 16 + 0],
+ [[8.5, 0.5], 16 + 0],
+ [[9.5, 2.5], 81 + 4],
+ [[-1, 0.5], None],
+ [[0.5, -1], None],
+ ]
+)
+def test_cursor_data_nonuniform(xy, data):
+ from matplotlib.backend_bases import MouseEvent
+
+ # Non-linear set of x-values
+ x = np.array([0, 1, 4, 9, 16])
+ y = np.array([0, 1, 2, 3, 4])
+ z = x[np.newaxis, :]**2 + y[:, np.newaxis]**2
+
+ fig, ax = plt.subplots()
+ im = NonUniformImage(ax, extent=(x.min(), x.max(), y.min(), y.max()))
+ im.set_data(x, y, z)
+ ax.add_image(im)
+ # Set lower min lim so we can test cursor outside image
+ ax.set_xlim(x.min() - 2, x.max())
+ ax.set_ylim(y.min() - 2, y.max())
+
+ xdisp, ydisp = ax.transData.transform(xy)
+ event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ assert im.get_cursor_data(event) == data, (im.get_cursor_data(event), data)
+
+
+@pytest.mark.parametrize(
+ "data, text", [
+ ([[10001, 10000]], "[10001.000]"),
+ ([[.123, .987]], "[0.123]"),
+ ([[np.nan, 1, 2]], "[]"),
+ ([[1, 1+1e-15]], "[1.0000000000000000]"),
+ ([[-1, -1]], "[-1.0]"),
+ ([[0, 0]], "[0.00]"),
+ ])
+def test_format_cursor_data(data, text):
+ from matplotlib.backend_bases import MouseEvent
+
+ fig, ax = plt.subplots()
+ im = ax.imshow(data)
+
+ xdisp, ydisp = ax.transData.transform([0, 0])
+ event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
+ assert im.format_cursor_data(im.get_cursor_data(event)) == text
+
+
+@image_comparison(['image_clip'], style='mpl20')
+def test_image_clip():
+ d = [[1, 2], [3, 4]]
+
+ fig, ax = plt.subplots()
+ im = ax.imshow(d)
+ patch = patches.Circle((0, 0), radius=1, transform=ax.transData)
+ im.set_clip_path(patch)
+
+
+@image_comparison(['image_cliprect'], style='mpl20')
+def test_image_cliprect():
+ fig, ax = plt.subplots()
+ d = [[1, 2], [3, 4]]
+
+ im = ax.imshow(d, extent=(0, 5, 0, 5))
+
+ rect = patches.Rectangle(
+ xy=(1, 1), width=2, height=2, transform=im.axes.transData)
+ im.set_clip_path(rect)
+
+
+@check_figures_equal(extensions=['png'])
+def test_imshow_10_10_1(fig_test, fig_ref):
+ # 10x10x1 should be the same as 10x10
+ arr = np.arange(100).reshape((10, 10, 1))
+ ax = fig_ref.subplots()
+ ax.imshow(arr[:, :, 0], interpolation="bilinear", extent=(1, 2, 1, 2))
+ ax.set_xlim(0, 3)
+ ax.set_ylim(0, 3)
+
+ ax = fig_test.subplots()
+ ax.imshow(arr, interpolation="bilinear", extent=(1, 2, 1, 2))
+ ax.set_xlim(0, 3)
+ ax.set_ylim(0, 3)
+
+
+def test_imshow_10_10_2():
+ fig, ax = plt.subplots()
+ arr = np.arange(200).reshape((10, 10, 2))
+ with pytest.raises(TypeError):
+ ax.imshow(arr)
+
+
+def test_imshow_10_10_5():
+ fig, ax = plt.subplots()
+ arr = np.arange(500).reshape((10, 10, 5))
+ with pytest.raises(TypeError):
+ ax.imshow(arr)
+
+
+@image_comparison(['no_interpolation_origin'], remove_text=True)
+def test_no_interpolation_origin():
+ fig, axs = plt.subplots(2)
+ axs[0].imshow(np.arange(100).reshape((2, 50)), origin="lower",
+ interpolation='none')
+ axs[1].imshow(np.arange(100).reshape((2, 50)), interpolation='none')
+
+
+@image_comparison(['image_shift'], remove_text=True, extensions=['pdf', 'svg'])
+def test_image_shift():
+ imgData = [[1 / x + 1 / y for x in range(1, 100)] for y in range(1, 100)]
+ tMin = 734717.945208
+ tMax = 734717.946366
+
+ fig, ax = plt.subplots()
+ ax.imshow(imgData, norm=colors.LogNorm(), interpolation='none',
+ extent=(tMin, tMax, 1, 100))
+ ax.set_aspect('auto')
+
+
+def test_image_edges():
+ fig = plt.figure(figsize=[1, 1])
+ ax = fig.add_axes([0, 0, 1, 1], frameon=False)
+
+ data = np.tile(np.arange(12), 15).reshape(20, 9)
+
+ im = ax.imshow(data, origin='upper', extent=[-10, 10, -10, 10],
+ interpolation='none', cmap='gray')
+
+ x = y = 2
+ ax.set_xlim([-x, x])
+ ax.set_ylim([-y, y])
+
+ ax.set_xticks([])
+ ax.set_yticks([])
+
+ buf = io.BytesIO()
+ fig.savefig(buf, facecolor=(0, 1, 0))
+
+ buf.seek(0)
+
+ im = plt.imread(buf)
+ r, g, b, a = sum(im[:, 0])
+ r, g, b, a = sum(im[:, -1])
+
+ assert g != 100, 'Expected a non-green edge - but sadly, it was.'
+
+
+@image_comparison(['image_composite_background'],
+ remove_text=True, style='mpl20')
+def test_image_composite_background():
+ fig, ax = plt.subplots()
+ arr = np.arange(12).reshape(4, 3)
+ ax.imshow(arr, extent=[0, 2, 15, 0])
+ ax.imshow(arr, extent=[4, 6, 15, 0])
+ ax.set_facecolor((1, 0, 0, 0.5))
+ ax.set_xlim([0, 12])
+
+
+@image_comparison(['image_composite_alpha'], remove_text=True)
+def test_image_composite_alpha():
+ """
+ Tests that the alpha value is recognized and correctly applied in the
+ process of compositing images together.
+ """
+ fig, ax = plt.subplots()
+ arr = np.zeros((11, 21, 4))
+ arr[:, :, 0] = 1
+ arr[:, :, 3] = np.concatenate(
+ (np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1]))
+ arr2 = np.zeros((21, 11, 4))
+ arr2[:, :, 0] = 1
+ arr2[:, :, 1] = 1
+ arr2[:, :, 3] = np.concatenate(
+ (np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1]))[:, np.newaxis]
+ ax.imshow(arr, extent=[1, 2, 5, 0], alpha=0.3)
+ ax.imshow(arr, extent=[2, 3, 5, 0], alpha=0.6)
+ ax.imshow(arr, extent=[3, 4, 5, 0])
+ ax.imshow(arr2, extent=[0, 5, 1, 2])
+ ax.imshow(arr2, extent=[0, 5, 2, 3], alpha=0.6)
+ ax.imshow(arr2, extent=[0, 5, 3, 4], alpha=0.3)
+ ax.set_facecolor((0, 0.5, 0, 1))
+ ax.set_xlim([0, 5])
+ ax.set_ylim([5, 0])
+
+
+@check_figures_equal(extensions=["pdf"])
+def test_clip_path_disables_compositing(fig_test, fig_ref):
+ t = np.arange(9).reshape((3, 3))
+ for fig in [fig_test, fig_ref]:
+ ax = fig.add_subplot()
+ ax.imshow(t, clip_path=(mpl.path.Path([(0, 0), (0, 1), (1, 0)]),
+ ax.transData))
+ ax.imshow(t, clip_path=(mpl.path.Path([(1, 1), (1, 2), (2, 1)]),
+ ax.transData))
+ fig_ref.suppressComposite = True
+
+
+@image_comparison(['rasterize_10dpi'],
+ extensions=['pdf', 'svg'], remove_text=True, style='mpl20')
+def test_rasterize_dpi():
+ # This test should check rasterized rendering with high output resolution.
+ # It plots a rasterized line and a normal image with imshow. So it will
+ # catch when images end up in the wrong place in case of non-standard dpi
+ # setting. Instead of high-res rasterization I use low-res. Therefore
+ # the fact that the resolution is non-standard is easily checked by
+ # image_comparison.
+ img = np.asarray([[1, 2], [3, 4]])
+
+ fig, axs = plt.subplots(1, 3, figsize=(3, 1))
+
+ axs[0].imshow(img)
+
+ axs[1].plot([0, 1], [0, 1], linewidth=20., rasterized=True)
+ axs[1].set(xlim=(0, 1), ylim=(-1, 2))
+
+ axs[2].plot([0, 1], [0, 1], linewidth=20.)
+ axs[2].set(xlim=(0, 1), ylim=(-1, 2))
+
+ # Low-dpi PDF rasterization errors prevent proper image comparison tests.
+ # Hide detailed structures like the axes spines.
+ for ax in axs:
+ ax.set_xticks([])
+ ax.set_yticks([])
+ ax.spines[:].set_visible(False)
+
+ rcParams['savefig.dpi'] = 10
+
+
+@image_comparison(['bbox_image_inverted'], remove_text=True, style='mpl20')
+def test_bbox_image_inverted():
+ # This is just used to produce an image to feed to BboxImage
+ image = np.arange(100).reshape((10, 10))
+
+ fig, ax = plt.subplots()
+ bbox_im = BboxImage(
+ TransformedBbox(Bbox([[100, 100], [0, 0]]), ax.transData),
+ interpolation='nearest')
+ bbox_im.set_data(image)
+ bbox_im.set_clip_on(False)
+ ax.set_xlim(0, 100)
+ ax.set_ylim(0, 100)
+ ax.add_artist(bbox_im)
+
+ image = np.identity(10)
+
+ bbox_im = BboxImage(TransformedBbox(Bbox([[0.1, 0.2], [0.3, 0.25]]),
+ ax.get_figure().transFigure),
+ interpolation='nearest')
+ bbox_im.set_data(image)
+ bbox_im.set_clip_on(False)
+ ax.add_artist(bbox_im)
+
+
+def test_get_window_extent_for_AxisImage():
+ # Create a figure of known size (1000x1000 pixels), place an image
+ # object at a given location and check that get_window_extent()
+ # returns the correct bounding box values (in pixels).
+
+ im = np.array([[0.25, 0.75, 1.0, 0.75], [0.1, 0.65, 0.5, 0.4],
+ [0.6, 0.3, 0.0, 0.2], [0.7, 0.9, 0.4, 0.6]])
+ fig, ax = plt.subplots(figsize=(10, 10), dpi=100)
+ ax.set_position([0, 0, 1, 1])
+ ax.set_xlim(0, 1)
+ ax.set_ylim(0, 1)
+ im_obj = ax.imshow(
+ im, extent=[0.4, 0.7, 0.2, 0.9], interpolation='nearest')
+
+ fig.canvas.draw()
+ renderer = fig.canvas.renderer
+ im_bbox = im_obj.get_window_extent(renderer)
+
+ assert_array_equal(im_bbox.get_points(), [[400, 200], [700, 900]])
+
+ fig, ax = plt.subplots(figsize=(10, 10), dpi=100)
+ ax.set_position([0, 0, 1, 1])
+ ax.set_xlim(1, 2)
+ ax.set_ylim(0, 1)
+ im_obj = ax.imshow(
+ im, extent=[0.4, 0.7, 0.2, 0.9], interpolation='nearest',
+ transform=ax.transAxes)
+
+ fig.canvas.draw()
+ renderer = fig.canvas.renderer
+ im_bbox = im_obj.get_window_extent(renderer)
+
+ assert_array_equal(im_bbox.get_points(), [[400, 200], [700, 900]])
+
+
+@image_comparison(['zoom_and_clip_upper_origin.png'],
+ remove_text=True, style='mpl20')
+def test_zoom_and_clip_upper_origin():
+ image = np.arange(100)
+ image = image.reshape((10, 10))
+
+ fig, ax = plt.subplots()
+ ax.imshow(image)
+ ax.set_ylim(2.0, -0.5)
+ ax.set_xlim(-0.5, 2.0)
+
+
+def test_nonuniformimage_setcmap():
+ ax = plt.gca()
+ im = NonUniformImage(ax)
+ im.set_cmap('Blues')
+
+
+def test_nonuniformimage_setnorm():
+ ax = plt.gca()
+ im = NonUniformImage(ax)
+ im.set_norm(plt.Normalize())
+
+
+def test_jpeg_2d():
+ # smoke test that mode-L pillow images work.
+ imd = np.ones((10, 10), dtype='uint8')
+ for i in range(10):
+ imd[i, :] = np.linspace(0.0, 1.0, 10) * 255
+ im = Image.new('L', (10, 10))
+ im.putdata(imd.flatten())
+ fig, ax = plt.subplots()
+ ax.imshow(im)
+
+
+def test_jpeg_alpha():
+ plt.figure(figsize=(1, 1), dpi=300)
+ # Create an image that is all black, with a gradient from 0-1 in
+ # the alpha channel from left to right.
+ im = np.zeros((300, 300, 4), dtype=float)
+ im[..., 3] = np.linspace(0.0, 1.0, 300)
+
+ plt.figimage(im)
+
+ buff = io.BytesIO()
+ plt.savefig(buff, facecolor="red", format='jpg', dpi=300)
+
+ buff.seek(0)
+ image = Image.open(buff)
+
+ # If this fails, there will be only one color (all black). If this
+ # is working, we should have all 256 shades of grey represented.
+ num_colors = len(image.getcolors(256))
+ assert 175 <= num_colors <= 230
+ # The fully transparent part should be red.
+ corner_pixel = image.getpixel((0, 0))
+ assert corner_pixel == (254, 0, 0)
+
+
+def test_axesimage_setdata():
+ ax = plt.gca()
+ im = AxesImage(ax)
+ z = np.arange(12, dtype=float).reshape((4, 3))
+ im.set_data(z)
+ z[0, 0] = 9.9
+ assert im._A[0, 0] == 0, 'value changed'
+
+
+def test_figureimage_setdata():
+ fig = plt.gcf()
+ im = FigureImage(fig)
+ z = np.arange(12, dtype=float).reshape((4, 3))
+ im.set_data(z)
+ z[0, 0] = 9.9
+ assert im._A[0, 0] == 0, 'value changed'
+
+
+@pytest.mark.parametrize(
+ "image_cls,x,y,a", [
+ (NonUniformImage,
+ np.arange(3.), np.arange(4.), np.arange(12.).reshape((4, 3))),
+ (PcolorImage,
+ np.arange(3.), np.arange(4.), np.arange(6.).reshape((3, 2))),
+ ])
+def test_setdata_xya(image_cls, x, y, a):
+ ax = plt.gca()
+ im = image_cls(ax)
+ im.set_data(x, y, a)
+ x[0] = y[0] = a[0, 0] = 9.9
+ assert im._A[0, 0] == im._Ax[0] == im._Ay[0] == 0, 'value changed'
+ im.set_data(x, y, a.reshape((*a.shape, -1))) # Just a smoketest.
+
+
+def test_minimized_rasterized():
+ # This ensures that the rasterized content in the colorbars is
+ # only as thick as the colorbar, and doesn't extend to other parts
+ # of the image. See #5814. While the original bug exists only
+ # in Postscript, the best way to detect it is to generate SVG
+ # and then parse the output to make sure the two colorbar images
+ # are the same size.
+ from xml.etree import ElementTree
+
+ np.random.seed(0)
+ data = np.random.rand(10, 10)
+
+ fig, ax = plt.subplots(1, 2)
+ p1 = ax[0].pcolormesh(data)
+ p2 = ax[1].pcolormesh(data)
+
+ plt.colorbar(p1, ax=ax[0])
+ plt.colorbar(p2, ax=ax[1])
+
+ buff = io.BytesIO()
+ plt.savefig(buff, format='svg')
+
+ buff = io.BytesIO(buff.getvalue())
+ tree = ElementTree.parse(buff)
+ width = None
+ for image in tree.iter('image'):
+ if width is None:
+ width = image['width']
+ else:
+ if image['width'] != width:
+ assert False
+
+
+def test_load_from_url():
+ path = Path(__file__).parent / "baseline_images/pngsuite/basn3p04.png"
+ url = ('file:'
+ + ('///' if sys.platform == 'win32' else '')
+ + path.resolve().as_posix())
+ with pytest.raises(ValueError, match="Please open the URL"):
+ plt.imread(url)
+ with urllib.request.urlopen(url) as file:
+ plt.imread(file)
+
+
+@image_comparison(['log_scale_image'], remove_text=True)
+def test_log_scale_image():
+ Z = np.zeros((10, 10))
+ Z[::2] = 1
+
+ fig, ax = plt.subplots()
+ ax.imshow(Z, extent=[1, 100, 1, 100], cmap='viridis', vmax=1, vmin=-1,
+ aspect='auto')
+ ax.set(yscale='log')
+
+
+@image_comparison(['rotate_image'], remove_text=True)
+def test_rotate_image():
+ delta = 0.25
+ x = y = np.arange(-3.0, 3.0, delta)
+ X, Y = np.meshgrid(x, y)
+ Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
+ Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
+ (2 * np.pi * 0.5 * 1.5))
+ Z = Z2 - Z1 # difference of Gaussians
+
+ fig, ax1 = plt.subplots(1, 1)
+ im1 = ax1.imshow(Z, interpolation='none', cmap='viridis',
+ origin='lower',
+ extent=[-2, 4, -3, 2], clip_on=True)
+
+ trans_data2 = Affine2D().rotate_deg(30) + ax1.transData
+ im1.set_transform(trans_data2)
+
+ # display intended extent of the image
+ x1, x2, y1, y2 = im1.get_extent()
+
+ ax1.plot([x1, x2, x2, x1, x1], [y1, y1, y2, y2, y1], "r--", lw=3,
+ transform=trans_data2)
+
+ ax1.set_xlim(2, 5)
+ ax1.set_ylim(0, 4)
+
+
+def test_image_preserve_size():
+ buff = io.BytesIO()
+
+ im = np.zeros((481, 321))
+ plt.imsave(buff, im, format="png")
+
+ buff.seek(0)
+ img = plt.imread(buff)
+
+ assert img.shape[:2] == im.shape
+
+
+def test_image_preserve_size2():
+ n = 7
+ data = np.identity(n, float)
+
+ fig = plt.figure(figsize=(n, n), frameon=False)
+ ax = fig.add_axes((0.0, 0.0, 1.0, 1.0))
+ ax.set_axis_off()
+ ax.imshow(data, interpolation='nearest', origin='lower', aspect='auto')
+ buff = io.BytesIO()
+ fig.savefig(buff, dpi=1)
+
+ buff.seek(0)
+ img = plt.imread(buff)
+
+ assert img.shape == (7, 7, 4)
+
+ assert_array_equal(np.asarray(img[:, :, 0], bool),
+ np.identity(n, bool)[::-1])
+
+
+@image_comparison(['mask_image_over_under.png'], remove_text=True, tol=1.0)
+def test_mask_image_over_under():
+
+ delta = 0.025
+ x = y = np.arange(-3.0, 3.0, delta)
+ X, Y = np.meshgrid(x, y)
+ Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
+ Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
+ (2 * np.pi * 0.5 * 1.5))
+ Z = 10*(Z2 - Z1) # difference of Gaussians
+
+ palette = plt.cm.gray.with_extremes(over='r', under='g', bad='b')
+ Zm = np.ma.masked_where(Z > 1.2, Z)
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+ im = ax1.imshow(Zm, interpolation='bilinear',
+ cmap=palette,
+ norm=colors.Normalize(vmin=-1.0, vmax=1.0, clip=False),
+ origin='lower', extent=[-3, 3, -3, 3])
+ ax1.set_title('Green=low, Red=high, Blue=bad')
+ fig.colorbar(im, extend='both', orientation='horizontal',
+ ax=ax1, aspect=10)
+
+ im = ax2.imshow(Zm, interpolation='nearest',
+ cmap=palette,
+ norm=colors.BoundaryNorm([-1, -0.5, -0.2, 0, 0.2, 0.5, 1],
+ ncolors=256, clip=False),
+ origin='lower', extent=[-3, 3, -3, 3])
+ ax2.set_title('With BoundaryNorm')
+ fig.colorbar(im, extend='both', spacing='proportional',
+ orientation='horizontal', ax=ax2, aspect=10)
+
+
+@image_comparison(['mask_image'], remove_text=True)
+def test_mask_image():
+ # Test mask image two ways: Using nans and using a masked array.
+
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+
+ A = np.ones((5, 5))
+ A[1:2, 1:2] = np.nan
+
+ ax1.imshow(A, interpolation='nearest')
+
+ A = np.zeros((5, 5), dtype=bool)
+ A[1:2, 1:2] = True
+ A = np.ma.masked_array(np.ones((5, 5), dtype=np.uint16), A)
+
+ ax2.imshow(A, interpolation='nearest')
+
+
+def test_mask_image_all():
+ # Test behavior with an image that is entirely masked does not warn
+ data = np.full((2, 2), np.nan)
+ fig, ax = plt.subplots()
+ ax.imshow(data)
+ fig.canvas.draw_idle() # would emit a warning
+
+
+@image_comparison(['imshow_endianess.png'], remove_text=True)
+def test_imshow_endianess():
+ x = np.arange(10)
+ X, Y = np.meshgrid(x, x)
+ Z = np.hypot(X - 5, Y - 5)
+
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+
+ kwargs = dict(origin="lower", interpolation='nearest', cmap='viridis')
+
+ ax1.imshow(Z.astype('f8'), **kwargs)
+
+
+@image_comparison(['imshow_masked_interpolation'],
+ tol=0 if platform.machine() == 'x86_64' else 0.01,
+ remove_text=True, style='mpl20')
+def test_imshow_masked_interpolation():
+
+ cmap = mpl.colormaps['viridis'].with_extremes(over='r', under='b', bad='k')
+
+ N = 20
+ n = colors.Normalize(vmin=0, vmax=N*N-1)
+
+ data = np.arange(N*N, dtype=float).reshape(N, N)
+
+ data[5, 5] = -1
+ # This will cause crazy ringing for the higher-order
+ # interpolations
+ data[15, 5] = 1e5
+
+ # data[3, 3] = np.nan
+
+ data[15, 15] = np.inf
+
+ mask = np.zeros_like(data).astype('bool')
+ mask[5, 15] = True
+
+ data = np.ma.masked_array(data, mask)
+
+ fig, ax_grid = plt.subplots(3, 6)
+ interps = sorted(mimage._interpd_)
+ interps.remove('auto')
+ interps.remove('antialiased')
+
+ for interp, ax in zip(interps, ax_grid.ravel()):
+ ax.set_title(interp)
+ ax.imshow(data, norm=n, cmap=cmap, interpolation=interp)
+ ax.axis('off')
+
+
+def test_imshow_no_warn_invalid():
+ plt.imshow([[1, 2], [3, np.nan]]) # Check that no warning is emitted.
+
+
+@pytest.mark.parametrize(
+ 'dtype', [np.dtype(s) for s in 'u2 u4 i2 i4 i8 f4 f8'.split()])
+def test_imshow_clips_rgb_to_valid_range(dtype):
+ arr = np.arange(300, dtype=dtype).reshape((10, 10, 3))
+ if dtype.kind != 'u':
+ arr -= 10
+ too_low = arr < 0
+ too_high = arr > 255
+ if dtype.kind == 'f':
+ arr = arr / 255
+ _, ax = plt.subplots()
+ out = ax.imshow(arr).get_array()
+ assert (out[too_low] == 0).all()
+ if dtype.kind == 'f':
+ assert (out[too_high] == 1).all()
+ assert out.dtype.kind == 'f'
+ else:
+ assert (out[too_high] == 255).all()
+ assert out.dtype == np.uint8
+
+
+@image_comparison(['imshow_flatfield.png'], remove_text=True, style='mpl20')
+def test_imshow_flatfield():
+ fig, ax = plt.subplots()
+ im = ax.imshow(np.ones((5, 5)), interpolation='nearest')
+ im.set_clim(.5, 1.5)
+
+
+@image_comparison(['imshow_bignumbers.png'], remove_text=True, style='mpl20')
+def test_imshow_bignumbers():
+ rcParams['image.interpolation'] = 'nearest'
+ # putting a big number in an array of integers shouldn't
+ # ruin the dynamic range of the resolved bits.
+ fig, ax = plt.subplots()
+ img = np.array([[1, 2, 1e12], [3, 1, 4]], dtype=np.uint64)
+ pc = ax.imshow(img)
+ pc.set_clim(0, 5)
+
+
+@image_comparison(['imshow_bignumbers_real.png'],
+ remove_text=True, style='mpl20')
+def test_imshow_bignumbers_real():
+ rcParams['image.interpolation'] = 'nearest'
+ # putting a big number in an array of integers shouldn't
+ # ruin the dynamic range of the resolved bits.
+ fig, ax = plt.subplots()
+ img = np.array([[2., 1., 1.e22], [4., 1., 3.]])
+ pc = ax.imshow(img)
+ pc.set_clim(0, 5)
+
+
+@pytest.mark.parametrize(
+ "make_norm",
+ [colors.Normalize,
+ colors.LogNorm,
+ lambda: colors.SymLogNorm(1),
+ lambda: colors.PowerNorm(1)])
+def test_empty_imshow(make_norm):
+ fig, ax = plt.subplots()
+ with pytest.warns(UserWarning,
+ match="Attempting to set identical low and high xlims"):
+ im = ax.imshow([[]], norm=make_norm())
+ im.set_extent([-5, 5, -5, 5])
+ fig.canvas.draw()
+
+ with pytest.raises(RuntimeError):
+ im.make_image(fig.canvas.get_renderer())
+
+
+def test_imshow_float16():
+ fig, ax = plt.subplots()
+ ax.imshow(np.zeros((3, 3), dtype=np.float16))
+ # Ensure that drawing doesn't cause crash.
+ fig.canvas.draw()
+
+
+def test_imshow_float128():
+ fig, ax = plt.subplots()
+ ax.imshow(np.zeros((3, 3), dtype=np.longdouble))
+ with (ExitStack() if np.can_cast(np.longdouble, np.float64, "equiv")
+ else pytest.warns(UserWarning)):
+ # Ensure that drawing doesn't cause crash.
+ fig.canvas.draw()
+
+
+def test_imshow_bool():
+ fig, ax = plt.subplots()
+ ax.imshow(np.array([[True, False], [False, True]], dtype=bool))
+
+
+def test_full_invalid():
+ fig, ax = plt.subplots()
+ ax.imshow(np.full((10, 10), np.nan))
+
+ fig.canvas.draw()
+
+
+@pytest.mark.parametrize("fmt,counted",
+ [("ps", b" colorimage"), ("svg", b" 1e20
+ data = np.full((5, 5), x, dtype=np.float64)
+ data[0:2, :] = 1E20
+
+ ax = fig_test.subplots()
+ ax.imshow(data, norm=colors.LogNorm(vmin=1, vmax=data.max()),
+ interpolation='nearest', cmap='viridis')
+
+ data = np.full((5, 5), x, dtype=np.float64)
+ data[0:2, :] = 1000
+
+ ax = fig_ref.subplots()
+ cmap = mpl.colormaps['viridis'].with_extremes(under='w')
+ ax.imshow(data, norm=colors.Normalize(vmin=1, vmax=data.max()),
+ interpolation='nearest', cmap=cmap)
+
+
+@check_figures_equal(extensions=['png'])
+def test_spy_box(fig_test, fig_ref):
+ # setting up reference and test
+ ax_test = fig_test.subplots(1, 3)
+ ax_ref = fig_ref.subplots(1, 3)
+
+ plot_data = (
+ [[1, 1], [1, 1]],
+ [[0, 0], [0, 0]],
+ [[0, 1], [1, 0]],
+ )
+ plot_titles = ["ones", "zeros", "mixed"]
+
+ for i, (z, title) in enumerate(zip(plot_data, plot_titles)):
+ ax_test[i].set_title(title)
+ ax_test[i].spy(z)
+ ax_ref[i].set_title(title)
+ ax_ref[i].imshow(z, interpolation='nearest',
+ aspect='equal', origin='upper', cmap='Greys',
+ vmin=0, vmax=1)
+ ax_ref[i].set_xlim(-0.5, 1.5)
+ ax_ref[i].set_ylim(1.5, -0.5)
+ ax_ref[i].xaxis.tick_top()
+ ax_ref[i].title.set_y(1.05)
+ ax_ref[i].xaxis.set_ticks_position('both')
+ ax_ref[i].xaxis.set_major_locator(
+ mticker.MaxNLocator(nbins=9, steps=[1, 2, 5, 10], integer=True)
+ )
+ ax_ref[i].yaxis.set_major_locator(
+ mticker.MaxNLocator(nbins=9, steps=[1, 2, 5, 10], integer=True)
+ )
+
+
+@image_comparison(["nonuniform_and_pcolor.png"], style="mpl20")
+def test_nonuniform_and_pcolor():
+ axs = plt.figure(figsize=(3, 3)).subplots(3, sharex=True, sharey=True)
+ for ax, interpolation in zip(axs, ["nearest", "bilinear"]):
+ im = NonUniformImage(ax, interpolation=interpolation)
+ im.set_data(np.arange(3) ** 2, np.arange(3) ** 2,
+ np.arange(9).reshape((3, 3)))
+ ax.add_image(im)
+ axs[2].pcolorfast( # PcolorImage
+ np.arange(4) ** 2, np.arange(4) ** 2, np.arange(9).reshape((3, 3)))
+ for ax in axs:
+ ax.set_axis_off()
+ # NonUniformImage "leaks" out of extents, not PColorImage.
+ ax.set(xlim=(0, 10))
+
+
+@image_comparison(["nonuniform_logscale.png"], style="mpl20")
+def test_nonuniform_logscale():
+ _, axs = plt.subplots(ncols=3, nrows=1)
+
+ for i in range(3):
+ ax = axs[i]
+ im = NonUniformImage(ax)
+ im.set_data(np.arange(1, 4) ** 2, np.arange(1, 4) ** 2,
+ np.arange(9).reshape((3, 3)))
+ ax.set_xlim(1, 16)
+ ax.set_ylim(1, 16)
+ ax.set_box_aspect(1)
+ if i == 1:
+ ax.set_xscale("log", base=2)
+ ax.set_yscale("log", base=2)
+ if i == 2:
+ ax.set_xscale("log", base=4)
+ ax.set_yscale("log", base=4)
+ ax.add_image(im)
+
+
+@image_comparison(['rgba_antialias.png'], style='mpl20', remove_text=True, tol=0.02)
+def test_rgba_antialias():
+ fig, axs = plt.subplots(2, 2, figsize=(3.5, 3.5), sharex=False,
+ sharey=False, constrained_layout=True)
+ N = 250
+ aa = np.ones((N, N))
+ aa[::2, :] = -1
+
+ x = np.arange(N) / N - 0.5
+ y = np.arange(N) / N - 0.5
+
+ X, Y = np.meshgrid(x, y)
+ R = np.sqrt(X**2 + Y**2)
+ f0 = 10
+ k = 75
+ # aliased concentric circles
+ a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2))
+
+ # stripes on lhs
+ a[:int(N/2), :][R[:int(N/2), :] < 0.4] = -1
+ a[:int(N/2), :][R[:int(N/2), :] < 0.3] = 1
+ aa[:, int(N/2):] = a[:, int(N/2):]
+
+ # set some over/unders and NaNs
+ aa[20:50, 20:50] = np.nan
+ aa[70:90, 70:90] = 1e6
+ aa[70:90, 20:30] = -1e6
+ aa[70:90, 195:215] = 1e6
+ aa[20:30, 195:215] = -1e6
+
+ cmap = copy(plt.cm.RdBu_r)
+ cmap.set_over('yellow')
+ cmap.set_under('cyan')
+
+ axs = axs.flatten()
+ # zoom in
+ axs[0].imshow(aa, interpolation='nearest', cmap=cmap, vmin=-1.2, vmax=1.2)
+ axs[0].set_xlim([N/2-25, N/2+25])
+ axs[0].set_ylim([N/2+50, N/2-10])
+
+ # no anti-alias
+ axs[1].imshow(aa, interpolation='nearest', cmap=cmap, vmin=-1.2, vmax=1.2)
+
+ # data antialias: Note no purples, and white in circle. Note
+ # that alternating red and blue stripes become white.
+ axs[2].imshow(aa, interpolation='auto', interpolation_stage='data',
+ cmap=cmap, vmin=-1.2, vmax=1.2)
+
+ # rgba antialias: Note purples at boundary with circle. Note that
+ # alternating red and blue stripes become purple
+ axs[3].imshow(aa, interpolation='auto', interpolation_stage='rgba',
+ cmap=cmap, vmin=-1.2, vmax=1.2)
+
+
+@check_figures_equal(extensions=('png', ))
+def test_upsample_interpolation_stage(fig_test, fig_ref):
+ """
+ Show that interpolation_stage='auto' gives the same as 'data'
+ for upsampling.
+ """
+ # Fixing random state for reproducibility. This non-standard seed
+ # gives red splotches for 'rgba'.
+ np.random.seed(19680801+9)
+
+ grid = np.random.rand(4, 4)
+ ax = fig_ref.subplots()
+ ax.imshow(grid, interpolation='bilinear', cmap='viridis',
+ interpolation_stage='data')
+
+ ax = fig_test.subplots()
+ ax.imshow(grid, interpolation='bilinear', cmap='viridis',
+ interpolation_stage='auto')
+
+
+@check_figures_equal(extensions=('png', ))
+def test_downsample_interpolation_stage(fig_test, fig_ref):
+ """
+ Show that interpolation_stage='auto' gives the same as 'rgba'
+ for downsampling.
+ """
+ # Fixing random state for reproducibility
+ np.random.seed(19680801)
+
+ grid = np.random.rand(4000, 4000)
+ ax = fig_ref.subplots()
+ ax.imshow(grid, interpolation='auto', cmap='viridis',
+ interpolation_stage='rgba')
+
+ ax = fig_test.subplots()
+ ax.imshow(grid, interpolation='auto', cmap='viridis',
+ interpolation_stage='auto')
+
+
+def test_rc_interpolation_stage():
+ for val in ["data", "rgba"]:
+ with mpl.rc_context({"image.interpolation_stage": val}):
+ assert plt.imshow([[1, 2]]).get_interpolation_stage() == val
+ for val in ["DATA", "foo", None]:
+ with pytest.raises(ValueError):
+ mpl.rcParams["image.interpolation_stage"] = val
+
+
+# We check for the warning with a draw() in the test, but we also need to
+# filter the warning as it is emitted by the figure test decorator
+@pytest.mark.filterwarnings(r'ignore:Data with more than .* '
+ 'cannot be accurately displayed')
+@pytest.mark.parametrize('origin', ['upper', 'lower'])
+@pytest.mark.parametrize(
+ 'dim, size, msg', [['row', 2**23, r'2\*\*23 columns'],
+ ['col', 2**24, r'2\*\*24 rows']])
+@check_figures_equal(extensions=('png', ))
+def test_large_image(fig_test, fig_ref, dim, size, msg, origin):
+ # Check that Matplotlib downsamples images that are too big for AGG
+ # See issue #19276. Currently the fix only works for png output but not
+ # pdf or svg output.
+ ax_test = fig_test.subplots()
+ ax_ref = fig_ref.subplots()
+
+ array = np.zeros((1, size + 2))
+ array[:, array.size // 2:] = 1
+ if dim == 'col':
+ array = array.T
+ im = ax_test.imshow(array, vmin=0, vmax=1,
+ aspect='auto', extent=(0, 1, 0, 1),
+ interpolation='none',
+ origin=origin)
+
+ with pytest.warns(UserWarning,
+ match=f'Data with more than {msg} cannot be '
+ 'accurately displayed.'):
+ fig_test.canvas.draw()
+
+ array = np.zeros((1, 2))
+ array[:, 1] = 1
+ if dim == 'col':
+ array = array.T
+ im = ax_ref.imshow(array, vmin=0, vmax=1, aspect='auto',
+ extent=(0, 1, 0, 1),
+ interpolation='none',
+ origin=origin)
+
+
+@check_figures_equal(extensions=["png"])
+def test_str_norms(fig_test, fig_ref):
+ t = np.random.rand(10, 10) * .8 + .1 # between 0 and 1
+ axts = fig_test.subplots(1, 5)
+ axts[0].imshow(t, norm="log")
+ axts[1].imshow(t, norm="log", vmin=.2)
+ axts[2].imshow(t, norm="symlog")
+ axts[3].imshow(t, norm="symlog", vmin=.3, vmax=.7)
+ axts[4].imshow(t, norm="logit", vmin=.3, vmax=.7)
+ axrs = fig_ref.subplots(1, 5)
+ axrs[0].imshow(t, norm=colors.LogNorm())
+ axrs[1].imshow(t, norm=colors.LogNorm(vmin=.2))
+ # same linthresh as SymmetricalLogScale's default.
+ axrs[2].imshow(t, norm=colors.SymLogNorm(linthresh=2))
+ axrs[3].imshow(t, norm=colors.SymLogNorm(linthresh=2, vmin=.3, vmax=.7))
+ axrs[4].imshow(t, norm="logit", clim=(.3, .7))
+
+ assert type(axts[0].images[0].norm) is colors.LogNorm # Exactly that class
+ with pytest.raises(ValueError):
+ axts[0].imshow(t, norm="foobar")
+
+
+def test__resample_valid_output():
+ resample = functools.partial(mpl._image.resample, transform=Affine2D())
+ with pytest.raises(TypeError, match="incompatible function arguments"):
+ resample(np.zeros((9, 9)), None)
+ with pytest.raises(ValueError, match="different dimensionalities"):
+ resample(np.zeros((9, 9)), np.zeros((9, 9, 4)))
+ with pytest.raises(ValueError, match="different dimensionalities"):
+ resample(np.zeros((9, 9, 4)), np.zeros((9, 9)))
+ with pytest.raises(ValueError, match="3D input array must be RGBA"):
+ resample(np.zeros((9, 9, 3)), np.zeros((9, 9, 4)))
+ with pytest.raises(ValueError, match="3D output array must be RGBA"):
+ resample(np.zeros((9, 9, 4)), np.zeros((9, 9, 3)))
+ with pytest.raises(ValueError, match="mismatched types"):
+ resample(np.zeros((9, 9), np.uint8), np.zeros((9, 9)))
+ with pytest.raises(ValueError, match="must be C-contiguous"):
+ resample(np.zeros((9, 9)), np.zeros((9, 9)).T)
+
+ out = np.zeros((9, 9))
+ out.flags.writeable = False
+ with pytest.raises(ValueError, match="Output array must be writeable"):
+ resample(np.zeros((9, 9)), out)
+
+
+def test_axesimage_get_shape():
+ # generate dummy image to test get_shape method
+ ax = plt.gca()
+ im = AxesImage(ax)
+ with pytest.raises(RuntimeError, match="You must first set the image array"):
+ im.get_shape()
+ z = np.arange(12, dtype=float).reshape((4, 3))
+ im.set_data(z)
+ assert im.get_shape() == (4, 3)
+ assert im.get_size() == im.get_shape()
+
+
+def test_non_transdata_image_does_not_touch_aspect():
+ ax = plt.figure().add_subplot()
+ im = np.arange(4).reshape((2, 2))
+ ax.imshow(im, transform=ax.transAxes)
+ assert ax.get_aspect() == "auto"
+ ax.imshow(im, transform=Affine2D().scale(2) + ax.transData)
+ assert ax.get_aspect() == 1
+ ax.imshow(im, transform=ax.transAxes, aspect=2)
+ assert ax.get_aspect() == 2
+
+
+@image_comparison(
+ ['downsampling.png'], style='mpl20', remove_text=True, tol=0.09)
+def test_downsampling():
+ N = 450
+ x = np.arange(N) / N - 0.5
+ y = np.arange(N) / N - 0.5
+ aa = np.ones((N, N))
+ aa[::2, :] = -1
+
+ X, Y = np.meshgrid(x, y)
+ R = np.sqrt(X**2 + Y**2)
+ f0 = 5
+ k = 100
+ a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2))
+ # make the left hand side of this
+ a[:int(N / 2), :][R[:int(N / 2), :] < 0.4] = -1
+ a[:int(N / 2), :][R[:int(N / 2), :] < 0.3] = 1
+ aa[:, int(N / 3):] = a[:, int(N / 3):]
+ a = aa
+
+ fig, axs = plt.subplots(2, 3, figsize=(7, 6), layout='compressed')
+ axs[0, 0].imshow(a, interpolation='nearest', interpolation_stage='rgba',
+ cmap='RdBu_r')
+ axs[0, 0].set_xlim(125, 175)
+ axs[0, 0].set_ylim(250, 200)
+ axs[0, 0].set_title('Zoom')
+
+ for ax, interp, space in zip(axs.flat[1:], ['nearest', 'nearest', 'hanning',
+ 'hanning', 'auto'],
+ ['data', 'rgba', 'data', 'rgba', 'auto']):
+ ax.imshow(a, interpolation=interp, interpolation_stage=space,
+ cmap='RdBu_r')
+ ax.set_title(f"interpolation='{interp}'\nspace='{space}'")
+
+
+@image_comparison(
+ ['downsampling_speckle.png'], style='mpl20', remove_text=True, tol=0.09)
+def test_downsampling_speckle():
+ fig, axs = plt.subplots(1, 2, figsize=(5, 2.7), sharex=True, sharey=True,
+ layout="compressed")
+ axs = axs.flatten()
+ img = ((np.arange(1024).reshape(-1, 1) * np.ones(720)) // 50).T
+
+ cm = plt.get_cmap("viridis")
+ cm.set_over("m")
+ norm = colors.LogNorm(vmin=3, vmax=11)
+
+ # old default cannot be tested because it creates over/under speckles
+ # in the following that are machine dependent.
+
+ axs[0].set_title("interpolation='auto', stage='rgba'")
+ axs[0].imshow(np.triu(img), cmap=cm, norm=norm, interpolation_stage='rgba')
+
+ # Should be same as previous
+ axs[1].set_title("interpolation='auto', stage='auto'")
+ axs[1].imshow(np.triu(img), cmap=cm, norm=norm)
+
+
+@image_comparison(
+ ['upsampling.png'], style='mpl20', remove_text=True)
+def test_upsampling():
+
+ np.random.seed(19680801+9) # need this seed to get yellow next to blue
+ a = np.random.rand(4, 4)
+
+ fig, axs = plt.subplots(1, 3, figsize=(6.5, 3), layout='compressed')
+ im = axs[0].imshow(a, cmap='viridis')
+ axs[0].set_title(
+ "interpolation='auto'\nstage='antialaised'\n(default for upsampling)")
+
+ # probably what people want:
+ axs[1].imshow(a, cmap='viridis', interpolation='sinc')
+ axs[1].set_title(
+ "interpolation='sinc'\nstage='auto'\n(default for upsampling)")
+
+ # probably not what people want:
+ axs[2].imshow(a, cmap='viridis', interpolation='sinc', interpolation_stage='rgba')
+ axs[2].set_title("interpolation='sinc'\nstage='rgba'")
+ fig.colorbar(im, ax=axs, shrink=0.7, extend='both')
+
+
+@pytest.mark.parametrize(
+ 'dtype',
+ ('float64', 'float32', 'int16', 'uint16', 'int8', 'uint8'),
+)
+@pytest.mark.parametrize('ndim', (2, 3))
+def test_resample_dtypes(dtype, ndim):
+ # Issue 28448, incorrect dtype comparisons in C++ image_resample can raise
+ # ValueError: arrays must be of dtype byte, short, float32 or float64
+ rng = np.random.default_rng(4181)
+ shape = (2, 2) if ndim == 2 else (2, 2, 3)
+ data = rng.uniform(size=shape).astype(np.dtype(dtype, copy=True))
+ fig, ax = plt.subplots()
+ axes_image = ax.imshow(data)
+ # Before fix the following raises ValueError for some dtypes.
+ axes_image.make_image(None)[0]
+
+
+@pytest.mark.parametrize('intp_stage', ('data', 'rgba'))
+@check_figures_equal()
+def test_interpolation_stage_rgba_respects_alpha_param(fig_test, fig_ref, intp_stage):
+ axs_tst = fig_test.subplots(2, 3)
+ axs_ref = fig_ref.subplots(2, 3)
+ ny, nx = 3, 3
+ scalar_alpha = 0.5
+ array_alpha = np.random.rand(ny, nx)
+
+ # When the image does not have an alpha channel, alpha should be specified
+ # by the user or default to 1.0
+ im_rgb = np.random.rand(ny, nx, 3)
+ im_concat_default_a = np.ones((ny, nx, 1)) # alpha defaults to 1.0
+ im_rgba = np.concatenate( # combine rgb channels with array alpha
+ (im_rgb, array_alpha.reshape((ny, nx, 1))), axis=-1
+ )
+ axs_tst[0][0].imshow(im_rgb)
+ axs_ref[0][0].imshow(np.concatenate((im_rgb, im_concat_default_a), axis=-1))
+ axs_tst[0][1].imshow(im_rgb, interpolation_stage=intp_stage, alpha=scalar_alpha)
+ axs_ref[0][1].imshow(
+ np.concatenate( # combine rgb channels with broadcasted scalar alpha
+ (im_rgb, scalar_alpha * im_concat_default_a), axis=-1
+ ), interpolation_stage=intp_stage
+ )
+ axs_tst[0][2].imshow(im_rgb, interpolation_stage=intp_stage, alpha=array_alpha)
+ axs_ref[0][2].imshow(im_rgba, interpolation_stage=intp_stage)
+
+ # When the image already has an alpha channel, multiply it by the
+ # scalar alpha param, or replace it by the array alpha param
+ axs_tst[1][0].imshow(im_rgba)
+ axs_ref[1][0].imshow(im_rgb, alpha=array_alpha)
+ axs_tst[1][1].imshow(im_rgba, interpolation_stage=intp_stage, alpha=scalar_alpha)
+ axs_ref[1][1].imshow(
+ np.concatenate( # combine rgb channels with scaled array alpha
+ (im_rgb, scalar_alpha * array_alpha.reshape((ny, nx, 1))), axis=-1
+ ), interpolation_stage=intp_stage
+ )
+ new_array_alpha = np.random.rand(ny, nx)
+ axs_tst[1][2].imshow(im_rgba, interpolation_stage=intp_stage, alpha=new_array_alpha)
+ axs_ref[1][2].imshow(
+ np.concatenate( # combine rgb channels with new array alpha
+ (im_rgb, new_array_alpha.reshape((ny, nx, 1))), axis=-1
+ ), interpolation_stage=intp_stage
+ )
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_legend.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_legend.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb3bfca7c293885ad79e052272f0000a8c89c528
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_legend.py
@@ -0,0 +1,1486 @@
+import collections
+import io
+import itertools
+import platform
+import time
+from unittest import mock
+import warnings
+
+import numpy as np
+from numpy.testing import assert_allclose
+import pytest
+
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+from matplotlib.testing._markers import needs_usetex
+import matplotlib.pyplot as plt
+import matplotlib as mpl
+import matplotlib.patches as mpatches
+import matplotlib.transforms as mtransforms
+import matplotlib.collections as mcollections
+import matplotlib.lines as mlines
+from matplotlib.legend_handler import HandlerTuple
+import matplotlib.legend as mlegend
+from matplotlib import rc_context
+from matplotlib.font_manager import FontProperties
+
+
+def test_legend_ordereddict():
+ # smoketest that ordereddict inputs work...
+
+ X = np.random.randn(10)
+ Y = np.random.randn(10)
+ labels = ['a'] * 5 + ['b'] * 5
+ colors = ['r'] * 5 + ['g'] * 5
+
+ fig, ax = plt.subplots()
+ for x, y, label, color in zip(X, Y, labels, colors):
+ ax.scatter(x, y, label=label, c=color)
+
+ handles, labels = ax.get_legend_handles_labels()
+ legend = collections.OrderedDict(zip(labels, handles))
+ ax.legend(legend.values(), legend.keys(),
+ loc='center left', bbox_to_anchor=(1, .5))
+
+
+def test_legend_generator():
+ # smoketest that generator inputs work
+ fig, ax = plt.subplots()
+ ax.plot([0, 1])
+ ax.plot([0, 2])
+
+ handles = (line for line in ax.get_lines())
+ labels = (label for label in ['spam', 'eggs'])
+
+ ax.legend(handles, labels, loc='upper left')
+
+
+@image_comparison(['legend_auto1.png'], remove_text=True)
+def test_legend_auto1():
+ """Test automatic legend placement"""
+ fig, ax = plt.subplots()
+ x = np.arange(100)
+ ax.plot(x, 50 - x, 'o', label='y=1')
+ ax.plot(x, x - 50, 'o', label='y=-1')
+ ax.legend(loc='best')
+
+
+@image_comparison(['legend_auto2.png'], remove_text=True)
+def test_legend_auto2():
+ """Test automatic legend placement"""
+ fig, ax = plt.subplots()
+ x = np.arange(100)
+ b1 = ax.bar(x, x, align='edge', color='m')
+ b2 = ax.bar(x, x[::-1], align='edge', color='g')
+ ax.legend([b1[0], b2[0]], ['up', 'down'], loc='best')
+
+
+@image_comparison(['legend_auto3.png'])
+def test_legend_auto3():
+ """Test automatic legend placement"""
+ fig, ax = plt.subplots()
+ x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5]
+ y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5]
+ ax.plot(x, y, 'o-', label='line')
+ ax.set_xlim(0.0, 1.0)
+ ax.set_ylim(0.0, 1.0)
+ ax.legend(loc='best')
+
+
+def test_legend_auto4():
+ """
+ Check that the legend location with automatic placement is the same,
+ whatever the histogram type is. Related to issue #9580.
+ """
+ # NB: barstacked is pointless with a single dataset.
+ fig, axs = plt.subplots(ncols=3, figsize=(6.4, 2.4))
+ leg_bboxes = []
+ for ax, ht in zip(axs.flat, ('bar', 'step', 'stepfilled')):
+ ax.set_title(ht)
+ # A high bar on the left but an even higher one on the right.
+ ax.hist([0] + 5*[9], bins=range(10), label="Legend", histtype=ht)
+ leg = ax.legend(loc="best")
+ fig.canvas.draw()
+ leg_bboxes.append(
+ leg.get_window_extent().transformed(ax.transAxes.inverted()))
+
+ # The histogram type "bar" is assumed to be the correct reference.
+ assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds)
+ assert_allclose(leg_bboxes[2].bounds, leg_bboxes[0].bounds)
+
+
+def test_legend_auto5():
+ """
+ Check that the automatic placement handle a rather complex
+ case with non rectangular patch. Related to issue #9580.
+ """
+ fig, axs = plt.subplots(ncols=2, figsize=(9.6, 4.8))
+
+ leg_bboxes = []
+ for ax, loc in zip(axs.flat, ("center", "best")):
+ # An Ellipse patch at the top, a U-shaped Polygon patch at the
+ # bottom and a ring-like Wedge patch: the correct placement of
+ # the legend should be in the center.
+ for _patch in [
+ mpatches.Ellipse(
+ xy=(0.5, 0.9), width=0.8, height=0.2, fc="C1"),
+ mpatches.Polygon(np.array([
+ [0, 1], [0, 0], [1, 0], [1, 1], [0.9, 1.0], [0.9, 0.1],
+ [0.1, 0.1], [0.1, 1.0], [0.1, 1.0]]), fc="C1"),
+ mpatches.Wedge((0.5, 0.5), 0.5, 0, 360, width=0.05, fc="C0")
+ ]:
+ ax.add_patch(_patch)
+
+ ax.plot([0.1, 0.9], [0.9, 0.9], label="A segment") # sthg to label
+
+ leg = ax.legend(loc=loc)
+ fig.canvas.draw()
+ leg_bboxes.append(
+ leg.get_window_extent().transformed(ax.transAxes.inverted()))
+
+ assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds)
+
+
+@image_comparison(['legend_various_labels.png'], remove_text=True)
+def test_various_labels():
+ # tests all sorts of label types
+ fig = plt.figure()
+ ax = fig.add_subplot(121)
+ ax.plot(np.arange(4), 'o', label=1)
+ ax.plot(np.linspace(4, 4.1), 'o', label='Développés')
+ ax.plot(np.arange(4, 1, -1), 'o', label='__nolegend__')
+ ax.legend(numpoints=1, loc='best')
+
+
+@image_comparison(['legend_labels_first.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.013)
+def test_labels_first():
+ # test labels to left of markers
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), '-o', label=1)
+ ax.plot(np.ones(10)*5, ':x', label="x")
+ ax.plot(np.arange(20, 10, -1), 'd', label="diamond")
+ ax.legend(loc='best', markerfirst=False)
+
+
+@image_comparison(['legend_multiple_keys.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.013)
+def test_multiple_keys():
+ # test legend entries with multiple keys
+ fig, ax = plt.subplots()
+ p1, = ax.plot([1, 2, 3], '-o')
+ p2, = ax.plot([2, 3, 4], '-x')
+ p3, = ax.plot([3, 4, 5], '-d')
+ ax.legend([(p1, p2), (p2, p1), p3], ['two keys', 'pad=0', 'one key'],
+ numpoints=1,
+ handler_map={(p1, p2): HandlerTuple(ndivide=None),
+ (p2, p1): HandlerTuple(ndivide=None, pad=0)})
+
+
+@image_comparison(['rgba_alpha.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.03)
+def test_alpha_rgba():
+ fig, ax = plt.subplots()
+ ax.plot(range(10), lw=5)
+ leg = plt.legend(['Longlabel that will go away'], loc='center')
+ leg.legendPatch.set_facecolor([1, 0, 0, 0.5])
+
+
+@image_comparison(['rcparam_alpha.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.03)
+def test_alpha_rcparam():
+ fig, ax = plt.subplots()
+ ax.plot(range(10), lw=5)
+ with mpl.rc_context(rc={'legend.framealpha': .75}):
+ leg = plt.legend(['Longlabel that will go away'], loc='center')
+ # this alpha is going to be over-ridden by the rcparam with
+ # sets the alpha of the patch to be non-None which causes the alpha
+ # value of the face color to be discarded. This behavior may not be
+ # ideal, but it is what it is and we should keep track of it changing
+ leg.legendPatch.set_facecolor([1, 0, 0, 0.5])
+
+
+@image_comparison(['fancy.png'], remove_text=True, tol=0.05)
+def test_fancy():
+ # Tolerance caused by changing default shadow "shade" from 0.3 to 1 - 0.7 =
+ # 0.30000000000000004
+ # using subplot triggers some offsetbox functionality untested elsewhere
+ plt.subplot(121)
+ plt.plot([5] * 10, 'o--', label='XX')
+ plt.scatter(np.arange(10), np.arange(10, 0, -1), label='XX\nXX')
+ plt.errorbar(np.arange(10), np.arange(10), xerr=0.5,
+ yerr=0.5, label='XX')
+ plt.legend(loc="center left", bbox_to_anchor=[1.0, 0.5],
+ ncols=2, shadow=True, title="My legend", numpoints=1)
+
+
+@image_comparison(['framealpha'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.024)
+def test_framealpha():
+ x = np.linspace(1, 100, 100)
+ y = x
+ plt.plot(x, y, label='mylabel', lw=10)
+ plt.legend(framealpha=0.5)
+
+
+@image_comparison(['scatter_rc3.png', 'scatter_rc1.png'], remove_text=True)
+def test_rc():
+ # using subplot triggers some offsetbox functionality untested elsewhere
+ plt.figure()
+ ax = plt.subplot(121)
+ ax.scatter(np.arange(10), np.arange(10, 0, -1), label='three')
+ ax.legend(loc="center left", bbox_to_anchor=[1.0, 0.5],
+ title="My legend")
+
+ mpl.rcParams['legend.scatterpoints'] = 1
+ plt.figure()
+ ax = plt.subplot(121)
+ ax.scatter(np.arange(10), np.arange(10, 0, -1), label='one')
+ ax.legend(loc="center left", bbox_to_anchor=[1.0, 0.5],
+ title="My legend")
+
+
+@image_comparison(['legend_expand.png'], remove_text=True)
+def test_legend_expand():
+ """Test expand mode"""
+ legend_modes = [None, "expand"]
+ fig, axs = plt.subplots(len(legend_modes), 1)
+ x = np.arange(100)
+ for ax, mode in zip(axs, legend_modes):
+ ax.plot(x, 50 - x, 'o', label='y=1')
+ l1 = ax.legend(loc='upper left', mode=mode)
+ ax.add_artist(l1)
+ ax.plot(x, x - 50, 'o', label='y=-1')
+ l2 = ax.legend(loc='right', mode=mode)
+ ax.add_artist(l2)
+ ax.legend(loc='lower left', mode=mode, ncols=2)
+
+
+@image_comparison(['hatching'], remove_text=True, style='default')
+def test_hatching():
+ # Remove legend texts when this image is regenerated.
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['text.kerning_factor'] = 6
+
+ fig, ax = plt.subplots()
+
+ # Patches
+ patch = plt.Rectangle((0, 0), 0.3, 0.3, hatch='xx',
+ label='Patch\ndefault color\nfilled')
+ ax.add_patch(patch)
+ patch = plt.Rectangle((0.33, 0), 0.3, 0.3, hatch='||', edgecolor='C1',
+ label='Patch\nexplicit color\nfilled')
+ ax.add_patch(patch)
+ patch = plt.Rectangle((0, 0.4), 0.3, 0.3, hatch='xx', fill=False,
+ label='Patch\ndefault color\nunfilled')
+ ax.add_patch(patch)
+ patch = plt.Rectangle((0.33, 0.4), 0.3, 0.3, hatch='||', fill=False,
+ edgecolor='C1',
+ label='Patch\nexplicit color\nunfilled')
+ ax.add_patch(patch)
+
+ # Paths
+ ax.fill_between([0, .15, .3], [.8, .8, .8], [.9, 1.0, .9],
+ hatch='+', label='Path\ndefault color')
+ ax.fill_between([.33, .48, .63], [.8, .8, .8], [.9, 1.0, .9],
+ hatch='+', edgecolor='C2', label='Path\nexplicit color')
+
+ ax.set_xlim(-0.01, 1.1)
+ ax.set_ylim(-0.01, 1.1)
+ ax.legend(handlelength=4, handleheight=4)
+
+
+def test_legend_remove():
+ fig, ax = plt.subplots()
+ lines = ax.plot(range(10))
+ leg = fig.legend(lines, "test")
+ leg.remove()
+ assert fig.legends == []
+ leg = ax.legend("test")
+ leg.remove()
+ assert ax.get_legend() is None
+
+
+def test_reverse_legend_handles_and_labels():
+ """Check that the legend handles and labels are reversed."""
+ fig, ax = plt.subplots()
+ x = 1
+ y = 1
+ labels = ["First label", "Second label", "Third label"]
+ markers = ['.', ',', 'o']
+
+ ax.plot(x, y, markers[0], label=labels[0])
+ ax.plot(x, y, markers[1], label=labels[1])
+ ax.plot(x, y, markers[2], label=labels[2])
+ leg = ax.legend(reverse=True)
+ actual_labels = [t.get_text() for t in leg.get_texts()]
+ actual_markers = [h.get_marker() for h in leg.legend_handles]
+ assert actual_labels == list(reversed(labels))
+ assert actual_markers == list(reversed(markers))
+
+
+@check_figures_equal(extensions=["png"])
+def test_reverse_legend_display(fig_test, fig_ref):
+ """Check that the rendered legend entries are reversed"""
+ ax = fig_test.subplots()
+ ax.plot([1], 'ro', label="first")
+ ax.plot([2], 'bx', label="second")
+ ax.legend(reverse=True)
+
+ ax = fig_ref.subplots()
+ ax.plot([2], 'bx', label="second")
+ ax.plot([1], 'ro', label="first")
+ ax.legend()
+
+
+class TestLegendFunction:
+ # Tests the legend function on the Axes and pyplot.
+ def test_legend_no_args(self):
+ lines = plt.plot(range(10), label='hello world')
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ plt.legend()
+ Legend.assert_called_with(plt.gca(), lines, ['hello world'])
+
+ def test_legend_positional_handles_labels(self):
+ lines = plt.plot(range(10))
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ plt.legend(lines, ['hello world'])
+ Legend.assert_called_with(plt.gca(), lines, ['hello world'])
+
+ def test_legend_positional_handles_only(self):
+ lines = plt.plot(range(10))
+ with pytest.raises(TypeError, match='but found an Artist'):
+ # a single arg is interpreted as labels
+ # it's a common error to just pass handles
+ plt.legend(lines)
+
+ def test_legend_positional_labels_only(self):
+ lines = plt.plot(range(10), label='hello world')
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ plt.legend(['foobar'])
+ Legend.assert_called_with(plt.gca(), lines, ['foobar'])
+
+ def test_legend_three_args(self):
+ lines = plt.plot(range(10), label='hello world')
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ plt.legend(lines, ['foobar'], loc='right')
+ Legend.assert_called_with(plt.gca(), lines, ['foobar'], loc='right')
+
+ def test_legend_handler_map(self):
+ lines = plt.plot(range(10), label='hello world')
+ with mock.patch('matplotlib.legend.'
+ '_get_legend_handles_labels') as handles_labels:
+ handles_labels.return_value = lines, ['hello world']
+ plt.legend(handler_map={'1': 2})
+ handles_labels.assert_called_with([plt.gca()], {'1': 2})
+
+ def test_legend_kwargs_handles_only(self):
+ fig, ax = plt.subplots()
+ x = np.linspace(0, 1, 11)
+ ln1, = ax.plot(x, x, label='x')
+ ln2, = ax.plot(x, 2*x, label='2x')
+ ln3, = ax.plot(x, 3*x, label='3x')
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ ax.legend(handles=[ln3, ln2]) # reversed and not ln1
+ Legend.assert_called_with(ax, [ln3, ln2], ['3x', '2x'])
+
+ def test_legend_kwargs_labels_only(self):
+ fig, ax = plt.subplots()
+ x = np.linspace(0, 1, 11)
+ ln1, = ax.plot(x, x)
+ ln2, = ax.plot(x, 2*x)
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ ax.legend(labels=['x', '2x'])
+ Legend.assert_called_with(ax, [ln1, ln2], ['x', '2x'])
+
+ def test_legend_kwargs_handles_labels(self):
+ fig, ax = plt.subplots()
+ th = np.linspace(0, 2*np.pi, 1024)
+ lns, = ax.plot(th, np.sin(th), label='sin')
+ lnc, = ax.plot(th, np.cos(th), label='cos')
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ # labels of lns, lnc are overwritten with explicit ('a', 'b')
+ ax.legend(labels=('a', 'b'), handles=(lnc, lns))
+ Legend.assert_called_with(ax, (lnc, lns), ('a', 'b'))
+
+ def test_warn_mixed_args_and_kwargs(self):
+ fig, ax = plt.subplots()
+ th = np.linspace(0, 2*np.pi, 1024)
+ lns, = ax.plot(th, np.sin(th), label='sin')
+ lnc, = ax.plot(th, np.cos(th), label='cos')
+ with pytest.warns(DeprecationWarning) as record:
+ ax.legend((lnc, lns), labels=('a', 'b'))
+ assert len(record) == 1
+ assert str(record[0].message).startswith(
+ "You have mixed positional and keyword arguments, some input may "
+ "be discarded.")
+
+ def test_parasite(self):
+ from mpl_toolkits.axes_grid1 import host_subplot # type: ignore[import]
+
+ host = host_subplot(111)
+ par = host.twinx()
+
+ p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
+ p2, = par.plot([0, 1, 2], [0, 3, 2], label="Temperature")
+
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ plt.legend()
+ Legend.assert_called_with(host, [p1, p2], ['Density', 'Temperature'])
+
+
+class TestLegendFigureFunction:
+ # Tests the legend function for figure
+ def test_legend_handle_label(self):
+ fig, ax = plt.subplots()
+ lines = ax.plot(range(10))
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ fig.legend(lines, ['hello world'])
+ Legend.assert_called_with(fig, lines, ['hello world'],
+ bbox_transform=fig.transFigure)
+
+ def test_legend_no_args(self):
+ fig, ax = plt.subplots()
+ lines = ax.plot(range(10), label='hello world')
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ fig.legend()
+ Legend.assert_called_with(fig, lines, ['hello world'],
+ bbox_transform=fig.transFigure)
+
+ def test_legend_label_arg(self):
+ fig, ax = plt.subplots()
+ lines = ax.plot(range(10))
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ fig.legend(['foobar'])
+ Legend.assert_called_with(fig, lines, ['foobar'],
+ bbox_transform=fig.transFigure)
+
+ def test_legend_label_three_args(self):
+ fig, ax = plt.subplots()
+ lines = ax.plot(range(10))
+ with pytest.raises(TypeError, match="0-2"):
+ fig.legend(lines, ['foobar'], 'right')
+ with pytest.raises(TypeError, match="0-2"):
+ fig.legend(lines, ['foobar'], 'right', loc='left')
+
+ def test_legend_kw_args(self):
+ fig, axs = plt.subplots(1, 2)
+ lines = axs[0].plot(range(10))
+ lines2 = axs[1].plot(np.arange(10) * 2.)
+ with mock.patch('matplotlib.legend.Legend') as Legend:
+ fig.legend(loc='right', labels=('a', 'b'), handles=(lines, lines2))
+ Legend.assert_called_with(
+ fig, (lines, lines2), ('a', 'b'), loc='right',
+ bbox_transform=fig.transFigure)
+
+ def test_warn_args_kwargs(self):
+ fig, axs = plt.subplots(1, 2)
+ lines = axs[0].plot(range(10))
+ lines2 = axs[1].plot(np.arange(10) * 2.)
+ with pytest.warns(DeprecationWarning) as record:
+ fig.legend((lines, lines2), labels=('a', 'b'))
+ assert len(record) == 1
+ assert str(record[0].message).startswith(
+ "You have mixed positional and keyword arguments, some input may "
+ "be discarded.")
+
+
+def test_figure_legend_outside():
+ todos = ['upper ' + pos for pos in ['left', 'center', 'right']]
+ todos += ['lower ' + pos for pos in ['left', 'center', 'right']]
+ todos += ['left ' + pos for pos in ['lower', 'center', 'upper']]
+ todos += ['right ' + pos for pos in ['lower', 'center', 'upper']]
+
+ upperext = [20.347556, 27.722556, 790.583, 545.499]
+ lowerext = [20.347556, 71.056556, 790.583, 588.833]
+ leftext = [151.681556, 27.722556, 790.583, 588.833]
+ rightext = [20.347556, 27.722556, 659.249, 588.833]
+ axbb = [upperext, upperext, upperext,
+ lowerext, lowerext, lowerext,
+ leftext, leftext, leftext,
+ rightext, rightext, rightext]
+
+ legbb = [[10., 555., 133., 590.], # upper left
+ [338.5, 555., 461.5, 590.], # upper center
+ [667, 555., 790., 590.], # upper right
+ [10., 10., 133., 45.], # lower left
+ [338.5, 10., 461.5, 45.], # lower center
+ [667., 10., 790., 45.], # lower right
+ [10., 10., 133., 45.], # left lower
+ [10., 282.5, 133., 317.5], # left center
+ [10., 555., 133., 590.], # left upper
+ [667, 10., 790., 45.], # right lower
+ [667., 282.5, 790., 317.5], # right center
+ [667., 555., 790., 590.]] # right upper
+
+ for nn, todo in enumerate(todos):
+ print(todo)
+ fig, axs = plt.subplots(constrained_layout=True, dpi=100)
+ axs.plot(range(10), label='Boo1')
+ leg = fig.legend(loc='outside ' + todo)
+ fig.draw_without_rendering()
+
+ assert_allclose(axs.get_window_extent().extents,
+ axbb[nn])
+ assert_allclose(leg.get_window_extent().extents,
+ legbb[nn])
+
+
+@image_comparison(['legend_stackplot.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.031)
+def test_legend_stackplot():
+ """Test legend for PolyCollection using stackplot."""
+ # related to #1341, #1943, and PR #3303
+ fig, ax = plt.subplots()
+ x = np.linspace(0, 10, 10)
+ y1 = 1.0 * x
+ y2 = 2.0 * x + 1
+ y3 = 3.0 * x + 2
+ ax.stackplot(x, y1, y2, y3, labels=['y1', 'y2', 'y3'])
+ ax.set_xlim((0, 10))
+ ax.set_ylim((0, 70))
+ ax.legend(loc='best')
+
+
+def test_cross_figure_patch_legend():
+ fig, ax = plt.subplots()
+ fig2, ax2 = plt.subplots()
+
+ brs = ax.bar(range(3), range(3))
+ fig2.legend(brs, 'foo')
+
+
+def test_nanscatter():
+ fig, ax = plt.subplots()
+
+ h = ax.scatter([np.nan], [np.nan], marker="o",
+ facecolor="r", edgecolor="r", s=3)
+
+ ax.legend([h], ["scatter"])
+
+ fig, ax = plt.subplots()
+ for color in ['red', 'green', 'blue']:
+ n = 750
+ x, y = np.random.rand(2, n)
+ scale = 200.0 * np.random.rand(n)
+ ax.scatter(x, y, c=color, s=scale, label=color,
+ alpha=0.3, edgecolors='none')
+
+ ax.legend()
+ ax.grid(True)
+
+
+def test_legend_repeatcheckok():
+ fig, ax = plt.subplots()
+ ax.scatter(0.0, 1.0, color='k', marker='o', label='test')
+ ax.scatter(0.5, 0.0, color='r', marker='v', label='test')
+ ax.legend()
+ hand, lab = mlegend._get_legend_handles_labels([ax])
+ assert len(lab) == 2
+ fig, ax = plt.subplots()
+ ax.scatter(0.0, 1.0, color='k', marker='o', label='test')
+ ax.scatter(0.5, 0.0, color='k', marker='v', label='test')
+ ax.legend()
+ hand, lab = mlegend._get_legend_handles_labels([ax])
+ assert len(lab) == 2
+
+
+@image_comparison(['not_covering_scatter.png'])
+def test_not_covering_scatter():
+ colors = ['b', 'g', 'r']
+
+ for n in range(3):
+ plt.scatter([n], [n], color=colors[n])
+
+ plt.legend(['foo', 'foo', 'foo'], loc='best')
+ plt.gca().set_xlim(-0.5, 2.2)
+ plt.gca().set_ylim(-0.5, 2.2)
+
+
+@image_comparison(['not_covering_scatter_transform.png'])
+def test_not_covering_scatter_transform():
+ # Offsets point to top left, the default auto position
+ offset = mtransforms.Affine2D().translate(-20, 20)
+ x = np.linspace(0, 30, 1000)
+ plt.plot(x, x)
+
+ plt.scatter([20], [10], transform=offset + plt.gca().transData)
+
+ plt.legend(['foo', 'bar'], loc='best')
+
+
+def test_linecollection_scaled_dashes():
+ lines1 = [[(0, .5), (.5, 1)], [(.3, .6), (.2, .2)]]
+ lines2 = [[[0.7, .2], [.8, .4]], [[.5, .7], [.6, .1]]]
+ lines3 = [[[0.6, .2], [.8, .4]], [[.5, .7], [.1, .1]]]
+ lc1 = mcollections.LineCollection(lines1, linestyles="--", lw=3)
+ lc2 = mcollections.LineCollection(lines2, linestyles="-.")
+ lc3 = mcollections.LineCollection(lines3, linestyles=":", lw=.5)
+
+ fig, ax = plt.subplots()
+ ax.add_collection(lc1)
+ ax.add_collection(lc2)
+ ax.add_collection(lc3)
+
+ leg = ax.legend([lc1, lc2, lc3], ["line1", "line2", 'line 3'])
+ h1, h2, h3 = leg.legend_handles
+
+ for oh, lh in zip((lc1, lc2, lc3), (h1, h2, h3)):
+ assert oh.get_linestyles()[0] == lh._dash_pattern
+
+
+def test_handler_numpoints():
+ """Test legend handler with numpoints <= 1."""
+ # related to #6921 and PR #8478
+ fig, ax = plt.subplots()
+ ax.plot(range(5), label='test')
+ ax.legend(numpoints=0.5)
+
+
+def test_text_nohandler_warning():
+ """Test that Text artists with labels raise a warning"""
+ fig, ax = plt.subplots()
+ ax.plot([0], label="mock data")
+ ax.text(x=0, y=0, s="text", label="label")
+ with pytest.warns(UserWarning) as record:
+ ax.legend()
+ assert len(record) == 1
+
+ # this should _not_ warn:
+ f, ax = plt.subplots()
+ ax.pcolormesh(np.random.uniform(0, 1, (10, 10)))
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ ax.get_legend_handles_labels()
+
+
+def test_empty_bar_chart_with_legend():
+ """Test legend when bar chart is empty with a label."""
+ # related to issue #13003. Calling plt.legend() should not
+ # raise an IndexError.
+ plt.bar([], [], label='test')
+ plt.legend()
+
+
+@image_comparison(['shadow_argument_types.png'], remove_text=True, style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.028)
+def test_shadow_argument_types():
+ # Test that different arguments for shadow work as expected
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3], label='test')
+
+ # Test various shadow configurations
+ # as well as different ways of specifying colors
+ legs = (ax.legend(loc='upper left', shadow=True), # True
+ ax.legend(loc='upper right', shadow=False), # False
+ ax.legend(loc='center left', # string
+ shadow={'color': 'red', 'alpha': 0.1}),
+ ax.legend(loc='center right', # tuple
+ shadow={'color': (0.1, 0.2, 0.5), 'oy': -5}),
+ ax.legend(loc='lower left', # tab
+ shadow={'color': 'tab:cyan', 'ox': 10})
+ )
+ for l in legs:
+ ax.add_artist(l)
+ ax.legend(loc='lower right') # default
+
+
+def test_shadow_invalid_argument():
+ # Test if invalid argument to legend shadow
+ # (i.e. not [color|bool]) raises ValueError
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3], label='test')
+ with pytest.raises(ValueError, match="dict or bool"):
+ ax.legend(loc="upper left", shadow="aardvark") # Bad argument
+
+
+def test_shadow_framealpha():
+ # Test if framealpha is activated when shadow is True
+ # and framealpha is not explicitly passed'''
+ fig, ax = plt.subplots()
+ ax.plot(range(100), label="test")
+ leg = ax.legend(shadow=True, facecolor='w')
+ assert leg.get_frame().get_alpha() == 1
+
+
+def test_legend_title_empty():
+ # test that if we don't set the legend title, that
+ # it comes back as an empty string, and that it is not
+ # visible:
+ fig, ax = plt.subplots()
+ ax.plot(range(10), label="mock data")
+ leg = ax.legend()
+ assert leg.get_title().get_text() == ""
+ assert not leg.get_title().get_visible()
+
+
+def test_legend_proper_window_extent():
+ # test that legend returns the expected extent under various dpi...
+ fig, ax = plt.subplots(dpi=100)
+ ax.plot(range(10), label='Aardvark')
+ leg = ax.legend()
+ x01 = leg.get_window_extent(fig.canvas.get_renderer()).x0
+
+ fig, ax = plt.subplots(dpi=200)
+ ax.plot(range(10), label='Aardvark')
+ leg = ax.legend()
+ x02 = leg.get_window_extent(fig.canvas.get_renderer()).x0
+ assert pytest.approx(x01*2, 0.1) == x02
+
+
+def test_window_extent_cached_renderer():
+ fig, ax = plt.subplots(dpi=100)
+ ax.plot(range(10), label='Aardvark')
+ leg = ax.legend()
+ leg2 = fig.legend()
+ fig.canvas.draw()
+ # check that get_window_extent will use the cached renderer
+ leg.get_window_extent()
+ leg2.get_window_extent()
+
+
+def test_legend_title_fontprop_fontsize():
+ # test the title_fontsize kwarg
+ plt.plot(range(10), label="mock data")
+ with pytest.raises(ValueError):
+ plt.legend(title='Aardvark', title_fontsize=22,
+ title_fontproperties={'family': 'serif', 'size': 22})
+
+ leg = plt.legend(title='Aardvark', title_fontproperties=FontProperties(
+ family='serif', size=22))
+ assert leg.get_title().get_size() == 22
+
+ fig, axes = plt.subplots(2, 3, figsize=(10, 6))
+ axes = axes.flat
+ axes[0].plot(range(10), label="mock data")
+ leg0 = axes[0].legend(title='Aardvark', title_fontsize=22)
+ assert leg0.get_title().get_fontsize() == 22
+ axes[1].plot(range(10), label="mock data")
+ leg1 = axes[1].legend(title='Aardvark',
+ title_fontproperties={'family': 'serif', 'size': 22})
+ assert leg1.get_title().get_fontsize() == 22
+ axes[2].plot(range(10), label="mock data")
+ mpl.rcParams['legend.title_fontsize'] = None
+ leg2 = axes[2].legend(title='Aardvark',
+ title_fontproperties={'family': 'serif'})
+ assert leg2.get_title().get_fontsize() == mpl.rcParams['font.size']
+ axes[3].plot(range(10), label="mock data")
+ leg3 = axes[3].legend(title='Aardvark')
+ assert leg3.get_title().get_fontsize() == mpl.rcParams['font.size']
+ axes[4].plot(range(10), label="mock data")
+ mpl.rcParams['legend.title_fontsize'] = 20
+ leg4 = axes[4].legend(title='Aardvark',
+ title_fontproperties={'family': 'serif'})
+ assert leg4.get_title().get_fontsize() == 20
+ axes[5].plot(range(10), label="mock data")
+ leg5 = axes[5].legend(title='Aardvark')
+ assert leg5.get_title().get_fontsize() == 20
+
+
+@pytest.mark.parametrize('alignment', ('center', 'left', 'right'))
+def test_legend_alignment(alignment):
+ fig, ax = plt.subplots()
+ ax.plot(range(10), label='test')
+ leg = ax.legend(title="Aardvark", alignment=alignment)
+ assert leg.get_children()[0].align == alignment
+ assert leg.get_alignment() == alignment
+
+
+@pytest.mark.parametrize('loc', ('center', 'best',))
+def test_ax_legend_set_loc(loc):
+ fig, ax = plt.subplots()
+ ax.plot(range(10), label='test')
+ leg = ax.legend()
+ leg.set_loc(loc)
+ assert leg._get_loc() == mlegend.Legend.codes[loc]
+
+
+@pytest.mark.parametrize('loc', ('outside right', 'right',))
+def test_fig_legend_set_loc(loc):
+ fig, ax = plt.subplots()
+ ax.plot(range(10), label='test')
+ leg = fig.legend()
+ leg.set_loc(loc)
+
+ loc = loc.split()[1] if loc.startswith("outside") else loc
+ assert leg._get_loc() == mlegend.Legend.codes[loc]
+
+
+@pytest.mark.parametrize('alignment', ('center', 'left', 'right'))
+def test_legend_set_alignment(alignment):
+ fig, ax = plt.subplots()
+ ax.plot(range(10), label='test')
+ leg = ax.legend()
+ leg.set_alignment(alignment)
+ assert leg.get_children()[0].align == alignment
+ assert leg.get_alignment() == alignment
+
+
+@pytest.mark.parametrize('color', ('red', 'none', (.5, .5, .5)))
+def test_legend_labelcolor_single(color):
+ # test labelcolor for a single color
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3')
+
+ leg = ax.legend(labelcolor=color)
+ for text in leg.get_texts():
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_labelcolor_list():
+ # test labelcolor for a list of colors
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3')
+
+ leg = ax.legend(labelcolor=['r', 'g', 'b'])
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_labelcolor_linecolor():
+ # test the labelcolor for labelcolor='linecolor'
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1', color='r')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2', color='g')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3', color='b')
+
+ leg = ax.legend(labelcolor='linecolor')
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_pathcollection_labelcolor_linecolor():
+ # test the labelcolor for labelcolor='linecolor' on PathCollection
+ fig, ax = plt.subplots()
+ ax.scatter(np.arange(10), np.arange(10)*1, label='#1', c='r')
+ ax.scatter(np.arange(10), np.arange(10)*2, label='#2', c='g')
+ ax.scatter(np.arange(10), np.arange(10)*3, label='#3', c='b')
+
+ leg = ax.legend(labelcolor='linecolor')
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_pathcollection_labelcolor_linecolor_iterable():
+ # test the labelcolor for labelcolor='linecolor' on PathCollection
+ # with iterable colors
+ fig, ax = plt.subplots()
+ colors = np.array(['r', 'g', 'b', 'c', 'm'] * 2)
+ ax.scatter(np.arange(10), np.arange(10), label='#1', c=colors)
+
+ leg = ax.legend(labelcolor='linecolor')
+ text, = leg.get_texts()
+ assert mpl.colors.same_color(text.get_color(), 'black')
+
+
+def test_legend_pathcollection_labelcolor_linecolor_cmap():
+ # test the labelcolor for labelcolor='linecolor' on PathCollection
+ # with a colormap
+ fig, ax = plt.subplots()
+ ax.scatter(np.arange(10), np.arange(10), c=np.arange(10), label='#1')
+
+ leg = ax.legend(labelcolor='linecolor')
+ text, = leg.get_texts()
+ assert mpl.colors.same_color(text.get_color(), 'black')
+
+
+def test_legend_labelcolor_markeredgecolor():
+ # test the labelcolor for labelcolor='markeredgecolor'
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b')
+
+ leg = ax.legend(labelcolor='markeredgecolor')
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_pathcollection_labelcolor_markeredgecolor():
+ # test the labelcolor for labelcolor='markeredgecolor' on PathCollection
+ fig, ax = plt.subplots()
+ ax.scatter(np.arange(10), np.arange(10)*1, label='#1', edgecolor='r')
+ ax.scatter(np.arange(10), np.arange(10)*2, label='#2', edgecolor='g')
+ ax.scatter(np.arange(10), np.arange(10)*3, label='#3', edgecolor='b')
+
+ leg = ax.legend(labelcolor='markeredgecolor')
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_pathcollection_labelcolor_markeredgecolor_iterable():
+ # test the labelcolor for labelcolor='markeredgecolor' on PathCollection
+ # with iterable colors
+ fig, ax = plt.subplots()
+ colors = np.array(['r', 'g', 'b', 'c', 'm'] * 2)
+ ax.scatter(np.arange(10), np.arange(10), label='#1', edgecolor=colors)
+
+ leg = ax.legend(labelcolor='markeredgecolor')
+ for text, color in zip(leg.get_texts(), ['k']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_pathcollection_labelcolor_markeredgecolor_cmap():
+ # test the labelcolor for labelcolor='markeredgecolor' on PathCollection
+ # with a colormap
+ fig, ax = plt.subplots()
+ edgecolors = mpl.cm.viridis(np.random.rand(10))
+ ax.scatter(
+ np.arange(10),
+ np.arange(10),
+ label='#1',
+ c=np.arange(10),
+ edgecolor=edgecolors,
+ cmap="Reds"
+ )
+
+ leg = ax.legend(labelcolor='markeredgecolor')
+ for text, color in zip(leg.get_texts(), ['k']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_labelcolor_markerfacecolor():
+ # test the labelcolor for labelcolor='markerfacecolor'
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b')
+
+ leg = ax.legend(labelcolor='markerfacecolor')
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_pathcollection_labelcolor_markerfacecolor():
+ # test the labelcolor for labelcolor='markerfacecolor' on PathCollection
+ fig, ax = plt.subplots()
+ ax.scatter(np.arange(10), np.arange(10)*1, label='#1', facecolor='r')
+ ax.scatter(np.arange(10), np.arange(10)*2, label='#2', facecolor='g')
+ ax.scatter(np.arange(10), np.arange(10)*3, label='#3', facecolor='b')
+
+ leg = ax.legend(labelcolor='markerfacecolor')
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_pathcollection_labelcolor_markerfacecolor_iterable():
+ # test the labelcolor for labelcolor='markerfacecolor' on PathCollection
+ # with iterable colors
+ fig, ax = plt.subplots()
+ colors = np.array(['r', 'g', 'b', 'c', 'm'] * 2)
+ ax.scatter(np.arange(10), np.arange(10), label='#1', facecolor=colors)
+
+ leg = ax.legend(labelcolor='markerfacecolor')
+ for text, color in zip(leg.get_texts(), ['k']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_pathcollection_labelcolor_markfacecolor_cmap():
+ # test the labelcolor for labelcolor='markerfacecolor' on PathCollection
+ # with colormaps
+ fig, ax = plt.subplots()
+ colors = mpl.cm.viridis(np.random.rand(10))
+ ax.scatter(
+ np.arange(10),
+ np.arange(10),
+ label='#1',
+ c=colors
+ )
+
+ leg = ax.legend(labelcolor='markerfacecolor')
+ for text, color in zip(leg.get_texts(), ['k']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+@pytest.mark.parametrize('color', ('red', 'none', (.5, .5, .5)))
+def test_legend_labelcolor_rcparam_single(color):
+ # test the rcParams legend.labelcolor for a single color
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3')
+
+ mpl.rcParams['legend.labelcolor'] = color
+ leg = ax.legend()
+ for text in leg.get_texts():
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_labelcolor_rcparam_linecolor():
+ # test the rcParams legend.labelcolor for a linecolor
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1', color='r')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2', color='g')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3', color='b')
+
+ mpl.rcParams['legend.labelcolor'] = 'linecolor'
+ leg = ax.legend()
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_labelcolor_rcparam_markeredgecolor():
+ # test the labelcolor for labelcolor='markeredgecolor'
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b')
+
+ mpl.rcParams['legend.labelcolor'] = 'markeredgecolor'
+ leg = ax.legend()
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_labelcolor_rcparam_markeredgecolor_short():
+ # test the labelcolor for labelcolor='markeredgecolor'
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b')
+
+ mpl.rcParams['legend.labelcolor'] = 'mec'
+ leg = ax.legend()
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_labelcolor_rcparam_markerfacecolor():
+ # test the labelcolor for labelcolor='markeredgecolor'
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b')
+
+ mpl.rcParams['legend.labelcolor'] = 'markerfacecolor'
+ leg = ax.legend()
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+def test_legend_labelcolor_rcparam_markerfacecolor_short():
+ # test the labelcolor for labelcolor='markeredgecolor'
+ fig, ax = plt.subplots()
+ ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r')
+ ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g')
+ ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b')
+
+ mpl.rcParams['legend.labelcolor'] = 'mfc'
+ leg = ax.legend()
+ for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
+ assert mpl.colors.same_color(text.get_color(), color)
+
+
+@pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend")
+def test_get_set_draggable():
+ legend = plt.legend()
+ assert not legend.get_draggable()
+ legend.set_draggable(True)
+ assert legend.get_draggable()
+ legend.set_draggable(False)
+ assert not legend.get_draggable()
+
+
+@pytest.mark.parametrize('draggable', (True, False))
+def test_legend_draggable(draggable):
+ fig, ax = plt.subplots()
+ ax.plot(range(10), label='shabnams')
+ leg = ax.legend(draggable=draggable)
+ assert leg.get_draggable() is draggable
+
+
+def test_alpha_handles():
+ x, n, hh = plt.hist([1, 2, 3], alpha=0.25, label='data', color='red')
+ legend = plt.legend()
+ for lh in legend.legend_handles:
+ lh.set_alpha(1.0)
+ assert lh.get_facecolor()[:-1] == hh[1].get_facecolor()[:-1]
+ assert lh.get_edgecolor()[:-1] == hh[1].get_edgecolor()[:-1]
+
+
+@needs_usetex
+def test_usetex_no_warn(caplog):
+ mpl.rcParams['font.family'] = 'serif'
+ mpl.rcParams['font.serif'] = 'Computer Modern'
+ mpl.rcParams['text.usetex'] = True
+
+ fig, ax = plt.subplots()
+ ax.plot(0, 0, label='input')
+ ax.legend(title="My legend")
+
+ fig.canvas.draw()
+ assert "Font family ['serif'] not found." not in caplog.text
+
+
+def test_warn_big_data_best_loc(monkeypatch):
+ # Force _find_best_position to think it took a long time.
+ counter = itertools.count(0, step=1.5)
+ monkeypatch.setattr(time, 'perf_counter', lambda: next(counter))
+
+ fig, ax = plt.subplots()
+ fig.canvas.draw() # So that we can call draw_artist later.
+
+ # Place line across all possible legend locations.
+ x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5]
+ y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5]
+ ax.plot(x, y, 'o-', label='line')
+
+ with rc_context({'legend.loc': 'best'}):
+ legend = ax.legend()
+ with pytest.warns(UserWarning,
+ match='Creating legend with loc="best" can be slow with large '
+ 'amounts of data.') as records:
+ fig.draw_artist(legend) # Don't bother drawing the lines -- it's slow.
+ # The _find_best_position method of Legend is called twice, duplicating
+ # the warning message.
+ assert len(records) == 2
+
+
+def test_no_warn_big_data_when_loc_specified(monkeypatch):
+ # Force _find_best_position to think it took a long time.
+ counter = itertools.count(0, step=1.5)
+ monkeypatch.setattr(time, 'perf_counter', lambda: next(counter))
+
+ fig, ax = plt.subplots()
+ fig.canvas.draw()
+
+ # Place line across all possible legend locations.
+ x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5]
+ y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5]
+ ax.plot(x, y, 'o-', label='line')
+
+ legend = ax.legend('best')
+ fig.draw_artist(legend) # Check that no warning is emitted.
+
+
+@pytest.mark.parametrize('label_array', [['low', 'high'],
+ ('low', 'high'),
+ np.array(['low', 'high'])])
+def test_plot_multiple_input_multiple_label(label_array):
+ # test ax.plot() with multidimensional input
+ # and multiple labels
+ x = [1, 2, 3]
+ y = [[1, 2],
+ [2, 5],
+ [4, 9]]
+
+ fig, ax = plt.subplots()
+ ax.plot(x, y, label=label_array)
+ leg = ax.legend()
+ legend_texts = [entry.get_text() for entry in leg.get_texts()]
+ assert legend_texts == ['low', 'high']
+
+
+@pytest.mark.parametrize('label', ['one', 1, int])
+def test_plot_multiple_input_single_label(label):
+ # test ax.plot() with multidimensional input
+ # and single label
+ x = [1, 2, 3]
+ y = [[1, 2],
+ [2, 5],
+ [4, 9]]
+
+ fig, ax = plt.subplots()
+ ax.plot(x, y, label=label)
+ leg = ax.legend()
+ legend_texts = [entry.get_text() for entry in leg.get_texts()]
+ assert legend_texts == [str(label)] * 2
+
+
+@pytest.mark.parametrize('label_array', [['low', 'high'],
+ ('low', 'high'),
+ np.array(['low', 'high'])])
+def test_plot_single_input_multiple_label(label_array):
+ # test ax.plot() with 1D array like input
+ # and iterable label
+ x = [1, 2, 3]
+ y = [2, 5, 6]
+ fig, ax = plt.subplots()
+ with pytest.warns(mpl.MatplotlibDeprecationWarning,
+ match='Passing label as a length 2 sequence'):
+ ax.plot(x, y, label=label_array)
+ leg = ax.legend()
+ assert len(leg.get_texts()) == 1
+ assert leg.get_texts()[0].get_text() == str(label_array)
+
+
+def test_plot_single_input_list_label():
+ fig, ax = plt.subplots()
+ line, = ax.plot([[0], [1]], label=['A'])
+ assert line.get_label() == 'A'
+
+
+def test_plot_multiple_label_incorrect_length_exception():
+ # check that exception is raised if multiple labels
+ # are given, but number of on labels != number of lines
+ with pytest.raises(ValueError):
+ x = [1, 2, 3]
+ y = [[1, 2],
+ [2, 5],
+ [4, 9]]
+ label = ['high', 'low', 'medium']
+ fig, ax = plt.subplots()
+ ax.plot(x, y, label=label)
+
+
+def test_legend_face_edgecolor():
+ # Smoke test for PolyCollection legend handler with 'face' edgecolor.
+ fig, ax = plt.subplots()
+ ax.fill_between([0, 1, 2], [1, 2, 3], [2, 3, 4],
+ facecolor='r', edgecolor='face', label='Fill')
+ ax.legend()
+
+
+def test_legend_text_axes():
+ fig, ax = plt.subplots()
+ ax.plot([1, 2], [3, 4], label='line')
+ leg = ax.legend()
+
+ assert leg.axes is ax
+ assert leg.get_texts()[0].axes is ax
+
+
+def test_handlerline2d():
+ # Test marker consistency for monolithic Line2D legend handler (#11357).
+ fig, ax = plt.subplots()
+ ax.scatter([0, 1], [0, 1], marker="v")
+ handles = [mlines.Line2D([0], [0], marker="v")]
+ leg = ax.legend(handles, ["Aardvark"], numpoints=1)
+ assert handles[0].get_marker() == leg.legend_handles[0].get_marker()
+
+
+def test_subfigure_legend():
+ # Test that legend can be added to subfigure (#20723)
+ subfig = plt.figure().subfigures()
+ ax = subfig.subplots()
+ ax.plot([0, 1], [0, 1], label="line")
+ leg = subfig.legend()
+ assert leg.get_figure(root=False) is subfig
+
+
+def test_setting_alpha_keeps_polycollection_color():
+ pc = plt.fill_between([0, 1], [2, 3], color='#123456', label='label')
+ patch = plt.legend().get_patches()[0]
+ patch.set_alpha(0.5)
+ assert patch.get_facecolor()[:3] == tuple(pc.get_facecolor()[0][:3])
+ assert patch.get_edgecolor()[:3] == tuple(pc.get_edgecolor()[0][:3])
+
+
+def test_legend_markers_from_line2d():
+ # Test that markers can be copied for legend lines (#17960)
+ _markers = ['.', '*', 'v']
+ fig, ax = plt.subplots()
+ lines = [mlines.Line2D([0], [0], ls='None', marker=mark)
+ for mark in _markers]
+ labels = ["foo", "bar", "xyzzy"]
+ markers = [line.get_marker() for line in lines]
+ legend = ax.legend(lines, labels)
+
+ new_markers = [line.get_marker() for line in legend.get_lines()]
+ new_labels = [text.get_text() for text in legend.get_texts()]
+
+ assert markers == new_markers == _markers
+ assert labels == new_labels
+
+
+@check_figures_equal(extensions=['png'])
+def test_ncol_ncols(fig_test, fig_ref):
+ # Test that both ncol and ncols work
+ strings = ["a", "b", "c", "d", "e", "f"]
+ ncols = 3
+ fig_test.legend(strings, ncol=ncols)
+ fig_ref.legend(strings, ncols=ncols)
+
+
+def test_loc_invalid_tuple_exception():
+ # check that exception is raised if the loc arg
+ # of legend is not a 2-tuple of numbers
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError, match=('loc must be string, coordinate '
+ 'tuple, or an integer 0-10, not \\(1.1,\\)')):
+ ax.legend(loc=(1.1, ), labels=["mock data"])
+
+ with pytest.raises(ValueError, match=('loc must be string, coordinate '
+ 'tuple, or an integer 0-10, not \\(0.481, 0.4227, 0.4523\\)')):
+ ax.legend(loc=(0.481, 0.4227, 0.4523), labels=["mock data"])
+
+ with pytest.raises(ValueError, match=('loc must be string, coordinate '
+ 'tuple, or an integer 0-10, not \\(0.481, \'go blue\'\\)')):
+ ax.legend(loc=(0.481, "go blue"), labels=["mock data"])
+
+
+def test_loc_valid_tuple():
+ fig, ax = plt.subplots()
+ ax.legend(loc=(0.481, 0.442), labels=["mock data"])
+ ax.legend(loc=(1, 2), labels=["mock data"])
+
+
+def test_loc_valid_list():
+ fig, ax = plt.subplots()
+ ax.legend(loc=[0.481, 0.442], labels=["mock data"])
+ ax.legend(loc=[1, 2], labels=["mock data"])
+
+
+def test_loc_invalid_list_exception():
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError, match=('loc must be string, coordinate '
+ 'tuple, or an integer 0-10, not \\[1.1, 2.2, 3.3\\]')):
+ ax.legend(loc=[1.1, 2.2, 3.3], labels=["mock data"])
+
+
+def test_loc_invalid_type():
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError, match=("loc must be string, coordinate "
+ "tuple, or an integer 0-10, not {'not': True}")):
+ ax.legend(loc={'not': True}, labels=["mock data"])
+
+
+def test_loc_validation_numeric_value():
+ fig, ax = plt.subplots()
+ ax.legend(loc=0, labels=["mock data"])
+ ax.legend(loc=1, labels=["mock data"])
+ ax.legend(loc=5, labels=["mock data"])
+ ax.legend(loc=10, labels=["mock data"])
+ with pytest.raises(ValueError, match=('loc must be string, coordinate '
+ 'tuple, or an integer 0-10, not 11')):
+ ax.legend(loc=11, labels=["mock data"])
+
+ with pytest.raises(ValueError, match=('loc must be string, coordinate '
+ 'tuple, or an integer 0-10, not -1')):
+ ax.legend(loc=-1, labels=["mock data"])
+
+
+def test_loc_validation_string_value():
+ fig, ax = plt.subplots()
+ labels = ["mock data"]
+ ax.legend(loc='best', labels=labels)
+ ax.legend(loc='upper right', labels=labels)
+ ax.legend(loc='best', labels=labels)
+ ax.legend(loc='upper right', labels=labels)
+ ax.legend(loc='upper left', labels=labels)
+ ax.legend(loc='lower left', labels=labels)
+ ax.legend(loc='lower right', labels=labels)
+ ax.legend(loc='right', labels=labels)
+ ax.legend(loc='center left', labels=labels)
+ ax.legend(loc='center right', labels=labels)
+ ax.legend(loc='lower center', labels=labels)
+ ax.legend(loc='upper center', labels=labels)
+ with pytest.raises(ValueError, match="'wrong' is not a valid value for"):
+ ax.legend(loc='wrong', labels=labels)
+
+
+def test_legend_handle_label_mismatch():
+ pl1, = plt.plot(range(10))
+ pl2, = plt.plot(range(10))
+ with pytest.warns(UserWarning, match="number of handles and labels"):
+ legend = plt.legend(handles=[pl1, pl2], labels=["pl1", "pl2", "pl3"])
+ assert len(legend.legend_handles) == 2
+ assert len(legend.get_texts()) == 2
+
+
+def test_legend_handle_label_mismatch_no_len():
+ pl1, = plt.plot(range(10))
+ pl2, = plt.plot(range(10))
+ legend = plt.legend(handles=iter([pl1, pl2]),
+ labels=iter(["pl1", "pl2", "pl3"]))
+ assert len(legend.legend_handles) == 2
+ assert len(legend.get_texts()) == 2
+
+
+def test_legend_nolabels_warning():
+ plt.plot([1, 2, 3])
+ with pytest.raises(UserWarning, match="No artists with labels found"):
+ plt.legend()
+
+
+@pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend")
+def test_legend_nolabels_draw():
+ plt.plot([1, 2, 3])
+ plt.legend()
+ assert plt.gca().get_legend() is not None
+
+
+def test_legend_loc_polycollection():
+ # Test that the legend is placed in the correct
+ # position for 'best' for polycollection
+ x = [3, 4, 5]
+ y1 = [1, 1, 1]
+ y2 = [5, 5, 5]
+ leg_bboxes = []
+ fig, axs = plt.subplots(ncols=2, figsize=(10, 5))
+ for ax, loc in zip(axs.flat, ('best', 'lower left')):
+ ax.fill_between(x, y1, y2, color='gray', alpha=0.5, label='Shaded Area')
+ ax.set_xlim(0, 6)
+ ax.set_ylim(-1, 5)
+ leg = ax.legend(loc=loc)
+ fig.canvas.draw()
+ leg_bboxes.append(
+ leg.get_window_extent().transformed(ax.transAxes.inverted()))
+ assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds)
+
+
+def test_legend_text():
+ # Test that legend is place in the correct
+ # position for 'best' when there is text in figure
+ fig, axs = plt.subplots(ncols=2, figsize=(10, 5))
+ leg_bboxes = []
+ for ax, loc in zip(axs.flat, ('best', 'lower left')):
+ x = [1, 2]
+ y = [2, 1]
+ ax.plot(x, y, label='plot name')
+ ax.text(1.5, 2, 'some text blahblah', verticalalignment='top')
+ leg = ax.legend(loc=loc)
+ fig.canvas.draw()
+ leg_bboxes.append(
+ leg.get_window_extent().transformed(ax.transAxes.inverted()))
+ assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds)
+
+
+def test_legend_annotate():
+ fig, ax = plt.subplots()
+
+ ax.plot([1, 2, 3], label="Line")
+ ax.annotate("a", xy=(1, 1))
+ ax.legend(loc=0)
+
+ with mock.patch.object(
+ fig, '_get_renderer', wraps=fig._get_renderer) as mocked_get_renderer:
+ fig.savefig(io.BytesIO())
+
+ # Finding the legend position should not require _get_renderer to be called
+ mocked_get_renderer.assert_not_called()
+
+
+def test_boxplot_legend_labels():
+ # Test that legend entries are generated when passing `label`.
+ np.random.seed(19680801)
+ data = np.random.random((10, 4))
+ fig, axs = plt.subplots(nrows=1, ncols=4)
+ legend_labels = ['box A', 'box B', 'box C', 'box D']
+
+ # Testing legend labels and patch passed to legend.
+ bp1 = axs[0].boxplot(data, patch_artist=True, label=legend_labels)
+ assert [v.get_label() for v in bp1['boxes']] == legend_labels
+ handles, labels = axs[0].get_legend_handles_labels()
+ assert labels == legend_labels
+ assert all(isinstance(h, mpl.patches.PathPatch) for h in handles)
+
+ # Testing legend without `box`.
+ bp2 = axs[1].boxplot(data, label=legend_labels, showbox=False)
+ # Without a box, The legend entries should be passed from the medians.
+ assert [v.get_label() for v in bp2['medians']] == legend_labels
+ handles, labels = axs[1].get_legend_handles_labels()
+ assert labels == legend_labels
+ assert all(isinstance(h, mpl.lines.Line2D) for h in handles)
+
+ # Testing legend with number of labels different from number of boxes.
+ with pytest.raises(ValueError, match='values must have same the length'):
+ bp3 = axs[2].boxplot(data, label=legend_labels[:-1])
+
+ # Test that for a string label, only the first box gets a label.
+ bp4 = axs[3].boxplot(data, label='box A')
+ assert bp4['medians'][0].get_label() == 'box A'
+ assert all(x.get_label().startswith("_") for x in bp4['medians'][1:])
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_lines.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_lines.py
new file mode 100644
index 0000000000000000000000000000000000000000..81252198cd8f56242e41cccfbc8a34bbe3f9c4cf
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_lines.py
@@ -0,0 +1,452 @@
+"""
+Tests specific to the lines module.
+"""
+
+import itertools
+import platform
+import timeit
+from types import SimpleNamespace
+
+from cycler import cycler
+import numpy as np
+from numpy.testing import assert_array_equal
+import pytest
+
+import matplotlib
+import matplotlib as mpl
+from matplotlib import _path
+import matplotlib.lines as mlines
+from matplotlib.markers import MarkerStyle
+from matplotlib.path import Path
+import matplotlib.pyplot as plt
+import matplotlib.transforms as mtransforms
+from matplotlib.testing.decorators import image_comparison, check_figures_equal
+
+
+def test_segment_hits():
+ """Test a problematic case."""
+ cx, cy = 553, 902
+ x, y = np.array([553., 553.]), np.array([95., 947.])
+ radius = 6.94
+ assert_array_equal(mlines.segment_hits(cx, cy, x, y, radius), [0])
+
+
+# Runtimes on a loaded system are inherently flaky. Not so much that a rerun
+# won't help, hopefully.
+@pytest.mark.flaky(reruns=3)
+def test_invisible_Line_rendering():
+ """
+ GitHub issue #1256 identified a bug in Line.draw method
+
+ Despite visibility attribute set to False, the draw method was not
+ returning early enough and some pre-rendering code was executed
+ though not necessary.
+
+ Consequence was an excessive draw time for invisible Line instances
+ holding a large number of points (Npts> 10**6)
+ """
+ # Creates big x and y data:
+ N = 10**7
+ x = np.linspace(0, 1, N)
+ y = np.random.normal(size=N)
+
+ # Create a plot figure:
+ fig = plt.figure()
+ ax = plt.subplot()
+
+ # Create a "big" Line instance:
+ l = mlines.Line2D(x, y)
+ l.set_visible(False)
+ # but don't add it to the Axis instance `ax`
+
+ # [here Interactive panning and zooming is pretty responsive]
+ # Time the canvas drawing:
+ t_no_line = min(timeit.repeat(fig.canvas.draw, number=1, repeat=3))
+ # (gives about 25 ms)
+
+ # Add the big invisible Line:
+ ax.add_line(l)
+
+ # [Now interactive panning and zooming is very slow]
+ # Time the canvas drawing:
+ t_invisible_line = min(timeit.repeat(fig.canvas.draw, number=1, repeat=3))
+ # gives about 290 ms for N = 10**7 pts
+
+ slowdown_factor = t_invisible_line / t_no_line
+ slowdown_threshold = 2 # trying to avoid false positive failures
+ assert slowdown_factor < slowdown_threshold
+
+
+def test_set_line_coll_dash():
+ fig, ax = plt.subplots()
+ np.random.seed(0)
+ # Testing setting linestyles for line collections.
+ # This should not produce an error.
+ ax.contour(np.random.randn(20, 30), linestyles=[(0, (3, 3))])
+
+
+def test_invalid_line_data():
+ with pytest.raises(RuntimeError, match='xdata must be'):
+ mlines.Line2D(0, [])
+ with pytest.raises(RuntimeError, match='ydata must be'):
+ mlines.Line2D([], 1)
+
+ line = mlines.Line2D([], [])
+ with pytest.raises(RuntimeError, match='x must be'):
+ line.set_xdata(0)
+ with pytest.raises(RuntimeError, match='y must be'):
+ line.set_ydata(0)
+
+
+@image_comparison(['line_dashes'], remove_text=True, tol=0.003)
+def test_line_dashes():
+ # Tolerance introduced after reordering of floating-point operations
+ # Remove when regenerating the images
+ fig, ax = plt.subplots()
+
+ ax.plot(range(10), linestyle=(0, (3, 3)), lw=5)
+
+
+def test_line_colors():
+ fig, ax = plt.subplots()
+ ax.plot(range(10), color='none')
+ ax.plot(range(10), color='r')
+ ax.plot(range(10), color='.3')
+ ax.plot(range(10), color=(1, 0, 0, 1))
+ ax.plot(range(10), color=(1, 0, 0))
+ fig.canvas.draw()
+
+
+def test_valid_colors():
+ line = mlines.Line2D([], [])
+ with pytest.raises(ValueError):
+ line.set_color("foobar")
+
+
+def test_linestyle_variants():
+ fig, ax = plt.subplots()
+ for ls in ["-", "solid", "--", "dashed",
+ "-.", "dashdot", ":", "dotted",
+ (0, None), (0, ()), (0, []), # gh-22930
+ ]:
+ ax.plot(range(10), linestyle=ls)
+ fig.canvas.draw()
+
+
+def test_valid_linestyles():
+ line = mlines.Line2D([], [])
+ with pytest.raises(ValueError):
+ line.set_linestyle('aardvark')
+
+
+@image_comparison(['drawstyle_variants.png'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.03)
+def test_drawstyle_variants():
+ fig, axs = plt.subplots(6)
+ dss = ["default", "steps-mid", "steps-pre", "steps-post", "steps", None]
+ # We want to check that drawstyles are properly handled even for very long
+ # lines (for which the subslice optimization is on); however, we need
+ # to zoom in so that the difference between the drawstyles is actually
+ # visible.
+ for ax, ds in zip(axs.flat, dss):
+ ax.plot(range(2000), drawstyle=ds)
+ ax.set(xlim=(0, 2), ylim=(0, 2))
+
+
+@check_figures_equal(extensions=('png',))
+def test_no_subslice_with_transform(fig_ref, fig_test):
+ ax = fig_ref.add_subplot()
+ x = np.arange(2000)
+ ax.plot(x + 2000, x)
+
+ ax = fig_test.add_subplot()
+ t = mtransforms.Affine2D().translate(2000.0, 0.0)
+ ax.plot(x, x, transform=t+ax.transData)
+
+
+def test_valid_drawstyles():
+ line = mlines.Line2D([], [])
+ with pytest.raises(ValueError):
+ line.set_drawstyle('foobar')
+
+
+def test_set_drawstyle():
+ x = np.linspace(0, 2*np.pi, 10)
+ y = np.sin(x)
+
+ fig, ax = plt.subplots()
+ line, = ax.plot(x, y)
+ line.set_drawstyle("steps-pre")
+ assert len(line.get_path().vertices) == 2*len(x)-1
+
+ line.set_drawstyle("default")
+ assert len(line.get_path().vertices) == len(x)
+
+
+@image_comparison(['line_collection_dashes'], remove_text=True, style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.65)
+def test_set_line_coll_dash_image():
+ fig, ax = plt.subplots()
+ np.random.seed(0)
+ ax.contour(np.random.randn(20, 30), linestyles=[(0, (3, 3))])
+
+
+@image_comparison(['marker_fill_styles.png'], remove_text=True)
+def test_marker_fill_styles():
+ colors = itertools.cycle([[0, 0, 1], 'g', '#ff0000', 'c', 'm', 'y',
+ np.array([0, 0, 0])])
+ altcolor = 'lightgreen'
+
+ y = np.array([1, 1])
+ x = np.array([0, 9])
+ fig, ax = plt.subplots()
+
+ # This hard-coded list of markers correspond to an earlier iteration of
+ # MarkerStyle.filled_markers; the value of that attribute has changed but
+ # we kept the old value here to not regenerate the baseline image.
+ # Replace with mlines.Line2D.filled_markers when the image is regenerated.
+ for j, marker in enumerate("ov^<>8sp*hHDdPX"):
+ for i, fs in enumerate(mlines.Line2D.fillStyles):
+ color = next(colors)
+ ax.plot(j * 10 + x, y + i + .5 * (j % 2),
+ marker=marker,
+ markersize=20,
+ markerfacecoloralt=altcolor,
+ fillstyle=fs,
+ label=fs,
+ linewidth=5,
+ color=color,
+ markeredgecolor=color,
+ markeredgewidth=2)
+
+ ax.set_ylim([0, 7.5])
+ ax.set_xlim([-5, 155])
+
+
+def test_markerfacecolor_fillstyle():
+ """Test that markerfacecolor does not override fillstyle='none'."""
+ l, = plt.plot([1, 3, 2], marker=MarkerStyle('o', fillstyle='none'),
+ markerfacecolor='red')
+ assert l.get_fillstyle() == 'none'
+ assert l.get_markerfacecolor() == 'none'
+
+
+@image_comparison(['scaled_lines'], style='default')
+def test_lw_scaling():
+ th = np.linspace(0, 32)
+ fig, ax = plt.subplots()
+ lins_styles = ['dashed', 'dotted', 'dashdot']
+ cy = cycler(matplotlib.rcParams['axes.prop_cycle'])
+ for j, (ls, sty) in enumerate(zip(lins_styles, cy)):
+ for lw in np.linspace(.5, 10, 10):
+ ax.plot(th, j*np.ones(50) + .1 * lw, linestyle=ls, lw=lw, **sty)
+
+
+def test_is_sorted_and_has_non_nan():
+ assert _path.is_sorted_and_has_non_nan(np.array([1, 2, 3]))
+ assert _path.is_sorted_and_has_non_nan(np.array([1, np.nan, 3]))
+ assert not _path.is_sorted_and_has_non_nan([3, 5] + [np.nan] * 100 + [0, 2])
+ # [2, 256] byteswapped:
+ assert not _path.is_sorted_and_has_non_nan(np.array([33554432, 65536], ">i4"))
+ n = 2 * mlines.Line2D._subslice_optim_min_size
+ plt.plot([np.nan] * n, range(n))
+
+
+@check_figures_equal(extensions=['png'])
+def test_step_markers(fig_test, fig_ref):
+ fig_test.subplots().step([0, 1], "-o")
+ fig_ref.subplots().plot([0, 0, 1], [0, 1, 1], "-o", markevery=[0, 2])
+
+
+@pytest.mark.parametrize("parent", ["figure", "axes"])
+@check_figures_equal(extensions=('png',))
+def test_markevery(fig_test, fig_ref, parent):
+ np.random.seed(42)
+ x = np.linspace(0, 1, 14)
+ y = np.random.rand(len(x))
+
+ cases_test = [None, 4, (2, 5), [1, 5, 11],
+ [0, -1], slice(5, 10, 2),
+ np.arange(len(x))[y > 0.5],
+ 0.3, (0.3, 0.4)]
+ cases_ref = ["11111111111111", "10001000100010", "00100001000010",
+ "01000100000100", "10000000000001", "00000101010000",
+ "01110001110110", "11011011011110", "01010011011101"]
+
+ if parent == "figure":
+ # float markevery ("relative to axes size") is not supported.
+ cases_test = cases_test[:-2]
+ cases_ref = cases_ref[:-2]
+
+ def add_test(x, y, *, markevery):
+ fig_test.add_artist(
+ mlines.Line2D(x, y, marker="o", markevery=markevery))
+
+ def add_ref(x, y, *, markevery):
+ fig_ref.add_artist(
+ mlines.Line2D(x, y, marker="o", markevery=markevery))
+
+ elif parent == "axes":
+ axs_test = iter(fig_test.subplots(3, 3).flat)
+ axs_ref = iter(fig_ref.subplots(3, 3).flat)
+
+ def add_test(x, y, *, markevery):
+ next(axs_test).plot(x, y, "-gD", markevery=markevery)
+
+ def add_ref(x, y, *, markevery):
+ next(axs_ref).plot(x, y, "-gD", markevery=markevery)
+
+ for case in cases_test:
+ add_test(x, y, markevery=case)
+
+ for case in cases_ref:
+ me = np.array(list(case)).astype(int).astype(bool)
+ add_ref(x, y, markevery=me)
+
+
+def test_markevery_figure_line_unsupported_relsize():
+ fig = plt.figure()
+ fig.add_artist(mlines.Line2D([0, 1], [0, 1], marker="o", markevery=.5))
+ with pytest.raises(ValueError):
+ fig.canvas.draw()
+
+
+def test_marker_as_markerstyle():
+ fig, ax = plt.subplots()
+ line, = ax.plot([2, 4, 3], marker=MarkerStyle("D"))
+ fig.canvas.draw()
+ assert line.get_marker() == "D"
+
+ # continue with smoke tests:
+ line.set_marker("s")
+ fig.canvas.draw()
+ line.set_marker(MarkerStyle("o"))
+ fig.canvas.draw()
+ # test Path roundtrip
+ triangle1 = Path._create_closed([[-1, -1], [1, -1], [0, 2]])
+ line2, = ax.plot([1, 3, 2], marker=MarkerStyle(triangle1), ms=22)
+ line3, = ax.plot([0, 2, 1], marker=triangle1, ms=22)
+
+ assert_array_equal(line2.get_marker().vertices, triangle1.vertices)
+ assert_array_equal(line3.get_marker().vertices, triangle1.vertices)
+
+
+@image_comparison(['striped_line.png'], remove_text=True, style='mpl20')
+def test_striped_lines():
+ rng = np.random.default_rng(19680801)
+ _, ax = plt.subplots()
+ ax.plot(rng.uniform(size=12), color='orange', gapcolor='blue',
+ linestyle='--', lw=5, label=' ')
+ ax.plot(rng.uniform(size=12), color='red', gapcolor='black',
+ linestyle=(0, (2, 5, 4, 2)), lw=5, label=' ', alpha=0.5)
+ ax.legend(handlelength=5)
+
+
+@check_figures_equal(extensions=['png'])
+def test_odd_dashes(fig_test, fig_ref):
+ fig_test.add_subplot().plot([1, 2], dashes=[1, 2, 3])
+ fig_ref.add_subplot().plot([1, 2], dashes=[1, 2, 3, 1, 2, 3])
+
+
+def test_picking():
+ fig, ax = plt.subplots()
+ mouse_event = SimpleNamespace(x=fig.bbox.width // 2,
+ y=fig.bbox.height // 2 + 15)
+
+ # Default pickradius is 5, so event should not pick this line.
+ l0, = ax.plot([0, 1], [0, 1], picker=True)
+ found, indices = l0.contains(mouse_event)
+ assert not found
+
+ # But with a larger pickradius, this should be picked.
+ l1, = ax.plot([0, 1], [0, 1], picker=True, pickradius=20)
+ found, indices = l1.contains(mouse_event)
+ assert found
+ assert_array_equal(indices['ind'], [0])
+
+ # And if we modify the pickradius after creation, it should work as well.
+ l2, = ax.plot([0, 1], [0, 1], picker=True)
+ found, indices = l2.contains(mouse_event)
+ assert not found
+ l2.set_pickradius(20)
+ found, indices = l2.contains(mouse_event)
+ assert found
+ assert_array_equal(indices['ind'], [0])
+
+
+@check_figures_equal(extensions=['png'])
+def test_input_copy(fig_test, fig_ref):
+
+ t = np.arange(0, 6, 2)
+ l, = fig_test.add_subplot().plot(t, t, ".-")
+ t[:] = range(3)
+ # Trigger cache invalidation
+ l.set_drawstyle("steps")
+ fig_ref.add_subplot().plot([0, 2, 4], [0, 2, 4], ".-", drawstyle="steps")
+
+
+@check_figures_equal(extensions=["png"])
+def test_markevery_prop_cycle(fig_test, fig_ref):
+ """Test that we can set markevery prop_cycle."""
+ cases = [None, 8, (30, 8), [16, 24, 30], [0, -1],
+ slice(100, 200, 3), 0.1, 0.3, 1.5,
+ (0.0, 0.1), (0.45, 0.1)]
+
+ cmap = mpl.colormaps['jet']
+ colors = cmap(np.linspace(0.2, 0.8, len(cases)))
+
+ x = np.linspace(-1, 1)
+ y = 5 * x**2
+
+ axs = fig_ref.add_subplot()
+ for i, markevery in enumerate(cases):
+ axs.plot(y - i, 'o-', markevery=markevery, color=colors[i])
+
+ matplotlib.rcParams['axes.prop_cycle'] = cycler(markevery=cases,
+ color=colors)
+
+ ax = fig_test.add_subplot()
+ for i, _ in enumerate(cases):
+ ax.plot(y - i, 'o-')
+
+
+def test_axline_setters():
+ fig, ax = plt.subplots()
+ line1 = ax.axline((.1, .1), slope=0.6)
+ line2 = ax.axline((.1, .1), (.8, .4))
+ # Testing xy1, xy2 and slope setters.
+ # This should not produce an error.
+ line1.set_xy1((.2, .3))
+ line1.set_slope(2.4)
+ line2.set_xy1((.3, .2))
+ line2.set_xy2((.6, .8))
+ # Testing xy1, xy2 and slope getters.
+ # Should return the modified values.
+ assert line1.get_xy1() == (.2, .3)
+ assert line1.get_slope() == 2.4
+ assert line2.get_xy1() == (.3, .2)
+ assert line2.get_xy2() == (.6, .8)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ line1.set_xy1(.2, .3)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ line2.set_xy2(.6, .8)
+ # Testing setting xy2 and slope together.
+ # These test should raise a ValueError
+ with pytest.raises(ValueError,
+ match="Cannot set an 'xy2' value while 'slope' is set"):
+ line1.set_xy2(.2, .3)
+
+ with pytest.raises(ValueError,
+ match="Cannot set a 'slope' value while 'xy2' is set"):
+ line2.set_slope(3)
+
+
+def test_axline_small_slope():
+ """Test that small slopes are not coerced to zero in the transform."""
+ line = plt.axline((0, 0), slope=1e-14)
+ p1 = line.get_transform().transform_point((0, 0))
+ p2 = line.get_transform().transform_point((1, 1))
+ # y-values must be slightly different
+ dy = p2[1] - p1[1]
+ assert dy > 0
+ assert dy < 4e-12
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_marker.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_marker.py
new file mode 100644
index 0000000000000000000000000000000000000000..463ff1d05c96ab677cd7396d86fc7daa357d2a98
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_marker.py
@@ -0,0 +1,303 @@
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib import markers
+from matplotlib.path import Path
+from matplotlib.testing.decorators import check_figures_equal
+from matplotlib.transforms import Affine2D
+
+import pytest
+
+
+def test_marker_fillstyle():
+ marker_style = markers.MarkerStyle(marker='o', fillstyle='none')
+ assert marker_style.get_fillstyle() == 'none'
+ assert not marker_style.is_filled()
+
+
+@pytest.mark.parametrize('marker', [
+ 'o',
+ 'x',
+ '',
+ 'None',
+ r'$\frac{1}{2}$',
+ "$\u266B$",
+ 1,
+ markers.TICKLEFT,
+ [[-1, 0], [1, 0]],
+ np.array([[-1, 0], [1, 0]]),
+ Path([[0, 0], [1, 0]], [Path.MOVETO, Path.LINETO]),
+ (5, 0), # a pentagon
+ (7, 1), # a 7-pointed star
+ (5, 2), # asterisk
+ (5, 0, 10), # a pentagon, rotated by 10 degrees
+ (7, 1, 10), # a 7-pointed star, rotated by 10 degrees
+ (5, 2, 10), # asterisk, rotated by 10 degrees
+ markers.MarkerStyle('o'),
+])
+def test_markers_valid(marker):
+ # Checking this doesn't fail.
+ markers.MarkerStyle(marker)
+
+
+@pytest.mark.parametrize('marker', [
+ 'square', # arbitrary string
+ np.array([[-0.5, 0, 1, 2, 3]]), # 1D array
+ (1,),
+ (5, 3), # second parameter of tuple must be 0, 1, or 2
+ (1, 2, 3, 4),
+])
+def test_markers_invalid(marker):
+ with pytest.raises(ValueError):
+ markers.MarkerStyle(marker)
+
+
+class UnsnappedMarkerStyle(markers.MarkerStyle):
+ """
+ A MarkerStyle where the snap threshold is force-disabled.
+
+ This is used to compare to polygon/star/asterisk markers which do not have
+ any snap threshold set.
+ """
+ def _recache(self):
+ super()._recache()
+ self._snap_threshold = None
+
+
+@check_figures_equal()
+def test_poly_marker(fig_test, fig_ref):
+ ax_test = fig_test.add_subplot()
+ ax_ref = fig_ref.add_subplot()
+
+ # Note, some reference sizes must be different because they have unit
+ # *length*, while polygon markers are inscribed in a circle of unit
+ # *radius*. This introduces a factor of np.sqrt(2), but since size is
+ # squared, that becomes 2.
+ size = 20**2
+
+ # Squares
+ ax_test.scatter([0], [0], marker=(4, 0, 45), s=size)
+ ax_ref.scatter([0], [0], marker='s', s=size/2)
+
+ # Diamonds, with and without rotation argument
+ ax_test.scatter([1], [1], marker=(4, 0), s=size)
+ ax_ref.scatter([1], [1], marker=UnsnappedMarkerStyle('D'), s=size/2)
+ ax_test.scatter([1], [1.5], marker=(4, 0, 0), s=size)
+ ax_ref.scatter([1], [1.5], marker=UnsnappedMarkerStyle('D'), s=size/2)
+
+ # Pentagon, with and without rotation argument
+ ax_test.scatter([2], [2], marker=(5, 0), s=size)
+ ax_ref.scatter([2], [2], marker=UnsnappedMarkerStyle('p'), s=size)
+ ax_test.scatter([2], [2.5], marker=(5, 0, 0), s=size)
+ ax_ref.scatter([2], [2.5], marker=UnsnappedMarkerStyle('p'), s=size)
+
+ # Hexagon, with and without rotation argument
+ ax_test.scatter([3], [3], marker=(6, 0), s=size)
+ ax_ref.scatter([3], [3], marker='h', s=size)
+ ax_test.scatter([3], [3.5], marker=(6, 0, 0), s=size)
+ ax_ref.scatter([3], [3.5], marker='h', s=size)
+
+ # Rotated hexagon
+ ax_test.scatter([4], [4], marker=(6, 0, 30), s=size)
+ ax_ref.scatter([4], [4], marker='H', s=size)
+
+ # Octagons
+ ax_test.scatter([5], [5], marker=(8, 0, 22.5), s=size)
+ ax_ref.scatter([5], [5], marker=UnsnappedMarkerStyle('8'), s=size)
+
+ ax_test.set(xlim=(-0.5, 5.5), ylim=(-0.5, 5.5))
+ ax_ref.set(xlim=(-0.5, 5.5), ylim=(-0.5, 5.5))
+
+
+def test_star_marker():
+ # We don't really have a strict equivalent to this marker, so we'll just do
+ # a smoke test.
+ size = 20**2
+
+ fig, ax = plt.subplots()
+ ax.scatter([0], [0], marker=(5, 1), s=size)
+ ax.scatter([1], [1], marker=(5, 1, 0), s=size)
+ ax.set(xlim=(-0.5, 0.5), ylim=(-0.5, 1.5))
+
+
+# The asterisk marker is really a star with 0-size inner circle, so the ends
+# are corners and get a slight bevel. The reference markers are just singular
+# lines without corners, so they have no bevel, and we need to add a slight
+# tolerance.
+@check_figures_equal(tol=1.45)
+def test_asterisk_marker(fig_test, fig_ref, request):
+ ax_test = fig_test.add_subplot()
+ ax_ref = fig_ref.add_subplot()
+
+ # Note, some reference sizes must be different because they have unit
+ # *length*, while asterisk markers are inscribed in a circle of unit
+ # *radius*. This introduces a factor of np.sqrt(2), but since size is
+ # squared, that becomes 2.
+ size = 20**2
+
+ def draw_ref_marker(y, style, size):
+ # As noted above, every line is doubled. Due to antialiasing, these
+ # doubled lines make a slight difference in the .png results.
+ ax_ref.scatter([y], [y], marker=UnsnappedMarkerStyle(style), s=size)
+ if request.getfixturevalue('ext') == 'png':
+ ax_ref.scatter([y], [y], marker=UnsnappedMarkerStyle(style),
+ s=size)
+
+ # Plus
+ ax_test.scatter([0], [0], marker=(4, 2), s=size)
+ draw_ref_marker(0, '+', size)
+ ax_test.scatter([0.5], [0.5], marker=(4, 2, 0), s=size)
+ draw_ref_marker(0.5, '+', size)
+
+ # Cross
+ ax_test.scatter([1], [1], marker=(4, 2, 45), s=size)
+ draw_ref_marker(1, 'x', size/2)
+
+ ax_test.set(xlim=(-0.5, 1.5), ylim=(-0.5, 1.5))
+ ax_ref.set(xlim=(-0.5, 1.5), ylim=(-0.5, 1.5))
+
+
+# The bullet mathtext marker is not quite a circle, so this is not a perfect match, but
+# it is close enough to confirm that the text-based marker is centred correctly. But we
+# still need a small tolerance to work around that difference.
+@check_figures_equal(extensions=['png'], tol=1.86)
+def test_text_marker(fig_ref, fig_test):
+ ax_ref = fig_ref.add_subplot()
+ ax_test = fig_test.add_subplot()
+
+ ax_ref.plot(0, 0, marker=r'o', markersize=100, markeredgewidth=0)
+ ax_test.plot(0, 0, marker=r'$\bullet$', markersize=100, markeredgewidth=0)
+
+
+@check_figures_equal()
+def test_marker_clipping(fig_ref, fig_test):
+ # Plotting multiple markers can trigger different optimized paths in
+ # backends, so compare single markers vs multiple to ensure they are
+ # clipped correctly.
+ marker_count = len(markers.MarkerStyle.markers)
+ marker_size = 50
+ ncol = 7
+ nrow = marker_count // ncol + 1
+
+ width = 2 * marker_size * ncol
+ height = 2 * marker_size * nrow * 2
+ fig_ref.set_size_inches((width / fig_ref.dpi, height / fig_ref.dpi))
+ ax_ref = fig_ref.add_axes([0, 0, 1, 1])
+ fig_test.set_size_inches((width / fig_test.dpi, height / fig_ref.dpi))
+ ax_test = fig_test.add_axes([0, 0, 1, 1])
+
+ for i, marker in enumerate(markers.MarkerStyle.markers):
+ x = i % ncol
+ y = i // ncol * 2
+
+ # Singular markers per call.
+ ax_ref.plot([x, x], [y, y + 1], c='k', linestyle='-', lw=3)
+ ax_ref.plot(x, y, c='k',
+ marker=marker, markersize=marker_size, markeredgewidth=10,
+ fillstyle='full', markerfacecolor='white')
+ ax_ref.plot(x, y + 1, c='k',
+ marker=marker, markersize=marker_size, markeredgewidth=10,
+ fillstyle='full', markerfacecolor='white')
+
+ # Multiple markers in a single call.
+ ax_test.plot([x, x], [y, y + 1], c='k', linestyle='-', lw=3,
+ marker=marker, markersize=marker_size, markeredgewidth=10,
+ fillstyle='full', markerfacecolor='white')
+
+ ax_ref.set(xlim=(-0.5, ncol), ylim=(-0.5, 2 * nrow))
+ ax_test.set(xlim=(-0.5, ncol), ylim=(-0.5, 2 * nrow))
+ ax_ref.axis('off')
+ ax_test.axis('off')
+
+
+def test_marker_init_transforms():
+ """Test that initializing marker with transform is a simple addition."""
+ marker = markers.MarkerStyle("o")
+ t = Affine2D().translate(1, 1)
+ t_marker = markers.MarkerStyle("o", transform=t)
+ assert marker.get_transform() + t == t_marker.get_transform()
+
+
+def test_marker_init_joinstyle():
+ marker = markers.MarkerStyle("*")
+ styled_marker = markers.MarkerStyle("*", joinstyle="round")
+ assert styled_marker.get_joinstyle() == "round"
+ assert marker.get_joinstyle() != "round"
+
+
+def test_marker_init_captyle():
+ marker = markers.MarkerStyle("*")
+ styled_marker = markers.MarkerStyle("*", capstyle="round")
+ assert styled_marker.get_capstyle() == "round"
+ assert marker.get_capstyle() != "round"
+
+
+@pytest.mark.parametrize("marker,transform,expected", [
+ (markers.MarkerStyle("o"), Affine2D().translate(1, 1),
+ Affine2D().translate(1, 1)),
+ (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)),
+ Affine2D().translate(1, 1), Affine2D().translate(2, 2)),
+ (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)),
+ Affine2D().translate(1, 1), Affine2D().translate(2, 2)),
+ (markers.MarkerStyle(
+ markers.TICKLEFT, transform=Affine2D().translate(1, 1)),
+ Affine2D().translate(1, 1), Affine2D().translate(2, 2)),
+])
+def test_marker_transformed(marker, transform, expected):
+ new_marker = marker.transformed(transform)
+ assert new_marker is not marker
+ assert new_marker.get_user_transform() == expected
+ assert marker._user_transform is not new_marker._user_transform
+
+
+def test_marker_rotated_invalid():
+ marker = markers.MarkerStyle("o")
+ with pytest.raises(ValueError):
+ new_marker = marker.rotated()
+ with pytest.raises(ValueError):
+ new_marker = marker.rotated(deg=10, rad=10)
+
+
+@pytest.mark.parametrize("marker,deg,rad,expected", [
+ (markers.MarkerStyle("o"), 10, None, Affine2D().rotate_deg(10)),
+ (markers.MarkerStyle("o"), None, 0.01, Affine2D().rotate(0.01)),
+ (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)),
+ 10, None, Affine2D().translate(1, 1).rotate_deg(10)),
+ (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)),
+ None, 0.01, Affine2D().translate(1, 1).rotate(0.01)),
+ (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)),
+ 10, None, Affine2D().translate(1, 1).rotate_deg(10)),
+ (markers.MarkerStyle(
+ markers.TICKLEFT, transform=Affine2D().translate(1, 1)),
+ 10, None, Affine2D().translate(1, 1).rotate_deg(10)),
+])
+def test_marker_rotated(marker, deg, rad, expected):
+ new_marker = marker.rotated(deg=deg, rad=rad)
+ assert new_marker is not marker
+ assert new_marker.get_user_transform() == expected
+ assert marker._user_transform is not new_marker._user_transform
+
+
+def test_marker_scaled():
+ marker = markers.MarkerStyle("1")
+ new_marker = marker.scaled(2)
+ assert new_marker is not marker
+ assert new_marker.get_user_transform() == Affine2D().scale(2)
+ assert marker._user_transform is not new_marker._user_transform
+
+ new_marker = marker.scaled(2, 3)
+ assert new_marker is not marker
+ assert new_marker.get_user_transform() == Affine2D().scale(2, 3)
+ assert marker._user_transform is not new_marker._user_transform
+
+ marker = markers.MarkerStyle("1", transform=Affine2D().translate(1, 1))
+ new_marker = marker.scaled(2)
+ assert new_marker is not marker
+ expected = Affine2D().translate(1, 1).scale(2)
+ assert new_marker.get_user_transform() == expected
+ assert marker._user_transform is not new_marker._user_transform
+
+
+def test_alt_transform():
+ m1 = markers.MarkerStyle("o", "left")
+ m2 = markers.MarkerStyle("o", "left", Affine2D().rotate_deg(90))
+ assert m1.get_alt_transform().rotate_deg(90) == m2.get_alt_transform()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_mathtext.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_mathtext.py
new file mode 100644
index 0000000000000000000000000000000000000000..4dcd08ba071879a09263e126d39264c47c3e54d9
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_mathtext.py
@@ -0,0 +1,560 @@
+from __future__ import annotations
+
+import io
+from pathlib import Path
+import platform
+import re
+from xml.etree import ElementTree as ET
+from typing import Any
+
+import numpy as np
+from packaging.version import parse as parse_version
+import pyparsing
+import pytest
+
+
+import matplotlib as mpl
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+import matplotlib.pyplot as plt
+from matplotlib import mathtext, _mathtext
+
+pyparsing_version = parse_version(pyparsing.__version__)
+
+
+# If test is removed, use None as placeholder
+math_tests = [
+ r'$a+b+\dot s+\dot{s}+\ldots$',
+ r'$x\hspace{-0.2}\doteq\hspace{-0.2}y$',
+ r'\$100.00 $\alpha \_$',
+ r'$\frac{\$100.00}{y}$',
+ r'$x y$',
+ r'$x+y\ x=y\ x1}f\left(t\right) d\pi \left(t\right)$',
+ # mathtex doesn't support array
+ # 'mmltt23' : r'$\left(\begin{array}{cc}\hfill \left(\begin{array}{cc}\hfill a\hfill & \hfill b\hfill \\ \hfill c\hfill & \hfill d\hfill \end{array}\right)\hfill & \hfill \left(\begin{array}{cc}\hfill e\hfill & \hfill f\hfill \\ \hfill g\hfill & \hfill h\hfill \end{array}\right)\hfill \\ \hfill 0\hfill & \hfill \left(\begin{array}{cc}\hfill i\hfill & \hfill j\hfill \\ \hfill k\hfill & \hfill l\hfill \end{array}\right)\hfill \end{array}\right)$',
+ # mathtex doesn't support array
+ # 'mmltt24' : r'$det|\begin{array}{ccccc}\hfill {c}_{0}\hfill & \hfill {c}_{1}\hfill & \hfill {c}_{2}\hfill & \hfill \dots \hfill & \hfill {c}_{n}\hfill \\ \hfill {c}_{1}\hfill & \hfill {c}_{2}\hfill & \hfill {c}_{3}\hfill & \hfill \dots \hfill & \hfill {c}_{n+1}\hfill \\ \hfill {c}_{2}\hfill & \hfill {c}_{3}\hfill & \hfill {c}_{4}\hfill & \hfill \dots \hfill & \hfill {c}_{n+2}\hfill \\ \hfill \u22ee\hfill & \hfill \u22ee\hfill & \hfill \u22ee\hfill & \hfill \hfill & \hfill \u22ee\hfill \\ \hfill {c}_{n}\hfill & \hfill {c}_{n+1}\hfill & \hfill {c}_{n+2}\hfill & \hfill \dots \hfill & \hfill {c}_{2n}\hfill \end{array}|>0$',
+ r'${y}_{{x}_{2}}$',
+ r'${x}_{92}^{31415}+\pi $',
+ r'${x}_{{y}_{b}^{a}}^{{z}_{c}^{d}}$',
+ r'${y}_{3}^{\prime \prime \prime }$',
+ # End of the MathML torture tests.
+
+ r"$\left( \xi \left( 1 - \xi \right) \right)$", # Bug 2969451
+ r"$\left(2 \, a=b\right)$", # Sage bug #8125
+ r"$? ! &$", # github issue #466
+ None,
+ None,
+ r"$\left\Vert \frac{a}{b} \right\Vert \left\vert \frac{a}{b} \right\vert \left\| \frac{a}{b}\right\| \left| \frac{a}{b} \right| \Vert a \Vert \vert b \vert \| a \| | b |$",
+ r'$\mathring{A} \AA$',
+ r'$M \, M \thinspace M \/ M \> M \: M \; M \ M \enspace M \quad M \qquad M \! M$',
+ r'$\Cap$ $\Cup$ $\leftharpoonup$ $\barwedge$ $\rightharpoonup$',
+ r'$\hspace{-0.2}\dotplus\hspace{-0.2}$ $\hspace{-0.2}\doteq\hspace{-0.2}$ $\hspace{-0.2}\doteqdot\hspace{-0.2}$ $\ddots$',
+ r'$xyz^kx_kx^py^{p-2} d_i^jb_jc_kd x^j_i E^0 E^0_u$', # github issue #4873
+ r'${xyz}^k{x}_{k}{x}^{p}{y}^{p-2} {d}_{i}^{j}{b}_{j}{c}_{k}{d} {x}^{j}_{i}{E}^{0}{E}^0_u$',
+ r'${\int}_x^x x\oint_x^x x\int_{X}^{X}x\int_x x \int^x x \int_{x} x\int^{x}{\int}_{x} x{\int}^{x}_{x}x$',
+ r'testing$^{123}$',
+ None,
+ r'$6-2$; $-2$; $ -2$; ${-2}$; ${ -2}$; $20^{+3}_{-2}$',
+ r'$\overline{\omega}^x \frac{1}{2}_0^x$', # github issue #5444
+ r'$,$ $.$ $1{,}234{, }567{ , }890$ and $1,234,567,890$', # github issue 5799
+ r'$\left(X\right)_{a}^{b}$', # github issue 7615
+ r'$\dfrac{\$100.00}{y}$', # github issue #1888
+ r'$a=-b-c$' # github issue #28180
+]
+# 'svgastext' tests switch svg output to embed text as text (rather than as
+# paths).
+svgastext_math_tests = [
+ r'$-$-',
+]
+# 'lightweight' tests test only a single fontset (dejavusans, which is the
+# default) and only png outputs, in order to minimize the size of baseline
+# images.
+lightweight_math_tests = [
+ r'$\sqrt[ab]{123}$', # github issue #8665
+ r'$x \overset{f}{\rightarrow} \overset{f}{x} \underset{xx}{ff} \overset{xx}{ff} \underset{f}{x} \underset{f}{\leftarrow} x$', # github issue #18241
+ r'$\sum x\quad\sum^nx\quad\sum_nx\quad\sum_n^nx\quad\prod x\quad\prod^nx\quad\prod_nx\quad\prod_n^nx$', # GitHub issue 18085
+ r'$1.$ $2.$ $19680801.$ $a.$ $b.$ $mpl.$',
+ r'$\text{text}_{\text{sub}}^{\text{sup}} + \text{\$foo\$} + \frac{\text{num}}{\mathbf{\text{den}}}\text{with space, curly brackets \{\}, and dash -}$',
+ r'$\boldsymbol{abcde} \boldsymbol{+} \boldsymbol{\Gamma + \Omega} \boldsymbol{01234} \boldsymbol{\alpha * \beta}$',
+ r'$\left\lbrace\frac{\left\lbrack A^b_c\right\rbrace}{\left\leftbrace D^e_f \right\rbrack}\right\rightbrace\ \left\leftparen\max_{x} \left\lgroup \frac{A}{B}\right\rgroup \right\rightparen$',
+ r'$\left( a\middle. b \right)$ $\left( \frac{a}{b} \middle\vert x_i \in P^S \right)$ $\left[ 1 - \middle| a\middle| + \left( x - \left\lfloor \dfrac{a}{b}\right\rfloor \right) \right]$',
+ r'$\sum_{\substack{k = 1\\ k \neq \lfloor n/2\rfloor}}^{n}P(i,j) \sum_{\substack{i \neq 0\\ -1 \leq i \leq 3\\ 1 \leq j \leq 5}} F^i(x,y) \sum_{\substack{\left \lfloor \frac{n}{2} \right\rfloor}} F(n)$',
+]
+
+digits = "0123456789"
+uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+lowercase = "abcdefghijklmnopqrstuvwxyz"
+uppergreek = ("\\Gamma \\Delta \\Theta \\Lambda \\Xi \\Pi \\Sigma \\Upsilon \\Phi \\Psi "
+ "\\Omega")
+lowergreek = ("\\alpha \\beta \\gamma \\delta \\epsilon \\zeta \\eta \\theta \\iota "
+ "\\lambda \\mu \\nu \\xi \\pi \\kappa \\rho \\sigma \\tau \\upsilon "
+ "\\phi \\chi \\psi")
+all = [digits, uppercase, lowercase, uppergreek, lowergreek]
+
+# Use stubs to reserve space if tests are removed
+# stub should be of the form (None, N) where N is the number of strings that
+# used to be tested
+# Add new tests at the end.
+font_test_specs: list[tuple[None | list[str], Any]] = [
+ ([], all),
+ (['mathrm'], all),
+ (['mathbf'], all),
+ (['mathit'], all),
+ (['mathtt'], [digits, uppercase, lowercase]),
+ (None, 3),
+ (None, 3),
+ (None, 3),
+ (['mathbb'], [digits, uppercase, lowercase,
+ r'\Gamma \Pi \Sigma \gamma \pi']),
+ (['mathrm', 'mathbb'], [digits, uppercase, lowercase,
+ r'\Gamma \Pi \Sigma \gamma \pi']),
+ (['mathbf', 'mathbb'], [digits, uppercase, lowercase,
+ r'\Gamma \Pi \Sigma \gamma \pi']),
+ (['mathcal'], [uppercase]),
+ (['mathfrak'], [uppercase, lowercase]),
+ (['mathbf', 'mathfrak'], [uppercase, lowercase]),
+ (['mathscr'], [uppercase, lowercase]),
+ (['mathsf'], [digits, uppercase, lowercase]),
+ (['mathrm', 'mathsf'], [digits, uppercase, lowercase]),
+ (['mathbf', 'mathsf'], [digits, uppercase, lowercase]),
+ (['mathbfit'], all),
+ ]
+
+font_tests: list[None | str] = []
+for fonts, chars in font_test_specs:
+ if fonts is None:
+ font_tests.extend([None] * chars)
+ else:
+ wrapper = ''.join([
+ ' '.join(fonts),
+ ' $',
+ *(r'\%s{' % font for font in fonts),
+ '%s',
+ *('}' for font in fonts),
+ '$',
+ ])
+ for font_set in chars:
+ font_tests.append(wrapper % font_set)
+
+
+@pytest.fixture
+def baseline_images(request, fontset, index, text):
+ if text is None:
+ pytest.skip("test has been removed")
+ return ['%s_%s_%02d' % (request.param, fontset, index)]
+
+
+@pytest.mark.parametrize(
+ 'index, text', enumerate(math_tests), ids=range(len(math_tests)))
+@pytest.mark.parametrize(
+ 'fontset', ['cm', 'stix', 'stixsans', 'dejavusans', 'dejavuserif'])
+@pytest.mark.parametrize('baseline_images', ['mathtext'], indirect=True)
+@image_comparison(baseline_images=None,
+ tol=0.011 if platform.machine() in ('ppc64le', 's390x') else 0)
+def test_mathtext_rendering(baseline_images, fontset, index, text):
+ mpl.rcParams['mathtext.fontset'] = fontset
+ fig = plt.figure(figsize=(5.25, 0.75))
+ fig.text(0.5, 0.5, text,
+ horizontalalignment='center', verticalalignment='center')
+
+
+@pytest.mark.parametrize('index, text', enumerate(svgastext_math_tests),
+ ids=range(len(svgastext_math_tests)))
+@pytest.mark.parametrize('fontset', ['cm', 'dejavusans'])
+@pytest.mark.parametrize('baseline_images', ['mathtext0'], indirect=True)
+@image_comparison(
+ baseline_images=None, extensions=['svg'],
+ savefig_kwarg={'metadata': { # Minimize image size.
+ 'Creator': None, 'Date': None, 'Format': None, 'Type': None}})
+def test_mathtext_rendering_svgastext(baseline_images, fontset, index, text):
+ mpl.rcParams['mathtext.fontset'] = fontset
+ mpl.rcParams['svg.fonttype'] = 'none' # Minimize image size.
+ fig = plt.figure(figsize=(5.25, 0.75))
+ fig.patch.set(visible=False) # Minimize image size.
+ fig.text(0.5, 0.5, text,
+ horizontalalignment='center', verticalalignment='center')
+
+
+@pytest.mark.parametrize('index, text', enumerate(lightweight_math_tests),
+ ids=range(len(lightweight_math_tests)))
+@pytest.mark.parametrize('fontset', ['dejavusans'])
+@pytest.mark.parametrize('baseline_images', ['mathtext1'], indirect=True)
+@image_comparison(baseline_images=None, extensions=['png'])
+def test_mathtext_rendering_lightweight(baseline_images, fontset, index, text):
+ fig = plt.figure(figsize=(5.25, 0.75))
+ fig.text(0.5, 0.5, text, math_fontfamily=fontset,
+ horizontalalignment='center', verticalalignment='center')
+
+
+@pytest.mark.parametrize(
+ 'index, text', enumerate(font_tests), ids=range(len(font_tests)))
+@pytest.mark.parametrize(
+ 'fontset', ['cm', 'stix', 'stixsans', 'dejavusans', 'dejavuserif'])
+@pytest.mark.parametrize('baseline_images', ['mathfont'], indirect=True)
+@image_comparison(baseline_images=None, extensions=['png'],
+ tol=0.011 if platform.machine() in ('ppc64le', 's390x') else 0)
+def test_mathfont_rendering(baseline_images, fontset, index, text):
+ mpl.rcParams['mathtext.fontset'] = fontset
+ fig = plt.figure(figsize=(5.25, 0.75))
+ fig.text(0.5, 0.5, text,
+ horizontalalignment='center', verticalalignment='center')
+
+
+@check_figures_equal(extensions=["png"])
+def test_short_long_accents(fig_test, fig_ref):
+ acc_map = _mathtext.Parser._accent_map
+ short_accs = [s for s in acc_map if len(s) == 1]
+ corresponding_long_accs = []
+ for s in short_accs:
+ l, = (l for l in acc_map if len(l) > 1 and acc_map[l] == acc_map[s])
+ corresponding_long_accs.append(l)
+ fig_test.text(0, .5, "$" + "".join(rf"\{s}a" for s in short_accs) + "$")
+ fig_ref.text(
+ 0, .5, "$" + "".join(fr"\{l} a" for l in corresponding_long_accs) + "$")
+
+
+def test_fontinfo():
+ fontpath = mpl.font_manager.findfont("DejaVu Sans")
+ font = mpl.ft2font.FT2Font(fontpath)
+ table = font.get_sfnt_table("head")
+ assert table is not None
+ assert table['version'] == (1, 0)
+
+
+# See gh-26152 for more context on this xfail
+@pytest.mark.xfail(pyparsing_version.release == (3, 1, 0),
+ reason="Error messages are incorrect for this version")
+@pytest.mark.parametrize(
+ 'math, msg',
+ [
+ (r'$\hspace{}$', r'Expected \hspace{space}'),
+ (r'$\hspace{foo}$', r'Expected \hspace{space}'),
+ (r'$\sinx$', r'Unknown symbol: \sinx'),
+ (r'$\dotx$', r'Unknown symbol: \dotx'),
+ (r'$\frac$', r'Expected \frac{num}{den}'),
+ (r'$\frac{}{}$', r'Expected \frac{num}{den}'),
+ (r'$\binom$', r'Expected \binom{num}{den}'),
+ (r'$\binom{}{}$', r'Expected \binom{num}{den}'),
+ (r'$\genfrac$',
+ r'Expected \genfrac{ldelim}{rdelim}{rulesize}{style}{num}{den}'),
+ (r'$\genfrac{}{}{}{}{}{}$',
+ r'Expected \genfrac{ldelim}{rdelim}{rulesize}{style}{num}{den}'),
+ (r'$\sqrt$', r'Expected \sqrt{value}'),
+ (r'$\sqrt f$', r'Expected \sqrt{value}'),
+ (r'$\overline$', r'Expected \overline{body}'),
+ (r'$\overline{}$', r'Expected \overline{body}'),
+ (r'$\leftF$', r'Expected a delimiter'),
+ (r'$\rightF$', r'Unknown symbol: \rightF'),
+ (r'$\left(\right$', r'Expected a delimiter'),
+ # PyParsing 2 uses double quotes, PyParsing 3 uses single quotes and an
+ # extra backslash.
+ (r'$\left($', re.compile(r'Expected ("|\'\\)\\right["\']')),
+ (r'$\dfrac$', r'Expected \dfrac{num}{den}'),
+ (r'$\dfrac{}{}$', r'Expected \dfrac{num}{den}'),
+ (r'$\overset$', r'Expected \overset{annotation}{body}'),
+ (r'$\underset$', r'Expected \underset{annotation}{body}'),
+ (r'$\foo$', r'Unknown symbol: \foo'),
+ (r'$a^2^2$', r'Double superscript'),
+ (r'$a_2_2$', r'Double subscript'),
+ (r'$a^2_a^2$', r'Double superscript'),
+ (r'$a = {b$', r"Expected '}'"),
+ ],
+ ids=[
+ 'hspace without value',
+ 'hspace with invalid value',
+ 'function without space',
+ 'accent without space',
+ 'frac without parameters',
+ 'frac with empty parameters',
+ 'binom without parameters',
+ 'binom with empty parameters',
+ 'genfrac without parameters',
+ 'genfrac with empty parameters',
+ 'sqrt without parameters',
+ 'sqrt with invalid value',
+ 'overline without parameters',
+ 'overline with empty parameter',
+ 'left with invalid delimiter',
+ 'right with invalid delimiter',
+ 'unclosed parentheses with sizing',
+ 'unclosed parentheses without sizing',
+ 'dfrac without parameters',
+ 'dfrac with empty parameters',
+ 'overset without parameters',
+ 'underset without parameters',
+ 'unknown symbol',
+ 'double superscript',
+ 'double subscript',
+ 'super on sub without braces',
+ 'unclosed group',
+ ]
+)
+def test_mathtext_exceptions(math, msg):
+ parser = mathtext.MathTextParser('agg')
+ match = re.escape(msg) if isinstance(msg, str) else msg
+ with pytest.raises(ValueError, match=match):
+ parser.parse(math)
+
+
+def test_get_unicode_index_exception():
+ with pytest.raises(ValueError):
+ _mathtext.get_unicode_index(r'\foo')
+
+
+def test_single_minus_sign():
+ fig = plt.figure()
+ fig.text(0.5, 0.5, '$-$')
+ fig.canvas.draw()
+ t = np.asarray(fig.canvas.renderer.buffer_rgba())
+ assert (t != 0xff).any() # assert that canvas is not all white.
+
+
+@check_figures_equal(extensions=["png"])
+def test_spaces(fig_test, fig_ref):
+ fig_test.text(.5, .5, r"$1\,2\>3\ 4$")
+ fig_ref.text(.5, .5, r"$1\/2\:3~4$")
+
+
+@check_figures_equal(extensions=["png"])
+def test_operator_space(fig_test, fig_ref):
+ fig_test.text(0.1, 0.1, r"$\log 6$")
+ fig_test.text(0.1, 0.2, r"$\log(6)$")
+ fig_test.text(0.1, 0.3, r"$\arcsin 6$")
+ fig_test.text(0.1, 0.4, r"$\arcsin|6|$")
+ fig_test.text(0.1, 0.5, r"$\operatorname{op} 6$") # GitHub issue #553
+ fig_test.text(0.1, 0.6, r"$\operatorname{op}[6]$")
+ fig_test.text(0.1, 0.7, r"$\cos^2$")
+ fig_test.text(0.1, 0.8, r"$\log_2$")
+ fig_test.text(0.1, 0.9, r"$\sin^2 \cos$") # GitHub issue #17852
+
+ fig_ref.text(0.1, 0.1, r"$\mathrm{log\,}6$")
+ fig_ref.text(0.1, 0.2, r"$\mathrm{log}(6)$")
+ fig_ref.text(0.1, 0.3, r"$\mathrm{arcsin\,}6$")
+ fig_ref.text(0.1, 0.4, r"$\mathrm{arcsin}|6|$")
+ fig_ref.text(0.1, 0.5, r"$\mathrm{op\,}6$")
+ fig_ref.text(0.1, 0.6, r"$\mathrm{op}[6]$")
+ fig_ref.text(0.1, 0.7, r"$\mathrm{cos}^2$")
+ fig_ref.text(0.1, 0.8, r"$\mathrm{log}_2$")
+ fig_ref.text(0.1, 0.9, r"$\mathrm{sin}^2 \mathrm{\,cos}$")
+
+
+@check_figures_equal(extensions=["png"])
+def test_inverted_delimiters(fig_test, fig_ref):
+ fig_test.text(.5, .5, r"$\left)\right($", math_fontfamily="dejavusans")
+ fig_ref.text(.5, .5, r"$)($", math_fontfamily="dejavusans")
+
+
+@check_figures_equal(extensions=["png"])
+def test_genfrac_displaystyle(fig_test, fig_ref):
+ fig_test.text(0.1, 0.1, r"$\dfrac{2x}{3y}$")
+
+ thickness = _mathtext.TruetypeFonts.get_underline_thickness(
+ None, None, fontsize=mpl.rcParams["font.size"],
+ dpi=mpl.rcParams["savefig.dpi"])
+ fig_ref.text(0.1, 0.1, r"$\genfrac{}{}{%f}{0}{2x}{3y}$" % thickness)
+
+
+def test_mathtext_fallback_valid():
+ for fallback in ['cm', 'stix', 'stixsans', 'None']:
+ mpl.rcParams['mathtext.fallback'] = fallback
+
+
+def test_mathtext_fallback_invalid():
+ for fallback in ['abc', '']:
+ with pytest.raises(ValueError, match="not a valid fallback font name"):
+ mpl.rcParams['mathtext.fallback'] = fallback
+
+
+@pytest.mark.parametrize(
+ "fallback,fontlist",
+ [("cm", ['DejaVu Sans', 'mpltest', 'STIXGeneral', 'cmr10', 'STIXGeneral']),
+ ("stix", ['DejaVu Sans', 'mpltest', 'STIXGeneral', 'STIXGeneral', 'STIXGeneral'])])
+def test_mathtext_fallback(fallback, fontlist):
+ mpl.font_manager.fontManager.addfont(
+ str(Path(__file__).resolve().parent / 'mpltest.ttf'))
+ mpl.rcParams["svg.fonttype"] = 'none'
+ mpl.rcParams['mathtext.fontset'] = 'custom'
+ mpl.rcParams['mathtext.rm'] = 'mpltest'
+ mpl.rcParams['mathtext.it'] = 'mpltest:italic'
+ mpl.rcParams['mathtext.bf'] = 'mpltest:bold'
+ mpl.rcParams['mathtext.bfit'] = 'mpltest:italic:bold'
+ mpl.rcParams['mathtext.fallback'] = fallback
+
+ test_str = r'a$A\AA\breve\gimel$'
+
+ buff = io.BytesIO()
+ fig, ax = plt.subplots()
+ fig.text(.5, .5, test_str, fontsize=40, ha='center')
+ fig.savefig(buff, format="svg")
+ tspans = (ET.fromstring(buff.getvalue())
+ .findall(".//{http://www.w3.org/2000/svg}tspan[@style]"))
+ char_fonts = [
+ re.search(r"font-family: '([\w ]+)'", tspan.attrib["style"]).group(1)
+ for tspan in tspans]
+ assert char_fonts == fontlist, f'Expected {fontlist}, got {char_fonts}'
+ mpl.font_manager.fontManager.ttflist.pop()
+
+
+def test_math_to_image(tmp_path):
+ mathtext.math_to_image('$x^2$', tmp_path / 'example.png')
+ mathtext.math_to_image('$x^2$', io.BytesIO())
+ mathtext.math_to_image('$x^2$', io.BytesIO(), color='Maroon')
+
+
+@image_comparison(baseline_images=['math_fontfamily_image.png'],
+ savefig_kwarg={'dpi': 40})
+def test_math_fontfamily():
+ fig = plt.figure(figsize=(10, 3))
+ fig.text(0.2, 0.7, r"$This\ text\ should\ have\ one\ font$",
+ size=24, math_fontfamily='dejavusans')
+ fig.text(0.2, 0.3, r"$This\ text\ should\ have\ another$",
+ size=24, math_fontfamily='stix')
+
+
+def test_default_math_fontfamily():
+ mpl.rcParams['mathtext.fontset'] = 'cm'
+ test_str = r'abc$abc\alpha$'
+ fig, ax = plt.subplots()
+
+ text1 = fig.text(0.1, 0.1, test_str, font='Arial')
+ prop1 = text1.get_fontproperties()
+ assert prop1.get_math_fontfamily() == 'cm'
+ text2 = fig.text(0.2, 0.2, test_str, fontproperties='Arial')
+ prop2 = text2.get_fontproperties()
+ assert prop2.get_math_fontfamily() == 'cm'
+
+ fig.draw_without_rendering()
+
+
+def test_argument_order():
+ mpl.rcParams['mathtext.fontset'] = 'cm'
+ test_str = r'abc$abc\alpha$'
+ fig, ax = plt.subplots()
+
+ text1 = fig.text(0.1, 0.1, test_str,
+ math_fontfamily='dejavusans', font='Arial')
+ prop1 = text1.get_fontproperties()
+ assert prop1.get_math_fontfamily() == 'dejavusans'
+ text2 = fig.text(0.2, 0.2, test_str,
+ math_fontfamily='dejavusans', fontproperties='Arial')
+ prop2 = text2.get_fontproperties()
+ assert prop2.get_math_fontfamily() == 'dejavusans'
+ text3 = fig.text(0.3, 0.3, test_str,
+ font='Arial', math_fontfamily='dejavusans')
+ prop3 = text3.get_fontproperties()
+ assert prop3.get_math_fontfamily() == 'dejavusans'
+ text4 = fig.text(0.4, 0.4, test_str,
+ fontproperties='Arial', math_fontfamily='dejavusans')
+ prop4 = text4.get_fontproperties()
+ assert prop4.get_math_fontfamily() == 'dejavusans'
+
+ fig.draw_without_rendering()
+
+
+def test_mathtext_cmr10_minus_sign():
+ # cmr10 does not contain a minus sign and used to issue a warning
+ # RuntimeWarning: Glyph 8722 missing from current font.
+ mpl.rcParams['font.family'] = 'cmr10'
+ mpl.rcParams['axes.formatter.use_mathtext'] = True
+ fig, ax = plt.subplots()
+ ax.plot(range(-1, 1), range(-1, 1))
+ # draw to make sure we have no warnings
+ fig.canvas.draw()
+
+
+def test_mathtext_operators():
+ test_str = r'''
+ \increment \smallin \notsmallowns
+ \smallowns \QED \rightangle
+ \smallintclockwise \smallvarointclockwise
+ \smallointctrcclockwise
+ \ratio \minuscolon \dotsminusdots
+ \sinewave \simneqq \nlesssim
+ \ngtrsim \nlessgtr \ngtrless
+ \cupleftarrow \oequal \rightassert
+ \rightModels \hermitmatrix \barvee
+ \measuredrightangle \varlrtriangle
+ \equalparallel \npreccurlyeq \nsucccurlyeq
+ \nsqsubseteq \nsqsupseteq \sqsubsetneq
+ \sqsupsetneq \disin \varisins
+ \isins \isindot \varisinobar
+ \isinobar \isinvb \isinE
+ \nisd \varnis \nis
+ \varniobar \niobar \bagmember
+ \triangle'''.split()
+
+ fig = plt.figure()
+ for x, i in enumerate(test_str):
+ fig.text(0.5, (x + 0.5)/len(test_str), r'${%s}$' % i)
+
+ fig.draw_without_rendering()
+
+
+@check_figures_equal(extensions=["png"])
+def test_boldsymbol(fig_test, fig_ref):
+ fig_test.text(0.1, 0.2, r"$\boldsymbol{\mathrm{abc0123\alpha}}$")
+ fig_ref.text(0.1, 0.2, r"$\mathrm{abc0123\alpha}$")
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_matplotlib.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_matplotlib.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0a3f8c617e1542ba145a731ef726809435e00dd
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_matplotlib.py
@@ -0,0 +1,96 @@
+import os
+import subprocess
+import sys
+from unittest.mock import patch
+
+import pytest
+
+import matplotlib
+from matplotlib.testing import subprocess_run_for_testing
+
+
+@pytest.mark.parametrize('version_str, version_tuple', [
+ ('3.5.0', (3, 5, 0, 'final', 0)),
+ ('3.5.0rc2', (3, 5, 0, 'candidate', 2)),
+ ('3.5.0.dev820+g6768ef8c4c', (3, 5, 0, 'alpha', 820)),
+ ('3.5.0.post820+g6768ef8c4c', (3, 5, 1, 'alpha', 820)),
+])
+def test_parse_to_version_info(version_str, version_tuple):
+ assert matplotlib._parse_to_version_info(version_str) == version_tuple
+
+
+@pytest.mark.skipif(sys.platform == "win32",
+ reason="chmod() doesn't work as is on Windows")
+@pytest.mark.skipif(sys.platform != "win32" and os.geteuid() == 0,
+ reason="chmod() doesn't work as root")
+def test_tmpconfigdir_warning(tmp_path):
+ """Test that a warning is emitted if a temporary configdir must be used."""
+ mode = os.stat(tmp_path).st_mode
+ try:
+ os.chmod(tmp_path, 0)
+ proc = subprocess_run_for_testing(
+ [sys.executable, "-c", "import matplotlib"],
+ env={**os.environ, "MPLCONFIGDIR": str(tmp_path)},
+ stderr=subprocess.PIPE, text=True, check=True)
+ assert "set the MPLCONFIGDIR" in proc.stderr
+ finally:
+ os.chmod(tmp_path, mode)
+
+
+def test_importable_with_no_home(tmp_path):
+ subprocess_run_for_testing(
+ [sys.executable, "-c",
+ "import pathlib; pathlib.Path.home = lambda *args: 1/0; "
+ "import matplotlib.pyplot"],
+ env={**os.environ, "MPLCONFIGDIR": str(tmp_path)}, check=True)
+
+
+def test_use_doc_standard_backends():
+ """
+ Test that the standard backends mentioned in the docstring of
+ matplotlib.use() are the same as in matplotlib.rcsetup.
+ """
+ def parse(key):
+ backends = []
+ for line in matplotlib.use.__doc__.split(key)[1].split('\n'):
+ if not line.strip():
+ break
+ backends += [e.strip().lower() for e in line.split(',') if e]
+ return backends
+
+ from matplotlib.backends import BackendFilter, backend_registry
+
+ assert (set(parse('- interactive backends:\n')) ==
+ set(backend_registry.list_builtin(BackendFilter.INTERACTIVE)))
+ assert (set(parse('- non-interactive backends:\n')) ==
+ set(backend_registry.list_builtin(BackendFilter.NON_INTERACTIVE)))
+
+
+def test_importable_with__OO():
+ """
+ When using -OO or export PYTHONOPTIMIZE=2, docstrings are discarded,
+ this simple test may prevent something like issue #17970.
+ """
+ program = (
+ "import matplotlib as mpl; "
+ "import matplotlib.pyplot as plt; "
+ "import matplotlib.cbook as cbook; "
+ "import matplotlib.patches as mpatches"
+ )
+ subprocess_run_for_testing(
+ [sys.executable, "-OO", "-c", program],
+ env={**os.environ, "MPLBACKEND": ""}, check=True
+ )
+
+
+@patch('matplotlib.subprocess.check_output')
+def test_get_executable_info_timeout(mock_check_output):
+ """
+ Test that _get_executable_info raises ExecutableNotFoundError if the
+ command times out.
+ """
+
+ mock_check_output.side_effect = subprocess.TimeoutExpired(cmd=['mock'], timeout=30)
+
+ with pytest.raises(matplotlib.ExecutableNotFoundError, match='Timed out'):
+ matplotlib._get_executable_info.__wrapped__('inkscape')
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_mlab.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_mlab.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b0d2529b5f10fb68fd8a634fab4615f78fe3520
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_mlab.py
@@ -0,0 +1,1022 @@
+from numpy.testing import (assert_allclose, assert_almost_equal,
+ assert_array_equal, assert_array_almost_equal_nulp)
+import numpy as np
+import pytest
+
+from matplotlib import mlab
+
+
+def test_window():
+ np.random.seed(0)
+ n = 1000
+ rand = np.random.standard_normal(n) + 100
+ ones = np.ones(n)
+ assert_array_equal(mlab.window_none(ones), ones)
+ assert_array_equal(mlab.window_none(rand), rand)
+ assert_array_equal(np.hanning(len(rand)) * rand, mlab.window_hanning(rand))
+ assert_array_equal(np.hanning(len(ones)), mlab.window_hanning(ones))
+
+
+class TestDetrend:
+ def setup_method(self):
+ np.random.seed(0)
+ n = 1000
+ x = np.linspace(0., 100, n)
+
+ self.sig_zeros = np.zeros(n)
+
+ self.sig_off = self.sig_zeros + 100.
+ self.sig_slope = np.linspace(-10., 90., n)
+ self.sig_slope_mean = x - x.mean()
+
+ self.sig_base = (
+ np.random.standard_normal(n) + np.sin(x*2*np.pi/(n/100)))
+ self.sig_base -= self.sig_base.mean()
+
+ def allclose(self, *args):
+ assert_allclose(*args, atol=1e-8)
+
+ def test_detrend_none(self):
+ assert mlab.detrend_none(0.) == 0.
+ assert mlab.detrend_none(0., axis=1) == 0.
+ assert mlab.detrend(0., key="none") == 0.
+ assert mlab.detrend(0., key=mlab.detrend_none) == 0.
+ for sig in [
+ 5.5, self.sig_off, self.sig_slope, self.sig_base,
+ (self.sig_base + self.sig_slope + self.sig_off).tolist(),
+ np.vstack([self.sig_base, # 2D case.
+ self.sig_base + self.sig_off,
+ self.sig_base + self.sig_slope,
+ self.sig_base + self.sig_off + self.sig_slope]),
+ np.vstack([self.sig_base, # 2D transposed case.
+ self.sig_base + self.sig_off,
+ self.sig_base + self.sig_slope,
+ self.sig_base + self.sig_off + self.sig_slope]).T,
+ ]:
+ if isinstance(sig, np.ndarray):
+ assert_array_equal(mlab.detrend_none(sig), sig)
+ else:
+ assert mlab.detrend_none(sig) == sig
+
+ def test_detrend_mean(self):
+ for sig in [0., 5.5]: # 0D.
+ assert mlab.detrend_mean(sig) == 0.
+ assert mlab.detrend(sig, key="mean") == 0.
+ assert mlab.detrend(sig, key=mlab.detrend_mean) == 0.
+ # 1D.
+ self.allclose(mlab.detrend_mean(self.sig_zeros), self.sig_zeros)
+ self.allclose(mlab.detrend_mean(self.sig_base), self.sig_base)
+ self.allclose(mlab.detrend_mean(self.sig_base + self.sig_off),
+ self.sig_base)
+ self.allclose(mlab.detrend_mean(self.sig_base + self.sig_slope),
+ self.sig_base + self.sig_slope_mean)
+ self.allclose(
+ mlab.detrend_mean(self.sig_base + self.sig_slope + self.sig_off),
+ self.sig_base + self.sig_slope_mean)
+
+ def test_detrend_mean_1d_base_slope_off_list_andor_axis0(self):
+ input = self.sig_base + self.sig_slope + self.sig_off
+ target = self.sig_base + self.sig_slope_mean
+ self.allclose(mlab.detrend_mean(input, axis=0), target)
+ self.allclose(mlab.detrend_mean(input.tolist()), target)
+ self.allclose(mlab.detrend_mean(input.tolist(), axis=0), target)
+
+ def test_detrend_mean_2d(self):
+ input = np.vstack([self.sig_off,
+ self.sig_base + self.sig_off])
+ target = np.vstack([self.sig_zeros,
+ self.sig_base])
+ self.allclose(mlab.detrend_mean(input), target)
+ self.allclose(mlab.detrend_mean(input, axis=None), target)
+ self.allclose(mlab.detrend_mean(input.T, axis=None).T, target)
+ self.allclose(mlab.detrend(input), target)
+ self.allclose(mlab.detrend(input, axis=None), target)
+ self.allclose(
+ mlab.detrend(input.T, key="constant", axis=None), target.T)
+
+ input = np.vstack([self.sig_base,
+ self.sig_base + self.sig_off,
+ self.sig_base + self.sig_slope,
+ self.sig_base + self.sig_off + self.sig_slope])
+ target = np.vstack([self.sig_base,
+ self.sig_base,
+ self.sig_base + self.sig_slope_mean,
+ self.sig_base + self.sig_slope_mean])
+ self.allclose(mlab.detrend_mean(input.T, axis=0), target.T)
+ self.allclose(mlab.detrend_mean(input, axis=1), target)
+ self.allclose(mlab.detrend_mean(input, axis=-1), target)
+ self.allclose(mlab.detrend(input, key="default", axis=1), target)
+ self.allclose(mlab.detrend(input.T, key="mean", axis=0), target.T)
+ self.allclose(
+ mlab.detrend(input.T, key=mlab.detrend_mean, axis=0), target.T)
+
+ def test_detrend_ValueError(self):
+ for signal, kwargs in [
+ (self.sig_slope[np.newaxis], {"key": "spam"}),
+ (self.sig_slope[np.newaxis], {"key": 5}),
+ (5.5, {"axis": 0}),
+ (self.sig_slope, {"axis": 1}),
+ (self.sig_slope[np.newaxis], {"axis": 2}),
+ ]:
+ with pytest.raises(ValueError):
+ mlab.detrend(signal, **kwargs)
+
+ def test_detrend_mean_ValueError(self):
+ for signal, kwargs in [
+ (5.5, {"axis": 0}),
+ (self.sig_slope, {"axis": 1}),
+ (self.sig_slope[np.newaxis], {"axis": 2}),
+ ]:
+ with pytest.raises(ValueError):
+ mlab.detrend_mean(signal, **kwargs)
+
+ def test_detrend_linear(self):
+ # 0D.
+ assert mlab.detrend_linear(0.) == 0.
+ assert mlab.detrend_linear(5.5) == 0.
+ assert mlab.detrend(5.5, key="linear") == 0.
+ assert mlab.detrend(5.5, key=mlab.detrend_linear) == 0.
+ for sig in [ # 1D.
+ self.sig_off,
+ self.sig_slope,
+ self.sig_slope + self.sig_off,
+ ]:
+ self.allclose(mlab.detrend_linear(sig), self.sig_zeros)
+
+ def test_detrend_str_linear_1d(self):
+ input = self.sig_slope + self.sig_off
+ target = self.sig_zeros
+ self.allclose(mlab.detrend(input, key="linear"), target)
+ self.allclose(mlab.detrend(input, key=mlab.detrend_linear), target)
+ self.allclose(mlab.detrend_linear(input.tolist()), target)
+
+ def test_detrend_linear_2d(self):
+ input = np.vstack([self.sig_off,
+ self.sig_slope,
+ self.sig_slope + self.sig_off])
+ target = np.vstack([self.sig_zeros,
+ self.sig_zeros,
+ self.sig_zeros])
+ self.allclose(
+ mlab.detrend(input.T, key="linear", axis=0), target.T)
+ self.allclose(
+ mlab.detrend(input.T, key=mlab.detrend_linear, axis=0), target.T)
+ self.allclose(
+ mlab.detrend(input, key="linear", axis=1), target)
+ self.allclose(
+ mlab.detrend(input, key=mlab.detrend_linear, axis=1), target)
+
+ with pytest.raises(ValueError):
+ mlab.detrend_linear(self.sig_slope[np.newaxis])
+
+
+@pytest.mark.parametrize('iscomplex', [False, True],
+ ids=['real', 'complex'], scope='class')
+@pytest.mark.parametrize('sides', ['onesided', 'twosided', 'default'],
+ scope='class')
+@pytest.mark.parametrize(
+ 'fstims,len_x,NFFT_density,nover_density,pad_to_density,pad_to_spectrum',
+ [
+ ([], None, -1, -1, -1, -1),
+ ([4], None, -1, -1, -1, -1),
+ ([4, 5, 10], None, -1, -1, -1, -1),
+ ([], None, None, -1, -1, None),
+ ([], None, -1, -1, None, None),
+ ([], None, None, -1, None, None),
+ ([], 1024, 512, -1, -1, 128),
+ ([], 256, -1, -1, 33, 257),
+ ([], 255, 33, -1, -1, None),
+ ([], 256, 128, -1, 256, 256),
+ ([], None, -1, 32, -1, -1),
+ ],
+ ids=[
+ 'nosig',
+ 'Fs4',
+ 'FsAll',
+ 'nosig_noNFFT',
+ 'nosig_nopad_to',
+ 'nosig_noNFFT_no_pad_to',
+ 'nosig_trim',
+ 'nosig_odd',
+ 'nosig_oddlen',
+ 'nosig_stretch',
+ 'nosig_overlap',
+ ],
+ scope='class')
+class TestSpectral:
+ @pytest.fixture(scope='class', autouse=True)
+ def stim(self, request, fstims, iscomplex, sides, len_x, NFFT_density,
+ nover_density, pad_to_density, pad_to_spectrum):
+ Fs = 100.
+
+ x = np.arange(0, 10, 1 / Fs)
+ if len_x is not None:
+ x = x[:len_x]
+
+ # get the stimulus frequencies, defaulting to None
+ fstims = [Fs / fstim for fstim in fstims]
+
+ # get the constants, default to calculated values
+ if NFFT_density is None:
+ NFFT_density_real = 256
+ elif NFFT_density < 0:
+ NFFT_density_real = NFFT_density = 100
+ else:
+ NFFT_density_real = NFFT_density
+
+ if nover_density is None:
+ nover_density_real = 0
+ elif nover_density < 0:
+ nover_density_real = nover_density = NFFT_density_real // 2
+ else:
+ nover_density_real = nover_density
+
+ if pad_to_density is None:
+ pad_to_density_real = NFFT_density_real
+ elif pad_to_density < 0:
+ pad_to_density = int(2**np.ceil(np.log2(NFFT_density_real)))
+ pad_to_density_real = pad_to_density
+ else:
+ pad_to_density_real = pad_to_density
+
+ if pad_to_spectrum is None:
+ pad_to_spectrum_real = len(x)
+ elif pad_to_spectrum < 0:
+ pad_to_spectrum_real = pad_to_spectrum = len(x)
+ else:
+ pad_to_spectrum_real = pad_to_spectrum
+
+ if pad_to_spectrum is None:
+ NFFT_spectrum_real = NFFT_spectrum = pad_to_spectrum_real
+ else:
+ NFFT_spectrum_real = NFFT_spectrum = len(x)
+ nover_spectrum = 0
+
+ NFFT_specgram = NFFT_density
+ nover_specgram = nover_density
+ pad_to_specgram = pad_to_density
+ NFFT_specgram_real = NFFT_density_real
+ nover_specgram_real = nover_density_real
+
+ if sides == 'onesided' or (sides == 'default' and not iscomplex):
+ # frequencies for specgram, psd, and csd
+ # need to handle even and odd differently
+ if pad_to_density_real % 2:
+ freqs_density = np.linspace(0, Fs / 2,
+ num=pad_to_density_real,
+ endpoint=False)[::2]
+ else:
+ freqs_density = np.linspace(0, Fs / 2,
+ num=pad_to_density_real // 2 + 1)
+
+ # frequencies for complex, magnitude, angle, and phase spectrums
+ # need to handle even and odd differently
+ if pad_to_spectrum_real % 2:
+ freqs_spectrum = np.linspace(0, Fs / 2,
+ num=pad_to_spectrum_real,
+ endpoint=False)[::2]
+ else:
+ freqs_spectrum = np.linspace(0, Fs / 2,
+ num=pad_to_spectrum_real // 2 + 1)
+ else:
+ # frequencies for specgram, psd, and csd
+ # need to handle even and odd differently
+ if pad_to_density_real % 2:
+ freqs_density = np.linspace(-Fs / 2, Fs / 2,
+ num=2 * pad_to_density_real,
+ endpoint=False)[1::2]
+ else:
+ freqs_density = np.linspace(-Fs / 2, Fs / 2,
+ num=pad_to_density_real,
+ endpoint=False)
+
+ # frequencies for complex, magnitude, angle, and phase spectrums
+ # need to handle even and odd differently
+ if pad_to_spectrum_real % 2:
+ freqs_spectrum = np.linspace(-Fs / 2, Fs / 2,
+ num=2 * pad_to_spectrum_real,
+ endpoint=False)[1::2]
+ else:
+ freqs_spectrum = np.linspace(-Fs / 2, Fs / 2,
+ num=pad_to_spectrum_real,
+ endpoint=False)
+
+ freqs_specgram = freqs_density
+ # time points for specgram
+ t_start = NFFT_specgram_real // 2
+ t_stop = len(x) - NFFT_specgram_real // 2 + 1
+ t_step = NFFT_specgram_real - nover_specgram_real
+ t_specgram = x[t_start:t_stop:t_step]
+ if NFFT_specgram_real % 2:
+ t_specgram += 1 / Fs / 2
+ if len(t_specgram) == 0:
+ t_specgram = np.array([NFFT_specgram_real / (2 * Fs)])
+ t_spectrum = np.array([NFFT_spectrum_real / (2 * Fs)])
+ t_density = t_specgram
+
+ y = np.zeros_like(x)
+ for i, fstim in enumerate(fstims):
+ y += np.sin(fstim * x * np.pi * 2) * 10**i
+
+ if iscomplex:
+ y = y.astype('complex')
+
+ # Interestingly, the instance on which this fixture is called is not
+ # the same as the one on which a test is run. So we need to modify the
+ # class itself when using a class-scoped fixture.
+ cls = request.cls
+
+ cls.Fs = Fs
+ cls.sides = sides
+ cls.fstims = fstims
+
+ cls.NFFT_density = NFFT_density
+ cls.nover_density = nover_density
+ cls.pad_to_density = pad_to_density
+
+ cls.NFFT_spectrum = NFFT_spectrum
+ cls.nover_spectrum = nover_spectrum
+ cls.pad_to_spectrum = pad_to_spectrum
+
+ cls.NFFT_specgram = NFFT_specgram
+ cls.nover_specgram = nover_specgram
+ cls.pad_to_specgram = pad_to_specgram
+
+ cls.t_specgram = t_specgram
+ cls.t_density = t_density
+ cls.t_spectrum = t_spectrum
+ cls.y = y
+
+ cls.freqs_density = freqs_density
+ cls.freqs_spectrum = freqs_spectrum
+ cls.freqs_specgram = freqs_specgram
+
+ cls.NFFT_density_real = NFFT_density_real
+
+ def check_freqs(self, vals, targfreqs, resfreqs, fstims):
+ assert resfreqs.argmin() == 0
+ assert resfreqs.argmax() == len(resfreqs)-1
+ assert_allclose(resfreqs, targfreqs, atol=1e-06)
+ for fstim in fstims:
+ i = np.abs(resfreqs - fstim).argmin()
+ assert vals[i] > vals[i+2]
+ assert vals[i] > vals[i-2]
+
+ def check_maxfreq(self, spec, fsp, fstims):
+ # skip the test if there are no frequencies
+ if len(fstims) == 0:
+ return
+
+ # if twosided, do the test for each side
+ if fsp.min() < 0:
+ fspa = np.abs(fsp)
+ zeroind = fspa.argmin()
+ self.check_maxfreq(spec[:zeroind], fspa[:zeroind], fstims)
+ self.check_maxfreq(spec[zeroind:], fspa[zeroind:], fstims)
+ return
+
+ fstimst = fstims[:]
+ spect = spec.copy()
+
+ # go through each peak and make sure it is correctly the maximum peak
+ while fstimst:
+ maxind = spect.argmax()
+ maxfreq = fsp[maxind]
+ assert_almost_equal(maxfreq, fstimst[-1])
+ del fstimst[-1]
+ spect[maxind-5:maxind+5] = 0
+
+ def test_spectral_helper_raises(self):
+ # We don't use parametrize here to handle ``y = self.y``.
+ for kwargs in [ # Various error conditions:
+ {"y": self.y+1, "mode": "complex"}, # Modes requiring ``x is y``.
+ {"y": self.y+1, "mode": "magnitude"},
+ {"y": self.y+1, "mode": "angle"},
+ {"y": self.y+1, "mode": "phase"},
+ {"mode": "spam"}, # Bad mode.
+ {"y": self.y, "sides": "eggs"}, # Bad sides.
+ {"y": self.y, "NFFT": 10, "noverlap": 20}, # noverlap > NFFT.
+ {"NFFT": 10, "noverlap": 10}, # noverlap == NFFT.
+ {"y": self.y, "NFFT": 10,
+ "window": np.ones(9)}, # len(win) != NFFT.
+ ]:
+ with pytest.raises(ValueError):
+ mlab._spectral_helper(x=self.y, **kwargs)
+
+ @pytest.mark.parametrize('mode', ['default', 'psd'])
+ def test_single_spectrum_helper_unsupported_modes(self, mode):
+ with pytest.raises(ValueError):
+ mlab._single_spectrum_helper(x=self.y, mode=mode)
+
+ @pytest.mark.parametrize("mode, case", [
+ ("psd", "density"),
+ ("magnitude", "specgram"),
+ ("magnitude", "spectrum"),
+ ])
+ def test_spectral_helper_psd(self, mode, case):
+ freqs = getattr(self, f"freqs_{case}")
+ spec, fsp, t = mlab._spectral_helper(
+ x=self.y, y=self.y,
+ NFFT=getattr(self, f"NFFT_{case}"),
+ Fs=self.Fs,
+ noverlap=getattr(self, f"nover_{case}"),
+ pad_to=getattr(self, f"pad_to_{case}"),
+ sides=self.sides,
+ mode=mode)
+
+ assert_allclose(fsp, freqs, atol=1e-06)
+ assert_allclose(t, getattr(self, f"t_{case}"), atol=1e-06)
+ assert spec.shape[0] == freqs.shape[0]
+ assert spec.shape[1] == getattr(self, f"t_{case}").shape[0]
+
+ def test_csd(self):
+ freqs = self.freqs_density
+ spec, fsp = mlab.csd(x=self.y, y=self.y+1,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides)
+ assert_allclose(fsp, freqs, atol=1e-06)
+ assert spec.shape == freqs.shape
+
+ def test_csd_padding(self):
+ """Test zero padding of csd()."""
+ if self.NFFT_density is None: # for derived classes
+ return
+ sargs = dict(x=self.y, y=self.y+1, Fs=self.Fs, window=mlab.window_none,
+ sides=self.sides)
+
+ spec0, _ = mlab.csd(NFFT=self.NFFT_density, **sargs)
+ spec1, _ = mlab.csd(NFFT=self.NFFT_density*2, **sargs)
+ assert_almost_equal(np.sum(np.conjugate(spec0)*spec0).real,
+ np.sum(np.conjugate(spec1/2)*spec1/2).real)
+
+ def test_psd(self):
+ freqs = self.freqs_density
+ spec, fsp = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides)
+ assert spec.shape == freqs.shape
+ self.check_freqs(spec, freqs, fsp, self.fstims)
+
+ @pytest.mark.parametrize(
+ 'make_data, detrend',
+ [(np.zeros, mlab.detrend_mean), (np.zeros, 'mean'),
+ (np.arange, mlab.detrend_linear), (np.arange, 'linear')])
+ def test_psd_detrend(self, make_data, detrend):
+ if self.NFFT_density is None:
+ return
+ ydata = make_data(self.NFFT_density)
+ ydata1 = ydata+5
+ ydata2 = ydata+3.3
+ ydata = np.vstack([ydata1, ydata2])
+ ydata = np.tile(ydata, (20, 1))
+ ydatab = ydata.T.flatten()
+ ydata = ydata.flatten()
+ ycontrol = np.zeros_like(ydata)
+ spec_g, fsp_g = mlab.psd(x=ydata,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ detrend=detrend)
+ spec_b, fsp_b = mlab.psd(x=ydatab,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ detrend=detrend)
+ spec_c, fsp_c = mlab.psd(x=ycontrol,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides)
+ assert_array_equal(fsp_g, fsp_c)
+ assert_array_equal(fsp_b, fsp_c)
+ assert_allclose(spec_g, spec_c, atol=1e-08)
+ # these should not be almost equal
+ with pytest.raises(AssertionError):
+ assert_allclose(spec_b, spec_c, atol=1e-08)
+
+ def test_psd_window_hanning(self):
+ if self.NFFT_density is None:
+ return
+ ydata = np.arange(self.NFFT_density)
+ ydata1 = ydata+5
+ ydata2 = ydata+3.3
+ windowVals = mlab.window_hanning(np.ones_like(ydata1))
+ ycontrol1 = ydata1 * windowVals
+ ycontrol2 = mlab.window_hanning(ydata2)
+ ydata = np.vstack([ydata1, ydata2])
+ ycontrol = np.vstack([ycontrol1, ycontrol2])
+ ydata = np.tile(ydata, (20, 1))
+ ycontrol = np.tile(ycontrol, (20, 1))
+ ydatab = ydata.T.flatten()
+ ydataf = ydata.flatten()
+ ycontrol = ycontrol.flatten()
+ spec_g, fsp_g = mlab.psd(x=ydataf,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ window=mlab.window_hanning)
+ spec_b, fsp_b = mlab.psd(x=ydatab,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ window=mlab.window_hanning)
+ spec_c, fsp_c = mlab.psd(x=ycontrol,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ window=mlab.window_none)
+ spec_c *= len(ycontrol1)/(windowVals**2).sum()
+ assert_array_equal(fsp_g, fsp_c)
+ assert_array_equal(fsp_b, fsp_c)
+ assert_allclose(spec_g, spec_c, atol=1e-08)
+ # these should not be almost equal
+ with pytest.raises(AssertionError):
+ assert_allclose(spec_b, spec_c, atol=1e-08)
+
+ def test_psd_window_hanning_detrend_linear(self):
+ if self.NFFT_density is None:
+ return
+ ydata = np.arange(self.NFFT_density)
+ ycontrol = np.zeros(self.NFFT_density)
+ ydata1 = ydata+5
+ ydata2 = ydata+3.3
+ ycontrol1 = ycontrol
+ ycontrol2 = ycontrol
+ windowVals = mlab.window_hanning(np.ones_like(ycontrol1))
+ ycontrol1 = ycontrol1 * windowVals
+ ycontrol2 = mlab.window_hanning(ycontrol2)
+ ydata = np.vstack([ydata1, ydata2])
+ ycontrol = np.vstack([ycontrol1, ycontrol2])
+ ydata = np.tile(ydata, (20, 1))
+ ycontrol = np.tile(ycontrol, (20, 1))
+ ydatab = ydata.T.flatten()
+ ydataf = ydata.flatten()
+ ycontrol = ycontrol.flatten()
+ spec_g, fsp_g = mlab.psd(x=ydataf,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ detrend=mlab.detrend_linear,
+ window=mlab.window_hanning)
+ spec_b, fsp_b = mlab.psd(x=ydatab,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ detrend=mlab.detrend_linear,
+ window=mlab.window_hanning)
+ spec_c, fsp_c = mlab.psd(x=ycontrol,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ window=mlab.window_none)
+ spec_c *= len(ycontrol1)/(windowVals**2).sum()
+ assert_array_equal(fsp_g, fsp_c)
+ assert_array_equal(fsp_b, fsp_c)
+ assert_allclose(spec_g, spec_c, atol=1e-08)
+ # these should not be almost equal
+ with pytest.raises(AssertionError):
+ assert_allclose(spec_b, spec_c, atol=1e-08)
+
+ def test_psd_window_flattop(self):
+ # flattop window
+ # adaption from https://github.com/scipy/scipy/blob\
+ # /v1.10.0/scipy/signal/windows/_windows.py#L562-L622
+ a = [0.21557895, 0.41663158, 0.277263158, 0.083578947, 0.006947368]
+ fac = np.linspace(-np.pi, np.pi, self.NFFT_density_real)
+ win = np.zeros(self.NFFT_density_real)
+ for k in range(len(a)):
+ win += a[k] * np.cos(k * fac)
+
+ spec, fsp = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ window=win,
+ scale_by_freq=False)
+ spec_a, fsp_a = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=0,
+ sides=self.sides,
+ window=win)
+ assert_allclose(spec*win.sum()**2,
+ spec_a*self.Fs*(win**2).sum(),
+ atol=1e-08)
+
+ def test_psd_windowarray(self):
+ freqs = self.freqs_density
+ spec, fsp = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides,
+ window=np.ones(self.NFFT_density_real))
+ assert_allclose(fsp, freqs, atol=1e-06)
+ assert spec.shape == freqs.shape
+
+ def test_psd_windowarray_scale_by_freq(self):
+ win = mlab.window_hanning(np.ones(self.NFFT_density_real))
+
+ spec, fsp = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides,
+ window=mlab.window_hanning)
+ spec_s, fsp_s = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides,
+ window=mlab.window_hanning,
+ scale_by_freq=True)
+ spec_n, fsp_n = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides,
+ window=mlab.window_hanning,
+ scale_by_freq=False)
+ assert_array_equal(fsp, fsp_s)
+ assert_array_equal(fsp, fsp_n)
+ assert_array_equal(spec, spec_s)
+ assert_allclose(spec_s*(win**2).sum(),
+ spec_n/self.Fs*win.sum()**2,
+ atol=1e-08)
+
+ @pytest.mark.parametrize(
+ "kind", ["complex", "magnitude", "angle", "phase"])
+ def test_spectrum(self, kind):
+ freqs = self.freqs_spectrum
+ spec, fsp = getattr(mlab, f"{kind}_spectrum")(
+ x=self.y,
+ Fs=self.Fs, sides=self.sides, pad_to=self.pad_to_spectrum)
+ assert_allclose(fsp, freqs, atol=1e-06)
+ assert spec.shape == freqs.shape
+ if kind == "magnitude":
+ self.check_maxfreq(spec, fsp, self.fstims)
+ self.check_freqs(spec, freqs, fsp, self.fstims)
+
+ @pytest.mark.parametrize(
+ 'kwargs',
+ [{}, {'mode': 'default'}, {'mode': 'psd'}, {'mode': 'magnitude'},
+ {'mode': 'complex'}, {'mode': 'angle'}, {'mode': 'phase'}])
+ def test_specgram(self, kwargs):
+ freqs = self.freqs_specgram
+ spec, fsp, t = mlab.specgram(x=self.y,
+ NFFT=self.NFFT_specgram,
+ Fs=self.Fs,
+ noverlap=self.nover_specgram,
+ pad_to=self.pad_to_specgram,
+ sides=self.sides,
+ **kwargs)
+ if kwargs.get('mode') == 'complex':
+ spec = np.abs(spec)
+ specm = np.mean(spec, axis=1)
+
+ assert_allclose(fsp, freqs, atol=1e-06)
+ assert_allclose(t, self.t_specgram, atol=1e-06)
+
+ assert spec.shape[0] == freqs.shape[0]
+ assert spec.shape[1] == self.t_specgram.shape[0]
+
+ if kwargs.get('mode') not in ['complex', 'angle', 'phase']:
+ # using a single freq, so all time slices should be about the same
+ if np.abs(spec.max()) != 0:
+ assert_allclose(
+ np.diff(spec, axis=1).max() / np.abs(spec.max()), 0,
+ atol=1e-02)
+ if kwargs.get('mode') not in ['angle', 'phase']:
+ self.check_freqs(specm, freqs, fsp, self.fstims)
+
+ def test_specgram_warn_only1seg(self):
+ """Warning should be raised if len(x) <= NFFT."""
+ with pytest.warns(UserWarning, match="Only one segment is calculated"):
+ mlab.specgram(x=self.y, NFFT=len(self.y), Fs=self.Fs)
+
+ def test_psd_csd_equal(self):
+ Pxx, freqsxx = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides)
+ Pxy, freqsxy = mlab.csd(x=self.y, y=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides)
+ assert_array_almost_equal_nulp(Pxx, Pxy)
+ assert_array_equal(freqsxx, freqsxy)
+
+ @pytest.mark.parametrize("mode", ["default", "psd"])
+ def test_specgram_auto_default_psd_equal(self, mode):
+ """
+ Test that mlab.specgram without mode and with mode 'default' and 'psd'
+ are all the same.
+ """
+ speca, freqspeca, ta = mlab.specgram(x=self.y,
+ NFFT=self.NFFT_specgram,
+ Fs=self.Fs,
+ noverlap=self.nover_specgram,
+ pad_to=self.pad_to_specgram,
+ sides=self.sides)
+ specb, freqspecb, tb = mlab.specgram(x=self.y,
+ NFFT=self.NFFT_specgram,
+ Fs=self.Fs,
+ noverlap=self.nover_specgram,
+ pad_to=self.pad_to_specgram,
+ sides=self.sides,
+ mode=mode)
+ assert_array_equal(speca, specb)
+ assert_array_equal(freqspeca, freqspecb)
+ assert_array_equal(ta, tb)
+
+ @pytest.mark.parametrize(
+ "mode, conv", [
+ ("magnitude", np.abs),
+ ("angle", np.angle),
+ ("phase", lambda x: np.unwrap(np.angle(x), axis=0))
+ ])
+ def test_specgram_complex_equivalent(self, mode, conv):
+ specc, freqspecc, tc = mlab.specgram(x=self.y,
+ NFFT=self.NFFT_specgram,
+ Fs=self.Fs,
+ noverlap=self.nover_specgram,
+ pad_to=self.pad_to_specgram,
+ sides=self.sides,
+ mode='complex')
+ specm, freqspecm, tm = mlab.specgram(x=self.y,
+ NFFT=self.NFFT_specgram,
+ Fs=self.Fs,
+ noverlap=self.nover_specgram,
+ pad_to=self.pad_to_specgram,
+ sides=self.sides,
+ mode=mode)
+
+ assert_array_equal(freqspecc, freqspecm)
+ assert_array_equal(tc, tm)
+ assert_allclose(conv(specc), specm, atol=1e-06)
+
+ def test_psd_windowarray_equal(self):
+ win = mlab.window_hanning(np.ones(self.NFFT_density_real))
+ speca, fspa = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides,
+ window=win)
+ specb, fspb = mlab.psd(x=self.y,
+ NFFT=self.NFFT_density,
+ Fs=self.Fs,
+ noverlap=self.nover_density,
+ pad_to=self.pad_to_density,
+ sides=self.sides)
+ assert_array_equal(fspa, fspb)
+ assert_allclose(speca, specb, atol=1e-08)
+
+
+# extra test for cohere...
+def test_cohere():
+ N = 1024
+ np.random.seed(19680801)
+ x = np.random.randn(N)
+ # phase offset
+ y = np.roll(x, 20)
+ # high-freq roll-off
+ y = np.convolve(y, np.ones(20) / 20., mode='same')
+ cohsq, f = mlab.cohere(x, y, NFFT=256, Fs=2, noverlap=128)
+ assert_allclose(np.mean(cohsq), 0.837, atol=1.e-3)
+ assert np.isreal(np.mean(cohsq))
+
+
+# *****************************************************************
+# These Tests were taken from SCIPY with some minor modifications
+# this can be retrieved from:
+# https://github.com/scipy/scipy/blob/master/scipy/stats/tests/test_kdeoth.py
+# *****************************************************************
+
+class TestGaussianKDE:
+
+ def test_kde_integer_input(self):
+ """Regression test for #1181."""
+ x1 = np.arange(5)
+ kde = mlab.GaussianKDE(x1)
+ y_expected = [0.13480721, 0.18222869, 0.19514935, 0.18222869,
+ 0.13480721]
+ np.testing.assert_array_almost_equal(kde(x1), y_expected, decimal=6)
+
+ def test_gaussian_kde_covariance_caching(self):
+ x1 = np.array([-7, -5, 1, 4, 5], dtype=float)
+ xs = np.linspace(-10, 10, num=5)
+ # These expected values are from scipy 0.10, before some changes to
+ # gaussian_kde. They were not compared with any external reference.
+ y_expected = [0.02463386, 0.04689208, 0.05395444, 0.05337754,
+ 0.01664475]
+
+ # set it to the default bandwidth.
+ kde2 = mlab.GaussianKDE(x1, 'scott')
+ y2 = kde2(xs)
+
+ np.testing.assert_array_almost_equal(y_expected, y2, decimal=7)
+
+ def test_kde_bandwidth_method(self):
+
+ np.random.seed(8765678)
+ n_basesample = 50
+ xn = np.random.randn(n_basesample)
+
+ # Default
+ gkde = mlab.GaussianKDE(xn)
+ # Supply a callable
+ gkde2 = mlab.GaussianKDE(xn, 'scott')
+ # Supply a scalar
+ gkde3 = mlab.GaussianKDE(xn, bw_method=gkde.factor)
+
+ xs = np.linspace(-7, 7, 51)
+ kdepdf = gkde.evaluate(xs)
+ kdepdf2 = gkde2.evaluate(xs)
+ assert kdepdf.all() == kdepdf2.all()
+ kdepdf3 = gkde3.evaluate(xs)
+ assert kdepdf.all() == kdepdf3.all()
+
+
+class TestGaussianKDECustom:
+ def test_no_data(self):
+ """Pass no data into the GaussianKDE class."""
+ with pytest.raises(ValueError):
+ mlab.GaussianKDE([])
+
+ def test_single_dataset_element(self):
+ """Pass a single dataset element into the GaussianKDE class."""
+ with pytest.raises(ValueError):
+ mlab.GaussianKDE([42])
+
+ def test_silverman_multidim_dataset(self):
+ """Test silverman's for a multi-dimensional array."""
+ x1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
+ with pytest.raises(np.linalg.LinAlgError):
+ mlab.GaussianKDE(x1, "silverman")
+
+ def test_silverman_singledim_dataset(self):
+ """Test silverman's output for a single dimension list."""
+ x1 = np.array([-7, -5, 1, 4, 5])
+ mygauss = mlab.GaussianKDE(x1, "silverman")
+ y_expected = 0.76770389927475502
+ assert_almost_equal(mygauss.covariance_factor(), y_expected, 7)
+
+ def test_scott_multidim_dataset(self):
+ """Test scott's output for a multi-dimensional array."""
+ x1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
+ with pytest.raises(np.linalg.LinAlgError):
+ mlab.GaussianKDE(x1, "scott")
+
+ def test_scott_singledim_dataset(self):
+ """Test scott's output a single-dimensional array."""
+ x1 = np.array([-7, -5, 1, 4, 5])
+ mygauss = mlab.GaussianKDE(x1, "scott")
+ y_expected = 0.72477966367769553
+ assert_almost_equal(mygauss.covariance_factor(), y_expected, 7)
+
+ def test_scalar_empty_dataset(self):
+ """Test the scalar's cov factor for an empty array."""
+ with pytest.raises(ValueError):
+ mlab.GaussianKDE([], bw_method=5)
+
+ def test_scalar_covariance_dataset(self):
+ """Test a scalar's cov factor."""
+ np.random.seed(8765678)
+ n_basesample = 50
+ multidim_data = [np.random.randn(n_basesample) for i in range(5)]
+
+ kde = mlab.GaussianKDE(multidim_data, bw_method=0.5)
+ assert kde.covariance_factor() == 0.5
+
+ def test_callable_covariance_dataset(self):
+ """Test the callable's cov factor for a multi-dimensional array."""
+ np.random.seed(8765678)
+ n_basesample = 50
+ multidim_data = [np.random.randn(n_basesample) for i in range(5)]
+
+ def callable_fun(x):
+ return 0.55
+ kde = mlab.GaussianKDE(multidim_data, bw_method=callable_fun)
+ assert kde.covariance_factor() == 0.55
+
+ def test_callable_singledim_dataset(self):
+ """Test the callable's cov factor for a single-dimensional array."""
+ np.random.seed(8765678)
+ n_basesample = 50
+ multidim_data = np.random.randn(n_basesample)
+
+ kde = mlab.GaussianKDE(multidim_data, bw_method='silverman')
+ y_expected = 0.48438841363348911
+ assert_almost_equal(kde.covariance_factor(), y_expected, 7)
+
+ def test_wrong_bw_method(self):
+ """Test the error message that should be called when bw is invalid."""
+ np.random.seed(8765678)
+ n_basesample = 50
+ data = np.random.randn(n_basesample)
+ with pytest.raises(ValueError):
+ mlab.GaussianKDE(data, bw_method="invalid")
+
+
+class TestGaussianKDEEvaluate:
+
+ def test_evaluate_diff_dim(self):
+ """
+ Test the evaluate method when the dim's of dataset and points have
+ different dimensions.
+ """
+ x1 = np.arange(3, 10, 2)
+ kde = mlab.GaussianKDE(x1)
+ x2 = np.arange(3, 12, 2)
+ y_expected = [
+ 0.08797252, 0.11774109, 0.11774109, 0.08797252, 0.0370153
+ ]
+ y = kde.evaluate(x2)
+ np.testing.assert_array_almost_equal(y, y_expected, 7)
+
+ def test_evaluate_inv_dim(self):
+ """
+ Invert the dimensions; i.e., for a dataset of dimension 1 [3, 2, 4],
+ the points should have a dimension of 3 [[3], [2], [4]].
+ """
+ np.random.seed(8765678)
+ n_basesample = 50
+ multidim_data = np.random.randn(n_basesample)
+ kde = mlab.GaussianKDE(multidim_data)
+ x2 = [[1], [2], [3]]
+ with pytest.raises(ValueError):
+ kde.evaluate(x2)
+
+ def test_evaluate_dim_and_num(self):
+ """Tests if evaluated against a one by one array"""
+ x1 = np.arange(3, 10, 2)
+ x2 = np.array([3])
+ kde = mlab.GaussianKDE(x1)
+ y_expected = [0.08797252]
+ y = kde.evaluate(x2)
+ np.testing.assert_array_almost_equal(y, y_expected, 7)
+
+ def test_evaluate_point_dim_not_one(self):
+ x1 = np.arange(3, 10, 2)
+ x2 = [np.arange(3, 10, 2), np.arange(3, 10, 2)]
+ kde = mlab.GaussianKDE(x1)
+ with pytest.raises(ValueError):
+ kde.evaluate(x2)
+
+ def test_evaluate_equal_dim_and_num_lt(self):
+ x1 = np.arange(3, 10, 2)
+ x2 = np.arange(3, 8, 2)
+ kde = mlab.GaussianKDE(x1)
+ y_expected = [0.08797252, 0.11774109, 0.11774109]
+ y = kde.evaluate(x2)
+ np.testing.assert_array_almost_equal(y, y_expected, 7)
+
+
+def test_psd_onesided_norm():
+ u = np.array([0, 1, 2, 3, 1, 2, 1])
+ dt = 1.0
+ Su = np.abs(np.fft.fft(u) * dt)**2 / (dt * u.size)
+ P, f = mlab.psd(u, NFFT=u.size, Fs=1/dt, window=mlab.window_none,
+ detrend=mlab.detrend_none, noverlap=0, pad_to=None,
+ scale_by_freq=None,
+ sides='onesided')
+ Su_1side = np.append([Su[0]], Su[1:4] + Su[4:][::-1])
+ assert_allclose(P, Su_1side, atol=1e-06)
+
+
+def test_psd_oversampling():
+ """Test the case len(x) < NFFT for psd()."""
+ u = np.array([0, 1, 2, 3, 1, 2, 1])
+ dt = 1.0
+ Su = np.abs(np.fft.fft(u) * dt)**2 / (dt * u.size)
+ P, f = mlab.psd(u, NFFT=u.size*2, Fs=1/dt, window=mlab.window_none,
+ detrend=mlab.detrend_none, noverlap=0, pad_to=None,
+ scale_by_freq=None,
+ sides='onesided')
+ Su_1side = np.append([Su[0]], Su[1:4] + Su[4:][::-1])
+ assert_almost_equal(np.sum(P), np.sum(Su_1side)) # same energy
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_multivariate_colormaps.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_multivariate_colormaps.py
new file mode 100644
index 0000000000000000000000000000000000000000..81a2e6adeb356969ed3bf5b9acf77980fc40ac19
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_multivariate_colormaps.py
@@ -0,0 +1,564 @@
+import numpy as np
+from numpy.testing import assert_array_equal, assert_allclose
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import (image_comparison,
+ remove_ticks_and_titles)
+import matplotlib as mpl
+import pytest
+from pathlib import Path
+from io import BytesIO
+from PIL import Image
+import base64
+
+
+@image_comparison(["bivariate_cmap_shapes.png"])
+def test_bivariate_cmap_shapes():
+ x_0 = np.repeat(np.linspace(-0.1, 1.1, 10, dtype='float32')[None, :], 10, axis=0)
+ x_1 = x_0.T
+
+ fig, axes = plt.subplots(1, 4, figsize=(10, 2))
+
+ # shape = 'square'
+ cmap = mpl.bivar_colormaps['BiPeak']
+ axes[0].imshow(cmap((x_0, x_1)), interpolation='nearest')
+
+ # shape = 'circle'
+ cmap = mpl.bivar_colormaps['BiCone']
+ axes[1].imshow(cmap((x_0, x_1)), interpolation='nearest')
+
+ # shape = 'ignore'
+ cmap = mpl.bivar_colormaps['BiPeak']
+ cmap = cmap.with_extremes(shape='ignore')
+ axes[2].imshow(cmap((x_0, x_1)), interpolation='nearest')
+
+ # shape = circleignore
+ cmap = mpl.bivar_colormaps['BiCone']
+ cmap = cmap.with_extremes(shape='circleignore')
+ axes[3].imshow(cmap((x_0, x_1)), interpolation='nearest')
+ remove_ticks_and_titles(fig)
+
+
+def test_multivar_creation():
+ # test creation of a custom multivariate colorbar
+ blues = mpl.colormaps['Blues']
+ cmap = mpl.colors.MultivarColormap((blues, 'Oranges'), 'sRGB_sub')
+ y, x = np.mgrid[0:3, 0:3]/2
+ im = cmap((y, x))
+ res = np.array([[[0.96862745, 0.94509804, 0.92156863, 1],
+ [0.96004614, 0.53504037, 0.23277201, 1],
+ [0.46666667, 0.1372549, 0.01568627, 1]],
+ [[0.41708574, 0.64141484, 0.75980008, 1],
+ [0.40850442, 0.23135717, 0.07100346, 1],
+ [0, 0, 0, 1]],
+ [[0.03137255, 0.14901961, 0.34117647, 1],
+ [0.02279123, 0, 0, 1],
+ [0, 0, 0, 1]]])
+ assert_allclose(im, res, atol=0.01)
+
+ with pytest.raises(ValueError, match="colormaps must be a list of"):
+ cmap = mpl.colors.MultivarColormap((blues, [blues]), 'sRGB_sub')
+ with pytest.raises(ValueError, match="A MultivarColormap must"):
+ cmap = mpl.colors.MultivarColormap('blues', 'sRGB_sub')
+ with pytest.raises(ValueError, match="A MultivarColormap must"):
+ cmap = mpl.colors.MultivarColormap((blues), 'sRGB_sub')
+
+
+@image_comparison(["multivar_alpha_mixing.png"])
+def test_multivar_alpha_mixing():
+ # test creation of a custom colormap using 'rainbow'
+ # and a colormap that goes from alpha = 1 to alpha = 0
+ rainbow = mpl.colormaps['rainbow']
+ alpha = np.zeros((256, 4))
+ alpha[:, 3] = np.linspace(1, 0, 256)
+ alpha_cmap = mpl.colors.LinearSegmentedColormap.from_list('from_list', alpha)
+
+ cmap = mpl.colors.MultivarColormap((rainbow, alpha_cmap), 'sRGB_add')
+ y, x = np.mgrid[0:10, 0:10]/9
+ im = cmap((y, x))
+
+ fig, ax = plt.subplots()
+ ax.imshow(im, interpolation='nearest')
+ remove_ticks_and_titles(fig)
+
+
+def test_multivar_cmap_call():
+ cmap = mpl.multivar_colormaps['2VarAddA']
+ assert_array_equal(cmap((0.0, 0.0)), (0, 0, 0, 1))
+ assert_array_equal(cmap((1.0, 1.0)), (1, 1, 1, 1))
+ assert_allclose(cmap((0.0, 0.0), alpha=0.1), (0, 0, 0, 0.1), atol=0.1)
+
+ cmap = mpl.multivar_colormaps['2VarSubA']
+ assert_array_equal(cmap((0.0, 0.0)), (1, 1, 1, 1))
+ assert_allclose(cmap((1.0, 1.0)), (0, 0, 0, 1), atol=0.1)
+
+ # check outside and bad
+ cs = cmap([(0., 0., 0., 1.2, np.nan), (0., 1.2, np.nan, 0., 0., )])
+ assert_allclose(cs, [[1., 1., 1., 1.],
+ [0.801, 0.426, 0.119, 1.],
+ [0., 0., 0., 0.],
+ [0.199, 0.574, 0.881, 1.],
+ [0., 0., 0., 0.]])
+
+ assert_array_equal(cmap((0.0, 0.0), bytes=True), (255, 255, 255, 255))
+
+ with pytest.raises(ValueError, match="alpha is array-like but its shape"):
+ cs = cmap([(0, 5, 9), (0, 0, 0)], alpha=(0.5, 0.3))
+
+ with pytest.raises(ValueError, match="For the selected colormap the data"):
+ cs = cmap([(0, 5, 9), (0, 0, 0), (0, 0, 0)])
+
+ with pytest.raises(ValueError, match="clip cannot be false"):
+ cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, clip=False)
+ # Tests calling a multivariate colormap with integer values
+ cmap = mpl.multivar_colormaps['2VarSubA']
+
+ # call only integers
+ cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)])
+ res = np.array([[1, 1, 1, 1],
+ [0.85176471, 0.91029412, 0.96023529, 1],
+ [0.70452941, 0.82764706, 0.93358824, 1],
+ [0.94358824, 0.88505882, 0.83511765, 1],
+ [0.89729412, 0.77417647, 0.66823529, 1],
+ [0, 0, 0, 1]])
+ assert_allclose(cs, res, atol=0.01)
+
+ # call only integers, wrong byte order
+ swapped_dt = np.dtype(int).newbyteorder()
+ cs = cmap([np.array([0, 50, 100, 0, 0, 300], dtype=swapped_dt),
+ np.array([0, 0, 0, 50, 100, 300], dtype=swapped_dt)])
+ assert_allclose(cs, res, atol=0.01)
+
+ # call mix floats integers
+ # check calling with bytes = True
+ cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)], bytes=True)
+ res = np.array([[255, 255, 255, 255],
+ [217, 232, 244, 255],
+ [179, 211, 238, 255],
+ [240, 225, 212, 255],
+ [228, 197, 170, 255],
+ [0, 0, 0, 255]])
+ assert_allclose(cs, res, atol=0.01)
+
+ cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)], alpha=0.5)
+ res = np.array([[1, 1, 1, 0.5],
+ [0.85176471, 0.91029412, 0.96023529, 0.5],
+ [0.70452941, 0.82764706, 0.93358824, 0.5],
+ [0.94358824, 0.88505882, 0.83511765, 0.5],
+ [0.89729412, 0.77417647, 0.66823529, 0.5],
+ [0, 0, 0, 0.5]])
+ assert_allclose(cs, res, atol=0.01)
+ # call with tuple
+ assert_allclose(cmap((100, 120), bytes=True, alpha=0.5),
+ [149, 142, 136, 127], atol=0.01)
+
+ # alpha and bytes
+ cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], bytes=True, alpha=0.5)
+ res = np.array([[0, 0, 255, 127],
+ [141, 0, 255, 127],
+ [255, 0, 255, 127],
+ [0, 115, 255, 127],
+ [0, 255, 255, 127],
+ [255, 255, 255, 127]])
+
+ # bad alpha shape
+ with pytest.raises(ValueError, match="alpha is array-like but its shape"):
+ cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, alpha=(0.5, 0.3))
+
+ cmap = cmap.with_extremes(bad=(1, 1, 1, 1))
+ cs = cmap([(0., 1.1, np.nan), (0., 1.2, 1.)])
+ res = np.array([[1., 1., 1., 1.],
+ [0., 0., 0., 1.],
+ [1., 1., 1., 1.]])
+ assert_allclose(cs, res, atol=0.01)
+
+ # call outside with tuple
+ assert_allclose(cmap((300, 300), bytes=True, alpha=0.5),
+ [0, 0, 0, 127], atol=0.01)
+ with pytest.raises(ValueError,
+ match="For the selected colormap the data must have"):
+ cs = cmap((0, 5, 9))
+
+ # test over/under
+ cmap = mpl.multivar_colormaps['2VarAddA']
+ with pytest.raises(ValueError, match='i.e. be of length 2'):
+ cmap.with_extremes(over=0)
+ with pytest.raises(ValueError, match='i.e. be of length 2'):
+ cmap.with_extremes(under=0)
+
+ cmap = cmap.with_extremes(under=[(0, 0, 0, 0)]*2)
+ assert_allclose((0, 0, 0, 0), cmap((-1., 0)), atol=1e-2)
+ cmap = cmap.with_extremes(over=[(0, 0, 0, 0)]*2)
+ assert_allclose((0, 0, 0, 0), cmap((2., 0)), atol=1e-2)
+
+
+def test_multivar_bad_mode():
+ cmap = mpl.multivar_colormaps['2VarSubA']
+ with pytest.raises(ValueError, match="is not a valid value for"):
+ cmap = mpl.colors.MultivarColormap(cmap[:], 'bad')
+
+
+def test_multivar_resample():
+ cmap = mpl.multivar_colormaps['3VarAddA']
+ cmap_resampled = cmap.resampled((None, 10, 3))
+
+ assert_allclose(cmap_resampled[1](0.25), (0.093, 0.116, 0.059, 1.0))
+ assert_allclose(cmap_resampled((0, 0.25, 0)), (0.093, 0.116, 0.059, 1.0))
+ assert_allclose(cmap_resampled((1, 0.25, 1)), (0.417271, 0.264624, 0.274976, 1.),
+ atol=0.01)
+
+ with pytest.raises(ValueError, match="lutshape must be of length"):
+ cmap = cmap.resampled(4)
+
+
+def test_bivar_cmap_call_tuple():
+ cmap = mpl.bivar_colormaps['BiOrangeBlue']
+ assert_allclose(cmap((1.0, 1.0)), (1, 1, 1, 1), atol=0.01)
+ assert_allclose(cmap((0.0, 0.0)), (0, 0, 0, 1), atol=0.1)
+ assert_allclose(cmap((0.0, 0.0), alpha=0.1), (0, 0, 0, 0.1), atol=0.1)
+
+
+def test_bivar_cmap_call():
+ """
+ Tests calling a bivariate colormap with integer values
+ """
+ im = np.ones((10, 12, 4))
+ im[:, :, 0] = np.linspace(0, 1, 10)[:, np.newaxis]
+ im[:, :, 1] = np.linspace(0, 1, 12)[np.newaxis, :]
+ cmap = mpl.colors.BivarColormapFromImage(im)
+
+ # call only integers
+ cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)])
+ res = np.array([[0, 0, 1, 1],
+ [0.556, 0, 1, 1],
+ [1, 0, 1, 1],
+ [0, 0.454, 1, 1],
+ [0, 1, 1, 1],
+ [1, 1, 1, 1]])
+ assert_allclose(cs, res, atol=0.01)
+ # call only integers, wrong byte order
+ swapped_dt = np.dtype(int).newbyteorder()
+ cs = cmap([np.array([0, 5, 9, 0, 0, 10], dtype=swapped_dt),
+ np.array([0, 0, 0, 5, 11, 12], dtype=swapped_dt)])
+ assert_allclose(cs, res, atol=0.01)
+
+ # call mix floats integers
+ cmap = cmap.with_extremes(outside=(1, 0, 0, 0))
+ cs = cmap([(0.5, 0), (0, 3)])
+ res = np.array([[0.555, 0, 1, 1],
+ [0, 0.2727, 1, 1]])
+ assert_allclose(cs, res, atol=0.01)
+
+ # check calling with bytes = True
+ cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], bytes=True)
+ res = np.array([[0, 0, 255, 255],
+ [141, 0, 255, 255],
+ [255, 0, 255, 255],
+ [0, 115, 255, 255],
+ [0, 255, 255, 255],
+ [255, 255, 255, 255]])
+ assert_allclose(cs, res, atol=0.01)
+
+ # test alpha
+ cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], alpha=0.5)
+ res = np.array([[0, 0, 1, 0.5],
+ [0.556, 0, 1, 0.5],
+ [1, 0, 1, 0.5],
+ [0, 0.454, 1, 0.5],
+ [0, 1, 1, 0.5],
+ [1, 1, 1, 0.5]])
+ assert_allclose(cs, res, atol=0.01)
+ # call with tuple
+ assert_allclose(cmap((10, 12), bytes=True, alpha=0.5),
+ [255, 255, 255, 127], atol=0.01)
+
+ # alpha and bytes
+ cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], bytes=True, alpha=0.5)
+ res = np.array([[0, 0, 255, 127],
+ [141, 0, 255, 127],
+ [255, 0, 255, 127],
+ [0, 115, 255, 127],
+ [0, 255, 255, 127],
+ [255, 255, 255, 127]])
+
+ # bad alpha shape
+ with pytest.raises(ValueError, match="alpha is array-like but its shape"):
+ cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, alpha=(0.5, 0.3))
+
+ # set shape to 'ignore'.
+ # final point is outside colormap and should then receive
+ # the 'outside' (in this case [1,0,0,0])
+ # also test 'bad' (in this case [1,1,1,0])
+ cmap = cmap.with_extremes(outside=(1, 0, 0, 0), bad=(1, 1, 1, 0), shape='ignore')
+ cs = cmap([(0., 1.1, np.nan), (0., 1.2, 1.)])
+ res = np.array([[0, 0, 1, 1],
+ [1, 0, 0, 0],
+ [1, 1, 1, 0]])
+ assert_allclose(cs, res, atol=0.01)
+ # call outside with tuple
+ assert_allclose(cmap((10, 12), bytes=True, alpha=0.5),
+ [255, 0, 0, 127], atol=0.01)
+ # with integers
+ cs = cmap([(0, 10), (0, 12)])
+ res = np.array([[0, 0, 1, 1],
+ [1, 0, 0, 0]])
+ assert_allclose(cs, res, atol=0.01)
+
+ with pytest.raises(ValueError,
+ match="For a `BivarColormap` the data must have"):
+ cs = cmap((0, 5, 9))
+
+ cmap = cmap.with_extremes(shape='circle')
+ with pytest.raises(NotImplementedError,
+ match="only implemented for use with with floats"):
+ cs = cmap([(0, 5, 9, 0, 0, 9), (0, 0, 0, 5, 11, 11)])
+
+ # test origin
+ cmap = mpl.bivar_colormaps['BiOrangeBlue'].with_extremes(origin=(0.5, 0.5))
+ assert_allclose(cmap[0](0.5),
+ (0.50244140625, 0.5024222412109375, 0.50244140625, 1))
+ assert_allclose(cmap[1](0.5),
+ (0.50244140625, 0.5024222412109375, 0.50244140625, 1))
+ cmap = mpl.bivar_colormaps['BiOrangeBlue'].with_extremes(origin=(1, 1))
+ assert_allclose(cmap[0](1.),
+ (0.99853515625, 0.9985467529296875, 0.99853515625, 1.0))
+ assert_allclose(cmap[1](1.),
+ (0.99853515625, 0.9985467529296875, 0.99853515625, 1.0))
+ with pytest.raises(KeyError,
+ match="only 0 or 1 are valid keys"):
+ cs = cmap[2]
+
+
+def test_bivar_getitem():
+ """Test __getitem__ on BivarColormap"""
+ xA = ([.0, .25, .5, .75, 1., -1, 2], [.5]*7)
+ xB = ([.5]*7, [.0, .25, .5, .75, 1., -1, 2])
+
+ cmaps = mpl.bivar_colormaps['BiPeak']
+ assert_array_equal(cmaps(xA), cmaps[0](xA[0]))
+ assert_array_equal(cmaps(xB), cmaps[1](xB[1]))
+
+ cmaps = cmaps.with_extremes(shape='ignore')
+ assert_array_equal(cmaps(xA), cmaps[0](xA[0]))
+ assert_array_equal(cmaps(xB), cmaps[1](xB[1]))
+
+ xA = ([.0, .25, .5, .75, 1., -1, 2], [.0]*7)
+ xB = ([.0]*7, [.0, .25, .5, .75, 1., -1, 2])
+ cmaps = mpl.bivar_colormaps['BiOrangeBlue']
+ assert_array_equal(cmaps(xA), cmaps[0](xA[0]))
+ assert_array_equal(cmaps(xB), cmaps[1](xB[1]))
+
+ cmaps = cmaps.with_extremes(shape='ignore')
+ assert_array_equal(cmaps(xA), cmaps[0](xA[0]))
+ assert_array_equal(cmaps(xB), cmaps[1](xB[1]))
+
+
+def test_bivar_cmap_bad_shape():
+ """
+ Tests calling a bivariate colormap with integer values
+ """
+ cmap = mpl.bivar_colormaps['BiCone']
+ _ = cmap.lut
+ with pytest.raises(ValueError,
+ match="is not a valid value for shape"):
+ cmap.with_extremes(shape='bad_shape')
+
+ with pytest.raises(ValueError,
+ match="is not a valid value for shape"):
+ mpl.colors.BivarColormapFromImage(np.ones((3, 3, 4)),
+ shape='bad_shape')
+
+
+def test_bivar_cmap_bad_lut():
+ """
+ Tests calling a bivariate colormap with integer values
+ """
+ with pytest.raises(ValueError,
+ match="The lut must be an array of shape"):
+ cmap = mpl.colors.BivarColormapFromImage(np.ones((3, 3, 5)))
+
+
+def test_bivar_cmap_from_image():
+ """
+ This tests the creation and use of a bivariate colormap
+ generated from an image
+ """
+
+ data_0 = np.arange(6).reshape((2, 3))/5
+ data_1 = np.arange(6).reshape((3, 2)).T/5
+
+ # bivariate colormap from array
+ cim = np.ones((10, 12, 3))
+ cim[:, :, 0] = np.arange(10)[:, np.newaxis]/10
+ cim[:, :, 1] = np.arange(12)[np.newaxis, :]/12
+
+ cmap = mpl.colors.BivarColormapFromImage(cim)
+ im = cmap((data_0, data_1))
+ res = np.array([[[0, 0, 1, 1],
+ [0.2, 0.33333333, 1, 1],
+ [0.4, 0.75, 1, 1]],
+ [[0.6, 0.16666667, 1, 1],
+ [0.8, 0.58333333, 1, 1],
+ [0.9, 0.91666667, 1, 1]]])
+ assert_allclose(im, res, atol=0.01)
+
+ # input as unit8
+ cim = np.ones((10, 12, 3))*255
+ cim[:, :, 0] = np.arange(10)[:, np.newaxis]/10*255
+ cim[:, :, 1] = np.arange(12)[np.newaxis, :]/12*255
+
+ cmap = mpl.colors.BivarColormapFromImage(cim.astype(np.uint8))
+ im = cmap((data_0, data_1))
+ res = np.array([[[0, 0, 1, 1],
+ [0.2, 0.33333333, 1, 1],
+ [0.4, 0.75, 1, 1]],
+ [[0.6, 0.16666667, 1, 1],
+ [0.8, 0.58333333, 1, 1],
+ [0.9, 0.91666667, 1, 1]]])
+ assert_allclose(im, res, atol=0.01)
+
+ # bivariate colormap from array
+ png_path = Path(__file__).parent / "baseline_images/pngsuite/basn2c16.png"
+ cim = Image.open(png_path)
+ cim = np.asarray(cim.convert('RGBA'))
+
+ cmap = mpl.colors.BivarColormapFromImage(cim)
+ im = cmap((data_0, data_1), bytes=True)
+ res = np.array([[[255, 255, 0, 255],
+ [156, 206, 0, 255],
+ [49, 156, 49, 255]],
+ [[206, 99, 0, 255],
+ [99, 49, 107, 255],
+ [0, 0, 255, 255]]])
+ assert_allclose(im, res, atol=0.01)
+
+
+def test_bivar_resample():
+ cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((2, 2))
+ assert_allclose(cmap((0.25, 0.25)), (0, 0, 0, 1), atol=1e-2)
+
+ cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((-2, 2))
+ assert_allclose(cmap((0.25, 0.25)), (1., 0.5, 0., 1.), atol=1e-2)
+
+ cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((2, -2))
+ assert_allclose(cmap((0.25, 0.25)), (0., 0.5, 1., 1.), atol=1e-2)
+
+ cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((-2, -2))
+ assert_allclose(cmap((0.25, 0.25)), (1, 1, 1, 1), atol=1e-2)
+
+ cmap = mpl.bivar_colormaps['BiOrangeBlue'].reversed()
+ assert_allclose(cmap((0.25, 0.25)), (0.748535, 0.748547, 0.748535, 1.), atol=1e-2)
+ cmap = mpl.bivar_colormaps['BiOrangeBlue'].transposed()
+ assert_allclose(cmap((0.25, 0.25)), (0.252441, 0.252422, 0.252441, 1.), atol=1e-2)
+
+ with pytest.raises(ValueError, match="lutshape must be of length"):
+ cmap = cmap.resampled(4)
+
+
+def test_bivariate_repr_png():
+ cmap = mpl.bivar_colormaps['BiCone']
+ png = cmap._repr_png_()
+ assert len(png) > 0
+ img = Image.open(BytesIO(png))
+ assert img.width > 0
+ assert img.height > 0
+ assert 'Title' in img.text
+ assert 'Description' in img.text
+ assert 'Author' in img.text
+ assert 'Software' in img.text
+
+
+def test_bivariate_repr_html():
+ cmap = mpl.bivar_colormaps['BiCone']
+ html = cmap._repr_html_()
+ assert len(html) > 0
+ png = cmap._repr_png_()
+ assert base64.b64encode(png).decode('ascii') in html
+ assert cmap.name in html
+ assert html.startswith('')
+
+
+def test_multivariate_repr_png():
+ cmap = mpl.multivar_colormaps['3VarAddA']
+ png = cmap._repr_png_()
+ assert len(png) > 0
+ img = Image.open(BytesIO(png))
+ assert img.width > 0
+ assert img.height > 0
+ assert 'Title' in img.text
+ assert 'Description' in img.text
+ assert 'Author' in img.text
+ assert 'Software' in img.text
+
+
+def test_multivariate_repr_html():
+ cmap = mpl.multivar_colormaps['3VarAddA']
+ html = cmap._repr_html_()
+ assert len(html) > 0
+ for c in cmap:
+ png = c._repr_png_()
+ assert base64.b64encode(png).decode('ascii') in html
+ assert cmap.name in html
+ assert html.startswith('
')
+
+
+def test_bivar_eq():
+ """
+ Tests equality between multivariate colormaps
+ """
+ cmap_0 = mpl.bivar_colormaps['BiPeak']
+
+ cmap_1 = mpl.bivar_colormaps['BiPeak']
+ assert (cmap_0 == cmap_1) is True
+
+ cmap_1 = mpl.multivar_colormaps['2VarAddA']
+ assert (cmap_0 == cmap_1) is False
+
+ cmap_1 = mpl.bivar_colormaps['BiCone']
+ assert (cmap_0 == cmap_1) is False
+
+ cmap_1 = mpl.bivar_colormaps['BiPeak']
+ cmap_1 = cmap_1.with_extremes(bad='k')
+ assert (cmap_0 == cmap_1) is False
+
+ cmap_1 = mpl.bivar_colormaps['BiPeak']
+ cmap_1 = cmap_1.with_extremes(outside='k')
+ assert (cmap_0 == cmap_1) is False
+
+ cmap_1 = mpl.bivar_colormaps['BiPeak']
+ cmap_1._init()
+ cmap_1._lut *= 0.5
+ assert (cmap_0 == cmap_1) is False
+
+ cmap_1 = mpl.bivar_colormaps['BiPeak']
+ cmap_1 = cmap_1.with_extremes(shape='ignore')
+ assert (cmap_0 == cmap_1) is False
+
+
+def test_multivar_eq():
+ """
+ Tests equality between multivariate colormaps
+ """
+ cmap_0 = mpl.multivar_colormaps['2VarAddA']
+
+ cmap_1 = mpl.multivar_colormaps['2VarAddA']
+ assert (cmap_0 == cmap_1) is True
+
+ cmap_1 = mpl.bivar_colormaps['BiPeak']
+ assert (cmap_0 == cmap_1) is False
+
+ cmap_1 = mpl.colors.MultivarColormap([cmap_0[0]]*2,
+ 'sRGB_add')
+ assert (cmap_0 == cmap_1) is False
+
+ cmap_1 = mpl.multivar_colormaps['3VarAddA']
+ assert (cmap_0 == cmap_1) is False
+
+ cmap_1 = mpl.multivar_colormaps['2VarAddA']
+ cmap_1 = cmap_1.with_extremes(bad='k')
+ assert (cmap_0 == cmap_1) is False
+
+ cmap_1 = mpl.multivar_colormaps['2VarAddA']
+ cmap_1 = mpl.colors.MultivarColormap(cmap_1[:], 'sRGB_sub')
+ assert (cmap_0 == cmap_1) is False
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_offsetbox.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_offsetbox.py
new file mode 100644
index 0000000000000000000000000000000000000000..57add85b110a6a5ea944dfc7283eec1f7ca83863
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_offsetbox.py
@@ -0,0 +1,472 @@
+from collections import namedtuple
+import io
+
+import numpy as np
+from numpy.testing import assert_allclose
+import pytest
+
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+import matplotlib.pyplot as plt
+import matplotlib.patches as mpatches
+import matplotlib.lines as mlines
+from matplotlib.backend_bases import MouseButton, MouseEvent
+
+from matplotlib.offsetbox import (
+ AnchoredOffsetbox, AnnotationBbox, AnchoredText, DrawingArea, HPacker,
+ OffsetBox, OffsetImage, PaddedBox, TextArea, VPacker, _get_packed_offsets)
+
+
+@image_comparison(['offsetbox_clipping'], remove_text=True)
+def test_offsetbox_clipping():
+ # - create a plot
+ # - put an AnchoredOffsetbox with a child DrawingArea
+ # at the center of the axes
+ # - give the DrawingArea a gray background
+ # - put a black line across the bounds of the DrawingArea
+ # - see that the black line is clipped to the edges of
+ # the DrawingArea.
+ fig, ax = plt.subplots()
+ size = 100
+ da = DrawingArea(size, size, clip=True)
+ assert da.clip_children
+ bg = mpatches.Rectangle((0, 0), size, size,
+ facecolor='#CCCCCC',
+ edgecolor='None',
+ linewidth=0)
+ line = mlines.Line2D([-size*.5, size*1.5], [size/2, size/2],
+ color='black',
+ linewidth=10)
+ anchored_box = AnchoredOffsetbox(
+ loc='center',
+ child=da,
+ pad=0.,
+ frameon=False,
+ bbox_to_anchor=(.5, .5),
+ bbox_transform=ax.transAxes,
+ borderpad=0.)
+
+ da.add_artist(bg)
+ da.add_artist(line)
+ ax.add_artist(anchored_box)
+ ax.set_xlim((0, 1))
+ ax.set_ylim((0, 1))
+
+
+def test_offsetbox_clip_children():
+ # - create a plot
+ # - put an AnchoredOffsetbox with a child DrawingArea
+ # at the center of the axes
+ # - give the DrawingArea a gray background
+ # - put a black line across the bounds of the DrawingArea
+ # - see that the black line is clipped to the edges of
+ # the DrawingArea.
+ fig, ax = plt.subplots()
+ size = 100
+ da = DrawingArea(size, size, clip=True)
+ bg = mpatches.Rectangle((0, 0), size, size,
+ facecolor='#CCCCCC',
+ edgecolor='None',
+ linewidth=0)
+ line = mlines.Line2D([-size*.5, size*1.5], [size/2, size/2],
+ color='black',
+ linewidth=10)
+ anchored_box = AnchoredOffsetbox(
+ loc='center',
+ child=da,
+ pad=0.,
+ frameon=False,
+ bbox_to_anchor=(.5, .5),
+ bbox_transform=ax.transAxes,
+ borderpad=0.)
+
+ da.add_artist(bg)
+ da.add_artist(line)
+ ax.add_artist(anchored_box)
+
+ fig.canvas.draw()
+ assert not fig.stale
+ da.clip_children = True
+ assert fig.stale
+
+
+def test_offsetbox_loc_codes():
+ # Check that valid string location codes all work with an AnchoredOffsetbox
+ codes = {'upper right': 1,
+ 'upper left': 2,
+ 'lower left': 3,
+ 'lower right': 4,
+ 'right': 5,
+ 'center left': 6,
+ 'center right': 7,
+ 'lower center': 8,
+ 'upper center': 9,
+ 'center': 10,
+ }
+ fig, ax = plt.subplots()
+ da = DrawingArea(100, 100)
+ for code in codes:
+ anchored_box = AnchoredOffsetbox(loc=code, child=da)
+ ax.add_artist(anchored_box)
+ fig.canvas.draw()
+
+
+def test_expand_with_tight_layout():
+ # Check issue reported in #10476, and updated due to #10784
+ fig, ax = plt.subplots()
+
+ d1 = [1, 2]
+ d2 = [2, 1]
+ ax.plot(d1, label='series 1')
+ ax.plot(d2, label='series 2')
+ ax.legend(ncols=2, mode='expand')
+
+ fig.tight_layout() # where the crash used to happen
+
+
+@pytest.mark.parametrize('widths',
+ ([150], [150, 150, 150], [0.1], [0.1, 0.1]))
+@pytest.mark.parametrize('total', (250, 100, 0, -1, None))
+@pytest.mark.parametrize('sep', (250, 1, 0, -1))
+@pytest.mark.parametrize('mode', ("expand", "fixed", "equal"))
+def test_get_packed_offsets(widths, total, sep, mode):
+ # Check a (rather arbitrary) set of parameters due to successive similar
+ # issue tickets (at least #10476 and #10784) related to corner cases
+ # triggered inside this function when calling higher-level functions
+ # (e.g. `Axes.legend`).
+ # These are just some additional smoke tests. The output is untested.
+ _get_packed_offsets(widths, total, sep, mode=mode)
+
+
+_Params = namedtuple('_Params', 'wd_list, total, sep, expected')
+
+
+@pytest.mark.parametrize('widths, total, sep, expected', [
+ _Params( # total=None
+ [3, 1, 2], total=None, sep=1, expected=(8, [0, 4, 6])),
+ _Params( # total larger than required
+ [3, 1, 2], total=10, sep=1, expected=(10, [0, 4, 6])),
+ _Params( # total smaller than required
+ [3, 1, 2], total=5, sep=1, expected=(5, [0, 4, 6])),
+])
+def test_get_packed_offsets_fixed(widths, total, sep, expected):
+ result = _get_packed_offsets(widths, total, sep, mode='fixed')
+ assert result[0] == expected[0]
+ assert_allclose(result[1], expected[1])
+
+
+@pytest.mark.parametrize('widths, total, sep, expected', [
+ _Params( # total=None (implicit 1)
+ [.1, .1, .1], total=None, sep=None, expected=(1, [0, .45, .9])),
+ _Params( # total larger than sum of widths
+ [3, 1, 2], total=10, sep=1, expected=(10, [0, 5, 8])),
+ _Params( # total smaller sum of widths: overlapping boxes
+ [3, 1, 2], total=5, sep=1, expected=(5, [0, 2.5, 3])),
+])
+def test_get_packed_offsets_expand(widths, total, sep, expected):
+ result = _get_packed_offsets(widths, total, sep, mode='expand')
+ assert result[0] == expected[0]
+ assert_allclose(result[1], expected[1])
+
+
+@pytest.mark.parametrize('widths, total, sep, expected', [
+ _Params( # total larger than required
+ [3, 2, 1], total=6, sep=None, expected=(6, [0, 2, 4])),
+ _Params( # total smaller sum of widths: overlapping boxes
+ [3, 2, 1, .5], total=2, sep=None, expected=(2, [0, 0.5, 1, 1.5])),
+ _Params( # total larger than required
+ [.5, 1, .2], total=None, sep=1, expected=(6, [0, 2, 4])),
+ # the case total=None, sep=None is tested separately below
+])
+def test_get_packed_offsets_equal(widths, total, sep, expected):
+ result = _get_packed_offsets(widths, total, sep, mode='equal')
+ assert result[0] == expected[0]
+ assert_allclose(result[1], expected[1])
+
+
+def test_get_packed_offsets_equal_total_none_sep_none():
+ with pytest.raises(ValueError):
+ _get_packed_offsets([1, 1, 1], total=None, sep=None, mode='equal')
+
+
+@pytest.mark.parametrize('child_type', ['draw', 'image', 'text'])
+@pytest.mark.parametrize('boxcoords',
+ ['axes fraction', 'axes pixels', 'axes points',
+ 'data'])
+def test_picking(child_type, boxcoords):
+ # These all take up approximately the same area.
+ if child_type == 'draw':
+ picking_child = DrawingArea(5, 5)
+ picking_child.add_artist(mpatches.Rectangle((0, 0), 5, 5, linewidth=0))
+ elif child_type == 'image':
+ im = np.ones((5, 5))
+ im[2, 2] = 0
+ picking_child = OffsetImage(im)
+ elif child_type == 'text':
+ picking_child = TextArea('\N{Black Square}', textprops={'fontsize': 5})
+ else:
+ assert False, f'Unknown picking child type {child_type}'
+
+ fig, ax = plt.subplots()
+ ab = AnnotationBbox(picking_child, (0.5, 0.5), boxcoords=boxcoords)
+ ab.set_picker(True)
+ ax.add_artist(ab)
+
+ calls = []
+ fig.canvas.mpl_connect('pick_event', lambda event: calls.append(event))
+
+ # Annotation should be picked by an event occurring at its center.
+ if boxcoords == 'axes points':
+ x, y = ax.transAxes.transform_point((0, 0))
+ x += 0.5 * fig.dpi / 72
+ y += 0.5 * fig.dpi / 72
+ elif boxcoords == 'axes pixels':
+ x, y = ax.transAxes.transform_point((0, 0))
+ x += 0.5
+ y += 0.5
+ else:
+ x, y = ax.transAxes.transform_point((0.5, 0.5))
+ fig.canvas.draw()
+ calls.clear()
+ MouseEvent(
+ "button_press_event", fig.canvas, x, y, MouseButton.LEFT)._process()
+ assert len(calls) == 1 and calls[0].artist == ab
+
+ # Annotation should *not* be picked by an event at its original center
+ # point when the limits have changed enough to hide the *xy* point.
+ ax.set_xlim(-1, 0)
+ ax.set_ylim(-1, 0)
+ fig.canvas.draw()
+ calls.clear()
+ MouseEvent(
+ "button_press_event", fig.canvas, x, y, MouseButton.LEFT)._process()
+ assert len(calls) == 0
+
+
+@image_comparison(['anchoredtext_align.png'], remove_text=True, style='mpl20')
+def test_anchoredtext_horizontal_alignment():
+ fig, ax = plt.subplots()
+
+ text0 = AnchoredText("test\ntest long text", loc="center left",
+ pad=0.2, prop={"ha": "left"})
+ ax.add_artist(text0)
+ text1 = AnchoredText("test\ntest long text", loc="center",
+ pad=0.2, prop={"ha": "center"})
+ ax.add_artist(text1)
+ text2 = AnchoredText("test\ntest long text", loc="center right",
+ pad=0.2, prop={"ha": "right"})
+ ax.add_artist(text2)
+
+
+@pytest.mark.parametrize("extent_kind", ["window_extent", "tightbbox"])
+def test_annotationbbox_extents(extent_kind):
+ plt.rcParams.update(plt.rcParamsDefault)
+ fig, ax = plt.subplots(figsize=(4, 3), dpi=100)
+
+ ax.axis([0, 1, 0, 1])
+
+ an1 = ax.annotate("Annotation", xy=(.9, .9), xytext=(1.1, 1.1),
+ arrowprops=dict(arrowstyle="->"), clip_on=False,
+ va="baseline", ha="left")
+
+ da = DrawingArea(20, 20, 0, 0, clip=True)
+ p = mpatches.Circle((-10, 30), 32)
+ da.add_artist(p)
+
+ ab3 = AnnotationBbox(da, [.5, .5], xybox=(-0.2, 0.5), xycoords='data',
+ boxcoords="axes fraction", box_alignment=(0., .5),
+ arrowprops=dict(arrowstyle="->"))
+ ax.add_artist(ab3)
+
+ im = OffsetImage(np.random.rand(10, 10), zoom=3)
+ im.image.axes = ax
+ ab6 = AnnotationBbox(im, (0.5, -.3), xybox=(0, 75),
+ xycoords='axes fraction',
+ boxcoords="offset points", pad=0.3,
+ arrowprops=dict(arrowstyle="->"))
+ ax.add_artist(ab6)
+
+ # Test Annotation
+ bb1 = getattr(an1, f"get_{extent_kind}")()
+
+ target1 = [332.9, 242.8, 467.0, 298.9]
+ assert_allclose(bb1.extents, target1, atol=2)
+
+ # Test AnnotationBbox
+ bb3 = getattr(ab3, f"get_{extent_kind}")()
+
+ target3 = [-17.6, 129.0, 200.7, 167.9]
+ assert_allclose(bb3.extents, target3, atol=2)
+
+ bb6 = getattr(ab6, f"get_{extent_kind}")()
+
+ target6 = [180.0, -32.0, 230.0, 92.9]
+ assert_allclose(bb6.extents, target6, atol=2)
+
+ # Test bbox_inches='tight'
+ buf = io.BytesIO()
+ fig.savefig(buf, bbox_inches='tight')
+ buf.seek(0)
+ shape = plt.imread(buf).shape
+ targetshape = (350, 504, 4)
+ assert_allclose(shape, targetshape, atol=2)
+
+ # Simple smoke test for tight_layout, to make sure it does not error out.
+ fig.canvas.draw()
+ fig.tight_layout()
+ fig.canvas.draw()
+
+
+def test_zorder():
+ assert OffsetBox(zorder=42).zorder == 42
+
+
+def test_arrowprops_copied():
+ da = DrawingArea(20, 20, 0, 0, clip=True)
+ arrowprops = {"arrowstyle": "->", "relpos": (.3, .7)}
+ ab = AnnotationBbox(da, [.5, .5], xybox=(-0.2, 0.5), xycoords='data',
+ boxcoords="axes fraction", box_alignment=(0., .5),
+ arrowprops=arrowprops)
+ assert ab.arrowprops is not ab
+ assert arrowprops["relpos"] == (.3, .7)
+
+
+@pytest.mark.parametrize("align", ["baseline", "bottom", "top",
+ "left", "right", "center"])
+def test_packers(align):
+ # set the DPI to match points to make the math easier below
+ fig = plt.figure(dpi=72)
+ renderer = fig.canvas.get_renderer()
+
+ x1, y1 = 10, 30
+ x2, y2 = 20, 60
+ r1 = DrawingArea(x1, y1)
+ r2 = DrawingArea(x2, y2)
+
+ # HPacker
+ hpacker = HPacker(children=[r1, r2], align=align)
+ hpacker.draw(renderer)
+ bbox = hpacker.get_bbox(renderer)
+ px, py = hpacker.get_offset(bbox, renderer)
+ # width, height, xdescent, ydescent
+ assert_allclose(bbox.bounds, (0, 0, x1 + x2, max(y1, y2)))
+ # internal element placement
+ if align in ("baseline", "left", "bottom"):
+ y_height = 0
+ elif align in ("right", "top"):
+ y_height = y2 - y1
+ elif align == "center":
+ y_height = (y2 - y1) / 2
+ # x-offsets, y-offsets
+ assert_allclose([child.get_offset() for child in hpacker.get_children()],
+ [(px, py + y_height), (px + x1, py)])
+
+ # VPacker
+ vpacker = VPacker(children=[r1, r2], align=align)
+ vpacker.draw(renderer)
+ bbox = vpacker.get_bbox(renderer)
+ px, py = vpacker.get_offset(bbox, renderer)
+ # width, height, xdescent, ydescent
+ assert_allclose(bbox.bounds, (0, -max(y1, y2), max(x1, x2), y1 + y2))
+ # internal element placement
+ if align in ("baseline", "left", "bottom"):
+ x_height = 0
+ elif align in ("right", "top"):
+ x_height = x2 - x1
+ elif align == "center":
+ x_height = (x2 - x1) / 2
+ # x-offsets, y-offsets
+ assert_allclose([child.get_offset() for child in vpacker.get_children()],
+ [(px + x_height, py), (px, py - y2)])
+
+
+def test_paddedbox_default_values():
+ # smoke test paddedbox for correct default value
+ fig, ax = plt.subplots()
+ at = AnchoredText("foo", 'upper left')
+ pb = PaddedBox(at, patch_attrs={'facecolor': 'r'}, draw_frame=True)
+ ax.add_artist(pb)
+ fig.draw_without_rendering()
+
+
+def test_annotationbbox_properties():
+ ab = AnnotationBbox(DrawingArea(20, 20, 0, 0, clip=True), (0.5, 0.5),
+ xycoords='data')
+ assert ab.xyann == (0.5, 0.5) # xy if xybox not given
+ assert ab.anncoords == 'data' # xycoords if boxcoords not given
+
+ ab = AnnotationBbox(DrawingArea(20, 20, 0, 0, clip=True), (0.5, 0.5),
+ xybox=(-0.2, 0.4), xycoords='data',
+ boxcoords='axes fraction')
+ assert ab.xyann == (-0.2, 0.4) # xybox if given
+ assert ab.anncoords == 'axes fraction' # boxcoords if given
+
+
+def test_textarea_properties():
+ ta = TextArea('Foo')
+ assert ta.get_text() == 'Foo'
+ assert not ta.get_multilinebaseline()
+
+ ta.set_text('Bar')
+ ta.set_multilinebaseline(True)
+ assert ta.get_text() == 'Bar'
+ assert ta.get_multilinebaseline()
+
+
+@check_figures_equal(extensions=['png'])
+def test_textarea_set_text(fig_test, fig_ref):
+ ax_ref = fig_ref.add_subplot()
+ text0 = AnchoredText("Foo", "upper left")
+ ax_ref.add_artist(text0)
+
+ ax_test = fig_test.add_subplot()
+ text1 = AnchoredText("Bar", "upper left")
+ ax_test.add_artist(text1)
+ text1.txt.set_text("Foo")
+
+
+@image_comparison(['paddedbox.png'], remove_text=True, style='mpl20')
+def test_paddedbox():
+ fig, ax = plt.subplots()
+
+ ta = TextArea("foo")
+ pb = PaddedBox(ta, pad=5, patch_attrs={'facecolor': 'r'}, draw_frame=True)
+ ab = AnchoredOffsetbox('upper left', child=pb)
+ ax.add_artist(ab)
+
+ ta = TextArea("bar")
+ pb = PaddedBox(ta, pad=10, patch_attrs={'facecolor': 'b'})
+ ab = AnchoredOffsetbox('upper right', child=pb)
+ ax.add_artist(ab)
+
+ ta = TextArea("foobar")
+ pb = PaddedBox(ta, pad=15, draw_frame=True)
+ ab = AnchoredOffsetbox('lower right', child=pb)
+ ax.add_artist(ab)
+
+
+def test_remove_draggable():
+ fig, ax = plt.subplots()
+ an = ax.annotate("foo", (.5, .5))
+ an.draggable(True)
+ an.remove()
+ MouseEvent("button_release_event", fig.canvas, 1, 1)._process()
+
+
+def test_draggable_in_subfigure():
+ fig = plt.figure()
+ # Put annotation at lower left corner to make it easily pickable below.
+ ann = fig.subfigures().add_axes([0, 0, 1, 1]).annotate("foo", (0, 0))
+ ann.draggable(True)
+ fig.canvas.draw() # Texts are non-pickable until the first draw.
+ MouseEvent("button_press_event", fig.canvas, 1, 1)._process()
+ assert ann._draggable.got_artist
+ # Stop dragging the annotation.
+ MouseEvent("button_release_event", fig.canvas, 1, 1)._process()
+ assert not ann._draggable.got_artist
+ # A scroll event should not initiate a drag.
+ MouseEvent("scroll_event", fig.canvas, 1, 1)._process()
+ assert not ann._draggable.got_artist
+ # An event outside the annotation should not initiate a drag.
+ bbox = ann.get_window_extent()
+ MouseEvent("button_press_event", fig.canvas, bbox.x1+2, bbox.y1+2)._process()
+ assert not ann._draggable.got_artist
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_patches.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_patches.py
new file mode 100644
index 0000000000000000000000000000000000000000..98e45dbad9e089d9c768d2eea96e176adf05d404
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_patches.py
@@ -0,0 +1,1007 @@
+"""
+Tests specific to the patches module.
+"""
+import platform
+
+import numpy as np
+from numpy.testing import assert_almost_equal, assert_array_equal
+import pytest
+
+import matplotlib as mpl
+from matplotlib.patches import (Annulus, Ellipse, Patch, Polygon, Rectangle,
+ FancyArrowPatch, FancyArrow, BoxStyle, Arc)
+from matplotlib.testing.decorators import image_comparison, check_figures_equal
+from matplotlib.transforms import Bbox
+import matplotlib.pyplot as plt
+from matplotlib import (
+ collections as mcollections, colors as mcolors, patches as mpatches,
+ path as mpath, transforms as mtransforms, rcParams)
+
+
+def test_Polygon_close():
+ #: GitHub issue #1018 identified a bug in the Polygon handling
+ #: of the closed attribute; the path was not getting closed
+ #: when set_xy was used to set the vertices.
+
+ # open set of vertices:
+ xy = [[0, 0], [0, 1], [1, 1]]
+ # closed set:
+ xyclosed = xy + [[0, 0]]
+
+ # start with open path and close it:
+ p = Polygon(xy, closed=True)
+ assert p.get_closed()
+ assert_array_equal(p.get_xy(), xyclosed)
+ p.set_xy(xy)
+ assert_array_equal(p.get_xy(), xyclosed)
+
+ # start with closed path and open it:
+ p = Polygon(xyclosed, closed=False)
+ assert_array_equal(p.get_xy(), xy)
+ p.set_xy(xyclosed)
+ assert_array_equal(p.get_xy(), xy)
+
+ # start with open path and leave it open:
+ p = Polygon(xy, closed=False)
+ assert not p.get_closed()
+ assert_array_equal(p.get_xy(), xy)
+ p.set_xy(xy)
+ assert_array_equal(p.get_xy(), xy)
+
+ # start with closed path and leave it closed:
+ p = Polygon(xyclosed, closed=True)
+ assert_array_equal(p.get_xy(), xyclosed)
+ p.set_xy(xyclosed)
+ assert_array_equal(p.get_xy(), xyclosed)
+
+
+def test_corner_center():
+ loc = [10, 20]
+ width = 1
+ height = 2
+
+ # Rectangle
+ # No rotation
+ corners = ((10, 20), (11, 20), (11, 22), (10, 22))
+ rect = Rectangle(loc, width, height)
+ assert_array_equal(rect.get_corners(), corners)
+ assert_array_equal(rect.get_center(), (10.5, 21))
+
+ # 90 deg rotation
+ corners_rot = ((10, 20), (10, 21), (8, 21), (8, 20))
+ rect.set_angle(90)
+ assert_array_equal(rect.get_corners(), corners_rot)
+ assert_array_equal(rect.get_center(), (9, 20.5))
+
+ # Rotation not a multiple of 90 deg
+ theta = 33
+ t = mtransforms.Affine2D().rotate_around(*loc, np.deg2rad(theta))
+ corners_rot = t.transform(corners)
+ rect.set_angle(theta)
+ assert_almost_equal(rect.get_corners(), corners_rot)
+
+ # Ellipse
+ loc = [loc[0] + width / 2,
+ loc[1] + height / 2]
+ ellipse = Ellipse(loc, width, height)
+
+ # No rotation
+ assert_array_equal(ellipse.get_corners(), corners)
+
+ # 90 deg rotation
+ corners_rot = ((11.5, 20.5), (11.5, 21.5), (9.5, 21.5), (9.5, 20.5))
+ ellipse.set_angle(90)
+ assert_array_equal(ellipse.get_corners(), corners_rot)
+ # Rotation shouldn't change ellipse center
+ assert_array_equal(ellipse.get_center(), loc)
+
+ # Rotation not a multiple of 90 deg
+ theta = 33
+ t = mtransforms.Affine2D().rotate_around(*loc, np.deg2rad(theta))
+ corners_rot = t.transform(corners)
+ ellipse.set_angle(theta)
+ assert_almost_equal(ellipse.get_corners(), corners_rot)
+
+
+def test_ellipse_vertices():
+ # expect 0 for 0 ellipse width, height
+ ellipse = Ellipse(xy=(0, 0), width=0, height=0, angle=0)
+ assert_almost_equal(
+ ellipse.get_vertices(),
+ [(0.0, 0.0), (0.0, 0.0)],
+ )
+ assert_almost_equal(
+ ellipse.get_co_vertices(),
+ [(0.0, 0.0), (0.0, 0.0)],
+ )
+
+ ellipse = Ellipse(xy=(0, 0), width=2, height=1, angle=30)
+ assert_almost_equal(
+ ellipse.get_vertices(),
+ [
+ (
+ ellipse.center[0] + ellipse.width / 4 * np.sqrt(3),
+ ellipse.center[1] + ellipse.width / 4,
+ ),
+ (
+ ellipse.center[0] - ellipse.width / 4 * np.sqrt(3),
+ ellipse.center[1] - ellipse.width / 4,
+ ),
+ ],
+ )
+ assert_almost_equal(
+ ellipse.get_co_vertices(),
+ [
+ (
+ ellipse.center[0] - ellipse.height / 4,
+ ellipse.center[1] + ellipse.height / 4 * np.sqrt(3),
+ ),
+ (
+ ellipse.center[0] + ellipse.height / 4,
+ ellipse.center[1] - ellipse.height / 4 * np.sqrt(3),
+ ),
+ ],
+ )
+ v1, v2 = np.array(ellipse.get_vertices())
+ np.testing.assert_almost_equal((v1 + v2) / 2, ellipse.center)
+ v1, v2 = np.array(ellipse.get_co_vertices())
+ np.testing.assert_almost_equal((v1 + v2) / 2, ellipse.center)
+
+ ellipse = Ellipse(xy=(2.252, -10.859), width=2.265, height=1.98, angle=68.78)
+ v1, v2 = np.array(ellipse.get_vertices())
+ np.testing.assert_almost_equal((v1 + v2) / 2, ellipse.center)
+ v1, v2 = np.array(ellipse.get_co_vertices())
+ np.testing.assert_almost_equal((v1 + v2) / 2, ellipse.center)
+
+
+def test_rotate_rect():
+ loc = np.asarray([1.0, 2.0])
+ width = 2
+ height = 3
+ angle = 30.0
+
+ # A rotated rectangle
+ rect1 = Rectangle(loc, width, height, angle=angle)
+
+ # A non-rotated rectangle
+ rect2 = Rectangle(loc, width, height)
+
+ # Set up an explicit rotation matrix (in radians)
+ angle_rad = np.pi * angle / 180.0
+ rotation_matrix = np.array([[np.cos(angle_rad), -np.sin(angle_rad)],
+ [np.sin(angle_rad), np.cos(angle_rad)]])
+
+ # Translate to origin, rotate each vertex, and then translate back
+ new_verts = np.inner(rotation_matrix, rect2.get_verts() - loc).T + loc
+
+ # They should be the same
+ assert_almost_equal(rect1.get_verts(), new_verts)
+
+
+@check_figures_equal(extensions=['png'])
+def test_rotate_rect_draw(fig_test, fig_ref):
+ ax_test = fig_test.add_subplot()
+ ax_ref = fig_ref.add_subplot()
+
+ loc = (0, 0)
+ width, height = (1, 1)
+ angle = 30
+ rect_ref = Rectangle(loc, width, height, angle=angle)
+ ax_ref.add_patch(rect_ref)
+ assert rect_ref.get_angle() == angle
+
+ # Check that when the angle is updated after adding to an Axes, that the
+ # patch is marked stale and redrawn in the correct location
+ rect_test = Rectangle(loc, width, height)
+ assert rect_test.get_angle() == 0
+ ax_test.add_patch(rect_test)
+ rect_test.set_angle(angle)
+ assert rect_test.get_angle() == angle
+
+
+@check_figures_equal(extensions=['png'])
+def test_dash_offset_patch_draw(fig_test, fig_ref):
+ ax_test = fig_test.add_subplot()
+ ax_ref = fig_ref.add_subplot()
+
+ loc = (0.1, 0.1)
+ width, height = (0.8, 0.8)
+ rect_ref = Rectangle(loc, width, height, linewidth=3, edgecolor='b',
+ linestyle=(0, [6, 6]))
+ # fill the line gaps using a linestyle (0, [0, 6, 6, 0]), which is
+ # equivalent to (6, [6, 6]) but has 0 dash offset
+ rect_ref2 = Rectangle(loc, width, height, linewidth=3, edgecolor='r',
+ linestyle=(0, [0, 6, 6, 0]))
+ assert rect_ref.get_linestyle() == (0, [6, 6])
+ assert rect_ref2.get_linestyle() == (0, [0, 6, 6, 0])
+
+ ax_ref.add_patch(rect_ref)
+ ax_ref.add_patch(rect_ref2)
+
+ # Check that the dash offset of the rect is the same if we pass it in the
+ # init method and if we create two rects with appropriate onoff sequence
+ # of linestyle.
+
+ rect_test = Rectangle(loc, width, height, linewidth=3, edgecolor='b',
+ linestyle=(0, [6, 6]))
+ rect_test2 = Rectangle(loc, width, height, linewidth=3, edgecolor='r',
+ linestyle=(6, [6, 6]))
+ assert rect_test.get_linestyle() == (0, [6, 6])
+ assert rect_test2.get_linestyle() == (6, [6, 6])
+
+ ax_test.add_patch(rect_test)
+ ax_test.add_patch(rect_test2)
+
+
+def test_negative_rect():
+ # These two rectangles have the same vertices, but starting from a
+ # different point. (We also drop the last vertex, which is a duplicate.)
+ pos_vertices = Rectangle((-3, -2), 3, 2).get_verts()[:-1]
+ neg_vertices = Rectangle((0, 0), -3, -2).get_verts()[:-1]
+ assert_array_equal(np.roll(neg_vertices, 2, 0), pos_vertices)
+
+
+@image_comparison(['clip_to_bbox.png'])
+def test_clip_to_bbox():
+ fig, ax = plt.subplots()
+ ax.set_xlim([-18, 20])
+ ax.set_ylim([-150, 100])
+
+ path = mpath.Path.unit_regular_star(8).deepcopy()
+ path.vertices *= [10, 100]
+ path.vertices -= [5, 25]
+
+ path2 = mpath.Path.unit_circle().deepcopy()
+ path2.vertices *= [10, 100]
+ path2.vertices += [10, -25]
+
+ combined = mpath.Path.make_compound_path(path, path2)
+
+ patch = mpatches.PathPatch(
+ combined, alpha=0.5, facecolor='coral', edgecolor='none')
+ ax.add_patch(patch)
+
+ bbox = mtransforms.Bbox([[-12, -77.5], [50, -110]])
+ result_path = combined.clip_to_bbox(bbox)
+ result_patch = mpatches.PathPatch(
+ result_path, alpha=0.5, facecolor='green', lw=4, edgecolor='black')
+
+ ax.add_patch(result_patch)
+
+
+@image_comparison(['patch_alpha_coloring'], remove_text=True)
+def test_patch_alpha_coloring():
+ """
+ Test checks that the patch and collection are rendered with the specified
+ alpha values in their facecolor and edgecolor.
+ """
+ star = mpath.Path.unit_regular_star(6)
+ circle = mpath.Path.unit_circle()
+ # concatenate the star with an internal cutout of the circle
+ verts = np.concatenate([circle.vertices, star.vertices[::-1]])
+ codes = np.concatenate([circle.codes, star.codes])
+ cut_star1 = mpath.Path(verts, codes)
+ cut_star2 = mpath.Path(verts + 1, codes)
+
+ ax = plt.axes()
+ col = mcollections.PathCollection([cut_star2],
+ linewidth=5, linestyles='dashdot',
+ facecolor=(1, 0, 0, 0.5),
+ edgecolor=(0, 0, 1, 0.75))
+ ax.add_collection(col)
+
+ patch = mpatches.PathPatch(cut_star1,
+ linewidth=5, linestyle='dashdot',
+ facecolor=(1, 0, 0, 0.5),
+ edgecolor=(0, 0, 1, 0.75))
+ ax.add_patch(patch)
+
+ ax.set_xlim(-1, 2)
+ ax.set_ylim(-1, 2)
+
+
+@image_comparison(['patch_alpha_override'], remove_text=True)
+def test_patch_alpha_override():
+ #: Test checks that specifying an alpha attribute for a patch or
+ #: collection will override any alpha component of the facecolor
+ #: or edgecolor.
+ star = mpath.Path.unit_regular_star(6)
+ circle = mpath.Path.unit_circle()
+ # concatenate the star with an internal cutout of the circle
+ verts = np.concatenate([circle.vertices, star.vertices[::-1]])
+ codes = np.concatenate([circle.codes, star.codes])
+ cut_star1 = mpath.Path(verts, codes)
+ cut_star2 = mpath.Path(verts + 1, codes)
+
+ ax = plt.axes()
+ col = mcollections.PathCollection([cut_star2],
+ linewidth=5, linestyles='dashdot',
+ alpha=0.25,
+ facecolor=(1, 0, 0, 0.5),
+ edgecolor=(0, 0, 1, 0.75))
+ ax.add_collection(col)
+
+ patch = mpatches.PathPatch(cut_star1,
+ linewidth=5, linestyle='dashdot',
+ alpha=0.25,
+ facecolor=(1, 0, 0, 0.5),
+ edgecolor=(0, 0, 1, 0.75))
+ ax.add_patch(patch)
+
+ ax.set_xlim(-1, 2)
+ ax.set_ylim(-1, 2)
+
+
+@mpl.style.context('default')
+def test_patch_color_none():
+ # Make sure the alpha kwarg does not override 'none' facecolor.
+ # Addresses issue #7478.
+ c = plt.Circle((0, 0), 1, facecolor='none', alpha=1)
+ assert c.get_facecolor()[0] == 0
+
+
+@image_comparison(['patch_custom_linestyle'], remove_text=True)
+def test_patch_custom_linestyle():
+ #: A test to check that patches and collections accept custom dash
+ #: patterns as linestyle and that they display correctly.
+ star = mpath.Path.unit_regular_star(6)
+ circle = mpath.Path.unit_circle()
+ # concatenate the star with an internal cutout of the circle
+ verts = np.concatenate([circle.vertices, star.vertices[::-1]])
+ codes = np.concatenate([circle.codes, star.codes])
+ cut_star1 = mpath.Path(verts, codes)
+ cut_star2 = mpath.Path(verts + 1, codes)
+
+ ax = plt.axes()
+ col = mcollections.PathCollection(
+ [cut_star2],
+ linewidth=5, linestyles=[(0, (5, 7, 10, 7))],
+ facecolor=(1, 0, 0), edgecolor=(0, 0, 1))
+ ax.add_collection(col)
+
+ patch = mpatches.PathPatch(
+ cut_star1,
+ linewidth=5, linestyle=(0, (5, 7, 10, 7)),
+ facecolor=(1, 0, 0), edgecolor=(0, 0, 1))
+ ax.add_patch(patch)
+
+ ax.set_xlim(-1, 2)
+ ax.set_ylim(-1, 2)
+
+
+def test_patch_linestyle_accents():
+ #: Test if linestyle can also be specified with short mnemonics like "--"
+ #: c.f. GitHub issue #2136
+ star = mpath.Path.unit_regular_star(6)
+ circle = mpath.Path.unit_circle()
+ # concatenate the star with an internal cutout of the circle
+ verts = np.concatenate([circle.vertices, star.vertices[::-1]])
+ codes = np.concatenate([circle.codes, star.codes])
+
+ linestyles = ["-", "--", "-.", ":",
+ "solid", "dashed", "dashdot", "dotted"]
+
+ fig, ax = plt.subplots()
+ for i, ls in enumerate(linestyles):
+ star = mpath.Path(verts + i, codes)
+ patch = mpatches.PathPatch(star,
+ linewidth=3, linestyle=ls,
+ facecolor=(1, 0, 0),
+ edgecolor=(0, 0, 1))
+ ax.add_patch(patch)
+
+ ax.set_xlim([-1, i + 1])
+ ax.set_ylim([-1, i + 1])
+ fig.canvas.draw()
+
+
+@check_figures_equal(extensions=['png'])
+def test_patch_linestyle_none(fig_test, fig_ref):
+ circle = mpath.Path.unit_circle()
+
+ ax_test = fig_test.add_subplot()
+ ax_ref = fig_ref.add_subplot()
+ for i, ls in enumerate(['none', 'None', ' ', '']):
+ path = mpath.Path(circle.vertices + i, circle.codes)
+ patch = mpatches.PathPatch(path,
+ linewidth=3, linestyle=ls,
+ facecolor=(1, 0, 0),
+ edgecolor=(0, 0, 1))
+ ax_test.add_patch(patch)
+
+ patch = mpatches.PathPatch(path,
+ linewidth=3, linestyle='-',
+ facecolor=(1, 0, 0),
+ edgecolor='none')
+ ax_ref.add_patch(patch)
+
+ ax_test.set_xlim([-1, i + 1])
+ ax_test.set_ylim([-1, i + 1])
+ ax_ref.set_xlim([-1, i + 1])
+ ax_ref.set_ylim([-1, i + 1])
+
+
+def test_wedge_movement():
+ param_dict = {'center': ((0, 0), (1, 1), 'set_center'),
+ 'r': (5, 8, 'set_radius'),
+ 'width': (2, 3, 'set_width'),
+ 'theta1': (0, 30, 'set_theta1'),
+ 'theta2': (45, 50, 'set_theta2')}
+
+ init_args = {k: v[0] for k, v in param_dict.items()}
+
+ w = mpatches.Wedge(**init_args)
+ for attr, (old_v, new_v, func) in param_dict.items():
+ assert getattr(w, attr) == old_v
+ getattr(w, func)(new_v)
+ assert getattr(w, attr) == new_v
+
+
+@image_comparison(['wedge_range'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.009)
+def test_wedge_range():
+ ax = plt.axes()
+
+ t1 = 2.313869244286224
+
+ args = [[52.31386924, 232.31386924],
+ [52.313869244286224, 232.31386924428622],
+ [t1, t1 + 180.0],
+ [0, 360],
+ [90, 90 + 360],
+ [-180, 180],
+ [0, 380],
+ [45, 46],
+ [46, 45]]
+
+ for i, (theta1, theta2) in enumerate(args):
+ x = i % 3
+ y = i // 3
+
+ wedge = mpatches.Wedge((x * 3, y * 3), 1, theta1, theta2,
+ facecolor='none', edgecolor='k', lw=3)
+
+ ax.add_artist(wedge)
+
+ ax.set_xlim(-2, 8)
+ ax.set_ylim(-2, 9)
+
+
+def test_patch_str():
+ """
+ Check that patches have nice and working `str` representation.
+
+ Note that the logic is that `__str__` is defined such that:
+ str(eval(str(p))) == str(p)
+ """
+ p = mpatches.Circle(xy=(1, 2), radius=3)
+ assert str(p) == 'Circle(xy=(1, 2), radius=3)'
+
+ p = mpatches.Ellipse(xy=(1, 2), width=3, height=4, angle=5)
+ assert str(p) == 'Ellipse(xy=(1, 2), width=3, height=4, angle=5)'
+
+ p = mpatches.Rectangle(xy=(1, 2), width=3, height=4, angle=5)
+ assert str(p) == 'Rectangle(xy=(1, 2), width=3, height=4, angle=5)'
+
+ p = mpatches.Wedge(center=(1, 2), r=3, theta1=4, theta2=5, width=6)
+ assert str(p) == 'Wedge(center=(1, 2), r=3, theta1=4, theta2=5, width=6)'
+
+ p = mpatches.Arc(xy=(1, 2), width=3, height=4, angle=5, theta1=6, theta2=7)
+ expected = 'Arc(xy=(1, 2), width=3, height=4, angle=5, theta1=6, theta2=7)'
+ assert str(p) == expected
+
+ p = mpatches.Annulus(xy=(1, 2), r=(3, 4), width=1, angle=2)
+ expected = "Annulus(xy=(1, 2), r=(3, 4), width=1, angle=2)"
+ assert str(p) == expected
+
+ p = mpatches.RegularPolygon((1, 2), 20, radius=5)
+ assert str(p) == "RegularPolygon((1, 2), 20, radius=5, orientation=0)"
+
+ p = mpatches.CirclePolygon(xy=(1, 2), radius=5, resolution=20)
+ assert str(p) == "CirclePolygon((1, 2), radius=5, resolution=20)"
+
+ p = mpatches.FancyBboxPatch((1, 2), width=3, height=4)
+ assert str(p) == "FancyBboxPatch((1, 2), width=3, height=4)"
+
+ # Further nice __str__ which cannot be `eval`uated:
+ path = mpath.Path([(1, 2), (2, 2), (1, 2)], closed=True)
+ p = mpatches.PathPatch(path)
+ assert str(p) == "PathPatch3((1, 2) ...)"
+
+ p = mpatches.Polygon(np.empty((0, 2)))
+ assert str(p) == "Polygon0()"
+
+ data = [[1, 2], [2, 2], [1, 2]]
+ p = mpatches.Polygon(data)
+ assert str(p) == "Polygon3((1, 2) ...)"
+
+ p = mpatches.FancyArrowPatch(path=path)
+ assert str(p)[:27] == "FancyArrowPatch(Path(array("
+
+ p = mpatches.FancyArrowPatch((1, 2), (3, 4))
+ assert str(p) == "FancyArrowPatch((1, 2)->(3, 4))"
+
+ p = mpatches.ConnectionPatch((1, 2), (3, 4), 'data')
+ assert str(p) == "ConnectionPatch((1, 2), (3, 4))"
+
+ s = mpatches.Shadow(p, 1, 1)
+ assert str(s) == "Shadow(ConnectionPatch((1, 2), (3, 4)))"
+
+ # Not testing Arrow, FancyArrow here
+ # because they seem to exist only for historical reasons.
+
+
+@image_comparison(['multi_color_hatch'], remove_text=True, style='default')
+def test_multi_color_hatch():
+ fig, ax = plt.subplots()
+
+ rects = ax.bar(range(5), range(1, 6))
+ for i, rect in enumerate(rects):
+ rect.set_facecolor('none')
+ rect.set_edgecolor(f'C{i}')
+ rect.set_hatch('/')
+
+ ax.autoscale_view()
+ ax.autoscale(False)
+
+ for i in range(5):
+ with mpl.style.context({'hatch.color': f'C{i}'}):
+ r = Rectangle((i - .8 / 2, 5), .8, 1, hatch='//', fc='none')
+ ax.add_patch(r)
+
+
+@image_comparison(['units_rectangle.png'])
+def test_units_rectangle():
+ import matplotlib.testing.jpl_units as U
+ U.register()
+
+ p = mpatches.Rectangle((5*U.km, 6*U.km), 1*U.km, 2*U.km)
+
+ fig, ax = plt.subplots()
+ ax.add_patch(p)
+ ax.set_xlim([4*U.km, 7*U.km])
+ ax.set_ylim([5*U.km, 9*U.km])
+
+
+@image_comparison(['connection_patch.png'], style='mpl20', remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.024)
+def test_connection_patch():
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+
+ con = mpatches.ConnectionPatch(xyA=(0.1, 0.1), xyB=(0.9, 0.9),
+ coordsA='data', coordsB='data',
+ axesA=ax2, axesB=ax1,
+ arrowstyle="->")
+ ax2.add_artist(con)
+
+ xyA = (0.6, 1.0) # in axes coordinates
+ xyB = (0.0, 0.2) # x in axes coordinates, y in data coordinates
+ coordsA = "axes fraction"
+ coordsB = ax2.get_yaxis_transform()
+ con = mpatches.ConnectionPatch(xyA=xyA, xyB=xyB, coordsA=coordsA,
+ coordsB=coordsB, arrowstyle="-")
+ ax2.add_artist(con)
+
+
+@check_figures_equal(extensions=["png"])
+def test_connection_patch_fig(fig_test, fig_ref):
+ # Test that connection patch can be added as figure artist, and that figure
+ # pixels count negative values from the top right corner (this API may be
+ # changed in the future).
+ ax1, ax2 = fig_test.subplots(1, 2)
+ con = mpatches.ConnectionPatch(
+ xyA=(.3, .2), coordsA="data", axesA=ax1,
+ xyB=(-30, -20), coordsB="figure pixels",
+ arrowstyle="->", shrinkB=5)
+ fig_test.add_artist(con)
+
+ ax1, ax2 = fig_ref.subplots(1, 2)
+ bb = fig_ref.bbox
+ # Necessary so that pixel counts match on both sides.
+ plt.rcParams["savefig.dpi"] = plt.rcParams["figure.dpi"]
+ con = mpatches.ConnectionPatch(
+ xyA=(.3, .2), coordsA="data", axesA=ax1,
+ xyB=(bb.width - 30, bb.height - 20), coordsB="figure pixels",
+ arrowstyle="->", shrinkB=5)
+ fig_ref.add_artist(con)
+
+
+@check_figures_equal(extensions=["png"])
+def test_connection_patch_pixel_points(fig_test, fig_ref):
+ xyA_pts = (.3, .2)
+ xyB_pts = (-30, -20)
+
+ ax1, ax2 = fig_test.subplots(1, 2)
+ con = mpatches.ConnectionPatch(xyA=xyA_pts, coordsA="axes points", axesA=ax1,
+ xyB=xyB_pts, coordsB="figure points",
+ arrowstyle="->", shrinkB=5)
+ fig_test.add_artist(con)
+
+ plt.rcParams["savefig.dpi"] = plt.rcParams["figure.dpi"]
+
+ ax1, ax2 = fig_ref.subplots(1, 2)
+ xyA_pix = (xyA_pts[0]*(fig_ref.dpi/72), xyA_pts[1]*(fig_ref.dpi/72))
+ xyB_pix = (xyB_pts[0]*(fig_ref.dpi/72), xyB_pts[1]*(fig_ref.dpi/72))
+ con = mpatches.ConnectionPatch(xyA=xyA_pix, coordsA="axes pixels", axesA=ax1,
+ xyB=xyB_pix, coordsB="figure pixels",
+ arrowstyle="->", shrinkB=5)
+ fig_ref.add_artist(con)
+
+
+def test_datetime_rectangle():
+ # Check that creating a rectangle with timedeltas doesn't fail
+ from datetime import datetime, timedelta
+
+ start = datetime(2017, 1, 1, 0, 0, 0)
+ delta = timedelta(seconds=16)
+ patch = mpatches.Rectangle((start, 0), delta, 1)
+
+ fig, ax = plt.subplots()
+ ax.add_patch(patch)
+
+
+def test_datetime_datetime_fails():
+ from datetime import datetime
+
+ start = datetime(2017, 1, 1, 0, 0, 0)
+ dt_delta = datetime(1970, 1, 5) # Will be 5 days if units are done wrong.
+
+ with pytest.raises(TypeError):
+ mpatches.Rectangle((start, 0), dt_delta, 1)
+
+ with pytest.raises(TypeError):
+ mpatches.Rectangle((0, start), 1, dt_delta)
+
+
+def test_contains_point():
+ ell = mpatches.Ellipse((0.5, 0.5), 0.5, 1.0)
+ points = [(0.0, 0.5), (0.2, 0.5), (0.25, 0.5), (0.5, 0.5)]
+ path = ell.get_path()
+ transform = ell.get_transform()
+ radius = ell._process_radius(None)
+ expected = np.array([path.contains_point(point,
+ transform,
+ radius) for point in points])
+ result = np.array([ell.contains_point(point) for point in points])
+ assert np.all(result == expected)
+
+
+def test_contains_points():
+ ell = mpatches.Ellipse((0.5, 0.5), 0.5, 1.0)
+ points = [(0.0, 0.5), (0.2, 0.5), (0.25, 0.5), (0.5, 0.5)]
+ path = ell.get_path()
+ transform = ell.get_transform()
+ radius = ell._process_radius(None)
+ expected = path.contains_points(points, transform, radius)
+ result = ell.contains_points(points)
+ assert np.all(result == expected)
+
+
+# Currently fails with pdf/svg, probably because some parts assume a dpi of 72.
+@check_figures_equal(extensions=["png"])
+def test_shadow(fig_test, fig_ref):
+ xy = np.array([.2, .3])
+ dxy = np.array([.1, .2])
+ # We need to work around the nonsensical (dpi-dependent) interpretation of
+ # offsets by the Shadow class...
+ plt.rcParams["savefig.dpi"] = "figure"
+ # Test image.
+ a1 = fig_test.subplots()
+ rect = mpatches.Rectangle(xy=xy, width=.5, height=.5)
+ shadow = mpatches.Shadow(rect, ox=dxy[0], oy=dxy[1])
+ a1.add_patch(rect)
+ a1.add_patch(shadow)
+ # Reference image.
+ a2 = fig_ref.subplots()
+ rect = mpatches.Rectangle(xy=xy, width=.5, height=.5)
+ shadow = mpatches.Rectangle(
+ xy=xy + fig_ref.dpi / 72 * dxy, width=.5, height=.5,
+ fc=np.asarray(mcolors.to_rgb(rect.get_facecolor())) * .3,
+ ec=np.asarray(mcolors.to_rgb(rect.get_facecolor())) * .3,
+ alpha=.5)
+ a2.add_patch(shadow)
+ a2.add_patch(rect)
+
+
+def test_fancyarrow_units():
+ from datetime import datetime
+ # Smoke test to check that FancyArrowPatch works with units
+ dtime = datetime(2000, 1, 1)
+ fig, ax = plt.subplots()
+ arrow = FancyArrowPatch((0, dtime), (0.01, dtime))
+
+
+def test_fancyarrow_setdata():
+ fig, ax = plt.subplots()
+ arrow = ax.arrow(0, 0, 10, 10, head_length=5, head_width=1, width=.5)
+ expected1 = np.array(
+ [[13.54, 13.54],
+ [10.35, 9.65],
+ [10.18, 9.82],
+ [0.18, -0.18],
+ [-0.18, 0.18],
+ [9.82, 10.18],
+ [9.65, 10.35],
+ [13.54, 13.54]]
+ )
+ assert np.allclose(expected1, np.round(arrow.verts, 2))
+
+ expected2 = np.array(
+ [[16.71, 16.71],
+ [16.71, 15.29],
+ [16.71, 15.29],
+ [1.71, 0.29],
+ [0.29, 1.71],
+ [15.29, 16.71],
+ [15.29, 16.71],
+ [16.71, 16.71]]
+ )
+ arrow.set_data(
+ x=1, y=1, dx=15, dy=15, width=2, head_width=2, head_length=1
+ )
+ assert np.allclose(expected2, np.round(arrow.verts, 2))
+
+
+@image_comparison(["large_arc.svg"], style="mpl20")
+def test_large_arc():
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+ x = 210
+ y = -2115
+ diameter = 4261
+ for ax in [ax1, ax2]:
+ a = Arc((x, y), diameter, diameter, lw=2, color='k')
+ ax.add_patch(a)
+ ax.set_axis_off()
+ ax.set_aspect('equal')
+ # force the high accuracy case
+ ax1.set_xlim(7, 8)
+ ax1.set_ylim(5, 6)
+
+ # force the low accuracy case
+ ax2.set_xlim(-25000, 18000)
+ ax2.set_ylim(-20000, 6600)
+
+
+@image_comparison(["all_quadrants_arcs.svg"], style="mpl20")
+def test_rotated_arcs():
+ fig, ax_arr = plt.subplots(2, 2, squeeze=False, figsize=(10, 10))
+
+ scale = 10_000_000
+ diag_centers = ((-1, -1), (-1, 1), (1, 1), (1, -1))
+ on_axis_centers = ((0, 1), (1, 0), (0, -1), (-1, 0))
+ skews = ((2, 2), (2, 1/10), (2, 1/100), (2, 1/1000))
+
+ for ax, (sx, sy) in zip(ax_arr.ravel(), skews):
+ k = 0
+ for prescale, centers in zip((1 - .0001, (1 - .0001) / np.sqrt(2)),
+ (on_axis_centers, diag_centers)):
+ for j, (x_sign, y_sign) in enumerate(centers, start=k):
+ a = Arc(
+ (x_sign * scale * prescale,
+ y_sign * scale * prescale),
+ scale * sx,
+ scale * sy,
+ lw=4,
+ color=f"C{j}",
+ zorder=1 + j,
+ angle=np.rad2deg(np.arctan2(y_sign, x_sign)) % 360,
+ label=f'big {j}',
+ gid=f'big {j}'
+ )
+ ax.add_patch(a)
+
+ k = j+1
+ ax.set_xlim(-scale / 4000, scale / 4000)
+ ax.set_ylim(-scale / 4000, scale / 4000)
+ ax.axhline(0, color="k")
+ ax.axvline(0, color="k")
+ ax.set_axis_off()
+ ax.set_aspect("equal")
+
+
+def test_fancyarrow_shape_error():
+ with pytest.raises(ValueError, match="Got unknown shape: 'foo'"):
+ FancyArrow(0, 0, 0.2, 0.2, shape='foo')
+
+
+@pytest.mark.parametrize('fmt, match', (
+ ("foo", "Unknown style: 'foo'"),
+ ("Round,foo", "Incorrect style argument: 'Round,foo'"),
+))
+def test_boxstyle_errors(fmt, match):
+ with pytest.raises(ValueError, match=match):
+ BoxStyle(fmt)
+
+
+@image_comparison(baseline_images=['annulus'], extensions=['png'])
+def test_annulus():
+
+ fig, ax = plt.subplots()
+ cir = Annulus((0.5, 0.5), 0.2, 0.05, fc='g') # circular annulus
+ ell = Annulus((0.5, 0.5), (0.5, 0.3), 0.1, 45, # elliptical
+ fc='m', ec='b', alpha=0.5, hatch='xxx')
+ ax.add_patch(cir)
+ ax.add_patch(ell)
+ ax.set_aspect('equal')
+
+
+@image_comparison(baseline_images=['annulus'], extensions=['png'])
+def test_annulus_setters():
+
+ fig, ax = plt.subplots()
+ cir = Annulus((0., 0.), 0.2, 0.01, fc='g') # circular annulus
+ ell = Annulus((0., 0.), (1, 2), 0.1, 0, # elliptical
+ fc='m', ec='b', alpha=0.5, hatch='xxx')
+ ax.add_patch(cir)
+ ax.add_patch(ell)
+ ax.set_aspect('equal')
+
+ cir.center = (0.5, 0.5)
+ cir.radii = 0.2
+ cir.width = 0.05
+
+ ell.center = (0.5, 0.5)
+ ell.radii = (0.5, 0.3)
+ ell.width = 0.1
+ ell.angle = 45
+
+
+@image_comparison(baseline_images=['annulus'], extensions=['png'])
+def test_annulus_setters2():
+
+ fig, ax = plt.subplots()
+ cir = Annulus((0., 0.), 0.2, 0.01, fc='g') # circular annulus
+ ell = Annulus((0., 0.), (1, 2), 0.1, 0, # elliptical
+ fc='m', ec='b', alpha=0.5, hatch='xxx')
+ ax.add_patch(cir)
+ ax.add_patch(ell)
+ ax.set_aspect('equal')
+
+ cir.center = (0.5, 0.5)
+ cir.set_semimajor(0.2)
+ cir.set_semiminor(0.2)
+ assert cir.radii == (0.2, 0.2)
+ cir.width = 0.05
+
+ ell.center = (0.5, 0.5)
+ ell.set_semimajor(0.5)
+ ell.set_semiminor(0.3)
+ assert ell.radii == (0.5, 0.3)
+ ell.width = 0.1
+ ell.angle = 45
+
+
+def test_degenerate_polygon():
+ point = [0, 0]
+ correct_extents = Bbox([point, point]).extents
+ assert np.all(Polygon([point]).get_extents().extents == correct_extents)
+
+
+@pytest.mark.parametrize('kwarg', ('edgecolor', 'facecolor'))
+def test_color_override_warning(kwarg):
+ with pytest.warns(UserWarning,
+ match="Setting the 'color' property will override "
+ "the edgecolor or facecolor properties."):
+ Patch(color='black', **{kwarg: 'black'})
+
+
+def test_empty_verts():
+ poly = Polygon(np.zeros((0, 2)))
+ assert poly.get_verts() == []
+
+
+def test_default_antialiased():
+ patch = Patch()
+
+ patch.set_antialiased(not rcParams['patch.antialiased'])
+ assert patch.get_antialiased() == (not rcParams['patch.antialiased'])
+ # Check that None resets the state
+ patch.set_antialiased(None)
+ assert patch.get_antialiased() == rcParams['patch.antialiased']
+
+
+def test_default_linestyle():
+ patch = Patch()
+ patch.set_linestyle('--')
+ patch.set_linestyle(None)
+ assert patch.get_linestyle() == 'solid'
+
+
+def test_default_capstyle():
+ patch = Patch()
+ assert patch.get_capstyle() == 'butt'
+
+
+def test_default_joinstyle():
+ patch = Patch()
+ assert patch.get_joinstyle() == 'miter'
+
+
+@image_comparison(["autoscale_arc"], extensions=['png', 'svg'],
+ style="mpl20", remove_text=True)
+def test_autoscale_arc():
+ fig, axs = plt.subplots(1, 3, figsize=(4, 1))
+ arc_lists = (
+ [Arc((0, 0), 1, 1, theta1=0, theta2=90)],
+ [Arc((0.5, 0.5), 1.5, 0.5, theta1=10, theta2=20)],
+ [Arc((0.5, 0.5), 1.5, 0.5, theta1=10, theta2=20),
+ Arc((0.5, 0.5), 2.5, 0.5, theta1=110, theta2=120),
+ Arc((0.5, 0.5), 3.5, 0.5, theta1=210, theta2=220),
+ Arc((0.5, 0.5), 4.5, 0.5, theta1=310, theta2=320)])
+
+ for ax, arcs in zip(axs, arc_lists):
+ for arc in arcs:
+ ax.add_patch(arc)
+ ax.autoscale()
+
+
+@check_figures_equal(extensions=["png", 'svg', 'pdf', 'eps'])
+def test_arc_in_collection(fig_test, fig_ref):
+ arc1 = Arc([.5, .5], .5, 1, theta1=0, theta2=60, angle=20)
+ arc2 = Arc([.5, .5], .5, 1, theta1=0, theta2=60, angle=20)
+ col = mcollections.PatchCollection(patches=[arc2], facecolors='none',
+ edgecolors='k')
+ fig_ref.subplots().add_patch(arc1)
+ fig_test.subplots().add_collection(col)
+
+
+@check_figures_equal(extensions=["png", 'svg', 'pdf', 'eps'])
+def test_modifying_arc(fig_test, fig_ref):
+ arc1 = Arc([.5, .5], .5, 1, theta1=0, theta2=60, angle=20)
+ arc2 = Arc([.5, .5], 1.5, 1, theta1=0, theta2=60, angle=10)
+ fig_ref.subplots().add_patch(arc1)
+ fig_test.subplots().add_patch(arc2)
+ arc2.set_width(.5)
+ arc2.set_angle(20)
+
+
+def test_arrow_set_data():
+ fig, ax = plt.subplots()
+ arrow = mpl.patches.Arrow(2, 0, 0, 10)
+ expected1 = np.array(
+ [[1.9, 0.],
+ [2.1, -0.],
+ [2.1, 8.],
+ [2.3, 8.],
+ [2., 10.],
+ [1.7, 8.],
+ [1.9, 8.],
+ [1.9, 0.]]
+ )
+ assert np.allclose(expected1, np.round(arrow.get_verts(), 2))
+
+ expected2 = np.array(
+ [[0.39, 0.04],
+ [0.61, -0.04],
+ [3.01, 6.36],
+ [3.24, 6.27],
+ [3.5, 8.],
+ [2.56, 6.53],
+ [2.79, 6.44],
+ [0.39, 0.04]]
+ )
+ arrow.set_data(x=.5, dx=3, dy=8, width=1.2)
+ assert np.allclose(expected2, np.round(arrow.get_verts(), 2))
+
+
+@check_figures_equal(extensions=["png", "pdf", "svg", "eps"])
+def test_set_and_get_hatch_linewidth(fig_test, fig_ref):
+ ax_test = fig_test.add_subplot()
+ ax_ref = fig_ref.add_subplot()
+
+ lw = 2.0
+
+ with plt.rc_context({"hatch.linewidth": lw}):
+ ax_ref.add_patch(mpatches.Rectangle((0, 0), 1, 1, hatch="x"))
+
+ ax_test.add_patch(mpatches.Rectangle((0, 0), 1, 1, hatch="x"))
+ ax_test.patches[0].set_hatch_linewidth(lw)
+
+ assert ax_ref.patches[0].get_hatch_linewidth() == lw
+ assert ax_test.patches[0].get_hatch_linewidth() == lw
+
+
+def test_empty_fancyarrow():
+ fig, ax = plt.subplots()
+ arrow = ax.arrow([], [], [], [])
+ assert arrow is not None
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_path.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_path.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ec6209bbbb402bdd1496a72599733e66b6e5531
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_path.py
@@ -0,0 +1,658 @@
+import platform
+import re
+
+import numpy as np
+
+from numpy.testing import assert_array_equal
+import pytest
+
+from matplotlib import patches
+from matplotlib.path import Path
+from matplotlib.patches import Polygon
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.pyplot as plt
+from matplotlib import transforms
+from matplotlib.backend_bases import MouseEvent
+
+
+def test_empty_closed_path():
+ path = Path(np.zeros((0, 2)), closed=True)
+ assert path.vertices.shape == (0, 2)
+ assert path.codes is None
+ assert_array_equal(path.get_extents().extents,
+ transforms.Bbox.null().extents)
+
+
+def test_readonly_path():
+ path = Path.unit_circle()
+
+ def modify_vertices():
+ path.vertices = path.vertices * 2.0
+
+ with pytest.raises(AttributeError):
+ modify_vertices()
+
+
+def test_path_exceptions():
+ bad_verts1 = np.arange(12).reshape(4, 3)
+ with pytest.raises(ValueError,
+ match=re.escape(f'has shape {bad_verts1.shape}')):
+ Path(bad_verts1)
+
+ bad_verts2 = np.arange(12).reshape(2, 3, 2)
+ with pytest.raises(ValueError,
+ match=re.escape(f'has shape {bad_verts2.shape}')):
+ Path(bad_verts2)
+
+ good_verts = np.arange(12).reshape(6, 2)
+ bad_codes = np.arange(2)
+ msg = re.escape(f"Your vertices have shape {good_verts.shape} "
+ f"but your codes have shape {bad_codes.shape}")
+ with pytest.raises(ValueError, match=msg):
+ Path(good_verts, bad_codes)
+
+
+def test_point_in_path():
+ # Test #1787
+ path = Path._create_closed([(0, 0), (0, 1), (1, 1), (1, 0)])
+ points = [(0.5, 0.5), (1.5, 0.5)]
+ ret = path.contains_points(points)
+ assert ret.dtype == 'bool'
+ np.testing.assert_equal(ret, [True, False])
+
+
+@pytest.mark.parametrize(
+ "other_path, inside, inverted_inside",
+ [(Path([(0.25, 0.25), (0.25, 0.75), (0.75, 0.75), (0.75, 0.25), (0.25, 0.25)],
+ closed=True), True, False),
+ (Path([(-0.25, -0.25), (-0.25, 1.75), (1.75, 1.75), (1.75, -0.25), (-0.25, -0.25)],
+ closed=True), False, True),
+ (Path([(-0.25, -0.25), (-0.25, 1.75), (0.5, 0.5),
+ (1.75, 1.75), (1.75, -0.25), (-0.25, -0.25)],
+ closed=True), False, False),
+ (Path([(0.25, 0.25), (0.25, 1.25), (1.25, 1.25), (1.25, 0.25), (0.25, 0.25)],
+ closed=True), False, False),
+ (Path([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], closed=True), False, False),
+ (Path([(2, 2), (2, 3), (3, 3), (3, 2), (2, 2)], closed=True), False, False)])
+def test_contains_path(other_path, inside, inverted_inside):
+ path = Path([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], closed=True)
+ assert path.contains_path(other_path) is inside
+ assert other_path.contains_path(path) is inverted_inside
+
+
+def test_contains_points_negative_radius():
+ path = Path.unit_circle()
+
+ points = [(0.0, 0.0), (1.25, 0.0), (0.9, 0.9)]
+ result = path.contains_points(points, radius=-0.5)
+ np.testing.assert_equal(result, [True, False, False])
+
+
+_test_paths = [
+ # interior extrema determine extents and degenerate derivative
+ Path([[0, 0], [1, 0], [1, 1], [0, 1]],
+ [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]),
+ # a quadratic curve
+ Path([[0, 0], [0, 1], [1, 0]], [Path.MOVETO, Path.CURVE3, Path.CURVE3]),
+ # a linear curve, degenerate vertically
+ Path([[0, 1], [1, 1]], [Path.MOVETO, Path.LINETO]),
+ # a point
+ Path([[1, 2]], [Path.MOVETO]),
+]
+
+
+_test_path_extents = [(0., 0., 0.75, 1.), (0., 0., 1., 0.5), (0., 1., 1., 1.),
+ (1., 2., 1., 2.)]
+
+
+@pytest.mark.parametrize('path, extents', zip(_test_paths, _test_path_extents))
+def test_exact_extents(path, extents):
+ # notice that if we just looked at the control points to get the bounding
+ # box of each curve, we would get the wrong answers. For example, for
+ # hard_curve = Path([[0, 0], [1, 0], [1, 1], [0, 1]],
+ # [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4])
+ # we would get that the extents area (0, 0, 1, 1). This code takes into
+ # account the curved part of the path, which does not typically extend all
+ # the way out to the control points.
+ # Note that counterintuitively, path.get_extents() returns a Bbox, so we
+ # have to get that Bbox's `.extents`.
+ assert np.all(path.get_extents().extents == extents)
+
+
+@pytest.mark.parametrize('ignored_code', [Path.CLOSEPOLY, Path.STOP])
+def test_extents_with_ignored_codes(ignored_code):
+ # Check that STOP and CLOSEPOLY points are ignored when calculating extents
+ # of a path with only straight lines
+ path = Path([[0, 0],
+ [1, 1],
+ [2, 2]], [Path.MOVETO, Path.MOVETO, ignored_code])
+ assert np.all(path.get_extents().extents == (0., 0., 1., 1.))
+
+
+def test_point_in_path_nan():
+ box = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
+ p = Path(box)
+ test = np.array([[np.nan, 0.5]])
+ contains = p.contains_points(test)
+ assert len(contains) == 1
+ assert not contains[0]
+
+
+def test_nonlinear_containment():
+ fig, ax = plt.subplots()
+ ax.set(xscale="log", ylim=(0, 1))
+ polygon = ax.axvspan(1, 10)
+ assert polygon.get_path().contains_point(
+ ax.transData.transform((5, .5)), polygon.get_transform())
+ assert not polygon.get_path().contains_point(
+ ax.transData.transform((.5, .5)), polygon.get_transform())
+ assert not polygon.get_path().contains_point(
+ ax.transData.transform((50, .5)), polygon.get_transform())
+
+
+@image_comparison(['arrow_contains_point.png'], remove_text=True, style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.027)
+def test_arrow_contains_point():
+ # fix bug (#8384)
+ fig, ax = plt.subplots()
+ ax.set_xlim((0, 2))
+ ax.set_ylim((0, 2))
+
+ # create an arrow with Curve style
+ arrow = patches.FancyArrowPatch((0.5, 0.25), (1.5, 0.75),
+ arrowstyle='->',
+ mutation_scale=40)
+ ax.add_patch(arrow)
+ # create an arrow with Bracket style
+ arrow1 = patches.FancyArrowPatch((0.5, 1), (1.5, 1.25),
+ arrowstyle=']-[',
+ mutation_scale=40)
+ ax.add_patch(arrow1)
+ # create an arrow with other arrow style
+ arrow2 = patches.FancyArrowPatch((0.5, 1.5), (1.5, 1.75),
+ arrowstyle='fancy',
+ fill=False,
+ mutation_scale=40)
+ ax.add_patch(arrow2)
+ patches_list = [arrow, arrow1, arrow2]
+
+ # generate some points
+ X, Y = np.meshgrid(np.arange(0, 2, 0.1),
+ np.arange(0, 2, 0.1))
+ for k, (x, y) in enumerate(zip(X.ravel(), Y.ravel())):
+ xdisp, ydisp = ax.transData.transform([x, y])
+ event = MouseEvent('button_press_event', fig.canvas, xdisp, ydisp)
+ for m, patch in enumerate(patches_list):
+ # set the points to red only if the arrow contains the point
+ inside, res = patch.contains(event)
+ if inside:
+ ax.scatter(x, y, s=5, c="r")
+
+
+@image_comparison(['path_clipping.svg'], remove_text=True)
+def test_path_clipping():
+ fig = plt.figure(figsize=(6.0, 6.2))
+
+ for i, xy in enumerate([
+ [(200, 200), (200, 350), (400, 350), (400, 200)],
+ [(200, 200), (200, 350), (400, 350), (400, 100)],
+ [(200, 100), (200, 350), (400, 350), (400, 100)],
+ [(200, 100), (200, 415), (400, 350), (400, 100)],
+ [(200, 100), (200, 415), (400, 415), (400, 100)],
+ [(200, 415), (400, 415), (400, 100), (200, 100)],
+ [(400, 415), (400, 100), (200, 100), (200, 415)]]):
+ ax = fig.add_subplot(4, 2, i+1)
+ bbox = [0, 140, 640, 260]
+ ax.set_xlim(bbox[0], bbox[0] + bbox[2])
+ ax.set_ylim(bbox[1], bbox[1] + bbox[3])
+ ax.add_patch(Polygon(
+ xy, facecolor='none', edgecolor='red', closed=True))
+
+
+@image_comparison(['semi_log_with_zero.png'], style='mpl20')
+def test_log_transform_with_zero():
+ x = np.arange(-10, 10)
+ y = (1.0 - 1.0/(x**2+1))**20
+
+ fig, ax = plt.subplots()
+ ax.semilogy(x, y, "-o", lw=15, markeredgecolor='k')
+ ax.set_ylim(1e-7, 1)
+ ax.grid(True)
+
+
+def test_make_compound_path_empty():
+ # We should be able to make a compound path with no arguments.
+ # This makes it easier to write generic path based code.
+ empty = Path.make_compound_path()
+ assert empty.vertices.shape == (0, 2)
+ r2 = Path.make_compound_path(empty, empty)
+ assert r2.vertices.shape == (0, 2)
+ assert r2.codes.shape == (0,)
+ r3 = Path.make_compound_path(Path([(0, 0)]), empty)
+ assert r3.vertices.shape == (1, 2)
+ assert r3.codes.shape == (1,)
+
+
+def test_make_compound_path_stops():
+ zero = [0, 0]
+ paths = 3*[Path([zero, zero], [Path.MOVETO, Path.STOP])]
+ compound_path = Path.make_compound_path(*paths)
+ # the choice to not preserve the terminal STOP is arbitrary, but
+ # documented, so we test that it is in fact respected here
+ assert np.sum(compound_path.codes == Path.STOP) == 0
+
+
+@image_comparison(['xkcd.png'], remove_text=True)
+def test_xkcd():
+ np.random.seed(0)
+
+ x = np.linspace(0, 2 * np.pi, 100)
+ y = np.sin(x)
+
+ with plt.xkcd():
+ fig, ax = plt.subplots()
+ ax.plot(x, y)
+
+
+@image_comparison(['xkcd_marker.png'], remove_text=True)
+def test_xkcd_marker():
+ np.random.seed(0)
+
+ x = np.linspace(0, 5, 8)
+ y1 = x
+ y2 = 5 - x
+ y3 = 2.5 * np.ones(8)
+
+ with plt.xkcd():
+ fig, ax = plt.subplots()
+ ax.plot(x, y1, '+', ms=10)
+ ax.plot(x, y2, 'o', ms=10)
+ ax.plot(x, y3, '^', ms=10)
+
+
+@image_comparison(['marker_paths.pdf'], remove_text=True)
+def test_marker_paths_pdf():
+ N = 7
+
+ plt.errorbar(np.arange(N),
+ np.ones(N) + 4,
+ np.ones(N))
+ plt.xlim(-1, N)
+ plt.ylim(-1, 7)
+
+
+@image_comparison(['nan_path'], style='default', remove_text=True,
+ extensions=['pdf', 'svg', 'eps', 'png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.009)
+def test_nan_isolated_points():
+
+ y0 = [0, np.nan, 2, np.nan, 4, 5, 6]
+ y1 = [np.nan, 7, np.nan, 9, 10, np.nan, 12]
+
+ fig, ax = plt.subplots()
+
+ ax.plot(y0, '-o')
+ ax.plot(y1, '-o')
+
+
+def test_path_no_doubled_point_in_to_polygon():
+ hand = np.array(
+ [[1.64516129, 1.16145833],
+ [1.64516129, 1.59375],
+ [1.35080645, 1.921875],
+ [1.375, 2.18229167],
+ [1.68548387, 1.9375],
+ [1.60887097, 2.55208333],
+ [1.68548387, 2.69791667],
+ [1.76209677, 2.56770833],
+ [1.83064516, 1.97395833],
+ [1.89516129, 2.75],
+ [1.9516129, 2.84895833],
+ [2.01209677, 2.76041667],
+ [1.99193548, 1.99479167],
+ [2.11290323, 2.63020833],
+ [2.2016129, 2.734375],
+ [2.25403226, 2.60416667],
+ [2.14919355, 1.953125],
+ [2.30645161, 2.36979167],
+ [2.39112903, 2.36979167],
+ [2.41532258, 2.1875],
+ [2.1733871, 1.703125],
+ [2.07782258, 1.16666667]])
+
+ (r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5)
+
+ poly = Path(np.vstack((hand[:, 1], hand[:, 0])).T, closed=True)
+ clip_rect = transforms.Bbox([[r0, c0], [r1, c1]])
+ poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]
+
+ assert np.all(poly_clipped[-2] != poly_clipped[-1])
+ assert np.all(poly_clipped[-1] == poly_clipped[0])
+
+
+def test_path_to_polygons():
+ data = [[10, 10], [20, 20]]
+ p = Path(data)
+
+ assert_array_equal(p.to_polygons(width=40, height=40), [])
+ assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
+ [data])
+ assert_array_equal(p.to_polygons(), [])
+ assert_array_equal(p.to_polygons(closed_only=False), [data])
+
+ data = [[10, 10], [20, 20], [30, 30]]
+ closed_data = [[10, 10], [20, 20], [30, 30], [10, 10]]
+ p = Path(data)
+
+ assert_array_equal(p.to_polygons(width=40, height=40), [closed_data])
+ assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
+ [data])
+ assert_array_equal(p.to_polygons(), [closed_data])
+ assert_array_equal(p.to_polygons(closed_only=False), [data])
+
+
+def test_path_deepcopy():
+ # Should not raise any error
+ verts = [[0, 0], [1, 1]]
+ codes = [Path.MOVETO, Path.LINETO]
+ path1 = Path(verts, readonly=True)
+ path2 = Path(verts, codes, readonly=True)
+ path1_copy = path1.deepcopy()
+ path2_copy = path2.deepcopy()
+ assert path1 is not path1_copy
+ assert path1.vertices is not path1_copy.vertices
+ assert_array_equal(path1.vertices, path1_copy.vertices)
+ assert path1.readonly
+ assert not path1_copy.readonly
+ assert path2 is not path2_copy
+ assert path2.vertices is not path2_copy.vertices
+ assert_array_equal(path2.vertices, path2_copy.vertices)
+ assert path2.codes is not path2_copy.codes
+ assert_array_equal(path2.codes, path2_copy.codes)
+ assert path2.readonly
+ assert not path2_copy.readonly
+
+
+def test_path_deepcopy_cycle():
+ class PathWithCycle(Path):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.x = self
+
+ p = PathWithCycle([[0, 0], [1, 1]], readonly=True)
+ p_copy = p.deepcopy()
+ assert p_copy is not p
+ assert p.readonly
+ assert not p_copy.readonly
+ assert p_copy.x is p_copy
+
+ class PathWithCycle2(Path):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.x = [self] * 2
+
+ p2 = PathWithCycle2([[0, 0], [1, 1]], readonly=True)
+ p2_copy = p2.deepcopy()
+ assert p2_copy is not p2
+ assert p2.readonly
+ assert not p2_copy.readonly
+ assert p2_copy.x[0] is p2_copy
+ assert p2_copy.x[1] is p2_copy
+
+
+def test_path_shallowcopy():
+ # Should not raise any error
+ verts = [[0, 0], [1, 1]]
+ codes = [Path.MOVETO, Path.LINETO]
+ path1 = Path(verts)
+ path2 = Path(verts, codes)
+ path1_copy = path1.copy()
+ path2_copy = path2.copy()
+ assert path1 is not path1_copy
+ assert path1.vertices is path1_copy.vertices
+ assert path2 is not path2_copy
+ assert path2.vertices is path2_copy.vertices
+ assert path2.codes is path2_copy.codes
+
+
+@pytest.mark.parametrize('phi', np.concatenate([
+ np.array([0, 15, 30, 45, 60, 75, 90, 105, 120, 135]) + delta
+ for delta in [-1, 0, 1]]))
+def test_path_intersect_path(phi):
+ # test for the range of intersection angles
+ eps_array = [1e-5, 1e-8, 1e-10, 1e-12]
+
+ transform = transforms.Affine2D().rotate(np.deg2rad(phi))
+
+ # a and b intersect at angle phi
+ a = Path([(-2, 0), (2, 0)])
+ b = transform.transform_path(a)
+ assert a.intersects_path(b) and b.intersects_path(a)
+
+ # a and b touch at angle phi at (0, 0)
+ a = Path([(0, 0), (2, 0)])
+ b = transform.transform_path(a)
+ assert a.intersects_path(b) and b.intersects_path(a)
+
+ # a and b are orthogonal and intersect at (0, 3)
+ a = transform.transform_path(Path([(0, 1), (0, 3)]))
+ b = transform.transform_path(Path([(1, 3), (0, 3)]))
+ assert a.intersects_path(b) and b.intersects_path(a)
+
+ # a and b are collinear and intersect at (0, 3)
+ a = transform.transform_path(Path([(0, 1), (0, 3)]))
+ b = transform.transform_path(Path([(0, 5), (0, 3)]))
+ assert a.intersects_path(b) and b.intersects_path(a)
+
+ # self-intersect
+ assert a.intersects_path(a)
+
+ # a contains b
+ a = transform.transform_path(Path([(0, 0), (5, 5)]))
+ b = transform.transform_path(Path([(1, 1), (3, 3)]))
+ assert a.intersects_path(b) and b.intersects_path(a)
+
+ # a and b are collinear but do not intersect
+ a = transform.transform_path(Path([(0, 1), (0, 5)]))
+ b = transform.transform_path(Path([(3, 0), (3, 3)]))
+ assert not a.intersects_path(b) and not b.intersects_path(a)
+
+ # a and b are on the same line but do not intersect
+ a = transform.transform_path(Path([(0, 1), (0, 5)]))
+ b = transform.transform_path(Path([(0, 6), (0, 7)]))
+ assert not a.intersects_path(b) and not b.intersects_path(a)
+
+ # Note: 1e-13 is the absolute tolerance error used for
+ # `isclose` function from src/_path.h
+
+ # a and b are parallel but do not touch
+ for eps in eps_array:
+ a = transform.transform_path(Path([(0, 1), (0, 5)]))
+ b = transform.transform_path(Path([(0 + eps, 1), (0 + eps, 5)]))
+ assert not a.intersects_path(b) and not b.intersects_path(a)
+
+ # a and b are on the same line but do not intersect (really close)
+ for eps in eps_array:
+ a = transform.transform_path(Path([(0, 1), (0, 5)]))
+ b = transform.transform_path(Path([(0, 5 + eps), (0, 7)]))
+ assert not a.intersects_path(b) and not b.intersects_path(a)
+
+ # a and b are on the same line and intersect (really close)
+ for eps in eps_array:
+ a = transform.transform_path(Path([(0, 1), (0, 5)]))
+ b = transform.transform_path(Path([(0, 5 - eps), (0, 7)]))
+ assert a.intersects_path(b) and b.intersects_path(a)
+
+ # b is the same as a but with an extra point
+ a = transform.transform_path(Path([(0, 1), (0, 5)]))
+ b = transform.transform_path(Path([(0, 1), (0, 2), (0, 5)]))
+ assert a.intersects_path(b) and b.intersects_path(a)
+
+ # a and b are collinear but do not intersect
+ a = transform.transform_path(Path([(1, -1), (0, -1)]))
+ b = transform.transform_path(Path([(0, 1), (0.9, 1)]))
+ assert not a.intersects_path(b) and not b.intersects_path(a)
+
+ # a and b are collinear but do not intersect
+ a = transform.transform_path(Path([(0., -5.), (1., -5.)]))
+ b = transform.transform_path(Path([(1., 5.), (0., 5.)]))
+ assert not a.intersects_path(b) and not b.intersects_path(a)
+
+
+@pytest.mark.parametrize('offset', range(-720, 361, 45))
+def test_full_arc(offset):
+ low = offset
+ high = 360 + offset
+
+ path = Path.arc(low, high)
+ mins = np.min(path.vertices, axis=0)
+ maxs = np.max(path.vertices, axis=0)
+ np.testing.assert_allclose(mins, -1)
+ np.testing.assert_allclose(maxs, 1)
+
+
+def test_disjoint_zero_length_segment():
+ this_path = Path(
+ np.array([
+ [824.85064295, 2056.26489203],
+ [861.69033931, 2041.00539016],
+ [868.57864109, 2057.63522175],
+ [831.73894473, 2072.89472361],
+ [824.85064295, 2056.26489203]]),
+ np.array([1, 2, 2, 2, 79], dtype=Path.code_type))
+
+ outline_path = Path(
+ np.array([
+ [859.91051028, 2165.38461538],
+ [859.06772495, 2149.30331334],
+ [859.06772495, 2181.46591743],
+ [859.91051028, 2165.38461538],
+ [859.91051028, 2165.38461538]]),
+ np.array([1, 2, 2, 2, 2],
+ dtype=Path.code_type))
+
+ assert not outline_path.intersects_path(this_path)
+ assert not this_path.intersects_path(outline_path)
+
+
+def test_intersect_zero_length_segment():
+ this_path = Path(
+ np.array([
+ [0, 0],
+ [1, 1],
+ ]))
+
+ outline_path = Path(
+ np.array([
+ [1, 0],
+ [.5, .5],
+ [.5, .5],
+ [0, 1],
+ ]))
+
+ assert outline_path.intersects_path(this_path)
+ assert this_path.intersects_path(outline_path)
+
+
+def test_cleanup_closepoly():
+ # if the first connected component of a Path ends in a CLOSEPOLY, but that
+ # component contains a NaN, then Path.cleaned should ignore not just the
+ # control points but also the CLOSEPOLY, since it has nowhere valid to
+ # point.
+ paths = [
+ Path([[np.nan, np.nan], [np.nan, np.nan]],
+ [Path.MOVETO, Path.CLOSEPOLY]),
+ # we trigger a different path in the C++ code if we don't pass any
+ # codes explicitly, so we must also make sure that this works
+ Path([[np.nan, np.nan], [np.nan, np.nan]]),
+ # we should also make sure that this cleanup works if there's some
+ # multi-vertex curves
+ Path([[np.nan, np.nan], [np.nan, np.nan], [np.nan, np.nan],
+ [np.nan, np.nan]],
+ [Path.MOVETO, Path.CURVE3, Path.CURVE3, Path.CLOSEPOLY])
+ ]
+ for p in paths:
+ cleaned = p.cleaned(remove_nans=True)
+ assert len(cleaned) == 1
+ assert cleaned.codes[0] == Path.STOP
+
+
+def test_interpolated_moveto():
+ # Initial path has two subpaths with two LINETOs each
+ vertices = np.array([[0, 0],
+ [0, 1],
+ [1, 2],
+ [4, 4],
+ [4, 5],
+ [5, 5]])
+ codes = [Path.MOVETO, Path.LINETO, Path.LINETO] * 2
+
+ path = Path(vertices, codes)
+ result = path.interpolated(3)
+
+ # Result should have two subpaths with six LINETOs each
+ expected_subpath_codes = [Path.MOVETO] + [Path.LINETO] * 6
+ np.testing.assert_array_equal(result.codes, expected_subpath_codes * 2)
+
+
+def test_interpolated_closepoly():
+ codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY]
+ vertices = [(4, 3), (5, 4), (5, 3), (0, 0)]
+
+ path = Path(vertices, codes)
+ result = path.interpolated(2)
+
+ expected_vertices = np.array([[4, 3],
+ [4.5, 3.5],
+ [5, 4],
+ [5, 3.5],
+ [5, 3],
+ [4.5, 3],
+ [4, 3]])
+ expected_codes = [Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY]
+
+ np.testing.assert_allclose(result.vertices, expected_vertices)
+ np.testing.assert_array_equal(result.codes, expected_codes)
+
+ # Usually closepoly is the last vertex but does not have to be.
+ codes += [Path.LINETO]
+ vertices += [(2, 1)]
+
+ path = Path(vertices, codes)
+ result = path.interpolated(2)
+
+ extra_expected_vertices = np.array([[3, 2],
+ [2, 1]])
+ expected_vertices = np.concatenate([expected_vertices, extra_expected_vertices])
+
+ expected_codes += [Path.LINETO] * 2
+
+ np.testing.assert_allclose(result.vertices, expected_vertices)
+ np.testing.assert_array_equal(result.codes, expected_codes)
+
+
+def test_interpolated_moveto_closepoly():
+ # Initial path has two closed subpaths
+ codes = ([Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY]) * 2
+ vertices = [(4, 3), (5, 4), (5, 3), (0, 0), (8, 6), (10, 8), (10, 6), (0, 0)]
+
+ path = Path(vertices, codes)
+ result = path.interpolated(2)
+
+ expected_vertices1 = np.array([[4, 3],
+ [4.5, 3.5],
+ [5, 4],
+ [5, 3.5],
+ [5, 3],
+ [4.5, 3],
+ [4, 3]])
+ expected_vertices = np.concatenate([expected_vertices1, expected_vertices1 * 2])
+ expected_codes = ([Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY]) * 2
+
+ np.testing.assert_allclose(result.vertices, expected_vertices)
+ np.testing.assert_array_equal(result.codes, expected_codes)
+
+
+def test_interpolated_empty_path():
+ path = Path(np.zeros((0, 2)))
+ assert path.interpolated(42) is path
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_patheffects.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_patheffects.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c89d1c531e0c7e7504b30fefbd587b971ca0436
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_patheffects.py
@@ -0,0 +1,217 @@
+import platform
+
+import numpy as np
+
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.pyplot as plt
+import matplotlib.patheffects as path_effects
+from matplotlib.path import Path
+import matplotlib.patches as patches
+from matplotlib.backend_bases import RendererBase
+from matplotlib.patheffects import PathEffectRenderer
+
+
+@image_comparison(['patheffect1'], remove_text=True)
+def test_patheffect1():
+ ax1 = plt.subplot()
+ ax1.imshow([[1, 2], [2, 3]])
+ txt = ax1.annotate("test", (1., 1.), (0., 0),
+ arrowprops=dict(arrowstyle="->",
+ connectionstyle="angle3", lw=2),
+ size=20, ha="center",
+ path_effects=[path_effects.withStroke(linewidth=3,
+ foreground="w")])
+ txt.arrow_patch.set_path_effects([path_effects.Stroke(linewidth=5,
+ foreground="w"),
+ path_effects.Normal()])
+
+ pe = [path_effects.withStroke(linewidth=3, foreground="w")]
+ ax1.grid(True, linestyle="-", path_effects=pe)
+
+
+@image_comparison(['patheffect2'], remove_text=True, style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.06)
+def test_patheffect2():
+
+ ax2 = plt.subplot()
+ arr = np.arange(25).reshape((5, 5))
+ ax2.imshow(arr, interpolation='nearest')
+ cntr = ax2.contour(arr, colors="k")
+ cntr.set(path_effects=[path_effects.withStroke(linewidth=3, foreground="w")])
+
+ clbls = ax2.clabel(cntr, fmt="%2.0f", use_clabeltext=True)
+ plt.setp(clbls,
+ path_effects=[path_effects.withStroke(linewidth=3,
+ foreground="w")])
+
+
+@image_comparison(['patheffect3'],
+ tol=0 if platform.machine() == 'x86_64' else 0.019)
+def test_patheffect3():
+ p1, = plt.plot([1, 3, 5, 4, 3], 'o-b', lw=4)
+ p1.set_path_effects([path_effects.SimpleLineShadow(),
+ path_effects.Normal()])
+ plt.title(
+ r'testing$^{123}$',
+ path_effects=[path_effects.withStroke(linewidth=1, foreground="r")])
+ leg = plt.legend([p1], [r'Line 1$^2$'], fancybox=True, loc='upper left')
+ leg.legendPatch.set_path_effects([path_effects.withSimplePatchShadow()])
+
+ text = plt.text(2, 3, 'Drop test', color='white',
+ bbox={'boxstyle': 'circle,pad=0.1', 'color': 'red'})
+ pe = [path_effects.Stroke(linewidth=3.75, foreground='k'),
+ path_effects.withSimplePatchShadow((6, -3), shadow_rgbFace='blue')]
+ text.set_path_effects(pe)
+ text.get_bbox_patch().set_path_effects(pe)
+
+ pe = [path_effects.PathPatchEffect(offset=(4, -4), hatch='xxxx',
+ facecolor='gray'),
+ path_effects.PathPatchEffect(edgecolor='white', facecolor='black',
+ lw=1.1)]
+
+ t = plt.gcf().text(0.02, 0.1, 'Hatch shadow', fontsize=75, weight=1000,
+ va='center')
+ t.set_path_effects(pe)
+
+
+@image_comparison(['stroked_text.png'])
+def test_patheffects_stroked_text():
+ text_chunks = [
+ 'A B C D E F G H I J K L',
+ 'M N O P Q R S T U V W',
+ 'X Y Z a b c d e f g h i j',
+ 'k l m n o p q r s t u v',
+ 'w x y z 0123456789',
+ r"!@#$%^&*()-=_+[]\;'",
+ ',./{}|:"<>?'
+ ]
+ font_size = 50
+
+ ax = plt.axes((0, 0, 1, 1))
+ for i, chunk in enumerate(text_chunks):
+ text = ax.text(x=0.01, y=(0.9 - i * 0.13), s=chunk,
+ fontdict={'ha': 'left', 'va': 'center',
+ 'size': font_size, 'color': 'white'})
+
+ text.set_path_effects([path_effects.Stroke(linewidth=font_size / 10,
+ foreground='black'),
+ path_effects.Normal()])
+
+ ax.set_xlim(0, 1)
+ ax.set_ylim(0, 1)
+ ax.axis('off')
+
+
+def test_PathEffect_points_to_pixels():
+ fig = plt.figure(dpi=150)
+ p1, = plt.plot(range(10))
+ p1.set_path_effects([path_effects.SimpleLineShadow(),
+ path_effects.Normal()])
+ renderer = fig.canvas.get_renderer()
+ pe_renderer = path_effects.PathEffectRenderer(
+ p1.get_path_effects(), renderer)
+ # Confirm that using a path effects renderer maintains point sizes
+ # appropriately. Otherwise rendered font would be the wrong size.
+ assert renderer.points_to_pixels(15) == pe_renderer.points_to_pixels(15)
+
+
+def test_SimplePatchShadow_offset():
+ pe = path_effects.SimplePatchShadow(offset=(4, 5))
+ assert pe._offset == (4, 5)
+
+
+@image_comparison(['collection'], tol=0.03, style='mpl20')
+def test_collection():
+ x, y = np.meshgrid(np.linspace(0, 10, 150), np.linspace(-5, 5, 100))
+ data = np.sin(x) + np.cos(y)
+ cs = plt.contour(data)
+ cs.set(path_effects=[
+ path_effects.PathPatchEffect(edgecolor='black', facecolor='none', linewidth=12),
+ path_effects.Stroke(linewidth=5)])
+ for text in plt.clabel(cs, colors='white'):
+ text.set_path_effects([path_effects.withStroke(foreground='k',
+ linewidth=3)])
+ text.set_bbox({'boxstyle': 'sawtooth', 'facecolor': 'none',
+ 'edgecolor': 'blue'})
+
+
+@image_comparison(['tickedstroke'], remove_text=True, extensions=['png'],
+ tol=0.22) # Increased tolerance due to fixed clipping.
+def test_tickedstroke():
+ fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12, 4))
+ path = Path.unit_circle()
+ patch = patches.PathPatch(path, facecolor='none', lw=2, path_effects=[
+ path_effects.withTickedStroke(angle=-90, spacing=10,
+ length=1)])
+
+ ax1.add_patch(patch)
+ ax1.axis('equal')
+ ax1.set_xlim(-2, 2)
+ ax1.set_ylim(-2, 2)
+
+ ax2.plot([0, 1], [0, 1], label=' ',
+ path_effects=[path_effects.withTickedStroke(spacing=7,
+ angle=135)])
+ nx = 101
+ x = np.linspace(0.0, 1.0, nx)
+ y = 0.3 * np.sin(x * 8) + 0.4
+ ax2.plot(x, y, label=' ', path_effects=[path_effects.withTickedStroke()])
+
+ ax2.legend()
+
+ nx = 101
+ ny = 105
+
+ # Set up survey vectors
+ xvec = np.linspace(0.001, 4.0, nx)
+ yvec = np.linspace(0.001, 4.0, ny)
+
+ # Set up survey matrices. Design disk loading and gear ratio.
+ x1, x2 = np.meshgrid(xvec, yvec)
+
+ # Evaluate some stuff to plot
+ g1 = -(3 * x1 + x2 - 5.5)
+ g2 = -(x1 + 2 * x2 - 4)
+ g3 = .8 + x1 ** -3 - x2
+
+ cg1 = ax3.contour(x1, x2, g1, [0], colors=('k',))
+ cg1.set(path_effects=[path_effects.withTickedStroke(angle=135)])
+
+ cg2 = ax3.contour(x1, x2, g2, [0], colors=('r',))
+ cg2.set(path_effects=[path_effects.withTickedStroke(angle=60, length=2)])
+
+ cg3 = ax3.contour(x1, x2, g3, [0], colors=('b',))
+ cg3.set(path_effects=[path_effects.withTickedStroke(spacing=7)])
+
+ ax3.set_xlim(0, 4)
+ ax3.set_ylim(0, 4)
+
+
+@image_comparison(['spaces_and_newlines.png'], remove_text=True)
+def test_patheffects_spaces_and_newlines():
+ ax = plt.subplot()
+ s1 = " "
+ s2 = "\nNewline also causes problems"
+ text1 = ax.text(0.5, 0.75, s1, ha='center', va='center', size=20,
+ bbox={'color': 'salmon'})
+ text2 = ax.text(0.5, 0.25, s2, ha='center', va='center', size=20,
+ bbox={'color': 'thistle'})
+ text1.set_path_effects([path_effects.Normal()])
+ text2.set_path_effects([path_effects.Normal()])
+
+
+def test_patheffects_overridden_methods_open_close_group():
+ class CustomRenderer(RendererBase):
+ def __init__(self):
+ super().__init__()
+
+ def open_group(self, s, gid=None):
+ return "open_group overridden"
+
+ def close_group(self, s):
+ return "close_group overridden"
+
+ renderer = PathEffectRenderer([path_effects.Normal()], CustomRenderer())
+
+ assert renderer.open_group('s') == "open_group overridden"
+ assert renderer.close_group('s') == "close_group overridden"
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_pickle.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_pickle.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb47b26681018c922b92dc7364d6da1a4d54ed05
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_pickle.py
@@ -0,0 +1,339 @@
+from io import BytesIO
+import ast
+import os
+import sys
+import pickle
+import pickletools
+
+import numpy as np
+import pytest
+
+import matplotlib as mpl
+from matplotlib import cm
+from matplotlib.testing import subprocess_run_helper, is_ci_environment
+from matplotlib.testing.decorators import check_figures_equal
+from matplotlib.dates import rrulewrapper
+from matplotlib.lines import VertexSelector
+import matplotlib.pyplot as plt
+import matplotlib.transforms as mtransforms
+import matplotlib.figure as mfigure
+from mpl_toolkits.axes_grid1 import axes_divider, parasite_axes # type: ignore[import]
+
+
+def test_simple():
+ fig = plt.figure()
+ pickle.dump(fig, BytesIO(), pickle.HIGHEST_PROTOCOL)
+
+ ax = plt.subplot(121)
+ pickle.dump(ax, BytesIO(), pickle.HIGHEST_PROTOCOL)
+
+ ax = plt.axes(projection='polar')
+ plt.plot(np.arange(10), label='foobar')
+ plt.legend()
+
+ pickle.dump(ax, BytesIO(), pickle.HIGHEST_PROTOCOL)
+
+# ax = plt.subplot(121, projection='hammer')
+# pickle.dump(ax, BytesIO(), pickle.HIGHEST_PROTOCOL)
+
+ plt.figure()
+ plt.bar(x=np.arange(10), height=np.arange(10))
+ pickle.dump(plt.gca(), BytesIO(), pickle.HIGHEST_PROTOCOL)
+
+ fig = plt.figure()
+ ax = plt.axes()
+ plt.plot(np.arange(10))
+ ax.set_yscale('log')
+ pickle.dump(fig, BytesIO(), pickle.HIGHEST_PROTOCOL)
+
+
+def _generate_complete_test_figure(fig_ref):
+ fig_ref.set_size_inches((10, 6))
+ plt.figure(fig_ref)
+
+ plt.suptitle('Can you fit any more in a figure?')
+
+ # make some arbitrary data
+ x, y = np.arange(8), np.arange(10)
+ data = u = v = np.linspace(0, 10, 80).reshape(10, 8)
+ v = np.sin(v * -0.6)
+
+ # Ensure lists also pickle correctly.
+ plt.subplot(3, 3, 1)
+ plt.plot(list(range(10)))
+ plt.ylabel("hello")
+
+ plt.subplot(3, 3, 2)
+ plt.contourf(data, hatches=['//', 'ooo'])
+ plt.colorbar()
+
+ plt.subplot(3, 3, 3)
+ plt.pcolormesh(data)
+
+ plt.subplot(3, 3, 4)
+ plt.imshow(data)
+ plt.ylabel("hello\nworld!")
+
+ plt.subplot(3, 3, 5)
+ plt.pcolor(data)
+
+ ax = plt.subplot(3, 3, 6)
+ ax.set_xlim(0, 7)
+ ax.set_ylim(0, 9)
+ plt.streamplot(x, y, u, v)
+
+ ax = plt.subplot(3, 3, 7)
+ ax.set_xlim(0, 7)
+ ax.set_ylim(0, 9)
+ plt.quiver(x, y, u, v)
+
+ plt.subplot(3, 3, 8)
+ plt.scatter(x, x ** 2, label='$x^2$')
+ plt.legend(loc='upper left')
+
+ plt.subplot(3, 3, 9)
+ plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4, label='$-.5 x$')
+ plt.legend(draggable=True)
+
+ # Ensure subfigure parenting works.
+ subfigs = fig_ref.subfigures(2)
+ subfigs[0].subplots(1, 2)
+ subfigs[1].subplots(1, 2)
+
+ fig_ref.align_ylabels() # Test handling of _align_label_groups Groupers.
+
+
+@mpl.style.context("default")
+@check_figures_equal(extensions=["png"])
+def test_complete(fig_test, fig_ref):
+ _generate_complete_test_figure(fig_ref)
+ # plotting is done, now test its pickle-ability
+ pkl = pickle.dumps(fig_ref, pickle.HIGHEST_PROTOCOL)
+ # FigureCanvasAgg is picklable and GUI canvases are generally not, but there should
+ # be no reference to the canvas in the pickle stream in either case. In order to
+ # keep the test independent of GUI toolkits, run it with Agg and check that there's
+ # no reference to FigureCanvasAgg in the pickle stream.
+ assert "FigureCanvasAgg" not in [arg for op, arg, pos in pickletools.genops(pkl)]
+ loaded = pickle.loads(pkl)
+ loaded.canvas.draw()
+
+ fig_test.set_size_inches(loaded.get_size_inches())
+ fig_test.figimage(loaded.canvas.renderer.buffer_rgba())
+
+ plt.close(loaded)
+
+
+def _pickle_load_subprocess():
+ import os
+ import pickle
+
+ path = os.environ['PICKLE_FILE_PATH']
+
+ with open(path, 'rb') as blob:
+ fig = pickle.load(blob)
+
+ print(str(pickle.dumps(fig)))
+
+
+@mpl.style.context("default")
+@check_figures_equal(extensions=['png'])
+def test_pickle_load_from_subprocess(fig_test, fig_ref, tmp_path):
+ _generate_complete_test_figure(fig_ref)
+
+ fp = tmp_path / 'sinus.pickle'
+ assert not fp.exists()
+
+ with fp.open('wb') as file:
+ pickle.dump(fig_ref, file, pickle.HIGHEST_PROTOCOL)
+ assert fp.exists()
+
+ proc = subprocess_run_helper(
+ _pickle_load_subprocess,
+ timeout=60,
+ extra_env={
+ "PICKLE_FILE_PATH": str(fp),
+ "MPLBACKEND": "Agg",
+ # subprocess_run_helper will set SOURCE_DATE_EPOCH=0, so for a dirty tree,
+ # the version will have the date 19700101. As we aren't trying to test the
+ # version compatibility warning, force setuptools-scm to use the same
+ # version as us.
+ "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MATPLOTLIB": mpl.__version__,
+ },
+ )
+
+ loaded_fig = pickle.loads(ast.literal_eval(proc.stdout))
+
+ loaded_fig.canvas.draw()
+
+ fig_test.set_size_inches(loaded_fig.get_size_inches())
+ fig_test.figimage(loaded_fig.canvas.renderer.buffer_rgba())
+
+ plt.close(loaded_fig)
+
+
+def test_gcf():
+ fig = plt.figure("a label")
+ buf = BytesIO()
+ pickle.dump(fig, buf, pickle.HIGHEST_PROTOCOL)
+ plt.close("all")
+ assert plt._pylab_helpers.Gcf.figs == {} # No figures must be left.
+ fig = pickle.loads(buf.getbuffer())
+ assert plt._pylab_helpers.Gcf.figs != {} # A manager is there again.
+ assert fig.get_label() == "a label"
+
+
+def test_no_pyplot():
+ # tests pickle-ability of a figure not created with pyplot
+ from matplotlib.backends.backend_pdf import FigureCanvasPdf
+ fig = mfigure.Figure()
+ _ = FigureCanvasPdf(fig)
+ ax = fig.add_subplot(1, 1, 1)
+ ax.plot([1, 2, 3], [1, 2, 3])
+ pickle.dump(fig, BytesIO(), pickle.HIGHEST_PROTOCOL)
+
+
+def test_renderer():
+ from matplotlib.backends.backend_agg import RendererAgg
+ renderer = RendererAgg(10, 20, 30)
+ pickle.dump(renderer, BytesIO())
+
+
+def test_image():
+ # Prior to v1.4.0 the Image would cache data which was not picklable
+ # once it had been drawn.
+ from matplotlib.backends.backend_agg import new_figure_manager
+ manager = new_figure_manager(1000)
+ fig = manager.canvas.figure
+ ax = fig.add_subplot(1, 1, 1)
+ ax.imshow(np.arange(12).reshape(3, 4))
+ manager.canvas.draw()
+ pickle.dump(fig, BytesIO())
+
+
+def test_polar():
+ plt.subplot(polar=True)
+ fig = plt.gcf()
+ pf = pickle.dumps(fig)
+ pickle.loads(pf)
+ plt.draw()
+
+
+class TransformBlob:
+ def __init__(self):
+ self.identity = mtransforms.IdentityTransform()
+ self.identity2 = mtransforms.IdentityTransform()
+ # Force use of the more complex composition.
+ self.composite = mtransforms.CompositeGenericTransform(
+ self.identity,
+ self.identity2)
+ # Check parent -> child links of TransformWrapper.
+ self.wrapper = mtransforms.TransformWrapper(self.composite)
+ # Check child -> parent links of TransformWrapper.
+ self.composite2 = mtransforms.CompositeGenericTransform(
+ self.wrapper,
+ self.identity)
+
+
+def test_transform():
+ obj = TransformBlob()
+ pf = pickle.dumps(obj)
+ del obj
+
+ obj = pickle.loads(pf)
+ # Check parent -> child links of TransformWrapper.
+ assert obj.wrapper._child == obj.composite
+ # Check child -> parent links of TransformWrapper.
+ assert [v() for v in obj.wrapper._parents.values()] == [obj.composite2]
+ # Check input and output dimensions are set as expected.
+ assert obj.wrapper.input_dims == obj.composite.input_dims
+ assert obj.wrapper.output_dims == obj.composite.output_dims
+
+
+def test_rrulewrapper():
+ r = rrulewrapper(2)
+ try:
+ pickle.loads(pickle.dumps(r))
+ except RecursionError:
+ print('rrulewrapper pickling test failed')
+ raise
+
+
+def test_shared():
+ fig, axs = plt.subplots(2, sharex=True)
+ fig = pickle.loads(pickle.dumps(fig))
+ fig.axes[0].set_xlim(10, 20)
+ assert fig.axes[1].get_xlim() == (10, 20)
+
+
+def test_inset_and_secondary():
+ fig, ax = plt.subplots()
+ ax.inset_axes([.1, .1, .3, .3])
+ ax.secondary_xaxis("top", functions=(np.square, np.sqrt))
+ pickle.loads(pickle.dumps(fig))
+
+
+@pytest.mark.parametrize("cmap", cm._colormaps.values())
+def test_cmap(cmap):
+ pickle.dumps(cmap)
+
+
+def test_unpickle_canvas():
+ fig = mfigure.Figure()
+ assert fig.canvas is not None
+ out = BytesIO()
+ pickle.dump(fig, out)
+ out.seek(0)
+ fig2 = pickle.load(out)
+ assert fig2.canvas is not None
+
+
+def test_mpl_toolkits():
+ ax = parasite_axes.host_axes([0, 0, 1, 1])
+ axes_divider.make_axes_area_auto_adjustable(ax)
+ assert type(pickle.loads(pickle.dumps(ax))) == parasite_axes.HostAxes
+
+
+def test_standard_norm():
+ assert type(pickle.loads(pickle.dumps(mpl.colors.LogNorm()))) \
+ == mpl.colors.LogNorm
+
+
+def test_dynamic_norm():
+ logit_norm_instance = mpl.colors.make_norm_from_scale(
+ mpl.scale.LogitScale, mpl.colors.Normalize)()
+ assert type(pickle.loads(pickle.dumps(logit_norm_instance))) \
+ == type(logit_norm_instance)
+
+
+def test_vertexselector():
+ line, = plt.plot([0, 1], picker=True)
+ pickle.loads(pickle.dumps(VertexSelector(line)))
+
+
+def test_cycler():
+ ax = plt.figure().add_subplot()
+ ax.set_prop_cycle(c=["c", "m", "y", "k"])
+ ax.plot([1, 2])
+ ax = pickle.loads(pickle.dumps(ax))
+ l, = ax.plot([3, 4])
+ assert l.get_color() == "m"
+
+
+# Run under an interactive backend to test that we don't try to pickle the
+# (interactive and non-picklable) canvas.
+def _test_axeswidget_interactive():
+ ax = plt.figure().add_subplot()
+ pickle.dumps(mpl.widgets.Button(ax, "button"))
+
+
+@pytest.mark.xfail( # https://github.com/actions/setup-python/issues/649
+ ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and
+ sys.platform == 'darwin' and sys.version_info[:2] < (3, 11),
+ reason='Tk version mismatch on Azure macOS CI'
+ )
+def test_axeswidget_interactive():
+ subprocess_run_helper(
+ _test_axeswidget_interactive,
+ timeout=120 if is_ci_environment() else 20,
+ extra_env={'MPLBACKEND': 'tkagg'}
+ )
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_png.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_png.py
new file mode 100644
index 0000000000000000000000000000000000000000..9208c31df2bf7181ab003ee860543ebbf2975b52
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_png.py
@@ -0,0 +1,53 @@
+from io import BytesIO
+from pathlib import Path
+
+import pytest
+
+from matplotlib.testing.decorators import image_comparison
+from matplotlib import cm, pyplot as plt
+
+
+@image_comparison(['pngsuite.png'], tol=0.04)
+def test_pngsuite():
+ files = sorted(
+ (Path(__file__).parent / "baseline_images/pngsuite").glob("basn*.png"))
+
+ plt.figure(figsize=(len(files), 2))
+
+ for i, fname in enumerate(files):
+ data = plt.imread(fname)
+ cmap = None # use default colormap
+ if data.ndim == 2:
+ # keep grayscale images gray
+ cmap = cm.gray
+ # Using the old default data interpolation stage lets us
+ # continue to use the existing reference image
+ plt.imshow(data, extent=(i, i + 1, 0, 1), cmap=cmap,
+ interpolation_stage='data')
+
+ plt.gca().patch.set_facecolor("#ddffff")
+ plt.gca().set_xlim(0, len(files))
+
+
+def test_truncated_file(tmp_path):
+ path = tmp_path / 'test.png'
+ path_t = tmp_path / 'test_truncated.png'
+ plt.savefig(path)
+ with open(path, 'rb') as fin:
+ buf = fin.read()
+ with open(path_t, 'wb') as fout:
+ fout.write(buf[:20])
+
+ with pytest.raises(Exception):
+ plt.imread(path_t)
+
+
+def test_truncated_buffer():
+ b = BytesIO()
+ plt.savefig(b)
+ b.seek(0)
+ b2 = BytesIO(b.read(20))
+ b2.seek(0)
+
+ with pytest.raises(Exception):
+ plt.imread(b2)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_polar.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_polar.py
new file mode 100644
index 0000000000000000000000000000000000000000..844ab8cf3a0669b5861ca94979a0946eceb0ce5b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_polar.py
@@ -0,0 +1,528 @@
+import numpy as np
+from numpy.testing import assert_allclose
+import pytest
+
+import matplotlib as mpl
+from matplotlib import pyplot as plt
+from matplotlib.testing.decorators import image_comparison, check_figures_equal
+
+
+@image_comparison(['polar_axes.png'], style='default', tol=0.012)
+def test_polar_annotations():
+ # You can specify the xypoint and the xytext in different positions and
+ # coordinate systems, and optionally turn on a connecting line and mark the
+ # point with a marker. Annotations work on polar axes too. In the example
+ # below, the xy point is in native coordinates (xycoords defaults to
+ # 'data'). For a polar axes, this is in (theta, radius) space. The text
+ # in this example is placed in the fractional figure coordinate system.
+ # Text keyword args like horizontal and vertical alignment are respected.
+
+ # Setup some data
+ r = np.arange(0.0, 1.0, 0.001)
+ theta = 2.0 * 2.0 * np.pi * r
+
+ fig = plt.figure()
+ ax = fig.add_subplot(polar=True)
+ line, = ax.plot(theta, r, color='#ee8d18', lw=3)
+ line, = ax.plot((0, 0), (0, 1), color="#0000ff", lw=1)
+
+ ind = 800
+ thisr, thistheta = r[ind], theta[ind]
+ ax.plot([thistheta], [thisr], 'o')
+ ax.annotate('a polar annotation',
+ xy=(thistheta, thisr), # theta, radius
+ xytext=(0.05, 0.05), # fraction, fraction
+ textcoords='figure fraction',
+ arrowprops=dict(facecolor='black', shrink=0.05),
+ horizontalalignment='left',
+ verticalalignment='baseline',
+ )
+
+ ax.tick_params(axis='x', tick1On=True, tick2On=True, direction='out')
+
+
+@image_comparison(['polar_coords.png'], style='default', remove_text=True,
+ tol=0.014)
+def test_polar_coord_annotations():
+ # You can also use polar notation on a cartesian axes. Here the native
+ # coordinate system ('data') is cartesian, so you need to specify the
+ # xycoords and textcoords as 'polar' if you want to use (theta, radius).
+ el = mpl.patches.Ellipse((0, 0), 10, 20, facecolor='r', alpha=0.5)
+
+ fig = plt.figure()
+ ax = fig.add_subplot(aspect='equal')
+
+ ax.add_artist(el)
+ el.set_clip_box(ax.bbox)
+
+ ax.annotate('the top',
+ xy=(np.pi/2., 10.), # theta, radius
+ xytext=(np.pi/3, 20.), # theta, radius
+ xycoords='polar',
+ textcoords='polar',
+ arrowprops=dict(facecolor='black', shrink=0.05),
+ horizontalalignment='left',
+ verticalalignment='baseline',
+ clip_on=True, # clip to the axes bounding box
+ )
+
+ ax.set_xlim(-20, 20)
+ ax.set_ylim(-20, 20)
+
+
+@image_comparison(['polar_alignment.png'])
+def test_polar_alignment():
+ # Test changing the vertical/horizontal alignment of a polar graph.
+ angles = np.arange(0, 360, 90)
+ grid_values = [0, 0.2, 0.4, 0.6, 0.8, 1]
+
+ fig = plt.figure()
+ rect = [0.1, 0.1, 0.8, 0.8]
+
+ horizontal = fig.add_axes(rect, polar=True, label='horizontal')
+ horizontal.set_thetagrids(angles)
+
+ vertical = fig.add_axes(rect, polar=True, label='vertical')
+ vertical.patch.set_visible(False)
+
+ for i in range(2):
+ fig.axes[i].set_rgrids(
+ grid_values, angle=angles[i],
+ horizontalalignment='left', verticalalignment='top')
+
+
+def test_polar_twice():
+ fig = plt.figure()
+ plt.polar([1, 2], [.1, .2])
+ plt.polar([3, 4], [.3, .4])
+ assert len(fig.axes) == 1, 'More than one polar Axes created.'
+
+
+@check_figures_equal(extensions=['png'])
+def test_polar_wrap(fig_test, fig_ref):
+ ax = fig_test.add_subplot(projection="polar")
+ ax.plot(np.deg2rad([179, -179]), [0.2, 0.1])
+ ax.plot(np.deg2rad([2, -2]), [0.2, 0.1])
+ ax = fig_ref.add_subplot(projection="polar")
+ ax.plot(np.deg2rad([179, 181]), [0.2, 0.1])
+ ax.plot(np.deg2rad([2, 358]), [0.2, 0.1])
+
+
+@check_figures_equal(extensions=['png'])
+def test_polar_units_1(fig_test, fig_ref):
+ import matplotlib.testing.jpl_units as units
+ units.register()
+ xs = [30.0, 45.0, 60.0, 90.0]
+ ys = [1.0, 2.0, 3.0, 4.0]
+
+ plt.figure(fig_test.number)
+ plt.polar([x * units.deg for x in xs], ys)
+
+ ax = fig_ref.add_subplot(projection="polar")
+ ax.plot(np.deg2rad(xs), ys)
+ ax.set(xlabel="deg")
+
+
+@check_figures_equal(extensions=['png'])
+def test_polar_units_2(fig_test, fig_ref):
+ import matplotlib.testing.jpl_units as units
+ units.register()
+ xs = [30.0, 45.0, 60.0, 90.0]
+ xs_deg = [x * units.deg for x in xs]
+ ys = [1.0, 2.0, 3.0, 4.0]
+ ys_km = [y * units.km for y in ys]
+
+ plt.figure(fig_test.number)
+ # test {theta,r}units.
+ plt.polar(xs_deg, ys_km, thetaunits="rad", runits="km")
+ assert isinstance(plt.gca().xaxis.get_major_formatter(),
+ units.UnitDblFormatter)
+
+ ax = fig_ref.add_subplot(projection="polar")
+ ax.plot(np.deg2rad(xs), ys)
+ ax.xaxis.set_major_formatter(mpl.ticker.FuncFormatter("{:.12}".format))
+ ax.set(xlabel="rad", ylabel="km")
+
+
+@image_comparison(['polar_rmin.png'], style='default')
+def test_polar_rmin():
+ r = np.arange(0, 3.0, 0.01)
+ theta = 2*np.pi*r
+
+ fig = plt.figure()
+ ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
+ ax.plot(theta, r)
+ ax.set_rmax(2.0)
+ ax.set_rmin(0.5)
+
+
+@image_comparison(['polar_negative_rmin.png'], style='default')
+def test_polar_negative_rmin():
+ r = np.arange(-3.0, 0.0, 0.01)
+ theta = 2*np.pi*r
+
+ fig = plt.figure()
+ ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
+ ax.plot(theta, r)
+ ax.set_rmax(0.0)
+ ax.set_rmin(-3.0)
+
+
+@image_comparison(['polar_rorigin.png'], style='default')
+def test_polar_rorigin():
+ r = np.arange(0, 3.0, 0.01)
+ theta = 2*np.pi*r
+
+ fig = plt.figure()
+ ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
+ ax.plot(theta, r)
+ ax.set_rmax(2.0)
+ ax.set_rmin(0.5)
+ ax.set_rorigin(0.0)
+
+
+@image_comparison(['polar_invertedylim.png'], style='default')
+def test_polar_invertedylim():
+ fig = plt.figure()
+ ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
+ ax.set_ylim(2, 0)
+
+
+@image_comparison(['polar_invertedylim_rorigin.png'], style='default')
+def test_polar_invertedylim_rorigin():
+ fig = plt.figure()
+ ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
+ ax.yaxis.set_inverted(True)
+ # Set the rlims to inverted (2, 0) without calling set_rlim, to check that
+ # viewlims are correctly unstaled before draw()ing.
+ ax.plot([0, 0], [0, 2], c="none")
+ ax.margins(0)
+ ax.set_rorigin(3)
+
+
+@image_comparison(['polar_theta_position.png'], style='default')
+def test_polar_theta_position():
+ r = np.arange(0, 3.0, 0.01)
+ theta = 2*np.pi*r
+
+ fig = plt.figure()
+ ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
+ ax.plot(theta, r)
+ ax.set_theta_zero_location("NW", 30)
+ ax.set_theta_direction('clockwise')
+
+
+@image_comparison(['polar_rlabel_position.png'], style='default')
+def test_polar_rlabel_position():
+ fig = plt.figure()
+ ax = fig.add_subplot(projection='polar')
+ ax.set_rlabel_position(315)
+ ax.tick_params(rotation='auto')
+
+
+@image_comparison(['polar_title_position.png'], style='mpl20')
+def test_polar_title_position():
+ fig = plt.figure()
+ ax = fig.add_subplot(projection='polar')
+ ax.set_title('foo')
+
+
+@image_comparison(['polar_theta_wedge.png'], style='default')
+def test_polar_theta_limits():
+ r = np.arange(0, 3.0, 0.01)
+ theta = 2*np.pi*r
+
+ theta_mins = np.arange(15.0, 361.0, 90.0)
+ theta_maxs = np.arange(50.0, 361.0, 90.0)
+ DIRECTIONS = ('out', 'in', 'inout')
+
+ fig, axs = plt.subplots(len(theta_mins), len(theta_maxs),
+ subplot_kw={'polar': True},
+ figsize=(8, 6))
+
+ for i, start in enumerate(theta_mins):
+ for j, end in enumerate(theta_maxs):
+ ax = axs[i, j]
+ ax.plot(theta, r)
+ if start < end:
+ ax.set_thetamin(start)
+ ax.set_thetamax(end)
+ else:
+ # Plot with clockwise orientation instead.
+ ax.set_thetamin(end)
+ ax.set_thetamax(start)
+ ax.set_theta_direction('clockwise')
+ ax.tick_params(tick1On=True, tick2On=True,
+ direction=DIRECTIONS[i % len(DIRECTIONS)],
+ rotation='auto')
+ ax.yaxis.set_tick_params(label2On=True, rotation='auto')
+ ax.xaxis.get_major_locator().base.set_params( # backcompat
+ steps=[1, 2, 2.5, 5, 10])
+
+
+@check_figures_equal(extensions=["png"])
+def test_polar_rlim(fig_test, fig_ref):
+ ax = fig_test.subplots(subplot_kw={'polar': True})
+ ax.set_rlim(top=10)
+ ax.set_rlim(bottom=.5)
+
+ ax = fig_ref.subplots(subplot_kw={'polar': True})
+ ax.set_rmax(10.)
+ ax.set_rmin(.5)
+
+
+@check_figures_equal(extensions=["png"])
+def test_polar_rlim_bottom(fig_test, fig_ref):
+ ax = fig_test.subplots(subplot_kw={'polar': True})
+ ax.set_rlim(bottom=[.5, 10])
+
+ ax = fig_ref.subplots(subplot_kw={'polar': True})
+ ax.set_rmax(10.)
+ ax.set_rmin(.5)
+
+
+def test_polar_rlim_zero():
+ ax = plt.figure().add_subplot(projection='polar')
+ ax.plot(np.arange(10), np.arange(10) + .01)
+ assert ax.get_ylim()[0] == 0
+
+
+def test_polar_no_data():
+ plt.subplot(projection="polar")
+ ax = plt.gca()
+ assert ax.get_rmin() == 0 and ax.get_rmax() == 1
+ plt.close("all")
+ # Used to behave differently (by triggering an autoscale with no data).
+ plt.polar()
+ ax = plt.gca()
+ assert ax.get_rmin() == 0 and ax.get_rmax() == 1
+
+
+def test_polar_default_log_lims():
+ plt.subplot(projection='polar')
+ ax = plt.gca()
+ ax.set_rscale('log')
+ assert ax.get_rmin() > 0
+
+
+def test_polar_not_datalim_adjustable():
+ ax = plt.figure().add_subplot(projection="polar")
+ with pytest.raises(ValueError):
+ ax.set_adjustable("datalim")
+
+
+def test_polar_gridlines():
+ fig = plt.figure()
+ ax = fig.add_subplot(polar=True)
+ # make all major grid lines lighter, only x grid lines set in 2.1.0
+ ax.grid(alpha=0.2)
+ # hide y tick labels, no effect in 2.1.0
+ plt.setp(ax.yaxis.get_ticklabels(), visible=False)
+ fig.canvas.draw()
+ assert ax.xaxis.majorTicks[0].gridline.get_alpha() == .2
+ assert ax.yaxis.majorTicks[0].gridline.get_alpha() == .2
+
+
+def test_get_tightbbox_polar():
+ fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
+ fig.canvas.draw()
+ bb = ax.get_tightbbox(fig.canvas.get_renderer())
+ assert_allclose(
+ bb.extents, [107.7778, 29.2778, 539.7847, 450.7222], rtol=1e-03)
+
+
+@check_figures_equal(extensions=["png"])
+def test_polar_interpolation_steps_constant_r(fig_test, fig_ref):
+ # Check that an extra half-turn doesn't make any difference -- modulo
+ # antialiasing, which we disable here.
+ p1 = (fig_test.add_subplot(121, projection="polar")
+ .bar([0], [1], 3*np.pi, edgecolor="none", antialiased=False))
+ p2 = (fig_test.add_subplot(122, projection="polar")
+ .bar([0], [1], -3*np.pi, edgecolor="none", antialiased=False))
+ p3 = (fig_ref.add_subplot(121, projection="polar")
+ .bar([0], [1], 2*np.pi, edgecolor="none", antialiased=False))
+ p4 = (fig_ref.add_subplot(122, projection="polar")
+ .bar([0], [1], -2*np.pi, edgecolor="none", antialiased=False))
+
+
+@check_figures_equal(extensions=["png"])
+def test_polar_interpolation_steps_variable_r(fig_test, fig_ref):
+ l, = fig_test.add_subplot(projection="polar").plot([0, np.pi/2], [1, 2])
+ l.get_path()._interpolation_steps = 100
+ fig_ref.add_subplot(projection="polar").plot(
+ np.linspace(0, np.pi/2, 101), np.linspace(1, 2, 101))
+
+
+def test_thetalim_valid_invalid():
+ ax = plt.subplot(projection='polar')
+ ax.set_thetalim(0, 2 * np.pi) # doesn't raise.
+ ax.set_thetalim(thetamin=800, thetamax=440) # doesn't raise.
+ with pytest.raises(ValueError,
+ match='angle range must be less than a full circle'):
+ ax.set_thetalim(0, 3 * np.pi)
+ with pytest.raises(ValueError,
+ match='angle range must be less than a full circle'):
+ ax.set_thetalim(thetamin=800, thetamax=400)
+
+
+def test_thetalim_args():
+ ax = plt.subplot(projection='polar')
+ ax.set_thetalim(0, 1)
+ assert tuple(np.radians((ax.get_thetamin(), ax.get_thetamax()))) == (0, 1)
+ ax.set_thetalim((2, 3))
+ assert tuple(np.radians((ax.get_thetamin(), ax.get_thetamax()))) == (2, 3)
+
+
+def test_default_thetalocator():
+ # Ideally we would check AAAABBC, but the smallest axes currently puts a
+ # single tick at 150° because MaxNLocator doesn't have a way to accept 15°
+ # while rejecting 150°.
+ fig, axs = plt.subplot_mosaic(
+ "AAAABB.", subplot_kw={"projection": "polar"})
+ for ax in axs.values():
+ ax.set_thetalim(0, np.pi)
+ for ax in axs.values():
+ ticklocs = np.degrees(ax.xaxis.get_majorticklocs()).tolist()
+ assert pytest.approx(90) in ticklocs
+ assert pytest.approx(100) not in ticklocs
+
+
+def test_axvspan():
+ ax = plt.subplot(projection="polar")
+ span = ax.axvspan(0, np.pi/4)
+ assert span.get_path()._interpolation_steps > 1
+
+
+@check_figures_equal(extensions=["png"])
+def test_remove_shared_polar(fig_ref, fig_test):
+ # Removing shared polar axes used to crash. Test removing them, keeping in
+ # both cases just the lower left axes of a grid to avoid running into a
+ # separate issue (now being fixed) of ticklabel visibility for shared axes.
+ axs = fig_ref.subplots(
+ 2, 2, sharex=True, subplot_kw={"projection": "polar"})
+ for i in [0, 1, 3]:
+ axs.flat[i].remove()
+ axs = fig_test.subplots(
+ 2, 2, sharey=True, subplot_kw={"projection": "polar"})
+ for i in [0, 1, 3]:
+ axs.flat[i].remove()
+
+
+def test_shared_polar_keeps_ticklabels():
+ fig, axs = plt.subplots(
+ 2, 2, subplot_kw={"projection": "polar"}, sharex=True, sharey=True)
+ fig.canvas.draw()
+ assert axs[0, 1].xaxis.majorTicks[0].get_visible()
+ assert axs[0, 1].yaxis.majorTicks[0].get_visible()
+ fig, axs = plt.subplot_mosaic(
+ "ab\ncd", subplot_kw={"projection": "polar"}, sharex=True, sharey=True)
+ fig.canvas.draw()
+ assert axs["b"].xaxis.majorTicks[0].get_visible()
+ assert axs["b"].yaxis.majorTicks[0].get_visible()
+
+
+def test_axvline_axvspan_do_not_modify_rlims():
+ ax = plt.subplot(projection="polar")
+ ax.axvspan(0, 1)
+ ax.axvline(.5)
+ ax.plot([.1, .2])
+ assert ax.get_ylim() == (0, .2)
+
+
+def test_cursor_precision():
+ ax = plt.subplot(projection="polar")
+ # Higher radii correspond to higher theta-precisions.
+ assert ax.format_coord(0, 0.005) == "θ=0.0π (0°), r=0.005"
+ assert ax.format_coord(0, .1) == "θ=0.00π (0°), r=0.100"
+ assert ax.format_coord(0, 1) == "θ=0.000π (0.0°), r=1.000"
+ assert ax.format_coord(1, 0.005) == "θ=0.3π (57°), r=0.005"
+ assert ax.format_coord(1, .1) == "θ=0.32π (57°), r=0.100"
+ assert ax.format_coord(1, 1) == "θ=0.318π (57.3°), r=1.000"
+ assert ax.format_coord(2, 0.005) == "θ=0.6π (115°), r=0.005"
+ assert ax.format_coord(2, .1) == "θ=0.64π (115°), r=0.100"
+ assert ax.format_coord(2, 1) == "θ=0.637π (114.6°), r=1.000"
+
+
+def test_custom_fmt_data():
+ ax = plt.subplot(projection="polar")
+ def millions(x):
+ return '$%1.1fM' % (x*1e-6)
+
+ # Test only x formatter
+ ax.fmt_xdata = None
+ ax.fmt_ydata = millions
+ assert ax.format_coord(12, 2e7) == "θ=3.8197186342π (687.54935416°), r=$20.0M"
+ assert ax.format_coord(1234, 2e6) == "θ=392.794399551π (70702.9919191°), r=$2.0M"
+ assert ax.format_coord(3, 100) == "θ=0.95493π (171.887°), r=$0.0M"
+
+ # Test only y formatter
+ ax.fmt_xdata = millions
+ ax.fmt_ydata = None
+ assert ax.format_coord(2e5, 1) == "θ=$0.2M, r=1.000"
+ assert ax.format_coord(1, .1) == "θ=$0.0M, r=0.100"
+ assert ax.format_coord(1e6, 0.005) == "θ=$1.0M, r=0.005"
+
+ # Test both x and y formatters
+ ax.fmt_xdata = millions
+ ax.fmt_ydata = millions
+ assert ax.format_coord(2e6, 2e4*3e5) == "θ=$2.0M, r=$6000.0M"
+ assert ax.format_coord(1e18, 12891328123) == "θ=$1000000000000.0M, r=$12891.3M"
+ assert ax.format_coord(63**7, 1081968*1024) == "θ=$3938980.6M, r=$1107.9M"
+
+
+@image_comparison(['polar_log.png'], style='default')
+def test_polar_log():
+ fig = plt.figure()
+ ax = fig.add_subplot(polar=True)
+
+ ax.set_rscale('log')
+ ax.set_rlim(1, 1000)
+
+ n = 100
+ ax.plot(np.linspace(0, 2 * np.pi, n), np.logspace(0, 2, n))
+
+
+@check_figures_equal()
+def test_polar_log_rorigin(fig_ref, fig_test):
+ # Test that equivalent linear and log radial settings give the same axes patch
+ # and spines.
+ ax_ref = fig_ref.add_subplot(projection='polar', facecolor='red')
+ ax_ref.set_rlim(0, 2)
+ ax_ref.set_rorigin(-3)
+ ax_ref.set_rticks(np.linspace(0, 2, 5))
+
+ ax_test = fig_test.add_subplot(projection='polar', facecolor='red')
+ ax_test.set_rscale('log')
+ ax_test.set_rlim(1, 100)
+ ax_test.set_rorigin(10**-3)
+ ax_test.set_rticks(np.logspace(0, 2, 5))
+
+ for ax in ax_ref, ax_test:
+ # Radial tick labels should be the only difference, so turn them off.
+ ax.tick_params(labelleft=False)
+
+
+def test_polar_neg_theta_lims():
+ fig = plt.figure()
+ ax = fig.add_subplot(projection='polar')
+ ax.set_thetalim(-np.pi, np.pi)
+ labels = [l.get_text() for l in ax.xaxis.get_ticklabels()]
+ assert labels == ['-180°', '-135°', '-90°', '-45°', '0°', '45°', '90°', '135°']
+
+
+@pytest.mark.parametrize("order", ["before", "after"])
+@image_comparison(baseline_images=['polar_errorbar.png'], remove_text=True,
+ style='mpl20')
+def test_polar_errorbar(order):
+ theta = np.arange(0, 2 * np.pi, np.pi / 8)
+ r = theta / np.pi / 2 + 0.5
+ fig = plt.figure(figsize=(5, 5))
+ ax = fig.add_subplot(projection='polar')
+ if order == "before":
+ ax.set_theta_zero_location("N")
+ ax.set_theta_direction(-1)
+ ax.errorbar(theta, r, xerr=0.1, yerr=0.1, capsize=7, fmt="o", c="seagreen")
+ else:
+ ax.errorbar(theta, r, xerr=0.1, yerr=0.1, capsize=7, fmt="o", c="seagreen")
+ ax.set_theta_zero_location("N")
+ ax.set_theta_direction(-1)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_preprocess_data.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_preprocess_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..0684f0dbb9aef3a7ee195a4e6de723e1e271478d
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_preprocess_data.py
@@ -0,0 +1,288 @@
+import re
+import sys
+
+import numpy as np
+import pytest
+
+from matplotlib import _preprocess_data
+from matplotlib.axes import Axes
+from matplotlib.testing import subprocess_run_for_testing
+from matplotlib.testing.decorators import check_figures_equal
+
+# Notes on testing the plotting functions itself
+# * the individual decorated plotting functions are tested in 'test_axes.py'
+# * that pyplot functions accept a data kwarg is only tested in
+# test_axes.test_pie_linewidth_0
+
+
+# this gets used in multiple tests, so define it here
+@_preprocess_data(replace_names=["x", "y"], label_namer="y")
+def plot_func(ax, x, y, ls="x", label=None, w="xyz"):
+ return f"x: {list(x)}, y: {list(y)}, ls: {ls}, w: {w}, label: {label}"
+
+
+all_funcs = [plot_func]
+all_func_ids = ['plot_func']
+
+
+def test_compiletime_checks():
+ """Test decorator invocations -> no replacements."""
+
+ def func(ax, x, y): pass
+ def func_args(ax, x, y, *args): pass
+ def func_kwargs(ax, x, y, **kwargs): pass
+ def func_no_ax_args(*args, **kwargs): pass
+
+ # this is ok
+ _preprocess_data(replace_names=["x", "y"])(func)
+ _preprocess_data(replace_names=["x", "y"])(func_kwargs)
+ # this has "enough" information to do all the replaces
+ _preprocess_data(replace_names=["x", "y"])(func_args)
+
+ # no positional_parameter_names but needed due to replaces
+ with pytest.raises(AssertionError):
+ # z is unknown
+ _preprocess_data(replace_names=["x", "y", "z"])(func_args)
+
+ # no replacements at all -> all ok...
+ _preprocess_data(replace_names=[], label_namer=None)(func)
+ _preprocess_data(replace_names=[], label_namer=None)(func_args)
+ _preprocess_data(replace_names=[], label_namer=None)(func_kwargs)
+ _preprocess_data(replace_names=[], label_namer=None)(func_no_ax_args)
+
+ # label namer is unknown
+ with pytest.raises(AssertionError):
+ _preprocess_data(label_namer="z")(func)
+
+ with pytest.raises(AssertionError):
+ _preprocess_data(label_namer="z")(func_args)
+
+
+@pytest.mark.parametrize('func', all_funcs, ids=all_func_ids)
+def test_function_call_without_data(func):
+ """Test without data -> no replacements."""
+ assert (func(None, "x", "y") ==
+ "x: ['x'], y: ['y'], ls: x, w: xyz, label: None")
+ assert (func(None, x="x", y="y") ==
+ "x: ['x'], y: ['y'], ls: x, w: xyz, label: None")
+ assert (func(None, "x", "y", label="") ==
+ "x: ['x'], y: ['y'], ls: x, w: xyz, label: ")
+ assert (func(None, "x", "y", label="text") ==
+ "x: ['x'], y: ['y'], ls: x, w: xyz, label: text")
+ assert (func(None, x="x", y="y", label="") ==
+ "x: ['x'], y: ['y'], ls: x, w: xyz, label: ")
+ assert (func(None, x="x", y="y", label="text") ==
+ "x: ['x'], y: ['y'], ls: x, w: xyz, label: text")
+
+
+@pytest.mark.parametrize('func', all_funcs, ids=all_func_ids)
+def test_function_call_with_dict_input(func):
+ """Tests with dict input, unpacking via preprocess_pipeline"""
+ data = {'a': 1, 'b': 2}
+ assert (func(None, data.keys(), data.values()) ==
+ "x: ['a', 'b'], y: [1, 2], ls: x, w: xyz, label: None")
+
+
+@pytest.mark.parametrize('func', all_funcs, ids=all_func_ids)
+def test_function_call_with_dict_data(func):
+ """Test with dict data -> label comes from the value of 'x' parameter."""
+ data = {"a": [1, 2], "b": [8, 9], "w": "NOT"}
+ assert (func(None, "a", "b", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
+ assert (func(None, x="a", y="b", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
+ assert (func(None, "a", "b", label="", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
+ assert (func(None, "a", "b", label="text", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
+ assert (func(None, x="a", y="b", label="", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
+ assert (func(None, x="a", y="b", label="text", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
+
+
+@pytest.mark.parametrize('func', all_funcs, ids=all_func_ids)
+def test_function_call_with_dict_data_not_in_data(func):
+ """Test the case that one var is not in data -> half replaces, half kept"""
+ data = {"a": [1, 2], "w": "NOT"}
+ assert (func(None, "a", "b", data=data) ==
+ "x: [1, 2], y: ['b'], ls: x, w: xyz, label: b")
+ assert (func(None, x="a", y="b", data=data) ==
+ "x: [1, 2], y: ['b'], ls: x, w: xyz, label: b")
+ assert (func(None, "a", "b", label="", data=data) ==
+ "x: [1, 2], y: ['b'], ls: x, w: xyz, label: ")
+ assert (func(None, "a", "b", label="text", data=data) ==
+ "x: [1, 2], y: ['b'], ls: x, w: xyz, label: text")
+ assert (func(None, x="a", y="b", label="", data=data) ==
+ "x: [1, 2], y: ['b'], ls: x, w: xyz, label: ")
+ assert (func(None, x="a", y="b", label="text", data=data) ==
+ "x: [1, 2], y: ['b'], ls: x, w: xyz, label: text")
+
+
+@pytest.mark.parametrize('func', all_funcs, ids=all_func_ids)
+def test_function_call_with_pandas_data(func, pd):
+ """Test with pandas dataframe -> label comes from ``data["col"].name``."""
+ data = pd.DataFrame({"a": np.array([1, 2], dtype=np.int32),
+ "b": np.array([8, 9], dtype=np.int32),
+ "w": ["NOT", "NOT"]})
+
+ assert (func(None, "a", "b", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
+ assert (func(None, x="a", y="b", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
+ assert (func(None, "a", "b", label="", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
+ assert (func(None, "a", "b", label="text", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
+ assert (func(None, x="a", y="b", label="", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
+ assert (func(None, x="a", y="b", label="text", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
+
+
+def test_function_call_replace_all():
+ """Test without a "replace_names" argument, all vars should be replaced."""
+ data = {"a": [1, 2], "b": [8, 9], "x": "xyz"}
+
+ @_preprocess_data(label_namer="y")
+ def func_replace_all(ax, x, y, ls="x", label=None, w="NOT"):
+ return f"x: {list(x)}, y: {list(y)}, ls: {ls}, w: {w}, label: {label}"
+
+ assert (func_replace_all(None, "a", "b", w="x", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
+ assert (func_replace_all(None, x="a", y="b", w="x", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
+ assert (func_replace_all(None, "a", "b", w="x", label="", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
+ assert (
+ func_replace_all(None, "a", "b", w="x", label="text", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
+ assert (
+ func_replace_all(None, x="a", y="b", w="x", label="", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
+ assert (
+ func_replace_all(None, x="a", y="b", w="x", label="text", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
+
+
+def test_no_label_replacements():
+ """Test with "label_namer=None" -> no label replacement at all."""
+
+ @_preprocess_data(replace_names=["x", "y"], label_namer=None)
+ def func_no_label(ax, x, y, ls="x", label=None, w="xyz"):
+ return f"x: {list(x)}, y: {list(y)}, ls: {ls}, w: {w}, label: {label}"
+
+ data = {"a": [1, 2], "b": [8, 9], "w": "NOT"}
+ assert (func_no_label(None, "a", "b", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: None")
+ assert (func_no_label(None, x="a", y="b", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: None")
+ assert (func_no_label(None, "a", "b", label="", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
+ assert (func_no_label(None, "a", "b", label="text", data=data) ==
+ "x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
+
+
+def test_more_args_than_pos_parameter():
+ @_preprocess_data(replace_names=["x", "y"], label_namer="y")
+ def func(ax, x, y, z=1):
+ pass
+
+ data = {"a": [1, 2], "b": [8, 9], "w": "NOT"}
+ with pytest.raises(TypeError):
+ func(None, "a", "b", "z", "z", data=data)
+
+
+def test_docstring_addition():
+ @_preprocess_data()
+ def funcy(ax, *args, **kwargs):
+ """
+ Parameters
+ ----------
+ data : indexable object, optional
+ DATA_PARAMETER_PLACEHOLDER
+ """
+
+ assert re.search(r"all parameters also accept a string", funcy.__doc__)
+ assert not re.search(r"the following parameters", funcy.__doc__)
+
+ @_preprocess_data(replace_names=[])
+ def funcy(ax, x, y, z, bar=None):
+ """
+ Parameters
+ ----------
+ data : indexable object, optional
+ DATA_PARAMETER_PLACEHOLDER
+ """
+
+ assert not re.search(r"all parameters also accept a string", funcy.__doc__)
+ assert not re.search(r"the following parameters", funcy.__doc__)
+
+ @_preprocess_data(replace_names=["bar"])
+ def funcy(ax, x, y, z, bar=None):
+ """
+ Parameters
+ ----------
+ data : indexable object, optional
+ DATA_PARAMETER_PLACEHOLDER
+ """
+
+ assert not re.search(r"all parameters also accept a string", funcy.__doc__)
+ assert not re.search(r"the following parameters .*: \*bar\*\.",
+ funcy.__doc__)
+
+ @_preprocess_data(replace_names=["x", "t"])
+ def funcy(ax, x, y, z, t=None):
+ """
+ Parameters
+ ----------
+ data : indexable object, optional
+ DATA_PARAMETER_PLACEHOLDER
+ """
+
+ assert not re.search(r"all parameters also accept a string", funcy.__doc__)
+ assert not re.search(r"the following parameters .*: \*x\*, \*t\*\.",
+ funcy.__doc__)
+
+
+def test_data_parameter_replacement():
+ """
+ Test that the docstring contains the correct *data* parameter stub
+ for all methods that we run _preprocess_data() on.
+ """
+ program = (
+ "import logging; "
+ "logging.basicConfig(level=logging.DEBUG); "
+ "import matplotlib.pyplot as plt"
+ )
+ cmd = [sys.executable, "-c", program]
+ completed_proc = subprocess_run_for_testing(
+ cmd, text=True, capture_output=True
+ )
+ assert 'data parameter docstring error' not in completed_proc.stderr
+
+
+class TestPlotTypes:
+
+ plotters = [Axes.scatter, Axes.bar, Axes.plot]
+
+ @pytest.mark.parametrize('plotter', plotters)
+ @check_figures_equal(extensions=['png'])
+ def test_dict_unpack(self, plotter, fig_test, fig_ref):
+ x = [1, 2, 3]
+ y = [4, 5, 6]
+ ddict = dict(zip(x, y))
+
+ plotter(fig_test.subplots(),
+ ddict.keys(), ddict.values())
+ plotter(fig_ref.subplots(), x, y)
+
+ @pytest.mark.parametrize('plotter', plotters)
+ @check_figures_equal(extensions=['png'])
+ def test_data_kwarg(self, plotter, fig_test, fig_ref):
+ x = [1, 2, 3]
+ y = [4, 5, 6]
+
+ plotter(fig_test.subplots(), 'xval', 'yval',
+ data={'xval': x, 'yval': y})
+ plotter(fig_ref.subplots(), x, y)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_pyplot.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_pyplot.py
new file mode 100644
index 0000000000000000000000000000000000000000..46bd6e456d32d896fcfd040374379a81448f114c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_pyplot.py
@@ -0,0 +1,485 @@
+import difflib
+
+import numpy as np
+import sys
+from pathlib import Path
+
+import pytest
+
+import matplotlib as mpl
+from matplotlib.testing import subprocess_run_for_testing
+from matplotlib import pyplot as plt
+
+
+def test_pyplot_up_to_date(tmp_path):
+ pytest.importorskip("black", minversion="24.1")
+
+ gen_script = Path(mpl.__file__).parents[2] / "tools/boilerplate.py"
+ if not gen_script.exists():
+ pytest.skip("boilerplate.py not found")
+ orig_contents = Path(plt.__file__).read_text()
+ plt_file = tmp_path / 'pyplot.py'
+ plt_file.write_text(orig_contents, 'utf-8')
+
+ subprocess_run_for_testing(
+ [sys.executable, str(gen_script), str(plt_file)],
+ check=True)
+ new_contents = plt_file.read_text('utf-8')
+
+ if orig_contents != new_contents:
+ diff_msg = '\n'.join(
+ difflib.unified_diff(
+ orig_contents.split('\n'), new_contents.split('\n'),
+ fromfile='found pyplot.py',
+ tofile='expected pyplot.py',
+ n=0, lineterm=''))
+ pytest.fail(
+ "pyplot.py is not up-to-date. Please run "
+ "'python tools/boilerplate.py' to update pyplot.py. "
+ "This needs to be done from an environment where your "
+ "current working copy is installed (e.g. 'pip install -e'd). "
+ "Here is a diff of unexpected differences:\n%s" % diff_msg
+ )
+
+
+def test_copy_docstring_and_deprecators(recwarn):
+ @mpl._api.rename_parameter(mpl.__version__, "old", "new")
+ @mpl._api.make_keyword_only(mpl.__version__, "kwo")
+ def func(new, kwo=None):
+ pass
+
+ @plt._copy_docstring_and_deprecators(func)
+ def wrapper_func(new, kwo=None):
+ pass
+
+ wrapper_func(None)
+ wrapper_func(new=None)
+ wrapper_func(None, kwo=None)
+ wrapper_func(new=None, kwo=None)
+ assert not recwarn
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ wrapper_func(old=None)
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ wrapper_func(None, None)
+
+
+def test_pyplot_box():
+ fig, ax = plt.subplots()
+ plt.box(False)
+ assert not ax.get_frame_on()
+ plt.box(True)
+ assert ax.get_frame_on()
+ plt.box()
+ assert not ax.get_frame_on()
+ plt.box()
+ assert ax.get_frame_on()
+
+
+def test_stackplot_smoke():
+ # Small smoke test for stackplot (see #12405)
+ plt.stackplot([1, 2, 3], [1, 2, 3])
+
+
+def test_nrows_error():
+ with pytest.raises(TypeError):
+ plt.subplot(nrows=1)
+ with pytest.raises(TypeError):
+ plt.subplot(ncols=1)
+
+
+def test_ioff():
+ plt.ion()
+ assert mpl.is_interactive()
+ with plt.ioff():
+ assert not mpl.is_interactive()
+ assert mpl.is_interactive()
+
+ plt.ioff()
+ assert not mpl.is_interactive()
+ with plt.ioff():
+ assert not mpl.is_interactive()
+ assert not mpl.is_interactive()
+
+
+def test_ion():
+ plt.ioff()
+ assert not mpl.is_interactive()
+ with plt.ion():
+ assert mpl.is_interactive()
+ assert not mpl.is_interactive()
+
+ plt.ion()
+ assert mpl.is_interactive()
+ with plt.ion():
+ assert mpl.is_interactive()
+ assert mpl.is_interactive()
+
+
+def test_nested_ion_ioff():
+ # initial state is interactive
+ plt.ion()
+
+ # mixed ioff/ion
+ with plt.ioff():
+ assert not mpl.is_interactive()
+ with plt.ion():
+ assert mpl.is_interactive()
+ assert not mpl.is_interactive()
+ assert mpl.is_interactive()
+
+ # redundant contexts
+ with plt.ioff():
+ with plt.ioff():
+ assert not mpl.is_interactive()
+ assert mpl.is_interactive()
+
+ with plt.ion():
+ plt.ioff()
+ assert mpl.is_interactive()
+
+ # initial state is not interactive
+ plt.ioff()
+
+ # mixed ioff/ion
+ with plt.ion():
+ assert mpl.is_interactive()
+ with plt.ioff():
+ assert not mpl.is_interactive()
+ assert mpl.is_interactive()
+ assert not mpl.is_interactive()
+
+ # redundant contexts
+ with plt.ion():
+ with plt.ion():
+ assert mpl.is_interactive()
+ assert not mpl.is_interactive()
+
+ with plt.ioff():
+ plt.ion()
+ assert not mpl.is_interactive()
+
+
+def test_close():
+ try:
+ plt.close(1.1)
+ except TypeError as e:
+ assert str(e) == "close() argument must be a Figure, an int, " \
+ "a string, or None, not "
+
+
+def test_subplot_reuse():
+ ax1 = plt.subplot(121)
+ assert ax1 is plt.gca()
+ ax2 = plt.subplot(122)
+ assert ax2 is plt.gca()
+ ax3 = plt.subplot(121)
+ assert ax1 is plt.gca()
+ assert ax1 is ax3
+
+
+def test_axes_kwargs():
+ # plt.axes() always creates new axes, even if axes kwargs differ.
+ plt.figure()
+ ax = plt.axes()
+ ax1 = plt.axes()
+ assert ax is not None
+ assert ax1 is not ax
+ plt.close()
+
+ plt.figure()
+ ax = plt.axes(projection='polar')
+ ax1 = plt.axes(projection='polar')
+ assert ax is not None
+ assert ax1 is not ax
+ plt.close()
+
+ plt.figure()
+ ax = plt.axes(projection='polar')
+ ax1 = plt.axes()
+ assert ax is not None
+ assert ax1.name == 'rectilinear'
+ assert ax1 is not ax
+ plt.close()
+
+
+def test_subplot_replace_projection():
+ # plt.subplot() searches for axes with the same subplot spec, and if one
+ # exists, and the kwargs match returns it, create a new one if they do not
+ fig = plt.figure()
+ ax = plt.subplot(1, 2, 1)
+ ax1 = plt.subplot(1, 2, 1)
+ ax2 = plt.subplot(1, 2, 2)
+ ax3 = plt.subplot(1, 2, 1, projection='polar')
+ ax4 = plt.subplot(1, 2, 1, projection='polar')
+ assert ax is not None
+ assert ax1 is ax
+ assert ax2 is not ax
+ assert ax3 is not ax
+ assert ax3 is ax4
+
+ assert ax in fig.axes
+ assert ax2 in fig.axes
+ assert ax3 in fig.axes
+
+ assert ax.name == 'rectilinear'
+ assert ax2.name == 'rectilinear'
+ assert ax3.name == 'polar'
+
+
+def test_subplot_kwarg_collision():
+ ax1 = plt.subplot(projection='polar', theta_offset=0)
+ ax2 = plt.subplot(projection='polar', theta_offset=0)
+ assert ax1 is ax2
+ ax1.remove()
+ ax3 = plt.subplot(projection='polar', theta_offset=1)
+ assert ax1 is not ax3
+ assert ax1 not in plt.gcf().axes
+
+
+def test_gca():
+ # plt.gca() returns an existing axes, unless there were no axes.
+ plt.figure()
+ ax = plt.gca()
+ ax1 = plt.gca()
+ assert ax is not None
+ assert ax1 is ax
+ plt.close()
+
+
+def test_subplot_projection_reuse():
+ # create an Axes
+ ax1 = plt.subplot(111)
+ # check that it is current
+ assert ax1 is plt.gca()
+ # make sure we get it back if we ask again
+ assert ax1 is plt.subplot(111)
+ # remove it
+ ax1.remove()
+ # create a polar plot
+ ax2 = plt.subplot(111, projection='polar')
+ assert ax2 is plt.gca()
+ # this should have deleted the first axes
+ assert ax1 not in plt.gcf().axes
+ # assert we get it back if no extra parameters passed
+ assert ax2 is plt.subplot(111)
+ ax2.remove()
+ # now check explicitly setting the projection to rectilinear
+ # makes a new axes
+ ax3 = plt.subplot(111, projection='rectilinear')
+ assert ax3 is plt.gca()
+ assert ax3 is not ax2
+ assert ax2 not in plt.gcf().axes
+
+
+def test_subplot_polar_normalization():
+ ax1 = plt.subplot(111, projection='polar')
+ ax2 = plt.subplot(111, polar=True)
+ ax3 = plt.subplot(111, polar=True, projection='polar')
+ assert ax1 is ax2
+ assert ax1 is ax3
+
+ with pytest.raises(ValueError,
+ match="polar=True, yet projection='3d'"):
+ ax2 = plt.subplot(111, polar=True, projection='3d')
+
+
+def test_subplot_change_projection():
+ created_axes = set()
+ ax = plt.subplot()
+ created_axes.add(ax)
+ projections = ('aitoff', 'hammer', 'lambert', 'mollweide',
+ 'polar', 'rectilinear', '3d')
+ for proj in projections:
+ ax.remove()
+ ax = plt.subplot(projection=proj)
+ assert ax is plt.subplot()
+ assert ax.name == proj
+ created_axes.add(ax)
+ # Check that each call created a new Axes.
+ assert len(created_axes) == 1 + len(projections)
+
+
+def test_polar_second_call():
+ # the first call creates the axes with polar projection
+ ln1, = plt.polar(0., 1., 'ro')
+ assert isinstance(ln1, mpl.lines.Line2D)
+ # the second call should reuse the existing axes
+ ln2, = plt.polar(1.57, .5, 'bo')
+ assert isinstance(ln2, mpl.lines.Line2D)
+ assert ln1.axes is ln2.axes
+
+
+def test_fallback_position():
+ # check that position kwarg works if rect not supplied
+ axref = plt.axes([0.2, 0.2, 0.5, 0.5])
+ axtest = plt.axes(position=[0.2, 0.2, 0.5, 0.5])
+ np.testing.assert_allclose(axtest.bbox.get_points(),
+ axref.bbox.get_points())
+
+ # check that position kwarg ignored if rect is supplied
+ axref = plt.axes([0.2, 0.2, 0.5, 0.5])
+ axtest = plt.axes([0.2, 0.2, 0.5, 0.5], position=[0.1, 0.1, 0.8, 0.8])
+ np.testing.assert_allclose(axtest.bbox.get_points(),
+ axref.bbox.get_points())
+
+
+def test_set_current_figure_via_subfigure():
+ fig1 = plt.figure()
+ subfigs = fig1.subfigures(2)
+
+ plt.figure()
+ assert plt.gcf() != fig1
+
+ current = plt.figure(subfigs[1])
+ assert plt.gcf() == fig1
+ assert current == fig1
+
+
+def test_set_current_axes_on_subfigure():
+ fig = plt.figure()
+ subfigs = fig.subfigures(2)
+
+ ax = subfigs[0].subplots(1, squeeze=True)
+ subfigs[1].subplots(1, squeeze=True)
+
+ assert plt.gca() != ax
+ plt.sca(ax)
+ assert plt.gca() == ax
+
+
+def test_pylab_integration():
+ IPython = pytest.importorskip("IPython")
+ mpl.testing.subprocess_run_helper(
+ IPython.start_ipython,
+ "--pylab",
+ "-c",
+ ";".join((
+ "import matplotlib.pyplot as plt",
+ "assert plt._REPL_DISPLAYHOOK == plt._ReplDisplayHook.IPYTHON",
+ )),
+ timeout=60,
+ )
+
+
+def test_doc_pyplot_summary():
+ """Test that pyplot_summary lists all the plot functions."""
+ pyplot_docs = Path(__file__).parent / '../../../doc/api/pyplot_summary.rst'
+ if not pyplot_docs.exists():
+ pytest.skip("Documentation sources not available")
+
+ def extract_documented_functions(lines):
+ """
+ Return a list of all the functions that are mentioned in the
+ autosummary blocks contained in *lines*.
+
+ An autosummary block looks like this::
+
+ .. autosummary::
+ :toctree: _as_gen
+ :template: autosummary.rst
+ :nosignatures:
+
+ plot
+ plot_date
+
+ """
+ functions = []
+ in_autosummary = False
+ for line in lines:
+ if not in_autosummary:
+ if line.startswith(".. autosummary::"):
+ in_autosummary = True
+ else:
+ if not line or line.startswith(" :"):
+ # empty line or autosummary parameter
+ continue
+ if not line[0].isspace():
+ # no more indentation: end of autosummary block
+ in_autosummary = False
+ continue
+ functions.append(line.strip())
+ return functions
+
+ lines = pyplot_docs.read_text().split("\n")
+ doc_functions = set(extract_documented_functions(lines))
+ plot_commands = set(plt._get_pyplot_commands())
+ missing = plot_commands.difference(doc_functions)
+ if missing:
+ raise AssertionError(
+ f"The following pyplot functions are not listed in the "
+ f"documentation. Please add them to doc/api/pyplot_summary.rst: "
+ f"{missing!r}")
+ extra = doc_functions.difference(plot_commands)
+ if extra:
+ raise AssertionError(
+ f"The following functions are listed in the pyplot documentation, "
+ f"but they do not exist in pyplot. "
+ f"Please remove them from doc/api/pyplot_summary.rst: {extra!r}")
+
+
+def test_minor_ticks():
+ plt.figure()
+ plt.plot(np.arange(1, 10))
+ tick_pos, tick_labels = plt.xticks(minor=True)
+ assert np.all(tick_labels == np.array([], dtype=np.float64))
+ assert tick_labels == []
+
+ plt.yticks(ticks=[3.5, 6.5], labels=["a", "b"], minor=True)
+ ax = plt.gca()
+ tick_pos = ax.get_yticks(minor=True)
+ tick_labels = ax.get_yticklabels(minor=True)
+ assert np.all(tick_pos == np.array([3.5, 6.5]))
+ assert [l.get_text() for l in tick_labels] == ['a', 'b']
+
+
+def test_switch_backend_no_close():
+ plt.switch_backend('agg')
+ fig = plt.figure()
+ fig = plt.figure()
+ assert len(plt.get_fignums()) == 2
+ plt.switch_backend('agg')
+ assert len(plt.get_fignums()) == 2
+ plt.switch_backend('svg')
+ assert len(plt.get_fignums()) == 2
+
+
+def figure_hook_example(figure):
+ figure._test_was_here = True
+
+
+def test_figure_hook():
+
+ test_rc = {
+ 'figure.hooks': ['matplotlib.tests.test_pyplot:figure_hook_example']
+ }
+ with mpl.rc_context(test_rc):
+ fig = plt.figure()
+
+ assert fig._test_was_here
+
+
+def test_multiple_same_figure_calls():
+ fig = plt.figure(1, figsize=(1, 2))
+ with pytest.warns(UserWarning, match="Ignoring specified arguments in this call"):
+ fig2 = plt.figure(1, figsize=np.array([3, 4]))
+ with pytest.warns(UserWarning, match="Ignoring specified arguments in this call"):
+ plt.figure(fig, figsize=np.array([5, 6]))
+ assert fig is fig2
+ fig3 = plt.figure(1) # Checks for false warnings
+ assert fig is fig3
+
+
+def test_close_all_warning():
+ fig1 = plt.figure()
+
+ # Check that the warning is issued when 'all' is passed to plt.figure
+ with pytest.warns(UserWarning, match="closes all existing figures"):
+ fig2 = plt.figure("all")
+
+
+def test_matshow():
+ fig = plt.figure()
+ arr = [[0, 1], [1, 2]]
+
+ # Smoke test that matshow does not ask for a new figsize on the existing figure
+ plt.matshow(arr, fignum=fig.number)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_quiver.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_quiver.py
new file mode 100644
index 0000000000000000000000000000000000000000..9cbe1639dadcfce03819e4c8853842091a0be2b2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_quiver.py
@@ -0,0 +1,387 @@
+import platform
+import sys
+
+import numpy as np
+import pytest
+
+from matplotlib import pyplot as plt
+from matplotlib.testing.decorators import image_comparison
+from matplotlib.testing.decorators import check_figures_equal
+
+
+def draw_quiver(ax, **kwargs):
+ X, Y = np.meshgrid(np.arange(0, 2 * np.pi, 1),
+ np.arange(0, 2 * np.pi, 1))
+ U = np.cos(X)
+ V = np.sin(Y)
+
+ Q = ax.quiver(U, V, **kwargs)
+ return Q
+
+
+@pytest.mark.skipif(platform.python_implementation() != 'CPython',
+ reason='Requires CPython')
+def test_quiver_memory_leak():
+ fig, ax = plt.subplots()
+
+ Q = draw_quiver(ax)
+ ttX = Q.X
+ orig_refcount = sys.getrefcount(ttX)
+ Q.remove()
+
+ del Q
+
+ assert sys.getrefcount(ttX) < orig_refcount
+
+
+@pytest.mark.skipif(platform.python_implementation() != 'CPython',
+ reason='Requires CPython')
+def test_quiver_key_memory_leak():
+ fig, ax = plt.subplots()
+
+ Q = draw_quiver(ax)
+
+ qk = ax.quiverkey(Q, 0.5, 0.92, 2, r'$2 \frac{m}{s}$',
+ labelpos='W',
+ fontproperties={'weight': 'bold'})
+ orig_refcount = sys.getrefcount(qk)
+ qk.remove()
+ assert sys.getrefcount(qk) < orig_refcount
+
+
+def test_quiver_number_of_args():
+ X = [1, 2]
+ with pytest.raises(
+ TypeError,
+ match='takes from 2 to 5 positional arguments but 1 were given'):
+ plt.quiver(X)
+ with pytest.raises(
+ TypeError,
+ match='takes from 2 to 5 positional arguments but 6 were given'):
+ plt.quiver(X, X, X, X, X, X)
+
+
+def test_quiver_arg_sizes():
+ X2 = [1, 2]
+ X3 = [1, 2, 3]
+ with pytest.raises(
+ ValueError, match=('X and Y must be the same size, but '
+ 'X.size is 2 and Y.size is 3.')):
+ plt.quiver(X2, X3, X2, X2)
+ with pytest.raises(
+ ValueError, match=('Argument U has a size 3 which does not match '
+ '2, the number of arrow positions')):
+ plt.quiver(X2, X2, X3, X2)
+ with pytest.raises(
+ ValueError, match=('Argument V has a size 3 which does not match '
+ '2, the number of arrow positions')):
+ plt.quiver(X2, X2, X2, X3)
+ with pytest.raises(
+ ValueError, match=('Argument C has a size 3 which does not match '
+ '2, the number of arrow positions')):
+ plt.quiver(X2, X2, X2, X2, X3)
+
+
+def test_no_warnings():
+ fig, ax = plt.subplots()
+ X, Y = np.meshgrid(np.arange(15), np.arange(10))
+ U = V = np.ones_like(X)
+ phi = (np.random.rand(15, 10) - .5) * 150
+ ax.quiver(X, Y, U, V, angles=phi)
+ fig.canvas.draw() # Check that no warning is emitted.
+
+
+def test_zero_headlength():
+ # Based on report by Doug McNeil:
+ # https://discourse.matplotlib.org/t/quiver-warnings/16722
+ fig, ax = plt.subplots()
+ X, Y = np.meshgrid(np.arange(10), np.arange(10))
+ U, V = np.cos(X), np.sin(Y)
+ ax.quiver(U, V, headlength=0, headaxislength=0)
+ fig.canvas.draw() # Check that no warning is emitted.
+
+
+@image_comparison(['quiver_animated_test_image.png'])
+def test_quiver_animate():
+ # Tests fix for #2616
+ fig, ax = plt.subplots()
+ Q = draw_quiver(ax, animated=True)
+ ax.quiverkey(Q, 0.5, 0.92, 2, r'$2 \frac{m}{s}$',
+ labelpos='W', fontproperties={'weight': 'bold'})
+
+
+@image_comparison(['quiver_with_key_test_image.png'])
+def test_quiver_with_key():
+ fig, ax = plt.subplots()
+ ax.margins(0.1)
+ Q = draw_quiver(ax)
+ ax.quiverkey(Q, 0.5, 0.95, 2,
+ r'$2\, \mathrm{m}\, \mathrm{s}^{-1}$',
+ angle=-10,
+ coordinates='figure',
+ labelpos='W',
+ fontproperties={'weight': 'bold', 'size': 'large'})
+
+
+@image_comparison(['quiver_single_test_image.png'], remove_text=True)
+def test_quiver_single():
+ fig, ax = plt.subplots()
+ ax.margins(0.1)
+ ax.quiver([1], [1], [2], [2])
+
+
+def test_quiver_copy():
+ fig, ax = plt.subplots()
+ uv = dict(u=np.array([1.1]), v=np.array([2.0]))
+ q0 = ax.quiver([1], [1], uv['u'], uv['v'])
+ uv['v'][0] = 0
+ assert q0.V[0] == 2.0
+
+
+@image_comparison(['quiver_key_pivot.png'], remove_text=True)
+def test_quiver_key_pivot():
+ fig, ax = plt.subplots()
+
+ u, v = np.mgrid[0:2*np.pi:10j, 0:2*np.pi:10j]
+
+ q = ax.quiver(np.sin(u), np.cos(v))
+ ax.set_xlim(-2, 11)
+ ax.set_ylim(-2, 11)
+ ax.quiverkey(q, 0.5, 1, 1, 'N', labelpos='N')
+ ax.quiverkey(q, 1, 0.5, 1, 'E', labelpos='E')
+ ax.quiverkey(q, 0.5, 0, 1, 'S', labelpos='S')
+ ax.quiverkey(q, 0, 0.5, 1, 'W', labelpos='W')
+
+
+@image_comparison(['quiver_key_xy.png'], remove_text=True)
+def test_quiver_key_xy():
+ # With scale_units='xy', ensure quiverkey still matches its quiver.
+ # Note that the quiver and quiverkey lengths depend on the axes aspect
+ # ratio, and that with angles='xy' their angles also depend on the axes
+ # aspect ratio.
+ X = np.arange(8)
+ Y = np.zeros(8)
+ angles = X * (np.pi / 4)
+ uv = np.exp(1j * angles)
+ U = uv.real
+ V = uv.imag
+ fig, axs = plt.subplots(2)
+ for ax, angle_str in zip(axs, ('uv', 'xy')):
+ ax.set_xlim(-1, 8)
+ ax.set_ylim(-0.2, 0.2)
+ q = ax.quiver(X, Y, U, V, pivot='middle',
+ units='xy', width=0.05,
+ scale=2, scale_units='xy',
+ angles=angle_str)
+ for x, angle in zip((0.2, 0.5, 0.8), (0, 45, 90)):
+ ax.quiverkey(q, X=x, Y=0.8, U=1, angle=angle, label='', color='b')
+
+
+@image_comparison(['barbs_test_image.png'], remove_text=True)
+def test_barbs():
+ x = np.linspace(-5, 5, 5)
+ X, Y = np.meshgrid(x, x)
+ U, V = 12*X, 12*Y
+ fig, ax = plt.subplots()
+ ax.barbs(X, Y, U, V, np.hypot(U, V), fill_empty=True, rounding=False,
+ sizes=dict(emptybarb=0.25, spacing=0.2, height=0.3),
+ cmap='viridis')
+
+
+@image_comparison(['barbs_pivot_test_image.png'], remove_text=True)
+def test_barbs_pivot():
+ x = np.linspace(-5, 5, 5)
+ X, Y = np.meshgrid(x, x)
+ U, V = 12*X, 12*Y
+ fig, ax = plt.subplots()
+ ax.barbs(X, Y, U, V, fill_empty=True, rounding=False, pivot=1.7,
+ sizes=dict(emptybarb=0.25, spacing=0.2, height=0.3))
+ ax.scatter(X, Y, s=49, c='black')
+
+
+@image_comparison(['barbs_test_flip.png'], remove_text=True)
+def test_barbs_flip():
+ """Test barbs with an array for flip_barb."""
+ x = np.linspace(-5, 5, 5)
+ X, Y = np.meshgrid(x, x)
+ U, V = 12*X, 12*Y
+ fig, ax = plt.subplots()
+ ax.barbs(X, Y, U, V, fill_empty=True, rounding=False, pivot=1.7,
+ sizes=dict(emptybarb=0.25, spacing=0.2, height=0.3),
+ flip_barb=Y < 0)
+
+
+def test_barb_copy():
+ fig, ax = plt.subplots()
+ u = np.array([1.1])
+ v = np.array([2.2])
+ b0 = ax.barbs([1], [1], u, v)
+ u[0] = 0
+ assert b0.u[0] == 1.1
+ v[0] = 0
+ assert b0.v[0] == 2.2
+
+
+def test_bad_masked_sizes():
+ """Test error handling when given differing sized masked arrays."""
+ x = np.arange(3)
+ y = np.arange(3)
+ u = np.ma.array(15. * np.ones((4,)))
+ v = np.ma.array(15. * np.ones_like(u))
+ u[1] = np.ma.masked
+ v[1] = np.ma.masked
+ fig, ax = plt.subplots()
+ with pytest.raises(ValueError):
+ ax.barbs(x, y, u, v)
+
+
+def test_angles_and_scale():
+ # angles array + scale_units kwarg
+ fig, ax = plt.subplots()
+ X, Y = np.meshgrid(np.arange(15), np.arange(10))
+ U = V = np.ones_like(X)
+ phi = (np.random.rand(15, 10) - .5) * 150
+ ax.quiver(X, Y, U, V, angles=phi, scale_units='xy')
+
+
+@image_comparison(['quiver_xy.png'], remove_text=True)
+def test_quiver_xy():
+ # simple arrow pointing from SW to NE
+ fig, ax = plt.subplots(subplot_kw=dict(aspect='equal'))
+ ax.quiver(0, 0, 1, 1, angles='xy', scale_units='xy', scale=1)
+ ax.set_xlim(0, 1.1)
+ ax.set_ylim(0, 1.1)
+ ax.grid()
+
+
+def test_quiverkey_angles():
+ # Check that only a single arrow is plotted for a quiverkey when an array
+ # of angles is given to the original quiver plot
+ fig, ax = plt.subplots()
+
+ X, Y = np.meshgrid(np.arange(2), np.arange(2))
+ U = V = angles = np.ones_like(X)
+
+ q = ax.quiver(X, Y, U, V, angles=angles)
+ qk = ax.quiverkey(q, 1, 1, 2, 'Label')
+ # The arrows are only created when the key is drawn
+ fig.canvas.draw()
+ assert len(qk.verts) == 1
+
+
+def test_quiverkey_angles_xy_aitoff():
+ # GH 26316 and GH 26748
+ # Test that only one arrow will be plotted with non-cartesian
+ # when angles='xy' and/or scale_units='xy'
+
+ # only for test purpose
+ # scale_units='xy' may not be a valid use case for non-cartesian
+ kwargs_list = [
+ {'angles': 'xy'},
+ {'angles': 'xy', 'scale_units': 'xy'},
+ {'scale_units': 'xy'}
+ ]
+
+ for kwargs_dict in kwargs_list:
+
+ x = np.linspace(-np.pi, np.pi, 11)
+ y = np.ones_like(x) * np.pi / 6
+ vx = np.zeros_like(x)
+ vy = np.ones_like(x)
+
+ fig = plt.figure()
+ ax = fig.add_subplot(projection='aitoff')
+ q = ax.quiver(x, y, vx, vy, **kwargs_dict)
+ qk = ax.quiverkey(q, 0, 0, 1, '1 units')
+
+ fig.canvas.draw()
+ assert len(qk.verts) == 1
+
+
+def test_quiverkey_angles_scale_units_cartesian():
+ # GH 26316
+ # Test that only one arrow will be plotted with normal cartesian
+ # when angles='xy' and/or scale_units='xy'
+
+ kwargs_list = [
+ {'angles': 'xy'},
+ {'angles': 'xy', 'scale_units': 'xy'},
+ {'scale_units': 'xy'}
+ ]
+
+ for kwargs_dict in kwargs_list:
+ X = [0, -1, 0]
+ Y = [0, -1, 0]
+ U = [1, -1, 1]
+ V = [1, -1, 0]
+
+ fig, ax = plt.subplots()
+ q = ax.quiver(X, Y, U, V, **kwargs_dict)
+ ax.quiverkey(q, X=0.3, Y=1.1, U=1,
+ label='Quiver key, length = 1', labelpos='E')
+ qk = ax.quiverkey(q, 0, 0, 1, '1 units')
+
+ fig.canvas.draw()
+ assert len(qk.verts) == 1
+
+
+def test_quiver_setuvc_numbers():
+ """Check that it is possible to set all arrow UVC to the same numbers"""
+
+ fig, ax = plt.subplots()
+
+ X, Y = np.meshgrid(np.arange(2), np.arange(2))
+ U = V = np.ones_like(X)
+
+ q = ax.quiver(X, Y, U, V)
+ q.set_UVC(0, 1)
+
+
+def draw_quiverkey_zorder_argument(fig, zorder=None):
+ """Draw Quiver and QuiverKey using zorder argument"""
+ x = np.arange(1, 6, 1)
+ y = np.arange(1, 6, 1)
+ X, Y = np.meshgrid(x, y)
+ U, V = 2, 2
+
+ ax = fig.subplots()
+ q = ax.quiver(X, Y, U, V, pivot='middle')
+ ax.set_xlim(0.5, 5.5)
+ ax.set_ylim(0.5, 5.5)
+ if zorder is None:
+ ax.quiverkey(q, 4, 4, 25, coordinates='data',
+ label='U', color='blue')
+ ax.quiverkey(q, 5.5, 2, 20, coordinates='data',
+ label='V', color='blue', angle=90)
+ else:
+ ax.quiverkey(q, 4, 4, 25, coordinates='data',
+ label='U', color='blue', zorder=zorder)
+ ax.quiverkey(q, 5.5, 2, 20, coordinates='data',
+ label='V', color='blue', angle=90, zorder=zorder)
+
+
+def draw_quiverkey_setzorder(fig, zorder=None):
+ """Draw Quiver and QuiverKey using set_zorder"""
+ x = np.arange(1, 6, 1)
+ y = np.arange(1, 6, 1)
+ X, Y = np.meshgrid(x, y)
+ U, V = 2, 2
+
+ ax = fig.subplots()
+ q = ax.quiver(X, Y, U, V, pivot='middle')
+ ax.set_xlim(0.5, 5.5)
+ ax.set_ylim(0.5, 5.5)
+ qk1 = ax.quiverkey(q, 4, 4, 25, coordinates='data',
+ label='U', color='blue')
+ qk2 = ax.quiverkey(q, 5.5, 2, 20, coordinates='data',
+ label='V', color='blue', angle=90)
+ if zorder is not None:
+ qk1.set_zorder(zorder)
+ qk2.set_zorder(zorder)
+
+
+@pytest.mark.parametrize('zorder', [0, 2, 5, None])
+@check_figures_equal(extensions=['png'])
+def test_quiverkey_zorder(fig_test, fig_ref, zorder):
+ draw_quiverkey_zorder_argument(fig_test, zorder=zorder)
+ draw_quiverkey_setzorder(fig_ref, zorder=zorder)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_rcparams.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_rcparams.py
new file mode 100644
index 0000000000000000000000000000000000000000..bea5e90ea4e54cb1ea480b2b20c7eb72e087bdcb
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_rcparams.py
@@ -0,0 +1,682 @@
+import copy
+import os
+import subprocess
+import sys
+from unittest import mock
+
+from cycler import cycler, Cycler
+from packaging.version import parse as parse_version
+import pytest
+
+import matplotlib as mpl
+from matplotlib import _api, _c_internal_utils
+import matplotlib.pyplot as plt
+import matplotlib.colors as mcolors
+import numpy as np
+from matplotlib.rcsetup import (
+ validate_bool,
+ validate_color,
+ validate_colorlist,
+ _validate_color_or_linecolor,
+ validate_cycler,
+ validate_float,
+ validate_fontstretch,
+ validate_fontweight,
+ validate_hatch,
+ validate_hist_bins,
+ validate_int,
+ validate_markevery,
+ validate_stringlist,
+ validate_sketch,
+ _validate_linestyle,
+ _listify_validator)
+from matplotlib.testing import subprocess_run_for_testing
+
+
+def test_rcparams(tmp_path):
+ mpl.rc('text', usetex=False)
+ mpl.rc('lines', linewidth=22)
+
+ usetex = mpl.rcParams['text.usetex']
+ linewidth = mpl.rcParams['lines.linewidth']
+
+ rcpath = tmp_path / 'test_rcparams.rc'
+ rcpath.write_text('lines.linewidth: 33', encoding='utf-8')
+
+ # test context given dictionary
+ with mpl.rc_context(rc={'text.usetex': not usetex}):
+ assert mpl.rcParams['text.usetex'] == (not usetex)
+ assert mpl.rcParams['text.usetex'] == usetex
+
+ # test context given filename (mpl.rc sets linewidth to 33)
+ with mpl.rc_context(fname=rcpath):
+ assert mpl.rcParams['lines.linewidth'] == 33
+ assert mpl.rcParams['lines.linewidth'] == linewidth
+
+ # test context given filename and dictionary
+ with mpl.rc_context(fname=rcpath, rc={'lines.linewidth': 44}):
+ assert mpl.rcParams['lines.linewidth'] == 44
+ assert mpl.rcParams['lines.linewidth'] == linewidth
+
+ # test context as decorator (and test reusability, by calling func twice)
+ @mpl.rc_context({'lines.linewidth': 44})
+ def func():
+ assert mpl.rcParams['lines.linewidth'] == 44
+
+ func()
+ func()
+
+ # test rc_file
+ mpl.rc_file(rcpath)
+ assert mpl.rcParams['lines.linewidth'] == 33
+
+
+def test_RcParams_class():
+ rc = mpl.RcParams({'font.cursive': ['Apple Chancery',
+ 'Textile',
+ 'Zapf Chancery',
+ 'cursive'],
+ 'font.family': 'sans-serif',
+ 'font.weight': 'normal',
+ 'font.size': 12})
+
+ expected_repr = """
+RcParams({'font.cursive': ['Apple Chancery',
+ 'Textile',
+ 'Zapf Chancery',
+ 'cursive'],
+ 'font.family': ['sans-serif'],
+ 'font.size': 12.0,
+ 'font.weight': 'normal'})""".lstrip()
+
+ assert expected_repr == repr(rc)
+
+ expected_str = """
+font.cursive: ['Apple Chancery', 'Textile', 'Zapf Chancery', 'cursive']
+font.family: ['sans-serif']
+font.size: 12.0
+font.weight: normal""".lstrip()
+
+ assert expected_str == str(rc)
+
+ # test the find_all functionality
+ assert ['font.cursive', 'font.size'] == sorted(rc.find_all('i[vz]'))
+ assert ['font.family'] == list(rc.find_all('family'))
+
+
+def test_rcparams_update():
+ rc = mpl.RcParams({'figure.figsize': (3.5, 42)})
+ bad_dict = {'figure.figsize': (3.5, 42, 1)}
+ # make sure validation happens on input
+ with pytest.raises(ValueError):
+ rc.update(bad_dict)
+
+
+def test_rcparams_init():
+ with pytest.raises(ValueError):
+ mpl.RcParams({'figure.figsize': (3.5, 42, 1)})
+
+
+def test_nargs_cycler():
+ from matplotlib.rcsetup import cycler as ccl
+ with pytest.raises(TypeError, match='3 were given'):
+ # cycler() takes 0-2 arguments.
+ ccl(ccl(color=list('rgb')), 2, 3)
+
+
+def test_Bug_2543():
+ # Test that it possible to add all values to itself / deepcopy
+ # https://github.com/matplotlib/matplotlib/issues/2543
+ # We filter warnings at this stage since a number of them are raised
+ # for deprecated rcparams as they should. We don't want these in the
+ # printed in the test suite.
+ with _api.suppress_matplotlib_deprecation_warning():
+ with mpl.rc_context():
+ _copy = mpl.rcParams.copy()
+ for key in _copy:
+ mpl.rcParams[key] = _copy[key]
+ with mpl.rc_context():
+ copy.deepcopy(mpl.rcParams)
+ with pytest.raises(ValueError):
+ validate_bool(None)
+ with pytest.raises(ValueError):
+ with mpl.rc_context():
+ mpl.rcParams['svg.fonttype'] = True
+
+
+legend_color_tests = [
+ ('face', {'color': 'r'}, mcolors.to_rgba('r')),
+ ('face', {'color': 'inherit', 'axes.facecolor': 'r'},
+ mcolors.to_rgba('r')),
+ ('face', {'color': 'g', 'axes.facecolor': 'r'}, mcolors.to_rgba('g')),
+ ('edge', {'color': 'r'}, mcolors.to_rgba('r')),
+ ('edge', {'color': 'inherit', 'axes.edgecolor': 'r'},
+ mcolors.to_rgba('r')),
+ ('edge', {'color': 'g', 'axes.facecolor': 'r'}, mcolors.to_rgba('g'))
+]
+legend_color_test_ids = [
+ 'same facecolor',
+ 'inherited facecolor',
+ 'different facecolor',
+ 'same edgecolor',
+ 'inherited edgecolor',
+ 'different facecolor',
+]
+
+
+@pytest.mark.parametrize('color_type, param_dict, target', legend_color_tests,
+ ids=legend_color_test_ids)
+def test_legend_colors(color_type, param_dict, target):
+ param_dict[f'legend.{color_type}color'] = param_dict.pop('color')
+ get_func = f'get_{color_type}color'
+
+ with mpl.rc_context(param_dict):
+ _, ax = plt.subplots()
+ ax.plot(range(3), label='test')
+ leg = ax.legend()
+ assert getattr(leg.legendPatch, get_func)() == target
+
+
+def test_mfc_rcparams():
+ mpl.rcParams['lines.markerfacecolor'] = 'r'
+ ln = mpl.lines.Line2D([1, 2], [1, 2])
+ assert ln.get_markerfacecolor() == 'r'
+
+
+def test_mec_rcparams():
+ mpl.rcParams['lines.markeredgecolor'] = 'r'
+ ln = mpl.lines.Line2D([1, 2], [1, 2])
+ assert ln.get_markeredgecolor() == 'r'
+
+
+def test_axes_titlecolor_rcparams():
+ mpl.rcParams['axes.titlecolor'] = 'r'
+ _, ax = plt.subplots()
+ title = ax.set_title("Title")
+ assert title.get_color() == 'r'
+
+
+def test_Issue_1713(tmp_path):
+ rcpath = tmp_path / 'test_rcparams.rc'
+ rcpath.write_text('timezone: UTC', encoding='utf-8')
+ with mock.patch('locale.getpreferredencoding', return_value='UTF-32-BE'):
+ rc = mpl.rc_params_from_file(rcpath, True, False)
+ assert rc.get('timezone') == 'UTC'
+
+
+def test_animation_frame_formats():
+ # Animation frame_format should allow any of the following
+ # if any of these are not allowed, an exception will be raised
+ # test for gh issue #17908
+ for fmt in ['png', 'jpeg', 'tiff', 'raw', 'rgba', 'ppm',
+ 'sgi', 'bmp', 'pbm', 'svg']:
+ mpl.rcParams['animation.frame_format'] = fmt
+
+
+def generate_validator_testcases(valid):
+ validation_tests = (
+ {'validator': validate_bool,
+ 'success': (*((_, True) for _ in
+ ('t', 'y', 'yes', 'on', 'true', '1', 1, True)),
+ *((_, False) for _ in
+ ('f', 'n', 'no', 'off', 'false', '0', 0, False))),
+ 'fail': ((_, ValueError)
+ for _ in ('aardvark', 2, -1, [], ))
+ },
+ {'validator': validate_stringlist,
+ 'success': (('', []),
+ ('a,b', ['a', 'b']),
+ ('aardvark', ['aardvark']),
+ ('aardvark, ', ['aardvark']),
+ ('aardvark, ,', ['aardvark']),
+ (['a', 'b'], ['a', 'b']),
+ (('a', 'b'), ['a', 'b']),
+ (iter(['a', 'b']), ['a', 'b']),
+ (np.array(['a', 'b']), ['a', 'b']),
+ ),
+ 'fail': ((set(), ValueError),
+ (1, ValueError),
+ )
+ },
+ {'validator': _listify_validator(validate_int, n=2),
+ 'success': ((_, [1, 2])
+ for _ in ('1, 2', [1.5, 2.5], [1, 2],
+ (1, 2), np.array((1, 2)))),
+ 'fail': ((_, ValueError)
+ for _ in ('aardvark', ('a', 1),
+ (1, 2, 3)
+ ))
+ },
+ {'validator': _listify_validator(validate_float, n=2),
+ 'success': ((_, [1.5, 2.5])
+ for _ in ('1.5, 2.5', [1.5, 2.5], [1.5, 2.5],
+ (1.5, 2.5), np.array((1.5, 2.5)))),
+ 'fail': ((_, ValueError)
+ for _ in ('aardvark', ('a', 1), (1, 2, 3), (None, ), None))
+ },
+ {'validator': validate_cycler,
+ 'success': (('cycler("color", "rgb")',
+ cycler("color", 'rgb')),
+ (cycler('linestyle', ['-', '--']),
+ cycler('linestyle', ['-', '--'])),
+ ("""(cycler("color", ["r", "g", "b"]) +
+ cycler("mew", [2, 3, 5]))""",
+ (cycler("color", 'rgb') +
+ cycler("markeredgewidth", [2, 3, 5]))),
+ ("cycler(c='rgb', lw=[1, 2, 3])",
+ cycler('color', 'rgb') + cycler('linewidth', [1, 2, 3])),
+ ("cycler('c', 'rgb') * cycler('linestyle', ['-', '--'])",
+ (cycler('color', 'rgb') *
+ cycler('linestyle', ['-', '--']))),
+ (cycler('ls', ['-', '--']),
+ cycler('linestyle', ['-', '--'])),
+ (cycler(mew=[2, 5]),
+ cycler('markeredgewidth', [2, 5])),
+ ),
+ # This is *so* incredibly important: validate_cycler() eval's
+ # an arbitrary string! I think I have it locked down enough,
+ # and that is what this is testing.
+ # TODO: Note that these tests are actually insufficient, as it may
+ # be that they raised errors, but still did an action prior to
+ # raising the exception. We should devise some additional tests
+ # for that...
+ 'fail': ((4, ValueError), # Gotta be a string or Cycler object
+ ('cycler("bleh, [])', ValueError), # syntax error
+ ('Cycler("linewidth", [1, 2, 3])',
+ ValueError), # only 'cycler()' function is allowed
+ # do not allow dunder in string literals
+ ("cycler('c', [j.__class__(j) for j in ['r', 'b']])",
+ ValueError),
+ ("cycler('c', [j. __class__(j) for j in ['r', 'b']])",
+ ValueError),
+ ("cycler('c', [j.\t__class__(j) for j in ['r', 'b']])",
+ ValueError),
+ ("cycler('c', [j.\u000c__class__(j) for j in ['r', 'b']])",
+ ValueError),
+ ("cycler('c', [j.__class__(j).lower() for j in ['r', 'b']])",
+ ValueError),
+ ('1 + 2', ValueError), # doesn't produce a Cycler object
+ ('os.system("echo Gotcha")', ValueError), # os not available
+ ('import os', ValueError), # should not be able to import
+ ('def badjuju(a): return a; badjuju(cycler("color", "rgb"))',
+ ValueError), # Should not be able to define anything
+ # even if it does return a cycler
+ ('cycler("waka", [1, 2, 3])', ValueError), # not a property
+ ('cycler(c=[1, 2, 3])', ValueError), # invalid values
+ ("cycler(lw=['a', 'b', 'c'])", ValueError), # invalid values
+ (cycler('waka', [1, 3, 5]), ValueError), # not a property
+ (cycler('color', ['C1', 'r', 'g']), ValueError) # no CN
+ )
+ },
+ {'validator': validate_hatch,
+ 'success': (('--|', '--|'), ('\\oO', '\\oO'),
+ ('/+*/.x', '/+*/.x'), ('', '')),
+ 'fail': (('--_', ValueError),
+ (8, ValueError),
+ ('X', ValueError)),
+ },
+ {'validator': validate_colorlist,
+ 'success': (('r,g,b', ['r', 'g', 'b']),
+ (['r', 'g', 'b'], ['r', 'g', 'b']),
+ ('r, ,', ['r']),
+ (['', 'g', 'blue'], ['g', 'blue']),
+ ([np.array([1, 0, 0]), np.array([0, 1, 0])],
+ np.array([[1, 0, 0], [0, 1, 0]])),
+ (np.array([[1, 0, 0], [0, 1, 0]]),
+ np.array([[1, 0, 0], [0, 1, 0]])),
+ ),
+ 'fail': (('fish', ValueError),
+ ),
+ },
+ {'validator': validate_color,
+ 'success': (('None', 'none'),
+ ('none', 'none'),
+ ('AABBCC', '#AABBCC'), # RGB hex code
+ ('AABBCC00', '#AABBCC00'), # RGBA hex code
+ ('tab:blue', 'tab:blue'), # named color
+ ('C12', 'C12'), # color from cycle
+ ('(0, 1, 0)', (0.0, 1.0, 0.0)), # RGB tuple
+ ((0, 1, 0), (0, 1, 0)), # non-string version
+ ('(0, 1, 0, 1)', (0.0, 1.0, 0.0, 1.0)), # RGBA tuple
+ ((0, 1, 0, 1), (0, 1, 0, 1)), # non-string version
+ ),
+ 'fail': (('tab:veryblue', ValueError), # invalid name
+ ('(0, 1)', ValueError), # tuple with length < 3
+ ('(0, 1, 0, 1, 0)', ValueError), # tuple with length > 4
+ ('(0, 1, none)', ValueError), # cannot cast none to float
+ ('(0, 1, "0.5")', ValueError), # last one not a float
+ ),
+ },
+ {'validator': _validate_color_or_linecolor,
+ 'success': (('linecolor', 'linecolor'),
+ ('markerfacecolor', 'markerfacecolor'),
+ ('mfc', 'markerfacecolor'),
+ ('markeredgecolor', 'markeredgecolor'),
+ ('mec', 'markeredgecolor')
+ ),
+ 'fail': (('line', ValueError),
+ ('marker', ValueError)
+ )
+ },
+ {'validator': validate_hist_bins,
+ 'success': (('auto', 'auto'),
+ ('fd', 'fd'),
+ ('10', 10),
+ ('1, 2, 3', [1, 2, 3]),
+ ([1, 2, 3], [1, 2, 3]),
+ (np.arange(15), np.arange(15))
+ ),
+ 'fail': (('aardvark', ValueError),
+ )
+ },
+ {'validator': validate_markevery,
+ 'success': ((None, None),
+ (1, 1),
+ (0.1, 0.1),
+ ((1, 1), (1, 1)),
+ ((0.1, 0.1), (0.1, 0.1)),
+ ([1, 2, 3], [1, 2, 3]),
+ (slice(2), slice(None, 2, None)),
+ (slice(1, 2, 3), slice(1, 2, 3))
+ ),
+ 'fail': (((1, 2, 3), TypeError),
+ ([1, 2, 0.3], TypeError),
+ (['a', 2, 3], TypeError),
+ ([1, 2, 'a'], TypeError),
+ ((0.1, 0.2, 0.3), TypeError),
+ ((0.1, 2, 3), TypeError),
+ ((1, 0.2, 0.3), TypeError),
+ ((1, 0.1), TypeError),
+ ((0.1, 1), TypeError),
+ (('abc'), TypeError),
+ ((1, 'a'), TypeError),
+ ((0.1, 'b'), TypeError),
+ (('a', 1), TypeError),
+ (('a', 0.1), TypeError),
+ ('abc', TypeError),
+ ('a', TypeError),
+ (object(), TypeError)
+ )
+ },
+ {'validator': _validate_linestyle,
+ 'success': (('-', '-'), ('solid', 'solid'),
+ ('--', '--'), ('dashed', 'dashed'),
+ ('-.', '-.'), ('dashdot', 'dashdot'),
+ (':', ':'), ('dotted', 'dotted'),
+ ('', ''), (' ', ' '),
+ ('None', 'none'), ('none', 'none'),
+ ('DoTtEd', 'dotted'), # case-insensitive
+ ('1, 3', (0, (1, 3))),
+ ([1.23, 456], (0, [1.23, 456.0])),
+ ([1, 2, 3, 4], (0, [1.0, 2.0, 3.0, 4.0])),
+ ((0, [1, 2]), (0, [1, 2])),
+ ((-1, [1, 2]), (-1, [1, 2])),
+ ),
+ 'fail': (('aardvark', ValueError), # not a valid string
+ (b'dotted', ValueError),
+ ('dotted'.encode('utf-16'), ValueError),
+ ([1, 2, 3], ValueError), # sequence with odd length
+ (1.23, ValueError), # not a sequence
+ (("a", [1, 2]), ValueError), # wrong explicit offset
+ ((None, [1, 2]), ValueError), # wrong explicit offset
+ ((1, [1, 2, 3]), ValueError), # odd length sequence
+ (([1, 2], 1), ValueError), # inverted offset/onoff
+ )
+ },
+ )
+
+ for validator_dict in validation_tests:
+ validator = validator_dict['validator']
+ if valid:
+ for arg, target in validator_dict['success']:
+ yield validator, arg, target
+ else:
+ for arg, error_type in validator_dict['fail']:
+ yield validator, arg, error_type
+
+
+@pytest.mark.parametrize('validator, arg, target',
+ generate_validator_testcases(True))
+def test_validator_valid(validator, arg, target):
+ res = validator(arg)
+ if isinstance(target, np.ndarray):
+ np.testing.assert_equal(res, target)
+ elif not isinstance(target, Cycler):
+ assert res == target
+ else:
+ # Cyclers can't simply be asserted equal. They don't implement __eq__
+ assert list(res) == list(target)
+
+
+@pytest.mark.parametrize('validator, arg, exception_type',
+ generate_validator_testcases(False))
+def test_validator_invalid(validator, arg, exception_type):
+ with pytest.raises(exception_type):
+ validator(arg)
+
+
+@pytest.mark.parametrize('weight, parsed_weight', [
+ ('bold', 'bold'),
+ ('BOLD', ValueError), # weight is case-sensitive
+ (100, 100),
+ ('100', 100),
+ (np.array(100), 100),
+ # fractional fontweights are not defined. This should actually raise a
+ # ValueError, but historically did not.
+ (20.6, 20),
+ ('20.6', ValueError),
+ ([100], ValueError),
+])
+def test_validate_fontweight(weight, parsed_weight):
+ if parsed_weight is ValueError:
+ with pytest.raises(ValueError):
+ validate_fontweight(weight)
+ else:
+ assert validate_fontweight(weight) == parsed_weight
+
+
+@pytest.mark.parametrize('stretch, parsed_stretch', [
+ ('expanded', 'expanded'),
+ ('EXPANDED', ValueError), # stretch is case-sensitive
+ (100, 100),
+ ('100', 100),
+ (np.array(100), 100),
+ # fractional fontweights are not defined. This should actually raise a
+ # ValueError, but historically did not.
+ (20.6, 20),
+ ('20.6', ValueError),
+ ([100], ValueError),
+])
+def test_validate_fontstretch(stretch, parsed_stretch):
+ if parsed_stretch is ValueError:
+ with pytest.raises(ValueError):
+ validate_fontstretch(stretch)
+ else:
+ assert validate_fontstretch(stretch) == parsed_stretch
+
+
+def test_keymaps():
+ key_list = [k for k in mpl.rcParams if 'keymap' in k]
+ for k in key_list:
+ assert isinstance(mpl.rcParams[k], list)
+
+
+def test_no_backend_reset_rccontext():
+ assert mpl.rcParams['backend'] != 'module://aardvark'
+ with mpl.rc_context():
+ mpl.rcParams['backend'] = 'module://aardvark'
+ assert mpl.rcParams['backend'] == 'module://aardvark'
+
+
+def test_rcparams_reset_after_fail():
+ # There was previously a bug that meant that if rc_context failed and
+ # raised an exception due to issues in the supplied rc parameters, the
+ # global rc parameters were left in a modified state.
+ with mpl.rc_context(rc={'text.usetex': False}):
+ assert mpl.rcParams['text.usetex'] is False
+ with pytest.raises(KeyError):
+ with mpl.rc_context(rc={'text.usetex': True, 'test.blah': True}):
+ pass
+ assert mpl.rcParams['text.usetex'] is False
+
+
+@pytest.mark.skipif(sys.platform != "linux", reason="Linux only")
+def test_backend_fallback_headless_invalid_backend(tmp_path):
+ env = {**os.environ,
+ "DISPLAY": "", "WAYLAND_DISPLAY": "",
+ "MPLBACKEND": "", "MPLCONFIGDIR": str(tmp_path)}
+ # plotting should fail with the tkagg backend selected in a headless environment
+ with pytest.raises(subprocess.CalledProcessError):
+ subprocess_run_for_testing(
+ [sys.executable, "-c",
+ "import matplotlib;"
+ "matplotlib.use('tkagg');"
+ "import matplotlib.pyplot;"
+ "matplotlib.pyplot.plot(42);"
+ ],
+ env=env, check=True, stderr=subprocess.DEVNULL)
+
+
+@pytest.mark.skipif(sys.platform != "linux", reason="Linux only")
+def test_backend_fallback_headless_auto_backend(tmp_path):
+ # specify a headless mpl environment, but request a graphical (tk) backend
+ env = {**os.environ,
+ "DISPLAY": "", "WAYLAND_DISPLAY": "",
+ "MPLBACKEND": "TkAgg", "MPLCONFIGDIR": str(tmp_path)}
+
+ # allow fallback to an available interactive backend explicitly in configuration
+ rc_path = tmp_path / "matplotlibrc"
+ rc_path.write_text("backend_fallback: true")
+
+ # plotting should succeed, by falling back to use the generic agg backend
+ backend = subprocess_run_for_testing(
+ [sys.executable, "-c",
+ "import matplotlib.pyplot;"
+ "matplotlib.pyplot.plot(42);"
+ "print(matplotlib.get_backend());"
+ ],
+ env=env, text=True, check=True, capture_output=True).stdout
+ assert backend.strip().lower() == "agg"
+
+
+@pytest.mark.skipif(
+ sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(),
+ reason="headless")
+def test_backend_fallback_headful(tmp_path):
+ if parse_version(pytest.__version__) >= parse_version('8.2.0'):
+ pytest_kwargs = dict(exc_type=ImportError)
+ else:
+ pytest_kwargs = {}
+
+ pytest.importorskip("tkinter", **pytest_kwargs)
+ env = {**os.environ, "MPLBACKEND": "", "MPLCONFIGDIR": str(tmp_path)}
+ backend = subprocess_run_for_testing(
+ [sys.executable, "-c",
+ "import matplotlib as mpl; "
+ "sentinel = mpl.rcsetup._auto_backend_sentinel; "
+ # Check that access on another instance does not resolve the sentinel.
+ "assert mpl.RcParams({'backend': sentinel})['backend'] == sentinel; "
+ "assert mpl.rcParams._get('backend') == sentinel; "
+ "assert mpl.get_backend(auto_select=False) is None; "
+ "import matplotlib.pyplot; "
+ "print(matplotlib.get_backend())"],
+ env=env, text=True, check=True, capture_output=True).stdout
+ # The actual backend will depend on what's installed, but at least tkagg is
+ # present.
+ assert backend.strip().lower() != "agg"
+
+
+def test_deprecation(monkeypatch):
+ monkeypatch.setitem(
+ mpl._deprecated_map, "patch.linewidth",
+ ("0.0", "axes.linewidth", lambda old: 2 * old, lambda new: new / 2))
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ assert mpl.rcParams["patch.linewidth"] \
+ == mpl.rcParams["axes.linewidth"] / 2
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ mpl.rcParams["patch.linewidth"] = 1
+ assert mpl.rcParams["axes.linewidth"] == 2
+
+ monkeypatch.setitem(
+ mpl._deprecated_ignore_map, "patch.edgecolor",
+ ("0.0", "axes.edgecolor"))
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ assert mpl.rcParams["patch.edgecolor"] \
+ == mpl.rcParams["axes.edgecolor"]
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ mpl.rcParams["patch.edgecolor"] = "#abcd"
+ assert mpl.rcParams["axes.edgecolor"] != "#abcd"
+
+ monkeypatch.setitem(
+ mpl._deprecated_ignore_map, "patch.force_edgecolor",
+ ("0.0", None))
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ assert mpl.rcParams["patch.force_edgecolor"] is None
+
+ monkeypatch.setitem(
+ mpl._deprecated_remain_as_none, "svg.hashsalt",
+ ("0.0",))
+ with pytest.warns(mpl.MatplotlibDeprecationWarning):
+ mpl.rcParams["svg.hashsalt"] = "foobar"
+ assert mpl.rcParams["svg.hashsalt"] == "foobar" # Doesn't warn.
+ mpl.rcParams["svg.hashsalt"] = None # Doesn't warn.
+
+ mpl.rcParams.update(mpl.rcParams.copy()) # Doesn't warn.
+ # Note that the warning suppression actually arises from the
+ # iteration over the updater rcParams being protected by
+ # suppress_matplotlib_deprecation_warning, rather than any explicit check.
+
+
+@pytest.mark.parametrize("value", [
+ "best",
+ 1,
+ "1",
+ (0.9, .7),
+ (-0.9, .7),
+ "(0.9, .7)"
+])
+def test_rcparams_legend_loc(value):
+ # rcParams['legend.loc'] should allow any of the following formats.
+ # if any of these are not allowed, an exception will be raised
+ # test for gh issue #22338
+ mpl.rcParams["legend.loc"] = value
+
+
+@pytest.mark.parametrize("value", [
+ "best",
+ 1,
+ (0.9, .7),
+ (-0.9, .7),
+])
+def test_rcparams_legend_loc_from_file(tmp_path, value):
+ # rcParams['legend.loc'] should be settable from matplotlibrc.
+ # if any of these are not allowed, an exception will be raised.
+ # test for gh issue #22338
+ rc_path = tmp_path / "matplotlibrc"
+ rc_path.write_text(f"legend.loc: {value}")
+
+ with mpl.rc_context(fname=rc_path):
+ assert mpl.rcParams["legend.loc"] == value
+
+
+@pytest.mark.parametrize("value", [(1, 2, 3), '1, 2, 3', '(1, 2, 3)'])
+def test_validate_sketch(value):
+ mpl.rcParams["path.sketch"] = value
+ assert mpl.rcParams["path.sketch"] == (1, 2, 3)
+ assert validate_sketch(value) == (1, 2, 3)
+
+
+@pytest.mark.parametrize("value", [1, '1', '1 2 3'])
+def test_validate_sketch_error(value):
+ with pytest.raises(ValueError, match="scale, length, randomness"):
+ validate_sketch(value)
+ with pytest.raises(ValueError, match="scale, length, randomness"):
+ mpl.rcParams["path.sketch"] = value
+
+
+@pytest.mark.parametrize("value", ['1, 2, 3', '(1,2,3)'])
+def test_rcparams_path_sketch_from_file(tmp_path, value):
+ rc_path = tmp_path / "matplotlibrc"
+ rc_path.write_text(f"path.sketch: {value}")
+ with mpl.rc_context(fname=rc_path):
+ assert mpl.rcParams["path.sketch"] == (1, 2, 3)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_sankey.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_sankey.py
new file mode 100644
index 0000000000000000000000000000000000000000..cbb7f516a65c4a9bb745365ade4c0cdfe87fd43b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_sankey.py
@@ -0,0 +1,105 @@
+import pytest
+from numpy.testing import assert_allclose, assert_array_equal
+
+from matplotlib.sankey import Sankey
+from matplotlib.testing.decorators import check_figures_equal
+
+
+def test_sankey():
+ # lets just create a sankey instance and check the code runs
+ sankey = Sankey()
+ sankey.add()
+
+
+def test_label():
+ s = Sankey(flows=[0.25], labels=['First'], orientations=[-1])
+ assert s.diagrams[0].texts[0].get_text() == 'First\n0.25'
+
+
+def test_format_using_callable():
+ # test using callable by slightly incrementing above label example
+
+ def show_three_decimal_places(value):
+ return f'{value:.3f}'
+
+ s = Sankey(flows=[0.25], labels=['First'], orientations=[-1],
+ format=show_three_decimal_places)
+
+ assert s.diagrams[0].texts[0].get_text() == 'First\n0.250'
+
+
+@pytest.mark.parametrize('kwargs, msg', (
+ ({'gap': -1}, "'gap' is negative"),
+ ({'gap': 1, 'radius': 2}, "'radius' is greater than 'gap'"),
+ ({'head_angle': -1}, "'head_angle' is negative"),
+ ({'tolerance': -1}, "'tolerance' is negative"),
+ ({'flows': [1, -1], 'orientations': [-1, 0, 1]},
+ r"The shapes of 'flows' \(2,\) and 'orientations'"),
+ ({'flows': [1, -1], 'labels': ['a', 'b', 'c']},
+ r"The shapes of 'flows' \(2,\) and 'labels'"),
+ ))
+def test_sankey_errors(kwargs, msg):
+ with pytest.raises(ValueError, match=msg):
+ Sankey(**kwargs)
+
+
+@pytest.mark.parametrize('kwargs, msg', (
+ ({'trunklength': -1}, "'trunklength' is negative"),
+ ({'flows': [0.2, 0.3], 'prior': 0}, "The scaled sum of the connected"),
+ ({'prior': -1}, "The index of the prior diagram is negative"),
+ ({'prior': 1}, "The index of the prior diagram is 1"),
+ ({'connect': (-1, 1), 'prior': 0}, "At least one of the connection"),
+ ({'connect': (2, 1), 'prior': 0}, "The connection index to the source"),
+ ({'connect': (1, 3), 'prior': 0}, "The connection index to this dia"),
+ ({'connect': (1, 1), 'prior': 0, 'flows': [-0.2, 0.2],
+ 'orientations': [2]}, "The value of orientations"),
+ ({'connect': (1, 1), 'prior': 0, 'flows': [-0.2, 0.2],
+ 'pathlengths': [2]}, "The lengths of 'flows'"),
+ ))
+def test_sankey_add_errors(kwargs, msg):
+ sankey = Sankey()
+ with pytest.raises(ValueError, match=msg):
+ sankey.add(flows=[0.2, -0.2])
+ sankey.add(**kwargs)
+
+
+def test_sankey2():
+ s = Sankey(flows=[0.25, -0.25, 0.5, -0.5], labels=['Foo'],
+ orientations=[-1], unit='Bar')
+ sf = s.finish()
+ assert_array_equal(sf[0].flows, [0.25, -0.25, 0.5, -0.5])
+ assert sf[0].angles == [1, 3, 1, 3]
+ assert all([text.get_text()[0:3] == 'Foo' for text in sf[0].texts])
+ assert all([text.get_text()[-3:] == 'Bar' for text in sf[0].texts])
+ assert sf[0].text.get_text() == ''
+ assert_allclose(sf[0].tips,
+ [(-1.375, -0.52011255),
+ (1.375, -0.75506044),
+ (-0.75, -0.41522509),
+ (0.75, -0.8599479)])
+
+ s = Sankey(flows=[0.25, -0.25, 0, 0.5, -0.5], labels=['Foo'],
+ orientations=[-1], unit='Bar')
+ sf = s.finish()
+ assert_array_equal(sf[0].flows, [0.25, -0.25, 0, 0.5, -0.5])
+ assert sf[0].angles == [1, 3, None, 1, 3]
+ assert_allclose(sf[0].tips,
+ [(-1.375, -0.52011255),
+ (1.375, -0.75506044),
+ (0, 0),
+ (-0.75, -0.41522509),
+ (0.75, -0.8599479)])
+
+
+@check_figures_equal(extensions=['png'])
+def test_sankey3(fig_test, fig_ref):
+ ax_test = fig_test.gca()
+ s_test = Sankey(ax=ax_test, flows=[0.25, -0.25, -0.25, 0.25, 0.5, -0.5],
+ orientations=[1, -1, 1, -1, 0, 0])
+ s_test.finish()
+
+ ax_ref = fig_ref.gca()
+ s_ref = Sankey(ax=ax_ref)
+ s_ref.add(flows=[0.25, -0.25, -0.25, 0.25, 0.5, -0.5],
+ orientations=[1, -1, 1, -1, 0, 0])
+ s_ref.finish()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_scale.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_scale.py
new file mode 100644
index 0000000000000000000000000000000000000000..be46d9593931cb4d65c83fbc7c3a21723cadaa6b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_scale.py
@@ -0,0 +1,295 @@
+import copy
+
+import matplotlib.pyplot as plt
+from matplotlib.scale import (
+ AsinhScale, AsinhTransform,
+ LogTransform, InvertedLogTransform,
+ SymmetricalLogTransform)
+import matplotlib.scale as mscale
+from matplotlib.ticker import AsinhLocator, LogFormatterSciNotation
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+
+import numpy as np
+from numpy.testing import assert_allclose
+import io
+import pytest
+
+
+@check_figures_equal(extensions=['png'])
+def test_log_scales(fig_test, fig_ref):
+ ax_test = fig_test.add_subplot(122, yscale='log', xscale='symlog')
+ ax_test.axvline(24.1)
+ ax_test.axhline(24.1)
+ xlim = ax_test.get_xlim()
+ ylim = ax_test.get_ylim()
+ ax_ref = fig_ref.add_subplot(122, yscale='log', xscale='symlog')
+ ax_ref.set(xlim=xlim, ylim=ylim)
+ ax_ref.plot([24.1, 24.1], ylim, 'b')
+ ax_ref.plot(xlim, [24.1, 24.1], 'b')
+
+
+def test_symlog_mask_nan():
+ # Use a transform round-trip to verify that the forward and inverse
+ # transforms work, and that they respect nans and/or masking.
+ slt = SymmetricalLogTransform(10, 2, 1)
+ slti = slt.inverted()
+
+ x = np.arange(-1.5, 5, 0.5)
+ out = slti.transform_non_affine(slt.transform_non_affine(x))
+ assert_allclose(out, x)
+ assert type(out) is type(x)
+
+ x[4] = np.nan
+ out = slti.transform_non_affine(slt.transform_non_affine(x))
+ assert_allclose(out, x)
+ assert type(out) is type(x)
+
+ x = np.ma.array(x)
+ out = slti.transform_non_affine(slt.transform_non_affine(x))
+ assert_allclose(out, x)
+ assert type(out) is type(x)
+
+ x[3] = np.ma.masked
+ out = slti.transform_non_affine(slt.transform_non_affine(x))
+ assert_allclose(out, x)
+ assert type(out) is type(x)
+
+
+@image_comparison(['logit_scales.png'], remove_text=True)
+def test_logit_scales():
+ fig, ax = plt.subplots()
+
+ # Typical extinction curve for logit
+ x = np.array([0.001, 0.003, 0.01, 0.03, 0.1, 0.2, 0.3, 0.4, 0.5,
+ 0.6, 0.7, 0.8, 0.9, 0.97, 0.99, 0.997, 0.999])
+ y = 1.0 / x
+
+ ax.plot(x, y)
+ ax.set_xscale('logit')
+ ax.grid(True)
+ bbox = ax.get_tightbbox(fig.canvas.get_renderer())
+ assert np.isfinite(bbox.x0)
+ assert np.isfinite(bbox.y0)
+
+
+def test_log_scatter():
+ """Issue #1799"""
+ fig, ax = plt.subplots(1)
+
+ x = np.arange(10)
+ y = np.arange(10) - 1
+
+ ax.scatter(x, y)
+
+ buf = io.BytesIO()
+ fig.savefig(buf, format='pdf')
+
+ buf = io.BytesIO()
+ fig.savefig(buf, format='eps')
+
+ buf = io.BytesIO()
+ fig.savefig(buf, format='svg')
+
+
+def test_logscale_subs():
+ fig, ax = plt.subplots()
+ ax.set_yscale('log', subs=np.array([2, 3, 4]))
+ # force draw
+ fig.canvas.draw()
+
+
+@image_comparison(['logscale_mask.png'], remove_text=True)
+def test_logscale_mask():
+ # Check that zero values are masked correctly on log scales.
+ # See github issue 8045
+ xs = np.linspace(0, 50, 1001)
+
+ fig, ax = plt.subplots()
+ ax.plot(np.exp(-xs**2))
+ fig.canvas.draw()
+ ax.set(yscale="log")
+
+
+def test_extra_kwargs_raise():
+ fig, ax = plt.subplots()
+
+ for scale in ['linear', 'log', 'symlog']:
+ with pytest.raises(TypeError):
+ ax.set_yscale(scale, foo='mask')
+
+
+def test_logscale_invert_transform():
+ fig, ax = plt.subplots()
+ ax.set_yscale('log')
+ # get transformation from data to axes
+ tform = (ax.transAxes + ax.transData.inverted()).inverted()
+
+ # direct test of log transform inversion
+ inverted_transform = LogTransform(base=2).inverted()
+ assert isinstance(inverted_transform, InvertedLogTransform)
+ assert inverted_transform.base == 2
+
+
+def test_logscale_transform_repr():
+ fig, ax = plt.subplots()
+ ax.set_yscale('log')
+ repr(ax.transData)
+ repr(LogTransform(10, nonpositive='clip'))
+
+
+@image_comparison(['logscale_nonpos_values.png'],
+ remove_text=True, tol=0.02, style='mpl20')
+def test_logscale_nonpos_values():
+ np.random.seed(19680801)
+ xs = np.random.normal(size=int(1e3))
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
+ ax1.hist(xs, range=(-5, 5), bins=10)
+ ax1.set_yscale('log')
+ ax2.hist(xs, range=(-5, 5), bins=10)
+ ax2.set_yscale('log', nonpositive='mask')
+
+ xdata = np.arange(0, 10, 0.01)
+ ydata = np.exp(-xdata)
+ edata = 0.2*(10-xdata)*np.cos(5*xdata)*np.exp(-xdata)
+
+ ax3.fill_between(xdata, ydata - edata, ydata + edata)
+ ax3.set_yscale('log')
+
+ x = np.logspace(-1, 1)
+ y = x ** 3
+ yerr = x**2
+ ax4.errorbar(x, y, yerr=yerr)
+
+ ax4.set_yscale('log')
+ ax4.set_xscale('log')
+
+
+def test_invalid_log_lims():
+ # Check that invalid log scale limits are ignored
+ fig, ax = plt.subplots()
+ ax.scatter(range(0, 4), range(0, 4))
+
+ ax.set_xscale('log')
+ original_xlim = ax.get_xlim()
+ with pytest.warns(UserWarning):
+ ax.set_xlim(left=0)
+ assert ax.get_xlim() == original_xlim
+ with pytest.warns(UserWarning):
+ ax.set_xlim(right=-1)
+ assert ax.get_xlim() == original_xlim
+
+ ax.set_yscale('log')
+ original_ylim = ax.get_ylim()
+ with pytest.warns(UserWarning):
+ ax.set_ylim(bottom=0)
+ assert ax.get_ylim() == original_ylim
+ with pytest.warns(UserWarning):
+ ax.set_ylim(top=-1)
+ assert ax.get_ylim() == original_ylim
+
+
+@image_comparison(['function_scales.png'], remove_text=True, style='mpl20')
+def test_function_scale():
+ def inverse(x):
+ return x**2
+
+ def forward(x):
+ return x**(1/2)
+
+ fig, ax = plt.subplots()
+
+ x = np.arange(1, 1000)
+
+ ax.plot(x, x)
+ ax.set_xscale('function', functions=(forward, inverse))
+ ax.set_xlim(1, 1000)
+
+
+def test_pass_scale():
+ # test passing a scale object works...
+ fig, ax = plt.subplots()
+ scale = mscale.LogScale(axis=None)
+ ax.set_xscale(scale)
+ scale = mscale.LogScale(axis=None)
+ ax.set_yscale(scale)
+ assert ax.xaxis.get_scale() == 'log'
+ assert ax.yaxis.get_scale() == 'log'
+
+
+def test_scale_deepcopy():
+ sc = mscale.LogScale(axis='x', base=10)
+ sc2 = copy.deepcopy(sc)
+ assert str(sc.get_transform()) == str(sc2.get_transform())
+ assert sc._transform is not sc2._transform
+
+
+class TestAsinhScale:
+ def test_transforms(self):
+ a0 = 17.0
+ a = np.linspace(-50, 50, 100)
+
+ forward = AsinhTransform(a0)
+ inverse = forward.inverted()
+ invinv = inverse.inverted()
+
+ a_forward = forward.transform_non_affine(a)
+ a_inverted = inverse.transform_non_affine(a_forward)
+ assert_allclose(a_inverted, a)
+
+ a_invinv = invinv.transform_non_affine(a)
+ assert_allclose(a_invinv, a0 * np.arcsinh(a / a0))
+
+ def test_init(self):
+ fig, ax = plt.subplots()
+
+ s = AsinhScale(axis=None, linear_width=23.0)
+ assert s.linear_width == 23
+ assert s._base == 10
+ assert s._subs == (2, 5)
+
+ tx = s.get_transform()
+ assert isinstance(tx, AsinhTransform)
+ assert tx.linear_width == s.linear_width
+
+ def test_base_init(self):
+ fig, ax = plt.subplots()
+
+ s3 = AsinhScale(axis=None, base=3)
+ assert s3._base == 3
+ assert s3._subs == (2,)
+
+ s7 = AsinhScale(axis=None, base=7, subs=(2, 4))
+ assert s7._base == 7
+ assert s7._subs == (2, 4)
+
+ def test_fmtloc(self):
+ class DummyAxis:
+ def __init__(self):
+ self.fields = {}
+ def set(self, **kwargs):
+ self.fields.update(**kwargs)
+ def set_major_formatter(self, f):
+ self.fields['major_formatter'] = f
+
+ ax0 = DummyAxis()
+ s0 = AsinhScale(axis=ax0, base=0)
+ s0.set_default_locators_and_formatters(ax0)
+ assert isinstance(ax0.fields['major_locator'], AsinhLocator)
+ assert isinstance(ax0.fields['major_formatter'], str)
+
+ ax5 = DummyAxis()
+ s7 = AsinhScale(axis=ax5, base=5)
+ s7.set_default_locators_and_formatters(ax5)
+ assert isinstance(ax5.fields['major_locator'], AsinhLocator)
+ assert isinstance(ax5.fields['major_formatter'],
+ LogFormatterSciNotation)
+
+ def test_bad_scale(self):
+ fig, ax = plt.subplots()
+
+ with pytest.raises(ValueError):
+ AsinhScale(axis=None, linear_width=0)
+ with pytest.raises(ValueError):
+ AsinhScale(axis=None, linear_width=-1)
+ s0 = AsinhScale(axis=None, )
+ s1 = AsinhScale(axis=None, linear_width=3.0)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_simplification.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_simplification.py
new file mode 100644
index 0000000000000000000000000000000000000000..58b4d7a9b24fea5ef8c54f51c6f7095ce19e33a3
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_simplification.py
@@ -0,0 +1,571 @@
+import base64
+import io
+import platform
+
+import numpy as np
+from numpy.testing import assert_array_almost_equal, assert_array_equal
+
+import pytest
+
+from matplotlib.testing.decorators import (
+ check_figures_equal, image_comparison, remove_ticks_and_titles)
+import matplotlib.pyplot as plt
+
+from matplotlib import patches, transforms
+from matplotlib.path import Path
+
+
+# NOTE: All of these tests assume that path.simplify is set to True
+# (the default)
+
+@image_comparison(['clipping'], remove_text=True)
+def test_clipping():
+ t = np.arange(0.0, 2.0, 0.01)
+ s = np.sin(2*np.pi*t)
+
+ fig, ax = plt.subplots()
+ ax.plot(t, s, linewidth=1.0)
+ ax.set_ylim((-0.20, -0.28))
+
+
+@image_comparison(['overflow'], remove_text=True,
+ tol=0 if platform.machine() == 'x86_64' else 0.007)
+def test_overflow():
+ x = np.array([1.0, 2.0, 3.0, 2.0e5])
+ y = np.arange(len(x))
+
+ fig, ax = plt.subplots()
+ ax.plot(x, y)
+ ax.set_xlim(2, 6)
+
+
+@image_comparison(['clipping_diamond'], remove_text=True)
+def test_diamond():
+ x = np.array([0.0, 1.0, 0.0, -1.0, 0.0])
+ y = np.array([1.0, 0.0, -1.0, 0.0, 1.0])
+
+ fig, ax = plt.subplots()
+ ax.plot(x, y)
+ ax.set_xlim(-0.6, 0.6)
+ ax.set_ylim(-0.6, 0.6)
+
+
+def test_clipping_out_of_bounds():
+ # Should work on a Path *without* codes.
+ path = Path([(0, 0), (1, 2), (2, 1)])
+ simplified = path.cleaned(clip=(10, 10, 20, 20))
+ assert_array_equal(simplified.vertices, [(0, 0)])
+ assert simplified.codes == [Path.STOP]
+
+ # Should work on a Path *with* codes, and no curves.
+ path = Path([(0, 0), (1, 2), (2, 1)],
+ [Path.MOVETO, Path.LINETO, Path.LINETO])
+ simplified = path.cleaned(clip=(10, 10, 20, 20))
+ assert_array_equal(simplified.vertices, [(0, 0)])
+ assert simplified.codes == [Path.STOP]
+
+ # A Path with curves does not do any clipping yet.
+ path = Path([(0, 0), (1, 2), (2, 3)],
+ [Path.MOVETO, Path.CURVE3, Path.CURVE3])
+ simplified = path.cleaned()
+ simplified_clipped = path.cleaned(clip=(10, 10, 20, 20))
+ assert_array_equal(simplified.vertices, simplified_clipped.vertices)
+ assert_array_equal(simplified.codes, simplified_clipped.codes)
+
+
+def test_noise():
+ np.random.seed(0)
+ x = np.random.uniform(size=50000) * 50
+
+ fig, ax = plt.subplots()
+ p1 = ax.plot(x, solid_joinstyle='round', linewidth=2.0)
+
+ # Ensure that the path's transform takes the new axes limits into account.
+ fig.canvas.draw()
+ path = p1[0].get_path()
+ transform = p1[0].get_transform()
+ path = transform.transform_path(path)
+ simplified = path.cleaned(simplify=True)
+
+ assert simplified.vertices.size == 25512
+
+
+def test_antiparallel_simplification():
+ def _get_simplified(x, y):
+ fig, ax = plt.subplots()
+ p1 = ax.plot(x, y)
+
+ path = p1[0].get_path()
+ transform = p1[0].get_transform()
+ path = transform.transform_path(path)
+ simplified = path.cleaned(simplify=True)
+ simplified = transform.inverted().transform_path(simplified)
+
+ return simplified
+
+ # test ending on a maximum
+ x = [0, 0, 0, 0, 0, 1]
+ y = [.5, 1, -1, 1, 2, .5]
+
+ simplified = _get_simplified(x, y)
+
+ assert_array_almost_equal([[0., 0.5],
+ [0., -1.],
+ [0., 2.],
+ [1., 0.5]],
+ simplified.vertices[:-2, :])
+
+ # test ending on a minimum
+ x = [0, 0, 0, 0, 0, 1]
+ y = [.5, 1, -1, 1, -2, .5]
+
+ simplified = _get_simplified(x, y)
+
+ assert_array_almost_equal([[0., 0.5],
+ [0., 1.],
+ [0., -2.],
+ [1., 0.5]],
+ simplified.vertices[:-2, :])
+
+ # test ending in between
+ x = [0, 0, 0, 0, 0, 1]
+ y = [.5, 1, -1, 1, 0, .5]
+
+ simplified = _get_simplified(x, y)
+
+ assert_array_almost_equal([[0., 0.5],
+ [0., 1.],
+ [0., -1.],
+ [0., 0.],
+ [1., 0.5]],
+ simplified.vertices[:-2, :])
+
+ # test no anti-parallel ending at max
+ x = [0, 0, 0, 0, 0, 1]
+ y = [.5, 1, 2, 1, 3, .5]
+
+ simplified = _get_simplified(x, y)
+
+ assert_array_almost_equal([[0., 0.5],
+ [0., 3.],
+ [1., 0.5]],
+ simplified.vertices[:-2, :])
+
+ # test no anti-parallel ending in middle
+ x = [0, 0, 0, 0, 0, 1]
+ y = [.5, 1, 2, 1, 1, .5]
+
+ simplified = _get_simplified(x, y)
+
+ assert_array_almost_equal([[0., 0.5],
+ [0., 2.],
+ [0., 1.],
+ [1., 0.5]],
+ simplified.vertices[:-2, :])
+
+
+# Only consider angles in 0 <= angle <= pi/2, otherwise
+# using min/max will get the expected results out of order:
+# min/max for simplification code depends on original vector,
+# and if angle is outside above range then simplification
+# min/max will be opposite from actual min/max.
+@pytest.mark.parametrize('angle', [0, np.pi/4, np.pi/3, np.pi/2])
+@pytest.mark.parametrize('offset', [0, .5])
+def test_angled_antiparallel(angle, offset):
+ scale = 5
+ np.random.seed(19680801)
+ # get 15 random offsets
+ # TODO: guarantee offset > 0 results in some offsets < 0
+ vert_offsets = (np.random.rand(15) - offset) * scale
+ # always start at 0 so rotation makes sense
+ vert_offsets[0] = 0
+ # always take the first step the same direction
+ vert_offsets[1] = 1
+ # compute points along a diagonal line
+ x = np.sin(angle) * vert_offsets
+ y = np.cos(angle) * vert_offsets
+
+ # will check these later
+ x_max = x[1:].max()
+ x_min = x[1:].min()
+
+ y_max = y[1:].max()
+ y_min = y[1:].min()
+
+ if offset > 0:
+ p_expected = Path([[0, 0],
+ [x_max, y_max],
+ [x_min, y_min],
+ [x[-1], y[-1]],
+ [0, 0]],
+ codes=[1, 2, 2, 2, 0])
+
+ else:
+ p_expected = Path([[0, 0],
+ [x_max, y_max],
+ [x[-1], y[-1]],
+ [0, 0]],
+ codes=[1, 2, 2, 0])
+
+ p = Path(np.vstack([x, y]).T)
+ p2 = p.cleaned(simplify=True)
+
+ assert_array_almost_equal(p_expected.vertices,
+ p2.vertices)
+ assert_array_equal(p_expected.codes, p2.codes)
+
+
+def test_sine_plus_noise():
+ np.random.seed(0)
+ x = (np.sin(np.linspace(0, np.pi * 2.0, 50000)) +
+ np.random.uniform(size=50000) * 0.01)
+
+ fig, ax = plt.subplots()
+ p1 = ax.plot(x, solid_joinstyle='round', linewidth=2.0)
+
+ # Ensure that the path's transform takes the new axes limits into account.
+ fig.canvas.draw()
+ path = p1[0].get_path()
+ transform = p1[0].get_transform()
+ path = transform.transform_path(path)
+ simplified = path.cleaned(simplify=True)
+
+ assert simplified.vertices.size == 25240
+
+
+@image_comparison(['simplify_curve'], remove_text=True, tol=0.017)
+def test_simplify_curve():
+ pp1 = patches.PathPatch(
+ Path([(0, 0), (1, 0), (1, 1), (np.nan, 1), (0, 0), (2, 0), (2, 2),
+ (0, 0)],
+ [Path.MOVETO, Path.CURVE3, Path.CURVE3, Path.CURVE3, Path.CURVE3,
+ Path.CURVE3, Path.CURVE3, Path.CLOSEPOLY]),
+ fc="none")
+
+ fig, ax = plt.subplots()
+ ax.add_patch(pp1)
+ ax.set_xlim((0, 2))
+ ax.set_ylim((0, 2))
+
+
+@check_figures_equal()
+def test_closed_path_nan_removal(fig_test, fig_ref):
+ ax_test = fig_test.subplots(2, 2).flatten()
+ ax_ref = fig_ref.subplots(2, 2).flatten()
+
+ # NaN on the first point also removes the last point, because it's closed.
+ path = Path(
+ [[-3, np.nan], [3, -3], [3, 3], [-3, 3], [-3, -3]],
+ [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])
+ ax_test[0].add_patch(patches.PathPatch(path, facecolor='none'))
+ path = Path(
+ [[-3, np.nan], [3, -3], [3, 3], [-3, 3], [-3, np.nan]],
+ [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO])
+ ax_ref[0].add_patch(patches.PathPatch(path, facecolor='none'))
+
+ # NaN on second-last point should not re-close.
+ path = Path(
+ [[-2, -2], [2, -2], [2, 2], [-2, np.nan], [-2, -2]],
+ [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])
+ ax_test[0].add_patch(patches.PathPatch(path, facecolor='none'))
+ path = Path(
+ [[-2, -2], [2, -2], [2, 2], [-2, np.nan], [-2, -2]],
+ [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO])
+ ax_ref[0].add_patch(patches.PathPatch(path, facecolor='none'))
+
+ # Test multiple loops in a single path (with same paths as above).
+ path = Path(
+ [[-3, np.nan], [3, -3], [3, 3], [-3, 3], [-3, -3],
+ [-2, -2], [2, -2], [2, 2], [-2, np.nan], [-2, -2]],
+ [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY,
+ Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])
+ ax_test[1].add_patch(patches.PathPatch(path, facecolor='none'))
+ path = Path(
+ [[-3, np.nan], [3, -3], [3, 3], [-3, 3], [-3, np.nan],
+ [-2, -2], [2, -2], [2, 2], [-2, np.nan], [-2, -2]],
+ [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO,
+ Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO])
+ ax_ref[1].add_patch(patches.PathPatch(path, facecolor='none'))
+
+ # NaN in first point of CURVE3 should not re-close, and hide entire curve.
+ path = Path(
+ [[-1, -1], [1, -1], [1, np.nan], [0, 1], [-1, 1], [-1, -1]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE3, Path.CURVE3, Path.LINETO,
+ Path.CLOSEPOLY])
+ ax_test[2].add_patch(patches.PathPatch(path, facecolor='none'))
+ path = Path(
+ [[-1, -1], [1, -1], [1, np.nan], [0, 1], [-1, 1], [-1, -1]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE3, Path.CURVE3, Path.LINETO,
+ Path.CLOSEPOLY])
+ ax_ref[2].add_patch(patches.PathPatch(path, facecolor='none'))
+
+ # NaN in second point of CURVE3 should not re-close, and hide entire curve
+ # plus next line segment.
+ path = Path(
+ [[-3, -3], [3, -3], [3, 0], [0, np.nan], [-3, 3], [-3, -3]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE3, Path.CURVE3, Path.LINETO,
+ Path.LINETO])
+ ax_test[2].add_patch(patches.PathPatch(path, facecolor='none'))
+ path = Path(
+ [[-3, -3], [3, -3], [3, 0], [0, np.nan], [-3, 3], [-3, -3]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE3, Path.CURVE3, Path.LINETO,
+ Path.LINETO])
+ ax_ref[2].add_patch(patches.PathPatch(path, facecolor='none'))
+
+ # NaN in first point of CURVE4 should not re-close, and hide entire curve.
+ path = Path(
+ [[-1, -1], [1, -1], [1, np.nan], [0, 0], [0, 1], [-1, 1], [-1, -1]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.LINETO, Path.CLOSEPOLY])
+ ax_test[3].add_patch(patches.PathPatch(path, facecolor='none'))
+ path = Path(
+ [[-1, -1], [1, -1], [1, np.nan], [0, 0], [0, 1], [-1, 1], [-1, -1]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.LINETO, Path.CLOSEPOLY])
+ ax_ref[3].add_patch(patches.PathPatch(path, facecolor='none'))
+
+ # NaN in second point of CURVE4 should not re-close, and hide entire curve.
+ path = Path(
+ [[-2, -2], [2, -2], [2, 0], [0, np.nan], [0, 2], [-2, 2], [-2, -2]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.LINETO, Path.LINETO])
+ ax_test[3].add_patch(patches.PathPatch(path, facecolor='none'))
+ path = Path(
+ [[-2, -2], [2, -2], [2, 0], [0, np.nan], [0, 2], [-2, 2], [-2, -2]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.LINETO, Path.LINETO])
+ ax_ref[3].add_patch(patches.PathPatch(path, facecolor='none'))
+
+ # NaN in third point of CURVE4 should not re-close, and hide entire curve
+ # plus next line segment.
+ path = Path(
+ [[-3, -3], [3, -3], [3, 0], [0, 0], [0, np.nan], [-3, 3], [-3, -3]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.LINETO, Path.LINETO])
+ ax_test[3].add_patch(patches.PathPatch(path, facecolor='none'))
+ path = Path(
+ [[-3, -3], [3, -3], [3, 0], [0, 0], [0, np.nan], [-3, 3], [-3, -3]],
+ [Path.MOVETO, Path.LINETO, Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.LINETO, Path.LINETO])
+ ax_ref[3].add_patch(patches.PathPatch(path, facecolor='none'))
+
+ # Keep everything clean.
+ for ax in [*ax_test.flat, *ax_ref.flat]:
+ ax.set(xlim=(-3.5, 3.5), ylim=(-3.5, 3.5))
+ remove_ticks_and_titles(fig_test)
+ remove_ticks_and_titles(fig_ref)
+
+
+@check_figures_equal()
+def test_closed_path_clipping(fig_test, fig_ref):
+ vertices = []
+ for roll in range(8):
+ offset = 0.1 * roll + 0.1
+
+ # A U-like pattern.
+ pattern = [
+ [-0.5, 1.5], [-0.5, -0.5], [1.5, -0.5], [1.5, 1.5], # Outer square
+ # With a notch in the top.
+ [1 - offset / 2, 1.5], [1 - offset / 2, offset],
+ [offset / 2, offset], [offset / 2, 1.5],
+ ]
+
+ # Place the initial/final point anywhere in/out of the clipping area.
+ pattern = np.roll(pattern, roll, axis=0)
+ pattern = np.concatenate((pattern, pattern[:1, :]))
+
+ vertices.append(pattern)
+
+ # Multiple subpaths are used here to ensure they aren't broken by closed
+ # loop clipping.
+ codes = np.full(len(vertices[0]), Path.LINETO)
+ codes[0] = Path.MOVETO
+ codes[-1] = Path.CLOSEPOLY
+ codes = np.tile(codes, len(vertices))
+ vertices = np.concatenate(vertices)
+
+ fig_test.set_size_inches((5, 5))
+ path = Path(vertices, codes)
+ fig_test.add_artist(patches.PathPatch(path, facecolor='none'))
+
+ # For reference, we draw the same thing, but unclosed by using a line to
+ # the last point only.
+ fig_ref.set_size_inches((5, 5))
+ codes = codes.copy()
+ codes[codes == Path.CLOSEPOLY] = Path.LINETO
+ path = Path(vertices, codes)
+ fig_ref.add_artist(patches.PathPatch(path, facecolor='none'))
+
+
+@image_comparison(['hatch_simplify'], remove_text=True)
+def test_hatch():
+ fig, ax = plt.subplots()
+ ax.add_patch(plt.Rectangle((0, 0), 1, 1, fill=False, hatch="/"))
+ ax.set_xlim((0.45, 0.55))
+ ax.set_ylim((0.45, 0.55))
+
+
+@image_comparison(['fft_peaks'], remove_text=True)
+def test_fft_peaks():
+ fig, ax = plt.subplots()
+ t = np.arange(65536)
+ p1 = ax.plot(abs(np.fft.fft(np.sin(2*np.pi*.01*t)*np.blackman(len(t)))))
+
+ # Ensure that the path's transform takes the new axes limits into account.
+ fig.canvas.draw()
+ path = p1[0].get_path()
+ transform = p1[0].get_transform()
+ path = transform.transform_path(path)
+ simplified = path.cleaned(simplify=True)
+
+ assert simplified.vertices.size == 36
+
+
+def test_start_with_moveto():
+ # Should be entirely clipped away to a single MOVETO
+ data = b"""
+ZwAAAAku+v9UAQAA+Tj6/z8CAADpQ/r/KAMAANlO+v8QBAAAyVn6//UEAAC6ZPr/2gUAAKpv+v+8
+BgAAm3r6/50HAACLhfr/ewgAAHyQ+v9ZCQAAbZv6/zQKAABepvr/DgsAAE+x+v/lCwAAQLz6/7wM
+AAAxx/r/kA0AACPS+v9jDgAAFN36/zQPAAAF6Pr/AxAAAPfy+v/QEAAA6f36/5wRAADbCPv/ZhIA
+AMwT+/8uEwAAvh77//UTAACwKfv/uRQAAKM0+/98FQAAlT/7/z0WAACHSvv//RYAAHlV+/+7FwAA
+bGD7/3cYAABea/v/MRkAAFF2+//pGQAARIH7/6AaAAA3jPv/VRsAACmX+/8JHAAAHKL7/7ocAAAP
+rfv/ah0AAAO4+/8YHgAA9sL7/8QeAADpzfv/bx8AANzY+/8YIAAA0OP7/78gAADD7vv/ZCEAALf5
++/8IIgAAqwT8/6kiAACeD/z/SiMAAJIa/P/oIwAAhiX8/4QkAAB6MPz/HyUAAG47/P+4JQAAYkb8
+/1AmAABWUfz/5SYAAEpc/P95JwAAPmf8/wsoAAAzcvz/nCgAACd9/P8qKQAAHIj8/7cpAAAQk/z/
+QyoAAAWe/P/MKgAA+aj8/1QrAADus/z/2isAAOO+/P9eLAAA2Mn8/+AsAADM1Pz/YS0AAMHf/P/g
+LQAAtur8/10uAACr9fz/2C4AAKEA/f9SLwAAlgv9/8ovAACLFv3/QDAAAIAh/f+1MAAAdSz9/ycx
+AABrN/3/mDEAAGBC/f8IMgAAVk39/3UyAABLWP3/4TIAAEFj/f9LMwAANm79/7MzAAAsef3/GjQA
+ACKE/f9+NAAAF4/9/+E0AAANmv3/QzUAAAOl/f+iNQAA+a/9/wA2AADvuv3/XDYAAOXF/f+2NgAA
+29D9/w83AADR2/3/ZjcAAMfm/f+7NwAAvfH9/w44AACz/P3/XzgAAKkH/v+vOAAAnxL+//04AACW
+Hf7/SjkAAIwo/v+UOQAAgjP+/905AAB5Pv7/JDoAAG9J/v9pOgAAZVT+/606AABcX/7/7zoAAFJq
+/v8vOwAASXX+/207AAA/gP7/qjsAADaL/v/lOwAALZb+/x48AAAjof7/VTwAABqs/v+LPAAAELf+
+/788AAAHwv7/8TwAAP7M/v8hPQAA9df+/1A9AADr4v7/fT0AAOLt/v+oPQAA2fj+/9E9AADQA///
++T0AAMYO//8fPgAAvRn//0M+AAC0JP//ZT4AAKsv//+GPgAAojr//6U+AACZRf//wj4AAJBQ///d
+PgAAh1v///c+AAB+Zv//Dz8AAHRx//8lPwAAa3z//zk/AABih///TD8AAFmS//9dPwAAUJ3//2w/
+AABHqP//ej8AAD6z//+FPwAANb7//48/AAAsyf//lz8AACPU//+ePwAAGt///6M/AAAR6v//pj8A
+AAj1//+nPwAA/////w=="""
+
+ verts = np.frombuffer(base64.decodebytes(data), dtype=' modification_times[3]
+ assert filecmp.cmp(range_6, plot_file(5))
+
+
+def test_plot_html_show_source_link(tmp_path):
+ parent = Path(__file__).parent
+ shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py')
+ shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static')
+ doctree_dir = tmp_path / 'doctrees'
+ (tmp_path / 'index.rst').write_text("""
+.. plot::
+
+ plt.plot(range(2))
+""")
+ # Make sure source scripts are created by default
+ html_dir1 = tmp_path / '_build' / 'html1'
+ build_sphinx_html(tmp_path, doctree_dir, html_dir1)
+ assert len(list(html_dir1.glob("**/index-1.py"))) == 1
+ # Make sure source scripts are NOT created when
+ # plot_html_show_source_link` is False
+ html_dir2 = tmp_path / '_build' / 'html2'
+ build_sphinx_html(tmp_path, doctree_dir, html_dir2,
+ extra_args=['-D', 'plot_html_show_source_link=0'])
+ assert len(list(html_dir2.glob("**/index-1.py"))) == 0
+
+
+@pytest.mark.parametrize('plot_html_show_source_link', [0, 1])
+def test_show_source_link_true(tmp_path, plot_html_show_source_link):
+ # Test that a source link is generated if :show-source-link: is true,
+ # whether or not plot_html_show_source_link is true.
+ parent = Path(__file__).parent
+ shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py')
+ shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static')
+ doctree_dir = tmp_path / 'doctrees'
+ (tmp_path / 'index.rst').write_text("""
+.. plot::
+ :show-source-link: true
+
+ plt.plot(range(2))
+""")
+ html_dir = tmp_path / '_build' / 'html'
+ build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
+ '-D', f'plot_html_show_source_link={plot_html_show_source_link}'])
+ assert len(list(html_dir.glob("**/index-1.py"))) == 1
+
+
+@pytest.mark.parametrize('plot_html_show_source_link', [0, 1])
+def test_show_source_link_false(tmp_path, plot_html_show_source_link):
+ # Test that a source link is NOT generated if :show-source-link: is false,
+ # whether or not plot_html_show_source_link is true.
+ parent = Path(__file__).parent
+ shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py')
+ shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static')
+ doctree_dir = tmp_path / 'doctrees'
+ (tmp_path / 'index.rst').write_text("""
+.. plot::
+ :show-source-link: false
+
+ plt.plot(range(2))
+""")
+ html_dir = tmp_path / '_build' / 'html'
+ build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
+ '-D', f'plot_html_show_source_link={plot_html_show_source_link}'])
+ assert len(list(html_dir.glob("**/index-1.py"))) == 0
+
+
+def test_srcset_version(tmp_path):
+ shutil.copytree(Path(__file__).parent / 'tinypages', tmp_path,
+ dirs_exist_ok=True)
+ html_dir = tmp_path / '_build' / 'html'
+ img_dir = html_dir / '_images'
+ doctree_dir = tmp_path / 'doctrees'
+
+ build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
+ '-D', 'plot_srcset=2x'])
+
+ def plot_file(num, suff=''):
+ return img_dir / f'some_plots-{num}{suff}.png'
+
+ # check some-plots
+ for ind in [1, 2, 3, 5, 7, 11, 13, 15, 17]:
+ assert plot_file(ind).exists()
+ assert plot_file(ind, suff='.2x').exists()
+
+ assert (img_dir / 'nestedpage-index-1.png').exists()
+ assert (img_dir / 'nestedpage-index-1.2x.png').exists()
+ assert (img_dir / 'nestedpage-index-2.png').exists()
+ assert (img_dir / 'nestedpage-index-2.2x.png').exists()
+ assert (img_dir / 'nestedpage2-index-1.png').exists()
+ assert (img_dir / 'nestedpage2-index-1.2x.png').exists()
+ assert (img_dir / 'nestedpage2-index-2.png').exists()
+ assert (img_dir / 'nestedpage2-index-2.2x.png').exists()
+
+ # Check html for srcset
+
+ assert ('srcset="_images/some_plots-1.png, _images/some_plots-1.2x.png 2.00x"'
+ in (html_dir / 'some_plots.html').read_text(encoding='utf-8'))
+
+ st = ('srcset="../_images/nestedpage-index-1.png, '
+ '../_images/nestedpage-index-1.2x.png 2.00x"')
+ assert st in (html_dir / 'nestedpage/index.html').read_text(encoding='utf-8')
+
+ st = ('srcset="../_images/nestedpage2-index-2.png, '
+ '../_images/nestedpage2-index-2.2x.png 2.00x"')
+ assert st in (html_dir / 'nestedpage2/index.html').read_text(encoding='utf-8')
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_spines.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_spines.py
new file mode 100644
index 0000000000000000000000000000000000000000..99b8461ad8512029a3cb92286e175c9b37eade3b
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_spines.py
@@ -0,0 +1,168 @@
+import numpy as np
+import pytest
+
+import matplotlib.pyplot as plt
+from matplotlib.spines import Spines
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+
+
+def test_spine_class():
+ """Test Spines and SpinesProxy in isolation."""
+ class SpineMock:
+ def __init__(self):
+ self.val = None
+
+ def set(self, **kwargs):
+ vars(self).update(kwargs)
+
+ def set_val(self, val):
+ self.val = val
+
+ spines_dict = {
+ 'left': SpineMock(),
+ 'right': SpineMock(),
+ 'top': SpineMock(),
+ 'bottom': SpineMock(),
+ }
+ spines = Spines(**spines_dict)
+
+ assert spines['left'] is spines_dict['left']
+ assert spines.left is spines_dict['left']
+
+ spines[['left', 'right']].set_val('x')
+ assert spines.left.val == 'x'
+ assert spines.right.val == 'x'
+ assert spines.top.val is None
+ assert spines.bottom.val is None
+
+ spines[:].set_val('y')
+ assert all(spine.val == 'y' for spine in spines.values())
+
+ spines[:].set(foo='bar')
+ assert all(spine.foo == 'bar' for spine in spines.values())
+
+ with pytest.raises(AttributeError, match='foo'):
+ spines.foo
+ with pytest.raises(KeyError, match='foo'):
+ spines['foo']
+ with pytest.raises(KeyError, match='foo, bar'):
+ spines[['left', 'foo', 'right', 'bar']]
+ with pytest.raises(ValueError, match='single list'):
+ spines['left', 'right']
+ with pytest.raises(ValueError, match='Spines does not support slicing'):
+ spines['left':'right']
+ with pytest.raises(ValueError, match='Spines does not support slicing'):
+ spines['top':]
+
+
+@image_comparison(['spines_axes_positions.png'])
+def test_spines_axes_positions():
+ # SF bug 2852168
+ fig = plt.figure()
+ x = np.linspace(0, 2*np.pi, 100)
+ y = 2*np.sin(x)
+ ax = fig.add_subplot(1, 1, 1)
+ ax.set_title('centered spines')
+ ax.plot(x, y)
+ ax.spines.right.set_position(('axes', 0.1))
+ ax.yaxis.set_ticks_position('right')
+ ax.spines.top.set_position(('axes', 0.25))
+ ax.xaxis.set_ticks_position('top')
+ ax.spines.left.set_color('none')
+ ax.spines.bottom.set_color('none')
+
+
+@image_comparison(['spines_data_positions.png'])
+def test_spines_data_positions():
+ fig, ax = plt.subplots()
+ ax.spines.left.set_position(('data', -1.5))
+ ax.spines.top.set_position(('data', 0.5))
+ ax.spines.right.set_position(('data', -0.5))
+ ax.spines.bottom.set_position('zero')
+ ax.set_xlim([-2, 2])
+ ax.set_ylim([-2, 2])
+
+
+@check_figures_equal(extensions=["png"])
+def test_spine_nonlinear_data_positions(fig_test, fig_ref):
+ plt.style.use("default")
+
+ ax = fig_test.add_subplot()
+ ax.set(xscale="log", xlim=(.1, 1))
+ # Use position="data" to visually swap the left and right spines, using
+ # linewidth to distinguish them. The calls to tick_params removes labels
+ # (for image comparison purposes) and harmonizes tick positions with the
+ # reference).
+ ax.spines.left.set_position(("data", 1))
+ ax.spines.left.set_linewidth(2)
+ ax.spines.right.set_position(("data", .1))
+ ax.tick_params(axis="y", labelleft=False, direction="in")
+
+ ax = fig_ref.add_subplot()
+ ax.set(xscale="log", xlim=(.1, 1))
+ ax.spines.right.set_linewidth(2)
+ ax.tick_params(axis="y", labelleft=False, left=False, right=True)
+
+
+@image_comparison(['spines_capstyle.png'])
+def test_spines_capstyle():
+ # issue 2542
+ plt.rc('axes', linewidth=20)
+ fig, ax = plt.subplots()
+ ax.set_xticks([])
+ ax.set_yticks([])
+
+
+def test_label_without_ticks():
+ fig, ax = plt.subplots()
+ plt.subplots_adjust(left=0.3, bottom=0.3)
+ ax.plot(np.arange(10))
+ ax.yaxis.set_ticks_position('left')
+ ax.spines.left.set_position(('outward', 30))
+ ax.spines.right.set_visible(False)
+ ax.set_ylabel('y label')
+ ax.xaxis.set_ticks_position('bottom')
+ ax.spines.bottom.set_position(('outward', 30))
+ ax.spines.top.set_visible(False)
+ ax.set_xlabel('x label')
+ ax.xaxis.set_ticks([])
+ ax.yaxis.set_ticks([])
+ plt.draw()
+
+ spine = ax.spines.left
+ spinebbox = spine.get_transform().transform_path(
+ spine.get_path()).get_extents()
+ assert ax.yaxis.label.get_position()[0] < spinebbox.xmin, \
+ "Y-Axis label not left of the spine"
+
+ spine = ax.spines.bottom
+ spinebbox = spine.get_transform().transform_path(
+ spine.get_path()).get_extents()
+ assert ax.xaxis.label.get_position()[1] < spinebbox.ymin, \
+ "X-Axis label not below the spine"
+
+
+@image_comparison(['black_axes.png'])
+def test_spines_black_axes():
+ # GitHub #18804
+ plt.rcParams["savefig.pad_inches"] = 0
+ plt.rcParams["savefig.bbox"] = 'tight'
+ fig = plt.figure(0, figsize=(4, 4))
+ ax = fig.add_axes((0, 0, 1, 1))
+ ax.set_xticklabels([])
+ ax.set_yticklabels([])
+ ax.set_xticks([])
+ ax.set_yticks([])
+ ax.set_facecolor((0, 0, 0))
+
+
+def test_arc_spine_inner_no_axis():
+ # Backcompat: smoke test that inner arc spine does not need a registered
+ # axis in order to be drawn
+ fig = plt.figure()
+ ax = fig.add_subplot(projection="polar")
+ inner_spine = ax.spines["inner"]
+ inner_spine.register_axis(None)
+ assert ax.spines["inner"].axis is None
+
+ fig.draw_without_rendering()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_streamplot.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_streamplot.py
new file mode 100644
index 0000000000000000000000000000000000000000..92e72c5baeca1d8f6212310d9f0b5b98beb39f82
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_streamplot.py
@@ -0,0 +1,169 @@
+import numpy as np
+from numpy.testing import assert_array_almost_equal
+import pytest
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.transforms as mtransforms
+
+
+def velocity_field():
+ Y, X = np.mgrid[-3:3:100j, -3:3:200j]
+ U = -1 - X**2 + Y
+ V = 1 + X - Y**2
+ return X, Y, U, V
+
+
+def swirl_velocity_field():
+ x = np.linspace(-3., 3., 200)
+ y = np.linspace(-3., 3., 100)
+ X, Y = np.meshgrid(x, y)
+ a = 0.1
+ U = np.cos(a) * (-Y) - np.sin(a) * X
+ V = np.sin(a) * (-Y) + np.cos(a) * X
+ return x, y, U, V
+
+
+@image_comparison(['streamplot_startpoints'], remove_text=True, style='mpl20',
+ extensions=['png'])
+def test_startpoints():
+ X, Y, U, V = velocity_field()
+ start_x, start_y = np.meshgrid(np.linspace(X.min(), X.max(), 5),
+ np.linspace(Y.min(), Y.max(), 5))
+ start_points = np.column_stack([start_x.ravel(), start_y.ravel()])
+ plt.streamplot(X, Y, U, V, start_points=start_points)
+ plt.plot(start_x, start_y, 'ok')
+
+
+@image_comparison(['streamplot_colormap.png'], remove_text=True, style='mpl20',
+ tol=0.022)
+def test_colormap():
+ X, Y, U, V = velocity_field()
+ plt.streamplot(X, Y, U, V, color=U, density=0.6, linewidth=2,
+ cmap=plt.cm.autumn)
+ plt.colorbar()
+
+
+@image_comparison(['streamplot_linewidth'], remove_text=True, style='mpl20',
+ tol=0.004)
+def test_linewidth():
+ X, Y, U, V = velocity_field()
+ speed = np.hypot(U, V)
+ lw = 5 * speed / speed.max()
+ ax = plt.figure().subplots()
+ ax.streamplot(X, Y, U, V, density=[0.5, 1], color='k', linewidth=lw)
+
+
+@image_comparison(['streamplot_masks_and_nans.png'],
+ remove_text=True, style='mpl20')
+def test_masks_and_nans():
+ X, Y, U, V = velocity_field()
+ mask = np.zeros(U.shape, dtype=bool)
+ mask[40:60, 80:120] = 1
+ U[:20, :40] = np.nan
+ U = np.ma.array(U, mask=mask)
+ ax = plt.figure().subplots()
+ with np.errstate(invalid='ignore'):
+ ax.streamplot(X, Y, U, V, color=U, cmap=plt.cm.Blues)
+
+
+@image_comparison(['streamplot_maxlength.png'],
+ remove_text=True, style='mpl20', tol=0.302)
+def test_maxlength():
+ x, y, U, V = swirl_velocity_field()
+ ax = plt.figure().subplots()
+ ax.streamplot(x, y, U, V, maxlength=10., start_points=[[0., 1.5]],
+ linewidth=2, density=2)
+ assert ax.get_xlim()[-1] == ax.get_ylim()[-1] == 3
+ # Compatibility for old test image
+ ax.set(xlim=(None, 3.2555988021882305), ylim=(None, 3.078326760195413))
+
+
+@image_comparison(['streamplot_maxlength_no_broken.png'],
+ remove_text=True, style='mpl20', tol=0.302)
+def test_maxlength_no_broken():
+ x, y, U, V = swirl_velocity_field()
+ ax = plt.figure().subplots()
+ ax.streamplot(x, y, U, V, maxlength=10., start_points=[[0., 1.5]],
+ linewidth=2, density=2, broken_streamlines=False)
+ assert ax.get_xlim()[-1] == ax.get_ylim()[-1] == 3
+ # Compatibility for old test image
+ ax.set(xlim=(None, 3.2555988021882305), ylim=(None, 3.078326760195413))
+
+
+@image_comparison(['streamplot_direction.png'],
+ remove_text=True, style='mpl20', tol=0.073)
+def test_direction():
+ x, y, U, V = swirl_velocity_field()
+ plt.streamplot(x, y, U, V, integration_direction='backward',
+ maxlength=1.5, start_points=[[1.5, 0.]],
+ linewidth=2, density=2)
+
+
+def test_streamplot_limits():
+ ax = plt.axes()
+ x = np.linspace(-5, 10, 20)
+ y = np.linspace(-2, 4, 10)
+ y, x = np.meshgrid(y, x)
+ trans = mtransforms.Affine2D().translate(25, 32) + ax.transData
+ plt.barbs(x, y, np.sin(x), np.cos(y), transform=trans)
+ # The calculated bounds are approximately the bounds of the original data,
+ # this is because the entire path is taken into account when updating the
+ # datalim.
+ assert_array_almost_equal(ax.dataLim.bounds, (20, 30, 15, 6),
+ decimal=1)
+
+
+def test_streamplot_grid():
+ u = np.ones((2, 2))
+ v = np.zeros((2, 2))
+
+ # Test for same rows and columns
+ x = np.array([[10, 20], [10, 30]])
+ y = np.array([[10, 10], [20, 20]])
+
+ with pytest.raises(ValueError, match="The rows of 'x' must be equal"):
+ plt.streamplot(x, y, u, v)
+
+ x = np.array([[10, 20], [10, 20]])
+ y = np.array([[10, 10], [20, 30]])
+
+ with pytest.raises(ValueError, match="The columns of 'y' must be equal"):
+ plt.streamplot(x, y, u, v)
+
+ x = np.array([[10, 20], [10, 20]])
+ y = np.array([[10, 10], [20, 20]])
+ plt.streamplot(x, y, u, v)
+
+ # Test for maximum dimensions
+ x = np.array([0, 10])
+ y = np.array([[[0, 10]]])
+
+ with pytest.raises(ValueError, match="'y' can have at maximum "
+ "2 dimensions"):
+ plt.streamplot(x, y, u, v)
+
+ # Test for equal spacing
+ u = np.ones((3, 3))
+ v = np.zeros((3, 3))
+ x = np.array([0, 10, 20])
+ y = np.array([0, 10, 30])
+
+ with pytest.raises(ValueError, match="'y' values must be equally spaced"):
+ plt.streamplot(x, y, u, v)
+
+ # Test for strictly increasing
+ x = np.array([0, 20, 40])
+ y = np.array([0, 20, 10])
+
+ with pytest.raises(ValueError, match="'y' must be strictly increasing"):
+ plt.streamplot(x, y, u, v)
+
+
+def test_streamplot_inputs(): # test no exception occurs.
+ # fully-masked
+ plt.streamplot(np.arange(3), np.arange(3),
+ np.full((3, 3), np.nan), np.full((3, 3), np.nan),
+ color=np.random.rand(3, 3))
+ # array-likes
+ plt.streamplot(range(3), range(3),
+ np.random.rand(3, 3), np.random.rand(3, 3))
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_style.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_style.py
new file mode 100644
index 0000000000000000000000000000000000000000..be038965e33da3a0646b5a330a7247e51488fb16
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_style.py
@@ -0,0 +1,197 @@
+from contextlib import contextmanager
+from pathlib import Path
+from tempfile import TemporaryDirectory
+import sys
+
+import numpy as np
+import pytest
+
+import matplotlib as mpl
+from matplotlib import pyplot as plt, style
+from matplotlib.style.core import USER_LIBRARY_PATHS, STYLE_EXTENSION
+
+
+PARAM = 'image.cmap'
+VALUE = 'pink'
+DUMMY_SETTINGS = {PARAM: VALUE}
+
+
+@contextmanager
+def temp_style(style_name, settings=None):
+ """Context manager to create a style sheet in a temporary directory."""
+ if not settings:
+ settings = DUMMY_SETTINGS
+ temp_file = f'{style_name}.{STYLE_EXTENSION}'
+ try:
+ with TemporaryDirectory() as tmpdir:
+ # Write style settings to file in the tmpdir.
+ Path(tmpdir, temp_file).write_text(
+ "\n".join(f"{k}: {v}" for k, v in settings.items()),
+ encoding="utf-8")
+ # Add tmpdir to style path and reload so we can access this style.
+ USER_LIBRARY_PATHS.append(tmpdir)
+ style.reload_library()
+ yield
+ finally:
+ style.reload_library()
+
+
+def test_invalid_rc_warning_includes_filename(caplog):
+ SETTINGS = {'foo': 'bar'}
+ basename = 'basename'
+ with temp_style(basename, SETTINGS):
+ # style.reload_library() in temp_style() triggers the warning
+ pass
+ assert (len(caplog.records) == 1
+ and basename in caplog.records[0].getMessage())
+
+
+def test_available():
+ with temp_style('_test_', DUMMY_SETTINGS):
+ assert '_test_' in style.available
+
+
+def test_use():
+ mpl.rcParams[PARAM] = 'gray'
+ with temp_style('test', DUMMY_SETTINGS):
+ with style.context('test'):
+ assert mpl.rcParams[PARAM] == VALUE
+
+
+def test_use_url(tmp_path):
+ path = tmp_path / 'file'
+ path.write_text('axes.facecolor: adeade', encoding='utf-8')
+ with temp_style('test', DUMMY_SETTINGS):
+ url = ('file:'
+ + ('///' if sys.platform == 'win32' else '')
+ + path.resolve().as_posix())
+ with style.context(url):
+ assert mpl.rcParams['axes.facecolor'] == "#adeade"
+
+
+def test_single_path(tmp_path):
+ mpl.rcParams[PARAM] = 'gray'
+ path = tmp_path / f'text.{STYLE_EXTENSION}'
+ path.write_text(f'{PARAM} : {VALUE}', encoding='utf-8')
+ with style.context(path):
+ assert mpl.rcParams[PARAM] == VALUE
+ assert mpl.rcParams[PARAM] == 'gray'
+
+
+def test_context():
+ mpl.rcParams[PARAM] = 'gray'
+ with temp_style('test', DUMMY_SETTINGS):
+ with style.context('test'):
+ assert mpl.rcParams[PARAM] == VALUE
+ # Check that this value is reset after the exiting the context.
+ assert mpl.rcParams[PARAM] == 'gray'
+
+
+def test_context_with_dict():
+ original_value = 'gray'
+ other_value = 'blue'
+ mpl.rcParams[PARAM] = original_value
+ with style.context({PARAM: other_value}):
+ assert mpl.rcParams[PARAM] == other_value
+ assert mpl.rcParams[PARAM] == original_value
+
+
+def test_context_with_dict_after_namedstyle():
+ # Test dict after style name where dict modifies the same parameter.
+ original_value = 'gray'
+ other_value = 'blue'
+ mpl.rcParams[PARAM] = original_value
+ with temp_style('test', DUMMY_SETTINGS):
+ with style.context(['test', {PARAM: other_value}]):
+ assert mpl.rcParams[PARAM] == other_value
+ assert mpl.rcParams[PARAM] == original_value
+
+
+def test_context_with_dict_before_namedstyle():
+ # Test dict before style name where dict modifies the same parameter.
+ original_value = 'gray'
+ other_value = 'blue'
+ mpl.rcParams[PARAM] = original_value
+ with temp_style('test', DUMMY_SETTINGS):
+ with style.context([{PARAM: other_value}, 'test']):
+ assert mpl.rcParams[PARAM] == VALUE
+ assert mpl.rcParams[PARAM] == original_value
+
+
+def test_context_with_union_of_dict_and_namedstyle():
+ # Test dict after style name where dict modifies the a different parameter.
+ original_value = 'gray'
+ other_param = 'text.usetex'
+ other_value = True
+ d = {other_param: other_value}
+ mpl.rcParams[PARAM] = original_value
+ mpl.rcParams[other_param] = (not other_value)
+ with temp_style('test', DUMMY_SETTINGS):
+ with style.context(['test', d]):
+ assert mpl.rcParams[PARAM] == VALUE
+ assert mpl.rcParams[other_param] == other_value
+ assert mpl.rcParams[PARAM] == original_value
+ assert mpl.rcParams[other_param] == (not other_value)
+
+
+def test_context_with_badparam():
+ original_value = 'gray'
+ other_value = 'blue'
+ with style.context({PARAM: other_value}):
+ assert mpl.rcParams[PARAM] == other_value
+ x = style.context({PARAM: original_value, 'badparam': None})
+ with pytest.raises(KeyError):
+ with x:
+ pass
+ assert mpl.rcParams[PARAM] == other_value
+
+
+@pytest.mark.parametrize('equiv_styles',
+ [('mpl20', 'default'),
+ ('mpl15', 'classic')],
+ ids=['mpl20', 'mpl15'])
+def test_alias(equiv_styles):
+ rc_dicts = []
+ for sty in equiv_styles:
+ with style.context(sty):
+ rc_dicts.append(mpl.rcParams.copy())
+
+ rc_base = rc_dicts[0]
+ for nm, rc in zip(equiv_styles[1:], rc_dicts[1:]):
+ assert rc_base == rc
+
+
+def test_xkcd_no_cm():
+ assert mpl.rcParams["path.sketch"] is None
+ plt.xkcd()
+ assert mpl.rcParams["path.sketch"] == (1, 100, 2)
+ np.testing.break_cycles()
+ assert mpl.rcParams["path.sketch"] == (1, 100, 2)
+
+
+def test_xkcd_cm():
+ assert mpl.rcParams["path.sketch"] is None
+ with plt.xkcd():
+ assert mpl.rcParams["path.sketch"] == (1, 100, 2)
+ assert mpl.rcParams["path.sketch"] is None
+
+
+def test_up_to_date_blacklist():
+ assert mpl.style.core.STYLE_BLACKLIST <= {*mpl.rcsetup._validators}
+
+
+def test_style_from_module(tmp_path, monkeypatch):
+ monkeypatch.syspath_prepend(tmp_path)
+ monkeypatch.chdir(tmp_path)
+ pkg_path = tmp_path / "mpl_test_style_pkg"
+ pkg_path.mkdir()
+ (pkg_path / "test_style.mplstyle").write_text(
+ "lines.linewidth: 42", encoding="utf-8")
+ pkg_path.with_suffix(".mplstyle").write_text(
+ "lines.linewidth: 84", encoding="utf-8")
+ mpl.style.use("mpl_test_style_pkg.test_style")
+ assert mpl.rcParams["lines.linewidth"] == 42
+ mpl.style.use("mpl_test_style_pkg.mplstyle")
+ assert mpl.rcParams["lines.linewidth"] == 84
+ mpl.style.use("./mpl_test_style_pkg.mplstyle")
+ assert mpl.rcParams["lines.linewidth"] == 84
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_subplots.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_subplots.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa5debd0b7871f80ac90082389e00be4998d50bd
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_subplots.py
@@ -0,0 +1,293 @@
+import itertools
+import platform
+
+import numpy as np
+import pytest
+
+import matplotlib as mpl
+from matplotlib.axes import Axes, SubplotBase
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+
+
+def check_shared(axs, x_shared, y_shared):
+ """
+ x_shared and y_shared are n x n boolean matrices; entry (i, j) indicates
+ whether the x (or y) axes of subplots i and j should be shared.
+ """
+ for (i1, ax1), (i2, ax2), (i3, (name, shared)) in itertools.product(
+ enumerate(axs),
+ enumerate(axs),
+ enumerate(zip("xy", [x_shared, y_shared]))):
+ if i2 <= i1:
+ continue
+ assert axs[0]._shared_axes[name].joined(ax1, ax2) == shared[i1, i2], \
+ "axes %i and %i incorrectly %ssharing %s axis" % (
+ i1, i2, "not " if shared[i1, i2] else "", name)
+
+
+def check_ticklabel_visible(axs, x_visible, y_visible):
+ """Check that the x and y ticklabel visibility is as specified."""
+ for i, (ax, vx, vy) in enumerate(zip(axs, x_visible, y_visible)):
+ for l in ax.get_xticklabels() + [ax.xaxis.offsetText]:
+ assert l.get_visible() == vx, \
+ f"Visibility of x axis #{i} is incorrectly {vx}"
+ for l in ax.get_yticklabels() + [ax.yaxis.offsetText]:
+ assert l.get_visible() == vy, \
+ f"Visibility of y axis #{i} is incorrectly {vy}"
+ # axis label "visibility" is toggled by label_outer by resetting the
+ # label to empty, but it can also be empty to start with.
+ if not vx:
+ assert ax.get_xlabel() == ""
+ if not vy:
+ assert ax.get_ylabel() == ""
+
+
+def check_tick1_visible(axs, x_visible, y_visible):
+ """
+ Check that the x and y tick visibility is as specified.
+
+ Note: This only checks the tick1line, i.e. bottom / left ticks.
+ """
+ for ax, visible, in zip(axs, x_visible):
+ for tick in ax.xaxis.get_major_ticks():
+ assert tick.tick1line.get_visible() == visible
+ for ax, y_visible, in zip(axs, y_visible):
+ for tick in ax.yaxis.get_major_ticks():
+ assert tick.tick1line.get_visible() == visible
+
+
+def test_shared():
+ rdim = (4, 4, 2)
+ share = {
+ 'all': np.ones(rdim[:2], dtype=bool),
+ 'none': np.zeros(rdim[:2], dtype=bool),
+ 'row': np.array([
+ [False, True, False, False],
+ [True, False, False, False],
+ [False, False, False, True],
+ [False, False, True, False]]),
+ 'col': np.array([
+ [False, False, True, False],
+ [False, False, False, True],
+ [True, False, False, False],
+ [False, True, False, False]]),
+ }
+ visible = {
+ 'x': {
+ 'all': [False, False, True, True],
+ 'col': [False, False, True, True],
+ 'row': [True] * 4,
+ 'none': [True] * 4,
+ False: [True] * 4,
+ True: [False, False, True, True],
+ },
+ 'y': {
+ 'all': [True, False, True, False],
+ 'col': [True] * 4,
+ 'row': [True, False, True, False],
+ 'none': [True] * 4,
+ False: [True] * 4,
+ True: [True, False, True, False],
+ },
+ }
+ share[False] = share['none']
+ share[True] = share['all']
+
+ # test default
+ f, ((a1, a2), (a3, a4)) = plt.subplots(2, 2)
+ axs = [a1, a2, a3, a4]
+ check_shared(axs, share['none'], share['none'])
+ plt.close(f)
+
+ # test all option combinations
+ ops = [False, True, 'all', 'none', 'row', 'col', 0, 1]
+ for xo in ops:
+ for yo in ops:
+ f, ((a1, a2), (a3, a4)) = plt.subplots(2, 2, sharex=xo, sharey=yo)
+ axs = [a1, a2, a3, a4]
+ check_shared(axs, share[xo], share[yo])
+ check_ticklabel_visible(axs, visible['x'][xo], visible['y'][yo])
+ plt.close(f)
+
+
+@pytest.mark.parametrize('remove_ticks', [True, False])
+@pytest.mark.parametrize('layout_engine', ['none', 'tight', 'constrained'])
+@pytest.mark.parametrize('with_colorbar', [True, False])
+def test_label_outer(remove_ticks, layout_engine, with_colorbar):
+ fig = plt.figure(layout=layout_engine)
+ axs = fig.subplots(2, 2, sharex=True, sharey=True)
+ for ax in axs.flat:
+ ax.set(xlabel="foo", ylabel="bar")
+ if with_colorbar:
+ fig.colorbar(mpl.cm.ScalarMappable(), ax=ax)
+ ax.label_outer(remove_inner_ticks=remove_ticks)
+ check_ticklabel_visible(
+ axs.flat, [False, False, True, True], [True, False, True, False])
+ if remove_ticks:
+ check_tick1_visible(
+ axs.flat, [False, False, True, True], [True, False, True, False])
+ else:
+ check_tick1_visible(
+ axs.flat, [True, True, True, True], [True, True, True, True])
+
+
+def test_label_outer_span():
+ fig = plt.figure()
+ gs = fig.add_gridspec(3, 3)
+ # +---+---+---+
+ # | 1 | |
+ # +---+---+---+
+ # | | | 3 |
+ # + 2 +---+---+
+ # | | 4 | |
+ # +---+---+---+
+ a1 = fig.add_subplot(gs[0, 0:2])
+ a2 = fig.add_subplot(gs[1:3, 0])
+ a3 = fig.add_subplot(gs[1, 2])
+ a4 = fig.add_subplot(gs[2, 1])
+ for ax in fig.axes:
+ ax.label_outer()
+ check_ticklabel_visible(
+ fig.axes, [False, True, False, True], [True, True, False, False])
+
+
+def test_label_outer_non_gridspec():
+ ax = plt.axes((0, 0, 1, 1))
+ ax.label_outer() # Does nothing.
+ check_ticklabel_visible([ax], [True], [True])
+
+
+def test_shared_and_moved():
+ # test if sharey is on, but then tick_left is called that labels don't
+ # re-appear. Seaborn does this just to be sure yaxis is on left...
+ f, (a1, a2) = plt.subplots(1, 2, sharey=True)
+ check_ticklabel_visible([a2], [True], [False])
+ a2.yaxis.tick_left()
+ check_ticklabel_visible([a2], [True], [False])
+
+ f, (a1, a2) = plt.subplots(2, 1, sharex=True)
+ check_ticklabel_visible([a1], [False], [True])
+ a2.xaxis.tick_bottom()
+ check_ticklabel_visible([a1], [False], [True])
+
+
+def test_exceptions():
+ # TODO should this test more options?
+ with pytest.raises(ValueError):
+ plt.subplots(2, 2, sharex='blah')
+ with pytest.raises(ValueError):
+ plt.subplots(2, 2, sharey='blah')
+
+
+@image_comparison(['subplots_offset_text.png'],
+ tol=0 if platform.machine() == 'x86_64' else 0.028)
+def test_subplots_offsettext():
+ x = np.arange(0, 1e10, 1e9)
+ y = np.arange(0, 100, 10)+1e4
+ fig, axs = plt.subplots(2, 2, sharex='col', sharey='all')
+ axs[0, 0].plot(x, x)
+ axs[1, 0].plot(x, x)
+ axs[0, 1].plot(y, x)
+ axs[1, 1].plot(y, x)
+
+
+@pytest.mark.parametrize("top", [True, False])
+@pytest.mark.parametrize("bottom", [True, False])
+@pytest.mark.parametrize("left", [True, False])
+@pytest.mark.parametrize("right", [True, False])
+def test_subplots_hide_ticklabels(top, bottom, left, right):
+ # Ideally, we would also test offset-text visibility (and remove
+ # test_subplots_offsettext), but currently, setting rcParams fails to move
+ # the offset texts as well.
+ with plt.rc_context({"xtick.labeltop": top, "xtick.labelbottom": bottom,
+ "ytick.labelleft": left, "ytick.labelright": right}):
+ axs = plt.figure().subplots(3, 3, sharex=True, sharey=True)
+ for (i, j), ax in np.ndenumerate(axs):
+ xtop = ax.xaxis._major_tick_kw["label2On"]
+ xbottom = ax.xaxis._major_tick_kw["label1On"]
+ yleft = ax.yaxis._major_tick_kw["label1On"]
+ yright = ax.yaxis._major_tick_kw["label2On"]
+ assert xtop == (top and i == 0)
+ assert xbottom == (bottom and i == 2)
+ assert yleft == (left and j == 0)
+ assert yright == (right and j == 2)
+
+
+@pytest.mark.parametrize("xlabel_position", ["bottom", "top"])
+@pytest.mark.parametrize("ylabel_position", ["left", "right"])
+def test_subplots_hide_axislabels(xlabel_position, ylabel_position):
+ axs = plt.figure().subplots(3, 3, sharex=True, sharey=True)
+ for (i, j), ax in np.ndenumerate(axs):
+ ax.set(xlabel="foo", ylabel="bar")
+ ax.xaxis.set_label_position(xlabel_position)
+ ax.yaxis.set_label_position(ylabel_position)
+ ax.label_outer()
+ assert bool(ax.get_xlabel()) == (
+ xlabel_position == "bottom" and i == 2
+ or xlabel_position == "top" and i == 0)
+ assert bool(ax.get_ylabel()) == (
+ ylabel_position == "left" and j == 0
+ or ylabel_position == "right" and j == 2)
+
+
+def test_get_gridspec():
+ # ahem, pretty trivial, but...
+ fig, ax = plt.subplots()
+ assert ax.get_subplotspec().get_gridspec() == ax.get_gridspec()
+
+
+def test_dont_mutate_kwargs():
+ subplot_kw = {'sharex': 'all'}
+ gridspec_kw = {'width_ratios': [1, 2]}
+ fig, ax = plt.subplots(1, 2, subplot_kw=subplot_kw,
+ gridspec_kw=gridspec_kw)
+ assert subplot_kw == {'sharex': 'all'}
+ assert gridspec_kw == {'width_ratios': [1, 2]}
+
+
+@pytest.mark.parametrize("width_ratios", [None, [1, 3, 2]])
+@pytest.mark.parametrize("height_ratios", [None, [1, 2]])
+@check_figures_equal(extensions=['png'])
+def test_width_and_height_ratios(fig_test, fig_ref,
+ height_ratios, width_ratios):
+ fig_test.subplots(2, 3, height_ratios=height_ratios,
+ width_ratios=width_ratios)
+ fig_ref.subplots(2, 3, gridspec_kw={
+ 'height_ratios': height_ratios,
+ 'width_ratios': width_ratios})
+
+
+@pytest.mark.parametrize("width_ratios", [None, [1, 3, 2]])
+@pytest.mark.parametrize("height_ratios", [None, [1, 2]])
+@check_figures_equal(extensions=['png'])
+def test_width_and_height_ratios_mosaic(fig_test, fig_ref,
+ height_ratios, width_ratios):
+ mosaic_spec = [['A', 'B', 'B'], ['A', 'C', 'D']]
+ fig_test.subplot_mosaic(mosaic_spec, height_ratios=height_ratios,
+ width_ratios=width_ratios)
+ fig_ref.subplot_mosaic(mosaic_spec, gridspec_kw={
+ 'height_ratios': height_ratios,
+ 'width_ratios': width_ratios})
+
+
+@pytest.mark.parametrize('method,args', [
+ ('subplots', (2, 3)),
+ ('subplot_mosaic', ('abc;def', ))
+ ]
+)
+def test_ratio_overlapping_kws(method, args):
+ with pytest.raises(ValueError, match='height_ratios'):
+ getattr(plt, method)(*args, height_ratios=[1, 2],
+ gridspec_kw={'height_ratios': [1, 2]})
+ with pytest.raises(ValueError, match='width_ratios'):
+ getattr(plt, method)(*args, width_ratios=[1, 2, 3],
+ gridspec_kw={'width_ratios': [1, 2, 3]})
+
+
+def test_old_subplot_compat():
+ fig = plt.figure()
+ assert isinstance(fig.add_subplot(), SubplotBase)
+ assert not isinstance(fig.add_axes(rect=[0, 0, 1, 1]), SubplotBase)
+ with pytest.raises(TypeError):
+ Axes(fig, [0, 0, 1, 1], rect=[0, 0, 1, 1])
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_table.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..29d1944323fc3e6141b0cc2bac229ae4a82bec35
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_table.py
@@ -0,0 +1,283 @@
+import datetime
+from unittest.mock import Mock
+
+import numpy as np
+
+import matplotlib.pyplot as plt
+from matplotlib.path import Path
+from matplotlib.table import CustomCell, Table
+from matplotlib.testing.decorators import image_comparison, check_figures_equal
+from matplotlib.transforms import Bbox
+import matplotlib.units as munits
+
+
+def test_non_square():
+ # Check that creating a non-square table works
+ cellcolors = ['b', 'r']
+ plt.table(cellColours=cellcolors)
+
+
+@image_comparison(['table_zorder.png'], remove_text=True)
+def test_zorder():
+ data = [[66386, 174296],
+ [58230, 381139]]
+
+ colLabels = ('Freeze', 'Wind')
+ rowLabels = ['%d year' % x for x in (100, 50)]
+
+ cellText = []
+ yoff = np.zeros(len(colLabels))
+ for row in reversed(data):
+ yoff += row
+ cellText.append(['%1.1f' % (x/1000.0) for x in yoff])
+
+ t = np.linspace(0, 2*np.pi, 100)
+ plt.plot(t, np.cos(t), lw=4, zorder=2)
+
+ plt.table(cellText=cellText,
+ rowLabels=rowLabels,
+ colLabels=colLabels,
+ loc='center',
+ zorder=-2,
+ )
+
+ plt.table(cellText=cellText,
+ rowLabels=rowLabels,
+ colLabels=colLabels,
+ loc='upper center',
+ zorder=4,
+ )
+ plt.yticks([])
+
+
+@image_comparison(['table_labels.png'])
+def test_label_colours():
+ dim = 3
+
+ c = np.linspace(0, 1, dim)
+ colours = plt.cm.RdYlGn(c)
+ cellText = [['1'] * dim] * dim
+
+ fig = plt.figure()
+
+ ax1 = fig.add_subplot(4, 1, 1)
+ ax1.axis('off')
+ ax1.table(cellText=cellText,
+ rowColours=colours,
+ loc='best')
+
+ ax2 = fig.add_subplot(4, 1, 2)
+ ax2.axis('off')
+ ax2.table(cellText=cellText,
+ rowColours=colours,
+ rowLabels=['Header'] * dim,
+ loc='best')
+
+ ax3 = fig.add_subplot(4, 1, 3)
+ ax3.axis('off')
+ ax3.table(cellText=cellText,
+ colColours=colours,
+ loc='best')
+
+ ax4 = fig.add_subplot(4, 1, 4)
+ ax4.axis('off')
+ ax4.table(cellText=cellText,
+ colColours=colours,
+ colLabels=['Header'] * dim,
+ loc='best')
+
+
+@image_comparison(['table_cell_manipulation.png'], style='mpl20')
+def test_diff_cell_table(text_placeholders):
+ cells = ('horizontal', 'vertical', 'open', 'closed', 'T', 'R', 'B', 'L')
+ cellText = [['1'] * len(cells)] * 2
+ colWidths = [0.1] * len(cells)
+
+ _, axs = plt.subplots(nrows=len(cells), figsize=(4, len(cells)+1), layout='tight')
+ for ax, cell in zip(axs, cells):
+ ax.table(
+ colWidths=colWidths,
+ cellText=cellText,
+ loc='center',
+ edges=cell,
+ )
+ ax.axis('off')
+
+
+def test_customcell():
+ types = ('horizontal', 'vertical', 'open', 'closed', 'T', 'R', 'B', 'L')
+ codes = (
+ (Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO, Path.MOVETO),
+ (Path.MOVETO, Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO),
+ (Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.MOVETO),
+ (Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY),
+ (Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.LINETO, Path.MOVETO),
+ (Path.MOVETO, Path.MOVETO, Path.LINETO, Path.MOVETO, Path.MOVETO),
+ (Path.MOVETO, Path.LINETO, Path.MOVETO, Path.MOVETO, Path.MOVETO),
+ (Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.LINETO),
+ )
+
+ for t, c in zip(types, codes):
+ cell = CustomCell((0, 0), visible_edges=t, width=1, height=1)
+ code = tuple(s for _, s in cell.get_path().iter_segments())
+ assert c == code
+
+
+@image_comparison(['table_auto_column.png'])
+def test_auto_column():
+ fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1)
+
+ # iterable list input
+ ax1.axis('off')
+ tb1 = ax1.table(
+ cellText=[['Fit Text', 2],
+ ['very long long text, Longer text than default', 1]],
+ rowLabels=["A", "B"],
+ colLabels=["Col1", "Col2"],
+ loc="center")
+ tb1.auto_set_font_size(False)
+ tb1.set_fontsize(12)
+ tb1.auto_set_column_width([-1, 0, 1])
+
+ # iterable tuple input
+ ax2.axis('off')
+ tb2 = ax2.table(
+ cellText=[['Fit Text', 2],
+ ['very long long text, Longer text than default', 1]],
+ rowLabels=["A", "B"],
+ colLabels=["Col1", "Col2"],
+ loc="center")
+ tb2.auto_set_font_size(False)
+ tb2.set_fontsize(12)
+ tb2.auto_set_column_width((-1, 0, 1))
+
+ # 3 single inputs
+ ax3.axis('off')
+ tb3 = ax3.table(
+ cellText=[['Fit Text', 2],
+ ['very long long text, Longer text than default', 1]],
+ rowLabels=["A", "B"],
+ colLabels=["Col1", "Col2"],
+ loc="center")
+ tb3.auto_set_font_size(False)
+ tb3.set_fontsize(12)
+ tb3.auto_set_column_width(-1)
+ tb3.auto_set_column_width(0)
+ tb3.auto_set_column_width(1)
+
+ # 4 this used to test non-integer iterable input, which did nothing, but only
+ # remains to avoid re-generating the test image.
+ ax4.axis('off')
+ tb4 = ax4.table(
+ cellText=[['Fit Text', 2],
+ ['very long long text, Longer text than default', 1]],
+ rowLabels=["A", "B"],
+ colLabels=["Col1", "Col2"],
+ loc="center")
+ tb4.auto_set_font_size(False)
+ tb4.set_fontsize(12)
+
+
+def test_table_cells():
+ fig, ax = plt.subplots()
+ table = Table(ax)
+
+ cell = table.add_cell(1, 2, 1, 1)
+ assert isinstance(cell, CustomCell)
+ assert cell is table[1, 2]
+
+ cell2 = CustomCell((0, 0), 1, 2, visible_edges=None)
+ table[2, 1] = cell2
+ assert table[2, 1] is cell2
+
+ # make sure getitem support has not broken
+ # properties and setp
+ table.properties()
+ plt.setp(table)
+
+
+@check_figures_equal(extensions=["png"])
+def test_table_bbox(fig_test, fig_ref):
+ data = [[2, 3],
+ [4, 5]]
+
+ col_labels = ('Foo', 'Bar')
+ row_labels = ('Ada', 'Bob')
+
+ cell_text = [[f"{x}" for x in row] for row in data]
+
+ ax_list = fig_test.subplots()
+ ax_list.table(cellText=cell_text,
+ rowLabels=row_labels,
+ colLabels=col_labels,
+ loc='center',
+ bbox=[0.1, 0.2, 0.8, 0.6]
+ )
+
+ ax_bbox = fig_ref.subplots()
+ ax_bbox.table(cellText=cell_text,
+ rowLabels=row_labels,
+ colLabels=col_labels,
+ loc='center',
+ bbox=Bbox.from_extents(0.1, 0.2, 0.9, 0.8)
+ )
+
+
+@check_figures_equal(extensions=['png'])
+def test_table_unit(fig_test, fig_ref):
+ # test that table doesn't participate in unit machinery, instead uses repr/str
+
+ class FakeUnit:
+ def __init__(self, thing):
+ pass
+ def __repr__(self):
+ return "Hello"
+
+ fake_convertor = munits.ConversionInterface()
+ # v, u, a = value, unit, axis
+ fake_convertor.convert = Mock(side_effect=lambda v, u, a: 0)
+ # not used, here for completeness
+ fake_convertor.default_units = Mock(side_effect=lambda v, a: None)
+ fake_convertor.axisinfo = Mock(side_effect=lambda u, a: munits.AxisInfo())
+
+ munits.registry[FakeUnit] = fake_convertor
+
+ data = [[FakeUnit("yellow"), FakeUnit(42)],
+ [FakeUnit(datetime.datetime(1968, 8, 1)), FakeUnit(True)]]
+
+ fig_test.subplots().table(data)
+ fig_ref.subplots().table([["Hello", "Hello"], ["Hello", "Hello"]])
+ fig_test.canvas.draw()
+ fake_convertor.convert.assert_not_called()
+
+ munits.registry.pop(FakeUnit)
+ assert not munits.registry.get_converter(FakeUnit)
+
+
+def test_table_dataframe(pd):
+ # Test if Pandas Data Frame can be passed in cellText
+
+ data = {
+ 'Letter': ['A', 'B', 'C'],
+ 'Number': [100, 200, 300]
+ }
+
+ df = pd.DataFrame(data)
+ fig, ax = plt.subplots()
+ table = ax.table(df, loc='center')
+
+ for r, (index, row) in enumerate(df.iterrows()):
+ for c, col in enumerate(df.columns if r == 0 else row.values):
+ assert table[r if r == 0 else r+1, c].get_text().get_text() == str(col)
+
+
+def test_table_fontsize():
+ # Test that the passed fontsize propagates to cells
+ tableData = [['a', 1], ['b', 2]]
+ fig, ax = plt.subplots()
+ test_fontsize = 20
+ t = ax.table(cellText=tableData, loc='top', fontsize=test_fontsize)
+ cell_fontsize = t[(0, 0)].get_fontsize()
+ assert cell_fontsize == test_fontsize, f"Actual:{test_fontsize},got:{cell_fontsize}"
+ cell_fontsize = t[(1, 1)].get_fontsize()
+ assert cell_fontsize == test_fontsize, f"Actual:{test_fontsize},got:{cell_fontsize}"
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_testing.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_testing.py
new file mode 100644
index 0000000000000000000000000000000000000000..f13839d6b3b68a6ce7e2f7d21e7ba68088e05f95
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_testing.py
@@ -0,0 +1,41 @@
+import warnings
+
+import pytest
+
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import check_figures_equal
+
+
+@pytest.mark.xfail(
+ strict=True, reason="testing that warnings fail tests"
+)
+def test_warn_to_fail():
+ warnings.warn("This should fail the test")
+
+
+@pytest.mark.parametrize("a", [1])
+@check_figures_equal(extensions=["png"])
+@pytest.mark.parametrize("b", [1])
+def test_parametrize_with_check_figure_equal(a, fig_ref, b, fig_test):
+ assert a == b
+
+
+def test_wrap_failure():
+ with pytest.raises(ValueError, match="^The decorated function"):
+ @check_figures_equal()
+ def should_fail(test, ref):
+ pass
+
+
+@pytest.mark.xfail(raises=RuntimeError, strict=True,
+ reason='Test for check_figures_equal test creating '
+ 'new figures')
+@check_figures_equal()
+def test_check_figures_equal_extra_fig(fig_test, fig_ref):
+ plt.figure()
+
+
+@check_figures_equal()
+def test_check_figures_equal_closed_fig(fig_test, fig_ref):
+ fig = plt.figure()
+ plt.close(fig)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_texmanager.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_texmanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..64dcbf46456de679c6c29f104a7e86e6afcd9ac2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_texmanager.py
@@ -0,0 +1,75 @@
+import os
+from pathlib import Path
+import re
+import sys
+
+import pytest
+
+import matplotlib.pyplot as plt
+from matplotlib.testing import subprocess_run_for_testing
+from matplotlib.testing._markers import needs_usetex
+from matplotlib.texmanager import TexManager
+
+
+def test_fontconfig_preamble():
+ """Test that the preamble is included in the source."""
+ plt.rcParams['text.usetex'] = True
+
+ src1 = TexManager()._get_tex_source("", fontsize=12)
+ plt.rcParams['text.latex.preamble'] = '\\usepackage{txfonts}'
+ src2 = TexManager()._get_tex_source("", fontsize=12)
+
+ assert src1 != src2
+
+
+@pytest.mark.parametrize(
+ "rc, preamble, family", [
+ ({"font.family": "sans-serif", "font.sans-serif": "helvetica"},
+ r"\usepackage{helvet}", r"\sffamily"),
+ ({"font.family": "serif", "font.serif": "palatino"},
+ r"\usepackage{mathpazo}", r"\rmfamily"),
+ ({"font.family": "cursive", "font.cursive": "zapf chancery"},
+ r"\usepackage{chancery}", r"\rmfamily"),
+ ({"font.family": "monospace", "font.monospace": "courier"},
+ r"\usepackage{courier}", r"\ttfamily"),
+ ({"font.family": "helvetica"}, r"\usepackage{helvet}", r"\sffamily"),
+ ({"font.family": "palatino"}, r"\usepackage{mathpazo}", r"\rmfamily"),
+ ({"font.family": "zapf chancery"},
+ r"\usepackage{chancery}", r"\rmfamily"),
+ ({"font.family": "courier"}, r"\usepackage{courier}", r"\ttfamily")
+ ])
+def test_font_selection(rc, preamble, family):
+ plt.rcParams.update(rc)
+ tm = TexManager()
+ src = Path(tm.make_tex("hello, world", fontsize=12)).read_text()
+ assert preamble in src
+ assert [*re.findall(r"\\\w+family", src)] == [family]
+
+
+@needs_usetex
+def test_unicode_characters():
+ # Smoke test to see that Unicode characters does not cause issues
+ # See #23019
+ plt.rcParams['text.usetex'] = True
+ fig, ax = plt.subplots()
+ ax.set_ylabel('\\textit{Velocity (\N{DEGREE SIGN}/sec)}')
+ ax.set_xlabel('\N{VULGAR FRACTION ONE QUARTER}Öøæ')
+ fig.canvas.draw()
+
+ # But not all characters.
+ # Should raise RuntimeError, not UnicodeDecodeError
+ with pytest.raises(RuntimeError):
+ ax.set_title('\N{SNOWMAN}')
+ fig.canvas.draw()
+
+
+@needs_usetex
+def test_openin_any_paranoid():
+ completed = subprocess_run_for_testing(
+ [sys.executable, "-c",
+ 'import matplotlib.pyplot as plt;'
+ 'plt.rcParams.update({"text.usetex": True});'
+ 'plt.title("paranoid");'
+ 'plt.show(block=False);'],
+ env={**os.environ, 'openin_any': 'p'}, check=True, capture_output=True)
+ assert completed.stderr == ""
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_text.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_text.py
new file mode 100644
index 0000000000000000000000000000000000000000..19262202e5c160c32fe1ebda3d4feabb54233616
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_text.py
@@ -0,0 +1,1137 @@
+from datetime import datetime
+import io
+import warnings
+
+import numpy as np
+from numpy.testing import assert_almost_equal
+from packaging.version import parse as parse_version
+import pyparsing
+import pytest
+
+import matplotlib as mpl
+from matplotlib.backend_bases import MouseEvent
+from matplotlib.backends.backend_agg import RendererAgg
+from matplotlib.figure import Figure
+from matplotlib.font_manager import FontProperties
+import matplotlib.patches as mpatches
+import matplotlib.pyplot as plt
+from matplotlib.gridspec import GridSpec
+import matplotlib.transforms as mtransforms
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+from matplotlib.testing._markers import needs_usetex
+from matplotlib.text import Text, Annotation, OffsetFrom
+
+pyparsing_version = parse_version(pyparsing.__version__)
+
+
+@image_comparison(['font_styles'])
+def test_font_styles():
+
+ def find_matplotlib_font(**kw):
+ prop = FontProperties(**kw)
+ path = findfont(prop, directory=mpl.get_data_path())
+ return FontProperties(fname=path)
+
+ from matplotlib.font_manager import FontProperties, findfont
+ warnings.filterwarnings(
+ 'ignore',
+ r"findfont: Font family \[u?'Foo'\] not found. Falling back to .",
+ UserWarning,
+ module='matplotlib.font_manager')
+
+ fig, ax = plt.subplots()
+
+ normal_font = find_matplotlib_font(
+ family="sans-serif",
+ style="normal",
+ variant="normal",
+ size=14)
+ a = ax.annotate(
+ "Normal Font",
+ (0.1, 0.1),
+ xycoords='axes fraction',
+ fontproperties=normal_font)
+ assert a.get_fontname() == 'DejaVu Sans'
+ assert a.get_fontstyle() == 'normal'
+ assert a.get_fontvariant() == 'normal'
+ assert a.get_weight() == 'normal'
+ assert a.get_stretch() == 'normal'
+
+ bold_font = find_matplotlib_font(
+ family="Foo",
+ style="normal",
+ variant="normal",
+ weight="bold",
+ stretch=500,
+ size=14)
+ ax.annotate(
+ "Bold Font",
+ (0.1, 0.2),
+ xycoords='axes fraction',
+ fontproperties=bold_font)
+
+ bold_italic_font = find_matplotlib_font(
+ family="sans serif",
+ style="italic",
+ variant="normal",
+ weight=750,
+ stretch=500,
+ size=14)
+ ax.annotate(
+ "Bold Italic Font",
+ (0.1, 0.3),
+ xycoords='axes fraction',
+ fontproperties=bold_italic_font)
+
+ light_font = find_matplotlib_font(
+ family="sans-serif",
+ style="normal",
+ variant="normal",
+ weight=200,
+ stretch=500,
+ size=14)
+ ax.annotate(
+ "Light Font",
+ (0.1, 0.4),
+ xycoords='axes fraction',
+ fontproperties=light_font)
+
+ condensed_font = find_matplotlib_font(
+ family="sans-serif",
+ style="normal",
+ variant="normal",
+ weight=500,
+ stretch=100,
+ size=14)
+ ax.annotate(
+ "Condensed Font",
+ (0.1, 0.5),
+ xycoords='axes fraction',
+ fontproperties=condensed_font)
+
+ ax.set_xticks([])
+ ax.set_yticks([])
+
+
+@image_comparison(['multiline'])
+def test_multiline():
+ plt.figure()
+ ax = plt.subplot(1, 1, 1)
+ ax.set_title("multiline\ntext alignment")
+
+ plt.text(
+ 0.2, 0.5, "TpTpTp\n$M$\nTpTpTp", size=20, ha="center", va="top")
+
+ plt.text(
+ 0.5, 0.5, "TpTpTp\n$M^{M^{M^{M}}}$\nTpTpTp", size=20,
+ ha="center", va="top")
+
+ plt.text(
+ 0.8, 0.5, "TpTpTp\n$M_{q_{q_{q}}}$\nTpTpTp", size=20,
+ ha="center", va="top")
+
+ plt.xlim(0, 1)
+ plt.ylim(0, 0.8)
+
+ ax.set_xticks([])
+ ax.set_yticks([])
+
+
+@image_comparison(['multiline2'], style='mpl20')
+def test_multiline2():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['text.kerning_factor'] = 6
+
+ fig, ax = plt.subplots()
+
+ ax.set_xlim([0, 1.4])
+ ax.set_ylim([0, 2])
+ ax.axhline(0.5, color='C2', linewidth=0.3)
+ sts = ['Line', '2 Lineg\n 2 Lg', '$\\sum_i x $', 'hi $\\sum_i x $\ntest',
+ 'test\n $\\sum_i x $', '$\\sum_i x $\n $\\sum_i x $']
+ renderer = fig.canvas.get_renderer()
+
+ def draw_box(ax, tt):
+ r = mpatches.Rectangle((0, 0), 1, 1, clip_on=False,
+ transform=ax.transAxes)
+ r.set_bounds(
+ tt.get_window_extent(renderer)
+ .transformed(ax.transAxes.inverted())
+ .bounds)
+ ax.add_patch(r)
+
+ horal = 'left'
+ for nn, st in enumerate(sts):
+ tt = ax.text(0.2 * nn + 0.1, 0.5, st, horizontalalignment=horal,
+ verticalalignment='bottom')
+ draw_box(ax, tt)
+ ax.text(1.2, 0.5, 'Bottom align', color='C2')
+
+ ax.axhline(1.3, color='C2', linewidth=0.3)
+ for nn, st in enumerate(sts):
+ tt = ax.text(0.2 * nn + 0.1, 1.3, st, horizontalalignment=horal,
+ verticalalignment='top')
+ draw_box(ax, tt)
+ ax.text(1.2, 1.3, 'Top align', color='C2')
+
+ ax.axhline(1.8, color='C2', linewidth=0.3)
+ for nn, st in enumerate(sts):
+ tt = ax.text(0.2 * nn + 0.1, 1.8, st, horizontalalignment=horal,
+ verticalalignment='baseline')
+ draw_box(ax, tt)
+ ax.text(1.2, 1.8, 'Baseline align', color='C2')
+
+ ax.axhline(0.1, color='C2', linewidth=0.3)
+ for nn, st in enumerate(sts):
+ tt = ax.text(0.2 * nn + 0.1, 0.1, st, horizontalalignment=horal,
+ verticalalignment='bottom', rotation=20)
+ draw_box(ax, tt)
+ ax.text(1.2, 0.1, 'Bot align, rot20', color='C2')
+
+
+@image_comparison(['antialiased.png'], style='mpl20')
+def test_antialiasing():
+ mpl.rcParams['text.antialiased'] = False # Passed arguments should override.
+
+ fig = plt.figure(figsize=(5.25, 0.75))
+ fig.text(0.3, 0.75, "antialiased", horizontalalignment='center',
+ verticalalignment='center', antialiased=True)
+ fig.text(0.3, 0.25, r"$\sqrt{x}$", horizontalalignment='center',
+ verticalalignment='center', antialiased=True)
+
+ mpl.rcParams['text.antialiased'] = True # Passed arguments should override.
+ fig.text(0.7, 0.75, "not antialiased", horizontalalignment='center',
+ verticalalignment='center', antialiased=False)
+ fig.text(0.7, 0.25, r"$\sqrt{x}$", horizontalalignment='center',
+ verticalalignment='center', antialiased=False)
+
+ mpl.rcParams['text.antialiased'] = False # Should not affect existing text.
+
+
+def test_afm_kerning():
+ fn = mpl.font_manager.findfont("Helvetica", fontext="afm")
+ with open(fn, 'rb') as fh:
+ afm = mpl._afm.AFM(fh)
+ assert afm.string_width_height('VAVAVAVAVAVA') == (7174.0, 718)
+
+
+@image_comparison(['text_contains.png'])
+def test_contains():
+ fig = plt.figure()
+ ax = plt.axes()
+
+ mevent = MouseEvent('button_press_event', fig.canvas, 0.5, 0.5, 1, None)
+
+ xs = np.linspace(0.25, 0.75, 30)
+ ys = np.linspace(0.25, 0.75, 30)
+ xs, ys = np.meshgrid(xs, ys)
+
+ txt = plt.text(
+ 0.5, 0.4, 'hello world', ha='center', fontsize=30, rotation=30)
+ # uncomment to draw the text's bounding box
+ # txt.set_bbox(dict(edgecolor='black', facecolor='none'))
+
+ # draw the text. This is important, as the contains method can only work
+ # when a renderer exists.
+ fig.canvas.draw()
+
+ for x, y in zip(xs.flat, ys.flat):
+ mevent.x, mevent.y = plt.gca().transAxes.transform([x, y])
+ contains, _ = txt.contains(mevent)
+ color = 'yellow' if contains else 'red'
+
+ # capture the viewLim, plot a point, and reset the viewLim
+ vl = ax.viewLim.frozen()
+ ax.plot(x, y, 'o', color=color)
+ ax.viewLim.set(vl)
+
+
+def test_annotation_contains():
+ # Check that Annotation.contains looks at the bboxes of the text and the
+ # arrow separately, not at the joint bbox.
+ fig, ax = plt.subplots()
+ ann = ax.annotate(
+ "hello", xy=(.4, .4), xytext=(.6, .6), arrowprops={"arrowstyle": "->"})
+ fig.canvas.draw() # Needed for the same reason as in test_contains.
+ event = MouseEvent(
+ "button_press_event", fig.canvas, *ax.transData.transform((.5, .6)))
+ assert ann.contains(event) == (False, {})
+
+
+@pytest.mark.parametrize('err, xycoords, match', (
+ (TypeError, print, "xycoords callable must return a BboxBase or Transform, not a"),
+ (TypeError, [0, 0], r"'xycoords' must be an instance of str, tuple"),
+ (ValueError, "foo", "'foo' is not a valid coordinate"),
+ (ValueError, "foo bar", "'foo bar' is not a valid coordinate"),
+ (ValueError, "offset foo", "xycoords cannot be an offset coordinate"),
+ (ValueError, "axes foo", "'foo' is not a recognized unit"),
+))
+def test_annotate_errors(err, xycoords, match):
+ fig, ax = plt.subplots()
+ with pytest.raises(err, match=match):
+ ax.annotate('xy', (0, 0), xytext=(0.5, 0.5), xycoords=xycoords)
+ fig.canvas.draw()
+
+
+@image_comparison(['titles'])
+def test_titles():
+ # left and right side titles
+ plt.figure()
+ ax = plt.subplot(1, 1, 1)
+ ax.set_title("left title", loc="left")
+ ax.set_title("right title", loc="right")
+ ax.set_xticks([])
+ ax.set_yticks([])
+
+
+@image_comparison(['text_alignment'], style='mpl20')
+def test_alignment():
+ plt.figure()
+ ax = plt.subplot(1, 1, 1)
+
+ x = 0.1
+ for rotation in (0, 30):
+ for alignment in ('top', 'bottom', 'baseline', 'center'):
+ ax.text(
+ x, 0.5, alignment + " Tj", va=alignment, rotation=rotation,
+ bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
+ ax.text(
+ x, 1.0, r'$\sum_{i=0}^{j}$', va=alignment, rotation=rotation)
+ x += 0.1
+
+ ax.plot([0, 1], [0.5, 0.5])
+ ax.plot([0, 1], [1.0, 1.0])
+
+ ax.set_xlim(0, 1)
+ ax.set_ylim(0, 1.5)
+ ax.set_xticks([])
+ ax.set_yticks([])
+
+
+@image_comparison(['axes_titles.png'])
+def test_axes_titles():
+ # Related to issue #3327
+ plt.figure()
+ ax = plt.subplot(1, 1, 1)
+ ax.set_title('center', loc='center', fontsize=20, fontweight=700)
+ ax.set_title('left', loc='left', fontsize=12, fontweight=400)
+ ax.set_title('right', loc='right', fontsize=12, fontweight=400)
+
+
+def test_set_position():
+ fig, ax = plt.subplots()
+
+ # test set_position
+ ann = ax.annotate(
+ 'test', (0, 0), xytext=(0, 0), textcoords='figure pixels')
+ fig.canvas.draw()
+
+ init_pos = ann.get_window_extent(fig.canvas.renderer)
+ shift_val = 15
+ ann.set_position((shift_val, shift_val))
+ fig.canvas.draw()
+ post_pos = ann.get_window_extent(fig.canvas.renderer)
+
+ for a, b in zip(init_pos.min, post_pos.min):
+ assert a + shift_val == b
+
+ # test xyann
+ ann = ax.annotate(
+ 'test', (0, 0), xytext=(0, 0), textcoords='figure pixels')
+ fig.canvas.draw()
+
+ init_pos = ann.get_window_extent(fig.canvas.renderer)
+ shift_val = 15
+ ann.xyann = (shift_val, shift_val)
+ fig.canvas.draw()
+ post_pos = ann.get_window_extent(fig.canvas.renderer)
+
+ for a, b in zip(init_pos.min, post_pos.min):
+ assert a + shift_val == b
+
+
+def test_char_index_at():
+ fig = plt.figure()
+ text = fig.text(0.1, 0.9, "")
+
+ text.set_text("i")
+ bbox = text.get_window_extent()
+ size_i = bbox.x1 - bbox.x0
+
+ text.set_text("m")
+ bbox = text.get_window_extent()
+ size_m = bbox.x1 - bbox.x0
+
+ text.set_text("iiiimmmm")
+ bbox = text.get_window_extent()
+ origin = bbox.x0
+
+ assert text._char_index_at(origin - size_i) == 0 # left of first char
+ assert text._char_index_at(origin) == 0
+ assert text._char_index_at(origin + 0.499*size_i) == 0
+ assert text._char_index_at(origin + 0.501*size_i) == 1
+ assert text._char_index_at(origin + size_i*3) == 3
+ assert text._char_index_at(origin + size_i*4 + size_m*3) == 7
+ assert text._char_index_at(origin + size_i*4 + size_m*4) == 8
+ assert text._char_index_at(origin + size_i*4 + size_m*10) == 8
+
+
+@pytest.mark.parametrize('text', ['', 'O'], ids=['empty', 'non-empty'])
+def test_non_default_dpi(text):
+ fig, ax = plt.subplots()
+
+ t1 = ax.text(0.5, 0.5, text, ha='left', va='bottom')
+ fig.canvas.draw()
+ dpi = fig.dpi
+
+ bbox1 = t1.get_window_extent()
+ bbox2 = t1.get_window_extent(dpi=dpi * 10)
+ np.testing.assert_allclose(bbox2.get_points(), bbox1.get_points() * 10,
+ rtol=5e-2)
+ # Text.get_window_extent should not permanently change dpi.
+ assert fig.dpi == dpi
+
+
+def test_get_rotation_string():
+ assert Text(rotation='horizontal').get_rotation() == 0.
+ assert Text(rotation='vertical').get_rotation() == 90.
+
+
+def test_get_rotation_float():
+ for i in [15., 16.70, 77.4]:
+ assert Text(rotation=i).get_rotation() == i
+
+
+def test_get_rotation_int():
+ for i in [67, 16, 41]:
+ assert Text(rotation=i).get_rotation() == float(i)
+
+
+def test_get_rotation_raises():
+ with pytest.raises(ValueError):
+ Text(rotation='hozirontal')
+
+
+def test_get_rotation_none():
+ assert Text(rotation=None).get_rotation() == 0.0
+
+
+def test_get_rotation_mod360():
+ for i, j in zip([360., 377., 720+177.2], [0., 17., 177.2]):
+ assert_almost_equal(Text(rotation=i).get_rotation(), j)
+
+
+@pytest.mark.parametrize("ha", ["center", "right", "left"])
+@pytest.mark.parametrize("va", ["center", "top", "bottom",
+ "baseline", "center_baseline"])
+def test_null_rotation_with_rotation_mode(ha, va):
+ fig, ax = plt.subplots()
+ kw = dict(rotation=0, va=va, ha=ha)
+ t0 = ax.text(.5, .5, 'test', rotation_mode='anchor', **kw)
+ t1 = ax.text(.5, .5, 'test', rotation_mode='default', **kw)
+ fig.canvas.draw()
+ assert_almost_equal(t0.get_window_extent(fig.canvas.renderer).get_points(),
+ t1.get_window_extent(fig.canvas.renderer).get_points())
+
+
+@image_comparison(['text_bboxclip'])
+def test_bbox_clipping():
+ plt.text(0.9, 0.2, 'Is bbox clipped?', backgroundcolor='r', clip_on=True)
+ t = plt.text(0.9, 0.5, 'Is fancy bbox clipped?', clip_on=True)
+ t.set_bbox({"boxstyle": "round, pad=0.1"})
+
+
+@image_comparison(['annotation_negative_ax_coords.png'])
+def test_annotation_negative_ax_coords():
+ fig, ax = plt.subplots()
+
+ ax.annotate('+ pts',
+ xytext=[30, 20], textcoords='axes points',
+ xy=[30, 20], xycoords='axes points', fontsize=32)
+ ax.annotate('- pts',
+ xytext=[30, -20], textcoords='axes points',
+ xy=[30, -20], xycoords='axes points', fontsize=32,
+ va='top')
+ ax.annotate('+ frac',
+ xytext=[0.75, 0.05], textcoords='axes fraction',
+ xy=[0.75, 0.05], xycoords='axes fraction', fontsize=32)
+ ax.annotate('- frac',
+ xytext=[0.75, -0.05], textcoords='axes fraction',
+ xy=[0.75, -0.05], xycoords='axes fraction', fontsize=32,
+ va='top')
+
+ ax.annotate('+ pixels',
+ xytext=[160, 25], textcoords='axes pixels',
+ xy=[160, 25], xycoords='axes pixels', fontsize=32)
+ ax.annotate('- pixels',
+ xytext=[160, -25], textcoords='axes pixels',
+ xy=[160, -25], xycoords='axes pixels', fontsize=32,
+ va='top')
+
+
+@image_comparison(['annotation_negative_fig_coords.png'])
+def test_annotation_negative_fig_coords():
+ fig, ax = plt.subplots()
+
+ ax.annotate('+ pts',
+ xytext=[10, 120], textcoords='figure points',
+ xy=[10, 120], xycoords='figure points', fontsize=32)
+ ax.annotate('- pts',
+ xytext=[-10, 180], textcoords='figure points',
+ xy=[-10, 180], xycoords='figure points', fontsize=32,
+ va='top')
+ ax.annotate('+ frac',
+ xytext=[0.05, 0.55], textcoords='figure fraction',
+ xy=[0.05, 0.55], xycoords='figure fraction', fontsize=32)
+ ax.annotate('- frac',
+ xytext=[-0.05, 0.5], textcoords='figure fraction',
+ xy=[-0.05, 0.5], xycoords='figure fraction', fontsize=32,
+ va='top')
+
+ ax.annotate('+ pixels',
+ xytext=[50, 50], textcoords='figure pixels',
+ xy=[50, 50], xycoords='figure pixels', fontsize=32)
+ ax.annotate('- pixels',
+ xytext=[-50, 100], textcoords='figure pixels',
+ xy=[-50, 100], xycoords='figure pixels', fontsize=32,
+ va='top')
+
+
+def test_text_stale():
+ fig, (ax1, ax2) = plt.subplots(1, 2)
+ plt.draw_all()
+ assert not ax1.stale
+ assert not ax2.stale
+ assert not fig.stale
+
+ txt1 = ax1.text(.5, .5, 'aardvark')
+ assert ax1.stale
+ assert txt1.stale
+ assert fig.stale
+
+ ann1 = ax2.annotate('aardvark', xy=[.5, .5])
+ assert ax2.stale
+ assert ann1.stale
+ assert fig.stale
+
+ plt.draw_all()
+ assert not ax1.stale
+ assert not ax2.stale
+ assert not fig.stale
+
+
+@image_comparison(['agg_text_clip.png'])
+def test_agg_text_clip():
+ np.random.seed(1)
+ fig, (ax1, ax2) = plt.subplots(2)
+ for x, y in np.random.rand(10, 2):
+ ax1.text(x, y, "foo", clip_on=True)
+ ax2.text(x, y, "foo")
+
+
+def test_text_size_binding():
+ mpl.rcParams['font.size'] = 10
+ fp = mpl.font_manager.FontProperties(size='large')
+ sz1 = fp.get_size_in_points()
+ mpl.rcParams['font.size'] = 100
+
+ assert sz1 == fp.get_size_in_points()
+
+
+@image_comparison(['font_scaling.pdf'])
+def test_font_scaling():
+ mpl.rcParams['pdf.fonttype'] = 42
+ fig, ax = plt.subplots(figsize=(6.4, 12.4))
+ ax.xaxis.set_major_locator(plt.NullLocator())
+ ax.yaxis.set_major_locator(plt.NullLocator())
+ ax.set_ylim(-10, 600)
+
+ for i, fs in enumerate(range(4, 43, 2)):
+ ax.text(0.1, i*30, f"{fs} pt font size", fontsize=fs)
+
+
+@pytest.mark.parametrize('spacing1, spacing2', [(0.4, 2), (2, 0.4), (2, 2)])
+def test_two_2line_texts(spacing1, spacing2):
+ text_string = 'line1\nline2'
+ fig = plt.figure()
+ renderer = fig.canvas.get_renderer()
+
+ text1 = fig.text(0.25, 0.5, text_string, linespacing=spacing1)
+ text2 = fig.text(0.25, 0.5, text_string, linespacing=spacing2)
+ fig.canvas.draw()
+
+ box1 = text1.get_window_extent(renderer=renderer)
+ box2 = text2.get_window_extent(renderer=renderer)
+
+ # line spacing only affects height
+ assert box1.width == box2.width
+ if spacing1 == spacing2:
+ assert box1.height == box2.height
+ else:
+ assert box1.height != box2.height
+
+
+def test_validate_linespacing():
+ with pytest.raises(TypeError):
+ plt.text(.25, .5, "foo", linespacing="abc")
+
+
+def test_nonfinite_pos():
+ fig, ax = plt.subplots()
+ ax.text(0, np.nan, 'nan')
+ ax.text(np.inf, 0, 'inf')
+ fig.canvas.draw()
+
+
+def test_hinting_factor_backends():
+ plt.rcParams['text.hinting_factor'] = 1
+ fig = plt.figure()
+ t = fig.text(0.5, 0.5, 'some text')
+
+ fig.savefig(io.BytesIO(), format='svg')
+ expected = t.get_window_extent().intervalx
+
+ fig.savefig(io.BytesIO(), format='png')
+ # Backends should apply hinting_factor consistently (within 10%).
+ np.testing.assert_allclose(t.get_window_extent().intervalx, expected,
+ rtol=0.1)
+
+
+@needs_usetex
+def test_usetex_is_copied():
+ # Indirectly tests that update_from (which is used to copy tick label
+ # properties) copies usetex state.
+ fig = plt.figure()
+ plt.rcParams["text.usetex"] = False
+ ax1 = fig.add_subplot(121)
+ plt.rcParams["text.usetex"] = True
+ ax2 = fig.add_subplot(122)
+ fig.canvas.draw()
+ for ax, usetex in [(ax1, False), (ax2, True)]:
+ for t in ax.xaxis.majorTicks:
+ assert t.label1.get_usetex() == usetex
+
+
+@needs_usetex
+def test_single_artist_usetex():
+ # Check that a single artist marked with usetex does not get passed through
+ # the mathtext parser at all (for the Agg backend) (the mathtext parser
+ # currently fails to parse \frac12, requiring \frac{1}{2} instead).
+ fig = plt.figure()
+ fig.text(.5, .5, r"$\frac12$", usetex=True)
+ fig.canvas.draw()
+
+
+@pytest.mark.parametrize("fmt", ["png", "pdf", "svg"])
+def test_single_artist_usenotex(fmt):
+ # Check that a single artist can be marked as not-usetex even though the
+ # rcParam is on ("2_2_2" fails if passed to TeX). This currently skips
+ # postscript output as the ps renderer doesn't support mixing usetex and
+ # non-usetex.
+ plt.rcParams["text.usetex"] = True
+ fig = plt.figure()
+ fig.text(.5, .5, "2_2_2", usetex=False)
+ fig.savefig(io.BytesIO(), format=fmt)
+
+
+@image_comparison(['text_as_path_opacity.svg'])
+def test_text_as_path_opacity():
+ plt.figure()
+ plt.gca().set_axis_off()
+ plt.text(0.25, 0.25, 'c', color=(0, 0, 0, 0.5))
+ plt.text(0.25, 0.5, 'a', alpha=0.5)
+ plt.text(0.25, 0.75, 'x', alpha=0.5, color=(0, 0, 0, 1))
+
+
+@image_comparison(['text_as_text_opacity.svg'])
+def test_text_as_text_opacity():
+ mpl.rcParams['svg.fonttype'] = 'none'
+ plt.figure()
+ plt.gca().set_axis_off()
+ plt.text(0.25, 0.25, '50% using `color`', color=(0, 0, 0, 0.5))
+ plt.text(0.25, 0.5, '50% using `alpha`', alpha=0.5)
+ plt.text(0.25, 0.75, '50% using `alpha` and 100% `color`', alpha=0.5,
+ color=(0, 0, 0, 1))
+
+
+def test_text_repr():
+ # smoketest to make sure text repr doesn't error for category
+ plt.plot(['A', 'B'], [1, 2])
+ repr(plt.text(['A'], 0.5, 'Boo'))
+
+
+def test_annotation_update():
+ fig, ax = plt.subplots(1, 1)
+ an = ax.annotate('annotation', xy=(0.5, 0.5))
+ extent1 = an.get_window_extent(fig.canvas.get_renderer())
+ fig.tight_layout()
+ extent2 = an.get_window_extent(fig.canvas.get_renderer())
+
+ assert not np.allclose(extent1.get_points(), extent2.get_points(),
+ rtol=1e-6)
+
+
+@check_figures_equal(extensions=["png"])
+def test_annotation_units(fig_test, fig_ref):
+ ax = fig_test.add_subplot()
+ ax.plot(datetime.now(), 1, "o") # Implicitly set axes extents.
+ ax.annotate("x", (datetime.now(), 0.5), xycoords=("data", "axes fraction"),
+ # This used to crash before.
+ xytext=(0, 0), textcoords="offset points")
+ ax = fig_ref.add_subplot()
+ ax.plot(datetime.now(), 1, "o")
+ ax.annotate("x", (datetime.now(), 0.5), xycoords=("data", "axes fraction"))
+
+
+@image_comparison(['large_subscript_title.png'], style='mpl20')
+def test_large_subscript_title():
+ # Remove this line when this test image is regenerated.
+ plt.rcParams['text.kerning_factor'] = 6
+ plt.rcParams['axes.titley'] = None
+
+ fig, axs = plt.subplots(1, 2, figsize=(9, 2.5), constrained_layout=True)
+ ax = axs[0]
+ ax.set_title(r'$\sum_{i} x_i$')
+ ax.set_title('New way', loc='left')
+ ax.set_xticklabels([])
+
+ ax = axs[1]
+ ax.set_title(r'$\sum_{i} x_i$', y=1.01)
+ ax.set_title('Old Way', loc='left')
+ ax.set_xticklabels([])
+
+
+@pytest.mark.parametrize(
+ "x, rotation, halign",
+ [(0.7, 0, 'left'),
+ (0.5, 95, 'left'),
+ (0.3, 0, 'right'),
+ (0.3, 185, 'left')])
+def test_wrap(x, rotation, halign):
+ fig = plt.figure(figsize=(18, 18))
+ gs = GridSpec(nrows=3, ncols=3, figure=fig)
+ subfig = fig.add_subfigure(gs[1, 1])
+ # we only use the central subfigure, which does not align with any
+ # figure boundary, to ensure only subfigure boundaries are relevant
+ s = 'This is a very long text that should be wrapped multiple times.'
+ text = subfig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign)
+ fig.canvas.draw()
+ assert text._get_wrapped_text() == ('This is a very long\n'
+ 'text that should be\n'
+ 'wrapped multiple\n'
+ 'times.')
+
+
+def test_mathwrap():
+ fig = plt.figure(figsize=(6, 4))
+ s = r'This is a very $\overline{\mathrm{long}}$ line of Mathtext.'
+ text = fig.text(0, 0.5, s, size=40, wrap=True)
+ fig.canvas.draw()
+ assert text._get_wrapped_text() == ('This is a very $\\overline{\\mathrm{long}}$\n'
+ 'line of Mathtext.')
+
+
+def test_get_window_extent_wrapped():
+ # Test that a long title that wraps to two lines has the same vertical
+ # extent as an explicit two line title.
+
+ fig1 = plt.figure(figsize=(3, 3))
+ fig1.suptitle("suptitle that is clearly too long in this case", wrap=True)
+ window_extent_test = fig1._suptitle.get_window_extent()
+
+ fig2 = plt.figure(figsize=(3, 3))
+ fig2.suptitle("suptitle that is clearly\ntoo long in this case")
+ window_extent_ref = fig2._suptitle.get_window_extent()
+
+ assert window_extent_test.y0 == window_extent_ref.y0
+ assert window_extent_test.y1 == window_extent_ref.y1
+
+
+def test_long_word_wrap():
+ fig = plt.figure(figsize=(6, 4))
+ text = fig.text(9.5, 8, 'Alonglineoftexttowrap', wrap=True)
+ fig.canvas.draw()
+ assert text._get_wrapped_text() == 'Alonglineoftexttowrap'
+
+
+def test_wrap_no_wrap():
+ fig = plt.figure(figsize=(6, 4))
+ text = fig.text(0, 0, 'non wrapped text', wrap=True)
+ fig.canvas.draw()
+ assert text._get_wrapped_text() == 'non wrapped text'
+
+
+@check_figures_equal(extensions=["png"])
+def test_buffer_size(fig_test, fig_ref):
+ # On old versions of the Agg renderer, large non-ascii single-character
+ # strings (here, "€") would be rendered clipped because the rendering
+ # buffer would be set by the physical size of the smaller "a" character.
+ ax = fig_test.add_subplot()
+ ax.set_yticks([0, 1])
+ ax.set_yticklabels(["€", "a"])
+ ax.yaxis.majorTicks[1].label1.set_color("w")
+ ax = fig_ref.add_subplot()
+ ax.set_yticks([0, 1])
+ ax.set_yticklabels(["€", ""])
+
+
+def test_fontproperties_kwarg_precedence():
+ """Test that kwargs take precedence over fontproperties defaults."""
+ plt.figure()
+ text1 = plt.xlabel("value", fontproperties='Times New Roman', size=40.0)
+ text2 = plt.ylabel("counts", size=40.0, fontproperties='Times New Roman')
+ assert text1.get_size() == 40.0
+ assert text2.get_size() == 40.0
+
+
+def test_transform_rotates_text():
+ ax = plt.gca()
+ transform = mtransforms.Affine2D().rotate_deg(30)
+ text = ax.text(0, 0, 'test', transform=transform,
+ transform_rotates_text=True)
+ result = text.get_rotation()
+ assert_almost_equal(result, 30)
+
+
+def test_update_mutate_input():
+ inp = dict(fontproperties=FontProperties(weight="bold"),
+ bbox=None)
+ cache = dict(inp)
+ t = Text()
+ t.update(inp)
+ assert inp['fontproperties'] == cache['fontproperties']
+ assert inp['bbox'] == cache['bbox']
+
+
+@pytest.mark.parametrize('rotation', ['invalid string', [90]])
+def test_invalid_rotation_values(rotation):
+ with pytest.raises(
+ ValueError,
+ match=("rotation must be 'vertical', 'horizontal' or a number")):
+ Text(0, 0, 'foo', rotation=rotation)
+
+
+def test_invalid_color():
+ with pytest.raises(ValueError):
+ plt.figtext(.5, .5, "foo", c="foobar")
+
+
+@image_comparison(['text_pdf_kerning.pdf'], style='mpl20')
+def test_pdf_kerning():
+ plt.figure()
+ plt.figtext(0.1, 0.5, "ATATATATATATATATATA", size=30)
+
+
+def test_unsupported_script(recwarn):
+ fig = plt.figure()
+ t = fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}")
+ fig.canvas.draw()
+ assert all(isinstance(warn.message, UserWarning) for warn in recwarn)
+ assert (
+ [warn.message.args for warn in recwarn] ==
+ [(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from font(s) "
+ + f"{t.get_fontname()}.",),
+ (r"Matplotlib currently does not support Bengali natively.",)])
+
+
+# See gh-26152 for more information on this xfail
+@pytest.mark.xfail(pyparsing_version.release == (3, 1, 0),
+ reason="Error messages are incorrect with pyparsing 3.1.0")
+def test_parse_math():
+ fig, ax = plt.subplots()
+ ax.text(0, 0, r"$ \wrong{math} $", parse_math=False)
+ fig.canvas.draw()
+
+ ax.text(0, 0, r"$ \wrong{math} $", parse_math=True)
+ with pytest.raises(ValueError, match='Unknown symbol'):
+ fig.canvas.draw()
+
+
+# See gh-26152 for more information on this xfail
+@pytest.mark.xfail(pyparsing_version.release == (3, 1, 0),
+ reason="Error messages are incorrect with pyparsing 3.1.0")
+def test_parse_math_rcparams():
+ # Default is True
+ fig, ax = plt.subplots()
+ ax.text(0, 0, r"$ \wrong{math} $")
+ with pytest.raises(ValueError, match='Unknown symbol'):
+ fig.canvas.draw()
+
+ # Setting rcParams to False
+ with mpl.rc_context({'text.parse_math': False}):
+ fig, ax = plt.subplots()
+ ax.text(0, 0, r"$ \wrong{math} $")
+ fig.canvas.draw()
+
+
+@image_comparison(['text_pdf_font42_kerning.pdf'], style='mpl20')
+def test_pdf_font42_kerning():
+ plt.rcParams['pdf.fonttype'] = 42
+ plt.figure()
+ plt.figtext(0.1, 0.5, "ATAVATAVATAVATAVATA", size=30)
+
+
+@image_comparison(['text_pdf_chars_beyond_bmp.pdf'], style='mpl20')
+def test_pdf_chars_beyond_bmp():
+ plt.rcParams['pdf.fonttype'] = 42
+ plt.rcParams['mathtext.fontset'] = 'stixsans'
+ plt.figure()
+ plt.figtext(0.1, 0.5, "Mass $m$ \U00010308", size=30)
+
+
+@needs_usetex
+def test_metrics_cache():
+ mpl.text._get_text_metrics_with_cache_impl.cache_clear()
+
+ fig = plt.figure()
+ fig.text(.3, .5, "foo\nbar")
+ fig.text(.3, .5, "foo\nbar", usetex=True)
+ fig.text(.5, .5, "foo\nbar", usetex=True)
+ fig.canvas.draw()
+ renderer = fig._get_renderer()
+ ys = {} # mapping of strings to where they were drawn in y with draw_tex.
+
+ def call(*args, **kwargs):
+ renderer, x, y, s, *_ = args
+ ys.setdefault(s, set()).add(y)
+
+ renderer.draw_tex = call
+ fig.canvas.draw()
+ assert [*ys] == ["foo", "bar"]
+ # Check that both TeX strings were drawn with the same y-position for both
+ # single-line substrings. Previously, there used to be an incorrect cache
+ # collision with the non-TeX string (drawn first here) whose metrics would
+ # get incorrectly reused by the first TeX string.
+ assert len(ys["foo"]) == len(ys["bar"]) == 1
+
+ info = mpl.text._get_text_metrics_with_cache_impl.cache_info()
+ # Every string gets a miss for the first layouting (extents), then a hit
+ # when drawing, but "foo\nbar" gets two hits as it's drawn twice.
+ assert info.hits > info.misses
+
+
+def test_annotate_offset_fontsize():
+ # Test that offset_fontsize parameter works and uses accurate values
+ fig, ax = plt.subplots()
+ text_coords = ['offset points', 'offset fontsize']
+ # 10 points should be equal to 1 fontsize unit at fontsize=10
+ xy_text = [(10, 10), (1, 1)]
+ anns = [ax.annotate('test', xy=(0.5, 0.5),
+ xytext=xy_text[i],
+ fontsize='10',
+ xycoords='data',
+ textcoords=text_coords[i]) for i in range(2)]
+ points_coords, fontsize_coords = (ann.get_window_extent() for ann in anns)
+ fig.canvas.draw()
+ assert str(points_coords) == str(fontsize_coords)
+
+
+def test_get_set_antialiased():
+ txt = Text(.5, .5, "foo\nbar")
+ assert txt._antialiased == mpl.rcParams['text.antialiased']
+ assert txt.get_antialiased() == mpl.rcParams['text.antialiased']
+
+ txt.set_antialiased(True)
+ assert txt._antialiased is True
+ assert txt.get_antialiased() == txt._antialiased
+
+ txt.set_antialiased(False)
+ assert txt._antialiased is False
+ assert txt.get_antialiased() == txt._antialiased
+
+
+def test_annotation_antialiased():
+ annot = Annotation("foo\nbar", (.5, .5), antialiased=True)
+ assert annot._antialiased is True
+ assert annot.get_antialiased() == annot._antialiased
+
+ annot2 = Annotation("foo\nbar", (.5, .5), antialiased=False)
+ assert annot2._antialiased is False
+ assert annot2.get_antialiased() == annot2._antialiased
+
+ annot3 = Annotation("foo\nbar", (.5, .5), antialiased=False)
+ annot3.set_antialiased(True)
+ assert annot3.get_antialiased() is True
+ assert annot3._antialiased is True
+
+ annot4 = Annotation("foo\nbar", (.5, .5))
+ assert annot4._antialiased == mpl.rcParams['text.antialiased']
+
+
+@check_figures_equal(extensions=["png"])
+def test_annotate_and_offsetfrom_copy_input(fig_test, fig_ref):
+ # Both approaches place the text (10, 0) pixels away from the center of the line.
+ ax = fig_test.add_subplot()
+ l, = ax.plot([0, 2], [0, 2])
+ of_xy = np.array([.5, .5])
+ ax.annotate("foo", textcoords=OffsetFrom(l, of_xy), xytext=(10, 0),
+ xy=(0, 0)) # xy is unused.
+ of_xy[:] = 1
+ ax = fig_ref.add_subplot()
+ l, = ax.plot([0, 2], [0, 2])
+ an_xy = np.array([.5, .5])
+ ax.annotate("foo", xy=an_xy, xycoords=l, xytext=(10, 0), textcoords="offset points")
+ an_xy[:] = 2
+
+
+@check_figures_equal()
+def test_text_antialiased_off_default_vs_manual(fig_test, fig_ref):
+ fig_test.text(0.5, 0.5, '6 inches x 2 inches',
+ antialiased=False)
+
+ mpl.rcParams['text.antialiased'] = False
+ fig_ref.text(0.5, 0.5, '6 inches x 2 inches')
+
+
+@check_figures_equal()
+def test_text_antialiased_on_default_vs_manual(fig_test, fig_ref):
+ fig_test.text(0.5, 0.5, '6 inches x 2 inches', antialiased=True)
+
+ mpl.rcParams['text.antialiased'] = True
+ fig_ref.text(0.5, 0.5, '6 inches x 2 inches')
+
+
+def test_text_annotation_get_window_extent():
+ figure = Figure(dpi=100)
+ renderer = RendererAgg(200, 200, 100)
+
+ # Only text annotation
+ annotation = Annotation('test', xy=(0, 0), xycoords='figure pixels')
+ annotation.set_figure(figure)
+
+ text = Text(text='test', x=0, y=0)
+ text.set_figure(figure)
+
+ bbox = annotation.get_window_extent(renderer=renderer)
+
+ text_bbox = text.get_window_extent(renderer=renderer)
+ assert bbox.width == text_bbox.width
+ assert bbox.height == text_bbox.height
+
+ _, _, d = renderer.get_text_width_height_descent(
+ 'text', annotation._fontproperties, ismath=False)
+ _, _, lp_d = renderer.get_text_width_height_descent(
+ 'lp', annotation._fontproperties, ismath=False)
+ below_line = max(d, lp_d)
+
+ # These numbers are specific to the current implementation of Text
+ points = bbox.get_points()
+ assert points[0, 0] == 0.0
+ assert points[1, 0] == text_bbox.width
+ assert points[0, 1] == -below_line
+ assert points[1, 1] == text_bbox.height - below_line
+
+
+def test_text_with_arrow_annotation_get_window_extent():
+ headwidth = 21
+ fig, ax = plt.subplots(dpi=100)
+ txt = ax.text(s='test', x=0, y=0)
+ ann = ax.annotate(
+ 'test',
+ xy=(0.0, 50.0),
+ xytext=(50.0, 50.0), xycoords='figure pixels',
+ arrowprops={
+ 'facecolor': 'black', 'width': 2,
+ 'headwidth': headwidth, 'shrink': 0.0})
+
+ plt.draw()
+ renderer = fig.canvas.renderer
+ # bounding box of text
+ text_bbox = txt.get_window_extent(renderer=renderer)
+ # bounding box of annotation (text + arrow)
+ bbox = ann.get_window_extent(renderer=renderer)
+ # bounding box of arrow
+ arrow_bbox = ann.arrow_patch.get_window_extent(renderer)
+ # bounding box of annotation text
+ ann_txt_bbox = Text.get_window_extent(ann)
+
+ # make sure annotation width is 50 px wider than
+ # just the text
+ assert bbox.width == text_bbox.width + 50.0
+ # make sure the annotation text bounding box is same size
+ # as the bounding box of the same string as a Text object
+ assert ann_txt_bbox.height == text_bbox.height
+ assert ann_txt_bbox.width == text_bbox.width
+ # compute the expected bounding box of arrow + text
+ expected_bbox = mtransforms.Bbox.union([ann_txt_bbox, arrow_bbox])
+ assert_almost_equal(bbox.height, expected_bbox.height)
+
+
+def test_arrow_annotation_get_window_extent():
+ dpi = 100
+ dots_per_point = dpi / 72
+ figure = Figure(dpi=dpi)
+ figure.set_figwidth(2.0)
+ figure.set_figheight(2.0)
+ renderer = RendererAgg(200, 200, 100)
+
+ # Text annotation with arrow; arrow dimensions are in points
+ annotation = Annotation(
+ '', xy=(0.0, 50.0), xytext=(50.0, 50.0), xycoords='figure pixels',
+ arrowprops={
+ 'facecolor': 'black', 'width': 8, 'headwidth': 10, 'shrink': 0.0})
+ annotation.set_figure(figure)
+ annotation.draw(renderer)
+
+ bbox = annotation.get_window_extent()
+ points = bbox.get_points()
+
+ assert bbox.width == 50.0
+ assert_almost_equal(bbox.height, 10.0 * dots_per_point)
+ assert points[0, 0] == 0.0
+ assert points[0, 1] == 50.0 - 5 * dots_per_point
+
+
+def test_empty_annotation_get_window_extent():
+ figure = Figure(dpi=100)
+ figure.set_figwidth(2.0)
+ figure.set_figheight(2.0)
+ renderer = RendererAgg(200, 200, 100)
+
+ # Text annotation with arrow
+ annotation = Annotation(
+ '', xy=(0.0, 50.0), xytext=(0.0, 50.0), xycoords='figure pixels')
+ annotation.set_figure(figure)
+ annotation.draw(renderer)
+
+ bbox = annotation.get_window_extent()
+ points = bbox.get_points()
+
+ assert points[0, 0] == 0.0
+ assert points[1, 0] == 0.0
+ assert points[1, 1] == 50.0
+ assert points[0, 1] == 50.0
+
+
+@image_comparison(baseline_images=['basictext_wrap'],
+ extensions=['png'])
+def test_basic_wrap():
+ fig = plt.figure()
+ plt.axis([0, 10, 0, 10])
+ t = "This is a really long string that I'd rather have wrapped so that" \
+ " it doesn't go outside of the figure, but if it's long enough it" \
+ " will go off the top or bottom!"
+ plt.text(4, 1, t, ha='left', rotation=15, wrap=True)
+ plt.text(6, 5, t, ha='left', rotation=15, wrap=True)
+ plt.text(5, 5, t, ha='right', rotation=-15, wrap=True)
+ plt.text(5, 10, t, fontsize=18, style='oblique', ha='center',
+ va='top', wrap=True)
+ plt.text(3, 4, t, family='serif', style='italic', ha='right', wrap=True)
+ plt.text(-1, 0, t, ha='left', rotation=-15, wrap=True)
+
+
+@image_comparison(baseline_images=['fonttext_wrap'],
+ extensions=['png'])
+def test_font_wrap():
+ fig = plt.figure()
+ plt.axis([0, 10, 0, 10])
+ t = "This is a really long string that I'd rather have wrapped so that" \
+ " it doesn't go outside of the figure, but if it's long enough it" \
+ " will go off the top or bottom!"
+ plt.text(4, -1, t, fontsize=18, family='serif', ha='left', rotation=15,
+ wrap=True)
+ plt.text(6, 5, t, family='sans serif', ha='left', rotation=15, wrap=True)
+ plt.text(5, 10, t, weight='heavy', ha='center', va='top', wrap=True)
+ plt.text(3, 4, t, family='monospace', ha='right', wrap=True)
+ plt.text(-1, 0, t, fontsize=14, style='italic', ha='left', rotation=-15,
+ wrap=True)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_textpath.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_textpath.py
new file mode 100644
index 0000000000000000000000000000000000000000..e421d2623cadac7931f1f21148bdc0a603c290b1
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_textpath.py
@@ -0,0 +1,10 @@
+import copy
+
+from matplotlib.textpath import TextPath
+
+
+def test_copy():
+ tp = TextPath((0, 0), ".")
+ assert copy.deepcopy(tp).vertices is not tp.vertices
+ assert (copy.deepcopy(tp).vertices == tp.vertices).all()
+ assert copy.copy(tp).vertices is tp.vertices
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_ticker.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_ticker.py
new file mode 100644
index 0000000000000000000000000000000000000000..77c0e917df8a1fd2c7deb450e3dd8b917391f3b8
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_ticker.py
@@ -0,0 +1,1939 @@
+from contextlib import nullcontext
+import itertools
+import locale
+import logging
+import re
+from packaging.version import parse as parse_version
+
+import numpy as np
+from numpy.testing import assert_almost_equal, assert_array_equal
+import pytest
+
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+import matplotlib.ticker as mticker
+
+
+class TestMaxNLocator:
+ basic_data = [
+ (20, 100, np.array([20., 40., 60., 80., 100.])),
+ (0.001, 0.0001, np.array([0., 0.0002, 0.0004, 0.0006, 0.0008, 0.001])),
+ (-1e15, 1e15, np.array([-1.0e+15, -5.0e+14, 0e+00, 5e+14, 1.0e+15])),
+ (0, 0.85e-50, np.arange(6) * 2e-51),
+ (-0.85e-50, 0, np.arange(-5, 1) * 2e-51),
+ ]
+
+ integer_data = [
+ (-0.1, 1.1, None, np.array([-1, 0, 1, 2])),
+ (-0.1, 0.95, None, np.array([-0.25, 0, 0.25, 0.5, 0.75, 1.0])),
+ (1, 55, [1, 1.5, 5, 6, 10], np.array([0, 15, 30, 45, 60])),
+ ]
+
+ @pytest.mark.parametrize('vmin, vmax, expected', basic_data)
+ def test_basic(self, vmin, vmax, expected):
+ loc = mticker.MaxNLocator(nbins=5)
+ assert_almost_equal(loc.tick_values(vmin, vmax), expected)
+
+ @pytest.mark.parametrize('vmin, vmax, steps, expected', integer_data)
+ def test_integer(self, vmin, vmax, steps, expected):
+ loc = mticker.MaxNLocator(nbins=5, integer=True, steps=steps)
+ assert_almost_equal(loc.tick_values(vmin, vmax), expected)
+
+ @pytest.mark.parametrize('kwargs, errortype, match', [
+ ({'foo': 0}, TypeError,
+ re.escape("set_params() got an unexpected keyword argument 'foo'")),
+ ({'steps': [2, 1]}, ValueError, "steps argument must be an increasing"),
+ ({'steps': 2}, ValueError, "steps argument must be an increasing"),
+ ({'steps': [2, 11]}, ValueError, "steps argument must be an increasing"),
+ ])
+ def test_errors(self, kwargs, errortype, match):
+ with pytest.raises(errortype, match=match):
+ mticker.MaxNLocator(**kwargs)
+
+ @pytest.mark.parametrize('steps, result', [
+ ([1, 2, 10], [1, 2, 10]),
+ ([2, 10], [1, 2, 10]),
+ ([1, 2], [1, 2, 10]),
+ ([2], [1, 2, 10]),
+ ])
+ def test_padding(self, steps, result):
+ loc = mticker.MaxNLocator(steps=steps)
+ assert (loc._steps == result).all()
+
+
+class TestLinearLocator:
+ def test_basic(self):
+ loc = mticker.LinearLocator(numticks=3)
+ test_value = np.array([-0.8, -0.3, 0.2])
+ assert_almost_equal(loc.tick_values(-0.8, 0.2), test_value)
+
+ def test_zero_numticks(self):
+ loc = mticker.LinearLocator(numticks=0)
+ loc.tick_values(-0.8, 0.2) == []
+
+ def test_set_params(self):
+ """
+ Create linear locator with presets={}, numticks=2 and change it to
+ something else. See if change was successful. Should not exception.
+ """
+ loc = mticker.LinearLocator(numticks=2)
+ loc.set_params(numticks=8, presets={(0, 1): []})
+ assert loc.numticks == 8
+ assert loc.presets == {(0, 1): []}
+
+ def test_presets(self):
+ loc = mticker.LinearLocator(presets={(1, 2): [1, 1.25, 1.75],
+ (0, 2): [0.5, 1.5]})
+ assert loc.tick_values(1, 2) == [1, 1.25, 1.75]
+ assert loc.tick_values(2, 1) == [1, 1.25, 1.75]
+ assert loc.tick_values(0, 2) == [0.5, 1.5]
+ assert loc.tick_values(0.0, 2.0) == [0.5, 1.5]
+ assert (loc.tick_values(0, 1) == np.linspace(0, 1, 11)).all()
+
+
+class TestMultipleLocator:
+ def test_basic(self):
+ loc = mticker.MultipleLocator(base=3.147)
+ test_value = np.array([-9.441, -6.294, -3.147, 0., 3.147, 6.294,
+ 9.441, 12.588])
+ assert_almost_equal(loc.tick_values(-7, 10), test_value)
+
+ def test_basic_with_offset(self):
+ loc = mticker.MultipleLocator(base=3.147, offset=1.2)
+ test_value = np.array([-8.241, -5.094, -1.947, 1.2, 4.347, 7.494,
+ 10.641])
+ assert_almost_equal(loc.tick_values(-7, 10), test_value)
+
+ def test_view_limits(self):
+ """
+ Test basic behavior of view limits.
+ """
+ with mpl.rc_context({'axes.autolimit_mode': 'data'}):
+ loc = mticker.MultipleLocator(base=3.147)
+ assert_almost_equal(loc.view_limits(-5, 5), (-5, 5))
+
+ def test_view_limits_round_numbers(self):
+ """
+ Test that everything works properly with 'round_numbers' for auto
+ limit.
+ """
+ with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}):
+ loc = mticker.MultipleLocator(base=3.147)
+ assert_almost_equal(loc.view_limits(-4, 4), (-6.294, 6.294))
+
+ def test_view_limits_round_numbers_with_offset(self):
+ """
+ Test that everything works properly with 'round_numbers' for auto
+ limit.
+ """
+ with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}):
+ loc = mticker.MultipleLocator(base=3.147, offset=1.3)
+ assert_almost_equal(loc.view_limits(-4, 4), (-4.994, 4.447))
+
+ def test_view_limits_single_bin(self):
+ """
+ Test that 'round_numbers' works properly with a single bin.
+ """
+ with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}):
+ loc = mticker.MaxNLocator(nbins=1)
+ assert_almost_equal(loc.view_limits(-2.3, 2.3), (-4, 4))
+
+ def test_set_params(self):
+ """
+ Create multiple locator with 0.7 base, and change it to something else.
+ See if change was successful.
+ """
+ mult = mticker.MultipleLocator(base=0.7)
+ mult.set_params(base=1.7)
+ assert mult._edge.step == 1.7
+ mult.set_params(offset=3)
+ assert mult._offset == 3
+
+
+class TestAutoMinorLocator:
+ def test_basic(self):
+ fig, ax = plt.subplots()
+ ax.set_xlim(0, 1.39)
+ ax.minorticks_on()
+ test_value = np.array([0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45,
+ 0.5, 0.55, 0.65, 0.7, 0.75, 0.85, 0.9,
+ 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35])
+ assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value)
+
+ # NB: the following values are assuming that *xlim* is [0, 5]
+ params = [
+ (0, 0), # no major tick => no minor tick either
+ (1, 0) # a single major tick => no minor tick
+ ]
+
+ def test_first_and_last_minorticks(self):
+ """
+ Test that first and last minor tick appear as expected.
+ """
+ # This test is related to issue #22331
+ fig, ax = plt.subplots()
+ ax.set_xlim(-1.9, 1.9)
+ ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
+ test_value = np.array([-1.9, -1.8, -1.7, -1.6, -1.4, -1.3, -1.2, -1.1,
+ -0.9, -0.8, -0.7, -0.6, -0.4, -0.3, -0.2, -0.1,
+ 0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, 1.1,
+ 1.2, 1.3, 1.4, 1.6, 1.7, 1.8, 1.9])
+ assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value)
+
+ ax.set_xlim(-5, 5)
+ test_value = np.array([-5.0, -4.5, -3.5, -3.0, -2.5, -1.5, -1.0, -0.5,
+ 0.5, 1.0, 1.5, 2.5, 3.0, 3.5, 4.5, 5.0])
+ assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value)
+
+ @pytest.mark.parametrize('nb_majorticks, expected_nb_minorticks', params)
+ def test_low_number_of_majorticks(
+ self, nb_majorticks, expected_nb_minorticks):
+ # This test is related to issue #8804
+ fig, ax = plt.subplots()
+ xlims = (0, 5) # easier to test the different code paths
+ ax.set_xlim(*xlims)
+ ax.set_xticks(np.linspace(xlims[0], xlims[1], nb_majorticks))
+ ax.minorticks_on()
+ ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
+ assert len(ax.xaxis.get_minorticklocs()) == expected_nb_minorticks
+
+ majorstep_minordivisions = [(1, 5),
+ (2, 4),
+ (2.5, 5),
+ (5, 5),
+ (10, 5)]
+
+ # This test is meant to verify the parameterization for
+ # test_number_of_minor_ticks
+ def test_using_all_default_major_steps(self):
+ with mpl.rc_context({'_internal.classic_mode': False}):
+ majorsteps = [x[0] for x in self.majorstep_minordivisions]
+ np.testing.assert_allclose(majorsteps,
+ mticker.AutoLocator()._steps)
+
+ @pytest.mark.parametrize('major_step, expected_nb_minordivisions',
+ majorstep_minordivisions)
+ def test_number_of_minor_ticks(
+ self, major_step, expected_nb_minordivisions):
+ fig, ax = plt.subplots()
+ xlims = (0, major_step)
+ ax.set_xlim(*xlims)
+ ax.set_xticks(xlims)
+ ax.minorticks_on()
+ ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
+ nb_minor_divisions = len(ax.xaxis.get_minorticklocs()) + 1
+ assert nb_minor_divisions == expected_nb_minordivisions
+
+ limits = [(0, 1.39), (0, 0.139),
+ (0, 0.11e-19), (0, 0.112e-12),
+ (-2.0e-07, -3.3e-08), (1.20e-06, 1.42e-06),
+ (-1.34e-06, -1.44e-06), (-8.76e-07, -1.51e-06)]
+
+ reference = [
+ [0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45, 0.5, 0.55, 0.65, 0.7,
+ 0.75, 0.85, 0.9, 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35],
+ [0.005, 0.01, 0.015, 0.025, 0.03, 0.035, 0.045, 0.05, 0.055, 0.065,
+ 0.07, 0.075, 0.085, 0.09, 0.095, 0.105, 0.11, 0.115, 0.125, 0.13,
+ 0.135],
+ [5.00e-22, 1.00e-21, 1.50e-21, 2.50e-21, 3.00e-21, 3.50e-21, 4.50e-21,
+ 5.00e-21, 5.50e-21, 6.50e-21, 7.00e-21, 7.50e-21, 8.50e-21, 9.00e-21,
+ 9.50e-21, 1.05e-20, 1.10e-20],
+ [5.00e-15, 1.00e-14, 1.50e-14, 2.50e-14, 3.00e-14, 3.50e-14, 4.50e-14,
+ 5.00e-14, 5.50e-14, 6.50e-14, 7.00e-14, 7.50e-14, 8.50e-14, 9.00e-14,
+ 9.50e-14, 1.05e-13, 1.10e-13],
+ [-1.95e-07, -1.90e-07, -1.85e-07, -1.75e-07, -1.70e-07, -1.65e-07,
+ -1.55e-07, -1.50e-07, -1.45e-07, -1.35e-07, -1.30e-07, -1.25e-07,
+ -1.15e-07, -1.10e-07, -1.05e-07, -9.50e-08, -9.00e-08, -8.50e-08,
+ -7.50e-08, -7.00e-08, -6.50e-08, -5.50e-08, -5.00e-08, -4.50e-08,
+ -3.50e-08],
+ [1.21e-06, 1.22e-06, 1.23e-06, 1.24e-06, 1.26e-06, 1.27e-06, 1.28e-06,
+ 1.29e-06, 1.31e-06, 1.32e-06, 1.33e-06, 1.34e-06, 1.36e-06, 1.37e-06,
+ 1.38e-06, 1.39e-06, 1.41e-06, 1.42e-06],
+ [-1.435e-06, -1.430e-06, -1.425e-06, -1.415e-06, -1.410e-06,
+ -1.405e-06, -1.395e-06, -1.390e-06, -1.385e-06, -1.375e-06,
+ -1.370e-06, -1.365e-06, -1.355e-06, -1.350e-06, -1.345e-06],
+ [-1.48e-06, -1.46e-06, -1.44e-06, -1.42e-06, -1.38e-06, -1.36e-06,
+ -1.34e-06, -1.32e-06, -1.28e-06, -1.26e-06, -1.24e-06, -1.22e-06,
+ -1.18e-06, -1.16e-06, -1.14e-06, -1.12e-06, -1.08e-06, -1.06e-06,
+ -1.04e-06, -1.02e-06, -9.80e-07, -9.60e-07, -9.40e-07, -9.20e-07,
+ -8.80e-07]]
+
+ additional_data = list(zip(limits, reference))
+
+ @pytest.mark.parametrize('lim, ref', additional_data)
+ def test_additional(self, lim, ref):
+ fig, ax = plt.subplots()
+
+ ax.minorticks_on()
+ ax.grid(True, 'minor', 'y', linewidth=1)
+ ax.grid(True, 'major', color='k', linewidth=1)
+ ax.set_ylim(lim)
+
+ assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref)
+
+ @pytest.mark.parametrize('use_rcparam', [False, True])
+ @pytest.mark.parametrize(
+ 'lim, ref', [
+ ((0, 1.39),
+ [0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45, 0.5, 0.55, 0.65, 0.7,
+ 0.75, 0.85, 0.9, 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35]),
+ ((0, 0.139),
+ [0.005, 0.01, 0.015, 0.025, 0.03, 0.035, 0.045, 0.05, 0.055,
+ 0.065, 0.07, 0.075, 0.085, 0.09, 0.095, 0.105, 0.11, 0.115,
+ 0.125, 0.13, 0.135]),
+ ])
+ def test_number_of_minor_ticks_auto(self, lim, ref, use_rcparam):
+ if use_rcparam:
+ context = {'xtick.minor.ndivs': 'auto', 'ytick.minor.ndivs': 'auto'}
+ kwargs = {}
+ else:
+ context = {}
+ kwargs = {'n': 'auto'}
+
+ with mpl.rc_context(context):
+ fig, ax = plt.subplots()
+ ax.set_xlim(*lim)
+ ax.set_ylim(*lim)
+ ax.xaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs))
+ ax.yaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs))
+ assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), ref)
+ assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref)
+
+ @pytest.mark.parametrize('use_rcparam', [False, True])
+ @pytest.mark.parametrize(
+ 'n, lim, ref', [
+ (2, (0, 4), [0.5, 1.5, 2.5, 3.5]),
+ (4, (0, 2), [0.25, 0.5, 0.75, 1.25, 1.5, 1.75]),
+ (10, (0, 1), [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]),
+ ])
+ def test_number_of_minor_ticks_int(self, n, lim, ref, use_rcparam):
+ if use_rcparam:
+ context = {'xtick.minor.ndivs': n, 'ytick.minor.ndivs': n}
+ kwargs = {}
+ else:
+ context = {}
+ kwargs = {'n': n}
+
+ with mpl.rc_context(context):
+ fig, ax = plt.subplots()
+ ax.set_xlim(*lim)
+ ax.set_ylim(*lim)
+ ax.xaxis.set_major_locator(mticker.MultipleLocator(1))
+ ax.xaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs))
+ ax.yaxis.set_major_locator(mticker.MultipleLocator(1))
+ ax.yaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs))
+ assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), ref)
+ assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref)
+
+
+class TestLogLocator:
+ def test_basic(self):
+ loc = mticker.LogLocator(numticks=5)
+ with pytest.raises(ValueError):
+ loc.tick_values(0, 1000)
+
+ test_value = np.array([1.00000000e-05, 1.00000000e-03, 1.00000000e-01,
+ 1.00000000e+01, 1.00000000e+03, 1.00000000e+05,
+ 1.00000000e+07, 1.000000000e+09])
+ assert_almost_equal(loc.tick_values(0.001, 1.1e5), test_value)
+
+ loc = mticker.LogLocator(base=2)
+ test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.])
+ assert_almost_equal(loc.tick_values(1, 100), test_value)
+
+ def test_polar_axes(self):
+ """
+ Polar Axes have a different ticking logic.
+ """
+ fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
+ ax.set_yscale('log')
+ ax.set_ylim(1, 100)
+ assert_array_equal(ax.get_yticks(), [10, 100, 1000])
+
+ def test_switch_to_autolocator(self):
+ loc = mticker.LogLocator(subs="all")
+ assert_array_equal(loc.tick_values(0.45, 0.55),
+ [0.44, 0.46, 0.48, 0.5, 0.52, 0.54, 0.56])
+ # check that we *skip* 1.0, and 10, because this is a minor locator
+ loc = mticker.LogLocator(subs=np.arange(2, 10))
+ assert 1.0 not in loc.tick_values(0.9, 20.)
+ assert 10.0 not in loc.tick_values(0.9, 20.)
+
+ def test_set_params(self):
+ """
+ Create log locator with default value, base=10.0, subs=[1.0],
+ numticks=15 and change it to something else.
+ See if change was successful. Should not raise exception.
+ """
+ loc = mticker.LogLocator()
+ loc.set_params(numticks=7, subs=[2.0], base=4)
+ assert loc.numticks == 7
+ assert loc._base == 4
+ assert list(loc._subs) == [2.0]
+
+ def test_tick_values_correct(self):
+ ll = mticker.LogLocator(subs=(1, 2, 5))
+ test_value = np.array([1.e-01, 2.e-01, 5.e-01, 1.e+00, 2.e+00, 5.e+00,
+ 1.e+01, 2.e+01, 5.e+01, 1.e+02, 2.e+02, 5.e+02,
+ 1.e+03, 2.e+03, 5.e+03, 1.e+04, 2.e+04, 5.e+04,
+ 1.e+05, 2.e+05, 5.e+05, 1.e+06, 2.e+06, 5.e+06,
+ 1.e+07, 2.e+07, 5.e+07, 1.e+08, 2.e+08, 5.e+08])
+ assert_almost_equal(ll.tick_values(1, 1e7), test_value)
+
+ def test_tick_values_not_empty(self):
+ mpl.rcParams['_internal.classic_mode'] = False
+ ll = mticker.LogLocator(subs=(1, 2, 5))
+ test_value = np.array([1.e-01, 2.e-01, 5.e-01, 1.e+00, 2.e+00, 5.e+00,
+ 1.e+01, 2.e+01, 5.e+01, 1.e+02, 2.e+02, 5.e+02,
+ 1.e+03, 2.e+03, 5.e+03, 1.e+04, 2.e+04, 5.e+04,
+ 1.e+05, 2.e+05, 5.e+05, 1.e+06, 2.e+06, 5.e+06,
+ 1.e+07, 2.e+07, 5.e+07, 1.e+08, 2.e+08, 5.e+08,
+ 1.e+09, 2.e+09, 5.e+09])
+ assert_almost_equal(ll.tick_values(1, 1e8), test_value)
+
+ def test_multiple_shared_axes(self):
+ rng = np.random.default_rng(19680801)
+ dummy_data = [rng.normal(size=100), [], []]
+ fig, axes = plt.subplots(len(dummy_data), sharex=True, sharey=True)
+
+ for ax, data in zip(axes.flatten(), dummy_data):
+ ax.hist(data, bins=10)
+ ax.set_yscale('log', nonpositive='clip')
+
+ for ax in axes.flatten():
+ assert all(ax.get_yticks() == axes[0].get_yticks())
+ assert ax.get_ylim() == axes[0].get_ylim()
+
+
+class TestNullLocator:
+ def test_set_params(self):
+ """
+ Create null locator, and attempt to call set_params() on it.
+ Should not exception, and should raise a warning.
+ """
+ loc = mticker.NullLocator()
+ with pytest.warns(UserWarning):
+ loc.set_params()
+
+
+class _LogitHelper:
+ @staticmethod
+ def isclose(x, y):
+ return (np.isclose(-np.log(1/x-1), -np.log(1/y-1))
+ if 0 < x < 1 and 0 < y < 1 else False)
+
+ @staticmethod
+ def assert_almost_equal(x, y):
+ ax = np.array(x)
+ ay = np.array(y)
+ assert np.all(ax > 0) and np.all(ax < 1)
+ assert np.all(ay > 0) and np.all(ay < 1)
+ lx = -np.log(1/ax-1)
+ ly = -np.log(1/ay-1)
+ assert_almost_equal(lx, ly)
+
+
+class TestLogitLocator:
+ ref_basic_limits = [
+ (5e-2, 1 - 5e-2),
+ (5e-3, 1 - 5e-3),
+ (5e-4, 1 - 5e-4),
+ (5e-5, 1 - 5e-5),
+ (5e-6, 1 - 5e-6),
+ (5e-7, 1 - 5e-7),
+ (5e-8, 1 - 5e-8),
+ (5e-9, 1 - 5e-9),
+ ]
+
+ ref_basic_major_ticks = [
+ 1 / (10 ** np.arange(1, 3)),
+ 1 / (10 ** np.arange(1, 4)),
+ 1 / (10 ** np.arange(1, 5)),
+ 1 / (10 ** np.arange(1, 6)),
+ 1 / (10 ** np.arange(1, 7)),
+ 1 / (10 ** np.arange(1, 8)),
+ 1 / (10 ** np.arange(1, 9)),
+ 1 / (10 ** np.arange(1, 10)),
+ ]
+
+ ref_maxn_limits = [(0.4, 0.6), (5e-2, 2e-1), (1 - 2e-1, 1 - 5e-2)]
+
+ @pytest.mark.parametrize(
+ "lims, expected_low_ticks",
+ zip(ref_basic_limits, ref_basic_major_ticks),
+ )
+ def test_basic_major(self, lims, expected_low_ticks):
+ """
+ Create logit locator with huge number of major, and tests ticks.
+ """
+ expected_ticks = sorted(
+ [*expected_low_ticks, 0.5, *(1 - expected_low_ticks)]
+ )
+ loc = mticker.LogitLocator(nbins=100)
+ _LogitHelper.assert_almost_equal(
+ loc.tick_values(*lims),
+ expected_ticks
+ )
+
+ @pytest.mark.parametrize("lims", ref_maxn_limits)
+ def test_maxn_major(self, lims):
+ """
+ When the axis is zoomed, the locator must have the same behavior as
+ MaxNLocator.
+ """
+ loc = mticker.LogitLocator(nbins=100)
+ maxn_loc = mticker.MaxNLocator(nbins=100, steps=[1, 2, 5, 10])
+ for nbins in (4, 8, 16):
+ loc.set_params(nbins=nbins)
+ maxn_loc.set_params(nbins=nbins)
+ ticks = loc.tick_values(*lims)
+ maxn_ticks = maxn_loc.tick_values(*lims)
+ assert ticks.shape == maxn_ticks.shape
+ assert (ticks == maxn_ticks).all()
+
+ @pytest.mark.parametrize("lims", ref_basic_limits + ref_maxn_limits)
+ def test_nbins_major(self, lims):
+ """
+ Assert logit locator for respecting nbins param.
+ """
+
+ basic_needed = int(-np.floor(np.log10(lims[0]))) * 2 + 1
+ loc = mticker.LogitLocator(nbins=100)
+ for nbins in range(basic_needed, 2, -1):
+ loc.set_params(nbins=nbins)
+ assert len(loc.tick_values(*lims)) <= nbins + 2
+
+ @pytest.mark.parametrize(
+ "lims, expected_low_ticks",
+ zip(ref_basic_limits, ref_basic_major_ticks),
+ )
+ def test_minor(self, lims, expected_low_ticks):
+ """
+ In large scale, test the presence of minor,
+ and assert no minor when major are subsampled.
+ """
+
+ expected_ticks = sorted(
+ [*expected_low_ticks, 0.5, *(1 - expected_low_ticks)]
+ )
+ basic_needed = len(expected_ticks)
+ loc = mticker.LogitLocator(nbins=100)
+ minor_loc = mticker.LogitLocator(nbins=100, minor=True)
+ for nbins in range(basic_needed, 2, -1):
+ loc.set_params(nbins=nbins)
+ minor_loc.set_params(nbins=nbins)
+ major_ticks = loc.tick_values(*lims)
+ minor_ticks = minor_loc.tick_values(*lims)
+ if len(major_ticks) >= len(expected_ticks):
+ # no subsample, we must have a lot of minors ticks
+ assert (len(major_ticks) - 1) * 5 < len(minor_ticks)
+ else:
+ # subsample
+ _LogitHelper.assert_almost_equal(
+ sorted([*major_ticks, *minor_ticks]), expected_ticks)
+
+ def test_minor_attr(self):
+ loc = mticker.LogitLocator(nbins=100)
+ assert not loc.minor
+ loc.minor = True
+ assert loc.minor
+ loc.set_params(minor=False)
+ assert not loc.minor
+
+ acceptable_vmin_vmax = [
+ *(2.5 ** np.arange(-3, 0)),
+ *(1 - 2.5 ** np.arange(-3, 0)),
+ ]
+
+ @pytest.mark.parametrize(
+ "lims",
+ [
+ (a, b)
+ for (a, b) in itertools.product(acceptable_vmin_vmax, repeat=2)
+ if a != b
+ ],
+ )
+ def test_nonsingular_ok(self, lims):
+ """
+ Create logit locator, and test the nonsingular method for acceptable
+ value
+ """
+ loc = mticker.LogitLocator()
+ lims2 = loc.nonsingular(*lims)
+ assert sorted(lims) == sorted(lims2)
+
+ @pytest.mark.parametrize("okval", acceptable_vmin_vmax)
+ def test_nonsingular_nok(self, okval):
+ """
+ Create logit locator, and test the nonsingular method for non
+ acceptable value
+ """
+ loc = mticker.LogitLocator()
+ vmin, vmax = (-1, okval)
+ vmin2, vmax2 = loc.nonsingular(vmin, vmax)
+ assert vmax2 == vmax
+ assert 0 < vmin2 < vmax2
+ vmin, vmax = (okval, 2)
+ vmin2, vmax2 = loc.nonsingular(vmin, vmax)
+ assert vmin2 == vmin
+ assert vmin2 < vmax2 < 1
+
+
+class TestFixedLocator:
+ def test_set_params(self):
+ """
+ Create fixed locator with 5 nbins, and change it to something else.
+ See if change was successful.
+ Should not exception.
+ """
+ fixed = mticker.FixedLocator(range(0, 24), nbins=5)
+ fixed.set_params(nbins=7)
+ assert fixed.nbins == 7
+
+
+class TestIndexLocator:
+ def test_set_params(self):
+ """
+ Create index locator with 3 base, 4 offset. and change it to something
+ else. See if change was successful.
+ Should not exception.
+ """
+ index = mticker.IndexLocator(base=3, offset=4)
+ index.set_params(base=7, offset=7)
+ assert index._base == 7
+ assert index.offset == 7
+
+
+class TestSymmetricalLogLocator:
+ def test_set_params(self):
+ """
+ Create symmetrical log locator with default subs =[1.0] numticks = 15,
+ and change it to something else.
+ See if change was successful.
+ Should not exception.
+ """
+ sym = mticker.SymmetricalLogLocator(base=10, linthresh=1)
+ sym.set_params(subs=[2.0], numticks=8)
+ assert sym._subs == [2.0]
+ assert sym.numticks == 8
+
+ @pytest.mark.parametrize(
+ 'vmin, vmax, expected',
+ [
+ (0, 1, [0, 1]),
+ (-1, 1, [-1, 0, 1]),
+ ],
+ )
+ def test_values(self, vmin, vmax, expected):
+ # https://github.com/matplotlib/matplotlib/issues/25945
+ sym = mticker.SymmetricalLogLocator(base=10, linthresh=1)
+ ticks = sym.tick_values(vmin=vmin, vmax=vmax)
+ assert_array_equal(ticks, expected)
+
+ def test_subs(self):
+ sym = mticker.SymmetricalLogLocator(base=10, linthresh=1, subs=[2.0, 4.0])
+ sym.create_dummy_axis()
+ sym.axis.set_view_interval(-10, 10)
+ assert_array_equal(sym(), [-20, -40, -2, -4, 0, 2, 4, 20, 40])
+
+ def test_extending(self):
+ sym = mticker.SymmetricalLogLocator(base=10, linthresh=1)
+ sym.create_dummy_axis()
+ sym.axis.set_view_interval(8, 9)
+ assert (sym() == [1.0]).all()
+ sym.axis.set_view_interval(8, 12)
+ assert (sym() == [1.0, 10.0]).all()
+ assert sym.view_limits(10, 10) == (1, 100)
+ assert sym.view_limits(-10, -10) == (-100, -1)
+ assert sym.view_limits(0, 0) == (-0.001, 0.001)
+
+
+class TestAsinhLocator:
+ def test_init(self):
+ lctr = mticker.AsinhLocator(linear_width=2.718, numticks=19)
+ assert lctr.linear_width == 2.718
+ assert lctr.numticks == 19
+ assert lctr.base == 10
+
+ def test_set_params(self):
+ lctr = mticker.AsinhLocator(linear_width=5,
+ numticks=17, symthresh=0.125,
+ base=4, subs=(2.5, 3.25))
+ assert lctr.numticks == 17
+ assert lctr.symthresh == 0.125
+ assert lctr.base == 4
+ assert lctr.subs == (2.5, 3.25)
+
+ lctr.set_params(numticks=23)
+ assert lctr.numticks == 23
+ lctr.set_params(None)
+ assert lctr.numticks == 23
+
+ lctr.set_params(symthresh=0.5)
+ assert lctr.symthresh == 0.5
+ lctr.set_params(symthresh=None)
+ assert lctr.symthresh == 0.5
+
+ lctr.set_params(base=7)
+ assert lctr.base == 7
+ lctr.set_params(base=None)
+ assert lctr.base == 7
+
+ lctr.set_params(subs=(2, 4.125))
+ assert lctr.subs == (2, 4.125)
+ lctr.set_params(subs=None)
+ assert lctr.subs == (2, 4.125)
+ lctr.set_params(subs=[])
+ assert lctr.subs is None
+
+ def test_linear_values(self):
+ lctr = mticker.AsinhLocator(linear_width=100, numticks=11, base=0)
+
+ assert_almost_equal(lctr.tick_values(-1, 1),
+ np.arange(-1, 1.01, 0.2))
+ assert_almost_equal(lctr.tick_values(-0.1, 0.1),
+ np.arange(-0.1, 0.101, 0.02))
+ assert_almost_equal(lctr.tick_values(-0.01, 0.01),
+ np.arange(-0.01, 0.0101, 0.002))
+
+ def test_wide_values(self):
+ lctr = mticker.AsinhLocator(linear_width=0.1, numticks=11, base=0)
+
+ assert_almost_equal(lctr.tick_values(-100, 100),
+ [-100, -20, -5, -1, -0.2,
+ 0, 0.2, 1, 5, 20, 100])
+ assert_almost_equal(lctr.tick_values(-1000, 1000),
+ [-1000, -100, -20, -3, -0.4,
+ 0, 0.4, 3, 20, 100, 1000])
+
+ def test_near_zero(self):
+ """Check that manually injected zero will supersede nearby tick"""
+ lctr = mticker.AsinhLocator(linear_width=100, numticks=3, base=0)
+
+ assert_almost_equal(lctr.tick_values(-1.1, 0.9), [-1.0, 0.0, 0.9])
+
+ def test_fallback(self):
+ lctr = mticker.AsinhLocator(1.0, numticks=11)
+
+ assert_almost_equal(lctr.tick_values(101, 102),
+ np.arange(101, 102.01, 0.1))
+
+ def test_symmetrizing(self):
+ lctr = mticker.AsinhLocator(linear_width=1, numticks=3,
+ symthresh=0.25, base=0)
+ lctr.create_dummy_axis()
+
+ lctr.axis.set_view_interval(-1, 2)
+ assert_almost_equal(lctr(), [-1, 0, 2])
+
+ lctr.axis.set_view_interval(-1, 0.9)
+ assert_almost_equal(lctr(), [-1, 0, 1])
+
+ lctr.axis.set_view_interval(-0.85, 1.05)
+ assert_almost_equal(lctr(), [-1, 0, 1])
+
+ lctr.axis.set_view_interval(1, 1.1)
+ assert_almost_equal(lctr(), [1, 1.05, 1.1])
+
+ def test_base_rounding(self):
+ lctr10 = mticker.AsinhLocator(linear_width=1, numticks=8,
+ base=10, subs=(1, 3, 5))
+ assert_almost_equal(lctr10.tick_values(-110, 110),
+ [-500, -300, -100, -50, -30, -10, -5, -3, -1,
+ -0.5, -0.3, -0.1, 0, 0.1, 0.3, 0.5,
+ 1, 3, 5, 10, 30, 50, 100, 300, 500])
+
+ lctr5 = mticker.AsinhLocator(linear_width=1, numticks=20, base=5)
+ assert_almost_equal(lctr5.tick_values(-1050, 1050),
+ [-625, -125, -25, -5, -1, -0.2, 0,
+ 0.2, 1, 5, 25, 125, 625])
+
+
+class TestScalarFormatter:
+ offset_data = [
+ (123, 189, 0),
+ (-189, -123, 0),
+ (12341, 12349, 12340),
+ (-12349, -12341, -12340),
+ (99999.5, 100010.5, 100000),
+ (-100010.5, -99999.5, -100000),
+ (99990.5, 100000.5, 100000),
+ (-100000.5, -99990.5, -100000),
+ (1233999, 1234001, 1234000),
+ (-1234001, -1233999, -1234000),
+ (1, 1, 1),
+ (123, 123, 0),
+ # Test cases courtesy of @WeatherGod
+ (.4538, .4578, .45),
+ (3789.12, 3783.1, 3780),
+ (45124.3, 45831.75, 45000),
+ (0.000721, 0.0007243, 0.00072),
+ (12592.82, 12591.43, 12590),
+ (9., 12., 0),
+ (900., 1200., 0),
+ (1900., 1200., 0),
+ (0.99, 1.01, 1),
+ (9.99, 10.01, 10),
+ (99.99, 100.01, 100),
+ (5.99, 6.01, 6),
+ (15.99, 16.01, 16),
+ (-0.452, 0.492, 0),
+ (-0.492, 0.492, 0),
+ (12331.4, 12350.5, 12300),
+ (-12335.3, 12335.3, 0),
+ ]
+
+ use_offset_data = [True, False]
+
+ useMathText_data = [True, False]
+
+ # (sci_type, scilimits, lim, orderOfMag, fewticks)
+ scilimits_data = [
+ (False, (0, 0), (10.0, 20.0), 0, False),
+ (True, (-2, 2), (-10, 20), 0, False),
+ (True, (-2, 2), (-20, 10), 0, False),
+ (True, (-2, 2), (-110, 120), 2, False),
+ (True, (-2, 2), (-120, 110), 2, False),
+ (True, (-2, 2), (-.001, 0.002), -3, False),
+ (True, (-7, 7), (0.18e10, 0.83e10), 9, True),
+ (True, (0, 0), (-1e5, 1e5), 5, False),
+ (True, (6, 6), (-1e5, 1e5), 6, False),
+ ]
+
+ cursor_data = [
+ [0., "0.000"],
+ [0.0123, "0.012"],
+ [0.123, "0.123"],
+ [1.23, "1.230"],
+ [12.3, "12.300"],
+ ]
+
+ format_data = [
+ (.1, "1e-1"),
+ (.11, "1.1e-1"),
+ (1e8, "1e8"),
+ (1.1e8, "1.1e8"),
+ ]
+
+ @pytest.mark.parametrize('unicode_minus, result',
+ [(True, "\N{MINUS SIGN}1"), (False, "-1")])
+ def test_unicode_minus(self, unicode_minus, result):
+ mpl.rcParams['axes.unicode_minus'] = unicode_minus
+ assert (
+ plt.gca().xaxis.get_major_formatter().format_data_short(-1).strip()
+ == result)
+
+ @pytest.mark.parametrize('left, right, offset', offset_data)
+ def test_offset_value(self, left, right, offset):
+ fig, ax = plt.subplots()
+ formatter = ax.xaxis.get_major_formatter()
+
+ with (pytest.warns(UserWarning, match='Attempting to set identical')
+ if left == right else nullcontext()):
+ ax.set_xlim(left, right)
+ ax.xaxis._update_ticks()
+ assert formatter.offset == offset
+
+ with (pytest.warns(UserWarning, match='Attempting to set identical')
+ if left == right else nullcontext()):
+ ax.set_xlim(right, left)
+ ax.xaxis._update_ticks()
+ assert formatter.offset == offset
+
+ @pytest.mark.parametrize('use_offset', use_offset_data)
+ def test_use_offset(self, use_offset):
+ with mpl.rc_context({'axes.formatter.useoffset': use_offset}):
+ tmp_form = mticker.ScalarFormatter()
+ assert use_offset == tmp_form.get_useOffset()
+ assert tmp_form.offset == 0
+
+ @pytest.mark.parametrize('use_math_text', useMathText_data)
+ def test_useMathText(self, use_math_text):
+ with mpl.rc_context({'axes.formatter.use_mathtext': use_math_text}):
+ tmp_form = mticker.ScalarFormatter()
+ assert use_math_text == tmp_form.get_useMathText()
+
+ def test_set_use_offset_float(self):
+ tmp_form = mticker.ScalarFormatter()
+ tmp_form.set_useOffset(0.5)
+ assert not tmp_form.get_useOffset()
+ assert tmp_form.offset == 0.5
+
+ def test_use_locale(self):
+ conv = locale.localeconv()
+ sep = conv['thousands_sep']
+ if not sep or conv['grouping'][-1:] in ([], [locale.CHAR_MAX]):
+ pytest.skip('Locale does not apply grouping') # pragma: no cover
+
+ with mpl.rc_context({'axes.formatter.use_locale': True}):
+ tmp_form = mticker.ScalarFormatter()
+ assert tmp_form.get_useLocale()
+
+ tmp_form.create_dummy_axis()
+ tmp_form.axis.set_data_interval(0, 10)
+ tmp_form.set_locs([1, 2, 3])
+ assert sep in tmp_form(1e9)
+
+ @pytest.mark.parametrize(
+ 'sci_type, scilimits, lim, orderOfMag, fewticks', scilimits_data)
+ def test_scilimits(self, sci_type, scilimits, lim, orderOfMag, fewticks):
+ tmp_form = mticker.ScalarFormatter()
+ tmp_form.set_scientific(sci_type)
+ tmp_form.set_powerlimits(scilimits)
+ fig, ax = plt.subplots()
+ ax.yaxis.set_major_formatter(tmp_form)
+ ax.set_ylim(*lim)
+ if fewticks:
+ ax.yaxis.set_major_locator(mticker.MaxNLocator(4))
+
+ tmp_form.set_locs(ax.yaxis.get_majorticklocs())
+ assert orderOfMag == tmp_form.orderOfMagnitude
+
+ @pytest.mark.parametrize('value, expected', format_data)
+ def test_format_data(self, value, expected):
+ mpl.rcParams['axes.unicode_minus'] = False
+ sf = mticker.ScalarFormatter()
+ assert sf.format_data(value) == expected
+
+ @pytest.mark.parametrize('data, expected', cursor_data)
+ def test_cursor_precision(self, data, expected):
+ fig, ax = plt.subplots()
+ ax.set_xlim(-1, 1) # Pointing precision of 0.001.
+ fmt = ax.xaxis.get_major_formatter().format_data_short
+ assert fmt(data) == expected
+
+ @pytest.mark.parametrize('data, expected', cursor_data)
+ def test_cursor_dummy_axis(self, data, expected):
+ # Issue #17624
+ sf = mticker.ScalarFormatter()
+ sf.create_dummy_axis()
+ sf.axis.set_view_interval(0, 10)
+ fmt = sf.format_data_short
+ assert fmt(data) == expected
+ assert sf.axis.get_tick_space() == 9
+ assert sf.axis.get_minpos() == 0
+
+ def test_mathtext_ticks(self):
+ mpl.rcParams.update({
+ 'font.family': 'serif',
+ 'font.serif': 'cmr10',
+ 'axes.formatter.use_mathtext': False
+ })
+
+ if parse_version(pytest.__version__).major < 8:
+ with pytest.warns(UserWarning, match='cmr10 font should ideally'):
+ fig, ax = plt.subplots()
+ ax.set_xticks([-1, 0, 1])
+ fig.canvas.draw()
+ else:
+ with (pytest.warns(UserWarning, match="Glyph 8722"),
+ pytest.warns(UserWarning, match='cmr10 font should ideally')):
+ fig, ax = plt.subplots()
+ ax.set_xticks([-1, 0, 1])
+ fig.canvas.draw()
+
+ def test_cmr10_substitutions(self, caplog):
+ mpl.rcParams.update({
+ 'font.family': 'cmr10',
+ 'mathtext.fontset': 'cm',
+ 'axes.formatter.use_mathtext': True,
+ })
+
+ # Test that it does not log a warning about missing glyphs.
+ with caplog.at_level(logging.WARNING, logger='matplotlib.mathtext'):
+ fig, ax = plt.subplots()
+ ax.plot([-0.03, 0.05], [40, 0.05])
+ ax.set_yscale('log')
+ yticks = [0.02, 0.3, 4, 50]
+ formatter = mticker.LogFormatterSciNotation()
+ ax.set_yticks(yticks, map(formatter, yticks))
+ fig.canvas.draw()
+ assert not caplog.text
+
+ def test_empty_locs(self):
+ sf = mticker.ScalarFormatter()
+ sf.set_locs([])
+ assert sf(0.5) == ''
+
+
+class TestLogFormatterExponent:
+ param_data = [
+ (True, 4, np.arange(-3, 4.0), np.arange(-3, 4.0),
+ ['-3', '-2', '-1', '0', '1', '2', '3']),
+ # With labelOnlyBase=False, non-integer powers should be nicely
+ # formatted.
+ (False, 10, np.array([0.1, 0.00001, np.pi, 0.2, -0.2, -0.00001]),
+ range(6), ['0.1', '1e-05', '3.14', '0.2', '-0.2', '-1e-05']),
+ (False, 50, np.array([3, 5, 12, 42], dtype=float), range(6),
+ ['3', '5', '12', '42']),
+ ]
+
+ base_data = [2.0, 5.0, 10.0, np.pi, np.e]
+
+ @pytest.mark.parametrize(
+ 'labelOnlyBase, exponent, locs, positions, expected', param_data)
+ @pytest.mark.parametrize('base', base_data)
+ def test_basic(self, labelOnlyBase, base, exponent, locs, positions,
+ expected):
+ formatter = mticker.LogFormatterExponent(base=base,
+ labelOnlyBase=labelOnlyBase)
+ formatter.create_dummy_axis()
+ formatter.axis.set_view_interval(1, base**exponent)
+ vals = base**locs
+ labels = [formatter(x, pos) for (x, pos) in zip(vals, positions)]
+ expected = [label.replace('-', '\N{Minus Sign}') for label in expected]
+ assert labels == expected
+
+ def test_blank(self):
+ # Should be a blank string for non-integer powers if labelOnlyBase=True
+ formatter = mticker.LogFormatterExponent(base=10, labelOnlyBase=True)
+ formatter.create_dummy_axis()
+ formatter.axis.set_view_interval(1, 10)
+ assert formatter(10**0.1) == ''
+
+
+class TestLogFormatterMathtext:
+ fmt = mticker.LogFormatterMathtext()
+ test_data = [
+ (0, 1, '$\\mathdefault{10^{0}}$'),
+ (0, 1e-2, '$\\mathdefault{10^{-2}}$'),
+ (0, 1e2, '$\\mathdefault{10^{2}}$'),
+ (3, 1, '$\\mathdefault{1}$'),
+ (3, 1e-2, '$\\mathdefault{0.01}$'),
+ (3, 1e2, '$\\mathdefault{100}$'),
+ (3, 1e-3, '$\\mathdefault{10^{-3}}$'),
+ (3, 1e3, '$\\mathdefault{10^{3}}$'),
+ ]
+
+ @pytest.mark.parametrize('min_exponent, value, expected', test_data)
+ def test_min_exponent(self, min_exponent, value, expected):
+ with mpl.rc_context({'axes.formatter.min_exponent': min_exponent}):
+ assert self.fmt(value) == expected
+
+
+class TestLogFormatterSciNotation:
+ test_data = [
+ (2, 0.03125, '$\\mathdefault{2^{-5}}$'),
+ (2, 1, '$\\mathdefault{2^{0}}$'),
+ (2, 32, '$\\mathdefault{2^{5}}$'),
+ (2, 0.0375, '$\\mathdefault{1.2\\times2^{-5}}$'),
+ (2, 1.2, '$\\mathdefault{1.2\\times2^{0}}$'),
+ (2, 38.4, '$\\mathdefault{1.2\\times2^{5}}$'),
+ (10, -1, '$\\mathdefault{-10^{0}}$'),
+ (10, 1e-05, '$\\mathdefault{10^{-5}}$'),
+ (10, 1, '$\\mathdefault{10^{0}}$'),
+ (10, 100000, '$\\mathdefault{10^{5}}$'),
+ (10, 2e-05, '$\\mathdefault{2\\times10^{-5}}$'),
+ (10, 2, '$\\mathdefault{2\\times10^{0}}$'),
+ (10, 200000, '$\\mathdefault{2\\times10^{5}}$'),
+ (10, 5e-05, '$\\mathdefault{5\\times10^{-5}}$'),
+ (10, 5, '$\\mathdefault{5\\times10^{0}}$'),
+ (10, 500000, '$\\mathdefault{5\\times10^{5}}$'),
+ ]
+
+ @mpl.style.context('default')
+ @pytest.mark.parametrize('base, value, expected', test_data)
+ def test_basic(self, base, value, expected):
+ formatter = mticker.LogFormatterSciNotation(base=base)
+ with mpl.rc_context({'text.usetex': False}):
+ assert formatter(value) == expected
+
+
+class TestLogFormatter:
+ pprint_data = [
+ (3.141592654e-05, 0.001, '3.142e-5'),
+ (0.0003141592654, 0.001, '3.142e-4'),
+ (0.003141592654, 0.001, '3.142e-3'),
+ (0.03141592654, 0.001, '3.142e-2'),
+ (0.3141592654, 0.001, '3.142e-1'),
+ (3.141592654, 0.001, '3.142'),
+ (31.41592654, 0.001, '3.142e1'),
+ (314.1592654, 0.001, '3.142e2'),
+ (3141.592654, 0.001, '3.142e3'),
+ (31415.92654, 0.001, '3.142e4'),
+ (314159.2654, 0.001, '3.142e5'),
+ (1e-05, 0.001, '1e-5'),
+ (0.0001, 0.001, '1e-4'),
+ (0.001, 0.001, '1e-3'),
+ (0.01, 0.001, '1e-2'),
+ (0.1, 0.001, '1e-1'),
+ (1, 0.001, '1'),
+ (10, 0.001, '10'),
+ (100, 0.001, '100'),
+ (1000, 0.001, '1000'),
+ (10000, 0.001, '1e4'),
+ (100000, 0.001, '1e5'),
+ (3.141592654e-05, 0.015, '0'),
+ (0.0003141592654, 0.015, '0'),
+ (0.003141592654, 0.015, '0.003'),
+ (0.03141592654, 0.015, '0.031'),
+ (0.3141592654, 0.015, '0.314'),
+ (3.141592654, 0.015, '3.142'),
+ (31.41592654, 0.015, '31.416'),
+ (314.1592654, 0.015, '314.159'),
+ (3141.592654, 0.015, '3141.593'),
+ (31415.92654, 0.015, '31415.927'),
+ (314159.2654, 0.015, '314159.265'),
+ (1e-05, 0.015, '0'),
+ (0.0001, 0.015, '0'),
+ (0.001, 0.015, '0.001'),
+ (0.01, 0.015, '0.01'),
+ (0.1, 0.015, '0.1'),
+ (1, 0.015, '1'),
+ (10, 0.015, '10'),
+ (100, 0.015, '100'),
+ (1000, 0.015, '1000'),
+ (10000, 0.015, '10000'),
+ (100000, 0.015, '100000'),
+ (3.141592654e-05, 0.5, '0'),
+ (0.0003141592654, 0.5, '0'),
+ (0.003141592654, 0.5, '0.003'),
+ (0.03141592654, 0.5, '0.031'),
+ (0.3141592654, 0.5, '0.314'),
+ (3.141592654, 0.5, '3.142'),
+ (31.41592654, 0.5, '31.416'),
+ (314.1592654, 0.5, '314.159'),
+ (3141.592654, 0.5, '3141.593'),
+ (31415.92654, 0.5, '31415.927'),
+ (314159.2654, 0.5, '314159.265'),
+ (1e-05, 0.5, '0'),
+ (0.0001, 0.5, '0'),
+ (0.001, 0.5, '0.001'),
+ (0.01, 0.5, '0.01'),
+ (0.1, 0.5, '0.1'),
+ (1, 0.5, '1'),
+ (10, 0.5, '10'),
+ (100, 0.5, '100'),
+ (1000, 0.5, '1000'),
+ (10000, 0.5, '10000'),
+ (100000, 0.5, '100000'),
+ (3.141592654e-05, 5, '0'),
+ (0.0003141592654, 5, '0'),
+ (0.003141592654, 5, '0'),
+ (0.03141592654, 5, '0.03'),
+ (0.3141592654, 5, '0.31'),
+ (3.141592654, 5, '3.14'),
+ (31.41592654, 5, '31.42'),
+ (314.1592654, 5, '314.16'),
+ (3141.592654, 5, '3141.59'),
+ (31415.92654, 5, '31415.93'),
+ (314159.2654, 5, '314159.27'),
+ (1e-05, 5, '0'),
+ (0.0001, 5, '0'),
+ (0.001, 5, '0'),
+ (0.01, 5, '0.01'),
+ (0.1, 5, '0.1'),
+ (1, 5, '1'),
+ (10, 5, '10'),
+ (100, 5, '100'),
+ (1000, 5, '1000'),
+ (10000, 5, '10000'),
+ (100000, 5, '100000'),
+ (3.141592654e-05, 100, '0'),
+ (0.0003141592654, 100, '0'),
+ (0.003141592654, 100, '0'),
+ (0.03141592654, 100, '0'),
+ (0.3141592654, 100, '0.3'),
+ (3.141592654, 100, '3.1'),
+ (31.41592654, 100, '31.4'),
+ (314.1592654, 100, '314.2'),
+ (3141.592654, 100, '3141.6'),
+ (31415.92654, 100, '31415.9'),
+ (314159.2654, 100, '314159.3'),
+ (1e-05, 100, '0'),
+ (0.0001, 100, '0'),
+ (0.001, 100, '0'),
+ (0.01, 100, '0'),
+ (0.1, 100, '0.1'),
+ (1, 100, '1'),
+ (10, 100, '10'),
+ (100, 100, '100'),
+ (1000, 100, '1000'),
+ (10000, 100, '10000'),
+ (100000, 100, '100000'),
+ (3.141592654e-05, 1000000.0, '3.1e-5'),
+ (0.0003141592654, 1000000.0, '3.1e-4'),
+ (0.003141592654, 1000000.0, '3.1e-3'),
+ (0.03141592654, 1000000.0, '3.1e-2'),
+ (0.3141592654, 1000000.0, '3.1e-1'),
+ (3.141592654, 1000000.0, '3.1'),
+ (31.41592654, 1000000.0, '3.1e1'),
+ (314.1592654, 1000000.0, '3.1e2'),
+ (3141.592654, 1000000.0, '3.1e3'),
+ (31415.92654, 1000000.0, '3.1e4'),
+ (314159.2654, 1000000.0, '3.1e5'),
+ (1e-05, 1000000.0, '1e-5'),
+ (0.0001, 1000000.0, '1e-4'),
+ (0.001, 1000000.0, '1e-3'),
+ (0.01, 1000000.0, '1e-2'),
+ (0.1, 1000000.0, '1e-1'),
+ (1, 1000000.0, '1'),
+ (10, 1000000.0, '10'),
+ (100, 1000000.0, '100'),
+ (1000, 1000000.0, '1000'),
+ (10000, 1000000.0, '1e4'),
+ (100000, 1000000.0, '1e5'),
+ ]
+
+ @pytest.mark.parametrize('value, domain, expected', pprint_data)
+ def test_pprint(self, value, domain, expected):
+ fmt = mticker.LogFormatter()
+ label = fmt._pprint_val(value, domain)
+ assert label == expected
+
+ @pytest.mark.parametrize('value, long, short', [
+ (0.0, "0", "0"),
+ (0, "0", "0"),
+ (-1.0, "-10^0", "-1"),
+ (2e-10, "2x10^-10", "2e-10"),
+ (1e10, "10^10", "1e+10"),
+ ])
+ def test_format_data(self, value, long, short):
+ fig, ax = plt.subplots()
+ ax.set_xscale('log')
+ fmt = ax.xaxis.get_major_formatter()
+ assert fmt.format_data(value) == long
+ assert fmt.format_data_short(value) == short
+
+ def _sub_labels(self, axis, subs=()):
+ """Test whether locator marks subs to be labeled."""
+ fmt = axis.get_minor_formatter()
+ minor_tlocs = axis.get_minorticklocs()
+ fmt.set_locs(minor_tlocs)
+ coefs = minor_tlocs / 10**(np.floor(np.log10(minor_tlocs)))
+ label_expected = [round(c) in subs for c in coefs]
+ label_test = [fmt(x) != '' for x in minor_tlocs]
+ assert label_test == label_expected
+
+ @mpl.style.context('default')
+ def test_sublabel(self):
+ # test label locator
+ fig, ax = plt.subplots()
+ ax.set_xscale('log')
+ ax.xaxis.set_major_locator(mticker.LogLocator(base=10, subs=[]))
+ ax.xaxis.set_minor_locator(mticker.LogLocator(base=10,
+ subs=np.arange(2, 10)))
+ ax.xaxis.set_major_formatter(mticker.LogFormatter(labelOnlyBase=True))
+ ax.xaxis.set_minor_formatter(mticker.LogFormatter(labelOnlyBase=False))
+ # axis range above 3 decades, only bases are labeled
+ ax.set_xlim(1, 1e4)
+ fmt = ax.xaxis.get_major_formatter()
+ fmt.set_locs(ax.xaxis.get_majorticklocs())
+ show_major_labels = [fmt(x) != ''
+ for x in ax.xaxis.get_majorticklocs()]
+ assert np.all(show_major_labels)
+ self._sub_labels(ax.xaxis, subs=[])
+
+ # For the next two, if the numdec threshold in LogFormatter.set_locs
+ # were 3, then the label sub would be 3 for 2-3 decades and (2, 5)
+ # for 1-2 decades. With a threshold of 1, subs are not labeled.
+ # axis range at 2 to 3 decades
+ ax.set_xlim(1, 800)
+ self._sub_labels(ax.xaxis, subs=[])
+
+ # axis range at 1 to 2 decades
+ ax.set_xlim(1, 80)
+ self._sub_labels(ax.xaxis, subs=[])
+
+ # axis range at 0.4 to 1 decades, label subs 2, 3, 4, 6
+ ax.set_xlim(1, 8)
+ self._sub_labels(ax.xaxis, subs=[2, 3, 4, 6])
+
+ # axis range at 0 to 0.4 decades, label all
+ ax.set_xlim(0.5, 0.9)
+ self._sub_labels(ax.xaxis, subs=np.arange(2, 10, dtype=int))
+
+ @pytest.mark.parametrize('val', [1, 10, 100, 1000])
+ def test_LogFormatter_call(self, val):
+ # test _num_to_string method used in __call__
+ temp_lf = mticker.LogFormatter()
+ temp_lf.create_dummy_axis()
+ temp_lf.axis.set_view_interval(1, 10)
+ assert temp_lf(val) == str(val)
+
+ @pytest.mark.parametrize('val', [1e-323, 2e-323, 10e-323, 11e-323])
+ def test_LogFormatter_call_tiny(self, val):
+ # test coeff computation in __call__
+ temp_lf = mticker.LogFormatter()
+ temp_lf.create_dummy_axis()
+ temp_lf.axis.set_view_interval(1, 10)
+ temp_lf(val)
+
+
+class TestLogitFormatter:
+ @staticmethod
+ def logit_deformatter(string):
+ r"""
+ Parser to convert string as r'$\mathdefault{1.41\cdot10^{-4}}$' in
+ float 1.41e-4, as '0.5' or as r'$\mathdefault{\frac{1}{2}}$' in float
+ 0.5,
+ """
+ match = re.match(
+ r"[^\d]*"
+ r"(?P1-)?"
+ r"(?P\d*\.?\d*)?"
+ r"(?:\\cdot)?"
+ r"(?:10\^\{(?P-?\d*)})?"
+ r"[^\d]*$",
+ string,
+ )
+ if match:
+ comp = match["comp"] is not None
+ mantissa = float(match["mant"]) if match["mant"] else 1
+ expo = int(match["expo"]) if match["expo"] is not None else 0
+ value = mantissa * 10 ** expo
+ if match["mant"] or match["expo"] is not None:
+ if comp:
+ return 1 - value
+ return value
+ match = re.match(
+ r"[^\d]*\\frac\{(?P\d+)\}\{(?P\d+)\}[^\d]*$", string
+ )
+ if match:
+ num, deno = float(match["num"]), float(match["deno"])
+ return num / deno
+ raise ValueError("Not formatted by LogitFormatter")
+
+ @pytest.mark.parametrize(
+ "fx, x",
+ [
+ (r"STUFF0.41OTHERSTUFF", 0.41),
+ (r"STUFF1.41\cdot10^{-2}OTHERSTUFF", 1.41e-2),
+ (r"STUFF1-0.41OTHERSTUFF", 1 - 0.41),
+ (r"STUFF1-1.41\cdot10^{-2}OTHERSTUFF", 1 - 1.41e-2),
+ (r"STUFF", None),
+ (r"STUFF12.4e-3OTHERSTUFF", None),
+ ],
+ )
+ def test_logit_deformater(self, fx, x):
+ if x is None:
+ with pytest.raises(ValueError):
+ TestLogitFormatter.logit_deformatter(fx)
+ else:
+ y = TestLogitFormatter.logit_deformatter(fx)
+ assert _LogitHelper.isclose(x, y)
+
+ decade_test = sorted(
+ [10 ** (-i) for i in range(1, 10)]
+ + [1 - 10 ** (-i) for i in range(1, 10)]
+ + [1 / 2]
+ )
+
+ @pytest.mark.parametrize("x", decade_test)
+ def test_basic(self, x):
+ """
+ Test the formatted value correspond to the value for ideal ticks in
+ logit space.
+ """
+ formatter = mticker.LogitFormatter(use_overline=False)
+ formatter.set_locs(self.decade_test)
+ s = formatter(x)
+ x2 = TestLogitFormatter.logit_deformatter(s)
+ assert _LogitHelper.isclose(x, x2)
+
+ @pytest.mark.parametrize("x", (-1, -0.5, -0.1, 1.1, 1.5, 2))
+ def test_invalid(self, x):
+ """
+ Test that invalid value are formatted with empty string without
+ raising exception.
+ """
+ formatter = mticker.LogitFormatter(use_overline=False)
+ formatter.set_locs(self.decade_test)
+ s = formatter(x)
+ assert s == ""
+
+ @pytest.mark.parametrize("x", 1 / (1 + np.exp(-np.linspace(-7, 7, 10))))
+ def test_variablelength(self, x):
+ """
+ The format length should change depending on the neighbor labels.
+ """
+ formatter = mticker.LogitFormatter(use_overline=False)
+ for N in (10, 20, 50, 100, 200, 1000, 2000, 5000, 10000):
+ if x + 1 / N < 1:
+ formatter.set_locs([x - 1 / N, x, x + 1 / N])
+ sx = formatter(x)
+ sx1 = formatter(x + 1 / N)
+ d = (
+ TestLogitFormatter.logit_deformatter(sx1)
+ - TestLogitFormatter.logit_deformatter(sx)
+ )
+ assert 0 < d < 2 / N
+
+ lims_minor_major = [
+ (True, (5e-8, 1 - 5e-8), ((25, False), (75, False))),
+ (True, (5e-5, 1 - 5e-5), ((25, False), (75, True))),
+ (True, (5e-2, 1 - 5e-2), ((25, True), (75, True))),
+ (False, (0.75, 0.76, 0.77), ((7, True), (25, True), (75, True))),
+ ]
+
+ @pytest.mark.parametrize("method, lims, cases", lims_minor_major)
+ def test_minor_vs_major(self, method, lims, cases):
+ """
+ Test minor/major displays.
+ """
+
+ if method:
+ min_loc = mticker.LogitLocator(minor=True)
+ ticks = min_loc.tick_values(*lims)
+ else:
+ ticks = np.array(lims)
+ min_form = mticker.LogitFormatter(minor=True)
+ for threshold, has_minor in cases:
+ min_form.set_minor_threshold(threshold)
+ formatted = min_form.format_ticks(ticks)
+ labelled = [f for f in formatted if len(f) > 0]
+ if has_minor:
+ assert len(labelled) > 0, (threshold, has_minor)
+ else:
+ assert len(labelled) == 0, (threshold, has_minor)
+
+ def test_minor_number(self):
+ """
+ Test the parameter minor_number
+ """
+ min_loc = mticker.LogitLocator(minor=True)
+ min_form = mticker.LogitFormatter(minor=True)
+ ticks = min_loc.tick_values(5e-2, 1 - 5e-2)
+ for minor_number in (2, 4, 8, 16):
+ min_form.set_minor_number(minor_number)
+ formatted = min_form.format_ticks(ticks)
+ labelled = [f for f in formatted if len(f) > 0]
+ assert len(labelled) == minor_number
+
+ def test_use_overline(self):
+ """
+ Test the parameter use_overline
+ """
+ x = 1 - 1e-2
+ fx1 = r"$\mathdefault{1-10^{-2}}$"
+ fx2 = r"$\mathdefault{\overline{10^{-2}}}$"
+ form = mticker.LogitFormatter(use_overline=False)
+ assert form(x) == fx1
+ form.use_overline(True)
+ assert form(x) == fx2
+ form.use_overline(False)
+ assert form(x) == fx1
+
+ def test_one_half(self):
+ """
+ Test the parameter one_half
+ """
+ form = mticker.LogitFormatter()
+ assert r"\frac{1}{2}" in form(1/2)
+ form.set_one_half("1/2")
+ assert "1/2" in form(1/2)
+ form.set_one_half("one half")
+ assert "one half" in form(1/2)
+
+ @pytest.mark.parametrize("N", (100, 253, 754))
+ def test_format_data_short(self, N):
+ locs = np.linspace(0, 1, N)[1:-1]
+ form = mticker.LogitFormatter()
+ for x in locs:
+ fx = form.format_data_short(x)
+ if fx.startswith("1-"):
+ x2 = 1 - float(fx[2:])
+ else:
+ x2 = float(fx)
+ assert abs(x - x2) < 1 / N
+
+
+class TestFormatStrFormatter:
+ def test_basic(self):
+ # test % style formatter
+ tmp_form = mticker.FormatStrFormatter('%05d')
+ assert '00002' == tmp_form(2)
+
+
+class TestStrMethodFormatter:
+ test_data = [
+ ('{x:05d}', (2,), False, '00002'),
+ ('{x:05d}', (2,), True, '00002'),
+ ('{x:05d}', (-2,), False, '-0002'),
+ ('{x:05d}', (-2,), True, '\N{MINUS SIGN}0002'),
+ ('{x:03d}-{pos:02d}', (2, 1), False, '002-01'),
+ ('{x:03d}-{pos:02d}', (2, 1), True, '002-01'),
+ ('{x:03d}-{pos:02d}', (-2, 1), False, '-02-01'),
+ ('{x:03d}-{pos:02d}', (-2, 1), True, '\N{MINUS SIGN}02-01'),
+ ]
+
+ @pytest.mark.parametrize('format, input, unicode_minus, expected', test_data)
+ def test_basic(self, format, input, unicode_minus, expected):
+ with mpl.rc_context({"axes.unicode_minus": unicode_minus}):
+ fmt = mticker.StrMethodFormatter(format)
+ assert fmt(*input) == expected
+
+
+class TestEngFormatter:
+ # (unicode_minus, input, expected) where ''expected'' corresponds to the
+ # outputs respectively returned when (places=None, places=0, places=2)
+ # unicode_minus is a boolean value for the rcParam['axes.unicode_minus']
+ raw_format_data = [
+ (False, -1234.56789, ('-1.23457 k', '-1 k', '-1.23 k')),
+ (True, -1234.56789, ('\N{MINUS SIGN}1.23457 k', '\N{MINUS SIGN}1 k',
+ '\N{MINUS SIGN}1.23 k')),
+ (False, -1.23456789, ('-1.23457', '-1', '-1.23')),
+ (True, -1.23456789, ('\N{MINUS SIGN}1.23457', '\N{MINUS SIGN}1',
+ '\N{MINUS SIGN}1.23')),
+ (False, -0.123456789, ('-123.457 m', '-123 m', '-123.46 m')),
+ (True, -0.123456789, ('\N{MINUS SIGN}123.457 m', '\N{MINUS SIGN}123 m',
+ '\N{MINUS SIGN}123.46 m')),
+ (False, -0.00123456789, ('-1.23457 m', '-1 m', '-1.23 m')),
+ (True, -0.00123456789, ('\N{MINUS SIGN}1.23457 m', '\N{MINUS SIGN}1 m',
+ '\N{MINUS SIGN}1.23 m')),
+ (True, -0.0, ('0', '0', '0.00')),
+ (True, -0, ('0', '0', '0.00')),
+ (True, 0, ('0', '0', '0.00')),
+ (True, 1.23456789e-6, ('1.23457 µ', '1 µ', '1.23 µ')),
+ (True, 0.123456789, ('123.457 m', '123 m', '123.46 m')),
+ (True, 0.1, ('100 m', '100 m', '100.00 m')),
+ (True, 1, ('1', '1', '1.00')),
+ (True, 1.23456789, ('1.23457', '1', '1.23')),
+ # places=0: corner-case rounding
+ (True, 999.9, ('999.9', '1 k', '999.90')),
+ # corner-case rounding for all
+ (True, 999.9999, ('1 k', '1 k', '1.00 k')),
+ # negative corner-case
+ (False, -999.9999, ('-1 k', '-1 k', '-1.00 k')),
+ (True, -999.9999, ('\N{MINUS SIGN}1 k', '\N{MINUS SIGN}1 k',
+ '\N{MINUS SIGN}1.00 k')),
+ (True, 1000, ('1 k', '1 k', '1.00 k')),
+ (True, 1001, ('1.001 k', '1 k', '1.00 k')),
+ (True, 100001, ('100.001 k', '100 k', '100.00 k')),
+ (True, 987654.321, ('987.654 k', '988 k', '987.65 k')),
+ # OoR value (> 1000 Q)
+ (True, 1.23e33, ('1230 Q', '1230 Q', '1230.00 Q'))
+ ]
+
+ @pytest.mark.parametrize('unicode_minus, input, expected', raw_format_data)
+ def test_params(self, unicode_minus, input, expected):
+ """
+ Test the formatting of EngFormatter for various values of the 'places'
+ argument, in several cases:
+
+ 0. without a unit symbol but with a (default) space separator;
+ 1. with both a unit symbol and a (default) space separator;
+ 2. with both a unit symbol and some non default separators;
+ 3. without a unit symbol but with some non default separators.
+
+ Note that cases 2. and 3. are looped over several separator strings.
+ """
+
+ plt.rcParams['axes.unicode_minus'] = unicode_minus
+ UNIT = 's' # seconds
+ DIGITS = '0123456789' # %timeit showed 10-20% faster search than set
+
+ # Case 0: unit='' (default) and sep=' ' (default).
+ # 'expected' already corresponds to this reference case.
+ exp_outputs = expected
+ formatters = (
+ mticker.EngFormatter(), # places=None (default)
+ mticker.EngFormatter(places=0),
+ mticker.EngFormatter(places=2)
+ )
+ for _formatter, _exp_output in zip(formatters, exp_outputs):
+ assert _formatter(input) == _exp_output
+
+ # Case 1: unit=UNIT and sep=' ' (default).
+ # Append a unit symbol to the reference case.
+ # Beware of the values in [1, 1000), where there is no prefix!
+ exp_outputs = (_s + " " + UNIT if _s[-1] in DIGITS # case w/o prefix
+ else _s + UNIT for _s in expected)
+ formatters = (
+ mticker.EngFormatter(unit=UNIT), # places=None (default)
+ mticker.EngFormatter(unit=UNIT, places=0),
+ mticker.EngFormatter(unit=UNIT, places=2)
+ )
+ for _formatter, _exp_output in zip(formatters, exp_outputs):
+ assert _formatter(input) == _exp_output
+
+ # Test several non default separators: no separator, a narrow
+ # no-break space (Unicode character) and an extravagant string.
+ for _sep in ("", "\N{NARROW NO-BREAK SPACE}", "@_@"):
+ # Case 2: unit=UNIT and sep=_sep.
+ # Replace the default space separator from the reference case
+ # with the tested one `_sep` and append a unit symbol to it.
+ exp_outputs = (_s + _sep + UNIT if _s[-1] in DIGITS # no prefix
+ else _s.replace(" ", _sep) + UNIT
+ for _s in expected)
+ formatters = (
+ mticker.EngFormatter(unit=UNIT, sep=_sep), # places=None
+ mticker.EngFormatter(unit=UNIT, places=0, sep=_sep),
+ mticker.EngFormatter(unit=UNIT, places=2, sep=_sep)
+ )
+ for _formatter, _exp_output in zip(formatters, exp_outputs):
+ assert _formatter(input) == _exp_output
+
+ # Case 3: unit='' (default) and sep=_sep.
+ # Replace the default space separator from the reference case
+ # with the tested one `_sep`. Reference case is already unitless.
+ exp_outputs = (_s.replace(" ", _sep) for _s in expected)
+ formatters = (
+ mticker.EngFormatter(sep=_sep), # places=None (default)
+ mticker.EngFormatter(places=0, sep=_sep),
+ mticker.EngFormatter(places=2, sep=_sep)
+ )
+ for _formatter, _exp_output in zip(formatters, exp_outputs):
+ assert _formatter(input) == _exp_output
+
+
+def test_engformatter_usetex_useMathText():
+ fig, ax = plt.subplots()
+ ax.plot([0, 500, 1000], [0, 500, 1000])
+ ax.set_xticks([0, 500, 1000])
+ for formatter in (mticker.EngFormatter(usetex=True),
+ mticker.EngFormatter(useMathText=True)):
+ ax.xaxis.set_major_formatter(formatter)
+ fig.canvas.draw()
+ x_tick_label_text = [labl.get_text() for labl in ax.get_xticklabels()]
+ # Checking if the dollar `$` signs have been inserted around numbers
+ # in tick labels.
+ assert x_tick_label_text == ['$0$', '$500$', '$1$ k']
+
+
+@pytest.mark.parametrize(
+ 'data_offset, noise, oom_center_desired, oom_noise_desired', [
+ (271_490_000_000.0, 10, 9, 0),
+ (27_149_000_000_000.0, 10_000_000, 12, 6),
+ (27.149, 0.01, 0, -3),
+ (2_714.9, 0.01, 3, -3),
+ (271_490.0, 0.001, 3, -3),
+ (271.49, 0.001, 0, -3),
+ # The following sets of parameters demonstrates that when
+ # oom(data_offset)-1 and oom(noise)-2 equal a standard 3*N oom, we get
+ # that oom_noise_desired < oom(noise)
+ (27_149_000_000.0, 100, 9, +3),
+ (27.149, 1e-07, 0, -6),
+ (271.49, 0.0001, 0, -3),
+ (27.149, 0.0001, 0, -3),
+ # Tests where oom(data_offset) <= oom(noise), those are probably
+ # covered by the part where formatter.offset != 0
+ (27_149.0, 10_000, 0, 3),
+ (27.149, 10_000, 0, 3),
+ (27.149, 1_000, 0, 3),
+ (27.149, 100, 0, 0),
+ (27.149, 10, 0, 0),
+ ]
+)
+def test_engformatter_offset_oom(
+ data_offset,
+ noise,
+ oom_center_desired,
+ oom_noise_desired
+):
+ UNIT = "eV"
+ fig, ax = plt.subplots()
+ ydata = data_offset + np.arange(-5, 7, dtype=float)*noise
+ ax.plot(ydata)
+ formatter = mticker.EngFormatter(useOffset=True, unit=UNIT)
+ # So that offset strings will always have the same size
+ formatter.ENG_PREFIXES[0] = "_"
+ ax.yaxis.set_major_formatter(formatter)
+ fig.canvas.draw()
+ offset_got = formatter.get_offset()
+ ticks_got = [labl.get_text() for labl in ax.get_yticklabels()]
+ # Predicting whether offset should be 0 or not is essentially testing
+ # ScalarFormatter._compute_offset . This function is pretty complex and it
+ # would be nice to test it, but this is out of scope for this test which
+ # only makes sure that offset text and the ticks gets the correct unit
+ # prefixes and the ticks.
+ if formatter.offset:
+ prefix_noise_got = offset_got[2]
+ prefix_noise_desired = formatter.ENG_PREFIXES[oom_noise_desired]
+ prefix_center_got = offset_got[-1-len(UNIT)]
+ prefix_center_desired = formatter.ENG_PREFIXES[oom_center_desired]
+ assert prefix_noise_desired == prefix_noise_got
+ assert prefix_center_desired == prefix_center_got
+ # Make sure the ticks didn't get the UNIT
+ for tick in ticks_got:
+ assert UNIT not in tick
+ else:
+ assert oom_center_desired == 0
+ assert offset_got == ""
+ # Make sure the ticks contain now the prefixes
+ for tick in ticks_got:
+ # 0 is zero on all orders of magnitudes, no matter what is
+ # oom_noise_desired
+ prefix_idx = 0 if tick[0] == "0" else oom_noise_desired
+ assert tick.endswith(formatter.ENG_PREFIXES[prefix_idx] + UNIT)
+
+
+class TestPercentFormatter:
+ percent_data = [
+ # Check explicitly set decimals over different intervals and values
+ (100, 0, '%', 120, 100, '120%'),
+ (100, 0, '%', 100, 90, '100%'),
+ (100, 0, '%', 90, 50, '90%'),
+ (100, 0, '%', -1.7, 40, '-2%'),
+ (100, 1, '%', 90.0, 100, '90.0%'),
+ (100, 1, '%', 80.1, 90, '80.1%'),
+ (100, 1, '%', 70.23, 50, '70.2%'),
+ # 60.554 instead of 60.55: see https://bugs.python.org/issue5118
+ (100, 1, '%', -60.554, 40, '-60.6%'),
+ # Check auto decimals over different intervals and values
+ (100, None, '%', 95, 1, '95.00%'),
+ (1.0, None, '%', 3, 6, '300%'),
+ (17.0, None, '%', 1, 8.5, '6%'),
+ (17.0, None, '%', 1, 8.4, '5.9%'),
+ (5, None, '%', -100, 0.000001, '-2000.00000%'),
+ # Check percent symbol
+ (1.0, 2, None, 1.2, 100, '120.00'),
+ (75, 3, '', 50, 100, '66.667'),
+ (42, None, '^^Foobar$$', 21, 12, '50.0^^Foobar$$'),
+ ]
+
+ percent_ids = [
+ # Check explicitly set decimals over different intervals and values
+ 'decimals=0, x>100%',
+ 'decimals=0, x=100%',
+ 'decimals=0, x<100%',
+ 'decimals=0, x<0%',
+ 'decimals=1, x>100%',
+ 'decimals=1, x=100%',
+ 'decimals=1, x<100%',
+ 'decimals=1, x<0%',
+ # Check auto decimals over different intervals and values
+ 'autodecimal, x<100%, display_range=1',
+ 'autodecimal, x>100%, display_range=6 (custom xmax test)',
+ 'autodecimal, x<100%, display_range=8.5 (autodecimal test 1)',
+ 'autodecimal, x<100%, display_range=8.4 (autodecimal test 2)',
+ 'autodecimal, x<-100%, display_range=1e-6 (tiny display range)',
+ # Check percent symbol
+ 'None as percent symbol',
+ 'Empty percent symbol',
+ 'Custom percent symbol',
+ ]
+
+ latex_data = [
+ (False, False, r'50\{t}%'),
+ (False, True, r'50\\\{t\}\%'),
+ (True, False, r'50\{t}%'),
+ (True, True, r'50\{t}%'),
+ ]
+
+ @pytest.mark.parametrize(
+ 'xmax, decimals, symbol, x, display_range, expected',
+ percent_data, ids=percent_ids)
+ def test_basic(self, xmax, decimals, symbol,
+ x, display_range, expected):
+ formatter = mticker.PercentFormatter(xmax, decimals, symbol)
+ with mpl.rc_context(rc={'text.usetex': False}):
+ assert formatter.format_pct(x, display_range) == expected
+
+ @pytest.mark.parametrize('is_latex, usetex, expected', latex_data)
+ def test_latex(self, is_latex, usetex, expected):
+ fmt = mticker.PercentFormatter(symbol='\\{t}%', is_latex=is_latex)
+ with mpl.rc_context(rc={'text.usetex': usetex}):
+ assert fmt.format_pct(50, 100) == expected
+
+
+def _impl_locale_comma():
+ try:
+ locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')
+ except locale.Error:
+ print('SKIP: Locale de_DE.UTF-8 is not supported on this machine')
+ return
+ ticks = mticker.ScalarFormatter(useMathText=True, useLocale=True)
+ fmt = '$\\mathdefault{%1.1f}$'
+ x = ticks._format_maybe_minus_and_locale(fmt, 0.5)
+ assert x == '$\\mathdefault{0{,}5}$'
+ # Do not change , in the format string
+ fmt = ',$\\mathdefault{,%1.1f},$'
+ x = ticks._format_maybe_minus_and_locale(fmt, 0.5)
+ assert x == ',$\\mathdefault{,0{,}5},$'
+ # Make sure no brackets are added if not using math text
+ ticks = mticker.ScalarFormatter(useMathText=False, useLocale=True)
+ fmt = '%1.1f'
+ x = ticks._format_maybe_minus_and_locale(fmt, 0.5)
+ assert x == '0,5'
+
+
+def test_locale_comma():
+ # On some systems/pytest versions, `pytest.skip` in an exception handler
+ # does not skip, but is treated as an exception, so directly running this
+ # test can incorrectly fail instead of skip.
+ # Instead, run this test in a subprocess, which avoids the problem, and the
+ # need to fix the locale after.
+ proc = mpl.testing.subprocess_run_helper(_impl_locale_comma, timeout=60,
+ extra_env={'MPLBACKEND': 'Agg'})
+ skip_msg = next((line[len('SKIP:'):].strip()
+ for line in proc.stdout.splitlines()
+ if line.startswith('SKIP:')),
+ '')
+ if skip_msg:
+ pytest.skip(skip_msg)
+
+
+def test_majformatter_type():
+ fig, ax = plt.subplots()
+ with pytest.raises(TypeError):
+ ax.xaxis.set_major_formatter(mticker.LogLocator())
+
+
+def test_minformatter_type():
+ fig, ax = plt.subplots()
+ with pytest.raises(TypeError):
+ ax.xaxis.set_minor_formatter(mticker.LogLocator())
+
+
+def test_majlocator_type():
+ fig, ax = plt.subplots()
+ with pytest.raises(TypeError):
+ ax.xaxis.set_major_locator(mticker.LogFormatter())
+
+
+def test_minlocator_type():
+ fig, ax = plt.subplots()
+ with pytest.raises(TypeError):
+ ax.xaxis.set_minor_locator(mticker.LogFormatter())
+
+
+def test_minorticks_rc():
+ fig = plt.figure()
+
+ def minorticksubplot(xminor, yminor, i):
+ rc = {'xtick.minor.visible': xminor,
+ 'ytick.minor.visible': yminor}
+ with plt.rc_context(rc=rc):
+ ax = fig.add_subplot(2, 2, i)
+
+ assert (len(ax.xaxis.get_minor_ticks()) > 0) == xminor
+ assert (len(ax.yaxis.get_minor_ticks()) > 0) == yminor
+
+ minorticksubplot(False, False, 1)
+ minorticksubplot(True, False, 2)
+ minorticksubplot(False, True, 3)
+ minorticksubplot(True, True, 4)
+
+
+def test_minorticks_toggle():
+ """
+ Test toggling minor ticks
+
+ Test `.Axis.minorticks_on()` and `.Axis.minorticks_off()`. Testing is
+ limited to a subset of built-in scales - `'linear'`, `'log'`, `'asinh'`
+ and `'logit'`. `symlog` scale does not seem to have a working minor
+ locator and is omitted. In future, this test should cover all scales in
+ `matplotlib.scale.get_scale_names()`.
+ """
+ fig = plt.figure()
+ def minortickstoggle(xminor, yminor, scale, i):
+ ax = fig.add_subplot(2, 2, i)
+ ax.set_xscale(scale)
+ ax.set_yscale(scale)
+ if not xminor and not yminor:
+ ax.minorticks_off()
+ if xminor and not yminor:
+ ax.xaxis.minorticks_on()
+ ax.yaxis.minorticks_off()
+ if not xminor and yminor:
+ ax.xaxis.minorticks_off()
+ ax.yaxis.minorticks_on()
+ if xminor and yminor:
+ ax.minorticks_on()
+
+ assert (len(ax.xaxis.get_minor_ticks()) > 0) == xminor
+ assert (len(ax.yaxis.get_minor_ticks()) > 0) == yminor
+
+ scales = ['linear', 'log', 'asinh', 'logit']
+ for scale in scales:
+ minortickstoggle(False, False, scale, 1)
+ minortickstoggle(True, False, scale, 2)
+ minortickstoggle(False, True, scale, 3)
+ minortickstoggle(True, True, scale, 4)
+ fig.clear()
+
+ plt.close(fig)
+
+
+@pytest.mark.parametrize('remove_overlapping_locs, expected_num',
+ ((True, 6),
+ (None, 6), # this tests the default
+ (False, 9)))
+def test_remove_overlap(remove_overlapping_locs, expected_num):
+ t = np.arange("2018-11-03", "2018-11-06", dtype="datetime64")
+ x = np.ones(len(t))
+
+ fig, ax = plt.subplots()
+ ax.plot(t, x)
+
+ ax.xaxis.set_major_locator(mpl.dates.DayLocator())
+ ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('\n%a'))
+
+ ax.xaxis.set_minor_locator(mpl.dates.HourLocator((0, 6, 12, 18)))
+ ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%H:%M'))
+ # force there to be extra ticks
+ ax.xaxis.get_minor_ticks(15)
+ if remove_overlapping_locs is not None:
+ ax.xaxis.remove_overlapping_locs = remove_overlapping_locs
+
+ # check that getter/setter exists
+ current = ax.xaxis.remove_overlapping_locs
+ assert (current == ax.xaxis.get_remove_overlapping_locs())
+ plt.setp(ax.xaxis, remove_overlapping_locs=current)
+ new = ax.xaxis.remove_overlapping_locs
+ assert (new == ax.xaxis.remove_overlapping_locs)
+
+ # check that the accessors filter correctly
+ # this is the method that does the actual filtering
+ assert len(ax.xaxis.get_minorticklocs()) == expected_num
+ # these three are derivative
+ assert len(ax.xaxis.get_minor_ticks()) == expected_num
+ assert len(ax.xaxis.get_minorticklabels()) == expected_num
+ assert len(ax.xaxis.get_minorticklines()) == expected_num*2
+
+
+@pytest.mark.parametrize('sub', [
+ ['hi', 'aardvark'],
+ np.zeros((2, 2))])
+def test_bad_locator_subs(sub):
+ ll = mticker.LogLocator()
+ with pytest.raises(ValueError):
+ ll.set_params(subs=sub)
+
+
+@pytest.mark.parametrize('numticks', [1, 2, 3, 9])
+@mpl.style.context('default')
+def test_small_range_loglocator(numticks):
+ ll = mticker.LogLocator()
+ ll.set_params(numticks=numticks)
+ for top in [5, 7, 9, 11, 15, 50, 100, 1000]:
+ ticks = ll.tick_values(.5, top)
+ assert (np.diff(np.log10(ll.tick_values(6, 150))) == 1).all()
+
+
+def test_NullFormatter():
+ formatter = mticker.NullFormatter()
+ assert formatter(1.0) == ''
+ assert formatter.format_data(1.0) == ''
+ assert formatter.format_data_short(1.0) == ''
+
+
+@pytest.mark.parametrize('formatter', (
+ mticker.FuncFormatter(lambda a: f'val: {a}'),
+ mticker.FixedFormatter(('foo', 'bar'))))
+def test_set_offset_string(formatter):
+ assert formatter.get_offset() == ''
+ formatter.set_offset_string('mpl')
+ assert formatter.get_offset() == 'mpl'
+
+
+def test_minorticks_on_multi_fig():
+ """
+ Turning on minor gridlines in a multi-Axes Figure
+ that contains more than one boxplot and shares the x-axis
+ should not raise an exception.
+ """
+ fig, ax = plt.subplots()
+
+ ax.boxplot(np.arange(10), positions=[0])
+ ax.boxplot(np.arange(10), positions=[0])
+ ax.boxplot(np.arange(10), positions=[1])
+
+ ax.grid(which="major")
+ ax.grid(which="minor")
+ ax.minorticks_on()
+ fig.draw_without_rendering()
+
+ assert ax.get_xgridlines()
+ assert isinstance(ax.xaxis.get_minor_locator(), mpl.ticker.AutoMinorLocator)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_tightlayout.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_tightlayout.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6b6d8f644cc50537a7466fa7548a97833033e48
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_tightlayout.py
@@ -0,0 +1,430 @@
+import warnings
+
+import numpy as np
+from numpy.testing import assert_array_equal
+import pytest
+
+import matplotlib as mpl
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.pyplot as plt
+from matplotlib.offsetbox import AnchoredOffsetbox, DrawingArea
+from matplotlib.patches import Rectangle
+
+
+pytestmark = [
+ pytest.mark.usefixtures('text_placeholders')
+]
+
+
+def example_plot(ax, fontsize=12):
+ ax.plot([1, 2])
+ ax.locator_params(nbins=3)
+ ax.set_xlabel('x-label', fontsize=fontsize)
+ ax.set_ylabel('y-label', fontsize=fontsize)
+ ax.set_title('Title', fontsize=fontsize)
+
+
+@image_comparison(['tight_layout1'], style='mpl20')
+def test_tight_layout1():
+ """Test tight_layout for a single subplot."""
+ fig, ax = plt.subplots()
+ example_plot(ax, fontsize=24)
+ plt.tight_layout()
+
+
+@image_comparison(['tight_layout2'], style='mpl20')
+def test_tight_layout2():
+ """Test tight_layout for multiple subplots."""
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
+ example_plot(ax1)
+ example_plot(ax2)
+ example_plot(ax3)
+ example_plot(ax4)
+ plt.tight_layout()
+
+
+@image_comparison(['tight_layout3'], style='mpl20')
+def test_tight_layout3():
+ """Test tight_layout for multiple subplots."""
+ ax1 = plt.subplot(221)
+ ax2 = plt.subplot(223)
+ ax3 = plt.subplot(122)
+ example_plot(ax1)
+ example_plot(ax2)
+ example_plot(ax3)
+ plt.tight_layout()
+
+
+@image_comparison(['tight_layout4'], style='mpl20')
+def test_tight_layout4():
+ """Test tight_layout for subplot2grid."""
+ ax1 = plt.subplot2grid((3, 3), (0, 0))
+ ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2)
+ ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2)
+ ax4 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
+ example_plot(ax1)
+ example_plot(ax2)
+ example_plot(ax3)
+ example_plot(ax4)
+ plt.tight_layout()
+
+
+@image_comparison(['tight_layout5'], style='mpl20')
+def test_tight_layout5():
+ """Test tight_layout for image."""
+ ax = plt.subplot()
+ arr = np.arange(100).reshape((10, 10))
+ ax.imshow(arr, interpolation="none")
+ plt.tight_layout()
+
+
+@image_comparison(['tight_layout6'], style='mpl20')
+def test_tight_layout6():
+ """Test tight_layout for gridspec."""
+
+ # This raises warnings since tight layout cannot
+ # do this fully automatically. But the test is
+ # correct since the layout is manually edited
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ fig = plt.figure()
+
+ gs1 = mpl.gridspec.GridSpec(2, 1)
+ ax1 = fig.add_subplot(gs1[0])
+ ax2 = fig.add_subplot(gs1[1])
+
+ example_plot(ax1)
+ example_plot(ax2)
+
+ gs1.tight_layout(fig, rect=[0, 0, 0.5, 1])
+
+ gs2 = mpl.gridspec.GridSpec(3, 1)
+
+ for ss in gs2:
+ ax = fig.add_subplot(ss)
+ example_plot(ax)
+ ax.set_title("")
+ ax.set_xlabel("")
+
+ ax.set_xlabel("x-label", fontsize=12)
+
+ gs2.tight_layout(fig, rect=[0.5, 0, 1, 1], h_pad=0.45)
+
+ top = min(gs1.top, gs2.top)
+ bottom = max(gs1.bottom, gs2.bottom)
+
+ gs1.tight_layout(fig, rect=[None, 0 + (bottom-gs1.bottom),
+ 0.5, 1 - (gs1.top-top)])
+ gs2.tight_layout(fig, rect=[0.5, 0 + (bottom-gs2.bottom),
+ None, 1 - (gs2.top-top)],
+ h_pad=0.45)
+
+
+@image_comparison(['tight_layout7'], style='mpl20')
+def test_tight_layout7():
+ # tight layout with left and right titles
+ fontsize = 24
+ fig, ax = plt.subplots()
+ ax.plot([1, 2])
+ ax.locator_params(nbins=3)
+ ax.set_xlabel('x-label', fontsize=fontsize)
+ ax.set_ylabel('y-label', fontsize=fontsize)
+ ax.set_title('Left Title', loc='left', fontsize=fontsize)
+ ax.set_title('Right Title', loc='right', fontsize=fontsize)
+ plt.tight_layout()
+
+
+@image_comparison(['tight_layout8'], style='mpl20', tol=0.005)
+def test_tight_layout8():
+ """Test automatic use of tight_layout."""
+ fig = plt.figure()
+ fig.set_layout_engine(layout='tight', pad=0.1)
+ ax = fig.add_subplot()
+ example_plot(ax, fontsize=24)
+ fig.draw_without_rendering()
+
+
+@image_comparison(['tight_layout9'], style='mpl20')
+def test_tight_layout9():
+ # Test tight_layout for non-visible subplots
+ # GH 8244
+ f, axarr = plt.subplots(2, 2)
+ axarr[1][1].set_visible(False)
+ plt.tight_layout()
+
+
+def test_outward_ticks():
+ """Test automatic use of tight_layout."""
+ fig = plt.figure()
+ ax = fig.add_subplot(221)
+ ax.xaxis.set_tick_params(tickdir='out', length=16, width=3)
+ ax.yaxis.set_tick_params(tickdir='out', length=16, width=3)
+ ax.xaxis.set_tick_params(
+ tickdir='out', length=32, width=3, tick1On=True, which='minor')
+ ax.yaxis.set_tick_params(
+ tickdir='out', length=32, width=3, tick1On=True, which='minor')
+ ax.xaxis.set_ticks([0], minor=True)
+ ax.yaxis.set_ticks([0], minor=True)
+ ax = fig.add_subplot(222)
+ ax.xaxis.set_tick_params(tickdir='in', length=32, width=3)
+ ax.yaxis.set_tick_params(tickdir='in', length=32, width=3)
+ ax = fig.add_subplot(223)
+ ax.xaxis.set_tick_params(tickdir='inout', length=32, width=3)
+ ax.yaxis.set_tick_params(tickdir='inout', length=32, width=3)
+ ax = fig.add_subplot(224)
+ ax.xaxis.set_tick_params(tickdir='out', length=32, width=3)
+ ax.yaxis.set_tick_params(tickdir='out', length=32, width=3)
+ plt.tight_layout()
+ # These values were obtained after visual checking that they correspond
+ # to a tight layouting that did take the ticks into account.
+ expected = [
+ [[0.092, 0.605], [0.433, 0.933]],
+ [[0.581, 0.605], [0.922, 0.933]],
+ [[0.092, 0.138], [0.433, 0.466]],
+ [[0.581, 0.138], [0.922, 0.466]],
+ ]
+ for nn, ax in enumerate(fig.axes):
+ assert_array_equal(np.round(ax.get_position().get_points(), 3),
+ expected[nn])
+
+
+def add_offsetboxes(ax, size=10, margin=.1, color='black'):
+ """
+ Surround ax with OffsetBoxes
+ """
+ m, mp = margin, 1+margin
+ anchor_points = [(-m, -m), (-m, .5), (-m, mp),
+ (.5, mp), (mp, mp), (mp, .5),
+ (mp, -m), (.5, -m)]
+ for point in anchor_points:
+ da = DrawingArea(size, size)
+ background = Rectangle((0, 0), width=size,
+ height=size,
+ facecolor=color,
+ edgecolor='None',
+ linewidth=0,
+ antialiased=False)
+ da.add_artist(background)
+
+ anchored_box = AnchoredOffsetbox(
+ loc='center',
+ child=da,
+ pad=0.,
+ frameon=False,
+ bbox_to_anchor=point,
+ bbox_transform=ax.transAxes,
+ borderpad=0.)
+ ax.add_artist(anchored_box)
+
+
+def test_tight_layout_offsetboxes():
+ # 0.
+ # - Create 4 subplots
+ # - Plot a diagonal line on them
+ # - Use tight_layout
+ #
+ # 1.
+ # - Same 4 subplots
+ # - Surround each plot with 7 boxes
+ # - Use tight_layout
+ # - See that the squares are included in the tight_layout and that the squares do
+ # not overlap
+ #
+ # 2.
+ # - Make the squares around the Axes invisible
+ # - See that the invisible squares do not affect the tight_layout
+ rows = cols = 2
+ colors = ['red', 'blue', 'green', 'yellow']
+ x = y = [0, 1]
+
+ def _subplots(with_boxes):
+ fig, axs = plt.subplots(rows, cols)
+ for ax, color in zip(axs.flat, colors):
+ ax.plot(x, y, color=color)
+ if with_boxes:
+ add_offsetboxes(ax, 20, color=color)
+ return fig, axs
+
+ # 0.
+ fig0, axs0 = _subplots(False)
+ fig0.tight_layout()
+
+ # 1.
+ fig1, axs1 = _subplots(True)
+ fig1.tight_layout()
+
+ # The AnchoredOffsetbox should be added to the bounding of the Axes, causing them to
+ # be smaller than the plain figure.
+ for ax0, ax1 in zip(axs0.flat, axs1.flat):
+ bbox0 = ax0.get_position()
+ bbox1 = ax1.get_position()
+ assert bbox1.x0 > bbox0.x0
+ assert bbox1.x1 < bbox0.x1
+ assert bbox1.y0 > bbox0.y0
+ assert bbox1.y1 < bbox0.y1
+
+ # No AnchoredOffsetbox should overlap with another.
+ bboxes = []
+ for ax1 in axs1.flat:
+ for child in ax1.get_children():
+ if not isinstance(child, AnchoredOffsetbox):
+ continue
+ bbox = child.get_window_extent()
+ for other_bbox in bboxes:
+ assert not bbox.overlaps(other_bbox)
+ bboxes.append(bbox)
+
+ # 2.
+ fig2, axs2 = _subplots(True)
+ for ax in axs2.flat:
+ for child in ax.get_children():
+ if isinstance(child, AnchoredOffsetbox):
+ child.set_visible(False)
+ fig2.tight_layout()
+ # The invisible AnchoredOffsetbox should not count for tight layout, so it should
+ # look the same as when they were never added.
+ for ax0, ax2 in zip(axs0.flat, axs2.flat):
+ bbox0 = ax0.get_position()
+ bbox2 = ax2.get_position()
+ assert_array_equal(bbox2.get_points(), bbox0.get_points())
+
+
+def test_empty_layout():
+ """Test that tight layout doesn't cause an error when there are no Axes."""
+ fig = plt.gcf()
+ fig.tight_layout()
+
+
+@pytest.mark.parametrize("label", ["xlabel", "ylabel"])
+def test_verybig_decorators(label):
+ """Test that no warning emitted when xlabel/ylabel too big."""
+ fig, ax = plt.subplots(figsize=(3, 2))
+ ax.set(**{label: 'a' * 100})
+
+
+def test_big_decorators_horizontal():
+ """Test that doesn't warn when xlabel too big."""
+ fig, axs = plt.subplots(1, 2, figsize=(3, 2))
+ axs[0].set_xlabel('a' * 30)
+ axs[1].set_xlabel('b' * 30)
+
+
+def test_big_decorators_vertical():
+ """Test that doesn't warn when ylabel too big."""
+ fig, axs = plt.subplots(2, 1, figsize=(3, 2))
+ axs[0].set_ylabel('a' * 20)
+ axs[1].set_ylabel('b' * 20)
+
+
+def test_badsubplotgrid():
+ # test that we get warning for mismatched subplot grids, not than an error
+ plt.subplot2grid((4, 5), (0, 0))
+ # this is the bad entry:
+ plt.subplot2grid((5, 5), (0, 3), colspan=3, rowspan=5)
+ with pytest.warns(UserWarning):
+ plt.tight_layout()
+
+
+def test_collapsed():
+ # test that if the amount of space required to make all the axes
+ # decorations fit would mean that the actual Axes would end up with size
+ # zero (i.e. margins add up to more than the available width) that a call
+ # to tight_layout will not get applied:
+ fig, ax = plt.subplots(tight_layout=True)
+ ax.set_xlim([0, 1])
+ ax.set_ylim([0, 1])
+
+ ax.annotate('BIG LONG STRING', xy=(1.25, 2), xytext=(10.5, 1.75),
+ annotation_clip=False)
+ p1 = ax.get_position()
+ with pytest.warns(UserWarning):
+ plt.tight_layout()
+ p2 = ax.get_position()
+ assert p1.width == p2.width
+ # test that passing a rect doesn't crash...
+ with pytest.warns(UserWarning):
+ plt.tight_layout(rect=[0, 0, 0.8, 0.8])
+
+
+def test_suptitle():
+ fig, ax = plt.subplots(tight_layout=True)
+ st = fig.suptitle("foo")
+ t = ax.set_title("bar")
+ fig.canvas.draw()
+ assert st.get_window_extent().y0 > t.get_window_extent().y1
+
+
+@pytest.mark.backend("pdf")
+def test_non_agg_renderer(monkeypatch, recwarn):
+ unpatched_init = mpl.backend_bases.RendererBase.__init__
+
+ def __init__(self, *args, **kwargs):
+ # Check that we don't instantiate any other renderer than a pdf
+ # renderer to perform pdf tight layout.
+ assert isinstance(self, mpl.backends.backend_pdf.RendererPdf)
+ unpatched_init(self, *args, **kwargs)
+
+ monkeypatch.setattr(mpl.backend_bases.RendererBase, "__init__", __init__)
+ fig, ax = plt.subplots()
+ fig.tight_layout()
+
+
+def test_manual_colorbar():
+ # This should warn, but not raise
+ fig, axes = plt.subplots(1, 2)
+ pts = axes[1].scatter([0, 1], [0, 1], c=[1, 5])
+ ax_rect = axes[1].get_position()
+ cax = fig.add_axes(
+ [ax_rect.x1 + 0.005, ax_rect.y0, 0.015, ax_rect.height]
+ )
+ fig.colorbar(pts, cax=cax)
+ with pytest.warns(UserWarning, match="This figure includes Axes"):
+ fig.tight_layout()
+
+
+def test_clipped_to_axes():
+ # Ensure that _fully_clipped_to_axes() returns True under default
+ # conditions for all projection types. Axes.get_tightbbox()
+ # uses this to skip artists in layout calculations.
+ arr = np.arange(100).reshape((10, 10))
+ fig = plt.figure(figsize=(6, 2))
+ ax1 = fig.add_subplot(131, projection='rectilinear')
+ ax2 = fig.add_subplot(132, projection='mollweide')
+ ax3 = fig.add_subplot(133, projection='polar')
+ for ax in (ax1, ax2, ax3):
+ # Default conditions (clipped by ax.bbox or ax.patch)
+ ax.grid(False)
+ h, = ax.plot(arr[:, 0])
+ m = ax.pcolor(arr)
+ assert h._fully_clipped_to_axes()
+ assert m._fully_clipped_to_axes()
+ # Non-default conditions (not clipped by ax.patch)
+ rect = Rectangle((0, 0), 0.5, 0.5, transform=ax.transAxes)
+ h.set_clip_path(rect)
+ m.set_clip_path(rect.get_path(), rect.get_transform())
+ assert not h._fully_clipped_to_axes()
+ assert not m._fully_clipped_to_axes()
+
+
+def test_tight_pads():
+ fig, ax = plt.subplots()
+ with pytest.warns(PendingDeprecationWarning,
+ match='will be deprecated'):
+ fig.set_tight_layout({'pad': 0.15})
+ fig.draw_without_rendering()
+
+
+def test_tight_kwargs():
+ fig, ax = plt.subplots(tight_layout={'pad': 0.15})
+ fig.draw_without_rendering()
+
+
+def test_tight_toggle():
+ fig, ax = plt.subplots()
+ with pytest.warns(PendingDeprecationWarning):
+ fig.set_tight_layout(True)
+ assert fig.get_tight_layout()
+ fig.set_tight_layout(False)
+ assert not fig.get_tight_layout()
+ fig.set_tight_layout(True)
+ assert fig.get_tight_layout()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_transforms.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_transforms.py
new file mode 100644
index 0000000000000000000000000000000000000000..96e78b6828f8d061ef61826678a7d5c91df8d884
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_transforms.py
@@ -0,0 +1,1131 @@
+import copy
+
+import numpy as np
+from numpy.testing import (assert_allclose, assert_almost_equal,
+ assert_array_equal, assert_array_almost_equal)
+import pytest
+
+from matplotlib import scale
+import matplotlib.pyplot as plt
+import matplotlib.patches as mpatches
+import matplotlib.transforms as mtransforms
+from matplotlib.transforms import Affine2D, Bbox, TransformedBbox, _ScaledRotation
+from matplotlib.path import Path
+from matplotlib.testing.decorators import image_comparison, check_figures_equal
+from unittest.mock import MagicMock
+
+
+class TestAffine2D:
+ single_point = [1.0, 1.0]
+ multiple_points = [[0.0, 2.0], [3.0, 3.0], [4.0, 0.0]]
+ pivot = single_point
+
+ def test_init(self):
+ Affine2D([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
+ Affine2D(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], int))
+ Affine2D(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], float))
+
+ def test_values(self):
+ np.random.seed(19680801)
+ values = np.random.random(6)
+ assert_array_equal(Affine2D.from_values(*values).to_values(), values)
+
+ def test_modify_inplace(self):
+ # Some polar transforms require modifying the matrix in place.
+ trans = Affine2D()
+ mtx = trans.get_matrix()
+ mtx[0, 0] = 42
+ assert_array_equal(trans.get_matrix(), [[42, 0, 0], [0, 1, 0], [0, 0, 1]])
+
+ def test_clear(self):
+ a = Affine2D(np.random.rand(3, 3) + 5) # Anything non-identity.
+ a.clear()
+ assert_array_equal(a.get_matrix(), [[1, 0, 0], [0, 1, 0], [0, 0, 1]])
+
+ def test_rotate(self):
+ r_pi_2 = Affine2D().rotate(np.pi / 2)
+ r90 = Affine2D().rotate_deg(90)
+ assert_array_equal(r_pi_2.get_matrix(), r90.get_matrix())
+ assert_array_almost_equal(r90.transform(self.single_point), [-1, 1])
+ assert_array_almost_equal(r90.transform(self.multiple_points),
+ [[-2, 0], [-3, 3], [0, 4]])
+
+ r_pi = Affine2D().rotate(np.pi)
+ r180 = Affine2D().rotate_deg(180)
+ assert_array_equal(r_pi.get_matrix(), r180.get_matrix())
+ assert_array_almost_equal(r180.transform(self.single_point), [-1, -1])
+ assert_array_almost_equal(r180.transform(self.multiple_points),
+ [[0, -2], [-3, -3], [-4, 0]])
+
+ r_pi_3_2 = Affine2D().rotate(3 * np.pi / 2)
+ r270 = Affine2D().rotate_deg(270)
+ assert_array_equal(r_pi_3_2.get_matrix(), r270.get_matrix())
+ assert_array_almost_equal(r270.transform(self.single_point), [1, -1])
+ assert_array_almost_equal(r270.transform(self.multiple_points),
+ [[2, 0], [3, -3], [0, -4]])
+
+ assert_array_equal((r90 + r90).get_matrix(), r180.get_matrix())
+ assert_array_equal((r90 + r180).get_matrix(), r270.get_matrix())
+
+ def test_rotate_around(self):
+ r_pi_2 = Affine2D().rotate_around(*self.pivot, np.pi / 2)
+ r90 = Affine2D().rotate_deg_around(*self.pivot, 90)
+ assert_array_equal(r_pi_2.get_matrix(), r90.get_matrix())
+ assert_array_almost_equal(r90.transform(self.single_point), [1, 1])
+ assert_array_almost_equal(r90.transform(self.multiple_points),
+ [[0, 0], [-1, 3], [2, 4]])
+
+ r_pi = Affine2D().rotate_around(*self.pivot, np.pi)
+ r180 = Affine2D().rotate_deg_around(*self.pivot, 180)
+ assert_array_equal(r_pi.get_matrix(), r180.get_matrix())
+ assert_array_almost_equal(r180.transform(self.single_point), [1, 1])
+ assert_array_almost_equal(r180.transform(self.multiple_points),
+ [[2, 0], [-1, -1], [-2, 2]])
+
+ r_pi_3_2 = Affine2D().rotate_around(*self.pivot, 3 * np.pi / 2)
+ r270 = Affine2D().rotate_deg_around(*self.pivot, 270)
+ assert_array_equal(r_pi_3_2.get_matrix(), r270.get_matrix())
+ assert_array_almost_equal(r270.transform(self.single_point), [1, 1])
+ assert_array_almost_equal(r270.transform(self.multiple_points),
+ [[2, 2], [3, -1], [0, -2]])
+
+ assert_array_almost_equal((r90 + r90).get_matrix(), r180.get_matrix())
+ assert_array_almost_equal((r90 + r180).get_matrix(), r270.get_matrix())
+
+ def test_scale(self):
+ sx = Affine2D().scale(3, 1)
+ sy = Affine2D().scale(1, -2)
+ trans = Affine2D().scale(3, -2)
+ assert_array_equal((sx + sy).get_matrix(), trans.get_matrix())
+ assert_array_equal(trans.transform(self.single_point), [3, -2])
+ assert_array_equal(trans.transform(self.multiple_points),
+ [[0, -4], [9, -6], [12, 0]])
+
+ def test_skew(self):
+ trans_rad = Affine2D().skew(np.pi / 8, np.pi / 12)
+ trans_deg = Affine2D().skew_deg(22.5, 15)
+ assert_array_equal(trans_rad.get_matrix(), trans_deg.get_matrix())
+ # Using ~atan(0.5), ~atan(0.25) produces roundish numbers on output.
+ trans = Affine2D().skew_deg(26.5650512, 14.0362435)
+ assert_array_almost_equal(trans.transform(self.single_point), [1.5, 1.25])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[1, 2], [4.5, 3.75], [4, 1]])
+
+ def test_translate(self):
+ tx = Affine2D().translate(23, 0)
+ ty = Affine2D().translate(0, 42)
+ trans = Affine2D().translate(23, 42)
+ assert_array_equal((tx + ty).get_matrix(), trans.get_matrix())
+ assert_array_equal(trans.transform(self.single_point), [24, 43])
+ assert_array_equal(trans.transform(self.multiple_points),
+ [[23, 44], [26, 45], [27, 42]])
+
+ def test_rotate_plus_other(self):
+ trans = Affine2D().rotate_deg(90).rotate_deg_around(*self.pivot, 180)
+ trans_added = (Affine2D().rotate_deg(90) +
+ Affine2D().rotate_deg_around(*self.pivot, 180))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [3, 1])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[4, 2], [5, -1], [2, -2]])
+
+ trans = Affine2D().rotate_deg(90).scale(3, -2)
+ trans_added = Affine2D().rotate_deg(90) + Affine2D().scale(3, -2)
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [-3, -2])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[-6, -0], [-9, -6], [0, -8]])
+
+ trans = (Affine2D().rotate_deg(90)
+ .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25)
+ trans_added = (Affine2D().rotate_deg(90) +
+ Affine2D().skew_deg(26.5650512, 14.0362435))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [-0.5, 0.75])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[-2, -0.5], [-1.5, 2.25], [2, 4]])
+
+ trans = Affine2D().rotate_deg(90).translate(23, 42)
+ trans_added = Affine2D().rotate_deg(90) + Affine2D().translate(23, 42)
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [22, 43])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[21, 42], [20, 45], [23, 46]])
+
+ def test_rotate_around_plus_other(self):
+ trans = Affine2D().rotate_deg_around(*self.pivot, 90).rotate_deg(180)
+ trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) +
+ Affine2D().rotate_deg(180))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [-1, -1])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[0, 0], [1, -3], [-2, -4]])
+
+ trans = Affine2D().rotate_deg_around(*self.pivot, 90).scale(3, -2)
+ trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) +
+ Affine2D().scale(3, -2))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [3, -2])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[0, 0], [-3, -6], [6, -8]])
+
+ trans = (Affine2D().rotate_deg_around(*self.pivot, 90)
+ .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25)
+ trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) +
+ Affine2D().skew_deg(26.5650512, 14.0362435))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [1.5, 1.25])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[0, 0], [0.5, 2.75], [4, 4.5]])
+
+ trans = Affine2D().rotate_deg_around(*self.pivot, 90).translate(23, 42)
+ trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) +
+ Affine2D().translate(23, 42))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [24, 43])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[23, 42], [22, 45], [25, 46]])
+
+ def test_scale_plus_other(self):
+ trans = Affine2D().scale(3, -2).rotate_deg(90)
+ trans_added = Affine2D().scale(3, -2) + Affine2D().rotate_deg(90)
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_equal(trans.transform(self.single_point), [2, 3])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[4, 0], [6, 9], [0, 12]])
+
+ trans = Affine2D().scale(3, -2).rotate_deg_around(*self.pivot, 90)
+ trans_added = (Affine2D().scale(3, -2) +
+ Affine2D().rotate_deg_around(*self.pivot, 90))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_equal(trans.transform(self.single_point), [4, 3])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[6, 0], [8, 9], [2, 12]])
+
+ trans = (Affine2D().scale(3, -2)
+ .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25)
+ trans_added = (Affine2D().scale(3, -2) +
+ Affine2D().skew_deg(26.5650512, 14.0362435))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [2, -1.25])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[-2, -4], [6, -3.75], [12, 3]])
+
+ trans = Affine2D().scale(3, -2).translate(23, 42)
+ trans_added = Affine2D().scale(3, -2) + Affine2D().translate(23, 42)
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_equal(trans.transform(self.single_point), [26, 40])
+ assert_array_equal(trans.transform(self.multiple_points),
+ [[23, 38], [32, 36], [35, 42]])
+
+ def test_skew_plus_other(self):
+ # Using ~atan(0.5), ~atan(0.25) produces roundish numbers on output.
+ trans = Affine2D().skew_deg(26.5650512, 14.0362435).rotate_deg(90)
+ trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) +
+ Affine2D().rotate_deg(90))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [-1.25, 1.5])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[-2, 1], [-3.75, 4.5], [-1, 4]])
+
+ trans = (Affine2D().skew_deg(26.5650512, 14.0362435)
+ .rotate_deg_around(*self.pivot, 90))
+ trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) +
+ Affine2D().rotate_deg_around(*self.pivot, 90))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [0.75, 1.5])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[0, 1], [-1.75, 4.5], [1, 4]])
+
+ trans = Affine2D().skew_deg(26.5650512, 14.0362435).scale(3, -2)
+ trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) +
+ Affine2D().scale(3, -2))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [4.5, -2.5])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[3, -4], [13.5, -7.5], [12, -2]])
+
+ trans = Affine2D().skew_deg(26.5650512, 14.0362435).translate(23, 42)
+ trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) +
+ Affine2D().translate(23, 42))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [24.5, 43.25])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[24, 44], [27.5, 45.75], [27, 43]])
+
+ def test_translate_plus_other(self):
+ trans = Affine2D().translate(23, 42).rotate_deg(90)
+ trans_added = Affine2D().translate(23, 42) + Affine2D().rotate_deg(90)
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [-43, 24])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[-44, 23], [-45, 26], [-42, 27]])
+
+ trans = Affine2D().translate(23, 42).rotate_deg_around(*self.pivot, 90)
+ trans_added = (Affine2D().translate(23, 42) +
+ Affine2D().rotate_deg_around(*self.pivot, 90))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [-41, 24])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[-42, 23], [-43, 26], [-40, 27]])
+
+ trans = Affine2D().translate(23, 42).scale(3, -2)
+ trans_added = Affine2D().translate(23, 42) + Affine2D().scale(3, -2)
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [72, -86])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[69, -88], [78, -90], [81, -84]])
+
+ trans = (Affine2D().translate(23, 42)
+ .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25)
+ trans_added = (Affine2D().translate(23, 42) +
+ Affine2D().skew_deg(26.5650512, 14.0362435))
+ assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
+ assert_array_almost_equal(trans.transform(self.single_point), [45.5, 49])
+ assert_array_almost_equal(trans.transform(self.multiple_points),
+ [[45, 49.75], [48.5, 51.5], [48, 48.75]])
+
+ def test_invalid_transform(self):
+ t = mtransforms.Affine2D()
+ # There are two different exceptions, since the wrong number of
+ # dimensions is caught when constructing an array_view, and that
+ # raises a ValueError, and a wrong shape with a possible number
+ # of dimensions is caught by our CALL_CPP macro, which always
+ # raises the less precise RuntimeError.
+ with pytest.raises(ValueError):
+ t.transform(1)
+ with pytest.raises(ValueError):
+ t.transform([[[1]]])
+ with pytest.raises(RuntimeError):
+ t.transform([])
+ with pytest.raises(RuntimeError):
+ t.transform([1])
+ with pytest.raises(ValueError):
+ t.transform([[1]])
+ with pytest.raises(ValueError):
+ t.transform([[1, 2, 3]])
+
+ def test_copy(self):
+ a = mtransforms.Affine2D()
+ b = mtransforms.Affine2D()
+ s = a + b
+ # Updating a dependee should invalidate a copy of the dependent.
+ s.get_matrix() # resolve it.
+ s1 = copy.copy(s)
+ assert not s._invalid and not s1._invalid
+ a.translate(1, 2)
+ assert s._invalid and s1._invalid
+ assert (s1.get_matrix() == a.get_matrix()).all()
+ # Updating a copy of a dependee shouldn't invalidate a dependent.
+ s.get_matrix() # resolve it.
+ b1 = copy.copy(b)
+ b1.translate(3, 4)
+ assert not s._invalid
+ assert_array_equal(s.get_matrix(), a.get_matrix())
+
+ def test_deepcopy(self):
+ a = mtransforms.Affine2D()
+ b = mtransforms.Affine2D()
+ s = a + b
+ # Updating a dependee shouldn't invalidate a deepcopy of the dependent.
+ s.get_matrix() # resolve it.
+ s1 = copy.deepcopy(s)
+ assert not s._invalid and not s1._invalid
+ a.translate(1, 2)
+ assert s._invalid and not s1._invalid
+ assert_array_equal(s1.get_matrix(), mtransforms.Affine2D().get_matrix())
+ # Updating a deepcopy of a dependee shouldn't invalidate a dependent.
+ s.get_matrix() # resolve it.
+ b1 = copy.deepcopy(b)
+ b1.translate(3, 4)
+ assert not s._invalid
+ assert_array_equal(s.get_matrix(), a.get_matrix())
+
+
+class TestAffineDeltaTransform:
+ def test_invalidate(self):
+ before = np.array([[1.0, 4.0, 0.0],
+ [5.0, 1.0, 0.0],
+ [0.0, 0.0, 1.0]])
+ after = np.array([[1.0, 3.0, 0.0],
+ [5.0, 1.0, 0.0],
+ [0.0, 0.0, 1.0]])
+
+ # Translation and skew present
+ base = mtransforms.Affine2D.from_values(1, 5, 4, 1, 2, 3)
+ t = mtransforms.AffineDeltaTransform(base)
+ assert_array_equal(t.get_matrix(), before)
+
+ # Mess with the internal structure of `base` without invalidating
+ # This should not affect this transform because it's a passthrough:
+ # it's always invalid
+ base.get_matrix()[0, 1:] = 3
+ assert_array_equal(t.get_matrix(), after)
+
+ # Invalidate the base
+ base.invalidate()
+ assert_array_equal(t.get_matrix(), after)
+
+
+def test_non_affine_caching():
+ class AssertingNonAffineTransform(mtransforms.Transform):
+ """
+ This transform raises an assertion error when called when it
+ shouldn't be and ``self.raise_on_transform`` is True.
+
+ """
+ input_dims = output_dims = 2
+ is_affine = False
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.raise_on_transform = False
+ self.underlying_transform = mtransforms.Affine2D().scale(10, 10)
+
+ def transform_path_non_affine(self, path):
+ assert not self.raise_on_transform, \
+ 'Invalidated affine part of transform unnecessarily.'
+ return self.underlying_transform.transform_path(path)
+ transform_path = transform_path_non_affine
+
+ def transform_non_affine(self, path):
+ assert not self.raise_on_transform, \
+ 'Invalidated affine part of transform unnecessarily.'
+ return self.underlying_transform.transform(path)
+ transform = transform_non_affine
+
+ my_trans = AssertingNonAffineTransform()
+ ax = plt.axes()
+ plt.plot(np.arange(10), transform=my_trans + ax.transData)
+ plt.draw()
+ # enable the transform to raise an exception if it's non-affine transform
+ # method is triggered again.
+ my_trans.raise_on_transform = True
+ ax.transAxes.invalidate()
+ plt.draw()
+
+
+def test_external_transform_api():
+ class ScaledBy:
+ def __init__(self, scale_factor):
+ self._scale_factor = scale_factor
+
+ def _as_mpl_transform(self, axes):
+ return (mtransforms.Affine2D().scale(self._scale_factor)
+ + axes.transData)
+
+ ax = plt.axes()
+ line, = plt.plot(np.arange(10), transform=ScaledBy(10))
+ ax.set_xlim(0, 100)
+ ax.set_ylim(0, 100)
+ # assert that the top transform of the line is the scale transform.
+ assert_allclose(line.get_transform()._a.get_matrix(),
+ mtransforms.Affine2D().scale(10).get_matrix())
+
+
+@image_comparison(['pre_transform_data'], remove_text=True, style='mpl20',
+ tol=0.05)
+def test_pre_transform_plotting():
+ # a catch-all for as many as possible plot layouts which handle
+ # pre-transforming the data NOTE: The axis range is important in this
+ # plot. It should be x10 what the data suggests it should be
+
+ ax = plt.axes()
+ times10 = mtransforms.Affine2D().scale(10)
+
+ ax.contourf(np.arange(48).reshape(6, 8), transform=times10 + ax.transData)
+
+ ax.pcolormesh(np.linspace(0, 4, 7),
+ np.linspace(5.5, 8, 9),
+ np.arange(48).reshape(8, 6),
+ transform=times10 + ax.transData)
+
+ ax.scatter(np.linspace(0, 10), np.linspace(10, 0),
+ transform=times10 + ax.transData)
+
+ x = np.linspace(8, 10, 20)
+ y = np.linspace(1, 5, 20)
+ u = 2*np.sin(x) + np.cos(y[:, np.newaxis])
+ v = np.sin(x) - np.cos(y[:, np.newaxis])
+
+ ax.streamplot(x, y, u, v, transform=times10 + ax.transData,
+ linewidth=np.hypot(u, v))
+
+ # reduce the vector data down a bit for barb and quiver plotting
+ x, y = x[::3], y[::3]
+ u, v = u[::3, ::3], v[::3, ::3]
+
+ ax.quiver(x, y + 5, u, v, transform=times10 + ax.transData)
+
+ ax.barbs(x - 3, y + 5, u**2, v**2, transform=times10 + ax.transData)
+
+
+def test_contour_pre_transform_limits():
+ ax = plt.axes()
+ xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20))
+ ax.contourf(xs, ys, np.log(xs * ys),
+ transform=mtransforms.Affine2D().scale(0.1) + ax.transData)
+
+ expected = np.array([[1.5, 1.24],
+ [2., 1.25]])
+ assert_almost_equal(expected, ax.dataLim.get_points())
+
+
+def test_pcolor_pre_transform_limits():
+ # Based on test_contour_pre_transform_limits()
+ ax = plt.axes()
+ xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20))
+ ax.pcolor(xs, ys, np.log(xs * ys)[:-1, :-1],
+ transform=mtransforms.Affine2D().scale(0.1) + ax.transData)
+
+ expected = np.array([[1.5, 1.24],
+ [2., 1.25]])
+ assert_almost_equal(expected, ax.dataLim.get_points())
+
+
+def test_pcolormesh_pre_transform_limits():
+ # Based on test_contour_pre_transform_limits()
+ ax = plt.axes()
+ xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20))
+ ax.pcolormesh(xs, ys, np.log(xs * ys)[:-1, :-1],
+ transform=mtransforms.Affine2D().scale(0.1) + ax.transData)
+
+ expected = np.array([[1.5, 1.24],
+ [2., 1.25]])
+ assert_almost_equal(expected, ax.dataLim.get_points())
+
+
+def test_pcolormesh_gouraud_nans():
+ np.random.seed(19680801)
+
+ values = np.linspace(0, 180, 3)
+ radii = np.linspace(100, 1000, 10)
+ z, y = np.meshgrid(values, radii)
+ x = np.radians(np.random.rand(*z.shape) * 100)
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111, projection="polar")
+ # Setting the limit to cause clipping of the r values causes NaN to be
+ # introduced; these should not crash but be ignored as in other path
+ # operations.
+ ax.set_rlim(101, 1000)
+ ax.pcolormesh(x, y, z, shading="gouraud")
+
+ fig.canvas.draw()
+
+
+def test_Affine2D_from_values():
+ points = np.array([[0, 0],
+ [10, 20],
+ [-1, 0],
+ ])
+
+ t = mtransforms.Affine2D.from_values(1, 0, 0, 0, 0, 0)
+ actual = t.transform(points)
+ expected = np.array([[0, 0], [10, 0], [-1, 0]])
+ assert_almost_equal(actual, expected)
+
+ t = mtransforms.Affine2D.from_values(0, 2, 0, 0, 0, 0)
+ actual = t.transform(points)
+ expected = np.array([[0, 0], [0, 20], [0, -2]])
+ assert_almost_equal(actual, expected)
+
+ t = mtransforms.Affine2D.from_values(0, 0, 3, 0, 0, 0)
+ actual = t.transform(points)
+ expected = np.array([[0, 0], [60, 0], [0, 0]])
+ assert_almost_equal(actual, expected)
+
+ t = mtransforms.Affine2D.from_values(0, 0, 0, 4, 0, 0)
+ actual = t.transform(points)
+ expected = np.array([[0, 0], [0, 80], [0, 0]])
+ assert_almost_equal(actual, expected)
+
+ t = mtransforms.Affine2D.from_values(0, 0, 0, 0, 5, 0)
+ actual = t.transform(points)
+ expected = np.array([[5, 0], [5, 0], [5, 0]])
+ assert_almost_equal(actual, expected)
+
+ t = mtransforms.Affine2D.from_values(0, 0, 0, 0, 0, 6)
+ actual = t.transform(points)
+ expected = np.array([[0, 6], [0, 6], [0, 6]])
+ assert_almost_equal(actual, expected)
+
+
+def test_affine_inverted_invalidated():
+ # Ensure that the an affine transform is not declared valid on access
+ point = [1.0, 1.0]
+ t = mtransforms.Affine2D()
+
+ assert_almost_equal(point, t.transform(t.inverted().transform(point)))
+ # Change and access the transform
+ t.translate(1.0, 1.0).get_matrix()
+ assert_almost_equal(point, t.transform(t.inverted().transform(point)))
+
+
+def test_clipping_of_log():
+ # issue 804
+ path = Path._create_closed([(0.2, -99), (0.4, -99), (0.4, 20), (0.2, 20)])
+ # something like this happens in plotting logarithmic histograms
+ trans = mtransforms.BlendedGenericTransform(
+ mtransforms.Affine2D(), scale.LogTransform(10, 'clip'))
+ tpath = trans.transform_path_non_affine(path)
+ result = tpath.iter_segments(trans.get_affine(),
+ clip=(0, 0, 100, 100),
+ simplify=False)
+ tpoints, tcodes = zip(*result)
+ assert_allclose(tcodes, path.codes[:-1]) # No longer closed.
+
+
+class NonAffineForTest(mtransforms.Transform):
+ """
+ A class which looks like a non affine transform, but does whatever
+ the given transform does (even if it is affine). This is very useful
+ for testing NonAffine behaviour with a simple Affine transform.
+
+ """
+ is_affine = False
+ output_dims = 2
+ input_dims = 2
+
+ def __init__(self, real_trans, *args, **kwargs):
+ self.real_trans = real_trans
+ super().__init__(*args, **kwargs)
+
+ def transform_non_affine(self, values):
+ return self.real_trans.transform(values)
+
+ def transform_path_non_affine(self, path):
+ return self.real_trans.transform_path(path)
+
+
+class TestBasicTransform:
+ def setup_method(self):
+
+ self.ta1 = mtransforms.Affine2D(shorthand_name='ta1').rotate(np.pi / 2)
+ self.ta2 = mtransforms.Affine2D(shorthand_name='ta2').translate(10, 0)
+ self.ta3 = mtransforms.Affine2D(shorthand_name='ta3').scale(1, 2)
+
+ self.tn1 = NonAffineForTest(mtransforms.Affine2D().translate(1, 2),
+ shorthand_name='tn1')
+ self.tn2 = NonAffineForTest(mtransforms.Affine2D().translate(1, 2),
+ shorthand_name='tn2')
+ self.tn3 = NonAffineForTest(mtransforms.Affine2D().translate(1, 2),
+ shorthand_name='tn3')
+
+ # creates a transform stack which looks like ((A, (N, A)), A)
+ self.stack1 = (self.ta1 + (self.tn1 + self.ta2)) + self.ta3
+ # creates a transform stack which looks like (((A, N), A), A)
+ self.stack2 = self.ta1 + self.tn1 + self.ta2 + self.ta3
+ # creates a transform stack which is a subset of stack2
+ self.stack2_subset = self.tn1 + self.ta2 + self.ta3
+
+ # when in debug, the transform stacks can produce dot images:
+# self.stack1.write_graphviz(file('stack1.dot', 'w'))
+# self.stack2.write_graphviz(file('stack2.dot', 'w'))
+# self.stack2_subset.write_graphviz(file('stack2_subset.dot', 'w'))
+
+ def test_transform_depth(self):
+ assert self.stack1.depth == 4
+ assert self.stack2.depth == 4
+ assert self.stack2_subset.depth == 3
+
+ def test_left_to_right_iteration(self):
+ stack3 = (self.ta1 + (self.tn1 + (self.ta2 + self.tn2))) + self.ta3
+# stack3.write_graphviz(file('stack3.dot', 'w'))
+
+ target_transforms = [stack3,
+ (self.tn1 + (self.ta2 + self.tn2)) + self.ta3,
+ (self.ta2 + self.tn2) + self.ta3,
+ self.tn2 + self.ta3,
+ self.ta3,
+ ]
+ r = [rh for _, rh in stack3._iter_break_from_left_to_right()]
+ assert len(r) == len(target_transforms)
+
+ for target_stack, stack in zip(target_transforms, r):
+ assert target_stack == stack
+
+ def test_transform_shortcuts(self):
+ assert self.stack1 - self.stack2_subset == self.ta1
+ assert self.stack2 - self.stack2_subset == self.ta1
+
+ assert self.stack2_subset - self.stack2 == self.ta1.inverted()
+ assert (self.stack2_subset - self.stack2).depth == 1
+
+ with pytest.raises(ValueError):
+ self.stack1 - self.stack2
+
+ aff1 = self.ta1 + (self.ta2 + self.ta3)
+ aff2 = self.ta2 + self.ta3
+
+ assert aff1 - aff2 == self.ta1
+ assert aff1 - self.ta2 == aff1 + self.ta2.inverted()
+
+ assert self.stack1 - self.ta3 == self.ta1 + (self.tn1 + self.ta2)
+ assert self.stack2 - self.ta3 == self.ta1 + self.tn1 + self.ta2
+
+ assert ((self.ta2 + self.ta3) - self.ta3 + self.ta3 ==
+ self.ta2 + self.ta3)
+
+ def test_contains_branch(self):
+ r1 = (self.ta2 + self.ta1)
+ r2 = (self.ta2 + self.ta1)
+ assert r1 == r2
+ assert r1 != self.ta1
+ assert r1.contains_branch(r2)
+ assert r1.contains_branch(self.ta1)
+ assert not r1.contains_branch(self.ta2)
+ assert not r1.contains_branch(self.ta2 + self.ta2)
+
+ assert r1 == r2
+
+ assert self.stack1.contains_branch(self.ta3)
+ assert self.stack2.contains_branch(self.ta3)
+
+ assert self.stack1.contains_branch(self.stack2_subset)
+ assert self.stack2.contains_branch(self.stack2_subset)
+
+ assert not self.stack2_subset.contains_branch(self.stack1)
+ assert not self.stack2_subset.contains_branch(self.stack2)
+
+ assert self.stack1.contains_branch(self.ta2 + self.ta3)
+ assert self.stack2.contains_branch(self.ta2 + self.ta3)
+
+ assert not self.stack1.contains_branch(self.tn1 + self.ta2)
+
+ blend = mtransforms.BlendedGenericTransform(self.tn2, self.stack2)
+ x, y = blend.contains_branch_seperately(self.stack2_subset)
+ stack_blend = self.tn3 + blend
+ sx, sy = stack_blend.contains_branch_seperately(self.stack2_subset)
+ assert x is sx is False
+ assert y is sy is True
+
+ def test_affine_simplification(self):
+ # tests that a transform stack only calls as much is absolutely
+ # necessary "non-affine" allowing the best possible optimization with
+ # complex transformation stacks.
+ points = np.array([[0, 0], [10, 20], [np.nan, 1], [-1, 0]],
+ dtype=np.float64)
+ na_pts = self.stack1.transform_non_affine(points)
+ all_pts = self.stack1.transform(points)
+
+ na_expected = np.array([[1., 2.], [-19., 12.],
+ [np.nan, np.nan], [1., 1.]], dtype=np.float64)
+ all_expected = np.array([[11., 4.], [-9., 24.],
+ [np.nan, np.nan], [11., 2.]],
+ dtype=np.float64)
+
+ # check we have the expected results from doing the affine part only
+ assert_array_almost_equal(na_pts, na_expected)
+ # check we have the expected results from a full transformation
+ assert_array_almost_equal(all_pts, all_expected)
+ # check we have the expected results from doing the transformation in
+ # two steps
+ assert_array_almost_equal(self.stack1.transform_affine(na_pts),
+ all_expected)
+ # check that getting the affine transformation first, then fully
+ # transforming using that yields the same result as before.
+ assert_array_almost_equal(self.stack1.get_affine().transform(na_pts),
+ all_expected)
+
+ # check that the affine part of stack1 & stack2 are equivalent
+ # (i.e. the optimization is working)
+ expected_result = (self.ta2 + self.ta3).get_matrix()
+ result = self.stack1.get_affine().get_matrix()
+ assert_array_equal(expected_result, result)
+
+ result = self.stack2.get_affine().get_matrix()
+ assert_array_equal(expected_result, result)
+
+
+class TestTransformPlotInterface:
+ def test_line_extent_axes_coords(self):
+ # a simple line in axes coordinates
+ ax = plt.axes()
+ ax.plot([0.1, 1.2, 0.8], [0.9, 0.5, 0.8], transform=ax.transAxes)
+ assert_array_equal(ax.dataLim.get_points(),
+ np.array([[np.inf, np.inf],
+ [-np.inf, -np.inf]]))
+
+ def test_line_extent_data_coords(self):
+ # a simple line in data coordinates
+ ax = plt.axes()
+ ax.plot([0.1, 1.2, 0.8], [0.9, 0.5, 0.8], transform=ax.transData)
+ assert_array_equal(ax.dataLim.get_points(),
+ np.array([[0.1, 0.5], [1.2, 0.9]]))
+
+ def test_line_extent_compound_coords1(self):
+ # a simple line in data coordinates in the y component, and in axes
+ # coordinates in the x
+ ax = plt.axes()
+ trans = mtransforms.blended_transform_factory(ax.transAxes,
+ ax.transData)
+ ax.plot([0.1, 1.2, 0.8], [35, -5, 18], transform=trans)
+ assert_array_equal(ax.dataLim.get_points(),
+ np.array([[np.inf, -5.],
+ [-np.inf, 35.]]))
+
+ def test_line_extent_predata_transform_coords(self):
+ # a simple line in (offset + data) coordinates
+ ax = plt.axes()
+ trans = mtransforms.Affine2D().scale(10) + ax.transData
+ ax.plot([0.1, 1.2, 0.8], [35, -5, 18], transform=trans)
+ assert_array_equal(ax.dataLim.get_points(),
+ np.array([[1., -50.], [12., 350.]]))
+
+ def test_line_extent_compound_coords2(self):
+ # a simple line in (offset + data) coordinates in the y component, and
+ # in axes coordinates in the x
+ ax = plt.axes()
+ trans = mtransforms.blended_transform_factory(
+ ax.transAxes, mtransforms.Affine2D().scale(10) + ax.transData)
+ ax.plot([0.1, 1.2, 0.8], [35, -5, 18], transform=trans)
+ assert_array_equal(ax.dataLim.get_points(),
+ np.array([[np.inf, -50.], [-np.inf, 350.]]))
+
+ def test_line_extents_affine(self):
+ ax = plt.axes()
+ offset = mtransforms.Affine2D().translate(10, 10)
+ plt.plot(np.arange(10), transform=offset + ax.transData)
+ expected_data_lim = np.array([[0., 0.], [9., 9.]]) + 10
+ assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
+
+ def test_line_extents_non_affine(self):
+ ax = plt.axes()
+ offset = mtransforms.Affine2D().translate(10, 10)
+ na_offset = NonAffineForTest(mtransforms.Affine2D().translate(10, 10))
+ plt.plot(np.arange(10), transform=offset + na_offset + ax.transData)
+ expected_data_lim = np.array([[0., 0.], [9., 9.]]) + 20
+ assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
+
+ def test_pathc_extents_non_affine(self):
+ ax = plt.axes()
+ offset = mtransforms.Affine2D().translate(10, 10)
+ na_offset = NonAffineForTest(mtransforms.Affine2D().translate(10, 10))
+ pth = Path([[0, 0], [0, 10], [10, 10], [10, 0]])
+ patch = mpatches.PathPatch(pth,
+ transform=offset + na_offset + ax.transData)
+ ax.add_patch(patch)
+ expected_data_lim = np.array([[0., 0.], [10., 10.]]) + 20
+ assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
+
+ def test_pathc_extents_affine(self):
+ ax = plt.axes()
+ offset = mtransforms.Affine2D().translate(10, 10)
+ pth = Path([[0, 0], [0, 10], [10, 10], [10, 0]])
+ patch = mpatches.PathPatch(pth, transform=offset + ax.transData)
+ ax.add_patch(patch)
+ expected_data_lim = np.array([[0., 0.], [10., 10.]]) + 10
+ assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
+
+ def test_line_extents_for_non_affine_transData(self):
+ ax = plt.axes(projection='polar')
+ # add 10 to the radius of the data
+ offset = mtransforms.Affine2D().translate(0, 10)
+
+ plt.plot(np.arange(10), transform=offset + ax.transData)
+ # the data lim of a polar plot is stored in coordinates
+ # before a transData transformation, hence the data limits
+ # are not what is being shown on the actual plot.
+ expected_data_lim = np.array([[0., 0.], [9., 9.]]) + [0, 10]
+ assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
+
+
+def assert_bbox_eq(bbox1, bbox2):
+ assert_array_equal(bbox1.bounds, bbox2.bounds)
+
+
+def test_bbox_frozen_copies_minpos():
+ bbox = mtransforms.Bbox.from_extents(0.0, 0.0, 1.0, 1.0, minpos=1.0)
+ frozen = bbox.frozen()
+ assert_array_equal(frozen.minpos, bbox.minpos)
+
+
+def test_bbox_intersection():
+ bbox_from_ext = mtransforms.Bbox.from_extents
+ inter = mtransforms.Bbox.intersection
+
+ r1 = bbox_from_ext(0, 0, 1, 1)
+ r2 = bbox_from_ext(0.5, 0.5, 1.5, 1.5)
+ r3 = bbox_from_ext(0.5, 0, 0.75, 0.75)
+ r4 = bbox_from_ext(0.5, 1.5, 1, 2.5)
+ r5 = bbox_from_ext(1, 1, 2, 2)
+
+ # self intersection -> no change
+ assert_bbox_eq(inter(r1, r1), r1)
+ # simple intersection
+ assert_bbox_eq(inter(r1, r2), bbox_from_ext(0.5, 0.5, 1, 1))
+ # r3 contains r2
+ assert_bbox_eq(inter(r1, r3), r3)
+ # no intersection
+ assert inter(r1, r4) is None
+ # single point
+ assert_bbox_eq(inter(r1, r5), bbox_from_ext(1, 1, 1, 1))
+
+
+def test_bbox_as_strings():
+ b = mtransforms.Bbox([[.5, 0], [.75, .75]])
+ assert_bbox_eq(b, eval(repr(b), {'Bbox': mtransforms.Bbox}))
+ asdict = eval(str(b), {'Bbox': dict})
+ for k, v in asdict.items():
+ assert getattr(b, k) == v
+ fmt = '.1f'
+ asdict = eval(format(b, fmt), {'Bbox': dict})
+ for k, v in asdict.items():
+ assert eval(format(getattr(b, k), fmt)) == v
+
+
+def test_str_transform():
+ # The str here should not be considered as "absolutely stable", and may be
+ # reformatted later; this is just a smoketest for __str__.
+ assert str(plt.subplot(projection="polar").transData) == """\
+CompositeGenericTransform(
+ CompositeGenericTransform(
+ CompositeGenericTransform(
+ TransformWrapper(
+ BlendedAffine2D(
+ IdentityTransform(),
+ IdentityTransform())),
+ CompositeAffine2D(
+ Affine2D().scale(1.0),
+ Affine2D().scale(1.0))),
+ PolarTransform(
+ PolarAxes(0.125,0.1;0.775x0.8),
+ use_rmin=True,
+ apply_theta_transforms=False)),
+ CompositeGenericTransform(
+ CompositeGenericTransform(
+ PolarAffine(
+ TransformWrapper(
+ BlendedAffine2D(
+ IdentityTransform(),
+ IdentityTransform())),
+ LockableBbox(
+ Bbox(x0=0.0, y0=0.0, x1=6.283185307179586, y1=1.0),
+ [[-- --]
+ [-- --]])),
+ BboxTransformFrom(
+ _WedgeBbox(
+ (0.5, 0.5),
+ TransformedBbox(
+ Bbox(x0=0.0, y0=0.0, x1=6.283185307179586, y1=1.0),
+ CompositeAffine2D(
+ Affine2D().scale(1.0),
+ Affine2D().scale(1.0))),
+ LockableBbox(
+ Bbox(x0=0.0, y0=0.0, x1=6.283185307179586, y1=1.0),
+ [[-- --]
+ [-- --]])))),
+ BboxTransformTo(
+ TransformedBbox(
+ Bbox(x0=0.125, y0=0.09999999999999998, x1=0.9, y1=0.9),
+ BboxTransformTo(
+ TransformedBbox(
+ Bbox(x0=0.0, y0=0.0, x1=8.0, y1=6.0),
+ Affine2D().scale(80.0)))))))"""
+
+
+def test_transform_single_point():
+ t = mtransforms.Affine2D()
+ r = t.transform_affine((1, 1))
+ assert r.shape == (2,)
+
+
+def test_log_transform():
+ # Tests that the last line runs without exception (previously the
+ # transform would fail if one of the axes was logarithmic).
+ fig, ax = plt.subplots()
+ ax.set_yscale('log')
+ ax.transData.transform((1, 1))
+
+
+def test_nan_overlap():
+ a = mtransforms.Bbox([[0, 0], [1, 1]])
+ b = mtransforms.Bbox([[0, 0], [1, np.nan]])
+ assert not a.overlaps(b)
+
+
+def test_transform_angles():
+ t = mtransforms.Affine2D() # Identity transform
+ angles = np.array([20, 45, 60])
+ points = np.array([[0, 0], [1, 1], [2, 2]])
+
+ # Identity transform does not change angles
+ new_angles = t.transform_angles(angles, points)
+ assert_array_almost_equal(angles, new_angles)
+
+ # points missing a 2nd dimension
+ with pytest.raises(ValueError):
+ t.transform_angles(angles, points[0:2, 0:1])
+
+ # Number of angles != Number of points
+ with pytest.raises(ValueError):
+ t.transform_angles(angles, points[0:2, :])
+
+
+def test_nonsingular():
+ # test for zero-expansion type cases; other cases may be added later
+ zero_expansion = np.array([-0.001, 0.001])
+ cases = [(0, np.nan), (0, 0), (0, 7.9e-317)]
+ for args in cases:
+ out = np.array(mtransforms.nonsingular(*args))
+ assert_array_equal(out, zero_expansion)
+
+
+def test_transformed_path():
+ points = [(0, 0), (1, 0), (1, 1), (0, 1)]
+ path = Path(points, closed=True)
+
+ trans = mtransforms.Affine2D()
+ trans_path = mtransforms.TransformedPath(path, trans)
+ assert_allclose(trans_path.get_fully_transformed_path().vertices, points)
+
+ # Changing the transform should change the result.
+ r2 = 1 / np.sqrt(2)
+ trans.rotate(np.pi / 4)
+ assert_allclose(trans_path.get_fully_transformed_path().vertices,
+ [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)],
+ atol=1e-15)
+
+ # Changing the path does not change the result (it's cached).
+ path.points = [(0, 0)] * 4
+ assert_allclose(trans_path.get_fully_transformed_path().vertices,
+ [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)],
+ atol=1e-15)
+
+
+def test_transformed_patch_path():
+ trans = mtransforms.Affine2D()
+ patch = mpatches.Wedge((0, 0), 1, 45, 135, transform=trans)
+
+ tpatch = mtransforms.TransformedPatchPath(patch)
+ points = tpatch.get_fully_transformed_path().vertices
+
+ # Changing the transform should change the result.
+ trans.scale(2)
+ assert_allclose(tpatch.get_fully_transformed_path().vertices, points * 2)
+
+ # Changing the path should change the result (and cancel out the scaling
+ # from the transform).
+ patch.set_radius(0.5)
+ assert_allclose(tpatch.get_fully_transformed_path().vertices, points)
+
+
+@pytest.mark.parametrize('locked_element', ['x0', 'y0', 'x1', 'y1'])
+def test_lockable_bbox(locked_element):
+ other_elements = ['x0', 'y0', 'x1', 'y1']
+ other_elements.remove(locked_element)
+
+ orig = mtransforms.Bbox.unit()
+ locked = mtransforms.LockableBbox(orig, **{locked_element: 2})
+
+ # LockableBbox should keep its locked element as specified in __init__.
+ assert getattr(locked, locked_element) == 2
+ assert getattr(locked, 'locked_' + locked_element) == 2
+ for elem in other_elements:
+ assert getattr(locked, elem) == getattr(orig, elem)
+
+ # Changing underlying Bbox should update everything but locked element.
+ orig.set_points(orig.get_points() + 10)
+ assert getattr(locked, locked_element) == 2
+ assert getattr(locked, 'locked_' + locked_element) == 2
+ for elem in other_elements:
+ assert getattr(locked, elem) == getattr(orig, elem)
+
+ # Unlocking element should revert values back to the underlying Bbox.
+ setattr(locked, 'locked_' + locked_element, None)
+ assert getattr(locked, 'locked_' + locked_element) is None
+ assert np.all(orig.get_points() == locked.get_points())
+
+ # Relocking an element should change its value, but not others.
+ setattr(locked, 'locked_' + locked_element, 3)
+ assert getattr(locked, locked_element) == 3
+ assert getattr(locked, 'locked_' + locked_element) == 3
+ for elem in other_elements:
+ assert getattr(locked, elem) == getattr(orig, elem)
+
+
+def test_transformwrapper():
+ t = mtransforms.TransformWrapper(mtransforms.Affine2D())
+ with pytest.raises(ValueError, match=(
+ r"The input and output dims of the new child \(1, 1\) "
+ r"do not match those of current child \(2, 2\)")):
+ t.set(scale.LogTransform(10))
+
+
+@check_figures_equal(extensions=["png"])
+def test_scale_swapping(fig_test, fig_ref):
+ np.random.seed(19680801)
+ samples = np.random.normal(size=10)
+ x = np.linspace(-5, 5, 10)
+
+ for fig, log_state in zip([fig_test, fig_ref], [True, False]):
+ ax = fig.subplots()
+ ax.hist(samples, log=log_state, density=True)
+ ax.plot(x, np.exp(-(x**2) / 2) / np.sqrt(2 * np.pi))
+ fig.canvas.draw()
+ ax.set_yscale('linear')
+
+
+def test_offset_copy_errors():
+ with pytest.raises(ValueError,
+ match="'fontsize' is not a valid value for units;"
+ " supported values are 'dots', 'points', 'inches'"):
+ mtransforms.offset_copy(None, units='fontsize')
+
+ with pytest.raises(ValueError,
+ match='For units of inches or points a fig kwarg is needed'):
+ mtransforms.offset_copy(None, units='inches')
+
+
+def test_transformedbbox_contains():
+ bb = TransformedBbox(Bbox.unit(), Affine2D().rotate_deg(30))
+ assert bb.contains(.8, .5)
+ assert bb.contains(-.4, .85)
+ assert not bb.contains(.9, .5)
+ bb = TransformedBbox(Bbox.unit(), Affine2D().translate(.25, .5))
+ assert bb.contains(1.25, 1.5)
+ assert not bb.fully_contains(1.25, 1.5)
+ assert not bb.fully_contains(.1, .1)
+
+
+def test_interval_contains():
+ assert mtransforms.interval_contains((0, 1), 0.5)
+ assert mtransforms.interval_contains((0, 1), 0)
+ assert mtransforms.interval_contains((0, 1), 1)
+ assert not mtransforms.interval_contains((0, 1), -1)
+ assert not mtransforms.interval_contains((0, 1), 2)
+ assert mtransforms.interval_contains((1, 0), 0.5)
+
+
+def test_interval_contains_open():
+ assert mtransforms.interval_contains_open((0, 1), 0.5)
+ assert not mtransforms.interval_contains_open((0, 1), 0)
+ assert not mtransforms.interval_contains_open((0, 1), 1)
+ assert not mtransforms.interval_contains_open((0, 1), -1)
+ assert not mtransforms.interval_contains_open((0, 1), 2)
+ assert mtransforms.interval_contains_open((1, 0), 0.5)
+
+
+def test_scaledrotation_initialization():
+ """Test that the ScaledRotation object is initialized correctly."""
+ theta = 1.0 # Arbitrary theta value for testing
+ trans_shift = MagicMock() # Mock the trans_shift transformation
+ scaled_rot = _ScaledRotation(theta, trans_shift)
+ assert scaled_rot._theta == theta
+ assert scaled_rot._trans_shift == trans_shift
+ assert scaled_rot._mtx is None
+
+
+def test_scaledrotation_get_matrix_invalid():
+ """Test get_matrix when the matrix is invalid and needs recalculation."""
+ theta = np.pi / 2
+ trans_shift = MagicMock(transform=MagicMock(return_value=[[theta, 0]]))
+ scaled_rot = _ScaledRotation(theta, trans_shift)
+ scaled_rot._invalid = True
+ matrix = scaled_rot.get_matrix()
+ trans_shift.transform.assert_called_once_with([[theta, 0]])
+ expected_rotation = np.array([[0, -1],
+ [1, 0]])
+ assert matrix is not None
+ assert_allclose(matrix[:2, :2], expected_rotation, atol=1e-15)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_triangulation.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_triangulation.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd91144f240cefa592a0c73387974c7fcd977ba9
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_triangulation.py
@@ -0,0 +1,1403 @@
+import numpy as np
+from numpy.testing import (
+ assert_array_equal, assert_array_almost_equal, assert_array_less)
+import numpy.ma.testutils as matest
+import pytest
+
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+import matplotlib.tri as mtri
+from matplotlib.path import Path
+from matplotlib.testing.decorators import image_comparison, check_figures_equal
+
+
+class TestTriangulationParams:
+ x = [-1, 0, 1, 0]
+ y = [0, -1, 0, 1]
+ triangles = [[0, 1, 2], [0, 2, 3]]
+ mask = [False, True]
+
+ @pytest.mark.parametrize('args, kwargs, expected', [
+ ([x, y], {}, [x, y, None, None]),
+ ([x, y, triangles], {}, [x, y, triangles, None]),
+ ([x, y], dict(triangles=triangles), [x, y, triangles, None]),
+ ([x, y], dict(mask=mask), [x, y, None, mask]),
+ ([x, y, triangles], dict(mask=mask), [x, y, triangles, mask]),
+ ([x, y], dict(triangles=triangles, mask=mask), [x, y, triangles, mask])
+ ])
+ def test_extract_triangulation_params(self, args, kwargs, expected):
+ other_args = [1, 2]
+ other_kwargs = {'a': 3, 'b': '4'}
+ x_, y_, triangles_, mask_, args_, kwargs_ = \
+ mtri.Triangulation._extract_triangulation_params(
+ args + other_args, {**kwargs, **other_kwargs})
+ x, y, triangles, mask = expected
+ assert x_ is x
+ assert y_ is y
+ assert_array_equal(triangles_, triangles)
+ assert mask_ is mask
+ assert args_ == other_args
+ assert kwargs_ == other_kwargs
+
+
+def test_extract_triangulation_positional_mask():
+ # mask cannot be passed positionally
+ mask = [True]
+ args = [[0, 2, 1], [0, 0, 1], [[0, 1, 2]], mask]
+ x_, y_, triangles_, mask_, args_, kwargs_ = \
+ mtri.Triangulation._extract_triangulation_params(args, {})
+ assert mask_ is None
+ assert args_ == [mask]
+ # the positional mask must be caught downstream because this must pass
+ # unknown args through
+
+
+def test_triangulation_init():
+ x = [-1, 0, 1, 0]
+ y = [0, -1, 0, 1]
+ with pytest.raises(ValueError, match="x and y must be equal-length"):
+ mtri.Triangulation(x, [1, 2])
+ with pytest.raises(
+ ValueError,
+ match=r"triangles must be a \(N, 3\) int array, but found shape "
+ r"\(3,\)"):
+ mtri.Triangulation(x, y, [0, 1, 2])
+ with pytest.raises(
+ ValueError,
+ match=r"triangles must be a \(N, 3\) int array, not 'other'"):
+ mtri.Triangulation(x, y, 'other')
+ with pytest.raises(ValueError, match="found value 99"):
+ mtri.Triangulation(x, y, [[0, 1, 99]])
+ with pytest.raises(ValueError, match="found value -1"):
+ mtri.Triangulation(x, y, [[0, 1, -1]])
+
+
+def test_triangulation_set_mask():
+ x = [-1, 0, 1, 0]
+ y = [0, -1, 0, 1]
+ triangles = [[0, 1, 2], [2, 3, 0]]
+ triang = mtri.Triangulation(x, y, triangles)
+
+ # Check neighbors, which forces creation of C++ triangulation
+ assert_array_equal(triang.neighbors, [[-1, -1, 1], [-1, -1, 0]])
+
+ # Set mask
+ triang.set_mask([False, True])
+ assert_array_equal(triang.mask, [False, True])
+
+ # Reset mask
+ triang.set_mask(None)
+ assert triang.mask is None
+
+ msg = r"mask array must have same length as triangles array"
+ for mask in ([False, True, False], [False], [True], False, True):
+ with pytest.raises(ValueError, match=msg):
+ triang.set_mask(mask)
+
+
+def test_delaunay():
+ # No duplicate points, regular grid.
+ nx = 5
+ ny = 4
+ x, y = np.meshgrid(np.linspace(0.0, 1.0, nx), np.linspace(0.0, 1.0, ny))
+ x = x.ravel()
+ y = y.ravel()
+ npoints = nx*ny
+ ntriangles = 2 * (nx-1) * (ny-1)
+ nedges = 3*nx*ny - 2*nx - 2*ny + 1
+
+ # Create delaunay triangulation.
+ triang = mtri.Triangulation(x, y)
+
+ # The tests in the remainder of this function should be passed by any
+ # triangulation that does not contain duplicate points.
+
+ # Points - floating point.
+ assert_array_almost_equal(triang.x, x)
+ assert_array_almost_equal(triang.y, y)
+
+ # Triangles - integers.
+ assert len(triang.triangles) == ntriangles
+ assert np.min(triang.triangles) == 0
+ assert np.max(triang.triangles) == npoints-1
+
+ # Edges - integers.
+ assert len(triang.edges) == nedges
+ assert np.min(triang.edges) == 0
+ assert np.max(triang.edges) == npoints-1
+
+ # Neighbors - integers.
+ # Check that neighbors calculated by C++ triangulation class are the same
+ # as those returned from delaunay routine.
+ neighbors = triang.neighbors
+ triang._neighbors = None
+ assert_array_equal(triang.neighbors, neighbors)
+
+ # Is each point used in at least one triangle?
+ assert_array_equal(np.unique(triang.triangles), np.arange(npoints))
+
+
+def test_delaunay_duplicate_points():
+ npoints = 10
+ duplicate = 7
+ duplicate_of = 3
+
+ np.random.seed(23)
+ x = np.random.random(npoints)
+ y = np.random.random(npoints)
+ x[duplicate] = x[duplicate_of]
+ y[duplicate] = y[duplicate_of]
+
+ # Create delaunay triangulation.
+ triang = mtri.Triangulation(x, y)
+
+ # Duplicate points should be ignored, so the index of the duplicate points
+ # should not appear in any triangle.
+ assert_array_equal(np.unique(triang.triangles),
+ np.delete(np.arange(npoints), duplicate))
+
+
+def test_delaunay_points_in_line():
+ # Cannot triangulate points that are all in a straight line, but check
+ # that delaunay code fails gracefully.
+ x = np.linspace(0.0, 10.0, 11)
+ y = np.linspace(0.0, 10.0, 11)
+ with pytest.raises(RuntimeError):
+ mtri.Triangulation(x, y)
+
+ # Add an extra point not on the line and the triangulation is OK.
+ x = np.append(x, 2.0)
+ y = np.append(y, 8.0)
+ mtri.Triangulation(x, y)
+
+
+@pytest.mark.parametrize('x, y', [
+ # Triangulation should raise a ValueError if passed less than 3 points.
+ ([], []),
+ ([1], [5]),
+ ([1, 2], [5, 6]),
+ # Triangulation should also raise a ValueError if passed duplicate points
+ # such that there are less than 3 unique points.
+ ([1, 2, 1], [5, 6, 5]),
+ ([1, 2, 2], [5, 6, 6]),
+ ([1, 1, 1, 2, 1, 2], [5, 5, 5, 6, 5, 6]),
+])
+def test_delaunay_insufficient_points(x, y):
+ with pytest.raises(ValueError):
+ mtri.Triangulation(x, y)
+
+
+def test_delaunay_robust():
+ # Fails when mtri.Triangulation uses matplotlib.delaunay, works when using
+ # qhull.
+ tri_points = np.array([
+ [0.8660254037844384, -0.5000000000000004],
+ [0.7577722283113836, -0.5000000000000004],
+ [0.6495190528383288, -0.5000000000000003],
+ [0.5412658773652739, -0.5000000000000003],
+ [0.811898816047911, -0.40625000000000044],
+ [0.7036456405748561, -0.4062500000000004],
+ [0.5953924651018013, -0.40625000000000033]])
+ test_points = np.asarray([
+ [0.58, -0.46],
+ [0.65, -0.46],
+ [0.65, -0.42],
+ [0.7, -0.48],
+ [0.7, -0.44],
+ [0.75, -0.44],
+ [0.8, -0.48]])
+
+ # Utility function that indicates if a triangle defined by 3 points
+ # (xtri, ytri) contains the test point xy. Avoid calling with a point that
+ # lies on or very near to an edge of the triangle.
+ def tri_contains_point(xtri, ytri, xy):
+ tri_points = np.vstack((xtri, ytri)).T
+ return Path(tri_points).contains_point(xy)
+
+ # Utility function that returns how many triangles of the specified
+ # triangulation contain the test point xy. Avoid calling with a point that
+ # lies on or very near to an edge of any triangle in the triangulation.
+ def tris_contain_point(triang, xy):
+ return sum(tri_contains_point(triang.x[tri], triang.y[tri], xy)
+ for tri in triang.triangles)
+
+ # Using matplotlib.delaunay, an invalid triangulation is created with
+ # overlapping triangles; qhull is OK.
+ triang = mtri.Triangulation(tri_points[:, 0], tri_points[:, 1])
+ for test_point in test_points:
+ assert tris_contain_point(triang, test_point) == 1
+
+ # If ignore the first point of tri_points, matplotlib.delaunay throws a
+ # KeyError when calculating the convex hull; qhull is OK.
+ triang = mtri.Triangulation(tri_points[1:, 0], tri_points[1:, 1])
+
+
+@image_comparison(['tripcolor1.png'])
+def test_tripcolor():
+ x = np.asarray([0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1, 0.75])
+ y = np.asarray([0, 0, 0, 0.5, 0.5, 0.5, 1, 1, 1, 0.75])
+ triangles = np.asarray([
+ [0, 1, 3], [1, 4, 3],
+ [1, 2, 4], [2, 5, 4],
+ [3, 4, 6], [4, 7, 6],
+ [4, 5, 9], [7, 4, 9], [8, 7, 9], [5, 8, 9]])
+
+ # Triangulation with same number of points and triangles.
+ triang = mtri.Triangulation(x, y, triangles)
+
+ Cpoints = x + 0.5*y
+
+ xmid = x[triang.triangles].mean(axis=1)
+ ymid = y[triang.triangles].mean(axis=1)
+ Cfaces = 0.5*xmid + ymid
+
+ plt.subplot(121)
+ plt.tripcolor(triang, Cpoints, edgecolors='k')
+ plt.title('point colors')
+
+ plt.subplot(122)
+ plt.tripcolor(triang, facecolors=Cfaces, edgecolors='k')
+ plt.title('facecolors')
+
+
+def test_tripcolor_color():
+ x = [-1, 0, 1, 0]
+ y = [0, -1, 0, 1]
+ fig, ax = plt.subplots()
+ with pytest.raises(TypeError, match=r"tripcolor\(\) missing 1 required "):
+ ax.tripcolor(x, y)
+ with pytest.raises(ValueError, match="The length of c must match either"):
+ ax.tripcolor(x, y, [1, 2, 3])
+ with pytest.raises(ValueError,
+ match="length of facecolors must match .* triangles"):
+ ax.tripcolor(x, y, facecolors=[1, 2, 3, 4])
+ with pytest.raises(ValueError,
+ match="'gouraud' .* at the points.* not at the faces"):
+ ax.tripcolor(x, y, facecolors=[1, 2], shading='gouraud')
+ with pytest.raises(ValueError,
+ match="'gouraud' .* at the points.* not at the faces"):
+ ax.tripcolor(x, y, [1, 2], shading='gouraud') # faces
+ with pytest.raises(TypeError,
+ match="positional.*'c'.*keyword-only.*'facecolors'"):
+ ax.tripcolor(x, y, C=[1, 2, 3, 4])
+ with pytest.raises(TypeError, match="Unexpected positional parameter"):
+ ax.tripcolor(x, y, [1, 2], 'unused_positional')
+
+ # smoke test for valid color specifications (via C or facecolors)
+ ax.tripcolor(x, y, [1, 2, 3, 4]) # edges
+ ax.tripcolor(x, y, [1, 2, 3, 4], shading='gouraud') # edges
+ ax.tripcolor(x, y, [1, 2]) # faces
+ ax.tripcolor(x, y, facecolors=[1, 2]) # faces
+
+
+def test_tripcolor_clim():
+ np.random.seed(19680801)
+ a, b, c = np.random.rand(10), np.random.rand(10), np.random.rand(10)
+
+ ax = plt.figure().add_subplot()
+ clim = (0.25, 0.75)
+ norm = ax.tripcolor(a, b, c, clim=clim).norm
+ assert (norm.vmin, norm.vmax) == clim
+
+
+def test_tripcolor_warnings():
+ x = [-1, 0, 1, 0]
+ y = [0, -1, 0, 1]
+ c = [0.4, 0.5]
+ fig, ax = plt.subplots()
+ # facecolors takes precedence over c
+ with pytest.warns(UserWarning, match="Positional parameter c .*no effect"):
+ ax.tripcolor(x, y, c, facecolors=c)
+ with pytest.warns(UserWarning, match="Positional parameter c .*no effect"):
+ ax.tripcolor(x, y, 'interpreted as c', facecolors=c)
+
+
+def test_no_modify():
+ # Test that Triangulation does not modify triangles array passed to it.
+ triangles = np.array([[3, 2, 0], [3, 1, 0]], dtype=np.int32)
+ points = np.array([(0, 0), (0, 1.1), (1, 0), (1, 1)])
+
+ old_triangles = triangles.copy()
+ mtri.Triangulation(points[:, 0], points[:, 1], triangles).edges
+ assert_array_equal(old_triangles, triangles)
+
+
+def test_trifinder():
+ # Test points within triangles of masked triangulation.
+ x, y = np.meshgrid(np.arange(4), np.arange(4))
+ x = x.ravel()
+ y = y.ravel()
+ triangles = [[0, 1, 4], [1, 5, 4], [1, 2, 5], [2, 6, 5], [2, 3, 6],
+ [3, 7, 6], [4, 5, 8], [5, 9, 8], [5, 6, 9], [6, 10, 9],
+ [6, 7, 10], [7, 11, 10], [8, 9, 12], [9, 13, 12], [9, 10, 13],
+ [10, 14, 13], [10, 11, 14], [11, 15, 14]]
+ mask = np.zeros(len(triangles))
+ mask[8:10] = 1
+ triang = mtri.Triangulation(x, y, triangles, mask)
+ trifinder = triang.get_trifinder()
+
+ xs = [0.25, 1.25, 2.25, 3.25]
+ ys = [0.25, 1.25, 2.25, 3.25]
+ xs, ys = np.meshgrid(xs, ys)
+ xs = xs.ravel()
+ ys = ys.ravel()
+ tris = trifinder(xs, ys)
+ assert_array_equal(tris, [0, 2, 4, -1, 6, -1, 10, -1,
+ 12, 14, 16, -1, -1, -1, -1, -1])
+ tris = trifinder(xs-0.5, ys-0.5)
+ assert_array_equal(tris, [-1, -1, -1, -1, -1, 1, 3, 5,
+ -1, 7, -1, 11, -1, 13, 15, 17])
+
+ # Test points exactly on boundary edges of masked triangulation.
+ xs = [0.5, 1.5, 2.5, 0.5, 1.5, 2.5, 1.5, 1.5, 0.0, 1.0, 2.0, 3.0]
+ ys = [0.0, 0.0, 0.0, 3.0, 3.0, 3.0, 1.0, 2.0, 1.5, 1.5, 1.5, 1.5]
+ tris = trifinder(xs, ys)
+ assert_array_equal(tris, [0, 2, 4, 13, 15, 17, 3, 14, 6, 7, 10, 11])
+
+ # Test points exactly on boundary corners of masked triangulation.
+ xs = [0.0, 3.0]
+ ys = [0.0, 3.0]
+ tris = trifinder(xs, ys)
+ assert_array_equal(tris, [0, 17])
+
+ #
+ # Test triangles with horizontal colinear points. These are not valid
+ # triangulations, but we try to deal with the simplest violations.
+ #
+
+ # If +ve, triangulation is OK, if -ve triangulation invalid,
+ # if zero have colinear points but should pass tests anyway.
+ delta = 0.0
+
+ x = [1.5, 0, 1, 2, 3, 1.5, 1.5]
+ y = [-1, 0, 0, 0, 0, delta, 1]
+ triangles = [[0, 2, 1], [0, 3, 2], [0, 4, 3], [1, 2, 5], [2, 3, 5],
+ [3, 4, 5], [1, 5, 6], [4, 6, 5]]
+ triang = mtri.Triangulation(x, y, triangles)
+ trifinder = triang.get_trifinder()
+
+ xs = [-0.1, 0.4, 0.9, 1.4, 1.9, 2.4, 2.9]
+ ys = [-0.1, 0.1]
+ xs, ys = np.meshgrid(xs, ys)
+ tris = trifinder(xs, ys)
+ assert_array_equal(tris, [[-1, 0, 0, 1, 1, 2, -1],
+ [-1, 6, 6, 6, 7, 7, -1]])
+
+ #
+ # Test triangles with vertical colinear points. These are not valid
+ # triangulations, but we try to deal with the simplest violations.
+ #
+
+ # If +ve, triangulation is OK, if -ve triangulation invalid,
+ # if zero have colinear points but should pass tests anyway.
+ delta = 0.0
+
+ x = [-1, -delta, 0, 0, 0, 0, 1]
+ y = [1.5, 1.5, 0, 1, 2, 3, 1.5]
+ triangles = [[0, 1, 2], [0, 1, 5], [1, 2, 3], [1, 3, 4], [1, 4, 5],
+ [2, 6, 3], [3, 6, 4], [4, 6, 5]]
+ triang = mtri.Triangulation(x, y, triangles)
+ trifinder = triang.get_trifinder()
+
+ xs = [-0.1, 0.1]
+ ys = [-0.1, 0.4, 0.9, 1.4, 1.9, 2.4, 2.9]
+ xs, ys = np.meshgrid(xs, ys)
+ tris = trifinder(xs, ys)
+ assert_array_equal(tris, [[-1, -1], [0, 5], [0, 5], [0, 6], [1, 6], [1, 7],
+ [-1, -1]])
+
+ # Test that changing triangulation by setting a mask causes the trifinder
+ # to be reinitialised.
+ x = [0, 1, 0, 1]
+ y = [0, 0, 1, 1]
+ triangles = [[0, 1, 2], [1, 3, 2]]
+ triang = mtri.Triangulation(x, y, triangles)
+ trifinder = triang.get_trifinder()
+
+ xs = [-0.2, 0.2, 0.8, 1.2]
+ ys = [0.5, 0.5, 0.5, 0.5]
+ tris = trifinder(xs, ys)
+ assert_array_equal(tris, [-1, 0, 1, -1])
+
+ triang.set_mask([1, 0])
+ assert trifinder == triang.get_trifinder()
+ tris = trifinder(xs, ys)
+ assert_array_equal(tris, [-1, -1, 1, -1])
+
+
+def test_triinterp():
+ # Test points within triangles of masked triangulation.
+ x, y = np.meshgrid(np.arange(4), np.arange(4))
+ x = x.ravel()
+ y = y.ravel()
+ z = 1.23*x - 4.79*y
+ triangles = [[0, 1, 4], [1, 5, 4], [1, 2, 5], [2, 6, 5], [2, 3, 6],
+ [3, 7, 6], [4, 5, 8], [5, 9, 8], [5, 6, 9], [6, 10, 9],
+ [6, 7, 10], [7, 11, 10], [8, 9, 12], [9, 13, 12], [9, 10, 13],
+ [10, 14, 13], [10, 11, 14], [11, 15, 14]]
+ mask = np.zeros(len(triangles))
+ mask[8:10] = 1
+ triang = mtri.Triangulation(x, y, triangles, mask)
+ linear_interp = mtri.LinearTriInterpolator(triang, z)
+ cubic_min_E = mtri.CubicTriInterpolator(triang, z)
+ cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom')
+
+ xs = np.linspace(0.25, 2.75, 6)
+ ys = [0.25, 0.75, 2.25, 2.75]
+ xs, ys = np.meshgrid(xs, ys) # Testing arrays with array.ndim = 2
+ for interp in (linear_interp, cubic_min_E, cubic_geom):
+ zs = interp(xs, ys)
+ assert_array_almost_equal(zs, (1.23*xs - 4.79*ys))
+
+ # Test points outside triangulation.
+ xs = [-0.25, 1.25, 1.75, 3.25]
+ ys = xs
+ xs, ys = np.meshgrid(xs, ys)
+ for interp in (linear_interp, cubic_min_E, cubic_geom):
+ zs = linear_interp(xs, ys)
+ assert_array_equal(zs.mask, [[True]*4]*4)
+
+ # Test mixed configuration (outside / inside).
+ xs = np.linspace(0.25, 1.75, 6)
+ ys = [0.25, 0.75, 1.25, 1.75]
+ xs, ys = np.meshgrid(xs, ys)
+ for interp in (linear_interp, cubic_min_E, cubic_geom):
+ zs = interp(xs, ys)
+ matest.assert_array_almost_equal(zs, (1.23*xs - 4.79*ys))
+ mask = (xs >= 1) * (xs <= 2) * (ys >= 1) * (ys <= 2)
+ assert_array_equal(zs.mask, mask)
+
+ # 2nd order patch test: on a grid with an 'arbitrary shaped' triangle,
+ # patch test shall be exact for quadratic functions and cubic
+ # interpolator if *kind* = user
+ (a, b, c) = (1.23, -4.79, 0.6)
+
+ def quad(x, y):
+ return a*(x-0.5)**2 + b*(y-0.5)**2 + c*x*y
+
+ def gradient_quad(x, y):
+ return (2*a*(x-0.5) + c*y, 2*b*(y-0.5) + c*x)
+
+ x = np.array([0.2, 0.33367, 0.669, 0., 1., 1., 0.])
+ y = np.array([0.3, 0.80755, 0.4335, 0., 0., 1., 1.])
+ triangles = np.array([[0, 1, 2], [3, 0, 4], [4, 0, 2], [4, 2, 5],
+ [1, 5, 2], [6, 5, 1], [6, 1, 0], [6, 0, 3]])
+ triang = mtri.Triangulation(x, y, triangles)
+ z = quad(x, y)
+ dz = gradient_quad(x, y)
+ # test points for 2nd order patch test
+ xs = np.linspace(0., 1., 5)
+ ys = np.linspace(0., 1., 5)
+ xs, ys = np.meshgrid(xs, ys)
+ cubic_user = mtri.CubicTriInterpolator(triang, z, kind='user', dz=dz)
+ interp_zs = cubic_user(xs, ys)
+ assert_array_almost_equal(interp_zs, quad(xs, ys))
+ (interp_dzsdx, interp_dzsdy) = cubic_user.gradient(x, y)
+ (dzsdx, dzsdy) = gradient_quad(x, y)
+ assert_array_almost_equal(interp_dzsdx, dzsdx)
+ assert_array_almost_equal(interp_dzsdy, dzsdy)
+
+ # Cubic improvement: cubic interpolation shall perform better than linear
+ # on a sufficiently dense mesh for a quadratic function.
+ n = 11
+ x, y = np.meshgrid(np.linspace(0., 1., n+1), np.linspace(0., 1., n+1))
+ x = x.ravel()
+ y = y.ravel()
+ z = quad(x, y)
+ triang = mtri.Triangulation(x, y, triangles=meshgrid_triangles(n+1))
+ xs, ys = np.meshgrid(np.linspace(0.1, 0.9, 5), np.linspace(0.1, 0.9, 5))
+ xs = xs.ravel()
+ ys = ys.ravel()
+ linear_interp = mtri.LinearTriInterpolator(triang, z)
+ cubic_min_E = mtri.CubicTriInterpolator(triang, z)
+ cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom')
+ zs = quad(xs, ys)
+ diff_lin = np.abs(linear_interp(xs, ys) - zs)
+ for interp in (cubic_min_E, cubic_geom):
+ diff_cubic = np.abs(interp(xs, ys) - zs)
+ assert np.max(diff_lin) >= 10 * np.max(diff_cubic)
+ assert (np.dot(diff_lin, diff_lin) >=
+ 100 * np.dot(diff_cubic, diff_cubic))
+
+
+def test_triinterpcubic_C1_continuity():
+ # Below the 4 tests which demonstrate C1 continuity of the
+ # TriCubicInterpolator (testing the cubic shape functions on arbitrary
+ # triangle):
+ #
+ # 1) Testing continuity of function & derivatives at corner for all 9
+ # shape functions. Testing also function values at same location.
+ # 2) Testing C1 continuity along each edge (as gradient is polynomial of
+ # 2nd order, it is sufficient to test at the middle).
+ # 3) Testing C1 continuity at triangle barycenter (where the 3 subtriangles
+ # meet)
+ # 4) Testing C1 continuity at median 1/3 points (midside between 2
+ # subtriangles)
+
+ # Utility test function check_continuity
+ def check_continuity(interpolator, loc, values=None):
+ """
+ Checks the continuity of interpolator (and its derivatives) near
+ location loc. Can check the value at loc itself if *values* is
+ provided.
+
+ *interpolator* TriInterpolator
+ *loc* location to test (x0, y0)
+ *values* (optional) array [z0, dzx0, dzy0] to check the value at *loc*
+ """
+ n_star = 24 # Number of continuity points in a boundary of loc
+ epsilon = 1.e-10 # Distance for loc boundary
+ k = 100. # Continuity coefficient
+ (loc_x, loc_y) = loc
+ star_x = loc_x + epsilon*np.cos(np.linspace(0., 2*np.pi, n_star))
+ star_y = loc_y + epsilon*np.sin(np.linspace(0., 2*np.pi, n_star))
+ z = interpolator([loc_x], [loc_y])[0]
+ (dzx, dzy) = interpolator.gradient([loc_x], [loc_y])
+ if values is not None:
+ assert_array_almost_equal(z, values[0])
+ assert_array_almost_equal(dzx[0], values[1])
+ assert_array_almost_equal(dzy[0], values[2])
+ diff_z = interpolator(star_x, star_y) - z
+ (tab_dzx, tab_dzy) = interpolator.gradient(star_x, star_y)
+ diff_dzx = tab_dzx - dzx
+ diff_dzy = tab_dzy - dzy
+ assert_array_less(diff_z, epsilon*k)
+ assert_array_less(diff_dzx, epsilon*k)
+ assert_array_less(diff_dzy, epsilon*k)
+
+ # Drawing arbitrary triangle (a, b, c) inside a unit square.
+ (ax, ay) = (0.2, 0.3)
+ (bx, by) = (0.33367, 0.80755)
+ (cx, cy) = (0.669, 0.4335)
+ x = np.array([ax, bx, cx, 0., 1., 1., 0.])
+ y = np.array([ay, by, cy, 0., 0., 1., 1.])
+ triangles = np.array([[0, 1, 2], [3, 0, 4], [4, 0, 2], [4, 2, 5],
+ [1, 5, 2], [6, 5, 1], [6, 1, 0], [6, 0, 3]])
+ triang = mtri.Triangulation(x, y, triangles)
+
+ for idof in range(9):
+ z = np.zeros(7, dtype=np.float64)
+ dzx = np.zeros(7, dtype=np.float64)
+ dzy = np.zeros(7, dtype=np.float64)
+ values = np.zeros([3, 3], dtype=np.float64)
+ case = idof//3
+ values[case, idof % 3] = 1.0
+ if case == 0:
+ z[idof] = 1.0
+ elif case == 1:
+ dzx[idof % 3] = 1.0
+ elif case == 2:
+ dzy[idof % 3] = 1.0
+ interp = mtri.CubicTriInterpolator(triang, z, kind='user',
+ dz=(dzx, dzy))
+ # Test 1) Checking values and continuity at nodes
+ check_continuity(interp, (ax, ay), values[:, 0])
+ check_continuity(interp, (bx, by), values[:, 1])
+ check_continuity(interp, (cx, cy), values[:, 2])
+ # Test 2) Checking continuity at midside nodes
+ check_continuity(interp, ((ax+bx)*0.5, (ay+by)*0.5))
+ check_continuity(interp, ((ax+cx)*0.5, (ay+cy)*0.5))
+ check_continuity(interp, ((cx+bx)*0.5, (cy+by)*0.5))
+ # Test 3) Checking continuity at barycenter
+ check_continuity(interp, ((ax+bx+cx)/3., (ay+by+cy)/3.))
+ # Test 4) Checking continuity at median 1/3-point
+ check_continuity(interp, ((4.*ax+bx+cx)/6., (4.*ay+by+cy)/6.))
+ check_continuity(interp, ((ax+4.*bx+cx)/6., (ay+4.*by+cy)/6.))
+ check_continuity(interp, ((ax+bx+4.*cx)/6., (ay+by+4.*cy)/6.))
+
+
+def test_triinterpcubic_cg_solver():
+ # Now 3 basic tests of the Sparse CG solver, used for
+ # TriCubicInterpolator with *kind* = 'min_E'
+ # 1) A commonly used test involves a 2d Poisson matrix.
+ def poisson_sparse_matrix(n, m):
+ """
+ Return the sparse, (n*m, n*m) matrix in coo format resulting from the
+ discretisation of the 2-dimensional Poisson equation according to a
+ finite difference numerical scheme on a uniform (n, m) grid.
+ """
+ l = m*n
+ rows = np.concatenate([
+ np.arange(l, dtype=np.int32),
+ np.arange(l-1, dtype=np.int32), np.arange(1, l, dtype=np.int32),
+ np.arange(l-n, dtype=np.int32), np.arange(n, l, dtype=np.int32)])
+ cols = np.concatenate([
+ np.arange(l, dtype=np.int32),
+ np.arange(1, l, dtype=np.int32), np.arange(l-1, dtype=np.int32),
+ np.arange(n, l, dtype=np.int32), np.arange(l-n, dtype=np.int32)])
+ vals = np.concatenate([
+ 4*np.ones(l, dtype=np.float64),
+ -np.ones(l-1, dtype=np.float64), -np.ones(l-1, dtype=np.float64),
+ -np.ones(l-n, dtype=np.float64), -np.ones(l-n, dtype=np.float64)])
+ # In fact +1 and -1 diags have some zeros
+ vals[l:2*l-1][m-1::m] = 0.
+ vals[2*l-1:3*l-2][m-1::m] = 0.
+ return vals, rows, cols, (n*m, n*m)
+
+ # Instantiating a sparse Poisson matrix of size 48 x 48:
+ (n, m) = (12, 4)
+ mat = mtri._triinterpolate._Sparse_Matrix_coo(*poisson_sparse_matrix(n, m))
+ mat.compress_csc()
+ mat_dense = mat.to_dense()
+ # Testing a sparse solve for all 48 basis vector
+ for itest in range(n*m):
+ b = np.zeros(n*m, dtype=np.float64)
+ b[itest] = 1.
+ x, _ = mtri._triinterpolate._cg(A=mat, b=b, x0=np.zeros(n*m),
+ tol=1.e-10)
+ assert_array_almost_equal(np.dot(mat_dense, x), b)
+
+ # 2) Same matrix with inserting 2 rows - cols with null diag terms
+ # (but still linked with the rest of the matrix by extra-diag terms)
+ (i_zero, j_zero) = (12, 49)
+ vals, rows, cols, _ = poisson_sparse_matrix(n, m)
+ rows = rows + 1*(rows >= i_zero) + 1*(rows >= j_zero)
+ cols = cols + 1*(cols >= i_zero) + 1*(cols >= j_zero)
+ # adding extra-diag terms
+ rows = np.concatenate([rows, [i_zero, i_zero-1, j_zero, j_zero-1]])
+ cols = np.concatenate([cols, [i_zero-1, i_zero, j_zero-1, j_zero]])
+ vals = np.concatenate([vals, [1., 1., 1., 1.]])
+ mat = mtri._triinterpolate._Sparse_Matrix_coo(vals, rows, cols,
+ (n*m + 2, n*m + 2))
+ mat.compress_csc()
+ mat_dense = mat.to_dense()
+ # Testing a sparse solve for all 50 basis vec
+ for itest in range(n*m + 2):
+ b = np.zeros(n*m + 2, dtype=np.float64)
+ b[itest] = 1.
+ x, _ = mtri._triinterpolate._cg(A=mat, b=b, x0=np.ones(n * m + 2),
+ tol=1.e-10)
+ assert_array_almost_equal(np.dot(mat_dense, x), b)
+
+ # 3) Now a simple test that summation of duplicate (i.e. with same rows,
+ # same cols) entries occurs when compressed.
+ vals = np.ones(17, dtype=np.float64)
+ rows = np.array([0, 1, 2, 0, 0, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1],
+ dtype=np.int32)
+ cols = np.array([0, 1, 2, 1, 1, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
+ dtype=np.int32)
+ dim = (3, 3)
+ mat = mtri._triinterpolate._Sparse_Matrix_coo(vals, rows, cols, dim)
+ mat.compress_csc()
+ mat_dense = mat.to_dense()
+ assert_array_almost_equal(mat_dense, np.array([
+ [1., 2., 0.], [2., 1., 5.], [0., 5., 1.]], dtype=np.float64))
+
+
+def test_triinterpcubic_geom_weights():
+ # Tests to check computation of weights for _DOF_estimator_geom:
+ # The weight sum per triangle can be 1. (in case all angles < 90 degrees)
+ # or (2*w_i) where w_i = 1-alpha_i/np.pi is the weight of apex i; alpha_i
+ # is the apex angle > 90 degrees.
+ (ax, ay) = (0., 1.687)
+ x = np.array([ax, 0.5*ax, 0., 1.])
+ y = np.array([ay, -ay, 0., 0.])
+ z = np.zeros(4, dtype=np.float64)
+ triangles = [[0, 2, 3], [1, 3, 2]]
+ sum_w = np.zeros([4, 2]) # 4 possibilities; 2 triangles
+ for theta in np.linspace(0., 2*np.pi, 14): # rotating the figure...
+ x_rot = np.cos(theta)*x + np.sin(theta)*y
+ y_rot = -np.sin(theta)*x + np.cos(theta)*y
+ triang = mtri.Triangulation(x_rot, y_rot, triangles)
+ cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom')
+ dof_estimator = mtri._triinterpolate._DOF_estimator_geom(cubic_geom)
+ weights = dof_estimator.compute_geom_weights()
+ # Testing for the 4 possibilities...
+ sum_w[0, :] = np.sum(weights, 1) - 1
+ for itri in range(3):
+ sum_w[itri+1, :] = np.sum(weights, 1) - 2*weights[:, itri]
+ assert_array_almost_equal(np.min(np.abs(sum_w), axis=0),
+ np.array([0., 0.], dtype=np.float64))
+
+
+def test_triinterp_colinear():
+ # Tests interpolating inside a triangulation with horizontal colinear
+ # points (refer also to the tests :func:`test_trifinder` ).
+ #
+ # These are not valid triangulations, but we try to deal with the
+ # simplest violations (i. e. those handled by default TriFinder).
+ #
+ # Note that the LinearTriInterpolator and the CubicTriInterpolator with
+ # kind='min_E' or 'geom' still pass a linear patch test.
+ # We also test interpolation inside a flat triangle, by forcing
+ # *tri_index* in a call to :meth:`_interpolate_multikeys`.
+
+ # If +ve, triangulation is OK, if -ve triangulation invalid,
+ # if zero have colinear points but should pass tests anyway.
+ delta = 0.
+
+ x0 = np.array([1.5, 0, 1, 2, 3, 1.5, 1.5])
+ y0 = np.array([-1, 0, 0, 0, 0, delta, 1])
+
+ # We test different affine transformations of the initial figure; to
+ # avoid issues related to round-off errors we only use integer
+ # coefficients (otherwise the Triangulation might become invalid even with
+ # delta == 0).
+ transformations = [[1, 0], [0, 1], [1, 1], [1, 2], [-2, -1], [-2, 1]]
+ for transformation in transformations:
+ x_rot = transformation[0]*x0 + transformation[1]*y0
+ y_rot = -transformation[1]*x0 + transformation[0]*y0
+ (x, y) = (x_rot, y_rot)
+ z = 1.23*x - 4.79*y
+ triangles = [[0, 2, 1], [0, 3, 2], [0, 4, 3], [1, 2, 5], [2, 3, 5],
+ [3, 4, 5], [1, 5, 6], [4, 6, 5]]
+ triang = mtri.Triangulation(x, y, triangles)
+ xs = np.linspace(np.min(triang.x), np.max(triang.x), 20)
+ ys = np.linspace(np.min(triang.y), np.max(triang.y), 20)
+ xs, ys = np.meshgrid(xs, ys)
+ xs = xs.ravel()
+ ys = ys.ravel()
+ mask_out = (triang.get_trifinder()(xs, ys) == -1)
+ zs_target = np.ma.array(1.23*xs - 4.79*ys, mask=mask_out)
+
+ linear_interp = mtri.LinearTriInterpolator(triang, z)
+ cubic_min_E = mtri.CubicTriInterpolator(triang, z)
+ cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom')
+
+ for interp in (linear_interp, cubic_min_E, cubic_geom):
+ zs = interp(xs, ys)
+ assert_array_almost_equal(zs_target, zs)
+
+ # Testing interpolation inside the flat triangle number 4: [2, 3, 5]
+ # by imposing *tri_index* in a call to :meth:`_interpolate_multikeys`
+ itri = 4
+ pt1 = triang.triangles[itri, 0]
+ pt2 = triang.triangles[itri, 1]
+ xs = np.linspace(triang.x[pt1], triang.x[pt2], 10)
+ ys = np.linspace(triang.y[pt1], triang.y[pt2], 10)
+ zs_target = 1.23*xs - 4.79*ys
+ for interp in (linear_interp, cubic_min_E, cubic_geom):
+ zs, = interp._interpolate_multikeys(
+ xs, ys, tri_index=itri*np.ones(10, dtype=np.int32))
+ assert_array_almost_equal(zs_target, zs)
+
+
+def test_triinterp_transformations():
+ # 1) Testing that the interpolation scheme is invariant by rotation of the
+ # whole figure.
+ # Note: This test is non-trivial for a CubicTriInterpolator with
+ # kind='min_E'. It does fail for a non-isotropic stiffness matrix E of
+ # :class:`_ReducedHCT_Element` (tested with E=np.diag([1., 1., 1.])), and
+ # provides a good test for :meth:`get_Kff_and_Ff`of the same class.
+ #
+ # 2) Also testing that the interpolation scheme is invariant by expansion
+ # of the whole figure along one axis.
+ n_angles = 20
+ n_radii = 10
+ min_radius = 0.15
+
+ def z(x, y):
+ r1 = np.hypot(0.5 - x, 0.5 - y)
+ theta1 = np.arctan2(0.5 - x, 0.5 - y)
+ r2 = np.hypot(-x - 0.2, -y - 0.2)
+ theta2 = np.arctan2(-x - 0.2, -y - 0.2)
+ z = -(2*(np.exp((r1/10)**2)-1)*30. * np.cos(7.*theta1) +
+ (np.exp((r2/10)**2)-1)*30. * np.cos(11.*theta2) +
+ 0.7*(x**2 + y**2))
+ return (np.max(z)-z)/(np.max(z)-np.min(z))
+
+ # First create the x and y coordinates of the points.
+ radii = np.linspace(min_radius, 0.95, n_radii)
+ angles = np.linspace(0 + n_angles, 2*np.pi + n_angles,
+ n_angles, endpoint=False)
+ angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
+ angles[:, 1::2] += np.pi/n_angles
+ x0 = (radii*np.cos(angles)).flatten()
+ y0 = (radii*np.sin(angles)).flatten()
+ triang0 = mtri.Triangulation(x0, y0) # Delaunay triangulation
+ z0 = z(x0, y0)
+
+ # Then create the test points
+ xs0 = np.linspace(-1., 1., 23)
+ ys0 = np.linspace(-1., 1., 23)
+ xs0, ys0 = np.meshgrid(xs0, ys0)
+ xs0 = xs0.ravel()
+ ys0 = ys0.ravel()
+
+ interp_z0 = {}
+ for i_angle in range(2):
+ # Rotating everything
+ theta = 2*np.pi / n_angles * i_angle
+ x = np.cos(theta)*x0 + np.sin(theta)*y0
+ y = -np.sin(theta)*x0 + np.cos(theta)*y0
+ xs = np.cos(theta)*xs0 + np.sin(theta)*ys0
+ ys = -np.sin(theta)*xs0 + np.cos(theta)*ys0
+ triang = mtri.Triangulation(x, y, triang0.triangles)
+ linear_interp = mtri.LinearTriInterpolator(triang, z0)
+ cubic_min_E = mtri.CubicTriInterpolator(triang, z0)
+ cubic_geom = mtri.CubicTriInterpolator(triang, z0, kind='geom')
+ dic_interp = {'lin': linear_interp,
+ 'min_E': cubic_min_E,
+ 'geom': cubic_geom}
+ # Testing that the interpolation is invariant by rotation...
+ for interp_key in ['lin', 'min_E', 'geom']:
+ interp = dic_interp[interp_key]
+ if i_angle == 0:
+ interp_z0[interp_key] = interp(xs0, ys0) # storage
+ else:
+ interpz = interp(xs, ys)
+ matest.assert_array_almost_equal(interpz,
+ interp_z0[interp_key])
+
+ scale_factor = 987654.3210
+ for scaled_axis in ('x', 'y'):
+ # Scaling everything (expansion along scaled_axis)
+ if scaled_axis == 'x':
+ x = scale_factor * x0
+ y = y0
+ xs = scale_factor * xs0
+ ys = ys0
+ else:
+ x = x0
+ y = scale_factor * y0
+ xs = xs0
+ ys = scale_factor * ys0
+ triang = mtri.Triangulation(x, y, triang0.triangles)
+ linear_interp = mtri.LinearTriInterpolator(triang, z0)
+ cubic_min_E = mtri.CubicTriInterpolator(triang, z0)
+ cubic_geom = mtri.CubicTriInterpolator(triang, z0, kind='geom')
+ dic_interp = {'lin': linear_interp,
+ 'min_E': cubic_min_E,
+ 'geom': cubic_geom}
+ # Test that the interpolation is invariant by expansion along 1 axis...
+ for interp_key in ['lin', 'min_E', 'geom']:
+ interpz = dic_interp[interp_key](xs, ys)
+ matest.assert_array_almost_equal(interpz, interp_z0[interp_key])
+
+
+@image_comparison(['tri_smooth_contouring.png'], remove_text=True, tol=0.072)
+def test_tri_smooth_contouring():
+ # Image comparison based on example tricontour_smooth_user.
+ n_angles = 20
+ n_radii = 10
+ min_radius = 0.15
+
+ def z(x, y):
+ r1 = np.hypot(0.5 - x, 0.5 - y)
+ theta1 = np.arctan2(0.5 - x, 0.5 - y)
+ r2 = np.hypot(-x - 0.2, -y - 0.2)
+ theta2 = np.arctan2(-x - 0.2, -y - 0.2)
+ z = -(2*(np.exp((r1/10)**2)-1)*30. * np.cos(7.*theta1) +
+ (np.exp((r2/10)**2)-1)*30. * np.cos(11.*theta2) +
+ 0.7*(x**2 + y**2))
+ return (np.max(z)-z)/(np.max(z)-np.min(z))
+
+ # First create the x and y coordinates of the points.
+ radii = np.linspace(min_radius, 0.95, n_radii)
+ angles = np.linspace(0 + n_angles, 2*np.pi + n_angles,
+ n_angles, endpoint=False)
+ angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
+ angles[:, 1::2] += np.pi/n_angles
+ x0 = (radii*np.cos(angles)).flatten()
+ y0 = (radii*np.sin(angles)).flatten()
+ triang0 = mtri.Triangulation(x0, y0) # Delaunay triangulation
+ z0 = z(x0, y0)
+ triang0.set_mask(np.hypot(x0[triang0.triangles].mean(axis=1),
+ y0[triang0.triangles].mean(axis=1))
+ < min_radius)
+
+ # Then the plot
+ refiner = mtri.UniformTriRefiner(triang0)
+ tri_refi, z_test_refi = refiner.refine_field(z0, subdiv=4)
+ levels = np.arange(0., 1., 0.025)
+ plt.triplot(triang0, lw=0.5, color='0.5')
+ plt.tricontour(tri_refi, z_test_refi, levels=levels, colors="black")
+
+
+@image_comparison(['tri_smooth_gradient.png'], remove_text=True, tol=0.092)
+def test_tri_smooth_gradient():
+ # Image comparison based on example trigradient_demo.
+
+ def dipole_potential(x, y):
+ """An electric dipole potential V."""
+ r_sq = x**2 + y**2
+ theta = np.arctan2(y, x)
+ z = np.cos(theta)/r_sq
+ return (np.max(z)-z) / (np.max(z)-np.min(z))
+
+ # Creating a Triangulation
+ n_angles = 30
+ n_radii = 10
+ min_radius = 0.2
+ radii = np.linspace(min_radius, 0.95, n_radii)
+ angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
+ angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
+ angles[:, 1::2] += np.pi/n_angles
+ x = (radii*np.cos(angles)).flatten()
+ y = (radii*np.sin(angles)).flatten()
+ V = dipole_potential(x, y)
+ triang = mtri.Triangulation(x, y)
+ triang.set_mask(np.hypot(x[triang.triangles].mean(axis=1),
+ y[triang.triangles].mean(axis=1))
+ < min_radius)
+
+ # Refine data - interpolates the electrical potential V
+ refiner = mtri.UniformTriRefiner(triang)
+ tri_refi, z_test_refi = refiner.refine_field(V, subdiv=3)
+
+ # Computes the electrical field (Ex, Ey) as gradient of -V
+ tci = mtri.CubicTriInterpolator(triang, -V)
+ Ex, Ey = tci.gradient(triang.x, triang.y)
+ E_norm = np.hypot(Ex, Ey)
+
+ # Plot the triangulation, the potential iso-contours and the vector field
+ plt.figure()
+ plt.gca().set_aspect('equal')
+ plt.triplot(triang, color='0.8')
+
+ levels = np.arange(0., 1., 0.01)
+ cmap = mpl.colormaps['hot']
+ plt.tricontour(tri_refi, z_test_refi, levels=levels, cmap=cmap,
+ linewidths=[2.0, 1.0, 1.0, 1.0])
+ # Plots direction of the electrical vector field
+ plt.quiver(triang.x, triang.y, Ex/E_norm, Ey/E_norm,
+ units='xy', scale=10., zorder=3, color='blue',
+ width=0.007, headwidth=3., headlength=4.)
+ # We are leaving ax.use_sticky_margins as True, so the
+ # view limits are the contour data limits.
+
+
+def test_tritools():
+ # Tests TriAnalyzer.scale_factors on masked triangulation
+ # Tests circle_ratios on equilateral and right-angled triangle.
+ x = np.array([0., 1., 0.5, 0., 2.])
+ y = np.array([0., 0., 0.5*np.sqrt(3.), -1., 1.])
+ triangles = np.array([[0, 1, 2], [0, 1, 3], [1, 2, 4]], dtype=np.int32)
+ mask = np.array([False, False, True], dtype=bool)
+ triang = mtri.Triangulation(x, y, triangles, mask=mask)
+ analyser = mtri.TriAnalyzer(triang)
+ assert_array_almost_equal(analyser.scale_factors, [1, 1/(1+3**.5/2)])
+ assert_array_almost_equal(
+ analyser.circle_ratios(rescale=False),
+ np.ma.masked_array([0.5, 1./(1.+np.sqrt(2.)), np.nan], mask))
+
+ # Tests circle ratio of a flat triangle
+ x = np.array([0., 1., 2.])
+ y = np.array([1., 1.+3., 1.+6.])
+ triangles = np.array([[0, 1, 2]], dtype=np.int32)
+ triang = mtri.Triangulation(x, y, triangles)
+ analyser = mtri.TriAnalyzer(triang)
+ assert_array_almost_equal(analyser.circle_ratios(), np.array([0.]))
+
+ # Tests TriAnalyzer.get_flat_tri_mask
+ # Creates a triangulation of [-1, 1] x [-1, 1] with contiguous groups of
+ # 'flat' triangles at the 4 corners and at the center. Checks that only
+ # those at the borders are eliminated by TriAnalyzer.get_flat_tri_mask
+ n = 9
+
+ def power(x, a):
+ return np.abs(x)**a*np.sign(x)
+
+ x = np.linspace(-1., 1., n+1)
+ x, y = np.meshgrid(power(x, 2.), power(x, 0.25))
+ x = x.ravel()
+ y = y.ravel()
+
+ triang = mtri.Triangulation(x, y, triangles=meshgrid_triangles(n+1))
+ analyser = mtri.TriAnalyzer(triang)
+ mask_flat = analyser.get_flat_tri_mask(0.2)
+ verif_mask = np.zeros(162, dtype=bool)
+ corners_index = [0, 1, 2, 3, 14, 15, 16, 17, 18, 19, 34, 35, 126, 127,
+ 142, 143, 144, 145, 146, 147, 158, 159, 160, 161]
+ verif_mask[corners_index] = True
+ assert_array_equal(mask_flat, verif_mask)
+
+ # Now including a hole (masked triangle) at the center. The center also
+ # shall be eliminated by get_flat_tri_mask.
+ mask = np.zeros(162, dtype=bool)
+ mask[80] = True
+ triang.set_mask(mask)
+ mask_flat = analyser.get_flat_tri_mask(0.2)
+ center_index = [44, 45, 62, 63, 78, 79, 80, 81, 82, 83, 98, 99, 116, 117]
+ verif_mask[center_index] = True
+ assert_array_equal(mask_flat, verif_mask)
+
+
+def test_trirefine():
+ # Testing subdiv=2 refinement
+ n = 3
+ subdiv = 2
+ x = np.linspace(-1., 1., n+1)
+ x, y = np.meshgrid(x, x)
+ x = x.ravel()
+ y = y.ravel()
+ mask = np.zeros(2*n**2, dtype=bool)
+ mask[n**2:] = True
+ triang = mtri.Triangulation(x, y, triangles=meshgrid_triangles(n+1),
+ mask=mask)
+ refiner = mtri.UniformTriRefiner(triang)
+ refi_triang = refiner.refine_triangulation(subdiv=subdiv)
+ x_refi = refi_triang.x
+ y_refi = refi_triang.y
+
+ n_refi = n * subdiv**2
+ x_verif = np.linspace(-1., 1., n_refi+1)
+ x_verif, y_verif = np.meshgrid(x_verif, x_verif)
+ x_verif = x_verif.ravel()
+ y_verif = y_verif.ravel()
+ ind1d = np.isin(np.around(x_verif*(2.5+y_verif), 8),
+ np.around(x_refi*(2.5+y_refi), 8))
+ assert_array_equal(ind1d, True)
+
+ # Testing the mask of the refined triangulation
+ refi_mask = refi_triang.mask
+ refi_tri_barycenter_x = np.sum(refi_triang.x[refi_triang.triangles],
+ axis=1) / 3.
+ refi_tri_barycenter_y = np.sum(refi_triang.y[refi_triang.triangles],
+ axis=1) / 3.
+ tri_finder = triang.get_trifinder()
+ refi_tri_indices = tri_finder(refi_tri_barycenter_x,
+ refi_tri_barycenter_y)
+ refi_tri_mask = triang.mask[refi_tri_indices]
+ assert_array_equal(refi_mask, refi_tri_mask)
+
+ # Testing that the numbering of triangles does not change the
+ # interpolation result.
+ x = np.asarray([0.0, 1.0, 0.0, 1.0])
+ y = np.asarray([0.0, 0.0, 1.0, 1.0])
+ triang = [mtri.Triangulation(x, y, [[0, 1, 3], [3, 2, 0]]),
+ mtri.Triangulation(x, y, [[0, 1, 3], [2, 0, 3]])]
+ z = np.hypot(x - 0.3, y - 0.4)
+ # Refining the 2 triangulations and reordering the points
+ xyz_data = []
+ for i in range(2):
+ refiner = mtri.UniformTriRefiner(triang[i])
+ refined_triang, refined_z = refiner.refine_field(z, subdiv=1)
+ xyz = np.dstack((refined_triang.x, refined_triang.y, refined_z))[0]
+ xyz = xyz[np.lexsort((xyz[:, 1], xyz[:, 0]))]
+ xyz_data += [xyz]
+ assert_array_almost_equal(xyz_data[0], xyz_data[1])
+
+
+@pytest.mark.parametrize('interpolator',
+ [mtri.LinearTriInterpolator,
+ mtri.CubicTriInterpolator],
+ ids=['linear', 'cubic'])
+def test_trirefine_masked(interpolator):
+ # Repeated points means we will have fewer triangles than points, and thus
+ # get masking.
+ x, y = np.mgrid[:2, :2]
+ x = np.repeat(x.flatten(), 2)
+ y = np.repeat(y.flatten(), 2)
+
+ z = np.zeros_like(x)
+ tri = mtri.Triangulation(x, y)
+ refiner = mtri.UniformTriRefiner(tri)
+ interp = interpolator(tri, z)
+ refiner.refine_field(z, triinterpolator=interp, subdiv=2)
+
+
+def meshgrid_triangles(n):
+ """
+ Return (2*(N-1)**2, 3) array of triangles to mesh (N, N)-point np.meshgrid.
+ """
+ tri = []
+ for i in range(n-1):
+ for j in range(n-1):
+ a = i + j*n
+ b = (i+1) + j*n
+ c = i + (j+1)*n
+ d = (i+1) + (j+1)*n
+ tri += [[a, b, d], [a, d, c]]
+ return np.array(tri, dtype=np.int32)
+
+
+def test_triplot_return():
+ # Check that triplot returns the artists it adds
+ ax = plt.figure().add_subplot()
+ triang = mtri.Triangulation(
+ [0.0, 1.0, 0.0, 1.0], [0.0, 0.0, 1.0, 1.0],
+ triangles=[[0, 1, 3], [3, 2, 0]])
+ assert ax.triplot(triang, "b-") is not None, \
+ 'triplot should return the artist it adds'
+
+
+def test_trirefiner_fortran_contiguous_triangles():
+ # github issue 4180. Test requires two arrays of triangles that are
+ # identical except that one is C-contiguous and one is fortran-contiguous.
+ triangles1 = np.array([[2, 0, 3], [2, 1, 0]])
+ assert not np.isfortran(triangles1)
+
+ triangles2 = np.array(triangles1, copy=True, order='F')
+ assert np.isfortran(triangles2)
+
+ x = np.array([0.39, 0.59, 0.43, 0.32])
+ y = np.array([33.99, 34.01, 34.19, 34.18])
+ triang1 = mtri.Triangulation(x, y, triangles1)
+ triang2 = mtri.Triangulation(x, y, triangles2)
+
+ refiner1 = mtri.UniformTriRefiner(triang1)
+ refiner2 = mtri.UniformTriRefiner(triang2)
+
+ fine_triang1 = refiner1.refine_triangulation(subdiv=1)
+ fine_triang2 = refiner2.refine_triangulation(subdiv=1)
+
+ assert_array_equal(fine_triang1.triangles, fine_triang2.triangles)
+
+
+def test_qhull_triangle_orientation():
+ # github issue 4437.
+ xi = np.linspace(-2, 2, 100)
+ x, y = map(np.ravel, np.meshgrid(xi, xi))
+ w = (x > y - 1) & (x < -1.95) & (y > -1.2)
+ x, y = x[w], y[w]
+ theta = np.radians(25)
+ x1 = x*np.cos(theta) - y*np.sin(theta)
+ y1 = x*np.sin(theta) + y*np.cos(theta)
+
+ # Calculate Delaunay triangulation using Qhull.
+ triang = mtri.Triangulation(x1, y1)
+
+ # Neighbors returned by Qhull.
+ qhull_neighbors = triang.neighbors
+
+ # Obtain neighbors using own C++ calculation.
+ triang._neighbors = None
+ own_neighbors = triang.neighbors
+
+ assert_array_equal(qhull_neighbors, own_neighbors)
+
+
+def test_trianalyzer_mismatched_indices():
+ # github issue 4999.
+ x = np.array([0., 1., 0.5, 0., 2.])
+ y = np.array([0., 0., 0.5*np.sqrt(3.), -1., 1.])
+ triangles = np.array([[0, 1, 2], [0, 1, 3], [1, 2, 4]], dtype=np.int32)
+ mask = np.array([False, False, True], dtype=bool)
+ triang = mtri.Triangulation(x, y, triangles, mask=mask)
+ analyser = mtri.TriAnalyzer(triang)
+ # numpy >= 1.10 raises a VisibleDeprecationWarning in the following line
+ # prior to the fix.
+ analyser._get_compressed_triangulation()
+
+
+def test_tricontourf_decreasing_levels():
+ # github issue 5477.
+ x = [0.0, 1.0, 1.0]
+ y = [0.0, 0.0, 1.0]
+ z = [0.2, 0.4, 0.6]
+ plt.figure()
+ with pytest.raises(ValueError):
+ plt.tricontourf(x, y, z, [1.0, 0.0])
+
+
+def test_internal_cpp_api() -> None:
+ # Following github issue 8197.
+ from matplotlib import _tri # noqa: F401, ensure lazy-loaded module *is* loaded.
+
+ # C++ Triangulation.
+ with pytest.raises(
+ TypeError,
+ match=r'__init__\(\): incompatible constructor arguments.'):
+ mpl._tri.Triangulation() # type: ignore[call-arg]
+
+ with pytest.raises(
+ ValueError, match=r'x and y must be 1D arrays of the same length'):
+ mpl._tri.Triangulation(np.array([]), np.array([1]), np.array([[]]), (), (), (),
+ False)
+
+ x = np.array([0, 1, 1], dtype=np.float64)
+ y = np.array([0, 0, 1], dtype=np.float64)
+ with pytest.raises(
+ ValueError,
+ match=r'triangles must be a 2D array of shape \(\?,3\)'):
+ mpl._tri.Triangulation(x, y, np.array([[0, 1]]), (), (), (), False)
+
+ tris = np.array([[0, 1, 2]], dtype=np.int_)
+ with pytest.raises(
+ ValueError,
+ match=r'mask must be a 1D array with the same length as the '
+ r'triangles array'):
+ mpl._tri.Triangulation(x, y, tris, np.array([0, 1]), (), (), False)
+
+ with pytest.raises(
+ ValueError, match=r'edges must be a 2D array with shape \(\?,2\)'):
+ mpl._tri.Triangulation(x, y, tris, (), np.array([[1]]), (), False)
+
+ with pytest.raises(
+ ValueError,
+ match=r'neighbors must be a 2D array with the same shape as the '
+ r'triangles array'):
+ mpl._tri.Triangulation(x, y, tris, (), (), np.array([[-1]]), False)
+
+ triang = mpl._tri.Triangulation(x, y, tris, (), (), (), False)
+
+ with pytest.raises(
+ ValueError,
+ match=r'z must be a 1D array with the same length as the '
+ r'triangulation x and y arrays'):
+ triang.calculate_plane_coefficients([])
+
+ for mask in ([0, 1], None):
+ with pytest.raises(
+ ValueError,
+ match=r'mask must be a 1D array with the same length as the '
+ r'triangles array'):
+ triang.set_mask(mask) # type: ignore[arg-type]
+
+ triang.set_mask(np.array([True]))
+ assert_array_equal(triang.get_edges(), np.empty((0, 2)))
+
+ triang.set_mask(()) # Equivalent to Python Triangulation mask=None
+ assert_array_equal(triang.get_edges(), [[1, 0], [2, 0], [2, 1]])
+
+ # C++ TriContourGenerator.
+ with pytest.raises(
+ TypeError,
+ match=r'__init__\(\): incompatible constructor arguments.'):
+ mpl._tri.TriContourGenerator() # type: ignore[call-arg]
+
+ with pytest.raises(
+ ValueError,
+ match=r'z must be a 1D array with the same length as the x and y arrays'):
+ mpl._tri.TriContourGenerator(triang, np.array([1]))
+
+ z = np.array([0, 1, 2])
+ tcg = mpl._tri.TriContourGenerator(triang, z)
+
+ with pytest.raises(
+ ValueError, match=r'filled contour levels must be increasing'):
+ tcg.create_filled_contour(1, 0)
+
+ # C++ TrapezoidMapTriFinder.
+ with pytest.raises(
+ TypeError,
+ match=r'__init__\(\): incompatible constructor arguments.'):
+ mpl._tri.TrapezoidMapTriFinder() # type: ignore[call-arg]
+
+ trifinder = mpl._tri.TrapezoidMapTriFinder(triang)
+
+ with pytest.raises(
+ ValueError, match=r'x and y must be array-like with same shape'):
+ trifinder.find_many(np.array([0]), np.array([0, 1]))
+
+
+def test_qhull_large_offset():
+ # github issue 8682.
+ x = np.asarray([0, 1, 0, 1, 0.5])
+ y = np.asarray([0, 0, 1, 1, 0.5])
+
+ offset = 1e10
+ triang = mtri.Triangulation(x, y)
+ triang_offset = mtri.Triangulation(x + offset, y + offset)
+ assert len(triang.triangles) == len(triang_offset.triangles)
+
+
+def test_tricontour_non_finite_z():
+ # github issue 10167.
+ x = [0, 1, 0, 1]
+ y = [0, 0, 1, 1]
+ triang = mtri.Triangulation(x, y)
+ plt.figure()
+
+ with pytest.raises(ValueError, match='z array must not contain non-finite '
+ 'values within the triangulation'):
+ plt.tricontourf(triang, [0, 1, 2, np.inf])
+
+ with pytest.raises(ValueError, match='z array must not contain non-finite '
+ 'values within the triangulation'):
+ plt.tricontourf(triang, [0, 1, 2, -np.inf])
+
+ with pytest.raises(ValueError, match='z array must not contain non-finite '
+ 'values within the triangulation'):
+ plt.tricontourf(triang, [0, 1, 2, np.nan])
+
+ with pytest.raises(ValueError, match='z must not contain masked points '
+ 'within the triangulation'):
+ plt.tricontourf(triang, np.ma.array([0, 1, 2, 3], mask=[1, 0, 0, 0]))
+
+
+def test_tricontourset_reuse():
+ # If TriContourSet returned from one tricontour(f) call is passed as first
+ # argument to another the underlying C++ contour generator will be reused.
+ x = [0.0, 0.5, 1.0]
+ y = [0.0, 1.0, 0.0]
+ z = [1.0, 2.0, 3.0]
+ fig, ax = plt.subplots()
+ tcs1 = ax.tricontourf(x, y, z)
+ tcs2 = ax.tricontour(x, y, z)
+ assert tcs2._contour_generator != tcs1._contour_generator
+ tcs3 = ax.tricontour(tcs1, z)
+ assert tcs3._contour_generator == tcs1._contour_generator
+
+
+@check_figures_equal(extensions=['png'])
+def test_triplot_with_ls(fig_test, fig_ref):
+ x = [0, 2, 1]
+ y = [0, 0, 1]
+ data = [[0, 1, 2]]
+ fig_test.subplots().triplot(x, y, data, ls='--')
+ fig_ref.subplots().triplot(x, y, data, linestyle='--')
+
+
+def test_triplot_label():
+ x = [0, 2, 1]
+ y = [0, 0, 1]
+ data = [[0, 1, 2]]
+ fig, ax = plt.subplots()
+ lines, markers = ax.triplot(x, y, data, label='label')
+ handles, labels = ax.get_legend_handles_labels()
+ assert labels == ['label']
+ assert len(handles) == 1
+ assert handles[0] is lines
+
+
+def test_tricontour_path():
+ x = [0, 4, 4, 0, 2]
+ y = [0, 0, 4, 4, 2]
+ triang = mtri.Triangulation(x, y)
+ _, ax = plt.subplots()
+
+ # Line strip from boundary to boundary
+ cs = ax.tricontour(triang, [1, 0, 0, 0, 0], levels=[0.5])
+ paths = cs.get_paths()
+ assert len(paths) == 1
+ expected_vertices = [[2, 0], [1, 1], [0, 2]]
+ assert_array_almost_equal(paths[0].vertices, expected_vertices)
+ assert_array_equal(paths[0].codes, [1, 2, 2])
+ assert_array_almost_equal(
+ paths[0].to_polygons(closed_only=False), [expected_vertices])
+
+ # Closed line loop inside domain
+ cs = ax.tricontour(triang, [0, 0, 0, 0, 1], levels=[0.5])
+ paths = cs.get_paths()
+ assert len(paths) == 1
+ expected_vertices = [[3, 1], [3, 3], [1, 3], [1, 1], [3, 1]]
+ assert_array_almost_equal(paths[0].vertices, expected_vertices)
+ assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79])
+ assert_array_almost_equal(paths[0].to_polygons(), [expected_vertices])
+
+
+def test_tricontourf_path():
+ x = [0, 4, 4, 0, 2]
+ y = [0, 0, 4, 4, 2]
+ triang = mtri.Triangulation(x, y)
+ _, ax = plt.subplots()
+
+ # Polygon inside domain
+ cs = ax.tricontourf(triang, [0, 0, 0, 0, 1], levels=[0.5, 1.5])
+ paths = cs.get_paths()
+ assert len(paths) == 1
+ expected_vertices = [[3, 1], [3, 3], [1, 3], [1, 1], [3, 1]]
+ assert_array_almost_equal(paths[0].vertices, expected_vertices)
+ assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79])
+ assert_array_almost_equal(paths[0].to_polygons(), [expected_vertices])
+
+ # Polygon following boundary and inside domain
+ cs = ax.tricontourf(triang, [1, 0, 0, 0, 0], levels=[0.5, 1.5])
+ paths = cs.get_paths()
+ assert len(paths) == 1
+ expected_vertices = [[2, 0], [1, 1], [0, 2], [0, 0], [2, 0]]
+ assert_array_almost_equal(paths[0].vertices, expected_vertices)
+ assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79])
+ assert_array_almost_equal(paths[0].to_polygons(), [expected_vertices])
+
+ # Polygon is outer boundary with hole
+ cs = ax.tricontourf(triang, [0, 0, 0, 0, 1], levels=[-0.5, 0.5])
+ paths = cs.get_paths()
+ assert len(paths) == 1
+ expected_vertices = [[0, 0], [4, 0], [4, 4], [0, 4], [0, 0],
+ [1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]
+ assert_array_almost_equal(paths[0].vertices, expected_vertices)
+ assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79, 1, 2, 2, 2, 79])
+ assert_array_almost_equal(paths[0].to_polygons(), np.split(expected_vertices, [5]))
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_type1font.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_type1font.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b8a2d1f07c6fd7124a4bc8349766c01c3a58435
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_type1font.py
@@ -0,0 +1,160 @@
+import matplotlib._type1font as t1f
+import os.path
+import difflib
+import pytest
+
+
+def test_Type1Font():
+ filename = os.path.join(os.path.dirname(__file__), 'cmr10.pfb')
+ font = t1f.Type1Font(filename)
+ slanted = font.transform({'slant': 1})
+ condensed = font.transform({'extend': 0.5})
+ with open(filename, 'rb') as fd:
+ rawdata = fd.read()
+ assert font.parts[0] == rawdata[0x0006:0x10c5]
+ assert font.parts[1] == rawdata[0x10cb:0x897f]
+ assert font.parts[2] == rawdata[0x8985:0x8ba6]
+ assert font.decrypted.startswith(b'dup\n/Private 18 dict dup begin')
+ assert font.decrypted.endswith(b'mark currentfile closefile\n')
+ assert slanted.decrypted.startswith(b'dup\n/Private 18 dict dup begin')
+ assert slanted.decrypted.endswith(b'mark currentfile closefile\n')
+ assert b'UniqueID 5000793' in font.parts[0]
+ assert b'UniqueID 5000793' in font.decrypted
+ assert font._pos['UniqueID'] == [(797, 818), (4483, 4504)]
+
+ len0 = len(font.parts[0])
+ for key in font._pos.keys():
+ for pos0, pos1 in font._pos[key]:
+ if pos0 < len0:
+ data = font.parts[0][pos0:pos1]
+ else:
+ data = font.decrypted[pos0-len0:pos1-len0]
+ assert data.startswith(f'/{key}'.encode('ascii'))
+ assert {'FontType', 'FontMatrix', 'PaintType', 'ItalicAngle', 'RD'
+ } < set(font._pos.keys())
+
+ assert b'UniqueID 5000793' not in slanted.parts[0]
+ assert b'UniqueID 5000793' not in slanted.decrypted
+ assert 'UniqueID' not in slanted._pos
+ assert font.prop['Weight'] == 'Medium'
+ assert not font.prop['isFixedPitch']
+ assert font.prop['ItalicAngle'] == 0
+ assert slanted.prop['ItalicAngle'] == -45
+ assert font.prop['Encoding'][5] == 'Pi'
+ assert isinstance(font.prop['CharStrings']['Pi'], bytes)
+ assert font._abbr['ND'] == 'ND'
+
+ differ = difflib.Differ()
+ diff = list(differ.compare(
+ font.parts[0].decode('latin-1').splitlines(),
+ slanted.parts[0].decode('latin-1').splitlines()))
+ for line in (
+ # Removes UniqueID
+ '- /UniqueID 5000793 def',
+ # Changes the font name
+ '- /FontName /CMR10 def',
+ '+ /FontName/CMR10_Slant_1000 def',
+ # Alters FontMatrix
+ '- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def',
+ '+ /FontMatrix [0.001 0 0.001 0.001 0 0] readonly def',
+ # Alters ItalicAngle
+ '- /ItalicAngle 0 def',
+ '+ /ItalicAngle -45.0 def'):
+ assert line in diff, 'diff to slanted font must contain %s' % line
+
+ diff = list(differ.compare(
+ font.parts[0].decode('latin-1').splitlines(),
+ condensed.parts[0].decode('latin-1').splitlines()))
+ for line in (
+ # Removes UniqueID
+ '- /UniqueID 5000793 def',
+ # Changes the font name
+ '- /FontName /CMR10 def',
+ '+ /FontName/CMR10_Extend_500 def',
+ # Alters FontMatrix
+ '- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def',
+ '+ /FontMatrix [0.0005 0 0 0.001 0 0] readonly def'):
+ assert line in diff, 'diff to condensed font must contain %s' % line
+
+
+def test_Type1Font_2():
+ filename = os.path.join(os.path.dirname(__file__),
+ 'Courier10PitchBT-Bold.pfb')
+ font = t1f.Type1Font(filename)
+ assert font.prop['Weight'] == 'Bold'
+ assert font.prop['isFixedPitch']
+ assert font.prop['Encoding'][65] == 'A' # the font uses StandardEncoding
+ (pos0, pos1), = font._pos['Encoding']
+ assert font.parts[0][pos0:pos1] == b'/Encoding StandardEncoding'
+ assert font._abbr['ND'] == '|-'
+
+
+def test_tokenize():
+ data = (b'1234/abc false -9.81 Foo <<[0 1 2]<0 1ef a\t>>>\n'
+ b'(string with(nested\t\\) par)ens\\\\)')
+ # 1 2 x 2 xx1
+ # 1 and 2 are matching parens, x means escaped character
+ n, w, num, kw, d = 'name', 'whitespace', 'number', 'keyword', 'delimiter'
+ b, s = 'boolean', 'string'
+ correct = [
+ (num, 1234), (n, 'abc'), (w, ' '), (b, False), (w, ' '), (num, -9.81),
+ (w, ' '), (kw, 'Foo'), (w, ' '), (d, '<<'), (d, '['), (num, 0),
+ (w, ' '), (num, 1), (w, ' '), (num, 2), (d, ']'), (s, b'\x01\xef\xa0'),
+ (d, '>>'), (w, '\n'), (s, 'string with(nested\t) par)ens\\')
+ ]
+ correct_no_ws = [x for x in correct if x[0] != w]
+
+ def convert(tokens):
+ return [(t.kind, t.value()) for t in tokens]
+
+ assert convert(t1f._tokenize(data, False)) == correct
+ assert convert(t1f._tokenize(data, True)) == correct_no_ws
+
+ def bin_after(n):
+ tokens = t1f._tokenize(data, True)
+ result = []
+ for _ in range(n):
+ result.append(next(tokens))
+ result.append(tokens.send(10))
+ return convert(result)
+
+ for n in range(1, len(correct_no_ws)):
+ result = bin_after(n)
+ assert result[:-1] == correct_no_ws[:n]
+ assert result[-1][0] == 'binary'
+ assert isinstance(result[-1][1], bytes)
+
+
+def test_tokenize_errors():
+ with pytest.raises(ValueError):
+ list(t1f._tokenize(b'1234 (this (string) is unterminated\\)', True))
+ with pytest.raises(ValueError):
+ list(t1f._tokenize(b'/Foo<01234', True))
+ with pytest.raises(ValueError):
+ list(t1f._tokenize(b'/Foo<01234abcg>/Bar', True))
+
+
+def test_overprecision():
+ # We used to output too many digits in FontMatrix entries and
+ # ItalicAngle, which could make Type-1 parsers unhappy.
+ filename = os.path.join(os.path.dirname(__file__), 'cmr10.pfb')
+ font = t1f.Type1Font(filename)
+ slanted = font.transform({'slant': .167})
+ lines = slanted.parts[0].decode('ascii').splitlines()
+ matrix, = (line[line.index('[')+1:line.index(']')]
+ for line in lines if '/FontMatrix' in line)
+ angle, = (word
+ for line in lines if '/ItalicAngle' in line
+ for word in line.split() if word[0] in '-0123456789')
+ # the following used to include 0.00016700000000000002
+ assert matrix == '0.001 0 0.000167 0.001 0 0'
+ # and here we had -9.48090361795083
+ assert angle == '-9.4809'
+
+
+def test_encrypt_decrypt_roundtrip():
+ data = b'this is my plaintext \0\1\2\3'
+ encrypted = t1f.Type1Font._encrypt(data, 'eexec')
+ decrypted = t1f.Type1Font._decrypt(encrypted, 'eexec')
+ assert encrypted != decrypted
+ assert data == decrypted
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_units.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_units.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc71f685857e2e6da4677969837bd66c828cc17c
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_units.py
@@ -0,0 +1,353 @@
+from datetime import datetime, timezone, timedelta
+import platform
+from unittest.mock import MagicMock
+
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+import matplotlib.patches as mpatches
+import matplotlib.units as munits
+from matplotlib.category import StrCategoryConverter, UnitData
+from matplotlib.dates import DateConverter
+import numpy as np
+import pytest
+
+
+# Basic class that wraps numpy array and has units
+class Quantity:
+ def __init__(self, data, units):
+ self.magnitude = data
+ self.units = units
+
+ def to(self, new_units):
+ factors = {('hours', 'seconds'): 3600, ('minutes', 'hours'): 1 / 60,
+ ('minutes', 'seconds'): 60, ('feet', 'miles'): 1 / 5280.,
+ ('feet', 'inches'): 12, ('miles', 'inches'): 12 * 5280}
+ if self.units != new_units:
+ mult = factors[self.units, new_units]
+ return Quantity(mult * self.magnitude, new_units)
+ else:
+ return Quantity(self.magnitude, self.units)
+
+ def __copy__(self):
+ return Quantity(self.magnitude, self.units)
+
+ def __getattr__(self, attr):
+ return getattr(self.magnitude, attr)
+
+ def __getitem__(self, item):
+ if np.iterable(self.magnitude):
+ return Quantity(self.magnitude[item], self.units)
+ else:
+ return Quantity(self.magnitude, self.units)
+
+ def __array__(self):
+ return np.asarray(self.magnitude)
+
+
+@pytest.fixture
+def quantity_converter():
+ # Create an instance of the conversion interface and
+ # mock so we can check methods called
+ qc = munits.ConversionInterface()
+
+ def convert(value, unit, axis):
+ if hasattr(value, 'units'):
+ return value.to(unit).magnitude
+ elif np.iterable(value):
+ try:
+ return [v.to(unit).magnitude for v in value]
+ except AttributeError:
+ return [Quantity(v, axis.get_units()).to(unit).magnitude
+ for v in value]
+ else:
+ return Quantity(value, axis.get_units()).to(unit).magnitude
+
+ def default_units(value, axis):
+ if hasattr(value, 'units'):
+ return value.units
+ elif np.iterable(value):
+ for v in value:
+ if hasattr(v, 'units'):
+ return v.units
+ return None
+
+ qc.convert = MagicMock(side_effect=convert)
+ qc.axisinfo = MagicMock(side_effect=lambda u, a:
+ munits.AxisInfo(label=u, default_limits=(0, 100)))
+ qc.default_units = MagicMock(side_effect=default_units)
+ return qc
+
+
+# Tests that the conversion machinery works properly for classes that
+# work as a facade over numpy arrays (like pint)
+@image_comparison(['plot_pint.png'], style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.03)
+def test_numpy_facade(quantity_converter):
+ # use former defaults to match existing baseline image
+ plt.rcParams['axes.formatter.limits'] = -7, 7
+
+ # Register the class
+ munits.registry[Quantity] = quantity_converter
+
+ # Simple test
+ y = Quantity(np.linspace(0, 30), 'miles')
+ x = Quantity(np.linspace(0, 5), 'hours')
+
+ fig, ax = plt.subplots()
+ fig.subplots_adjust(left=0.15) # Make space for label
+ ax.plot(x, y, 'tab:blue')
+ ax.axhline(Quantity(26400, 'feet'), color='tab:red')
+ ax.axvline(Quantity(120, 'minutes'), color='tab:green')
+ ax.yaxis.set_units('inches')
+ ax.xaxis.set_units('seconds')
+
+ assert quantity_converter.convert.called
+ assert quantity_converter.axisinfo.called
+ assert quantity_converter.default_units.called
+
+
+# Tests gh-8908
+@image_comparison(['plot_masked_units.png'], remove_text=True, style='mpl20',
+ tol=0 if platform.machine() == 'x86_64' else 0.02)
+def test_plot_masked_units():
+ data = np.linspace(-5, 5)
+ data_masked = np.ma.array(data, mask=(data > -2) & (data < 2))
+ data_masked_units = Quantity(data_masked, 'meters')
+
+ fig, ax = plt.subplots()
+ ax.plot(data_masked_units)
+
+
+def test_empty_set_limits_with_units(quantity_converter):
+ # Register the class
+ munits.registry[Quantity] = quantity_converter
+
+ fig, ax = plt.subplots()
+ ax.set_xlim(Quantity(-1, 'meters'), Quantity(6, 'meters'))
+ ax.set_ylim(Quantity(-1, 'hours'), Quantity(16, 'hours'))
+
+
+@image_comparison(['jpl_bar_units.png'],
+ savefig_kwarg={'dpi': 120}, style='mpl20')
+def test_jpl_bar_units():
+ import matplotlib.testing.jpl_units as units
+ units.register()
+
+ day = units.Duration("ET", 24.0 * 60.0 * 60.0)
+ x = [0 * units.km, 1 * units.km, 2 * units.km]
+ w = [1 * day, 2 * day, 3 * day]
+ b = units.Epoch("ET", dt=datetime(2009, 4, 26))
+ fig, ax = plt.subplots()
+ ax.bar(x, w, bottom=b)
+ ax.set_ylim([b - 1 * day, b + w[-1] + (1.001) * day])
+
+
+@image_comparison(['jpl_barh_units.png'],
+ savefig_kwarg={'dpi': 120}, style='mpl20')
+def test_jpl_barh_units():
+ import matplotlib.testing.jpl_units as units
+ units.register()
+
+ day = units.Duration("ET", 24.0 * 60.0 * 60.0)
+ x = [0 * units.km, 1 * units.km, 2 * units.km]
+ w = [1 * day, 2 * day, 3 * day]
+ b = units.Epoch("ET", dt=datetime(2009, 4, 26))
+
+ fig, ax = plt.subplots()
+ ax.barh(x, w, left=b)
+ ax.set_xlim([b - 1 * day, b + w[-1] + (1.001) * day])
+
+
+def test_jpl_datetime_units_consistent():
+ import matplotlib.testing.jpl_units as units
+ units.register()
+
+ dt = datetime(2009, 4, 26)
+ jpl = units.Epoch("ET", dt=dt)
+ dt_conv = munits.registry.get_converter(dt).convert(dt, None, None)
+ jpl_conv = munits.registry.get_converter(jpl).convert(jpl, None, None)
+ assert dt_conv == jpl_conv
+
+
+def test_empty_arrays():
+ # Check that plotting an empty array with a dtype works
+ plt.scatter(np.array([], dtype='datetime64[ns]'), np.array([]))
+
+
+def test_scatter_element0_masked():
+ times = np.arange('2005-02', '2005-03', dtype='datetime64[D]')
+ y = np.arange(len(times), dtype=float)
+ y[0] = np.nan
+ fig, ax = plt.subplots()
+ ax.scatter(times, y)
+ fig.canvas.draw()
+
+
+def test_errorbar_mixed_units():
+ x = np.arange(10)
+ y = [datetime(2020, 5, i * 2 + 1) for i in x]
+ fig, ax = plt.subplots()
+ ax.errorbar(x, y, timedelta(days=0.5))
+ fig.canvas.draw()
+
+
+@check_figures_equal(extensions=["png"])
+def test_subclass(fig_test, fig_ref):
+ class subdate(datetime):
+ pass
+
+ fig_test.subplots().plot(subdate(2000, 1, 1), 0, "o")
+ fig_ref.subplots().plot(datetime(2000, 1, 1), 0, "o")
+
+
+def test_shared_axis_quantity(quantity_converter):
+ munits.registry[Quantity] = quantity_converter
+ x = Quantity(np.linspace(0, 1, 10), "hours")
+ y1 = Quantity(np.linspace(1, 2, 10), "feet")
+ y2 = Quantity(np.linspace(3, 4, 10), "feet")
+ fig, (ax1, ax2) = plt.subplots(2, 1, sharex='all', sharey='all')
+ ax1.plot(x, y1)
+ ax2.plot(x, y2)
+ assert ax1.xaxis.get_units() == ax2.xaxis.get_units() == "hours"
+ assert ax2.yaxis.get_units() == ax2.yaxis.get_units() == "feet"
+ ax1.xaxis.set_units("seconds")
+ ax2.yaxis.set_units("inches")
+ assert ax1.xaxis.get_units() == ax2.xaxis.get_units() == "seconds"
+ assert ax1.yaxis.get_units() == ax2.yaxis.get_units() == "inches"
+
+
+def test_shared_axis_datetime():
+ # datetime uses dates.DateConverter
+ y1 = [datetime(2020, i, 1, tzinfo=timezone.utc) for i in range(1, 13)]
+ y2 = [datetime(2021, i, 1, tzinfo=timezone.utc) for i in range(1, 13)]
+ fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
+ ax1.plot(y1)
+ ax2.plot(y2)
+ ax1.yaxis.set_units(timezone(timedelta(hours=5)))
+ assert ax2.yaxis.units == timezone(timedelta(hours=5))
+
+
+def test_shared_axis_categorical():
+ # str uses category.StrCategoryConverter
+ d1 = {"a": 1, "b": 2}
+ d2 = {"a": 3, "b": 4}
+ fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, sharey=True)
+ ax1.plot(d1.keys(), d1.values())
+ ax2.plot(d2.keys(), d2.values())
+ ax1.xaxis.set_units(UnitData(["c", "d"]))
+ assert "c" in ax2.xaxis.get_units()._mapping.keys()
+
+
+def test_explicit_converter():
+ d1 = {"a": 1, "b": 2}
+ str_cat_converter = StrCategoryConverter()
+ str_cat_converter_2 = StrCategoryConverter()
+ date_converter = DateConverter()
+
+ # Explicit is set
+ fig1, ax1 = plt.subplots()
+ ax1.xaxis.set_converter(str_cat_converter)
+ assert ax1.xaxis.get_converter() == str_cat_converter
+ # Explicit not overridden by implicit
+ ax1.plot(d1.keys(), d1.values())
+ assert ax1.xaxis.get_converter() == str_cat_converter
+ # No error when called twice with equivalent input
+ ax1.xaxis.set_converter(str_cat_converter)
+ # Error when explicit called twice
+ with pytest.raises(RuntimeError):
+ ax1.xaxis.set_converter(str_cat_converter_2)
+
+ fig2, ax2 = plt.subplots()
+ ax2.plot(d1.keys(), d1.values())
+
+ # No error when equivalent type is used
+ ax2.xaxis.set_converter(str_cat_converter)
+
+ fig3, ax3 = plt.subplots()
+ ax3.plot(d1.keys(), d1.values())
+
+ # Warn when implicit overridden
+ with pytest.warns():
+ ax3.xaxis.set_converter(date_converter)
+
+
+def test_empty_default_limits(quantity_converter):
+ munits.registry[Quantity] = quantity_converter
+ fig, ax1 = plt.subplots()
+ ax1.xaxis.update_units(Quantity([10], "miles"))
+ fig.draw_without_rendering()
+ assert ax1.get_xlim() == (0, 100)
+ ax1.yaxis.update_units(Quantity([10], "miles"))
+ fig.draw_without_rendering()
+ assert ax1.get_ylim() == (0, 100)
+
+ fig, ax = plt.subplots()
+ ax.axhline(30)
+ ax.plot(Quantity(np.arange(0, 3), "miles"),
+ Quantity(np.arange(0, 6, 2), "feet"))
+ fig.draw_without_rendering()
+ assert ax.get_xlim() == (0, 2)
+ assert ax.get_ylim() == (0, 30)
+
+ fig, ax = plt.subplots()
+ ax.axvline(30)
+ ax.plot(Quantity(np.arange(0, 3), "miles"),
+ Quantity(np.arange(0, 6, 2), "feet"))
+ fig.draw_without_rendering()
+ assert ax.get_xlim() == (0, 30)
+ assert ax.get_ylim() == (0, 4)
+
+ fig, ax = plt.subplots()
+ ax.xaxis.update_units(Quantity([10], "miles"))
+ ax.axhline(30)
+ fig.draw_without_rendering()
+ assert ax.get_xlim() == (0, 100)
+ assert ax.get_ylim() == (28.5, 31.5)
+
+ fig, ax = plt.subplots()
+ ax.yaxis.update_units(Quantity([10], "miles"))
+ ax.axvline(30)
+ fig.draw_without_rendering()
+ assert ax.get_ylim() == (0, 100)
+ assert ax.get_xlim() == (28.5, 31.5)
+
+
+# test array-like objects...
+class Kernel:
+ def __init__(self, array):
+ self._array = np.asanyarray(array)
+
+ def __array__(self, dtype=None, copy=None):
+ if dtype is not None and dtype != self._array.dtype:
+ if copy is not None and not copy:
+ raise ValueError(
+ f"Converting array from {self._array.dtype} to "
+ f"{dtype} requires a copy"
+ )
+
+ arr = np.asarray(self._array, dtype=dtype)
+ return (arr if not copy else np.copy(arr))
+
+ @property
+ def shape(self):
+ return self._array.shape
+
+
+def test_plot_kernel():
+ # just a smoketest that fail
+ kernel = Kernel([1, 2, 3, 4, 5])
+ plt.plot(kernel)
+
+
+def test_connection_patch_units(pd):
+ # tests that this doesn't raise an error
+ fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(10, 5))
+ x = pd.Timestamp('2017-01-01T12')
+ ax1.axvline(x)
+ y = "test test"
+ ax2.axhline(y)
+ arr = mpatches.ConnectionPatch((x, 0), (0, y),
+ coordsA='data', coordsB='data',
+ axesA=ax1, axesB=ax2)
+ fig.add_artist(arr)
+ fig.draw_without_rendering()
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_usetex.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_usetex.py
new file mode 100644
index 0000000000000000000000000000000000000000..342face4504fbc444c530fa08335cf19b3c98e6f
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_usetex.py
@@ -0,0 +1,187 @@
+from tempfile import TemporaryFile
+
+import numpy as np
+from packaging.version import parse as parse_version
+import pytest
+
+import matplotlib as mpl
+from matplotlib import dviread
+from matplotlib.testing import _has_tex_package
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+from matplotlib.testing._markers import needs_usetex
+import matplotlib.pyplot as plt
+
+
+pytestmark = needs_usetex
+
+
+@image_comparison(
+ baseline_images=['test_usetex'],
+ extensions=['pdf', 'png'],
+ style="mpl20")
+def test_usetex():
+ mpl.rcParams['text.usetex'] = True
+ fig, ax = plt.subplots()
+ kwargs = {"verticalalignment": "baseline", "size": 24,
+ "bbox": dict(pad=0, edgecolor="k", facecolor="none")}
+ ax.text(0.2, 0.7,
+ # the \LaTeX macro exercises character sizing and placement,
+ # \left[ ... \right\} draw some variable-height characters,
+ # \sqrt and \frac draw horizontal rules, \mathrm changes the font
+ r'\LaTeX\ $\left[\int\limits_e^{2e}'
+ r'\sqrt\frac{\log^3 x}{x}\,\mathrm{d}x \right\}$',
+ **kwargs)
+ ax.text(0.2, 0.3, "lg", **kwargs)
+ ax.text(0.4, 0.3, r"$\frac{1}{2}\pi$", **kwargs)
+ ax.text(0.6, 0.3, "$p^{3^A}$", **kwargs)
+ ax.text(0.8, 0.3, "$p_{3_2}$", **kwargs)
+ for x in {t.get_position()[0] for t in ax.texts}:
+ ax.axvline(x)
+ for y in {t.get_position()[1] for t in ax.texts}:
+ ax.axhline(y)
+ ax.set_axis_off()
+
+
+@check_figures_equal()
+def test_empty(fig_test, fig_ref):
+ mpl.rcParams['text.usetex'] = True
+ fig_test.text(.5, .5, "% a comment")
+
+
+@check_figures_equal()
+def test_unicode_minus(fig_test, fig_ref):
+ mpl.rcParams['text.usetex'] = True
+ fig_test.text(.5, .5, "$-$")
+ fig_ref.text(.5, .5, "\N{MINUS SIGN}")
+
+
+def test_mathdefault():
+ plt.rcParams["axes.formatter.use_mathtext"] = True
+ fig = plt.figure()
+ fig.add_subplot().set_xlim(-1, 1)
+ # Check that \mathdefault commands generated by tickers don't cause
+ # problems when later switching usetex on.
+ mpl.rcParams['text.usetex'] = True
+ fig.canvas.draw()
+
+
+@image_comparison(['eqnarray.png'])
+def test_multiline_eqnarray():
+ text = (
+ r'\begin{eqnarray*}'
+ r'foo\\'
+ r'bar\\'
+ r'baz\\'
+ r'\end{eqnarray*}'
+ )
+
+ fig = plt.figure(figsize=(1, 1))
+ fig.text(0.5, 0.5, text, usetex=True,
+ horizontalalignment='center', verticalalignment='center')
+
+
+@pytest.mark.parametrize("fontsize", [8, 10, 12])
+def test_minus_no_descent(fontsize):
+ # Test special-casing of minus descent in DviFont._height_depth_of, by
+ # checking that overdrawing a 1 and a -1 results in an overall height
+ # equivalent to drawing either of them separately.
+ mpl.style.use("mpl20")
+ mpl.rcParams['font.size'] = fontsize
+ heights = {}
+ fig = plt.figure()
+ for vals in [(1,), (-1,), (-1, 1)]:
+ fig.clear()
+ for x in vals:
+ fig.text(.5, .5, f"${x}$", usetex=True)
+ fig.canvas.draw()
+ # The following counts the number of non-fully-blank pixel rows.
+ heights[vals] = ((np.array(fig.canvas.buffer_rgba())[..., 0] != 255)
+ .any(axis=1).sum())
+ assert len({*heights.values()}) == 1
+
+
+@pytest.mark.parametrize('pkg', ['xcolor', 'chemformula'])
+def test_usetex_packages(pkg):
+ if not _has_tex_package(pkg):
+ pytest.skip(f'{pkg} is not available')
+ mpl.rcParams['text.usetex'] = True
+
+ fig = plt.figure()
+ text = fig.text(0.5, 0.5, "Some text 0123456789")
+ fig.canvas.draw()
+
+ mpl.rcParams['text.latex.preamble'] = (
+ r'\PassOptionsToPackage{dvipsnames}{xcolor}\usepackage{%s}' % pkg)
+ fig = plt.figure()
+ text2 = fig.text(0.5, 0.5, "Some text 0123456789")
+ fig.canvas.draw()
+ np.testing.assert_array_equal(text2.get_window_extent(),
+ text.get_window_extent())
+
+
+@pytest.mark.parametrize(
+ "preamble",
+ [r"\usepackage[full]{textcomp}", r"\usepackage{underscore}"],
+)
+def test_latex_pkg_already_loaded(preamble):
+ plt.rcParams["text.latex.preamble"] = preamble
+ fig = plt.figure()
+ fig.text(.5, .5, "hello, world", usetex=True)
+ fig.canvas.draw()
+
+
+def test_usetex_with_underscore():
+ plt.rcParams["text.usetex"] = True
+ df = {"a_b": range(5)[::-1], "c": range(5)}
+ fig, ax = plt.subplots()
+ ax.plot("c", "a_b", data=df)
+ ax.legend()
+ ax.text(0, 0, "foo_bar", usetex=True)
+ plt.draw()
+
+
+@pytest.mark.flaky(reruns=3) # Tends to hit a TeX cache lock on AppVeyor.
+@pytest.mark.parametrize("fmt", ["pdf", "svg"])
+def test_missing_psfont(fmt, monkeypatch):
+ """An error is raised if a TeX font lacks a Type-1 equivalent"""
+ monkeypatch.setattr(
+ dviread.PsfontsMap, '__getitem__',
+ lambda self, k: dviread.PsFont(
+ texname=b'texfont', psname=b'Some Font',
+ effects=None, encoding=None, filename=None))
+ mpl.rcParams['text.usetex'] = True
+ fig, ax = plt.subplots()
+ ax.text(0.5, 0.5, 'hello')
+ with TemporaryFile() as tmpfile, pytest.raises(ValueError):
+ fig.savefig(tmpfile, format=fmt)
+
+
+try:
+ _old_gs_version = mpl._get_executable_info('gs').version < parse_version('9.55')
+except mpl.ExecutableNotFoundError:
+ _old_gs_version = True
+
+
+@image_comparison(baseline_images=['rotation'], extensions=['eps', 'pdf', 'png', 'svg'],
+ style='mpl20', tol=3.91 if _old_gs_version else 0)
+def test_rotation():
+ mpl.rcParams['text.usetex'] = True
+
+ fig = plt.figure()
+ ax = fig.add_axes([0, 0, 1, 1])
+ ax.set(xlim=[-0.5, 5], xticks=[], ylim=[-0.5, 3], yticks=[], frame_on=False)
+
+ text = {val: val[0] for val in ['top', 'center', 'bottom', 'left', 'right']}
+ text['baseline'] = 'B'
+ text['center_baseline'] = 'C'
+
+ for i, va in enumerate(['top', 'center', 'bottom', 'baseline', 'center_baseline']):
+ for j, ha in enumerate(['left', 'center', 'right']):
+ for k, angle in enumerate([0, 90, 180, 270]):
+ k //= 2
+ x = i + k / 2
+ y = j + k / 2
+ ax.plot(x, y, '+', c=f'C{k}', markersize=20, markeredgewidth=0.5)
+ # 'My' checks full height letters plus descenders.
+ ax.text(x, y, f"$\\mathrm{{My {text[ha]}{text[va]} {angle}}}$",
+ rotation=angle, horizontalalignment=ha, verticalalignment=va)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_widgets.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_widgets.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b42d1ca7c2dc07b086263c3161b428cfb8a275a
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/tests/test_widgets.py
@@ -0,0 +1,1759 @@
+import functools
+import io
+import operator
+from unittest import mock
+
+from matplotlib.backend_bases import MouseEvent
+import matplotlib.colors as mcolors
+import matplotlib.widgets as widgets
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import check_figures_equal, image_comparison
+from matplotlib.testing.widgets import (click_and_drag, do_event, get_ax,
+ mock_event, noop)
+
+import numpy as np
+from numpy.testing import assert_allclose
+
+import pytest
+
+
+@pytest.fixture
+def ax():
+ return get_ax()
+
+
+def test_save_blitted_widget_as_pdf():
+ from matplotlib.widgets import CheckButtons, RadioButtons
+ from matplotlib.cbook import _get_running_interactive_framework
+ if _get_running_interactive_framework() not in ['headless', None]:
+ pytest.xfail("Callback exceptions are not raised otherwise.")
+
+ fig, ax = plt.subplots(
+ nrows=2, ncols=2, figsize=(5, 2), width_ratios=[1, 2]
+ )
+ default_rb = RadioButtons(ax[0, 0], ['Apples', 'Oranges'])
+ styled_rb = RadioButtons(
+ ax[0, 1], ['Apples', 'Oranges'],
+ label_props={'color': ['red', 'orange'],
+ 'fontsize': [16, 20]},
+ radio_props={'edgecolor': ['red', 'orange'],
+ 'facecolor': ['mistyrose', 'peachpuff']}
+ )
+
+ default_cb = CheckButtons(ax[1, 0], ['Apples', 'Oranges'],
+ actives=[True, True])
+ styled_cb = CheckButtons(
+ ax[1, 1], ['Apples', 'Oranges'],
+ actives=[True, True],
+ label_props={'color': ['red', 'orange'],
+ 'fontsize': [16, 20]},
+ frame_props={'edgecolor': ['red', 'orange'],
+ 'facecolor': ['mistyrose', 'peachpuff']},
+ check_props={'color': ['darkred', 'darkorange']}
+ )
+
+ ax[0, 0].set_title('Default')
+ ax[0, 1].set_title('Stylized')
+ # force an Agg render
+ fig.canvas.draw()
+ # force a pdf save
+ with io.BytesIO() as result_after:
+ fig.savefig(result_after, format='pdf')
+
+
+@pytest.mark.parametrize('kwargs', [
+ dict(),
+ dict(useblit=True, button=1),
+ dict(minspanx=10, minspany=10, spancoords='pixels'),
+ dict(props=dict(fill=True)),
+])
+def test_rectangle_selector(ax, kwargs):
+ onselect = mock.Mock(spec=noop, return_value=None)
+
+ tool = widgets.RectangleSelector(ax, onselect=onselect, **kwargs)
+ do_event(tool, 'press', xdata=100, ydata=100, button=1)
+ do_event(tool, 'onmove', xdata=199, ydata=199, button=1)
+
+ # purposely drag outside of axis for release
+ do_event(tool, 'release', xdata=250, ydata=250, button=1)
+
+ if kwargs.get('drawtype', None) not in ['line', 'none']:
+ assert_allclose(tool.geometry,
+ [[100., 100, 199, 199, 100],
+ [100, 199, 199, 100, 100]],
+ err_msg=tool.geometry)
+
+ onselect.assert_called_once()
+ (epress, erelease), kwargs = onselect.call_args
+ assert epress.xdata == 100
+ assert epress.ydata == 100
+ assert erelease.xdata == 199
+ assert erelease.ydata == 199
+ assert kwargs == {}
+
+
+@pytest.mark.parametrize('spancoords', ['data', 'pixels'])
+@pytest.mark.parametrize('minspanx, x1', [[0, 10], [1, 10.5], [1, 11]])
+@pytest.mark.parametrize('minspany, y1', [[0, 10], [1, 10.5], [1, 11]])
+def test_rectangle_minspan(ax, spancoords, minspanx, x1, minspany, y1):
+
+ onselect = mock.Mock(spec=noop, return_value=None)
+
+ x0, y0 = (10, 10)
+ if spancoords == 'pixels':
+ minspanx, minspany = (ax.transData.transform((x1, y1)) -
+ ax.transData.transform((x0, y0)))
+
+ tool = widgets.RectangleSelector(ax, onselect=onselect, interactive=True,
+ spancoords=spancoords,
+ minspanx=minspanx, minspany=minspany)
+ # Too small to create a selector
+ click_and_drag(tool, start=(x0, x1), end=(y0, y1))
+ assert not tool._selection_completed
+ onselect.assert_not_called()
+
+ click_and_drag(tool, start=(20, 20), end=(30, 30))
+ assert tool._selection_completed
+ onselect.assert_called_once()
+
+ # Too small to create a selector. Should clear existing selector, and
+ # trigger onselect because there was a preexisting selector
+ onselect.reset_mock()
+ click_and_drag(tool, start=(x0, y0), end=(x1, y1))
+ assert not tool._selection_completed
+ onselect.assert_called_once()
+ (epress, erelease), kwargs = onselect.call_args
+ assert epress.xdata == x0
+ assert epress.ydata == y0
+ assert erelease.xdata == x1
+ assert erelease.ydata == y1
+ assert kwargs == {}
+
+
+@pytest.mark.parametrize('drag_from_anywhere, new_center',
+ [[True, (60, 75)],
+ [False, (30, 20)]])
+def test_rectangle_drag(ax, drag_from_anywhere, new_center):
+ tool = widgets.RectangleSelector(ax, interactive=True,
+ drag_from_anywhere=drag_from_anywhere)
+ # Create rectangle
+ click_and_drag(tool, start=(0, 10), end=(100, 120))
+ assert tool.center == (50, 65)
+ # Drag inside rectangle, but away from centre handle
+ #
+ # If drag_from_anywhere == True, this will move the rectangle by (10, 10),
+ # giving it a new center of (60, 75)
+ #
+ # If drag_from_anywhere == False, this will create a new rectangle with
+ # center (30, 20)
+ click_and_drag(tool, start=(25, 15), end=(35, 25))
+ assert tool.center == new_center
+ # Check that in both cases, dragging outside the rectangle draws a new
+ # rectangle
+ click_and_drag(tool, start=(175, 185), end=(185, 195))
+ assert tool.center == (180, 190)
+
+
+def test_rectangle_selector_set_props_handle_props(ax):
+ tool = widgets.RectangleSelector(ax, interactive=True,
+ props=dict(facecolor='b', alpha=0.2),
+ handle_props=dict(alpha=0.5))
+ # Create rectangle
+ click_and_drag(tool, start=(0, 10), end=(100, 120))
+
+ artist = tool._selection_artist
+ assert artist.get_facecolor() == mcolors.to_rgba('b', alpha=0.2)
+ tool.set_props(facecolor='r', alpha=0.3)
+ assert artist.get_facecolor() == mcolors.to_rgba('r', alpha=0.3)
+
+ for artist in tool._handles_artists:
+ assert artist.get_markeredgecolor() == 'black'
+ assert artist.get_alpha() == 0.5
+ tool.set_handle_props(markeredgecolor='r', alpha=0.3)
+ for artist in tool._handles_artists:
+ assert artist.get_markeredgecolor() == 'r'
+ assert artist.get_alpha() == 0.3
+
+
+def test_rectangle_resize(ax):
+ tool = widgets.RectangleSelector(ax, interactive=True)
+ # Create rectangle
+ click_and_drag(tool, start=(0, 10), end=(100, 120))
+ assert tool.extents == (0.0, 100.0, 10.0, 120.0)
+
+ # resize NE handle
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[3]
+ xdata_new, ydata_new = xdata + 10, ydata + 5
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert tool.extents == (extents[0], xdata_new, extents[2], ydata_new)
+
+ # resize E handle
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
+ xdata_new, ydata_new = xdata + 10, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert tool.extents == (extents[0], xdata_new, extents[2], extents[3])
+
+ # resize W handle
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
+ xdata_new, ydata_new = xdata + 15, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert tool.extents == (xdata_new, extents[1], extents[2], extents[3])
+
+ # resize SW handle
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2]
+ xdata_new, ydata_new = xdata + 20, ydata + 25
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert tool.extents == (xdata_new, extents[1], ydata_new, extents[3])
+
+
+def test_rectangle_add_state(ax):
+ tool = widgets.RectangleSelector(ax, interactive=True)
+ # Create rectangle
+ click_and_drag(tool, start=(70, 65), end=(125, 130))
+
+ with pytest.raises(ValueError):
+ tool.add_state('unsupported_state')
+
+ with pytest.raises(ValueError):
+ tool.add_state('clear')
+ tool.add_state('move')
+ tool.add_state('square')
+ tool.add_state('center')
+
+
+@pytest.mark.parametrize('add_state', [True, False])
+def test_rectangle_resize_center(ax, add_state):
+ tool = widgets.RectangleSelector(ax, interactive=True)
+ # Create rectangle
+ click_and_drag(tool, start=(70, 65), end=(125, 130))
+ assert tool.extents == (70.0, 125.0, 65.0, 130.0)
+
+ if add_state:
+ tool.add_state('center')
+ use_key = None
+ else:
+ use_key = 'control'
+
+ # resize NE handle
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[3]
+ xdiff, ydiff = 10, 5
+ xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (extents[0] - xdiff, xdata_new,
+ extents[2] - ydiff, ydata_new)
+
+ # resize E handle
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = 10
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (extents[0] - xdiff, xdata_new,
+ extents[2], extents[3])
+
+ # resize E handle negative diff
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = -20
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (extents[0] - xdiff, xdata_new,
+ extents[2], extents[3])
+
+ # resize W handle
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = 15
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (xdata_new, extents[1] - xdiff,
+ extents[2], extents[3])
+
+ # resize W handle negative diff
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = -25
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (xdata_new, extents[1] - xdiff,
+ extents[2], extents[3])
+
+ # resize SW handle
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2]
+ xdiff, ydiff = 20, 25
+ xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (xdata_new, extents[1] - xdiff,
+ ydata_new, extents[3] - ydiff)
+
+
+@pytest.mark.parametrize('add_state', [True, False])
+def test_rectangle_resize_square(ax, add_state):
+ tool = widgets.RectangleSelector(ax, interactive=True)
+ # Create rectangle
+ click_and_drag(tool, start=(70, 65), end=(120, 115))
+ assert tool.extents == (70.0, 120.0, 65.0, 115.0)
+
+ if add_state:
+ tool.add_state('square')
+ use_key = None
+ else:
+ use_key = 'shift'
+
+ # resize NE handle
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[3]
+ xdiff, ydiff = 10, 5
+ xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (extents[0], xdata_new,
+ extents[2], extents[3] + xdiff)
+
+ # resize E handle
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = 10
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (extents[0], xdata_new,
+ extents[2], extents[3] + xdiff)
+
+ # resize E handle negative diff
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = -20
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (extents[0], xdata_new,
+ extents[2], extents[3] + xdiff)
+
+ # resize W handle
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = 15
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (xdata_new, extents[1],
+ extents[2], extents[3] - xdiff)
+
+ # resize W handle negative diff
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = -25
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (xdata_new, extents[1],
+ extents[2], extents[3] - xdiff)
+
+ # resize SW handle
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2]
+ xdiff, ydiff = 20, 25
+ xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
+ key=use_key)
+ assert tool.extents == (extents[0] + ydiff, extents[1],
+ ydata_new, extents[3])
+
+
+def test_rectangle_resize_square_center(ax):
+ tool = widgets.RectangleSelector(ax, interactive=True)
+ # Create rectangle
+ click_and_drag(tool, start=(70, 65), end=(120, 115))
+ tool.add_state('square')
+ tool.add_state('center')
+ assert_allclose(tool.extents, (70.0, 120.0, 65.0, 115.0))
+
+ # resize NE handle
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[3]
+ xdiff, ydiff = 10, 5
+ xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert_allclose(tool.extents, (extents[0] - xdiff, xdata_new,
+ extents[2] - xdiff, extents[3] + xdiff))
+
+ # resize E handle
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = 10
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert_allclose(tool.extents, (extents[0] - xdiff, xdata_new,
+ extents[2] - xdiff, extents[3] + xdiff))
+
+ # resize E handle negative diff
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = -20
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert_allclose(tool.extents, (extents[0] - xdiff, xdata_new,
+ extents[2] - xdiff, extents[3] + xdiff))
+
+ # resize W handle
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = 5
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert_allclose(tool.extents, (xdata_new, extents[1] - xdiff,
+ extents[2] + xdiff, extents[3] - xdiff))
+
+ # resize W handle negative diff
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
+ xdiff = -25
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert_allclose(tool.extents, (xdata_new, extents[1] - xdiff,
+ extents[2] + xdiff, extents[3] - xdiff))
+
+ # resize SW handle
+ extents = tool.extents
+ xdata, ydata = extents[0], extents[2]
+ xdiff, ydiff = 20, 25
+ xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert_allclose(tool.extents, (extents[0] + ydiff, extents[1] - ydiff,
+ ydata_new, extents[3] - ydiff))
+
+
+@pytest.mark.parametrize('selector_class',
+ [widgets.RectangleSelector, widgets.EllipseSelector])
+def test_rectangle_rotate(ax, selector_class):
+ tool = selector_class(ax, interactive=True)
+ # Draw rectangle
+ click_and_drag(tool, start=(100, 100), end=(130, 140))
+ assert tool.extents == (100, 130, 100, 140)
+ assert len(tool._state) == 0
+
+ # Rotate anticlockwise using top-right corner
+ do_event(tool, 'on_key_press', key='r')
+ assert tool._state == {'rotate'}
+ assert len(tool._state) == 1
+ click_and_drag(tool, start=(130, 140), end=(120, 145))
+ do_event(tool, 'on_key_press', key='r')
+ assert len(tool._state) == 0
+ # Extents shouldn't change (as shape of rectangle hasn't changed)
+ assert tool.extents == (100, 130, 100, 140)
+ assert_allclose(tool.rotation, 25.56, atol=0.01)
+ tool.rotation = 45
+ assert tool.rotation == 45
+ # Corners should move
+ assert_allclose(tool.corners,
+ np.array([[118.53, 139.75, 111.46, 90.25],
+ [95.25, 116.46, 144.75, 123.54]]), atol=0.01)
+
+ # Scale using top-right corner
+ click_and_drag(tool, start=(110, 145), end=(110, 160))
+ assert_allclose(tool.extents, (100, 139.75, 100, 151.82), atol=0.01)
+
+ if selector_class == widgets.RectangleSelector:
+ with pytest.raises(ValueError):
+ tool._selection_artist.rotation_point = 'unvalid_value'
+
+
+def test_rectangle_add_remove_set(ax):
+ tool = widgets.RectangleSelector(ax, interactive=True)
+ # Draw rectangle
+ click_and_drag(tool, start=(100, 100), end=(130, 140))
+ assert tool.extents == (100, 130, 100, 140)
+ assert len(tool._state) == 0
+ for state in ['rotate', 'square', 'center']:
+ tool.add_state(state)
+ assert len(tool._state) == 1
+ tool.remove_state(state)
+ assert len(tool._state) == 0
+
+
+@pytest.mark.parametrize('use_data_coordinates', [False, True])
+def test_rectangle_resize_square_center_aspect(ax, use_data_coordinates):
+ ax.set_aspect(0.8)
+
+ tool = widgets.RectangleSelector(ax, interactive=True,
+ use_data_coordinates=use_data_coordinates)
+ # Create rectangle
+ click_and_drag(tool, start=(70, 65), end=(120, 115))
+ assert tool.extents == (70.0, 120.0, 65.0, 115.0)
+ tool.add_state('square')
+ tool.add_state('center')
+
+ if use_data_coordinates:
+ # resize E handle
+ extents = tool.extents
+ xdata, ydata, width = extents[1], extents[3], extents[1] - extents[0]
+ xdiff, ycenter = 10, extents[2] + (extents[3] - extents[2]) / 2
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ ychange = width / 2 + xdiff
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert_allclose(tool.extents, [extents[0] - xdiff, xdata_new,
+ ycenter - ychange, ycenter + ychange])
+ else:
+ # resize E handle
+ extents = tool.extents
+ xdata, ydata = extents[1], extents[3]
+ xdiff = 10
+ xdata_new, ydata_new = xdata + xdiff, ydata
+ ychange = xdiff * 1 / tool._aspect_ratio_correction
+ click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
+ assert_allclose(tool.extents, [extents[0] - xdiff, xdata_new,
+ 46.25, 133.75])
+
+
+def test_ellipse(ax):
+ """For ellipse, test out the key modifiers"""
+ tool = widgets.EllipseSelector(ax, grab_range=10, interactive=True)
+ tool.extents = (100, 150, 100, 150)
+
+ # drag the rectangle
+ click_and_drag(tool, start=(125, 125), end=(145, 145))
+ assert tool.extents == (120, 170, 120, 170)
+
+ # create from center
+ click_and_drag(tool, start=(100, 100), end=(125, 125), key='control')
+ assert tool.extents == (75, 125, 75, 125)
+
+ # create a square
+ click_and_drag(tool, start=(10, 10), end=(35, 30), key='shift')
+ extents = [int(e) for e in tool.extents]
+ assert extents == [10, 35, 10, 35]
+
+ # create a square from center
+ click_and_drag(tool, start=(100, 100), end=(125, 130), key='ctrl+shift')
+ extents = [int(e) for e in tool.extents]
+ assert extents == [70, 130, 70, 130]
+
+ assert tool.geometry.shape == (2, 73)
+ assert_allclose(tool.geometry[:, 0], [70., 100])
+
+
+def test_rectangle_handles(ax):
+ tool = widgets.RectangleSelector(ax, grab_range=10, interactive=True,
+ handle_props={'markerfacecolor': 'r',
+ 'markeredgecolor': 'b'})
+ tool.extents = (100, 150, 100, 150)
+
+ assert_allclose(tool.corners, ((100, 150, 150, 100), (100, 100, 150, 150)))
+ assert tool.extents == (100, 150, 100, 150)
+ assert_allclose(tool.edge_centers,
+ ((100, 125.0, 150, 125.0), (125.0, 100, 125.0, 150)))
+ assert tool.extents == (100, 150, 100, 150)
+
+ # grab a corner and move it
+ click_and_drag(tool, start=(100, 100), end=(120, 120))
+ assert tool.extents == (120, 150, 120, 150)
+
+ # grab the center and move it
+ click_and_drag(tool, start=(132, 132), end=(120, 120))
+ assert tool.extents == (108, 138, 108, 138)
+
+ # create a new rectangle
+ click_and_drag(tool, start=(10, 10), end=(100, 100))
+ assert tool.extents == (10, 100, 10, 100)
+
+ # Check that marker_props worked.
+ assert mcolors.same_color(
+ tool._corner_handles.artists[0].get_markerfacecolor(), 'r')
+ assert mcolors.same_color(
+ tool._corner_handles.artists[0].get_markeredgecolor(), 'b')
+
+
+@pytest.mark.parametrize('interactive', [True, False])
+def test_rectangle_selector_onselect(ax, interactive):
+ # check when press and release events take place at the same position
+ onselect = mock.Mock(spec=noop, return_value=None)
+
+ tool = widgets.RectangleSelector(ax, onselect=onselect, interactive=interactive)
+ # move outside of axis
+ click_and_drag(tool, start=(100, 110), end=(150, 120))
+
+ onselect.assert_called_once()
+ assert tool.extents == (100.0, 150.0, 110.0, 120.0)
+
+ onselect.reset_mock()
+ click_and_drag(tool, start=(10, 100), end=(10, 100))
+ onselect.assert_called_once()
+
+
+@pytest.mark.parametrize('ignore_event_outside', [True, False])
+def test_rectangle_selector_ignore_outside(ax, ignore_event_outside):
+ onselect = mock.Mock(spec=noop, return_value=None)
+
+ tool = widgets.RectangleSelector(ax, onselect=onselect,
+ ignore_event_outside=ignore_event_outside)
+ click_and_drag(tool, start=(100, 110), end=(150, 120))
+ onselect.assert_called_once()
+ assert tool.extents == (100.0, 150.0, 110.0, 120.0)
+
+ onselect.reset_mock()
+ # Trigger event outside of span
+ click_and_drag(tool, start=(150, 150), end=(160, 160))
+ if ignore_event_outside:
+ # event have been ignored and span haven't changed.
+ onselect.assert_not_called()
+ assert tool.extents == (100.0, 150.0, 110.0, 120.0)
+ else:
+ # A new shape is created
+ onselect.assert_called_once()
+ assert tool.extents == (150.0, 160.0, 150.0, 160.0)
+
+
+@pytest.mark.parametrize('orientation, onmove_callback, kwargs', [
+ ('horizontal', False, dict(minspan=10, useblit=True)),
+ ('vertical', True, dict(button=1)),
+ ('horizontal', False, dict(props=dict(fill=True))),
+ ('horizontal', False, dict(interactive=True)),
+])
+def test_span_selector(ax, orientation, onmove_callback, kwargs):
+ onselect = mock.Mock(spec=noop, return_value=None)
+ onmove = mock.Mock(spec=noop, return_value=None)
+ if onmove_callback:
+ kwargs['onmove_callback'] = onmove
+
+ # While at it, also test that span selectors work in the presence of twin axes on
+ # top of the axes that contain the selector. Note that we need to unforce the axes
+ # aspect here, otherwise the twin axes forces the original axes' limits (to respect
+ # aspect=1) which makes some of the values below go out of bounds.
+ ax.set_aspect("auto")
+ tax = ax.twinx()
+
+ tool = widgets.SpanSelector(ax, onselect, orientation, **kwargs)
+ do_event(tool, 'press', xdata=100, ydata=100, button=1)
+ # move outside of axis
+ do_event(tool, 'onmove', xdata=199, ydata=199, button=1)
+ do_event(tool, 'release', xdata=250, ydata=250, button=1)
+
+ onselect.assert_called_once_with(100, 199)
+ if onmove_callback:
+ onmove.assert_called_once_with(100, 199)
+
+
+@pytest.mark.parametrize('interactive', [True, False])
+def test_span_selector_onselect(ax, interactive):
+ onselect = mock.Mock(spec=noop, return_value=None)
+
+ tool = widgets.SpanSelector(ax, onselect, 'horizontal',
+ interactive=interactive)
+ # move outside of axis
+ click_and_drag(tool, start=(100, 100), end=(150, 100))
+ onselect.assert_called_once()
+ assert tool.extents == (100, 150)
+
+ onselect.reset_mock()
+ click_and_drag(tool, start=(10, 100), end=(10, 100))
+ onselect.assert_called_once()
+
+
+@pytest.mark.parametrize('ignore_event_outside', [True, False])
+def test_span_selector_ignore_outside(ax, ignore_event_outside):
+ onselect = mock.Mock(spec=noop, return_value=None)
+ onmove = mock.Mock(spec=noop, return_value=None)
+
+ tool = widgets.SpanSelector(ax, onselect, 'horizontal',
+ onmove_callback=onmove,
+ ignore_event_outside=ignore_event_outside)
+ click_and_drag(tool, start=(100, 100), end=(125, 125))
+ onselect.assert_called_once()
+ onmove.assert_called_once()
+ assert tool.extents == (100, 125)
+
+ onselect.reset_mock()
+ onmove.reset_mock()
+ # Trigger event outside of span
+ click_and_drag(tool, start=(150, 150), end=(160, 160))
+ if ignore_event_outside:
+ # event have been ignored and span haven't changed.
+ onselect.assert_not_called()
+ onmove.assert_not_called()
+ assert tool.extents == (100, 125)
+ else:
+ # A new shape is created
+ onselect.assert_called_once()
+ onmove.assert_called_once()
+ assert tool.extents == (150, 160)
+
+
+@pytest.mark.parametrize('drag_from_anywhere', [True, False])
+def test_span_selector_drag(ax, drag_from_anywhere):
+ # Create span
+ tool = widgets.SpanSelector(ax, onselect=noop, direction='horizontal',
+ interactive=True,
+ drag_from_anywhere=drag_from_anywhere)
+ click_and_drag(tool, start=(10, 10), end=(100, 120))
+ assert tool.extents == (10, 100)
+ # Drag inside span
+ #
+ # If drag_from_anywhere == True, this will move the span by 10,
+ # giving new value extents = 20, 110
+ #
+ # If drag_from_anywhere == False, this will create a new span with
+ # value extents = 25, 35
+ click_and_drag(tool, start=(25, 15), end=(35, 25))
+ if drag_from_anywhere:
+ assert tool.extents == (20, 110)
+ else:
+ assert tool.extents == (25, 35)
+
+ # Check that in both cases, dragging outside the span draws a new span
+ click_and_drag(tool, start=(175, 185), end=(185, 195))
+ assert tool.extents == (175, 185)
+
+
+def test_span_selector_direction(ax):
+ tool = widgets.SpanSelector(ax, onselect=noop, direction='horizontal',
+ interactive=True)
+ assert tool.direction == 'horizontal'
+ assert tool._edge_handles.direction == 'horizontal'
+
+ with pytest.raises(ValueError):
+ tool = widgets.SpanSelector(ax, onselect=noop,
+ direction='invalid_direction')
+
+ tool.direction = 'vertical'
+ assert tool.direction == 'vertical'
+ assert tool._edge_handles.direction == 'vertical'
+
+ with pytest.raises(ValueError):
+ tool.direction = 'invalid_string'
+
+
+def test_span_selector_set_props_handle_props(ax):
+ tool = widgets.SpanSelector(ax, onselect=noop, direction='horizontal',
+ interactive=True,
+ props=dict(facecolor='b', alpha=0.2),
+ handle_props=dict(alpha=0.5))
+ # Create rectangle
+ click_and_drag(tool, start=(0, 10), end=(100, 120))
+
+ artist = tool._selection_artist
+ assert artist.get_facecolor() == mcolors.to_rgba('b', alpha=0.2)
+ tool.set_props(facecolor='r', alpha=0.3)
+ assert artist.get_facecolor() == mcolors.to_rgba('r', alpha=0.3)
+
+ for artist in tool._handles_artists:
+ assert artist.get_color() == 'b'
+ assert artist.get_alpha() == 0.5
+ tool.set_handle_props(color='r', alpha=0.3)
+ for artist in tool._handles_artists:
+ assert artist.get_color() == 'r'
+ assert artist.get_alpha() == 0.3
+
+
+@pytest.mark.parametrize('selector', ['span', 'rectangle'])
+def test_selector_clear(ax, selector):
+ kwargs = dict(ax=ax, interactive=True)
+ if selector == 'span':
+ Selector = widgets.SpanSelector
+ kwargs['direction'] = 'horizontal'
+ kwargs['onselect'] = noop
+ else:
+ Selector = widgets.RectangleSelector
+
+ tool = Selector(**kwargs)
+ click_and_drag(tool, start=(10, 10), end=(100, 120))
+
+ # press-release event outside the selector to clear the selector
+ click_and_drag(tool, start=(130, 130), end=(130, 130))
+ assert not tool._selection_completed
+
+ kwargs['ignore_event_outside'] = True
+ tool = Selector(**kwargs)
+ assert tool.ignore_event_outside
+ click_and_drag(tool, start=(10, 10), end=(100, 120))
+
+ # press-release event outside the selector ignored
+ click_and_drag(tool, start=(130, 130), end=(130, 130))
+ assert tool._selection_completed
+
+ do_event(tool, 'on_key_press', key='escape')
+ assert not tool._selection_completed
+
+
+@pytest.mark.parametrize('selector', ['span', 'rectangle'])
+def test_selector_clear_method(ax, selector):
+ if selector == 'span':
+ tool = widgets.SpanSelector(ax, onselect=noop, direction='horizontal',
+ interactive=True,
+ ignore_event_outside=True)
+ else:
+ tool = widgets.RectangleSelector(ax, interactive=True)
+ click_and_drag(tool, start=(10, 10), end=(100, 120))
+ assert tool._selection_completed
+ assert tool.get_visible()
+ if selector == 'span':
+ assert tool.extents == (10, 100)
+
+ tool.clear()
+ assert not tool._selection_completed
+ assert not tool.get_visible()
+
+ # Do another cycle of events to make sure we can
+ click_and_drag(tool, start=(10, 10), end=(50, 120))
+ assert tool._selection_completed
+ assert tool.get_visible()
+ if selector == 'span':
+ assert tool.extents == (10, 50)
+
+
+def test_span_selector_add_state(ax):
+ tool = widgets.SpanSelector(ax, noop, 'horizontal',
+ interactive=True)
+
+ with pytest.raises(ValueError):
+ tool.add_state('unsupported_state')
+ with pytest.raises(ValueError):
+ tool.add_state('center')
+ with pytest.raises(ValueError):
+ tool.add_state('square')
+
+ tool.add_state('move')
+
+
+def test_tool_line_handle(ax):
+ positions = [20, 30, 50]
+ tool_line_handle = widgets.ToolLineHandles(ax, positions, 'horizontal',
+ useblit=False)
+
+ for artist in tool_line_handle.artists:
+ assert not artist.get_animated()
+ assert not artist.get_visible()
+
+ tool_line_handle.set_visible(True)
+ tool_line_handle.set_animated(True)
+
+ for artist in tool_line_handle.artists:
+ assert artist.get_animated()
+ assert artist.get_visible()
+
+ assert tool_line_handle.positions == positions
+
+
+@pytest.mark.parametrize('direction', ("horizontal", "vertical"))
+def test_span_selector_bound(direction):
+ fig, ax = plt.subplots(1, 1)
+ ax.plot([10, 20], [10, 30])
+ fig.canvas.draw()
+ x_bound = ax.get_xbound()
+ y_bound = ax.get_ybound()
+
+ tool = widgets.SpanSelector(ax, print, direction, interactive=True)
+ assert ax.get_xbound() == x_bound
+ assert ax.get_ybound() == y_bound
+
+ bound = x_bound if direction == 'horizontal' else y_bound
+ assert tool._edge_handles.positions == list(bound)
+
+ press_data = (10.5, 11.5)
+ move_data = (11, 13) # Updating selector is done in onmove
+ release_data = move_data
+ click_and_drag(tool, start=press_data, end=move_data)
+
+ assert ax.get_xbound() == x_bound
+ assert ax.get_ybound() == y_bound
+
+ index = 0 if direction == 'horizontal' else 1
+ handle_positions = [press_data[index], release_data[index]]
+ assert tool._edge_handles.positions == handle_positions
+
+
+@pytest.mark.backend('QtAgg', skip_on_importerror=True)
+def test_span_selector_animated_artists_callback():
+ """Check that the animated artists changed in callbacks are updated."""
+ x = np.linspace(0, 2 * np.pi, 100)
+ values = np.sin(x)
+
+ fig, ax = plt.subplots()
+ ln, = ax.plot(x, values, animated=True)
+ ln2, = ax.plot([], animated=True)
+
+ # spin the event loop to let the backend process any pending operations
+ # before drawing artists
+ # See blitting tutorial
+ plt.pause(0.1)
+ ax.draw_artist(ln)
+ fig.canvas.blit(fig.bbox)
+
+ def mean(vmin, vmax):
+ # Return mean of values in x between *vmin* and *vmax*
+ indmin, indmax = np.searchsorted(x, (vmin, vmax))
+ v = values[indmin:indmax].mean()
+ ln2.set_data(x, np.full_like(x, v))
+
+ span = widgets.SpanSelector(ax, mean, direction='horizontal',
+ onmove_callback=mean,
+ interactive=True,
+ drag_from_anywhere=True,
+ useblit=True)
+
+ # Add span selector and check that the line is draw after it was updated
+ # by the callback
+ press_data = [1, 2]
+ move_data = [2, 2]
+ do_event(span, 'press', xdata=press_data[0], ydata=press_data[1], button=1)
+ do_event(span, 'onmove', xdata=move_data[0], ydata=move_data[1], button=1)
+ assert span._get_animated_artists() == (ln, ln2)
+ assert ln.stale is False
+ assert ln2.stale
+ assert_allclose(ln2.get_ydata(), 0.9547335049088455)
+ span.update()
+ assert ln2.stale is False
+
+ # Change span selector and check that the line is drawn/updated after its
+ # value was updated by the callback
+ press_data = [4, 0]
+ move_data = [5, 2]
+ release_data = [5, 2]
+ do_event(span, 'press', xdata=press_data[0], ydata=press_data[1], button=1)
+ do_event(span, 'onmove', xdata=move_data[0], ydata=move_data[1], button=1)
+ assert ln.stale is False
+ assert ln2.stale
+ assert_allclose(ln2.get_ydata(), -0.9424150707548072)
+ do_event(span, 'release', xdata=release_data[0],
+ ydata=release_data[1], button=1)
+ assert ln2.stale is False
+
+
+def test_snapping_values_span_selector(ax):
+ def onselect(*args):
+ pass
+
+ tool = widgets.SpanSelector(ax, onselect, direction='horizontal',)
+ snap_function = tool._snap
+
+ snap_values = np.linspace(0, 5, 11)
+ values = np.array([-0.1, 0.1, 0.2, 0.5, 0.6, 0.7, 0.9, 4.76, 5.0, 5.5])
+ expect = np.array([00.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 5.00, 5.0, 5.0])
+ values = snap_function(values, snap_values)
+ assert_allclose(values, expect)
+
+
+def test_span_selector_snap(ax):
+ def onselect(vmin, vmax):
+ ax._got_onselect = True
+
+ snap_values = np.arange(50) * 4
+
+ tool = widgets.SpanSelector(ax, onselect, direction='horizontal',
+ snap_values=snap_values)
+ tool.extents = (17, 35)
+ assert tool.extents == (16, 36)
+
+ tool.snap_values = None
+ assert tool.snap_values is None
+ tool.extents = (17, 35)
+ assert tool.extents == (17, 35)
+
+
+def test_span_selector_extents(ax):
+ tool = widgets.SpanSelector(
+ ax, lambda a, b: None, "horizontal", ignore_event_outside=True
+ )
+ tool.extents = (5, 10)
+
+ assert tool.extents == (5, 10)
+ assert tool._selection_completed
+
+ # Since `ignore_event_outside=True`, this event should be ignored
+ press_data = (12, 14)
+ release_data = (20, 14)
+ click_and_drag(tool, start=press_data, end=release_data)
+
+ assert tool.extents == (5, 10)
+
+
+@pytest.mark.parametrize('kwargs', [
+ dict(),
+ dict(useblit=False, props=dict(color='red')),
+ dict(useblit=True, button=1),
+])
+def test_lasso_selector(ax, kwargs):
+ onselect = mock.Mock(spec=noop, return_value=None)
+
+ tool = widgets.LassoSelector(ax, onselect=onselect, **kwargs)
+ do_event(tool, 'press', xdata=100, ydata=100, button=1)
+ do_event(tool, 'onmove', xdata=125, ydata=125, button=1)
+ do_event(tool, 'release', xdata=150, ydata=150, button=1)
+
+ onselect.assert_called_once_with([(100, 100), (125, 125), (150, 150)])
+
+
+def test_lasso_selector_set_props(ax):
+ onselect = mock.Mock(spec=noop, return_value=None)
+
+ tool = widgets.LassoSelector(ax, onselect=onselect,
+ props=dict(color='b', alpha=0.2))
+
+ artist = tool._selection_artist
+ assert mcolors.same_color(artist.get_color(), 'b')
+ assert artist.get_alpha() == 0.2
+ tool.set_props(color='r', alpha=0.3)
+ assert mcolors.same_color(artist.get_color(), 'r')
+ assert artist.get_alpha() == 0.3
+
+
+def test_lasso_set_props(ax):
+ onselect = mock.Mock(spec=noop, return_value=None)
+ tool = widgets.Lasso(ax, (100, 100), onselect)
+ line = tool.line
+ assert mcolors.same_color(line.get_color(), 'black')
+ assert line.get_linestyle() == '-'
+ assert line.get_lw() == 2
+ tool = widgets.Lasso(ax, (100, 100), onselect, props=dict(
+ linestyle='-', color='darkblue', alpha=0.2, lw=1))
+
+ line = tool.line
+ assert mcolors.same_color(line.get_color(), 'darkblue')
+ assert line.get_alpha() == 0.2
+ assert line.get_lw() == 1
+ assert line.get_linestyle() == '-'
+ line.set_color('r')
+ line.set_alpha(0.3)
+ assert mcolors.same_color(line.get_color(), 'r')
+ assert line.get_alpha() == 0.3
+
+
+def test_CheckButtons(ax):
+ labels = ('a', 'b', 'c')
+ check = widgets.CheckButtons(ax, labels, (True, False, True))
+ assert check.get_status() == [True, False, True]
+ check.set_active(0)
+ assert check.get_status() == [False, False, True]
+ assert check.get_checked_labels() == ['c']
+ check.clear()
+ assert check.get_status() == [False, False, False]
+ assert check.get_checked_labels() == []
+
+ for invalid_index in [-1, len(labels), len(labels)+5]:
+ with pytest.raises(ValueError):
+ check.set_active(index=invalid_index)
+
+ for invalid_value in ['invalid', -1]:
+ with pytest.raises(TypeError):
+ check.set_active(1, state=invalid_value)
+
+ cid = check.on_clicked(lambda: None)
+ check.disconnect(cid)
+
+
+@pytest.mark.parametrize("toolbar", ["none", "toolbar2", "toolmanager"])
+def test_TextBox(ax, toolbar):
+ # Avoid "toolmanager is provisional" warning.
+ plt.rcParams._set("toolbar", toolbar)
+
+ submit_event = mock.Mock(spec=noop, return_value=None)
+ text_change_event = mock.Mock(spec=noop, return_value=None)
+ tool = widgets.TextBox(ax, '')
+ tool.on_submit(submit_event)
+ tool.on_text_change(text_change_event)
+
+ assert tool.text == ''
+
+ do_event(tool, '_click')
+
+ tool.set_val('x**2')
+
+ assert tool.text == 'x**2'
+ assert text_change_event.call_count == 1
+
+ tool.begin_typing()
+ tool.stop_typing()
+
+ assert submit_event.call_count == 2
+
+ do_event(tool, '_click', xdata=.5, ydata=.5) # Ensure the click is in the axes.
+ do_event(tool, '_keypress', key='+')
+ do_event(tool, '_keypress', key='5')
+
+ assert text_change_event.call_count == 3
+
+
+def test_RadioButtons(ax):
+ radio = widgets.RadioButtons(ax, ('Radio 1', 'Radio 2', 'Radio 3'))
+ radio.set_active(1)
+ assert radio.value_selected == 'Radio 2'
+ assert radio.index_selected == 1
+ radio.clear()
+ assert radio.value_selected == 'Radio 1'
+ assert radio.index_selected == 0
+
+
+@image_comparison(['check_radio_buttons.png'], style='mpl20', remove_text=True)
+def test_check_radio_buttons_image():
+ ax = get_ax()
+ fig = ax.get_figure(root=False)
+ fig.subplots_adjust(left=0.3)
+
+ rax1 = fig.add_axes((0.05, 0.7, 0.2, 0.15))
+ rb1 = widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3'))
+
+ rax2 = fig.add_axes((0.05, 0.5, 0.2, 0.15))
+ cb1 = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'),
+ (False, True, True))
+
+ rax3 = fig.add_axes((0.05, 0.3, 0.2, 0.15))
+ rb3 = widgets.RadioButtons(
+ rax3, ('Radio 1', 'Radio 2', 'Radio 3'),
+ label_props={'fontsize': [8, 12, 16],
+ 'color': ['red', 'green', 'blue']},
+ radio_props={'edgecolor': ['red', 'green', 'blue'],
+ 'facecolor': ['mistyrose', 'palegreen', 'lightblue']})
+
+ rax4 = fig.add_axes((0.05, 0.1, 0.2, 0.15))
+ cb4 = widgets.CheckButtons(
+ rax4, ('Check 1', 'Check 2', 'Check 3'), (False, True, True),
+ label_props={'fontsize': [8, 12, 16],
+ 'color': ['red', 'green', 'blue']},
+ frame_props={'edgecolor': ['red', 'green', 'blue'],
+ 'facecolor': ['mistyrose', 'palegreen', 'lightblue']},
+ check_props={'color': ['red', 'green', 'blue']})
+
+
+@check_figures_equal(extensions=["png"])
+def test_radio_buttons(fig_test, fig_ref):
+ widgets.RadioButtons(fig_test.subplots(), ["tea", "coffee"])
+ ax = fig_ref.add_subplot(xticks=[], yticks=[])
+ ax.scatter([.15, .15], [2/3, 1/3], transform=ax.transAxes,
+ s=(plt.rcParams["font.size"] / 2) ** 2, c=["C0", "none"])
+ ax.text(.25, 2/3, "tea", transform=ax.transAxes, va="center")
+ ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center")
+
+
+@check_figures_equal(extensions=['png'])
+def test_radio_buttons_props(fig_test, fig_ref):
+ label_props = {'color': ['red'], 'fontsize': [24]}
+ radio_props = {'facecolor': 'green', 'edgecolor': 'blue', 'linewidth': 2}
+
+ widgets.RadioButtons(fig_ref.subplots(), ['tea', 'coffee'],
+ label_props=label_props, radio_props=radio_props)
+
+ cb = widgets.RadioButtons(fig_test.subplots(), ['tea', 'coffee'])
+ cb.set_label_props(label_props)
+ # Setting the label size automatically increases default marker size, so we
+ # need to do that here as well.
+ cb.set_radio_props({**radio_props, 's': (24 / 2)**2})
+
+
+def test_radio_button_active_conflict(ax):
+ with pytest.warns(UserWarning,
+ match=r'Both the \*activecolor\* parameter'):
+ rb = widgets.RadioButtons(ax, ['tea', 'coffee'], activecolor='red',
+ radio_props={'facecolor': 'green'})
+ # *radio_props*' facecolor wins over *activecolor*
+ assert mcolors.same_color(rb._buttons.get_facecolor(), ['green', 'none'])
+
+
+@check_figures_equal(extensions=['png'])
+def test_radio_buttons_activecolor_change(fig_test, fig_ref):
+ widgets.RadioButtons(fig_ref.subplots(), ['tea', 'coffee'],
+ activecolor='green')
+
+ # Test property setter.
+ cb = widgets.RadioButtons(fig_test.subplots(), ['tea', 'coffee'],
+ activecolor='red')
+ cb.activecolor = 'green'
+
+
+@check_figures_equal(extensions=["png"])
+def test_check_buttons(fig_test, fig_ref):
+ widgets.CheckButtons(fig_test.subplots(), ["tea", "coffee"], [True, True])
+ ax = fig_ref.add_subplot(xticks=[], yticks=[])
+ ax.scatter([.15, .15], [2/3, 1/3], marker='s', transform=ax.transAxes,
+ s=(plt.rcParams["font.size"] / 2) ** 2, c=["none", "none"])
+ ax.scatter([.15, .15], [2/3, 1/3], marker='x', transform=ax.transAxes,
+ s=(plt.rcParams["font.size"] / 2) ** 2, c=["k", "k"])
+ ax.text(.25, 2/3, "tea", transform=ax.transAxes, va="center")
+ ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center")
+
+
+@check_figures_equal(extensions=['png'])
+def test_check_button_props(fig_test, fig_ref):
+ label_props = {'color': ['red'], 'fontsize': [24]}
+ frame_props = {'facecolor': 'green', 'edgecolor': 'blue', 'linewidth': 2}
+ check_props = {'facecolor': 'red', 'linewidth': 2}
+
+ widgets.CheckButtons(fig_ref.subplots(), ['tea', 'coffee'], [True, True],
+ label_props=label_props, frame_props=frame_props,
+ check_props=check_props)
+
+ cb = widgets.CheckButtons(fig_test.subplots(), ['tea', 'coffee'],
+ [True, True])
+ cb.set_label_props(label_props)
+ # Setting the label size automatically increases default marker size, so we
+ # need to do that here as well.
+ cb.set_frame_props({**frame_props, 's': (24 / 2)**2})
+ # FIXME: Axes.scatter promotes facecolor to edgecolor on unfilled markers,
+ # but Collection.update doesn't do that (it forgot the marker already).
+ # This means we cannot pass facecolor to both setters directly.
+ check_props['edgecolor'] = check_props.pop('facecolor')
+ cb.set_check_props({**check_props, 's': (24 / 2)**2})
+
+
+def test_slider_slidermin_slidermax_invalid():
+ fig, ax = plt.subplots()
+ # test min/max with floats
+ with pytest.raises(ValueError):
+ widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
+ slidermin=10.0)
+ with pytest.raises(ValueError):
+ widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
+ slidermax=10.0)
+
+
+def test_slider_slidermin_slidermax():
+ fig, ax = plt.subplots()
+ slider_ = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
+ valinit=5.0)
+
+ slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
+ valinit=1.0, slidermin=slider_)
+ assert slider.val == slider_.val
+
+ slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
+ valinit=10.0, slidermax=slider_)
+ assert slider.val == slider_.val
+
+
+def test_slider_valmin_valmax():
+ fig, ax = plt.subplots()
+ slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
+ valinit=-10.0)
+ assert slider.val == slider.valmin
+
+ slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
+ valinit=25.0)
+ assert slider.val == slider.valmax
+
+
+def test_slider_valstep_snapping():
+ fig, ax = plt.subplots()
+ slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
+ valinit=11.4, valstep=1)
+ assert slider.val == 11
+
+ slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
+ valinit=11.4, valstep=[0, 1, 5.5, 19.7])
+ assert slider.val == 5.5
+
+
+def test_slider_horizontal_vertical():
+ fig, ax = plt.subplots()
+ slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=24,
+ valinit=12, orientation='horizontal')
+ slider.set_val(10)
+ assert slider.val == 10
+ # check the dimension of the slider patch in axes units
+ box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
+ assert_allclose(box.bounds, [0, .25, 10/24, .5])
+
+ fig, ax = plt.subplots()
+ slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=24,
+ valinit=12, orientation='vertical')
+ slider.set_val(10)
+ assert slider.val == 10
+ # check the dimension of the slider patch in axes units
+ box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
+ assert_allclose(box.bounds, [.25, 0, .5, 10/24])
+
+
+def test_slider_reset():
+ fig, ax = plt.subplots()
+ slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=1, valinit=.5)
+ slider.set_val(0.75)
+ slider.reset()
+ assert slider.val == 0.5
+
+
+@pytest.mark.parametrize("orientation", ["horizontal", "vertical"])
+def test_range_slider(orientation):
+ if orientation == "vertical":
+ idx = [1, 0, 3, 2]
+ else:
+ idx = [0, 1, 2, 3]
+
+ fig, ax = plt.subplots()
+
+ slider = widgets.RangeSlider(
+ ax=ax, label="", valmin=0.0, valmax=1.0, orientation=orientation,
+ valinit=[0.1, 0.34]
+ )
+ box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
+ assert_allclose(box.get_points().flatten()[idx], [0.1, 0.25, 0.34, 0.75])
+
+ # Check initial value is set correctly
+ assert_allclose(slider.val, (0.1, 0.34))
+
+ def handle_positions(slider):
+ if orientation == "vertical":
+ return [h.get_ydata()[0] for h in slider._handles]
+ else:
+ return [h.get_xdata()[0] for h in slider._handles]
+
+ slider.set_val((0.4, 0.6))
+ assert_allclose(slider.val, (0.4, 0.6))
+ assert_allclose(handle_positions(slider), (0.4, 0.6))
+
+ box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
+ assert_allclose(box.get_points().flatten()[idx], [0.4, .25, 0.6, .75])
+
+ slider.set_val((0.2, 0.1))
+ assert_allclose(slider.val, (0.1, 0.2))
+ assert_allclose(handle_positions(slider), (0.1, 0.2))
+
+ slider.set_val((-1, 10))
+ assert_allclose(slider.val, (0, 1))
+ assert_allclose(handle_positions(slider), (0, 1))
+
+ slider.reset()
+ assert_allclose(slider.val, (0.1, 0.34))
+ assert_allclose(handle_positions(slider), (0.1, 0.34))
+
+
+@pytest.mark.parametrize("orientation", ["horizontal", "vertical"])
+def test_range_slider_same_init_values(orientation):
+ if orientation == "vertical":
+ idx = [1, 0, 3, 2]
+ else:
+ idx = [0, 1, 2, 3]
+
+ fig, ax = plt.subplots()
+
+ slider = widgets.RangeSlider(
+ ax=ax, label="", valmin=0.0, valmax=1.0, orientation=orientation,
+ valinit=[0, 0]
+ )
+ box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
+ assert_allclose(box.get_points().flatten()[idx], [0, 0.25, 0, 0.75])
+
+
+def check_polygon_selector(event_sequence, expected_result, selections_count,
+ **kwargs):
+ """
+ Helper function to test Polygon Selector.
+
+ Parameters
+ ----------
+ event_sequence : list of tuples (etype, dict())
+ A sequence of events to perform. The sequence is a list of tuples
+ where the first element of the tuple is an etype (e.g., 'onmove',
+ 'press', etc.), and the second element of the tuple is a dictionary of
+ the arguments for the event (e.g., xdata=5, key='shift', etc.).
+ expected_result : list of vertices (xdata, ydata)
+ The list of vertices that are expected to result from the event
+ sequence.
+ selections_count : int
+ Wait for the tool to call its `onselect` function `selections_count`
+ times, before comparing the result to the `expected_result`
+ **kwargs
+ Keyword arguments are passed to PolygonSelector.
+ """
+ ax = get_ax()
+
+ onselect = mock.Mock(spec=noop, return_value=None)
+
+ tool = widgets.PolygonSelector(ax, onselect=onselect, **kwargs)
+
+ for (etype, event_args) in event_sequence:
+ do_event(tool, etype, **event_args)
+
+ assert onselect.call_count == selections_count
+ assert onselect.call_args == ((expected_result, ), {})
+
+
+def polygon_place_vertex(xdata, ydata):
+ return [('onmove', dict(xdata=xdata, ydata=ydata)),
+ ('press', dict(xdata=xdata, ydata=ydata)),
+ ('release', dict(xdata=xdata, ydata=ydata))]
+
+
+def polygon_remove_vertex(xdata, ydata):
+ return [('onmove', dict(xdata=xdata, ydata=ydata)),
+ ('press', dict(xdata=xdata, ydata=ydata, button=3)),
+ ('release', dict(xdata=xdata, ydata=ydata, button=3))]
+
+
+@pytest.mark.parametrize('draw_bounding_box', [False, True])
+def test_polygon_selector(draw_bounding_box):
+ check_selector = functools.partial(
+ check_polygon_selector, draw_bounding_box=draw_bounding_box)
+
+ # Simple polygon
+ expected_result = [(50, 50), (150, 50), (50, 150)]
+ event_sequence = [
+ *polygon_place_vertex(50, 50),
+ *polygon_place_vertex(150, 50),
+ *polygon_place_vertex(50, 150),
+ *polygon_place_vertex(50, 50),
+ ]
+ check_selector(event_sequence, expected_result, 1)
+
+ # Move first vertex before completing the polygon.
+ expected_result = [(75, 50), (150, 50), (50, 150)]
+ event_sequence = [
+ *polygon_place_vertex(50, 50),
+ *polygon_place_vertex(150, 50),
+ ('on_key_press', dict(key='control')),
+ ('onmove', dict(xdata=50, ydata=50)),
+ ('press', dict(xdata=50, ydata=50)),
+ ('onmove', dict(xdata=75, ydata=50)),
+ ('release', dict(xdata=75, ydata=50)),
+ ('on_key_release', dict(key='control')),
+ *polygon_place_vertex(50, 150),
+ *polygon_place_vertex(75, 50),
+ ]
+ check_selector(event_sequence, expected_result, 1)
+
+ # Move first two vertices at once before completing the polygon.
+ expected_result = [(50, 75), (150, 75), (50, 150)]
+ event_sequence = [
+ *polygon_place_vertex(50, 50),
+ *polygon_place_vertex(150, 50),
+ ('on_key_press', dict(key='shift')),
+ ('onmove', dict(xdata=100, ydata=100)),
+ ('press', dict(xdata=100, ydata=100)),
+ ('onmove', dict(xdata=100, ydata=125)),
+ ('release', dict(xdata=100, ydata=125)),
+ ('on_key_release', dict(key='shift')),
+ *polygon_place_vertex(50, 150),
+ *polygon_place_vertex(50, 75),
+ ]
+ check_selector(event_sequence, expected_result, 1)
+
+ # Move first vertex after completing the polygon.
+ expected_result = [(75, 50), (150, 50), (50, 150)]
+ event_sequence = [
+ *polygon_place_vertex(50, 50),
+ *polygon_place_vertex(150, 50),
+ *polygon_place_vertex(50, 150),
+ *polygon_place_vertex(50, 50),
+ ('onmove', dict(xdata=50, ydata=50)),
+ ('press', dict(xdata=50, ydata=50)),
+ ('onmove', dict(xdata=75, ydata=50)),
+ ('release', dict(xdata=75, ydata=50)),
+ ]
+ check_selector(event_sequence, expected_result, 2)
+
+ # Move all vertices after completing the polygon.
+ expected_result = [(75, 75), (175, 75), (75, 175)]
+ event_sequence = [
+ *polygon_place_vertex(50, 50),
+ *polygon_place_vertex(150, 50),
+ *polygon_place_vertex(50, 150),
+ *polygon_place_vertex(50, 50),
+ ('on_key_press', dict(key='shift')),
+ ('onmove', dict(xdata=100, ydata=100)),
+ ('press', dict(xdata=100, ydata=100)),
+ ('onmove', dict(xdata=125, ydata=125)),
+ ('release', dict(xdata=125, ydata=125)),
+ ('on_key_release', dict(key='shift')),
+ ]
+ check_selector(event_sequence, expected_result, 2)
+
+ # Try to move a vertex and move all before placing any vertices.
+ expected_result = [(50, 50), (150, 50), (50, 150)]
+ event_sequence = [
+ ('on_key_press', dict(key='control')),
+ ('onmove', dict(xdata=100, ydata=100)),
+ ('press', dict(xdata=100, ydata=100)),
+ ('onmove', dict(xdata=125, ydata=125)),
+ ('release', dict(xdata=125, ydata=125)),
+ ('on_key_release', dict(key='control')),
+ ('on_key_press', dict(key='shift')),
+ ('onmove', dict(xdata=100, ydata=100)),
+ ('press', dict(xdata=100, ydata=100)),
+ ('onmove', dict(xdata=125, ydata=125)),
+ ('release', dict(xdata=125, ydata=125)),
+ ('on_key_release', dict(key='shift')),
+ *polygon_place_vertex(50, 50),
+ *polygon_place_vertex(150, 50),
+ *polygon_place_vertex(50, 150),
+ *polygon_place_vertex(50, 50),
+ ]
+ check_selector(event_sequence, expected_result, 1)
+
+ # Try to place vertex out-of-bounds, then reset, and start a new polygon.
+ expected_result = [(50, 50), (150, 50), (50, 150)]
+ event_sequence = [
+ *polygon_place_vertex(50, 50),
+ *polygon_place_vertex(250, 50),
+ ('on_key_press', dict(key='escape')),
+ ('on_key_release', dict(key='escape')),
+ *polygon_place_vertex(50, 50),
+ *polygon_place_vertex(150, 50),
+ *polygon_place_vertex(50, 150),
+ *polygon_place_vertex(50, 50),
+ ]
+ check_selector(event_sequence, expected_result, 1)
+
+
+@pytest.mark.parametrize('draw_bounding_box', [False, True])
+def test_polygon_selector_set_props_handle_props(ax, draw_bounding_box):
+ tool = widgets.PolygonSelector(ax,
+ props=dict(color='b', alpha=0.2),
+ handle_props=dict(alpha=0.5),
+ draw_bounding_box=draw_bounding_box)
+
+ event_sequence = [
+ *polygon_place_vertex(50, 50),
+ *polygon_place_vertex(150, 50),
+ *polygon_place_vertex(50, 150),
+ *polygon_place_vertex(50, 50),
+ ]
+
+ for (etype, event_args) in event_sequence:
+ do_event(tool, etype, **event_args)
+
+ artist = tool._selection_artist
+ assert artist.get_color() == 'b'
+ assert artist.get_alpha() == 0.2
+ tool.set_props(color='r', alpha=0.3)
+ assert artist.get_color() == 'r'
+ assert artist.get_alpha() == 0.3
+
+ for artist in tool._handles_artists:
+ assert artist.get_color() == 'b'
+ assert artist.get_alpha() == 0.5
+ tool.set_handle_props(color='r', alpha=0.3)
+ for artist in tool._handles_artists:
+ assert artist.get_color() == 'r'
+ assert artist.get_alpha() == 0.3
+
+
+@check_figures_equal(extensions=['png'])
+def test_rect_visibility(fig_test, fig_ref):
+ # Check that requesting an invisible selector makes it invisible
+ ax_test = fig_test.subplots()
+ _ = fig_ref.subplots()
+
+ tool = widgets.RectangleSelector(ax_test, props={'visible': False})
+ tool.extents = (0.2, 0.8, 0.3, 0.7)
+
+
+# Change the order that the extra point is inserted in
+@pytest.mark.parametrize('idx', [1, 2, 3])
+@pytest.mark.parametrize('draw_bounding_box', [False, True])
+def test_polygon_selector_remove(idx, draw_bounding_box):
+ verts = [(50, 50), (150, 50), (50, 150)]
+ event_sequence = [polygon_place_vertex(*verts[0]),
+ polygon_place_vertex(*verts[1]),
+ polygon_place_vertex(*verts[2]),
+ # Finish the polygon
+ polygon_place_vertex(*verts[0])]
+ # Add an extra point
+ event_sequence.insert(idx, polygon_place_vertex(200, 200))
+ # Remove the extra point
+ event_sequence.append(polygon_remove_vertex(200, 200))
+ # Flatten list of lists
+ event_sequence = functools.reduce(operator.iadd, event_sequence, [])
+ check_polygon_selector(event_sequence, verts, 2,
+ draw_bounding_box=draw_bounding_box)
+
+
+@pytest.mark.parametrize('draw_bounding_box', [False, True])
+def test_polygon_selector_remove_first_point(draw_bounding_box):
+ verts = [(50, 50), (150, 50), (50, 150)]
+ event_sequence = [
+ *polygon_place_vertex(*verts[0]),
+ *polygon_place_vertex(*verts[1]),
+ *polygon_place_vertex(*verts[2]),
+ *polygon_place_vertex(*verts[0]),
+ *polygon_remove_vertex(*verts[0]),
+ ]
+ check_polygon_selector(event_sequence, verts[1:], 2,
+ draw_bounding_box=draw_bounding_box)
+
+
+@pytest.mark.parametrize('draw_bounding_box', [False, True])
+def test_polygon_selector_redraw(ax, draw_bounding_box):
+ verts = [(50, 50), (150, 50), (50, 150)]
+ event_sequence = [
+ *polygon_place_vertex(*verts[0]),
+ *polygon_place_vertex(*verts[1]),
+ *polygon_place_vertex(*verts[2]),
+ *polygon_place_vertex(*verts[0]),
+ # Polygon completed, now remove first two verts.
+ *polygon_remove_vertex(*verts[1]),
+ *polygon_remove_vertex(*verts[2]),
+ # At this point the tool should be reset so we can add more vertices.
+ *polygon_place_vertex(*verts[1]),
+ ]
+
+ tool = widgets.PolygonSelector(ax, draw_bounding_box=draw_bounding_box)
+ for (etype, event_args) in event_sequence:
+ do_event(tool, etype, **event_args)
+ # After removing two verts, only one remains, and the
+ # selector should be automatically resete
+ assert tool.verts == verts[0:2]
+
+
+@pytest.mark.parametrize('draw_bounding_box', [False, True])
+@check_figures_equal(extensions=['png'])
+def test_polygon_selector_verts_setter(fig_test, fig_ref, draw_bounding_box):
+ verts = [(0.1, 0.4), (0.5, 0.9), (0.3, 0.2)]
+ ax_test = fig_test.add_subplot()
+
+ tool_test = widgets.PolygonSelector(ax_test, draw_bounding_box=draw_bounding_box)
+ tool_test.verts = verts
+ assert tool_test.verts == verts
+
+ ax_ref = fig_ref.add_subplot()
+ tool_ref = widgets.PolygonSelector(ax_ref, draw_bounding_box=draw_bounding_box)
+ event_sequence = [
+ *polygon_place_vertex(*verts[0]),
+ *polygon_place_vertex(*verts[1]),
+ *polygon_place_vertex(*verts[2]),
+ *polygon_place_vertex(*verts[0]),
+ ]
+ for (etype, event_args) in event_sequence:
+ do_event(tool_ref, etype, **event_args)
+
+
+def test_polygon_selector_box(ax):
+ # Create a diamond (adjusting axes lims s.t. the diamond lies within axes limits).
+ ax.set(xlim=(-10, 50), ylim=(-10, 50))
+ verts = [(20, 0), (0, 20), (20, 40), (40, 20)]
+ event_sequence = [
+ *polygon_place_vertex(*verts[0]),
+ *polygon_place_vertex(*verts[1]),
+ *polygon_place_vertex(*verts[2]),
+ *polygon_place_vertex(*verts[3]),
+ *polygon_place_vertex(*verts[0]),
+ ]
+
+ # Create selector
+ tool = widgets.PolygonSelector(ax, draw_bounding_box=True)
+ for (etype, event_args) in event_sequence:
+ do_event(tool, etype, **event_args)
+
+ # In order to trigger the correct callbacks, trigger events on the canvas
+ # instead of the individual tools
+ t = ax.transData
+ canvas = ax.get_figure(root=True).canvas
+
+ # Scale to half size using the top right corner of the bounding box
+ MouseEvent(
+ "button_press_event", canvas, *t.transform((40, 40)), 1)._process()
+ MouseEvent(
+ "motion_notify_event", canvas, *t.transform((20, 20)))._process()
+ MouseEvent(
+ "button_release_event", canvas, *t.transform((20, 20)), 1)._process()
+ np.testing.assert_allclose(
+ tool.verts, [(10, 0), (0, 10), (10, 20), (20, 10)])
+
+ # Move using the center of the bounding box
+ MouseEvent(
+ "button_press_event", canvas, *t.transform((10, 10)), 1)._process()
+ MouseEvent(
+ "motion_notify_event", canvas, *t.transform((30, 30)))._process()
+ MouseEvent(
+ "button_release_event", canvas, *t.transform((30, 30)), 1)._process()
+ np.testing.assert_allclose(
+ tool.verts, [(30, 20), (20, 30), (30, 40), (40, 30)])
+
+ # Remove a point from the polygon and check that the box extents update
+ np.testing.assert_allclose(
+ tool._box.extents, (20.0, 40.0, 20.0, 40.0))
+
+ MouseEvent(
+ "button_press_event", canvas, *t.transform((30, 20)), 3)._process()
+ MouseEvent(
+ "button_release_event", canvas, *t.transform((30, 20)), 3)._process()
+ np.testing.assert_allclose(
+ tool.verts, [(20, 30), (30, 40), (40, 30)])
+ np.testing.assert_allclose(
+ tool._box.extents, (20.0, 40.0, 30.0, 40.0))
+
+
+def test_polygon_selector_clear_method(ax):
+ onselect = mock.Mock(spec=noop, return_value=None)
+ tool = widgets.PolygonSelector(ax, onselect)
+
+ for result in ([(50, 50), (150, 50), (50, 150), (50, 50)],
+ [(50, 50), (100, 50), (50, 150), (50, 50)]):
+ for x, y in result:
+ for etype, event_args in polygon_place_vertex(x, y):
+ do_event(tool, etype, **event_args)
+
+ artist = tool._selection_artist
+
+ assert tool._selection_completed
+ assert tool.get_visible()
+ assert artist.get_visible()
+ np.testing.assert_equal(artist.get_xydata(), result)
+ assert onselect.call_args == ((result[:-1],), {})
+
+ tool.clear()
+ assert not tool._selection_completed
+ np.testing.assert_equal(artist.get_xydata(), [(0, 0)])
+
+
+@pytest.mark.parametrize("horizOn", [False, True])
+@pytest.mark.parametrize("vertOn", [False, True])
+def test_MultiCursor(horizOn, vertOn):
+ fig = plt.figure()
+ (ax1, ax3) = fig.subplots(2, sharex=True)
+ ax2 = plt.figure().subplots()
+
+ # useblit=false to avoid having to draw the figure to cache the renderer
+ multi = widgets.MultiCursor(
+ None, (ax1, ax2), useblit=False, horizOn=horizOn, vertOn=vertOn
+ )
+
+ # Only two of the axes should have a line drawn on them.
+ assert len(multi.vlines) == 2
+ assert len(multi.hlines) == 2
+
+ # mock a motion_notify_event
+ # Can't use `do_event` as that helper requires the widget
+ # to have a single .ax attribute.
+ event = mock_event(ax1, xdata=.5, ydata=.25)
+ multi.onmove(event)
+ # force a draw + draw event to exercise clear
+ fig.canvas.draw()
+
+ # the lines in the first two ax should both move
+ for l in multi.vlines:
+ assert l.get_xdata() == (.5, .5)
+ for l in multi.hlines:
+ assert l.get_ydata() == (.25, .25)
+ # The relevant lines get turned on after move.
+ assert len([line for line in multi.vlines if line.get_visible()]) == (
+ 2 if vertOn else 0)
+ assert len([line for line in multi.hlines if line.get_visible()]) == (
+ 2 if horizOn else 0)
+
+ # After toggling settings, the opposite lines should be visible after move.
+ multi.horizOn = not multi.horizOn
+ multi.vertOn = not multi.vertOn
+ event = mock_event(ax1, xdata=.5, ydata=.25)
+ multi.onmove(event)
+ assert len([line for line in multi.vlines if line.get_visible()]) == (
+ 0 if vertOn else 2)
+ assert len([line for line in multi.hlines if line.get_visible()]) == (
+ 0 if horizOn else 2)
+
+ # test a move event in an Axes not part of the MultiCursor
+ # the lines in ax1 and ax2 should not have moved.
+ event = mock_event(ax3, xdata=.75, ydata=.75)
+ multi.onmove(event)
+ for l in multi.vlines:
+ assert l.get_xdata() == (.5, .5)
+ for l in multi.hlines:
+ assert l.get_ydata() == (.25, .25)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/texmanager.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/texmanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..0424aede16eb8e6ba3f44a12a42222f97994d209
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/texmanager.py
@@ -0,0 +1,369 @@
+r"""
+Support for embedded TeX expressions in Matplotlib.
+
+Requirements:
+
+* LaTeX.
+* \*Agg backends: dvipng>=1.6.
+* PS backend: PSfrag, dvips, and Ghostscript>=9.0.
+* PDF and SVG backends: if LuaTeX is present, it will be used to speed up some
+ post-processing steps, but note that it is not used to parse the TeX string
+ itself (only LaTeX is supported).
+
+To enable TeX rendering of all text in your Matplotlib figure, set
+:rc:`text.usetex` to True.
+
+TeX and dvipng/dvips processing results are cached
+in ~/.matplotlib/tex.cache for reuse between sessions.
+
+`TexManager.get_rgba` can also be used to directly obtain raster output as RGBA
+NumPy arrays.
+"""
+
+import functools
+import hashlib
+import logging
+import os
+from pathlib import Path
+import subprocess
+from tempfile import TemporaryDirectory
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import cbook, dviread
+
+_log = logging.getLogger(__name__)
+
+
+def _usepackage_if_not_loaded(package, *, option=None):
+ """
+ Output LaTeX code that loads a package (possibly with an option) if it
+ hasn't been loaded yet.
+
+ LaTeX cannot load twice a package with different options, so this helper
+ can be used to protect against users loading arbitrary packages/options in
+ their custom preamble.
+ """
+ option = f"[{option}]" if option is not None else ""
+ return (
+ r"\makeatletter"
+ r"\@ifpackageloaded{%(package)s}{}{\usepackage%(option)s{%(package)s}}"
+ r"\makeatother"
+ ) % {"package": package, "option": option}
+
+
+class TexManager:
+ """
+ Convert strings to dvi files using TeX, caching the results to a directory.
+
+ The cache directory is called ``tex.cache`` and is located in the directory
+ returned by `.get_cachedir`.
+
+ Repeated calls to this constructor always return the same instance.
+ """
+
+ _texcache = os.path.join(mpl.get_cachedir(), 'tex.cache')
+ _grey_arrayd = {}
+
+ _font_families = ('serif', 'sans-serif', 'cursive', 'monospace')
+ _font_preambles = {
+ 'new century schoolbook': r'\renewcommand{\rmdefault}{pnc}',
+ 'bookman': r'\renewcommand{\rmdefault}{pbk}',
+ 'times': r'\usepackage{mathptmx}',
+ 'palatino': r'\usepackage{mathpazo}',
+ 'zapf chancery': r'\usepackage{chancery}',
+ 'cursive': r'\usepackage{chancery}',
+ 'charter': r'\usepackage{charter}',
+ 'serif': '',
+ 'sans-serif': '',
+ 'helvetica': r'\usepackage{helvet}',
+ 'avant garde': r'\usepackage{avant}',
+ 'courier': r'\usepackage{courier}',
+ # Loading the type1ec package ensures that cm-super is installed, which
+ # is necessary for Unicode computer modern. (It also allows the use of
+ # computer modern at arbitrary sizes, but that's just a side effect.)
+ 'monospace': r'\usepackage{type1ec}',
+ 'computer modern roman': r'\usepackage{type1ec}',
+ 'computer modern sans serif': r'\usepackage{type1ec}',
+ 'computer modern typewriter': r'\usepackage{type1ec}',
+ }
+ _font_types = {
+ 'new century schoolbook': 'serif',
+ 'bookman': 'serif',
+ 'times': 'serif',
+ 'palatino': 'serif',
+ 'zapf chancery': 'cursive',
+ 'charter': 'serif',
+ 'helvetica': 'sans-serif',
+ 'avant garde': 'sans-serif',
+ 'courier': 'monospace',
+ 'computer modern roman': 'serif',
+ 'computer modern sans serif': 'sans-serif',
+ 'computer modern typewriter': 'monospace',
+ }
+
+ @functools.lru_cache # Always return the same instance.
+ def __new__(cls):
+ Path(cls._texcache).mkdir(parents=True, exist_ok=True)
+ return object.__new__(cls)
+
+ @classmethod
+ def _get_font_family_and_reduced(cls):
+ """Return the font family name and whether the font is reduced."""
+ ff = mpl.rcParams['font.family']
+ ff_val = ff[0].lower() if len(ff) == 1 else None
+ if len(ff) == 1 and ff_val in cls._font_families:
+ return ff_val, False
+ elif len(ff) == 1 and ff_val in cls._font_preambles:
+ return cls._font_types[ff_val], True
+ else:
+ _log.info('font.family must be one of (%s) when text.usetex is '
+ 'True. serif will be used by default.',
+ ', '.join(cls._font_families))
+ return 'serif', False
+
+ @classmethod
+ def _get_font_preamble_and_command(cls):
+ requested_family, is_reduced_font = cls._get_font_family_and_reduced()
+
+ preambles = {}
+ for font_family in cls._font_families:
+ if is_reduced_font and font_family == requested_family:
+ preambles[font_family] = cls._font_preambles[
+ mpl.rcParams['font.family'][0].lower()]
+ else:
+ rcfonts = mpl.rcParams[f"font.{font_family}"]
+ for i, font in enumerate(map(str.lower, rcfonts)):
+ if font in cls._font_preambles:
+ preambles[font_family] = cls._font_preambles[font]
+ _log.debug(
+ 'family: %s, package: %s, font: %s, skipped: %s',
+ font_family, cls._font_preambles[font], rcfonts[i],
+ ', '.join(rcfonts[:i]),
+ )
+ break
+ else:
+ _log.info('No LaTeX-compatible font found for the %s font'
+ 'family in rcParams. Using default.',
+ font_family)
+ preambles[font_family] = cls._font_preambles[font_family]
+
+ # The following packages and commands need to be included in the latex
+ # file's preamble:
+ cmd = {preambles[family]
+ for family in ['serif', 'sans-serif', 'monospace']}
+ if requested_family == 'cursive':
+ cmd.add(preambles['cursive'])
+ cmd.add(r'\usepackage{type1cm}')
+ preamble = '\n'.join(sorted(cmd))
+ fontcmd = (r'\sffamily' if requested_family == 'sans-serif' else
+ r'\ttfamily' if requested_family == 'monospace' else
+ r'\rmfamily')
+ return preamble, fontcmd
+
+ @classmethod
+ def get_basefile(cls, tex, fontsize, dpi=None):
+ """
+ Return a filename based on a hash of the string, fontsize, and dpi.
+ """
+ src = cls._get_tex_source(tex, fontsize) + str(dpi)
+ filehash = hashlib.sha256(
+ src.encode('utf-8'),
+ usedforsecurity=False
+ ).hexdigest()
+ filepath = Path(cls._texcache)
+
+ num_letters, num_levels = 2, 2
+ for i in range(0, num_letters*num_levels, num_letters):
+ filepath = filepath / Path(filehash[i:i+2])
+
+ filepath.mkdir(parents=True, exist_ok=True)
+ return os.path.join(filepath, filehash)
+
+ @classmethod
+ def get_font_preamble(cls):
+ """
+ Return a string containing font configuration for the tex preamble.
+ """
+ font_preamble, command = cls._get_font_preamble_and_command()
+ return font_preamble
+
+ @classmethod
+ def get_custom_preamble(cls):
+ """Return a string containing user additions to the tex preamble."""
+ return mpl.rcParams['text.latex.preamble']
+
+ @classmethod
+ def _get_tex_source(cls, tex, fontsize):
+ """Return the complete TeX source for processing a TeX string."""
+ font_preamble, fontcmd = cls._get_font_preamble_and_command()
+ baselineskip = 1.25 * fontsize
+ return "\n".join([
+ r"\documentclass{article}",
+ r"% Pass-through \mathdefault, which is used in non-usetex mode",
+ r"% to use the default text font but was historically suppressed",
+ r"% in usetex mode.",
+ r"\newcommand{\mathdefault}[1]{#1}",
+ font_preamble,
+ r"\usepackage[utf8]{inputenc}",
+ r"\DeclareUnicodeCharacter{2212}{\ensuremath{-}}",
+ r"% geometry is loaded before the custom preamble as ",
+ r"% convert_psfrags relies on a custom preamble to change the ",
+ r"% geometry.",
+ r"\usepackage[papersize=72in, margin=1in]{geometry}",
+ cls.get_custom_preamble(),
+ r"% Use `underscore` package to take care of underscores in text.",
+ r"% The [strings] option allows to use underscores in file names.",
+ _usepackage_if_not_loaded("underscore", option="strings"),
+ r"% Custom packages (e.g. newtxtext) may already have loaded ",
+ r"% textcomp with different options.",
+ _usepackage_if_not_loaded("textcomp"),
+ r"\pagestyle{empty}",
+ r"\begin{document}",
+ r"% The empty hbox ensures that a page is printed even for empty",
+ r"% inputs, except when using psfrag which gets confused by it.",
+ r"% matplotlibbaselinemarker is used by dviread to detect the",
+ r"% last line's baseline.",
+ rf"\fontsize{{{fontsize}}}{{{baselineskip}}}%",
+ r"\ifdefined\psfrag\else\hbox{}\fi%",
+ rf"{{{fontcmd} {tex}}}%",
+ r"\end{document}",
+ ])
+
+ @classmethod
+ def make_tex(cls, tex, fontsize):
+ """
+ Generate a tex file to render the tex string at a specific font size.
+
+ Return the file name.
+ """
+ texfile = cls.get_basefile(tex, fontsize) + ".tex"
+ Path(texfile).write_text(cls._get_tex_source(tex, fontsize),
+ encoding='utf-8')
+ return texfile
+
+ @classmethod
+ def _run_checked_subprocess(cls, command, tex, *, cwd=None):
+ _log.debug(cbook._pformat_subprocess(command))
+ try:
+ report = subprocess.check_output(
+ command, cwd=cwd if cwd is not None else cls._texcache,
+ stderr=subprocess.STDOUT)
+ except FileNotFoundError as exc:
+ raise RuntimeError(
+ f'Failed to process string with tex because {command[0]} '
+ 'could not be found') from exc
+ except subprocess.CalledProcessError as exc:
+ raise RuntimeError(
+ '{prog} was not able to process the following string:\n'
+ '{tex!r}\n\n'
+ 'Here is the full command invocation and its output:\n\n'
+ '{format_command}\n\n'
+ '{exc}\n\n'.format(
+ prog=command[0],
+ format_command=cbook._pformat_subprocess(command),
+ tex=tex.encode('unicode_escape'),
+ exc=exc.output.decode('utf-8', 'backslashreplace'))
+ ) from None
+ _log.debug(report)
+ return report
+
+ @classmethod
+ def make_dvi(cls, tex, fontsize):
+ """
+ Generate a dvi file containing latex's layout of tex string.
+
+ Return the file name.
+ """
+ dvifile = Path(cls.get_basefile(tex, fontsize)).with_suffix(".dvi")
+ if not dvifile.exists():
+ # Generate the tex and dvi in a temporary directory to avoid race
+ # conditions e.g. if multiple processes try to process the same tex
+ # string at the same time. Having tmpdir be a subdirectory of the
+ # final output dir ensures that they are on the same filesystem,
+ # and thus replace() works atomically. It also allows referring to
+ # the texfile with a relative path (for pathological MPLCONFIGDIRs,
+ # the absolute path may contain characters (e.g. ~) that TeX does
+ # not support; n.b. relative paths cannot traverse parents, or it
+ # will be blocked when `openin_any = p` in texmf.cnf).
+ with TemporaryDirectory(dir=dvifile.parent) as tmpdir:
+ Path(tmpdir, "file.tex").write_text(
+ cls._get_tex_source(tex, fontsize), encoding='utf-8')
+ cls._run_checked_subprocess(
+ ["latex", "-interaction=nonstopmode", "--halt-on-error",
+ "file.tex"], tex, cwd=tmpdir)
+ Path(tmpdir, "file.dvi").replace(dvifile)
+ # Also move the tex source to the main cache directory, but
+ # only for backcompat.
+ Path(tmpdir, "file.tex").replace(dvifile.with_suffix(".tex"))
+ return str(dvifile)
+
+ @classmethod
+ def make_png(cls, tex, fontsize, dpi):
+ """
+ Generate a png file containing latex's rendering of tex string.
+
+ Return the file name.
+ """
+ pngfile = Path(cls.get_basefile(tex, fontsize, dpi)).with_suffix(".png")
+ # see get_rgba for a discussion of the background
+ if not pngfile.exists():
+ dvifile = cls.make_dvi(tex, fontsize)
+ with TemporaryDirectory(dir=pngfile.parent) as tmpdir:
+ cmd = ["dvipng", "-bg", "Transparent", "-D", str(dpi),
+ "-T", "tight", "-o", "file.png", dvifile]
+ # When testing, disable FreeType rendering for reproducibility;
+ # but dvipng 1.16 has a bug (fixed in f3ff241) that breaks
+ # --freetype0 mode, so for it we keep FreeType enabled; the
+ # image will be slightly off.
+ if (getattr(mpl, "_called_from_pytest", False) and
+ mpl._get_executable_info("dvipng").raw_version != "1.16"):
+ cmd.insert(1, "--freetype0")
+ cls._run_checked_subprocess(cmd, tex, cwd=tmpdir)
+ Path(tmpdir, "file.png").replace(pngfile)
+ return str(pngfile)
+
+ @classmethod
+ def get_grey(cls, tex, fontsize=None, dpi=None):
+ """Return the alpha channel."""
+ if not fontsize:
+ fontsize = mpl.rcParams['font.size']
+ if not dpi:
+ dpi = mpl.rcParams['savefig.dpi']
+ key = cls._get_tex_source(tex, fontsize), dpi
+ alpha = cls._grey_arrayd.get(key)
+ if alpha is None:
+ pngfile = cls.make_png(tex, fontsize, dpi)
+ rgba = mpl.image.imread(os.path.join(cls._texcache, pngfile))
+ cls._grey_arrayd[key] = alpha = rgba[:, :, -1]
+ return alpha
+
+ @classmethod
+ def get_rgba(cls, tex, fontsize=None, dpi=None, rgb=(0, 0, 0)):
+ r"""
+ Return latex's rendering of the tex string as an RGBA array.
+
+ Examples
+ --------
+ >>> texmanager = TexManager()
+ >>> s = r"\TeX\ is $\displaystyle\sum_n\frac{-e^{i\pi}}{2^n}$!"
+ >>> Z = texmanager.get_rgba(s, fontsize=12, dpi=80, rgb=(1, 0, 0))
+ """
+ alpha = cls.get_grey(tex, fontsize, dpi)
+ rgba = np.empty((*alpha.shape, 4))
+ rgba[..., :3] = mpl.colors.to_rgb(rgb)
+ rgba[..., -1] = alpha
+ return rgba
+
+ @classmethod
+ def get_text_width_height_descent(cls, tex, fontsize, renderer=None):
+ """Return width, height and descent of the text."""
+ if tex.strip() == '':
+ return 0, 0, 0
+ dvifile = cls.make_dvi(tex, fontsize)
+ dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
+ with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
+ page, = dvi
+ # A total height (including the descent) needs to be returned.
+ return page.width, page.height + page.descent, page.descent
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/texmanager.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/texmanager.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..94f0d76fa814c6015fd4acefca5b1c1732645c85
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/texmanager.pyi
@@ -0,0 +1,38 @@
+from .backend_bases import RendererBase
+
+from matplotlib.typing import ColorType
+
+import numpy as np
+
+class TexManager:
+ texcache: str
+ @classmethod
+ def get_basefile(
+ cls, tex: str, fontsize: float, dpi: float | None = ...
+ ) -> str: ...
+ @classmethod
+ def get_font_preamble(cls) -> str: ...
+ @classmethod
+ def get_custom_preamble(cls) -> str: ...
+ @classmethod
+ def make_tex(cls, tex: str, fontsize: float) -> str: ...
+ @classmethod
+ def make_dvi(cls, tex: str, fontsize: float) -> str: ...
+ @classmethod
+ def make_png(cls, tex: str, fontsize: float, dpi: float) -> str: ...
+ @classmethod
+ def get_grey(
+ cls, tex: str, fontsize: float | None = ..., dpi: float | None = ...
+ ) -> np.ndarray: ...
+ @classmethod
+ def get_rgba(
+ cls,
+ tex: str,
+ fontsize: float | None = ...,
+ dpi: float | None = ...,
+ rgb: ColorType = ...,
+ ) -> np.ndarray: ...
+ @classmethod
+ def get_text_width_height_descent(
+ cls, tex: str, fontsize, renderer: RendererBase | None = ...
+ ) -> tuple[int, int, int]: ...
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/text.py b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/text.py
new file mode 100644
index 0000000000000000000000000000000000000000..d18fc51b070c58a079f01b550bc9d2072c7e4d80
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/text.py
@@ -0,0 +1,2035 @@
+"""
+Classes for including text in a figure.
+"""
+
+import functools
+import logging
+import math
+from numbers import Real
+import weakref
+
+import numpy as np
+
+import matplotlib as mpl
+from . import _api, artist, cbook, _docstring
+from .artist import Artist
+from .font_manager import FontProperties
+from .patches import FancyArrowPatch, FancyBboxPatch, Rectangle
+from .textpath import TextPath, TextToPath # noqa # Logically located here
+from .transforms import (
+ Affine2D, Bbox, BboxBase, BboxTransformTo, IdentityTransform, Transform)
+
+
+_log = logging.getLogger(__name__)
+
+
+def _get_textbox(text, renderer):
+ """
+ Calculate the bounding box of the text.
+
+ The bbox position takes text rotation into account, but the width and
+ height are those of the unrotated box (unlike `.Text.get_window_extent`).
+ """
+ # TODO : This function may move into the Text class as a method. As a
+ # matter of fact, the information from the _get_textbox function
+ # should be available during the Text._get_layout() call, which is
+ # called within the _get_textbox. So, it would better to move this
+ # function as a method with some refactoring of _get_layout method.
+
+ projected_xs = []
+ projected_ys = []
+
+ theta = np.deg2rad(text.get_rotation())
+ tr = Affine2D().rotate(-theta)
+
+ _, parts, d = text._get_layout(renderer)
+
+ for t, wh, x, y in parts:
+ w, h = wh
+
+ xt1, yt1 = tr.transform((x, y))
+ yt1 -= d
+ xt2, yt2 = xt1 + w, yt1 + h
+
+ projected_xs.extend([xt1, xt2])
+ projected_ys.extend([yt1, yt2])
+
+ xt_box, yt_box = min(projected_xs), min(projected_ys)
+ w_box, h_box = max(projected_xs) - xt_box, max(projected_ys) - yt_box
+
+ x_box, y_box = Affine2D().rotate(theta).transform((xt_box, yt_box))
+
+ return x_box, y_box, w_box, h_box
+
+
+def _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi):
+ """Call ``renderer.get_text_width_height_descent``, caching the results."""
+ # Cached based on a copy of fontprop so that later in-place mutations of
+ # the passed-in argument do not mess up the cache.
+ return _get_text_metrics_with_cache_impl(
+ weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
+
+
+@functools.lru_cache(4096)
+def _get_text_metrics_with_cache_impl(
+ renderer_ref, text, fontprop, ismath, dpi):
+ # dpi is unused, but participates in cache invalidation (via the renderer).
+ return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
+
+
+@_docstring.interpd
+@_api.define_aliases({
+ "color": ["c"],
+ "fontproperties": ["font", "font_properties"],
+ "fontfamily": ["family"],
+ "fontname": ["name"],
+ "fontsize": ["size"],
+ "fontstretch": ["stretch"],
+ "fontstyle": ["style"],
+ "fontvariant": ["variant"],
+ "fontweight": ["weight"],
+ "horizontalalignment": ["ha"],
+ "verticalalignment": ["va"],
+ "multialignment": ["ma"],
+})
+class Text(Artist):
+ """Handle storing and drawing of text in window or data coordinates."""
+
+ zorder = 3
+ _charsize_cache = dict()
+
+ def __repr__(self):
+ return f"Text({self._x}, {self._y}, {self._text!r})"
+
+ def __init__(self,
+ x=0, y=0, text='', *,
+ color=None, # defaults to rc params
+ verticalalignment='baseline',
+ horizontalalignment='left',
+ multialignment=None,
+ fontproperties=None, # defaults to FontProperties()
+ rotation=None,
+ linespacing=None,
+ rotation_mode=None,
+ usetex=None, # defaults to rcParams['text.usetex']
+ wrap=False,
+ transform_rotates_text=False,
+ parse_math=None, # defaults to rcParams['text.parse_math']
+ antialiased=None, # defaults to rcParams['text.antialiased']
+ **kwargs
+ ):
+ """
+ Create a `.Text` instance at *x*, *y* with string *text*.
+
+ The text is aligned relative to the anchor point (*x*, *y*) according
+ to ``horizontalalignment`` (default: 'left') and ``verticalalignment``
+ (default: 'baseline'). See also
+ :doc:`/gallery/text_labels_and_annotations/text_alignment`.
+
+ While Text accepts the 'label' keyword argument, by default it is not
+ added to the handles of a legend.
+
+ Valid keyword arguments are:
+
+ %(Text:kwdoc)s
+ """
+ super().__init__()
+ self._x, self._y = x, y
+ self._text = ''
+ self._reset_visual_defaults(
+ text=text,
+ color=color,
+ fontproperties=fontproperties,
+ usetex=usetex,
+ parse_math=parse_math,
+ wrap=wrap,
+ verticalalignment=verticalalignment,
+ horizontalalignment=horizontalalignment,
+ multialignment=multialignment,
+ rotation=rotation,
+ transform_rotates_text=transform_rotates_text,
+ linespacing=linespacing,
+ rotation_mode=rotation_mode,
+ antialiased=antialiased
+ )
+ self.update(kwargs)
+
+ def _reset_visual_defaults(
+ self,
+ text='',
+ color=None,
+ fontproperties=None,
+ usetex=None,
+ parse_math=None,
+ wrap=False,
+ verticalalignment='baseline',
+ horizontalalignment='left',
+ multialignment=None,
+ rotation=None,
+ transform_rotates_text=False,
+ linespacing=None,
+ rotation_mode=None,
+ antialiased=None
+ ):
+ self.set_text(text)
+ self.set_color(mpl._val_or_rc(color, "text.color"))
+ self.set_fontproperties(fontproperties)
+ self.set_usetex(usetex)
+ self.set_parse_math(mpl._val_or_rc(parse_math, 'text.parse_math'))
+ self.set_wrap(wrap)
+ self.set_verticalalignment(verticalalignment)
+ self.set_horizontalalignment(horizontalalignment)
+ self._multialignment = multialignment
+ self.set_rotation(rotation)
+ self._transform_rotates_text = transform_rotates_text
+ self._bbox_patch = None # a FancyBboxPatch instance
+ self._renderer = None
+ if linespacing is None:
+ linespacing = 1.2 # Maybe use rcParam later.
+ self.set_linespacing(linespacing)
+ self.set_rotation_mode(rotation_mode)
+ self.set_antialiased(antialiased if antialiased is not None else
+ mpl.rcParams['text.antialiased'])
+
+ def update(self, kwargs):
+ # docstring inherited
+ ret = []
+ kwargs = cbook.normalize_kwargs(kwargs, Text)
+ sentinel = object() # bbox can be None, so use another sentinel.
+ # Update fontproperties first, as it has lowest priority.
+ fontproperties = kwargs.pop("fontproperties", sentinel)
+ if fontproperties is not sentinel:
+ ret.append(self.set_fontproperties(fontproperties))
+ # Update bbox last, as it depends on font properties.
+ bbox = kwargs.pop("bbox", sentinel)
+ ret.extend(super().update(kwargs))
+ if bbox is not sentinel:
+ ret.append(self.set_bbox(bbox))
+ return ret
+
+ def __getstate__(self):
+ d = super().__getstate__()
+ # remove the cached _renderer (if it exists)
+ d['_renderer'] = None
+ return d
+
+ def contains(self, mouseevent):
+ """
+ Return whether the mouse event occurred inside the axis-aligned
+ bounding-box of the text.
+ """
+ if (self._different_canvas(mouseevent) or not self.get_visible()
+ or self._renderer is None):
+ return False, {}
+ # Explicitly use Text.get_window_extent(self) and not
+ # self.get_window_extent() so that Annotation.contains does not
+ # accidentally cover the entire annotation bounding box.
+ bbox = Text.get_window_extent(self)
+ inside = (bbox.x0 <= mouseevent.x <= bbox.x1
+ and bbox.y0 <= mouseevent.y <= bbox.y1)
+ cattr = {}
+ # if the text has a surrounding patch, also check containment for it,
+ # and merge the results with the results for the text.
+ if self._bbox_patch:
+ patch_inside, patch_cattr = self._bbox_patch.contains(mouseevent)
+ inside = inside or patch_inside
+ cattr["bbox_patch"] = patch_cattr
+ return inside, cattr
+
+ def _get_xy_display(self):
+ """
+ Get the (possibly unit converted) transformed x, y in display coords.
+ """
+ x, y = self.get_unitless_position()
+ return self.get_transform().transform((x, y))
+
+ def _get_multialignment(self):
+ if self._multialignment is not None:
+ return self._multialignment
+ else:
+ return self._horizontalalignment
+
+ def _char_index_at(self, x):
+ """
+ Calculate the index closest to the coordinate x in display space.
+
+ The position of text[index] is assumed to be the sum of the widths
+ of all preceding characters text[:index].
+
+ This works only on single line texts.
+ """
+ if not self._text:
+ return 0
+
+ text = self._text
+
+ fontproperties = str(self._fontproperties)
+ if fontproperties not in Text._charsize_cache:
+ Text._charsize_cache[fontproperties] = dict()
+
+ charsize_cache = Text._charsize_cache[fontproperties]
+ for char in set(text):
+ if char not in charsize_cache:
+ self.set_text(char)
+ bb = self.get_window_extent()
+ charsize_cache[char] = bb.x1 - bb.x0
+
+ self.set_text(text)
+ bb = self.get_window_extent()
+
+ size_accum = np.cumsum([0] + [charsize_cache[x] for x in text])
+ std_x = x - bb.x0
+ return (np.abs(size_accum - std_x)).argmin()
+
+ def get_rotation(self):
+ """Return the text angle in degrees between 0 and 360."""
+ if self.get_transform_rotates_text():
+ return self.get_transform().transform_angles(
+ [self._rotation], [self.get_unitless_position()]).item(0)
+ else:
+ return self._rotation
+
+ def get_transform_rotates_text(self):
+ """
+ Return whether rotations of the transform affect the text direction.
+ """
+ return self._transform_rotates_text
+
+ def set_rotation_mode(self, m):
+ """
+ Set text rotation mode.
+
+ Parameters
+ ----------
+ m : {None, 'default', 'anchor'}
+ If ``"default"``, the text will be first rotated, then aligned according
+ to their horizontal and vertical alignments. If ``"anchor"``, then
+ alignment occurs before rotation. Passing ``None`` will set the rotation
+ mode to ``"default"``.
+ """
+ if m is None:
+ m = "default"
+ else:
+ _api.check_in_list(("anchor", "default"), rotation_mode=m)
+ self._rotation_mode = m
+ self.stale = True
+
+ def get_rotation_mode(self):
+ """Return the text rotation mode."""
+ return self._rotation_mode
+
+ def set_antialiased(self, antialiased):
+ """
+ Set whether to use antialiased rendering.
+
+ Parameters
+ ----------
+ antialiased : bool
+
+ Notes
+ -----
+ Antialiasing will be determined by :rc:`text.antialiased`
+ and the parameter *antialiased* will have no effect if the text contains
+ math expressions.
+ """
+ self._antialiased = antialiased
+ self.stale = True
+
+ def get_antialiased(self):
+ """Return whether antialiased rendering is used."""
+ return self._antialiased
+
+ def update_from(self, other):
+ # docstring inherited
+ super().update_from(other)
+ self._color = other._color
+ self._multialignment = other._multialignment
+ self._verticalalignment = other._verticalalignment
+ self._horizontalalignment = other._horizontalalignment
+ self._fontproperties = other._fontproperties.copy()
+ self._usetex = other._usetex
+ self._rotation = other._rotation
+ self._transform_rotates_text = other._transform_rotates_text
+ self._picker = other._picker
+ self._linespacing = other._linespacing
+ self._antialiased = other._antialiased
+ self.stale = True
+
+ def _get_layout(self, renderer):
+ """
+ Return the extent (bbox) of the text together with
+ multiple-alignment information. Note that it returns an extent
+ of a rotated text when necessary.
+ """
+ thisx, thisy = 0.0, 0.0
+ lines = self._get_wrapped_text().split("\n") # Ensures lines is not empty.
+
+ ws = []
+ hs = []
+ xs = []
+ ys = []
+
+ # Full vertical extent of font, including ascenders and descenders:
+ _, lp_h, lp_d = _get_text_metrics_with_cache(
+ renderer, "lp", self._fontproperties,
+ ismath="TeX" if self.get_usetex() else False,
+ dpi=self.get_figure(root=True).dpi)
+ min_dy = (lp_h - lp_d) * self._linespacing
+
+ for i, line in enumerate(lines):
+ clean_line, ismath = self._preprocess_math(line)
+ if clean_line:
+ w, h, d = _get_text_metrics_with_cache(
+ renderer, clean_line, self._fontproperties,
+ ismath=ismath, dpi=self.get_figure(root=True).dpi)
+ else:
+ w = h = d = 0
+
+ # For multiline text, increase the line spacing when the text
+ # net-height (excluding baseline) is larger than that of a "l"
+ # (e.g., use of superscripts), which seems what TeX does.
+ h = max(h, lp_h)
+ d = max(d, lp_d)
+
+ ws.append(w)
+ hs.append(h)
+
+ # Metrics of the last line that are needed later:
+ baseline = (h - d) - thisy
+
+ if i == 0:
+ # position at baseline
+ thisy = -(h - d)
+ else:
+ # put baseline a good distance from bottom of previous line
+ thisy -= max(min_dy, (h - d) * self._linespacing)
+
+ xs.append(thisx) # == 0.
+ ys.append(thisy)
+
+ thisy -= d
+
+ # Metrics of the last line that are needed later:
+ descent = d
+
+ # Bounding box definition:
+ width = max(ws)
+ xmin = 0
+ xmax = width
+ ymax = 0
+ ymin = ys[-1] - descent # baseline of last line minus its descent
+
+ # get the rotation matrix
+ M = Affine2D().rotate_deg(self.get_rotation())
+
+ # now offset the individual text lines within the box
+ malign = self._get_multialignment()
+ if malign == 'left':
+ offset_layout = [(x, y) for x, y in zip(xs, ys)]
+ elif malign == 'center':
+ offset_layout = [(x + width / 2 - w / 2, y)
+ for x, y, w in zip(xs, ys, ws)]
+ elif malign == 'right':
+ offset_layout = [(x + width - w, y)
+ for x, y, w in zip(xs, ys, ws)]
+
+ # the corners of the unrotated bounding box
+ corners_horiz = np.array(
+ [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)])
+
+ # now rotate the bbox
+ corners_rotated = M.transform(corners_horiz)
+ # compute the bounds of the rotated box
+ xmin = corners_rotated[:, 0].min()
+ xmax = corners_rotated[:, 0].max()
+ ymin = corners_rotated[:, 1].min()
+ ymax = corners_rotated[:, 1].max()
+ width = xmax - xmin
+ height = ymax - ymin
+
+ # Now move the box to the target position offset the display
+ # bbox by alignment
+ halign = self._horizontalalignment
+ valign = self._verticalalignment
+
+ rotation_mode = self.get_rotation_mode()
+ if rotation_mode != "anchor":
+ # compute the text location in display coords and the offsets
+ # necessary to align the bbox with that location
+ if halign == 'center':
+ offsetx = (xmin + xmax) / 2
+ elif halign == 'right':
+ offsetx = xmax
+ else:
+ offsetx = xmin
+
+ if valign == 'center':
+ offsety = (ymin + ymax) / 2
+ elif valign == 'top':
+ offsety = ymax
+ elif valign == 'baseline':
+ offsety = ymin + descent
+ elif valign == 'center_baseline':
+ offsety = ymin + height - baseline / 2.0
+ else:
+ offsety = ymin
+ else:
+ xmin1, ymin1 = corners_horiz[0]
+ xmax1, ymax1 = corners_horiz[2]
+
+ if halign == 'center':
+ offsetx = (xmin1 + xmax1) / 2.0
+ elif halign == 'right':
+ offsetx = xmax1
+ else:
+ offsetx = xmin1
+
+ if valign == 'center':
+ offsety = (ymin1 + ymax1) / 2.0
+ elif valign == 'top':
+ offsety = ymax1
+ elif valign == 'baseline':
+ offsety = ymax1 - baseline
+ elif valign == 'center_baseline':
+ offsety = ymax1 - baseline / 2.0
+ else:
+ offsety = ymin1
+
+ offsetx, offsety = M.transform((offsetx, offsety))
+
+ xmin -= offsetx
+ ymin -= offsety
+
+ bbox = Bbox.from_bounds(xmin, ymin, width, height)
+
+ # now rotate the positions around the first (x, y) position
+ xys = M.transform(offset_layout) - (offsetx, offsety)
+
+ return bbox, list(zip(lines, zip(ws, hs), *xys.T)), descent
+
+ def set_bbox(self, rectprops):
+ """
+ Draw a bounding box around self.
+
+ Parameters
+ ----------
+ rectprops : dict with properties for `.patches.FancyBboxPatch`
+ The default boxstyle is 'square'. The mutation
+ scale of the `.patches.FancyBboxPatch` is set to the fontsize.
+
+ Examples
+ --------
+ ::
+
+ t.set_bbox(dict(facecolor='red', alpha=0.5))
+ """
+
+ if rectprops is not None:
+ props = rectprops.copy()
+ boxstyle = props.pop("boxstyle", None)
+ pad = props.pop("pad", None)
+ if boxstyle is None:
+ boxstyle = "square"
+ if pad is None:
+ pad = 4 # points
+ pad /= self.get_size() # to fraction of font size
+ else:
+ if pad is None:
+ pad = 0.3
+ # boxstyle could be a callable or a string
+ if isinstance(boxstyle, str) and "pad" not in boxstyle:
+ boxstyle += ",pad=%0.2f" % pad
+ self._bbox_patch = FancyBboxPatch(
+ (0, 0), 1, 1,
+ boxstyle=boxstyle, transform=IdentityTransform(), **props)
+ else:
+ self._bbox_patch = None
+
+ self._update_clip_properties()
+
+ def get_bbox_patch(self):
+ """
+ Return the bbox Patch, or None if the `.patches.FancyBboxPatch`
+ is not made.
+ """
+ return self._bbox_patch
+
+ def update_bbox_position_size(self, renderer):
+ """
+ Update the location and the size of the bbox.
+
+ This method should be used when the position and size of the bbox needs
+ to be updated before actually drawing the bbox.
+ """
+ if self._bbox_patch:
+ # don't use self.get_unitless_position here, which refers to text
+ # position in Text:
+ posx = float(self.convert_xunits(self._x))
+ posy = float(self.convert_yunits(self._y))
+ posx, posy = self.get_transform().transform((posx, posy))
+
+ x_box, y_box, w_box, h_box = _get_textbox(self, renderer)
+ self._bbox_patch.set_bounds(0., 0., w_box, h_box)
+ self._bbox_patch.set_transform(
+ Affine2D()
+ .rotate_deg(self.get_rotation())
+ .translate(posx + x_box, posy + y_box))
+ fontsize_in_pixel = renderer.points_to_pixels(self.get_size())
+ self._bbox_patch.set_mutation_scale(fontsize_in_pixel)
+
+ def _update_clip_properties(self):
+ if self._bbox_patch:
+ clipprops = dict(clip_box=self.clipbox,
+ clip_path=self._clippath,
+ clip_on=self._clipon)
+ self._bbox_patch.update(clipprops)
+
+ def set_clip_box(self, clipbox):
+ # docstring inherited.
+ super().set_clip_box(clipbox)
+ self._update_clip_properties()
+
+ def set_clip_path(self, path, transform=None):
+ # docstring inherited.
+ super().set_clip_path(path, transform)
+ self._update_clip_properties()
+
+ def set_clip_on(self, b):
+ # docstring inherited.
+ super().set_clip_on(b)
+ self._update_clip_properties()
+
+ def get_wrap(self):
+ """Return whether the text can be wrapped."""
+ return self._wrap
+
+ def set_wrap(self, wrap):
+ """
+ Set whether the text can be wrapped.
+
+ Wrapping makes sure the text is confined to the (sub)figure box. It
+ does not take into account any other artists.
+
+ Parameters
+ ----------
+ wrap : bool
+
+ Notes
+ -----
+ Wrapping does not work together with
+ ``savefig(..., bbox_inches='tight')`` (which is also used internally
+ by ``%matplotlib inline`` in IPython/Jupyter). The 'tight' setting
+ rescales the canvas to accommodate all content and happens before
+ wrapping.
+ """
+ self._wrap = wrap
+
+ def _get_wrap_line_width(self):
+ """
+ Return the maximum line width for wrapping text based on the current
+ orientation.
+ """
+ x0, y0 = self.get_transform().transform(self.get_position())
+ figure_box = self.get_figure().get_window_extent()
+
+ # Calculate available width based on text alignment
+ alignment = self.get_horizontalalignment()
+ self.set_rotation_mode('anchor')
+ rotation = self.get_rotation()
+
+ left = self._get_dist_to_box(rotation, x0, y0, figure_box)
+ right = self._get_dist_to_box(
+ (180 + rotation) % 360, x0, y0, figure_box)
+
+ if alignment == 'left':
+ line_width = left
+ elif alignment == 'right':
+ line_width = right
+ else:
+ line_width = 2 * min(left, right)
+
+ return line_width
+
+ def _get_dist_to_box(self, rotation, x0, y0, figure_box):
+ """
+ Return the distance from the given points to the boundaries of a
+ rotated box, in pixels.
+ """
+ if rotation > 270:
+ quad = rotation - 270
+ h1 = (y0 - figure_box.y0) / math.cos(math.radians(quad))
+ h2 = (figure_box.x1 - x0) / math.cos(math.radians(90 - quad))
+ elif rotation > 180:
+ quad = rotation - 180
+ h1 = (x0 - figure_box.x0) / math.cos(math.radians(quad))
+ h2 = (y0 - figure_box.y0) / math.cos(math.radians(90 - quad))
+ elif rotation > 90:
+ quad = rotation - 90
+ h1 = (figure_box.y1 - y0) / math.cos(math.radians(quad))
+ h2 = (x0 - figure_box.x0) / math.cos(math.radians(90 - quad))
+ else:
+ h1 = (figure_box.x1 - x0) / math.cos(math.radians(rotation))
+ h2 = (figure_box.y1 - y0) / math.cos(math.radians(90 - rotation))
+
+ return min(h1, h2)
+
+ def _get_rendered_text_width(self, text):
+ """
+ Return the width of a given text string, in pixels.
+ """
+
+ w, h, d = _get_text_metrics_with_cache(
+ self._renderer, text, self.get_fontproperties(),
+ cbook.is_math_text(text),
+ self.get_figure(root=True).dpi)
+ return math.ceil(w)
+
+ def _get_wrapped_text(self):
+ """
+ Return a copy of the text string with new lines added so that the text
+ is wrapped relative to the parent figure (if `get_wrap` is True).
+ """
+ if not self.get_wrap():
+ return self.get_text()
+
+ # Not fit to handle breaking up latex syntax correctly, so
+ # ignore latex for now.
+ if self.get_usetex():
+ return self.get_text()
+
+ # Build the line incrementally, for a more accurate measure of length
+ line_width = self._get_wrap_line_width()
+ wrapped_lines = []
+
+ # New lines in the user's text force a split
+ unwrapped_lines = self.get_text().split('\n')
+
+ # Now wrap each individual unwrapped line
+ for unwrapped_line in unwrapped_lines:
+
+ sub_words = unwrapped_line.split(' ')
+ # Remove items from sub_words as we go, so stop when empty
+ while len(sub_words) > 0:
+ if len(sub_words) == 1:
+ # Only one word, so just add it to the end
+ wrapped_lines.append(sub_words.pop(0))
+ continue
+
+ for i in range(2, len(sub_words) + 1):
+ # Get width of all words up to and including here
+ line = ' '.join(sub_words[:i])
+ current_width = self._get_rendered_text_width(line)
+
+ # If all these words are too wide, append all not including
+ # last word
+ if current_width > line_width:
+ wrapped_lines.append(' '.join(sub_words[:i - 1]))
+ sub_words = sub_words[i - 1:]
+ break
+
+ # Otherwise if all words fit in the width, append them all
+ elif i == len(sub_words):
+ wrapped_lines.append(' '.join(sub_words[:i]))
+ sub_words = []
+ break
+
+ return '\n'.join(wrapped_lines)
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ # docstring inherited
+
+ if renderer is not None:
+ self._renderer = renderer
+ if not self.get_visible():
+ return
+ if self.get_text() == '':
+ return
+
+ renderer.open_group('text', self.get_gid())
+
+ with self._cm_set(text=self._get_wrapped_text()):
+ bbox, info, descent = self._get_layout(renderer)
+ trans = self.get_transform()
+
+ # don't use self.get_position here, which refers to text
+ # position in Text:
+ x, y = self._x, self._y
+ if np.ma.is_masked(x):
+ x = np.nan
+ if np.ma.is_masked(y):
+ y = np.nan
+ posx = float(self.convert_xunits(x))
+ posy = float(self.convert_yunits(y))
+ posx, posy = trans.transform((posx, posy))
+ if np.isnan(posx) or np.isnan(posy):
+ return # don't throw a warning here
+ if not np.isfinite(posx) or not np.isfinite(posy):
+ _log.warning("posx and posy should be finite values")
+ return
+ canvasw, canvash = renderer.get_canvas_width_height()
+
+ # Update the location and size of the bbox
+ # (`.patches.FancyBboxPatch`), and draw it.
+ if self._bbox_patch:
+ self.update_bbox_position_size(renderer)
+ self._bbox_patch.draw(renderer)
+
+ gc = renderer.new_gc()
+ gc.set_foreground(self.get_color())
+ gc.set_alpha(self.get_alpha())
+ gc.set_url(self._url)
+ gc.set_antialiased(self._antialiased)
+ self._set_gc_clip(gc)
+
+ angle = self.get_rotation()
+
+ for line, wh, x, y in info:
+
+ mtext = self if len(info) == 1 else None
+ x = x + posx
+ y = y + posy
+ if renderer.flipy():
+ y = canvash - y
+ clean_line, ismath = self._preprocess_math(line)
+
+ if self.get_path_effects():
+ from matplotlib.patheffects import PathEffectRenderer
+ textrenderer = PathEffectRenderer(
+ self.get_path_effects(), renderer)
+ else:
+ textrenderer = renderer
+
+ if self.get_usetex():
+ textrenderer.draw_tex(gc, x, y, clean_line,
+ self._fontproperties, angle,
+ mtext=mtext)
+ else:
+ textrenderer.draw_text(gc, x, y, clean_line,
+ self._fontproperties, angle,
+ ismath=ismath, mtext=mtext)
+
+ gc.restore()
+ renderer.close_group('text')
+ self.stale = False
+
+ def get_color(self):
+ """Return the color of the text."""
+ return self._color
+
+ def get_fontproperties(self):
+ """Return the `.font_manager.FontProperties`."""
+ return self._fontproperties
+
+ def get_fontfamily(self):
+ """
+ Return the list of font families used for font lookup.
+
+ See Also
+ --------
+ .font_manager.FontProperties.get_family
+ """
+ return self._fontproperties.get_family()
+
+ def get_fontname(self):
+ """
+ Return the font name as a string.
+
+ See Also
+ --------
+ .font_manager.FontProperties.get_name
+ """
+ return self._fontproperties.get_name()
+
+ def get_fontstyle(self):
+ """
+ Return the font style as a string.
+
+ See Also
+ --------
+ .font_manager.FontProperties.get_style
+ """
+ return self._fontproperties.get_style()
+
+ def get_fontsize(self):
+ """
+ Return the font size as an integer.
+
+ See Also
+ --------
+ .font_manager.FontProperties.get_size_in_points
+ """
+ return self._fontproperties.get_size_in_points()
+
+ def get_fontvariant(self):
+ """
+ Return the font variant as a string.
+
+ See Also
+ --------
+ .font_manager.FontProperties.get_variant
+ """
+ return self._fontproperties.get_variant()
+
+ def get_fontweight(self):
+ """
+ Return the font weight as a string or a number.
+
+ See Also
+ --------
+ .font_manager.FontProperties.get_weight
+ """
+ return self._fontproperties.get_weight()
+
+ def get_stretch(self):
+ """
+ Return the font stretch as a string or a number.
+
+ See Also
+ --------
+ .font_manager.FontProperties.get_stretch
+ """
+ return self._fontproperties.get_stretch()
+
+ def get_horizontalalignment(self):
+ """
+ Return the horizontal alignment as a string. Will be one of
+ 'left', 'center' or 'right'.
+ """
+ return self._horizontalalignment
+
+ def get_unitless_position(self):
+ """Return the (x, y) unitless position of the text."""
+ # This will get the position with all unit information stripped away.
+ # This is here for convenience since it is done in several locations.
+ x = float(self.convert_xunits(self._x))
+ y = float(self.convert_yunits(self._y))
+ return x, y
+
+ def get_position(self):
+ """Return the (x, y) position of the text."""
+ # This should return the same data (possible unitized) as was
+ # specified with 'set_x' and 'set_y'.
+ return self._x, self._y
+
+ def get_text(self):
+ """Return the text string."""
+ return self._text
+
+ def get_verticalalignment(self):
+ """
+ Return the vertical alignment as a string. Will be one of
+ 'top', 'center', 'bottom', 'baseline' or 'center_baseline'.
+ """
+ return self._verticalalignment
+
+ def get_window_extent(self, renderer=None, dpi=None):
+ """
+ Return the `.Bbox` bounding the text, in display units.
+
+ In addition to being used internally, this is useful for specifying
+ clickable regions in a png file on a web page.
+
+ Parameters
+ ----------
+ renderer : Renderer, optional
+ A renderer is needed to compute the bounding box. If the artist
+ has already been drawn, the renderer is cached; thus, it is only
+ necessary to pass this argument when calling `get_window_extent`
+ before the first draw. In practice, it is usually easier to
+ trigger a draw first, e.g. by calling
+ `~.Figure.draw_without_rendering` or ``plt.show()``.
+
+ dpi : float, optional
+ The dpi value for computing the bbox, defaults to
+ ``self.get_figure(root=True).dpi`` (*not* the renderer dpi); should be set
+ e.g. if to match regions with a figure saved with a custom dpi value.
+ """
+ if not self.get_visible():
+ return Bbox.unit()
+
+ fig = self.get_figure(root=True)
+ if dpi is None:
+ dpi = fig.dpi
+ if self.get_text() == '':
+ with cbook._setattr_cm(fig, dpi=dpi):
+ tx, ty = self._get_xy_display()
+ return Bbox.from_bounds(tx, ty, 0, 0)
+
+ if renderer is not None:
+ self._renderer = renderer
+ if self._renderer is None:
+ self._renderer = fig._get_renderer()
+ if self._renderer is None:
+ raise RuntimeError(
+ "Cannot get window extent of text w/o renderer. You likely "
+ "want to call 'figure.draw_without_rendering()' first.")
+
+ with cbook._setattr_cm(fig, dpi=dpi):
+ bbox, info, descent = self._get_layout(self._renderer)
+ x, y = self.get_unitless_position()
+ x, y = self.get_transform().transform((x, y))
+ bbox = bbox.translated(x, y)
+ return bbox
+
+ def set_backgroundcolor(self, color):
+ """
+ Set the background color of the text by updating the bbox.
+
+ Parameters
+ ----------
+ color : :mpltype:`color`
+
+ See Also
+ --------
+ .set_bbox : To change the position of the bounding box
+ """
+ if self._bbox_patch is None:
+ self.set_bbox(dict(facecolor=color, edgecolor=color))
+ else:
+ self._bbox_patch.update(dict(facecolor=color))
+
+ self._update_clip_properties()
+ self.stale = True
+
+ def set_color(self, color):
+ """
+ Set the foreground color of the text
+
+ Parameters
+ ----------
+ color : :mpltype:`color`
+ """
+ # "auto" is only supported by axisartist, but we can just let it error
+ # out at draw time for simplicity.
+ if not cbook._str_equal(color, "auto"):
+ mpl.colors._check_color_like(color=color)
+ self._color = color
+ self.stale = True
+
+ def set_horizontalalignment(self, align):
+ """
+ Set the horizontal alignment relative to the anchor point.
+
+ See also :doc:`/gallery/text_labels_and_annotations/text_alignment`.
+
+ Parameters
+ ----------
+ align : {'left', 'center', 'right'}
+ """
+ _api.check_in_list(['center', 'right', 'left'], align=align)
+ self._horizontalalignment = align
+ self.stale = True
+
+ def set_multialignment(self, align):
+ """
+ Set the text alignment for multiline texts.
+
+ The layout of the bounding box of all the lines is determined by the
+ horizontalalignment and verticalalignment properties. This property
+ controls the alignment of the text lines within that box.
+
+ Parameters
+ ----------
+ align : {'left', 'right', 'center'}
+ """
+ _api.check_in_list(['center', 'right', 'left'], align=align)
+ self._multialignment = align
+ self.stale = True
+
+ def set_linespacing(self, spacing):
+ """
+ Set the line spacing as a multiple of the font size.
+
+ The default line spacing is 1.2.
+
+ Parameters
+ ----------
+ spacing : float (multiple of font size)
+ """
+ _api.check_isinstance(Real, spacing=spacing)
+ self._linespacing = spacing
+ self.stale = True
+
+ def set_fontfamily(self, fontname):
+ """
+ Set the font family. Can be either a single string, or a list of
+ strings in decreasing priority. Each string may be either a real font
+ name or a generic font class name. If the latter, the specific font
+ names will be looked up in the corresponding rcParams.
+
+ If a `Text` instance is constructed with ``fontfamily=None``, then the
+ font is set to :rc:`font.family`, and the
+ same is done when `set_fontfamily()` is called on an existing
+ `Text` instance.
+
+ Parameters
+ ----------
+ fontname : {FONTNAME, 'serif', 'sans-serif', 'cursive', 'fantasy', \
+'monospace'}
+
+ See Also
+ --------
+ .font_manager.FontProperties.set_family
+ """
+ self._fontproperties.set_family(fontname)
+ self.stale = True
+
+ def set_fontvariant(self, variant):
+ """
+ Set the font variant.
+
+ Parameters
+ ----------
+ variant : {'normal', 'small-caps'}
+
+ See Also
+ --------
+ .font_manager.FontProperties.set_variant
+ """
+ self._fontproperties.set_variant(variant)
+ self.stale = True
+
+ def set_fontstyle(self, fontstyle):
+ """
+ Set the font style.
+
+ Parameters
+ ----------
+ fontstyle : {'normal', 'italic', 'oblique'}
+
+ See Also
+ --------
+ .font_manager.FontProperties.set_style
+ """
+ self._fontproperties.set_style(fontstyle)
+ self.stale = True
+
+ def set_fontsize(self, fontsize):
+ """
+ Set the font size.
+
+ Parameters
+ ----------
+ fontsize : float or {'xx-small', 'x-small', 'small', 'medium', \
+'large', 'x-large', 'xx-large'}
+ If a float, the fontsize in points. The string values denote sizes
+ relative to the default font size.
+
+ See Also
+ --------
+ .font_manager.FontProperties.set_size
+ """
+ self._fontproperties.set_size(fontsize)
+ self.stale = True
+
+ def get_math_fontfamily(self):
+ """
+ Return the font family name for math text rendered by Matplotlib.
+
+ The default value is :rc:`mathtext.fontset`.
+
+ See Also
+ --------
+ set_math_fontfamily
+ """
+ return self._fontproperties.get_math_fontfamily()
+
+ def set_math_fontfamily(self, fontfamily):
+ """
+ Set the font family for math text rendered by Matplotlib.
+
+ This does only affect Matplotlib's own math renderer. It has no effect
+ when rendering with TeX (``usetex=True``).
+
+ Parameters
+ ----------
+ fontfamily : str
+ The name of the font family.
+
+ Available font families are defined in the
+ :ref:`default matplotlibrc file
+ `.
+
+ See Also
+ --------
+ get_math_fontfamily
+ """
+ self._fontproperties.set_math_fontfamily(fontfamily)
+
+ def set_fontweight(self, weight):
+ """
+ Set the font weight.
+
+ Parameters
+ ----------
+ weight : {a numeric value in range 0-1000, 'ultralight', 'light', \
+'normal', 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', \
+'demi', 'bold', 'heavy', 'extra bold', 'black'}
+
+ See Also
+ --------
+ .font_manager.FontProperties.set_weight
+ """
+ self._fontproperties.set_weight(weight)
+ self.stale = True
+
+ def set_fontstretch(self, stretch):
+ """
+ Set the font stretch (horizontal condensation or expansion).
+
+ Parameters
+ ----------
+ stretch : {a numeric value in range 0-1000, 'ultra-condensed', \
+'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', \
+'expanded', 'extra-expanded', 'ultra-expanded'}
+
+ See Also
+ --------
+ .font_manager.FontProperties.set_stretch
+ """
+ self._fontproperties.set_stretch(stretch)
+ self.stale = True
+
+ def set_position(self, xy):
+ """
+ Set the (*x*, *y*) position of the text.
+
+ Parameters
+ ----------
+ xy : (float, float)
+ """
+ self.set_x(xy[0])
+ self.set_y(xy[1])
+
+ def set_x(self, x):
+ """
+ Set the *x* position of the text.
+
+ Parameters
+ ----------
+ x : float
+ """
+ self._x = x
+ self.stale = True
+
+ def set_y(self, y):
+ """
+ Set the *y* position of the text.
+
+ Parameters
+ ----------
+ y : float
+ """
+ self._y = y
+ self.stale = True
+
+ def set_rotation(self, s):
+ """
+ Set the rotation of the text.
+
+ Parameters
+ ----------
+ s : float or {'vertical', 'horizontal'}
+ The rotation angle in degrees in mathematically positive direction
+ (counterclockwise). 'horizontal' equals 0, 'vertical' equals 90.
+ """
+ if isinstance(s, Real):
+ self._rotation = float(s) % 360
+ elif cbook._str_equal(s, 'horizontal') or s is None:
+ self._rotation = 0.
+ elif cbook._str_equal(s, 'vertical'):
+ self._rotation = 90.
+ else:
+ raise ValueError("rotation must be 'vertical', 'horizontal' or "
+ f"a number, not {s}")
+ self.stale = True
+
+ def set_transform_rotates_text(self, t):
+ """
+ Whether rotations of the transform affect the text direction.
+
+ Parameters
+ ----------
+ t : bool
+ """
+ self._transform_rotates_text = t
+ self.stale = True
+
+ def set_verticalalignment(self, align):
+ """
+ Set the vertical alignment relative to the anchor point.
+
+ See also :doc:`/gallery/text_labels_and_annotations/text_alignment`.
+
+ Parameters
+ ----------
+ align : {'baseline', 'bottom', 'center', 'center_baseline', 'top'}
+ """
+ _api.check_in_list(
+ ['top', 'bottom', 'center', 'baseline', 'center_baseline'],
+ align=align)
+ self._verticalalignment = align
+ self.stale = True
+
+ def set_text(self, s):
+ r"""
+ Set the text string *s*.
+
+ It may contain newlines (``\n``) or math in LaTeX syntax.
+
+ Parameters
+ ----------
+ s : object
+ Any object gets converted to its `str` representation, except for
+ ``None`` which is converted to an empty string.
+ """
+ s = '' if s is None else str(s)
+ if s != self._text:
+ self._text = s
+ self.stale = True
+
+ def _preprocess_math(self, s):
+ """
+ Return the string *s* after mathtext preprocessing, and the kind of
+ mathtext support needed.
+
+ - If *self* is configured to use TeX, return *s* unchanged except that
+ a single space gets escaped, and the flag "TeX".
+ - Otherwise, if *s* is mathtext (has an even number of unescaped dollar
+ signs) and ``parse_math`` is not set to False, return *s* and the
+ flag True.
+ - Otherwise, return *s* with dollar signs unescaped, and the flag
+ False.
+ """
+ if self.get_usetex():
+ if s == " ":
+ s = r"\ "
+ return s, "TeX"
+ elif not self.get_parse_math():
+ return s, False
+ elif cbook.is_math_text(s):
+ return s, True
+ else:
+ return s.replace(r"\$", "$"), False
+
+ def set_fontproperties(self, fp):
+ """
+ Set the font properties that control the text.
+
+ Parameters
+ ----------
+ fp : `.font_manager.FontProperties` or `str` or `pathlib.Path`
+ If a `str`, it is interpreted as a fontconfig pattern parsed by
+ `.FontProperties`. If a `pathlib.Path`, it is interpreted as the
+ absolute path to a font file.
+ """
+ self._fontproperties = FontProperties._from_any(fp).copy()
+ self.stale = True
+
+ @_docstring.kwarg_doc("bool, default: :rc:`text.usetex`")
+ def set_usetex(self, usetex):
+ """
+ Parameters
+ ----------
+ usetex : bool or None
+ Whether to render using TeX, ``None`` means to use
+ :rc:`text.usetex`.
+ """
+ if usetex is None:
+ self._usetex = mpl.rcParams['text.usetex']
+ else:
+ self._usetex = bool(usetex)
+ self.stale = True
+
+ def get_usetex(self):
+ """Return whether this `Text` object uses TeX for rendering."""
+ return self._usetex
+
+ def set_parse_math(self, parse_math):
+ """
+ Override switch to disable any mathtext parsing for this `Text`.
+
+ Parameters
+ ----------
+ parse_math : bool
+ If False, this `Text` will never use mathtext. If True, mathtext
+ will be used if there is an even number of unescaped dollar signs.
+ """
+ self._parse_math = bool(parse_math)
+
+ def get_parse_math(self):
+ """Return whether mathtext parsing is considered for this `Text`."""
+ return self._parse_math
+
+ def set_fontname(self, fontname):
+ """
+ Alias for `set_fontfamily`.
+
+ One-way alias only: the getter differs.
+
+ Parameters
+ ----------
+ fontname : {FONTNAME, 'serif', 'sans-serif', 'cursive', 'fantasy', \
+'monospace'}
+
+ See Also
+ --------
+ .font_manager.FontProperties.set_family
+
+ """
+ self.set_fontfamily(fontname)
+
+
+class OffsetFrom:
+ """Callable helper class for working with `Annotation`."""
+
+ def __init__(self, artist, ref_coord, unit="points"):
+ """
+ Parameters
+ ----------
+ artist : `~matplotlib.artist.Artist` or `.BboxBase` or `.Transform`
+ The object to compute the offset from.
+
+ ref_coord : (float, float)
+ If *artist* is an `.Artist` or `.BboxBase`, this values is
+ the location to of the offset origin in fractions of the
+ *artist* bounding box.
+
+ If *artist* is a transform, the offset origin is the
+ transform applied to this value.
+
+ unit : {'points, 'pixels'}, default: 'points'
+ The screen units to use (pixels or points) for the offset input.
+ """
+ self._artist = artist
+ x, y = ref_coord # Make copy when ref_coord is an array (and check the shape).
+ self._ref_coord = x, y
+ self.set_unit(unit)
+
+ def set_unit(self, unit):
+ """
+ Set the unit for input to the transform used by ``__call__``.
+
+ Parameters
+ ----------
+ unit : {'points', 'pixels'}
+ """
+ _api.check_in_list(["points", "pixels"], unit=unit)
+ self._unit = unit
+
+ def get_unit(self):
+ """Return the unit for input to the transform used by ``__call__``."""
+ return self._unit
+
+ def __call__(self, renderer):
+ """
+ Return the offset transform.
+
+ Parameters
+ ----------
+ renderer : `RendererBase`
+ The renderer to use to compute the offset
+
+ Returns
+ -------
+ `Transform`
+ Maps (x, y) in pixel or point units to screen units
+ relative to the given artist.
+ """
+ if isinstance(self._artist, Artist):
+ bbox = self._artist.get_window_extent(renderer)
+ xf, yf = self._ref_coord
+ x = bbox.x0 + bbox.width * xf
+ y = bbox.y0 + bbox.height * yf
+ elif isinstance(self._artist, BboxBase):
+ bbox = self._artist
+ xf, yf = self._ref_coord
+ x = bbox.x0 + bbox.width * xf
+ y = bbox.y0 + bbox.height * yf
+ elif isinstance(self._artist, Transform):
+ x, y = self._artist.transform(self._ref_coord)
+ else:
+ _api.check_isinstance((Artist, BboxBase, Transform), artist=self._artist)
+ scale = 1 if self._unit == "pixels" else renderer.points_to_pixels(1)
+ return Affine2D().scale(scale).translate(x, y)
+
+
+class _AnnotationBase:
+ def __init__(self,
+ xy,
+ xycoords='data',
+ annotation_clip=None):
+
+ x, y = xy # Make copy when xy is an array (and check the shape).
+ self.xy = x, y
+ self.xycoords = xycoords
+ self.set_annotation_clip(annotation_clip)
+
+ self._draggable = None
+
+ def _get_xy(self, renderer, xy, coords):
+ x, y = xy
+ xcoord, ycoord = coords if isinstance(coords, tuple) else (coords, coords)
+ if xcoord == 'data':
+ x = float(self.convert_xunits(x))
+ if ycoord == 'data':
+ y = float(self.convert_yunits(y))
+ return self._get_xy_transform(renderer, coords).transform((x, y))
+
+ def _get_xy_transform(self, renderer, coords):
+
+ if isinstance(coords, tuple):
+ xcoord, ycoord = coords
+ from matplotlib.transforms import blended_transform_factory
+ tr1 = self._get_xy_transform(renderer, xcoord)
+ tr2 = self._get_xy_transform(renderer, ycoord)
+ return blended_transform_factory(tr1, tr2)
+ elif callable(coords):
+ tr = coords(renderer)
+ if isinstance(tr, BboxBase):
+ return BboxTransformTo(tr)
+ elif isinstance(tr, Transform):
+ return tr
+ else:
+ raise TypeError(
+ f"xycoords callable must return a BboxBase or Transform, not a "
+ f"{type(tr).__name__}")
+ elif isinstance(coords, Artist):
+ bbox = coords.get_window_extent(renderer)
+ return BboxTransformTo(bbox)
+ elif isinstance(coords, BboxBase):
+ return BboxTransformTo(coords)
+ elif isinstance(coords, Transform):
+ return coords
+ elif not isinstance(coords, str):
+ raise TypeError(
+ f"'xycoords' must be an instance of str, tuple[str, str], Artist, "
+ f"Transform, or Callable, not a {type(coords).__name__}")
+
+ if coords == 'data':
+ return self.axes.transData
+ elif coords == 'polar':
+ from matplotlib.projections import PolarAxes
+ tr = PolarAxes.PolarTransform(apply_theta_transforms=False)
+ trans = tr + self.axes.transData
+ return trans
+
+ try:
+ bbox_name, unit = coords.split()
+ except ValueError: # i.e. len(coords.split()) != 2.
+ raise ValueError(f"{coords!r} is not a valid coordinate") from None
+
+ bbox0, xy0 = None, None
+
+ # if unit is offset-like
+ if bbox_name == "figure":
+ bbox0 = self.get_figure(root=False).figbbox
+ elif bbox_name == "subfigure":
+ bbox0 = self.get_figure(root=False).bbox
+ elif bbox_name == "axes":
+ bbox0 = self.axes.bbox
+
+ # reference x, y in display coordinate
+ if bbox0 is not None:
+ xy0 = bbox0.p0
+ elif bbox_name == "offset":
+ xy0 = self._get_position_xy(renderer)
+ else:
+ raise ValueError(f"{coords!r} is not a valid coordinate")
+
+ if unit == "points":
+ tr = Affine2D().scale(
+ self.get_figure(root=True).dpi / 72) # dpi/72 dots per point
+ elif unit == "pixels":
+ tr = Affine2D()
+ elif unit == "fontsize":
+ tr = Affine2D().scale(
+ self.get_size() * self.get_figure(root=True).dpi / 72)
+ elif unit == "fraction":
+ tr = Affine2D().scale(*bbox0.size)
+ else:
+ raise ValueError(f"{unit!r} is not a recognized unit")
+
+ return tr.translate(*xy0)
+
+ def set_annotation_clip(self, b):
+ """
+ Set the annotation's clipping behavior.
+
+ Parameters
+ ----------
+ b : bool or None
+ - True: The annotation will be clipped when ``self.xy`` is
+ outside the Axes.
+ - False: The annotation will always be drawn.
+ - None: The annotation will be clipped when ``self.xy`` is
+ outside the Axes and ``self.xycoords == "data"``.
+ """
+ self._annotation_clip = b
+
+ def get_annotation_clip(self):
+ """
+ Return the annotation's clipping behavior.
+
+ See `set_annotation_clip` for the meaning of return values.
+ """
+ return self._annotation_clip
+
+ def _get_position_xy(self, renderer):
+ """Return the pixel position of the annotated point."""
+ return self._get_xy(renderer, self.xy, self.xycoords)
+
+ def _check_xy(self, renderer=None):
+ """Check whether the annotation at *xy_pixel* should be drawn."""
+ if renderer is None:
+ renderer = self.get_figure(root=True)._get_renderer()
+ b = self.get_annotation_clip()
+ if b or (b is None and self.xycoords == "data"):
+ # check if self.xy is inside the Axes.
+ xy_pixel = self._get_position_xy(renderer)
+ return self.axes.contains_point(xy_pixel)
+ return True
+
+ def draggable(self, state=None, use_blit=False):
+ """
+ Set whether the annotation is draggable with the mouse.
+
+ Parameters
+ ----------
+ state : bool or None
+ - True or False: set the draggability.
+ - None: toggle the draggability.
+ use_blit : bool, default: False
+ Use blitting for faster image composition. For details see
+ :ref:`func-animation`.
+
+ Returns
+ -------
+ DraggableAnnotation or None
+ If the annotation is draggable, the corresponding
+ `.DraggableAnnotation` helper is returned.
+ """
+ from matplotlib.offsetbox import DraggableAnnotation
+ is_draggable = self._draggable is not None
+
+ # if state is None we'll toggle
+ if state is None:
+ state = not is_draggable
+
+ if state:
+ if self._draggable is None:
+ self._draggable = DraggableAnnotation(self, use_blit)
+ else:
+ if self._draggable is not None:
+ self._draggable.disconnect()
+ self._draggable = None
+
+ return self._draggable
+
+
+class Annotation(Text, _AnnotationBase):
+ """
+ An `.Annotation` is a `.Text` that can refer to a specific position *xy*.
+ Optionally an arrow pointing from the text to *xy* can be drawn.
+
+ Attributes
+ ----------
+ xy
+ The annotated position.
+ xycoords
+ The coordinate system for *xy*.
+ arrow_patch
+ A `.FancyArrowPatch` to point from *xytext* to *xy*.
+ """
+
+ def __str__(self):
+ return f"Annotation({self.xy[0]:g}, {self.xy[1]:g}, {self._text!r})"
+
+ def __init__(self, text, xy,
+ xytext=None,
+ xycoords='data',
+ textcoords=None,
+ arrowprops=None,
+ annotation_clip=None,
+ **kwargs):
+ """
+ Annotate the point *xy* with text *text*.
+
+ In the simplest form, the text is placed at *xy*.
+
+ Optionally, the text can be displayed in another position *xytext*.
+ An arrow pointing from the text to the annotated point *xy* can then
+ be added by defining *arrowprops*.
+
+ Parameters
+ ----------
+ text : str
+ The text of the annotation.
+
+ xy : (float, float)
+ The point *(x, y)* to annotate. The coordinate system is determined
+ by *xycoords*.
+
+ xytext : (float, float), default: *xy*
+ The position *(x, y)* to place the text at. The coordinate system
+ is determined by *textcoords*.
+
+ xycoords : single or two-tuple of str or `.Artist` or `.Transform` or \
+callable, default: 'data'
+
+ The coordinate system that *xy* is given in. The following types
+ of values are supported:
+
+ - One of the following strings:
+
+ ==================== ============================================
+ Value Description
+ ==================== ============================================
+ 'figure points' Points from the lower left of the figure
+ 'figure pixels' Pixels from the lower left of the figure
+ 'figure fraction' Fraction of figure from lower left
+ 'subfigure points' Points from the lower left of the subfigure
+ 'subfigure pixels' Pixels from the lower left of the subfigure
+ 'subfigure fraction' Fraction of subfigure from lower left
+ 'axes points' Points from lower left corner of the Axes
+ 'axes pixels' Pixels from lower left corner of the Axes
+ 'axes fraction' Fraction of Axes from lower left
+ 'data' Use the coordinate system of the object
+ being annotated (default)
+ 'polar' *(theta, r)* if not native 'data'
+ coordinates
+ ==================== ============================================
+
+ Note that 'subfigure pixels' and 'figure pixels' are the same
+ for the parent figure, so users who want code that is usable in
+ a subfigure can use 'subfigure pixels'.
+
+ - An `.Artist`: *xy* is interpreted as a fraction of the artist's
+ `~matplotlib.transforms.Bbox`. E.g. *(0, 0)* would be the lower
+ left corner of the bounding box and *(0.5, 1)* would be the
+ center top of the bounding box.
+
+ - A `.Transform` to transform *xy* to screen coordinates.
+
+ - A function with one of the following signatures::
+
+ def transform(renderer) -> Bbox
+ def transform(renderer) -> Transform
+
+ where *renderer* is a `.RendererBase` subclass.
+
+ The result of the function is interpreted like the `.Artist` and
+ `.Transform` cases above.
+
+ - A tuple *(xcoords, ycoords)* specifying separate coordinate
+ systems for *x* and *y*. *xcoords* and *ycoords* must each be
+ of one of the above described types.
+
+ See :ref:`plotting-guide-annotation` for more details.
+
+ textcoords : single or two-tuple of str or `.Artist` or `.Transform` \
+or callable, default: value of *xycoords*
+ The coordinate system that *xytext* is given in.
+
+ All *xycoords* values are valid as well as the following strings:
+
+ ================= =================================================
+ Value Description
+ ================= =================================================
+ 'offset points' Offset, in points, from the *xy* value
+ 'offset pixels' Offset, in pixels, from the *xy* value
+ 'offset fontsize' Offset, relative to fontsize, from the *xy* value
+ ================= =================================================
+
+ arrowprops : dict, optional
+ The properties used to draw a `.FancyArrowPatch` arrow between the
+ positions *xy* and *xytext*. Defaults to None, i.e. no arrow is
+ drawn.
+
+ For historical reasons there are two different ways to specify
+ arrows, "simple" and "fancy":
+
+ **Simple arrow:**
+
+ If *arrowprops* does not contain the key 'arrowstyle' the
+ allowed keys are:
+
+ ========== =================================================
+ Key Description
+ ========== =================================================
+ width The width of the arrow in points
+ headwidth The width of the base of the arrow head in points
+ headlength The length of the arrow head in points
+ shrink Fraction of total length to shrink from both ends
+ ? Any `.FancyArrowPatch` property
+ ========== =================================================
+
+ The arrow is attached to the edge of the text box, the exact
+ position (corners or centers) depending on where it's pointing to.
+
+ **Fancy arrow:**
+
+ This is used if 'arrowstyle' is provided in the *arrowprops*.
+
+ Valid keys are the following `.FancyArrowPatch` parameters:
+
+ =============== ===================================
+ Key Description
+ =============== ===================================
+ arrowstyle The arrow style
+ connectionstyle The connection style
+ relpos See below; default is (0.5, 0.5)
+ patchA Default is bounding box of the text
+ patchB Default is None
+ shrinkA In points. Default is 2 points
+ shrinkB In points. Default is 2 points
+ mutation_scale Default is text size (in points)
+ mutation_aspect Default is 1
+ ? Any `.FancyArrowPatch` property
+ =============== ===================================
+
+ The exact starting point position of the arrow is defined by
+ *relpos*. It's a tuple of relative coordinates of the text box,
+ where (0, 0) is the lower left corner and (1, 1) is the upper
+ right corner. Values <0 and >1 are supported and specify points
+ outside the text box. By default (0.5, 0.5), so the starting point
+ is centered in the text box.
+
+ annotation_clip : bool or None, default: None
+ Whether to clip (i.e. not draw) the annotation when the annotation
+ point *xy* is outside the Axes area.
+
+ - If *True*, the annotation will be clipped when *xy* is outside
+ the Axes.
+ - If *False*, the annotation will always be drawn.
+ - If *None*, the annotation will be clipped when *xy* is outside
+ the Axes and *xycoords* is 'data'.
+
+ **kwargs
+ Additional kwargs are passed to `.Text`.
+
+ Returns
+ -------
+ `.Annotation`
+
+ See Also
+ --------
+ :ref:`annotations`
+
+ """
+ _AnnotationBase.__init__(self,
+ xy,
+ xycoords=xycoords,
+ annotation_clip=annotation_clip)
+ # warn about wonky input data
+ if (xytext is None and
+ textcoords is not None and
+ textcoords != xycoords):
+ _api.warn_external("You have used the `textcoords` kwarg, but "
+ "not the `xytext` kwarg. This can lead to "
+ "surprising results.")
+
+ # clean up textcoords and assign default
+ if textcoords is None:
+ textcoords = self.xycoords
+ self._textcoords = textcoords
+
+ # cleanup xytext defaults
+ if xytext is None:
+ xytext = self.xy
+ x, y = xytext
+
+ self.arrowprops = arrowprops
+ if arrowprops is not None:
+ arrowprops = arrowprops.copy()
+ if "arrowstyle" in arrowprops:
+ self._arrow_relpos = arrowprops.pop("relpos", (0.5, 0.5))
+ else:
+ # modified YAArrow API to be used with FancyArrowPatch
+ for key in ['width', 'headwidth', 'headlength', 'shrink']:
+ arrowprops.pop(key, None)
+ self.arrow_patch = FancyArrowPatch((0, 0), (1, 1), **arrowprops)
+ else:
+ self.arrow_patch = None
+
+ # Must come last, as some kwargs may be propagated to arrow_patch.
+ Text.__init__(self, x, y, text, **kwargs)
+
+ def contains(self, mouseevent):
+ if self._different_canvas(mouseevent):
+ return False, {}
+ contains, tinfo = Text.contains(self, mouseevent)
+ if self.arrow_patch is not None:
+ in_patch, _ = self.arrow_patch.contains(mouseevent)
+ contains = contains or in_patch
+ return contains, tinfo
+
+ @property
+ def xycoords(self):
+ return self._xycoords
+
+ @xycoords.setter
+ def xycoords(self, xycoords):
+ def is_offset(s):
+ return isinstance(s, str) and s.startswith("offset")
+
+ if (isinstance(xycoords, tuple) and any(map(is_offset, xycoords))
+ or is_offset(xycoords)):
+ raise ValueError("xycoords cannot be an offset coordinate")
+ self._xycoords = xycoords
+
+ @property
+ def xyann(self):
+ """
+ The text position.
+
+ See also *xytext* in `.Annotation`.
+ """
+ return self.get_position()
+
+ @xyann.setter
+ def xyann(self, xytext):
+ self.set_position(xytext)
+
+ def get_anncoords(self):
+ """
+ Return the coordinate system to use for `.Annotation.xyann`.
+
+ See also *xycoords* in `.Annotation`.
+ """
+ return self._textcoords
+
+ def set_anncoords(self, coords):
+ """
+ Set the coordinate system to use for `.Annotation.xyann`.
+
+ See also *xycoords* in `.Annotation`.
+ """
+ self._textcoords = coords
+
+ anncoords = property(get_anncoords, set_anncoords, doc="""
+ The coordinate system to use for `.Annotation.xyann`.""")
+
+ def set_figure(self, fig):
+ # docstring inherited
+ if self.arrow_patch is not None:
+ self.arrow_patch.set_figure(fig)
+ Artist.set_figure(self, fig)
+
+ def update_positions(self, renderer):
+ """
+ Update the pixel positions of the annotation text and the arrow patch.
+ """
+ # generate transformation
+ self.set_transform(self._get_xy_transform(renderer, self.anncoords))
+
+ arrowprops = self.arrowprops
+ if arrowprops is None:
+ return
+
+ bbox = Text.get_window_extent(self, renderer)
+
+ arrow_end = x1, y1 = self._get_position_xy(renderer) # Annotated pos.
+
+ ms = arrowprops.get("mutation_scale", self.get_size())
+ self.arrow_patch.set_mutation_scale(ms)
+
+ if "arrowstyle" not in arrowprops:
+ # Approximately simulate the YAArrow.
+ shrink = arrowprops.get('shrink', 0.0)
+ width = arrowprops.get('width', 4)
+ headwidth = arrowprops.get('headwidth', 12)
+ headlength = arrowprops.get('headlength', 12)
+
+ # NB: ms is in pts
+ stylekw = dict(head_length=headlength / ms,
+ head_width=headwidth / ms,
+ tail_width=width / ms)
+
+ self.arrow_patch.set_arrowstyle('simple', **stylekw)
+
+ # using YAArrow style:
+ # pick the corner of the text bbox closest to annotated point.
+ xpos = [(bbox.x0, 0), ((bbox.x0 + bbox.x1) / 2, 0.5), (bbox.x1, 1)]
+ ypos = [(bbox.y0, 0), ((bbox.y0 + bbox.y1) / 2, 0.5), (bbox.y1, 1)]
+ x, relposx = min(xpos, key=lambda v: abs(v[0] - x1))
+ y, relposy = min(ypos, key=lambda v: abs(v[0] - y1))
+ self._arrow_relpos = (relposx, relposy)
+ r = np.hypot(y - y1, x - x1)
+ shrink_pts = shrink * r / renderer.points_to_pixels(1)
+ self.arrow_patch.shrinkA = self.arrow_patch.shrinkB = shrink_pts
+
+ # adjust the starting point of the arrow relative to the textbox.
+ # TODO : Rotation needs to be accounted.
+ arrow_begin = bbox.p0 + bbox.size * self._arrow_relpos
+ # The arrow is drawn from arrow_begin to arrow_end. It will be first
+ # clipped by patchA and patchB. Then it will be shrunk by shrinkA and
+ # shrinkB (in points). If patchA is not set, self.bbox_patch is used.
+ self.arrow_patch.set_positions(arrow_begin, arrow_end)
+
+ if "patchA" in arrowprops:
+ patchA = arrowprops["patchA"]
+ elif self._bbox_patch:
+ patchA = self._bbox_patch
+ elif self.get_text() == "":
+ patchA = None
+ else:
+ pad = renderer.points_to_pixels(4)
+ patchA = Rectangle(
+ xy=(bbox.x0 - pad / 2, bbox.y0 - pad / 2),
+ width=bbox.width + pad, height=bbox.height + pad,
+ transform=IdentityTransform(), clip_on=False)
+ self.arrow_patch.set_patchA(patchA)
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ # docstring inherited
+ if renderer is not None:
+ self._renderer = renderer
+ if not self.get_visible() or not self._check_xy(renderer):
+ return
+ # Update text positions before `Text.draw` would, so that the
+ # FancyArrowPatch is correctly positioned.
+ self.update_positions(renderer)
+ self.update_bbox_position_size(renderer)
+ if self.arrow_patch is not None: # FancyArrowPatch
+ if (self.arrow_patch.get_figure(root=False) is None and
+ (fig := self.get_figure(root=False)) is not None):
+ self.arrow_patch.set_figure(fig)
+ self.arrow_patch.draw(renderer)
+ # Draw text, including FancyBboxPatch, after FancyArrowPatch.
+ # Otherwise, a wedge arrowstyle can land partly on top of the Bbox.
+ Text.draw(self, renderer)
+
+ def get_window_extent(self, renderer=None):
+ # docstring inherited
+ # This block is the same as in Text.get_window_extent, but we need to
+ # set the renderer before calling update_positions().
+ if not self.get_visible() or not self._check_xy(renderer):
+ return Bbox.unit()
+ if renderer is not None:
+ self._renderer = renderer
+ if self._renderer is None:
+ self._renderer = self.get_figure(root=True)._get_renderer()
+ if self._renderer is None:
+ raise RuntimeError('Cannot get window extent without renderer')
+
+ self.update_positions(self._renderer)
+
+ text_bbox = Text.get_window_extent(self)
+ bboxes = [text_bbox]
+
+ if self.arrow_patch is not None:
+ bboxes.append(self.arrow_patch.get_window_extent())
+
+ return Bbox.union(bboxes)
+
+ def get_tightbbox(self, renderer=None):
+ # docstring inherited
+ if not self._check_xy(renderer):
+ return Bbox.null()
+ return super().get_tightbbox(renderer)
+
+
+_docstring.interpd.register(Annotation=Annotation.__init__.__doc__)
diff --git a/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/text.pyi b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/text.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..fc1ab1c416cfcf8e70a70b70d86d6293751100f2
--- /dev/null
+++ b/outputs/audit_venv/lib/python3.11/site-packages/matplotlib/text.pyi
@@ -0,0 +1,181 @@
+from .artist import Artist
+from .backend_bases import RendererBase
+from .font_manager import FontProperties
+from .offsetbox import DraggableAnnotation
+from pathlib import Path
+from .patches import FancyArrowPatch, FancyBboxPatch
+from .textpath import ( # noqa: F401, reexported API
+ TextPath as TextPath,
+ TextToPath as TextToPath,
+)
+from .transforms import (
+ Bbox,
+ BboxBase,
+ Transform,
+)
+
+from collections.abc import Callable, Iterable
+from typing import Any, Literal
+from .typing import ColorType, CoordsType
+
+class Text(Artist):
+ zorder: float
+ def __init__(
+ self,
+ x: float = ...,
+ y: float = ...,
+ text: Any = ...,
+ *,
+ color: ColorType | None = ...,
+ verticalalignment: Literal[
+ "bottom", "baseline", "center", "center_baseline", "top"
+ ] = ...,
+ horizontalalignment: Literal["left", "center", "right"] = ...,
+ multialignment: Literal["left", "center", "right"] | None = ...,
+ fontproperties: str | Path | FontProperties | None = ...,
+ rotation: float | Literal["vertical", "horizontal"] | None = ...,
+ linespacing: float | None = ...,
+ rotation_mode: Literal["default", "anchor"] | None = ...,
+ usetex: bool | None = ...,
+ wrap: bool = ...,
+ transform_rotates_text: bool = ...,
+ parse_math: bool | None = ...,
+ antialiased: bool | None = ...,
+ **kwargs
+ ) -> None: ...
+ def update(self, kwargs: dict[str, Any]) -> list[Any]: ...
+ def get_rotation(self) -> float: ...
+ def get_transform_rotates_text(self) -> bool: ...
+ def set_rotation_mode(self, m: None | Literal["default", "anchor"]) -> None: ...
+ def get_rotation_mode(self) -> Literal["default", "anchor"]: ...
+ def set_bbox(self, rectprops: dict[str, Any]) -> None: ...
+ def get_bbox_patch(self) -> None | FancyBboxPatch: ...
+ def update_bbox_position_size(self, renderer: RendererBase) -> None: ...
+ def get_wrap(self) -> bool: ...
+ def set_wrap(self, wrap: bool) -> None: ...
+ def get_color(self) -> ColorType: ...
+ def get_fontproperties(self) -> FontProperties: ...
+ def get_fontfamily(self) -> list[str]: ...
+ def get_fontname(self) -> str: ...
+ def get_fontstyle(self) -> Literal["normal", "italic", "oblique"]: ...
+ def get_fontsize(self) -> float | str: ...
+ def get_fontvariant(self) -> Literal["normal", "small-caps"]: ...
+ def get_fontweight(self) -> int | str: ...
+ def get_stretch(self) -> int | str: ...
+ def get_horizontalalignment(self) -> Literal["left", "center", "right"]: ...
+ def get_unitless_position(self) -> tuple[float, float]: ...
+ def get_position(self) -> tuple[float, float]: ...
+ def get_text(self) -> str: ...
+ def get_verticalalignment(
+ self,
+ ) -> Literal["bottom", "baseline", "center", "center_baseline", "top"]: ...
+ def get_window_extent(
+ self, renderer: RendererBase | None = ..., dpi: float | None = ...
+ ) -> Bbox: ...
+ def set_backgroundcolor(self, color: ColorType) -> None: ...
+ def set_color(self, color: ColorType) -> None: ...
+ def set_horizontalalignment(
+ self, align: Literal["left", "center", "right"]
+ ) -> None: ...
+ def set_multialignment(self, align: Literal["left", "center", "right"]) -> None: ...
+ def set_linespacing(self, spacing: float) -> None: ...
+ def set_fontfamily(self, fontname: str | Iterable[str]) -> None: ...
+ def set_fontvariant(self, variant: Literal["normal", "small-caps"]) -> None: ...
+ def set_fontstyle(
+ self, fontstyle: Literal["normal", "italic", "oblique"]
+ ) -> None: ...
+ def set_fontsize(self, fontsize: float | str) -> None: ...
+ def get_math_fontfamily(self) -> str: ...
+ def set_math_fontfamily(self, fontfamily: str) -> None: ...
+ def set_fontweight(self, weight: int | str) -> None: ...
+ def set_fontstretch(self, stretch: int | str) -> None: ...
+ def set_position(self, xy: tuple[float, float]) -> None: ...
+ def set_x(self, x: float) -> None: ...
+ def set_y(self, y: float) -> None: ...
+ def set_rotation(self, s: float) -> None: ...
+ def set_transform_rotates_text(self, t: bool) -> None: ...
+ def set_verticalalignment(
+ self, align: Literal["bottom", "baseline", "center", "center_baseline", "top"]
+ ) -> None: ...
+ def set_text(self, s: Any) -> None: ...
+ def set_fontproperties(self, fp: FontProperties | str | Path | None) -> None: ...
+ def set_usetex(self, usetex: bool | None) -> None: ...
+ def get_usetex(self) -> bool: ...
+ def set_parse_math(self, parse_math: bool) -> None: ...
+ def get_parse_math(self) -> bool: ...
+ def set_fontname(self, fontname: str | Iterable[str]) -> None: ...
+ def get_antialiased(self) -> bool: ...
+ def set_antialiased(self, antialiased: bool) -> None: ...
+
+class OffsetFrom:
+ def __init__(
+ self,
+ artist: Artist | BboxBase | Transform,
+ ref_coord: tuple[float, float],
+ unit: Literal["points", "pixels"] = ...,
+ ) -> None: ...
+ def set_unit(self, unit: Literal["points", "pixels"]) -> None: ...
+ def get_unit(self) -> Literal["points", "pixels"]: ...
+ def __call__(self, renderer: RendererBase) -> Transform: ...
+
+class _AnnotationBase:
+ xy: tuple[float, float]
+ xycoords: CoordsType
+ def __init__(
+ self,
+ xy,
+ xycoords: CoordsType = ...,
+ annotation_clip: bool | None = ...,
+ ) -> None: ...
+ def set_annotation_clip(self, b: bool | None) -> None: ...
+ def get_annotation_clip(self) -> bool | None: ...
+ def draggable(
+ self, state: bool | None = ..., use_blit: bool = ...
+ ) -> DraggableAnnotation | None: ...
+
+class Annotation(Text, _AnnotationBase):
+ arrowprops: dict[str, Any] | None
+ arrow_patch: FancyArrowPatch | None
+ def __init__(
+ self,
+ text: str,
+ xy: tuple[float, float],
+ xytext: tuple[float, float] | None = ...,
+ xycoords: CoordsType = ...,
+ textcoords: CoordsType | None = ...,
+ arrowprops: dict[str, Any] | None = ...,
+ annotation_clip: bool | None = ...,
+ **kwargs
+ ) -> None: ...
+ @property
+ def xycoords(
+ self,
+ ) -> CoordsType: ...
+ @xycoords.setter
+ def xycoords(
+ self,
+ xycoords: CoordsType,
+ ) -> None: ...
+ @property
+ def xyann(self) -> tuple[float, float]: ...
+ @xyann.setter
+ def xyann(self, xytext: tuple[float, float]) -> None: ...
+ def get_anncoords(
+ self,
+ ) -> CoordsType: ...
+ def set_anncoords(
+ self,
+ coords: CoordsType,
+ ) -> None: ...
+ @property
+ def anncoords(
+ self,
+ ) -> CoordsType: ...
+ @anncoords.setter
+ def anncoords(
+ self,
+ coords: CoordsType,
+ ) -> None: ...
+ def update_positions(self, renderer: RendererBase) -> None: ...
+ # Drops `dpi` parameter from superclass
+ def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ... # type: ignore[override]