diff --git a/doc/api/next_api_changes/behavior/18900-JMK.rst b/doc/api/next_api_changes/behavior/18900-JMK.rst new file mode 100644 index 000000000000..d6d9834e0fe6 --- /dev/null +++ b/doc/api/next_api_changes/behavior/18900-JMK.rst @@ -0,0 +1,26 @@ +Axes used to make colorbar now wrapped +====================================== + +The axes used to place a colorbar is now wrapped by a new parent class +(``ColorbarAxes``) when the colorbar is created:: + + cb = fig.colorbar(im, cax=cax) + +Now ``cb.ax`` returns the parent ``ColorbarAxes``, and to get +``cax`` back, you can do:: + + cb.ax.parent_axes + +The parent axes (``cb.ax.parent_axes``) handles the +placement of the colorbar axes object, and its aspect ratio, but the child +axes (``cb.ax``) handles all the tick parameters, labelling, and holds the +artists that make up the colorbar. We have attempted to dispatch the +appropriate colorbar methods to the appropriate axes. + +Colorbar lines no longer clipped +================================ + +If a colorbar has lines added to it (e.g. for contour lines), these will +no longer be clipped. This is an improvement for lines on the edge of +the colorbar, but could lead to lines off the colorbar if the limits of +the colorbar are changed. diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 245679394bc2..c60a7220678d 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -451,6 +451,7 @@ def _get_pos_and_bbox(ax, renderer): pos = pos.transformed(fig.transSubfigure - fig.transFigure) try: tightbbox = ax.get_tightbbox(renderer=renderer, for_layout_only=True) + print('tight', tightbbox) except TypeError: tightbbox = ax.get_tightbbox(renderer=renderer) @@ -458,6 +459,7 @@ def _get_pos_and_bbox(ax, renderer): bbox = pos else: bbox = tightbbox.transformed(fig.transFigure.inverted()) + print('bbox', bbox) return pos, bbox diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 788fdb66b9af..1a0bb4d93f68 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -36,6 +36,8 @@ import matplotlib as mpl from matplotlib import _api, collections, cm, colors, contour, ticker +from matplotlib.axes._base import _TransformedBoundsLocator +from matplotlib.axes._axes import Axes import matplotlib.artist as martist import matplotlib.patches as mpatches import matplotlib.path as mpath @@ -222,102 +224,50 @@ def _set_ticks_on_axis_warn(*args, **kw): _api.warn_external("Use the colorbar set_ticks() method instead.") -class _ColorbarAutoLocator(ticker.MaxNLocator): +class ColorbarAxes(Axes): """ - AutoLocator for Colorbar - - This locator is just a `.MaxNLocator` except the min and max are - clipped by the norm's min and max (i.e. vmin/vmax from the - image/pcolor/contour object). This is necessary so ticks don't - extrude into the "extend regions". + ColorbarAxes packages two axes, a parent axes that takes care of + positioning the axes, and an inset_axes that takes care of the drawing, + labels, ticks, etc. The inset axes is used as a way to properly + position the extensions (triangles or rectangles) that are used to indicate + over/under colors. + + Users should not normally instantiate this class, but it is the class + returned by ``cbar = fig.colorbar(im); cax = cbar.ax``. """ - - def __init__(self, colorbar): + def __init__(self, parent): """ - This ticker needs to know the *colorbar* so that it can access - its *vmin* and *vmax*. Otherwise it is the same as - `~.ticker.AutoLocator`. """ - - self._colorbar = colorbar - nbins = 'auto' - steps = [1, 2, 2.5, 5, 10] - super().__init__(nbins=nbins, steps=steps) - - def tick_values(self, vmin, vmax): - # flip if needed: - if vmin > vmax: - vmin, vmax = vmax, vmin - vmin = max(vmin, self._colorbar.norm.vmin) - vmax = min(vmax, self._colorbar.norm.vmax) - ticks = super().tick_values(vmin, vmax) - rtol = (vmax - vmin) * 1e-10 - return ticks[(ticks >= vmin - rtol) & (ticks <= vmax + rtol)] - - -class _ColorbarAutoMinorLocator(ticker.AutoMinorLocator): - """ - AutoMinorLocator for Colorbar - - This locator is just a `.AutoMinorLocator` except the min and max are - clipped by the norm's min and max (i.e. vmin/vmax from the - image/pcolor/contour object). This is necessary so that the minorticks - don't extrude into the "extend regions". - """ - - def __init__(self, colorbar, n=None): + inner_ax = parent.inset_axes([0, 0, 1, 1]) + #self.__class__ = type(inner_ax.__class__.__name__+"ColorBar", + # (self.__class__, inner_ax.__class__), + # {}) + self.__dict__ = inner_ax.__dict__ + self.parent_ax = parent + self.inner_ax = inner_ax + self.parent_ax.xaxis.set_visible(False) + self.parent_ax.yaxis.set_visible(False) + self.parent_ax.set_facecolor('none') + # map some features to the parent so users have access... + self.parent_ax.tick_params = self.inner_ax.tick_params + + for attr in ["get_position", "set_position", "set_aspect"]: + setattr(self, attr, getattr(self.parent_ax, attr)) + + def _set_inner_bounds(self, bounds): """ - This ticker needs to know the *colorbar* so that it can access - its *vmin* and *vmax*. + Change the inset_axes location... """ - self._colorbar = colorbar - self.ndivs = n - super().__init__(n=None) - - def __call__(self): - vmin = self._colorbar.norm.vmin - vmax = self._colorbar.norm.vmax - ticks = super().__call__() - rtol = (vmax - vmin) * 1e-10 - return ticks[(ticks >= vmin - rtol) & (ticks <= vmax + rtol)] - - -class _ColorbarLogLocator(ticker.LogLocator): - """ - LogLocator for Colorbarbar - - This locator is just a `.LogLocator` except the min and max are - clipped by the norm's min and max (i.e. vmin/vmax from the - image/pcolor/contour object). This is necessary so ticks don't - extrude into the "extend regions". - - """ - def __init__(self, colorbar, *args, **kwargs): - """ - This ticker needs to know the *colorbar* so that it can access - its *vmin* and *vmax*. Otherwise it is the same as - `~.ticker.LogLocator`. The ``*args`` and ``**kwargs`` are the - same as `~.ticker.LogLocator`. - """ - self._colorbar = colorbar - super().__init__(*args, **kwargs) - - def tick_values(self, vmin, vmax): - if vmin > vmax: - vmin, vmax = vmax, vmin - vmin = max(vmin, self._colorbar.norm.vmin) - vmax = min(vmax, self._colorbar.norm.vmax) - ticks = super().tick_values(vmin, vmax) - rtol = (np.log10(vmax) - np.log10(vmin)) * 1e-10 - ticks = ticks[(np.log10(ticks) >= np.log10(vmin) - rtol) & - (np.log10(ticks) <= np.log10(vmax) + rtol)] - return ticks + self.inner_ax._axes_locator = _TransformedBoundsLocator( + bounds, self.parent_ax.transAxes) class _ColorbarSpine(mspines.Spine): def __init__(self, axes): + self._ax = axes super().__init__(axes, 'colorbar', mpath.Path(np.empty((0, 2)), closed=True)) + mpatches.Patch.set_transform(self, axes.parent_ax.transAxes) def get_window_extent(self, renderer=None): # This Spine has no Axis associated with it, and doesn't need to adjust @@ -327,6 +277,7 @@ def get_window_extent(self, renderer=None): def set_xy(self, xy): self._path = mpath.Path(xy, closed=True) + self._xy = xy self.stale = True def draw(self, renderer): @@ -438,6 +389,8 @@ def __init__(self, ax, cmap=None, _api.check_in_list( ['uniform', 'proportional'], spacing=spacing) + # wrap the axes so that it can be positioned as an inset axes: + ax = ColorbarAxes(ax) self.ax = ax # Bind some methods to the axes to warn users against using them. ax.set_xticks = ax.set_yticks = _set_ticks_on_axis_warn @@ -472,9 +425,11 @@ def __init__(self, ax, cmap=None, self.solids_patches = [] self.lines = [] - for spine in ax.spines.values(): + for spine in self.ax.spines.values(): + spine.set_visible(False) + for spine in self.ax.parent_ax.spines.values(): spine.set_visible(False) - self.outline = ax.spines['outline'] = _ColorbarSpine(ax) + self.outline = self.ax.spines['outline'] = _ColorbarSpine(self.ax) self.patch = mpatches.Polygon( np.empty((0, 2)), @@ -489,7 +444,6 @@ def __init__(self, ax, cmap=None, self.locator = None self.formatter = None - self._manual_tick_data_values = None self.__scale = None # linear, log10 for now. Hopefully more? if ticklocation == 'auto': @@ -510,166 +464,276 @@ def __init__(self, ax, cmap=None, self.formatter = format # Assume it is a Formatter or None self.draw_all() - def _extend_lower(self): - """Return whether the lower limit is open ended.""" - return self.extend in ('both', 'min') - - def _extend_upper(self): - """Return whether the upper limit is open ended.""" - return self.extend in ('both', 'max') - def draw_all(self): """ Calculate any free parameters based on the current cmap and norm, and do all the drawing. """ - self._config_axis() # Inline it after deprecation elapses. + if self.orientation == 'vertical': + if mpl.rcParams['ytick.minor.visible']: + self.minorticks_on() + else: + if mpl.rcParams['xtick.minor.visible']: + self.minorticks_on() + self._long_axis().set(label_position=self.ticklocation, + ticks_position=self.ticklocation) + self._short_axis().set_ticks([]) + self._short_axis().set_ticks([], minor=True) + # Set self._boundaries and self._values, including extensions. + # self._boundaries are the edges of each square of color, and + # self._values are the value to map into the norm to get the + # color: self._process_values() # Set self.vmin and self.vmax to first and last boundary, excluding - # extensions. + # extensions: self.vmin, self.vmax = self._boundaries[self._inside][[0, -1]] # Compute the X/Y mesh. - X, Y = self._mesh() - # Extract bounding polygon (the last entry's value (X[0, 1]) doesn't - # matter, it just matches the CLOSEPOLY code). - x = np.concatenate([X[[0, 1, -2, -1], 0], X[[-1, -2, 1, 0, 0], 1]]) - y = np.concatenate([Y[[0, 1, -2, -1], 0], Y[[-1, -2, 1, 0, 0], 1]]) - xy = np.column_stack([x, y]) - # Configure axes limits, patch, and outline. - xmin, ymin = xy.min(axis=0) - xmax, ymax = xy.max(axis=0) - self.ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax)) - self.outline.set_xy(xy) - self.patch.set_xy(xy) + X, Y, extendlen = self._mesh() + # draw the extend triangles, and shrink the inner axes to accomodate. + # returns the outline of the axes in axes-relative units, including + # the extends: + xyout = self._do_extends(extendlen) + # set the spine outline from the above: + self.outline.set_xy(xyout) + + self.ax.set_xlim(self.vmin, self.vmax) + self.ax.set_ylim(self.vmin, self.vmax) + + # set up the tick locators and formatters. A bit complicated because + # boundary norms + uniform spacing requires a manual locator. self.update_ticks() + if self.filled: - self._add_solids(X, Y, self._values[:, np.newaxis]) + ind = np.arange(len(self._values)) + if self._extend_lower(): + ind = ind[1:] + if self._extend_upper(): + ind = ind[:-1] + self._add_solids(X, Y, self._values[ind, np.newaxis]) - def _config_axis(self): - """Set up long and short axis.""" - ax = self.ax + def _add_solids(self, X, Y, C): + """Draw the colors; optionally add separators.""" + # Cleanup previously set artists. + if self.solids is not None: + self.solids.remove() + for solid in self.solids_patches: + solid.remove() + # Add new artist(s), based on mappable type. Use individual patches if + # hatching is needed, pcolormesh otherwise. + mappable = getattr(self, 'mappable', None) + if (isinstance(mappable, contour.ContourSet) + and any(hatch is not None for hatch in mappable.hatches)): + self._add_solids_patches(X, Y, C, mappable) + else: + self.solids = self.ax.pcolormesh( + X, Y, C, cmap=self.cmap, norm=self.norm, alpha=self.alpha, + edgecolors='none', shading='flat') + if not self.drawedges: + if len(self._y) >= self.n_rasterize: + self.solids.set_rasterized(True) + self.dividers.set_segments( + np.dstack([X, Y])[1:-1] if self.drawedges else []) + + def _add_solids_patches(self, X, Y, C, mappable): + hatches = mappable.hatches * len(C) # Have enough hatches. + patches = [] + for i in range(len(X) - 1): + xy = np.array([[X[i, 0], Y[i, 0]], + [X[i, 1], Y[i, 0]], + [X[i + 1, 1], Y[i + 1, 0]], + [X[i + 1, 0], Y[i + 1, 1]]]) + patch = mpatches.PathPatch(mpath.Path(xy), + facecolor=self.cmap(self.norm(C[i][0])), + hatch=hatches[i], linewidth=0, + antialiased=False, alpha=self.alpha) + self.ax.add_patch(patch) + patches.append(patch) + self.solids_patches = patches + + def _do_extends(self, extendlen): + """ + Make adjustments of the inner axes for the extend triangles (or + rectanges) and add them as patches. + """ + # extend lengths are fraction of the *inner* part of colorbar, + # not the total colorbar: + elower = extendlen[0] if self._extend_lower() else 0 + eupper = extendlen[1] if self._extend_upper() else 0 + tot = eupper + elower + 1 + elower = elower / tot + eupper = eupper / tot + width = 1 / tot + + bounds = np.array([0.0, elower, 1.0, width]) + + # make the inner axes smaller to make room for the extend rectangle + top = bounds[1] + bounds[3] + if not self.extendrect: + # triangle: + xyout = np.array([[0, bounds[1]], [0.5, 0], [1, bounds[1]], + [1, top], [0.5, 1], [0, top], [0, bounds[1]]]) + else: + # rectangle: + xyout = np.array([[0, bounds[1]], [0, 0], [1, 0], [1, bounds[1]], + [1, top], [1, 1], [0, 1], [0, top], + [0, bounds[1]]]) + if self.orientation == 'horizontal': + bounds = bounds[[1, 0, 3, 2]] + xyout = xyout[:, ::-1] + self.ax._set_inner_bounds(bounds) + + if not self.filled: + return xyout + # Make extend triangles or rectangles. These are defined in the + # outer parent axes' co-ordinates: + mappable = getattr(self, 'mappable', None) + if (isinstance(mappable, contour.ContourSet) + and any(hatch is not None for hatch in mappable.hatches)): + hatches = mappable.hatches + else: + hatches = [None] + if self._extend_lower: + if not self.extendrect: + xy = np.array([[0.5, 0], [1, elower], [0, elower]]) + else: + xy = np.array([[0, 0], [1., 0], [1, elower], [0, elower]]) + if self.orientation == 'horizontal': + xy = xy[:, ::-1] + color = self.cmap(self.norm(self._values[0])) + patch = mpatches.PathPatch( + mpath.Path(xy), facecolor=color, linewidth=0, + antialiased=False, transform=self.ax.parent_ax.transAxes, + hatch=hatches[0]) + self.ax.parent_ax.add_patch(patch) + if self._extend_upper: + if not self.extendrect: + xy = np.array([[0.5, 1], [1, 1-eupper], [0, 1-eupper]]) + else: + xy = np.array([[0, 1], [1, 1], [1, 1-eupper], [0, 1-eupper]]) + if self.orientation == 'horizontal': + xy = xy[:, ::-1] + color = self.cmap(self.norm(self._values[-1])) + patch = mpatches.PathPatch( + mpath.Path(xy), facecolor=color, + linewidth=0, antialiased=False, + transform=self.ax.parent_ax.transAxes, hatch=hatches[-1]) + self.ax.parent_ax.add_patch(patch) + return xyout + + def add_lines(self, levels, colors, linewidths, erase=True): + """ + Draw lines on the colorbar. + + The lines are appended to the list :attr:`lines`. + + Parameters + ---------- + levels : array-like + The positions of the lines. + colors : color or list of colors + Either a single color applying to all lines or one color value for + each line. + linewidths : float or array-like + Either a single linewidth applying to all lines or one linewidth + for each line. + erase : bool, default: True + Whether to remove any previously added lines. + """ + y = self._locate(levels) + rtol = (self._y[-1] - self._y[0]) * 1e-10 + igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol) + y = y[igood] + if np.iterable(colors): + colors = np.asarray(colors)[igood] + if np.iterable(linewidths): + linewidths = np.asarray(linewidths)[igood] + X, Y = np.meshgrid([self._y[0], self._y[-1]], y) if self.orientation == 'vertical': - long_axis, short_axis = ax.yaxis, ax.xaxis - if mpl.rcParams['ytick.minor.visible']: - self.minorticks_on() + xy = np.stack([X, Y], axis=-1) else: - long_axis, short_axis = ax.xaxis, ax.yaxis - if mpl.rcParams['xtick.minor.visible']: - self.minorticks_on() - long_axis.set(label_position=self.ticklocation, - ticks_position=self.ticklocation) - short_axis.set_ticks([]) - short_axis.set_ticks([], minor=True) + xy = np.stack([Y, X], axis=-1) + col = collections.LineCollection(xy, linewidths=linewidths, + colors=colors) + + if erase and self.lines: + for lc in self.lines: + lc.remove() + self.lines = [] + self.lines.append(col) + + # make a clip path that is just a linewidth bigger than the axes... + fac = np.max(linewidths) / 72 + xy = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]) + inches = self.ax.get_figure().dpi_scale_trans + # do in inches: + xy = inches.inverted().transform(self.ax.transAxes.transform(xy)) + xy[[0, 1, 4], 1] -= fac + xy[[2, 3], 1] += fac + # back to axes units... + xy = self.ax.transAxes.inverted().transform(inches.transform(xy)) + if self.orientation == 'horizontal': + xy = xy.T + col.set_clip_path(mpath.Path(xy, closed=True), + self.ax.transAxes) + self.ax.add_collection(col) self.stale = True - config_axis = _api.deprecate_privatize_attribute("3.3") + def update_ticks(self): + """ + Setup the ticks and ticklabels. This should not be needed by users. + """ + ax = self.ax + # Get the locator and formatter; defaults to self.locator if not None. + self._get_ticker_locator_formatter() + self._long_axis().set_major_locator(self.locator) + self._long_axis().set_minor_locator(self.minorlocator) + self._long_axis().set_major_formatter(self.formatter) def _get_ticker_locator_formatter(self): """ Return the ``locator`` and ``formatter`` of the colorbar. - If they have not been defined (i.e. are *None*), suitable formatter - and locator instances will be created, attached to the respective - attributes and returned. + If they have not been defined (i.e. are *None*), the formatter and + locator are retrieved from the axis, or from the value of the + boundaries for a boundary norm. + + Called by update_ticks... """ locator = self.locator formatter = self.formatter - if locator is None: - 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 = _ColorbarLogLocator(self) - elif isinstance(self.norm, colors.SymLogNorm): - # The subs setting here should be replaced - # by logic in the locator. - locator = ticker.SymmetricalLogLocator( - subs=np.arange(1, 10), - linthresh=self.norm.linthresh, - base=10) - else: - if mpl.rcParams['_internal.classic_mode']: - locator = ticker.MaxNLocator() - else: - locator = _ColorbarAutoLocator(self) - else: - b = self._boundaries[self._inside] + minorlocator = self.minorlocator + if isinstance(self.norm, colors.BoundaryNorm): + b = self.norm.boundaries + if locator is None: + locator = ticker.FixedLocator(b, nbins=10) + elif self.boundaries is not None: + b = self._boundaries[self._inside] + if locator is None: locator = ticker.FixedLocator(b, nbins=10) + else: # most cases: + if locator is None: + locator = self._long_axis().get_major_locator() + if minorlocator is None: + minorlocator = self._long_axis().get_minor_locator() + if isinstance(self.norm, colors.NoNorm): + # default locator: + nv = len(self._values) + base = 1 + int(nv / 10) + locator = ticker.IndexLocator(base=base, offset=0) + + if minorlocator is None: + minorlocator = ticker.NullLocator() if formatter is None: - if isinstance(self.norm, colors.LogNorm): - formatter = ticker.LogFormatterSciNotation() - elif isinstance(self.norm, colors.SymLogNorm): - formatter = ticker.LogFormatterSciNotation( - linthresh=self.norm.linthresh) - else: - formatter = ticker.ScalarFormatter() - else: - formatter = self.formatter + formatter = self._long_axis().get_major_formatter() self.locator = locator self.formatter = formatter + self.minorlocator = minorlocator _log.debug('locator: %r', locator) - return locator, formatter - - def _use_auto_colorbar_locator(self): - """ - Return if we should use an adjustable tick locator or a fixed - one. (check is used twice so factored out here...) - """ - contouring = self.boundaries is not None and self.spacing == 'uniform' - return (type(self.norm) in [colors.Normalize, colors.LogNorm] and - not contouring) - - def _reset_locator_formatter_scale(self): - """ - Reset the locator et al to defaults. Any user-hardcoded changes - need to be re-entered if this gets called (either at init, or when - the mappable normal gets changed: Colorbar.update_normal) - """ - self.locator = None - self.formatter = None - if isinstance(self.norm, colors.LogNorm): - # *both* axes are made log so that determining the - # mid point is easier. - self.ax.set_xscale('log') - self.ax.set_yscale('log') - self.minorticks_on() - self.__scale = 'log' - else: - self.ax.set_xscale('linear') - self.ax.set_yscale('linear') - if type(self.norm) is colors.Normalize: - self.__scale = 'linear' - else: - self.__scale = 'manual' - - def update_ticks(self): - """ - Force the update of the ticks and ticklabels. This must be - called whenever the tick locator and/or tick formatter changes. - """ - ax = self.ax - # Get the locator and formatter; defaults to self.locator if not None. - locator, formatter = self._get_ticker_locator_formatter() - long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis - if self._use_auto_colorbar_locator(): - _log.debug('Using auto colorbar locator %r on colorbar', locator) - long_axis.set_major_locator(locator) - long_axis.set_major_formatter(formatter) - else: - _log.debug('Using fixed locator on colorbar') - ticks, ticklabels, offset_string = self._ticker(locator, formatter) - long_axis.set_ticks(ticks) - long_axis.set_ticklabels(ticklabels) - long_axis.get_major_formatter().set_offset_string(offset_string) + @_api.delete_parameter("3.5", "update_ticks") def set_ticks(self, ticks, update_ticks=True): """ Set tick locations. @@ -682,66 +746,46 @@ def set_ticks(self, ticks, update_ticks=True): to using a default locator. update_ticks : bool, default: True - If True, tick locations are updated immediately. If False, the - user has to call `update_ticks` later to update the ticks. + As of 3.5 this has no effect. """ if np.iterable(ticks): self.locator = ticker.FixedLocator(ticks, nbins=len(ticks)) else: self.locator = ticks - - if update_ticks: - self.update_ticks() + self._long_axis().set_major_locator(self.locator) self.stale = True def get_ticks(self, minor=False): """Return the x ticks as a list of locations.""" - if self._manual_tick_data_values is None: - ax = self.ax - long_axis = ( - ax.yaxis if self.orientation == 'vertical' else ax.xaxis) - return long_axis.get_majorticklocs() - else: - # We made the axes manually, the old way, and the ylim is 0-1, - # so the majorticklocs are in those units, not data units. - return self._manual_tick_data_values + return self._long_axis().get_majorticklocs() + @_api.delete_parameter("3.5", "update_ticks") def set_ticklabels(self, ticklabels, update_ticks=True): """ Set tick labels. - Tick labels are updated immediately unless *update_ticks* is *False*, - in which case one should call `.update_ticks` explicitly. + update_ticks : bool, default: True + This keyword argument is ignored and will be be removed. """ if isinstance(self.locator, ticker.FixedLocator): self.formatter = ticker.FixedFormatter(ticklabels) - if update_ticks: - self.update_ticks() else: - _api.warn_external("set_ticks() must have been called.") + _api._warn_external("set_ticks() must have been called.") self.stale = True def minorticks_on(self): """ - Turn the minor ticks of the colorbar on without extruding - into the "extend regions". + Turn on colorbar minor ticks. """ - ax = self.ax - long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis - - if long_axis.get_scale() == 'log': - long_axis.set_minor_locator(_ColorbarLogLocator(self, base=10., - subs='auto')) - long_axis.set_minor_formatter(ticker.LogFormatterSciNotation()) - else: - long_axis.set_minor_locator(_ColorbarAutoMinorLocator(self)) + self.ax.minorticks_on() + self.minorlocator = self._long_axis().get_minor_locator() + self._short_axis().set_minor_locator(ticker.NullLocator()) def minorticks_off(self): """Turn the minor ticks of the colorbar off.""" - ax = self.ax - long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis - long_axis.set_minor_locator(ticker.NullLocator()) + self.minorlocator = ticker.NullLocator() + self._long_axis().set_minor_locator(self.minorlocator) def set_label(self, label, *, loc=None, **kwargs): """ @@ -770,94 +814,14 @@ def set_label(self, label, *, loc=None, **kwargs): self.ax.set_xlabel(label, loc=loc, **kwargs) self.stale = True - def _add_solids(self, X, Y, C): - """Draw the colors; optionally add separators.""" - # Cleanup previously set artists. - if self.solids is not None: - self.solids.remove() - for solid in self.solids_patches: - solid.remove() - # Add new artist(s), based on mappable type. Use individual patches if - # hatching is needed, pcolormesh otherwise. - mappable = getattr(self, 'mappable', None) - if (isinstance(mappable, contour.ContourSet) - and any(hatch is not None for hatch in mappable.hatches)): - self._add_solids_patches(X, Y, C, mappable) - else: - self._add_solids_pcolormesh(X, Y, C) - self.dividers.set_segments( - np.dstack([X, Y])[1:-1] if self.drawedges else []) - - def _add_solids_pcolormesh(self, X, Y, C): - _log.debug('Setting pcolormesh') - if C.shape[0] == Y.shape[0]: - # trim the last one to be compatible with old behavior. - C = C[:-1] - self.solids = self.ax.pcolormesh( - X, Y, C, cmap=self.cmap, norm=self.norm, alpha=self.alpha, - edgecolors='none', shading='flat') - if not self.drawedges: - if len(self._y) >= self.n_rasterize: - self.solids.set_rasterized(True) - - def _add_solids_patches(self, X, Y, C, mappable): - hatches = mappable.hatches * len(C) # Have enough hatches. - patches = [] - for i in range(len(X) - 1): - xy = np.array([[X[i, 0], Y[i, 0]], - [X[i, 1], Y[i, 0]], - [X[i + 1, 1], Y[i + 1, 0]], - [X[i + 1, 0], Y[i + 1, 1]]]) - patch = mpatches.PathPatch(mpath.Path(xy), - facecolor=self.cmap(self.norm(C[i][0])), - hatch=hatches[i], linewidth=0, - antialiased=False, alpha=self.alpha) - self.ax.add_patch(patch) - patches.append(patch) - self.solids_patches = patches - - def add_lines(self, levels, colors, linewidths, erase=True): - """ - Draw lines on the colorbar. - - The lines are appended to the list :attr:`lines`. - - Parameters - ---------- - levels : array-like - The positions of the lines. - colors : color or list of colors - Either a single color applying to all lines or one color value for - each line. - linewidths : float or array-like - Either a single linewidth applying to all lines or one linewidth - for each line. - erase : bool, default: True - Whether to remove any previously added lines. - """ - y = self._locate(levels) - rtol = (self._y[-1] - self._y[0]) * 1e-10 - igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol) - y = y[igood] - if np.iterable(colors): - colors = np.asarray(colors)[igood] - if np.iterable(linewidths): - linewidths = np.asarray(linewidths)[igood] - X, Y = np.meshgrid([self._y[0], self._y[-1]], y) - if self.orientation == 'vertical': - xy = np.stack([X, Y], axis=-1) - else: - xy = np.stack([Y, X], axis=-1) - col = collections.LineCollection(xy, linewidths=linewidths) + def set_alpha(self, alpha): + """Set the transparency between 0 (transparent) and 1 (opaque).""" + self.alpha = alpha - if erase and self.lines: - for lc in self.lines: - lc.remove() - self.lines = [] - self.lines.append(col) - col.set_color(colors) - self.ax.add_collection(col) - self.stale = True + def remove(self): + """Remove this colorbar from the figure.""" + self.ax.inner_ax.remove() + self.ax.parent_ax.remove() def _ticker(self, locator, formatter): """ @@ -882,33 +846,22 @@ def _ticker(self, locator, formatter): else: eps = (intv[1] - intv[0]) * 1e-10 b = b[(b <= intv[1] + eps) & (b >= intv[0] - eps)] - self._manual_tick_data_values = b ticks = self._locate(b) ticklabels = formatter.format_ticks(b) offset_string = formatter.get_offset() return ticks, ticklabels, offset_string - def _process_values(self, b=None): + def _process_values(self): """ - 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*. + Set `_boundaries` and `_values` based on the self.boundaries and + self.values if not None, or based on the size of the colormap and + the vmin/vmax of the norm. """ - if 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) - else: - self._values = np.array(self.values) - return if self.values is not None: + # set self._boundaries from the values... self._values = np.array(self.values) if self.boundaries is None: + # bracket values by 1/2 dv: b = np.zeros(len(self.values) + 1) b[1:-1] = 0.5 * (self._values[:-1] + self._values[1:]) b[0] = 2.0 * b[1] - b[2] @@ -917,150 +870,34 @@ def _process_values(self, b=None): 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[self._inside] = np.arange(self.cmap.N, dtype=np.int16) - if self._extend_lower(): - v[0] = -1 - if self._extend_upper(): - v[-1] = self.cmap.N - self._boundaries = b - self._values = v - return - elif isinstance(self.norm, colors.BoundaryNorm): - b = list(self.norm.boundaries) - if self._extend_lower(): - b = [b[0] - 1] + b - if self._extend_upper(): - b = b + [b[-1] + 1] - b = np.array(b) - v = np.zeros(len(b) - 1) - bi = self.norm.boundaries - v[self._inside] = 0.5 * (bi[:-1] + bi[1:]) - if self._extend_lower(): - v[0] = b[0] - 1 - if self._extend_upper(): - v[-1] = b[-1] + 1 - self._boundaries = b - self._values = v - return - else: - if not self.norm.scaled(): - self.norm.vmin = 0 - self.norm.vmax = 1 - - self.norm.vmin, self.norm.vmax = mtransforms.nonsingular( - self.norm.vmin, - self.norm.vmax, - expander=0.1) - - b = self.norm.inverse(self._uniform_y(self.cmap.N + 1)) - - if isinstance(self.norm, (colors.PowerNorm, colors.LogNorm)): - # If using a lognorm or powernorm, ensure extensions don't - # go negative - if self._extend_lower(): - b[0] = 0.9 * b[0] - if self._extend_upper(): - b[-1] = 1.1 * b[-1] - else: - if self._extend_lower(): - b[0] = b[0] - 1 - if self._extend_upper(): - b[-1] = b[-1] + 1 - self._process_values(b) - - def _get_extension_lengths(self, frac, automin, automax, default=0.05): - """ - Return the lengths of colorbar extensions. - - This is a helper method for _uniform_y and _proportional_y. - """ - # Set the default value. - extendlength = np.array([default, default]) - if isinstance(frac, str): - _api.check_in_list(['auto'], extendfrac=frac.lower()) - # Use the provided values when 'auto' is required. - extendlength[:] = [automin, automax] - elif frac is not None: - try: - # Try to set min and max extension fractions directly. - extendlength[:] = frac - # If frac is a sequence containing None then NaN may - # be encountered. This is an error. - if np.isnan(extendlength).any(): - raise ValueError() - except (TypeError, ValueError) as err: - # Raise an error on encountering an invalid value for frac. - raise ValueError('invalid value for extendfrac') from err - return extendlength - - def _uniform_y(self, N): - """ - Return colorbar data coordinates for *N* uniformly - spaced boundaries, plus ends if required. - """ - if self.extend == 'neither': - y = np.linspace(0, 1, N) - else: - automin = automax = 1. / (N - 1.) - extendlength = self._get_extension_lengths(self.extendfrac, - automin, automax, - default=0.05) - if self.extend == 'both': - y = np.zeros(N + 2, 'd') - y[0] = 0. - extendlength[0] - y[-1] = 1. + extendlength[1] - elif self.extend == 'min': - y = np.zeros(N + 1, 'd') - y[0] = 0. - extendlength[0] - else: - y = np.zeros(N + 1, 'd') - y[-1] = 1. + extendlength[1] - y[self._inside] = np.linspace(0, 1, N) - return y - def _proportional_y(self): - """ - Return colorbar data coordinates for the boundaries of - a proportional colorbar. - """ + # otherwise values are set from the boundaries (and probably aren't + # that important) if isinstance(self.norm, colors.BoundaryNorm): - y = (self._boundaries - self._boundaries[0]) - y = y / (self._boundaries[-1] - self._boundaries[0]) + b = self.norm.boundaries else: - y = self.norm(self._boundaries.copy()) - y = np.ma.filled(y, np.nan) - if self.extend == 'min': - # Exclude leftmost interval of y. - clen = y[-1] - y[1] - automin = (y[2] - y[1]) / clen - automax = (y[-1] - y[-2]) / clen - elif self.extend == 'max': - # Exclude rightmost interval in y. - clen = y[-2] - y[0] - automin = (y[1] - y[0]) / clen - automax = (y[-2] - y[-3]) / clen - elif self.extend == 'both': - # Exclude leftmost and rightmost intervals in y. - clen = y[-2] - y[1] - automin = (y[2] - y[1]) / clen - automax = (y[-2] - y[-3]) / clen - if self.extend in ('both', 'min', 'max'): - extendlength = self._get_extension_lengths(self.extendfrac, - automin, automax, - default=0.05) - if self.extend in ('both', 'min'): - y[0] = 0. - extendlength[0] - if self.extend in ('both', 'max'): - y[-1] = 1. + extendlength[1] - yi = y[self._inside] - norm = colors.Normalize(yi[0], yi[-1]) - y[self._inside] = np.ma.filled(norm(yi), np.nan) - return y + # otherwise make the boundaries from the size of the cmap: + N = self.cmap.N + 1 + b, _ = self._uniform_y(N) + # add extra boundaries if needed: + if self._extend_lower(): + b = np.hstack((b[0] - 1, b)) + if self._extend_lower(): + b = np.hstack((b, b[-1] + 1)) + + # transform from 0-1 to vmin-vmax: + if not self.norm.scaled(): + self.norm.vmin = 0 + self.norm.vmax = 1 + self.norm.vmin, self.norm.vmax = mtransforms.nonsingular( + self.norm.vmin, self.norm.vmax, expander=0.1) + if not isinstance(self.norm, colors.BoundaryNorm): + b = self.norm.inverse(b) + + self._boundaries = np.asarray(b, dtype=float) + self._values = 0.5 * (self._boundaries[:-1] + self._boundaries[1:]) + if isinstance(self.norm, colors.NoNorm): + self._values = (self._values + 0.00001).astype(np.int16) def _mesh(self): """ @@ -1077,30 +914,64 @@ def _mesh(self): norm.vmin = self.vmin norm.vmax = self.vmax x = np.array([0.0, 1.0]) - if self.spacing == 'uniform': - n_boundaries_no_extensions = len(self._boundaries[self._inside]) - y = self._uniform_y(n_boundaries_no_extensions) - else: - y = self._proportional_y() - xmid = np.array([0.5]) - if self.__scale != 'manual': - y = norm.inverse(y) - x = norm.inverse(x) - xmid = norm.inverse(xmid) - else: - # if a norm doesn't have a named scale, or - # we are not using a norm + y, extendlen = self._proportional_y() + # invert: + if (isinstance(norm, (colors.BoundaryNorm, colors.NoNorm)) or + (self.__scale == 'manual')): + # if a norm doesn't have a named scale, or we are not using a norm: dv = self.vmax - self.vmin x = x * dv + self.vmin y = y * dv + self.vmin - xmid = xmid * dv + self.vmin + else: + y = norm.inverse(y) + x = norm.inverse(x) self._y = y X, Y = np.meshgrid(x, y) - if self._extend_lower() and not self.extendrect: - X[0, :] = xmid - if self._extend_upper() and not self.extendrect: - X[-1, :] = xmid - return (X, Y) if self.orientation == 'vertical' else (Y, X) + if self.orientation == 'vertical': + return (X, Y, extendlen) + else: + return (Y, X, extendlen) + + def _forward_boundaries(self, x): + b = self._boundaries + y = np.interp(x, b, np.linspace(0, b[-1], len(b))) + eps = (b[-1] - b[0]) * 1e-6 + y[x < b[0]-eps] = -1 + y[x > b[-1]+eps] = 2 + return y + + def _inverse_boundaries(self, x): + b = self._boundaries + return np.interp(x, np.linspace(0, b[-1], len(b)), b) + + def _reset_locator_formatter_scale(self): + """ + Reset the locator et al to defaults. Any user-hardcoded changes + need to be re-entered if this gets called (either at init, or when + the mappable normal gets changed: Colorbar.update_normal) + """ + self._process_values() + self.locator = None + self.minorlocator = None + self.formatter = None + if ((self.spacing == 'uniform') and + ((self.boundaries is not None) or + isinstance(self.norm, colors.BoundaryNorm))): + funcs = (self._forward_boundaries, self._inverse_boundaries) + self.ax.set_xscale('function', functions=funcs) + self.ax.set_yscale('function', functions=funcs) + self.__scale = 'function' + elif hasattr(self.norm, '_scale') and (self.norm._scale is not None): + self.ax.set_xscale(self.norm._scale) + self.ax.set_yscale(self.norm._scale) + self.__scale = self.norm._scale.name + else: + self.ax.set_xscale('linear') + self.ax.set_yscale('linear') + if type(self.norm) is colors.Normalize: + self.__scale = 'linear' + else: + self.__scale = 'manual' def _locate(self, x): """ @@ -1116,28 +987,106 @@ def _locate(self, x): b = self.norm(self._boundaries, clip=False).filled() xn = self.norm(x, clip=False).filled() - bunique = b + bunique = b[self._inside] yunique = self._y - # trim extra b values at beginning and end if they are - # not unique. These are here for extended colorbars, and are not - # wanted for the interpolation. - if b[0] == b[1]: - bunique = bunique[1:] - yunique = yunique[1:] - if b[-1] == b[-2]: - bunique = bunique[:-1] - yunique = yunique[:-1] z = np.interp(xn, bunique, yunique) return z - def set_alpha(self, alpha): - """Set the transparency between 0 (transparent) and 1 (opaque).""" - self.alpha = alpha + # trivial helpers - def remove(self): - """Remove this colorbar from the figure.""" - self.ax.remove() + def _uniform_y(self, N): + """ + Return colorbar data coordinates for *N* uniformly + spaced boundaries, plus extension lengths if required. + """ + automin = automax = 1. / (N - 1.) + extendlength = self._get_extension_lengths(self.extendfrac, + automin, automax, + default=0.05) + y = np.linspace(0, 1, N) + return y, extendlength + + def _proportional_y(self): + """ + Return colorbar data coordinates for the boundaries of + a proportional colorbar, plus extension lengths if required: + """ + if isinstance(self.norm, colors.BoundaryNorm): + y = (self._boundaries - self._boundaries[0]) + y = y / (self._boundaries[-1] - self._boundaries[0]) + # need yscaled the same as the axes scale to get + # the extend lengths. + if self.spacing == 'uniform': + yscaled = self._forward_boundaries(self._boundaries) + else: + yscaled = y + else: + y = self.norm(self._boundaries.copy()) + y = np.ma.filled(y, np.nan) + # the norm and the scale should be the same... + yscaled = y + y = y[self._inside] + yscaled = yscaled[self._inside] + # normalize from 0..1: + norm = colors.Normalize(y[0], y[-1]) + y = np.ma.filled(norm(y), np.nan) + norm = colors.Normalize(yscaled[0], yscaled[-1]) + yscaled = np.ma.filled(norm(yscaled), np.nan) + # make the lower and upper extend lengths proportional to the lengths + # of the first and last boundary spacing (if extendfrac='auto'): + automin = yscaled[1] - yscaled[0] + automax = yscaled[-1] - yscaled[-2] + extendlength = [0, 0] + if self._extend_lower() or self._extend_upper(): + extendlength = self._get_extension_lengths( + self.extendfrac, automin, automax, default=0.05) + return y, extendlength + + def _get_extension_lengths(self, frac, automin, automax, default=0.05): + """ + Return the lengths of colorbar extensions. + + This is a helper method for _uniform_y and _proportional_y. + """ + # Set the default value. + extendlength = np.array([default, default]) + if isinstance(frac, str): + _api.check_in_list(['auto'], extendfrac=frac.lower()) + # Use the provided values when 'auto' is required. + extendlength[:] = [automin, automax] + elif frac is not None: + try: + # Try to set min and max extension fractions directly. + extendlength[:] = frac + # If frac is a sequence containing None then NaN may + # be encountered. This is an error. + if np.isnan(extendlength).any(): + raise ValueError() + except (TypeError, ValueError) as err: + # Raise an error on encountering an invalid value for frac. + raise ValueError('invalid value for extendfrac') from err + return extendlength + + def _extend_lower(self): + """Return whether the lower limit is open ended.""" + return self.extend in ('both', 'min') + + def _extend_upper(self): + """Return whether the upper limit is open ended.""" + return self.extend in ('both', 'max') + + def _long_axis(self): + """Return the long axis""" + if self.orientation == 'vertical': + return self.ax.yaxis + return self.ax.xaxis + + def _short_axis(self): + """Return the short axis""" + if self.orientation == 'vertical': + return self.ax.xaxis + return self.ax.yaxis def _add_disjoint_kwargs(d, **kwargs): diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf index 8c1f477a66b1..71dfb99ef720 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png index 002557764760..a9910261f5d6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png and b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg index c5b68c60ecd0..daec125fb833 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg @@ -1,12 +1,23 @@ - + + + + + 2020-12-09T10:29:50.057062 + image/svg+xml + + + Matplotlib v3.3.2.post1806.dev0+g872e36a451, https://matplotlib.org/ + + + + + - + @@ -27,7 +38,7 @@ z " style="fill:#ffffff;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +" id="m6ebbcb5ec5" style="stroke:#000000;stroke-width:0.8;"/> - + - - + + - + - - - - +" id="DejaVuSans-33" transform="scale(0.015625)"/> + + + - + - - - - - + + + + + - + - - - - - + + + + + - + - - + + - - - +" id="DejaVuSans-30" transform="scale(0.015625)"/> + + - + - + - + - + - + - + - + - - + + - - - +" id="DejaVuSans-34" transform="scale(0.015625)"/> + + - + - - - - + + + + @@ -14679,123 +14695,123 @@ z +" id="m16bf73c6a3" style="stroke:#000000;stroke-width:0.8;"/> - + - - + + - + - - + + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + @@ -14803,7 +14819,7 @@ L -3.5 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +" style="fill:none;"/> - - + - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - +" id="me4489573a6" style="stroke:#000000;stroke-width:0.8;"/> + + + + - - - - - + + + + - + - - - - - - +" id="DejaVuSans-36" transform="scale(0.015625)"/> + + + + + + - - - - - + + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + - - - - - + + + + - - - - - - +" id="DejaVuSans-38" transform="scale(0.015625)"/> + + + + + + - - - - - + + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - + + - - - - - - - - - - - + - - - - - - - - - - + +" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/> + - + - - + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_change_lim_scale.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_change_lim_scale.png new file mode 100644 index 000000000000..2cb614a61fd6 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_change_lim_scale.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.pdf b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.pdf deleted file mode 100644 index 81271ec29c81..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.png index bb41d922189f..e48e4fbd26f1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.svg b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.svg deleted file mode 100644 index 5e205feadc5e..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_closed_patch.svg +++ /dev/null @@ -1,650 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png index 9f2067a45a40..230f8d7332ba 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_proportional.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_proportional.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_proportional.png index d68d71bb2d0e..7dbdbbd9b767 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_proportional.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_proportional.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_uniform.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_uniform.png index c4e72454cd4f..eb1e8b82bf02 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_uniform.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_shape_uniform.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png index 4a10633a6253..86e4094208d6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extensions_uniform.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png index 63a58245f7e0..18d9cf02add0 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/double_cbar.png b/lib/matplotlib/tests/baseline_images/test_colorbar/double_cbar.png index 2ddb219eda3a..e533132e71f0 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/double_cbar.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/double_cbar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colors/levels_and_colors.png b/lib/matplotlib/tests/baseline_images/test_colors/levels_and_colors.png index bb759674e557..bb0e9a2538da 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colors/levels_and_colors.png and b/lib/matplotlib/tests/baseline_images/test_colors/levels_and_colors.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png index 514bf02ce13c..f337d370dc33 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png index b674803ba2e8..534903300f7a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout13.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout13.png index 5889b0583432..4233f58a8ce4 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout13.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout13.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout14.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout14.png index e030c3c9f6c1..cfe9dca14c88 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout14.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout14.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout3.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout3.png index c679609be54e..ae6420dd04e9 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout3.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout3.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout4.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout4.png index 2a6e55c08f64..ef6d9e417f91 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout4.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout4.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout5.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout5.png index af691c44867d..89e71b765154 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout5.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout5.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png index 757230e25363..a239947ca46c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout9.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout9.png index 59fd2c76c5bc..2ac44b8a18ac 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout9.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout9.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_addlines.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_addlines.png index 2115054d13b2..ffce1cb042c2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_addlines.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_addlines.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png index c1feda1e9cba..316b6370edca 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png index 632d1f7e7594..7fd1be07e70e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png b/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png index 904e0c3d44a0..f416faa96d5f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png and b/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png differ diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index bbd1e9c5f590..1457160a0ca4 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -9,7 +9,7 @@ import matplotlib.pyplot as plt from matplotlib.colors import (BoundaryNorm, LogNorm, PowerNorm, Normalize, TwoSlopeNorm) -from matplotlib.colorbar import ColorbarBase, _ColorbarLogLocator +from matplotlib.colorbar import ColorbarBase from matplotlib.ticker import FixedLocator @@ -86,13 +86,13 @@ def _colorbar_extension_length(spacing): # Create a subplot. cax = fig.add_subplot(12, 1, i*3 + j + 1) # Generate the colorbar. - ColorbarBase(cax, cmap=cmap, norm=norm, - boundaries=boundaries, values=values, - extend=extension_type, extendfrac=extendfrac, - orientation='horizontal', spacing=spacing) + cb = ColorbarBase(cax, cmap=cmap, norm=norm, + boundaries=boundaries, values=values, + extend=extension_type, extendfrac=extendfrac, + orientation='horizontal', spacing=spacing) # Turn off text and ticks. cax.tick_params(left=False, labelleft=False, - bottom=False, labelbottom=False) + bottom=False, labelbottom=False) # Return the figure to the caller. return fig @@ -240,7 +240,7 @@ def test_colorbarbase(): ColorbarBase(ax, cmap=plt.cm.bone) -@image_comparison(['colorbar_closed_patch'], remove_text=True) +@image_comparison(['colorbar_closed_patch.png'], remove_text=True) def test_colorbar_closed_patch(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -308,10 +308,11 @@ def test_colorbar_minorticks_on_off(): im.set_clim(vmin=-1.2, vmax=1.2) cbar.minorticks_on() + #fig.canvas.draw() np.testing.assert_almost_equal( cbar.ax.yaxis.get_minorticklocs(), - [-1.2, -1.1, -0.9, -0.8, -0.7, -0.6, -0.4, -0.3, -0.2, -0.1, - 0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, 1.1, 1.2]) + [-1.1, -0.9, -0.8, -0.7, -0.6, -0.4, -0.3, -0.2, -0.1, + 0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, 1.1, 1.2, 1.3]) # tests for github issue #13257 and PR #13265 data = np.random.uniform(low=1, high=10, size=(20, 20)) @@ -319,8 +320,8 @@ def test_colorbar_minorticks_on_off(): fig, ax = plt.subplots() im = ax.pcolormesh(data, norm=LogNorm()) cbar = fig.colorbar(im) + fig.canvas.draw() default_minorticklocks = cbar.ax.yaxis.get_minorticklocs() - # test that minorticks turn off for LogNorm cbar.minorticks_off() np.testing.assert_equal(cbar.ax.yaxis.get_minorticklocs(), []) @@ -381,10 +382,12 @@ def test_colorbar_autoticks(): pcm = ax[1].pcolormesh(X, Y, Z) cbar2 = fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical', shrink=0.4) + # note only -10 to 10 are visible, np.testing.assert_almost_equal(cbar.ax.yaxis.get_ticklocs(), - np.arange(-10, 11, 5)) + np.arange(-15, 16, 5)) + # note only -10 to 10 are visible np.testing.assert_almost_equal(cbar2.ax.yaxis.get_ticklocs(), - np.arange(-10, 11, 10)) + np.arange(-20, 21, 10)) def test_colorbar_autotickslog(): @@ -403,10 +406,12 @@ def test_colorbar_autotickslog(): pcm = ax[1].pcolormesh(X, Y, 10**Z, norm=LogNorm()) cbar2 = fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical', shrink=0.4) + # note only -12 to +12 are visible np.testing.assert_almost_equal(cbar.ax.yaxis.get_ticklocs(), - 10**np.arange(-12., 12.2, 4.)) + 10**np.arange(-16., 16.2, 4.)) + # note only -24 to +24 are visible np.testing.assert_almost_equal(cbar2.ax.yaxis.get_ticklocs(), - 10**np.arange(-12., 13., 12.)) + 10**np.arange(-24., 25., 12.)) def test_colorbar_get_ticks(): @@ -426,12 +431,14 @@ def test_colorbar_get_ticks(): assert userTicks.get_ticks().tolist() == [600, 700, 800] # testing for getter after calling set_ticks with some ticks out of bounds - userTicks.set_ticks([600, 1300, 1400, 1500]) - assert userTicks.get_ticks().tolist() == [600] + # removed #18900: other axes don't trim fixed lists, so colorbars + # should not either: + # userTicks.set_ticks([600, 1300, 1400, 1500]) + # assert userTicks.get_ticks().tolist() == [600] # testing getter when no ticks are assigned defTicks = plt.colorbar(orientation='horizontal') - assert defTicks.get_ticks().tolist() == levels + np.testing.assert_allclose(defTicks.get_ticks().tolist(), levels) def test_colorbar_lognorm_extension(): @@ -465,13 +472,13 @@ def test_colorbar_log_minortick_labels(): pcm = ax.imshow([[10000, 50000]], norm=LogNorm()) cb = fig.colorbar(pcm) fig.canvas.draw() - lb = cb.ax.yaxis.get_ticklabels(which='both') + lb = [l.get_text() for l in cb.ax.yaxis.get_ticklabels(which='both')] expected = [r'$\mathdefault{10^{4}}$', r'$\mathdefault{2\times10^{4}}$', r'$\mathdefault{3\times10^{4}}$', r'$\mathdefault{4\times10^{4}}$'] - for l, exp in zip(lb, expected): - assert l.get_text() == exp + for exp in expected: + assert exp in lb def test_colorbar_renorm(): @@ -482,16 +489,15 @@ def test_colorbar_renorm(): im = ax.imshow(z) cbar = fig.colorbar(im) np.testing.assert_allclose(cbar.ax.yaxis.get_majorticklocs(), - np.arange(0, 120000.1, 15000)) + np.arange(0, 120000.1, 20000)) cbar.set_ticks([1, 2, 3]) assert isinstance(cbar.locator, FixedLocator) norm = LogNorm(z.min(), z.max()) im.set_norm(norm) - assert isinstance(cbar.locator, _ColorbarLogLocator) np.testing.assert_allclose(cbar.ax.yaxis.get_majorticklocs(), - np.logspace(-8, 5, 14)) + np.logspace(-10, 7, 18)) # note that set_norm removes the FixedLocator... assert np.isclose(cbar.vmin, z.min()) cbar.set_ticks([1, 2, 3]) @@ -514,19 +520,19 @@ def test_colorbar_format(): im = ax.imshow(z) cbar = fig.colorbar(im, format='%4.2e') fig.canvas.draw() - assert cbar.ax.yaxis.get_ticklabels()[4].get_text() == '6.00e+04' + assert cbar.ax.yaxis.get_ticklabels()[4].get_text() == '8.00e+04' # make sure that if we change the clim of the mappable that the # formatting is *not* lost: im.set_clim([4, 200]) fig.canvas.draw() - assert cbar.ax.yaxis.get_ticklabels()[4].get_text() == '8.00e+01' + assert cbar.ax.yaxis.get_ticklabels()[4].get_text() == '2.00e+02' # but if we change the norm: im.set_norm(LogNorm(vmin=0.1, vmax=10)) fig.canvas.draw() assert (cbar.ax.yaxis.get_ticklabels()[0].get_text() == - r'$\mathdefault{10^{-1}}$') + r'$\mathdefault{10^{-2}}$') def test_colorbar_scale_reset(): @@ -552,7 +558,7 @@ def test_colorbar_get_ticks_2(): fig, ax = plt.subplots() pc = ax.pcolormesh([[.05, .95]]) cb = fig.colorbar(pc) - np.testing.assert_allclose(cb.get_ticks(), [0.2, 0.4, 0.6, 0.8]) + np.testing.assert_allclose(cb.get_ticks(), [0., 0.2, 0.4, 0.6, 0.8, 1.0]) def test_colorbar_inverted_ticks(): @@ -582,7 +588,7 @@ def test_extend_colorbar_customnorm(): pcm = ax0.pcolormesh([[0]], norm=TwoSlopeNorm(vcenter=0., vmin=-2, vmax=1)) cb = fig.colorbar(pcm, ax=ax0, extend='both') np.testing.assert_allclose(cb.ax.get_position().extents, - [0.78375, 0.536364, 0.796147, 0.9], rtol=1e-3) + [0.78375, 0.536364, 0.796147, 0.9], rtol=2e-3) def test_mappable_no_alpha(): @@ -705,3 +711,16 @@ def test_anchored_cbar_position_using_specgrid(): np.testing.assert_allclose( [cx1, cx0], [x1 * shrink + (1 - shrink) * p0, p0 * (1 - shrink) + x0 * shrink]) + + +@image_comparison(['colorbar_change_lim_scale.png'], remove_text=True, + style='mpl20') +def test_colorbar_change_lim_scale(): + fig, ax = plt.subplots(1, 2, constrained_layout=True) + pc = ax[0].pcolormesh(np.arange(100).reshape(10, 10)+1) + cb = fig.colorbar(pc, ax=ax[0], extend='both') + cb.ax.set_yscale('log') + + pc = ax[1].pcolormesh(np.arange(100).reshape(10, 10)+1) + cb = fig.colorbar(pc, ax=ax[1], extend='both') + cb.ax.set_ylim([20, 90]) diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index d9c31a148a9b..872c9b9b0d7e 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -711,7 +711,7 @@ def test_SymLogNorm_single_zero(): norm = mcolors.SymLogNorm(1e-5, vmin=-1, vmax=1, base=np.e) cbar = mcolorbar.ColorbarBase(fig.add_subplot(), norm=norm) ticks = cbar.get_ticks() - assert sum(ticks == 0) == 1 + assert np.count_nonzero(ticks == 0) <= 1 plt.close(fig) @@ -778,7 +778,7 @@ def test_boundarynorm_and_colorbarbase(): # Default behavior norm = mcolors.BoundaryNorm(bounds, cmap.N) cb1 = mcolorbar.ColorbarBase(ax1, cmap=cmap, norm=norm, extend='both', - orientation='horizontal') + orientation='horizontal', spacing='uniform') # New behavior norm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both') cb2 = mcolorbar.ColorbarBase(ax2, cmap=cmap, norm=norm, diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index 67474628e7bf..5b1e99b5bde6 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -52,8 +52,6 @@ def test_constrained_layout2(): @image_comparison(['constrained_layout3.png']) def test_constrained_layout3(): """Test constrained_layout for colorbars with subplots""" - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False fig, axs = plt.subplots(2, 2, constrained_layout=True) for nn, ax in enumerate(axs.flat): @@ -68,8 +66,6 @@ def test_constrained_layout3(): @image_comparison(['constrained_layout4.png']) def test_constrained_layout4(): """Test constrained_layout for a single colorbar with subplots""" - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False fig, axs = plt.subplots(2, 2, constrained_layout=True) for ax in axs.flat: @@ -83,8 +79,6 @@ def test_constrained_layout5(): Test constrained_layout for a single colorbar with subplots, colorbar bottom """ - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False fig, axs = plt.subplots(2, 2, constrained_layout=True) for ax in axs.flat: @@ -140,8 +134,6 @@ def test_constrained_layout7(): @image_comparison(['constrained_layout8.png']) def test_constrained_layout8(): """Test for gridspecs that are not completely full""" - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False fig = plt.figure(figsize=(10, 5), constrained_layout=True) gs = gridspec.GridSpec(3, 5, figure=fig) @@ -170,8 +162,6 @@ def test_constrained_layout8(): @image_comparison(['constrained_layout9.png']) def test_constrained_layout9(): """Test for handling suptitle and for sharex and sharey""" - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False fig, axs = plt.subplots(2, 2, constrained_layout=True, sharex=False, sharey=False) @@ -196,8 +186,6 @@ def test_constrained_layout10(): @image_comparison(['constrained_layout11.png']) def test_constrained_layout11(): """Test for multiple nested gridspecs""" - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False fig = plt.figure(constrained_layout=True, figsize=(13, 3)) gs0 = gridspec.GridSpec(1, 2, figure=fig) @@ -218,8 +206,6 @@ def test_constrained_layout11(): @image_comparison(['constrained_layout11rat.png']) def test_constrained_layout11rat(): """Test for multiple nested gridspecs with width_ratios""" - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False fig = plt.figure(constrained_layout=True, figsize=(10, 3)) gs0 = gridspec.GridSpec(1, 2, figure=fig, width_ratios=[6, 1]) @@ -262,9 +248,6 @@ def test_constrained_layout12(): @image_comparison(['constrained_layout13.png'], tol=2.e-2) def test_constrained_layout13(): """Test that padding works.""" - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False - fig, axs = plt.subplots(2, 2, constrained_layout=True) for ax in axs.flat: pcm = example_pcolor(ax, fontsize=12) @@ -275,9 +258,6 @@ def test_constrained_layout13(): @image_comparison(['constrained_layout14.png']) def test_constrained_layout14(): """Test that padding works.""" - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False - fig, axs = plt.subplots(2, 2, constrained_layout=True) for ax in axs.flat: pcm = example_pcolor(ax, fontsize=12) diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index 58c15c9b34fa..5b65be9a4b6e 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -338,8 +338,6 @@ def test_contourf_log_extension(): cb = plt.colorbar(c2, ax=ax2) assert cb.ax.get_ylim() == (1e-4, 1e6) cb = plt.colorbar(c3, ax=ax3) - assert_array_almost_equal( - cb.ax.get_ylim(), [3.162277660168379e-05, 3162277.660168383], 2) @image_comparison(['contour_addlines.png'], diff --git a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png b/lib/mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png index a42548f9f6cd..eb16727ed407 100644 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png and b/lib/mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png differ diff --git a/lib/mpl_toolkits/tests/test_axes_grid.py b/lib/mpl_toolkits/tests/test_axes_grid.py index f789c31b7c67..4d77b90e5e03 100644 --- a/lib/mpl_toolkits/tests/test_axes_grid.py +++ b/lib/mpl_toolkits/tests/test_axes_grid.py @@ -1,6 +1,7 @@ import numpy as np import matplotlib as mpl +import matplotlib.ticker as mticker from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt from mpl_toolkits.axes_grid1 import ImageGrid @@ -43,9 +44,7 @@ def test_imagegrid_cbar_mode_edge(): # "second" ones. To achieve this, clear out the axes first. for ax in grid: ax.cax.cla() - cb = ax.cax.colorbar( - ax.images[0], - ticks=mpl.ticker.MaxNLocator(5)) # old default locator. + cb = ax.cax.colorbar(ax.images[0]) def test_imagegrid(): @@ -54,4 +53,4 @@ def test_imagegrid(): ax = grid[0] im = ax.imshow([[1, 2]], norm=mpl.colors.LogNorm()) cb = ax.cax.colorbar(im) - assert isinstance(cb.locator, mpl.colorbar._ColorbarLogLocator) + assert isinstance(cb.locator, mticker.LogLocator) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy