diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index a146df58c9f8..3e1bb8fbb9e9 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -47,21 +47,7 @@ Margins and Autoscaling :toctree: _as_gen :nosignatures: - Artist.get_bottom_margin - Artist.get_left_margin - Artist.get_margins - Artist.get_top_margin - Artist.margins - Artist.left_margin - Artist.get_right_margin - Artist.bottom_margin - Artist.right_margin - Artist.set_bottom_margin - Artist.set_left_margin - Artist.set_margins - Artist.set_right_margin - Artist.set_top_margin - Artist.top_margin + Artist.sticky_edges Clipping -------- diff --git a/doc/api/axes_api.rst b/doc/api/axes_api.rst index ef535514b11f..92752501480e 100644 --- a/doc/api/axes_api.rst +++ b/doc/api/axes_api.rst @@ -318,32 +318,6 @@ Autoscaling -Margins -~~~~~~~ - -.. autosummary:: - :toctree: _as_gen - :nosignatures: - - - Axes.margins - Axes.set_margins - Axes.get_margins - Axes.bottom_margin - Axes.get_bottom_margin - Axes.get_left_margin - Axes.get_right_margin - Axes.get_top_margin - Axes.left_margin - Axes.right_margin - Axes.set_bottom_margin - Axes.set_left_margin - Axes.set_top_margin - Axes.set_right_margin - Axes.set_xmargin - Axes.top_margin - - Aspect ratio ------------ diff --git a/doc/api/axis_api.rst b/doc/api/axis_api.rst index 287fa94fa015..67eb83a16313 100644 --- a/doc/api/axis_api.rst +++ b/doc/api/axis_api.rst @@ -437,7 +437,6 @@ Ticks Tick.add_callback Tick.aname Tick.axes - Tick.bottom_margin Tick.contains Tick.convert_xunits Tick.convert_yunits @@ -448,7 +447,6 @@ Ticks Tick.get_alpha Tick.get_animated Tick.get_axes - Tick.get_bottom_margin Tick.get_children Tick.get_clip_box Tick.get_clip_on @@ -458,15 +456,11 @@ Ticks Tick.get_figure Tick.get_gid Tick.get_label - Tick.get_left_margin - Tick.get_margins Tick.get_path_effects Tick.get_picker Tick.get_rasterized - Tick.get_right_margin Tick.get_sketch_params Tick.get_snap - Tick.get_top_margin Tick.get_transform Tick.get_transformed_clip_path_and_affine Tick.get_url @@ -477,8 +471,6 @@ Ticks Tick.hitlist Tick.is_figure_set Tick.is_transform_set - Tick.left_margin - Tick.margins Tick.mouseover Tick.pchanged Tick.pick @@ -486,13 +478,11 @@ Ticks Tick.properties Tick.remove Tick.remove_callback - Tick.right_margin Tick.set Tick.set_agg_filter Tick.set_alpha Tick.set_animated Tick.set_axes - Tick.set_bottom_margin Tick.set_clip_box Tick.set_clip_on Tick.set_clip_path @@ -500,21 +490,16 @@ Ticks Tick.set_figure Tick.set_gid Tick.set_label - Tick.set_left_margin - Tick.set_margins Tick.set_path_effects Tick.set_picker Tick.set_rasterized - Tick.set_right_margin Tick.set_sketch_params Tick.set_snap - Tick.set_top_margin Tick.set_transform Tick.set_url Tick.set_visible Tick.set_zorder Tick.stale - Tick.top_margin Tick.update Tick.update_from Tick.zorder @@ -522,7 +507,6 @@ Ticks XTick.add_callback XTick.aname XTick.axes - XTick.bottom_margin XTick.contains XTick.convert_xunits XTick.convert_yunits @@ -533,7 +517,6 @@ Ticks XTick.get_alpha XTick.get_animated XTick.get_axes - XTick.get_bottom_margin XTick.get_children XTick.get_clip_box XTick.get_clip_on @@ -543,15 +526,11 @@ Ticks XTick.get_figure XTick.get_gid XTick.get_label - XTick.get_left_margin - XTick.get_margins XTick.get_path_effects XTick.get_picker XTick.get_rasterized - XTick.get_right_margin XTick.get_sketch_params XTick.get_snap - XTick.get_top_margin XTick.get_transform XTick.get_transformed_clip_path_and_affine XTick.get_url @@ -562,8 +541,6 @@ Ticks XTick.hitlist XTick.is_figure_set XTick.is_transform_set - XTick.left_margin - XTick.margins XTick.mouseover XTick.pchanged XTick.pick @@ -571,13 +548,11 @@ Ticks XTick.properties XTick.remove XTick.remove_callback - XTick.right_margin XTick.set XTick.set_agg_filter XTick.set_alpha XTick.set_animated XTick.set_axes - XTick.set_bottom_margin XTick.set_clip_box XTick.set_clip_on XTick.set_clip_path @@ -585,21 +560,16 @@ Ticks XTick.set_figure XTick.set_gid XTick.set_label - XTick.set_left_margin - XTick.set_margins XTick.set_path_effects XTick.set_picker XTick.set_rasterized - XTick.set_right_margin XTick.set_sketch_params XTick.set_snap - XTick.set_top_margin XTick.set_transform XTick.set_url XTick.set_visible XTick.set_zorder XTick.stale - XTick.top_margin XTick.update XTick.update_from XTick.zorder @@ -607,7 +577,6 @@ Ticks YTick.add_callback YTick.aname YTick.axes - YTick.bottom_margin YTick.contains YTick.convert_xunits YTick.convert_yunits @@ -618,7 +587,6 @@ Ticks YTick.get_alpha YTick.get_animated YTick.get_axes - YTick.get_bottom_margin YTick.get_children YTick.get_clip_box YTick.get_clip_on @@ -628,15 +596,11 @@ Ticks YTick.get_figure YTick.get_gid YTick.get_label - YTick.get_left_margin - YTick.get_margins YTick.get_path_effects YTick.get_picker YTick.get_rasterized - YTick.get_right_margin YTick.get_sketch_params YTick.get_snap - YTick.get_top_margin YTick.get_transform YTick.get_transformed_clip_path_and_affine YTick.get_url @@ -647,8 +611,6 @@ Ticks YTick.hitlist YTick.is_figure_set YTick.is_transform_set - YTick.left_margin - YTick.margins YTick.mouseover YTick.pchanged YTick.pick @@ -656,13 +618,11 @@ Ticks YTick.properties YTick.remove YTick.remove_callback - YTick.right_margin YTick.set YTick.set_agg_filter YTick.set_alpha YTick.set_animated YTick.set_axes - YTick.set_bottom_margin YTick.set_clip_box YTick.set_clip_on YTick.set_clip_path @@ -670,21 +630,16 @@ Ticks YTick.set_figure YTick.set_gid YTick.set_label - YTick.set_left_margin - YTick.set_margins YTick.set_path_effects YTick.set_picker YTick.set_rasterized - YTick.set_right_margin YTick.set_sketch_params YTick.set_snap - YTick.set_top_margin YTick.set_transform YTick.set_url YTick.set_visible YTick.set_zorder YTick.stale - YTick.top_margin YTick.update YTick.update_from YTick.zorder @@ -701,7 +656,6 @@ Axis Axis.add_callback Axis.aname Axis.axes - Axis.bottom_margin Axis.contains Axis.convert_xunits Axis.convert_yunits @@ -712,7 +666,6 @@ Axis Axis.get_alpha Axis.get_animated Axis.get_axes - Axis.get_bottom_margin Axis.get_children Axis.get_clip_box Axis.get_clip_on @@ -722,15 +675,11 @@ Axis Axis.get_figure Axis.get_gid Axis.get_label - Axis.get_left_margin - Axis.get_margins Axis.get_path_effects Axis.get_picker Axis.get_rasterized - Axis.get_right_margin Axis.get_sketch_params Axis.get_snap - Axis.get_top_margin Axis.get_transform Axis.get_transformed_clip_path_and_affine Axis.get_url @@ -741,8 +690,6 @@ Axis Axis.hitlist Axis.is_figure_set Axis.is_transform_set - Axis.left_margin - Axis.margins Axis.mouseover Axis.pchanged Axis.pick @@ -750,13 +697,11 @@ Axis Axis.properties Axis.remove Axis.remove_callback - Axis.right_margin Axis.set Axis.set_agg_filter Axis.set_alpha Axis.set_animated Axis.set_axes - Axis.set_bottom_margin Axis.set_clip_box Axis.set_clip_on Axis.set_clip_path @@ -764,21 +709,16 @@ Axis Axis.set_figure Axis.set_gid Axis.set_label - Axis.set_left_margin - Axis.set_margins Axis.set_path_effects Axis.set_picker Axis.set_rasterized - Axis.set_right_margin Axis.set_sketch_params Axis.set_snap - Axis.set_top_margin Axis.set_transform Axis.set_url Axis.set_visible Axis.set_zorder Axis.stale - Axis.top_margin Axis.update Axis.update_from Axis.zorder @@ -786,7 +726,6 @@ Axis XAxis.add_callback XAxis.aname XAxis.axes - XAxis.bottom_margin XAxis.contains XAxis.convert_xunits XAxis.convert_yunits @@ -797,7 +736,6 @@ Axis XAxis.get_alpha XAxis.get_animated XAxis.get_axes - XAxis.get_bottom_margin XAxis.get_children XAxis.get_clip_box XAxis.get_clip_on @@ -807,15 +745,11 @@ Axis XAxis.get_figure XAxis.get_gid XAxis.get_label - XAxis.get_left_margin - XAxis.get_margins XAxis.get_path_effects XAxis.get_picker XAxis.get_rasterized - XAxis.get_right_margin XAxis.get_sketch_params XAxis.get_snap - XAxis.get_top_margin XAxis.get_transform XAxis.get_transformed_clip_path_and_affine XAxis.get_url @@ -826,8 +760,6 @@ Axis XAxis.hitlist XAxis.is_figure_set XAxis.is_transform_set - XAxis.left_margin - XAxis.margins XAxis.mouseover XAxis.pchanged XAxis.pick @@ -835,13 +767,11 @@ Axis XAxis.properties XAxis.remove XAxis.remove_callback - XAxis.right_margin XAxis.set XAxis.set_agg_filter XAxis.set_alpha XAxis.set_animated XAxis.set_axes - XAxis.set_bottom_margin XAxis.set_clip_box XAxis.set_clip_on XAxis.set_clip_path @@ -849,21 +779,16 @@ Axis XAxis.set_figure XAxis.set_gid XAxis.set_label - XAxis.set_left_margin - XAxis.set_margins XAxis.set_path_effects XAxis.set_picker XAxis.set_rasterized - XAxis.set_right_margin XAxis.set_sketch_params XAxis.set_snap - XAxis.set_top_margin XAxis.set_transform XAxis.set_url XAxis.set_visible XAxis.set_zorder XAxis.stale - XAxis.top_margin XAxis.update XAxis.update_from XAxis.zorder @@ -871,7 +796,6 @@ Axis YAxis.add_callback YAxis.aname YAxis.axes - YAxis.bottom_margin YAxis.contains YAxis.convert_xunits YAxis.convert_yunits @@ -882,7 +806,6 @@ Axis YAxis.get_alpha YAxis.get_animated YAxis.get_axes - YAxis.get_bottom_margin YAxis.get_children YAxis.get_clip_box YAxis.get_clip_on @@ -892,15 +815,11 @@ Axis YAxis.get_figure YAxis.get_gid YAxis.get_label - YAxis.get_left_margin - YAxis.get_margins YAxis.get_path_effects YAxis.get_picker YAxis.get_rasterized - YAxis.get_right_margin YAxis.get_sketch_params YAxis.get_snap - YAxis.get_top_margin YAxis.get_transform YAxis.get_transformed_clip_path_and_affine YAxis.get_url @@ -911,8 +830,6 @@ Axis YAxis.hitlist YAxis.is_figure_set YAxis.is_transform_set - YAxis.left_margin - YAxis.margins YAxis.mouseover YAxis.pchanged YAxis.pick @@ -920,13 +837,11 @@ Axis YAxis.properties YAxis.remove YAxis.remove_callback - YAxis.right_margin YAxis.set YAxis.set_agg_filter YAxis.set_alpha YAxis.set_animated YAxis.set_axes - YAxis.set_bottom_margin YAxis.set_clip_box YAxis.set_clip_on YAxis.set_clip_path @@ -934,21 +849,16 @@ Axis YAxis.set_figure YAxis.set_gid YAxis.set_label - YAxis.set_left_margin - YAxis.set_margins YAxis.set_path_effects YAxis.set_picker YAxis.set_rasterized - YAxis.set_right_margin YAxis.set_sketch_params YAxis.set_snap - YAxis.set_top_margin YAxis.set_transform YAxis.set_url YAxis.set_visible YAxis.set_zorder YAxis.stale - YAxis.top_margin YAxis.update YAxis.update_from YAxis.zorder diff --git a/doc/users/dflt_style_changes.rst b/doc/users/dflt_style_changes.rst index 8a01fa699656..b0ab000a6bcb 100644 --- a/doc/users/dflt_style_changes.rst +++ b/doc/users/dflt_style_changes.rst @@ -1022,37 +1022,7 @@ The size of the padding in the x and y directions is controlled by the ``'axes.xmargin'`` and ``'axes.ymargin'`` rcParams respectively. Whether the view limits should be 'round numbers' is controlled by the ``'axes.autolimit_mode'`` rcParam. In the original ``'round_number'`` mode, -the view limits coincide with ticks. With the new default value, ``'data'``, -the outermost ticks will usually be inside the view limits, not at the ends. -Also see `~matplotlib.axes.Axes.margins`. - -For a few `~matplotlib.artist.Artist` classes, margins are undesirable. -For example, a margin should not be added for a `~matplotlib.image.AxesImage` -created with `~matplotlib.axes.Axes.imshow`. To control the application of -the margins, the `~matplotlib.artist.Artist` class has gained the properties : - - - `~matplotlib.artist.Artist.top_margin` - - `~matplotlib.artist.Artist.bottom_margin` - - `~matplotlib.artist.Artist.left_margin` - - `~matplotlib.artist.Artist.right_margin` - - `~matplotlib.artist.Artist.margins` - -along with the complimentary ``get_*`` and ``set_*`` methods. When -computing the view limits, each `~matplotlib.artist.Artist` is -checked. If *any* artist returns `False` on a given side, -the margin will be omitted there. Some plotting methods and artists -have margins disabled (`False`) by default (for example -`~matplotlib.axes.Axes.bar` disables the bottom margin). To cancel -the margins for a specific artist, pass the kwargs : - - - ``top_margin=False`` - - ``bottom_margin=False`` - - ``left_margin=False`` - - ``right_margin=False`` - -to any plotting method or artist ``__init__`` which supports ``**kwargs`` (as -any unused kwargs eventually get passed to `~matplotlib.artist.Artist.update`). - +the view limits coincide with ticks. The previous default can be restored by using:: diff --git a/examples/api/filled_step.py b/examples/api/filled_step.py index 091d6cde7863..eec83329a0e2 100644 --- a/examples/api/filled_step.py +++ b/examples/api/filled_step.py @@ -70,10 +70,10 @@ def filled_hist(ax, edges, values, bottoms=None, orientation='v', values = np.r_[values, values[-1]] bottoms = np.r_[bottoms, bottoms[-1]] if orientation == 'h': - return ax.fill_betweenx(edges, values, bottoms, left_margin=False, + return ax.fill_betweenx(edges, values, bottoms, **kwargs) elif orientation == 'v': - return ax.fill_between(edges, values, bottoms, bottom_margin=False, + return ax.fill_between(edges, values, bottoms, **kwargs) else: raise AssertionError("you should never be here") diff --git a/examples/pylab_examples/trigradient_demo.py b/examples/pylab_examples/trigradient_demo.py index 6114b8493de5..dbc48c3f4c20 100644 --- a/examples/pylab_examples/trigradient_demo.py +++ b/examples/pylab_examples/trigradient_demo.py @@ -64,18 +64,22 @@ def dipole_potential(x, y): #----------------------------------------------------------------------------- # Plot the triangulation, the potential iso-contours and the vector field #----------------------------------------------------------------------------- -plt.figure() -plt.gca().set_aspect('equal') -plt.triplot(triang, color='0.8') +fig, ax = plt.subplots() +ax.set_aspect('equal') +# Enforce the margins, and enlarge them to give room for the vectors. +ax.use_sticky_edges = False +ax.margins(0.07) + +ax.triplot(triang, color='0.8') levels = np.arange(0., 1., 0.01) cmap = cm.get_cmap(name='hot', lut=None) -plt.tricontour(tri_refi, z_test_refi, levels=levels, cmap=cmap, - linewidths=[2.0, 1.0, 1.0, 1.0]) +ax.tricontour(tri_refi, z_test_refi, levels=levels, cmap=cmap, + linewidths=[2.0, 1.0, 1.0, 1.0]) # Plots direction of the electrical vector field -plt.quiver(triang.x, triang.y, Ex/E_norm, Ey/E_norm, - units='xy', scale=10., zorder=3, color='blue', - width=0.007, headwidth=3., headlength=4.) +ax.quiver(triang.x, triang.y, Ex/E_norm, Ey/E_norm, + units='xy', scale=10., zorder=3, color='blue', + width=0.007, headwidth=3., headlength=4.) -plt.title('Gradient plot: an electrical dipole') +ax.set_title('Gradient plot: an electrical dipole') plt.show() diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index a3ae49e1d40c..4c9e59d7deea 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -2,7 +2,7 @@ unicode_literals) import six -from collections import OrderedDict +from collections import OrderedDict, namedtuple import re import warnings @@ -76,6 +76,9 @@ def _stale_axes_callback(self, val): self.axes.stale = val +_XYPair = namedtuple("_XYPair", "x y") + + class Artist(object): """ Abstract base class for someone who renders into a @@ -123,8 +126,7 @@ def __init__(self): self._snap = None self._sketch = rcParams['path.sketch'] self._path_effects = rcParams['path.effects'] - - self._margins = {} + self._sticky_edges = _XYPair([], []) def __getstate__(self): d = self.__dict__.copy() @@ -926,98 +928,28 @@ 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. + @property + def sticky_edges(self): """ - return self._margins.get('left', True) + `x` and `y` sticky edge lists. - 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 + When performing autoscaling, if a data limit coincides with a value in + the corresponding sticky_edges list, then no margin will be added--the + view limit "sticks" to the edge. A typical usecase is histograms, + where one usually expects no margin on the bottom edge (0) of the + histogram. - left_margin = property(get_left_margin, set_left_margin) + This attribute cannot be assigned to; however, the `x` and `y` lists + can be modified in place as needed. - def get_right_margin(self): - """ - Get whether a margin should be applied to the right of the Artist. - """ - return self._margins.get('right', True) + Examples + -------- - 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 + >>> artist.sticky_edges.x[:] = (xmin, xmax) + >>> artist.sticky_edges.y[:] = (ymin, ymax) - 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) + return self._sticky_edges def update_from(self, other): 'Copy properties from *other* to *self*.' @@ -1031,6 +963,8 @@ def update_from(self, other): self._label = other._label self._sketch = other._sketch self._path_effects = other._path_effects + self.sticky_edges.x[:] = other.sticky_edges.x[:] + self.sticky_edges.y[:] = other.sticky_edges.y[:] self.pchanged() self.stale = True diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 3b1554580f41..333e8b15534e 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2097,13 +2097,6 @@ def make_iterable(x): if yerr is not None: yerr = self.convert_yunits(yerr) - 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))] @@ -2128,11 +2121,13 @@ def make_iterable(x): edgecolor=e, linewidth=lw, label='_nolegend_', - margins=margins ) r.update(kwargs) r.get_path()._interpolation_steps = 100 - #print r.get_label(), label, 'label' in kwargs + if orientation == 'vertical': + r.sticky_edges.y.append(0) + elif orientation == 'horizontal': + r.sticky_edges.x.append(0) self.add_patch(r) patches.append(r) @@ -4424,6 +4419,8 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, corners = ((xmin, ymin), (xmax, ymax)) self.update_datalim(corners) + collection.sticky_edges.x[:] = [xmin, xmax] + collection.sticky_edges.y[:] = [ymin, ymax] self.autoscale_view(tight=True) # add the collection last @@ -5452,7 +5449,7 @@ def pcolor(self, *args, **kwargs): kwargs.setdefault('snap', False) - collection = mcoll.PolyCollection(verts, margins=False, **kwargs) + collection = mcoll.PolyCollection(verts, **kwargs) collection.set_alpha(alpha) collection.set_array(C) @@ -5481,13 +5478,15 @@ def pcolor(self, *args, **kwargs): x = transformed_pts[..., 0] y = transformed_pts[..., 1] + self.add_collection(collection, autolim=False) + minx = np.amin(x) maxx = np.amax(x) miny = np.amin(y) maxy = np.amax(y) - + collection.sticky_edges.x[:] = [minx, maxx] + collection.sticky_edges.y[:] = [miny, maxy] corners = (minx, miny), (maxx, maxy) - self.add_collection(collection, autolim=False) self.update_datalim(corners) self.autoscale_view() return collection @@ -5603,10 +5602,9 @@ def pcolormesh(self, *args, **kwargs): coords[:, 0] = X coords[:, 1] = Y - collection = mcoll.QuadMesh( - Nx - 1, Ny - 1, coords, - antialiased=antialiased, shading=shading, margins=False, - **kwargs) + collection = mcoll.QuadMesh(Nx - 1, Ny - 1, coords, + antialiased=antialiased, shading=shading, + **kwargs) collection.set_alpha(alpha) collection.set_array(C) if norm is not None and not isinstance(norm, mcolors.Normalize): @@ -5632,13 +5630,15 @@ def pcolormesh(self, *args, **kwargs): X = transformed_pts[..., 0] Y = transformed_pts[..., 1] + self.add_collection(collection, autolim=False) + minx = np.amin(X) maxx = np.amax(X) miny = np.amin(Y) maxy = np.amax(Y) - + collection.sticky_edges.x[:] = [minx, maxx] + collection.sticky_edges.y[:] = [miny, maxy] corners = (minx, miny), (maxx, maxy) - self.add_collection(collection, autolim=False) self.update_datalim(corners) self.autoscale_view() return collection @@ -5790,8 +5790,7 @@ 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", - margins=False) + collection = mcoll.QuadMesh(nc, nr, coords, 0, edgecolors="None") collection.set_alpha(alpha) collection.set_array(C) collection.set_cmap(cmap) @@ -5800,27 +5799,23 @@ def pcolorfast(self, *args, **kwargs): xl, xr, yb, yt = X.min(), X.max(), Y.min(), Y.max() ret = collection - else: - # One of the image styles: + else: # It's one of the two image styles. xl, xr, yb, yt = x[0], x[-1], y[0], y[-1] - if style == "image": - - im = mimage.AxesImage(self, cmap, norm, - interpolation='nearest', - origin='lower', - extent=(xl, xr, yb, yt), - **kwargs) - im.set_data(C) - im.set_alpha(alpha) - self.add_image(im) - ret = im - if style == "pcolorimage": - im = mimage.PcolorImage(self, x, y, C, - cmap=cmap, - norm=norm, - alpha=alpha, - **kwargs) + if style == "image": + im = mimage.AxesImage(self, cmap, norm, + interpolation='nearest', + origin='lower', + extent=(xl, xr, yb, yt), + **kwargs) + im.set_data(C) + im.set_alpha(alpha) + elif style == "pcolorimage": + im = mimage.PcolorImage(self, x, y, C, + cmap=cmap, + norm=norm, + alpha=alpha, + **kwargs) self.add_image(im) ret = im @@ -5828,6 +5823,9 @@ def pcolorfast(self, *args, **kwargs): ret.set_clim(vmin, vmax) else: ret.autoscale_None() + + ret.sticky_edges.x[:] = [xl, xr] + ret.sticky_edges.y[:] = [yb, yt] self.update_datalim(np.array([[xl, yb], [xr, yt]])) self.autoscale_view(tight=True) return ret @@ -6240,21 +6238,17 @@ def _normalize_input(inp, ename='input'): else: n = [m[slc].cumsum()[slc] for m in n] - if orientation == 'horizontal': - margins = {'left': False} - else: - margins = {'bottom': False} - patches = [] + # Save autoscale state for later restoration; turn autoscaling + # off so we can do it all a single time at the end, instead + # of having it done by bar or fill and then having to be redone. + _saved_autoscalex = self.get_autoscalex_on() + _saved_autoscaley = self.get_autoscaley_on() + self.set_autoscalex_on(False) + self.set_autoscaley_on(False) + if histtype.startswith('bar'): - # Save autoscale state for later restoration; turn autoscaling - # off so we can do it all a single time at the end, instead - # of having it done by bar or fill and then having to be redone. - _saved_autoscalex = self.get_autoscalex_on() - _saved_autoscaley = self.get_autoscaley_on() - self.set_autoscalex_on(False) - self.set_autoscaley_on(False) totwidth = np.diff(bins) @@ -6301,10 +6295,6 @@ def _normalize_input(inp, ename='input'): bottom[:] = m boffset += dw - self.set_autoscalex_on(_saved_autoscalex) - self.set_autoscaley_on(_saved_autoscaley) - self.autoscale_view() - elif histtype.startswith('step'): # these define the perimeter of the polygon x = np.zeros(4 * len(bins) - 3) @@ -6331,19 +6321,19 @@ def _normalize_input(inp, ename='input'): if np.min(bottom) > 0: minimum = np.min(bottom) elif normed or weights is not None: - # For normed data, set to log base * minimum data value + # For normed data, set to minimum data value / logbase # (gives 1 full tick-label unit for the lowest filled bin) ndata = np.array(n) minimum = (np.min(ndata[ndata > 0])) / logbase else: - # For non-normed data, set the min to log base, + # For non-normed data, set the min to 1 / log base, # again so that there is 1 full tick-label unit # for the lowest bin minimum = 1.0 / logbase y[0], y[-1] = minimum, minimum else: - minimum = np.min(bins) + minimum = 0 if align == 'left' or align == 'center': x -= 0.5*(bins[1]-bins[0]) @@ -6384,42 +6374,20 @@ def _normalize_input(inp, ename='input'): closed=True if fill else None, facecolor=c, edgecolor=None if fill else c, - fill=fill if fill else None, - margins=margins)) + fill=fill if fill else None)) + for patch_list in patches: + for patch in patch_list: + if orientation == 'vertical': + patch.sticky_edges.y.append(minimum) + elif orientation == 'horizontal': + patch.sticky_edges.x.append(minimum) # we return patches, so put it back in the expected order patches.reverse() - # adopted from adjust_x/ylim part of the bar method - if orientation == 'horizontal': - xmin0 = max(_saved_bounds[0]*0.9, minimum) - xmax = self.dataLim.intervalx[1] - for m in n: - # make sure there are counts - if np.sum(m) > 0: - # filter out the 0 height bins - xmin = np.amin(m[m != 0]) - # If no counts, set min to zero - else: - xmin = 0.0 - xmin = max(xmin*0.9, minimum) if not input_empty else minimum - xmin = min(xmin0, xmin) - self.dataLim.intervalx = (xmin, xmax) - elif orientation == 'vertical': - ymin0 = max(_saved_bounds[1]*0.9, minimum) - ymax = self.dataLim.intervaly[1] - - for m in n: - # make sure there are counts - if np.sum(m) > 0: - # filter out the 0 height bins - ymin = np.amin(m[m != 0]) - # If no counts, set min to zero - else: - ymin = 0.0 - ymin = max(ymin*0.9, minimum) if not input_empty else minimum - ymin = min(ymin0, ymin) - self.dataLim.intervaly = (ymin, ymax) + self.set_autoscalex_on(_saved_autoscalex) + self.set_autoscaley_on(_saved_autoscaley) + self.autoscale_view() if label is None: labels = [None] @@ -6439,14 +6407,6 @@ def _normalize_input(inp, ename='input'): p.update(kwargs) p.set_label('_nolegend_') - if binsgiven: - if orientation == 'vertical': - self.update_datalim( - [(bins[0], 0), (bins[-1], 0)], updatey=False) - else: - self.update_datalim( - [(0, bins[0]), (0, bins[-1])], updatex=False) - if nx == 1: return n[0], bins, cbook.silent_list('Patch', patches[0]) else: diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index c7b8ccae1e37..664faf9e05df 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1030,6 +1030,7 @@ def cla(self): self._xmargin = rcParams['axes.xmargin'] self._ymargin = rcParams['axes.ymargin'] self._tight = None + self._use_sticky_edges = True self._update_transScale() # needed? self._get_lines = _process_plot_var_args(self) @@ -2034,6 +2035,28 @@ def set_autoscaley_on(self, b): """ self._autoscaleYon = b + @property + def use_sticky_edges(self): + """ + When autoscaling, whether to obey all `Artist.sticky_edges`. + + Default is ``True``. + + Setting this to ``False`` ensures that the specified margins + will be applied, even if the plot includes an image, for + example, which would otherwise force a view limit to coincide + with its data limit. + + The changing this property does not change the plot until + `autoscale` or `autoscale_view` is called. + """ + return self._use_sticky_edges + + @use_sticky_edges.setter + def use_sticky_edges(self, b): + self._use_sticky_edges = bool(b) + # No effect until next autoscaling, which will mark the axes as stale. + def set_xmargin(self, m): """ Set padding of X data limits prior to autoscaling. @@ -2199,53 +2222,22 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True): case, use :meth:`matplotlib.axes.Axes.relim` prior to calling autoscale_view. """ - if tight is None: - _tight = self._tight - else: - _tight = self._tight = bool(tight) - - 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 - } + if tight is not None: + self._tight = bool(tight) + + if self.use_sticky_edges and (self._xmargin or self._ymargin): + stickies = [artist.sticky_edges for artist in self.get_children()] + x_stickies = sum([sticky.x for sticky in stickies], []) + y_stickies = sum([sticky.y for sticky in stickies], []) + if self.get_xscale().lower() == 'log': + x_stickies = [xs for xs in x_stickies if xs > 0] + if self.get_yscale().lower() == 'log': + y_stickies = [ys for ys in y_stickies if ys > 0] + else: # Small optimization. + x_stickies, y_stickies = [], [] def handle_single_axis(scale, autoscaleon, shared_axes, interval, - minpos, axis, margin, do_lower_margin, - do_upper_margin, set_bound): + minpos, axis, margin, stickies, set_bound): if not (scale and autoscaleon): return # nothing to do... @@ -2268,43 +2260,35 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval, 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: + # Add the margin in figure space and then transform back, to handle + # non-linear scales. + minpos = getattr(bb, minpos) + transform = axis.get_transform() + inverse_trans = transform.inverted() + # We cannot use exact equality due to floating point issues e.g. + # with streamplot. + do_lower_margin = not np.any(np.isclose(x0, stickies)) + do_upper_margin = not np.any(np.isclose(x1, stickies)) + 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 self._tight: x0, x1 = locator.view_limits(x0, x1) set_bound(x0, x1) # End of definition of internal function 'handle_single_axis'. handle_single_axis( - scalex, self._autoscaleXon, self._shared_x_axes, - 'intervalx', 'minposx', self.xaxis, self._xmargin, - margins['left'], margins['right'], self.set_xbound) + scalex, self._autoscaleXon, self._shared_x_axes, 'intervalx', + 'minposx', self.xaxis, self._xmargin, x_stickies, 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) + scaley, self._autoscaleYon, self._shared_y_axes, 'intervaly', + 'minposy', self.yaxis, self._ymargin, y_stickies, self.set_ybound) def _get_axis_list(self): return (self.xaxis, self.yaxis) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 32ddabbb5a73..a7962d36079b 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -13,7 +13,6 @@ from six.moves import xrange, zip from itertools import repeat import collections - import datetime import errno import functools diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 102d1a18ee5d..e1cb1f1bf257 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -766,6 +766,7 @@ class ContourSet(cm.ScalarMappable, ContourLabeler): same as levels for line contours; half-way between levels for filled contours. See :meth:`_process_colors`. """ + def __init__(self, ax, *args, **kwargs): """ Draw contour lines or filled regions, depending on @@ -804,7 +805,7 @@ def __init__(self, ax, *args, **kwargs): level0segs = [polygon0] and level0kinds = [polygon0kinds]. Keyword arguments are as described in - :class:`~matplotlib.contour.QuadContourSet` object. + :attr:`matplotlib.contour.QuadContourSet.contour_doc`. **Examples:** @@ -937,8 +938,7 @@ def __init__(self, ax, *args, **kwargs): edgecolors='none', alpha=self.alpha, transform=self.get_transform(), - zorder=zorder, - margins=False) + zorder=zorder) self.ax.add_collection(col, autolim=False) self.collections.append(col) else: @@ -959,11 +959,17 @@ def __init__(self, ax, *args, **kwargs): linestyles=[lstyle], alpha=self.alpha, transform=self.get_transform(), - zorder=zorder, - margins=False) + zorder=zorder) col.set_label('_nolegend_') self.ax.add_collection(col, autolim=False) self.collections.append(col) + + for col in self.collections: + col.sticky_edges.x[:] = [self._mins[0], self._maxs[0]] + col.sticky_edges.y[:] = [self._mins[1], self._maxs[1]] + self.ax.update_datalim([self._mins, self._maxs]) + self.ax.autoscale_view(tight=True) + self.changed() # set the colors def get_transform(self): @@ -1069,21 +1075,10 @@ def _process_args(self, *args, **kwargs): raise ValueError('allkinds has different length to allsegs') # Determine x,y bounds and update axes data limits. - havelimits = False - for segs in self.allsegs: - for seg in segs: - seg = np.asarray(seg) - if havelimits: - min = np.minimum(min, seg.min(axis=0)) - max = np.maximum(max, seg.max(axis=0)) - else: - min = seg.min(axis=0) - max = seg.max(axis=0) - havelimits = True - - if havelimits: - self.ax.update_datalim([min, max]) - self.ax.autoscale_view(tight=True) + flatseglist = [s for seg in self.allsegs for s in seg] + points = np.concatenate(flatseglist, axis=0) + self._mins = points.min(axis=0) + self._maxs = points.max(axis=0) def _get_allsegs_and_allkinds(self): """ @@ -1423,16 +1418,6 @@ class QuadContourSet(ContourSet): Same as levels for line contours; half-way between levels for filled contours. See :meth:`_process_colors` method. """ - def __init__(self, ax, *args, **kwargs): - """ - Calculate and draw contour lines or filled regions, depending - on whether keyword arg 'filled' is False (default) or True. - - The first argument of the initializer must be an axes - object. The remaining arguments and keyword arguments - are described in QuadContourSet.contour_doc. - """ - ContourSet.__init__(self, ax, *args, **kwargs) def _process_args(self, *args, **kwargs): """ @@ -1448,6 +1433,8 @@ def _process_args(self, *args, **kwargs): contour_generator = args[0].Cntr else: contour_generator = args[0]._contour_generator + self._mins = args[0]._mins + self._maxs = args[0]._maxs else: self._corner_mask = kwargs.get('corner_mask', None) if self._corner_mask is None: @@ -1480,12 +1467,8 @@ def _process_args(self, *args, **kwargs): x = transformed_pts[..., 0] y = transformed_pts[..., 1] - x0 = ma.minimum(x) - x1 = ma.maximum(x) - y0 = ma.minimum(y) - y1 = ma.maximum(y) - self.ax.update_datalim([(x0, y0), (x1, y1)]) - self.ax.autoscale_view(tight=True) + self._mins = [ma.min(x), ma.min(y)] + self._maxs = [ma.max(x), ma.max(y)] if self._corner_mask == 'legacy': self.Cntr = contour_generator diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index a519cc17afe1..3cb306fb9371 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -234,7 +234,6 @@ def __init__(self, ax, self.set_filterrad(filterrad) self.set_interpolation(interpolation) self.set_resample(resample) - self.set_margins(False) self.axes = ax self._imcache = None @@ -741,6 +740,8 @@ def set_extent(self, extent): xmin, xmax, ymin, ymax = extent corners = (xmin, ymin), (xmax, ymax) self.axes.update_datalim(corners) + self.sticky_edges.x[:] = [xmin, xmax] + self.sticky_edges.y[:] = [ymin, ymax] if self.axes._autoscaleXon: self.axes.set_xlim((xmin, xmax), auto=None) if self.axes._autoscaleYon: diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 4ba04e3717dd..36a75cca3ad8 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -262,8 +262,8 @@ def limit_range_for_scale(self, vmin, vmax, minpos): """ Limit the domain to positive values. """ - return (vmin <= 0.0 and minpos or vmin, - vmax <= 0.0 and minpos or vmax) + return (minpos if vmin <= 0 else vmin, + minpos if vmax <= 0 else vmax) class SymmetricalLogTransform(Transform): @@ -497,8 +497,8 @@ def limit_range_for_scale(self, vmin, vmax, minpos): """ Limit the domain to values between 0 and 1 (excluded). """ - return (vmin <= 0 and minpos or vmin, - vmax >= 1 and (1 - minpos) or vmax) + return (minpos if vmin <= 0 else minpos, + 1 - minpos if vmax >= 1 else vmax) _scale_mapping = { diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index 412d288cd695..26b90ca66643 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -70,11 +70,8 @@ def stackplot(axes, x, *args, **kwargs): # Assume data passed has not been 'stacked', so stack it here. 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 @@ -85,7 +82,6 @@ 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 @@ -104,7 +100,6 @@ 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 @@ -113,11 +108,11 @@ def stackplot(axes, x, *args, **kwargs): # Color between x = 0 and the first array. color = axes._get_lines.get_next_color() - r.append(axes.fill_between(x, first_line, stack[0, :], - facecolor=color, - label= six.next(labels, None), - margins=margins, - **kwargs)) + coll = axes.fill_between(x, first_line, stack[0, :], + facecolor=color, label=six.next(labels, None), + **kwargs) + coll.sticky_edges.y[:] = [0] + r = [coll] # Color between array i-1 and array i for i in xrange(len(y) - 1): @@ -125,6 +120,5 @@ 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 ccf068739cd9..6dc15e95b2ae 100644 --- a/lib/matplotlib/streamplot.py +++ b/lib/matplotlib/streamplot.py @@ -156,10 +156,10 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, # Check if start_points are outside the data boundaries for xs, ys in sp2: - if (xs < grid.x_origin or xs > grid.x_origin + grid.width - or ys < grid.y_origin or ys > grid.y_origin + grid.height): - raise ValueError("Starting point ({}, {}) outside of" - " data boundaries".format(xs, ys)) + if not (grid.x_origin <= xs <= grid.x_origin + grid.width + and grid.y_origin <= ys <= grid.y_origin + grid.height): + raise ValueError("Starting point ({}, {}) outside of data " + "boundaries".format(xs, ys)) # Convert start_points from data to array coords # Shift the seed points from the bottom left of the data so that @@ -210,18 +210,15 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, line_colors.append(color_values) arrow_kw['color'] = cmap(norm(color_values[n])) - p = patches.FancyArrowPatch(arrow_tail, - arrow_head, - transform=transform, - margins=False, - **arrow_kw) + p = patches.FancyArrowPatch( + arrow_tail, arrow_head, transform=transform, **arrow_kw) axes.add_patch(p) arrows.append(p) - lc = mcollections.LineCollection(streamlines, - transform=transform, - margins=False, - **line_kw) + lc = mcollections.LineCollection( + streamlines, transform=transform, **line_kw) + lc.sticky_edges.x[:] = [grid.x_origin, grid.x_origin + grid.width] + lc.sticky_edges.y[:] = [grid.y_origin, grid.y_origin + grid.height] if use_multicolor_lines: lc.set_array(np.ma.hstack(line_colors)) lc.set_cmap(cmap) @@ -229,7 +226,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, margins=False) + ac = matplotlib.collections.PatchCollection(arrows) stream_container = StreamplotSet(lc, ac) return stream_container 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 2963038692f8..95155db23c99 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 24c69eb926b5..9a3c3186ea57 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 2f1c86fdf19e..64674b5f5bd2 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 @@ -27,452 +27,452 @@ z " style="fill:#ffffff;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -487,4328 +487,4328 @@ C -2.000462 -1.161816 -2.236068 -0.593012 -2.236068 0 C -2.236068 0.593012 -2.000462 1.161816 -1.581139 1.581139 C -1.161816 2.000462 -0.593012 2.236068 0 2.236068 z -" id="m8257232a59" style="stroke:#000000;"/> +" id="meff777c5d4" style="stroke:#000000;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -4838,20 +4838,20 @@ L 518.4 43.2 +" id="m41b0efa5af" style="stroke:#000000;stroke-width:0.5;"/> - + +" id="m80dd548a08" style="stroke:#000000;stroke-width:0.5;"/> - + @@ -4877,7 +4877,7 @@ Q 6.59375 54.828125 13.0625 64.515625 Q 19.53125 74.21875 31.78125 74.21875 " id="DejaVuSans-30"/> - + @@ -4885,12 +4885,12 @@ Q 19.53125 74.21875 31.78125 74.21875 - + - + @@ -4920,7 +4920,7 @@ Q 44.1875 33.984375 37.640625 27.21875 Q 31.109375 20.453125 19.1875 8.296875 " id="DejaVuSans-32"/> - + @@ -4929,12 +4929,12 @@ Q 31.109375 20.453125 19.1875 8.296875 - + - + @@ -4958,7 +4958,7 @@ L 4.890625 26.703125 z " id="DejaVuSans-34"/> - + @@ -4967,12 +4967,12 @@ z - + - + @@ -5007,7 +5007,7 @@ Q 40.921875 74.21875 44.703125 73.484375 Q 48.484375 72.75 52.59375 71.296875 " id="DejaVuSans-36"/> - + @@ -5016,12 +5016,12 @@ Q 48.484375 72.75 52.59375 71.296875 - + - + @@ -5064,7 +5064,7 @@ Q 25.390625 66.40625 21.84375 63.234375 Q 18.3125 60.0625 18.3125 54.390625 " id="DejaVuSans-38"/> - + @@ -5073,12 +5073,12 @@ Q 18.3125 60.0625 18.3125 54.390625 - + - + @@ -5098,7 +5098,7 @@ L 12.40625 0 z " id="DejaVuSans-31"/> - + @@ -5112,25 +5112,25 @@ z +" id="m49ab0988c4" style="stroke:#000000;stroke-width:0.5;"/> - + +" id="m67cbd1d5f8" style="stroke:#000000;stroke-width:0.5;"/> - + - + @@ -5138,17 +5138,17 @@ L -4 0 - + - + - + @@ -5157,17 +5157,17 @@ L -4 0 - + - + - + @@ -5176,17 +5176,17 @@ L -4 0 - + - + - + @@ -5195,17 +5195,17 @@ L -4 0 - + - + - + @@ -5214,17 +5214,17 @@ L -4 0 - + - + - + @@ -5235,7 +5235,7 @@ L -4 0 - + diff --git a/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_contouring.png b/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_contouring.png index 655841671c49..d6d2b993fd80 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_contouring.png and b/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_contouring.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_gradient.png b/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_gradient.png index 071102bcf92d..52b0d21362e4 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_gradient.png and b/lib/matplotlib/tests/baseline_images/test_triangulation/tri_smooth_gradient.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2524255729f6..c142271d1e42 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -174,6 +174,23 @@ def test_autoscale_tight(): assert_allclose(ax.get_xlim(), (-0.15, 3.15)) assert_allclose(ax.get_ylim(), (1.0, 4.0)) +@cleanup(style='default') +def test_use_sticky_edges(): + fig, ax = plt.subplots() + ax.imshow([[0, 1], [2, 3]], origin='lower') + assert_allclose(ax.get_xlim(), (-0.5, 1.5)) + assert_allclose(ax.get_ylim(), (-0.5, 1.5)) + ax.use_sticky_edges = False + ax.autoscale() + xlim = (-0.5 - 2 * ax._xmargin, 1.5 + 2 * ax._xmargin) + ylim = (-0.5 - 2 * ax._ymargin, 1.5 + 2 * ax._ymargin) + assert_allclose(ax.get_xlim(), xlim) + assert_allclose(ax.get_ylim(), ylim) + # Make sure it is reversible: + ax.use_sticky_edges = True + ax.autoscale() + assert_allclose(ax.get_xlim(), (-0.5, 1.5)) + assert_allclose(ax.get_ylim(), (-0.5, 1.5)) @image_comparison(baseline_images=['offset_points'], remove_text=True) diff --git a/lib/matplotlib/tests/test_triangulation.py b/lib/matplotlib/tests/test_triangulation.py index 49a7d40baffb..9f417f13d47d 100644 --- a/lib/matplotlib/tests/test_triangulation.py +++ b/lib/matplotlib/tests/test_triangulation.py @@ -773,7 +773,7 @@ def z(x, y): @image_comparison(baseline_images=['tri_smooth_gradient'], extensions=['png'], remove_text=True, - tol=0.015 if on_win else 0) + tol=0.03 if on_win else 0) def test_tri_smooth_gradient(): # Image comparison based on example trigradient_demo. @@ -823,6 +823,8 @@ def dipole_potential(x, y): plt.quiver(triang.x, triang.y, Ex/E_norm, Ey/E_norm, units='xy', scale=10., zorder=3, color='blue', width=0.007, headwidth=3., headlength=4.) + # We are leaving ax.use_sticky_margins as True, so the + # view limits are the contour data limits. def test_tritools(): diff --git a/lib/matplotlib/tri/tricontour.py b/lib/matplotlib/tri/tricontour.py index 02fac3822170..a528e13386b0 100644 --- a/lib/matplotlib/tri/tricontour.py +++ b/lib/matplotlib/tri/tricontour.py @@ -50,12 +50,8 @@ def _process_args(self, *args, **kwargs): else: tri, z = self._contour_args(args, kwargs) C = _tri.TriContourGenerator(tri.get_cpp_triangulation(), z) - x0 = tri.x.min() - x1 = tri.x.max() - y0 = tri.y.min() - y1 = tri.y.max() - self.ax.update_datalim([(x0, y0), (x1, y1)]) - self.ax.autoscale_view() + self._mins = [tri.x.min(), tri.y.min()] + self._maxs = [tri.x.max(), tri.y.max()] self.cppContourGenerator = C