diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 0f7478de0c66..fef2125c54d4 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -118,6 +118,8 @@ def __init__(self): self._sketch = rcParams['path.sketch'] self._path_effects = rcParams['path.effects'] + self._margins = {} + def __getstate__(self): d = self.__dict__.copy() # remove the unpicklable remove method, this will get re-added on load @@ -898,6 +900,99 @@ def set_zorder(self, level): self.pchanged() self.stale = True + def get_top_margin(self): + """ + Get whether a margin should be applied to the top of the Artist. + """ + return self._margins.get('top', True) + + def set_top_margin(self, margin): + """ + Set whether a margin should be applied to the top of the Artist. + """ + if margin != self._margins.get('top', True): + self.stale = True + self._margins['top'] = margin + + top_margin = property(get_top_margin, set_top_margin) + + def get_bottom_margin(self): + """ + Get whether a margin should be applied to the bottom of the Artist. + """ + return self._margins.get('bottom', True) + + def set_bottom_margin(self, margin): + """ + Set whether a margin should be applied to the bottom of the Artist. + """ + if margin != self._margins.get('bottom', True): + self.stale = True + self._margins['bottom'] = margin + + bottom_margin = property(get_bottom_margin, set_bottom_margin) + + def get_left_margin(self): + """ + Get whether a margin should be applied to the left of the Artist. + """ + return self._margins.get('left', True) + + def set_left_margin(self, margin): + """ + Set whether a margin should be applied to the left of the Artist. + """ + if margin != self._margins.get('left', True): + self.stale = True + self._margins['left'] = margin + + left_margin = property(get_left_margin, set_left_margin) + + def get_right_margin(self): + """ + Get whether a margin should be applied to the right of the Artist. + """ + return self._margins.get('right', True) + + def set_right_margin(self, margin): + """ + Set whether a margin should be applied to the right of the Artist. + """ + if margin != self._margins.get('right', True): + self.stale = True + self._margins['right'] = margin + + right_margin = property(get_right_margin, set_right_margin) + + def get_margins(self): + """ + Returns a dictionary describing whether a margin should be applied on + each of the sides (top, bottom, left and right). + """ + return self._margins + + def set_margins(self, margins): + """ + Set the dictionary describing whether a margin should be applied on + each of the sides (top, bottom, left and right). Missing keys are + assumed to be `True`. If `True` or `False` are passed in, all + sides are set to that value. + """ + if margins in (True, False): + margins = { + 'top': margins, + 'bottom': margins, + 'left': margins, + 'right': margins + } + + if margins != self._margins: + self.stale = True + + self._margins = margins + + margins = property(get_margins, set_margins) + def update_from(self, other): 'Copy properties from *other* to *self*.' self._transform = other._transform diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 16a249e0d6e8..938cbefa5bc5 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2104,16 +2104,21 @@ def make_iterable(x): if yerr is not None: yerr = self.convert_yunits(yerr) - if align == 'edge': - pass - elif align == 'center': + margins = {} + + if orientation == 'vertical': + margins = {'bottom': False} + elif orientation == 'horizontal': + margins = {'left': False} + + if align == 'center': if orientation == 'vertical': left = [left[i] - width[i] / 2. for i in xrange(len(left))] elif orientation == 'horizontal': bottom = [bottom[i] - height[i] / 2. for i in xrange(len(bottom))] - else: + elif align != 'edge': raise ValueError('invalid alignment: %s' % align) args = zip(left, bottom, width, height, color, edgecolor, linewidth) @@ -2129,7 +2134,8 @@ def make_iterable(x): facecolor=c, edgecolor=e, linewidth=lw, - label='_nolegend_' + label='_nolegend_', + margins=margins ) r.update(kwargs) r.get_path()._interpolation_steps = 100 @@ -5267,7 +5273,7 @@ def pcolor(self, *args, **kwargs): kwargs.setdefault('snap', False) - collection = mcoll.PolyCollection(verts, **kwargs) + collection = mcoll.PolyCollection(verts, margins=False, **kwargs) collection.set_alpha(alpha) collection.set_array(C) @@ -5302,9 +5308,9 @@ def pcolor(self, *args, **kwargs): maxy = np.amax(y) corners = (minx, miny), (maxx, maxy) + self.add_collection(collection, autolim=False) self.update_datalim(corners) self.autoscale_view() - self.add_collection(collection, autolim=False) return collection @unpack_labeled_data(label_namer=None) @@ -5419,7 +5425,8 @@ def pcolormesh(self, *args, **kwargs): collection = mcoll.QuadMesh( Nx - 1, Ny - 1, coords, - antialiased=antialiased, shading=shading, **kwargs) + antialiased=antialiased, shading=shading, margins=False, + **kwargs) collection.set_alpha(alpha) collection.set_array(C) if norm is not None and not isinstance(norm, mcolors.Normalize): @@ -5451,9 +5458,9 @@ def pcolormesh(self, *args, **kwargs): maxy = np.amax(Y) corners = (minx, miny), (maxx, maxy) + self.add_collection(collection, autolim=False) self.update_datalim(corners) self.autoscale_view() - self.add_collection(collection, autolim=False) return collection @unpack_labeled_data(label_namer=None) @@ -5603,7 +5610,8 @@ def pcolorfast(self, *args, **kwargs): # The QuadMesh class can also be changed to # handle relevant superclass kwargs; the initializer # should do much more than it does now. - collection = mcoll.QuadMesh(nc, nr, coords, 0, edgecolors="None") + collection = mcoll.QuadMesh(nc, nr, coords, 0, edgecolors="None", + margins=False) collection.set_alpha(alpha) collection.set_array(C) collection.set_cmap(cmap) @@ -5649,7 +5657,9 @@ def contour(self, *args, **kwargs): if not self._hold: self.cla() kwargs['filled'] = False - return mcontour.QuadContourSet(self, *args, **kwargs) + contours = mcontour.QuadContourSet(self, *args, **kwargs) + self.autoscale_view() + return contours contour.__doc__ = mcontour.QuadContourSet.contour_doc @unpack_labeled_data() @@ -5657,7 +5667,9 @@ def contourf(self, *args, **kwargs): if not self._hold: self.cla() kwargs['filled'] = True - return mcontour.QuadContourSet(self, *args, **kwargs) + contours = mcontour.QuadContourSet(self, *args, **kwargs) + self.autoscale_view() + return contours contourf.__doc__ = mcontour.QuadContourSet.contour_doc def clabel(self, CS, *args, **kwargs): @@ -6037,6 +6049,11 @@ def hist(self, x, bins=None, range=None, normed=False, weights=None, else: n = [m[slc].cumsum()[slc] for m in n] + if orientation == 'horizontal': + margins = {'left': False} + else: + margins = {'bottom': False} + patches = [] if histtype.startswith('bar'): @@ -6177,14 +6194,16 @@ def hist(self, x, bins=None, range=None, normed=False, weights=None, patches.append(self.fill( x, y, closed=True, - facecolor=c)) + facecolor=c, + margins=margins)) else: for x, y, c in reversed(list(zip(xvals, yvals, color))): split = 2 * len(bins) patches.append(self.fill( x[:split], y[:split], closed=False, edgecolor=c, - fill=False)) + fill=False, + margins=margins)) # we return patches, so put it back in the expected order patches.reverse() diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 8920b5e2b489..0d3ab8b15330 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -989,7 +989,7 @@ def cla(self): self._autoscaleYon = True self._xmargin = rcParams['axes.xmargin'] self._ymargin = rcParams['axes.ymargin'] - self._tight = False + self._tight = None self._update_transScale() # needed? self._get_lines = _process_plot_var_args(self) @@ -2141,62 +2141,107 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True): autoscale_view. """ if tight is None: - # if image data only just use the datalim - _tight = self._tight or (len(self.images) > 0 and - len(self.lines) == 0 and - len(self.patches) == 0) + _tight = self._tight else: _tight = self._tight = bool(tight) - if scalex and self._autoscaleXon: - xshared = self._shared_x_axes.get_siblings(self) - dl = [ax.dataLim for ax in xshared] - # ignore non-finite data limits if good limits exist - finite_dl = [d for d in dl if np.isfinite(d).all()] - if len(finite_dl): - dl = finite_dl - - bb = mtransforms.BboxBase.union(dl) - x0, x1 = bb.intervalx - xlocator = self.xaxis.get_major_locator() - try: - # e.g., DateLocator has its own nonsingular() - x0, x1 = xlocator.nonsingular(x0, x1) - except AttributeError: - # Default nonsingular for, e.g., MaxNLocator - x0, x1 = mtransforms.nonsingular(x0, x1, increasing=False, - expander=0.05) - if self._xmargin > 0: - delta = (x1 - x0) * self._xmargin - x0 -= delta - x1 += delta - if not _tight: - x0, x1 = xlocator.view_limits(x0, x1) - self.set_xbound(x0, x1) - - if scaley and self._autoscaleYon: - yshared = self._shared_y_axes.get_siblings(self) - dl = [ax.dataLim for ax in yshared] - # ignore non-finite data limits if good limits exist - finite_dl = [d for d in dl if np.isfinite(d).all()] - if len(finite_dl): - dl = finite_dl - - bb = mtransforms.BboxBase.union(dl) - y0, y1 = bb.intervaly - ylocator = self.yaxis.get_major_locator() - try: - y0, y1 = ylocator.nonsingular(y0, y1) - except AttributeError: - y0, y1 = mtransforms.nonsingular(y0, y1, increasing=False, - expander=0.05) - if self._ymargin > 0: - delta = (y1 - y0) * self._ymargin - y0 -= delta - y1 += delta - if not _tight: - y0, y1 = ylocator.view_limits(y0, y1) - self.set_ybound(y0, y1) + if self._xmargin or self._ymargin: + margins = { + 'top': True, + 'bottom': True, + 'left': True, + 'right': True + } + for artist_set in [self.collections, self.patches, self.lines, + self.artists, self.images]: + for artist in artist_set: + artist_margins = artist.margins + for key in ['left', 'right', 'top', 'bottom']: + margins[key] &= artist_margins.get(key, True) + + if self._xmargin: + for axes in self._shared_x_axes.get_siblings(self): + for artist_set in [axes.collections, axes.patches, + axes.lines, axes.artists, axes.images]: + for artist in artist_set: + artist_margins = artist.margins + for key in ['left', 'right']: + margins[key] &= artist_margins.get(key, True) + + if self._ymargin: + for axes in self._shared_y_axes.get_siblings(self): + for artist_set in [axes.collections, axes.patches, + axes.lines, axes.artists, axes.images]: + for artist in artist_set: + artist_margins = artist.margins + for key in ['top', 'bottom']: + margins[key] &= artist_margins.get(key, True) + else: + margins = { + 'top': False, + 'bottom': False, + 'left': False, + 'right': False + } + + def handle_single_axis(scale, autoscaleon, shared_axes, interval, + minpos, axis, margin, do_lower_margin, + do_upper_margin, set_bound): + if scale and autoscaleon: + shared = shared_axes.get_siblings(self) + dl = [ax.dataLim for ax in shared] + # ignore non-finite data limits if good limits exist + finite_dl = [d for d in dl if np.isfinite(d).all()] + if len(finite_dl): + dl = finite_dl + + bb = mtransforms.BboxBase.union(dl) + x0, x1 = getattr(bb, interval) + locator = axis.get_major_locator() + try: + # e.g., DateLocator has its own nonsingular() + x0, x1 = locator.nonsingular(x0, x1) + except AttributeError: + # Default nonsingular for, e.g., MaxNLocator + x0, x1 = mtransforms.nonsingular( + x0, x1, increasing=False, expander=0.05) + + if (margin > 0 and do_lower_margin or do_upper_margin): + if axis.get_scale() == 'linear': + delta = (x1 - x0) * margin + if do_lower_margin: + x0 -= delta + if do_upper_margin: + x1 += delta + else: + # If we have a non-linear scale, we need to + # add the margin in figure space and then + # transform back + minpos = getattr(bb, minpos) + transform = axis.get_transform() + inverse_trans = transform.inverted() + x0, x1 = axis._scale.limit_range_for_scale( + x0, x1, minpos) + x0t, x1t = transform.transform([x0, x1]) + delta = (x1t - x0t) * margin + if do_lower_margin: + x0t -= delta + if do_upper_margin: + x1t += delta + x0, x1 = inverse_trans.transform([x0t, x1t]) + + if not _tight: + x0, x1 = locator.view_limits(x0, x1) + set_bound(x0, x1) + + handle_single_axis( + scalex, self._autoscaleXon, self._shared_x_axes, + 'intervalx', 'minposx', self.xaxis, self._xmargin, + margins['left'], margins['right'], self.set_xbound) + handle_single_axis( + scaley, self._autoscaleYon, self._shared_y_axes, + 'intervaly', 'minposy', self.yaxis, self._ymargin, + margins['bottom'], margins['top'], self.set_ybound) def _get_axis_list(self): return (self.xaxis, self.yaxis) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 5416d58932fc..f6b2fde2f57f 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -947,7 +947,8 @@ def __init__(self, ax, *args, **kwargs): edgecolors='none', alpha=self.alpha, transform=self.get_transform(), - zorder=zorder) + zorder=zorder, + margins=False) self.ax.add_collection(col, autolim=False) self.collections.append(col) else: @@ -968,7 +969,8 @@ def __init__(self, ax, *args, **kwargs): linestyles=[lstyle], alpha=self.alpha, transform=self.get_transform(), - zorder=zorder) + zorder=zorder, + margins=False) col.set_label('_nolegend_') self.ax.add_collection(col, autolim=False) self.collections.append(col) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 7a3cd1dc9c39..eddabdd95922 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -117,6 +117,7 @@ def __init__(self, ax, self.set_interpolation(interpolation) self.set_resample(resample) + self.set_margins(False) self.axes = ax self._imcache = None diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index 7660f8575e9c..eb122d29c9c3 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -205,6 +205,7 @@ axes.prop_cycle : cycler('color', 'bgrcmyk') # as list of string colorspecs: # single letter, long name, or # web-style hex +axes.autolimit_mode : round_numbers axes.xmargin : 0 # x margin. See `axes.Axes.margins` axes.ymargin : 0 # y margin See `axes.Axes.margins` axes.spines.bottom : True diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 70c693563e64..a395b09e60a0 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -984,12 +984,17 @@ def validate_hist_bins(s): # to create the object. 'axes.prop_cycle': [ccycler('color', 'bgrcmyk'), validate_cycler], - 'axes.xmargin': [0, ValidateInterval(0, 1, - closedmin=True, - closedmax=True)], # margin added to xaxis - 'axes.ymargin': [0, ValidateInterval(0, 1, - closedmin=True, - closedmax=True)],# margin added to yaxis + # If 'data', axes limits are set close to the data. + # If 'round_numbers' axes limits are set to the nearest round numbers. + 'axes.autolimit_mode': [ + 'data', + ValidateInStrings('autolimit_mode', ['data', 'round_numbers'])], + 'axes.xmargin': [0.05, ValidateInterval(0, 1, + closedmin=True, + closedmax=True)], # margin added to xaxis + 'axes.ymargin': [0.05, ValidateInterval(0, 1, + closedmin=True, + closedmax=True)],# margin added to yaxis 'polaraxes.grid': [True, validate_bool], # display polar grid or # not diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index 4f48d0b85b9a..8a34f97c389a 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -71,8 +71,10 @@ def stackplot(axes, x, *args, **kwargs): stack = np.cumsum(y, axis=0) r = [] + margins = {} if baseline == 'zero': first_line = 0. + margins['bottom'] = False elif baseline == 'sym': first_line = -np.sum(y, 0) * 0.5 @@ -83,6 +85,7 @@ def stackplot(axes, x, *args, **kwargs): first_line = (y * (m - 0.5 - np.arange(0, m)[:, None])).sum(0) first_line /= -m stack += first_line + margins['bottom'] = False elif baseline == 'weighted_wiggle': m, n = y.shape @@ -97,6 +100,8 @@ def stackplot(axes, x, *args, **kwargs): center = np.cumsum(center.sum(0)) first_line = center - 0.5 * total stack += first_line + margins['bottom'] = False + else: errstr = "Baseline method %s not recognised. " % baseline errstr += "Expected 'zero', 'sym', 'wiggle' or 'weighted_wiggle'" @@ -110,6 +115,7 @@ def stackplot(axes, x, *args, **kwargs): r.append(axes.fill_between(x, first_line, stack[0, :], facecolor=color, label= six.next(labels, None), + margins=margins, **kwargs)) # Color between array i-1 and array i @@ -121,5 +127,6 @@ def stackplot(axes, x, *args, **kwargs): r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :], facecolor=color, label= six.next(labels, None), + margins=margins, **kwargs)) return r diff --git a/lib/matplotlib/streamplot.py b/lib/matplotlib/streamplot.py index a31f46cc7ba0..7d564c76eb77 100644 --- a/lib/matplotlib/streamplot.py +++ b/lib/matplotlib/streamplot.py @@ -184,12 +184,14 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, p = patches.FancyArrowPatch(arrow_tail, arrow_head, transform=transform, + margins=False, **arrow_kw) axes.add_patch(p) arrows.append(p) lc = mcollections.LineCollection(streamlines, transform=transform, + margins=False, **line_kw) if use_multicolor_lines: lc.set_array(np.ma.hstack(line_colors)) @@ -198,7 +200,7 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, axes.add_collection(lc) axes.autoscale_view() - ac = matplotlib.collections.PatchCollection(arrows) + ac = matplotlib.collections.PatchCollection(arrows, margins=False) stream_container = StreamplotSet(lc, ac) return stream_container diff --git a/lib/matplotlib/tests/baseline_images/test_artist/default_edges.png b/lib/matplotlib/tests/baseline_images/test_artist/default_edges.png index 932e2d9ec08b..074f1bf404d9 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_artist/default_edges.png and b/lib/matplotlib/tests/baseline_images/test_artist/default_edges.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf index 27c4722fcac9..36b665da2e20 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf and b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png index 414972144c25..16fa4a783ceb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png and b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg index 117ab1abf587..6fc0280cb41d 100644 --- a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg +++ b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg @@ -5,5291 +5,5163 @@ - - - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - +" id="m6af6546fe7" style="stroke:#000000;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:3.3439690036921244;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.992787371147062;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.5880496226161019;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.4698808500506423;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.1466405239365214;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:5.17633233023888;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:4.222007204006107;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.1860149908976063;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:4.29207840964949;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:4.316706464023581;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.930897433702203;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:3.8223004273876198;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:4.678638301222006;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:4.364802149768131;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:4.586196422339218;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:4.518532669069514;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:3.3770690495371407;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:3.4287055190179165;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:4.320976434506476;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.4525451284125355;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.7409014095942217;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.1709647787347088;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:5.0566743960149685;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.4266863346300078;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.04263692476679008;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.3974587330719239;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.0318610241083914;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.7549280089940171;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.2314084577083357;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.8283630330209364;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.9493623624496728;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.4985233172852523;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:3.2047519384860284;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.0176218095607115;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:3.8835187666479025;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.4478063572806037;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.2152930562336417;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.2466094115558994;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.7730026696948662;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.4359472564656426;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.8353100664908387;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.9301990528251347;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.0546006348442267;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:4.0837288733427;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.4967807622126851;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.1587558871477691;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.6535590915322094;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.949675008527799;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.420671882841679;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:0.44712651064990816;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.7102052908395757;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:2.4099230515610355;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:3.465175895089431;"/> - - + +" style="fill:#0000ff;stroke:#0000ff;stroke-linecap:round;stroke-width:1.9482227110072619;"/> - + - + - + - + - + - + - + - + - + - - + + - + - + - + - - - + + + - + - + - +" id="DejaVuSans-34"/> - - - + + + - + - + - + - - - + + + - + - + - + - - - + + + - + - + - +" id="DejaVuSans-31"/> - - - - + + + + @@ -5298,124 +5170,124 @@ z - + - + - + - + - - + + - + - + - - - + + + - + - + - - - + + + - + - + - - - + + + - + - + - - - + + + - + - + - - - - + + + + @@ -5423,7 +5295,7 @@ L-4 0" id="m81ec2b1422" style="stroke:#000000;stroke-width:0.5;"/> - + diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 21c2f0562513..28223b49694a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4157,6 +4157,40 @@ def test_loglog(): ax.tick_params(length=15, width=2, which='minor') +@cleanup('default') +def test_axes_margins(): + fig, ax = plt.subplots() + ax.plot([0, 1, 2, 3]) + assert ax.get_ybound()[0] != 0 + + fig, ax = plt.subplots() + ax.bar([0, 1, 2, 3], [1, 1, 1, 1]) + assert ax.get_ybound()[0] == 0 + + fig, ax = plt.subplots() + ax.barh([0, 1, 2, 3], [1, 1, 1, 1]) + assert ax.get_xbound()[0] == 0 + + fig, ax = plt.subplots() + ax.pcolor(np.zeros((10, 10))) + assert ax.get_xbound() == (0, 10) + assert ax.get_ybound() == (0, 10) + + fig, ax = plt.subplots() + ax.pcolorfast(np.zeros((10, 10))) + assert ax.get_xbound() == (0, 10) + assert ax.get_ybound() == (0, 10) + + fig, ax = plt.subplots() + ax.hist(np.arange(10)) + assert ax.get_ybound()[0] == 0 + + fig, ax = plt.subplots() + ax.imshow(np.zeros((10, 10))) + assert ax.get_xbound() == (-0.5, 9.5) + assert ax.get_ybound() == (-0.5, 9.5) + + if __name__ == '__main__': import nose import sys diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index b6f8adc6f69d..eea7e852187c 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1207,13 +1207,13 @@ def view_limits(self, vmin, vmax): vmin -= 1 vmax += 1 - exponent, remainder = divmod(math.log10(vmax - vmin), 1) - - if remainder < 0.5: - exponent -= 1 - scale = 10 ** (-exponent) - vmin = math.floor(scale * vmin) / scale - vmax = math.ceil(scale * vmax) / scale + if rcParams['axes.autolimit_mode'] == 'round_numbers': + exponent, remainder = divmod(math.log10(vmax - vmin), 1) + if remainder < 0.5: + exponent -= 1 + scale = 10 ** (-exponent) + vmin = math.floor(scale * vmin) / scale + vmax = math.ceil(scale * vmax) / scale return mtransforms.nonsingular(vmin, vmax) @@ -1299,11 +1299,15 @@ def view_limits(self, dmin, dmax): Set the view limits to the nearest multiples of base that contain the data """ - vmin = self._base.le(dmin) - vmax = self._base.ge(dmax) - if vmin == vmax: - vmin -= 1 - vmax += 1 + if rcParams['axes.autolimit_mode'] == 'round_numbers': + vmin = self._base.le(dmin) + vmax = self._base.ge(dmax) + if vmin == vmax: + vmin -= 1 + vmax += 1 + else: + vmin = dmin + vmax = dmax return mtransforms.nonsingular(vmin, vmax) @@ -1453,13 +1457,19 @@ def tick_values(self, vmin, vmax): return self.raise_if_exceeds(locs) def view_limits(self, dmin, dmax): - if self._symmetric: - maxabs = max(abs(dmin), abs(dmax)) - dmin = -maxabs - dmax = maxabs + if rcParams['axes.autolimit_mode'] == 'round_numbers': + if self._symmetric: + maxabs = max(abs(dmin), abs(dmax)) + dmin = -maxabs + dmax = maxabs + dmin, dmax = mtransforms.nonsingular(dmin, dmax, expander=1e-12, tiny=1.e-13) - return np.take(self.bin_boundaries(dmin, dmax), [0, -1]) + + if rcParams['axes.autolimit_mode'] == 'round_numbers': + return np.take(self.bin_boundaries(dmin, dmax), [0, -1]) + else: + return dmin, dmax def decade_down(x, base=10): @@ -1616,24 +1626,26 @@ def view_limits(self, vmin, vmax): vmin = b ** (vmax - self.numdecs) return vmin, vmax - minpos = self.axis.get_minpos() + if rcParams['axes.autolimit_mode'] == 'round_numbers': + minpos = self.axis.get_minpos() - if minpos <= 0 or not np.isfinite(minpos): - raise ValueError( - "Data has no positive values, and therefore can not be " - "log-scaled.") + if minpos <= 0 or not np.isfinite(minpos): + raise ValueError( + "Data has no positive values, and therefore can not be " + "log-scaled.") - if vmin <= minpos: - vmin = minpos + if vmin <= minpos: + vmin = minpos - if not is_decade(vmin, self._base): - vmin = decade_down(vmin, self._base) - if not is_decade(vmax, self._base): - vmax = decade_up(vmax, self._base) + if not is_decade(vmin, self._base): + vmin = decade_down(vmin, self._base) + if not is_decade(vmax, self._base): + vmax = decade_up(vmax, self._base) + + if vmin == vmax: + vmin = decade_down(vmin, self._base) + vmax = decade_up(vmax, self._base) - if vmin == vmax: - vmin = decade_down(vmin, self._base) - vmax = decade_up(vmax, self._base) result = mtransforms.nonsingular(vmin, vmax) return result @@ -1776,24 +1788,26 @@ def view_limits(self, vmin, vmax): if vmax < vmin: vmin, vmax = vmax, vmin - if not is_decade(abs(vmin), b): - if vmin < 0: - vmin = -decade_up(-vmin, b) - else: - vmin = decade_down(vmin, b) - if not is_decade(abs(vmax), b): - if vmax < 0: - vmax = -decade_down(-vmax, b) - else: - vmax = decade_up(vmax, b) + if rcParams['axes.autolimit_mode'] == 'round_numbers': + if not is_decade(abs(vmin), b): + if vmin < 0: + vmin = -decade_up(-vmin, b) + else: + vmin = decade_down(vmin, b) + if not is_decade(abs(vmax), b): + if vmax < 0: + vmax = -decade_down(-vmax, b) + else: + vmax = decade_up(vmax, b) + + if vmin == vmax: + if vmin < 0: + vmin = -decade_up(-vmin, b) + vmax = -decade_down(-vmax, b) + else: + vmin = decade_down(vmin, b) + vmax = decade_up(vmax, b) - if vmin == vmax: - if vmin < 0: - vmin = -decade_up(-vmin, b) - vmax = -decade_down(-vmax, b) - else: - vmin = decade_down(vmin, b) - vmax = decade_up(vmax, b) result = mtransforms.nonsingular(vmin, vmax) return result diff --git a/matplotlibrc.template b/matplotlibrc.template index b4e602b846c5..604b1036d911 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -319,8 +319,11 @@ backend : %(backend)s # as list of string colorspecs: # single letter, long name, or # web-style hex -#axes.xmargin : 0 # x margin. See `axes.Axes.margins` -#axes.ymargin : 0 # y margin See `axes.Axes.margins` +#axes.autolimit_mode : data # How to scale axes limits to the data. + # Use "data" to use data limits, plus some margin + # Use "round_number" move to the nearest "round" number +#axes.xmargin : .05 # x margin. See `axes.Axes.margins` +#axes.ymargin : .05 # y margin See `axes.Axes.margins` #polaraxes.grid : True # display grid on polar axes #axes3d.grid : True # display grid on 3d axes 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