diff options
Diffstat (limited to 'contrib/python/matplotlib/py2/matplotlib/contour.py')
-rw-r--r-- | contrib/python/matplotlib/py2/matplotlib/contour.py | 1836 |
1 files changed, 1836 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py2/matplotlib/contour.py b/contrib/python/matplotlib/py2/matplotlib/contour.py new file mode 100644 index 0000000000..f6fdfd61c2 --- /dev/null +++ b/contrib/python/matplotlib/py2/matplotlib/contour.py @@ -0,0 +1,1836 @@ +""" +These are classes to support contour plotting and labelling for the Axes class. +""" +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six +from six.moves import xrange + +import warnings +import matplotlib as mpl +import numpy as np +from numpy import ma +import matplotlib._contour as _contour +import matplotlib.path as mpath +import matplotlib.ticker as ticker +import matplotlib.cm as cm +import matplotlib.colors as colors +import matplotlib.collections as mcoll +import matplotlib.font_manager as font_manager +import matplotlib.text as text +import matplotlib.cbook as cbook +import matplotlib.mathtext as mathtext +import matplotlib.patches as mpatches +import matplotlib.texmanager as texmanager +import matplotlib.transforms as mtransforms + +# Import needed for adding manual selection capability to clabel +from matplotlib.blocking_input import BlockingContourLabeler + +# We can't use a single line collection for contour because a line +# collection can have only a single line style, and we want to be able to have +# dashed negative contours, for example, and solid positive contours. +# We could use a single polygon collection for filled contours, but it +# seems better to keep line and filled contours similar, with one collection +# per level. + + +class ClabelText(text.Text): + """ + Unlike the ordinary text, the get_rotation returns an updated + angle in the pixel coordinate assuming that the input rotation is + an angle in data coordinate (or whatever transform set). + """ + def get_rotation(self): + angle = text.Text.get_rotation(self) + trans = self.get_transform() + x, y = self.get_position() + new_angles = trans.transform_angles(np.array([angle]), + np.array([[x, y]])) + return new_angles[0] + + +class ContourLabeler(object): + """Mixin to provide labelling capability to `.ContourSet`.""" + + def clabel(self, *args, **kwargs): + """ + Label a contour plot. + + Call signature:: + + clabel(cs, **kwargs) + + Adds labels to line contours in *cs*, where *cs* is a + :class:`~matplotlib.contour.ContourSet` object returned by + contour. + + :: + + clabel(cs, v, **kwargs) + + only labels contours listed in *v*. + + Parameters + ---------- + fontsize : string or float, optional + Size in points or relative size e.g., 'smaller', 'x-large'. + See `Text.set_size` for accepted string values. + + colors : + Color of each label + + - if *None*, the color of each label matches the color of + the corresponding contour + + - if one string color, e.g., *colors* = 'r' or *colors* = + 'red', all labels will be plotted in this color + + - if a tuple of matplotlib color args (string, float, rgb, etc), + different labels will be plotted in different colors in the order + specified + + inline : bool, optional + If ``True`` the underlying contour is removed where the label is + placed. Default is ``True``. + + inline_spacing : float, optional + Space in pixels to leave on each side of label when + placing inline. Defaults to 5. + + This spacing will be exact for labels at locations where the + contour is straight, less so for labels on curved contours. + + fmt : string or dict, optional + A format string for the label. Default is '%1.3f' + + Alternatively, this can be a dictionary matching contour + levels with arbitrary strings to use for each contour level + (i.e., fmt[level]=string), or it can be any callable, such + as a :class:`~matplotlib.ticker.Formatter` instance, that + returns a string when called with a numeric contour level. + + manual : bool or iterable, optional + If ``True``, contour labels will be placed manually using + mouse clicks. Click the first button near a contour to + add a label, click the second button (or potentially both + mouse buttons at once) to finish adding labels. The third + button can be used to remove the last label added, but + only if labels are not inline. Alternatively, the keyboard + can be used to select label locations (enter to end label + placement, delete or backspace act like the third mouse button, + and any other key will select a label location). + + *manual* can also be an iterable object of x,y tuples. + Contour labels will be created as if mouse is clicked at each + x,y positions. + + rightside_up : bool, optional + If ``True``, label rotations will always be plus + or minus 90 degrees from level. Default is ``True``. + + use_clabeltext : bool, optional + If ``True``, `ClabelText` class (instead of `Text`) is used to + create labels. `ClabelText` recalculates rotation angles + of texts during the drawing time, therefore this can be used if + aspect of the axes changes. Default is ``False``. + """ + + """ + NOTES on how this all works: + + clabel basically takes the input arguments and uses them to + add a list of "label specific" attributes to the ContourSet + object. These attributes are all of the form label* and names + should be fairly self explanatory. + + Once these attributes are set, clabel passes control to the + labels method (case of automatic label placement) or + `BlockingContourLabeler` (case of manual label placement). + """ + + fontsize = kwargs.get('fontsize', None) + inline = kwargs.get('inline', 1) + inline_spacing = kwargs.get('inline_spacing', 5) + self.labelFmt = kwargs.get('fmt', '%1.3f') + _colors = kwargs.get('colors', None) + + self._use_clabeltext = kwargs.get('use_clabeltext', False) + + # Detect if manual selection is desired and remove from argument list + self.labelManual = kwargs.get('manual', False) + + self.rightside_up = kwargs.get('rightside_up', True) + if len(args) == 0: + levels = self.levels + indices = list(xrange(len(self.cvalues))) + elif len(args) == 1: + levlabs = list(args[0]) + indices, levels = [], [] + for i, lev in enumerate(self.levels): + if lev in levlabs: + indices.append(i) + levels.append(lev) + if len(levels) < len(levlabs): + raise ValueError("Specified levels {} don't match available " + "levels {}".format(levlabs, self.levels)) + else: + raise TypeError("Illegal arguments to clabel, see help(clabel)") + self.labelLevelList = levels + self.labelIndiceList = indices + + self.labelFontProps = font_manager.FontProperties() + self.labelFontProps.set_size(fontsize) + font_size_pts = self.labelFontProps.get_size_in_points() + self.labelFontSizeList = [font_size_pts] * len(levels) + + if _colors is None: + self.labelMappable = self + self.labelCValueList = np.take(self.cvalues, self.labelIndiceList) + else: + cmap = colors.ListedColormap(_colors, N=len(self.labelLevelList)) + self.labelCValueList = list(xrange(len(self.labelLevelList))) + self.labelMappable = cm.ScalarMappable(cmap=cmap, + norm=colors.NoNorm()) + + self.labelXYs = [] + + if cbook.iterable(self.labelManual): + for x, y in self.labelManual: + self.add_label_near(x, y, inline, + inline_spacing) + + elif self.labelManual: + print('Select label locations manually using first mouse button.') + print('End manual selection with second mouse button.') + if not inline: + print('Remove last label by clicking third mouse button.') + + blocking_contour_labeler = BlockingContourLabeler(self) + blocking_contour_labeler(inline, inline_spacing) + else: + self.labels(inline, inline_spacing) + + # Hold on to some old attribute names. These are deprecated and will + # be removed in the near future (sometime after 2008-08-01), but + # keeping for now for backwards compatibility + self.cl = self.labelTexts + self.cl_xy = self.labelXYs + self.cl_cvalues = self.labelCValues + + self.labelTextsList = cbook.silent_list('text.Text', self.labelTexts) + return self.labelTextsList + + def print_label(self, linecontour, labelwidth): + "Return *False* if contours are too short for a label." + return (len(linecontour) > 10 * labelwidth + or (np.ptp(linecontour, axis=0) > 1.2 * labelwidth).any()) + + def too_close(self, x, y, lw): + "Return *True* if a label is already near this location." + for loc in self.labelXYs: + d = np.sqrt((x - loc[0]) ** 2 + (y - loc[1]) ** 2) + if d < 1.2 * lw: + return True + return False + + def get_label_coords(self, distances, XX, YY, ysize, lw): + """ + Return x, y, and the index of a label location. + + Labels are plotted at a location with the smallest + deviation of the contour from a straight line + unless there is another label nearby, in which case + the next best place on the contour is picked up. + If all such candidates are rejected, the beginning + of the contour is chosen. + """ + hysize = int(ysize / 2) + adist = np.argsort(distances) + + for ind in adist: + x, y = XX[ind][hysize], YY[ind][hysize] + if self.too_close(x, y, lw): + continue + return x, y, ind + + ind = adist[0] + x, y = XX[ind][hysize], YY[ind][hysize] + return x, y, ind + + def get_label_width(self, lev, fmt, fsize): + """ + Return the width of the label in points. + """ + if not isinstance(lev, six.string_types): + lev = self.get_text(lev, fmt) + + lev, ismath = text.Text.is_math_text(lev) + if ismath == 'TeX': + if not hasattr(self, '_TeX_manager'): + self._TeX_manager = texmanager.TexManager() + lw, _, _ = self._TeX_manager.get_text_width_height_descent(lev, + fsize) + elif ismath: + if not hasattr(self, '_mathtext_parser'): + self._mathtext_parser = mathtext.MathTextParser('bitmap') + img, _ = self._mathtext_parser.parse(lev, dpi=72, + prop=self.labelFontProps) + lw = img.get_width() # at dpi=72, the units are PostScript points + else: + # width is much less than "font size" + lw = (len(lev)) * fsize * 0.6 + + return lw + + @cbook.deprecated("2.2") + def get_real_label_width(self, lev, fmt, fsize): + """ + This computes actual onscreen label width. + This uses some black magic to determine onscreen extent of non-drawn + label. This magic may not be very robust. + + This method is not being used, and may be modified or removed. + """ + # Find middle of axes + xx = np.mean(np.asarray(self.ax.axis()).reshape(2, 2), axis=1) + + # Temporarily create text object + t = text.Text(xx[0], xx[1]) + self.set_label_props(t, self.get_text(lev, fmt), 'k') + + # Some black magic to get onscreen extent + # NOTE: This will only work for already drawn figures, as the canvas + # does not have a renderer otherwise. This is the reason this function + # can't be integrated into the rest of the code. + bbox = t.get_window_extent(renderer=self.ax.figure.canvas.renderer) + + # difference in pixel extent of image + lw = np.diff(bbox.corners()[0::2, 0])[0] + + return lw + + def set_label_props(self, label, text, color): + """Set the label properties - color, fontsize, text.""" + label.set_text(text) + label.set_color(color) + label.set_fontproperties(self.labelFontProps) + label.set_clip_box(self.ax.bbox) + + def get_text(self, lev, fmt): + """Get the text of the label.""" + if isinstance(lev, six.string_types): + return lev + else: + if isinstance(fmt, dict): + return fmt.get(lev, '%1.3f') + elif callable(fmt): + return fmt(lev) + else: + return fmt % lev + + def locate_label(self, linecontour, labelwidth): + """ + Find good place to draw a label (relatively flat part of the contour). + """ + + # Number of contour points + nsize = len(linecontour) + if labelwidth > 1: + xsize = int(np.ceil(nsize / labelwidth)) + else: + xsize = 1 + if xsize == 1: + ysize = nsize + else: + ysize = int(labelwidth) + + XX = np.resize(linecontour[:, 0], (xsize, ysize)) + YY = np.resize(linecontour[:, 1], (xsize, ysize)) + # I might have fouled up the following: + yfirst = YY[:, :1] + ylast = YY[:, -1:] + xfirst = XX[:, :1] + xlast = XX[:, -1:] + s = (yfirst - YY) * (xlast - xfirst) - (xfirst - XX) * (ylast - yfirst) + L = np.hypot(xlast - xfirst, ylast - yfirst) + # Ignore warning that divide by zero throws, as this is a valid option + with np.errstate(divide='ignore', invalid='ignore'): + dist = np.sum(np.abs(s) / L, axis=-1) + x, y, ind = self.get_label_coords(dist, XX, YY, ysize, labelwidth) + + # There must be a more efficient way... + lc = [tuple(l) for l in linecontour] + dind = lc.index((x, y)) + + return x, y, dind + + def calc_label_rot_and_inline(self, slc, ind, lw, lc=None, spacing=5): + """ + This function calculates the appropriate label rotation given + the linecontour coordinates in screen units, the index of the + label location and the label width. + + It will also break contour and calculate inlining if *lc* is + not empty (lc defaults to the empty list if None). *spacing* + is the space around the label in pixels to leave empty. + + Do both of these tasks at once to avoid calculating path lengths + multiple times, which is relatively costly. + + The method used here involves calculating the path length + along the contour in pixel coordinates and then looking + approximately label width / 2 away from central point to + determine rotation and then to break contour if desired. + """ + + if lc is None: + lc = [] + # Half the label width + hlw = lw / 2.0 + + # Check if closed and, if so, rotate contour so label is at edge + closed = _is_closed_polygon(slc) + if closed: + slc = np.r_[slc[ind:-1], slc[:ind + 1]] + + if len(lc): # Rotate lc also if not empty + lc = np.r_[lc[ind:-1], lc[:ind + 1]] + + ind = 0 + + # Calculate path lengths + pl = np.zeros(slc.shape[0], dtype=float) + dx = np.diff(slc, axis=0) + pl[1:] = np.cumsum(np.hypot(dx[:, 0], dx[:, 1])) + pl = pl - pl[ind] + + # Use linear interpolation to get points around label + xi = np.array([-hlw, hlw]) + if closed: # Look at end also for closed contours + dp = np.array([pl[-1], 0]) + else: + dp = np.zeros_like(xi) + + # Get angle of vector between the two ends of the label - must be + # calculated in pixel space for text rotation to work correctly. + (dx,), (dy,) = (np.diff(np.interp(dp + xi, pl, slc_col)) + for slc_col in slc.T) + rotation = np.rad2deg(np.arctan2(dy, dx)) + + if self.rightside_up: + # Fix angle so text is never upside-down + rotation = (rotation + 90) % 180 - 90 + + # Break contour if desired + nlc = [] + if len(lc): + # Expand range by spacing + xi = dp + xi + np.array([-spacing, spacing]) + + # Get (integer) indices near points of interest; use -1 as marker + # for out of bounds. + I = np.interp(xi, pl, np.arange(len(pl)), left=-1, right=-1) + I = [np.floor(I[0]).astype(int), np.ceil(I[1]).astype(int)] + if I[0] != -1: + xy1 = [np.interp(xi[0], pl, lc_col) for lc_col in lc.T] + if I[1] != -1: + xy2 = [np.interp(xi[1], pl, lc_col) for lc_col in lc.T] + + # Actually break contours + if closed: + # This will remove contour if shorter than label + if all(i != -1 for i in I): + nlc.append(np.row_stack([xy2, lc[I[1]:I[0]+1], xy1])) + else: + # These will remove pieces of contour if they have length zero + if I[0] != -1: + nlc.append(np.row_stack([lc[:I[0]+1], xy1])) + if I[1] != -1: + nlc.append(np.row_stack([xy2, lc[I[1]:]])) + + # The current implementation removes contours completely + # covered by labels. Uncomment line below to keep + # original contour if this is the preferred behavior. + # if not len(nlc): nlc = [ lc ] + + return rotation, nlc + + def _get_label_text(self, x, y, rotation): + dx, dy = self.ax.transData.inverted().transform_point((x, y)) + t = text.Text(dx, dy, rotation=rotation, + horizontalalignment='center', + verticalalignment='center') + return t + + def _get_label_clabeltext(self, x, y, rotation): + # x, y, rotation is given in pixel coordinate. Convert them to + # the data coordinate and create a label using ClabelText + # class. This way, the roation of the clabel is along the + # contour line always. + transDataInv = self.ax.transData.inverted() + dx, dy = transDataInv.transform_point((x, y)) + drotation = transDataInv.transform_angles(np.array([rotation]), + np.array([[x, y]])) + t = ClabelText(dx, dy, rotation=drotation[0], + horizontalalignment='center', + verticalalignment='center') + + return t + + def _add_label(self, t, x, y, lev, cvalue): + color = self.labelMappable.to_rgba(cvalue, alpha=self.alpha) + + _text = self.get_text(lev, self.labelFmt) + self.set_label_props(t, _text, color) + self.labelTexts.append(t) + self.labelCValues.append(cvalue) + self.labelXYs.append((x, y)) + + # Add label to plot here - useful for manual mode label selection + self.ax.add_artist(t) + + def add_label(self, x, y, rotation, lev, cvalue): + """ + Add contour label using :class:`~matplotlib.text.Text` class. + """ + + t = self._get_label_text(x, y, rotation) + self._add_label(t, x, y, lev, cvalue) + + def add_label_clabeltext(self, x, y, rotation, lev, cvalue): + """ + Add contour label using :class:`ClabelText` class. + """ + # x, y, rotation is given in pixel coordinate. Convert them to + # the data coordinate and create a label using ClabelText + # class. This way, the roation of the clabel is along the + # contour line always. + + t = self._get_label_clabeltext(x, y, rotation) + self._add_label(t, x, y, lev, cvalue) + + def add_label_near(self, x, y, inline=True, inline_spacing=5, + transform=None): + """ + Add a label near the point (x, y). If transform is None + (default), (x, y) is in data coordinates; if transform is + False, (x, y) is in display coordinates; otherwise, the + specified transform will be used to translate (x, y) into + display coordinates. + + Parameters + ---------- + x, y : float + The approximate location of the label. + + inline : bool, optional, default: True + If *True* remove the segment of the contour beneath the label. + + inline_spacing : int, optional, default: 5 + Space in pixels to leave on each side of label when placing + inline. This spacing will be exact for labels at locations where + the contour is straight, less so for labels on curved contours. + """ + + if transform is None: + transform = self.ax.transData + + if transform: + x, y = transform.transform_point((x, y)) + + # find the nearest contour _in screen units_ + conmin, segmin, imin, xmin, ymin = self.find_nearest_contour( + x, y, self.labelIndiceList)[:5] + + # The calc_label_rot_and_inline routine requires that (xmin,ymin) + # be a vertex in the path. So, if it isn't, add a vertex here + + # grab the paths from the collections + paths = self.collections[conmin].get_paths() + # grab the correct segment + active_path = paths[segmin] + # grab its vertices + lc = active_path.vertices + # sort out where the new vertex should be added data-units + xcmin = self.ax.transData.inverted().transform_point([xmin, ymin]) + # if there isn't a vertex close enough + if not np.allclose(xcmin, lc[imin]): + # insert new data into the vertex list + lc = np.r_[lc[:imin], np.array(xcmin)[None, :], lc[imin:]] + # replace the path with the new one + paths[segmin] = mpath.Path(lc) + + # Get index of nearest level in subset of levels used for labeling + lmin = self.labelIndiceList.index(conmin) + + # Coordinates of contour + paths = self.collections[conmin].get_paths() + lc = paths[segmin].vertices + + # In pixel/screen space + slc = self.ax.transData.transform(lc) + + # Get label width for rotating labels and breaking contours + lw = self.get_label_width(self.labelLevelList[lmin], + self.labelFmt, self.labelFontSizeList[lmin]) + # lw is in points. + lw *= self.ax.figure.dpi / 72.0 # scale to screen coordinates + # now lw in pixels + + # Figure out label rotation. + if inline: + lcarg = lc + else: + lcarg = None + rotation, nlc = self.calc_label_rot_and_inline( + slc, imin, lw, lcarg, + inline_spacing) + + self.add_label(xmin, ymin, rotation, self.labelLevelList[lmin], + self.labelCValueList[lmin]) + + if inline: + # Remove old, not looping over paths so we can do this up front + paths.pop(segmin) + + # Add paths if not empty or single point + for n in nlc: + if len(n) > 1: + paths.append(mpath.Path(n)) + + def pop_label(self, index=-1): + """Defaults to removing last label, but any index can be supplied""" + self.labelCValues.pop(index) + t = self.labelTexts.pop(index) + t.remove() + + def labels(self, inline, inline_spacing): + + if self._use_clabeltext: + add_label = self.add_label_clabeltext + else: + add_label = self.add_label + + for icon, lev, fsize, cvalue in zip( + self.labelIndiceList, self.labelLevelList, + self.labelFontSizeList, self.labelCValueList): + + con = self.collections[icon] + trans = con.get_transform() + lw = self.get_label_width(lev, self.labelFmt, fsize) + lw *= self.ax.figure.dpi / 72.0 # scale to screen coordinates + additions = [] + paths = con.get_paths() + for segNum, linepath in enumerate(paths): + lc = linepath.vertices # Line contour + slc0 = trans.transform(lc) # Line contour in screen coords + + # For closed polygons, add extra point to avoid division by + # zero in print_label and locate_label. Other than these + # functions, this is not necessary and should probably be + # eventually removed. + if _is_closed_polygon(lc): + slc = np.r_[slc0, slc0[1:2, :]] + else: + slc = slc0 + + # Check if long enough for a label + if self.print_label(slc, lw): + x, y, ind = self.locate_label(slc, lw) + + if inline: + lcarg = lc + else: + lcarg = None + rotation, new = self.calc_label_rot_and_inline( + slc0, ind, lw, lcarg, + inline_spacing) + + # Actually add the label + add_label(x, y, rotation, lev, cvalue) + + # If inline, add new contours + if inline: + for n in new: + # Add path if not empty or single point + if len(n) > 1: + additions.append(mpath.Path(n)) + else: # If not adding label, keep old path + additions.append(linepath) + + # After looping over all segments on a contour, remove old + # paths and add new ones if inlining + if inline: + del paths[:] + paths.extend(additions) + + +def _find_closest_point_on_leg(p1, p2, p0): + """Find the closest point to p0 on line segment connecting p1 and p2.""" + + # handle degenerate case + if np.all(p2 == p1): + d = np.sum((p0 - p1)**2) + return d, p1 + + d21 = p2 - p1 + d01 = p0 - p1 + + # project on to line segment to find closest point + proj = np.dot(d01, d21) / np.dot(d21, d21) + if proj < 0: + proj = 0 + if proj > 1: + proj = 1 + pc = p1 + proj * d21 + + # find squared distance + d = np.sum((pc-p0)**2) + + return d, pc + + +def _is_closed_polygon(X): + """ + Return whether first and last object in a sequence are the same. These are + presumably coordinates on a polygonal curve, in which case this function + tests if that curve is closed. + """ + return np.all(X[0] == X[-1]) + + +def _find_closest_point_on_path(lc, point): + """ + lc: coordinates of vertices + point: coordinates of test point + """ + + # find index of closest vertex for this segment + ds = np.sum((lc - point[None, :])**2, 1) + imin = np.argmin(ds) + + dmin = np.inf + xcmin = None + legmin = (None, None) + + closed = _is_closed_polygon(lc) + + # build list of legs before and after this vertex + legs = [] + if imin > 0 or closed: + legs.append(((imin-1) % len(lc), imin)) + if imin < len(lc) - 1 or closed: + legs.append((imin, (imin+1) % len(lc))) + + for leg in legs: + d, xc = _find_closest_point_on_leg(lc[leg[0]], lc[leg[1]], point) + if d < dmin: + dmin = d + xcmin = xc + legmin = leg + + return (dmin, xcmin, legmin) + + +class ContourSet(cm.ScalarMappable, ContourLabeler): + """ + Store a set of contour lines or filled regions. + + User-callable method: `~.axes.Axes.clabel` + + Parameters + ---------- + ax : `~.axes.Axes` + + levels : [level0, level1, ..., leveln] + A list of floating point numbers indicating the contour + levels. + + allsegs : [level0segs, level1segs, ...] + List of all the polygon segments for all the *levels*. + For contour lines ``len(allsegs) == len(levels)``, and for + filled contour regions ``len(allsegs) = len(levels)-1``. The lists + should look like:: + + level0segs = [polygon0, polygon1, ...] + polygon0 = array_like [[x0,y0], [x1,y1], ...] + + allkinds : ``None`` or [level0kinds, level1kinds, ...] + Optional list of all the polygon vertex kinds (code types), as + described and used in Path. This is used to allow multiply- + connected paths such as holes within filled polygons. + If not ``None``, ``len(allkinds) == len(allsegs)``. The lists + should look like:: + + level0kinds = [polygon0kinds, ...] + polygon0kinds = [vertexcode0, vertexcode1, ...] + + If *allkinds* is not ``None``, usually all polygons for a + particular contour level are grouped together so that + ``level0segs = [polygon0]`` and ``level0kinds = [polygon0kinds]``. + + kwargs : + Keyword arguments are as described in the docstring of + `~.axes.Axes.contour`. + + Attributes + ---------- + ax: + The axes object in which the contours are drawn. + + collections: + A silent_list of LineCollections or PolyCollections. + + levels: + Contour levels. + + layers: + Same as levels for line contours; half-way between + levels for filled contours. See :meth:`_process_colors`. + """ + + def __init__(self, ax, *args, **kwargs): + """ + Draw contour lines or filled regions, depending on + whether keyword arg *filled* is ``False`` (default) or ``True``. + + Call signature:: + + ContourSet(ax, levels, allsegs, [allkinds], **kwargs) + + Parameters + ---------- + ax : + The `~.axes.Axes` object to draw on. + + levels : [level0, level1, ..., leveln] + A list of floating point numbers indicating the contour + levels. + + allsegs : [level0segs, level1segs, ...] + List of all the polygon segments for all the *levels*. + For contour lines ``len(allsegs) == len(levels)``, and for + filled contour regions ``len(allsegs) = len(levels)-1``. The lists + should look like:: + + level0segs = [polygon0, polygon1, ...] + polygon0 = array_like [[x0,y0], [x1,y1], ...] + + allkinds : [level0kinds, level1kinds, ...], optional + Optional list of all the polygon vertex kinds (code types), as + described and used in Path. This is used to allow multiply- + connected paths such as holes within filled polygons. + If not ``None``, ``len(allkinds) == len(allsegs)``. The lists + should look like:: + + level0kinds = [polygon0kinds, ...] + polygon0kinds = [vertexcode0, vertexcode1, ...] + + If *allkinds* is not ``None``, usually all polygons for a + particular contour level are grouped together so that + ``level0segs = [polygon0]`` and ``level0kinds = [polygon0kinds]``. + + **kwargs + Keyword arguments are as described in the docstring of + `~axes.Axes.contour`. + """ + self.ax = ax + self.levels = kwargs.pop('levels', None) + self.filled = kwargs.pop('filled', False) + self.linewidths = kwargs.pop('linewidths', None) + self.linestyles = kwargs.pop('linestyles', None) + + self.hatches = kwargs.pop('hatches', [None]) + + self.alpha = kwargs.pop('alpha', None) + self.origin = kwargs.pop('origin', None) + self.extent = kwargs.pop('extent', None) + cmap = kwargs.pop('cmap', None) + self.colors = kwargs.pop('colors', None) + norm = kwargs.pop('norm', None) + vmin = kwargs.pop('vmin', None) + vmax = kwargs.pop('vmax', None) + self.extend = kwargs.pop('extend', 'neither') + self.antialiased = kwargs.pop('antialiased', None) + if self.antialiased is None and self.filled: + self.antialiased = False # eliminate artifacts; we are not + # stroking the boundaries. + # The default for line contours will be taken from + # the LineCollection default, which uses the + # rcParams['lines.antialiased'] + + self.nchunk = kwargs.pop('nchunk', 0) + self.locator = kwargs.pop('locator', None) + if (isinstance(norm, colors.LogNorm) + or isinstance(self.locator, ticker.LogLocator)): + self.logscale = True + if norm is None: + norm = colors.LogNorm() + if self.extend is not 'neither': + raise ValueError('extend kwarg does not work yet with log ' + ' scale') + else: + self.logscale = False + + if self.origin not in [None, 'lower', 'upper', 'image']: + raise ValueError("If given, *origin* must be one of [ 'lower' |" + " 'upper' | 'image']") + if self.extent is not None and len(self.extent) != 4: + raise ValueError("If given, *extent* must be '[ *None* |" + " (x0,x1,y0,y1) ]'") + if self.colors is not None and cmap is not None: + raise ValueError('Either colors or cmap must be None') + if self.origin == 'image': + self.origin = mpl.rcParams['image.origin'] + + self._transform = kwargs.pop('transform', None) + + kwargs = self._process_args(*args, **kwargs) + self._process_levels() + + if self.colors is not None: + ncolors = len(self.levels) + if self.filled: + ncolors -= 1 + i0 = 0 + + # Handle the case where colors are given for the extended + # parts of the contour. + extend_min = self.extend in ['min', 'both'] + extend_max = self.extend in ['max', 'both'] + use_set_under_over = False + # if we are extending the lower end, and we've been given enough + # colors then skip the first color in the resulting cmap. For the + # extend_max case we don't need to worry about passing more colors + # than ncolors as ListedColormap will clip. + total_levels = ncolors + int(extend_min) + int(extend_max) + if (len(self.colors) == total_levels and + any([extend_min, extend_max])): + use_set_under_over = True + if extend_min: + i0 = 1 + + cmap = colors.ListedColormap(self.colors[i0:None], N=ncolors) + + if use_set_under_over: + if extend_min: + cmap.set_under(self.colors[0]) + if extend_max: + cmap.set_over(self.colors[-1]) + + if self.filled: + self.collections = cbook.silent_list('mcoll.PathCollection') + else: + self.collections = cbook.silent_list('mcoll.LineCollection') + # label lists must be initialized here + self.labelTexts = [] + self.labelCValues = [] + + kw = {'cmap': cmap} + if norm is not None: + kw['norm'] = norm + # sets self.cmap, norm if needed; + cm.ScalarMappable.__init__(self, **kw) + if vmin is not None: + self.norm.vmin = vmin + if vmax is not None: + self.norm.vmax = vmax + self._process_colors() + + self.allsegs, self.allkinds = self._get_allsegs_and_allkinds() + + if self.filled: + if self.linewidths is not None: + warnings.warn('linewidths is ignored by contourf') + + # Lower and upper contour levels. + lowers, uppers = self._get_lowers_and_uppers() + + # Ensure allkinds can be zipped below. + if self.allkinds is None: + self.allkinds = [None] * len(self.allsegs) + + # Default zorder taken from Collection + zorder = kwargs.pop('zorder', 1) + for level, level_upper, segs, kinds in \ + zip(lowers, uppers, self.allsegs, self.allkinds): + paths = self._make_paths(segs, kinds) + + col = mcoll.PathCollection( + paths, + antialiaseds=(self.antialiased,), + edgecolors='none', + alpha=self.alpha, + transform=self.get_transform(), + zorder=zorder) + self.ax.add_collection(col, autolim=False) + self.collections.append(col) + else: + tlinewidths = self._process_linewidths() + self.tlinewidths = tlinewidths + tlinestyles = self._process_linestyles() + aa = self.antialiased + if aa is not None: + aa = (self.antialiased,) + # Default zorder taken from LineCollection + zorder = kwargs.pop('zorder', 2) + for level, width, lstyle, segs in \ + zip(self.levels, tlinewidths, tlinestyles, self.allsegs): + col = mcoll.LineCollection( + segs, + antialiaseds=aa, + linewidths=width, + linestyles=[lstyle], + alpha=self.alpha, + transform=self.get_transform(), + zorder=zorder) + col.set_label('_nolegend_') + self.ax.add_collection(col, autolim=False) + self.collections.append(col) + + for col in self.collections: + col.sticky_edges.x[:] = [self._mins[0], self._maxs[0]] + col.sticky_edges.y[:] = [self._mins[1], self._maxs[1]] + self.ax.update_datalim([self._mins, self._maxs]) + self.ax.autoscale_view(tight=True) + + self.changed() # set the colors + + if kwargs: + s = ", ".join(map(repr, kwargs)) + warnings.warn('The following kwargs were not used by contour: ' + + s) + + def get_transform(self): + """ + Return the :class:`~matplotlib.transforms.Transform` + instance used by this ContourSet. + """ + if self._transform is None: + self._transform = self.ax.transData + elif (not isinstance(self._transform, mtransforms.Transform) + and hasattr(self._transform, '_as_mpl_transform')): + self._transform = self._transform._as_mpl_transform(self.ax) + return self._transform + + def __getstate__(self): + state = self.__dict__.copy() + # the C object _contour_generator cannot currently be pickled. This + # isn't a big issue as it is not actually used once the contour has + # been calculated. + state['_contour_generator'] = None + return state + + def legend_elements(self, variable_name='x', str_format=str): + """ + Return a list of artists and labels suitable for passing through + to :func:`plt.legend` which represent this ContourSet. + + The labels have the form "0 < x <= 1" stating the data ranges which + the artists represent. + + Parameters + ---------- + variable_name : str + The string used inside the inequality used on the labels. + + str_format : function: float -> str + Function used to format the numbers in the labels. + + Returns + ------- + artists : List[`.Artist`] + A list of the artists. + + labels : List[str] + A list of the labels. + + """ + artists = [] + labels = [] + + if self.filled: + lowers, uppers = self._get_lowers_and_uppers() + n_levels = len(self.collections) + + for i, (collection, lower, upper) in enumerate( + zip(self.collections, lowers, uppers)): + patch = mpatches.Rectangle( + (0, 0), 1, 1, + facecolor=collection.get_facecolor()[0], + hatch=collection.get_hatch(), + alpha=collection.get_alpha()) + artists.append(patch) + + lower = str_format(lower) + upper = str_format(upper) + + if i == 0 and self.extend in ('min', 'both'): + labels.append(r'$%s \leq %s$' % (variable_name, + lower)) + elif i == n_levels - 1 and self.extend in ('max', 'both'): + labels.append(r'$%s > %s$' % (variable_name, + upper)) + else: + labels.append(r'$%s < %s \leq %s$' % (lower, + variable_name, + upper)) + else: + for collection, level in zip(self.collections, self.levels): + + patch = mcoll.LineCollection(None) + patch.update_from(collection) + + artists.append(patch) + # format the level for insertion into the labels + level = str_format(level) + labels.append(r'$%s = %s$' % (variable_name, level)) + + return artists, labels + + def _process_args(self, *args, **kwargs): + """ + Process *args* and *kwargs*; override in derived classes. + + Must set self.levels, self.zmin and self.zmax, and update axes + limits. + """ + self.levels = args[0] + self.allsegs = args[1] + self.allkinds = len(args) > 2 and args[2] or None + self.zmax = np.max(self.levels) + self.zmin = np.min(self.levels) + self._auto = False + + # Check lengths of levels and allsegs. + if self.filled: + if len(self.allsegs) != len(self.levels) - 1: + raise ValueError('must be one less number of segments as ' + 'levels') + else: + if len(self.allsegs) != len(self.levels): + raise ValueError('must be same number of segments as levels') + + # Check length of allkinds. + if (self.allkinds is not None and + len(self.allkinds) != len(self.allsegs)): + raise ValueError('allkinds has different length to allsegs') + + # Determine x,y bounds and update axes data limits. + flatseglist = [s for seg in self.allsegs for s in seg] + points = np.concatenate(flatseglist, axis=0) + self._mins = points.min(axis=0) + self._maxs = points.max(axis=0) + + return kwargs + + def _get_allsegs_and_allkinds(self): + """ + Override in derived classes to create and return allsegs and allkinds. + allkinds can be None. + """ + return self.allsegs, self.allkinds + + def _get_lowers_and_uppers(self): + """ + Return (lowers,uppers) for filled contours. + """ + lowers = self._levels[:-1] + if self.zmin == lowers[0]: + # Include minimum values in lowest interval + lowers = lowers.copy() # so we don't change self._levels + if self.logscale: + lowers[0] = 0.99 * self.zmin + else: + lowers[0] -= 1 + uppers = self._levels[1:] + return (lowers, uppers) + + def _make_paths(self, segs, kinds): + if kinds is not None: + return [mpath.Path(seg, codes=kind) + for seg, kind in zip(segs, kinds)] + else: + return [mpath.Path(seg) for seg in segs] + + def changed(self): + tcolors = [(tuple(rgba),) + for rgba in self.to_rgba(self.cvalues, alpha=self.alpha)] + self.tcolors = tcolors + hatches = self.hatches * len(tcolors) + for color, hatch, collection in zip(tcolors, hatches, + self.collections): + if self.filled: + collection.set_facecolor(color) + # update the collection's hatch (may be None) + collection.set_hatch(hatch) + else: + collection.set_color(color) + for label, cv in zip(self.labelTexts, self.labelCValues): + label.set_alpha(self.alpha) + label.set_color(self.labelMappable.to_rgba(cv)) + # add label colors + cm.ScalarMappable.changed(self) + + def _autolev(self, N): + """ + Select contour levels to span the data. + + We need two more levels for filled contours than for + line contours, because for the latter we need to specify + the lower and upper boundary of each range. For example, + a single contour boundary, say at z = 0, requires only + one contour line, but two filled regions, and therefore + three levels to provide boundaries for both regions. + """ + if self.locator is None: + if self.logscale: + self.locator = ticker.LogLocator() + else: + self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1) + + lev = self.locator.tick_values(self.zmin, self.zmax) + self._auto = True + return lev + + def _contour_level_args(self, z, args): + """ + Determine the contour levels and store in self.levels. + """ + if self.filled: + fn = 'contourf' + else: + fn = 'contour' + self._auto = False + if self.levels is None: + if len(args) == 0: + lev = self._autolev(7) + else: + level_arg = args[0] + try: + if type(level_arg) == int: + lev = self._autolev(level_arg) + else: + lev = np.asarray(level_arg).astype(np.float64) + except: + raise TypeError( + "Last {0} arg must give levels; see help({0})" + .format(fn)) + self.levels = lev + else: + self.levels = np.asarray(self.levels).astype(np.float64) + + if not self.filled: + inside = (self.levels > self.zmin) & (self.levels < self.zmax) + self.levels = self.levels[inside] + if len(self.levels) == 0: + self.levels = [self.zmin] + warnings.warn("No contour levels were found" + " within the data range.") + + if self.filled and len(self.levels) < 2: + raise ValueError("Filled contours require at least 2 levels.") + + if len(self.levels) > 1 and np.min(np.diff(self.levels)) <= 0.0: + raise ValueError("Contour levels must be increasing") + + def _process_levels(self): + """ + Assign values to :attr:`layers` based on :attr:`levels`, + adding extended layers as needed if contours are filled. + + For line contours, layers simply coincide with levels; + a line is a thin layer. No extended levels are needed + with line contours. + """ + # Make a private _levels to include extended regions; we + # want to leave the original levels attribute unchanged. + # (Colorbar needs this even for line contours.) + self._levels = list(self.levels) + + if self.extend in ('both', 'min'): + self._levels.insert(0, min(self.levels[0], self.zmin) - 1) + if self.extend in ('both', 'max'): + self._levels.append(max(self.levels[-1], self.zmax) + 1) + self._levels = np.asarray(self._levels) + + if not self.filled: + self.layers = self.levels + return + + # layer values are mid-way between levels + self.layers = 0.5 * (self._levels[:-1] + self._levels[1:]) + # ...except that extended layers must be outside the + # normed range: + if self.extend in ('both', 'min'): + self.layers[0] = -1e150 + if self.extend in ('both', 'max'): + self.layers[-1] = 1e150 + + def _process_colors(self): + """ + Color argument processing for contouring. + + Note that we base the color mapping on the contour levels + and layers, not on the actual range of the Z values. This + means we don't have to worry about bad values in Z, and we + always have the full dynamic range available for the selected + levels. + + The color is based on the midpoint of the layer, except for + extended end layers. By default, the norm vmin and vmax + are the extreme values of the non-extended levels. Hence, + the layer color extremes are not the extreme values of + the colormap itself, but approach those values as the number + of levels increases. An advantage of this scheme is that + line contours, when added to filled contours, take on + colors that are consistent with those of the filled regions; + for example, a contour line on the boundary between two + regions will have a color intermediate between those + of the regions. + + """ + self.monochrome = self.cmap.monochrome + if self.colors is not None: + # Generate integers for direct indexing. + i0, i1 = 0, len(self.levels) + if self.filled: + i1 -= 1 + # Out of range indices for over and under: + if self.extend in ('both', 'min'): + i0 -= 1 + if self.extend in ('both', 'max'): + i1 += 1 + self.cvalues = list(range(i0, i1)) + self.set_norm(colors.NoNorm()) + else: + self.cvalues = self.layers + self.set_array(self.levels) + self.autoscale_None() + if self.extend in ('both', 'max', 'min'): + self.norm.clip = False + + # self.tcolors are set by the "changed" method + + def _process_linewidths(self): + linewidths = self.linewidths + Nlev = len(self.levels) + if linewidths is None: + tlinewidths = [(mpl.rcParams['lines.linewidth'],)] * Nlev + else: + if not cbook.iterable(linewidths): + linewidths = [linewidths] * Nlev + else: + linewidths = list(linewidths) + if len(linewidths) < Nlev: + nreps = int(np.ceil(Nlev / len(linewidths))) + linewidths = linewidths * nreps + if len(linewidths) > Nlev: + linewidths = linewidths[:Nlev] + tlinewidths = [(w,) for w in linewidths] + return tlinewidths + + def _process_linestyles(self): + linestyles = self.linestyles + Nlev = len(self.levels) + if linestyles is None: + tlinestyles = ['solid'] * Nlev + if self.monochrome: + neg_ls = mpl.rcParams['contour.negative_linestyle'] + eps = - (self.zmax - self.zmin) * 1e-15 + for i, lev in enumerate(self.levels): + if lev < eps: + tlinestyles[i] = neg_ls + else: + if isinstance(linestyles, six.string_types): + tlinestyles = [linestyles] * Nlev + elif cbook.iterable(linestyles): + tlinestyles = list(linestyles) + if len(tlinestyles) < Nlev: + nreps = int(np.ceil(Nlev / len(linestyles))) + tlinestyles = tlinestyles * nreps + if len(tlinestyles) > Nlev: + tlinestyles = tlinestyles[:Nlev] + else: + raise ValueError("Unrecognized type for linestyles kwarg") + return tlinestyles + + def get_alpha(self): + """returns alpha to be applied to all ContourSet artists""" + return self.alpha + + def set_alpha(self, alpha): + """ + Set the alpha blending value for all ContourSet artists. + *alpha* must be between 0 (transparent) and 1 (opaque). + """ + self.alpha = alpha + self.changed() + + def find_nearest_contour(self, x, y, indices=None, pixel=True): + """ + Finds contour that is closest to a point. Defaults to + measuring distance in pixels (screen space - useful for manual + contour labeling), but this can be controlled via a keyword + argument. + + Returns a tuple containing the contour, segment, index of + segment, x & y of segment point and distance to minimum point. + + Optional keyword arguments: + + *indices*: + Indexes of contour levels to consider when looking for + nearest point. Defaults to using all levels. + + *pixel*: + If *True*, measure distance in pixel space, if not, measure + distance in axes space. Defaults to *True*. + + """ + + # This function uses a method that is probably quite + # inefficient based on converting each contour segment to + # pixel coordinates and then comparing the given point to + # those coordinates for each contour. This will probably be + # quite slow for complex contours, but for normal use it works + # sufficiently well that the time is not noticeable. + # Nonetheless, improvements could probably be made. + + if indices is None: + indices = list(xrange(len(self.levels))) + + dmin = np.inf + conmin = None + segmin = None + xmin = None + ymin = None + + point = np.array([x, y]) + + for icon in indices: + con = self.collections[icon] + trans = con.get_transform() + paths = con.get_paths() + + for segNum, linepath in enumerate(paths): + lc = linepath.vertices + # transfer all data points to screen coordinates if desired + if pixel: + lc = trans.transform(lc) + + d, xc, leg = _find_closest_point_on_path(lc, point) + if d < dmin: + dmin = d + conmin = icon + segmin = segNum + imin = leg[1] + xmin = xc[0] + ymin = xc[1] + + return (conmin, segmin, imin, xmin, ymin, dmin) + + +class QuadContourSet(ContourSet): + """ + Create and store a set of contour lines or filled regions. + + User-callable method: `~axes.Axes.clabel` + + Attributes + ---------- + ax: + The axes object in which the contours are drawn. + + collections: + A silent_list of LineCollections or PolyCollections. + + levels: + Contour levels. + + layers: + Same as levels for line contours; half-way between + levels for filled contours. See :meth:`_process_colors` method. + """ + + def _process_args(self, *args, **kwargs): + """ + Process args and kwargs. + """ + if isinstance(args[0], QuadContourSet): + if self.levels is None: + self.levels = args[0].levels + self.zmin = args[0].zmin + self.zmax = args[0].zmax + self._corner_mask = args[0]._corner_mask + contour_generator = args[0]._contour_generator + self._mins = args[0]._mins + self._maxs = args[0]._maxs + else: + self._corner_mask = kwargs.pop('corner_mask', None) + if self._corner_mask is None: + self._corner_mask = mpl.rcParams['contour.corner_mask'] + + x, y, z = self._contour_args(args, kwargs) + + _mask = ma.getmask(z) + if _mask is ma.nomask or not _mask.any(): + _mask = None + + contour_generator = _contour.QuadContourGenerator( + x, y, z.filled(), _mask, self._corner_mask, self.nchunk) + + t = self.get_transform() + + # if the transform is not trans data, and some part of it + # contains transData, transform the xs and ys to data coordinates + if (t != self.ax.transData and + any(t.contains_branch_seperately(self.ax.transData))): + trans_to_data = t - self.ax.transData + pts = (np.vstack([x.flat, y.flat]).T) + transformed_pts = trans_to_data.transform(pts) + x = transformed_pts[..., 0] + y = transformed_pts[..., 1] + + self._mins = [ma.min(x), ma.min(y)] + self._maxs = [ma.max(x), ma.max(y)] + + self._contour_generator = contour_generator + + return kwargs + + def _get_allsegs_and_allkinds(self): + """Compute ``allsegs`` and ``allkinds`` using C extension.""" + allsegs = [] + if self.filled: + lowers, uppers = self._get_lowers_and_uppers() + allkinds = [] + for level, level_upper in zip(lowers, uppers): + vertices, kinds = \ + self._contour_generator.create_filled_contour( + level, level_upper) + allsegs.append(vertices) + allkinds.append(kinds) + else: + allkinds = None + for level in self.levels: + vertices = self._contour_generator.create_contour(level) + allsegs.append(vertices) + return allsegs, allkinds + + def _contour_args(self, args, kwargs): + if self.filled: + fn = 'contourf' + else: + fn = 'contour' + Nargs = len(args) + if Nargs <= 2: + z = ma.asarray(args[0], dtype=np.float64) + x, y = self._initialize_x_y(z) + args = args[1:] + elif Nargs <= 4: + x, y, z = self._check_xyz(args[:3], kwargs) + args = args[3:] + else: + raise TypeError("Too many arguments to %s; see help(%s)" % + (fn, fn)) + z = ma.masked_invalid(z, copy=False) + self.zmax = float(z.max()) + self.zmin = float(z.min()) + if self.logscale and self.zmin <= 0: + z = ma.masked_where(z <= 0, z) + warnings.warn('Log scale: values of z <= 0 have been masked') + self.zmin = float(z.min()) + self._contour_level_args(z, args) + return (x, y, z) + + def _check_xyz(self, args, kwargs): + """ + For functions like contour, check that the dimensions + of the input arrays match; if x and y are 1D, convert + them to 2D using meshgrid. + + Possible change: I think we should make and use an ArgumentError + Exception class (here and elsewhere). + """ + x, y = args[:2] + kwargs = self.ax._process_unit_info(xdata=x, ydata=y, kwargs=kwargs) + x = self.ax.convert_xunits(x) + y = self.ax.convert_yunits(y) + + x = np.asarray(x, dtype=np.float64) + y = np.asarray(y, dtype=np.float64) + z = ma.asarray(args[2], dtype=np.float64) + + if z.ndim != 2: + raise TypeError("Input z must be a 2D array.") + elif z.shape[0] < 2 or z.shape[1] < 2: + raise TypeError("Input z must be at least a 2x2 array.") + else: + Ny, Nx = z.shape + + if x.ndim != y.ndim: + raise TypeError("Number of dimensions of x and y should match.") + + if x.ndim == 1: + + nx, = x.shape + ny, = y.shape + + if nx != Nx: + raise TypeError("Length of x must be number of columns in z.") + + if ny != Ny: + raise TypeError("Length of y must be number of rows in z.") + + x, y = np.meshgrid(x, y) + + elif x.ndim == 2: + + if x.shape != z.shape: + raise TypeError("Shape of x does not match that of z: found " + "{0} instead of {1}.".format(x.shape, z.shape)) + + if y.shape != z.shape: + raise TypeError("Shape of y does not match that of z: found " + "{0} instead of {1}.".format(y.shape, z.shape)) + else: + raise TypeError("Inputs x and y must be 1D or 2D.") + + return x, y, z + + def _initialize_x_y(self, z): + """ + Return X, Y arrays such that contour(Z) will match imshow(Z) + if origin is not None. + The center of pixel Z[i,j] depends on origin: + if origin is None, x = j, y = i; + if origin is 'lower', x = j + 0.5, y = i + 0.5; + if origin is 'upper', x = j + 0.5, y = Nrows - i - 0.5 + If extent is not None, x and y will be scaled to match, + as in imshow. + If origin is None and extent is not None, then extent + will give the minimum and maximum values of x and y. + """ + if z.ndim != 2: + raise TypeError("Input must be a 2D array.") + elif z.shape[0] < 2 or z.shape[1] < 2: + raise TypeError("Input z must be at least a 2x2 array.") + else: + Ny, Nx = z.shape + if self.origin is None: # Not for image-matching. + if self.extent is None: + return np.meshgrid(np.arange(Nx), np.arange(Ny)) + else: + x0, x1, y0, y1 = self.extent + x = np.linspace(x0, x1, Nx) + y = np.linspace(y0, y1, Ny) + return np.meshgrid(x, y) + # Match image behavior: + if self.extent is None: + x0, x1, y0, y1 = (0, Nx, 0, Ny) + else: + x0, x1, y0, y1 = self.extent + dx = (x1 - x0) / Nx + dy = (y1 - y0) / Ny + x = x0 + (np.arange(Nx) + 0.5) * dx + y = y0 + (np.arange(Ny) + 0.5) * dy + if self.origin == 'upper': + y = y[::-1] + return np.meshgrid(x, y) + + _contour_doc = """ + Plot contours. + + Call signature:: + + contour([X, Y,] Z, [levels], **kwargs) + + :func:`~matplotlib.pyplot.contour` and + :func:`~matplotlib.pyplot.contourf` draw contour lines and + filled contours, respectively. Except as noted, function + signatures and return values are the same for both versions. + + + Parameters + ---------- + X, Y : array-like, optional + The coordinates of the values in *Z*. + + *X* and *Y* must both be 2-D with the same shape as *Z* (e.g. + created via :func:`numpy.meshgrid`), or they must both be 1-D such + that ``len(X) == M`` is the number of columns in *Z* and + ``len(Y) == N`` is the number of rows in *Z*. + + If not given, they are assumed to be integer indices, i.e. + ``X = range(M)``, ``Y = range(N)``. + + Z : array-like(N, M) + The height values over which the contour is drawn. + + levels : int or array-like, optional + Determines the number and positions of the contour lines / regions. + + If an int *n*, use *n* data intervals; i.e. draw *n+1* contour + lines. The level heights are automatically chosen. + + If array-like, draw contour lines at the specified levels. + The values must be in increasing order. + + Returns + ------- + :class:`~matplotlib.contour.QuadContourSet` + + Other Parameters + ---------------- + corner_mask : bool, optional + Enable/disable corner masking, which only has an effect if *Z* is + a masked array. If ``False``, any quad touching a masked point is + masked out. If ``True``, only the triangular corners of quads + nearest those points are always masked out, other triangular + corners comprising three unmasked points are contoured as usual. + + Defaults to ``rcParams['contour.corner_mask']``, which defaults to + ``True``. + + colors : color string or sequence of colors, optional + The colors of the levels, i.e. the lines for `.contour` and the + areas for `.contourf`. + + The sequence is cycled for the levels in ascending order. If the + sequence is shorter than the number of levels, it's repeated. + + As a shortcut, single color strings may be used in place of + one-element lists, i.e. ``'red'`` instead of ``['red']`` to color + all levels with the same color. This shortcut does only work for + color strings, not for other ways of specifying colors. + + By default (value *None*), the colormap specified by *cmap* + will be used. + + alpha : float, optional + The alpha blending value, between 0 (transparent) and 1 (opaque). + + cmap : str or `.Colormap`, optional + A `.Colormap` instance or registered colormap name. The colormap + maps the level values to colors. + Defaults to :rc:`image.cmap`. + + If given, *colors* take precedence over *cmap*. + + norm : `~matplotlib.colors.Normalize`, optional + If a colormap is used, the `.Normalize` instance scales the level + values to the canonical colormap range [0, 1] for mapping to + colors. If not given, the default linear scaling is used. + + vmin, vmax : float, optional + If not *None*, either or both of these values will be supplied to + the `.Normalize` instance, overriding the default color scaling + based on *levels*. + + origin : {*None*, 'upper', 'lower', 'image'}, optional + Determines the orientation and exact position of *Z* by specifying + the position of ``Z[0, 0]``. This is only relevant, if *X*, *Y* + are not given. + + - *None*: ``Z[0, 0]`` is at X=0, Y=0 in the lower left corner. + - 'lower': ``Z[0, 0]`` is at X=0.5, Y=0.5 in the lower left corner. + - 'upper': ``Z[0, 0]`` is at X=N+0.5, Y=0.5 in the upper left + corner. + - 'image': Use the value from :rc:`image.origin`. Note: The value + *None* in the rcParam is currently handled as 'lower'. + + extent : (x0, x1, y0, y1), optional + If *origin* is not *None*, then *extent* is interpreted as + in :func:`matplotlib.pyplot.imshow`: it gives the outer + pixel boundaries. In this case, the position of Z[0,0] + is the center of the pixel, not a corner. If *origin* is + *None*, then (*x0*, *y0*) is the position of Z[0,0], and + (*x1*, *y1*) is the position of Z[-1,-1]. + + This keyword is not active if *X* and *Y* are specified in + the call to contour. + + locator : ticker.Locator subclass, optional + The locator is used to determine the contour levels if they + are not given explicitly via *levels*. + Defaults to `~.ticker.MaxNLocator`. + + extend : {'neither', 'both', 'min', 'max'}, optional + Unless this is 'neither', contour levels are automatically + added to one or both ends of the range so that all data + are included. These added ranges are then mapped to the + special colormap values which default to the ends of the + colormap range, but can be set via + :meth:`matplotlib.colors.Colormap.set_under` and + :meth:`matplotlib.colors.Colormap.set_over` methods. + + xunits, yunits : registered units, optional + Override axis units by specifying an instance of a + :class:`matplotlib.units.ConversionInterface`. + + antialiased : bool, optinal + Enable antialiasing, overriding the defaults. For + filled contours, the default is *True*. For line contours, + it is taken from :rc:`lines.antialiased`. + + Nchunk : int >= 0, optional + If 0, no subdivision of the domain. Specify a positive integer to + divide the domain into subdomains of *nchunk* by *nchunk* quads. + Chunking reduces the maximum length of polygons generated by the + contouring algorithm which reduces the rendering workload passed + on to the backend and also requires slightly less RAM. It can + however introduce rendering artifacts at chunk boundaries depending + on the backend, the *antialiased* flag and value of *alpha*. + + linewidths : float or sequence of float, optional + *Only applies to* `.contour`. + + The line width of the contour lines. + + If a number, all levels will be plotted with this linewidth. + + If a sequence, the levels in ascending order will be plotted with + the linewidths in the order specified. + + Defaults to :rc:`lines.linewidth`. + + linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, optional + *Only applies to* `.contour`. + + If *linestyles* is *None*, the default is 'solid' unless the lines + are monochrome. In that case, negative contours will take their + linestyle from :rc:`contour.negative_linestyle` setting. + + *linestyles* can also be an iterable of the above strings + specifying a set of linestyles to be used. If this + iterable is shorter than the number of contour levels + it will be repeated as necessary. + + hatches : List[str], optional + *Only applies to* `.contourf`. + + A list of cross hatch patterns to use on the filled areas. + If None, no hatching will be added to the contour. + Hatching is supported in the PostScript, PDF, SVG and Agg + backends only. + + + Notes + ----- + 1. :func:`~matplotlib.pyplot.contourf` differs from the MATLAB + version in that it does not draw the polygon edges. + To draw edges, add line contours with + calls to :func:`~matplotlib.pyplot.contour`. + + 2. contourf fills intervals that are closed at the top; that + is, for boundaries *z1* and *z2*, the filled region is:: + + z1 < Z <= z2 + + There is one exception: if the lowest boundary coincides with + the minimum value of the *Z* array, then that minimum value + will be included in the lowest interval. + """ |