diff options
author | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 14:39:34 +0300 |
---|---|---|
committer | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 16:42:24 +0300 |
commit | 77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch) | |
tree | c51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1 | |
parent | dd6d20cadb65582270ac23f4b3b14ae189704b9d (diff) | |
download | ydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz |
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1')
10 files changed, 4820 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/__init__.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/__init__.py new file mode 100644 index 0000000000..3e225ba9f0 --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/__init__.py @@ -0,0 +1,12 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +from . import axes_size as Size +from .axes_divider import Divider, SubplotDivider, LocatableAxes, \ + make_axes_locatable +from .axes_grid import Grid, ImageGrid, AxesGrid +#from axes_divider import make_axes_locatable + +from .parasite_axes import host_subplot, host_axes diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/anchored_artists.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/anchored_artists.py new file mode 100644 index 0000000000..5b492858e8 --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/anchored_artists.py @@ -0,0 +1,376 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) +import six + +from matplotlib import docstring +from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox, + DrawingArea, TextArea, VPacker) +from matplotlib.patches import Rectangle, Ellipse + + +__all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox', + 'AnchoredEllipse', 'AnchoredSizeBar'] + + +class AnchoredDrawingArea(AnchoredOffsetbox): + @docstring.dedent + 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 : int or float + width and height of the container, in pixels. + + xdescent, ydescent : int or float + descent of the container in the x- and y- direction, in pixels. + + loc : int + Location of this artist. Valid location codes are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4, + 'right' : 5, + 'center left' : 6, + 'center right' : 7, + 'lower center' : 8, + 'upper center' : 9, + 'center' : 10 + + pad : int or float, optional + Padding around the child objects, in fraction of the font + size. Defaults to 0.4. + + borderpad : int or float, optional + Border padding, in fraction of the font size. + Defaults to 0.5. + + prop : `matplotlib.font_manager.FontProperties`, optional + Font property used as a reference for paddings. + + frameon : bool, optional + If True, draw a box around this artists. Defaults to True. + + **kwargs : + Keyworded arguments to pass to + :class:`matplotlib.offsetbox.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=1, 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(AnchoredDrawingArea, self).__init__( + loc, pad=pad, borderpad=borderpad, child=self.da, prop=None, + frameon=frameon, **kwargs + ) + + +class AnchoredAuxTransformBox(AnchoredOffsetbox): + @docstring.dedent + 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 : int + Location of this artist. Valid location codes are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4, + 'right' : 5, + 'center left' : 6, + 'center right' : 7, + 'lower center' : 8, + 'upper center' : 9, + 'center' : 10 + + pad : int or float, optional + Padding around the child objects, in fraction of the font + size. Defaults to 0.4. + + borderpad : int or float, optional + Border padding, in fraction of the font size. + Defaults to 0.5. + + prop : `matplotlib.font_manager.FontProperties`, optional + Font property used as a reference for paddings. + + frameon : bool, optional + If True, draw a box around this artists. Defaults to True. + + **kwargs : + Keyworded arguments to pass to + :class:`matplotlib.offsetbox.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=2) + >>> 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) + + AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad, + child=self.drawing_area, + prop=prop, + frameon=frameon, + **kwargs) + + +class AnchoredEllipse(AnchoredOffsetbox): + @docstring.dedent + 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 : int or float + Width and height of the ellipse, given in coordinates of + *transform*. + + angle : int or float + Rotation of the ellipse, in degrees, anti-clockwise. + + loc : int + Location of this size bar. Valid location codes are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4, + 'right' : 5, + 'center left' : 6, + 'center right' : 7, + 'lower center' : 8, + 'upper center' : 9, + 'center' : 10 + + pad : int or float, optional + Padding around the ellipse, in fraction of the font size. Defaults + to 0.1. + + borderpad : int or float, optional + Border padding, in fraction of the font size. Defaults to 0.1. + + frameon : bool, optional + If True, draw a box around the ellipse. Defaults to True. + + prop : `matplotlib.font_manager.FontProperties`, optional + Font property used as a reference for paddings. + + **kwargs : + Keyworded arguments to pass to + :class:`matplotlib.offsetbox.AnchoredOffsetbox`. + + Attributes + ---------- + ellipse : `matplotlib.patches.Ellipse` + Ellipse patch drawn. + """ + self._box = AuxTransformBox(transform) + self.ellipse = Ellipse((0, 0), width, height, angle) + self._box.add_artist(self.ellipse) + + AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad, + child=self._box, + prop=prop, + frameon=frameon, **kwargs) + + +class AnchoredSizeBar(AnchoredOffsetbox): + @docstring.dedent + 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 : int or float + Horizontal length of the size bar, given in coordinates of + *transform*. + + label : str + Label to display. + + loc : int + Location of this size bar. Valid location codes are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4, + 'right' : 5, + 'center left' : 6, + 'center right' : 7, + 'lower center' : 8, + 'upper center' : 9, + 'center' : 10 + + pad : int or float, optional + Padding around the label and size bar, in fraction of the font + size. Defaults to 0.1. + + borderpad : int or float, optional + Border padding, in fraction of the font size. + Defaults to 0.1. + + sep : int or float, optional + Separation between the label and the size bar, in points. + Defaults to 2. + + frameon : bool, optional + If True, draw a box around the horizontal bar and label. + Defaults to True. + + size_vertical : int or float, optional + Vertical length of the size bar, given in coordinates of + *transform*. Defaults to 0. + + color : str, optional + Color for the size bar and label. + Defaults to black. + + label_top : bool, optional + If True, the label will be over the size bar. + Defaults to False. + + 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 : + Keyworded arguments to pass to + :class:`matplotlib.offsetbox.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 keyworded argument, but *fontproperties* is + not, then *prop* is be 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, + minimumdescent=False, + 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) + + AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad, + child=self._box, + prop=fontproperties, + frameon=frameon, **kwargs) diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_divider.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_divider.py new file mode 100644 index 0000000000..b238e73cc5 --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_divider.py @@ -0,0 +1,975 @@ +""" +The axes_divider module provides helper classes to adjust the positions of +multiple axes at drawing time. + + Divider: this is the class that is used to calculate the axes + position. It divides the given rectangular area into several sub + rectangles. You initialize the divider by setting the horizontal + and vertical lists of sizes that the division will be based on. You + then use the new_locator method, whose return value is a callable + object that can be used to set the axes_locator of the axes. + +""" +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six +from six.moves import map + +import matplotlib.transforms as mtransforms + +from matplotlib.axes import SubplotBase + +from . import axes_size as Size + + +class Divider(object): + """ + This class calculates the axes position. It + divides the given rectangular area into several + sub-rectangles. You initialize the divider by setting the + horizontal and vertical lists of sizes + (:mod:`mpl_toolkits.axes_grid.axes_size`) that the division will + be based on. You then use the new_locator method to create 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_grid.axes_size` + sizes for horizontal division + vertical : list of :mod:`~mpl_toolkits.axes_grid.axes_size` + sizes for vertical division + aspect : bool + if True, the overall rectangular area is reduced + so that the relative part of the horizontal and + vertical scales have the same scale. + anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'} + 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._aspect = aspect + self._xrefindex = 0 + self._yrefindex = 0 + self._locator = None + + def get_horizontal_sizes(self, renderer): + return [s.get_size(renderer) for s in self.get_horizontal()] + + def get_vertical_sizes(self, renderer): + return [s.get_size(renderer) for s in self.get_vertical()] + + def get_vsize_hsize(self): + + from .axes_size import AddList + + vsize = AddList(self.get_vertical()) + hsize = AddList(self.get_horizontal()) + + return vsize, hsize + + @staticmethod + def _calc_k(l, total_size): + + rs_sum, as_sum = 0., 0. + + for _rs, _as in l: + rs_sum += _rs + as_sum += _as + + if rs_sum != 0.: + k = (total_size - as_sum) / rs_sum + return k + else: + return 0. + + @staticmethod + def _calc_offsets(l, k): + + offsets = [0.] + + #for s in l: + for _rs, _as in l: + #_rs, _as = s.get_size(renderer) + offsets.append(offsets[-1] + _rs*k + _as) + + return offsets + + 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 : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'} + anchor position + + ===== ============ + value description + ===== ============ + 'C' Center + 'SW' bottom left + 'S' bottom + 'SE' bottom right + 'E' right + 'NE' top right + 'N' top + 'NW' top left + 'W' left + ===== ============ + + """ + if anchor in mtransforms.Bbox.coefs or len(anchor) == 2: + self._anchor = anchor + else: + raise ValueError('argument must be among %s' % + ', '.join(mtransforms.BBox.coefs)) + + def get_anchor(self): + "return the anchor" + return self._anchor + + def set_horizontal(self, h): + """ + Parameters + ---------- + h : list of :mod:`~mpl_toolkits.axes_grid.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_grid.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 + + def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): + """ + 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. + axes + renderer + """ + + figW, figH = self._fig.get_size_inches() + 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, figW*w) + k_v = self._calc_k(vsizes, figH*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])/figW + hh = (oy[-1] - oy[0])/figH + pb = mtransforms.Bbox.from_bounds(x, y, w, h) + pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh) + pb1_anchored = pb1.anchored(self.get_anchor(), pb) + x0, y0 = pb1_anchored.x0, pb1_anchored.y0 + + else: + ox = self._calc_offsets(hsizes, k_h) + oy = self._calc_offsets(vsizes, k_v) + x0, y0 = x, y + + if nx1 is None: + nx1 = nx+1 + if ny1 is None: + ny1 = ny+1 + + x1, w1 = x0 + ox[nx]/figW, (ox[nx1] - ox[nx])/figW + y1, h1 = y0 + oy[ny]/figH, (oy[ny1] - oy[ny])/figH + + return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) + + def new_locator(self, nx, ny, nx1=None, ny1=None): + """ + Returns a new locator + (:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for + 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. + """ + return AxesLocator(self, nx, ny, nx1, ny1) + + def append_size(self, position, size): + + 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 + elif position == "top": + self._vertical.append(size) + else: + raise ValueError("the position must be one of left," + + " right, bottom, or top") + + def add_auto_adjustable_area(self, + use_axes, pad=0.1, + adjust_dirs=None, + ): + if adjust_dirs is None: + adjust_dirs = ["left", "right", "bottom", "top"] + from .axes_size import Padded, SizeFromFunc, GetExtentHelper + for d in adjust_dirs: + helper = GetExtentHelper(use_axes, d) + size = SizeFromFunc(helper) + padded_size = Padded(size, pad) # pad in inch + self.append_size(d, padded_size) + + +class AxesLocator(object): + """ + A simple callable object, initialized with AxesDivider class, + returns the position and size of the given cell. + """ + def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None): + """ + Parameters + ---------- + 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 = nx+1 + if ny1 is None: + ny1 = ny+1 + + 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): + if hasattr(self._axes_divider, "get_subplotspec"): + return self._axes_divider.get_subplotspec() + else: + return None + + +from matplotlib.gridspec import SubplotSpec, GridSpec + + +class SubplotDivider(Divider): + """ + The Divider class whose rectangle area is specified as a subplot geometry. + """ + + def __init__(self, fig, *args, **kwargs): + """ + Parameters + ---------- + fig : :class:`matplotlib.figure.Figure` + args : tuple (*numRows*, *numCols*, *plotNum*) + The array of subplots in the figure has dimensions *numRows*, + *numCols*, and *plotNum* is the number of the subplot + being created. *plotNum* starts at 1 in the upper left + corner and increases to the right. + + If *numRows* <= *numCols* <= *plotNum* < 10, *args* can be the + decimal integer *numRows* * 100 + *numCols* * 10 + *plotNum*. + """ + + self.figure = fig + + if len(args) == 1: + if isinstance(args[0], SubplotSpec): + self._subplotspec = args[0] + else: + try: + s = str(int(args[0])) + rows, cols, num = map(int, s) + except ValueError: + raise ValueError( + 'Single argument to subplot must be a 3-digit integer') + self._subplotspec = GridSpec(rows, cols)[num-1] + # num - 1 for converting from MATLAB to python indexing + elif len(args) == 3: + rows, cols, num = args + rows = int(rows) + cols = int(cols) + if isinstance(num, tuple) and len(num) == 2: + num = [int(n) for n in num] + self._subplotspec = GridSpec(rows, cols)[num[0]-1:num[1]] + else: + self._subplotspec = GridSpec(rows, cols)[int(num)-1] + # num - 1 for converting from MATLAB to python indexing + else: + raise ValueError('Illegal argument(s) to subplot: %s' % (args,)) + + # total = rows*cols + # num -= 1 # convert from matlab to python indexing + # # i.e., num in range(0,total) + # if num >= total: + # raise ValueError( 'Subplot number exceeds total subplots') + # self._rows = rows + # self._cols = cols + # self._num = num + + # self.update_params() + + # sets self.fixbox + self.update_params() + + pos = self.figbox.bounds + + horizontal = kwargs.pop("horizontal", []) + vertical = kwargs.pop("vertical", []) + aspect = kwargs.pop("aspect", None) + anchor = kwargs.pop("anchor", "C") + + if kwargs: + raise Exception("") + + Divider.__init__(self, fig, pos, horizontal, vertical, + aspect=aspect, anchor=anchor) + + def get_position(self): + "return the bounds of the subplot box" + + self.update_params() # update self.figbox + return self.figbox.bounds + + # def update_params(self): + # 'update the subplot position from fig.subplotpars' + + # rows = self._rows + # cols = self._cols + # num = self._num + + # pars = self.figure.subplotpars + # left = pars.left + # right = pars.right + # bottom = pars.bottom + # top = pars.top + # wspace = pars.wspace + # hspace = pars.hspace + # totWidth = right-left + # totHeight = top-bottom + + # figH = totHeight/(rows + hspace*(rows-1)) + # sepH = hspace*figH + + # figW = totWidth/(cols + wspace*(cols-1)) + # sepW = wspace*figW + + # rowNum, colNum = divmod(num, cols) + + # figBottom = top - (rowNum+1)*figH - rowNum*sepH + # figLeft = left + colNum*(figW + sepW) + + # self.figbox = mtransforms.Bbox.from_bounds(figLeft, figBottom, + # figW, figH) + + def update_params(self): + 'update the subplot position from fig.subplotpars' + + self.figbox = self.get_subplotspec().get_position(self.figure) + + def get_geometry(self): + 'get the subplot geometry, e.g., 2,2,3' + rows, cols, num1, num2 = self.get_subplotspec().get_geometry() + return rows, cols, num1+1 # for compatibility + + # COVERAGE NOTE: Never used internally or from examples + def change_geometry(self, numrows, numcols, num): + 'change subplot geometry, e.g., from 1,1,1 to 2,2,3' + self._subplotspec = GridSpec(numrows, numcols)[num-1] + self.update_params() + self.set_position(self.figbox) + + def get_subplotspec(self): + 'get the SubplotSpec instance' + return self._subplotspec + + def set_subplotspec(self, subplotspec): + 'set the SubplotSpec instance' + self._subplotspec = subplotspec + + +class AxesDivider(Divider): + """ + Divider based on the pre-existing 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 + + Divider.__init__(self, fig=axes.get_figure(), pos=None, + horizontal=[self._xref], vertical=[self._yref], + aspect=None, anchor="C") + + def _get_new_axes(self, **kwargs): + axes = self._axes + + axes_class = kwargs.pop("axes_class", None) + + if axes_class is None: + if isinstance(axes, SubplotBase): + axes_class = axes._axes_class + else: + axes_class = type(axes) + + ax = axes_class(axes.get_figure(), + axes.get_position(original=True), **kwargs) + + return ax + + def new_horizontal(self, size, pad=None, pack_start=False, **kwargs): + """ + Add a new axes on the right (or left) side of the main axes. + + Parameters + ---------- + size : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string + A width of the axes. If float or string is given, *from_any* + function is used to create the size, with *ref_size* set to AxesX + instance of the current axes. + pad : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string + Pad between the axes. It takes same argument as *size*. + pack_start : bool + If False, the new axes is appended at the end + of the list, i.e., it became the right-most axes. If True, it is + inserted at the start of the list, and becomes the left-most axes. + kwargs + All extra keywords arguments are passed to the created axes. + If *axes_class* is given, the new axes will be created as an + instance of the given class. Otherwise, the same class of the + main axes will be used. + """ + + if pad: + if not isinstance(pad, Size._Base): + pad = Size.from_any(pad, + fraction_ref=self._xref) + if pack_start: + self._horizontal.insert(0, pad) + self._xrefindex += 1 + else: + self._horizontal.append(pad) + + if not isinstance(size, Size._Base): + size = Size.from_any(size, + fraction_ref=self._xref) + + if pack_start: + self._horizontal.insert(0, size) + self._xrefindex += 1 + locator = self.new_locator(nx=0, ny=self._yrefindex) + else: + self._horizontal.append(size) + locator = self.new_locator(nx=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): + """ + Add a new axes on the top (or bottom) side of the main axes. + + Parameters + ---------- + size : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string + A height of the axes. If float or string is given, *from_any* + function is used to create the size, with *ref_size* set to AxesX + instance of the current axes. + pad : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string + Pad between the axes. It takes same argument as *size*. + pack_start : bool + If False, the new axes is appended at the end + of the list, i.e., it became the right-most axes. If True, it is + inserted at the start of the list, and becomes the left-most axes. + kwargs + All extra keywords arguments are passed to the created axes. + If *axes_class* is given, the new axes will be created as an + instance of the given class. Otherwise, the same class of the + main axes will be used. + """ + + if pad: + if not isinstance(pad, Size._Base): + pad = Size.from_any(pad, + fraction_ref=self._yref) + if pack_start: + self._vertical.insert(0, pad) + self._yrefindex += 1 + else: + self._vertical.append(pad) + + if not isinstance(size, Size._Base): + size = Size.from_any(size, + fraction_ref=self._yref) + + if pack_start: + self._vertical.insert(0, size) + self._yrefindex += 1 + locator = self.new_locator(nx=self._xrefindex, ny=0) + else: + self._vertical.append(size) + locator = self.new_locator(nx=self._xrefindex, ny=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, add_to_figure=True, + **kwargs): + """ + create an axes at the given *position* with the same height + (or width) of the main axes. + + *position* + ["left"|"right"|"bottom"|"top"] + + *size* and *pad* should be axes_grid.axes_size compatible. + """ + + if position == "left": + ax = self.new_horizontal(size, pad, pack_start=True, **kwargs) + elif position == "right": + ax = self.new_horizontal(size, pad, pack_start=False, **kwargs) + elif position == "bottom": + ax = self.new_vertical(size, pad, pack_start=True, **kwargs) + elif position == "top": + ax = self.new_vertical(size, pad, pack_start=False, **kwargs) + else: + raise ValueError("the position must be one of left," + + " right, bottom, or top") + + if add_to_figure: + 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): + if hasattr(self._axes, "get_subplotspec"): + return self._axes.get_subplotspec() + else: + return None + + +class HBoxDivider(SubplotDivider): + + def __init__(self, fig, *args, **kwargs): + SubplotDivider.__init__(self, fig, *args, **kwargs) + + @staticmethod + def _determine_karray(equivalent_sizes, appended_sizes, + max_equivalent_size, + total_appended_size): + + n = len(equivalent_sizes) + import numpy as np + A = np.mat(np.zeros((n+1, n+1), dtype="d")) + B = np.zeros((n+1), dtype="d") + # AxK = B + + # populated A + for i, (r, a) in enumerate(equivalent_sizes): + A[i, i] = r + A[i, -1] = -1 + B[i] = -a + A[-1, :-1] = [r for r, a in appended_sizes] + B[-1] = total_appended_size - sum([a for rs, a in appended_sizes]) + + karray_H = (A.I*np.mat(B).T).A1 + karray = karray_H[:-1] + H = karray_H[-1] + + if H > max_equivalent_size: + karray = ((max_equivalent_size - + np.array([a for r, a in equivalent_sizes])) + / np.array([r for r, a in equivalent_sizes])) + return karray + + @staticmethod + def _calc_offsets(appended_sizes, karray): + offsets = [0.] + + #for s in l: + for (r, a), k in zip(appended_sizes, karray): + offsets.append(offsets[-1] + r*k + a) + + return offsets + + def new_locator(self, nx, nx1=None): + """ + returns a new locator + (:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for + 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. + """ + return AxesLocator(self, nx, 0, nx1, None) + + def _locate(self, x, y, w, h, + y_equivalent_sizes, x_appended_sizes, + figW, figH): + """ + Parameters + ---------- + x + y + w + h + y_equivalent_sizes + x_appended_sizes + figW + figH + """ + + equivalent_sizes = y_equivalent_sizes + appended_sizes = x_appended_sizes + + max_equivalent_size = figH*h + total_appended_size = figW*w + karray = self._determine_karray(equivalent_sizes, appended_sizes, + max_equivalent_size, + total_appended_size) + + ox = self._calc_offsets(appended_sizes, karray) + + ww = (ox[-1] - ox[0])/figW + ref_h = equivalent_sizes[0] + hh = (karray[0]*ref_h[0] + ref_h[1])/figH + pb = mtransforms.Bbox.from_bounds(x, y, w, h) + pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh) + pb1_anchored = pb1.anchored(self.get_anchor(), pb) + x0, y0 = pb1_anchored.x0, pb1_anchored.y0 + + return x0, y0, ox, hh + + def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): + """ + Parameters + ---------- + 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. + axes + renderer + """ + + figW, figH = self._fig.get_size_inches() + x, y, w, h = self.get_position_runtime(axes, renderer) + + y_equivalent_sizes = self.get_vertical_sizes(renderer) + x_appended_sizes = self.get_horizontal_sizes(renderer) + x0, y0, ox, hh = self._locate(x, y, w, h, + y_equivalent_sizes, x_appended_sizes, + figW, figH) + if nx1 is None: + nx1 = nx+1 + + x1, w1 = x0 + ox[nx]/figW, (ox[nx1] - ox[nx])/figW + y1, h1 = y0, hh + + return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) + + +class VBoxDivider(HBoxDivider): + """ + The Divider class whose rectangle area is specified as a subplot geometry. + """ + + def new_locator(self, ny, ny1=None): + """ + returns a new locator + (:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for + 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 AxesLocator(self, 0, ny, None, ny1) + + def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): + """ + Parameters + ---------- + 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. + axes + renderer + """ + + figW, figH = self._fig.get_size_inches() + x, y, w, h = self.get_position_runtime(axes, renderer) + + x_equivalent_sizes = self.get_horizontal_sizes(renderer) + y_appended_sizes = self.get_vertical_sizes(renderer) + + y0, x0, oy, ww = self._locate(y, x, h, w, + x_equivalent_sizes, y_appended_sizes, + figH, figW) + if ny1 is None: + ny1 = ny+1 + + x1, w1 = x0, ww + y1, h1 = y0 + oy[ny]/figH, (oy[ny1] - oy[ny])/figH + + return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) + + +class LocatableAxesBase(object): + def __init__(self, *kl, **kw): + + self._axes_class.__init__(self, *kl, **kw) + + self._locator = None + self._locator_renderer = None + + def set_axes_locator(self, locator): + self._locator = locator + + def get_axes_locator(self): + return self._locator + + def apply_aspect(self, position=None): + + if self.get_axes_locator() is None: + self._axes_class.apply_aspect(self, position) + else: + pos = self.get_axes_locator()(self, self._locator_renderer) + self._axes_class.apply_aspect(self, position=pos) + + def draw(self, renderer=None, inframe=False): + + self._locator_renderer = renderer + + self._axes_class.draw(self, renderer, inframe) + + def _make_twin_axes(self, *kl, **kwargs): + """ + Need to overload so that twinx/twiny will work with + these axes. + """ + if 'sharex' in kwargs and 'sharey' in kwargs: + raise ValueError("Twinned Axes may share only one axis.") + ax2 = type(self)(self.figure, self.get_position(True), *kl, **kwargs) + ax2.set_axes_locator(self.get_axes_locator()) + self.figure.add_axes(ax2) + self.set_adjustable('datalim') + ax2.set_adjustable('datalim') + self._twinned_axes.join(self, ax2) + return ax2 + +_locatableaxes_classes = {} + + +def locatable_axes_factory(axes_class): + + new_class = _locatableaxes_classes.get(axes_class) + if new_class is None: + new_class = type(str("Locatable%s" % (axes_class.__name__)), + (LocatableAxesBase, axes_class), + {'_axes_class': axes_class}) + + _locatableaxes_classes[axes_class] = new_class + + return new_class + +#if hasattr(maxes.Axes, "get_axes_locator"): +# LocatableAxes = maxes.Axes +#else: + + +def make_axes_locatable(axes): + if not hasattr(axes, "set_axes_locator"): + new_class = locatable_axes_factory(type(axes)) + axes.__class__ = new_class + + 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): + 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) + +#from matplotlib.axes import Axes +from .mpl_axes import Axes +LocatableAxes = locatable_axes_factory(Axes) diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_grid.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_grid.py new file mode 100644 index 0000000000..dde0e8dd7c --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_grid.py @@ -0,0 +1,771 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +import matplotlib.axes as maxes +import matplotlib.cbook as cbook +import matplotlib.ticker as ticker +from matplotlib.gridspec import SubplotSpec + +from .axes_divider import Size, SubplotDivider, LocatableAxes, Divider +from .colorbar import Colorbar + + +def _extend_axes_pad(value): + # Check whether a list/tuple/array or scalar has been passed + ret = value + if not hasattr(ret, "__getitem__"): + ret = (value, value) + return ret + + +def _tick_only(ax, bottom_on, left_on): + bottom_off = not bottom_on + left_off = not left_on + # [l.set_visible(bottom_off) for l in ax.get_xticklabels()] + # [l.set_visible(left_off) for l in ax.get_yticklabels()] + # ax.xaxis.label.set_visible(bottom_off) + # ax.yaxis.label.set_visible(left_off) + ax.axis["bottom"].toggle(ticklabels=bottom_off, label=bottom_off) + ax.axis["left"].toggle(ticklabels=left_off, label=left_off) + + +class CbarAxesBase(object): + + def colorbar(self, mappable, **kwargs): + locator = kwargs.pop("locator", None) + + if locator is None: + if "ticks" not in kwargs: + kwargs["ticks"] = ticker.MaxNLocator(5) + if locator is not None: + if "ticks" in kwargs: + raise ValueError("Either *locator* or *ticks* need" + + " to be given, not both") + else: + kwargs["ticks"] = locator + + self._hold = True + if self.orientation in ["top", "bottom"]: + orientation = "horizontal" + else: + orientation = "vertical" + + cb = Colorbar(self, mappable, orientation=orientation, **kwargs) + self._config_axes() + + def on_changed(m): + cb.set_cmap(m.get_cmap()) + cb.set_clim(m.get_clim()) + cb.update_bruteforce(m) + + self.cbid = mappable.callbacksSM.connect('changed', on_changed) + mappable.colorbar = cb + + self.locator = cb.cbar_axis.get_major_locator() + + return cb + + def _config_axes(self): + ''' + Make an axes patch and outline. + ''' + ax = self + ax.set_navigate(False) + + ax.axis[:].toggle(all=False) + b = self._default_label_on + ax.axis[self.orientation].toggle(all=b) + + # for axis in ax.axis.values(): + # axis.major_ticks.set_visible(False) + # axis.minor_ticks.set_visible(False) + # axis.major_ticklabels.set_visible(False) + # axis.minor_ticklabels.set_visible(False) + # axis.label.set_visible(False) + + # axis = ax.axis[self.orientation] + # axis.major_ticks.set_visible(True) + # axis.minor_ticks.set_visible(True) + + #axis.major_ticklabels.set_size( + # int(axis.major_ticklabels.get_size()*.9)) + #axis.major_tick_pad = 3 + + # axis.major_ticklabels.set_visible(b) + # axis.minor_ticklabels.set_visible(b) + # axis.label.set_visible(b) + + def toggle_label(self, b): + self._default_label_on = b + axis = self.axis[self.orientation] + axis.toggle(ticklabels=b, label=b) + #axis.major_ticklabels.set_visible(b) + #axis.minor_ticklabels.set_visible(b) + #axis.label.set_visible(b) + + +class CbarAxes(CbarAxesBase, LocatableAxes): + def __init__(self, *kl, **kwargs): + orientation = kwargs.pop("orientation", None) + if orientation is None: + raise ValueError("orientation must be specified") + self.orientation = orientation + self._default_label_on = True + self.locator = None + + super(LocatableAxes, self).__init__(*kl, **kwargs) + + def cla(self): + super(LocatableAxes, self).cla() + self._config_axes() + + +class Grid(object): + """ + A class that creates a grid of Axes. In matplotlib, the axes + location (and size) is specified in the normalized figure + coordinates. This may not be ideal for images that needs to be + displayed with a given aspect ratio. For example, displaying + images of a same size with some fixed padding between them cannot + be easily done in matplotlib. AxesGrid is used in such case. + """ + + _defaultLocatableAxesClass = LocatableAxes + + def __init__(self, fig, + rect, + nrows_ncols, + ngrids=None, + direction="row", + axes_pad=0.02, + add_all=True, + share_all=False, + share_x=True, + share_y=True, + #aspect=True, + label_mode="L", + axes_class=None, + ): + """ + Build an :class:`Grid` instance with a grid nrows*ncols + :class:`~matplotlib.axes.Axes` in + :class:`~matplotlib.figure.Figure` *fig* with + *rect=[left, bottom, width, height]* (in + :class:`~matplotlib.figure.Figure` coordinates) or + the subplot position code (e.g., "121"). + + Optional keyword arguments: + + ================ ======== ========================================= + Keyword Default Description + ================ ======== ========================================= + direction "row" [ "row" | "column" ] + axes_pad 0.02 float| pad between axes given in inches + or tuple-like of floats, + (horizontal padding, vertical padding) + add_all True bool + share_all False bool + share_x True bool + share_y True bool + label_mode "L" [ "L" | "1" | "all" ] + axes_class None a type object which must be a subclass + of :class:`~matplotlib.axes.Axes` + ================ ======== ========================================= + """ + self._nrows, self._ncols = nrows_ncols + + if ngrids is None: + ngrids = self._nrows * self._ncols + else: + if (ngrids > self._nrows * self._ncols) or (ngrids <= 0): + raise Exception("") + + self.ngrids = ngrids + + self._init_axes_pad(axes_pad) + + if direction not in ["column", "row"]: + raise Exception("") + + self._direction = direction + + if axes_class is None: + axes_class = self._defaultLocatableAxesClass + axes_class_args = {} + else: + if (type(axes_class)) == type and \ + issubclass(axes_class, + self._defaultLocatableAxesClass.Axes): + axes_class_args = {} + else: + axes_class, axes_class_args = axes_class + + self.axes_all = [] + self.axes_column = [[] for _ in range(self._ncols)] + self.axes_row = [[] for _ in range(self._nrows)] + + h = [] + v = [] + if isinstance(rect, six.string_types) or cbook.is_numlike(rect): + self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v, + aspect=False) + elif isinstance(rect, SubplotSpec): + self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v, + aspect=False) + elif len(rect) == 3: + kw = dict(horizontal=h, vertical=v, aspect=False) + self._divider = SubplotDivider(fig, *rect, **kw) + elif len(rect) == 4: + self._divider = Divider(fig, rect, horizontal=h, vertical=v, + aspect=False) + else: + raise Exception("") + + rect = self._divider.get_position() + + # reference axes + self._column_refax = [None for _ in range(self._ncols)] + self._row_refax = [None for _ in range(self._nrows)] + self._refax = None + + for i in range(self.ngrids): + + col, row = self._get_col_row(i) + + if share_all: + sharex = self._refax + sharey = self._refax + else: + if share_x: + sharex = self._column_refax[col] + else: + sharex = None + + if share_y: + sharey = self._row_refax[row] + else: + sharey = None + + ax = axes_class(fig, rect, sharex=sharex, sharey=sharey, + **axes_class_args) + + if share_all: + if self._refax is None: + self._refax = ax + else: + if sharex is None: + self._column_refax[col] = ax + if sharey is None: + self._row_refax[row] = ax + + self.axes_all.append(ax) + self.axes_column[col].append(ax) + self.axes_row[row].append(ax) + + self.axes_llc = self.axes_column[0][-1] + + self._update_locators() + + if add_all: + for ax in self.axes_all: + fig.add_axes(ax) + + self.set_label_mode(label_mode) + + def _init_axes_pad(self, axes_pad): + axes_pad = _extend_axes_pad(axes_pad) + self._axes_pad = axes_pad + + self._horiz_pad_size = Size.Fixed(axes_pad[0]) + self._vert_pad_size = Size.Fixed(axes_pad[1]) + + def _update_locators(self): + + h = [] + + h_ax_pos = [] + + for _ in self._column_refax: + #if h: h.append(Size.Fixed(self._axes_pad)) + if h: + h.append(self._horiz_pad_size) + + h_ax_pos.append(len(h)) + + sz = Size.Scaled(1) + h.append(sz) + + v = [] + + v_ax_pos = [] + for _ in self._row_refax[::-1]: + #if v: v.append(Size.Fixed(self._axes_pad)) + if v: + v.append(self._vert_pad_size) + + v_ax_pos.append(len(v)) + sz = Size.Scaled(1) + v.append(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) + + self._divider.set_horizontal(h) + self._divider.set_vertical(v) + + 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): + """ + get geometry of the grid. Returns a tuple of two integer, + representing number of rows and number of columns. + """ + return self._nrows, self._ncols + + def set_axes_pad(self, axes_pad): + "set axes_pad" + self._axes_pad = axes_pad + + # These two lines actually differ from ones in _init_axes_pad + self._horiz_pad_size.fixed_size = axes_pad[0] + self._vert_pad_size.fixed_size = axes_pad[1] + + def get_axes_pad(self): + """ + get axes_pad + + Returns + ------- + tuple + Padding in inches, (horizontal pad, vertical pad) + """ + return self._axes_pad + + def set_aspect(self, aspect): + "set aspect" + self._divider.set_aspect(aspect) + + def get_aspect(self): + "get aspect" + return self._divider.get_aspect() + + def set_label_mode(self, mode): + "set label_mode" + if mode == "all": + for ax in self.axes_all: + _tick_only(ax, False, False) + elif mode == "L": + # left-most axes + for ax in self.axes_column[0][:-1]: + _tick_only(ax, bottom_on=True, left_on=False) + # lower-left axes + ax = self.axes_column[0][-1] + _tick_only(ax, bottom_on=False, left_on=False) + + for col in self.axes_column[1:]: + # axes with no labels + for ax in col[:-1]: + _tick_only(ax, bottom_on=True, left_on=True) + + # bottom + ax = col[-1] + _tick_only(ax, bottom_on=False, left_on=True) + + elif mode == "1": + for ax in self.axes_all: + _tick_only(ax, bottom_on=True, left_on=True) + + ax = self.axes_llc + _tick_only(ax, bottom_on=False, left_on=False) + + 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() + + def get_vsize_hsize(self): + + return self._divider.get_vsize_hsize() +# from axes_size import AddList + +# vsize = AddList(self._divider.get_vertical()) +# hsize = AddList(self._divider.get_horizontal()) + +# return vsize, hsize + + +class ImageGrid(Grid): + """ + A class that creates a grid of Axes. In matplotlib, the axes + location (and size) is specified in the normalized figure + coordinates. This may not be ideal for images that needs to be + displayed with a given aspect ratio. For example, displaying + images of a same size with some fixed padding between them cannot + be easily done in matplotlib. ImageGrid is used in such case. + """ + + _defaultCbarAxesClass = CbarAxes + + def __init__(self, fig, + rect, + nrows_ncols, + ngrids=None, + direction="row", + axes_pad=0.02, + add_all=True, + 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, + ): + """ + Build an :class:`ImageGrid` instance with a grid nrows*ncols + :class:`~matplotlib.axes.Axes` in + :class:`~matplotlib.figure.Figure` *fig* with + *rect=[left, bottom, width, height]* (in + :class:`~matplotlib.figure.Figure` coordinates) or + the subplot position code (e.g., "121"). + + Optional keyword arguments: + + ================ ======== ========================================= + Keyword Default Description + ================ ======== ========================================= + direction "row" [ "row" | "column" ] + axes_pad 0.02 float| pad between axes given in inches + or tuple-like of floats, + (horizontal padding, vertical padding) + add_all True bool + share_all False bool + aspect True bool + label_mode "L" [ "L" | "1" | "all" ] + cbar_mode None [ "each" | "single" | "edge" ] + cbar_location "right" [ "left" | "right" | "bottom" | "top" ] + cbar_pad None + cbar_size "5%" + cbar_set_cax True bool + axes_class None a type object which must be a subclass + of axes_grid's subclass of + :class:`~matplotlib.axes.Axes` + ================ ======== ========================================= + + *cbar_set_cax* : if True, each axes in the grid has a cax + attribute that is bind to associated cbar_axes. + """ + 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 Exception + + self.ngrids = ngrids + + axes_pad = _extend_axes_pad(axes_pad) + self._axes_pad = axes_pad + + self._colorbar_mode = cbar_mode + self._colorbar_location = cbar_location + if cbar_pad is None: + # horizontal or vertical arrangement? + if cbar_location in ("left", "right"): + self._colorbar_pad = axes_pad[0] + else: + self._colorbar_pad = axes_pad[1] + else: + self._colorbar_pad = cbar_pad + + self._colorbar_size = cbar_size + + self._init_axes_pad(axes_pad) + + if direction not in ["column", "row"]: + raise Exception("") + + self._direction = direction + + if axes_class is None: + axes_class = self._defaultLocatableAxesClass + axes_class_args = {} + else: + if isinstance(axes_class, maxes.Axes): + axes_class_args = {} + else: + axes_class, axes_class_args = axes_class + + self.axes_all = [] + self.axes_column = [[] for _ in range(self._ncols)] + self.axes_row = [[] for _ in range(self._nrows)] + + self.cbar_axes = [] + + h = [] + v = [] + if isinstance(rect, six.string_types) or cbook.is_numlike(rect): + self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v, + aspect=aspect) + elif isinstance(rect, SubplotSpec): + self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v, + aspect=aspect) + elif len(rect) == 3: + kw = dict(horizontal=h, vertical=v, aspect=aspect) + self._divider = SubplotDivider(fig, *rect, **kw) + elif len(rect) == 4: + self._divider = Divider(fig, rect, horizontal=h, vertical=v, + aspect=aspect) + else: + raise Exception("") + + rect = self._divider.get_position() + + # reference axes + self._column_refax = [None for _ in range(self._ncols)] + self._row_refax = [None for _ in range(self._nrows)] + self._refax = None + + for i in range(self.ngrids): + + col, row = self._get_col_row(i) + + if share_all: + if self.axes_all: + sharex = self.axes_all[0] + sharey = self.axes_all[0] + else: + sharex = None + sharey = None + else: + sharex = self._column_refax[col] + sharey = self._row_refax[row] + + ax = axes_class(fig, rect, sharex=sharex, sharey=sharey, + **axes_class_args) + + self.axes_all.append(ax) + self.axes_column[col].append(ax) + self.axes_row[row].append(ax) + + if share_all: + if self._refax is None: + self._refax = ax + if sharex is None: + self._column_refax[col] = ax + if sharey is None: + self._row_refax[row] = ax + + cax = self._defaultCbarAxesClass(fig, rect, + orientation=self._colorbar_location) + self.cbar_axes.append(cax) + + self.axes_llc = self.axes_column[0][-1] + + self._update_locators() + + if add_all: + for ax in self.axes_all+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 + + self.set_label_mode(label_mode) + + def _update_locators(self): + + h = [] + v = [] + + h_ax_pos = [] + h_cb_pos = [] + if (self._colorbar_mode == "single" and + self._colorbar_location in ('left', 'bottom')): + if self._colorbar_location == "left": + #sz = Size.Fraction(Size.AxesX(self.axes_llc), self._nrows) + sz = Size.Fraction(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 self._colorbar_location == "bottom": + #sz = Size.Fraction(Size.AxesY(self.axes_llc), self._ncols) + sz = Size.Fraction(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) # Size.Fixed(self._axes_pad)) + + 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 (self._colorbar_mode == "each" or + (self._colorbar_mode == 'edge' and + col == 0)) and self._colorbar_location == "left": + 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 ((self._colorbar_mode == "each" or + (self._colorbar_mode == 'edge' and + col == self._ncols - 1)) and + self._colorbar_location == "right"): + 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) # Size.Fixed(self._axes_pad)) + + 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 (self._colorbar_mode == "each" or + (self._colorbar_mode == 'edge' and + row == 0)) and self._colorbar_location == "bottom": + 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 ((self._colorbar_mode == "each" or + (self._colorbar_mode == 'edge' and + row == self._nrows - 1)) and + self._colorbar_location == "top"): + 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=4*col, + # ny=2*(self._nrows - row - 1)) + 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 self._colorbar_mode == "each": + if self._colorbar_location in ("right", "left"): + locator = self._divider.new_locator( + nx=h_cb_pos[col], ny=v_ax_pos[self._nrows - 1 - row]) + + elif self._colorbar_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 self._colorbar_mode == 'edge': + if ((self._colorbar_location == 'left' and col == 0) or + (self._colorbar_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 ((self._colorbar_location == 'bottom' and + row == self._nrows - 1) or + (self._colorbar_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 self._colorbar_mode == "single": + if self._colorbar_location == "right": + #sz = Size.Fraction(Size.AxesX(self.axes_llc), self._nrows) + sz = Size.Fraction(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 self._colorbar_location == "top": + #sz = Size.Fraction(Size.AxesY(self.axes_llc), self._ncols) + sz = Size.Fraction(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 self._colorbar_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 self._colorbar_mode == "each": + for i in range(self.ngrids): + self.cbar_axes[i].set_visible(True) + elif self._colorbar_mode == "edge": + if self._colorbar_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/py2/mpl_toolkits/axes_grid1/axes_rgb.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_rgb.py new file mode 100644 index 0000000000..e62d4f0615 --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_rgb.py @@ -0,0 +1,228 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +import numpy as np +from .axes_divider import make_axes_locatable, Size, locatable_axes_factory +import sys +from .mpl_axes import Axes + + +def make_rgb_axes(ax, pad=0.01, axes_class=None, add_all=True): + """ + pad : fraction of the axes height. + """ + + divider = make_axes_locatable(ax) + + pad_size = Size.Fraction(pad, Size.AxesY(ax)) + + xsize = Size.Fraction((1.-2.*pad)/3., Size.AxesX(ax)) + ysize = Size.Fraction((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: + try: + axes_class = locatable_axes_factory(ax._axes_class) + except AttributeError: + axes_class = locatable_axes_factory(type(ax)) + + for ny in [4, 2, 0]: + ax1 = axes_class(ax.get_figure(), + ax.get_position(original=True), + sharex=ax, sharey=ax) + 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) + + if add_all: + fig = ax.get_figure() + for ax1 in ax_rgb: + fig.add_axes(ax1) + + return ax_rgb + + +def imshow_rgb(ax, r, g, b, **kwargs): + ny, nx = r.shape + R = np.zeros([ny, nx, 3], dtype="d") + R[:,:,0] = r + G = np.zeros_like(R) + G[:,:,1] = g + B = np.zeros_like(R) + B[:,:,2] = b + + RGB = R + G + B + + im_rgb = ax.imshow(RGB, **kwargs) + + return im_rgb + + +class RGBAxesBase(object): + """base class for a 4-panel imshow (RGB, R, G, B) + + Layout: + +---------------+-----+ + | | R | + + +-----+ + | RGB | G | + + +-----+ + | | B | + +---------------+-----+ + + Attributes + ---------- + _defaultAxesClass : matplotlib.axes.Axes + defaults to 'Axes' in RGBAxes child class. + No default in abstract base class + RGB : _defaultAxesClass + The axes object for the three-channel imshow + R : _defaultAxesClass + The axes object for the red channel imshow + G : _defaultAxesClass + The axes object for the green channel imshow + B : _defaultAxesClass + The axes object for the blue channel imshow + """ + def __init__(self, *kl, **kwargs): + """ + Parameters + ---------- + pad : float + fraction of the axes height to put as padding. + defaults to 0.0 + add_all : bool + True: Add the {rgb, r, g, b} axes to the figure + defaults to True. + axes_class : matplotlib.axes.Axes + + kl : + Unpacked into axes_class() init for RGB + kwargs : + Unpacked into axes_class() init for RGB, R, G, B axes + """ + pad = kwargs.pop("pad", 0.0) + add_all = kwargs.pop("add_all", True) + try: + axes_class = kwargs.pop("axes_class", self._defaultAxesClass) + except AttributeError: + new_msg = ("A subclass of RGBAxesBase must have a " + "_defaultAxesClass attribute. If you are not sure which " + "axes class to use, consider using " + "mpl_toolkits.axes_grid1.mpl_axes.Axes.") + six.reraise(AttributeError, AttributeError(new_msg), + sys.exc_info()[2]) + + ax = axes_class(*kl, **kwargs) + + divider = make_axes_locatable(ax) + + pad_size = Size.Fraction(pad, Size.AxesY(ax)) + + xsize = Size.Fraction((1.-2.*pad)/3., Size.AxesX(ax)) + ysize = Size.Fraction((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 = [] + 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) + ax1.axis[:].toggle(ticklabels=False) + ax_rgb.append(ax1) + + self.RGB = ax + self.R, self.G, self.B = ax_rgb + + if add_all: + fig = ax.get_figure() + fig.add_axes(ax) + self.add_RGB_to_figure() + + self._config_axes() + + def _config_axes(self, line_color='w', marker_edge_color='w'): + """Set the line color and ticks for the axes + + Parameters + ---------- + line_color : any matplotlib color + marker_edge_color : any matplotlib color + """ + for ax1 in [self.RGB, self.R, self.G, self.B]: + ax1.axis[:].line.set_color(line_color) + ax1.axis[:].major_ticks.set_markeredgecolor(marker_edge_color) + + def add_RGB_to_figure(self): + """Add the red, green and blue axes to the RGB composite's axes figure + """ + self.RGB.get_figure().add_axes(self.R) + self.RGB.get_figure().add_axes(self.G) + self.RGB.get_figure().add_axes(self.B) + + def imshow_rgb(self, r, g, b, **kwargs): + """Create the four images {rgb, r, g, b} + + Parameters + ---------- + r : array-like + The red array + g : array-like + The green array + b : array-like + The blue array + kwargs : imshow kwargs + kwargs get unpacked into the 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('Input shapes do not match.' + '\nr.shape = {}' + '\ng.shape = {}' + '\nb.shape = {}' + .format(r.shape, g.shape, b.shape)) + 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 + + +class RGBAxes(RGBAxesBase): + _defaultAxesClass = Axes diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_size.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_size.py new file mode 100644 index 0000000000..163a6245fe --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_size.py @@ -0,0 +1,323 @@ + +""" +provides a 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 __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +import matplotlib.cbook as cbook +from matplotlib.axes import Axes + +class _Base(object): + "Base class" + + def __rmul__(self, other): + float(other) # just to check if number if given + return Fraction(other, self) + + def __add__(self, other): + if isinstance(other, _Base): + return Add(self, other) + else: + float(other) + other = Fixed(other) + return Add(self, other) + + +class Add(_Base): + 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 AddList(_Base): + def __init__(self, add_list): + self._list = add_list + + def get_size(self, renderer): + sum_rel_size = sum([a.get_size(renderer)[0] for a in self._list]) + sum_abs_size = sum([a.get_size(renderer)[1] for a in self._list]) + return sum_rel_size, sum_abs_size + + +class Fixed(_Base): + "Simple fixed size with absolute part = *fixed_size* and relative part = 0" + def __init__(self, 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() + # when aspec is "auto", consider it as 1. + if aspect in ('normal', 'auto'): + aspect = 1. + elif aspect == "equal": + aspect = 1 + else: + aspect = float(aspect) + + 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 the largest width (or height) of + the given *artist_list*. + """ + def __init__(self, artist_list, w_or_h): + self._artist_list = artist_list + + if w_or_h not in ["width", "height"]: + raise ValueError() + + 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. + w_list, h_list = [], [] + for a in self._artist_list: + bb = a.get_window_extent(renderer) + w_list.append(bb.width) + h_list.append(bb.height) + dpi = a.get_figure().get_dpi() + if self._w_or_h == "width": + abs_size = max(w_list)/dpi + elif self._w_or_h == "height": + abs_size = max(h_list)/dpi + + return rel_size, abs_size + + +class MaxWidth(_Base): + """ + Size whose absolute part is the largest width of + the given *artist_list*. + """ + def __init__(self, artist_list): + self._artist_list = artist_list + + def add_artist(self, a): + self._artist_list.append(a) + + def get_size(self, renderer): + rel_size = 0. + w_list = [] + for a in self._artist_list: + bb = a.get_window_extent(renderer) + w_list.append(bb.width) + dpi = a.get_figure().get_dpi() + abs_size = max(w_list)/dpi + + return rel_size, abs_size + + + +class MaxHeight(_Base): + """ + Size whose absolute part is the largest height of + the given *artist_list*. + """ + def __init__(self, artist_list): + self._artist_list = artist_list + + def add_artist(self, a): + self._artist_list.append(a) + + def get_size(self, renderer): + rel_size = 0. + h_list = [] + for a in self._artist_list: + bb = a.get_window_extent(renderer) + h_list.append(bb.height) + dpi = a.get_figure().get_dpi() + abs_size = max(h_list)/dpi + + return rel_size, abs_size + + +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): + 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 + +class Padded(_Base): + """ + Return a instance where the absolute part of *size* is + increase by the amount of *pad*. + """ + def __init__(self, size, pad): + self._size = size + self._pad = pad + + def get_size(self, renderer): + r, a = self._size.get_size(renderer) + rel_size = r + abs_size = a + self._pad + return rel_size, abs_size + +def from_any(size, fraction_ref=None): + """ + Creates 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.:: + + >>> a = Size.from_any(1.2) # => Size.Fixed(1.2) + >>> Size.from_any("50%", a) # => Size.Fraction(0.5, a) + + """ + if cbook.is_numlike(size): + return Fixed(size) + elif isinstance(size, six.string_types): + if size[-1] == "%": + return Fraction(float(size[:-1]) / 100, fraction_ref) + + raise ValueError("Unknown format") + + +class SizeFromFunc(_Base): + def __init__(self, func): + self._func = func + + def get_size(self, renderer): + rel_size = 0. + + bb = self._func(renderer) + dpi = renderer.points_to_pixels(72.) + abs_size = bb/dpi + + return rel_size, abs_size + +class GetExtentHelper(object): + def _get_left(tight_bbox, axes_bbox): + return axes_bbox.xmin - tight_bbox.xmin + + def _get_right(tight_bbox, axes_bbox): + return tight_bbox.xmax - axes_bbox.xmax + + def _get_bottom(tight_bbox, axes_bbox): + return axes_bbox.ymin - tight_bbox.ymin + + def _get_top(tight_bbox, axes_bbox): + return tight_bbox.ymax - axes_bbox.ymax + + _get_func_map = dict(left=_get_left, + right=_get_right, + bottom=_get_bottom, + top=_get_top) + + del _get_left, _get_right, _get_bottom, _get_top + + def __init__(self, ax, direction): + if isinstance(ax, Axes): + self._ax_list = [ax] + else: + self._ax_list = ax + + try: + self._get_func = self._get_func_map[direction] + except KeyError: + raise KeyError("direction must be one of left, right, bottom, top") + + def __call__(self, renderer): + vl = [self._get_func(ax.get_tightbbox(renderer, False), + ax.bbox) for ax in self._ax_list] + return max(vl) diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/colorbar.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/colorbar.py new file mode 100644 index 0000000000..34bdf3618a --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/colorbar.py @@ -0,0 +1,836 @@ +""" +Colorbar toolkit with two classes and a function: + + :class:`ColorbarBase` + the base class with full colorbar drawing functionality. + It can be used as-is to make a colorbar for a given colormap; + a mappable object (e.g., image) is not needed. + + :class:`Colorbar` + the derived class for use with images or contour plots. + + :func:`make_axes` + a function for resizing an axes and adding a second axes + suitable for a colorbar + +The :meth:`~matplotlib.figure.Figure.colorbar` method uses :func:`make_axes` +and :class:`Colorbar`; the :func:`~matplotlib.pyplot.colorbar` function +is a thin wrapper over :meth:`~matplotlib.figure.Figure.colorbar`. +""" + +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six +from six.moves import xrange, zip + +import numpy as np +import matplotlib as mpl +import matplotlib.colors as colors +import matplotlib.cm as cm +from matplotlib import docstring +import matplotlib.ticker as ticker +import matplotlib.cbook as cbook +import matplotlib.collections as collections +import matplotlib.contour as contour +from matplotlib.path import Path +from matplotlib.patches import PathPatch +from matplotlib.transforms import Bbox + + +make_axes_kw_doc = ''' + + ============= ==================================================== + Property Description + ============= ==================================================== + *orientation* vertical or horizontal + *fraction* 0.15; fraction of original axes to use for colorbar + *pad* 0.05 if vertical, 0.15 if horizontal; fraction + of original axes between colorbar and new image axes + *shrink* 1.0; fraction by which to shrink the colorbar + *aspect* 20; ratio of long to short dimensions + ============= ==================================================== + +''' + +colormap_kw_doc = ''' + + =========== ==================================================== + Property Description + =========== ==================================================== + *extend* [ 'neither' | 'both' | 'min' | 'max' ] + If not 'neither', make pointed end(s) for out-of- + range values. These are set for a given colormap + using the colormap set_under and set_over methods. + *spacing* [ 'uniform' | 'proportional' ] + Uniform spacing gives each discrete color the same + space; proportional makes the space proportional to + the data interval. + *ticks* [ None | list of ticks | Locator object ] + If None, ticks are determined automatically from the + input. + *format* [ None | format string | Formatter object ] + If None, the + :class:`~matplotlib.ticker.ScalarFormatter` is used. + If a format string is given, e.g., '%.3f', that is + used. An alternative + :class:`~matplotlib.ticker.Formatter` object may be + given instead. + *drawedges* bool + Whether to draw lines at color boundaries. + =========== ==================================================== + + The following will probably be useful only in the context of + indexed colors (that is, when the mappable has norm=NoNorm()), + or other unusual circumstances. + + ============ =================================================== + Property Description + ============ =================================================== + *boundaries* None or a sequence + *values* None or a sequence which must be of length 1 less + than the sequence of *boundaries*. For each region + delimited by adjacent entries in *boundaries*, the + color mapped to the corresponding value in values + will be used. + ============ =================================================== + +''' + +colorbar_doc = ''' + +Add a colorbar to a plot. + +Function signatures for the :mod:`~matplotlib.pyplot` interface; all +but the first are also method signatures for the +:meth:`~matplotlib.figure.Figure.colorbar` method:: + + colorbar(**kwargs) + colorbar(mappable, **kwargs) + colorbar(mappable, cax=cax, **kwargs) + colorbar(mappable, ax=ax, **kwargs) + +arguments: + + *mappable* + the :class:`~matplotlib.image.Image`, + :class:`~matplotlib.contour.ContourSet`, etc. to + which the colorbar applies; this argument is mandatory for the + :meth:`~matplotlib.figure.Figure.colorbar` method but optional for the + :func:`~matplotlib.pyplot.colorbar` function, which sets the + default to the current image. + +keyword arguments: + + *cax* + None | axes object into which the colorbar will be drawn + *ax* + None | parent axes object from which space for a new + colorbar axes will be stolen + + +Additional keyword arguments are of two kinds: + + axes properties: + %s + colorbar properties: + %s + +If *mappable* is a :class:`~matplotlib.contours.ContourSet`, its *extend* +kwarg is included automatically. + +Note that the *shrink* kwarg provides a simple way to keep a vertical +colorbar, for example, from being taller than the axes of the mappable +to which the colorbar is attached; but it is a manual method requiring +some trial and error. If the colorbar is too tall (or a horizontal +colorbar is too wide) use a smaller value of *shrink*. + +For more precise control, you can manually specify the positions of +the axes objects in which the mappable and the colorbar are drawn. In +this case, do not use any of the axes properties kwargs. + +It is known that some vector graphics viewer (svg and pdf) renders white gaps +between segments of the colorbar. This is due to bugs in the viewers not +matplotlib. As a workaround the colorbar can be rendered with overlapping +segments:: + + cbar = colorbar() + cbar.solids.set_edgecolor("face") + draw() + +However this has negative consequences in other circumstances. Particularly with +semi transparent images (alpha < 1) and colorbar extensions and is not enabled +by default see (issue #1188). + +returns: + :class:`~matplotlib.colorbar.Colorbar` instance; see also its base class, + :class:`~matplotlib.colorbar.ColorbarBase`. Call the + :meth:`~matplotlib.colorbar.ColorbarBase.set_label` method + to label the colorbar. + + +The transData of the *cax* is adjusted so that the limits in the +longest axis actually corresponds to the limits in colorbar range. On +the other hand, the shortest axis has a data limits of [1,2], whose +unconventional value is to prevent underflow when log scale is used. +''' % (make_axes_kw_doc, colormap_kw_doc) + +#docstring.interpd.update(colorbar_doc=colorbar_doc) + + +class CbarAxesLocator(object): + """ + CbarAxesLocator is a axes_locator for colorbar axes. It adjust the + position of the axes to make a room for extended ends, i.e., the + extended ends are located outside the axes area. + """ + + def __init__(self, locator=None, extend="neither", orientation="vertical"): + """ + *locator* : the bbox returned from the locator is used as a + initial axes location. If None, axes.bbox is used. + + *extend* : same as in ColorbarBase + *orientation* : same as in ColorbarBase + + """ + self._locator = locator + self.extesion_fraction = 0.05 + self.extend = extend + self.orientation = orientation + + def get_original_position(self, axes, renderer): + """ + get the original position of the axes. + """ + if self._locator is None: + bbox = axes.get_position(original=True) + else: + bbox = self._locator(axes, renderer) + return bbox + + def get_end_vertices(self): + """ + return a tuple of two vertices for the colorbar extended ends. + The first vertices is for the minimum end, and the second is for + the maximum end. + """ + # Note that concatenating two vertices needs to make a + # vertices for the frame. + extesion_fraction = self.extesion_fraction + + corx = extesion_fraction*2. + cory = 1./(1. - corx) + x1, y1, w, h = 0, 0, 1, 1 + x2, y2 = x1 + w, y1 + h + dw, dh = w*extesion_fraction, h*extesion_fraction*cory + + if self.extend in ["min", "both"]: + bottom = [(x1, y1), + (x1+w/2., y1-dh), + (x2, y1)] + else: + bottom = [(x1, y1), + (x2, y1)] + + if self.extend in ["max", "both"]: + top = [(x2, y2), + (x1+w/2., y2+dh), + (x1, y2)] + else: + top = [(x2, y2), + (x1, y2)] + + if self.orientation == "horizontal": + bottom = [(y,x) for (x,y) in bottom] + top = [(y,x) for (x,y) in top] + + return bottom, top + + + def get_path_patch(self): + """ + get the path for axes patch + """ + end1, end2 = self.get_end_vertices() + + verts = [] + end1 + end2 + end1[:1] + + return Path(verts) + + + def get_path_ends(self): + """ + get the paths for extended ends + """ + + end1, end2 = self.get_end_vertices() + + return Path(end1), Path(end2) + + + def __call__(self, axes, renderer): + """ + Return the adjusted position of the axes + """ + bbox0 = self.get_original_position(axes, renderer) + bbox = bbox0 + + x1, y1, w, h = bbox.bounds + extesion_fraction = self.extesion_fraction + dw, dh = w*extesion_fraction, h*extesion_fraction + + if self.extend in ["min", "both"]: + if self.orientation == "horizontal": + x1 = x1 + dw + else: + y1 = y1+dh + + if self.extend in ["max", "both"]: + if self.orientation == "horizontal": + w = w-2*dw + else: + h = h-2*dh + + return Bbox.from_bounds(x1, y1, w, h) + + + +class ColorbarBase(cm.ScalarMappable): + ''' + Draw a colorbar in an existing axes. + + This is a base class for the :class:`Colorbar` class, which is the + basis for the :func:`~matplotlib.pyplot.colorbar` method and pylab + function. + + It is also useful by itself for showing a colormap. If the *cmap* + kwarg is given but *boundaries* and *values* are left as None, + then the colormap will be displayed on a 0-1 scale. To show the + under- and over-value colors, specify the *norm* as:: + + colors.Normalize(clip=False) + + To show the colors versus index instead of on the 0-1 scale, + use:: + + norm=colors.NoNorm. + + Useful attributes: + + :attr:`ax` + the Axes instance in which the colorbar is drawn + + :attr:`lines` + a LineCollection if lines were drawn, otherwise None + + :attr:`dividers` + a LineCollection if *drawedges* is True, otherwise None + + Useful public methods are :meth:`set_label` and :meth:`add_lines`. + + ''' + + def __init__(self, ax, cmap=None, + norm=None, + alpha=1.0, + values=None, + boundaries=None, + orientation='vertical', + extend='neither', + spacing='uniform', # uniform or proportional + ticks=None, + format=None, + drawedges=False, + filled=True, + ): + self.ax = ax + + if cmap is None: cmap = cm.get_cmap() + if norm is None: norm = colors.Normalize() + self.alpha = alpha + cm.ScalarMappable.__init__(self, cmap=cmap, norm=norm) + self.values = values + self.boundaries = boundaries + self.extend = extend + self.spacing = spacing + self.orientation = orientation + self.drawedges = drawedges + self.filled = filled + + # artists + self.solids = None + self.lines = None + self.dividers = None + self.extension_patch1 = None + self.extension_patch2 = None + + if orientation == "vertical": + self.cbar_axis = self.ax.yaxis + else: + self.cbar_axis = self.ax.xaxis + + + if format is None: + if isinstance(self.norm, colors.LogNorm): + # change both axis for proper aspect + self.ax.set_xscale("log") + self.ax.set_yscale("log") + self.cbar_axis.set_minor_locator(ticker.NullLocator()) + formatter = ticker.LogFormatter() + else: + formatter = None + elif isinstance(format, six.string_types): + formatter = ticker.FormatStrFormatter(format) + else: + formatter = format # Assume it is a Formatter + + if formatter is None: + formatter = self.cbar_axis.get_major_formatter() + else: + self.cbar_axis.set_major_formatter(formatter) + + if cbook.iterable(ticks): + self.cbar_axis.set_ticks(ticks) + elif ticks is not None: + self.cbar_axis.set_major_locator(ticks) + else: + self._select_locator(formatter) + + + self._config_axes() + + self.update_artists() + + self.set_label_text('') + + + def _get_colorbar_limits(self): + """ + initial limits for colorbar range. The returned min, max values + will be used to create colorbar solid(?) and etc. + """ + if self.boundaries is not None: + C = self.boundaries + if self.extend in ["min", "both"]: + C = C[1:] + + if self.extend in ["max", "both"]: + C = C[:-1] + return min(C), max(C) + else: + return self.get_clim() + + + def _config_axes(self): + ''' + Adjust the properties of the axes to be adequate for colorbar display. + ''' + ax = self.ax + + axes_locator = CbarAxesLocator(ax.get_axes_locator(), + extend=self.extend, + orientation=self.orientation) + ax.set_axes_locator(axes_locator) + + # override the get_data_ratio for the aspect works. + def _f(): + return 1. + ax.get_data_ratio = _f + ax.get_data_ratio_log = _f + + ax.set_frame_on(True) + ax.set_navigate(False) + + self.ax.set_autoscalex_on(False) + self.ax.set_autoscaley_on(False) + + if self.orientation == 'horizontal': + ax.xaxis.set_label_position('bottom') + ax.set_yticks([]) + else: + ax.set_xticks([]) + ax.yaxis.set_label_position('right') + ax.yaxis.set_ticks_position('right') + + + + def update_artists(self): + """ + Update the colorbar associated artists, *filled* and + *ends*. Note that *lines* are not updated. This needs to be + called whenever clim of associated image changes. + """ + self._process_values() + self._add_ends() + + X, Y = self._mesh() + if self.filled: + C = self._values[:,np.newaxis] + self._add_solids(X, Y, C) + + ax = self.ax + vmin, vmax = self._get_colorbar_limits() + if self.orientation == 'horizontal': + ax.set_ylim(1, 2) + ax.set_xlim(vmin, vmax) + else: + ax.set_xlim(1, 2) + ax.set_ylim(vmin, vmax) + + + def _add_ends(self): + """ + Create patches from extended ends and add them to the axes. + """ + + del self.extension_patch1 + del self.extension_patch2 + + path1, path2 = self.ax.get_axes_locator().get_path_ends() + fc=mpl.rcParams['axes.facecolor'] + ec=mpl.rcParams['axes.edgecolor'] + linewidths=0.5*mpl.rcParams['axes.linewidth'] + self.extension_patch1 = PathPatch(path1, + fc=fc, ec=ec, lw=linewidths, + zorder=2., + transform=self.ax.transAxes, + clip_on=False) + self.extension_patch2 = PathPatch(path2, + fc=fc, ec=ec, lw=linewidths, + zorder=2., + transform=self.ax.transAxes, + clip_on=False) + self.ax.add_artist(self.extension_patch1) + self.ax.add_artist(self.extension_patch2) + + + + def _set_label_text(self): + """ + set label. + """ + self.cbar_axis.set_label_text(self._label, **self._labelkw) + + def set_label_text(self, label, **kw): + ''' + Label the long axis of the colorbar + ''' + self._label = label + self._labelkw = kw + self._set_label_text() + + + def _edges(self, X, Y): + ''' + Return the separator line segments; helper for _add_solids. + ''' + N = X.shape[0] + # Using the non-array form of these line segments is much + # simpler than making them into arrays. + if self.orientation == 'vertical': + return [list(zip(X[i], Y[i])) for i in xrange(1, N-1)] + else: + return [list(zip(Y[i], X[i])) for i in xrange(1, N-1)] + + def _add_solids(self, X, Y, C): + ''' + Draw the colors using :meth:`~matplotlib.axes.Axes.pcolormesh`; + optionally add separators. + ''' + ## Change to pcolorfast after fixing bugs in some backends... + + if self.extend in ["min", "both"]: + cc = self.to_rgba([C[0][0]]) + self.extension_patch1.set_fc(cc[0]) + X, Y, C = X[1:], Y[1:], C[1:] + + if self.extend in ["max", "both"]: + cc = self.to_rgba([C[-1][0]]) + self.extension_patch2.set_fc(cc[0]) + X, Y, C = X[:-1], Y[:-1], C[:-1] + + if self.orientation == 'vertical': + args = (X, Y, C) + else: + args = (np.transpose(Y), np.transpose(X), np.transpose(C)) + kw = {'cmap':self.cmap, 'norm':self.norm, + 'shading':'flat', 'alpha':self.alpha, + } + + del self.solids + del self.dividers + + col = self.ax.pcolormesh(*args, **kw) + + self.solids = col + if self.drawedges: + self.dividers = collections.LineCollection(self._edges(X,Y), + colors=(mpl.rcParams['axes.edgecolor'],), + linewidths=(0.5*mpl.rcParams['axes.linewidth'],), + ) + self.ax.add_collection(self.dividers) + else: + self.dividers = None + + def add_lines(self, levels, colors, linewidths): + ''' + Draw lines on the colorbar. It deletes preexisting lines. + ''' + del self.lines + + N = len(levels) + x = np.array([1.0, 2.0]) + X, Y = np.meshgrid(x,levels) + if self.orientation == 'vertical': + xy = [list(zip(X[i], Y[i])) for i in xrange(N)] + else: + xy = [list(zip(Y[i], X[i])) for i in xrange(N)] + col = collections.LineCollection(xy, linewidths=linewidths, + ) + self.lines = col + col.set_color(colors) + self.ax.add_collection(col) + + + def _select_locator(self, formatter): + ''' + select a suitable locator + ''' + if self.boundaries is None: + if isinstance(self.norm, colors.NoNorm): + nv = len(self._values) + base = 1 + int(nv/10) + locator = ticker.IndexLocator(base=base, offset=0) + elif isinstance(self.norm, colors.BoundaryNorm): + b = self.norm.boundaries + locator = ticker.FixedLocator(b, nbins=10) + elif isinstance(self.norm, colors.LogNorm): + locator = ticker.LogLocator() + else: + locator = ticker.MaxNLocator(nbins=5) + else: + b = self._boundaries[self._inside] + locator = ticker.FixedLocator(b) #, nbins=10) + + self.cbar_axis.set_major_locator(locator) + + + def _process_values(self, b=None): + ''' + Set the :attr:`_boundaries` and :attr:`_values` attributes + based on the input boundaries and values. Input boundaries + can be *self.boundaries* or the argument *b*. + ''' + if b is None: + b = self.boundaries + if b is not None: + self._boundaries = np.asarray(b, dtype=float) + if self.values is None: + self._values = 0.5*(self._boundaries[:-1] + + self._boundaries[1:]) + if isinstance(self.norm, colors.NoNorm): + self._values = (self._values + 0.00001).astype(np.int16) + return + self._values = np.array(self.values) + return + if self.values is not None: + self._values = np.array(self.values) + if self.boundaries is None: + b = np.zeros(len(self.values)+1, 'd') + b[1:-1] = 0.5*(self._values[:-1] - self._values[1:]) + b[0] = 2.0*b[1] - b[2] + b[-1] = 2.0*b[-2] - b[-3] + self._boundaries = b + return + self._boundaries = np.array(self.boundaries) + return + # Neither boundaries nor values are specified; + # make reasonable ones based on cmap and norm. + if isinstance(self.norm, colors.NoNorm): + b = self._uniform_y(self.cmap.N+1) * self.cmap.N - 0.5 + v = np.zeros((len(b)-1,), dtype=np.int16) + v = np.arange(self.cmap.N, dtype=np.int16) + self._boundaries = b + self._values = v + return + elif isinstance(self.norm, colors.BoundaryNorm): + b = np.array(self.norm.boundaries) + v = np.zeros((len(b)-1,), dtype=float) + bi = self.norm.boundaries + v = 0.5*(bi[:-1] + bi[1:]) + self._boundaries = b + self._values = v + return + else: + b = self._uniform_y(self.cmap.N+1) + + self._process_values(b) + + + def _uniform_y(self, N): + ''' + Return colorbar data coordinates for *N* uniformly + spaced boundaries. + ''' + vmin, vmax = self._get_colorbar_limits() + if isinstance(self.norm, colors.LogNorm): + y = np.logspace(np.log10(vmin), np.log10(vmax), N) + else: + y = np.linspace(vmin, vmax, N) + return y + + def _mesh(self): + ''' + Return X,Y, the coordinate arrays for the colorbar pcolormesh. + These are suitable for a vertical colorbar; swapping and + transposition for a horizontal colorbar are done outside + this function. + ''' + x = np.array([1.0, 2.0]) + if self.spacing == 'uniform': + y = self._uniform_y(len(self._boundaries)) + else: + y = self._boundaries + self._y = y + + X, Y = np.meshgrid(x,y) + return X, Y + + + def set_alpha(self, alpha): + """ + set alpha value. + """ + self.alpha = alpha + + +class Colorbar(ColorbarBase): + def __init__(self, ax, mappable, **kw): + mappable.autoscale_None() # Ensure mappable.norm.vmin, vmax + # are set when colorbar is called, + # even if mappable.draw has not yet + # been called. This will not change + # vmin, vmax if they are already set. + self.mappable = mappable + kw['cmap'] = mappable.cmap + kw['norm'] = mappable.norm + kw['alpha'] = mappable.get_alpha() + if isinstance(mappable, contour.ContourSet): + CS = mappable + kw['boundaries'] = CS._levels + kw['values'] = CS.cvalues + kw['extend'] = CS.extend + #kw['ticks'] = CS._levels + kw.setdefault('ticks', ticker.FixedLocator(CS.levels, nbins=10)) + kw['filled'] = CS.filled + ColorbarBase.__init__(self, ax, **kw) + if not CS.filled: + self.add_lines(CS) + else: + ColorbarBase.__init__(self, ax, **kw) + + + def add_lines(self, CS): + ''' + Add the lines from a non-filled + :class:`~matplotlib.contour.ContourSet` to the colorbar. + ''' + if not isinstance(CS, contour.ContourSet) or CS.filled: + raise ValueError('add_lines is only for a ContourSet of lines') + tcolors = [c[0] for c in CS.tcolors] + tlinewidths = [t[0] for t in CS.tlinewidths] + # The following was an attempt to get the colorbar lines + # to follow subsequent changes in the contour lines, + # but more work is needed: specifically, a careful + # look at event sequences, and at how + # to make one object track another automatically. + #tcolors = [col.get_colors()[0] for col in CS.collections] + #tlinewidths = [col.get_linewidth()[0] for lw in CS.collections] + ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths) + + def update_bruteforce(self, mappable): + """ + Update the colorbar artists to reflect the change of the + associated mappable. + """ + self.update_artists() + + if isinstance(mappable, contour.ContourSet): + if not mappable.filled: + self.add_lines(mappable) + +@docstring.Substitution(make_axes_kw_doc) +def make_axes(parent, **kw): + ''' + Resize and reposition a parent axes, and return a child + axes suitable for a colorbar + + :: + + cax, kw = make_axes(parent, **kw) + + Keyword arguments may include the following (with defaults): + + *orientation* + 'vertical' or 'horizontal' + + %s + + All but the first of these are stripped from the input kw set. + + Returns (cax, kw), the child axes and the reduced kw dictionary. + ''' + orientation = kw.setdefault('orientation', 'vertical') + fraction = kw.pop('fraction', 0.15) + shrink = kw.pop('shrink', 1.0) + aspect = kw.pop('aspect', 20) + #pb = transforms.PBox(parent.get_position()) + pb = parent.get_position(original=True).frozen() + if orientation == 'vertical': + pad = kw.pop('pad', 0.05) + x1 = 1.0-fraction + pb1, pbx, pbcb = pb.splitx(x1-pad, x1) + pbcb = pbcb.shrunk(1.0, shrink).anchored('C', pbcb) + anchor = (0.0, 0.5) + panchor = (1.0, 0.5) + else: + pad = kw.pop('pad', 0.15) + pbcb, pbx, pb1 = pb.splity(fraction, fraction+pad) + pbcb = pbcb.shrunk(shrink, 1.0).anchored('C', pbcb) + aspect = 1.0/aspect + anchor = (0.5, 1.0) + panchor = (0.5, 0.0) + parent.set_position(pb1) + parent.set_anchor(panchor) + fig = parent.get_figure() + cax = fig.add_axes(pbcb) + cax.set_aspect(aspect, anchor=anchor, adjustable='box') + return cax, kw + +@docstring.Substitution(colorbar_doc) +def colorbar(mappable, cax=None, ax=None, **kw): + """ + Create a colorbar for a ScalarMappable instance. + + Documentation for the pylab thin wrapper: + + %s + """ + import matplotlib.pyplot as plt + if ax is None: + ax = plt.gca() + if cax is None: + cax, kw = make_axes(ax, **kw) + cax._hold = True + cb = Colorbar(cax, mappable, **kw) + + def on_changed(m): + cb.set_cmap(m.get_cmap()) + cb.set_clim(m.get_clim()) + cb.update_bruteforce(m) + + cbid = mappable.callbacksSM.connect('changed', on_changed) + mappable.colorbar = cb + ax.figure.sca(ax) + return cb diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/inset_locator.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/inset_locator.py new file mode 100644 index 0000000000..9aeedcb088 --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/inset_locator.py @@ -0,0 +1,659 @@ +""" +A collection of functions and objects for creating or placing inset axes. +""" +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import warnings +from matplotlib import docstring +import six +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 + + +class InsetPosition(object): + @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's 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(ax, [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(AnchoredLocatorBase, self).__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): + self.axes = ax + + fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) + self._update_offset_func(renderer, fontsize) + + width, height, xdescent, ydescent = self.get_extent(renderer) + + px, py = self.get_offset(width, height, 0, 0, renderer) + bbox_canvas = Bbox.from_bounds(px, py, width, height) + tr = ax.figure.transFigure.inverted() + bb = TransformedBbox(bbox_canvas, tr) + + return bb + + +class AnchoredSizeLocator(AnchoredLocatorBase): + def __init__(self, bbox_to_anchor, x_size, y_size, loc, + borderpad=0.5, bbox_transform=None): + + super(AnchoredSizeLocator, self).__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_extent(self, renderer): + x, y, w, h = self.get_bbox_to_anchor().bounds + + dpi = renderer.points_to_pixels(72.) + + r, a = self.x_size.get_size(renderer) + width = w * r + a * dpi + + r, a = self.y_size.get_size(renderer) + height = h * r + a * dpi + xd, yd = 0, 0 + + fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) + pad = self.pad * fontsize + + return width + 2 * pad, height + 2 * pad, xd + pad, yd + 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(AnchoredZoomLocator, self).__init__( + bbox_to_anchor, None, loc, borderpad=borderpad, + bbox_transform=bbox_transform) + + def get_extent(self, renderer): + bb = TransformedBbox(self.axes.viewLim, + self.parent_axes.transData) + + x, y, w, h = bb.bounds + fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) + pad = self.pad * fontsize + + return abs(w * self.zoom) + 2 * pad, abs(h * self.zoom) + 2 * pad, pad, 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)s + """ + if "transform" in kwargs: + raise ValueError("transform should not be set") + + kwargs["transform"] = IdentityTransform() + Patch.__init__(self, **kwargs) + self.bbox = bbox + + def get_path(self): + x0, y0, x1, y1 = self.bbox.extents + + verts = [(x0, y0), + (x1, y0), + (x1, y1), + (x0, y1), + (x0, y0), + (0, 0)] + + codes = [Path.MOVETO, + Path.LINETO, + Path.LINETO, + Path.LINETO, + Path.LINETO, + Path.CLOSEPOLY] + + return Path(verts, codes) + + get_path.__doc__ = Patch.get_path.__doc__ + + +class BboxConnector(Patch): + @staticmethod + def get_bbox_edge_pos(bbox, loc): + """ + Helper function to obtain the location of a corner of a bbox + + Parameters + ---------- + bbox : `matplotlib.transforms.Bbox` + + loc : {1, 2, 3, 4} + Corner of *bbox*. Valid values are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4 + + Returns + ------- + x, y : float + Coordinates of the corner specified by *loc*. + """ + 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): + """ + Helper function to obtain a Path from one bbox to another. + + Parameters + ---------- + bbox1, bbox2 : `matplotlib.transforms.Bbox` + Bounding boxes to connect. + + loc1 : {1, 2, 3, 4} + Corner of *bbox1* to use. Valid values are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4 + + loc2 : {1, 2, 3, 4}, optional + Corner of *bbox2* to use. If None, defaults to *loc1*. + Valid values are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4 + + Returns + ------- + path : `matplotlib.path.Path` + A line segment from the *loc1* corner of *bbox1* to the *loc2* + corner of *bbox2*. + """ + if isinstance(bbox1, Rectangle): + transform = bbox1.get_transfrom() + bbox1 = Bbox.from_bounds(0, 0, 1, 1) + bbox1 = TransformedBbox(bbox1, transform) + + if isinstance(bbox2, Rectangle): + transform = bbox2.get_transform() + bbox2 = Bbox.from_bounds(0, 0, 1, 1) + bbox2 = TransformedBbox(bbox2, 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) + + verts = [[x1, y1], [x2, y2]] + codes = [Path.MOVETO, Path.LINETO] + + return Path(verts, codes) + + @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 : {1, 2, 3, 4} + Corner of *bbox1* to draw the line. Valid values are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4 + + loc2 : {1, 2, 3, 4}, optional + Corner of *bbox2* to draw the line. If None, defaults to *loc1*. + Valid values are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4 + + **kwargs + Patch properties for the line drawn. Valid arguments include: + %(Patch)s + """ + if "transform" in kwargs: + raise ValueError("transform should not be set") + + kwargs["transform"] = IdentityTransform() + Patch.__init__(self, fill=False, **kwargs) + self.bbox1 = bbox1 + self.bbox2 = bbox2 + self.loc1 = loc1 + self.loc2 = loc2 + + def get_path(self): + return self.connect_bbox(self.bbox1, self.bbox2, + self.loc1, self.loc2) + + get_path.__doc__ = Patch.get_path.__doc__ + + +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 : {1, 2, 3, 4} + Corners of *bbox1* and *bbox2* to draw the first line. + Valid values are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4 + + loc1b, loc2b : {1, 2, 3, 4} + Corners of *bbox1* and *bbox2* to draw the second line. + Valid values are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4 + + **kwargs + Patch properties for the line drawn: + %(Patch)s + """ + if "transform" in kwargs: + raise ValueError("transform should not be set") + BboxConnector.__init__(self, bbox1, bbox2, loc1a, loc2a, **kwargs) + self.loc1b = loc1b + self.loc2b = loc2b + + def get_path(self): + 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 = (list(path1.vertices) + + list(path2.vertices) + + [path1.vertices[0]]) + return Path(path_merged) + + get_path.__doc__ = BboxConnector.get_path.__doc__ + + +def _add_inset_axes(parent_axes, inset_axes): + """Helper function to add an inset axes and disable navigation in it""" + parent_axes.figure.add_axes(inset_axes) + inset_axes.set_navigate(False) + + +@docstring.dedent_interpd +def inset_axes(parent_axes, width, height, loc=1, + 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=3) + + 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 + :ref:`the examples <sphx_glr_gallery_axes_grid1_inset_locator_demo.py>`. + + 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 : int or string, optional, default to 1 + Location to place the inset axes. The valid locations are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4, + 'right' : 5, + 'center left' : 6, + 'center right' : 7, + 'lower center' : 8, + 'upper center' : 9, + 'center' : 10 + + 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, optional + If specified, the inset axes created will be created with this class's + constructor. + + axes_kwargs : dict, optional + Keyworded arguments to pass to the constructor of the inset axes. + Valid arguments include: + %(Axes)s + + borderpad : float, optional + Padding between inset axes and the bbox_to_anchor. Defaults to 0.5. + 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 axes_class is None: + axes_class = HostAxes + + if axes_kwargs is None: + inset_axes = axes_class(parent_axes.figure, parent_axes.get_position()) + else: + inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(), + **axes_kwargs) + + if bbox_transform in [parent_axes.transAxes, + parent_axes.figure.transFigure]: + if bbox_to_anchor is None: + warnings.warn("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.") + + axes_locator = AnchoredSizeLocator(bbox_to_anchor, + width, height, + loc=loc, + bbox_transform=bbox_transform, + borderpad=borderpad) + + inset_axes.set_axes_locator(axes_locator) + + _add_inset_axes(parent_axes, inset_axes) + + return inset_axes + + +@docstring.dedent_interpd +def zoomed_inset_axes(parent_axes, zoom, loc=1, + 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 + :ref:`the examples <sphx_glr_gallery_axes_grid1_inset_locator_demo2.py>`. + + Parameters + ---------- + parent_axes : `matplotlib.axes.Axes` + Axes to place the inset axes. + + zoom : float + Scaling factor of the data axes. *zoom* > 1 will enlargen the + coordinates (i.e., "zoomed in"), while *zoom* < 1 will shrink the + coordinates (i.e., "zoomed out"). + + loc : int or string, optional, default to 1 + Location to place the inset axes. The valid locations are:: + + 'upper right' : 1, + 'upper left' : 2, + 'lower left' : 3, + 'lower right' : 4, + 'right' : 5, + 'center left' : 6, + 'center right' : 7, + 'lower center' : 8, + 'upper center' : 9, + 'center' : 10 + + 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, optional + If specified, the inset axes created will be created with this class's + constructor. + + axes_kwargs : dict, optional + Keyworded arguments to pass to the constructor of the inset axes. + Valid arguments include: + %(Axes)s + + borderpad : float, optional + Padding between inset axes and the bbox_to_anchor. Defaults to 0.5. + 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 axes_class is None: + axes_class = HostAxes + + if axes_kwargs is None: + inset_axes = axes_class(parent_axes.figure, parent_axes.get_position()) + else: + inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(), + **axes_kwargs) + + axes_locator = AnchoredZoomLocator(parent_axes, zoom=zoom, loc=loc, + bbox_to_anchor=bbox_to_anchor, + bbox_transform=bbox_transform, + borderpad=borderpad) + inset_axes.set_axes_locator(axes_locator) + + _add_inset_axes(parent_axes, inset_axes) + + return inset_axes + + +@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)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 = TransformedBbox(inset_axes.viewLim, parent_axes.transData) + + fill = kwargs.pop("fill", False) + pp = BboxPatch(rect, fill=fill, **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/py2/mpl_toolkits/axes_grid1/mpl_axes.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/mpl_axes.py new file mode 100644 index 0000000000..aaff7b7692 --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/mpl_axes.py @@ -0,0 +1,154 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +import matplotlib.axes as maxes +from matplotlib.artist import Artist +from matplotlib.axis import XAxis, YAxis + +class SimpleChainedObjects(object): + 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, *kl, **kwargs): + for m in self._objects: + m(*kl, **kwargs) + + +class Axes(maxes.Axes): + + class AxisDict(dict): + def __init__(self, axes): + self.axes = axes + super(Axes.AxisDict, self).__init__() + + def __getitem__(self, k): + if isinstance(k, tuple): + r = SimpleChainedObjects( + [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: + r = SimpleChainedObjects(list(six.itervalues(self))) + return r + else: + raise ValueError("Unsupported slice") + else: + return dict.__getitem__(self, k) + + def __call__(self, *v, **kwargs): + return maxes.Axes.axis(self.axes, *v, **kwargs) + + def __init__(self, *kl, **kw): + super(Axes, self).__init__(*kl, **kw) + + def _init_axis_artists(self, axes=None): + if axes is None: + axes = self + + self._axislines = self.AxisDict(self) + + self._axislines["bottom"] = SimpleAxisArtist(self.xaxis, 1, self.spines["bottom"]) + self._axislines["top"] = SimpleAxisArtist(self.xaxis, 2, self.spines["top"]) + self._axislines["left"] = SimpleAxisArtist(self.yaxis, 1, self.spines["left"]) + self._axislines["right"] = SimpleAxisArtist(self.yaxis, 2, self.spines["right"]) + + + def _get_axislines(self): + return self._axislines + + axis = property(_get_axislines) + + def cla(self): + + super(Axes, self).cla() + self._init_axis_artists() + + +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("axis must be instance of XAxis or YAxis : %s is provided" % (axis,)) + Artist.__init__(self) + + + def _get_major_ticks(self): + tickline = "tick%dline" % self._axisnum + return SimpleChainedObjects([getattr(tick, tickline) + for tick in self._axis.get_major_ticks()]) + + def _get_major_ticklabels(self): + label = "label%d" % self._axisnum + return SimpleChainedObjects([getattr(tick, label) + for tick in self._axis.get_major_ticks()]) + + def _get_label(self): + return self._axis.label + + major_ticks = property(_get_major_ticks) + major_ticklabels = property(_get_major_ticklabels) + label = property(_get_label) + + def set_visible(self, b): + self.toggle(all=b) + self.line.set_visible(b) + self._axis.set_visible(True) + Artist.set_visible(self, 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 + + tickOn = "tick%dOn" % self._axisnum + labelOn = "label%dOn" % self._axisnum + + if _ticks is not None: + tickparam = {tickOn: _ticks} + self._axis.set_tick_params(**tickparam) + if _ticklabels is not None: + tickparam = {labelOn: _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) + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + fig = plt.figure() + ax = Axes(fig, [0.1, 0.1, 0.8, 0.8]) + fig.add_axes(ax) + ax.cla() diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/parasite_axes.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/parasite_axes.py new file mode 100644 index 0000000000..16a67b4d1f --- /dev/null +++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/parasite_axes.py @@ -0,0 +1,486 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +from matplotlib import ( + artist as martist, collections as mcoll, transforms as mtransforms, + rcParams) +from matplotlib.axes import subplot_class_factory +from matplotlib.transforms import Bbox +from .mpl_axes import Axes + +import numpy as np + + +class ParasiteAxesBase(object): + + def get_images_artists(self): + artists = {a for a in self.get_children() if a.get_visible()} + images = {a for a in self.images if a.get_visible()} + + return list(images), list(artists - images) + + def __init__(self, parent_axes, **kargs): + + self._parent_axes = parent_axes + kargs.update(dict(frameon=False)) + self._get_base_axes_attr("__init__")(self, parent_axes.figure, + parent_axes._position, **kargs) + + def cla(self): + self._get_base_axes_attr("cla")(self) + + martist.setp(self.get_children(), visible=False) + self._get_lines = self._parent_axes._get_lines + + # In mpl's Axes, zorders of x- and y-axis are originally set + # within Axes.draw(). + if self._axisbelow: + self.xaxis.set_zorder(0.5) + self.yaxis.set_zorder(0.5) + else: + self.xaxis.set_zorder(2.5) + self.yaxis.set_zorder(2.5) + + +_parasite_axes_classes = {} +def parasite_axes_class_factory(axes_class=None): + if axes_class is None: + axes_class = Axes + + new_class = _parasite_axes_classes.get(axes_class) + if new_class is None: + def _get_base_axes_attr(self, attrname): + return getattr(axes_class, attrname) + + new_class = type(str("%sParasite" % (axes_class.__name__)), + (ParasiteAxesBase, axes_class), + {'_get_base_axes_attr': _get_base_axes_attr}) + _parasite_axes_classes[axes_class] = new_class + + return new_class + +ParasiteAxes = parasite_axes_class_factory() + +# #class ParasiteAxes(ParasiteAxesBase, Axes): + +# @classmethod +# def _get_base_axes_attr(cls, attrname): +# return getattr(Axes, attrname) + + + +class ParasiteAxesAuxTransBase(object): + def __init__(self, parent_axes, aux_transform, viewlim_mode=None, + **kwargs): + + self.transAux = aux_transform + self.set_viewlim_mode(viewlim_mode) + + self._parasite_axes_class.__init__(self, parent_axes, **kwargs) + + def _set_lim_and_transforms(self): + + 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) + + def set_viewlim_mode(self, mode): + if mode not in [None, "equal", "transform"]: + raise ValueError("Unknown mode : %s" % (mode,)) + else: + self._viewlim_mode = mode + + def get_viewlim_mode(self): + return self._viewlim_mode + + + def update_viewlim(self): + viewlim = self._parent_axes.viewLim.frozen() + mode = self.get_viewlim_mode() + if mode is None: + pass + elif mode == "equal": + self.axes.viewLim.set(viewlim) + elif mode == "transform": + self.axes.viewLim.set(viewlim.transformed(self.transAux.inverted())) + else: + raise ValueError("Unknown mode : %s" % (self._viewlim_mode,)) + + + def _pcolor(self, method_name, *XYC, **kwargs): + if len(XYC) == 1: + C = XYC[0] + ny, nx = C.shape + + gx = np.arange(-0.5, nx, 1.) + gy = np.arange(-0.5, ny, 1.) + + X, Y = np.meshgrid(gx, gy) + else: + X, Y, C = XYC + + pcolor_routine = self._get_base_axes_attr(method_name) + + if "transform" in kwargs: + mesh = pcolor_routine(self, X, Y, C, **kwargs) + else: + orig_shape = X.shape + xy = np.vstack([X.flat, Y.flat]) + xyt=xy.transpose() + wxy = self.transAux.transform(xyt) + gx, gy = wxy[:,0].reshape(orig_shape), wxy[:,1].reshape(orig_shape) + mesh = pcolor_routine(self, gx, gy, C, **kwargs) + mesh.set_transform(self._parent_axes.transData) + + return mesh + + def pcolormesh(self, *XYC, **kwargs): + return self._pcolor("pcolormesh", *XYC, **kwargs) + + def pcolor(self, *XYC, **kwargs): + return self._pcolor("pcolor", *XYC, **kwargs) + + + def _contour(self, method_name, *XYCL, **kwargs): + + if len(XYCL) <= 2: + C = XYCL[0] + ny, nx = C.shape + + gx = np.arange(0., nx, 1.) + gy = np.arange(0., ny, 1.) + + X,Y = np.meshgrid(gx, gy) + CL = XYCL + else: + X, Y = XYCL[:2] + CL = XYCL[2:] + + contour_routine = self._get_base_axes_attr(method_name) + + if "transform" in kwargs: + cont = contour_routine(self, X, Y, *CL, **kwargs) + else: + orig_shape = X.shape + xy = np.vstack([X.flat, Y.flat]) + xyt=xy.transpose() + wxy = self.transAux.transform(xyt) + gx, gy = wxy[:,0].reshape(orig_shape), wxy[:,1].reshape(orig_shape) + cont = contour_routine(self, gx, gy, *CL, **kwargs) + for c in cont.collections: + c.set_transform(self._parent_axes.transData) + + return cont + + def contour(self, *XYCL, **kwargs): + return self._contour("contour", *XYCL, **kwargs) + + def contourf(self, *XYCL, **kwargs): + return self._contour("contourf", *XYCL, **kwargs) + + def apply_aspect(self, position=None): + self.update_viewlim() + self._get_base_axes_attr("apply_aspect")(self) + #ParasiteAxes.apply_aspect() + + + +_parasite_axes_auxtrans_classes = {} +def parasite_axes_auxtrans_class_factory(axes_class=None): + if axes_class is None: + parasite_axes_class = ParasiteAxes + elif not issubclass(axes_class, ParasiteAxesBase): + parasite_axes_class = parasite_axes_class_factory(axes_class) + else: + parasite_axes_class = axes_class + + new_class = _parasite_axes_auxtrans_classes.get(parasite_axes_class) + if new_class is None: + new_class = type(str("%sParasiteAuxTrans" % (parasite_axes_class.__name__)), + (ParasiteAxesAuxTransBase, parasite_axes_class), + {'_parasite_axes_class': parasite_axes_class, + 'name': 'parasite_axes'}) + _parasite_axes_auxtrans_classes[parasite_axes_class] = new_class + + return new_class + + +ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(axes_class=ParasiteAxes) + + + + +def _get_handles(ax): + handles = ax.lines[:] + handles.extend(ax.patches) + handles.extend([c for c in ax.collections + if isinstance(c, mcoll.LineCollection)]) + handles.extend([c for c in ax.collections + if isinstance(c, mcoll.RegularPolyCollection)]) + handles.extend([c for c in ax.collections + if isinstance(c, mcoll.CircleCollection)]) + + return handles + + +class HostAxesBase(object): + def __init__(self, *args, **kwargs): + + self.parasites = [] + self._get_base_axes_attr("__init__")(self, *args, **kwargs) + + + def get_aux_axes(self, tr, viewlim_mode="equal", axes_class=None): + parasite_axes_class = parasite_axes_auxtrans_class_factory(axes_class) + ax2 = parasite_axes_class(self, tr, viewlim_mode) + # note that ax2.transData == tr + ax1.transData + # Anthing you draw in ax2 will match the ticks and grids of ax1. + self.parasites.append(ax2) + ax2._remove_method = lambda h: self.parasites.remove(h) + return ax2 + + def _get_legend_handles(self, legend_handler_map=None): + # don't use this! + Axes_get_legend_handles = self._get_base_axes_attr("_get_legend_handles") + all_handles = list(Axes_get_legend_handles(self, legend_handler_map)) + + for ax in self.parasites: + all_handles.extend(ax._get_legend_handles(legend_handler_map)) + + return all_handles + + + def draw(self, renderer): + + orig_artists = list(self.artists) + orig_images = list(self.images) + + if hasattr(self, "get_axes_locator"): + 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() + else: + self.apply_aspect() + + rect = self.get_position() + + for ax in self.parasites: + ax.apply_aspect(rect) + images, artists = ax.get_images_artists() + self.images.extend(images) + self.artists.extend(artists) + + self._get_base_axes_attr("draw")(self, renderer) + self.artists = orig_artists + self.images = orig_images + + + def cla(self): + + for ax in self.parasites: + ax.cla() + + self._get_base_axes_attr("cla")(self) + #super(HostAxes, self).cla() + + + def twinx(self, axes_class=None): + """ + create a twin of Axes for generating a plot with a sharex + x-axis but independent y axis. The y-axis of self will have + ticks on left and the returned axes will have ticks on the + right + """ + + if axes_class is None: + axes_class = self._get_base_axes() + + parasite_axes_class = parasite_axes_class_factory(axes_class) + + ax2 = parasite_axes_class(self, sharex=self, frameon=False) + self.parasites.append(ax2) + + self.axis["right"].set_visible(False) + + ax2.axis["right"].set_visible(True) + ax2.axis["left", "top", "bottom"].set_visible(False) + + def _remove_method(h): + self.parasites.remove(h) + self.axis["right"].set_visible(True) + self.axis["right"].toggle(ticklabels=False, label=False) + ax2._remove_method = _remove_method + + return ax2 + + def twiny(self, axes_class=None): + """ + create a twin of Axes for generating a plot with a shared + y-axis but independent x axis. The x-axis of self will have + ticks on bottom and the returned axes will have ticks on the + top + """ + + if axes_class is None: + axes_class = self._get_base_axes() + + parasite_axes_class = parasite_axes_class_factory(axes_class) + + ax2 = parasite_axes_class(self, sharey=self, frameon=False) + self.parasites.append(ax2) + + self.axis["top"].set_visible(False) + + ax2.axis["top"].set_visible(True) + ax2.axis["left", "right", "bottom"].set_visible(False) + + def _remove_method(h): + self.parasites.remove(h) + self.axis["top"].set_visible(True) + self.axis["top"].toggle(ticklabels=False, label=False) + ax2._remove_method = _remove_method + + return ax2 + + + def twin(self, aux_trans=None, axes_class=None): + """ + create a twin of Axes for generating a plot with a sharex + x-axis but independent y axis. The y-axis of self will have + ticks on left and the returned axes will have ticks on the + right + """ + + if axes_class is None: + axes_class = self._get_base_axes() + + parasite_axes_auxtrans_class = parasite_axes_auxtrans_class_factory(axes_class) + + if aux_trans is None: + ax2 = parasite_axes_auxtrans_class(self, mtransforms.IdentityTransform(), + viewlim_mode="equal", + ) + else: + ax2 = parasite_axes_auxtrans_class(self, aux_trans, + viewlim_mode="transform", + ) + self.parasites.append(ax2) + ax2._remove_method = lambda h: self.parasites.remove(h) + + self.axis["top", "right"].set_visible(False) + + ax2.axis["top", "right"].set_visible(True) + ax2.axis["left", "bottom"].set_visible(False) + + def _remove_method(h): + self.parasites.remove(h) + self.axis["top", "right"].set_visible(True) + self.axis["top", "right"].toggle(ticklabels=False, label=False) + ax2._remove_method = _remove_method + + return ax2 + + def get_tightbbox(self, renderer, call_axes_locator=True): + + bbs = [ax.get_tightbbox(renderer, call_axes_locator) + for ax in self.parasites] + get_tightbbox = self._get_base_axes_attr("get_tightbbox") + bbs.append(get_tightbbox(self, renderer, call_axes_locator)) + + _bbox = Bbox.union([b for b in bbs if b.width!=0 or b.height!=0]) + + return _bbox + + + +_host_axes_classes = {} +def host_axes_class_factory(axes_class=None): + if axes_class is None: + axes_class = Axes + + new_class = _host_axes_classes.get(axes_class) + if new_class is None: + def _get_base_axes(self): + return axes_class + + def _get_base_axes_attr(self, attrname): + return getattr(axes_class, attrname) + + new_class = type(str("%sHostAxes" % (axes_class.__name__)), + (HostAxesBase, axes_class), + {'_get_base_axes_attr': _get_base_axes_attr, + '_get_base_axes': _get_base_axes}) + + _host_axes_classes[axes_class] = new_class + + return new_class + +def host_subplot_class_factory(axes_class): + host_axes_class = host_axes_class_factory(axes_class=axes_class) + subplot_host_class = subplot_class_factory(host_axes_class) + return subplot_host_class + +HostAxes = host_axes_class_factory(axes_class=Axes) +SubplotHost = subplot_class_factory(HostAxes) + + +def host_axes(*args, **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`` object creation. + """ + import matplotlib.pyplot as plt + axes_class = kwargs.pop("axes_class", None) + host_axes_class = host_axes_class_factory(axes_class) + fig = kwargs.get("figure", None) + if fig is None: + fig = plt.gcf() + ax = host_axes_class(fig, *args, **kwargs) + fig.add_axes(ax) + plt.draw_if_interactive() + return ax + +def host_subplot(*args, **kwargs): + """ + Create a subplot that can act as a host to parasitic axes. + + Parameters + ---------- + figure : `matplotlib.figure.Figure` + Figure to which the subplot will be added. Defaults to the current + figure `pyplot.gcf()`. + + *args, **kwargs : + Will be passed on to the underlying ``Axes`` object creation. + """ + import matplotlib.pyplot as plt + axes_class = kwargs.pop("axes_class", None) + host_subplot_class = host_subplot_class_factory(axes_class) + fig = kwargs.get("figure", None) + if fig is None: + fig = plt.gcf() + ax = host_subplot_class(fig, *args, **kwargs) + fig.add_subplot(ax) + plt.draw_if_interactive() + return ax |