diff options
author | maxim-yurchuk <maxim-yurchuk@yandex-team.com> | 2025-02-11 13:26:52 +0300 |
---|---|---|
committer | maxim-yurchuk <maxim-yurchuk@yandex-team.com> | 2025-02-11 13:57:59 +0300 |
commit | f895bba65827952ed934b2b46f9a45e30a191fd2 (patch) | |
tree | 03260c906d9ec41cdc03e2a496b15d407459cec0 /contrib/python/matplotlib/py3/mpl_toolkits | |
parent | 5f7060466f7b9707818c2091e1a25c14f33c3474 (diff) | |
download | ydb-f895bba65827952ed934b2b46f9a45e30a191fd2.tar.gz |
Remove deps on pandas
<https://github.com/ydb-platform/ydb/pull/14418>
<https://github.com/ydb-platform/ydb/pull/14419>
\-- аналогичные правки в gh
Хочу залить в обход синка, чтобы посмотреть удалится ли pandas в нашей gh репе через piglet
commit_hash:abca127aa37d4dbb94b07e1e18cdb8eb5b711860
Diffstat (limited to 'contrib/python/matplotlib/py3/mpl_toolkits')
26 files changed, 0 insertions, 12047 deletions
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/__init__.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/__init__.py deleted file mode 100644 index c55302485e3..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from . import axes_size as Size -from .axes_divider import Divider, SubplotDivider, make_axes_locatable -from .axes_grid import AxesGrid, Grid, ImageGrid - -from .parasite_axes import host_subplot, host_axes - -__all__ = ["Size", - "Divider", "SubplotDivider", "make_axes_locatable", - "AxesGrid", "Grid", "ImageGrid", - "host_subplot", "host_axes"] diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/anchored_artists.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/anchored_artists.py deleted file mode 100644 index 1238310b462..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/anchored_artists.py +++ /dev/null @@ -1,462 +0,0 @@ -from matplotlib import _api, transforms -from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox, - DrawingArea, TextArea, VPacker) -from matplotlib.patches import (Rectangle, Ellipse, ArrowStyle, - FancyArrowPatch, PathPatch) -from matplotlib.text import TextPath - -__all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox', - 'AnchoredEllipse', 'AnchoredSizeBar', 'AnchoredDirectionArrows'] - - -class AnchoredDrawingArea(AnchoredOffsetbox): - def __init__(self, width, height, xdescent, ydescent, - loc, pad=0.4, borderpad=0.5, prop=None, frameon=True, - **kwargs): - """ - An anchored container with a fixed size and fillable `.DrawingArea`. - - Artists added to the *drawing_area* will have their coordinates - interpreted as pixels. Any transformations set on the artists will be - overridden. - - Parameters - ---------- - width, height : float - Width and height of the container, in pixels. - xdescent, ydescent : float - Descent of the container in the x- and y- direction, in pixels. - loc : str - Location of this artist. 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 objects, in fraction of the font size. - borderpad : float, default: 0.5 - Border padding, in fraction of the font size. - prop : `~matplotlib.font_manager.FontProperties`, optional - Font property used as a reference for paddings. - frameon : bool, default: True - If True, draw a box around this artist. - **kwargs - Keyword arguments forwarded to `.AnchoredOffsetbox`. - - Attributes - ---------- - drawing_area : `~matplotlib.offsetbox.DrawingArea` - A container for artists to display. - - Examples - -------- - To display blue and red circles of different sizes in the upper right - of an Axes *ax*: - - >>> ada = AnchoredDrawingArea(20, 20, 0, 0, - ... loc='upper right', frameon=False) - >>> ada.drawing_area.add_artist(Circle((10, 10), 10, fc="b")) - >>> ada.drawing_area.add_artist(Circle((30, 10), 5, fc="r")) - >>> ax.add_artist(ada) - """ - self.da = DrawingArea(width, height, xdescent, ydescent) - self.drawing_area = self.da - - super().__init__( - loc, pad=pad, borderpad=borderpad, child=self.da, prop=None, - frameon=frameon, **kwargs - ) - - -class AnchoredAuxTransformBox(AnchoredOffsetbox): - def __init__(self, transform, loc, - pad=0.4, borderpad=0.5, prop=None, frameon=True, **kwargs): - """ - An anchored container with transformed coordinates. - - Artists added to the *drawing_area* are scaled according to the - coordinates of the transformation used. The dimensions of this artist - will scale to contain the artists added. - - Parameters - ---------- - transform : `~matplotlib.transforms.Transform` - The transformation object for the coordinate system in use, i.e., - :attr:`matplotlib.axes.Axes.transData`. - loc : str - Location of this artist. 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 objects, in fraction of the font size. - borderpad : float, default: 0.5 - Border padding, in fraction of the font size. - prop : `~matplotlib.font_manager.FontProperties`, optional - Font property used as a reference for paddings. - frameon : bool, default: True - If True, draw a box around this artist. - **kwargs - Keyword arguments forwarded to `.AnchoredOffsetbox`. - - Attributes - ---------- - drawing_area : `~matplotlib.offsetbox.AuxTransformBox` - A container for artists to display. - - Examples - -------- - To display an ellipse in the upper left, with a width of 0.1 and - height of 0.4 in data coordinates: - - >>> box = AnchoredAuxTransformBox(ax.transData, loc='upper left') - >>> el = Ellipse((0, 0), width=0.1, height=0.4, angle=30) - >>> box.drawing_area.add_artist(el) - >>> ax.add_artist(box) - """ - self.drawing_area = AuxTransformBox(transform) - - super().__init__(loc, pad=pad, borderpad=borderpad, - child=self.drawing_area, prop=prop, frameon=frameon, - **kwargs) - - -@_api.deprecated("3.8") -class AnchoredEllipse(AnchoredOffsetbox): - def __init__(self, transform, width, height, angle, loc, - pad=0.1, borderpad=0.1, prop=None, frameon=True, **kwargs): - """ - Draw an anchored ellipse of a given size. - - Parameters - ---------- - transform : `~matplotlib.transforms.Transform` - The transformation object for the coordinate system in use, i.e., - :attr:`matplotlib.axes.Axes.transData`. - width, height : float - Width and height of the ellipse, given in coordinates of - *transform*. - angle : float - Rotation of the ellipse, in degrees, anti-clockwise. - loc : str - Location of the ellipse. 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.1 - Padding around the ellipse, in fraction of the font size. - borderpad : float, default: 0.1 - Border padding, in fraction of the font size. - frameon : bool, default: True - If True, draw a box around the ellipse. - prop : `~matplotlib.font_manager.FontProperties`, optional - Font property used as a reference for paddings. - **kwargs - Keyword arguments forwarded to `.AnchoredOffsetbox`. - - Attributes - ---------- - ellipse : `~matplotlib.patches.Ellipse` - Ellipse patch drawn. - """ - self._box = AuxTransformBox(transform) - self.ellipse = Ellipse((0, 0), width, height, angle=angle) - self._box.add_artist(self.ellipse) - - super().__init__(loc, pad=pad, borderpad=borderpad, child=self._box, - prop=prop, frameon=frameon, **kwargs) - - -class AnchoredSizeBar(AnchoredOffsetbox): - def __init__(self, transform, size, label, loc, - pad=0.1, borderpad=0.1, sep=2, - frameon=True, size_vertical=0, color='black', - label_top=False, fontproperties=None, fill_bar=None, - **kwargs): - """ - Draw a horizontal scale bar with a center-aligned label underneath. - - Parameters - ---------- - transform : `~matplotlib.transforms.Transform` - The transformation object for the coordinate system in use, i.e., - :attr:`matplotlib.axes.Axes.transData`. - size : float - Horizontal length of the size bar, given in coordinates of - *transform*. - label : str - Label to display. - loc : str - Location of the size bar. 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.1 - Padding around the label and size bar, in fraction of the font - size. - borderpad : float, default: 0.1 - Border padding, in fraction of the font size. - sep : float, default: 2 - Separation between the label and the size bar, in points. - frameon : bool, default: True - If True, draw a box around the horizontal bar and label. - size_vertical : float, default: 0 - Vertical length of the size bar, given in coordinates of - *transform*. - color : str, default: 'black' - Color for the size bar and label. - label_top : bool, default: False - If True, the label will be over the size bar. - fontproperties : `~matplotlib.font_manager.FontProperties`, optional - Font properties for the label text. - fill_bar : bool, optional - If True and if *size_vertical* is nonzero, the size bar will - be filled in with the color specified by the size bar. - Defaults to True if *size_vertical* is greater than - zero and False otherwise. - **kwargs - Keyword arguments forwarded to `.AnchoredOffsetbox`. - - Attributes - ---------- - size_bar : `~matplotlib.offsetbox.AuxTransformBox` - Container for the size bar. - txt_label : `~matplotlib.offsetbox.TextArea` - Container for the label of the size bar. - - Notes - ----- - If *prop* is passed as a keyword argument, but *fontproperties* is - not, then *prop* is assumed to be the intended *fontproperties*. - Using both *prop* and *fontproperties* is not supported. - - Examples - -------- - >>> import matplotlib.pyplot as plt - >>> import numpy as np - >>> from mpl_toolkits.axes_grid1.anchored_artists import ( - ... AnchoredSizeBar) - >>> fig, ax = plt.subplots() - >>> ax.imshow(np.random.random((10, 10))) - >>> bar = AnchoredSizeBar(ax.transData, 3, '3 data units', 4) - >>> ax.add_artist(bar) - >>> fig.show() - - Using all the optional parameters - - >>> import matplotlib.font_manager as fm - >>> fontprops = fm.FontProperties(size=14, family='monospace') - >>> bar = AnchoredSizeBar(ax.transData, 3, '3 units', 4, pad=0.5, - ... sep=5, borderpad=0.5, frameon=False, - ... size_vertical=0.5, color='white', - ... fontproperties=fontprops) - """ - if fill_bar is None: - fill_bar = size_vertical > 0 - - self.size_bar = AuxTransformBox(transform) - self.size_bar.add_artist(Rectangle((0, 0), size, size_vertical, - fill=fill_bar, facecolor=color, - edgecolor=color)) - - if fontproperties is None and 'prop' in kwargs: - fontproperties = kwargs.pop('prop') - - if fontproperties is None: - textprops = {'color': color} - else: - textprops = {'color': color, 'fontproperties': fontproperties} - - self.txt_label = TextArea(label, textprops=textprops) - - if label_top: - _box_children = [self.txt_label, self.size_bar] - else: - _box_children = [self.size_bar, self.txt_label] - - self._box = VPacker(children=_box_children, - align="center", - pad=0, sep=sep) - - super().__init__(loc, pad=pad, borderpad=borderpad, child=self._box, - prop=fontproperties, frameon=frameon, **kwargs) - - -class AnchoredDirectionArrows(AnchoredOffsetbox): - def __init__(self, transform, label_x, label_y, length=0.15, - fontsize=0.08, loc='upper left', angle=0, aspect_ratio=1, - pad=0.4, borderpad=0.4, frameon=False, color='w', alpha=1, - sep_x=0.01, sep_y=0, fontproperties=None, back_length=0.15, - head_width=10, head_length=15, tail_width=2, - text_props=None, arrow_props=None, - **kwargs): - """ - Draw two perpendicular arrows to indicate directions. - - Parameters - ---------- - transform : `~matplotlib.transforms.Transform` - The transformation object for the coordinate system in use, i.e., - :attr:`matplotlib.axes.Axes.transAxes`. - label_x, label_y : str - Label text for the x and y arrows - length : float, default: 0.15 - Length of the arrow, given in coordinates of *transform*. - fontsize : float, default: 0.08 - Size of label strings, given in coordinates of *transform*. - loc : str, default: 'upper left' - Location of the arrow. 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. - angle : float, default: 0 - The angle of the arrows in degrees. - aspect_ratio : float, default: 1 - The ratio of the length of arrow_x and arrow_y. - Negative numbers can be used to change the direction. - pad : float, default: 0.4 - Padding around the labels and arrows, in fraction of the font size. - borderpad : float, default: 0.4 - Border padding, in fraction of the font size. - frameon : bool, default: False - If True, draw a box around the arrows and labels. - color : str, default: 'white' - Color for the arrows and labels. - alpha : float, default: 1 - Alpha values of the arrows and labels - sep_x, sep_y : float, default: 0.01 and 0 respectively - Separation between the arrows and labels in coordinates of - *transform*. - fontproperties : `~matplotlib.font_manager.FontProperties`, optional - Font properties for the label text. - back_length : float, default: 0.15 - Fraction of the arrow behind the arrow crossing. - head_width : float, default: 10 - Width of arrow head, sent to `.ArrowStyle`. - head_length : float, default: 15 - Length of arrow head, sent to `.ArrowStyle`. - tail_width : float, default: 2 - Width of arrow tail, sent to `.ArrowStyle`. - text_props, arrow_props : dict - Properties of the text and arrows, passed to `.TextPath` and - `.FancyArrowPatch`. - **kwargs - Keyword arguments forwarded to `.AnchoredOffsetbox`. - - Attributes - ---------- - arrow_x, arrow_y : `~matplotlib.patches.FancyArrowPatch` - Arrow x and y - text_path_x, text_path_y : `~matplotlib.text.TextPath` - Path for arrow labels - p_x, p_y : `~matplotlib.patches.PathPatch` - Patch for arrow labels - box : `~matplotlib.offsetbox.AuxTransformBox` - Container for the arrows and labels. - - Notes - ----- - If *prop* is passed as a keyword argument, but *fontproperties* is - not, then *prop* is assumed to be the intended *fontproperties*. - Using both *prop* and *fontproperties* is not supported. - - Examples - -------- - >>> import matplotlib.pyplot as plt - >>> import numpy as np - >>> from mpl_toolkits.axes_grid1.anchored_artists import ( - ... AnchoredDirectionArrows) - >>> fig, ax = plt.subplots() - >>> ax.imshow(np.random.random((10, 10))) - >>> arrows = AnchoredDirectionArrows(ax.transAxes, '111', '110') - >>> ax.add_artist(arrows) - >>> fig.show() - - Using several of the optional parameters, creating downward pointing - arrow and high contrast text labels. - - >>> import matplotlib.font_manager as fm - >>> fontprops = fm.FontProperties(family='monospace') - >>> arrows = AnchoredDirectionArrows(ax.transAxes, 'East', 'South', - ... loc='lower left', color='k', - ... aspect_ratio=-1, sep_x=0.02, - ... sep_y=-0.01, - ... text_props={'ec':'w', 'fc':'k'}, - ... fontproperties=fontprops) - """ - if arrow_props is None: - arrow_props = {} - - if text_props is None: - text_props = {} - - arrowstyle = ArrowStyle("Simple", - head_width=head_width, - head_length=head_length, - tail_width=tail_width) - - if fontproperties is None and 'prop' in kwargs: - fontproperties = kwargs.pop('prop') - - if 'color' not in arrow_props: - arrow_props['color'] = color - - if 'alpha' not in arrow_props: - arrow_props['alpha'] = alpha - - if 'color' not in text_props: - text_props['color'] = color - - if 'alpha' not in text_props: - text_props['alpha'] = alpha - - t_start = transform - t_end = t_start + transforms.Affine2D().rotate_deg(angle) - - self.box = AuxTransformBox(t_end) - - length_x = length - length_y = length*aspect_ratio - - self.arrow_x = FancyArrowPatch( - (0, back_length*length_y), - (length_x, back_length*length_y), - arrowstyle=arrowstyle, - shrinkA=0.0, - shrinkB=0.0, - **arrow_props) - - self.arrow_y = FancyArrowPatch( - (back_length*length_x, 0), - (back_length*length_x, length_y), - arrowstyle=arrowstyle, - shrinkA=0.0, - shrinkB=0.0, - **arrow_props) - - self.box.add_artist(self.arrow_x) - self.box.add_artist(self.arrow_y) - - text_path_x = TextPath(( - length_x+sep_x, back_length*length_y+sep_y), label_x, - size=fontsize, prop=fontproperties) - self.p_x = PathPatch(text_path_x, transform=t_start, **text_props) - self.box.add_artist(self.p_x) - - text_path_y = TextPath(( - length_x*back_length+sep_x, length_y*(1-back_length)+sep_y), - label_y, size=fontsize, prop=fontproperties) - self.p_y = PathPatch(text_path_y, **text_props) - self.box.add_artist(self.p_y) - - super().__init__(loc, pad=pad, borderpad=borderpad, child=self.box, - frameon=frameon, **kwargs) diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_divider.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_divider.py deleted file mode 100644 index f6c38f35dbc..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_divider.py +++ /dev/null @@ -1,694 +0,0 @@ -""" -Helper classes to adjust the positions of multiple axes at drawing time. -""" - -import functools - -import numpy as np - -import matplotlib as mpl -from matplotlib import _api -from matplotlib.gridspec import SubplotSpec -import matplotlib.transforms as mtransforms -from . import axes_size as Size - - -class Divider: - """ - An Axes positioning class. - - The divider is initialized with lists of horizontal and vertical sizes - (:mod:`mpl_toolkits.axes_grid1.axes_size`) based on which a given - rectangular area will be divided. - - The `new_locator` method then creates a callable object - that can be used as the *axes_locator* of the axes. - """ - - def __init__(self, fig, pos, horizontal, vertical, - aspect=None, anchor="C"): - """ - Parameters - ---------- - fig : Figure - pos : tuple of 4 floats - Position of the rectangle that will be divided. - horizontal : list of :mod:`~mpl_toolkits.axes_grid1.axes_size` - Sizes for horizontal division. - vertical : list of :mod:`~mpl_toolkits.axes_grid1.axes_size` - Sizes for vertical division. - aspect : bool, optional - Whether overall rectangular area is reduced so that the relative - part of the horizontal and vertical scales have the same scale. - anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \ -'NW', 'W'}, default: 'C' - Placement of the reduced rectangle, when *aspect* is True. - """ - - self._fig = fig - self._pos = pos - self._horizontal = horizontal - self._vertical = vertical - self._anchor = anchor - self.set_anchor(anchor) - self._aspect = aspect - self._xrefindex = 0 - self._yrefindex = 0 - self._locator = None - - def get_horizontal_sizes(self, renderer): - return np.array([s.get_size(renderer) for s in self.get_horizontal()]) - - def get_vertical_sizes(self, renderer): - return np.array([s.get_size(renderer) for s in self.get_vertical()]) - - def set_position(self, pos): - """ - Set the position of the rectangle. - - Parameters - ---------- - pos : tuple of 4 floats - position of the rectangle that will be divided - """ - self._pos = pos - - def get_position(self): - """Return the position of the rectangle.""" - return self._pos - - def set_anchor(self, anchor): - """ - Parameters - ---------- - anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \ -'NW', 'W'} - Either an (*x*, *y*) pair of relative coordinates (0 is left or - bottom, 1 is right or top), 'C' (center), or a cardinal direction - ('SW', southwest, is bottom left, etc.). - - See Also - -------- - .Axes.set_anchor - """ - if isinstance(anchor, str): - _api.check_in_list(mtransforms.Bbox.coefs, anchor=anchor) - elif not isinstance(anchor, (tuple, list)) or len(anchor) != 2: - raise TypeError("anchor must be str or 2-tuple") - self._anchor = anchor - - def get_anchor(self): - """Return the anchor.""" - return self._anchor - - def get_subplotspec(self): - return None - - def set_horizontal(self, h): - """ - Parameters - ---------- - h : list of :mod:`~mpl_toolkits.axes_grid1.axes_size` - sizes for horizontal division - """ - self._horizontal = h - - def get_horizontal(self): - """Return horizontal sizes.""" - return self._horizontal - - def set_vertical(self, v): - """ - Parameters - ---------- - v : list of :mod:`~mpl_toolkits.axes_grid1.axes_size` - sizes for vertical division - """ - self._vertical = v - - def get_vertical(self): - """Return vertical sizes.""" - return self._vertical - - def set_aspect(self, aspect=False): - """ - Parameters - ---------- - aspect : bool - """ - self._aspect = aspect - - def get_aspect(self): - """Return aspect.""" - return self._aspect - - def set_locator(self, _locator): - self._locator = _locator - - def get_locator(self): - return self._locator - - def get_position_runtime(self, ax, renderer): - if self._locator is None: - return self.get_position() - else: - return self._locator(ax, renderer).bounds - - @staticmethod - def _calc_k(sizes, total): - # sizes is a (n, 2) array of (rel_size, abs_size); this method finds - # the k factor such that sum(rel_size * k + abs_size) == total. - rel_sum, abs_sum = sizes.sum(0) - return (total - abs_sum) / rel_sum if rel_sum else 0 - - @staticmethod - def _calc_offsets(sizes, k): - # Apply k factors to (n, 2) sizes array of (rel_size, abs_size); return - # the resulting cumulative offset positions. - return np.cumsum([0, *(sizes @ [k, 1])]) - - def new_locator(self, nx, ny, nx1=None, ny1=None): - """ - Return an axes locator callable for the specified cell. - - Parameters - ---------- - nx, nx1 : int - Integers specifying the column-position of the - cell. When *nx1* is None, a single *nx*-th column is - specified. Otherwise, location of columns spanning between *nx* - to *nx1* (but excluding *nx1*-th column) is specified. - ny, ny1 : int - Same as *nx* and *nx1*, but for row positions. - """ - if nx1 is None: - nx1 = nx + 1 - if ny1 is None: - ny1 = ny + 1 - # append_size("left") adds a new size at the beginning of the - # horizontal size lists; this shift transforms e.g. - # new_locator(nx=2, ...) into effectively new_locator(nx=3, ...). To - # take that into account, instead of recording nx, we record - # nx-self._xrefindex, where _xrefindex is shifted by 1 by each - # append_size("left"), and re-add self._xrefindex back to nx in - # _locate, when the actual axes position is computed. Ditto for y. - xref = self._xrefindex - yref = self._yrefindex - locator = functools.partial( - self._locate, nx - xref, ny - yref, nx1 - xref, ny1 - yref) - locator.get_subplotspec = self.get_subplotspec - return locator - - @_api.deprecated( - "3.8", alternative="divider.new_locator(...)(ax, renderer)") - def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): - """ - Implementation of ``divider.new_locator().__call__``. - - Parameters - ---------- - nx, nx1 : int - Integers specifying the column-position of the cell. When *nx1* is - None, a single *nx*-th column is specified. Otherwise, the - location of columns spanning between *nx* to *nx1* (but excluding - *nx1*-th column) is specified. - ny, ny1 : int - Same as *nx* and *nx1*, but for row positions. - axes - renderer - """ - xref = self._xrefindex - yref = self._yrefindex - return self._locate( - nx - xref, (nx + 1 if nx1 is None else nx1) - xref, - ny - yref, (ny + 1 if ny1 is None else ny1) - yref, - axes, renderer) - - def _locate(self, nx, ny, nx1, ny1, axes, renderer): - """ - Implementation of ``divider.new_locator().__call__``. - - The axes locator callable returned by ``new_locator()`` is created as - a `functools.partial` of this method with *nx*, *ny*, *nx1*, and *ny1* - specifying the requested cell. - """ - nx += self._xrefindex - nx1 += self._xrefindex - ny += self._yrefindex - ny1 += self._yrefindex - - fig_w, fig_h = self._fig.bbox.size / self._fig.dpi - x, y, w, h = self.get_position_runtime(axes, renderer) - - hsizes = self.get_horizontal_sizes(renderer) - vsizes = self.get_vertical_sizes(renderer) - k_h = self._calc_k(hsizes, fig_w * w) - k_v = self._calc_k(vsizes, fig_h * h) - - if self.get_aspect(): - k = min(k_h, k_v) - ox = self._calc_offsets(hsizes, k) - oy = self._calc_offsets(vsizes, k) - - ww = (ox[-1] - ox[0]) / fig_w - hh = (oy[-1] - oy[0]) / fig_h - pb = mtransforms.Bbox.from_bounds(x, y, w, h) - pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh) - x0, y0 = pb1.anchored(self.get_anchor(), pb).p0 - - else: - ox = self._calc_offsets(hsizes, k_h) - oy = self._calc_offsets(vsizes, k_v) - x0, y0 = x, y - - if nx1 is None: - nx1 = -1 - if ny1 is None: - ny1 = -1 - - x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w - y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h - - return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) - - def append_size(self, position, size): - _api.check_in_list(["left", "right", "bottom", "top"], - position=position) - if position == "left": - self._horizontal.insert(0, size) - self._xrefindex += 1 - elif position == "right": - self._horizontal.append(size) - elif position == "bottom": - self._vertical.insert(0, size) - self._yrefindex += 1 - else: # 'top' - self._vertical.append(size) - - def add_auto_adjustable_area(self, use_axes, pad=0.1, adjust_dirs=None): - """ - Add auto-adjustable padding around *use_axes* to take their decorations - (title, labels, ticks, ticklabels) into account during layout. - - Parameters - ---------- - use_axes : `~matplotlib.axes.Axes` or list of `~matplotlib.axes.Axes` - The Axes whose decorations are taken into account. - pad : float, default: 0.1 - Additional padding in inches. - adjust_dirs : list of {"left", "right", "bottom", "top"}, optional - The sides where padding is added; defaults to all four sides. - """ - if adjust_dirs is None: - adjust_dirs = ["left", "right", "bottom", "top"] - for d in adjust_dirs: - self.append_size(d, Size._AxesDecorationsSize(use_axes, d) + pad) - - -@_api.deprecated("3.8") -class AxesLocator: - """ - A callable object which returns the position and size of a given - `.AxesDivider` cell. - """ - - def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None): - """ - Parameters - ---------- - axes_divider : `~mpl_toolkits.axes_grid1.axes_divider.AxesDivider` - nx, nx1 : int - Integers specifying the column-position of the - cell. When *nx1* is None, a single *nx*-th column is - specified. Otherwise, location of columns spanning between *nx* - to *nx1* (but excluding *nx1*-th column) is specified. - ny, ny1 : int - Same as *nx* and *nx1*, but for row positions. - """ - self._axes_divider = axes_divider - - _xrefindex = axes_divider._xrefindex - _yrefindex = axes_divider._yrefindex - - self._nx, self._ny = nx - _xrefindex, ny - _yrefindex - - if nx1 is None: - nx1 = len(self._axes_divider) - if ny1 is None: - ny1 = len(self._axes_divider[0]) - - self._nx1 = nx1 - _xrefindex - self._ny1 = ny1 - _yrefindex - - def __call__(self, axes, renderer): - - _xrefindex = self._axes_divider._xrefindex - _yrefindex = self._axes_divider._yrefindex - - return self._axes_divider.locate(self._nx + _xrefindex, - self._ny + _yrefindex, - self._nx1 + _xrefindex, - self._ny1 + _yrefindex, - axes, - renderer) - - def get_subplotspec(self): - return self._axes_divider.get_subplotspec() - - -class SubplotDivider(Divider): - """ - The Divider class whose rectangle area is specified as a subplot geometry. - """ - - def __init__(self, fig, *args, horizontal=None, vertical=None, - aspect=None, anchor='C'): - """ - Parameters - ---------- - fig : `~matplotlib.figure.Figure` - - *args : tuple (*nrows*, *ncols*, *index*) or int - The array of subplots in the figure has dimensions ``(nrows, - ncols)``, and *index* is the index of the subplot being created. - *index* starts at 1 in the upper left corner and increases to the - right. - - If *nrows*, *ncols*, and *index* are all single digit numbers, then - *args* can be passed as a single 3-digit number (e.g. 234 for - (2, 3, 4)). - horizontal : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`, optional - Sizes for horizontal division. - vertical : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`, optional - Sizes for vertical division. - aspect : bool, optional - Whether overall rectangular area is reduced so that the relative - part of the horizontal and vertical scales have the same scale. - anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \ -'NW', 'W'}, default: 'C' - Placement of the reduced rectangle, when *aspect* is True. - """ - self.figure = fig - super().__init__(fig, [0, 0, 1, 1], - horizontal=horizontal or [], vertical=vertical or [], - aspect=aspect, anchor=anchor) - self.set_subplotspec(SubplotSpec._from_subplot_args(fig, args)) - - def get_position(self): - """Return the bounds of the subplot box.""" - return self.get_subplotspec().get_position(self.figure).bounds - - def get_subplotspec(self): - """Get the SubplotSpec instance.""" - return self._subplotspec - - def set_subplotspec(self, subplotspec): - """Set the SubplotSpec instance.""" - self._subplotspec = subplotspec - self.set_position(subplotspec.get_position(self.figure)) - - -class AxesDivider(Divider): - """ - Divider based on the preexisting axes. - """ - - def __init__(self, axes, xref=None, yref=None): - """ - Parameters - ---------- - axes : :class:`~matplotlib.axes.Axes` - xref - yref - """ - self._axes = axes - if xref is None: - self._xref = Size.AxesX(axes) - else: - self._xref = xref - if yref is None: - self._yref = Size.AxesY(axes) - else: - self._yref = yref - - super().__init__(fig=axes.get_figure(), pos=None, - horizontal=[self._xref], vertical=[self._yref], - aspect=None, anchor="C") - - def _get_new_axes(self, *, axes_class=None, **kwargs): - axes = self._axes - if axes_class is None: - axes_class = type(axes) - return axes_class(axes.get_figure(), axes.get_position(original=True), - **kwargs) - - def new_horizontal(self, size, pad=None, pack_start=False, **kwargs): - """ - Helper method for ``append_axes("left")`` and ``append_axes("right")``. - - See the documentation of `append_axes` for more details. - - :meta private: - """ - if pad is None: - pad = mpl.rcParams["figure.subplot.wspace"] * self._xref - pos = "left" if pack_start else "right" - if pad: - if not isinstance(pad, Size._Base): - pad = Size.from_any(pad, fraction_ref=self._xref) - self.append_size(pos, pad) - if not isinstance(size, Size._Base): - size = Size.from_any(size, fraction_ref=self._xref) - self.append_size(pos, size) - locator = self.new_locator( - nx=0 if pack_start else len(self._horizontal) - 1, - ny=self._yrefindex) - ax = self._get_new_axes(**kwargs) - ax.set_axes_locator(locator) - return ax - - def new_vertical(self, size, pad=None, pack_start=False, **kwargs): - """ - Helper method for ``append_axes("top")`` and ``append_axes("bottom")``. - - See the documentation of `append_axes` for more details. - - :meta private: - """ - if pad is None: - pad = mpl.rcParams["figure.subplot.hspace"] * self._yref - pos = "bottom" if pack_start else "top" - if pad: - if not isinstance(pad, Size._Base): - pad = Size.from_any(pad, fraction_ref=self._yref) - self.append_size(pos, pad) - if not isinstance(size, Size._Base): - size = Size.from_any(size, fraction_ref=self._yref) - self.append_size(pos, size) - locator = self.new_locator( - nx=self._xrefindex, - ny=0 if pack_start else len(self._vertical) - 1) - ax = self._get_new_axes(**kwargs) - ax.set_axes_locator(locator) - return ax - - def append_axes(self, position, size, pad=None, *, axes_class=None, - **kwargs): - """ - Add a new axes on a given side of the main axes. - - Parameters - ---------- - position : {"left", "right", "bottom", "top"} - Where the new axes is positioned relative to the main axes. - size : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str - The axes width or height. float or str arguments are interpreted - as ``axes_size.from_any(size, AxesX(<main_axes>))`` for left or - right axes, and likewise with ``AxesY`` for bottom or top axes. - pad : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str - Padding between the axes. float or str arguments are interpreted - as for *size*. Defaults to :rc:`figure.subplot.wspace` times the - main Axes width (left or right axes) or :rc:`figure.subplot.hspace` - times the main Axes height (bottom or top axes). - axes_class : subclass type of `~.axes.Axes`, optional - The type of the new axes. Defaults to the type of the main axes. - **kwargs - All extra keywords arguments are passed to the created axes. - """ - create_axes, pack_start = _api.check_getitem({ - "left": (self.new_horizontal, True), - "right": (self.new_horizontal, False), - "bottom": (self.new_vertical, True), - "top": (self.new_vertical, False), - }, position=position) - ax = create_axes( - size, pad, pack_start=pack_start, axes_class=axes_class, **kwargs) - self._fig.add_axes(ax) - return ax - - def get_aspect(self): - if self._aspect is None: - aspect = self._axes.get_aspect() - if aspect == "auto": - return False - else: - return True - else: - return self._aspect - - def get_position(self): - if self._pos is None: - bbox = self._axes.get_position(original=True) - return bbox.bounds - else: - return self._pos - - def get_anchor(self): - if self._anchor is None: - return self._axes.get_anchor() - else: - return self._anchor - - def get_subplotspec(self): - return self._axes.get_subplotspec() - - -# Helper for HBoxDivider/VBoxDivider. -# The variable names are written for a horizontal layout, but the calculations -# work identically for vertical layouts. -def _locate(x, y, w, h, summed_widths, equal_heights, fig_w, fig_h, anchor): - - total_width = fig_w * w - max_height = fig_h * h - - # Determine the k factors. - n = len(equal_heights) - eq_rels, eq_abss = equal_heights.T - sm_rels, sm_abss = summed_widths.T - A = np.diag([*eq_rels, 0]) - A[:n, -1] = -1 - A[-1, :-1] = sm_rels - B = [*(-eq_abss), total_width - sm_abss.sum()] - # A @ K = B: This finds factors {k_0, ..., k_{N-1}, H} so that - # eq_rel_i * k_i + eq_abs_i = H for all i: all axes have the same height - # sum(sm_rel_i * k_i + sm_abs_i) = total_width: fixed total width - # (foo_rel_i * k_i + foo_abs_i will end up being the size of foo.) - *karray, height = np.linalg.solve(A, B) - if height > max_height: # Additionally, upper-bound the height. - karray = (max_height - eq_abss) / eq_rels - - # Compute the offsets corresponding to these factors. - ox = np.cumsum([0, *(sm_rels * karray + sm_abss)]) - ww = (ox[-1] - ox[0]) / fig_w - h0_rel, h0_abs = equal_heights[0] - hh = (karray[0]*h0_rel + h0_abs) / fig_h - pb = mtransforms.Bbox.from_bounds(x, y, w, h) - pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh) - x0, y0 = pb1.anchored(anchor, pb).p0 - - return x0, y0, ox, hh - - -class HBoxDivider(SubplotDivider): - """ - A `.SubplotDivider` for laying out axes horizontally, while ensuring that - they have equal heights. - - Examples - -------- - .. plot:: gallery/axes_grid1/demo_axes_hbox_divider.py - """ - - def new_locator(self, nx, nx1=None): - """ - Create an axes locator callable for the specified cell. - - Parameters - ---------- - nx, nx1 : int - Integers specifying the column-position of the - cell. When *nx1* is None, a single *nx*-th column is - specified. Otherwise, location of columns spanning between *nx* - to *nx1* (but excluding *nx1*-th column) is specified. - """ - return super().new_locator(nx, 0, nx1, 0) - - def _locate(self, nx, ny, nx1, ny1, axes, renderer): - # docstring inherited - nx += self._xrefindex - nx1 += self._xrefindex - fig_w, fig_h = self._fig.bbox.size / self._fig.dpi - x, y, w, h = self.get_position_runtime(axes, renderer) - summed_ws = self.get_horizontal_sizes(renderer) - equal_hs = self.get_vertical_sizes(renderer) - x0, y0, ox, hh = _locate( - x, y, w, h, summed_ws, equal_hs, fig_w, fig_h, self.get_anchor()) - if nx1 is None: - nx1 = -1 - x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w - y1, h1 = y0, hh - return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) - - -class VBoxDivider(SubplotDivider): - """ - A `.SubplotDivider` for laying out axes vertically, while ensuring that - they have equal widths. - """ - - def new_locator(self, ny, ny1=None): - """ - Create an axes locator callable for the specified cell. - - Parameters - ---------- - ny, ny1 : int - Integers specifying the row-position of the - cell. When *ny1* is None, a single *ny*-th row is - specified. Otherwise, location of rows spanning between *ny* - to *ny1* (but excluding *ny1*-th row) is specified. - """ - return super().new_locator(0, ny, 0, ny1) - - def _locate(self, nx, ny, nx1, ny1, axes, renderer): - # docstring inherited - ny += self._yrefindex - ny1 += self._yrefindex - fig_w, fig_h = self._fig.bbox.size / self._fig.dpi - x, y, w, h = self.get_position_runtime(axes, renderer) - summed_hs = self.get_vertical_sizes(renderer) - equal_ws = self.get_horizontal_sizes(renderer) - y0, x0, oy, ww = _locate( - y, x, h, w, summed_hs, equal_ws, fig_h, fig_w, self.get_anchor()) - if ny1 is None: - ny1 = -1 - x1, w1 = x0, ww - y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h - return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) - - -def make_axes_locatable(axes): - divider = AxesDivider(axes) - locator = divider.new_locator(nx=0, ny=0) - axes.set_axes_locator(locator) - - return divider - - -def make_axes_area_auto_adjustable( - ax, use_axes=None, pad=0.1, adjust_dirs=None): - """ - Add auto-adjustable padding around *ax* to take its decorations (title, - labels, ticks, ticklabels) into account during layout, using - `.Divider.add_auto_adjustable_area`. - - By default, padding is determined from the decorations of *ax*. - Pass *use_axes* to consider the decorations of other Axes instead. - """ - if adjust_dirs is None: - adjust_dirs = ["left", "right", "bottom", "top"] - divider = make_axes_locatable(ax) - if use_axes is None: - use_axes = ax - divider.add_auto_adjustable_area(use_axes=use_axes, pad=pad, - adjust_dirs=adjust_dirs) diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_grid.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_grid.py deleted file mode 100644 index 720d985414f..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_grid.py +++ /dev/null @@ -1,550 +0,0 @@ -from numbers import Number -import functools -from types import MethodType - -import numpy as np - -from matplotlib import _api, cbook -from matplotlib.gridspec import SubplotSpec - -from .axes_divider import Size, SubplotDivider, Divider -from .mpl_axes import Axes, SimpleAxisArtist - - -class CbarAxesBase: - def __init__(self, *args, orientation, **kwargs): - self.orientation = orientation - super().__init__(*args, **kwargs) - - def colorbar(self, mappable, **kwargs): - return self.figure.colorbar( - mappable, cax=self, location=self.orientation, **kwargs) - - @_api.deprecated("3.8", alternative="ax.tick_params and colorbar.set_label") - def toggle_label(self, b): - axis = self.axis[self.orientation] - axis.toggle(ticklabels=b, label=b) - - -_cbaraxes_class_factory = cbook._make_class_factory(CbarAxesBase, "Cbar{}") - - -class Grid: - """ - A grid of Axes. - - In Matplotlib, the Axes location (and size) is specified in normalized - figure coordinates. This may not be ideal for images that needs to be - displayed with a given aspect ratio; for example, it is difficult to - display multiple images of a same size with some fixed padding between - them. AxesGrid can be used in such case. - """ - - _defaultAxesClass = Axes - - def __init__(self, fig, - rect, - nrows_ncols, - ngrids=None, - direction="row", - axes_pad=0.02, - *, - share_all=False, - share_x=True, - share_y=True, - label_mode="L", - axes_class=None, - aspect=False, - ): - """ - Parameters - ---------- - fig : `.Figure` - The parent figure. - rect : (float, float, float, float), (int, int, int), int, or \ - `~.SubplotSpec` - The axes position, as a ``(left, bottom, width, height)`` tuple, - as a three-digit subplot position code (e.g., ``(1, 2, 1)`` or - ``121``), or as a `~.SubplotSpec`. - nrows_ncols : (int, int) - Number of rows and columns in the grid. - ngrids : int or None, default: None - If not None, only the first *ngrids* axes in the grid are created. - direction : {"row", "column"}, default: "row" - Whether axes are created in row-major ("row by row") or - column-major order ("column by column"). This also affects the - order in which axes are accessed using indexing (``grid[index]``). - axes_pad : float or (float, float), default: 0.02 - Padding or (horizontal padding, vertical padding) between axes, in - inches. - share_all : bool, default: False - Whether all axes share their x- and y-axis. Overrides *share_x* - and *share_y*. - share_x : bool, default: True - Whether all axes of a column share their x-axis. - share_y : bool, default: True - Whether all axes of a row share their y-axis. - label_mode : {"L", "1", "all", "keep"}, default: "L" - Determines which axes will get tick labels: - - - "L": All axes on the left column get vertical tick labels; - all axes on the bottom row get horizontal tick labels. - - "1": Only the bottom left axes is labelled. - - "all": All axes are labelled. - - "keep": Do not do anything. - - axes_class : subclass of `matplotlib.axes.Axes`, default: None - aspect : bool, default: False - Whether the axes aspect ratio follows the aspect ratio of the data - limits. - """ - self._nrows, self._ncols = nrows_ncols - - if ngrids is None: - ngrids = self._nrows * self._ncols - else: - if not 0 < ngrids <= self._nrows * self._ncols: - raise ValueError( - "ngrids must be positive and not larger than nrows*ncols") - - self.ngrids = ngrids - - self._horiz_pad_size, self._vert_pad_size = map( - Size.Fixed, np.broadcast_to(axes_pad, 2)) - - _api.check_in_list(["column", "row"], direction=direction) - self._direction = direction - - if axes_class is None: - axes_class = self._defaultAxesClass - elif isinstance(axes_class, (list, tuple)): - cls, kwargs = axes_class - axes_class = functools.partial(cls, **kwargs) - - kw = dict(horizontal=[], vertical=[], aspect=aspect) - if isinstance(rect, (Number, SubplotSpec)): - self._divider = SubplotDivider(fig, rect, **kw) - elif len(rect) == 3: - self._divider = SubplotDivider(fig, *rect, **kw) - elif len(rect) == 4: - self._divider = Divider(fig, rect, **kw) - else: - raise TypeError("Incorrect rect format") - - rect = self._divider.get_position() - - axes_array = np.full((self._nrows, self._ncols), None, dtype=object) - for i in range(self.ngrids): - col, row = self._get_col_row(i) - if share_all: - sharex = sharey = axes_array[0, 0] - else: - sharex = axes_array[0, col] if share_x else None - sharey = axes_array[row, 0] if share_y else None - axes_array[row, col] = axes_class( - fig, rect, sharex=sharex, sharey=sharey) - self.axes_all = axes_array.ravel( - order="C" if self._direction == "row" else "F").tolist() - self.axes_column = axes_array.T.tolist() - self.axes_row = axes_array.tolist() - self.axes_llc = self.axes_column[0][-1] - - self._init_locators() - - for ax in self.axes_all: - fig.add_axes(ax) - - self.set_label_mode(label_mode) - - def _init_locators(self): - self._divider.set_horizontal( - [Size.Scaled(1), self._horiz_pad_size] * (self._ncols-1) + [Size.Scaled(1)]) - self._divider.set_vertical( - [Size.Scaled(1), self._vert_pad_size] * (self._nrows-1) + [Size.Scaled(1)]) - for i in range(self.ngrids): - col, row = self._get_col_row(i) - self.axes_all[i].set_axes_locator( - self._divider.new_locator(nx=2 * col, ny=2 * (self._nrows - 1 - row))) - - def _get_col_row(self, n): - if self._direction == "column": - col, row = divmod(n, self._nrows) - else: - row, col = divmod(n, self._ncols) - - return col, row - - # Good to propagate __len__ if we have __getitem__ - def __len__(self): - return len(self.axes_all) - - def __getitem__(self, i): - return self.axes_all[i] - - def get_geometry(self): - """ - Return the number of rows and columns of the grid as (nrows, ncols). - """ - return self._nrows, self._ncols - - def set_axes_pad(self, axes_pad): - """ - Set the padding between the axes. - - Parameters - ---------- - axes_pad : (float, float) - The padding (horizontal pad, vertical pad) in inches. - """ - self._horiz_pad_size.fixed_size = axes_pad[0] - self._vert_pad_size.fixed_size = axes_pad[1] - - def get_axes_pad(self): - """ - Return the axes padding. - - Returns - ------- - hpad, vpad - Padding (horizontal pad, vertical pad) in inches. - """ - return (self._horiz_pad_size.fixed_size, - self._vert_pad_size.fixed_size) - - def set_aspect(self, aspect): - """Set the aspect of the SubplotDivider.""" - self._divider.set_aspect(aspect) - - def get_aspect(self): - """Return the aspect of the SubplotDivider.""" - return self._divider.get_aspect() - - def set_label_mode(self, mode): - """ - Define which axes have tick labels. - - Parameters - ---------- - mode : {"L", "1", "all", "keep"} - The label mode: - - - "L": All axes on the left column get vertical tick labels; - all axes on the bottom row get horizontal tick labels. - - "1": Only the bottom left axes is labelled. - - "all": All axes are labelled. - - "keep": Do not do anything. - """ - is_last_row, is_first_col = ( - np.mgrid[:self._nrows, :self._ncols] == [[[self._nrows - 1]], [[0]]]) - if mode == "all": - bottom = left = np.full((self._nrows, self._ncols), True) - elif mode == "L": - bottom = is_last_row - left = is_first_col - elif mode == "1": - bottom = left = is_last_row & is_first_col - else: - # Use _api.check_in_list at the top of the method when deprecation - # period expires - if mode != 'keep': - _api.warn_deprecated( - '3.7', name="Grid label_mode", - message='Passing an undefined label_mode is deprecated ' - 'since %(since)s and will become an error ' - '%(removal)s. To silence this warning, pass ' - '"keep", which gives the same behaviour.') - return - for i in range(self._nrows): - for j in range(self._ncols): - ax = self.axes_row[i][j] - if isinstance(ax.axis, MethodType): - bottom_axis = SimpleAxisArtist(ax.xaxis, 1, ax.spines["bottom"]) - left_axis = SimpleAxisArtist(ax.yaxis, 1, ax.spines["left"]) - else: - bottom_axis = ax.axis["bottom"] - left_axis = ax.axis["left"] - bottom_axis.toggle(ticklabels=bottom[i, j], label=bottom[i, j]) - left_axis.toggle(ticklabels=left[i, j], label=left[i, j]) - - def get_divider(self): - return self._divider - - def set_axes_locator(self, locator): - self._divider.set_locator(locator) - - def get_axes_locator(self): - return self._divider.get_locator() - - -class ImageGrid(Grid): - """ - A grid of Axes for Image display. - - This class is a specialization of `~.axes_grid1.axes_grid.Grid` for displaying a - grid of images. In particular, it forces all axes in a column to share their x-axis - and all axes in a row to share their y-axis. It further provides helpers to add - colorbars to some or all axes. - """ - - def __init__(self, fig, - rect, - nrows_ncols, - ngrids=None, - direction="row", - axes_pad=0.02, - *, - share_all=False, - aspect=True, - label_mode="L", - cbar_mode=None, - cbar_location="right", - cbar_pad=None, - cbar_size="5%", - cbar_set_cax=True, - axes_class=None, - ): - """ - Parameters - ---------- - fig : `.Figure` - The parent figure. - rect : (float, float, float, float) or int - The axes position, as a ``(left, bottom, width, height)`` tuple or - as a three-digit subplot position code (e.g., "121"). - nrows_ncols : (int, int) - Number of rows and columns in the grid. - ngrids : int or None, default: None - If not None, only the first *ngrids* axes in the grid are created. - direction : {"row", "column"}, default: "row" - Whether axes are created in row-major ("row by row") or - column-major order ("column by column"). This also affects the - order in which axes are accessed using indexing (``grid[index]``). - axes_pad : float or (float, float), default: 0.02in - Padding or (horizontal padding, vertical padding) between axes, in - inches. - share_all : bool, default: False - Whether all axes share their x- and y-axis. Note that in any case, - all axes in a column share their x-axis and all axes in a row share - their y-axis. - aspect : bool, default: True - Whether the axes aspect ratio follows the aspect ratio of the data - limits. - label_mode : {"L", "1", "all"}, default: "L" - Determines which axes will get tick labels: - - - "L": All axes on the left column get vertical tick labels; - all axes on the bottom row get horizontal tick labels. - - "1": Only the bottom left axes is labelled. - - "all": all axes are labelled. - - cbar_mode : {"each", "single", "edge", None}, default: None - Whether to create a colorbar for "each" axes, a "single" colorbar - for the entire grid, colorbars only for axes on the "edge" - determined by *cbar_location*, or no colorbars. The colorbars are - stored in the :attr:`cbar_axes` attribute. - cbar_location : {"left", "right", "bottom", "top"}, default: "right" - cbar_pad : float, default: None - Padding between the image axes and the colorbar axes. - cbar_size : size specification (see `.Size.from_any`), default: "5%" - Colorbar size. - cbar_set_cax : bool, default: True - If True, each axes in the grid has a *cax* attribute that is bound - to associated *cbar_axes*. - axes_class : subclass of `matplotlib.axes.Axes`, default: None - """ - _api.check_in_list(["each", "single", "edge", None], - cbar_mode=cbar_mode) - _api.check_in_list(["left", "right", "bottom", "top"], - cbar_location=cbar_location) - self._colorbar_mode = cbar_mode - self._colorbar_location = cbar_location - self._colorbar_pad = cbar_pad - self._colorbar_size = cbar_size - # The colorbar axes are created in _init_locators(). - - super().__init__( - fig, rect, nrows_ncols, ngrids, - direction=direction, axes_pad=axes_pad, - share_all=share_all, share_x=True, share_y=True, aspect=aspect, - label_mode=label_mode, axes_class=axes_class) - - for ax in self.cbar_axes: - fig.add_axes(ax) - - if cbar_set_cax: - if self._colorbar_mode == "single": - for ax in self.axes_all: - ax.cax = self.cbar_axes[0] - elif self._colorbar_mode == "edge": - for index, ax in enumerate(self.axes_all): - col, row = self._get_col_row(index) - if self._colorbar_location in ("left", "right"): - ax.cax = self.cbar_axes[row] - else: - ax.cax = self.cbar_axes[col] - else: - for ax, cax in zip(self.axes_all, self.cbar_axes): - ax.cax = cax - - def _init_locators(self): - # Slightly abusing this method to inject colorbar creation into init. - - if self._colorbar_pad is None: - # horizontal or vertical arrangement? - if self._colorbar_location in ("left", "right"): - self._colorbar_pad = self._horiz_pad_size.fixed_size - else: - self._colorbar_pad = self._vert_pad_size.fixed_size - self.cbar_axes = [ - _cbaraxes_class_factory(self._defaultAxesClass)( - self.axes_all[0].figure, self._divider.get_position(), - orientation=self._colorbar_location) - for _ in range(self.ngrids)] - - cb_mode = self._colorbar_mode - cb_location = self._colorbar_location - - h = [] - v = [] - - h_ax_pos = [] - h_cb_pos = [] - if cb_mode == "single" and cb_location in ("left", "bottom"): - if cb_location == "left": - sz = self._nrows * Size.AxesX(self.axes_llc) - h.append(Size.from_any(self._colorbar_size, sz)) - h.append(Size.from_any(self._colorbar_pad, sz)) - locator = self._divider.new_locator(nx=0, ny=0, ny1=-1) - elif cb_location == "bottom": - sz = self._ncols * Size.AxesY(self.axes_llc) - v.append(Size.from_any(self._colorbar_size, sz)) - v.append(Size.from_any(self._colorbar_pad, sz)) - locator = self._divider.new_locator(nx=0, nx1=-1, ny=0) - for i in range(self.ngrids): - self.cbar_axes[i].set_visible(False) - self.cbar_axes[0].set_axes_locator(locator) - self.cbar_axes[0].set_visible(True) - - for col, ax in enumerate(self.axes_row[0]): - if h: - h.append(self._horiz_pad_size) - - if ax: - sz = Size.AxesX(ax, aspect="axes", ref_ax=self.axes_all[0]) - else: - sz = Size.AxesX(self.axes_all[0], - aspect="axes", ref_ax=self.axes_all[0]) - - if (cb_location == "left" - and (cb_mode == "each" - or (cb_mode == "edge" and col == 0))): - h_cb_pos.append(len(h)) - h.append(Size.from_any(self._colorbar_size, sz)) - h.append(Size.from_any(self._colorbar_pad, sz)) - - h_ax_pos.append(len(h)) - h.append(sz) - - if (cb_location == "right" - and (cb_mode == "each" - or (cb_mode == "edge" and col == self._ncols - 1))): - h.append(Size.from_any(self._colorbar_pad, sz)) - h_cb_pos.append(len(h)) - h.append(Size.from_any(self._colorbar_size, sz)) - - v_ax_pos = [] - v_cb_pos = [] - for row, ax in enumerate(self.axes_column[0][::-1]): - if v: - v.append(self._vert_pad_size) - - if ax: - sz = Size.AxesY(ax, aspect="axes", ref_ax=self.axes_all[0]) - else: - sz = Size.AxesY(self.axes_all[0], - aspect="axes", ref_ax=self.axes_all[0]) - - if (cb_location == "bottom" - and (cb_mode == "each" - or (cb_mode == "edge" and row == 0))): - v_cb_pos.append(len(v)) - v.append(Size.from_any(self._colorbar_size, sz)) - v.append(Size.from_any(self._colorbar_pad, sz)) - - v_ax_pos.append(len(v)) - v.append(sz) - - if (cb_location == "top" - and (cb_mode == "each" - or (cb_mode == "edge" and row == self._nrows - 1))): - v.append(Size.from_any(self._colorbar_pad, sz)) - v_cb_pos.append(len(v)) - v.append(Size.from_any(self._colorbar_size, sz)) - - for i in range(self.ngrids): - col, row = self._get_col_row(i) - locator = self._divider.new_locator(nx=h_ax_pos[col], - ny=v_ax_pos[self._nrows-1-row]) - self.axes_all[i].set_axes_locator(locator) - - if cb_mode == "each": - if cb_location in ("right", "left"): - locator = self._divider.new_locator( - nx=h_cb_pos[col], ny=v_ax_pos[self._nrows - 1 - row]) - - elif cb_location in ("top", "bottom"): - locator = self._divider.new_locator( - nx=h_ax_pos[col], ny=v_cb_pos[self._nrows - 1 - row]) - - self.cbar_axes[i].set_axes_locator(locator) - elif cb_mode == "edge": - if (cb_location == "left" and col == 0 - or cb_location == "right" and col == self._ncols - 1): - locator = self._divider.new_locator( - nx=h_cb_pos[0], ny=v_ax_pos[self._nrows - 1 - row]) - self.cbar_axes[row].set_axes_locator(locator) - elif (cb_location == "bottom" and row == self._nrows - 1 - or cb_location == "top" and row == 0): - locator = self._divider.new_locator(nx=h_ax_pos[col], - ny=v_cb_pos[0]) - self.cbar_axes[col].set_axes_locator(locator) - - if cb_mode == "single": - if cb_location == "right": - sz = self._nrows * Size.AxesX(self.axes_llc) - h.append(Size.from_any(self._colorbar_pad, sz)) - h.append(Size.from_any(self._colorbar_size, sz)) - locator = self._divider.new_locator(nx=-2, ny=0, ny1=-1) - elif cb_location == "top": - sz = self._ncols * Size.AxesY(self.axes_llc) - v.append(Size.from_any(self._colorbar_pad, sz)) - v.append(Size.from_any(self._colorbar_size, sz)) - locator = self._divider.new_locator(nx=0, nx1=-1, ny=-2) - if cb_location in ("right", "top"): - for i in range(self.ngrids): - self.cbar_axes[i].set_visible(False) - self.cbar_axes[0].set_axes_locator(locator) - self.cbar_axes[0].set_visible(True) - elif cb_mode == "each": - for i in range(self.ngrids): - self.cbar_axes[i].set_visible(True) - elif cb_mode == "edge": - if cb_location in ("right", "left"): - count = self._nrows - else: - count = self._ncols - for i in range(count): - self.cbar_axes[i].set_visible(True) - for j in range(i + 1, self.ngrids): - self.cbar_axes[j].set_visible(False) - else: - for i in range(self.ngrids): - self.cbar_axes[i].set_visible(False) - self.cbar_axes[i].set_position([1., 1., 0.001, 0.001], - which="active") - - self._divider.set_horizontal(h) - self._divider.set_vertical(v) - - -AxesGrid = ImageGrid diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_rgb.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_rgb.py deleted file mode 100644 index 52fd707e870..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_rgb.py +++ /dev/null @@ -1,157 +0,0 @@ -from types import MethodType - -import numpy as np - -from .axes_divider import make_axes_locatable, Size -from .mpl_axes import Axes, SimpleAxisArtist - - -def make_rgb_axes(ax, pad=0.01, axes_class=None, **kwargs): - """ - Parameters - ---------- - ax : `~matplotlib.axes.Axes` - Axes instance to create the RGB Axes in. - pad : float, optional - Fraction of the Axes height to pad. - axes_class : `matplotlib.axes.Axes` or None, optional - Axes class to use for the R, G, and B Axes. If None, use - the same class as *ax*. - **kwargs - Forwarded to *axes_class* init for the R, G, and B Axes. - """ - - divider = make_axes_locatable(ax) - - pad_size = pad * Size.AxesY(ax) - - xsize = ((1-2*pad)/3) * Size.AxesX(ax) - ysize = ((1-2*pad)/3) * Size.AxesY(ax) - - divider.set_horizontal([Size.AxesX(ax), pad_size, xsize]) - divider.set_vertical([ysize, pad_size, ysize, pad_size, ysize]) - - ax.set_axes_locator(divider.new_locator(0, 0, ny1=-1)) - - ax_rgb = [] - if axes_class is None: - axes_class = type(ax) - - for ny in [4, 2, 0]: - ax1 = axes_class(ax.get_figure(), ax.get_position(original=True), - sharex=ax, sharey=ax, **kwargs) - locator = divider.new_locator(nx=2, ny=ny) - ax1.set_axes_locator(locator) - for t in ax1.yaxis.get_ticklabels() + ax1.xaxis.get_ticklabels(): - t.set_visible(False) - try: - for axis in ax1.axis.values(): - axis.major_ticklabels.set_visible(False) - except AttributeError: - pass - - ax_rgb.append(ax1) - - fig = ax.get_figure() - for ax1 in ax_rgb: - fig.add_axes(ax1) - - return ax_rgb - - -class RGBAxes: - """ - 4-panel `~.Axes.imshow` (RGB, R, G, B). - - Layout:: - - ┌───────────────┬─────┐ - │ │ R │ - │ ├─────┤ - │ RGB │ G │ - │ ├─────┤ - │ │ B │ - └───────────────┴─────┘ - - Subclasses can override the ``_defaultAxesClass`` attribute. - By default RGBAxes uses `.mpl_axes.Axes`. - - Attributes - ---------- - RGB : ``_defaultAxesClass`` - The Axes object for the three-channel `~.Axes.imshow`. - R : ``_defaultAxesClass`` - The Axes object for the red channel `~.Axes.imshow`. - G : ``_defaultAxesClass`` - The Axes object for the green channel `~.Axes.imshow`. - B : ``_defaultAxesClass`` - The Axes object for the blue channel `~.Axes.imshow`. - """ - - _defaultAxesClass = Axes - - def __init__(self, *args, pad=0, **kwargs): - """ - Parameters - ---------- - pad : float, default: 0 - Fraction of the Axes height to put as padding. - axes_class : `~matplotlib.axes.Axes` - Axes class to use. If not provided, ``_defaultAxesClass`` is used. - *args - Forwarded to *axes_class* init for the RGB Axes - **kwargs - Forwarded to *axes_class* init for the RGB, R, G, and B Axes - """ - axes_class = kwargs.pop("axes_class", self._defaultAxesClass) - self.RGB = ax = axes_class(*args, **kwargs) - ax.get_figure().add_axes(ax) - self.R, self.G, self.B = make_rgb_axes( - ax, pad=pad, axes_class=axes_class, **kwargs) - # Set the line color and ticks for the axes. - for ax1 in [self.RGB, self.R, self.G, self.B]: - if isinstance(ax1.axis, MethodType): - ad = Axes.AxisDict(self) - ad.update( - bottom=SimpleAxisArtist(ax1.xaxis, 1, ax1.spines["bottom"]), - top=SimpleAxisArtist(ax1.xaxis, 2, ax1.spines["top"]), - left=SimpleAxisArtist(ax1.yaxis, 1, ax1.spines["left"]), - right=SimpleAxisArtist(ax1.yaxis, 2, ax1.spines["right"])) - else: - ad = ax1.axis - ad[:].line.set_color("w") - ad[:].major_ticks.set_markeredgecolor("w") - - def imshow_rgb(self, r, g, b, **kwargs): - """ - Create the four images {rgb, r, g, b}. - - Parameters - ---------- - r, g, b : array-like - The red, green, and blue arrays. - **kwargs - Forwarded to `~.Axes.imshow` calls for the four images. - - Returns - ------- - rgb : `~matplotlib.image.AxesImage` - r : `~matplotlib.image.AxesImage` - g : `~matplotlib.image.AxesImage` - b : `~matplotlib.image.AxesImage` - """ - if not (r.shape == g.shape == b.shape): - raise ValueError( - f'Input shapes ({r.shape}, {g.shape}, {b.shape}) do not match') - RGB = np.dstack([r, g, b]) - R = np.zeros_like(RGB) - R[:, :, 0] = r - G = np.zeros_like(RGB) - G[:, :, 1] = g - B = np.zeros_like(RGB) - B[:, :, 2] = b - im_rgb = self.RGB.imshow(RGB, **kwargs) - im_r = self.R.imshow(R, **kwargs) - im_g = self.G.imshow(G, **kwargs) - im_b = self.B.imshow(B, **kwargs) - return im_rgb, im_r, im_g, im_b diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_size.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_size.py deleted file mode 100644 index d2514720772..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_size.py +++ /dev/null @@ -1,248 +0,0 @@ -""" -Provides classes of simple units that will be used with `.AxesDivider` -class (or others) to determine the size of each Axes. The unit -classes define `get_size` method that returns a tuple of two floats, -meaning relative and absolute sizes, respectively. - -Note that this class is nothing more than a simple tuple of two -floats. Take a look at the Divider class to see how these two -values are used. -""" - -from numbers import Real - -from matplotlib import _api -from matplotlib.axes import Axes - - -class _Base: - def __rmul__(self, other): - return Fraction(other, self) - - def __add__(self, other): - if isinstance(other, _Base): - return Add(self, other) - else: - return Add(self, Fixed(other)) - - def get_size(self, renderer): - """ - Return two-float tuple with relative and absolute sizes. - """ - raise NotImplementedError("Subclasses must implement") - - -class Add(_Base): - """ - Sum of two sizes. - """ - - def __init__(self, a, b): - self._a = a - self._b = b - - def get_size(self, renderer): - a_rel_size, a_abs_size = self._a.get_size(renderer) - b_rel_size, b_abs_size = self._b.get_size(renderer) - return a_rel_size + b_rel_size, a_abs_size + b_abs_size - - -class Fixed(_Base): - """ - Simple fixed size with absolute part = *fixed_size* and relative part = 0. - """ - - def __init__(self, fixed_size): - _api.check_isinstance(Real, fixed_size=fixed_size) - self.fixed_size = fixed_size - - def get_size(self, renderer): - rel_size = 0. - abs_size = self.fixed_size - return rel_size, abs_size - - -class Scaled(_Base): - """ - Simple scaled(?) size with absolute part = 0 and - relative part = *scalable_size*. - """ - - def __init__(self, scalable_size): - self._scalable_size = scalable_size - - def get_size(self, renderer): - rel_size = self._scalable_size - abs_size = 0. - return rel_size, abs_size - -Scalable = Scaled - - -def _get_axes_aspect(ax): - aspect = ax.get_aspect() - if aspect == "auto": - aspect = 1. - return aspect - - -class AxesX(_Base): - """ - Scaled size whose relative part corresponds to the data width - of the *axes* multiplied by the *aspect*. - """ - - def __init__(self, axes, aspect=1., ref_ax=None): - self._axes = axes - self._aspect = aspect - if aspect == "axes" and ref_ax is None: - raise ValueError("ref_ax must be set when aspect='axes'") - self._ref_ax = ref_ax - - def get_size(self, renderer): - l1, l2 = self._axes.get_xlim() - if self._aspect == "axes": - ref_aspect = _get_axes_aspect(self._ref_ax) - aspect = ref_aspect / _get_axes_aspect(self._axes) - else: - aspect = self._aspect - - rel_size = abs(l2-l1)*aspect - abs_size = 0. - return rel_size, abs_size - - -class AxesY(_Base): - """ - Scaled size whose relative part corresponds to the data height - of the *axes* multiplied by the *aspect*. - """ - - def __init__(self, axes, aspect=1., ref_ax=None): - self._axes = axes - self._aspect = aspect - if aspect == "axes" and ref_ax is None: - raise ValueError("ref_ax must be set when aspect='axes'") - self._ref_ax = ref_ax - - def get_size(self, renderer): - l1, l2 = self._axes.get_ylim() - - if self._aspect == "axes": - ref_aspect = _get_axes_aspect(self._ref_ax) - aspect = _get_axes_aspect(self._axes) - else: - aspect = self._aspect - - rel_size = abs(l2-l1)*aspect - abs_size = 0. - return rel_size, abs_size - - -class MaxExtent(_Base): - """ - Size whose absolute part is either the largest width or the largest height - of the given *artist_list*. - """ - - def __init__(self, artist_list, w_or_h): - self._artist_list = artist_list - _api.check_in_list(["width", "height"], w_or_h=w_or_h) - self._w_or_h = w_or_h - - def add_artist(self, a): - self._artist_list.append(a) - - def get_size(self, renderer): - rel_size = 0. - extent_list = [ - getattr(a.get_window_extent(renderer), self._w_or_h) / a.figure.dpi - for a in self._artist_list] - abs_size = max(extent_list, default=0) - return rel_size, abs_size - - -class MaxWidth(MaxExtent): - """ - Size whose absolute part is the largest width of the given *artist_list*. - """ - - def __init__(self, artist_list): - super().__init__(artist_list, "width") - - -class MaxHeight(MaxExtent): - """ - Size whose absolute part is the largest height of the given *artist_list*. - """ - - def __init__(self, artist_list): - super().__init__(artist_list, "height") - - -class Fraction(_Base): - """ - An instance whose size is a *fraction* of the *ref_size*. - - >>> s = Fraction(0.3, AxesX(ax)) - """ - - def __init__(self, fraction, ref_size): - _api.check_isinstance(Real, fraction=fraction) - self._fraction_ref = ref_size - self._fraction = fraction - - def get_size(self, renderer): - if self._fraction_ref is None: - return self._fraction, 0. - else: - r, a = self._fraction_ref.get_size(renderer) - rel_size = r*self._fraction - abs_size = a*self._fraction - return rel_size, abs_size - - -def from_any(size, fraction_ref=None): - """ - Create a Fixed unit when the first argument is a float, or a - Fraction unit if that is a string that ends with %. The second - argument is only meaningful when Fraction unit is created. - - >>> from mpl_toolkits.axes_grid1.axes_size import from_any - >>> a = from_any(1.2) # => Fixed(1.2) - >>> from_any("50%", a) # => Fraction(0.5, a) - """ - if isinstance(size, Real): - return Fixed(size) - elif isinstance(size, str): - if size[-1] == "%": - return Fraction(float(size[:-1]) / 100, fraction_ref) - raise ValueError("Unknown format") - - -class _AxesDecorationsSize(_Base): - """ - Fixed size, corresponding to the size of decorations on a given Axes side. - """ - - _get_size_map = { - "left": lambda tight_bb, axes_bb: axes_bb.xmin - tight_bb.xmin, - "right": lambda tight_bb, axes_bb: tight_bb.xmax - axes_bb.xmax, - "bottom": lambda tight_bb, axes_bb: axes_bb.ymin - tight_bb.ymin, - "top": lambda tight_bb, axes_bb: tight_bb.ymax - axes_bb.ymax, - } - - def __init__(self, ax, direction): - self._get_size = _api.check_getitem( - self._get_size_map, direction=direction) - self._ax_list = [ax] if isinstance(ax, Axes) else ax - - def get_size(self, renderer): - sz = max([ - self._get_size(ax.get_tightbbox(renderer, call_axes_locator=False), - ax.bbox) - for ax in self._ax_list]) - dpi = renderer.points_to_pixels(72) - abs_size = sz / dpi - rel_size = 0 - return rel_size, abs_size diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/inset_locator.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/inset_locator.py deleted file mode 100644 index 6d591a45311..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/inset_locator.py +++ /dev/null @@ -1,561 +0,0 @@ -""" -A collection of functions and objects for creating or placing inset axes. -""" - -from matplotlib import _api, _docstring -from matplotlib.offsetbox import AnchoredOffsetbox -from matplotlib.patches import Patch, Rectangle -from matplotlib.path import Path -from matplotlib.transforms import Bbox, BboxTransformTo -from matplotlib.transforms import IdentityTransform, TransformedBbox - -from . import axes_size as Size -from .parasite_axes import HostAxes - - -@_api.deprecated("3.8", alternative="Axes.inset_axes") -class InsetPosition: - @_docstring.dedent_interpd - def __init__(self, parent, lbwh): - """ - An object for positioning an inset axes. - - This is created by specifying the normalized coordinates in the axes, - instead of the figure. - - Parameters - ---------- - parent : `~matplotlib.axes.Axes` - Axes to use for normalizing coordinates. - - lbwh : iterable of four floats - The left edge, bottom edge, width, and height of the inset axes, in - units of the normalized coordinate of the *parent* axes. - - See Also - -------- - :meth:`matplotlib.axes.Axes.set_axes_locator` - - Examples - -------- - The following bounds the inset axes to a box with 20%% of the parent - axes height and 40%% of the width. The size of the axes specified - ([0, 0, 1, 1]) ensures that the axes completely fills the bounding box: - - >>> parent_axes = plt.gca() - >>> ax_ins = plt.axes([0, 0, 1, 1]) - >>> ip = InsetPosition(parent_axes, [0.5, 0.1, 0.4, 0.2]) - >>> ax_ins.set_axes_locator(ip) - """ - self.parent = parent - self.lbwh = lbwh - - def __call__(self, ax, renderer): - bbox_parent = self.parent.get_position(original=False) - trans = BboxTransformTo(bbox_parent) - bbox_inset = Bbox.from_bounds(*self.lbwh) - bb = TransformedBbox(bbox_inset, trans) - return bb - - -class AnchoredLocatorBase(AnchoredOffsetbox): - def __init__(self, bbox_to_anchor, offsetbox, loc, - borderpad=0.5, bbox_transform=None): - super().__init__( - loc, pad=0., child=None, borderpad=borderpad, - bbox_to_anchor=bbox_to_anchor, bbox_transform=bbox_transform - ) - - def draw(self, renderer): - raise RuntimeError("No draw method should be called") - - def __call__(self, ax, renderer): - if renderer is None: - renderer = ax.figure._get_renderer() - self.axes = ax - bbox = self.get_window_extent(renderer) - px, py = self.get_offset(bbox.width, bbox.height, 0, 0, renderer) - bbox_canvas = Bbox.from_bounds(px, py, bbox.width, bbox.height) - tr = ax.figure.transSubfigure.inverted() - return TransformedBbox(bbox_canvas, tr) - - -class AnchoredSizeLocator(AnchoredLocatorBase): - def __init__(self, bbox_to_anchor, x_size, y_size, loc, - borderpad=0.5, bbox_transform=None): - super().__init__( - bbox_to_anchor, None, loc, - borderpad=borderpad, bbox_transform=bbox_transform - ) - - self.x_size = Size.from_any(x_size) - self.y_size = Size.from_any(y_size) - - def get_bbox(self, renderer): - bbox = self.get_bbox_to_anchor() - dpi = renderer.points_to_pixels(72.) - - r, a = self.x_size.get_size(renderer) - width = bbox.width * r + a * dpi - r, a = self.y_size.get_size(renderer) - height = bbox.height * r + a * dpi - - fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) - pad = self.pad * fontsize - - return Bbox.from_bounds(0, 0, width, height).padded(pad) - - -class AnchoredZoomLocator(AnchoredLocatorBase): - def __init__(self, parent_axes, zoom, loc, - borderpad=0.5, - bbox_to_anchor=None, - bbox_transform=None): - self.parent_axes = parent_axes - self.zoom = zoom - if bbox_to_anchor is None: - bbox_to_anchor = parent_axes.bbox - super().__init__( - bbox_to_anchor, None, loc, borderpad=borderpad, - bbox_transform=bbox_transform) - - def get_bbox(self, renderer): - bb = self.parent_axes.transData.transform_bbox(self.axes.viewLim) - fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) - pad = self.pad * fontsize - return ( - Bbox.from_bounds( - 0, 0, abs(bb.width * self.zoom), abs(bb.height * self.zoom)) - .padded(pad)) - - -class BboxPatch(Patch): - @_docstring.dedent_interpd - def __init__(self, bbox, **kwargs): - """ - Patch showing the shape bounded by a Bbox. - - Parameters - ---------- - bbox : `~matplotlib.transforms.Bbox` - Bbox to use for the extents of this patch. - - **kwargs - Patch properties. Valid arguments include: - - %(Patch:kwdoc)s - """ - if "transform" in kwargs: - raise ValueError("transform should not be set") - - kwargs["transform"] = IdentityTransform() - super().__init__(**kwargs) - self.bbox = bbox - - def get_path(self): - # docstring inherited - x0, y0, x1, y1 = self.bbox.extents - return Path._create_closed([(x0, y0), (x1, y0), (x1, y1), (x0, y1)]) - - -class BboxConnector(Patch): - @staticmethod - def get_bbox_edge_pos(bbox, loc): - """ - Return the ``(x, y)`` coordinates of corner *loc* of *bbox*; parameters - behave as documented for the `.BboxConnector` constructor. - """ - x0, y0, x1, y1 = bbox.extents - if loc == 1: - return x1, y1 - elif loc == 2: - return x0, y1 - elif loc == 3: - return x0, y0 - elif loc == 4: - return x1, y0 - - @staticmethod - def connect_bbox(bbox1, bbox2, loc1, loc2=None): - """ - Construct a `.Path` connecting corner *loc1* of *bbox1* to corner - *loc2* of *bbox2*, where parameters behave as documented as for the - `.BboxConnector` constructor. - """ - if isinstance(bbox1, Rectangle): - bbox1 = TransformedBbox(Bbox.unit(), bbox1.get_transform()) - if isinstance(bbox2, Rectangle): - bbox2 = TransformedBbox(Bbox.unit(), bbox2.get_transform()) - if loc2 is None: - loc2 = loc1 - x1, y1 = BboxConnector.get_bbox_edge_pos(bbox1, loc1) - x2, y2 = BboxConnector.get_bbox_edge_pos(bbox2, loc2) - return Path([[x1, y1], [x2, y2]]) - - @_docstring.dedent_interpd - def __init__(self, bbox1, bbox2, loc1, loc2=None, **kwargs): - """ - Connect two bboxes with a straight line. - - Parameters - ---------- - bbox1, bbox2 : `~matplotlib.transforms.Bbox` - Bounding boxes to connect. - - loc1, loc2 : {1, 2, 3, 4} - Corner of *bbox1* and *bbox2* to draw the line. Valid values are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4 - - *loc2* is optional and defaults to *loc1*. - - **kwargs - Patch properties for the line drawn. Valid arguments include: - - %(Patch:kwdoc)s - """ - if "transform" in kwargs: - raise ValueError("transform should not be set") - - kwargs["transform"] = IdentityTransform() - kwargs.setdefault( - "fill", bool({'fc', 'facecolor', 'color'}.intersection(kwargs))) - super().__init__(**kwargs) - self.bbox1 = bbox1 - self.bbox2 = bbox2 - self.loc1 = loc1 - self.loc2 = loc2 - - def get_path(self): - # docstring inherited - return self.connect_bbox(self.bbox1, self.bbox2, - self.loc1, self.loc2) - - -class BboxConnectorPatch(BboxConnector): - @_docstring.dedent_interpd - def __init__(self, bbox1, bbox2, loc1a, loc2a, loc1b, loc2b, **kwargs): - """ - Connect two bboxes with a quadrilateral. - - The quadrilateral is specified by two lines that start and end at - corners of the bboxes. The four sides of the quadrilateral are defined - by the two lines given, the line between the two corners specified in - *bbox1* and the line between the two corners specified in *bbox2*. - - Parameters - ---------- - bbox1, bbox2 : `~matplotlib.transforms.Bbox` - Bounding boxes to connect. - - loc1a, loc2a, loc1b, loc2b : {1, 2, 3, 4} - The first line connects corners *loc1a* of *bbox1* and *loc2a* of - *bbox2*; the second line connects corners *loc1b* of *bbox1* and - *loc2b* of *bbox2*. Valid values are:: - - 'upper right' : 1, - 'upper left' : 2, - 'lower left' : 3, - 'lower right' : 4 - - **kwargs - Patch properties for the line drawn: - - %(Patch:kwdoc)s - """ - if "transform" in kwargs: - raise ValueError("transform should not be set") - super().__init__(bbox1, bbox2, loc1a, loc2a, **kwargs) - self.loc1b = loc1b - self.loc2b = loc2b - - def get_path(self): - # docstring inherited - path1 = self.connect_bbox(self.bbox1, self.bbox2, self.loc1, self.loc2) - path2 = self.connect_bbox(self.bbox2, self.bbox1, - self.loc2b, self.loc1b) - path_merged = [*path1.vertices, *path2.vertices, path1.vertices[0]] - return Path(path_merged) - - -def _add_inset_axes(parent_axes, axes_class, axes_kwargs, axes_locator): - """Helper function to add an inset axes and disable navigation in it.""" - if axes_class is None: - axes_class = HostAxes - if axes_kwargs is None: - axes_kwargs = {} - inset_axes = axes_class( - parent_axes.figure, parent_axes.get_position(), - **{"navigate": False, **axes_kwargs, "axes_locator": axes_locator}) - return parent_axes.figure.add_axes(inset_axes) - - -@_docstring.dedent_interpd -def inset_axes(parent_axes, width, height, loc='upper right', - bbox_to_anchor=None, bbox_transform=None, - axes_class=None, axes_kwargs=None, - borderpad=0.5): - """ - Create an inset axes with a given width and height. - - Both sizes used can be specified either in inches or percentage. - For example,:: - - inset_axes(parent_axes, width='40%%', height='30%%', loc='lower left') - - creates in inset axes in the lower left corner of *parent_axes* which spans - over 30%% in height and 40%% in width of the *parent_axes*. Since the usage - of `.inset_axes` may become slightly tricky when exceeding such standard - cases, it is recommended to read :doc:`the examples - </gallery/axes_grid1/inset_locator_demo>`. - - Notes - ----- - The meaning of *bbox_to_anchor* and *bbox_to_transform* is interpreted - differently from that of legend. The value of bbox_to_anchor - (or the return value of its get_points method; the default is - *parent_axes.bbox*) is transformed by the bbox_transform (the default - is Identity transform) and then interpreted as points in the pixel - coordinate (which is dpi dependent). - - Thus, following three calls are identical and creates an inset axes - with respect to the *parent_axes*:: - - axins = inset_axes(parent_axes, "30%%", "40%%") - axins = inset_axes(parent_axes, "30%%", "40%%", - bbox_to_anchor=parent_axes.bbox) - axins = inset_axes(parent_axes, "30%%", "40%%", - bbox_to_anchor=(0, 0, 1, 1), - bbox_transform=parent_axes.transAxes) - - Parameters - ---------- - parent_axes : `matplotlib.axes.Axes` - Axes to place the inset axes. - - width, height : float or str - Size of the inset axes to create. If a float is provided, it is - the size in inches, e.g. *width=1.3*. If a string is provided, it is - the size in relative units, e.g. *width='40%%'*. By default, i.e. if - neither *bbox_to_anchor* nor *bbox_transform* are specified, those - are relative to the parent_axes. Otherwise, they are to be understood - relative to the bounding box provided via *bbox_to_anchor*. - - loc : str, default: 'upper right' - Location to place the inset axes. 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. - - bbox_to_anchor : tuple or `~matplotlib.transforms.BboxBase`, optional - Bbox that the inset axes will be anchored to. If None, - a tuple of (0, 0, 1, 1) is used if *bbox_transform* is set - to *parent_axes.transAxes* or *parent_axes.figure.transFigure*. - Otherwise, *parent_axes.bbox* is used. If a tuple, can be either - [left, bottom, width, height], or [left, bottom]. - If the kwargs *width* and/or *height* are specified in relative units, - the 2-tuple [left, bottom] cannot be used. Note that, - unless *bbox_transform* is set, the units of the bounding box - are interpreted in the pixel coordinate. When using *bbox_to_anchor* - with tuple, it almost always makes sense to also specify - a *bbox_transform*. This might often be the axes transform - *parent_axes.transAxes*. - - bbox_transform : `~matplotlib.transforms.Transform`, optional - Transformation for the bbox that contains the inset axes. - If None, a `.transforms.IdentityTransform` is used. The value - of *bbox_to_anchor* (or the return value of its get_points method) - is transformed by the *bbox_transform* and then interpreted - as points in the pixel coordinate (which is dpi dependent). - You may provide *bbox_to_anchor* in some normalized coordinate, - and give an appropriate transform (e.g., *parent_axes.transAxes*). - - axes_class : `~matplotlib.axes.Axes` type, default: `.HostAxes` - The type of the newly created inset axes. - - axes_kwargs : dict, optional - Keyword arguments to pass to the constructor of the inset axes. - Valid arguments include: - - %(Axes:kwdoc)s - - borderpad : float, default: 0.5 - Padding between inset axes and the bbox_to_anchor. - The units are axes font size, i.e. for a default font size of 10 points - *borderpad = 0.5* is equivalent to a padding of 5 points. - - Returns - ------- - inset_axes : *axes_class* - Inset axes object created. - """ - - if (bbox_transform in [parent_axes.transAxes, parent_axes.figure.transFigure] - and bbox_to_anchor is None): - _api.warn_external("Using the axes or figure transform requires a " - "bounding box in the respective coordinates. " - "Using bbox_to_anchor=(0, 0, 1, 1) now.") - bbox_to_anchor = (0, 0, 1, 1) - if bbox_to_anchor is None: - bbox_to_anchor = parent_axes.bbox - if (isinstance(bbox_to_anchor, tuple) and - (isinstance(width, str) or isinstance(height, str))): - if len(bbox_to_anchor) != 4: - raise ValueError("Using relative units for width or height " - "requires to provide a 4-tuple or a " - "`Bbox` instance to `bbox_to_anchor.") - return _add_inset_axes( - parent_axes, axes_class, axes_kwargs, - AnchoredSizeLocator( - bbox_to_anchor, width, height, loc=loc, - bbox_transform=bbox_transform, borderpad=borderpad)) - - -@_docstring.dedent_interpd -def zoomed_inset_axes(parent_axes, zoom, loc='upper right', - bbox_to_anchor=None, bbox_transform=None, - axes_class=None, axes_kwargs=None, - borderpad=0.5): - """ - Create an anchored inset axes by scaling a parent axes. For usage, also see - :doc:`the examples </gallery/axes_grid1/inset_locator_demo2>`. - - Parameters - ---------- - parent_axes : `~matplotlib.axes.Axes` - Axes to place the inset axes. - - zoom : float - Scaling factor of the data axes. *zoom* > 1 will enlarge the - coordinates (i.e., "zoomed in"), while *zoom* < 1 will shrink the - coordinates (i.e., "zoomed out"). - - loc : str, default: 'upper right' - Location to place the inset axes. 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. - - bbox_to_anchor : tuple or `~matplotlib.transforms.BboxBase`, optional - Bbox that the inset axes will be anchored to. If None, - *parent_axes.bbox* is used. If a tuple, can be either - [left, bottom, width, height], or [left, bottom]. - If the kwargs *width* and/or *height* are specified in relative units, - the 2-tuple [left, bottom] cannot be used. Note that - the units of the bounding box are determined through the transform - in use. When using *bbox_to_anchor* it almost always makes sense to - also specify a *bbox_transform*. This might often be the axes transform - *parent_axes.transAxes*. - - bbox_transform : `~matplotlib.transforms.Transform`, optional - Transformation for the bbox that contains the inset axes. - If None, a `.transforms.IdentityTransform` is used (i.e. pixel - coordinates). This is useful when not providing any argument to - *bbox_to_anchor*. When using *bbox_to_anchor* it almost always makes - sense to also specify a *bbox_transform*. This might often be the - axes transform *parent_axes.transAxes*. Inversely, when specifying - the axes- or figure-transform here, be aware that not specifying - *bbox_to_anchor* will use *parent_axes.bbox*, the units of which are - in display (pixel) coordinates. - - axes_class : `~matplotlib.axes.Axes` type, default: `.HostAxes` - The type of the newly created inset axes. - - axes_kwargs : dict, optional - Keyword arguments to pass to the constructor of the inset axes. - Valid arguments include: - - %(Axes:kwdoc)s - - borderpad : float, default: 0.5 - Padding between inset axes and the bbox_to_anchor. - The units are axes font size, i.e. for a default font size of 10 points - *borderpad = 0.5* is equivalent to a padding of 5 points. - - Returns - ------- - inset_axes : *axes_class* - Inset axes object created. - """ - - return _add_inset_axes( - parent_axes, axes_class, axes_kwargs, - AnchoredZoomLocator( - parent_axes, zoom=zoom, loc=loc, - bbox_to_anchor=bbox_to_anchor, bbox_transform=bbox_transform, - borderpad=borderpad)) - - -class _TransformedBboxWithCallback(TransformedBbox): - """ - Variant of `.TransformBbox` which calls *callback* before returning points. - - Used by `.mark_inset` to unstale the parent axes' viewlim as needed. - """ - - def __init__(self, *args, callback, **kwargs): - super().__init__(*args, **kwargs) - self._callback = callback - - def get_points(self): - self._callback() - return super().get_points() - - -@_docstring.dedent_interpd -def mark_inset(parent_axes, inset_axes, loc1, loc2, **kwargs): - """ - Draw a box to mark the location of an area represented by an inset axes. - - This function draws a box in *parent_axes* at the bounding box of - *inset_axes*, and shows a connection with the inset axes by drawing lines - at the corners, giving a "zoomed in" effect. - - Parameters - ---------- - parent_axes : `~matplotlib.axes.Axes` - Axes which contains the area of the inset axes. - - inset_axes : `~matplotlib.axes.Axes` - The inset axes. - - loc1, loc2 : {1, 2, 3, 4} - Corners to use for connecting the inset axes and the area in the - parent axes. - - **kwargs - Patch properties for the lines and box drawn: - - %(Patch:kwdoc)s - - Returns - ------- - pp : `~matplotlib.patches.Patch` - The patch drawn to represent the area of the inset axes. - - p1, p2 : `~matplotlib.patches.Patch` - The patches connecting two corners of the inset axes and its area. - """ - rect = _TransformedBboxWithCallback( - inset_axes.viewLim, parent_axes.transData, - callback=parent_axes._unstale_viewLim) - - kwargs.setdefault("fill", bool({'fc', 'facecolor', 'color'}.intersection(kwargs))) - pp = BboxPatch(rect, **kwargs) - parent_axes.add_patch(pp) - - p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1, **kwargs) - inset_axes.add_patch(p1) - p1.set_clip_on(False) - p2 = BboxConnector(inset_axes.bbox, rect, loc1=loc2, **kwargs) - inset_axes.add_patch(p2) - p2.set_clip_on(False) - - return pp, p1, p2 diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/mpl_axes.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/mpl_axes.py deleted file mode 100644 index 51c8748758c..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/mpl_axes.py +++ /dev/null @@ -1,128 +0,0 @@ -import matplotlib.axes as maxes -from matplotlib.artist import Artist -from matplotlib.axis import XAxis, YAxis - - -class SimpleChainedObjects: - def __init__(self, objects): - self._objects = objects - - def __getattr__(self, k): - _a = SimpleChainedObjects([getattr(a, k) for a in self._objects]) - return _a - - def __call__(self, *args, **kwargs): - for m in self._objects: - m(*args, **kwargs) - - -class Axes(maxes.Axes): - - class AxisDict(dict): - def __init__(self, axes): - self.axes = axes - super().__init__() - - def __getitem__(self, k): - if isinstance(k, tuple): - r = SimpleChainedObjects( - # super() within a list comprehension needs explicit args. - [super(Axes.AxisDict, self).__getitem__(k1) for k1 in k]) - return r - elif isinstance(k, slice): - if k.start is None and k.stop is None and k.step is None: - return SimpleChainedObjects(list(self.values())) - else: - raise ValueError("Unsupported slice") - else: - return dict.__getitem__(self, k) - - def __call__(self, *v, **kwargs): - return maxes.Axes.axis(self.axes, *v, **kwargs) - - @property - def axis(self): - return self._axislines - - def clear(self): - # docstring inherited - super().clear() - # Init axis artists. - self._axislines = self.AxisDict(self) - self._axislines.update( - bottom=SimpleAxisArtist(self.xaxis, 1, self.spines["bottom"]), - top=SimpleAxisArtist(self.xaxis, 2, self.spines["top"]), - left=SimpleAxisArtist(self.yaxis, 1, self.spines["left"]), - right=SimpleAxisArtist(self.yaxis, 2, self.spines["right"])) - - -class SimpleAxisArtist(Artist): - def __init__(self, axis, axisnum, spine): - self._axis = axis - self._axisnum = axisnum - self.line = spine - - if isinstance(axis, XAxis): - self._axis_direction = ["bottom", "top"][axisnum-1] - elif isinstance(axis, YAxis): - self._axis_direction = ["left", "right"][axisnum-1] - else: - raise ValueError( - f"axis must be instance of XAxis or YAxis, but got {axis}") - super().__init__() - - @property - def major_ticks(self): - tickline = "tick%dline" % self._axisnum - return SimpleChainedObjects([getattr(tick, tickline) - for tick in self._axis.get_major_ticks()]) - - @property - def major_ticklabels(self): - label = "label%d" % self._axisnum - return SimpleChainedObjects([getattr(tick, label) - for tick in self._axis.get_major_ticks()]) - - @property - def label(self): - return self._axis.label - - def set_visible(self, b): - self.toggle(all=b) - self.line.set_visible(b) - self._axis.set_visible(True) - super().set_visible(b) - - def set_label(self, txt): - self._axis.set_label_text(txt) - - def toggle(self, all=None, ticks=None, ticklabels=None, label=None): - - if all: - _ticks, _ticklabels, _label = True, True, True - elif all is not None: - _ticks, _ticklabels, _label = False, False, False - else: - _ticks, _ticklabels, _label = None, None, None - - if ticks is not None: - _ticks = ticks - if ticklabels is not None: - _ticklabels = ticklabels - if label is not None: - _label = label - - if _ticks is not None: - tickparam = {f"tick{self._axisnum}On": _ticks} - self._axis.set_tick_params(**tickparam) - if _ticklabels is not None: - tickparam = {f"label{self._axisnum}On": _ticklabels} - self._axis.set_tick_params(**tickparam) - - if _label is not None: - pos = self._axis.get_label_position() - if (pos == self._axis_direction) and not _label: - self._axis.label.set_visible(False) - elif _label: - self._axis.label.set_visible(True) - self._axis.set_label_position(self._axis_direction) diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/parasite_axes.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/parasite_axes.py deleted file mode 100644 index 2a2b5957e84..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/parasite_axes.py +++ /dev/null @@ -1,257 +0,0 @@ -from matplotlib import _api, cbook -import matplotlib.artist as martist -import matplotlib.transforms as mtransforms -from matplotlib.transforms import Bbox -from .mpl_axes import Axes - - -class ParasiteAxesBase: - - def __init__(self, parent_axes, aux_transform=None, - *, viewlim_mode=None, **kwargs): - self._parent_axes = parent_axes - self.transAux = aux_transform - self.set_viewlim_mode(viewlim_mode) - kwargs["frameon"] = False - super().__init__(parent_axes.figure, parent_axes._position, **kwargs) - - def clear(self): - super().clear() - martist.setp(self.get_children(), visible=False) - self._get_lines = self._parent_axes._get_lines - self._parent_axes.callbacks._connect_picklable( - "xlim_changed", self._sync_lims) - self._parent_axes.callbacks._connect_picklable( - "ylim_changed", self._sync_lims) - - def pick(self, mouseevent): - # This most likely goes to Artist.pick (depending on axes_class given - # to the factory), which only handles pick events registered on the - # axes associated with each child: - super().pick(mouseevent) - # But parasite axes are additionally given pick events from their host - # axes (cf. HostAxesBase.pick), which we handle here: - for a in self.get_children(): - if (hasattr(mouseevent.inaxes, "parasites") - and self in mouseevent.inaxes.parasites): - a.pick(mouseevent) - - # aux_transform support - - def _set_lim_and_transforms(self): - if self.transAux is not None: - self.transAxes = self._parent_axes.transAxes - self.transData = self.transAux + self._parent_axes.transData - self._xaxis_transform = mtransforms.blended_transform_factory( - self.transData, self.transAxes) - self._yaxis_transform = mtransforms.blended_transform_factory( - self.transAxes, self.transData) - else: - super()._set_lim_and_transforms() - - def set_viewlim_mode(self, mode): - _api.check_in_list([None, "equal", "transform"], mode=mode) - self._viewlim_mode = mode - - def get_viewlim_mode(self): - return self._viewlim_mode - - def _sync_lims(self, parent): - viewlim = parent.viewLim.frozen() - mode = self.get_viewlim_mode() - if mode is None: - pass - elif mode == "equal": - self.viewLim.set(viewlim) - elif mode == "transform": - self.viewLim.set(viewlim.transformed(self.transAux.inverted())) - else: - _api.check_in_list([None, "equal", "transform"], mode=mode) - - # end of aux_transform support - - -parasite_axes_class_factory = cbook._make_class_factory( - ParasiteAxesBase, "{}Parasite") -ParasiteAxes = parasite_axes_class_factory(Axes) - - -class HostAxesBase: - def __init__(self, *args, **kwargs): - self.parasites = [] - super().__init__(*args, **kwargs) - - def get_aux_axes( - self, tr=None, viewlim_mode="equal", axes_class=None, **kwargs): - """ - Add a parasite axes to this host. - - Despite this method's name, this should actually be thought of as an - ``add_parasite_axes`` method. - - .. versionchanged:: 3.7 - Defaults to same base axes class as host axes. - - Parameters - ---------- - tr : `~matplotlib.transforms.Transform` or None, default: None - If a `.Transform`, the following relation will hold: - ``parasite.transData = tr + host.transData``. - If None, the parasite's and the host's ``transData`` are unrelated. - viewlim_mode : {"equal", "transform", None}, default: "equal" - How the parasite's view limits are set: directly equal to the - parent axes ("equal"), equal after application of *tr* - ("transform"), or independently (None). - axes_class : subclass type of `~matplotlib.axes.Axes`, optional - The `~.axes.Axes` subclass that is instantiated. If None, the base - class of the host axes is used. - **kwargs - Other parameters are forwarded to the parasite axes constructor. - """ - if axes_class is None: - axes_class = self._base_axes_class - parasite_axes_class = parasite_axes_class_factory(axes_class) - ax2 = parasite_axes_class( - self, tr, viewlim_mode=viewlim_mode, **kwargs) - # note that ax2.transData == tr + ax1.transData - # Anything you draw in ax2 will match the ticks and grids of ax1. - self.parasites.append(ax2) - ax2._remove_method = self.parasites.remove - return ax2 - - def draw(self, renderer): - orig_children_len = len(self._children) - - locator = self.get_axes_locator() - if locator: - pos = locator(self, renderer) - self.set_position(pos, which="active") - self.apply_aspect(pos) - else: - self.apply_aspect() - - rect = self.get_position() - for ax in self.parasites: - ax.apply_aspect(rect) - self._children.extend(ax.get_children()) - - super().draw(renderer) - del self._children[orig_children_len:] - - def clear(self): - super().clear() - for ax in self.parasites: - ax.clear() - - def pick(self, mouseevent): - super().pick(mouseevent) - # Also pass pick events on to parasite axes and, in turn, their - # children (cf. ParasiteAxesBase.pick) - for a in self.parasites: - a.pick(mouseevent) - - def twinx(self, axes_class=None): - """ - Create a twin of Axes with a shared x-axis but independent y-axis. - - The y-axis of self will have ticks on the left and the returned axes - will have ticks on the right. - """ - ax = self._add_twin_axes(axes_class, sharex=self) - self.axis["right"].set_visible(False) - ax.axis["right"].set_visible(True) - ax.axis["left", "top", "bottom"].set_visible(False) - return ax - - def twiny(self, axes_class=None): - """ - Create a twin of Axes with a shared y-axis but independent x-axis. - - The x-axis of self will have ticks on the bottom and the returned axes - will have ticks on the top. - """ - ax = self._add_twin_axes(axes_class, sharey=self) - self.axis["top"].set_visible(False) - ax.axis["top"].set_visible(True) - ax.axis["left", "right", "bottom"].set_visible(False) - return ax - - def twin(self, aux_trans=None, axes_class=None): - """ - Create a twin of Axes with no shared axis. - - While self will have ticks on the left and bottom axis, the returned - axes will have ticks on the top and right axis. - """ - if aux_trans is None: - aux_trans = mtransforms.IdentityTransform() - ax = self._add_twin_axes( - axes_class, aux_transform=aux_trans, viewlim_mode="transform") - self.axis["top", "right"].set_visible(False) - ax.axis["top", "right"].set_visible(True) - ax.axis["left", "bottom"].set_visible(False) - return ax - - def _add_twin_axes(self, axes_class, **kwargs): - """ - Helper for `.twinx`/`.twiny`/`.twin`. - - *kwargs* are forwarded to the parasite axes constructor. - """ - if axes_class is None: - axes_class = self._base_axes_class - ax = parasite_axes_class_factory(axes_class)(self, **kwargs) - self.parasites.append(ax) - ax._remove_method = self._remove_any_twin - return ax - - def _remove_any_twin(self, ax): - self.parasites.remove(ax) - restore = ["top", "right"] - if ax._sharex: - restore.remove("top") - if ax._sharey: - restore.remove("right") - self.axis[tuple(restore)].set_visible(True) - self.axis[tuple(restore)].toggle(ticklabels=False, label=False) - - @_api.make_keyword_only("3.8", "call_axes_locator") - def get_tightbbox(self, renderer=None, call_axes_locator=True, - bbox_extra_artists=None): - bbs = [ - *[ax.get_tightbbox(renderer, call_axes_locator=call_axes_locator) - for ax in self.parasites], - super().get_tightbbox(renderer, - call_axes_locator=call_axes_locator, - bbox_extra_artists=bbox_extra_artists)] - return Bbox.union([b for b in bbs if b.width != 0 or b.height != 0]) - - -host_axes_class_factory = host_subplot_class_factory = \ - cbook._make_class_factory(HostAxesBase, "{}HostAxes", "_base_axes_class") -HostAxes = SubplotHost = host_axes_class_factory(Axes) - - -def host_axes(*args, axes_class=Axes, figure=None, **kwargs): - """ - Create axes that can act as a hosts to parasitic axes. - - Parameters - ---------- - figure : `~matplotlib.figure.Figure` - Figure to which the axes will be added. Defaults to the current figure - `.pyplot.gcf()`. - - *args, **kwargs - Will be passed on to the underlying `~.axes.Axes` object creation. - """ - import matplotlib.pyplot as plt - host_axes_class = host_axes_class_factory(axes_class) - if figure is None: - figure = plt.gcf() - ax = host_axes_class(figure, *args, **kwargs) - figure.add_axes(ax) - return ax - - -host_subplot = host_axes diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/__init__.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/__init__.py deleted file mode 100644 index 47242cf7f0c..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .axislines import ( - Axes, AxesZero, AxisArtistHelper, AxisArtistHelperRectlinear, - GridHelperBase, GridHelperRectlinear, Subplot, SubplotZero) -from .axis_artist import AxisArtist, GridlinesCollection -from .grid_helper_curvelinear import GridHelperCurveLinear -from .floating_axes import FloatingAxes, FloatingSubplot -from mpl_toolkits.axes_grid1.parasite_axes import ( - host_axes_class_factory, parasite_axes_class_factory) - - -ParasiteAxes = parasite_axes_class_factory(Axes) -HostAxes = host_axes_class_factory(Axes) -SubplotHost = HostAxes diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/angle_helper.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/angle_helper.py deleted file mode 100644 index 1786cd70bcd..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/angle_helper.py +++ /dev/null @@ -1,394 +0,0 @@ -import numpy as np -import math - -from mpl_toolkits.axisartist.grid_finder import ExtremeFinderSimple - - -def select_step_degree(dv): - - degree_limits_ = [1.5, 3, 7, 13, 20, 40, 70, 120, 270, 520] - degree_steps_ = [1, 2, 5, 10, 15, 30, 45, 90, 180, 360] - degree_factors = [1.] * len(degree_steps_) - - minsec_limits_ = [1.5, 2.5, 3.5, 8, 11, 18, 25, 45] - minsec_steps_ = [1, 2, 3, 5, 10, 15, 20, 30] - - minute_limits_ = np.array(minsec_limits_) / 60 - minute_factors = [60.] * len(minute_limits_) - - second_limits_ = np.array(minsec_limits_) / 3600 - second_factors = [3600.] * len(second_limits_) - - degree_limits = [*second_limits_, *minute_limits_, *degree_limits_] - degree_steps = [*minsec_steps_, *minsec_steps_, *degree_steps_] - degree_factors = [*second_factors, *minute_factors, *degree_factors] - - n = np.searchsorted(degree_limits, dv) - step = degree_steps[n] - factor = degree_factors[n] - - return step, factor - - -def select_step_hour(dv): - - hour_limits_ = [1.5, 2.5, 3.5, 5, 7, 10, 15, 21, 36] - hour_steps_ = [1, 2, 3, 4, 6, 8, 12, 18, 24] - hour_factors = [1.] * len(hour_steps_) - - minsec_limits_ = [1.5, 2.5, 3.5, 4.5, 5.5, 8, 11, 14, 18, 25, 45] - minsec_steps_ = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30] - - minute_limits_ = np.array(minsec_limits_) / 60 - minute_factors = [60.] * len(minute_limits_) - - second_limits_ = np.array(minsec_limits_) / 3600 - second_factors = [3600.] * len(second_limits_) - - hour_limits = [*second_limits_, *minute_limits_, *hour_limits_] - hour_steps = [*minsec_steps_, *minsec_steps_, *hour_steps_] - hour_factors = [*second_factors, *minute_factors, *hour_factors] - - n = np.searchsorted(hour_limits, dv) - step = hour_steps[n] - factor = hour_factors[n] - - return step, factor - - -def select_step_sub(dv): - - # subarcsec or degree - tmp = 10.**(int(math.log10(dv))-1.) - - factor = 1./tmp - - if 1.5*tmp >= dv: - step = 1 - elif 3.*tmp >= dv: - step = 2 - elif 7.*tmp >= dv: - step = 5 - else: - step = 1 - factor = 0.1*factor - - return step, factor - - -def select_step(v1, v2, nv, hour=False, include_last=True, - threshold_factor=3600.): - - if v1 > v2: - v1, v2 = v2, v1 - - dv = (v2 - v1) / nv - - if hour: - _select_step = select_step_hour - cycle = 24. - else: - _select_step = select_step_degree - cycle = 360. - - # for degree - if dv > 1 / threshold_factor: - step, factor = _select_step(dv) - else: - step, factor = select_step_sub(dv*threshold_factor) - - factor = factor * threshold_factor - - levs = np.arange(np.floor(v1 * factor / step), - np.ceil(v2 * factor / step) + 0.5, - dtype=int) * step - - # n : number of valid levels. If there is a cycle, e.g., [0, 90, 180, - # 270, 360], the grid line needs to be extended from 0 to 360, so - # we need to return the whole array. However, the last level (360) - # needs to be ignored often. In this case, so we return n=4. - - n = len(levs) - - # we need to check the range of values - # for example, -90 to 90, 0 to 360, - - if factor == 1. and levs[-1] >= levs[0] + cycle: # check for cycle - nv = int(cycle / step) - if include_last: - levs = levs[0] + np.arange(0, nv+1, 1) * step - else: - levs = levs[0] + np.arange(0, nv, 1) * step - - n = len(levs) - - return np.array(levs), n, factor - - -def select_step24(v1, v2, nv, include_last=True, threshold_factor=3600): - v1, v2 = v1 / 15, v2 / 15 - levs, n, factor = select_step(v1, v2, nv, hour=True, - include_last=include_last, - threshold_factor=threshold_factor) - return levs * 15, n, factor - - -def select_step360(v1, v2, nv, include_last=True, threshold_factor=3600): - return select_step(v1, v2, nv, hour=False, - include_last=include_last, - threshold_factor=threshold_factor) - - -class LocatorBase: - def __init__(self, nbins, include_last=True): - self.nbins = nbins - self._include_last = include_last - - def set_params(self, nbins=None): - if nbins is not None: - self.nbins = int(nbins) - - -class LocatorHMS(LocatorBase): - def __call__(self, v1, v2): - return select_step24(v1, v2, self.nbins, self._include_last) - - -class LocatorHM(LocatorBase): - def __call__(self, v1, v2): - return select_step24(v1, v2, self.nbins, self._include_last, - threshold_factor=60) - - -class LocatorH(LocatorBase): - def __call__(self, v1, v2): - return select_step24(v1, v2, self.nbins, self._include_last, - threshold_factor=1) - - -class LocatorDMS(LocatorBase): - def __call__(self, v1, v2): - return select_step360(v1, v2, self.nbins, self._include_last) - - -class LocatorDM(LocatorBase): - def __call__(self, v1, v2): - return select_step360(v1, v2, self.nbins, self._include_last, - threshold_factor=60) - - -class LocatorD(LocatorBase): - def __call__(self, v1, v2): - return select_step360(v1, v2, self.nbins, self._include_last, - threshold_factor=1) - - -class FormatterDMS: - deg_mark = r"^{\circ}" - min_mark = r"^{\prime}" - sec_mark = r"^{\prime\prime}" - - fmt_d = "$%d" + deg_mark + "$" - fmt_ds = r"$%d.%s" + deg_mark + "$" - - # %s for sign - fmt_d_m = r"$%s%d" + deg_mark + r"\,%02d" + min_mark + "$" - fmt_d_ms = r"$%s%d" + deg_mark + r"\,%02d.%s" + min_mark + "$" - - fmt_d_m_partial = "$%s%d" + deg_mark + r"\,%02d" + min_mark + r"\," - fmt_s_partial = "%02d" + sec_mark + "$" - fmt_ss_partial = "%02d.%s" + sec_mark + "$" - - def _get_number_fraction(self, factor): - ## check for fractional numbers - number_fraction = None - # check for 60 - - for threshold in [1, 60, 3600]: - if factor <= threshold: - break - - d = factor // threshold - int_log_d = int(np.floor(np.log10(d))) - if 10**int_log_d == d and d != 1: - number_fraction = int_log_d - factor = factor // 10**int_log_d - return factor, number_fraction - - return factor, number_fraction - - def __call__(self, direction, factor, values): - if len(values) == 0: - return [] - - ss = np.sign(values) - signs = ["-" if v < 0 else "" for v in values] - - factor, number_fraction = self._get_number_fraction(factor) - - values = np.abs(values) - - if number_fraction is not None: - values, frac_part = divmod(values, 10 ** number_fraction) - frac_fmt = "%%0%dd" % (number_fraction,) - frac_str = [frac_fmt % (f1,) for f1 in frac_part] - - if factor == 1: - if number_fraction is None: - return [self.fmt_d % (s * int(v),) for s, v in zip(ss, values)] - else: - return [self.fmt_ds % (s * int(v), f1) - for s, v, f1 in zip(ss, values, frac_str)] - elif factor == 60: - deg_part, min_part = divmod(values, 60) - if number_fraction is None: - return [self.fmt_d_m % (s1, d1, m1) - for s1, d1, m1 in zip(signs, deg_part, min_part)] - else: - return [self.fmt_d_ms % (s, d1, m1, f1) - for s, d1, m1, f1 - in zip(signs, deg_part, min_part, frac_str)] - - elif factor == 3600: - if ss[-1] == -1: - inverse_order = True - values = values[::-1] - signs = signs[::-1] - else: - inverse_order = False - - l_hm_old = "" - r = [] - - deg_part, min_part_ = divmod(values, 3600) - min_part, sec_part = divmod(min_part_, 60) - - if number_fraction is None: - sec_str = [self.fmt_s_partial % (s1,) for s1 in sec_part] - else: - sec_str = [self.fmt_ss_partial % (s1, f1) - for s1, f1 in zip(sec_part, frac_str)] - - for s, d1, m1, s1 in zip(signs, deg_part, min_part, sec_str): - l_hm = self.fmt_d_m_partial % (s, d1, m1) - if l_hm != l_hm_old: - l_hm_old = l_hm - l = l_hm + s1 - else: - l = "$" + s + s1 - r.append(l) - - if inverse_order: - return r[::-1] - else: - return r - - else: # factor > 3600. - return [r"$%s^{\circ}$" % v for v in ss*values] - - -class FormatterHMS(FormatterDMS): - deg_mark = r"^\mathrm{h}" - min_mark = r"^\mathrm{m}" - sec_mark = r"^\mathrm{s}" - - fmt_d = "$%d" + deg_mark + "$" - fmt_ds = r"$%d.%s" + deg_mark + "$" - - # %s for sign - fmt_d_m = r"$%s%d" + deg_mark + r"\,%02d" + min_mark+"$" - fmt_d_ms = r"$%s%d" + deg_mark + r"\,%02d.%s" + min_mark+"$" - - fmt_d_m_partial = "$%s%d" + deg_mark + r"\,%02d" + min_mark + r"\," - fmt_s_partial = "%02d" + sec_mark + "$" - fmt_ss_partial = "%02d.%s" + sec_mark + "$" - - def __call__(self, direction, factor, values): # hour - return super().__call__(direction, factor, np.asarray(values) / 15) - - -class ExtremeFinderCycle(ExtremeFinderSimple): - # docstring inherited - - def __init__(self, nx, ny, - lon_cycle=360., lat_cycle=None, - lon_minmax=None, lat_minmax=(-90, 90)): - """ - This subclass handles the case where one or both coordinates should be - taken modulo 360, or be restricted to not exceed a specific range. - - Parameters - ---------- - nx, ny : int - The number of samples in each direction. - - lon_cycle, lat_cycle : 360 or None - If not None, values in the corresponding direction are taken modulo - *lon_cycle* or *lat_cycle*; in theory this can be any number but - the implementation actually assumes that it is 360 (if not None); - other values give nonsensical results. - - This is done by "unwrapping" the transformed grid coordinates so - that jumps are less than a half-cycle; then normalizing the span to - no more than a full cycle. - - For example, if values are in the union of the [0, 2] and - [358, 360] intervals (typically, angles measured modulo 360), the - values in the second interval are normalized to [-2, 0] instead so - that the values now cover [-2, 2]. If values are in a range of - [5, 1000], this gets normalized to [5, 365]. - - lon_minmax, lat_minmax : (float, float) or None - If not None, the computed bounding box is clipped to the given - range in the corresponding direction. - """ - self.nx, self.ny = nx, ny - self.lon_cycle, self.lat_cycle = lon_cycle, lat_cycle - self.lon_minmax = lon_minmax - self.lat_minmax = lat_minmax - - def __call__(self, transform_xy, x1, y1, x2, y2): - # docstring inherited - x, y = np.meshgrid( - np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny)) - lon, lat = transform_xy(np.ravel(x), np.ravel(y)) - - # iron out jumps, but algorithm should be improved. - # This is just naive way of doing and my fail for some cases. - # Consider replacing this with numpy.unwrap - # We are ignoring invalid warnings. They are triggered when - # comparing arrays with NaNs using > We are already handling - # that correctly using np.nanmin and np.nanmax - with np.errstate(invalid='ignore'): - if self.lon_cycle is not None: - lon0 = np.nanmin(lon) - lon -= 360. * ((lon - lon0) > 180.) - if self.lat_cycle is not None: - lat0 = np.nanmin(lat) - lat -= 360. * ((lat - lat0) > 180.) - - lon_min, lon_max = np.nanmin(lon), np.nanmax(lon) - lat_min, lat_max = np.nanmin(lat), np.nanmax(lat) - - lon_min, lon_max, lat_min, lat_max = \ - self._add_pad(lon_min, lon_max, lat_min, lat_max) - - # check cycle - if self.lon_cycle: - lon_max = min(lon_max, lon_min + self.lon_cycle) - if self.lat_cycle: - lat_max = min(lat_max, lat_min + self.lat_cycle) - - if self.lon_minmax is not None: - min0 = self.lon_minmax[0] - lon_min = max(min0, lon_min) - max0 = self.lon_minmax[1] - lon_max = min(max0, lon_max) - - if self.lat_minmax is not None: - min0 = self.lat_minmax[0] - lat_min = max(min0, lat_min) - max0 = self.lat_minmax[1] - lat_max = min(max0, lat_max) - - return lon_min, lon_max, lat_min, lat_max diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axes_divider.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axes_divider.py deleted file mode 100644 index a01d4e27df9..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axes_divider.py +++ /dev/null @@ -1,2 +0,0 @@ -from mpl_toolkits.axes_grid1.axes_divider import ( # noqa - Divider, AxesLocator, SubplotDivider, AxesDivider, make_axes_locatable) diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axes_grid.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axes_grid.py deleted file mode 100644 index ecb3e9d92c1..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axes_grid.py +++ /dev/null @@ -1,23 +0,0 @@ -from matplotlib import _api - -import mpl_toolkits.axes_grid1.axes_grid as axes_grid_orig -from .axislines import Axes - - -_api.warn_deprecated( - "3.8", name=__name__, obj_type="module", alternative="axes_grid1.axes_grid") - - -@_api.deprecated("3.8", alternative=( - "axes_grid1.axes_grid.Grid(..., axes_class=axislines.Axes")) -class Grid(axes_grid_orig.Grid): - _defaultAxesClass = Axes - - -@_api.deprecated("3.8", alternative=( - "axes_grid1.axes_grid.ImageGrid(..., axes_class=axislines.Axes")) -class ImageGrid(axes_grid_orig.ImageGrid): - _defaultAxesClass = Axes - - -AxesGrid = ImageGrid diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axes_rgb.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axes_rgb.py deleted file mode 100644 index 2195747469a..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axes_rgb.py +++ /dev/null @@ -1,18 +0,0 @@ -from matplotlib import _api -from mpl_toolkits.axes_grid1.axes_rgb import ( # noqa - make_rgb_axes, RGBAxes as _RGBAxes) -from .axislines import Axes - - -_api.warn_deprecated( - "3.8", name=__name__, obj_type="module", alternative="axes_grid1.axes_rgb") - - -@_api.deprecated("3.8", alternative=( - "axes_grid1.axes_rgb.RGBAxes(..., axes_class=axislines.Axes")) -class RGBAxes(_RGBAxes): - """ - Subclass of `~.axes_grid1.axes_rgb.RGBAxes` with - ``_defaultAxesClass`` = `.axislines.Axes`. - """ - _defaultAxesClass = Axes diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axis_artist.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axis_artist.py deleted file mode 100644 index 407ad07a3dc..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axis_artist.py +++ /dev/null @@ -1,1115 +0,0 @@ -""" -The :mod:`.axis_artist` module implements custom artists to draw axis elements -(axis lines and labels, tick lines and labels, grid lines). - -Axis lines and labels and tick lines and labels are managed by the `AxisArtist` -class; grid lines are managed by the `GridlinesCollection` class. - -There is one `AxisArtist` per Axis; it can be accessed through -the ``axis`` dictionary of the parent Axes (which should be a -`mpl_toolkits.axislines.Axes`), e.g. ``ax.axis["bottom"]``. - -Children of the AxisArtist are accessed as attributes: ``.line`` and ``.label`` -for the axis line and label, ``.major_ticks``, ``.major_ticklabels``, -``.minor_ticks``, ``.minor_ticklabels`` for the tick lines and labels (e.g. -``ax.axis["bottom"].line``). - -Children properties (colors, fonts, line widths, etc.) can be set using -setters, e.g. :: - - # Make the major ticks of the bottom axis red. - ax.axis["bottom"].major_ticks.set_color("red") - -However, things like the locations of ticks, and their ticklabels need to be -changed from the side of the grid_helper. - -axis_direction --------------- - -`AxisArtist`, `AxisLabel`, `TickLabels` have an *axis_direction* attribute, -which adjusts the location, angle, etc. The *axis_direction* must be one of -"left", "right", "bottom", "top", and follows the Matplotlib convention for -rectangular axis. - -For example, for the *bottom* axis (the left and right is relative to the -direction of the increasing coordinate), - -* ticklabels and axislabel are on the right -* ticklabels and axislabel have text angle of 0 -* ticklabels are baseline, center-aligned -* axislabel is top, center-aligned - -The text angles are actually relative to (90 + angle of the direction to the -ticklabel), which gives 0 for bottom axis. - -=================== ====== ======== ====== ======== -Property left bottom right top -=================== ====== ======== ====== ======== -ticklabel location left right right left -axislabel location left right right left -ticklabel angle 90 0 -90 180 -axislabel angle 180 0 0 180 -ticklabel va center baseline center baseline -axislabel va center top center bottom -ticklabel ha right center right center -axislabel ha right center right center -=================== ====== ======== ====== ======== - -Ticks are by default direct opposite side of the ticklabels. To make ticks to -the same side of the ticklabels, :: - - ax.axis["bottom"].major_ticks.set_tick_out(True) - -The following attributes can be customized (use the ``set_xxx`` methods): - -* `Ticks`: ticksize, tick_out -* `TickLabels`: pad -* `AxisLabel`: pad -""" - -# FIXME : -# angles are given in data coordinate - need to convert it to canvas coordinate - - -from operator import methodcaller - -import numpy as np - -import matplotlib as mpl -from matplotlib import _api, cbook -import matplotlib.artist as martist -import matplotlib.colors as mcolors -import matplotlib.text as mtext -from matplotlib.collections import LineCollection -from matplotlib.lines import Line2D -from matplotlib.patches import PathPatch -from matplotlib.path import Path -from matplotlib.transforms import ( - Affine2D, Bbox, IdentityTransform, ScaledTranslation) - -from .axisline_style import AxislineStyle - - -class AttributeCopier: - def get_ref_artist(self): - """ - Return the underlying artist that actually defines some properties - (e.g., color) of this artist. - """ - raise RuntimeError("get_ref_artist must overridden") - - def get_attribute_from_ref_artist(self, attr_name): - getter = methodcaller("get_" + attr_name) - prop = getter(super()) - return getter(self.get_ref_artist()) if prop == "auto" else prop - - -class Ticks(AttributeCopier, Line2D): - """ - Ticks are derived from `.Line2D`, and note that ticks themselves - are markers. Thus, you should use set_mec, set_mew, etc. - - To change the tick size (length), you need to use - `set_ticksize`. To change the direction of the ticks (ticks are - in opposite direction of ticklabels by default), use - ``set_tick_out(False)`` - """ - - def __init__(self, ticksize, tick_out=False, *, axis=None, **kwargs): - self._ticksize = ticksize - self.locs_angles_labels = [] - - self.set_tick_out(tick_out) - - self._axis = axis - if self._axis is not None: - if "color" not in kwargs: - kwargs["color"] = "auto" - if "mew" not in kwargs and "markeredgewidth" not in kwargs: - kwargs["markeredgewidth"] = "auto" - - Line2D.__init__(self, [0.], [0.], **kwargs) - self.set_snap(True) - - def get_ref_artist(self): - # docstring inherited - return self._axis.majorTicks[0].tick1line - - def set_color(self, color): - # docstring inherited - # Unlike the base Line2D.set_color, this also supports "auto". - if not cbook._str_equal(color, "auto"): - mcolors._check_color_like(color=color) - self._color = color - self.stale = True - - def get_color(self): - return self.get_attribute_from_ref_artist("color") - - def get_markeredgecolor(self): - return self.get_attribute_from_ref_artist("markeredgecolor") - - def get_markeredgewidth(self): - return self.get_attribute_from_ref_artist("markeredgewidth") - - def set_tick_out(self, b): - """Set whether ticks are drawn inside or outside the axes.""" - self._tick_out = b - - def get_tick_out(self): - """Return whether ticks are drawn inside or outside the axes.""" - return self._tick_out - - def set_ticksize(self, ticksize): - """Set length of the ticks in points.""" - self._ticksize = ticksize - - def get_ticksize(self): - """Return length of the ticks in points.""" - return self._ticksize - - def set_locs_angles(self, locs_angles): - self.locs_angles = locs_angles - - _tickvert_path = Path([[0., 0.], [1., 0.]]) - - def draw(self, renderer): - if not self.get_visible(): - return - - gc = renderer.new_gc() - gc.set_foreground(self.get_markeredgecolor()) - gc.set_linewidth(self.get_markeredgewidth()) - gc.set_alpha(self._alpha) - - path_trans = self.get_transform() - marker_transform = (Affine2D() - .scale(renderer.points_to_pixels(self._ticksize))) - if self.get_tick_out(): - marker_transform.rotate_deg(180) - - for loc, angle in self.locs_angles: - locs = path_trans.transform_non_affine(np.array([loc])) - if self.axes and not self.axes.viewLim.contains(*locs[0]): - continue - renderer.draw_markers( - gc, self._tickvert_path, - marker_transform + Affine2D().rotate_deg(angle), - Path(locs), path_trans.get_affine()) - - gc.restore() - - -class LabelBase(mtext.Text): - """ - A base class for `.AxisLabel` and `.TickLabels`. The position and - angle of the text are calculated by the offset_ref_angle, - text_ref_angle, and offset_radius attributes. - """ - - def __init__(self, *args, **kwargs): - self.locs_angles_labels = [] - self._ref_angle = 0 - self._offset_radius = 0. - - super().__init__(*args, **kwargs) - - self.set_rotation_mode("anchor") - self._text_follow_ref_angle = True - - @property - def _text_ref_angle(self): - if self._text_follow_ref_angle: - return self._ref_angle + 90 - else: - return 0 - - @property - def _offset_ref_angle(self): - return self._ref_angle - - _get_opposite_direction = {"left": "right", - "right": "left", - "top": "bottom", - "bottom": "top"}.__getitem__ - - def draw(self, renderer): - if not self.get_visible(): - return - - # save original and adjust some properties - tr = self.get_transform() - angle_orig = self.get_rotation() - theta = np.deg2rad(self._offset_ref_angle) - dd = self._offset_radius - dx, dy = dd * np.cos(theta), dd * np.sin(theta) - - self.set_transform(tr + Affine2D().translate(dx, dy)) - self.set_rotation(self._text_ref_angle + angle_orig) - super().draw(renderer) - # restore original properties - self.set_transform(tr) - self.set_rotation(angle_orig) - - def get_window_extent(self, renderer=None): - if renderer is None: - renderer = self.figure._get_renderer() - - # save original and adjust some properties - tr = self.get_transform() - angle_orig = self.get_rotation() - theta = np.deg2rad(self._offset_ref_angle) - dd = self._offset_radius - dx, dy = dd * np.cos(theta), dd * np.sin(theta) - - self.set_transform(tr + Affine2D().translate(dx, dy)) - self.set_rotation(self._text_ref_angle + angle_orig) - bbox = super().get_window_extent(renderer).frozen() - # restore original properties - self.set_transform(tr) - self.set_rotation(angle_orig) - - return bbox - - -class AxisLabel(AttributeCopier, LabelBase): - """ - Axis label. Derived from `.Text`. The position of the text is updated - in the fly, so changing text position has no effect. Otherwise, the - properties can be changed as a normal `.Text`. - - To change the pad between tick labels and axis label, use `set_pad`. - """ - - def __init__(self, *args, axis_direction="bottom", axis=None, **kwargs): - self._axis = axis - self._pad = 5 - self._external_pad = 0 # in pixels - LabelBase.__init__(self, *args, **kwargs) - self.set_axis_direction(axis_direction) - - def set_pad(self, pad): - """ - Set the internal pad in points. - - The actual pad will be the sum of the internal pad and the - external pad (the latter is set automatically by the `.AxisArtist`). - - Parameters - ---------- - pad : float - The internal pad in points. - """ - self._pad = pad - - def get_pad(self): - """ - Return the internal pad in points. - - See `.set_pad` for more details. - """ - return self._pad - - def get_ref_artist(self): - # docstring inherited - return self._axis.get_label() - - def get_text(self): - # docstring inherited - t = super().get_text() - if t == "__from_axes__": - return self._axis.get_label().get_text() - return self._text - - _default_alignments = dict(left=("bottom", "center"), - right=("top", "center"), - bottom=("top", "center"), - top=("bottom", "center")) - - def set_default_alignment(self, d): - """ - Set the default alignment. See `set_axis_direction` for details. - - Parameters - ---------- - d : {"left", "bottom", "right", "top"} - """ - va, ha = _api.check_getitem(self._default_alignments, d=d) - self.set_va(va) - self.set_ha(ha) - - _default_angles = dict(left=180, - right=0, - bottom=0, - top=180) - - def set_default_angle(self, d): - """ - Set the default angle. See `set_axis_direction` for details. - - Parameters - ---------- - d : {"left", "bottom", "right", "top"} - """ - self.set_rotation(_api.check_getitem(self._default_angles, d=d)) - - def set_axis_direction(self, d): - """ - Adjust the text angle and text alignment of axis label - according to the matplotlib convention. - - ===================== ========== ========= ========== ========== - Property left bottom right top - ===================== ========== ========= ========== ========== - axislabel angle 180 0 0 180 - axislabel va center top center bottom - axislabel ha right center right center - ===================== ========== ========= ========== ========== - - Note that the text angles are actually relative to (90 + angle - of the direction to the ticklabel), which gives 0 for bottom - axis. - - Parameters - ---------- - d : {"left", "bottom", "right", "top"} - """ - self.set_default_alignment(d) - self.set_default_angle(d) - - def get_color(self): - return self.get_attribute_from_ref_artist("color") - - def draw(self, renderer): - if not self.get_visible(): - return - - self._offset_radius = \ - self._external_pad + renderer.points_to_pixels(self.get_pad()) - - super().draw(renderer) - - def get_window_extent(self, renderer=None): - if renderer is None: - renderer = self.figure._get_renderer() - if not self.get_visible(): - return - - r = self._external_pad + renderer.points_to_pixels(self.get_pad()) - self._offset_radius = r - - bb = super().get_window_extent(renderer) - - return bb - - -class TickLabels(AxisLabel): # mtext.Text - """ - Tick labels. While derived from `.Text`, this single artist draws all - ticklabels. As in `.AxisLabel`, the position of the text is updated - in the fly, so changing text position has no effect. Otherwise, - the properties can be changed as a normal `.Text`. Unlike the - ticklabels of the mainline Matplotlib, properties of a single - ticklabel alone cannot be modified. - - To change the pad between ticks and ticklabels, use `~.AxisLabel.set_pad`. - """ - - def __init__(self, *, axis_direction="bottom", **kwargs): - super().__init__(**kwargs) - self.set_axis_direction(axis_direction) - self._axislabel_pad = 0 - - def get_ref_artist(self): - # docstring inherited - return self._axis.get_ticklabels()[0] - - def set_axis_direction(self, label_direction): - """ - Adjust the text angle and text alignment of ticklabels - according to the Matplotlib convention. - - The *label_direction* must be one of [left, right, bottom, top]. - - ===================== ========== ========= ========== ========== - Property left bottom right top - ===================== ========== ========= ========== ========== - ticklabel angle 90 0 -90 180 - ticklabel va center baseline center baseline - ticklabel ha right center right center - ===================== ========== ========= ========== ========== - - Note that the text angles are actually relative to (90 + angle - of the direction to the ticklabel), which gives 0 for bottom - axis. - - Parameters - ---------- - label_direction : {"left", "bottom", "right", "top"} - - """ - self.set_default_alignment(label_direction) - self.set_default_angle(label_direction) - self._axis_direction = label_direction - - def invert_axis_direction(self): - label_direction = self._get_opposite_direction(self._axis_direction) - self.set_axis_direction(label_direction) - - def _get_ticklabels_offsets(self, renderer, label_direction): - """ - Calculate the ticklabel offsets from the tick and their total heights. - - The offset only takes account the offset due to the vertical alignment - of the ticklabels: if axis direction is bottom and va is 'top', it will - return 0; if va is 'baseline', it will return (height-descent). - """ - whd_list = self.get_texts_widths_heights_descents(renderer) - - if not whd_list: - return 0, 0 - - r = 0 - va, ha = self.get_va(), self.get_ha() - - if label_direction == "left": - pad = max(w for w, h, d in whd_list) - if ha == "left": - r = pad - elif ha == "center": - r = .5 * pad - elif label_direction == "right": - pad = max(w for w, h, d in whd_list) - if ha == "right": - r = pad - elif ha == "center": - r = .5 * pad - elif label_direction == "bottom": - pad = max(h for w, h, d in whd_list) - if va == "bottom": - r = pad - elif va == "center": - r = .5 * pad - elif va == "baseline": - max_ascent = max(h - d for w, h, d in whd_list) - max_descent = max(d for w, h, d in whd_list) - r = max_ascent - pad = max_ascent + max_descent - elif label_direction == "top": - pad = max(h for w, h, d in whd_list) - if va == "top": - r = pad - elif va == "center": - r = .5 * pad - elif va == "baseline": - max_ascent = max(h - d for w, h, d in whd_list) - max_descent = max(d for w, h, d in whd_list) - r = max_descent - pad = max_ascent + max_descent - - # r : offset - # pad : total height of the ticklabels. This will be used to - # calculate the pad for the axislabel. - return r, pad - - _default_alignments = dict(left=("center", "right"), - right=("center", "left"), - bottom=("baseline", "center"), - top=("baseline", "center")) - - _default_angles = dict(left=90, - right=-90, - bottom=0, - top=180) - - def draw(self, renderer): - if not self.get_visible(): - self._axislabel_pad = self._external_pad - return - - r, total_width = self._get_ticklabels_offsets(renderer, - self._axis_direction) - - pad = self._external_pad + renderer.points_to_pixels(self.get_pad()) - self._offset_radius = r + pad - - for (x, y), a, l in self._locs_angles_labels: - if not l.strip(): - continue - self._ref_angle = a - self.set_x(x) - self.set_y(y) - self.set_text(l) - LabelBase.draw(self, renderer) - - # the value saved will be used to draw axislabel. - self._axislabel_pad = total_width + pad - - def set_locs_angles_labels(self, locs_angles_labels): - self._locs_angles_labels = locs_angles_labels - - def get_window_extents(self, renderer=None): - if renderer is None: - renderer = self.figure._get_renderer() - - if not self.get_visible(): - self._axislabel_pad = self._external_pad - return [] - - bboxes = [] - - r, total_width = self._get_ticklabels_offsets(renderer, - self._axis_direction) - - pad = self._external_pad + renderer.points_to_pixels(self.get_pad()) - self._offset_radius = r + pad - - for (x, y), a, l in self._locs_angles_labels: - self._ref_angle = a - self.set_x(x) - self.set_y(y) - self.set_text(l) - bb = LabelBase.get_window_extent(self, renderer) - bboxes.append(bb) - - # the value saved will be used to draw axislabel. - self._axislabel_pad = total_width + pad - - return bboxes - - def get_texts_widths_heights_descents(self, renderer): - """ - Return a list of ``(width, height, descent)`` tuples for ticklabels. - - Empty labels are left out. - """ - whd_list = [] - for _loc, _angle, label in self._locs_angles_labels: - if not label.strip(): - continue - clean_line, ismath = self._preprocess_math(label) - whd = renderer.get_text_width_height_descent( - clean_line, self._fontproperties, ismath=ismath) - whd_list.append(whd) - return whd_list - - -class GridlinesCollection(LineCollection): - def __init__(self, *args, which="major", axis="both", **kwargs): - """ - Collection of grid lines. - - Parameters - ---------- - which : {"major", "minor"} - Which grid to consider. - axis : {"both", "x", "y"} - Which axis to consider. - *args, **kwargs - Passed to `.LineCollection`. - """ - self._which = which - self._axis = axis - super().__init__(*args, **kwargs) - self.set_grid_helper(None) - - def set_which(self, which): - """ - Select major or minor grid lines. - - Parameters - ---------- - which : {"major", "minor"} - """ - self._which = which - - def set_axis(self, axis): - """ - Select axis. - - Parameters - ---------- - axis : {"both", "x", "y"} - """ - self._axis = axis - - def set_grid_helper(self, grid_helper): - """ - Set grid helper. - - Parameters - ---------- - grid_helper : `.GridHelperBase` subclass - """ - self._grid_helper = grid_helper - - def draw(self, renderer): - if self._grid_helper is not None: - self._grid_helper.update_lim(self.axes) - gl = self._grid_helper.get_gridlines(self._which, self._axis) - self.set_segments([np.transpose(l) for l in gl]) - super().draw(renderer) - - -class AxisArtist(martist.Artist): - """ - An artist which draws axis (a line along which the n-th axes coord - is constant) line, ticks, tick labels, and axis label. - """ - - zorder = 2.5 - - @property - def LABELPAD(self): - return self.label.get_pad() - - @LABELPAD.setter - def LABELPAD(self, v): - self.label.set_pad(v) - - def __init__(self, axes, - helper, - offset=None, - axis_direction="bottom", - **kwargs): - """ - Parameters - ---------- - axes : `mpl_toolkits.axisartist.axislines.Axes` - helper : `~mpl_toolkits.axisartist.axislines.AxisArtistHelper` - """ - # axes is also used to follow the axis attribute (tick color, etc). - - super().__init__(**kwargs) - - self.axes = axes - - self._axis_artist_helper = helper - - if offset is None: - offset = (0, 0) - self.offset_transform = ScaledTranslation( - *offset, - Affine2D().scale(1 / 72) # points to inches. - + self.axes.figure.dpi_scale_trans) - - if axis_direction in ["left", "right"]: - self.axis = axes.yaxis - else: - self.axis = axes.xaxis - - self._axisline_style = None - self._axis_direction = axis_direction - - self._init_line() - self._init_ticks(**kwargs) - self._init_offsetText(axis_direction) - self._init_label() - - # axis direction - self._ticklabel_add_angle = 0. - self._axislabel_add_angle = 0. - self.set_axis_direction(axis_direction) - - # axis direction - - def set_axis_direction(self, axis_direction): - """ - Adjust the direction, text angle, and text alignment of tick labels - and axis labels following the Matplotlib convention for the rectangle - axes. - - The *axis_direction* must be one of [left, right, bottom, top]. - - ===================== ========== ========= ========== ========== - Property left bottom right top - ===================== ========== ========= ========== ========== - ticklabel direction "-" "+" "+" "-" - axislabel direction "-" "+" "+" "-" - ticklabel angle 90 0 -90 180 - ticklabel va center baseline center baseline - ticklabel ha right center right center - axislabel angle 180 0 0 180 - axislabel va center top center bottom - axislabel ha right center right center - ===================== ========== ========= ========== ========== - - Note that the direction "+" and "-" are relative to the direction of - the increasing coordinate. Also, the text angles are actually - relative to (90 + angle of the direction to the ticklabel), - which gives 0 for bottom axis. - - Parameters - ---------- - axis_direction : {"left", "bottom", "right", "top"} - """ - self.major_ticklabels.set_axis_direction(axis_direction) - self.label.set_axis_direction(axis_direction) - self._axis_direction = axis_direction - if axis_direction in ["left", "top"]: - self.set_ticklabel_direction("-") - self.set_axislabel_direction("-") - else: - self.set_ticklabel_direction("+") - self.set_axislabel_direction("+") - - def set_ticklabel_direction(self, tick_direction): - r""" - Adjust the direction of the tick labels. - - Note that the *tick_direction*\s '+' and '-' are relative to the - direction of the increasing coordinate. - - Parameters - ---------- - tick_direction : {"+", "-"} - """ - self._ticklabel_add_angle = _api.check_getitem( - {"+": 0, "-": 180}, tick_direction=tick_direction) - - def invert_ticklabel_direction(self): - self._ticklabel_add_angle = (self._ticklabel_add_angle + 180) % 360 - self.major_ticklabels.invert_axis_direction() - self.minor_ticklabels.invert_axis_direction() - - def set_axislabel_direction(self, label_direction): - r""" - Adjust the direction of the axis label. - - Note that the *label_direction*\s '+' and '-' are relative to the - direction of the increasing coordinate. - - Parameters - ---------- - label_direction : {"+", "-"} - """ - self._axislabel_add_angle = _api.check_getitem( - {"+": 0, "-": 180}, label_direction=label_direction) - - def get_transform(self): - return self.axes.transAxes + self.offset_transform - - def get_helper(self): - """ - Return axis artist helper instance. - """ - return self._axis_artist_helper - - def set_axisline_style(self, axisline_style=None, **kwargs): - """ - Set the axisline style. - - The new style is completely defined by the passed attributes. Existing - style attributes are forgotten. - - Parameters - ---------- - axisline_style : str or None - The line style, e.g. '->', optionally followed by a comma-separated - list of attributes. Alternatively, the attributes can be provided - as keywords. - - If *None* this returns a string containing the available styles. - - Examples - -------- - The following two commands are equal: - - >>> set_axisline_style("->,size=1.5") - >>> set_axisline_style("->", size=1.5) - """ - if axisline_style is None: - return AxislineStyle.pprint_styles() - - if isinstance(axisline_style, AxislineStyle._Base): - self._axisline_style = axisline_style - else: - self._axisline_style = AxislineStyle(axisline_style, **kwargs) - - self._init_line() - - def get_axisline_style(self): - """Return the current axisline style.""" - return self._axisline_style - - def _init_line(self): - """ - Initialize the *line* artist that is responsible to draw the axis line. - """ - tran = (self._axis_artist_helper.get_line_transform(self.axes) - + self.offset_transform) - - axisline_style = self.get_axisline_style() - if axisline_style is None: - self.line = PathPatch( - self._axis_artist_helper.get_line(self.axes), - color=mpl.rcParams['axes.edgecolor'], - fill=False, - linewidth=mpl.rcParams['axes.linewidth'], - capstyle=mpl.rcParams['lines.solid_capstyle'], - joinstyle=mpl.rcParams['lines.solid_joinstyle'], - transform=tran) - else: - self.line = axisline_style(self, transform=tran) - - def _draw_line(self, renderer): - self.line.set_path(self._axis_artist_helper.get_line(self.axes)) - if self.get_axisline_style() is not None: - self.line.set_line_mutation_scale(self.major_ticklabels.get_size()) - self.line.draw(renderer) - - def _init_ticks(self, **kwargs): - axis_name = self.axis.axis_name - - trans = (self._axis_artist_helper.get_tick_transform(self.axes) - + self.offset_transform) - - self.major_ticks = Ticks( - kwargs.get( - "major_tick_size", - mpl.rcParams[f"{axis_name}tick.major.size"]), - axis=self.axis, transform=trans) - self.minor_ticks = Ticks( - kwargs.get( - "minor_tick_size", - mpl.rcParams[f"{axis_name}tick.minor.size"]), - axis=self.axis, transform=trans) - - size = mpl.rcParams[f"{axis_name}tick.labelsize"] - self.major_ticklabels = TickLabels( - axis=self.axis, - axis_direction=self._axis_direction, - figure=self.axes.figure, - transform=trans, - fontsize=size, - pad=kwargs.get( - "major_tick_pad", mpl.rcParams[f"{axis_name}tick.major.pad"]), - ) - self.minor_ticklabels = TickLabels( - axis=self.axis, - axis_direction=self._axis_direction, - figure=self.axes.figure, - transform=trans, - fontsize=size, - pad=kwargs.get( - "minor_tick_pad", mpl.rcParams[f"{axis_name}tick.minor.pad"]), - ) - - def _get_tick_info(self, tick_iter): - """ - Return a pair of: - - - list of locs and angles for ticks - - list of locs, angles and labels for ticklabels. - """ - ticks_loc_angle = [] - ticklabels_loc_angle_label = [] - - ticklabel_add_angle = self._ticklabel_add_angle - - for loc, angle_normal, angle_tangent, label in tick_iter: - angle_label = angle_tangent - 90 + ticklabel_add_angle - angle_tick = (angle_normal - if 90 <= (angle_label - angle_normal) % 360 <= 270 - else angle_normal + 180) - ticks_loc_angle.append([loc, angle_tick]) - ticklabels_loc_angle_label.append([loc, angle_label, label]) - - return ticks_loc_angle, ticklabels_loc_angle_label - - def _update_ticks(self, renderer=None): - # set extra pad for major and minor ticklabels: use ticksize of - # majorticks even for minor ticks. not clear what is best. - - if renderer is None: - renderer = self.figure._get_renderer() - - dpi_cor = renderer.points_to_pixels(1.) - if self.major_ticks.get_visible() and self.major_ticks.get_tick_out(): - ticklabel_pad = self.major_ticks._ticksize * dpi_cor - self.major_ticklabels._external_pad = ticklabel_pad - self.minor_ticklabels._external_pad = ticklabel_pad - else: - self.major_ticklabels._external_pad = 0 - self.minor_ticklabels._external_pad = 0 - - majortick_iter, minortick_iter = \ - self._axis_artist_helper.get_tick_iterators(self.axes) - - tick_loc_angle, ticklabel_loc_angle_label = \ - self._get_tick_info(majortick_iter) - self.major_ticks.set_locs_angles(tick_loc_angle) - self.major_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label) - - tick_loc_angle, ticklabel_loc_angle_label = \ - self._get_tick_info(minortick_iter) - self.minor_ticks.set_locs_angles(tick_loc_angle) - self.minor_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label) - - def _draw_ticks(self, renderer): - self._update_ticks(renderer) - self.major_ticks.draw(renderer) - self.major_ticklabels.draw(renderer) - self.minor_ticks.draw(renderer) - self.minor_ticklabels.draw(renderer) - if (self.major_ticklabels.get_visible() - or self.minor_ticklabels.get_visible()): - self._draw_offsetText(renderer) - - _offsetText_pos = dict(left=(0, 1, "bottom", "right"), - right=(1, 1, "bottom", "left"), - bottom=(1, 0, "top", "right"), - top=(1, 1, "bottom", "right")) - - def _init_offsetText(self, direction): - x, y, va, ha = self._offsetText_pos[direction] - self.offsetText = mtext.Annotation( - "", - xy=(x, y), xycoords="axes fraction", - xytext=(0, 0), textcoords="offset points", - color=mpl.rcParams['xtick.color'], - horizontalalignment=ha, verticalalignment=va, - ) - self.offsetText.set_transform(IdentityTransform()) - self.axes._set_artist_props(self.offsetText) - - def _update_offsetText(self): - self.offsetText.set_text(self.axis.major.formatter.get_offset()) - self.offsetText.set_size(self.major_ticklabels.get_size()) - offset = (self.major_ticklabels.get_pad() - + self.major_ticklabels.get_size() - + 2) - self.offsetText.xyann = (0, offset) - - def _draw_offsetText(self, renderer): - self._update_offsetText() - self.offsetText.draw(renderer) - - def _init_label(self, **kwargs): - tr = (self._axis_artist_helper.get_axislabel_transform(self.axes) - + self.offset_transform) - self.label = AxisLabel( - 0, 0, "__from_axes__", - color="auto", - fontsize=kwargs.get("labelsize", mpl.rcParams['axes.labelsize']), - fontweight=mpl.rcParams['axes.labelweight'], - axis=self.axis, - transform=tr, - axis_direction=self._axis_direction, - ) - self.label.set_figure(self.axes.figure) - labelpad = kwargs.get("labelpad", 5) - self.label.set_pad(labelpad) - - def _update_label(self, renderer): - if not self.label.get_visible(): - return - - if self._ticklabel_add_angle != self._axislabel_add_angle: - if ((self.major_ticks.get_visible() - and not self.major_ticks.get_tick_out()) - or (self.minor_ticks.get_visible() - and not self.major_ticks.get_tick_out())): - axislabel_pad = self.major_ticks._ticksize - else: - axislabel_pad = 0 - else: - axislabel_pad = max(self.major_ticklabels._axislabel_pad, - self.minor_ticklabels._axislabel_pad) - - self.label._external_pad = axislabel_pad - - xy, angle_tangent = \ - self._axis_artist_helper.get_axislabel_pos_angle(self.axes) - if xy is None: - return - - angle_label = angle_tangent - 90 - - x, y = xy - self.label._ref_angle = angle_label + self._axislabel_add_angle - self.label.set(x=x, y=y) - - def _draw_label(self, renderer): - self._update_label(renderer) - self.label.draw(renderer) - - def set_label(self, s): - # docstring inherited - self.label.set_text(s) - - def get_tightbbox(self, renderer=None): - if not self.get_visible(): - return - self._axis_artist_helper.update_lim(self.axes) - self._update_ticks(renderer) - self._update_label(renderer) - - self.line.set_path(self._axis_artist_helper.get_line(self.axes)) - if self.get_axisline_style() is not None: - self.line.set_line_mutation_scale(self.major_ticklabels.get_size()) - - bb = [ - *self.major_ticklabels.get_window_extents(renderer), - *self.minor_ticklabels.get_window_extents(renderer), - self.label.get_window_extent(renderer), - self.offsetText.get_window_extent(renderer), - self.line.get_window_extent(renderer), - ] - bb = [b for b in bb if b and (b.width != 0 or b.height != 0)] - if bb: - _bbox = Bbox.union(bb) - return _bbox - else: - return None - - @martist.allow_rasterization - def draw(self, renderer): - # docstring inherited - if not self.get_visible(): - return - renderer.open_group(__name__, gid=self.get_gid()) - self._axis_artist_helper.update_lim(self.axes) - self._draw_ticks(renderer) - self._draw_line(renderer) - self._draw_label(renderer) - renderer.close_group(__name__) - - def toggle(self, all=None, ticks=None, ticklabels=None, label=None): - """ - Toggle visibility of ticks, ticklabels, and (axis) label. - To turn all off, :: - - axis.toggle(all=False) - - To turn all off but ticks on :: - - axis.toggle(all=False, ticks=True) - - To turn all on but (axis) label off :: - - axis.toggle(all=True, label=False) - - """ - if all: - _ticks, _ticklabels, _label = True, True, True - elif all is not None: - _ticks, _ticklabels, _label = False, False, False - else: - _ticks, _ticklabels, _label = None, None, None - - if ticks is not None: - _ticks = ticks - if ticklabels is not None: - _ticklabels = ticklabels - if label is not None: - _label = label - - if _ticks is not None: - self.major_ticks.set_visible(_ticks) - self.minor_ticks.set_visible(_ticks) - if _ticklabels is not None: - self.major_ticklabels.set_visible(_ticklabels) - self.minor_ticklabels.set_visible(_ticklabels) - if _label is not None: - self.label.set_visible(_label) diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axisline_style.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axisline_style.py deleted file mode 100644 index 5ae188021bb..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axisline_style.py +++ /dev/null @@ -1,193 +0,0 @@ -""" -Provides classes to style the axis lines. -""" -import math - -import numpy as np - -import matplotlib as mpl -from matplotlib.patches import _Style, FancyArrowPatch -from matplotlib.path import Path -from matplotlib.transforms import IdentityTransform - - -class _FancyAxislineStyle: - class SimpleArrow(FancyArrowPatch): - """The artist class that will be returned for SimpleArrow style.""" - _ARROW_STYLE = "->" - - def __init__(self, axis_artist, line_path, transform, - line_mutation_scale): - self._axis_artist = axis_artist - self._line_transform = transform - self._line_path = line_path - self._line_mutation_scale = line_mutation_scale - - FancyArrowPatch.__init__(self, - path=self._line_path, - arrowstyle=self._ARROW_STYLE, - patchA=None, - patchB=None, - shrinkA=0., - shrinkB=0., - mutation_scale=line_mutation_scale, - mutation_aspect=None, - transform=IdentityTransform(), - ) - - def set_line_mutation_scale(self, scale): - self.set_mutation_scale(scale*self._line_mutation_scale) - - def _extend_path(self, path, mutation_size=10): - """ - Extend the path to make a room for drawing arrow. - """ - (x0, y0), (x1, y1) = path.vertices[-2:] - theta = math.atan2(y1 - y0, x1 - x0) - x2 = x1 + math.cos(theta) * mutation_size - y2 = y1 + math.sin(theta) * mutation_size - if path.codes is None: - return Path(np.concatenate([path.vertices, [[x2, y2]]])) - else: - return Path(np.concatenate([path.vertices, [[x2, y2]]]), - np.concatenate([path.codes, [Path.LINETO]])) - - def set_path(self, path): - self._line_path = path - - def draw(self, renderer): - """ - Draw the axis line. - 1) Transform the path to the display coordinate. - 2) Extend the path to make a room for arrow. - 3) Update the path of the FancyArrowPatch. - 4) Draw. - """ - path_in_disp = self._line_transform.transform_path(self._line_path) - mutation_size = self.get_mutation_scale() # line_mutation_scale() - extended_path = self._extend_path(path_in_disp, - mutation_size=mutation_size) - self._path_original = extended_path - FancyArrowPatch.draw(self, renderer) - - def get_window_extent(self, renderer=None): - - path_in_disp = self._line_transform.transform_path(self._line_path) - mutation_size = self.get_mutation_scale() # line_mutation_scale() - extended_path = self._extend_path(path_in_disp, - mutation_size=mutation_size) - self._path_original = extended_path - return FancyArrowPatch.get_window_extent(self, renderer) - - class FilledArrow(SimpleArrow): - """The artist class that will be returned for FilledArrow style.""" - _ARROW_STYLE = "-|>" - - def __init__(self, axis_artist, line_path, transform, - line_mutation_scale, facecolor): - super().__init__(axis_artist, line_path, transform, - line_mutation_scale) - self.set_facecolor(facecolor) - - -class AxislineStyle(_Style): - """ - A container class which defines style classes for AxisArtists. - - An instance of any axisline style class is a callable object, - whose call signature is :: - - __call__(self, axis_artist, path, transform) - - When called, this should return an `.Artist` with the following methods:: - - def set_path(self, path): - # set the path for axisline. - - def set_line_mutation_scale(self, scale): - # set the scale - - def draw(self, renderer): - # draw - """ - - _style_list = {} - - class _Base: - # 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. - - def __init__(self): - """ - initialization. - """ - super().__init__() - - def __call__(self, axis_artist, transform): - """ - Given the AxisArtist instance, and transform for the path (set_path - method), return the Matplotlib artist for drawing the axis line. - """ - return self.new_line(axis_artist, transform) - - class SimpleArrow(_Base): - """ - A simple arrow. - """ - - ArrowAxisClass = _FancyAxislineStyle.SimpleArrow - - def __init__(self, size=1): - """ - Parameters - ---------- - size : float - Size of the arrow as a fraction of the ticklabel size. - """ - - self.size = size - super().__init__() - - def new_line(self, axis_artist, transform): - - linepath = Path([(0, 0), (0, 1)]) - axisline = self.ArrowAxisClass(axis_artist, linepath, transform, - line_mutation_scale=self.size) - return axisline - - _style_list["->"] = SimpleArrow - - class FilledArrow(SimpleArrow): - """ - An arrow with a filled head. - """ - - ArrowAxisClass = _FancyAxislineStyle.FilledArrow - - def __init__(self, size=1, facecolor=None): - """ - Parameters - ---------- - size : float - Size of the arrow as a fraction of the ticklabel size. - facecolor : color, default: :rc:`axes.edgecolor` - Fill color. - - .. versionadded:: 3.7 - """ - - if facecolor is None: - facecolor = mpl.rcParams['axes.edgecolor'] - self.size = size - self._facecolor = facecolor - super().__init__(size=size) - - def new_line(self, axis_artist, transform): - linepath = Path([(0, 0), (0, 1)]) - axisline = self.ArrowAxisClass(axis_artist, linepath, transform, - line_mutation_scale=self.size, - facecolor=self._facecolor) - return axisline - - _style_list["-|>"] = FilledArrow diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axislines.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axislines.py deleted file mode 100644 index 35717da8eaa..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/axislines.py +++ /dev/null @@ -1,531 +0,0 @@ -""" -Axislines includes modified implementation of the Axes class. The -biggest difference is that the artists responsible for drawing the axis spine, -ticks, ticklabels and axis labels are separated out from Matplotlib's Axis -class. Originally, this change was motivated to support curvilinear -grid. Here are a few reasons that I came up with a new axes class: - -* "top" and "bottom" x-axis (or "left" and "right" y-axis) can have - different ticks (tick locations and labels). This is not possible - with the current Matplotlib, although some twin axes trick can help. - -* Curvilinear grid. - -* angled ticks. - -In the new axes class, xaxis and yaxis is set to not visible by -default, and new set of artist (AxisArtist) are defined to draw axis -line, ticks, ticklabels and axis label. Axes.axis attribute serves as -a dictionary of these artists, i.e., ax.axis["left"] is a AxisArtist -instance responsible to draw left y-axis. The default Axes.axis contains -"bottom", "left", "top" and "right". - -AxisArtist can be considered as a container artist and has the following -children artists which will draw ticks, labels, etc. - -* line -* major_ticks, major_ticklabels -* minor_ticks, minor_ticklabels -* offsetText -* label - -Note that these are separate artists from `matplotlib.axis.Axis`, thus most -tick-related functions in Matplotlib won't work. For example, color and -markerwidth of the ``ax.axis["bottom"].major_ticks`` will follow those of -Axes.xaxis unless explicitly specified. - -In addition to AxisArtist, the Axes will have *gridlines* attribute, -which obviously draws grid lines. The gridlines needs to be separated -from the axis as some gridlines can never pass any axis. -""" - -import numpy as np - -import matplotlib as mpl -from matplotlib import _api -import matplotlib.axes as maxes -from matplotlib.path import Path -from mpl_toolkits.axes_grid1 import mpl_axes -from .axisline_style import AxislineStyle # noqa -from .axis_artist import AxisArtist, GridlinesCollection - - -class _AxisArtistHelperBase: - """ - Base class for axis helper. - - Subclasses should define the methods listed below. The *axes* - argument will be the ``.axes`` attribute of the caller artist. :: - - # Construct the spine. - - def get_line_transform(self, axes): - return transform - - def get_line(self, axes): - return path - - # Construct the label. - - def get_axislabel_transform(self, axes): - return transform - - def get_axislabel_pos_angle(self, axes): - return (x, y), angle - - # Construct the ticks. - - def get_tick_transform(self, axes): - return transform - - def get_tick_iterators(self, axes): - # A pair of iterables (one for major ticks, one for minor ticks) - # that yield (tick_position, tick_angle, tick_label). - return iter_major, iter_minor - """ - - def update_lim(self, axes): - pass - - def _to_xy(self, values, const): - """ - Create a (*values.shape, 2)-shape array representing (x, y) pairs. - - The other coordinate is filled with the constant *const*. - - Example:: - - >>> self.nth_coord = 0 - >>> self._to_xy([1, 2, 3], const=0) - array([[1, 0], - [2, 0], - [3, 0]]) - """ - if self.nth_coord == 0: - return np.stack(np.broadcast_arrays(values, const), axis=-1) - elif self.nth_coord == 1: - return np.stack(np.broadcast_arrays(const, values), axis=-1) - else: - raise ValueError("Unexpected nth_coord") - - -class _FixedAxisArtistHelperBase(_AxisArtistHelperBase): - """Helper class for a fixed (in the axes coordinate) axis.""" - - passthru_pt = _api.deprecated("3.7")(property( - lambda self: {"left": (0, 0), "right": (1, 0), - "bottom": (0, 0), "top": (0, 1)}[self._loc])) - - def __init__(self, loc, nth_coord=None): - """``nth_coord = 0``: x-axis; ``nth_coord = 1``: y-axis.""" - self.nth_coord = ( - nth_coord if nth_coord is not None else - _api.check_getitem( - {"bottom": 0, "top": 0, "left": 1, "right": 1}, loc=loc)) - if (nth_coord == 0 and loc not in ["left", "right"] - or nth_coord == 1 and loc not in ["bottom", "top"]): - _api.warn_deprecated( - "3.7", message=f"{loc=!r} is incompatible with " - "{nth_coord=}; support is deprecated since %(since)s") - self._loc = loc - self._pos = {"bottom": 0, "top": 1, "left": 0, "right": 1}[loc] - super().__init__() - # axis line in transAxes - self._path = Path(self._to_xy((0, 1), const=self._pos)) - - def get_nth_coord(self): - return self.nth_coord - - # LINE - - def get_line(self, axes): - return self._path - - def get_line_transform(self, axes): - return axes.transAxes - - # LABEL - - def get_axislabel_transform(self, axes): - return axes.transAxes - - def get_axislabel_pos_angle(self, axes): - """ - Return the label reference position in transAxes. - - get_label_transform() returns a transform of (transAxes+offset) - """ - return dict(left=((0., 0.5), 90), # (position, angle_tangent) - right=((1., 0.5), 90), - bottom=((0.5, 0.), 0), - top=((0.5, 1.), 0))[self._loc] - - # TICK - - def get_tick_transform(self, axes): - return [axes.get_xaxis_transform(), - axes.get_yaxis_transform()][self.nth_coord] - - -class _FloatingAxisArtistHelperBase(_AxisArtistHelperBase): - - def __init__(self, nth_coord, value): - self.nth_coord = nth_coord - self._value = value - super().__init__() - - def get_nth_coord(self): - return self.nth_coord - - def get_line(self, axes): - raise RuntimeError( - "get_line method should be defined by the derived class") - - -class FixedAxisArtistHelperRectilinear(_FixedAxisArtistHelperBase): - - def __init__(self, axes, loc, nth_coord=None): - """ - nth_coord = along which coordinate value varies - in 2D, nth_coord = 0 -> x axis, nth_coord = 1 -> y axis - """ - super().__init__(loc, nth_coord) - self.axis = [axes.xaxis, axes.yaxis][self.nth_coord] - - # TICK - - def get_tick_iterators(self, axes): - """tick_loc, tick_angle, tick_label""" - if self._loc in ["bottom", "top"]: - angle_normal, angle_tangent = 90, 0 - else: # "left", "right" - angle_normal, angle_tangent = 0, 90 - - major = self.axis.major - major_locs = major.locator() - major_labels = major.formatter.format_ticks(major_locs) - - minor = self.axis.minor - minor_locs = minor.locator() - minor_labels = minor.formatter.format_ticks(minor_locs) - - tick_to_axes = self.get_tick_transform(axes) - axes.transAxes - - def _f(locs, labels): - for loc, label in zip(locs, labels): - c = self._to_xy(loc, const=self._pos) - # check if the tick point is inside axes - c2 = tick_to_axes.transform(c) - if mpl.transforms._interval_contains_close( - (0, 1), c2[self.nth_coord]): - yield c, angle_normal, angle_tangent, label - - return _f(major_locs, major_labels), _f(minor_locs, minor_labels) - - -class FloatingAxisArtistHelperRectilinear(_FloatingAxisArtistHelperBase): - - def __init__(self, axes, nth_coord, - passingthrough_point, axis_direction="bottom"): - super().__init__(nth_coord, passingthrough_point) - self._axis_direction = axis_direction - self.axis = [axes.xaxis, axes.yaxis][self.nth_coord] - - def get_line(self, axes): - fixed_coord = 1 - self.nth_coord - data_to_axes = axes.transData - axes.transAxes - p = data_to_axes.transform([self._value, self._value]) - return Path(self._to_xy((0, 1), const=p[fixed_coord])) - - def get_line_transform(self, axes): - return axes.transAxes - - def get_axislabel_transform(self, axes): - return axes.transAxes - - def get_axislabel_pos_angle(self, axes): - """ - Return the label reference position in transAxes. - - get_label_transform() returns a transform of (transAxes+offset) - """ - angle = [0, 90][self.nth_coord] - fixed_coord = 1 - self.nth_coord - data_to_axes = axes.transData - axes.transAxes - p = data_to_axes.transform([self._value, self._value]) - verts = self._to_xy(0.5, const=p[fixed_coord]) - if 0 <= verts[fixed_coord] <= 1: - return verts, angle - else: - return None, None - - def get_tick_transform(self, axes): - return axes.transData - - def get_tick_iterators(self, axes): - """tick_loc, tick_angle, tick_label""" - if self.nth_coord == 0: - angle_normal, angle_tangent = 90, 0 - else: - angle_normal, angle_tangent = 0, 90 - - major = self.axis.major - major_locs = major.locator() - major_labels = major.formatter.format_ticks(major_locs) - - minor = self.axis.minor - minor_locs = minor.locator() - minor_labels = minor.formatter.format_ticks(minor_locs) - - data_to_axes = axes.transData - axes.transAxes - - def _f(locs, labels): - for loc, label in zip(locs, labels): - c = self._to_xy(loc, const=self._value) - c1, c2 = data_to_axes.transform(c) - if 0 <= c1 <= 1 and 0 <= c2 <= 1: - yield c, angle_normal, angle_tangent, label - - return _f(major_locs, major_labels), _f(minor_locs, minor_labels) - - -class AxisArtistHelper: # Backcompat. - Fixed = _FixedAxisArtistHelperBase - Floating = _FloatingAxisArtistHelperBase - - -class AxisArtistHelperRectlinear: # Backcompat. - Fixed = FixedAxisArtistHelperRectilinear - Floating = FloatingAxisArtistHelperRectilinear - - -class GridHelperBase: - - def __init__(self): - self._old_limits = None - super().__init__() - - def update_lim(self, axes): - x1, x2 = axes.get_xlim() - y1, y2 = axes.get_ylim() - if self._old_limits != (x1, x2, y1, y2): - self._update_grid(x1, y1, x2, y2) - self._old_limits = (x1, x2, y1, y2) - - def _update_grid(self, x1, y1, x2, y2): - """Cache relevant computations when the axes limits have changed.""" - - def get_gridlines(self, which, axis): - """ - Return list of grid lines as a list of paths (list of points). - - Parameters - ---------- - which : {"both", "major", "minor"} - axis : {"both", "x", "y"} - """ - return [] - - -class GridHelperRectlinear(GridHelperBase): - - def __init__(self, axes): - super().__init__() - self.axes = axes - - def new_fixed_axis(self, loc, - nth_coord=None, - axis_direction=None, - offset=None, - axes=None, - ): - if axes is None: - _api.warn_external( - "'new_fixed_axis' explicitly requires the axes keyword.") - axes = self.axes - if axis_direction is None: - axis_direction = loc - - helper = FixedAxisArtistHelperRectilinear(axes, loc, nth_coord) - axisline = AxisArtist(axes, helper, offset=offset, - axis_direction=axis_direction) - return axisline - - def new_floating_axis(self, nth_coord, value, - axis_direction="bottom", - axes=None, - ): - if axes is None: - _api.warn_external( - "'new_floating_axis' explicitly requires the axes keyword.") - axes = self.axes - - helper = FloatingAxisArtistHelperRectilinear( - axes, nth_coord, value, axis_direction) - axisline = AxisArtist(axes, helper, axis_direction=axis_direction) - axisline.line.set_clip_on(True) - axisline.line.set_clip_box(axisline.axes.bbox) - return axisline - - def get_gridlines(self, which="major", axis="both"): - """ - Return list of gridline coordinates in data coordinates. - - Parameters - ---------- - which : {"both", "major", "minor"} - axis : {"both", "x", "y"} - """ - _api.check_in_list(["both", "major", "minor"], which=which) - _api.check_in_list(["both", "x", "y"], axis=axis) - gridlines = [] - - if axis in ("both", "x"): - locs = [] - y1, y2 = self.axes.get_ylim() - if which in ("both", "major"): - locs.extend(self.axes.xaxis.major.locator()) - if which in ("both", "minor"): - locs.extend(self.axes.xaxis.minor.locator()) - - for x in locs: - gridlines.append([[x, x], [y1, y2]]) - - if axis in ("both", "y"): - x1, x2 = self.axes.get_xlim() - locs = [] - if self.axes.yaxis._major_tick_kw["gridOn"]: - locs.extend(self.axes.yaxis.major.locator()) - if self.axes.yaxis._minor_tick_kw["gridOn"]: - locs.extend(self.axes.yaxis.minor.locator()) - - for y in locs: - gridlines.append([[x1, x2], [y, y]]) - - return gridlines - - -class Axes(maxes.Axes): - - @_api.deprecated("3.8", alternative="ax.axis") - def __call__(self, *args, **kwargs): - return maxes.Axes.axis(self.axes, *args, **kwargs) - - def __init__(self, *args, grid_helper=None, **kwargs): - self._axisline_on = True - self._grid_helper = (grid_helper if grid_helper - else GridHelperRectlinear(self)) - super().__init__(*args, **kwargs) - self.toggle_axisline(True) - - def toggle_axisline(self, b=None): - if b is None: - b = not self._axisline_on - if b: - self._axisline_on = True - self.spines[:].set_visible(False) - self.xaxis.set_visible(False) - self.yaxis.set_visible(False) - else: - self._axisline_on = False - self.spines[:].set_visible(True) - self.xaxis.set_visible(True) - self.yaxis.set_visible(True) - - @property - def axis(self): - return self._axislines - - def clear(self): - # docstring inherited - - # Init gridlines before clear() as clear() calls grid(). - self.gridlines = gridlines = GridlinesCollection( - [], - colors=mpl.rcParams['grid.color'], - linestyles=mpl.rcParams['grid.linestyle'], - linewidths=mpl.rcParams['grid.linewidth']) - self._set_artist_props(gridlines) - gridlines.set_grid_helper(self.get_grid_helper()) - - super().clear() - - # clip_path is set after Axes.clear(): that's when a patch is created. - gridlines.set_clip_path(self.axes.patch) - - # Init axis artists. - self._axislines = mpl_axes.Axes.AxisDict(self) - new_fixed_axis = self.get_grid_helper().new_fixed_axis - self._axislines.update({ - loc: new_fixed_axis(loc=loc, axes=self, axis_direction=loc) - for loc in ["bottom", "top", "left", "right"]}) - for axisline in [self._axislines["top"], self._axislines["right"]]: - axisline.label.set_visible(False) - axisline.major_ticklabels.set_visible(False) - axisline.minor_ticklabels.set_visible(False) - - def get_grid_helper(self): - return self._grid_helper - - def grid(self, visible=None, which='major', axis="both", **kwargs): - """ - Toggle the gridlines, and optionally set the properties of the lines. - """ - # There are some discrepancies in the behavior of grid() between - # axes_grid and Matplotlib, because axes_grid explicitly sets the - # visibility of the gridlines. - super().grid(visible, which=which, axis=axis, **kwargs) - if not self._axisline_on: - return - if visible is None: - visible = (self.axes.xaxis._minor_tick_kw["gridOn"] - or self.axes.xaxis._major_tick_kw["gridOn"] - or self.axes.yaxis._minor_tick_kw["gridOn"] - or self.axes.yaxis._major_tick_kw["gridOn"]) - self.gridlines.set(which=which, axis=axis, visible=visible) - self.gridlines.set(**kwargs) - - def get_children(self): - if self._axisline_on: - children = [*self._axislines.values(), self.gridlines] - else: - children = [] - children.extend(super().get_children()) - return children - - def new_fixed_axis(self, loc, offset=None): - gh = self.get_grid_helper() - axis = gh.new_fixed_axis(loc, - nth_coord=None, - axis_direction=None, - offset=offset, - axes=self, - ) - return axis - - def new_floating_axis(self, nth_coord, value, axis_direction="bottom"): - gh = self.get_grid_helper() - axis = gh.new_floating_axis(nth_coord, value, - axis_direction=axis_direction, - axes=self) - return axis - - -class AxesZero(Axes): - - def clear(self): - super().clear() - new_floating_axis = self.get_grid_helper().new_floating_axis - self._axislines.update( - xzero=new_floating_axis( - nth_coord=0, value=0., axis_direction="bottom", axes=self), - yzero=new_floating_axis( - nth_coord=1, value=0., axis_direction="left", axes=self), - ) - for k in ["xzero", "yzero"]: - self._axislines[k].line.set_clip_path(self.patch) - self._axislines[k].set_visible(False) - - -Subplot = Axes -SubplotZero = AxesZero diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/floating_axes.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/floating_axes.py deleted file mode 100644 index 97dafe98c69..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/floating_axes.py +++ /dev/null @@ -1,298 +0,0 @@ -""" -An experimental support for curvilinear grid. -""" - -# TODO : -# see if tick_iterator method can be simplified by reusing the parent method. - -import functools - -import numpy as np - -import matplotlib as mpl -from matplotlib import _api, cbook -import matplotlib.patches as mpatches -from matplotlib.path import Path - -from mpl_toolkits.axes_grid1.parasite_axes import host_axes_class_factory - -from . import axislines, grid_helper_curvelinear -from .axis_artist import AxisArtist -from .grid_finder import ExtremeFinderSimple - - -class FloatingAxisArtistHelper( - grid_helper_curvelinear.FloatingAxisArtistHelper): - pass - - -class FixedAxisArtistHelper(grid_helper_curvelinear.FloatingAxisArtistHelper): - - def __init__(self, grid_helper, side, nth_coord_ticks=None): - """ - nth_coord = along which coordinate value varies. - nth_coord = 0 -> x axis, nth_coord = 1 -> y axis - """ - lon1, lon2, lat1, lat2 = grid_helper.grid_finder.extreme_finder(*[None] * 5) - value, nth_coord = _api.check_getitem( - dict(left=(lon1, 0), right=(lon2, 0), bottom=(lat1, 1), top=(lat2, 1)), - side=side) - super().__init__(grid_helper, nth_coord, value, axis_direction=side) - if nth_coord_ticks is None: - nth_coord_ticks = nth_coord - self.nth_coord_ticks = nth_coord_ticks - - self.value = value - self.grid_helper = grid_helper - self._side = side - - def update_lim(self, axes): - self.grid_helper.update_lim(axes) - self._grid_info = self.grid_helper._grid_info - - def get_tick_iterators(self, axes): - """tick_loc, tick_angle, tick_label, (optionally) tick_label""" - - grid_finder = self.grid_helper.grid_finder - - lat_levs, lat_n, lat_factor = self._grid_info["lat_info"] - yy0 = lat_levs / lat_factor - - lon_levs, lon_n, lon_factor = self._grid_info["lon_info"] - xx0 = lon_levs / lon_factor - - extremes = self.grid_helper.grid_finder.extreme_finder(*[None] * 5) - xmin, xmax = sorted(extremes[:2]) - ymin, ymax = sorted(extremes[2:]) - - def trf_xy(x, y): - trf = grid_finder.get_transform() + axes.transData - return trf.transform(np.column_stack(np.broadcast_arrays(x, y))).T - - if self.nth_coord == 0: - mask = (ymin <= yy0) & (yy0 <= ymax) - (xx1, yy1), (dxx1, dyy1), (dxx2, dyy2) = \ - grid_helper_curvelinear._value_and_jacobian( - trf_xy, self.value, yy0[mask], (xmin, xmax), (ymin, ymax)) - labels = self._grid_info["lat_labels"] - - elif self.nth_coord == 1: - mask = (xmin <= xx0) & (xx0 <= xmax) - (xx1, yy1), (dxx2, dyy2), (dxx1, dyy1) = \ - grid_helper_curvelinear._value_and_jacobian( - trf_xy, xx0[mask], self.value, (xmin, xmax), (ymin, ymax)) - labels = self._grid_info["lon_labels"] - - labels = [l for l, m in zip(labels, mask) if m] - - angle_normal = np.arctan2(dyy1, dxx1) - angle_tangent = np.arctan2(dyy2, dxx2) - mm = (dyy1 == 0) & (dxx1 == 0) # points with degenerate normal - angle_normal[mm] = angle_tangent[mm] + np.pi / 2 - - tick_to_axes = self.get_tick_transform(axes) - axes.transAxes - in_01 = functools.partial( - mpl.transforms._interval_contains_close, (0, 1)) - - def f1(): - for x, y, normal, tangent, lab \ - in zip(xx1, yy1, angle_normal, angle_tangent, labels): - c2 = tick_to_axes.transform((x, y)) - if in_01(c2[0]) and in_01(c2[1]): - yield [x, y], *np.rad2deg([normal, tangent]), lab - - return f1(), iter([]) - - def get_line(self, axes): - self.update_lim(axes) - k, v = dict(left=("lon_lines0", 0), - right=("lon_lines0", 1), - bottom=("lat_lines0", 0), - top=("lat_lines0", 1))[self._side] - xx, yy = self._grid_info[k][v] - return Path(np.column_stack([xx, yy])) - - -class ExtremeFinderFixed(ExtremeFinderSimple): - # docstring inherited - - def __init__(self, extremes): - """ - This subclass always returns the same bounding box. - - Parameters - ---------- - extremes : (float, float, float, float) - The bounding box that this helper always returns. - """ - self._extremes = extremes - - def __call__(self, transform_xy, x1, y1, x2, y2): - # docstring inherited - return self._extremes - - -class GridHelperCurveLinear(grid_helper_curvelinear.GridHelperCurveLinear): - - def __init__(self, aux_trans, extremes, - grid_locator1=None, - grid_locator2=None, - tick_formatter1=None, - tick_formatter2=None): - # docstring inherited - super().__init__(aux_trans, - extreme_finder=ExtremeFinderFixed(extremes), - grid_locator1=grid_locator1, - grid_locator2=grid_locator2, - tick_formatter1=tick_formatter1, - tick_formatter2=tick_formatter2) - - @_api.deprecated("3.8") - def get_data_boundary(self, side): - """ - Return v=0, nth=1. - """ - lon1, lon2, lat1, lat2 = self.grid_finder.extreme_finder(*[None] * 5) - return dict(left=(lon1, 0), - right=(lon2, 0), - bottom=(lat1, 1), - top=(lat2, 1))[side] - - def new_fixed_axis(self, loc, - nth_coord=None, - axis_direction=None, - offset=None, - axes=None): - if axes is None: - axes = self.axes - if axis_direction is None: - axis_direction = loc - # This is not the same as the FixedAxisArtistHelper class used by - # grid_helper_curvelinear.GridHelperCurveLinear.new_fixed_axis! - helper = FixedAxisArtistHelper( - self, loc, nth_coord_ticks=nth_coord) - axisline = AxisArtist(axes, helper, axis_direction=axis_direction) - # Perhaps should be moved to the base class? - axisline.line.set_clip_on(True) - axisline.line.set_clip_box(axisline.axes.bbox) - return axisline - - # new_floating_axis will inherit the grid_helper's extremes. - - # def new_floating_axis(self, nth_coord, - # value, - # axes=None, - # axis_direction="bottom" - # ): - - # axis = super(GridHelperCurveLinear, - # self).new_floating_axis(nth_coord, - # value, axes=axes, - # axis_direction=axis_direction) - - # # set extreme values of the axis helper - # if nth_coord == 1: - # axis.get_helper().set_extremes(*self._extremes[:2]) - # elif nth_coord == 0: - # axis.get_helper().set_extremes(*self._extremes[2:]) - - # return axis - - def _update_grid(self, x1, y1, x2, y2): - if self._grid_info is None: - self._grid_info = dict() - - grid_info = self._grid_info - - grid_finder = self.grid_finder - extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy, - x1, y1, x2, y2) - - lon_min, lon_max = sorted(extremes[:2]) - lat_min, lat_max = sorted(extremes[2:]) - grid_info["extremes"] = lon_min, lon_max, lat_min, lat_max # extremes - - lon_levs, lon_n, lon_factor = \ - grid_finder.grid_locator1(lon_min, lon_max) - lon_levs = np.asarray(lon_levs) - lat_levs, lat_n, lat_factor = \ - grid_finder.grid_locator2(lat_min, lat_max) - lat_levs = np.asarray(lat_levs) - - grid_info["lon_info"] = lon_levs, lon_n, lon_factor - grid_info["lat_info"] = lat_levs, lat_n, lat_factor - - grid_info["lon_labels"] = grid_finder.tick_formatter1( - "bottom", lon_factor, lon_levs) - grid_info["lat_labels"] = grid_finder.tick_formatter2( - "bottom", lat_factor, lat_levs) - - lon_values = lon_levs[:lon_n] / lon_factor - lat_values = lat_levs[:lat_n] / lat_factor - - lon_lines, lat_lines = grid_finder._get_raw_grid_lines( - lon_values[(lon_min < lon_values) & (lon_values < lon_max)], - lat_values[(lat_min < lat_values) & (lat_values < lat_max)], - lon_min, lon_max, lat_min, lat_max) - - grid_info["lon_lines"] = lon_lines - grid_info["lat_lines"] = lat_lines - - lon_lines, lat_lines = grid_finder._get_raw_grid_lines( - # lon_min, lon_max, lat_min, lat_max) - extremes[:2], extremes[2:], *extremes) - - grid_info["lon_lines0"] = lon_lines - grid_info["lat_lines0"] = lat_lines - - def get_gridlines(self, which="major", axis="both"): - grid_lines = [] - if axis in ["both", "x"]: - grid_lines.extend(self._grid_info["lon_lines"]) - if axis in ["both", "y"]: - grid_lines.extend(self._grid_info["lat_lines"]) - return grid_lines - - -class FloatingAxesBase: - - def __init__(self, *args, grid_helper, **kwargs): - _api.check_isinstance(GridHelperCurveLinear, grid_helper=grid_helper) - super().__init__(*args, grid_helper=grid_helper, **kwargs) - self.set_aspect(1.) - - def _gen_axes_patch(self): - # docstring inherited - x0, x1, y0, y1 = self.get_grid_helper().grid_finder.extreme_finder(*[None] * 5) - patch = mpatches.Polygon([(x0, y0), (x1, y0), (x1, y1), (x0, y1)]) - patch.get_path()._interpolation_steps = 100 - return patch - - def clear(self): - super().clear() - self.patch.set_transform( - self.get_grid_helper().grid_finder.get_transform() - + self.transData) - # The original patch is not in the draw tree; it is only used for - # clipping purposes. - orig_patch = super()._gen_axes_patch() - orig_patch.set_figure(self.figure) - orig_patch.set_transform(self.transAxes) - self.patch.set_clip_path(orig_patch) - self.gridlines.set_clip_path(orig_patch) - self.adjust_axes_lim() - - def adjust_axes_lim(self): - bbox = self.patch.get_path().get_extents( - # First transform to pixel coords, then to parent data coords. - self.patch.get_transform() - self.transData) - bbox = bbox.expanded(1.02, 1.02) - self.set_xlim(bbox.xmin, bbox.xmax) - self.set_ylim(bbox.ymin, bbox.ymax) - - -floatingaxes_class_factory = cbook._make_class_factory( - FloatingAxesBase, "Floating{}") -FloatingAxes = floatingaxes_class_factory( - host_axes_class_factory(axislines.Axes)) -FloatingSubplot = FloatingAxes diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/grid_finder.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/grid_finder.py deleted file mode 100644 index f969b011c4c..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/grid_finder.py +++ /dev/null @@ -1,335 +0,0 @@ -import numpy as np - -from matplotlib import ticker as mticker -from matplotlib.transforms import Bbox, Transform - - -def _find_line_box_crossings(xys, bbox): - """ - Find the points where a polyline crosses a bbox, and the crossing angles. - - Parameters - ---------- - xys : (N, 2) array - The polyline coordinates. - bbox : `.Bbox` - The bounding box. - - Returns - ------- - list of ((float, float), float) - Four separate lists of crossings, for the left, right, bottom, and top - sides of the bbox, respectively. For each list, the entries are the - ``((x, y), ccw_angle_in_degrees)`` of the crossing, where an angle of 0 - means that the polyline is moving to the right at the crossing point. - - The entries are computed by linearly interpolating at each crossing - between the nearest points on either side of the bbox edges. - """ - crossings = [] - dxys = xys[1:] - xys[:-1] - for sl in [slice(None), slice(None, None, -1)]: - us, vs = xys.T[sl] # "this" coord, "other" coord - dus, dvs = dxys.T[sl] - umin, vmin = bbox.min[sl] - umax, vmax = bbox.max[sl] - for u0, inside in [(umin, us > umin), (umax, us < umax)]: - crossings.append([]) - idxs, = (inside[:-1] ^ inside[1:]).nonzero() - for idx in idxs: - v = vs[idx] + (u0 - us[idx]) * dvs[idx] / dus[idx] - if not vmin <= v <= vmax: - continue - crossing = (u0, v)[sl] - theta = np.degrees(np.arctan2(*dxys[idx][::-1])) - crossings[-1].append((crossing, theta)) - return crossings - - -class ExtremeFinderSimple: - """ - A helper class to figure out the range of grid lines that need to be drawn. - """ - - def __init__(self, nx, ny): - """ - Parameters - ---------- - nx, ny : int - The number of samples in each direction. - """ - self.nx = nx - self.ny = ny - - def __call__(self, transform_xy, x1, y1, x2, y2): - """ - Compute an approximation of the bounding box obtained by applying - *transform_xy* to the box delimited by ``(x1, y1, x2, y2)``. - - The intended use is to have ``(x1, y1, x2, y2)`` in axes coordinates, - and have *transform_xy* be the transform from axes coordinates to data - coordinates; this method then returns the range of data coordinates - that span the actual axes. - - The computation is done by sampling ``nx * ny`` equispaced points in - the ``(x1, y1, x2, y2)`` box and finding the resulting points with - extremal coordinates; then adding some padding to take into account the - finite sampling. - - As each sampling step covers a relative range of *1/nx* or *1/ny*, - the padding is computed by expanding the span covered by the extremal - coordinates by these fractions. - """ - x, y = np.meshgrid( - np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny)) - xt, yt = transform_xy(np.ravel(x), np.ravel(y)) - return self._add_pad(xt.min(), xt.max(), yt.min(), yt.max()) - - def _add_pad(self, x_min, x_max, y_min, y_max): - """Perform the padding mentioned in `__call__`.""" - dx = (x_max - x_min) / self.nx - dy = (y_max - y_min) / self.ny - return x_min - dx, x_max + dx, y_min - dy, y_max + dy - - -class _User2DTransform(Transform): - """A transform defined by two user-set functions.""" - - input_dims = output_dims = 2 - - def __init__(self, forward, backward): - """ - Parameters - ---------- - forward, backward : callable - The forward and backward transforms, taking ``x`` and ``y`` as - separate arguments and returning ``(tr_x, tr_y)``. - """ - # The normal Matplotlib convention would be to take and return an - # (N, 2) array but axisartist uses the transposed version. - super().__init__() - self._forward = forward - self._backward = backward - - def transform_non_affine(self, values): - # docstring inherited - return np.transpose(self._forward(*np.transpose(values))) - - def inverted(self): - # docstring inherited - return type(self)(self._backward, self._forward) - - -class GridFinder: - """ - Internal helper for `~.grid_helper_curvelinear.GridHelperCurveLinear`, with - the same constructor parameters; should not be directly instantiated. - """ - - def __init__(self, - transform, - extreme_finder=None, - grid_locator1=None, - grid_locator2=None, - tick_formatter1=None, - tick_formatter2=None): - if extreme_finder is None: - extreme_finder = ExtremeFinderSimple(20, 20) - if grid_locator1 is None: - grid_locator1 = MaxNLocator() - if grid_locator2 is None: - grid_locator2 = MaxNLocator() - if tick_formatter1 is None: - tick_formatter1 = FormatterPrettyPrint() - if tick_formatter2 is None: - tick_formatter2 = FormatterPrettyPrint() - self.extreme_finder = extreme_finder - self.grid_locator1 = grid_locator1 - self.grid_locator2 = grid_locator2 - self.tick_formatter1 = tick_formatter1 - self.tick_formatter2 = tick_formatter2 - self.set_transform(transform) - - def get_grid_info(self, x1, y1, x2, y2): - """ - lon_values, lat_values : list of grid values. if integer is given, - rough number of grids in each direction. - """ - - extremes = self.extreme_finder(self.inv_transform_xy, x1, y1, x2, y2) - - # min & max rage of lat (or lon) for each grid line will be drawn. - # i.e., gridline of lon=0 will be drawn from lat_min to lat_max. - - lon_min, lon_max, lat_min, lat_max = extremes - lon_levs, lon_n, lon_factor = self.grid_locator1(lon_min, lon_max) - lon_levs = np.asarray(lon_levs) - lat_levs, lat_n, lat_factor = self.grid_locator2(lat_min, lat_max) - lat_levs = np.asarray(lat_levs) - - lon_values = lon_levs[:lon_n] / lon_factor - lat_values = lat_levs[:lat_n] / lat_factor - - lon_lines, lat_lines = self._get_raw_grid_lines(lon_values, - lat_values, - lon_min, lon_max, - lat_min, lat_max) - - ddx = (x2-x1)*1.e-10 - ddy = (y2-y1)*1.e-10 - bb = Bbox.from_extents(x1-ddx, y1-ddy, x2+ddx, y2+ddy) - - grid_info = { - "extremes": extremes, - "lon_lines": lon_lines, - "lat_lines": lat_lines, - "lon": self._clip_grid_lines_and_find_ticks( - lon_lines, lon_values, lon_levs, bb), - "lat": self._clip_grid_lines_and_find_ticks( - lat_lines, lat_values, lat_levs, bb), - } - - tck_labels = grid_info["lon"]["tick_labels"] = {} - for direction in ["left", "bottom", "right", "top"]: - levs = grid_info["lon"]["tick_levels"][direction] - tck_labels[direction] = self.tick_formatter1( - direction, lon_factor, levs) - - tck_labels = grid_info["lat"]["tick_labels"] = {} - for direction in ["left", "bottom", "right", "top"]: - levs = grid_info["lat"]["tick_levels"][direction] - tck_labels[direction] = self.tick_formatter2( - direction, lat_factor, levs) - - return grid_info - - def _get_raw_grid_lines(self, - lon_values, lat_values, - lon_min, lon_max, lat_min, lat_max): - - lons_i = np.linspace(lon_min, lon_max, 100) # for interpolation - lats_i = np.linspace(lat_min, lat_max, 100) - - lon_lines = [self.transform_xy(np.full_like(lats_i, lon), lats_i) - for lon in lon_values] - lat_lines = [self.transform_xy(lons_i, np.full_like(lons_i, lat)) - for lat in lat_values] - - return lon_lines, lat_lines - - def _clip_grid_lines_and_find_ticks(self, lines, values, levs, bb): - gi = { - "values": [], - "levels": [], - "tick_levels": dict(left=[], bottom=[], right=[], top=[]), - "tick_locs": dict(left=[], bottom=[], right=[], top=[]), - "lines": [], - } - - tck_levels = gi["tick_levels"] - tck_locs = gi["tick_locs"] - for (lx, ly), v, lev in zip(lines, values, levs): - tcks = _find_line_box_crossings(np.column_stack([lx, ly]), bb) - gi["levels"].append(v) - gi["lines"].append([(lx, ly)]) - - for tck, direction in zip(tcks, - ["left", "right", "bottom", "top"]): - for t in tck: - tck_levels[direction].append(lev) - tck_locs[direction].append(t) - - return gi - - def set_transform(self, aux_trans): - if isinstance(aux_trans, Transform): - self._aux_transform = aux_trans - elif len(aux_trans) == 2 and all(map(callable, aux_trans)): - self._aux_transform = _User2DTransform(*aux_trans) - else: - raise TypeError("'aux_trans' must be either a Transform " - "instance or a pair of callables") - - def get_transform(self): - return self._aux_transform - - update_transform = set_transform # backcompat alias. - - def transform_xy(self, x, y): - return self._aux_transform.transform(np.column_stack([x, y])).T - - def inv_transform_xy(self, x, y): - return self._aux_transform.inverted().transform( - np.column_stack([x, y])).T - - def update(self, **kwargs): - for k, v in kwargs.items(): - if k in ["extreme_finder", - "grid_locator1", - "grid_locator2", - "tick_formatter1", - "tick_formatter2"]: - setattr(self, k, v) - else: - raise ValueError(f"Unknown update property {k!r}") - - -class MaxNLocator(mticker.MaxNLocator): - def __init__(self, nbins=10, steps=None, - trim=True, - integer=False, - symmetric=False, - prune=None): - # trim argument has no effect. It has been left for API compatibility - super().__init__(nbins, steps=steps, integer=integer, - symmetric=symmetric, prune=prune) - self.create_dummy_axis() - - def __call__(self, v1, v2): - locs = super().tick_values(v1, v2) - return np.array(locs), len(locs), 1 # 1: factor (see angle_helper) - - -class FixedLocator: - def __init__(self, locs): - self._locs = locs - - def __call__(self, v1, v2): - v1, v2 = sorted([v1, v2]) - locs = np.array([l for l in self._locs if v1 <= l <= v2]) - return locs, len(locs), 1 # 1: factor (see angle_helper) - - -# Tick Formatter - -class FormatterPrettyPrint: - def __init__(self, useMathText=True): - self._fmt = mticker.ScalarFormatter( - useMathText=useMathText, useOffset=False) - self._fmt.create_dummy_axis() - - def __call__(self, direction, factor, values): - return self._fmt.format_ticks(values) - - -class DictFormatter: - def __init__(self, format_dict, formatter=None): - """ - format_dict : dictionary for format strings to be used. - formatter : fall-back formatter - """ - super().__init__() - self._format_dict = format_dict - self._fallback_formatter = formatter - - def __call__(self, direction, factor, values): - """ - factor is ignored if value is found in the dictionary - """ - if self._fallback_formatter: - fallback_strings = self._fallback_formatter( - direction, factor, values) - else: - fallback_strings = [""] * len(values) - return [self._format_dict.get(k, v) - for k, v in zip(values, fallback_strings)] diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/grid_helper_curvelinear.py deleted file mode 100644 index ae17452b6c5..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ /dev/null @@ -1,336 +0,0 @@ -""" -An experimental support for curvilinear grid. -""" - -import functools -from itertools import chain - -import numpy as np - -import matplotlib as mpl -from matplotlib.path import Path -from matplotlib.transforms import Affine2D, IdentityTransform -from .axislines import ( - _FixedAxisArtistHelperBase, _FloatingAxisArtistHelperBase, GridHelperBase) -from .axis_artist import AxisArtist -from .grid_finder import GridFinder - - -def _value_and_jacobian(func, xs, ys, xlims, ylims): - """ - Compute *func* and its derivatives along x and y at positions *xs*, *ys*, - while ensuring that finite difference calculations don't try to evaluate - values outside of *xlims*, *ylims*. - """ - eps = np.finfo(float).eps ** (1/2) # see e.g. scipy.optimize.approx_fprime - val = func(xs, ys) - # Take the finite difference step in the direction where the bound is the - # furthest; the step size is min of epsilon and distance to that bound. - xlo, xhi = sorted(xlims) - dxlo = xs - xlo - dxhi = xhi - xs - xeps = (np.take([-1, 1], dxhi >= dxlo) - * np.minimum(eps, np.maximum(dxlo, dxhi))) - val_dx = func(xs + xeps, ys) - ylo, yhi = sorted(ylims) - dylo = ys - ylo - dyhi = yhi - ys - yeps = (np.take([-1, 1], dyhi >= dylo) - * np.minimum(eps, np.maximum(dylo, dyhi))) - val_dy = func(xs, ys + yeps) - return (val, (val_dx - val) / xeps, (val_dy - val) / yeps) - - -class FixedAxisArtistHelper(_FixedAxisArtistHelperBase): - """ - Helper class for a fixed axis. - """ - - def __init__(self, grid_helper, side, nth_coord_ticks=None): - """ - nth_coord = along which coordinate value varies. - nth_coord = 0 -> x axis, nth_coord = 1 -> y axis - """ - - super().__init__(loc=side) - - self.grid_helper = grid_helper - if nth_coord_ticks is None: - nth_coord_ticks = self.nth_coord - self.nth_coord_ticks = nth_coord_ticks - - self.side = side - - def update_lim(self, axes): - self.grid_helper.update_lim(axes) - - def get_tick_transform(self, axes): - return axes.transData - - def get_tick_iterators(self, axes): - """tick_loc, tick_angle, tick_label""" - v1, v2 = axes.get_ylim() if self.nth_coord == 0 else axes.get_xlim() - if v1 > v2: # Inverted limits. - side = {"left": "right", "right": "left", - "top": "bottom", "bottom": "top"}[self.side] - else: - side = self.side - g = self.grid_helper - ti1 = g.get_tick_iterator(self.nth_coord_ticks, side) - ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, side, minor=True) - return chain(ti1, ti2), iter([]) - - -class FloatingAxisArtistHelper(_FloatingAxisArtistHelperBase): - - def __init__(self, grid_helper, nth_coord, value, axis_direction=None): - """ - nth_coord = along which coordinate value varies. - nth_coord = 0 -> x axis, nth_coord = 1 -> y axis - """ - super().__init__(nth_coord, value) - self.value = value - self.grid_helper = grid_helper - self._extremes = -np.inf, np.inf - self._line_num_points = 100 # number of points to create a line - - def set_extremes(self, e1, e2): - if e1 is None: - e1 = -np.inf - if e2 is None: - e2 = np.inf - self._extremes = e1, e2 - - def update_lim(self, axes): - self.grid_helper.update_lim(axes) - - x1, x2 = axes.get_xlim() - y1, y2 = axes.get_ylim() - grid_finder = self.grid_helper.grid_finder - extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy, - x1, y1, x2, y2) - - lon_min, lon_max, lat_min, lat_max = extremes - e_min, e_max = self._extremes # ranges of other coordinates - if self.nth_coord == 0: - lat_min = max(e_min, lat_min) - lat_max = min(e_max, lat_max) - elif self.nth_coord == 1: - lon_min = max(e_min, lon_min) - lon_max = min(e_max, lon_max) - - lon_levs, lon_n, lon_factor = \ - grid_finder.grid_locator1(lon_min, lon_max) - lat_levs, lat_n, lat_factor = \ - grid_finder.grid_locator2(lat_min, lat_max) - - if self.nth_coord == 0: - xx0 = np.full(self._line_num_points, self.value) - yy0 = np.linspace(lat_min, lat_max, self._line_num_points) - xx, yy = grid_finder.transform_xy(xx0, yy0) - elif self.nth_coord == 1: - xx0 = np.linspace(lon_min, lon_max, self._line_num_points) - yy0 = np.full(self._line_num_points, self.value) - xx, yy = grid_finder.transform_xy(xx0, yy0) - - self._grid_info = { - "extremes": (lon_min, lon_max, lat_min, lat_max), - "lon_info": (lon_levs, lon_n, np.asarray(lon_factor)), - "lat_info": (lat_levs, lat_n, np.asarray(lat_factor)), - "lon_labels": grid_finder.tick_formatter1( - "bottom", lon_factor, lon_levs), - "lat_labels": grid_finder.tick_formatter2( - "bottom", lat_factor, lat_levs), - "line_xy": (xx, yy), - } - - def get_axislabel_transform(self, axes): - return Affine2D() # axes.transData - - def get_axislabel_pos_angle(self, axes): - def trf_xy(x, y): - trf = self.grid_helper.grid_finder.get_transform() + axes.transData - return trf.transform([x, y]).T - - xmin, xmax, ymin, ymax = self._grid_info["extremes"] - if self.nth_coord == 0: - xx0 = self.value - yy0 = (ymin + ymax) / 2 - elif self.nth_coord == 1: - xx0 = (xmin + xmax) / 2 - yy0 = self.value - xy1, dxy1_dx, dxy1_dy = _value_and_jacobian( - trf_xy, xx0, yy0, (xmin, xmax), (ymin, ymax)) - p = axes.transAxes.inverted().transform(xy1) - if 0 <= p[0] <= 1 and 0 <= p[1] <= 1: - d = [dxy1_dy, dxy1_dx][self.nth_coord] - return xy1, np.rad2deg(np.arctan2(*d[::-1])) - else: - return None, None - - def get_tick_transform(self, axes): - return IdentityTransform() # axes.transData - - def get_tick_iterators(self, axes): - """tick_loc, tick_angle, tick_label, (optionally) tick_label""" - - lat_levs, lat_n, lat_factor = self._grid_info["lat_info"] - yy0 = lat_levs / lat_factor - - lon_levs, lon_n, lon_factor = self._grid_info["lon_info"] - xx0 = lon_levs / lon_factor - - e0, e1 = self._extremes - - def trf_xy(x, y): - trf = self.grid_helper.grid_finder.get_transform() + axes.transData - return trf.transform(np.column_stack(np.broadcast_arrays(x, y))).T - - # find angles - if self.nth_coord == 0: - mask = (e0 <= yy0) & (yy0 <= e1) - (xx1, yy1), (dxx1, dyy1), (dxx2, dyy2) = _value_and_jacobian( - trf_xy, self.value, yy0[mask], (-np.inf, np.inf), (e0, e1)) - labels = self._grid_info["lat_labels"] - - elif self.nth_coord == 1: - mask = (e0 <= xx0) & (xx0 <= e1) - (xx1, yy1), (dxx2, dyy2), (dxx1, dyy1) = _value_and_jacobian( - trf_xy, xx0[mask], self.value, (-np.inf, np.inf), (e0, e1)) - labels = self._grid_info["lon_labels"] - - labels = [l for l, m in zip(labels, mask) if m] - - angle_normal = np.arctan2(dyy1, dxx1) - angle_tangent = np.arctan2(dyy2, dxx2) - mm = (dyy1 == 0) & (dxx1 == 0) # points with degenerate normal - angle_normal[mm] = angle_tangent[mm] + np.pi / 2 - - tick_to_axes = self.get_tick_transform(axes) - axes.transAxes - in_01 = functools.partial( - mpl.transforms._interval_contains_close, (0, 1)) - - def f1(): - for x, y, normal, tangent, lab \ - in zip(xx1, yy1, angle_normal, angle_tangent, labels): - c2 = tick_to_axes.transform((x, y)) - if in_01(c2[0]) and in_01(c2[1]): - yield [x, y], *np.rad2deg([normal, tangent]), lab - - return f1(), iter([]) - - def get_line_transform(self, axes): - return axes.transData - - def get_line(self, axes): - self.update_lim(axes) - x, y = self._grid_info["line_xy"] - return Path(np.column_stack([x, y])) - - -class GridHelperCurveLinear(GridHelperBase): - def __init__(self, aux_trans, - extreme_finder=None, - grid_locator1=None, - grid_locator2=None, - tick_formatter1=None, - tick_formatter2=None): - """ - Parameters - ---------- - aux_trans : `.Transform` or tuple[Callable, Callable] - The transform from curved coordinates to rectilinear coordinate: - either a `.Transform` instance (which provides also its inverse), - or a pair of callables ``(trans, inv_trans)`` that define the - transform and its inverse. The callables should have signature:: - - x_rect, y_rect = trans(x_curved, y_curved) - x_curved, y_curved = inv_trans(x_rect, y_rect) - - extreme_finder - - grid_locator1, grid_locator2 - Grid locators for each axis. - - tick_formatter1, tick_formatter2 - Tick formatters for each axis. - """ - super().__init__() - self._grid_info = None - self.grid_finder = GridFinder(aux_trans, - extreme_finder, - grid_locator1, - grid_locator2, - tick_formatter1, - tick_formatter2) - - def update_grid_finder(self, aux_trans=None, **kwargs): - if aux_trans is not None: - self.grid_finder.update_transform(aux_trans) - self.grid_finder.update(**kwargs) - self._old_limits = None # Force revalidation. - - def new_fixed_axis(self, loc, - nth_coord=None, - axis_direction=None, - offset=None, - axes=None): - if axes is None: - axes = self.axes - if axis_direction is None: - axis_direction = loc - helper = FixedAxisArtistHelper(self, loc, nth_coord_ticks=nth_coord) - axisline = AxisArtist(axes, helper, axis_direction=axis_direction) - # Why is clip not set on axisline, unlike in new_floating_axis or in - # the floating_axig.GridHelperCurveLinear subclass? - return axisline - - def new_floating_axis(self, nth_coord, - value, - axes=None, - axis_direction="bottom" - ): - if axes is None: - axes = self.axes - helper = FloatingAxisArtistHelper( - self, nth_coord, value, axis_direction) - axisline = AxisArtist(axes, helper) - axisline.line.set_clip_on(True) - axisline.line.set_clip_box(axisline.axes.bbox) - # axisline.major_ticklabels.set_visible(True) - # axisline.minor_ticklabels.set_visible(False) - return axisline - - def _update_grid(self, x1, y1, x2, y2): - self._grid_info = self.grid_finder.get_grid_info(x1, y1, x2, y2) - - def get_gridlines(self, which="major", axis="both"): - grid_lines = [] - if axis in ["both", "x"]: - for gl in self._grid_info["lon"]["lines"]: - grid_lines.extend(gl) - if axis in ["both", "y"]: - for gl in self._grid_info["lat"]["lines"]: - grid_lines.extend(gl) - return grid_lines - - def get_tick_iterator(self, nth_coord, axis_side, minor=False): - - # axisnr = dict(left=0, bottom=1, right=2, top=3)[axis_side] - angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side] - # angle = [0, 90, 180, 270][axisnr] - lon_or_lat = ["lon", "lat"][nth_coord] - if not minor: # major ticks - for (xy, a), l in zip( - self._grid_info[lon_or_lat]["tick_locs"][axis_side], - self._grid_info[lon_or_lat]["tick_labels"][axis_side]): - angle_normal = a - yield xy, angle_normal, angle_tangent, l - else: - for (xy, a), l in zip( - self._grid_info[lon_or_lat]["tick_locs"][axis_side], - self._grid_info[lon_or_lat]["tick_labels"][axis_side]): - angle_normal = a - yield xy, angle_normal, angle_tangent, "" - # for xy, a, l in self._grid_info[lon_or_lat]["ticks"][axis_side]: - # yield xy, a, "" diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/parasite_axes.py b/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/parasite_axes.py deleted file mode 100644 index 4ebd6acc03b..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/axisartist/parasite_axes.py +++ /dev/null @@ -1,7 +0,0 @@ -from mpl_toolkits.axes_grid1.parasite_axes import ( - host_axes_class_factory, parasite_axes_class_factory) -from .axislines import Axes - - -ParasiteAxes = parasite_axes_class_factory(Axes) -HostAxes = SubplotHost = host_axes_class_factory(Axes) diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/__init__.py b/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/__init__.py deleted file mode 100644 index a089fbd6b70..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .axes3d import Axes3D - -__all__ = ['Axes3D'] diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/art3d.py b/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/art3d.py deleted file mode 100644 index 4aff115b0c9..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/art3d.py +++ /dev/null @@ -1,1252 +0,0 @@ -# art3d.py, original mplot3d version by John Porter -# Parts rewritten by Reinier Heeres <reinier@heeres.eu> -# Minor additions by Ben Axelrod <baxelrod@coroware.com> - -""" -Module containing 3D artist code and functions to convert 2D -artists into 3D versions which can be added to an Axes3D. -""" - -import math - -import numpy as np - -from contextlib import contextmanager - -from matplotlib import ( - artist, cbook, colors as mcolors, lines, text as mtext, - path as mpath) -from matplotlib.collections import ( - Collection, LineCollection, PolyCollection, PatchCollection, PathCollection) -from matplotlib.colors import Normalize -from matplotlib.patches import Patch -from . import proj3d - - -def _norm_angle(a): - """Return the given angle normalized to -180 < *a* <= 180 degrees.""" - a = (a + 360) % 360 - if a > 180: - a = a - 360 - return a - - -def _norm_text_angle(a): - """Return the given angle normalized to -90 < *a* <= 90 degrees.""" - a = (a + 180) % 180 - if a > 90: - a = a - 180 - return a - - -def get_dir_vector(zdir): - """ - Return a direction vector. - - Parameters - ---------- - zdir : {'x', 'y', 'z', None, 3-tuple} - The direction. Possible values are: - - - 'x': equivalent to (1, 0, 0) - - 'y': equivalent to (0, 1, 0) - - 'z': equivalent to (0, 0, 1) - - *None*: equivalent to (0, 0, 0) - - an iterable (x, y, z) is converted to an array - - Returns - ------- - x, y, z : array - The direction vector. - """ - if zdir == 'x': - return np.array((1, 0, 0)) - elif zdir == 'y': - return np.array((0, 1, 0)) - elif zdir == 'z': - return np.array((0, 0, 1)) - elif zdir is None: - return np.array((0, 0, 0)) - elif np.iterable(zdir) and len(zdir) == 3: - return np.array(zdir) - else: - raise ValueError("'x', 'y', 'z', None or vector of length 3 expected") - - -class Text3D(mtext.Text): - """ - Text object with 3D position and direction. - - Parameters - ---------- - x, y, z : float - The position of the text. - text : str - The text string to display. - zdir : {'x', 'y', 'z', None, 3-tuple} - The direction of the text. See `.get_dir_vector` for a description of - the values. - - Other Parameters - ---------------- - **kwargs - All other parameters are passed on to `~matplotlib.text.Text`. - """ - - def __init__(self, x=0, y=0, z=0, text='', zdir='z', **kwargs): - mtext.Text.__init__(self, x, y, text, **kwargs) - self.set_3d_properties(z, zdir) - - def get_position_3d(self): - """Return the (x, y, z) position of the text.""" - return self._x, self._y, self._z - - def set_position_3d(self, xyz, zdir=None): - """ - Set the (*x*, *y*, *z*) position of the text. - - Parameters - ---------- - xyz : (float, float, float) - The position in 3D space. - zdir : {'x', 'y', 'z', None, 3-tuple} - The direction of the text. If unspecified, the *zdir* will not be - changed. See `.get_dir_vector` for a description of the values. - """ - super().set_position(xyz[:2]) - self.set_z(xyz[2]) - if zdir is not None: - self._dir_vec = get_dir_vector(zdir) - - def set_z(self, z): - """ - Set the *z* position of the text. - - Parameters - ---------- - z : float - """ - self._z = z - self.stale = True - - def set_3d_properties(self, z=0, zdir='z'): - """ - Set the *z* position and direction of the text. - - Parameters - ---------- - z : float - The z-position in 3D space. - zdir : {'x', 'y', 'z', 3-tuple} - The direction of the text. Default: 'z'. - See `.get_dir_vector` for a description of the values. - """ - self._z = z - self._dir_vec = get_dir_vector(zdir) - self.stale = True - - @artist.allow_rasterization - def draw(self, renderer): - position3d = np.array((self._x, self._y, self._z)) - proj = proj3d._proj_trans_points( - [position3d, position3d + self._dir_vec], self.axes.M) - dx = proj[0][1] - proj[0][0] - dy = proj[1][1] - proj[1][0] - angle = math.degrees(math.atan2(dy, dx)) - with cbook._setattr_cm(self, _x=proj[0][0], _y=proj[1][0], - _rotation=_norm_text_angle(angle)): - mtext.Text.draw(self, renderer) - self.stale = False - - def get_tightbbox(self, renderer=None): - # Overwriting the 2d Text behavior which is not valid for 3d. - # For now, just return None to exclude from layout calculation. - return None - - -def text_2d_to_3d(obj, z=0, zdir='z'): - """ - Convert a `.Text` to a `.Text3D` object. - - Parameters - ---------- - z : float - The z-position in 3D space. - zdir : {'x', 'y', 'z', 3-tuple} - The direction of the text. Default: 'z'. - See `.get_dir_vector` for a description of the values. - """ - obj.__class__ = Text3D - obj.set_3d_properties(z, zdir) - - -class Line3D(lines.Line2D): - """ - 3D line object. - - .. note:: Use `get_data_3d` to obtain the data associated with the line. - `~.Line2D.get_data`, `~.Line2D.get_xdata`, and `~.Line2D.get_ydata` return - the x- and y-coordinates of the projected 2D-line, not the x- and y-data of - the 3D-line. Similarly, use `set_data_3d` to set the data, not - `~.Line2D.set_data`, `~.Line2D.set_xdata`, and `~.Line2D.set_ydata`. - """ - - def __init__(self, xs, ys, zs, *args, **kwargs): - """ - - Parameters - ---------- - xs : array-like - The x-data to be plotted. - ys : array-like - The y-data to be plotted. - zs : array-like - The z-data to be plotted. - *args, **kwargs - Additional arguments are passed to `~matplotlib.lines.Line2D`. - """ - super().__init__([], [], *args, **kwargs) - self.set_data_3d(xs, ys, zs) - - def set_3d_properties(self, zs=0, zdir='z'): - """ - Set the *z* position and direction of the line. - - Parameters - ---------- - zs : float or array of floats - The location along the *zdir* axis in 3D space to position the - line. - zdir : {'x', 'y', 'z'} - Plane to plot line orthogonal to. Default: 'z'. - See `.get_dir_vector` for a description of the values. - """ - xs = self.get_xdata() - ys = self.get_ydata() - zs = cbook._to_unmasked_float_array(zs).ravel() - zs = np.broadcast_to(zs, len(xs)) - self._verts3d = juggle_axes(xs, ys, zs, zdir) - self.stale = True - - def set_data_3d(self, *args): - """ - Set the x, y and z data - - Parameters - ---------- - x : array-like - The x-data to be plotted. - y : array-like - The y-data to be plotted. - z : array-like - The z-data to be plotted. - - Notes - ----- - Accepts x, y, z arguments or a single array-like (x, y, z) - """ - if len(args) == 1: - args = args[0] - for name, xyz in zip('xyz', args): - if not np.iterable(xyz): - raise RuntimeError(f'{name} must be a sequence') - self._verts3d = args - self.stale = True - - def get_data_3d(self): - """ - Get the current data - - Returns - ------- - verts3d : length-3 tuple or array-like - The current data as a tuple or array-like. - """ - return self._verts3d - - @artist.allow_rasterization - def draw(self, renderer): - xs3d, ys3d, zs3d = self._verts3d - xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M) - self.set_data(xs, ys) - super().draw(renderer) - self.stale = False - - -def line_2d_to_3d(line, zs=0, zdir='z'): - """ - Convert a `.Line2D` to a `.Line3D` object. - - Parameters - ---------- - zs : float - The location along the *zdir* axis in 3D space to position the line. - zdir : {'x', 'y', 'z'} - Plane to plot line orthogonal to. Default: 'z'. - See `.get_dir_vector` for a description of the values. - """ - - line.__class__ = Line3D - line.set_3d_properties(zs, zdir) - - -def _path_to_3d_segment(path, zs=0, zdir='z'): - """Convert a path to a 3D segment.""" - - zs = np.broadcast_to(zs, len(path)) - pathsegs = path.iter_segments(simplify=False, curves=False) - seg = [(x, y, z) for (((x, y), code), z) in zip(pathsegs, zs)] - seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg] - return seg3d - - -def _paths_to_3d_segments(paths, zs=0, zdir='z'): - """Convert paths from a collection object to 3D segments.""" - - if not np.iterable(zs): - zs = np.broadcast_to(zs, len(paths)) - else: - if len(zs) != len(paths): - raise ValueError('Number of z-coordinates does not match paths.') - - segs = [_path_to_3d_segment(path, pathz, zdir) - for path, pathz in zip(paths, zs)] - return segs - - -def _path_to_3d_segment_with_codes(path, zs=0, zdir='z'): - """Convert a path to a 3D segment with path codes.""" - - zs = np.broadcast_to(zs, len(path)) - pathsegs = path.iter_segments(simplify=False, curves=False) - seg_codes = [((x, y, z), code) for ((x, y), code), z in zip(pathsegs, zs)] - if seg_codes: - seg, codes = zip(*seg_codes) - seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg] - else: - seg3d = [] - codes = [] - return seg3d, list(codes) - - -def _paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): - """ - Convert paths from a collection object to 3D segments with path codes. - """ - - zs = np.broadcast_to(zs, len(paths)) - segments_codes = [_path_to_3d_segment_with_codes(path, pathz, zdir) - for path, pathz in zip(paths, zs)] - if segments_codes: - segments, codes = zip(*segments_codes) - else: - segments, codes = [], [] - return list(segments), list(codes) - - -class Collection3D(Collection): - """A collection of 3D paths.""" - - def do_3d_projection(self): - """Project the points according to renderer matrix.""" - xyzs_list = [proj3d.proj_transform(*vs.T, self.axes.M) - for vs, _ in self._3dverts_codes] - self._paths = [mpath.Path(np.column_stack([xs, ys]), cs) - for (xs, ys, _), (_, cs) in zip(xyzs_list, self._3dverts_codes)] - zs = np.concatenate([zs for _, _, zs in xyzs_list]) - return zs.min() if len(zs) else 1e9 - - -def collection_2d_to_3d(col, zs=0, zdir='z'): - """Convert a `.Collection` to a `.Collection3D` object.""" - zs = np.broadcast_to(zs, len(col.get_paths())) - col._3dverts_codes = [ - (np.column_stack(juggle_axes( - *np.column_stack([p.vertices, np.broadcast_to(z, len(p.vertices))]).T, - zdir)), - p.codes) - for p, z in zip(col.get_paths(), zs)] - col.__class__ = cbook._make_class_factory(Collection3D, "{}3D")(type(col)) - - -class Line3DCollection(LineCollection): - """ - A collection of 3D lines. - """ - - def set_sort_zpos(self, val): - """Set the position to use for z-sorting.""" - self._sort_zpos = val - self.stale = True - - def set_segments(self, segments): - """ - Set 3D segments. - """ - self._segments3d = segments - super().set_segments([]) - - def do_3d_projection(self): - """ - Project the points according to renderer matrix. - """ - xyslist = [proj3d._proj_trans_points(points, self.axes.M) - for points in self._segments3d] - segments_2d = [np.column_stack([xs, ys]) for xs, ys, zs in xyslist] - LineCollection.set_segments(self, segments_2d) - - # FIXME - minz = 1e9 - for xs, ys, zs in xyslist: - minz = min(minz, min(zs)) - return minz - - -def line_collection_2d_to_3d(col, zs=0, zdir='z'): - """Convert a `.LineCollection` to a `.Line3DCollection` object.""" - segments3d = _paths_to_3d_segments(col.get_paths(), zs, zdir) - col.__class__ = Line3DCollection - col.set_segments(segments3d) - - -class Patch3D(Patch): - """ - 3D patch object. - """ - - def __init__(self, *args, zs=(), zdir='z', **kwargs): - """ - Parameters - ---------- - verts : - zs : float - The location along the *zdir* axis in 3D space to position the - patch. - zdir : {'x', 'y', 'z'} - Plane to plot patch orthogonal to. Default: 'z'. - See `.get_dir_vector` for a description of the values. - """ - super().__init__(*args, **kwargs) - self.set_3d_properties(zs, zdir) - - def set_3d_properties(self, verts, zs=0, zdir='z'): - """ - Set the *z* position and direction of the patch. - - Parameters - ---------- - verts : - zs : float - The location along the *zdir* axis in 3D space to position the - patch. - zdir : {'x', 'y', 'z'} - Plane to plot patch orthogonal to. Default: 'z'. - See `.get_dir_vector` for a description of the values. - """ - zs = np.broadcast_to(zs, len(verts)) - self._segment3d = [juggle_axes(x, y, z, zdir) - for ((x, y), z) in zip(verts, zs)] - - def get_path(self): - return self._path2d - - def do_3d_projection(self): - s = self._segment3d - xs, ys, zs = zip(*s) - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, - self.axes.M) - self._path2d = mpath.Path(np.column_stack([vxs, vys])) - return min(vzs) - - -class PathPatch3D(Patch3D): - """ - 3D PathPatch object. - """ - - def __init__(self, path, *, zs=(), zdir='z', **kwargs): - """ - Parameters - ---------- - path : - zs : float - The location along the *zdir* axis in 3D space to position the - path patch. - zdir : {'x', 'y', 'z', 3-tuple} - Plane to plot path patch orthogonal to. Default: 'z'. - See `.get_dir_vector` for a description of the values. - """ - # Not super().__init__! - Patch.__init__(self, **kwargs) - self.set_3d_properties(path, zs, zdir) - - def set_3d_properties(self, path, zs=0, zdir='z'): - """ - Set the *z* position and direction of the path patch. - - Parameters - ---------- - path : - zs : float - The location along the *zdir* axis in 3D space to position the - path patch. - zdir : {'x', 'y', 'z', 3-tuple} - Plane to plot path patch orthogonal to. Default: 'z'. - See `.get_dir_vector` for a description of the values. - """ - Patch3D.set_3d_properties(self, path.vertices, zs=zs, zdir=zdir) - self._code3d = path.codes - - def do_3d_projection(self): - s = self._segment3d - xs, ys, zs = zip(*s) - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, - self.axes.M) - self._path2d = mpath.Path(np.column_stack([vxs, vys]), self._code3d) - return min(vzs) - - -def _get_patch_verts(patch): - """Return a list of vertices for the path of a patch.""" - trans = patch.get_patch_transform() - path = patch.get_path() - polygons = path.to_polygons(trans) - return polygons[0] if len(polygons) else np.array([]) - - -def patch_2d_to_3d(patch, z=0, zdir='z'): - """Convert a `.Patch` to a `.Patch3D` object.""" - verts = _get_patch_verts(patch) - patch.__class__ = Patch3D - patch.set_3d_properties(verts, z, zdir) - - -def pathpatch_2d_to_3d(pathpatch, z=0, zdir='z'): - """Convert a `.PathPatch` to a `.PathPatch3D` object.""" - path = pathpatch.get_path() - trans = pathpatch.get_patch_transform() - - mpath = trans.transform_path(path) - pathpatch.__class__ = PathPatch3D - pathpatch.set_3d_properties(mpath, z, zdir) - - -class Patch3DCollection(PatchCollection): - """ - A collection of 3D patches. - """ - - def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): - """ - Create a collection of flat 3D patches with its normal vector - pointed in *zdir* direction, and located at *zs* on the *zdir* - axis. 'zs' can be a scalar or an array-like of the same length as - the number of patches in the collection. - - Constructor arguments are the same as for - :class:`~matplotlib.collections.PatchCollection`. In addition, - keywords *zs=0* and *zdir='z'* are available. - - Also, the keyword argument *depthshade* is available to indicate - whether to shade the patches in order to give the appearance of depth - (default is *True*). This is typically desired in scatter plots. - """ - self._depthshade = depthshade - super().__init__(*args, **kwargs) - self.set_3d_properties(zs, zdir) - - def get_depthshade(self): - return self._depthshade - - def set_depthshade(self, depthshade): - """ - Set whether depth shading is performed on collection members. - - Parameters - ---------- - depthshade : bool - Whether to shade the patches in order to give the appearance of - depth. - """ - self._depthshade = depthshade - self.stale = True - - def set_sort_zpos(self, val): - """Set the position to use for z-sorting.""" - self._sort_zpos = val - self.stale = True - - def set_3d_properties(self, zs, zdir): - """ - Set the *z* positions and direction of the patches. - - Parameters - ---------- - zs : float or array of floats - The location or locations to place the patches in the collection - along the *zdir* axis. - zdir : {'x', 'y', 'z'} - Plane to plot patches orthogonal to. - All patches must have the same direction. - See `.get_dir_vector` for a description of the values. - """ - # Force the collection to initialize the face and edgecolors - # just in case it is a scalarmappable with a colormap. - self.update_scalarmappable() - offsets = self.get_offsets() - if len(offsets) > 0: - xs, ys = offsets.T - else: - xs = [] - ys = [] - self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) - self._z_markers_idx = slice(-1) - self._vzs = None - self.stale = True - - def do_3d_projection(self): - xs, ys, zs = self._offsets3d - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, - self.axes.M) - self._vzs = vzs - super().set_offsets(np.column_stack([vxs, vys])) - - if vzs.size > 0: - return min(vzs) - else: - return np.nan - - def _maybe_depth_shade_and_sort_colors(self, color_array): - color_array = ( - _zalpha(color_array, self._vzs) - if self._vzs is not None and self._depthshade - else color_array - ) - if len(color_array) > 1: - color_array = color_array[self._z_markers_idx] - return mcolors.to_rgba_array(color_array, self._alpha) - - def get_facecolor(self): - return self._maybe_depth_shade_and_sort_colors(super().get_facecolor()) - - def get_edgecolor(self): - # We need this check here to make sure we do not double-apply the depth - # based alpha shading when the edge color is "face" which means the - # edge colour should be identical to the face colour. - if cbook._str_equal(self._edgecolors, 'face'): - return self.get_facecolor() - return self._maybe_depth_shade_and_sort_colors(super().get_edgecolor()) - - -class Path3DCollection(PathCollection): - """ - A collection of 3D paths. - """ - - def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): - """ - Create a collection of flat 3D paths with its normal vector - pointed in *zdir* direction, and located at *zs* on the *zdir* - axis. 'zs' can be a scalar or an array-like of the same length as - the number of paths in the collection. - - Constructor arguments are the same as for - :class:`~matplotlib.collections.PathCollection`. In addition, - keywords *zs=0* and *zdir='z'* are available. - - Also, the keyword argument *depthshade* is available to indicate - whether to shade the patches in order to give the appearance of depth - (default is *True*). This is typically desired in scatter plots. - """ - self._depthshade = depthshade - self._in_draw = False - super().__init__(*args, **kwargs) - self.set_3d_properties(zs, zdir) - self._offset_zordered = None - - def draw(self, renderer): - with self._use_zordered_offset(): - with cbook._setattr_cm(self, _in_draw=True): - super().draw(renderer) - - def set_sort_zpos(self, val): - """Set the position to use for z-sorting.""" - self._sort_zpos = val - self.stale = True - - def set_3d_properties(self, zs, zdir): - """ - Set the *z* positions and direction of the paths. - - Parameters - ---------- - zs : float or array of floats - The location or locations to place the paths in the collection - along the *zdir* axis. - zdir : {'x', 'y', 'z'} - Plane to plot paths orthogonal to. - All paths must have the same direction. - See `.get_dir_vector` for a description of the values. - """ - # Force the collection to initialize the face and edgecolors - # just in case it is a scalarmappable with a colormap. - self.update_scalarmappable() - offsets = self.get_offsets() - if len(offsets) > 0: - xs, ys = offsets.T - else: - xs = [] - ys = [] - self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) - # In the base draw methods we access the attributes directly which - # means we cannot resolve the shuffling in the getter methods like - # we do for the edge and face colors. - # - # This means we need to carry around a cache of the unsorted sizes and - # widths (postfixed with 3d) and in `do_3d_projection` set the - # depth-sorted version of that data into the private state used by the - # base collection class in its draw method. - # - # Grab the current sizes and linewidths to preserve them. - self._sizes3d = self._sizes - self._linewidths3d = np.array(self._linewidths) - xs, ys, zs = self._offsets3d - - # Sort the points based on z coordinates - # Performance optimization: Create a sorted index array and reorder - # points and point properties according to the index array - self._z_markers_idx = slice(-1) - self._vzs = None - self.stale = True - - def set_sizes(self, sizes, dpi=72.0): - super().set_sizes(sizes, dpi) - if not self._in_draw: - self._sizes3d = sizes - - def set_linewidth(self, lw): - super().set_linewidth(lw) - if not self._in_draw: - self._linewidths3d = np.array(self._linewidths) - - def get_depthshade(self): - return self._depthshade - - def set_depthshade(self, depthshade): - """ - Set whether depth shading is performed on collection members. - - Parameters - ---------- - depthshade : bool - Whether to shade the patches in order to give the appearance of - depth. - """ - self._depthshade = depthshade - self.stale = True - - def do_3d_projection(self): - xs, ys, zs = self._offsets3d - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, - self.axes.M) - # Sort the points based on z coordinates - # Performance optimization: Create a sorted index array and reorder - # points and point properties according to the index array - z_markers_idx = self._z_markers_idx = np.argsort(vzs)[::-1] - self._vzs = vzs - - # we have to special case the sizes because of code in collections.py - # as the draw method does - # self.set_sizes(self._sizes, self.figure.dpi) - # so we cannot rely on doing the sorting on the way out via get_* - - if len(self._sizes3d) > 1: - self._sizes = self._sizes3d[z_markers_idx] - - if len(self._linewidths3d) > 1: - self._linewidths = self._linewidths3d[z_markers_idx] - - PathCollection.set_offsets(self, np.column_stack((vxs, vys))) - - # Re-order items - vzs = vzs[z_markers_idx] - vxs = vxs[z_markers_idx] - vys = vys[z_markers_idx] - - # Store ordered offset for drawing purpose - self._offset_zordered = np.column_stack((vxs, vys)) - - return np.min(vzs) if vzs.size else np.nan - - @contextmanager - def _use_zordered_offset(self): - if self._offset_zordered is None: - # Do nothing - yield - else: - # Swap offset with z-ordered offset - old_offset = self._offsets - super().set_offsets(self._offset_zordered) - try: - yield - finally: - self._offsets = old_offset - - def _maybe_depth_shade_and_sort_colors(self, color_array): - color_array = ( - _zalpha(color_array, self._vzs) - if self._vzs is not None and self._depthshade - else color_array - ) - if len(color_array) > 1: - color_array = color_array[self._z_markers_idx] - return mcolors.to_rgba_array(color_array, self._alpha) - - def get_facecolor(self): - return self._maybe_depth_shade_and_sort_colors(super().get_facecolor()) - - def get_edgecolor(self): - # We need this check here to make sure we do not double-apply the depth - # based alpha shading when the edge color is "face" which means the - # edge colour should be identical to the face colour. - if cbook._str_equal(self._edgecolors, 'face'): - return self.get_facecolor() - return self._maybe_depth_shade_and_sort_colors(super().get_edgecolor()) - - -def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True): - """ - Convert a `.PatchCollection` into a `.Patch3DCollection` object - (or a `.PathCollection` into a `.Path3DCollection` object). - - Parameters - ---------- - zs : float or array of floats - The location or locations to place the patches in the collection along - the *zdir* axis. Default: 0. - zdir : {'x', 'y', 'z'} - The axis in which to place the patches. Default: "z". - See `.get_dir_vector` for a description of the values. - depthshade - Whether to shade the patches to give a sense of depth. Default: *True*. - - """ - if isinstance(col, PathCollection): - col.__class__ = Path3DCollection - col._offset_zordered = None - elif isinstance(col, PatchCollection): - col.__class__ = Patch3DCollection - col._depthshade = depthshade - col._in_draw = False - col.set_3d_properties(zs, zdir) - - -class Poly3DCollection(PolyCollection): - """ - A collection of 3D polygons. - - .. note:: - **Filling of 3D polygons** - - There is no simple definition of the enclosed surface of a 3D polygon - unless the polygon is planar. - - In practice, Matplotlib fills the 2D projection of the polygon. This - gives a correct filling appearance only for planar polygons. For all - other polygons, you'll find orientations in which the edges of the - polygon intersect in the projection. This will lead to an incorrect - visualization of the 3D area. - - If you need filled areas, it is recommended to create them via - `~mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf`, which creates a - triangulation and thus generates consistent surfaces. - """ - - def __init__(self, verts, *args, zsort='average', shade=False, - lightsource=None, **kwargs): - """ - Parameters - ---------- - verts : list of (N, 3) 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 (N, 3). - zsort : {'average', 'min', 'max'}, default: 'average' - The calculation method for the z-order. - See `~.Poly3DCollection.set_zsort` for details. - shade : bool, default: False - Whether to shade *facecolors* and *edgecolors*. When activating - *shade*, *facecolors* and/or *edgecolors* must be provided. - - .. versionadded:: 3.7 - - lightsource : `~matplotlib.colors.LightSource`, optional - The lightsource to use when *shade* is True. - - .. versionadded:: 3.7 - - *args, **kwargs - All other parameters are forwarded to `.PolyCollection`. - - Notes - ----- - Note that this class does a bit of magic with the _facecolors - and _edgecolors properties. - """ - if shade: - normals = _generate_normals(verts) - facecolors = kwargs.get('facecolors', None) - if facecolors is not None: - kwargs['facecolors'] = _shade_colors( - facecolors, normals, lightsource - ) - - edgecolors = kwargs.get('edgecolors', None) - if edgecolors is not None: - kwargs['edgecolors'] = _shade_colors( - edgecolors, normals, lightsource - ) - if facecolors is None and edgecolors is None: - raise ValueError( - "You must provide facecolors, edgecolors, or both for " - "shade to work.") - super().__init__(verts, *args, **kwargs) - if isinstance(verts, np.ndarray): - if verts.ndim != 3: - raise ValueError('verts must be a list of (N, 3) array-like') - else: - if any(len(np.shape(vert)) != 2 for vert in verts): - raise ValueError('verts must be a list of (N, 3) array-like') - self.set_zsort(zsort) - self._codes3d = None - - _zsort_functions = { - 'average': np.average, - 'min': np.min, - 'max': np.max, - } - - def set_zsort(self, zsort): - """ - Set the calculation method for the z-order. - - Parameters - ---------- - zsort : {'average', 'min', 'max'} - The function applied on the z-coordinates of the vertices in the - viewer's coordinate system, to determine the z-order. - """ - self._zsortfunc = self._zsort_functions[zsort] - self._sort_zpos = None - self.stale = True - - def get_vector(self, segments3d): - """Optimize points for projection.""" - if len(segments3d): - xs, ys, zs = np.vstack(segments3d).T - else: # vstack can't stack zero arrays. - xs, ys, zs = [], [], [] - ones = np.ones(len(xs)) - self._vec = np.array([xs, ys, zs, ones]) - - indices = [0, *np.cumsum([len(segment) for segment in segments3d])] - self._segslices = [*map(slice, indices[:-1], indices[1:])] - - def set_verts(self, verts, closed=True): - """ - Set 3D vertices. - - Parameters - ---------- - verts : list of (N, 3) 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 (N, 3). - closed : bool, default: True - Whether the polygon should be closed by adding a CLOSEPOLY - connection at the end. - """ - self.get_vector(verts) - # 2D verts will be updated at draw time - super().set_verts([], False) - self._closed = closed - - def set_verts_and_codes(self, verts, codes): - """Set 3D vertices with path codes.""" - # set vertices with closed=False to prevent PolyCollection from - # setting path codes - self.set_verts(verts, closed=False) - # and set our own codes instead. - self._codes3d = codes - - def set_3d_properties(self): - # Force the collection to initialize the face and edgecolors - # just in case it is a scalarmappable with a colormap. - self.update_scalarmappable() - self._sort_zpos = None - self.set_zsort('average') - self._facecolor3d = PolyCollection.get_facecolor(self) - self._edgecolor3d = PolyCollection.get_edgecolor(self) - self._alpha3d = PolyCollection.get_alpha(self) - self.stale = True - - def set_sort_zpos(self, val): - """Set the position to use for z-sorting.""" - self._sort_zpos = val - self.stale = True - - def do_3d_projection(self): - """ - Perform the 3D projection for this object. - """ - if self._A is not None: - # force update of color mapping because we re-order them - # below. If we do not do this here, the 2D draw will call - # this, but we will never port the color mapped values back - # to the 3D versions. - # - # We hold the 3D versions in a fixed order (the order the user - # passed in) and sort the 2D version by view depth. - self.update_scalarmappable() - if self._face_is_mapped: - self._facecolor3d = self._facecolors - if self._edge_is_mapped: - self._edgecolor3d = self._edgecolors - txs, tys, tzs = proj3d._proj_transform_vec(self._vec, self.axes.M) - xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices] - - # This extra fuss is to re-order face / edge colors - cface = self._facecolor3d - cedge = self._edgecolor3d - if len(cface) != len(xyzlist): - cface = cface.repeat(len(xyzlist), axis=0) - if len(cedge) != len(xyzlist): - if len(cedge) == 0: - cedge = cface - else: - cedge = cedge.repeat(len(xyzlist), axis=0) - - if xyzlist: - # sort by depth (furthest drawn first) - z_segments_2d = sorted( - ((self._zsortfunc(zs), np.column_stack([xs, ys]), fc, ec, idx) - for idx, ((xs, ys, zs), fc, ec) - in enumerate(zip(xyzlist, cface, cedge))), - key=lambda x: x[0], reverse=True) - - _, segments_2d, self._facecolors2d, self._edgecolors2d, idxs = \ - zip(*z_segments_2d) - else: - segments_2d = [] - self._facecolors2d = np.empty((0, 4)) - self._edgecolors2d = np.empty((0, 4)) - idxs = [] - - if self._codes3d is not None: - codes = [self._codes3d[idx] for idx in idxs] - PolyCollection.set_verts_and_codes(self, segments_2d, codes) - else: - PolyCollection.set_verts(self, segments_2d, self._closed) - - if len(self._edgecolor3d) != len(cface): - self._edgecolors2d = self._edgecolor3d - - # Return zorder value - if self._sort_zpos is not None: - zvec = np.array([[0], [0], [self._sort_zpos], [1]]) - ztrans = proj3d._proj_transform_vec(zvec, self.axes.M) - return ztrans[2][0] - elif tzs.size > 0: - # FIXME: Some results still don't look quite right. - # In particular, examine contourf3d_demo2.py - # with az = -54 and elev = -45. - return np.min(tzs) - else: - return np.nan - - def set_facecolor(self, colors): - # docstring inherited - super().set_facecolor(colors) - self._facecolor3d = PolyCollection.get_facecolor(self) - - def set_edgecolor(self, colors): - # docstring inherited - super().set_edgecolor(colors) - self._edgecolor3d = PolyCollection.get_edgecolor(self) - - def set_alpha(self, alpha): - # docstring inherited - artist.Artist.set_alpha(self, alpha) - try: - self._facecolor3d = mcolors.to_rgba_array( - self._facecolor3d, self._alpha) - except (AttributeError, TypeError, IndexError): - pass - try: - self._edgecolors = mcolors.to_rgba_array( - self._edgecolor3d, self._alpha) - except (AttributeError, TypeError, IndexError): - pass - self.stale = True - - def get_facecolor(self): - # docstring inherited - # self._facecolors2d is not initialized until do_3d_projection - if not hasattr(self, '_facecolors2d'): - self.axes.M = self.axes.get_proj() - self.do_3d_projection() - return np.asarray(self._facecolors2d) - - def get_edgecolor(self): - # docstring inherited - # self._edgecolors2d is not initialized until do_3d_projection - if not hasattr(self, '_edgecolors2d'): - self.axes.M = self.axes.get_proj() - self.do_3d_projection() - return np.asarray(self._edgecolors2d) - - -def poly_collection_2d_to_3d(col, zs=0, zdir='z'): - """ - Convert a `.PolyCollection` into a `.Poly3DCollection` object. - - Parameters - ---------- - zs : float or array of floats - The location or locations to place the polygons in the collection along - the *zdir* axis. Default: 0. - zdir : {'x', 'y', 'z'} - The axis in which to place the patches. Default: 'z'. - See `.get_dir_vector` for a description of the values. - """ - segments_3d, codes = _paths_to_3d_segments_with_codes( - col.get_paths(), zs, zdir) - col.__class__ = Poly3DCollection - col.set_verts_and_codes(segments_3d, codes) - col.set_3d_properties() - - -def juggle_axes(xs, ys, zs, zdir): - """ - Reorder coordinates so that 2D *xs*, *ys* can be plotted in the plane - orthogonal to *zdir*. *zdir* is normally 'x', 'y' or 'z'. However, if - *zdir* starts with a '-' it is interpreted as a compensation for - `rotate_axes`. - """ - if zdir == 'x': - return zs, xs, ys - elif zdir == 'y': - return xs, zs, ys - elif zdir[0] == '-': - return rotate_axes(xs, ys, zs, zdir) - else: - return xs, ys, zs - - -def rotate_axes(xs, ys, zs, zdir): - """ - Reorder coordinates so that the axes are rotated with *zdir* along - the original z axis. Prepending the axis with a '-' does the - inverse transform, so *zdir* can be 'x', '-x', 'y', '-y', 'z' or '-z'. - """ - if zdir in ('x', '-y'): - return ys, zs, xs - elif zdir in ('-x', 'y'): - return zs, xs, ys - else: - return xs, ys, zs - - -def _zalpha(colors, zs): - """Modify the alphas of the color list according to depth.""" - # FIXME: This only works well if the points for *zs* are well-spaced - # in all three dimensions. Otherwise, at certain orientations, - # the min and max zs are very close together. - # Should really normalize against the viewing depth. - if len(colors) == 0 or len(zs) == 0: - return np.zeros((0, 4)) - norm = Normalize(min(zs), max(zs)) - sats = 1 - norm(zs) * 0.7 - rgba = np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs), 4)) - return np.column_stack([rgba[:, :3], rgba[:, 3] * sats]) - - -def _generate_normals(polygons): - """ - Compute the normals of a list of polygons, one normal per polygon. - - Normals point towards the viewer for a face with its vertices in - counterclockwise order, following the right hand rule. - - Uses three points equally spaced around the polygon. This method assumes - that the points are in a plane. Otherwise, more than one shade is required, - which is not supported. - - Parameters - ---------- - polygons : list of (M_i, 3) array-like, or (..., M, 3) array-like - A sequence of polygons to compute normals for, which can have - varying numbers of vertices. If the polygons all have the same - number of vertices and array is passed, then the operation will - be vectorized. - - Returns - ------- - normals : (..., 3) array - A normal vector estimated for the polygon. - """ - if isinstance(polygons, np.ndarray): - # optimization: polygons all have the same number of points, so can - # vectorize - n = polygons.shape[-2] - i1, i2, i3 = 0, n//3, 2*n//3 - v1 = polygons[..., i1, :] - polygons[..., i2, :] - v2 = polygons[..., i2, :] - polygons[..., i3, :] - else: - # The subtraction doesn't vectorize because polygons is jagged. - v1 = np.empty((len(polygons), 3)) - v2 = np.empty((len(polygons), 3)) - for poly_i, ps in enumerate(polygons): - n = len(ps) - i1, i2, i3 = 0, n//3, 2*n//3 - v1[poly_i, :] = ps[i1, :] - ps[i2, :] - v2[poly_i, :] = ps[i2, :] - ps[i3, :] - return np.cross(v1, v2) - - -def _shade_colors(color, normals, lightsource=None): - """ - Shade *color* using normal vectors given by *normals*, - assuming a *lightsource* (using default position if not given). - *color* can also be an array of the same length as *normals*. - """ - if lightsource is None: - # chosen for backwards-compatibility - lightsource = mcolors.LightSource(azdeg=225, altdeg=19.4712) - - with np.errstate(invalid="ignore"): - shade = ((normals / np.linalg.norm(normals, axis=1, keepdims=True)) - @ lightsource.direction) - mask = ~np.isnan(shade) - - if mask.any(): - # convert dot product to allowed shading fractions - in_norm = mcolors.Normalize(-1, 1) - out_norm = mcolors.Normalize(0.3, 1).inverse - - def norm(x): - return out_norm(in_norm(x)) - - shade[~mask] = 0 - - color = mcolors.to_rgba_array(color) - # shape of color should be (M, 4) (where M is number of faces) - # shape of shade should be (M,) - # colors should have final shape of (M, 4) - alpha = color[:, 3] - colors = norm(shade)[:, np.newaxis] * color - colors[:, 3] = alpha - else: - colors = np.asanyarray(color).copy() - - return colors diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axes3d.py b/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axes3d.py deleted file mode 100644 index a74c11f54e6..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axes3d.py +++ /dev/null @@ -1,3448 +0,0 @@ -""" -axes3d.py, original mplot3d version by John Porter -Created: 23 Sep 2005 - -Parts fixed by Reinier Heeres <reinier@heeres.eu> -Minor additions by Ben Axelrod <baxelrod@coroware.com> -Significant updates and revisions by Ben Root <ben.v.root@gmail.com> - -Module containing Axes3D, an object which can plot 3D objects on a -2D matplotlib figure. -""" - -from collections import defaultdict -import functools -import itertools -import math -import textwrap - -import numpy as np - -import matplotlib as mpl -from matplotlib import _api, cbook, _docstring, _preprocess_data -import matplotlib.artist as martist -import matplotlib.axes as maxes -import matplotlib.collections as mcoll -import matplotlib.colors as mcolors -import matplotlib.image as mimage -import matplotlib.lines as mlines -import matplotlib.patches as mpatches -import matplotlib.container as mcontainer -import matplotlib.transforms as mtransforms -from matplotlib.axes import Axes -from matplotlib.axes._base import _axis_method_wrapper, _process_plot_format -from matplotlib.transforms import Bbox -from matplotlib.tri._triangulation import Triangulation - -from . import art3d -from . import proj3d -from . import axis3d - - -@_docstring.interpd -@_api.define_aliases({ - "xlim": ["xlim3d"], "ylim": ["ylim3d"], "zlim": ["zlim3d"]}) -class Axes3D(Axes): - """ - 3D Axes object. - - .. note:: - - As a user, you do not instantiate Axes directly, but use Axes creation - methods instead; e.g. from `.pyplot` or `.Figure`: - `~.pyplot.subplots`, `~.pyplot.subplot_mosaic` or `.Figure.add_axes`. - """ - name = '3d' - - _axis_names = ("x", "y", "z") - Axes._shared_axes["z"] = cbook.Grouper() - Axes._shared_axes["view"] = cbook.Grouper() - - vvec = _api.deprecate_privatize_attribute("3.7") - eye = _api.deprecate_privatize_attribute("3.7") - sx = _api.deprecate_privatize_attribute("3.7") - sy = _api.deprecate_privatize_attribute("3.7") - - def __init__( - self, fig, rect=None, *args, - elev=30, azim=-60, roll=0, sharez=None, proj_type='persp', - box_aspect=None, computed_zorder=True, focal_length=None, - shareview=None, - **kwargs): - """ - Parameters - ---------- - fig : Figure - The parent figure. - rect : tuple (left, bottom, width, height), default: None. - The ``(left, bottom, width, height)`` axes position. - elev : float, default: 30 - The elevation angle in degrees rotates the camera above and below - the x-y plane, with a positive angle corresponding to a location - above the plane. - azim : float, default: -60 - The azimuthal angle in degrees rotates the camera about the z axis, - with a positive angle corresponding to a right-handed rotation. In - other words, a positive azimuth rotates the camera about the origin - from its location along the +x axis towards the +y axis. - roll : float, default: 0 - The roll angle in degrees rotates the camera about the viewing - axis. A positive angle spins the camera clockwise, causing the - scene to rotate counter-clockwise. - sharez : Axes3D, optional - Other Axes to share z-limits with. - proj_type : {'persp', 'ortho'} - The projection type, default 'persp'. - box_aspect : 3-tuple of floats, default: None - Changes the physical dimensions of the Axes3D, such that the ratio - of the axis lengths in display units is x:y:z. - If None, defaults to 4:4:3 - computed_zorder : bool, default: True - If True, the draw order is computed based on the average position - of the `.Artist`\\s along the view direction. - Set to False if you want to manually control the order in which - Artists are drawn on top of each other using their *zorder* - attribute. This can be used for fine-tuning if the automatic order - does not produce the desired result. Note however, that a manual - zorder will only be correct for a limited view angle. If the figure - is rotated by the user, it will look wrong from certain angles. - focal_length : float, default: None - For a projection type of 'persp', the focal length of the virtual - camera. Must be > 0. If None, defaults to 1. - For a projection type of 'ortho', must be set to either None - or infinity (numpy.inf). If None, defaults to infinity. - The focal length can be computed from a desired Field Of View via - the equation: focal_length = 1/tan(FOV/2) - shareview : Axes3D, optional - Other Axes to share view angles with. - - **kwargs - Other optional keyword arguments: - - %(Axes3D:kwdoc)s - """ - - if rect is None: - rect = [0.0, 0.0, 1.0, 1.0] - - self.initial_azim = azim - self.initial_elev = elev - self.initial_roll = roll - self.set_proj_type(proj_type, focal_length) - self.computed_zorder = computed_zorder - - self.xy_viewLim = Bbox.unit() - self.zz_viewLim = Bbox.unit() - self.xy_dataLim = Bbox.unit() - # z-limits are encoded in the x-component of the Bbox, y is un-used - self.zz_dataLim = Bbox.unit() - - # inhibit autoscale_view until the axes are defined - # they can't be defined until Axes.__init__ has been called - self.view_init(self.initial_elev, self.initial_azim, self.initial_roll) - - self._sharez = sharez - if sharez is not None: - self._shared_axes["z"].join(self, sharez) - self._adjustable = 'datalim' - - self._shareview = shareview - if shareview is not None: - self._shared_axes["view"].join(self, shareview) - - if kwargs.pop('auto_add_to_figure', False): - raise AttributeError( - 'auto_add_to_figure is no longer supported for Axes3D. ' - 'Use fig.add_axes(ax) instead.' - ) - - super().__init__( - fig, rect, frameon=True, box_aspect=box_aspect, *args, **kwargs - ) - # Disable drawing of axes by base class - super().set_axis_off() - # Enable drawing of axes by Axes3D class - self.set_axis_on() - self.M = None - self.invM = None - - # func used to format z -- fall back on major formatters - self.fmt_zdata = None - - self.mouse_init() - self.figure.canvas.callbacks._connect_picklable( - 'motion_notify_event', self._on_move) - self.figure.canvas.callbacks._connect_picklable( - 'button_press_event', self._button_press) - self.figure.canvas.callbacks._connect_picklable( - 'button_release_event', self._button_release) - self.set_top_view() - - self.patch.set_linewidth(0) - # Calculate the pseudo-data width and height - pseudo_bbox = self.transLimits.inverted().transform([(0, 0), (1, 1)]) - self._pseudo_w, self._pseudo_h = pseudo_bbox[1] - pseudo_bbox[0] - - # mplot3d currently manages its own spines and needs these turned off - # for bounding box calculations - self.spines[:].set_visible(False) - - def set_axis_off(self): - self._axis3don = False - self.stale = True - - def set_axis_on(self): - self._axis3don = True - self.stale = True - - def convert_zunits(self, z): - """ - For artists in an Axes, if the zaxis has units support, - convert *z* using zaxis unit type - """ - return self.zaxis.convert_units(z) - - def set_top_view(self): - # this happens to be the right view for the viewing coordinates - # moved up and to the left slightly to fit labels and axes - xdwl = 0.95 / self._dist - xdw = 0.9 / self._dist - ydwl = 0.95 / self._dist - ydw = 0.9 / self._dist - # Set the viewing pane. - self.viewLim.intervalx = (-xdwl, xdw) - self.viewLim.intervaly = (-ydwl, ydw) - self.stale = True - - def _init_axis(self): - """Init 3D axes; overrides creation of regular X/Y axes.""" - self.xaxis = axis3d.XAxis(self) - self.yaxis = axis3d.YAxis(self) - self.zaxis = axis3d.ZAxis(self) - - def get_zaxis(self): - """Return the ``ZAxis`` (`~.axis3d.Axis`) instance.""" - return self.zaxis - - get_zgridlines = _axis_method_wrapper("zaxis", "get_gridlines") - get_zticklines = _axis_method_wrapper("zaxis", "get_ticklines") - - @_api.deprecated("3.7") - def unit_cube(self, vals=None): - return self._unit_cube(vals) - - def _unit_cube(self, vals=None): - minx, maxx, miny, maxy, minz, maxz = vals or self.get_w_lims() - return [(minx, miny, minz), - (maxx, miny, minz), - (maxx, maxy, minz), - (minx, maxy, minz), - (minx, miny, maxz), - (maxx, miny, maxz), - (maxx, maxy, maxz), - (minx, maxy, maxz)] - - @_api.deprecated("3.7") - def tunit_cube(self, vals=None, M=None): - return self._tunit_cube(vals, M) - - def _tunit_cube(self, vals=None, M=None): - if M is None: - M = self.M - xyzs = self._unit_cube(vals) - tcube = proj3d._proj_points(xyzs, M) - return tcube - - @_api.deprecated("3.7") - def tunit_edges(self, vals=None, M=None): - return self._tunit_edges(vals, M) - - def _tunit_edges(self, vals=None, M=None): - tc = self._tunit_cube(vals, M) - edges = [(tc[0], tc[1]), - (tc[1], tc[2]), - (tc[2], tc[3]), - (tc[3], tc[0]), - - (tc[0], tc[4]), - (tc[1], tc[5]), - (tc[2], tc[6]), - (tc[3], tc[7]), - - (tc[4], tc[5]), - (tc[5], tc[6]), - (tc[6], tc[7]), - (tc[7], tc[4])] - return edges - - def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): - """ - Set the aspect ratios. - - Parameters - ---------- - aspect : {'auto', 'equal', 'equalxy', 'equalxz', 'equalyz'} - Possible values: - - ========= ================================================== - value description - ========= ================================================== - 'auto' automatic; fill the position rectangle with data. - 'equal' adapt all the axes to have equal aspect ratios. - 'equalxy' adapt the x and y axes to have equal aspect ratios. - 'equalxz' adapt the x and z axes to have equal aspect ratios. - 'equalyz' adapt the y and z axes to have equal aspect ratios. - ========= ================================================== - - adjustable : None or {'box', 'datalim'}, optional - If not *None*, this defines which parameter will be adjusted to - meet the required aspect. See `.set_adjustable` for further - details. - - anchor : None or str or 2-tuple of float, optional - If not *None*, this defines where the Axes will be drawn if there - is extra space due to aspect constraints. The most common way to - specify the anchor are abbreviations of cardinal directions: - - ===== ===================== - value description - ===== ===================== - 'C' centered - 'SW' lower left corner - 'S' middle of bottom edge - 'SE' lower right corner - etc. - ===== ===================== - - See `~.Axes.set_anchor` for further details. - - share : bool, default: False - If ``True``, apply the settings to all shared Axes. - - See Also - -------- - mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect - """ - _api.check_in_list(('auto', 'equal', 'equalxy', 'equalyz', 'equalxz'), - aspect=aspect) - super().set_aspect( - aspect='auto', adjustable=adjustable, anchor=anchor, share=share) - self._aspect = aspect - - if aspect in ('equal', 'equalxy', 'equalxz', 'equalyz'): - ax_indices = self._equal_aspect_axis_indices(aspect) - - view_intervals = np.array([self.xaxis.get_view_interval(), - self.yaxis.get_view_interval(), - self.zaxis.get_view_interval()]) - ptp = np.ptp(view_intervals, axis=1) - if self._adjustable == 'datalim': - mean = np.mean(view_intervals, axis=1) - scale = max(ptp[ax_indices] / self._box_aspect[ax_indices]) - deltas = scale * self._box_aspect - - for i, set_lim in enumerate((self.set_xlim3d, - self.set_ylim3d, - self.set_zlim3d)): - if i in ax_indices: - set_lim(mean[i] - deltas[i]/2., mean[i] + deltas[i]/2.) - else: # 'box' - # Change the box aspect such that the ratio of the length of - # the unmodified axis to the length of the diagonal - # perpendicular to it remains unchanged. - box_aspect = np.array(self._box_aspect) - box_aspect[ax_indices] = ptp[ax_indices] - remaining_ax_indices = {0, 1, 2}.difference(ax_indices) - if remaining_ax_indices: - remaining = remaining_ax_indices.pop() - old_diag = np.linalg.norm(self._box_aspect[ax_indices]) - new_diag = np.linalg.norm(box_aspect[ax_indices]) - box_aspect[remaining] *= new_diag / old_diag - self.set_box_aspect(box_aspect) - - def _equal_aspect_axis_indices(self, aspect): - """ - Get the indices for which of the x, y, z axes are constrained to have - equal aspect ratios. - - Parameters - ---------- - aspect : {'auto', 'equal', 'equalxy', 'equalxz', 'equalyz'} - See descriptions in docstring for `.set_aspect()`. - """ - ax_indices = [] # aspect == 'auto' - if aspect == 'equal': - ax_indices = [0, 1, 2] - elif aspect == 'equalxy': - ax_indices = [0, 1] - elif aspect == 'equalxz': - ax_indices = [0, 2] - elif aspect == 'equalyz': - ax_indices = [1, 2] - return ax_indices - - def set_box_aspect(self, aspect, *, zoom=1): - """ - Set the Axes box aspect. - - The box aspect is the ratio of height to width in display - units for each face of the box when viewed perpendicular to - that face. This is not to be confused with the data aspect (see - `~.Axes3D.set_aspect`). The default ratios are 4:4:3 (x:y:z). - - To simulate having equal aspect in data space, set the box - aspect to match your data range in each dimension. - - *zoom* controls the overall size of the Axes3D in the figure. - - Parameters - ---------- - aspect : 3-tuple of floats or None - Changes the physical dimensions of the Axes3D, such that the ratio - of the axis lengths in display units is x:y:z. - If None, defaults to (4, 4, 3). - - zoom : float, default: 1 - Control overall size of the Axes3D in the figure. Must be > 0. - """ - if zoom <= 0: - raise ValueError(f'Argument zoom = {zoom} must be > 0') - - if aspect is None: - aspect = np.asarray((4, 4, 3), dtype=float) - else: - aspect = np.asarray(aspect, dtype=float) - _api.check_shape((3,), aspect=aspect) - # default scale tuned to match the mpl32 appearance. - aspect *= 1.8294640721620434 * zoom / np.linalg.norm(aspect) - - self._box_aspect = aspect - self.stale = True - - def apply_aspect(self, position=None): - if position is None: - position = self.get_position(original=True) - - # in the superclass, we would go through and actually deal with axis - # scales and box/datalim. Those are all irrelevant - all we need to do - # is make sure our coordinate system is square. - trans = self.get_figure().transSubfigure - bb = mtransforms.Bbox.unit().transformed(trans) - # this is the physical aspect of the panel (or figure): - fig_aspect = bb.height / bb.width - - box_aspect = 1 - pb = position.frozen() - pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect) - self._set_position(pb1.anchored(self.get_anchor(), pb), 'active') - - @martist.allow_rasterization - def draw(self, renderer): - if not self.get_visible(): - return - self._unstale_viewLim() - - # draw the background patch - self.patch.draw(renderer) - self._frameon = False - - # first, set the aspect - # this is duplicated from `axes._base._AxesBase.draw` - # but must be called before any of the artist are drawn as - # it adjusts the view limits and the size of the bounding box - # of the Axes - locator = self.get_axes_locator() - self.apply_aspect(locator(self, renderer) if locator else None) - - # add the projection matrix to the renderer - self.M = self.get_proj() - self.invM = np.linalg.inv(self.M) - - collections_and_patches = ( - artist for artist in self._children - if isinstance(artist, (mcoll.Collection, mpatches.Patch)) - and artist.get_visible()) - if self.computed_zorder: - # Calculate projection of collections and patches and zorder - # them. Make sure they are drawn above the grids. - zorder_offset = max(axis.get_zorder() - for axis in self._axis_map.values()) + 1 - collection_zorder = patch_zorder = zorder_offset - - for artist in sorted(collections_and_patches, - key=lambda artist: artist.do_3d_projection(), - reverse=True): - if isinstance(artist, mcoll.Collection): - artist.zorder = collection_zorder - collection_zorder += 1 - elif isinstance(artist, mpatches.Patch): - artist.zorder = patch_zorder - patch_zorder += 1 - else: - for artist in collections_and_patches: - artist.do_3d_projection() - - if self._axis3don: - # Draw panes first - for axis in self._axis_map.values(): - axis.draw_pane(renderer) - # Then gridlines - for axis in self._axis_map.values(): - axis.draw_grid(renderer) - # Then axes, labels, text, and ticks - for axis in self._axis_map.values(): - axis.draw(renderer) - - # Then rest - super().draw(renderer) - - def get_axis_position(self): - vals = self.get_w_lims() - tc = self._tunit_cube(vals, self.M) - xhigh = tc[1][2] > tc[2][2] - yhigh = tc[3][2] > tc[2][2] - zhigh = tc[0][2] > tc[2][2] - return xhigh, yhigh, zhigh - - def update_datalim(self, xys, **kwargs): - """ - Not implemented in `~mpl_toolkits.mplot3d.axes3d.Axes3D`. - """ - pass - - get_autoscalez_on = _axis_method_wrapper("zaxis", "_get_autoscale_on") - set_autoscalez_on = _axis_method_wrapper("zaxis", "_set_autoscale_on") - - def set_zmargin(self, m): - """ - Set padding of Z data limits prior to autoscaling. - - *m* times the data interval will be added to each end of that interval - before it is used in autoscaling. If *m* is negative, this will clip - the data range instead of expanding it. - - For example, if your data is in the range [0, 2], a margin of 0.1 will - result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range - of [0.2, 1.8]. - - Parameters - ---------- - m : float greater than -0.5 - """ - if m <= -0.5: - raise ValueError("margin must be greater than -0.5") - self._zmargin = m - self._request_autoscale_view("z") - self.stale = True - - def margins(self, *margins, x=None, y=None, z=None, tight=True): - """ - Set or retrieve autoscaling margins. - - See `.Axes.margins` for full documentation. Because this function - applies to 3D Axes, it also takes a *z* argument, and returns - ``(xmargin, ymargin, zmargin)``. - """ - if margins and (x is not None or y is not None or z is not None): - raise TypeError('Cannot pass both positional and keyword ' - 'arguments for x, y, and/or z.') - elif len(margins) == 1: - x = y = z = margins[0] - elif len(margins) == 3: - x, y, z = margins - elif margins: - raise TypeError('Must pass a single positional argument for all ' - 'margins, or one for each margin (x, y, z).') - - if x is None and y is None and z is None: - if tight is not True: - _api.warn_external(f'ignoring tight={tight!r} in get mode') - return self._xmargin, self._ymargin, self._zmargin - - if x is not None: - self.set_xmargin(x) - if y is not None: - self.set_ymargin(y) - if z is not None: - self.set_zmargin(z) - - self.autoscale_view( - tight=tight, scalex=(x is not None), scaley=(y is not None), - scalez=(z is not None) - ) - - def autoscale(self, enable=True, axis='both', tight=None): - """ - Convenience method for simple axis view autoscaling. - - See `.Axes.autoscale` for full documentation. Because this function - applies to 3D Axes, *axis* can also be set to 'z', and setting *axis* - to 'both' autoscales all three axes. - """ - if enable is None: - scalex = True - scaley = True - scalez = True - else: - if axis in ['x', 'both']: - self.set_autoscalex_on(bool(enable)) - scalex = self.get_autoscalex_on() - else: - scalex = False - if axis in ['y', 'both']: - self.set_autoscaley_on(bool(enable)) - scaley = self.get_autoscaley_on() - else: - scaley = False - if axis in ['z', 'both']: - self.set_autoscalez_on(bool(enable)) - scalez = self.get_autoscalez_on() - else: - scalez = False - if scalex: - self._request_autoscale_view("x", tight=tight) - if scaley: - self._request_autoscale_view("y", tight=tight) - if scalez: - self._request_autoscale_view("z", tight=tight) - - def auto_scale_xyz(self, X, Y, Z=None, had_data=None): - # This updates the bounding boxes as to keep a record as to what the - # minimum sized rectangular volume holds the data. - if np.shape(X) == np.shape(Y): - self.xy_dataLim.update_from_data_xy( - np.column_stack([np.ravel(X), np.ravel(Y)]), not had_data) - else: - self.xy_dataLim.update_from_data_x(X, not had_data) - self.xy_dataLim.update_from_data_y(Y, not had_data) - if Z is not None: - self.zz_dataLim.update_from_data_x(Z, not had_data) - # Let autoscale_view figure out how to use this data. - self.autoscale_view() - - def autoscale_view(self, tight=None, scalex=True, scaley=True, - scalez=True): - """ - Autoscale the view limits using the data limits. - - See `.Axes.autoscale_view` for full documentation. Because this - function applies to 3D Axes, it also takes a *scalez* argument. - """ - # This method looks at the rectangular volume (see above) - # of data and decides how to scale the view portal to fit it. - if tight is None: - _tight = self._tight - if not _tight: - # if image data only just use the datalim - for artist in self._children: - if isinstance(artist, mimage.AxesImage): - _tight = True - elif isinstance(artist, (mlines.Line2D, mpatches.Patch)): - _tight = False - break - else: - _tight = self._tight = bool(tight) - - if scalex and self.get_autoscalex_on(): - x0, x1 = self.xy_dataLim.intervalx - xlocator = self.xaxis.get_major_locator() - x0, x1 = xlocator.nonsingular(x0, x1) - if self._xmargin > 0: - delta = (x1 - x0) * self._xmargin - x0 -= delta - x1 += delta - if not _tight: - x0, x1 = xlocator.view_limits(x0, x1) - self.set_xbound(x0, x1) - - if scaley and self.get_autoscaley_on(): - y0, y1 = self.xy_dataLim.intervaly - ylocator = self.yaxis.get_major_locator() - y0, y1 = ylocator.nonsingular(y0, y1) - if self._ymargin > 0: - delta = (y1 - y0) * self._ymargin - y0 -= delta - y1 += delta - if not _tight: - y0, y1 = ylocator.view_limits(y0, y1) - self.set_ybound(y0, y1) - - if scalez and self.get_autoscalez_on(): - z0, z1 = self.zz_dataLim.intervalx - zlocator = self.zaxis.get_major_locator() - z0, z1 = zlocator.nonsingular(z0, z1) - if self._zmargin > 0: - delta = (z1 - z0) * self._zmargin - z0 -= delta - z1 += delta - if not _tight: - z0, z1 = zlocator.view_limits(z0, z1) - self.set_zbound(z0, z1) - - def get_w_lims(self): - """Get 3D world limits.""" - minx, maxx = self.get_xlim3d() - miny, maxy = self.get_ylim3d() - minz, maxz = self.get_zlim3d() - return minx, maxx, miny, maxy, minz, maxz - - # set_xlim, set_ylim are directly inherited from base Axes. - def set_zlim(self, bottom=None, top=None, *, emit=True, auto=False, - zmin=None, zmax=None): - """ - Set 3D z limits. - - See `.Axes.set_ylim` for full documentation - """ - if top is None and np.iterable(bottom): - bottom, top = bottom - if zmin is not None: - if bottom is not None: - raise TypeError("Cannot pass both 'bottom' and 'zmin'") - bottom = zmin - if zmax is not None: - if top is not None: - raise TypeError("Cannot pass both 'top' and 'zmax'") - top = zmax - return self.zaxis._set_lim(bottom, top, emit=emit, auto=auto) - - set_xlim3d = maxes.Axes.set_xlim - set_ylim3d = maxes.Axes.set_ylim - set_zlim3d = set_zlim - - def get_xlim(self): - # docstring inherited - return tuple(self.xy_viewLim.intervalx) - - def get_ylim(self): - # docstring inherited - return tuple(self.xy_viewLim.intervaly) - - def get_zlim(self): - """ - Return the 3D z-axis view limits. - - Returns - ------- - left, right : (float, float) - The current z-axis limits in data coordinates. - - See Also - -------- - set_zlim - set_zbound, get_zbound - invert_zaxis, zaxis_inverted - - Notes - ----- - The z-axis may be inverted, in which case the *left* value will - be greater than the *right* value. - """ - return tuple(self.zz_viewLim.intervalx) - - get_zscale = _axis_method_wrapper("zaxis", "get_scale") - - # Redefine all three methods to overwrite their docstrings. - set_xscale = _axis_method_wrapper("xaxis", "_set_axes_scale") - set_yscale = _axis_method_wrapper("yaxis", "_set_axes_scale") - set_zscale = _axis_method_wrapper("zaxis", "_set_axes_scale") - set_xscale.__doc__, set_yscale.__doc__, set_zscale.__doc__ = map( - """ - Set the {}-axis scale. - - Parameters - ---------- - value : {{"linear"}} - The axis scale type to apply. 3D axes currently only support - linear scales; other scales yield nonsensical results. - - **kwargs - Keyword arguments are nominally forwarded to the scale class, but - none of them is applicable for linear scales. - """.format, - ["x", "y", "z"]) - - get_zticks = _axis_method_wrapper("zaxis", "get_ticklocs") - set_zticks = _axis_method_wrapper("zaxis", "set_ticks") - get_zmajorticklabels = _axis_method_wrapper("zaxis", "get_majorticklabels") - get_zminorticklabels = _axis_method_wrapper("zaxis", "get_minorticklabels") - get_zticklabels = _axis_method_wrapper("zaxis", "get_ticklabels") - set_zticklabels = _axis_method_wrapper( - "zaxis", "set_ticklabels", - doc_sub={"Axis.set_ticks": "Axes3D.set_zticks"}) - - zaxis_date = _axis_method_wrapper("zaxis", "axis_date") - if zaxis_date.__doc__: - zaxis_date.__doc__ += textwrap.dedent(""" - - Notes - ----- - This function is merely provided for completeness, but 3D axes do not - support dates for ticks, and so this may not work as expected. - """) - - def clabel(self, *args, **kwargs): - """Currently not implemented for 3D axes, and returns *None*.""" - return None - - def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z", - share=False): - """ - Set the elevation and azimuth of the axes in degrees (not radians). - - This can be used to rotate the axes programmatically. - - To look normal to the primary planes, the following elevation and - azimuth angles can be used. A roll angle of 0, 90, 180, or 270 deg - will rotate these views while keeping the axes at right angles. - - ========== ==== ==== - view plane elev azim - ========== ==== ==== - XY 90 -90 - XZ 0 -90 - YZ 0 0 - -XY -90 90 - -XZ 0 90 - -YZ 0 180 - ========== ==== ==== - - Parameters - ---------- - elev : float, default: None - The elevation angle in degrees rotates the camera above the plane - pierced by the vertical axis, with a positive angle corresponding - to a location above that plane. For example, with the default - vertical axis of 'z', the elevation defines the angle of the camera - location above the x-y plane. - If None, then the initial value as specified in the `Axes3D` - constructor is used. - azim : float, default: None - The azimuthal angle in degrees rotates the camera about the - vertical axis, with a positive angle corresponding to a - right-handed rotation. For example, with the default vertical axis - of 'z', a positive azimuth rotates the camera about the origin from - its location along the +x axis towards the +y axis. - If None, then the initial value as specified in the `Axes3D` - constructor is used. - roll : float, default: None - The roll angle in degrees rotates the camera about the viewing - axis. A positive angle spins the camera clockwise, causing the - scene to rotate counter-clockwise. - If None, then the initial value as specified in the `Axes3D` - constructor is used. - vertical_axis : {"z", "x", "y"}, default: "z" - The axis to align vertically. *azim* rotates about this axis. - share : bool, default: False - If ``True``, apply the settings to all Axes with shared views. - """ - - self._dist = 10 # The camera distance from origin. Behaves like zoom - - if elev is None: - elev = self.initial_elev - if azim is None: - azim = self.initial_azim - if roll is None: - roll = self.initial_roll - vertical_axis = _api.check_getitem( - dict(x=0, y=1, z=2), vertical_axis=vertical_axis - ) - - if share: - axes = {sibling for sibling - in self._shared_axes['view'].get_siblings(self)} - else: - axes = [self] - - for ax in axes: - ax.elev = elev - ax.azim = azim - ax.roll = roll - ax._vertical_axis = vertical_axis - - def set_proj_type(self, proj_type, focal_length=None): - """ - Set the projection type. - - Parameters - ---------- - proj_type : {'persp', 'ortho'} - The projection type. - focal_length : float, default: None - For a projection type of 'persp', the focal length of the virtual - camera. Must be > 0. If None, defaults to 1. - The focal length can be computed from a desired Field Of View via - the equation: focal_length = 1/tan(FOV/2) - """ - _api.check_in_list(['persp', 'ortho'], proj_type=proj_type) - if proj_type == 'persp': - if focal_length is None: - focal_length = 1 - elif focal_length <= 0: - raise ValueError(f"focal_length = {focal_length} must be " - "greater than 0") - self._focal_length = focal_length - else: # 'ortho': - if focal_length not in (None, np.inf): - raise ValueError(f"focal_length = {focal_length} must be " - f"None for proj_type = {proj_type}") - self._focal_length = np.inf - - def _roll_to_vertical(self, arr): - """Roll arrays to match the different vertical axis.""" - return np.roll(arr, self._vertical_axis - 2) - - def get_proj(self): - """Create the projection matrix from the current viewing position.""" - - # Transform to uniform world coordinates 0-1, 0-1, 0-1 - box_aspect = self._roll_to_vertical(self._box_aspect) - worldM = proj3d.world_transformation( - *self.get_xlim3d(), - *self.get_ylim3d(), - *self.get_zlim3d(), - pb_aspect=box_aspect, - ) - - # Look into the middle of the world coordinates: - R = 0.5 * box_aspect - - # elev: elevation angle in the z plane. - # azim: azimuth angle in the xy plane. - # Coordinates for a point that rotates around the box of data. - # p0, p1 corresponds to rotating the box only around the vertical axis. - # p2 corresponds to rotating the box only around the horizontal axis. - elev_rad = np.deg2rad(self.elev) - azim_rad = np.deg2rad(self.azim) - p0 = np.cos(elev_rad) * np.cos(azim_rad) - p1 = np.cos(elev_rad) * np.sin(azim_rad) - p2 = np.sin(elev_rad) - - # When changing vertical axis the coordinates changes as well. - # Roll the values to get the same behaviour as the default: - ps = self._roll_to_vertical([p0, p1, p2]) - - # The coordinates for the eye viewing point. The eye is looking - # towards the middle of the box of data from a distance: - eye = R + self._dist * ps - - # vvec, self._vvec and self._eye are unused, remove when deprecated - vvec = R - eye - self._eye = eye - self._vvec = vvec / np.linalg.norm(vvec) - - # Calculate the viewing axes for the eye position - u, v, w = self._calc_view_axes(eye) - self._view_u = u # _view_u is towards the right of the screen - self._view_v = v # _view_v is towards the top of the screen - self._view_w = w # _view_w is out of the screen - - # Generate the view and projection transformation matrices - if self._focal_length == np.inf: - # Orthographic projection - viewM = proj3d._view_transformation_uvw(u, v, w, eye) - projM = proj3d._ortho_transformation(-self._dist, self._dist) - else: - # Perspective projection - # Scale the eye dist to compensate for the focal length zoom effect - eye_focal = R + self._dist * ps * self._focal_length - viewM = proj3d._view_transformation_uvw(u, v, w, eye_focal) - projM = proj3d._persp_transformation(-self._dist, - self._dist, - self._focal_length) - - # Combine all the transformation matrices to get the final projection - M0 = np.dot(viewM, worldM) - M = np.dot(projM, M0) - return M - - def mouse_init(self, rotate_btn=1, pan_btn=2, zoom_btn=3): - """ - Set the mouse buttons for 3D rotation and zooming. - - Parameters - ---------- - rotate_btn : int or list of int, default: 1 - The mouse button or buttons to use for 3D rotation of the axes. - pan_btn : int or list of int, default: 2 - The mouse button or buttons to use to pan the 3D axes. - zoom_btn : int or list of int, default: 3 - The mouse button or buttons to use to zoom the 3D axes. - """ - self.button_pressed = None - # coerce scalars into array-like, then convert into - # a regular list to avoid comparisons against None - # which breaks in recent versions of numpy. - self._rotate_btn = np.atleast_1d(rotate_btn).tolist() - self._pan_btn = np.atleast_1d(pan_btn).tolist() - self._zoom_btn = np.atleast_1d(zoom_btn).tolist() - - def disable_mouse_rotation(self): - """Disable mouse buttons for 3D rotation, panning, and zooming.""" - self.mouse_init(rotate_btn=[], pan_btn=[], zoom_btn=[]) - - def can_zoom(self): - # doc-string inherited - return True - - def can_pan(self): - # doc-string inherited - return True - - def sharez(self, other): - """ - Share the z-axis with *other*. - - This is equivalent to passing ``sharez=other`` when constructing the - Axes, and cannot be used if the z-axis is already being shared with - another Axes. - """ - _api.check_isinstance(Axes3D, other=other) - if self._sharez is not None and other is not self._sharez: - raise ValueError("z-axis is already shared") - self._shared_axes["z"].join(self, other) - self._sharez = other - self.zaxis.major = other.zaxis.major # Ticker instances holding - self.zaxis.minor = other.zaxis.minor # locator and formatter. - z0, z1 = other.get_zlim() - self.set_zlim(z0, z1, emit=False, auto=other.get_autoscalez_on()) - self.zaxis._scale = other.zaxis._scale - - def shareview(self, other): - """ - Share the view angles with *other*. - - This is equivalent to passing ``shareview=other`` when - constructing the Axes, and cannot be used if the view angles are - already being shared with another Axes. - """ - _api.check_isinstance(Axes3D, other=other) - if self._shareview is not None and other is not self._shareview: - raise ValueError("view angles are already shared") - self._shared_axes["view"].join(self, other) - self._shareview = other - vertical_axis = {0: "x", 1: "y", 2: "z"}[other._vertical_axis] - self.view_init(elev=other.elev, azim=other.azim, roll=other.roll, - vertical_axis=vertical_axis, share=True) - - def clear(self): - # docstring inherited. - super().clear() - if self._focal_length == np.inf: - self._zmargin = mpl.rcParams['axes.zmargin'] - else: - self._zmargin = 0. - self.grid(mpl.rcParams['axes3d.grid']) - - def _button_press(self, event): - if event.inaxes == self: - self.button_pressed = event.button - self._sx, self._sy = event.xdata, event.ydata - toolbar = self.figure.canvas.toolbar - if toolbar and toolbar._nav_stack() is None: - toolbar.push_current() - - def _button_release(self, event): - self.button_pressed = None - toolbar = self.figure.canvas.toolbar - # backend_bases.release_zoom and backend_bases.release_pan call - # push_current, so check the navigation mode so we don't call it twice - if toolbar and self.get_navigate_mode() is None: - toolbar.push_current() - - def _get_view(self): - # docstring inherited - return { - "xlim": self.get_xlim(), "autoscalex_on": self.get_autoscalex_on(), - "ylim": self.get_ylim(), "autoscaley_on": self.get_autoscaley_on(), - "zlim": self.get_zlim(), "autoscalez_on": self.get_autoscalez_on(), - }, (self.elev, self.azim, self.roll) - - def _set_view(self, view): - # docstring inherited - props, (elev, azim, roll) = view - self.set(**props) - self.elev = elev - self.azim = azim - self.roll = roll - - def format_zdata(self, z): - """ - Return *z* string formatted. This function will use the - :attr:`fmt_zdata` attribute if it is callable, else will fall - back on the zaxis major formatter - """ - try: - return self.fmt_zdata(z) - except (AttributeError, TypeError): - func = self.zaxis.get_major_formatter().format_data_short - val = func(z) - return val - - def format_coord(self, xv, yv, renderer=None): - """ - Return a string giving the current view rotation angles, or the x, y, z - coordinates of the point on the nearest axis pane underneath the mouse - cursor, depending on the mouse button pressed. - """ - coords = '' - - if self.button_pressed in self._rotate_btn: - # ignore xv and yv and display angles instead - coords = self._rotation_coords() - - elif self.M is not None: - coords = self._location_coords(xv, yv, renderer) - - return coords - - def _rotation_coords(self): - """ - Return the rotation angles as a string. - """ - norm_elev = art3d._norm_angle(self.elev) - norm_azim = art3d._norm_angle(self.azim) - norm_roll = art3d._norm_angle(self.roll) - coords = (f"elevation={norm_elev:.0f}\N{DEGREE SIGN}, " - f"azimuth={norm_azim:.0f}\N{DEGREE SIGN}, " - f"roll={norm_roll:.0f}\N{DEGREE SIGN}" - ).replace("-", "\N{MINUS SIGN}") - return coords - - def _location_coords(self, xv, yv, renderer): - """ - Return the location on the axis pane underneath the cursor as a string. - """ - p1, pane_idx = self._calc_coord(xv, yv, renderer) - xs = self.format_xdata(p1[0]) - ys = self.format_ydata(p1[1]) - zs = self.format_zdata(p1[2]) - if pane_idx == 0: - coords = f'x pane={xs}, y={ys}, z={zs}' - elif pane_idx == 1: - coords = f'x={xs}, y pane={ys}, z={zs}' - elif pane_idx == 2: - coords = f'x={xs}, y={ys}, z pane={zs}' - return coords - - def _get_camera_loc(self): - """ - Returns the current camera location in data coordinates. - """ - cx, cy, cz, dx, dy, dz = self._get_w_centers_ranges() - c = np.array([cx, cy, cz]) - r = np.array([dx, dy, dz]) - - if self._focal_length == np.inf: # orthographic projection - focal_length = 1e9 # large enough to be effectively infinite - else: # perspective projection - focal_length = self._focal_length - eye = c + self._view_w * self._dist * r / self._box_aspect * focal_length - return eye - - def _calc_coord(self, xv, yv, renderer=None): - """ - Given the 2D view coordinates, find the point on the nearest axis pane - that lies directly below those coordinates. Returns a 3D point in data - coordinates. - """ - if self._focal_length == np.inf: # orthographic projection - zv = 1 - else: # perspective projection - zv = -1 / self._focal_length - - # Convert point on view plane to data coordinates - p1 = np.array(proj3d.inv_transform(xv, yv, zv, self.invM)).ravel() - - # Get the vector from the camera to the point on the view plane - vec = self._get_camera_loc() - p1 - - # Get the pane locations for each of the axes - pane_locs = [] - for axis in self._axis_map.values(): - xys, loc = axis.active_pane(renderer) - pane_locs.append(loc) - - # Find the distance to the nearest pane by projecting the view vector - scales = np.zeros(3) - for i in range(3): - if vec[i] == 0: - scales[i] = np.inf - else: - scales[i] = (p1[i] - pane_locs[i]) / vec[i] - pane_idx = np.argmin(abs(scales)) - scale = scales[pane_idx] - - # Calculate the point on the closest pane - p2 = p1 - scale*vec - return p2, pane_idx - - def _on_move(self, event): - """ - Mouse moving. - - By default, button-1 rotates, button-2 pans, and button-3 zooms; - these buttons can be modified via `mouse_init`. - """ - - if not self.button_pressed: - return - - if self.get_navigate_mode() is not None: - # we don't want to rotate if we are zooming/panning - # from the toolbar - return - - if self.M is None: - return - - x, y = event.xdata, event.ydata - # In case the mouse is out of bounds. - if x is None or event.inaxes != self: - return - - dx, dy = x - self._sx, y - self._sy - w = self._pseudo_w - h = self._pseudo_h - - # Rotation - if self.button_pressed in self._rotate_btn: - # rotate viewing point - # get the x and y pixel coords - if dx == 0 and dy == 0: - return - - roll = np.deg2rad(self.roll) - delev = -(dy/h)*180*np.cos(roll) + (dx/w)*180*np.sin(roll) - dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll) - elev = self.elev + delev - azim = self.azim + dazim - self.view_init(elev=elev, azim=azim, roll=roll, share=True) - self.stale = True - - # Pan - elif self.button_pressed in self._pan_btn: - # Start the pan event with pixel coordinates - px, py = self.transData.transform([self._sx, self._sy]) - self.start_pan(px, py, 2) - # pan view (takes pixel coordinate input) - self.drag_pan(2, None, event.x, event.y) - self.end_pan() - - # Zoom - elif self.button_pressed in self._zoom_btn: - # zoom view (dragging down zooms in) - scale = h/(h - dy) - self._scale_axis_limits(scale, scale, scale) - - # Store the event coordinates for the next time through. - self._sx, self._sy = x, y - # Always request a draw update at the end of interaction - self.figure.canvas.draw_idle() - - def drag_pan(self, button, key, x, y): - # docstring inherited - - # Get the coordinates from the move event - p = self._pan_start - (xdata, ydata), (xdata_start, ydata_start) = p.trans_inverse.transform( - [(x, y), (p.x, p.y)]) - self._sx, self._sy = xdata, ydata - # Calling start_pan() to set the x/y of this event as the starting - # move location for the next event - self.start_pan(x, y, button) - du, dv = xdata - xdata_start, ydata - ydata_start - dw = 0 - if key == 'x': - dv = 0 - elif key == 'y': - du = 0 - if du == 0 and dv == 0: - return - - # Transform the pan from the view axes to the data axes - R = np.array([self._view_u, self._view_v, self._view_w]) - R = -R / self._box_aspect * self._dist - duvw_projected = R.T @ np.array([du, dv, dw]) - - # Calculate pan distance - minx, maxx, miny, maxy, minz, maxz = self.get_w_lims() - dx = (maxx - minx) * duvw_projected[0] - dy = (maxy - miny) * duvw_projected[1] - dz = (maxz - minz) * duvw_projected[2] - - # Set the new axis limits - self.set_xlim3d(minx + dx, maxx + dx) - self.set_ylim3d(miny + dy, maxy + dy) - self.set_zlim3d(minz + dz, maxz + dz) - - def _calc_view_axes(self, eye): - """ - Get the unit vectors for the viewing axes in data coordinates. - `u` is towards the right of the screen - `v` is towards the top of the screen - `w` is out of the screen - """ - elev_rad = np.deg2rad(art3d._norm_angle(self.elev)) - roll_rad = np.deg2rad(art3d._norm_angle(self.roll)) - - # Look into the middle of the world coordinates - R = 0.5 * self._roll_to_vertical(self._box_aspect) - - # Define which axis should be vertical. A negative value - # indicates the plot is upside down and therefore the values - # have been reversed: - V = np.zeros(3) - V[self._vertical_axis] = -1 if abs(elev_rad) > np.pi/2 else 1 - - u, v, w = proj3d._view_axes(eye, R, V, roll_rad) - return u, v, w - - def _set_view_from_bbox(self, bbox, direction='in', - mode=None, twinx=False, twiny=False): - """ - Zoom in or out of the bounding box. - - Will center the view in the center of the bounding box, and zoom by - the ratio of the size of the bounding box to the size of the Axes3D. - """ - (start_x, start_y, stop_x, stop_y) = bbox - if mode == 'x': - start_y = self.bbox.min[1] - stop_y = self.bbox.max[1] - elif mode == 'y': - start_x = self.bbox.min[0] - stop_x = self.bbox.max[0] - - # Clip to bounding box limits - start_x, stop_x = np.clip(sorted([start_x, stop_x]), - self.bbox.min[0], self.bbox.max[0]) - start_y, stop_y = np.clip(sorted([start_y, stop_y]), - self.bbox.min[1], self.bbox.max[1]) - - # Move the center of the view to the center of the bbox - zoom_center_x = (start_x + stop_x)/2 - zoom_center_y = (start_y + stop_y)/2 - - ax_center_x = (self.bbox.max[0] + self.bbox.min[0])/2 - ax_center_y = (self.bbox.max[1] + self.bbox.min[1])/2 - - self.start_pan(zoom_center_x, zoom_center_y, 2) - self.drag_pan(2, None, ax_center_x, ax_center_y) - self.end_pan() - - # Calculate zoom level - dx = abs(start_x - stop_x) - dy = abs(start_y - stop_y) - scale_u = dx / (self.bbox.max[0] - self.bbox.min[0]) - scale_v = dy / (self.bbox.max[1] - self.bbox.min[1]) - - # Keep aspect ratios equal - scale = max(scale_u, scale_v) - - # Zoom out - if direction == 'out': - scale = 1 / scale - - self._zoom_data_limits(scale, scale, scale) - - def _zoom_data_limits(self, scale_u, scale_v, scale_w): - """ - Zoom in or out of a 3D plot. - - Will scale the data limits by the scale factors. These will be - transformed to the x, y, z data axes based on the current view angles. - A scale factor > 1 zooms out and a scale factor < 1 zooms in. - - For an axes that has had its aspect ratio set to 'equal', 'equalxy', - 'equalyz', or 'equalxz', the relevant axes are constrained to zoom - equally. - - Parameters - ---------- - scale_u : float - Scale factor for the u view axis (view screen horizontal). - scale_v : float - Scale factor for the v view axis (view screen vertical). - scale_w : float - Scale factor for the w view axis (view screen depth). - """ - scale = np.array([scale_u, scale_v, scale_w]) - - # Only perform frame conversion if unequal scale factors - if not np.allclose(scale, scale_u): - # Convert the scale factors from the view frame to the data frame - R = np.array([self._view_u, self._view_v, self._view_w]) - S = scale * np.eye(3) - scale = np.linalg.norm(R.T @ S, axis=1) - - # Set the constrained scale factors to the factor closest to 1 - if self._aspect in ('equal', 'equalxy', 'equalxz', 'equalyz'): - ax_idxs = self._equal_aspect_axis_indices(self._aspect) - min_ax_idxs = np.argmin(np.abs(scale[ax_idxs] - 1)) - scale[ax_idxs] = scale[ax_idxs][min_ax_idxs] - - self._scale_axis_limits(scale[0], scale[1], scale[2]) - - def _scale_axis_limits(self, scale_x, scale_y, scale_z): - """ - Keeping the center of the x, y, and z data axes fixed, scale their - limits by scale factors. A scale factor > 1 zooms out and a scale - factor < 1 zooms in. - - Parameters - ---------- - scale_x : float - Scale factor for the x data axis. - scale_y : float - Scale factor for the y data axis. - scale_z : float - Scale factor for the z data axis. - """ - # Get the axis centers and ranges - cx, cy, cz, dx, dy, dz = self._get_w_centers_ranges() - - # Set the scaled axis limits - self.set_xlim3d(cx - dx*scale_x/2, cx + dx*scale_x/2) - self.set_ylim3d(cy - dy*scale_y/2, cy + dy*scale_y/2) - self.set_zlim3d(cz - dz*scale_z/2, cz + dz*scale_z/2) - - def _get_w_centers_ranges(self): - """Get 3D world centers and axis ranges.""" - # Calculate center of axis limits - minx, maxx, miny, maxy, minz, maxz = self.get_w_lims() - cx = (maxx + minx)/2 - cy = (maxy + miny)/2 - cz = (maxz + minz)/2 - - # Calculate range of axis limits - dx = (maxx - minx) - dy = (maxy - miny) - dz = (maxz - minz) - return cx, cy, cz, dx, dy, dz - - def set_zlabel(self, zlabel, fontdict=None, labelpad=None, **kwargs): - """ - Set zlabel. See doc for `.set_ylabel` for description. - """ - if labelpad is not None: - self.zaxis.labelpad = labelpad - return self.zaxis.set_label_text(zlabel, fontdict, **kwargs) - - def get_zlabel(self): - """ - Get the z-label text string. - """ - label = self.zaxis.get_label() - return label.get_text() - - # Axes rectangle characteristics - - # The frame_on methods are not available for 3D axes. - # Python will raise a TypeError if they are called. - get_frame_on = None - set_frame_on = None - - def grid(self, visible=True, **kwargs): - """ - Set / unset 3D grid. - - .. note:: - - Currently, this function does not behave the same as - `.axes.Axes.grid`, but it is intended to eventually support that - behavior. - """ - # TODO: Operate on each axes separately - if len(kwargs): - visible = True - self._draw_grid = visible - self.stale = True - - def tick_params(self, axis='both', **kwargs): - """ - Convenience method for changing the appearance of ticks and - tick labels. - - See `.Axes.tick_params` for full documentation. Because this function - applies to 3D Axes, *axis* can also be set to 'z', and setting *axis* - to 'both' autoscales all three axes. - - Also, because of how Axes3D objects are drawn very differently - from regular 2D axes, some of these settings may have - ambiguous meaning. For simplicity, the 'z' axis will - accept settings as if it was like the 'y' axis. - - .. note:: - Axes3D currently ignores some of these settings. - """ - _api.check_in_list(['x', 'y', 'z', 'both'], axis=axis) - if axis in ['x', 'y', 'both']: - super().tick_params(axis, **kwargs) - if axis in ['z', 'both']: - zkw = dict(kwargs) - zkw.pop('top', None) - zkw.pop('bottom', None) - zkw.pop('labeltop', None) - zkw.pop('labelbottom', None) - self.zaxis.set_tick_params(**zkw) - - # data limits, ticks, tick labels, and formatting - - def invert_zaxis(self): - """ - Invert the z-axis. - - See Also - -------- - zaxis_inverted - get_zlim, set_zlim - get_zbound, set_zbound - """ - bottom, top = self.get_zlim() - self.set_zlim(top, bottom, auto=None) - - zaxis_inverted = _axis_method_wrapper("zaxis", "get_inverted") - - def get_zbound(self): - """ - Return the lower and upper z-axis bounds, in increasing order. - - See Also - -------- - set_zbound - get_zlim, set_zlim - invert_zaxis, zaxis_inverted - """ - bottom, top = self.get_zlim() - if bottom < top: - return bottom, top - else: - return top, bottom - - def set_zbound(self, lower=None, upper=None): - """ - Set the lower and upper numerical bounds of the z-axis. - - This method will honor axes inversion regardless of parameter order. - It will not change the autoscaling setting (`.get_autoscalez_on()`). - - Parameters - ---------- - lower, upper : float or None - The lower and upper bounds. If *None*, the respective axis bound - is not modified. - - See Also - -------- - get_zbound - get_zlim, set_zlim - invert_zaxis, zaxis_inverted - """ - if upper is None and np.iterable(lower): - lower, upper = lower - - old_lower, old_upper = self.get_zbound() - if lower is None: - lower = old_lower - if upper is None: - upper = old_upper - - self.set_zlim(sorted((lower, upper), - reverse=bool(self.zaxis_inverted())), - auto=None) - - def text(self, x, y, z, s, zdir=None, **kwargs): - """ - Add the text *s* to the 3D Axes at location *x*, *y*, *z* in data coordinates. - - Parameters - ---------- - x, y, z : float - The position to place the text. - s : str - The text. - zdir : {'x', 'y', 'z', 3-tuple}, optional - The direction to be used as the z-direction. Default: 'z'. - See `.get_dir_vector` for a description of the values. - **kwargs - Other arguments are forwarded to `matplotlib.axes.Axes.text`. - - Returns - ------- - `.Text3D` - The created `.Text3D` instance. - """ - text = super().text(x, y, s, **kwargs) - art3d.text_2d_to_3d(text, z, zdir) - return text - - text3D = text - text2D = Axes.text - - def plot(self, xs, ys, *args, zdir='z', **kwargs): - """ - Plot 2D or 3D data. - - Parameters - ---------- - xs : 1D array-like - x coordinates of vertices. - ys : 1D array-like - y coordinates of vertices. - zs : float or 1D array-like - z coordinates of vertices; either one for all points or one for - each point. - zdir : {'x', 'y', 'z'}, default: 'z' - When plotting 2D data, the direction to use as z. - **kwargs - Other arguments are forwarded to `matplotlib.axes.Axes.plot`. - """ - had_data = self.has_data() - - # `zs` can be passed positionally or as keyword; checking whether - # args[0] is a string matches the behavior of 2D `plot` (via - # `_process_plot_var_args`). - if args and not isinstance(args[0], str): - zs, *args = args - if 'zs' in kwargs: - raise TypeError("plot() for multiple values for argument 'z'") - else: - zs = kwargs.pop('zs', 0) - - # Match length - zs = np.broadcast_to(zs, np.shape(xs)) - - lines = super().plot(xs, ys, *args, **kwargs) - for line in lines: - art3d.line_2d_to_3d(line, zs=zs, zdir=zdir) - - xs, ys, zs = art3d.juggle_axes(xs, ys, zs, zdir) - self.auto_scale_xyz(xs, ys, zs, had_data) - return lines - - plot3D = plot - - def plot_surface(self, X, Y, Z, *, norm=None, vmin=None, - vmax=None, lightsource=None, **kwargs): - """ - Create a surface plot. - - By default, it will be colored in shades of a solid color, but it also - supports colormapping by supplying the *cmap* argument. - - .. note:: - - The *rcount* and *ccount* kwargs, which both default to 50, - determine the maximum number of samples used in each direction. If - the input data is larger, it will be downsampled (by slicing) to - these numbers of points. - - .. note:: - - To maximize rendering speed consider setting *rstride* and *cstride* - to divisors of the number of rows minus 1 and columns minus 1 - respectively. For example, given 51 rows rstride can be any of the - divisors of 50. - - Similarly, a setting of *rstride* and *cstride* equal to 1 (or - *rcount* and *ccount* equal the number of rows and columns) can use - the optimized path. - - Parameters - ---------- - X, Y, Z : 2D arrays - Data values. - - rcount, ccount : int - Maximum number of samples used in each direction. If the input - data is larger, it will be downsampled (by slicing) to these - numbers of points. Defaults to 50. - - rstride, cstride : int - Downsampling stride in each direction. These arguments are - mutually exclusive with *rcount* and *ccount*. If only one of - *rstride* or *cstride* is set, the other defaults to 10. - - 'classic' mode uses a default of ``rstride = cstride = 10`` instead - of the new default of ``rcount = ccount = 50``. - - color : color-like - Color of the surface patches. - - cmap : Colormap - Colormap of the surface patches. - - facecolors : array-like of colors. - Colors of each individual patch. - - norm : Normalize - Normalization for the colormap. - - vmin, vmax : float - Bounds for the normalization. - - shade : bool, default: True - Whether to shade the facecolors. Shading is always disabled when - *cmap* is specified. - - lightsource : `~matplotlib.colors.LightSource` - The lightsource to use when *shade* is True. - - **kwargs - Other keyword arguments are forwarded to `.Poly3DCollection`. - """ - - had_data = self.has_data() - - if Z.ndim != 2: - raise ValueError("Argument Z must be 2-dimensional.") - - Z = cbook._to_unmasked_float_array(Z) - X, Y, Z = np.broadcast_arrays(X, Y, Z) - rows, cols = Z.shape - - has_stride = 'rstride' in kwargs or 'cstride' in kwargs - has_count = 'rcount' in kwargs or 'ccount' in kwargs - - if has_stride and has_count: - raise ValueError("Cannot specify both stride and count arguments") - - rstride = kwargs.pop('rstride', 10) - cstride = kwargs.pop('cstride', 10) - rcount = kwargs.pop('rcount', 50) - ccount = kwargs.pop('ccount', 50) - - if mpl.rcParams['_internal.classic_mode']: - # Strides have priority over counts in classic mode. - # So, only compute strides from counts - # if counts were explicitly given - compute_strides = has_count - else: - # If the strides are provided then it has priority. - # Otherwise, compute the strides from the counts. - compute_strides = not has_stride - - if compute_strides: - rstride = int(max(np.ceil(rows / rcount), 1)) - cstride = int(max(np.ceil(cols / ccount), 1)) - - fcolors = kwargs.pop('facecolors', None) - - cmap = kwargs.get('cmap', None) - shade = kwargs.pop('shade', cmap is None) - if shade is None: - raise ValueError("shade cannot be None.") - - colset = [] # the sampled facecolor - if (rows - 1) % rstride == 0 and \ - (cols - 1) % cstride == 0 and \ - fcolors is None: - polys = np.stack( - [cbook._array_patch_perimeters(a, rstride, cstride) - for a in (X, Y, Z)], - axis=-1) - else: - # evenly spaced, and including both endpoints - row_inds = list(range(0, rows-1, rstride)) + [rows-1] - col_inds = list(range(0, cols-1, cstride)) + [cols-1] - - polys = [] - for rs, rs_next in zip(row_inds[:-1], row_inds[1:]): - for cs, cs_next in zip(col_inds[:-1], col_inds[1:]): - ps = [ - # +1 ensures we share edges between polygons - cbook._array_perimeter(a[rs:rs_next+1, cs:cs_next+1]) - for a in (X, Y, Z) - ] - # ps = np.stack(ps, axis=-1) - ps = np.array(ps).T - polys.append(ps) - - if fcolors is not None: - colset.append(fcolors[rs][cs]) - - # In cases where there are non-finite values in the data (possibly NaNs from - # masked arrays), artifacts can be introduced. Here check whether such values - # are present and remove them. - if not isinstance(polys, np.ndarray) or not np.isfinite(polys).all(): - new_polys = [] - new_colset = [] - - # Depending on fcolors, colset is either an empty list or has as - # many elements as polys. In the former case new_colset results in - # a list with None entries, that is discarded later. - for p, col in itertools.zip_longest(polys, colset): - new_poly = np.array(p)[np.isfinite(p).all(axis=1)] - if len(new_poly): - new_polys.append(new_poly) - new_colset.append(col) - - # Replace previous polys and, if fcolors is not None, colset - polys = new_polys - if fcolors is not None: - colset = new_colset - - # note that the striding causes some polygons to have more coordinates - # than others - - if fcolors is not None: - polyc = art3d.Poly3DCollection( - polys, edgecolors=colset, facecolors=colset, shade=shade, - lightsource=lightsource, **kwargs) - elif cmap: - polyc = art3d.Poly3DCollection(polys, **kwargs) - # can't always vectorize, because polys might be jagged - if isinstance(polys, np.ndarray): - avg_z = polys[..., 2].mean(axis=-1) - else: - avg_z = np.array([ps[:, 2].mean() for ps in polys]) - polyc.set_array(avg_z) - if vmin is not None or vmax is not None: - polyc.set_clim(vmin, vmax) - if norm is not None: - polyc.set_norm(norm) - else: - color = kwargs.pop('color', None) - if color is None: - color = self._get_lines.get_next_color() - color = np.array(mcolors.to_rgba(color)) - - polyc = art3d.Poly3DCollection( - polys, facecolors=color, shade=shade, - lightsource=lightsource, **kwargs) - - self.add_collection(polyc) - self.auto_scale_xyz(X, Y, Z, had_data) - - return polyc - - def plot_wireframe(self, X, Y, Z, **kwargs): - """ - Plot a 3D wireframe. - - .. note:: - - The *rcount* and *ccount* kwargs, which both default to 50, - determine the maximum number of samples used in each direction. If - the input data is larger, it will be downsampled (by slicing) to - these numbers of points. - - Parameters - ---------- - X, Y, Z : 2D arrays - Data values. - - rcount, ccount : int - Maximum number of samples used in each direction. If the input - data is larger, it will be downsampled (by slicing) to these - numbers of points. Setting a count to zero causes the data to be - not sampled in the corresponding direction, producing a 3D line - plot rather than a wireframe plot. Defaults to 50. - - rstride, cstride : int - Downsampling stride in each direction. These arguments are - mutually exclusive with *rcount* and *ccount*. If only one of - *rstride* or *cstride* is set, the other defaults to 1. Setting a - stride to zero causes the data to be not sampled in the - corresponding direction, producing a 3D line plot rather than a - wireframe plot. - - 'classic' mode uses a default of ``rstride = cstride = 1`` instead - of the new default of ``rcount = ccount = 50``. - - **kwargs - Other keyword arguments are forwarded to `.Line3DCollection`. - """ - - had_data = self.has_data() - if Z.ndim != 2: - raise ValueError("Argument Z must be 2-dimensional.") - # FIXME: Support masked arrays - X, Y, Z = np.broadcast_arrays(X, Y, Z) - rows, cols = Z.shape - - has_stride = 'rstride' in kwargs or 'cstride' in kwargs - has_count = 'rcount' in kwargs or 'ccount' in kwargs - - if has_stride and has_count: - raise ValueError("Cannot specify both stride and count arguments") - - rstride = kwargs.pop('rstride', 1) - cstride = kwargs.pop('cstride', 1) - rcount = kwargs.pop('rcount', 50) - ccount = kwargs.pop('ccount', 50) - - if mpl.rcParams['_internal.classic_mode']: - # Strides have priority over counts in classic mode. - # So, only compute strides from counts - # if counts were explicitly given - if has_count: - rstride = int(max(np.ceil(rows / rcount), 1)) if rcount else 0 - cstride = int(max(np.ceil(cols / ccount), 1)) if ccount else 0 - else: - # If the strides are provided then it has priority. - # Otherwise, compute the strides from the counts. - if not has_stride: - rstride = int(max(np.ceil(rows / rcount), 1)) if rcount else 0 - cstride = int(max(np.ceil(cols / ccount), 1)) if ccount else 0 - - # We want two sets of lines, one running along the "rows" of - # Z and another set of lines running along the "columns" of Z. - # This transpose will make it easy to obtain the columns. - tX, tY, tZ = np.transpose(X), np.transpose(Y), np.transpose(Z) - - if rstride: - rii = list(range(0, rows, rstride)) - # Add the last index only if needed - if rows > 0 and rii[-1] != (rows - 1): - rii += [rows-1] - else: - rii = [] - if cstride: - cii = list(range(0, cols, cstride)) - # Add the last index only if needed - if cols > 0 and cii[-1] != (cols - 1): - cii += [cols-1] - else: - cii = [] - - if rstride == 0 and cstride == 0: - raise ValueError("Either rstride or cstride must be non zero") - - # If the inputs were empty, then just - # reset everything. - if Z.size == 0: - rii = [] - cii = [] - - xlines = [X[i] for i in rii] - ylines = [Y[i] for i in rii] - zlines = [Z[i] for i in rii] - - txlines = [tX[i] for i in cii] - tylines = [tY[i] for i in cii] - tzlines = [tZ[i] for i in cii] - - lines = ([list(zip(xl, yl, zl)) - for xl, yl, zl in zip(xlines, ylines, zlines)] - + [list(zip(xl, yl, zl)) - for xl, yl, zl in zip(txlines, tylines, tzlines)]) - - linec = art3d.Line3DCollection(lines, **kwargs) - self.add_collection(linec) - self.auto_scale_xyz(X, Y, Z, had_data) - - return linec - - def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None, - lightsource=None, **kwargs): - """ - Plot a triangulated surface. - - The (optional) triangulation can be specified in one of two ways; - either:: - - plot_trisurf(triangulation, ...) - - where triangulation is a `~matplotlib.tri.Triangulation` object, or:: - - plot_trisurf(X, Y, ...) - plot_trisurf(X, Y, triangles, ...) - plot_trisurf(X, Y, triangles=triangles, ...) - - in which case a Triangulation object will be created. See - `.Triangulation` for an explanation of these possibilities. - - The remaining arguments are:: - - plot_trisurf(..., Z) - - where *Z* is the array of values to contour, one per point - in the triangulation. - - Parameters - ---------- - X, Y, Z : array-like - Data values as 1D arrays. - color - Color of the surface patches. - cmap - A colormap for the surface patches. - norm : Normalize - An instance of Normalize to map values to colors. - vmin, vmax : float, default: None - Minimum and maximum value to map. - shade : bool, default: True - Whether to shade the facecolors. Shading is always disabled when - *cmap* is specified. - lightsource : `~matplotlib.colors.LightSource` - The lightsource to use when *shade* is True. - **kwargs - All other keyword arguments are passed on to - :class:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection` - - Examples - -------- - .. plot:: gallery/mplot3d/trisurf3d.py - .. plot:: gallery/mplot3d/trisurf3d_2.py - """ - - had_data = self.has_data() - - # TODO: Support custom face colours - if color is None: - color = self._get_lines.get_next_color() - color = np.array(mcolors.to_rgba(color)) - - cmap = kwargs.get('cmap', None) - shade = kwargs.pop('shade', cmap is None) - - tri, args, kwargs = \ - Triangulation.get_from_args_and_kwargs(*args, **kwargs) - try: - z = kwargs.pop('Z') - except KeyError: - # We do this so Z doesn't get passed as an arg to PolyCollection - z, *args = args - z = np.asarray(z) - - triangles = tri.get_masked_triangles() - xt = tri.x[triangles] - yt = tri.y[triangles] - zt = z[triangles] - verts = np.stack((xt, yt, zt), axis=-1) - - if cmap: - polyc = art3d.Poly3DCollection(verts, *args, **kwargs) - # average over the three points of each triangle - avg_z = verts[:, :, 2].mean(axis=1) - polyc.set_array(avg_z) - if vmin is not None or vmax is not None: - polyc.set_clim(vmin, vmax) - if norm is not None: - polyc.set_norm(norm) - else: - polyc = art3d.Poly3DCollection( - verts, *args, shade=shade, lightsource=lightsource, - facecolors=color, **kwargs) - - self.add_collection(polyc) - self.auto_scale_xyz(tri.x, tri.y, z, had_data) - - return polyc - - def _3d_extend_contour(self, cset, stride=5): - """ - Extend a contour in 3D by creating - """ - - dz = (cset.levels[1] - cset.levels[0]) / 2 - polyverts = [] - colors = [] - for idx, level in enumerate(cset.levels): - path = cset.get_paths()[idx] - subpaths = [*path._iter_connected_components()] - color = cset.get_edgecolor()[idx] - top = art3d._paths_to_3d_segments(subpaths, level - dz) - bot = art3d._paths_to_3d_segments(subpaths, level + dz) - if not len(top[0]): - continue - nsteps = max(round(len(top[0]) / stride), 2) - stepsize = (len(top[0]) - 1) / (nsteps - 1) - polyverts.extend([ - (top[0][round(i * stepsize)], top[0][round((i + 1) * stepsize)], - bot[0][round((i + 1) * stepsize)], bot[0][round(i * stepsize)]) - for i in range(round(nsteps) - 1)]) - colors.extend([color] * (round(nsteps) - 1)) - self.add_collection3d(art3d.Poly3DCollection( - np.array(polyverts), # All polygons have 4 vertices, so vectorize. - facecolors=colors, edgecolors=colors, shade=True)) - cset.remove() - - def add_contour_set( - self, cset, extend3d=False, stride=5, zdir='z', offset=None): - zdir = '-' + zdir - if extend3d: - self._3d_extend_contour(cset, stride) - else: - art3d.collection_2d_to_3d( - cset, zs=offset if offset is not None else cset.levels, zdir=zdir) - - def add_contourf_set(self, cset, zdir='z', offset=None): - self._add_contourf_set(cset, zdir=zdir, offset=offset) - - def _add_contourf_set(self, cset, zdir='z', offset=None): - """ - Returns - ------- - levels : `numpy.ndarray` - Levels at which the filled contours are added. - """ - zdir = '-' + zdir - - midpoints = cset.levels[:-1] + np.diff(cset.levels) / 2 - # Linearly interpolate to get levels for any extensions - if cset._extend_min: - min_level = cset.levels[0] - np.diff(cset.levels[:2]) / 2 - midpoints = np.insert(midpoints, 0, min_level) - if cset._extend_max: - max_level = cset.levels[-1] + np.diff(cset.levels[-2:]) / 2 - midpoints = np.append(midpoints, max_level) - - art3d.collection_2d_to_3d( - cset, zs=offset if offset is not None else midpoints, zdir=zdir) - return midpoints - - @_preprocess_data() - def contour(self, X, Y, Z, *args, - extend3d=False, stride=5, zdir='z', offset=None, **kwargs): - """ - Create a 3D contour plot. - - Parameters - ---------- - X, Y, Z : array-like, - Input data. See `.Axes.contour` for supported data shapes. - extend3d : bool, default: False - Whether to extend contour in 3D. - stride : int - Step size for extending contour. - zdir : {'x', 'y', 'z'}, default: 'z' - The direction to use. - offset : float, optional - If specified, plot a projection of the contour lines at this - position in a plane normal to *zdir*. - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - - *args, **kwargs - Other arguments are forwarded to `matplotlib.axes.Axes.contour`. - - Returns - ------- - matplotlib.contour.QuadContourSet - """ - had_data = self.has_data() - - jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir) - cset = super().contour(jX, jY, jZ, *args, **kwargs) - self.add_contour_set(cset, extend3d, stride, zdir, offset) - - self.auto_scale_xyz(X, Y, Z, had_data) - return cset - - contour3D = contour - - @_preprocess_data() - def tricontour(self, *args, - extend3d=False, stride=5, zdir='z', offset=None, **kwargs): - """ - Create a 3D contour plot. - - .. note:: - This method currently produces incorrect output due to a - longstanding bug in 3D PolyCollection rendering. - - Parameters - ---------- - X, Y, Z : array-like - Input data. See `.Axes.tricontour` for supported data shapes. - extend3d : bool, default: False - Whether to extend contour in 3D. - stride : int - Step size for extending contour. - zdir : {'x', 'y', 'z'}, default: 'z' - The direction to use. - offset : float, optional - If specified, plot a projection of the contour lines at this - position in a plane normal to *zdir*. - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - *args, **kwargs - Other arguments are forwarded to `matplotlib.axes.Axes.tricontour`. - - Returns - ------- - matplotlib.tri._tricontour.TriContourSet - """ - had_data = self.has_data() - - tri, args, kwargs = Triangulation.get_from_args_and_kwargs( - *args, **kwargs) - X = tri.x - Y = tri.y - if 'Z' in kwargs: - Z = kwargs.pop('Z') - else: - # We do this so Z doesn't get passed as an arg to Axes.tricontour - Z, *args = args - - jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir) - tri = Triangulation(jX, jY, tri.triangles, tri.mask) - - cset = super().tricontour(tri, jZ, *args, **kwargs) - self.add_contour_set(cset, extend3d, stride, zdir, offset) - - self.auto_scale_xyz(X, Y, Z, had_data) - return cset - - def _auto_scale_contourf(self, X, Y, Z, zdir, levels, had_data): - # Autoscale in the zdir based on the levels added, which are - # different from data range if any contour extensions are present - dim_vals = {'x': X, 'y': Y, 'z': Z, zdir: levels} - # Input data and levels have different sizes, but auto_scale_xyz - # expected same-size input, so manually take min/max limits - limits = [(np.nanmin(dim_vals[dim]), np.nanmax(dim_vals[dim])) - for dim in ['x', 'y', 'z']] - self.auto_scale_xyz(*limits, had_data) - - @_preprocess_data() - def contourf(self, X, Y, Z, *args, zdir='z', offset=None, **kwargs): - """ - Create a 3D filled contour plot. - - Parameters - ---------- - X, Y, Z : array-like - Input data. See `.Axes.contourf` for supported data shapes. - zdir : {'x', 'y', 'z'}, default: 'z' - The direction to use. - offset : float, optional - If specified, plot a projection of the contour lines at this - position in a plane normal to *zdir*. - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - *args, **kwargs - Other arguments are forwarded to `matplotlib.axes.Axes.contourf`. - - Returns - ------- - matplotlib.contour.QuadContourSet - """ - had_data = self.has_data() - - jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir) - cset = super().contourf(jX, jY, jZ, *args, **kwargs) - levels = self._add_contourf_set(cset, zdir, offset) - - self._auto_scale_contourf(X, Y, Z, zdir, levels, had_data) - return cset - - contourf3D = contourf - - @_preprocess_data() - def tricontourf(self, *args, zdir='z', offset=None, **kwargs): - """ - Create a 3D filled contour plot. - - .. note:: - This method currently produces incorrect output due to a - longstanding bug in 3D PolyCollection rendering. - - Parameters - ---------- - X, Y, Z : array-like - Input data. See `.Axes.tricontourf` for supported data shapes. - zdir : {'x', 'y', 'z'}, default: 'z' - The direction to use. - offset : float, optional - If specified, plot a projection of the contour lines at this - position in a plane normal to zdir. - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - *args, **kwargs - Other arguments are forwarded to - `matplotlib.axes.Axes.tricontourf`. - - Returns - ------- - matplotlib.tri._tricontour.TriContourSet - """ - had_data = self.has_data() - - tri, args, kwargs = Triangulation.get_from_args_and_kwargs( - *args, **kwargs) - X = tri.x - Y = tri.y - if 'Z' in kwargs: - Z = kwargs.pop('Z') - else: - # We do this so Z doesn't get passed as an arg to Axes.tricontourf - Z, *args = args - - jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir) - tri = Triangulation(jX, jY, tri.triangles, tri.mask) - - cset = super().tricontourf(tri, jZ, *args, **kwargs) - levels = self._add_contourf_set(cset, zdir, offset) - - self._auto_scale_contourf(X, Y, Z, zdir, levels, had_data) - return cset - - def add_collection3d(self, col, zs=0, zdir='z'): - """ - Add a 3D collection object to the plot. - - 2D collection types are converted to a 3D version by - modifying the object and adding z coordinate information. - - Supported are: - - - PolyCollection - - LineCollection - - PatchCollection - """ - zvals = np.atleast_1d(zs) - zsortval = (np.min(zvals) if zvals.size - else 0) # FIXME: arbitrary default - - # FIXME: use issubclass() (although, then a 3D collection - # object would also pass.) Maybe have a collection3d - # abstract class to test for and exclude? - if type(col) is mcoll.PolyCollection: - art3d.poly_collection_2d_to_3d(col, zs=zs, zdir=zdir) - col.set_sort_zpos(zsortval) - elif type(col) is mcoll.LineCollection: - art3d.line_collection_2d_to_3d(col, zs=zs, zdir=zdir) - col.set_sort_zpos(zsortval) - elif type(col) is mcoll.PatchCollection: - art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir) - col.set_sort_zpos(zsortval) - - collection = super().add_collection(col) - return collection - - @_preprocess_data(replace_names=["xs", "ys", "zs", "s", - "edgecolors", "c", "facecolor", - "facecolors", "color"]) - def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, - *args, **kwargs): - """ - Create a scatter plot. - - Parameters - ---------- - xs, ys : array-like - The data positions. - zs : float or array-like, default: 0 - The z-positions. Either an array of the same length as *xs* and - *ys* or a single value to place all points in the same plane. - zdir : {'x', 'y', 'z', '-x', '-y', '-z'}, default: 'z' - The axis direction for the *zs*. This is useful when plotting 2D - data on a 3D Axes. The data must be passed as *xs*, *ys*. Setting - *zdir* to 'y' then plots the data to the x-z-plane. - - See also :doc:`/gallery/mplot3d/2dcollections3d`. - - s : float or array-like, default: 20 - The marker size in points**2. Either an array of the same length - as *xs* and *ys* or a single value to make all markers the same - size. - c : color, sequence, or sequence of colors, optional - The marker color. Possible values: - - - A single color format string. - - A sequence of colors of length n. - - A sequence of n numbers to be mapped to colors using *cmap* and - *norm*. - - A 2D array in which the rows are RGB or RGBA. - - For more details see the *c* argument of `~.axes.Axes.scatter`. - depthshade : bool, default: True - Whether to shade the scatter markers to give the appearance of - depth. Each call to ``scatter()`` will perform its depthshading - independently. - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - **kwargs - All other keyword arguments are passed on to `~.axes.Axes.scatter`. - - Returns - ------- - paths : `~matplotlib.collections.PathCollection` - """ - - had_data = self.has_data() - zs_orig = zs - - xs, ys, zs = np.broadcast_arrays( - *[np.ravel(np.ma.filled(t, np.nan)) for t in [xs, ys, zs]]) - s = np.ma.ravel(s) # This doesn't have to match x, y in size. - - xs, ys, zs, s, c, color = cbook.delete_masked_points( - xs, ys, zs, s, c, kwargs.get('color', None) - ) - if kwargs.get("color") is not None: - kwargs['color'] = color - - # For xs and ys, 2D scatter() will do the copying. - if np.may_share_memory(zs_orig, zs): # Avoid unnecessary copies. - zs = zs.copy() - - patches = super().scatter(xs, ys, s=s, c=c, *args, **kwargs) - art3d.patch_collection_2d_to_3d(patches, zs=zs, zdir=zdir, - depthshade=depthshade) - - if self._zmargin < 0.05 and xs.size > 0: - self.set_zmargin(0.05) - - self.auto_scale_xyz(xs, ys, zs, had_data) - - return patches - - scatter3D = scatter - - @_preprocess_data() - def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): - """ - Add 2D bar(s). - - Parameters - ---------- - left : 1D array-like - The x coordinates of the left sides of the bars. - height : 1D array-like - The height of the bars. - zs : float or 1D array-like - Z coordinate of bars; if a single value is specified, it will be - used for all bars. - zdir : {'x', 'y', 'z'}, default: 'z' - When plotting 2D data, the direction to use as z ('x', 'y' or 'z'). - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - **kwargs - Other keyword arguments are forwarded to - `matplotlib.axes.Axes.bar`. - - Returns - ------- - mpl_toolkits.mplot3d.art3d.Patch3DCollection - """ - had_data = self.has_data() - - patches = super().bar(left, height, *args, **kwargs) - - zs = np.broadcast_to(zs, len(left)) - - verts = [] - verts_zs = [] - for p, z in zip(patches, zs): - vs = art3d._get_patch_verts(p) - verts += vs.tolist() - verts_zs += [z] * len(vs) - art3d.patch_2d_to_3d(p, z, zdir) - if 'alpha' in kwargs: - p.set_alpha(kwargs['alpha']) - - if len(verts) > 0: - # the following has to be skipped if verts is empty - # NOTE: Bugs could still occur if len(verts) > 0, - # but the "2nd dimension" is empty. - xs, ys = zip(*verts) - else: - xs, ys = [], [] - - xs, ys, verts_zs = art3d.juggle_axes(xs, ys, verts_zs, zdir) - self.auto_scale_xyz(xs, ys, verts_zs, had_data) - - return patches - - @_preprocess_data() - def bar3d(self, x, y, z, dx, dy, dz, color=None, - zsort='average', shade=True, lightsource=None, *args, **kwargs): - """ - Generate a 3D barplot. - - This method creates three-dimensional barplot where the width, - depth, height, and color of the bars can all be uniquely set. - - Parameters - ---------- - x, y, z : array-like - The coordinates of the anchor point of the bars. - - dx, dy, dz : float or array-like - The width, depth, and height of the bars, respectively. - - color : sequence of colors, optional - The color of the bars can be specified globally or - individually. This parameter can be: - - - A single color, to color all bars the same color. - - An array of colors of length N bars, to color each bar - independently. - - An array of colors of length 6, to color the faces of the - bars similarly. - - An array of colors of length 6 * N bars, to color each face - independently. - - When coloring the faces of the boxes specifically, this is - the order of the coloring: - - 1. -Z (bottom of box) - 2. +Z (top of box) - 3. -Y - 4. +Y - 5. -X - 6. +X - - zsort : str, optional - The z-axis sorting scheme passed onto `~.art3d.Poly3DCollection` - - shade : bool, default: True - When true, this shades the dark sides of the bars (relative - to the plot's source of light). - - lightsource : `~matplotlib.colors.LightSource` - The lightsource to use when *shade* is True. - - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - - **kwargs - Any additional keyword arguments are passed onto - `~.art3d.Poly3DCollection`. - - Returns - ------- - collection : `~.art3d.Poly3DCollection` - A collection of three-dimensional polygons representing the bars. - """ - - had_data = self.has_data() - - x, y, z, dx, dy, dz = np.broadcast_arrays( - np.atleast_1d(x), y, z, dx, dy, dz) - minx = np.min(x) - maxx = np.max(x + dx) - miny = np.min(y) - maxy = np.max(y + dy) - minz = np.min(z) - maxz = np.max(z + dz) - - # shape (6, 4, 3) - # All faces are oriented facing outwards - when viewed from the - # outside, their vertices are in a counterclockwise ordering. - cuboid = np.array([ - # -z - ( - (0, 0, 0), - (0, 1, 0), - (1, 1, 0), - (1, 0, 0), - ), - # +z - ( - (0, 0, 1), - (1, 0, 1), - (1, 1, 1), - (0, 1, 1), - ), - # -y - ( - (0, 0, 0), - (1, 0, 0), - (1, 0, 1), - (0, 0, 1), - ), - # +y - ( - (0, 1, 0), - (0, 1, 1), - (1, 1, 1), - (1, 1, 0), - ), - # -x - ( - (0, 0, 0), - (0, 0, 1), - (0, 1, 1), - (0, 1, 0), - ), - # +x - ( - (1, 0, 0), - (1, 1, 0), - (1, 1, 1), - (1, 0, 1), - ), - ]) - - # indexed by [bar, face, vertex, coord] - polys = np.empty(x.shape + cuboid.shape) - - # handle each coordinate separately - for i, p, dp in [(0, x, dx), (1, y, dy), (2, z, dz)]: - p = p[..., np.newaxis, np.newaxis] - dp = dp[..., np.newaxis, np.newaxis] - polys[..., i] = p + dp * cuboid[..., i] - - # collapse the first two axes - polys = polys.reshape((-1,) + polys.shape[2:]) - - facecolors = [] - if color is None: - color = [self._get_patches_for_fill.get_next_color()] - - color = list(mcolors.to_rgba_array(color)) - - if len(color) == len(x): - # bar colors specified, need to expand to number of faces - for c in color: - facecolors.extend([c] * 6) - else: - # a single color specified, or face colors specified explicitly - facecolors = color - if len(facecolors) < len(x): - facecolors *= (6 * len(x)) - - col = art3d.Poly3DCollection(polys, - zsort=zsort, - facecolors=facecolors, - shade=shade, - lightsource=lightsource, - *args, **kwargs) - self.add_collection(col) - - self.auto_scale_xyz((minx, maxx), (miny, maxy), (minz, maxz), had_data) - - return col - - def set_title(self, label, fontdict=None, loc='center', **kwargs): - # docstring inherited - ret = super().set_title(label, fontdict=fontdict, loc=loc, **kwargs) - (x, y) = self.title.get_position() - self.title.set_y(0.92 * y) - return ret - - @_preprocess_data() - def quiver(self, X, Y, Z, U, V, W, *, - length=1, arrow_length_ratio=.3, pivot='tail', normalize=False, - **kwargs): - """ - Plot a 3D field of arrows. - - The arguments can be array-like or scalars, so long as they can be - broadcast together. The arguments can also be masked arrays. If an - element in any of argument is masked, then that corresponding quiver - element will not be plotted. - - Parameters - ---------- - X, Y, Z : array-like - The x, y and z coordinates of the arrow locations (default is - tail of arrow; see *pivot* kwarg). - - U, V, W : array-like - The x, y and z components of the arrow vectors. - - length : float, default: 1 - The length of each quiver. - - arrow_length_ratio : float, default: 0.3 - The ratio of the arrow head with respect to the quiver. - - pivot : {'tail', 'middle', 'tip'}, default: 'tail' - The part of the arrow that is at the grid point; the arrow - rotates about this point, hence the name *pivot*. - - normalize : bool, default: False - Whether all arrows are normalized to have the same length, or keep - the lengths defined by *u*, *v*, and *w*. - - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - - **kwargs - Any additional keyword arguments are delegated to - :class:`.Line3DCollection` - """ - - def calc_arrows(UVW): - # get unit direction vector perpendicular to (u, v, w) - x = UVW[:, 0] - y = UVW[:, 1] - norm = np.linalg.norm(UVW[:, :2], axis=1) - x_p = np.divide(y, norm, where=norm != 0, out=np.zeros_like(x)) - y_p = np.divide(-x, norm, where=norm != 0, out=np.ones_like(x)) - # compute the two arrowhead direction unit vectors - rangle = math.radians(15) - c = math.cos(rangle) - s = math.sin(rangle) - # construct the rotation matrices of shape (3, 3, n) - r13 = y_p * s - r32 = x_p * s - r12 = x_p * y_p * (1 - c) - Rpos = np.array( - [[c + (x_p ** 2) * (1 - c), r12, r13], - [r12, c + (y_p ** 2) * (1 - c), -r32], - [-r13, r32, np.full_like(x_p, c)]]) - # opposite rotation negates all the sin terms - Rneg = Rpos.copy() - Rneg[[0, 1, 2, 2], [2, 2, 0, 1]] *= -1 - # Batch n (3, 3) x (3) matrix multiplications ((3, 3, n) x (n, 3)). - Rpos_vecs = np.einsum("ij...,...j->...i", Rpos, UVW) - Rneg_vecs = np.einsum("ij...,...j->...i", Rneg, UVW) - # Stack into (n, 2, 3) result. - return np.stack([Rpos_vecs, Rneg_vecs], axis=1) - - had_data = self.has_data() - - input_args = [X, Y, Z, U, V, W] - - # extract the masks, if any - masks = [k.mask for k in input_args - if isinstance(k, np.ma.MaskedArray)] - # broadcast to match the shape - bcast = np.broadcast_arrays(*input_args, *masks) - input_args = bcast[:6] - masks = bcast[6:] - if masks: - # combine the masks into one - mask = functools.reduce(np.logical_or, masks) - # put mask on and compress - input_args = [np.ma.array(k, mask=mask).compressed() - for k in input_args] - else: - input_args = [np.ravel(k) for k in input_args] - - if any(len(v) == 0 for v in input_args): - # No quivers, so just make an empty collection and return early - linec = art3d.Line3DCollection([], **kwargs) - self.add_collection(linec) - return linec - - shaft_dt = np.array([0., length], dtype=float) - arrow_dt = shaft_dt * arrow_length_ratio - - _api.check_in_list(['tail', 'middle', 'tip'], pivot=pivot) - if pivot == 'tail': - shaft_dt -= length - elif pivot == 'middle': - shaft_dt -= length / 2 - - XYZ = np.column_stack(input_args[:3]) - UVW = np.column_stack(input_args[3:]).astype(float) - - # Normalize rows of UVW - norm = np.linalg.norm(UVW, axis=1) - - # If any row of UVW is all zeros, don't make a quiver for it - mask = norm > 0 - XYZ = XYZ[mask] - if normalize: - UVW = UVW[mask] / norm[mask].reshape((-1, 1)) - else: - UVW = UVW[mask] - - if len(XYZ) > 0: - # compute the shaft lines all at once with an outer product - shafts = (XYZ - np.multiply.outer(shaft_dt, UVW)).swapaxes(0, 1) - # compute head direction vectors, n heads x 2 sides x 3 dimensions - head_dirs = calc_arrows(UVW) - # compute all head lines at once, starting from the shaft ends - heads = shafts[:, :1] - np.multiply.outer(arrow_dt, head_dirs) - # stack left and right head lines together - heads = heads.reshape((len(arrow_dt), -1, 3)) - # transpose to get a list of lines - heads = heads.swapaxes(0, 1) - - lines = [*shafts, *heads] - else: - lines = [] - - linec = art3d.Line3DCollection(lines, **kwargs) - self.add_collection(linec) - - self.auto_scale_xyz(XYZ[:, 0], XYZ[:, 1], XYZ[:, 2], had_data) - - return linec - - quiver3D = quiver - - def voxels(self, *args, facecolors=None, edgecolors=None, shade=True, - lightsource=None, **kwargs): - """ - ax.voxels([x, y, z,] /, filled, facecolors=None, edgecolors=None, \ -**kwargs) - - Plot a set of filled voxels - - All voxels are plotted as 1x1x1 cubes on the axis, with - ``filled[0, 0, 0]`` placed with its lower corner at the origin. - Occluded faces are not plotted. - - Parameters - ---------- - filled : 3D np.array of bool - A 3D array of values, with truthy values indicating which voxels - to fill - - x, y, z : 3D np.array, optional - The coordinates of the corners of the voxels. This should broadcast - to a shape one larger in every dimension than the shape of - *filled*. These can be used to plot non-cubic voxels. - - If not specified, defaults to increasing integers along each axis, - like those returned by :func:`~numpy.indices`. - As indicated by the ``/`` in the function signature, these - arguments can only be passed positionally. - - facecolors, edgecolors : array-like, optional - The color to draw the faces and edges of the voxels. Can only be - passed as keyword arguments. - These parameters can be: - - - A single color value, to color all voxels the same color. This - can be either a string, or a 1D RGB/RGBA array - - ``None``, the default, to use a single color for the faces, and - the style default for the edges. - - A 3D `~numpy.ndarray` of color names, with each item the color - for the corresponding voxel. The size must match the voxels. - - A 4D `~numpy.ndarray` of RGB/RGBA data, with the components - along the last axis. - - shade : bool, default: True - Whether to shade the facecolors. - - lightsource : `~matplotlib.colors.LightSource` - The lightsource to use when *shade* is True. - - **kwargs - Additional keyword arguments to pass onto - `~mpl_toolkits.mplot3d.art3d.Poly3DCollection`. - - Returns - ------- - faces : dict - A dictionary indexed by coordinate, where ``faces[i, j, k]`` is a - `.Poly3DCollection` of the faces drawn for the voxel - ``filled[i, j, k]``. If no faces were drawn for a given voxel, - either because it was not asked to be drawn, or it is fully - occluded, then ``(i, j, k) not in faces``. - - Examples - -------- - .. plot:: gallery/mplot3d/voxels.py - .. plot:: gallery/mplot3d/voxels_rgb.py - .. plot:: gallery/mplot3d/voxels_torus.py - .. plot:: gallery/mplot3d/voxels_numpy_logo.py - """ - - # work out which signature we should be using, and use it to parse - # the arguments. Name must be voxels for the correct error message - if len(args) >= 3: - # underscores indicate position only - def voxels(__x, __y, __z, filled, **kwargs): - return (__x, __y, __z), filled, kwargs - else: - def voxels(filled, **kwargs): - return None, filled, kwargs - - xyz, filled, kwargs = voxels(*args, **kwargs) - - # check dimensions - if filled.ndim != 3: - raise ValueError("Argument filled must be 3-dimensional") - size = np.array(filled.shape, dtype=np.intp) - - # check xyz coordinates, which are one larger than the filled shape - coord_shape = tuple(size + 1) - if xyz is None: - x, y, z = np.indices(coord_shape) - else: - x, y, z = (np.broadcast_to(c, coord_shape) for c in xyz) - - def _broadcast_color_arg(color, name): - if np.ndim(color) in (0, 1): - # single color, like "red" or [1, 0, 0] - return np.broadcast_to(color, filled.shape + np.shape(color)) - elif np.ndim(color) in (3, 4): - # 3D array of strings, or 4D array with last axis rgb - if np.shape(color)[:3] != filled.shape: - raise ValueError( - f"When multidimensional, {name} must match the shape " - "of filled") - return color - else: - raise ValueError(f"Invalid {name} argument") - - # broadcast and default on facecolors - if facecolors is None: - facecolors = self._get_patches_for_fill.get_next_color() - facecolors = _broadcast_color_arg(facecolors, 'facecolors') - - # broadcast but no default on edgecolors - edgecolors = _broadcast_color_arg(edgecolors, 'edgecolors') - - # scale to the full array, even if the data is only in the center - self.auto_scale_xyz(x, y, z) - - # points lying on corners of a square - square = np.array([ - [0, 0, 0], - [1, 0, 0], - [1, 1, 0], - [0, 1, 0], - ], dtype=np.intp) - - voxel_faces = defaultdict(list) - - def permutation_matrices(n): - """Generate cyclic permutation matrices.""" - mat = np.eye(n, dtype=np.intp) - for i in range(n): - yield mat - mat = np.roll(mat, 1, axis=0) - - # iterate over each of the YZ, ZX, and XY orientations, finding faces - # to render - for permute in permutation_matrices(3): - # find the set of ranges to iterate over - pc, qc, rc = permute.T.dot(size) - pinds = np.arange(pc) - qinds = np.arange(qc) - rinds = np.arange(rc) - - square_rot_pos = square.dot(permute.T) - square_rot_neg = square_rot_pos[::-1] - - # iterate within the current plane - for p in pinds: - for q in qinds: - # iterate perpendicularly to the current plane, handling - # boundaries. We only draw faces between a voxel and an - # empty space, to avoid drawing internal faces. - - # draw lower faces - p0 = permute.dot([p, q, 0]) - i0 = tuple(p0) - if filled[i0]: - voxel_faces[i0].append(p0 + square_rot_neg) - - # draw middle faces - for r1, r2 in zip(rinds[:-1], rinds[1:]): - p1 = permute.dot([p, q, r1]) - p2 = permute.dot([p, q, r2]) - - i1 = tuple(p1) - i2 = tuple(p2) - - if filled[i1] and not filled[i2]: - voxel_faces[i1].append(p2 + square_rot_pos) - elif not filled[i1] and filled[i2]: - voxel_faces[i2].append(p2 + square_rot_neg) - - # draw upper faces - pk = permute.dot([p, q, rc-1]) - pk2 = permute.dot([p, q, rc]) - ik = tuple(pk) - if filled[ik]: - voxel_faces[ik].append(pk2 + square_rot_pos) - - # iterate over the faces, and generate a Poly3DCollection for each - # voxel - polygons = {} - for coord, faces_inds in voxel_faces.items(): - # convert indices into 3D positions - if xyz is None: - faces = faces_inds - else: - faces = [] - for face_inds in faces_inds: - ind = face_inds[:, 0], face_inds[:, 1], face_inds[:, 2] - face = np.empty(face_inds.shape) - face[:, 0] = x[ind] - face[:, 1] = y[ind] - face[:, 2] = z[ind] - faces.append(face) - - # shade the faces - facecolor = facecolors[coord] - edgecolor = edgecolors[coord] - - poly = art3d.Poly3DCollection( - faces, facecolors=facecolor, edgecolors=edgecolor, - shade=shade, lightsource=lightsource, **kwargs) - self.add_collection3d(poly) - polygons[coord] = poly - - return polygons - - @_preprocess_data(replace_names=["x", "y", "z", "xerr", "yerr", "zerr"]) - def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', - barsabove=False, errorevery=1, ecolor=None, elinewidth=None, - capsize=None, capthick=None, xlolims=False, xuplims=False, - ylolims=False, yuplims=False, zlolims=False, zuplims=False, - **kwargs): - """ - Plot lines and/or markers with errorbars around them. - - *x*/*y*/*z* define the data locations, and *xerr*/*yerr*/*zerr* define - the errorbar sizes. By default, this draws the data markers/lines as - well the errorbars. Use fmt='none' to draw errorbars only. - - Parameters - ---------- - x, y, z : float or array-like - The data positions. - - xerr, yerr, zerr : float or array-like, shape (N,) or (2, N), optional - The errorbar sizes: - - - scalar: Symmetric +/- values for all data points. - - shape(N,): Symmetric +/-values for each data point. - - shape(2, N): Separate - and + values for each bar. First row - contains the lower errors, the second row contains the upper - errors. - - *None*: No errorbar. - - Note that all error arrays should have *positive* values. - - fmt : str, default: '' - The format for the data points / data lines. See `.plot` for - details. - - Use 'none' (case-insensitive) to plot errorbars without any data - markers. - - ecolor : color, default: None - The color of the errorbar lines. If None, use the color of the - line connecting the markers. - - elinewidth : float, default: None - The linewidth of the errorbar lines. If None, the linewidth of - the current style is used. - - capsize : float, default: :rc:`errorbar.capsize` - The length of the error bar caps in points. - - capthick : float, default: None - An alias to the keyword argument *markeredgewidth* (a.k.a. *mew*). - This setting is a more sensible name for the property that - controls the thickness of the error bar cap in points. For - backwards compatibility, if *mew* or *markeredgewidth* are given, - then they will over-ride *capthick*. This may change in future - releases. - - barsabove : bool, default: False - If True, will plot the errorbars above the plot - symbols. Default is below. - - xlolims, ylolims, zlolims : bool, default: False - These arguments can be used to indicate that a value gives only - lower limits. In that case a caret symbol is used to indicate - this. *lims*-arguments may be scalars, or array-likes of the same - length as the errors. To use limits with inverted axes, - `~.Axes.set_xlim` or `~.Axes.set_ylim` must be called before - `errorbar`. Note the tricky parameter names: setting e.g. - *ylolims* to True means that the y-value is a *lower* limit of the - True value, so, only an *upward*-pointing arrow will be drawn! - - xuplims, yuplims, zuplims : bool, default: False - Same as above, but for controlling the upper limits. - - errorevery : int or (int, int), default: 1 - draws error bars on a subset of the data. *errorevery* =N draws - error bars on the points (x[::N], y[::N], z[::N]). - *errorevery* =(start, N) draws error bars on the points - (x[start::N], y[start::N], z[start::N]). e.g. *errorevery* =(6, 3) - adds error bars to the data at (x[6], x[9], x[12], x[15], ...). - Used to avoid overlapping error bars when two series share x-axis - values. - - Returns - ------- - errlines : list - List of `~mpl_toolkits.mplot3d.art3d.Line3DCollection` instances - each containing an errorbar line. - caplines : list - List of `~mpl_toolkits.mplot3d.art3d.Line3D` instances each - containing a capline object. - limmarks : list - List of `~mpl_toolkits.mplot3d.art3d.Line3D` instances each - containing a marker with an upper or lower limit. - - Other Parameters - ---------------- - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - - **kwargs - All other keyword arguments for styling errorbar lines are passed - `~mpl_toolkits.mplot3d.art3d.Line3DCollection`. - - Examples - -------- - .. plot:: gallery/mplot3d/errorbar3d.py - """ - had_data = self.has_data() - - kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D) - # Drop anything that comes in as None to use the default instead. - kwargs = {k: v for k, v in kwargs.items() if v is not None} - kwargs.setdefault('zorder', 2) - - self._process_unit_info([("x", x), ("y", y), ("z", z)], kwargs, - convert=False) - - # make sure all the args are iterable; use lists not arrays to - # preserve units - x = x if np.iterable(x) else [x] - y = y if np.iterable(y) else [y] - z = z if np.iterable(z) else [z] - - if not len(x) == len(y) == len(z): - raise ValueError("'x', 'y', and 'z' must have the same size") - - everymask = self._errorevery_to_mask(x, errorevery) - - label = kwargs.pop("label", None) - kwargs['label'] = '_nolegend_' - - # Create the main line and determine overall kwargs for child artists. - # We avoid calling self.plot() directly, or self._get_lines(), because - # that would call self._process_unit_info again, and do other indirect - # data processing. - (data_line, base_style), = self._get_lines._plot_args( - self, (x, y) if fmt == '' else (x, y, fmt), kwargs, return_kwargs=True) - art3d.line_2d_to_3d(data_line, zs=z) - - # Do this after creating `data_line` to avoid modifying `base_style`. - if barsabove: - data_line.set_zorder(kwargs['zorder'] - .1) - else: - data_line.set_zorder(kwargs['zorder'] + .1) - - # Add line to plot, or throw it away and use it to determine kwargs. - if fmt.lower() != 'none': - self.add_line(data_line) - else: - data_line = None - # Remove alpha=0 color that _process_plot_format returns. - base_style.pop('color') - - if 'color' not in base_style: - base_style['color'] = 'C0' - if ecolor is None: - ecolor = base_style['color'] - - # Eject any line-specific information from format string, as it's not - # needed for bars or caps. - for key in ['marker', 'markersize', 'markerfacecolor', - 'markeredgewidth', 'markeredgecolor', 'markevery', - 'linestyle', 'fillstyle', 'drawstyle', 'dash_capstyle', - 'dash_joinstyle', 'solid_capstyle', 'solid_joinstyle']: - base_style.pop(key, None) - - # Make the style dict for the line collections (the bars). - eb_lines_style = {**base_style, 'color': ecolor} - - if elinewidth: - eb_lines_style['linewidth'] = elinewidth - elif 'linewidth' in kwargs: - eb_lines_style['linewidth'] = kwargs['linewidth'] - - for key in ('transform', 'alpha', 'zorder', 'rasterized'): - if key in kwargs: - eb_lines_style[key] = kwargs[key] - - # Make the style dict for caps (the "hats"). - eb_cap_style = {**base_style, 'linestyle': 'None'} - if capsize is None: - capsize = mpl.rcParams["errorbar.capsize"] - if capsize > 0: - eb_cap_style['markersize'] = 2. * capsize - if capthick is not None: - eb_cap_style['markeredgewidth'] = capthick - eb_cap_style['color'] = ecolor - - def _apply_mask(arrays, mask): - # Return, for each array in *arrays*, the elements for which *mask* - # is True, without using fancy indexing. - return [[*itertools.compress(array, mask)] for array in arrays] - - def _extract_errs(err, data, lomask, himask): - # For separate +/- error values we need to unpack err - if len(err.shape) == 2: - low_err, high_err = err - else: - low_err, high_err = err, err - - lows = np.where(lomask | ~everymask, data, data - low_err) - highs = np.where(himask | ~everymask, data, data + high_err) - - return lows, highs - - # collect drawn items while looping over the three coordinates - errlines, caplines, limmarks = [], [], [] - - # list of endpoint coordinates, used for auto-scaling - coorderrs = [] - - # define the markers used for errorbar caps and limits below - # the dictionary key is mapped by the `i_xyz` helper dictionary - capmarker = {0: '|', 1: '|', 2: '_'} - i_xyz = {'x': 0, 'y': 1, 'z': 2} - - # Calculate marker size from points to quiver length. Because these are - # not markers, and 3D Axes do not use the normal transform stack, this - # is a bit involved. Since the quiver arrows will change size as the - # scene is rotated, they are given a standard size based on viewing - # them directly in planar form. - quiversize = eb_cap_style.get('markersize', - mpl.rcParams['lines.markersize']) ** 2 - quiversize *= self.figure.dpi / 72 - quiversize = self.transAxes.inverted().transform([ - (0, 0), (quiversize, quiversize)]) - quiversize = np.mean(np.diff(quiversize, axis=0)) - # quiversize is now in Axes coordinates, and to convert back to data - # coordinates, we need to run it through the inverse 3D transform. For - # consistency, this uses a fixed elevation, azimuth, and roll. - with cbook._setattr_cm(self, elev=0, azim=0, roll=0): - invM = np.linalg.inv(self.get_proj()) - # elev=azim=roll=0 produces the Y-Z plane, so quiversize in 2D 'x' is - # 'y' in 3D, hence the 1 index. - quiversize = np.dot(invM, [quiversize, 0, 0, 0])[1] - # Quivers use a fixed 15-degree arrow head, so scale up the length so - # that the size corresponds to the base. In other words, this constant - # corresponds to the equation tan(15) = (base / 2) / (arrow length). - quiversize *= 1.8660254037844388 - eb_quiver_style = {**eb_cap_style, - 'length': quiversize, 'arrow_length_ratio': 1} - eb_quiver_style.pop('markersize', None) - - # loop over x-, y-, and z-direction and draw relevant elements - for zdir, data, err, lolims, uplims in zip( - ['x', 'y', 'z'], [x, y, z], [xerr, yerr, zerr], - [xlolims, ylolims, zlolims], [xuplims, yuplims, zuplims]): - - dir_vector = art3d.get_dir_vector(zdir) - i_zdir = i_xyz[zdir] - - if err is None: - continue - - if not np.iterable(err): - err = [err] * len(data) - - err = np.atleast_1d(err) - - # arrays fine here, they are booleans and hence not units - lolims = np.broadcast_to(lolims, len(data)).astype(bool) - uplims = np.broadcast_to(uplims, len(data)).astype(bool) - - # a nested list structure that expands to (xl,xh),(yl,yh),(zl,zh), - # where x/y/z and l/h correspond to dimensions and low/high - # positions of errorbars in a dimension we're looping over - coorderr = [ - _extract_errs(err * dir_vector[i], coord, lolims, uplims) - for i, coord in enumerate([x, y, z])] - (xl, xh), (yl, yh), (zl, zh) = coorderr - - # draws capmarkers - flat caps orthogonal to the error bars - nolims = ~(lolims | uplims) - if nolims.any() and capsize > 0: - lo_caps_xyz = _apply_mask([xl, yl, zl], nolims & everymask) - hi_caps_xyz = _apply_mask([xh, yh, zh], nolims & everymask) - - # setting '_' for z-caps and '|' for x- and y-caps; - # these markers will rotate as the viewing angle changes - cap_lo = art3d.Line3D(*lo_caps_xyz, ls='', - marker=capmarker[i_zdir], - **eb_cap_style) - cap_hi = art3d.Line3D(*hi_caps_xyz, ls='', - marker=capmarker[i_zdir], - **eb_cap_style) - self.add_line(cap_lo) - self.add_line(cap_hi) - caplines.append(cap_lo) - caplines.append(cap_hi) - - if lolims.any(): - xh0, yh0, zh0 = _apply_mask([xh, yh, zh], lolims & everymask) - self.quiver(xh0, yh0, zh0, *dir_vector, **eb_quiver_style) - if uplims.any(): - xl0, yl0, zl0 = _apply_mask([xl, yl, zl], uplims & everymask) - self.quiver(xl0, yl0, zl0, *-dir_vector, **eb_quiver_style) - - errline = art3d.Line3DCollection(np.array(coorderr).T, - **eb_lines_style) - self.add_collection(errline) - errlines.append(errline) - coorderrs.append(coorderr) - - coorderrs = np.array(coorderrs) - - def _digout_minmax(err_arr, coord_label): - return (np.nanmin(err_arr[:, i_xyz[coord_label], :, :]), - np.nanmax(err_arr[:, i_xyz[coord_label], :, :])) - - minx, maxx = _digout_minmax(coorderrs, 'x') - miny, maxy = _digout_minmax(coorderrs, 'y') - minz, maxz = _digout_minmax(coorderrs, 'z') - self.auto_scale_xyz((minx, maxx), (miny, maxy), (minz, maxz), had_data) - - # Adapting errorbar containers for 3d case, assuming z-axis points "up" - errorbar_container = mcontainer.ErrorbarContainer( - (data_line, tuple(caplines), tuple(errlines)), - has_xerr=(xerr is not None or yerr is not None), - has_yerr=(zerr is not None), - label=label) - self.containers.append(errorbar_container) - - return errlines, caplines, limmarks - - @_api.make_keyword_only("3.8", "call_axes_locator") - def get_tightbbox(self, renderer=None, call_axes_locator=True, - bbox_extra_artists=None, *, for_layout_only=False): - ret = super().get_tightbbox(renderer, - call_axes_locator=call_axes_locator, - bbox_extra_artists=bbox_extra_artists, - for_layout_only=for_layout_only) - batch = [ret] - if self._axis3don: - for axis in self._axis_map.values(): - if axis.get_visible(): - axis_bb = martist._get_tightbbox_for_layout_only( - axis, renderer) - if axis_bb: - batch.append(axis_bb) - return mtransforms.Bbox.union(batch) - - @_preprocess_data() - def stem(self, x, y, z, *, linefmt='C0-', markerfmt='C0o', basefmt='C3-', - bottom=0, label=None, orientation='z'): - """ - Create a 3D stem plot. - - A stem plot draws lines perpendicular to a baseline, and places markers - at the heads. By default, the baseline is defined by *x* and *y*, and - stems are drawn vertically from *bottom* to *z*. - - Parameters - ---------- - x, y, z : array-like - The positions of the heads of the stems. The stems are drawn along - the *orientation*-direction from the baseline at *bottom* (in the - *orientation*-coordinate) to the heads. By default, the *x* and *y* - positions are used for the baseline and *z* for the head position, - but this can be changed by *orientation*. - - linefmt : str, default: 'C0-' - A string defining the properties of the vertical lines. Usually, - this will be a color or a color and a linestyle: - - ========= ============= - Character Line Style - ========= ============= - ``'-'`` solid line - ``'--'`` dashed line - ``'-.'`` dash-dot line - ``':'`` dotted line - ========= ============= - - Note: While it is technically possible to specify valid formats - other than color or color and linestyle (e.g. 'rx' or '-.'), this - is beyond the intention of the method and will most likely not - result in a reasonable plot. - - markerfmt : str, default: 'C0o' - A string defining the properties of the markers at the stem heads. - - basefmt : str, default: 'C3-' - A format string defining the properties of the baseline. - - bottom : float, default: 0 - The position of the baseline, in *orientation*-coordinates. - - label : str, default: None - The label to use for the stems in legends. - - orientation : {'x', 'y', 'z'}, default: 'z' - The direction along which stems are drawn. - - data : indexable object, optional - DATA_PARAMETER_PLACEHOLDER - - Returns - ------- - `.StemContainer` - The container may be treated like a tuple - (*markerline*, *stemlines*, *baseline*) - - Examples - -------- - .. plot:: gallery/mplot3d/stem3d_demo.py - """ - - from matplotlib.container import StemContainer - - had_data = self.has_data() - - _api.check_in_list(['x', 'y', 'z'], orientation=orientation) - - xlim = (np.min(x), np.max(x)) - ylim = (np.min(y), np.max(y)) - zlim = (np.min(z), np.max(z)) - - # Determine the appropriate plane for the baseline and the direction of - # stemlines based on the value of orientation. - if orientation == 'x': - basex, basexlim = y, ylim - basey, baseylim = z, zlim - lines = [[(bottom, thisy, thisz), (thisx, thisy, thisz)] - for thisx, thisy, thisz in zip(x, y, z)] - elif orientation == 'y': - basex, basexlim = x, xlim - basey, baseylim = z, zlim - lines = [[(thisx, bottom, thisz), (thisx, thisy, thisz)] - for thisx, thisy, thisz in zip(x, y, z)] - else: - basex, basexlim = x, xlim - basey, baseylim = y, ylim - lines = [[(thisx, thisy, bottom), (thisx, thisy, thisz)] - for thisx, thisy, thisz in zip(x, y, z)] - - # Determine style for stem lines. - linestyle, linemarker, linecolor = _process_plot_format(linefmt) - if linestyle is None: - linestyle = mpl.rcParams['lines.linestyle'] - - # Plot everything in required order. - baseline, = self.plot(basex, basey, basefmt, zs=bottom, - zdir=orientation, label='_nolegend_') - stemlines = art3d.Line3DCollection( - lines, linestyles=linestyle, colors=linecolor, label='_nolegend_') - self.add_collection(stemlines) - markerline, = self.plot(x, y, z, markerfmt, label='_nolegend_') - - stem_container = StemContainer((markerline, stemlines, baseline), - label=label) - self.add_container(stem_container) - - jx, jy, jz = art3d.juggle_axes(basexlim, baseylim, [bottom, bottom], - orientation) - self.auto_scale_xyz([*jx, *xlim], [*jy, *ylim], [*jz, *zlim], had_data) - - return stem_container - - stem3D = stem - - -def get_test_data(delta=0.05): - """Return a tuple X, Y, Z with a test data set.""" - 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 - - X = X * 10 - Y = Y * 10 - Z = Z * 500 - return X, Y, Z diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py b/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py deleted file mode 100644 index 4c5fa8a9c90..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py +++ /dev/null @@ -1,753 +0,0 @@ -# axis3d.py, original mplot3d version by John Porter -# Created: 23 Sep 2005 -# Parts rewritten by Reinier Heeres <reinier@heeres.eu> - -import inspect - -import numpy as np - -import matplotlib as mpl -from matplotlib import ( - _api, artist, lines as mlines, axis as maxis, patches as mpatches, - transforms as mtransforms, colors as mcolors) -from . import art3d, proj3d - - -def _move_from_center(coord, centers, deltas, axmask=(True, True, True)): - """ - For each coordinate where *axmask* is True, move *coord* away from - *centers* by *deltas*. - """ - coord = np.asarray(coord) - return coord + axmask * np.copysign(1, coord - centers) * deltas - - -def _tick_update_position(tick, tickxs, tickys, labelpos): - """Update tick line and label position and style.""" - - tick.label1.set_position(labelpos) - tick.label2.set_position(labelpos) - tick.tick1line.set_visible(True) - tick.tick2line.set_visible(False) - tick.tick1line.set_linestyle('-') - tick.tick1line.set_marker('') - tick.tick1line.set_data(tickxs, tickys) - tick.gridline.set_data([0], [0]) - - -class Axis(maxis.XAxis): - """An Axis class for the 3D plots.""" - # These points from the unit cube make up the x, y and z-planes - _PLANES = ( - (0, 3, 7, 4), (1, 2, 6, 5), # yz planes - (0, 1, 5, 4), (3, 2, 6, 7), # xz planes - (0, 1, 2, 3), (4, 5, 6, 7), # xy planes - ) - - # Some properties for the axes - _AXINFO = { - 'x': {'i': 0, 'tickdir': 1, 'juggled': (1, 0, 2)}, - 'y': {'i': 1, 'tickdir': 0, 'juggled': (0, 1, 2)}, - 'z': {'i': 2, 'tickdir': 0, 'juggled': (0, 2, 1)}, - } - - def _old_init(self, adir, v_intervalx, d_intervalx, axes, *args, - rotate_label=None, **kwargs): - return locals() - - def _new_init(self, axes, *, rotate_label=None, **kwargs): - return locals() - - def __init__(self, *args, **kwargs): - params = _api.select_matching_signature( - [self._old_init, self._new_init], *args, **kwargs) - if "adir" in params: - _api.warn_deprecated( - "3.6", message=f"The signature of 3D Axis constructors has " - f"changed in %(since)s; the new signature is " - f"{inspect.signature(type(self).__init__)}", pending=True) - if params["adir"] != self.axis_name: - raise ValueError(f"Cannot instantiate {type(self).__name__} " - f"with adir={params['adir']!r}") - axes = params["axes"] - rotate_label = params["rotate_label"] - args = params.get("args", ()) - kwargs = params["kwargs"] - - name = self.axis_name - - self._label_position = 'default' - self._tick_position = 'default' - - # This is a temporary member variable. - # Do not depend on this existing in future releases! - self._axinfo = self._AXINFO[name].copy() - # Common parts - self._axinfo.update({ - 'label': {'va': 'center', 'ha': 'center', - 'rotation_mode': 'anchor'}, - 'color': mpl.rcParams[f'axes3d.{name}axis.panecolor'], - 'tick': { - 'inward_factor': 0.2, - 'outward_factor': 0.1, - }, - }) - - if mpl.rcParams['_internal.classic_mode']: - self._axinfo.update({ - 'axisline': {'linewidth': 0.75, 'color': (0, 0, 0, 1)}, - 'grid': { - 'color': (0.9, 0.9, 0.9, 1), - 'linewidth': 1.0, - 'linestyle': '-', - }, - }) - self._axinfo['tick'].update({ - 'linewidth': { - True: mpl.rcParams['lines.linewidth'], # major - False: mpl.rcParams['lines.linewidth'], # minor - } - }) - else: - self._axinfo.update({ - 'axisline': { - 'linewidth': mpl.rcParams['axes.linewidth'], - 'color': mpl.rcParams['axes.edgecolor'], - }, - 'grid': { - 'color': mpl.rcParams['grid.color'], - 'linewidth': mpl.rcParams['grid.linewidth'], - 'linestyle': mpl.rcParams['grid.linestyle'], - }, - }) - self._axinfo['tick'].update({ - 'linewidth': { - True: ( # major - mpl.rcParams['xtick.major.width'] if name in 'xz' - else mpl.rcParams['ytick.major.width']), - False: ( # minor - mpl.rcParams['xtick.minor.width'] if name in 'xz' - else mpl.rcParams['ytick.minor.width']), - } - }) - - super().__init__(axes, *args, **kwargs) - - # data and viewing intervals for this direction - if "d_intervalx" in params: - self.set_data_interval(*params["d_intervalx"]) - if "v_intervalx" in params: - self.set_view_interval(*params["v_intervalx"]) - self.set_rotate_label(rotate_label) - self._init3d() # Inline after init3d deprecation elapses. - - __init__.__signature__ = inspect.signature(_new_init) - adir = _api.deprecated("3.6", pending=True)( - property(lambda self: self.axis_name)) - - def _init3d(self): - self.line = mlines.Line2D( - xdata=(0, 0), ydata=(0, 0), - linewidth=self._axinfo['axisline']['linewidth'], - color=self._axinfo['axisline']['color'], - antialiased=True) - - # Store dummy data in Polygon object - self.pane = mpatches.Polygon([[0, 0], [0, 1]], closed=False) - self.set_pane_color(self._axinfo['color']) - - self.axes._set_artist_props(self.line) - self.axes._set_artist_props(self.pane) - self.gridlines = art3d.Line3DCollection([]) - self.axes._set_artist_props(self.gridlines) - self.axes._set_artist_props(self.label) - self.axes._set_artist_props(self.offsetText) - # Need to be able to place the label at the correct location - self.label._transform = self.axes.transData - self.offsetText._transform = self.axes.transData - - @_api.deprecated("3.6", pending=True) - def init3d(self): # After deprecation elapses, inline _init3d to __init__. - self._init3d() - - def get_major_ticks(self, numticks=None): - ticks = super().get_major_ticks(numticks) - for t in ticks: - for obj in [ - t.tick1line, t.tick2line, t.gridline, t.label1, t.label2]: - obj.set_transform(self.axes.transData) - return ticks - - def get_minor_ticks(self, numticks=None): - ticks = super().get_minor_ticks(numticks) - for t in ticks: - for obj in [ - t.tick1line, t.tick2line, t.gridline, t.label1, t.label2]: - obj.set_transform(self.axes.transData) - return ticks - - def set_ticks_position(self, position): - """ - Set the ticks position. - - Parameters - ---------- - position : {'lower', 'upper', 'both', 'default', 'none'} - The position of the bolded axis lines, ticks, and tick labels. - """ - if position in ['top', 'bottom']: - _api.warn_deprecated('3.8', name=f'{position=}', - obj_type='argument value', - alternative="'upper' or 'lower'") - return - _api.check_in_list(['lower', 'upper', 'both', 'default', 'none'], - position=position) - self._tick_position = position - - def get_ticks_position(self): - """ - Get the ticks position. - - Returns - ------- - str : {'lower', 'upper', 'both', 'default', 'none'} - The position of the bolded axis lines, ticks, and tick labels. - """ - return self._tick_position - - def set_label_position(self, position): - """ - Set the label position. - - Parameters - ---------- - position : {'lower', 'upper', 'both', 'default', 'none'} - The position of the axis label. - """ - if position in ['top', 'bottom']: - _api.warn_deprecated('3.8', name=f'{position=}', - obj_type='argument value', - alternative="'upper' or 'lower'") - return - _api.check_in_list(['lower', 'upper', 'both', 'default', 'none'], - position=position) - self._label_position = position - - def get_label_position(self): - """ - Get the label position. - - Returns - ------- - str : {'lower', 'upper', 'both', 'default', 'none'} - The position of the axis label. - """ - return self._label_position - - def set_pane_color(self, color, alpha=None): - """ - Set pane color. - - Parameters - ---------- - color : color - Color for axis pane. - alpha : float, optional - Alpha value for axis pane. If None, base it on *color*. - """ - color = mcolors.to_rgba(color, alpha) - self._axinfo['color'] = color - self.pane.set_edgecolor(color) - self.pane.set_facecolor(color) - self.pane.set_alpha(color[-1]) - self.stale = True - - def set_rotate_label(self, val): - """ - Whether to rotate the axis label: True, False or None. - If set to None the label will be rotated if longer than 4 chars. - """ - self._rotate_label = val - self.stale = True - - def get_rotate_label(self, text): - if self._rotate_label is not None: - return self._rotate_label - else: - return len(text) > 4 - - def _get_coord_info(self, renderer): - mins, maxs = np.array([ - self.axes.get_xbound(), - self.axes.get_ybound(), - self.axes.get_zbound(), - ]).T - - # Get the mean value for each bound: - centers = 0.5 * (maxs + mins) - - # Add a small offset between min/max point and the edge of the plot: - deltas = (maxs - mins) / 12 - mins -= 0.25 * deltas - maxs += 0.25 * deltas - - # Project the bounds along the current position of the cube: - bounds = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2] - bounds_proj = self.axes._tunit_cube(bounds, self.axes.M) - - # Determine which one of the parallel planes are higher up: - means_z0 = np.zeros(3) - means_z1 = np.zeros(3) - for i in range(3): - means_z0[i] = np.mean(bounds_proj[self._PLANES[2 * i], 2]) - means_z1[i] = np.mean(bounds_proj[self._PLANES[2 * i + 1], 2]) - highs = means_z0 < means_z1 - - # Special handling for edge-on views - equals = np.abs(means_z0 - means_z1) <= np.finfo(float).eps - if np.sum(equals) == 2: - vertical = np.where(~equals)[0][0] - if vertical == 2: # looking at XY plane - highs = np.array([True, True, highs[2]]) - elif vertical == 1: # looking at XZ plane - highs = np.array([True, highs[1], False]) - elif vertical == 0: # looking at YZ plane - highs = np.array([highs[0], False, False]) - - return mins, maxs, centers, deltas, bounds_proj, highs - - def _get_axis_line_edge_points(self, minmax, maxmin, position=None): - """Get the edge points for the black bolded axis line.""" - # When changing vertical axis some of the axes has to be - # moved to the other plane so it looks the same as if the z-axis - # was the vertical axis. - mb = [minmax, maxmin] # line from origin to nearest corner to camera - mb_rev = mb[::-1] - mm = [[mb, mb_rev, mb_rev], [mb_rev, mb_rev, mb], [mb, mb, mb]] - mm = mm[self.axes._vertical_axis][self._axinfo["i"]] - - juggled = self._axinfo["juggled"] - edge_point_0 = mm[0].copy() # origin point - - if ((position == 'lower' and mm[1][juggled[-1]] < mm[0][juggled[-1]]) or - (position == 'upper' and mm[1][juggled[-1]] > mm[0][juggled[-1]])): - edge_point_0[juggled[-1]] = mm[1][juggled[-1]] - else: - edge_point_0[juggled[0]] = mm[1][juggled[0]] - - edge_point_1 = edge_point_0.copy() - edge_point_1[juggled[1]] = mm[1][juggled[1]] - - return edge_point_0, edge_point_1 - - def _get_all_axis_line_edge_points(self, minmax, maxmin, axis_position=None): - # Determine edge points for the axis lines - edgep1s = [] - edgep2s = [] - position = [] - if axis_position in (None, 'default'): - edgep1, edgep2 = self._get_axis_line_edge_points(minmax, maxmin) - edgep1s = [edgep1] - edgep2s = [edgep2] - position = ['default'] - else: - edgep1_l, edgep2_l = self._get_axis_line_edge_points(minmax, maxmin, - position='lower') - edgep1_u, edgep2_u = self._get_axis_line_edge_points(minmax, maxmin, - position='upper') - if axis_position in ('lower', 'both'): - edgep1s.append(edgep1_l) - edgep2s.append(edgep2_l) - position.append('lower') - if axis_position in ('upper', 'both'): - edgep1s.append(edgep1_u) - edgep2s.append(edgep2_u) - position.append('upper') - return edgep1s, edgep2s, position - - def _get_tickdir(self, position): - """ - Get the direction of the tick. - - Parameters - ---------- - position : str, optional : {'upper', 'lower', 'default'} - The position of the axis. - - Returns - ------- - tickdir : int - Index which indicates which coordinate the tick line will - align with. - """ - _api.check_in_list(('upper', 'lower', 'default'), position=position) - - # TODO: Move somewhere else where it's triggered less: - tickdirs_base = [v["tickdir"] for v in self._AXINFO.values()] # default - elev_mod = np.mod(self.axes.elev + 180, 360) - 180 - azim_mod = np.mod(self.axes.azim, 360) - if position == 'upper': - if elev_mod >= 0: - tickdirs_base = [2, 2, 0] - else: - tickdirs_base = [1, 0, 0] - if 0 <= azim_mod < 180: - tickdirs_base[2] = 1 - elif position == 'lower': - if elev_mod >= 0: - tickdirs_base = [1, 0, 1] - else: - tickdirs_base = [2, 2, 1] - if 0 <= azim_mod < 180: - tickdirs_base[2] = 0 - info_i = [v["i"] for v in self._AXINFO.values()] - - i = self._axinfo["i"] - vert_ax = self.axes._vertical_axis - j = vert_ax - 2 - # default: tickdir = [[1, 2, 1], [2, 2, 0], [1, 0, 0]][vert_ax][i] - tickdir = np.roll(info_i, -j)[np.roll(tickdirs_base, j)][i] - return tickdir - - def active_pane(self, renderer): - mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer) - info = self._axinfo - index = info['i'] - if not highs[index]: - loc = mins[index] - plane = self._PLANES[2 * index] - else: - loc = maxs[index] - plane = self._PLANES[2 * index + 1] - xys = np.array([tc[p] for p in plane]) - return xys, loc - - def draw_pane(self, renderer): - """ - Draw pane. - - Parameters - ---------- - renderer : `~matplotlib.backend_bases.RendererBase` subclass - """ - renderer.open_group('pane3d', gid=self.get_gid()) - xys, loc = self.active_pane(renderer) - self.pane.xy = xys[:, :2] - self.pane.draw(renderer) - renderer.close_group('pane3d') - - def _axmask(self): - axmask = [True, True, True] - axmask[self._axinfo["i"]] = False - return axmask - - def _draw_ticks(self, renderer, edgep1, centers, deltas, highs, - deltas_per_point, pos): - ticks = self._update_ticks() - info = self._axinfo - index = info["i"] - - # Draw ticks: - tickdir = self._get_tickdir(pos) - tickdelta = deltas[tickdir] if highs[tickdir] else -deltas[tickdir] - - tick_info = info['tick'] - tick_out = tick_info['outward_factor'] * tickdelta - tick_in = tick_info['inward_factor'] * tickdelta - tick_lw = tick_info['linewidth'] - edgep1_tickdir = edgep1[tickdir] - out_tickdir = edgep1_tickdir + tick_out - in_tickdir = edgep1_tickdir - tick_in - - default_label_offset = 8. # A rough estimate - points = deltas_per_point * deltas - for tick in ticks: - # Get tick line positions - pos = edgep1.copy() - pos[index] = tick.get_loc() - pos[tickdir] = out_tickdir - x1, y1, z1 = proj3d.proj_transform(*pos, self.axes.M) - pos[tickdir] = in_tickdir - x2, y2, z2 = proj3d.proj_transform(*pos, self.axes.M) - - # Get position of label - labeldeltas = (tick.get_pad() + default_label_offset) * points - - pos[tickdir] = edgep1_tickdir - pos = _move_from_center(pos, centers, labeldeltas, self._axmask()) - lx, ly, lz = proj3d.proj_transform(*pos, self.axes.M) - - _tick_update_position(tick, (x1, x2), (y1, y2), (lx, ly)) - tick.tick1line.set_linewidth(tick_lw[tick._major]) - tick.draw(renderer) - - def _draw_offset_text(self, renderer, edgep1, edgep2, labeldeltas, centers, - highs, pep, dx, dy): - # Get general axis information: - info = self._axinfo - index = info["i"] - juggled = info["juggled"] - tickdir = info["tickdir"] - - # Which of the two edge points do we want to - # use for locating the offset text? - if juggled[2] == 2: - outeredgep = edgep1 - outerindex = 0 - else: - outeredgep = edgep2 - outerindex = 1 - - pos = _move_from_center(outeredgep, centers, labeldeltas, - self._axmask()) - olx, oly, olz = proj3d.proj_transform(*pos, self.axes.M) - self.offsetText.set_text(self.major.formatter.get_offset()) - self.offsetText.set_position((olx, oly)) - angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx))) - self.offsetText.set_rotation(angle) - # Must set rotation mode to "anchor" so that - # the alignment point is used as the "fulcrum" for rotation. - self.offsetText.set_rotation_mode('anchor') - - # ---------------------------------------------------------------------- - # Note: the following statement for determining the proper alignment of - # the offset text. This was determined entirely by trial-and-error - # and should not be in any way considered as "the way". There are - # still some edge cases where alignment is not quite right, but this - # seems to be more of a geometry issue (in other words, I might be - # using the wrong reference points). - # - # (TT, FF, TF, FT) are the shorthand for the tuple of - # (centpt[tickdir] <= pep[tickdir, outerindex], - # centpt[index] <= pep[index, outerindex]) - # - # Three-letters (e.g., TFT, FTT) are short-hand for the array of bools - # from the variable 'highs'. - # --------------------------------------------------------------------- - centpt = proj3d.proj_transform(*centers, self.axes.M) - if centpt[tickdir] > pep[tickdir, outerindex]: - # if FT and if highs has an even number of Trues - if (centpt[index] <= pep[index, outerindex] - and np.count_nonzero(highs) % 2 == 0): - # Usually, this means align right, except for the FTT case, - # in which offset for axis 1 and 2 are aligned left. - if highs.tolist() == [False, True, True] and index in (1, 2): - align = 'left' - else: - align = 'right' - else: - # The FF case - align = 'left' - else: - # if TF and if highs has an even number of Trues - if (centpt[index] > pep[index, outerindex] - and np.count_nonzero(highs) % 2 == 0): - # Usually mean align left, except if it is axis 2 - align = 'right' if index == 2 else 'left' - else: - # The TT case - align = 'right' - - self.offsetText.set_va('center') - self.offsetText.set_ha(align) - self.offsetText.draw(renderer) - - def _draw_labels(self, renderer, edgep1, edgep2, labeldeltas, centers, dx, dy): - label = self._axinfo["label"] - - # Draw labels - lxyz = 0.5 * (edgep1 + edgep2) - lxyz = _move_from_center(lxyz, centers, labeldeltas, self._axmask()) - tlx, tly, tlz = proj3d.proj_transform(*lxyz, self.axes.M) - self.label.set_position((tlx, tly)) - if self.get_rotate_label(self.label.get_text()): - angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx))) - self.label.set_rotation(angle) - self.label.set_va(label['va']) - self.label.set_ha(label['ha']) - self.label.set_rotation_mode(label['rotation_mode']) - self.label.draw(renderer) - - @artist.allow_rasterization - def draw(self, renderer): - self.label._transform = self.axes.transData - self.offsetText._transform = self.axes.transData - renderer.open_group("axis3d", gid=self.get_gid()) - - # Get general axis information: - mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer) - - # Calculate offset distances - # A rough estimate; points are ambiguous since 3D plots rotate - reltoinches = self.figure.dpi_scale_trans.inverted() - ax_inches = reltoinches.transform(self.axes.bbox.size) - ax_points_estimate = sum(72. * ax_inches) - deltas_per_point = 48 / ax_points_estimate - default_offset = 21. - labeldeltas = (self.labelpad + default_offset) * deltas_per_point * deltas - - # Determine edge points for the axis lines - minmax = np.where(highs, maxs, mins) # "origin" point - maxmin = np.where(~highs, maxs, mins) # "opposite" corner near camera - - for edgep1, edgep2, pos in zip(*self._get_all_axis_line_edge_points( - minmax, maxmin, self._tick_position)): - # Project the edge points along the current position - pep = proj3d._proj_trans_points([edgep1, edgep2], self.axes.M) - pep = np.asarray(pep) - - # The transAxes transform is used because the Text object - # rotates the text relative to the display coordinate system. - # Therefore, if we want the labels to remain parallel to the - # axis regardless of the aspect ratio, we need to convert the - # edge points of the plane to display coordinates and calculate - # an angle from that. - # TODO: Maybe Text objects should handle this themselves? - dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) - - self.axes.transAxes.transform([pep[0:2, 0]]))[0] - - # Draw the lines - self.line.set_data(pep[0], pep[1]) - self.line.draw(renderer) - - # Draw ticks - self._draw_ticks(renderer, edgep1, centers, deltas, highs, - deltas_per_point, pos) - - # Draw Offset text - self._draw_offset_text(renderer, edgep1, edgep2, labeldeltas, - centers, highs, pep, dx, dy) - - for edgep1, edgep2, pos in zip(*self._get_all_axis_line_edge_points( - minmax, maxmin, self._label_position)): - # See comments above - pep = proj3d._proj_trans_points([edgep1, edgep2], self.axes.M) - pep = np.asarray(pep) - dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) - - self.axes.transAxes.transform([pep[0:2, 0]]))[0] - - # Draw labels - self._draw_labels(renderer, edgep1, edgep2, labeldeltas, centers, dx, dy) - - renderer.close_group('axis3d') - self.stale = False - - @artist.allow_rasterization - def draw_grid(self, renderer): - if not self.axes._draw_grid: - return - - renderer.open_group("grid3d", gid=self.get_gid()) - - ticks = self._update_ticks() - if len(ticks): - # Get general axis information: - info = self._axinfo - index = info["i"] - - mins, maxs, _, _, _, highs = self._get_coord_info(renderer) - - minmax = np.where(highs, maxs, mins) - maxmin = np.where(~highs, maxs, mins) - - # Grid points where the planes meet - xyz0 = np.tile(minmax, (len(ticks), 1)) - xyz0[:, index] = [tick.get_loc() for tick in ticks] - - # Grid lines go from the end of one plane through the plane - # intersection (at xyz0) to the end of the other plane. The first - # point (0) differs along dimension index-2 and the last (2) along - # dimension index-1. - lines = np.stack([xyz0, xyz0, xyz0], axis=1) - lines[:, 0, index - 2] = maxmin[index - 2] - lines[:, 2, index - 1] = maxmin[index - 1] - self.gridlines.set_segments(lines) - gridinfo = info['grid'] - self.gridlines.set_color(gridinfo['color']) - self.gridlines.set_linewidth(gridinfo['linewidth']) - self.gridlines.set_linestyle(gridinfo['linestyle']) - self.gridlines.do_3d_projection() - self.gridlines.draw(renderer) - - renderer.close_group('grid3d') - - # TODO: Get this to work (more) properly when mplot3d supports the - # transforms framework. - def get_tightbbox(self, renderer=None, *, for_layout_only=False): - # docstring inherited - if not self.get_visible(): - return - # We have to directly access the internal data structures - # (and hope they are up to date) because at draw time we - # shift the ticks and their labels around in (x, y) space - # based on the projection, the current view port, and their - # position in 3D space. If we extend the transforms framework - # into 3D we would not need to do this different book keeping - # than we do in the normal axis - major_locs = self.get_majorticklocs() - minor_locs = self.get_minorticklocs() - - ticks = [*self.get_minor_ticks(len(minor_locs)), - *self.get_major_ticks(len(major_locs))] - view_low, view_high = self.get_view_interval() - if view_low > view_high: - view_low, view_high = view_high, view_low - interval_t = self.get_transform().transform([view_low, view_high]) - - ticks_to_draw = [] - for tick in ticks: - try: - loc_t = self.get_transform().transform(tick.get_loc()) - except AssertionError: - # Transform.transform doesn't allow masked values but - # some scales might make them, so we need this try/except. - pass - else: - if mtransforms._interval_contains_close(interval_t, loc_t): - ticks_to_draw.append(tick) - - ticks = ticks_to_draw - - bb_1, bb_2 = self._get_ticklabel_bboxes(ticks, renderer) - other = [] - - if self.line.get_visible(): - other.append(self.line.get_window_extent(renderer)) - if (self.label.get_visible() and not for_layout_only and - self.label.get_text()): - other.append(self.label.get_window_extent(renderer)) - - return mtransforms.Bbox.union([*bb_1, *bb_2, *other]) - - d_interval = _api.deprecated( - "3.6", alternative="get_data_interval", pending=True)( - property(lambda self: self.get_data_interval(), - lambda self, minmax: self.set_data_interval(*minmax))) - v_interval = _api.deprecated( - "3.6", alternative="get_view_interval", pending=True)( - property(lambda self: self.get_view_interval(), - lambda self, minmax: self.set_view_interval(*minmax))) - - -class XAxis(Axis): - axis_name = "x" - get_view_interval, set_view_interval = maxis._make_getset_interval( - "view", "xy_viewLim", "intervalx") - get_data_interval, set_data_interval = maxis._make_getset_interval( - "data", "xy_dataLim", "intervalx") - - -class YAxis(Axis): - axis_name = "y" - get_view_interval, set_view_interval = maxis._make_getset_interval( - "view", "xy_viewLim", "intervaly") - get_data_interval, set_data_interval = maxis._make_getset_interval( - "data", "xy_dataLim", "intervaly") - - -class ZAxis(Axis): - axis_name = "z" - get_view_interval, set_view_interval = maxis._make_getset_interval( - "view", "zz_viewLim", "intervalx") - get_data_interval, set_data_interval = maxis._make_getset_interval( - "data", "zz_dataLim", "intervalx") diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/proj3d.py b/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/proj3d.py deleted file mode 100644 index 098a7b6f666..00000000000 --- a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/proj3d.py +++ /dev/null @@ -1,259 +0,0 @@ -""" -Various transforms used for by the 3D code -""" - -import numpy as np - -from matplotlib import _api - - -def world_transformation(xmin, xmax, - ymin, ymax, - zmin, zmax, pb_aspect=None): - """ - Produce a matrix that scales homogeneous coords in the specified ranges - to [0, 1], or [0, pb_aspect[i]] if the plotbox aspect ratio is specified. - """ - dx = xmax - xmin - dy = ymax - ymin - dz = zmax - zmin - if pb_aspect is not None: - ax, ay, az = pb_aspect - dx /= ax - dy /= ay - dz /= az - - return np.array([[1/dx, 0, 0, -xmin/dx], - [0, 1/dy, 0, -ymin/dy], - [0, 0, 1/dz, -zmin/dz], - [0, 0, 0, 1]]) - - -@_api.deprecated("3.8") -def rotation_about_vector(v, angle): - """ - Produce a rotation matrix for an angle in radians about a vector. - """ - return _rotation_about_vector(v, angle) - - -def _rotation_about_vector(v, angle): - """ - Produce a rotation matrix for an angle in radians about a vector. - """ - vx, vy, vz = v / np.linalg.norm(v) - s = np.sin(angle) - c = np.cos(angle) - t = 2*np.sin(angle/2)**2 # more numerically stable than t = 1-c - - R = np.array([ - [t*vx*vx + c, t*vx*vy - vz*s, t*vx*vz + vy*s], - [t*vy*vx + vz*s, t*vy*vy + c, t*vy*vz - vx*s], - [t*vz*vx - vy*s, t*vz*vy + vx*s, t*vz*vz + c]]) - - return R - - -def _view_axes(E, R, V, roll): - """ - Get the unit viewing axes in data coordinates. - - Parameters - ---------- - E : 3-element numpy array - The coordinates of the eye/camera. - R : 3-element numpy array - The coordinates of the center of the view box. - V : 3-element numpy array - Unit vector in the direction of the vertical axis. - roll : float - The roll angle in radians. - - Returns - ------- - u : 3-element numpy array - Unit vector pointing towards the right of the screen. - v : 3-element numpy array - Unit vector pointing towards the top of the screen. - w : 3-element numpy array - Unit vector pointing out of the screen. - """ - w = (E - R) - w = w/np.linalg.norm(w) - u = np.cross(V, w) - u = u/np.linalg.norm(u) - v = np.cross(w, u) # Will be a unit vector - - # Save some computation for the default roll=0 - if roll != 0: - # A positive rotation of the camera is a negative rotation of the world - Rroll = _rotation_about_vector(w, -roll) - u = np.dot(Rroll, u) - v = np.dot(Rroll, v) - return u, v, w - - -def _view_transformation_uvw(u, v, w, E): - """ - Return the view transformation matrix. - - Parameters - ---------- - u : 3-element numpy array - Unit vector pointing towards the right of the screen. - v : 3-element numpy array - Unit vector pointing towards the top of the screen. - w : 3-element numpy array - Unit vector pointing out of the screen. - E : 3-element numpy array - The coordinates of the eye/camera. - """ - Mr = np.eye(4) - Mt = np.eye(4) - Mr[:3, :3] = [u, v, w] - Mt[:3, -1] = -E - M = np.dot(Mr, Mt) - return M - - -@_api.deprecated("3.8") -def view_transformation(E, R, V, roll): - """ - Return the view transformation matrix. - - Parameters - ---------- - E : 3-element numpy array - The coordinates of the eye/camera. - R : 3-element numpy array - The coordinates of the center of the view box. - V : 3-element numpy array - Unit vector in the direction of the vertical axis. - roll : float - The roll angle in radians. - """ - u, v, w = _view_axes(E, R, V, roll) - M = _view_transformation_uvw(u, v, w, E) - return M - - -@_api.deprecated("3.8") -def persp_transformation(zfront, zback, focal_length): - return _persp_transformation(zfront, zback, focal_length) - - -def _persp_transformation(zfront, zback, focal_length): - e = focal_length - a = 1 # aspect ratio - b = (zfront+zback)/(zfront-zback) - c = -2*(zfront*zback)/(zfront-zback) - proj_matrix = np.array([[e, 0, 0, 0], - [0, e/a, 0, 0], - [0, 0, b, c], - [0, 0, -1, 0]]) - return proj_matrix - - -@_api.deprecated("3.8") -def ortho_transformation(zfront, zback): - return _ortho_transformation(zfront, zback) - - -def _ortho_transformation(zfront, zback): - # note: w component in the resulting vector will be (zback-zfront), not 1 - a = -(zfront + zback) - b = -(zfront - zback) - proj_matrix = np.array([[2, 0, 0, 0], - [0, 2, 0, 0], - [0, 0, -2, 0], - [0, 0, a, b]]) - return proj_matrix - - -def _proj_transform_vec(vec, M): - vecw = np.dot(M, vec) - w = vecw[3] - # clip here.. - txs, tys, tzs = vecw[0]/w, vecw[1]/w, vecw[2]/w - return txs, tys, tzs - - -def _proj_transform_vec_clip(vec, M): - vecw = np.dot(M, vec) - w = vecw[3] - # clip here. - txs, tys, tzs = vecw[0] / w, vecw[1] / w, vecw[2] / w - tis = (0 <= vecw[0]) & (vecw[0] <= 1) & (0 <= vecw[1]) & (vecw[1] <= 1) - if np.any(tis): - tis = vecw[1] < 1 - return txs, tys, tzs, tis - - -def inv_transform(xs, ys, zs, invM): - """ - Transform the points by the inverse of the projection matrix, *invM*. - """ - vec = _vec_pad_ones(xs, ys, zs) - vecr = np.dot(invM, vec) - if vecr.shape == (4,): - vecr = vecr.reshape((4, 1)) - for i in range(vecr.shape[1]): - if vecr[3][i] != 0: - vecr[:, i] = vecr[:, i] / vecr[3][i] - return vecr[0], vecr[1], vecr[2] - - -def _vec_pad_ones(xs, ys, zs): - return np.array([xs, ys, zs, np.ones_like(xs)]) - - -def proj_transform(xs, ys, zs, M): - """ - Transform the points by the projection matrix *M*. - """ - vec = _vec_pad_ones(xs, ys, zs) - return _proj_transform_vec(vec, M) - - -transform = _api.deprecated( - "3.8", obj_type="function", name="transform", - alternative="proj_transform")(proj_transform) - - -def proj_transform_clip(xs, ys, zs, M): - """ - Transform the points by the projection matrix - and return the clipping result - returns txs, tys, tzs, tis - """ - vec = _vec_pad_ones(xs, ys, zs) - return _proj_transform_vec_clip(vec, M) - - -@_api.deprecated("3.8") -def proj_points(points, M): - return _proj_points(points, M) - - -def _proj_points(points, M): - return np.column_stack(_proj_trans_points(points, M)) - - -@_api.deprecated("3.8") -def proj_trans_points(points, M): - return _proj_trans_points(points, M) - - -def _proj_trans_points(points, M): - xs, ys, zs = zip(*points) - return proj_transform(xs, ys, zs, M) - - -@_api.deprecated("3.8") -def rot_x(V, alpha): - cosa, sina = np.cos(alpha), np.sin(alpha) - M1 = np.array([[1, 0, 0, 0], - [0, cosa, -sina, 0], - [0, sina, cosa, 0], - [0, 0, 0, 1]]) - return np.dot(M1, V) |