diff --git a/CHANGELOG b/CHANGELOG index 9bc96a63385a..6eff3d3e6e50 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,17 @@ +2012-08-05 When a norm is passed to contourf, either or both of the + vmin, vmax attributes of that norm are now respected. + Formerly they were respected only if both were + specified. In addition, vmin and/or vmax can now + be passed to contourf directly as kwargs. - EF + 2012-07-24 Contourf handles the extend kwarg by mapping the extended ranges outside the normed 0-1 range so that they are handled by colormap colors determined by the set_under and set_over methods. Previously the extended ranges were mapped to 0 or 1 so that the "under" and "over" - colormap colors were ignored. - EF + colormap colors were ignored. This change also increases + slightly the color contrast for a given set of contour + levels. - EF 2012-06-24 Make use of mathtext in tick labels configurable - DSD diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index f631543b7fe2..3ccf7cdacd11 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -3754,7 +3754,7 @@ def vlines(self, x, ymin, ymax, colors='k', linestyles='solid', linestyles=linestyles, label=label) self.add_collection(coll) coll.update(kwargs) - + if len(x) > 0: minx = min( x ) maxx = max( x ) @@ -6955,16 +6955,18 @@ def pcolor(self, *args, **kwargs): A :class:`matplotlib.colors.Colormap` instance. If *None*, use rc settings. - norm: [ *None* | Normalize ] + *norm*: [ *None* | Normalize ] An :class:`matplotlib.colors.Normalize` instance is used to scale luminance data to 0,1. If *None*, defaults to :func:`normalize`. *vmin*/*vmax*: [ *None* | scalar ] *vmin* and *vmax* are used in conjunction with *norm* to - normalize luminance data. If either are *None*, the min - and max of the color array *C* is used. If you pass a - *norm* instance, *vmin* and *vmax* will be ignored. + normalize luminance data. If either is *None*, it + is autoscaled to the respective min or max + of the color array *C*. If not *None*, *vmin* or + *vmax* passed in here override any pre-existing values + supplied in the *norm* instance. *shading*: [ 'flat' | 'faceted' ] If 'faceted', a black grid is drawn around each rectangle; if @@ -7121,10 +7123,8 @@ def pcolor(self, *args, **kwargs): if norm is not None: assert(isinstance(norm, mcolors.Normalize)) collection.set_cmap(cmap) collection.set_norm(norm) - if vmin is not None or vmax is not None: - collection.set_clim(vmin, vmax) - else: - collection.autoscale_None() + collection.set_clim(vmin, vmax) + collection.autoscale_None() self.grid(False) x = X.compressed() @@ -7167,9 +7167,11 @@ def pcolormesh(self, *args, **kwargs): *vmin*/*vmax*: [ *None* | scalar ] *vmin* and *vmax* are used in conjunction with *norm* to - normalize luminance data. If either are *None*, the min - and max of the color array *C* is used. If you pass a - *norm* instance, *vmin* and *vmax* will be ignored. + normalize luminance data. If either is *None*, it + is autoscaled to the respective min or max + of the color array *C*. If not *None*, *vmin* or + *vmax* passed in here override any pre-existing values + supplied in the *norm* instance. *shading*: [ 'flat' | 'gouraud' ] 'flat' indicates a solid color for each quad. When @@ -7235,10 +7237,8 @@ def pcolormesh(self, *args, **kwargs): if norm is not None: assert(isinstance(norm, mcolors.Normalize)) collection.set_cmap(cmap) collection.set_norm(norm) - if vmin is not None or vmax is not None: - collection.set_clim(vmin, vmax) - else: - collection.autoscale_None() + collection.set_clim(vmin, vmax) + collection.autoscale_None() self.grid(False) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 2f8a72092300..149d536d742a 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -255,7 +255,7 @@ def __init__(self, ax, cmap=None, self.filled = filled self.extendfrac = extendfrac self.solids = None - self.lines = None + self.lines = list() self.outline = None self.patch = None self.dividers = None @@ -467,14 +467,25 @@ def _add_solids(self, X, Y, C): ) self.ax.add_collection(self.dividers) - def add_lines(self, levels, colors, linewidths): + def add_lines(self, levels, colors, linewidths, erase=True): ''' Draw lines on the colorbar. + + *colors* and *linewidths* must be scalars or + sequences the same length as *levels*. + + Set *erase* to False to add lines without first + removing any previously added lines. ''' - N = len(levels) - dummy, y = self._locate(levels) - if len(y) != N: - raise ValueError("levels are outside colorbar range") + y = self._locate(levels) + nlevs = len(levels) + igood = (y < 1.001) & (y > -0.001) + y = y[igood] + if cbook.iterable(colors): + colors = np.asarray(colors)[igood] + if cbook.iterable(linewidths): + linewidths = np.asarray(linewidths)[igood] + N = len(y) x = np.array([0.0, 1.0]) X, Y = np.meshgrid(x,y) if self.orientation == 'vertical': @@ -483,9 +494,10 @@ def add_lines(self, levels, colors, linewidths): xy = [zip(Y[i], X[i]) for i in xrange(N)] col = collections.LineCollection(xy, linewidths=linewidths) - if self.lines: - self.lines.remove() - self.lines = col + if erase and self.lines: + for lc in self.lines.pop(): + lc.remove() + self.lines.append(col) col.set_color(colors) self.ax.add_collection(col) @@ -528,7 +540,10 @@ def _ticker(self): locator.axis.get_minpos = lambda : intv[0] formatter.axis.get_minpos = lambda : intv[0] b = np.array(locator()) - b, ticks = self._locate(b) + ticks = self._locate(b) + inrange = (ticks > -0.001) & (ticks < 1.001) + ticks = ticks[inrange] + b = b[inrange] formatter.set_locs(b) ticklabels = [formatter(t, i) for i, t in enumerate(b)] offset_string = formatter.get_offset() @@ -746,37 +761,36 @@ def _mesh(self): def _locate(self, x): ''' - Given a possible set of color data values, return the ones - within range, together with their corresponding colorbar - data coordinates. + Given a set of color data values, return their + corresponding colorbar data coordinates. ''' if isinstance(self.norm, (colors.NoNorm, colors.BoundaryNorm)): b = self._boundaries xn = x - xout = x else: # Do calculations using normalized coordinates so # as to make the interpolation more accurate. b = self.norm(self._boundaries, clip=False).filled() - # We do our own clipping so that we can allow a tiny - # bit of slop in the end point ticks to allow for - # floating point errors. xn = self.norm(x, clip=False).filled() - in_cond = (xn > -0.001) & (xn < 1.001) - xn = np.compress(in_cond, xn) - xout = np.compress(in_cond, x) - # The rest is linear interpolation with clipping. + # The rest is linear interpolation with extrapolation at ends. y = self._y N = len(b) - ii = np.minimum(np.searchsorted(b, xn), N-1) - i0 = np.maximum(ii - 1, 0) + ii = np.searchsorted(b, xn) + i0 = ii - 1 + itop = (ii == N) + ibot = (ii == 0) + i0[itop] -= 1 + ii[itop] -= 1 + i0[ibot] += 1 + ii[ibot] += 1 + #db = b[ii] - b[i0] db = np.take(b, ii) - np.take(b, i0) - db = np.where(i0==ii, 1.0, db) #dy = y[ii] - y[i0] dy = np.take(y, ii) - np.take(y, i0) z = np.take(y, i0) + (xn-np.take(b,i0))*dy/db - return xout, z + + return z def set_alpha(self, alpha): self.alpha = alpha @@ -834,10 +848,13 @@ def on_mappable_changed(self, mappable): self.set_clim(mappable.get_clim()) self.update_normal(mappable) - def add_lines(self, CS): + def add_lines(self, CS, erase=True): ''' Add the lines from a non-filled :class:`~matplotlib.contour.ContourSet` to the colorbar. + + Set *erase* to False if these lines should be added to + any pre-existing lines. ''' if not isinstance(CS, contour.ContourSet) or CS.filled: raise ValueError('add_lines is only for a ContourSet of lines') @@ -851,7 +868,8 @@ def add_lines(self, CS): #tcolors = [col.get_colors()[0] for col in CS.collections] #tlinewidths = [col.get_linewidth()[0] for lw in CS.collections] #print 'tlinewidths:', tlinewidths - ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths) + ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths, + erase=erase) def update_normal(self, mappable): ''' @@ -884,7 +902,7 @@ def update_bruteforce(self, mappable): self.outline = None self.patch = None self.solids = None - self.lines = None + self.lines = list() self.dividers = None self.set_alpha(mappable.get_alpha()) self.cmap = mappable.cmap diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 5e80abcf6675..7bd62e90051f 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -510,11 +510,10 @@ def __call__(self, X, alpha=None, bytes=False): xa = np.array([X]) else: vtype = 'array' - xma = ma.array(X, copy=False) - mask_bad = xma.mask - xa = xma.data.copy() # Copy here to avoid side effects. + xma = ma.array(X, copy=True) # Copy here to avoid side effects. + mask_bad = xma.mask # Mask will be used below. + xa = xma.filled() # Fill to avoid infs, etc. del xma - # masked values are substituted below; no need to fill them here if xa.dtype.char in np.typecodes['Float']: # Treat 1.0 as slightly less than 1. diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index b018c8086089..5cfd7a074d07 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -682,7 +682,7 @@ class ContourSet(cm.ScalarMappable, ContourLabeler): layers: same as levels for line contours; half-way between - levels for filled contours. See _process_colors method. + levels for filled contours. See :meth:`_process_colors`. """ def __init__(self, ax, *args, **kwargs): """ @@ -742,6 +742,8 @@ def __init__(self, ax, *args, **kwargs): cmap = kwargs.get('cmap', None) self.colors = kwargs.get('colors', None) norm = kwargs.get('norm', None) + vmin = kwargs.get('vmin', None) + vmax = kwargs.get('vmax', None) self.extend = kwargs.get('extend', 'neither') self.antialiased = kwargs.get('antialiased', None) if self.antialiased is None and self.filled: @@ -789,7 +791,11 @@ def __init__(self, ax, *args, **kwargs): kw = {'cmap': cmap} if norm is not None: kw['norm'] = norm - cm.ScalarMappable.__init__(self, **kw) # sets self.cmap; + cm.ScalarMappable.__init__(self, **kw) # sets self.cmap, norm if needed; + 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() @@ -1029,40 +1035,65 @@ def _contour_level_args(self, z, args): raise ValueError("Filled contours require at least 2 levels.") def _process_levels(self): - # Color mapping range (norm vmin, vmax) is based on levels. + """ + 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. + """ + # The following attributes are no longer needed, and + # should be deprecated and removed to reduce confusion. self.vmin = np.amin(self.levels) self.vmax = np.amax(self.levels) - # Make a private _levels to include extended regions. + + # 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 not self.filled: + self.layers = self.levels + return + 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 self.filled: - # 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] = -np.inf - if self.extend in ('both', 'max'): - self.layers[-1] = np.inf - else: - self.layers = self.levels # contour: a line is a thin layer - # Use only original levels--no extended levels + + # 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] = -np.inf + if self.extend in ('both', 'max'): + self.layers[-1] = np.inf def _process_colors(self): """ Color argument processing for contouring. - Note that we base the color mapping on the contour levels, - 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. + 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 - an extended end layers. + 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: @@ -1079,12 +1110,11 @@ def _process_colors(self): self.set_norm(colors.NoNorm()) else: self.cvalues = self.layers - if not self.norm.scaled(): - self.set_clim(self.vmin, self.vmax) + self.set_array(self.levels) + self.autoscale_None() if self.extend in ('both', 'max', 'min'): self.norm.clip = False - self.set_array(self.layers) # Required by colorbar, but not - # actually used. + # self.tcolors are set by the "changed" method def _process_linewidths(self): @@ -1464,6 +1494,12 @@ def _initialize_x_y(self, z): scaling data values to colors. If *norm* is *None* and *colors* is *None*, the default linear scaling is used. + *vmin*, *vmax*: [ *None* | scalar ] + If not *None*, either or both of these values will be + supplied to the :class:`matplotlib.colors.Normalize` + instance, overriding the default color scaling based on + *levels*. + *levels*: [level0, level1, ..., leveln] A list of floating point numbers indicating the level curves to draw; eg to draw just the zero contour pass @@ -1525,18 +1561,16 @@ def _initialize_x_y(self, z): linewidths in the order specified *linestyles*: [ *None* | 'solid' | 'dashed' | 'dashdot' | 'dotted' ] - If *linestyles* is *None*, the 'solid' is used. + If *linestyles* is *None*, the default is 'solid' unless + the lines are monochrome. In that case, negative + contours will take their linestyle from the ``matplotlibrc`` + ``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. - If contour is using a monochrome colormap and the contour - level is less than 0, then the linestyle specified - in ``contour.negative_linestyle`` in ``matplotlibrc`` - will be used. - contourf-only keyword arguments: *nchunk*: [ 0 | integer ] diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf new file mode 100644 index 000000000000..35c75cc90bd5 Binary files /dev/null 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 new file mode 100644 index 000000000000..43fe5ccad470 Binary files /dev/null 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 new file mode 100644 index 000000000000..012e400e201f --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg @@ -0,0 +1,20114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf index a944f701fab3..0554b3d9e4f6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png index 17d0307d81a7..1ad6c6a02263 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png and b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg index 9169421d54eb..98ccabe28e56 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg @@ -470,16 +470,16 @@ L397.232 -333.62 L398.561 -333.246" id="C1_3_7b940d139a"/> - + - + - + - + @@ -1346,16 +1346,16 @@ L398.251 -310.387 L398.561 -310.312" id="C2_3_a62ce94003"/> - + - + - + - + @@ -2433,16 +2433,16 @@ L402.159 -287.153 L404.553 -286.708" id="C3_3_34a084a7cd"/> - + - + - + - + @@ -3727,19 +3727,19 @@ L72 -252.303 L72 -249.398" id="C4_4_d8aa00cdc8"/> - + - + - + - + - + @@ -4860,10 +4860,10 @@ L512.408 -44.8733 L510.255 -43.2" id="C5_1_464c506fbc"/> - + - + @@ -5575,13 +5575,13 @@ L517.903 -182.602 L518.4 -182.223" id="C6_2_6c8c8dec59"/> - + - + - + @@ -6003,10 +6003,10 @@ L517.078 -208.739 L518.4 -207.322" id="C7_1_e3e6f9ba7a"/> - + - + @@ -6155,7 +6155,7 @@ L224.87 -197.123 L227.791 -196.513" id="C8_0_8de4362d0f"/> - + @@ -6693,38 +6693,8 @@ L72 43.2" style="fill:none;stroke:#000000;"/> - - - - - - + + - - + + - - - - - - + + - - + + - - + + - - + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index cd1769095e07..378a4c9e3436 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -666,14 +666,15 @@ def test_hist_log(): ax = fig.add_subplot(111) ax.hist(data, fill=False, log=True) +def contour_dat(): + x = np.linspace(-3, 5, 150) + y = np.linspace(-3, 5, 120) + z = np.cos(x) + np.sin(y[:, np.newaxis]) + return x, y, z + @image_comparison(baseline_images=['contour_hatching']) def test_contour_hatching(): - x = np.linspace(-3, 5, 150).reshape(1, -1) - y = np.linspace(-3, 5, 120).reshape(-1, 1) - z = np.cos(x) + np.sin(y) - - # we no longer need x and y to be 2 dimensional, so flatten them. - x, y = x.flatten(), y.flatten() + x, y, z = contour_dat() fig = plt.figure() ax = fig.add_subplot(111) @@ -681,6 +682,29 @@ def test_contour_hatching(): cmap=plt.get_cmap('gray'), extend='both', alpha=0.5) +@image_comparison(baseline_images=['contour_colorbar']) +def test_contour_colorbar(): + x, y, z = contour_dat() + + fig = plt.figure() + ax = fig.add_subplot(111) + cs = ax.contourf(x, y, z, levels=np.arange(-1.8, 1.801, 0.2), + cmap=plt.get_cmap('RdBu'), + vmin=-0.6, + vmax=0.6, + extend='both') + cs1 = ax.contour(x, y, z, levels=np.arange(-2.2, -0.599, 0.2), + colors=['y'], + linestyles='solid', + linewidths=2) + cs2 = ax.contour(x, y, z, levels=np.arange(0.6, 2.2, 0.2), + colors=['c'], + linewidths=2) + cbar = fig.colorbar(cs, ax=ax) + cbar.add_lines(cs1) + cbar.add_lines(cs2, erase=False) + + @image_comparison(baseline_images=['hist2d']) def test_hist2d(): np.random.seed(0) @@ -765,7 +789,7 @@ def test_log_scales(): plt.plot(np.log(np.linspace(0.1, 100))) ax.set_yscale('log', basey=5.5) ax.set_xscale('log', basex=9.0) - + @image_comparison(baseline_images=['stackplot_test_image']) def test_stackplot():