8000 Sticky margins cancellation system. · matplotlib/matplotlib@f8bd0b4 · GitHub
[go: up one dir, main page]

Skip to content

Commit f8bd0b4

Browse files
committed
Sticky margins cancellation system.
Let artists register lists of "sticky" x and y values. If an edge of the union of bounding boxes ends up falling on a sticky value, do not add margins to that edge. Docs still missing.
1 parent a9d1cdf commit f8bd0b4

File tree

7 files changed

+79
-214
lines changed

7 files changed

+79
-214
lines changed

lib/matplotlib/artist.py

Lines changed: 11 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
unicode_literals)
33

44
import six
5-
from collections import OrderedDict
5+
from collections import OrderedDict, namedtuple
66

77
import re
88
import warnings
@@ -76,6 +76,9 @@ def _stale_axes_callback(self, val):
7676
self.axes.stale = val
7777

7878

79+
_XYPair = namedtuple("_XYPair", "x y")
80+
81+
7982
class Artist(object):
8083
"""
8184
Abstract base class for someone who renders into a
@@ -123,8 +126,7 @@ def __init__(self):
123126
self._snap = None
124127
self._sketch = rcParams['path.sketch']
125128
self._path_effects = rcParams['path.effects']
126-
127-
self._margins = {}
129+
self._stickies = _XYPair([], [])
128130

129131
def __getstate__(self):
130132
d = self.__dict__.copy()
@@ -926,98 +928,15 @@ def set_zorder(self, level):
926928
self.pchanged()
927929
self.stale = True
928930

929-
def get_top_margin(self):
930-
"""
931-
Get whether a margin should be applied to the top of the Artist.
932-
"""
933-
return self._margins.get('top', True)
934-
935-
def set_top_margin(self, margin):
936-
"""
937-
Set whether a margin should be applied to the top of the Artist.
938-
"""
939-
if margin != self._margins.get('top', True):
940-
self.stale = True
941-
self._margins['top'] = margin
942-
943-
top_margin = property(get_top_margin, set_top_margin)
944-
945-
def get_bottom_margin(self):
946-
"""
947-
Get whether a margin should be applied to the bottom of the Artist.
948-
"""
949-
return self._margins.get('bottom', True)
950-
951-
def set_bottom_margin(self, margin):
952-
"""
953-
Set whether a margin should be applied to the bottom of the Artist.
954-
"""
955-
if margin != self._margins.get('bottom', True):
956-
self.stale = True
957-
self._margins['bottom'] = margin
958-
959-
bottom_margin = property(get_bottom_margin, set_bottom_margin)
960-
961-
def get_left_margin(self):
962-
"""
963-
Get whether a margin should be applied to the left of the Artist.
964-
"""
965-
return self._margins.get('left', True)
966-
967-
def set_left_margin(self, margin):
968-
"""
969-
Set whether a margin should be applied to the left of the Artist.
970-
"""
971-
if margin != self._margins.get('left', True):
972-
self.stale = True
973-
self._margins['left'] = margin
974-
975-
left_margin = property(get_left_margin, set_left_margin)
976-
977-
def get_right_margin(self):
978-
"""
979-
Get whether a margin should be applied to the right of the Artist.
980-
"""
981-
return self._margins.get('right', True)
982-
983-
def set_right_margin(self, margin):
984-
"""
985-
Set whether a margin should be applied to the right of the Artist.
986-
"""
987-
if margin != self._margins.get('right', True):
988-
self.stale = True
989-
self._margins['right'] = margin
990-
991-
right_margin = property(get_right_margin, set_right_margin)
992-
993-
def get_margins(self):
994-
"""
995-
Returns a dictionary describing whether a margin should be applied on
996-
each of the sides (top, bottom, left and right).
931+
@property
932+
def stickies(self):
997933
"""
998-
return self._margins
934+
The `x` and `y` sticky lists for the artist.
999935
1000-
def set_margins(self, margins):
936+
This attribute cannot be assigned to; however, the `x` and `y` lists
937+
can be modified in place as needed.
1001938
"""
1002-
Set the dictionary describing whether a margin should be applied on
1003-
each of the sides (top, bottom, left and right). Missing keys are
1004-
assumed to be `True`. If `True` or `False` are passed in, all
1005-
sides are set to that value.
1006-
"""
1007-
if margins in (True, False):
1008-
margins = {
1009-
'top': margins,
1010-
'bottom': margins,
1011-
'left': margins,
1012-
'right': margins
1013-
}
1014-
1015-
if margins != self._margins:
1016-
self.stale = True
1017-
1018-
self._margins = margins
1019-
1020-
margins = property(get_margins, set_margins)
939+
return self._stickies
1021940

1022941
def update_from(self, other):
1023942
'Copy properties from *other* to *self*.'

lib/matplotlib/axes/_axes.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2097,13 +2097,6 @@ def make_iterable(x):
20972097
if yerr is not None:
20982098
yerr = self.convert_yunits(yerr)
20992099

2100-
margins = {}
2101-
2102-
if orientation == 'vertical':
2103-
margins = {'bottom': False}
2104-
elif orientation == 'horizontal':
2105-
margins = {'left': False}
2106-
21072100
if align == 'center':
21082101
if orientation == 'vertical':
21092102
left = [left[i] - width[i] / 2. for i in xrange(len(left))]
@@ -2128,11 +2121,13 @@ def make_iterable(x):
21282121
edgecolor=e,
21292122
linewidth=lw,
21302123
label='_nolegend_',
2131-
margins=margins
21322124
)
21332125
r.update(kwargs)
21342126
r.get_path()._interpolation_steps = 100
2135-
#print r.get_label(), label, 'label' in kwargs
2127+
if orientation == 'vertical':
2128+
r.stickies.y.append(0)
2129+
elif orientation == 'horizontal':
2130+
r.stickies.x.append(0)
21362131
self.add_patch(r)
21372132
patches.append(r)
21382133

@@ -5466,7 +5461,7 @@ def pcolor(self, *args, **kwargs):
54665461

54675462
kwargs.setdefault('snap', False)
54685463

5469-
collection = mcoll.PolyCollection(verts, margins=False, **kwargs)
5464+
collection = mcoll.PolyCollection(verts, **kwargs)
54705465

54715466
collection.set_alpha(alpha)
54725467
collection.set_array(C)
@@ -5500,8 +5495,10 @@ def pcolor(self, *args, **kwargs):
55005495
miny = np.amin(y)
55015496
maxy = np.amax(y)
55025497

5503-
corners = (minx, miny), (maxx, maxy)
55045498
self.add_collection(collection, autolim=False)
5499+
corners = (minx, miny), (maxx, maxy)
5500+
collection.stickies.x[:] = [minx, maxx]
5501+
collection.stickies.y[:] = [miny, maxy]
55055502
self.update_datalim(corners)
55065503
self.autoscale_view()
55075504
return collection
@@ -5617,10 +5614,9 @@ def pcolormesh(self, *args, **kwargs):
56175614
coords[:, 0] = X
56185615
coords[:, 1] = Y
56195616

5620-
collection = mcoll.QuadMesh(
5621-
Nx - 1, Ny - 1, coords,
5622-
antialiased=antialiased, shading=shading, margins=False,
5623-
**kwargs)
5617+
collection = mcoll.QuadMesh(Nx - 1, Ny - 1, coords,
5618+
antialiased=antialiased, shading=shading,
5619+
**kwargs)
56245620
collection.set_alpha(alpha)
56255621
collection.set_array(C)
56265622
if norm is not None and not isinstance(norm, mcolors.Normalize):
@@ -5651,8 +5647,10 @@ def pcolormesh(self, *args, **kwargs):
56515647
miny = np.amin(Y)
56525648
maxy = np.amax(Y)
56535649

5654-
corners = (minx, miny), (maxx, maxy)
56555650
self.add_collection(collection, autolim=False)
5651+
corners = (minx, miny), (maxx, maxy)
5652+
collection.stickies.x[:] = [minx, maxx]
5653+
collection.stickies.y[:] = [miny, maxy]
56565654
self.update_datalim(corners)
56575655
self.autoscale_view()
56585656
return collection
@@ -5804,8 +5802,7 @@ def pcolorfast(self, *args, **kwargs):
58045802
# The QuadMesh class can also be changed to
58055803
# handle relevant superclass kwargs; the initializer
58065804
# should do much more than it does now.
5807-
collection = mcoll.QuadMesh(nc, nr, coords, 0, edgecolors="None",
5808-
margins=False)
5805+
collection = mcoll.QuadMesh(nc, nr, coords, 0, edgecolors="None")
58095806
collection.set_alpha(alpha)
58105807
collection.set_array(C)
58115808
collection.set_cmap(cmap)
@@ -5842,6 +5839,8 @@ def pcolorfast(self, *args, **kwargs):
58425839
ret.set_clim(vmin, vmax)
58435840
else:
58445841
ret.autoscale_None()
5842+
collection.stickies.x[:] = [xl, xr]
5843+
collection.stickies.y[:] = [yb, yt]
58455844
self.update_datalim(np.array([[xl, yb], [xr, yt]]))
58465845
self.autoscale_view(tight=True)
58475846
return ret
@@ -6254,11 +6253,6 @@ def _normalize_input(inp, ename='input'):
62546253
else:
62556254
n = [m[slc].cumsum()[slc] for m in n]
62566255

6257-
if orientation == 'horizontal':
6258-
margins = {'left': False}
6259-
else:
6260-
margins = {'bottom': False}
6261-
62626256
patches = []
62636257

62646258
if histtype.startswith('bar'):
@@ -6403,8 +6397,12 @@ def _normalize_input(inp, ename='input'):
64036397
closed=True if fill else None,
64046398
facecolor=c,
64056399
edgecolor=None if fill else c,
6406-
fill=fill if fill else None,
6407-
margins=margins))
6400+
fill=fill if fill else None))
6401+
for patch in patches:
6402+
if orientation == 'vertical':
6403+
patch.stickies.y.append(0)
6404+
elif orientation == 'horizontal':
6405+
patch.stickies.x.append(0)
64086406

64096407
# we return patches, so put it back in the expected order
64106408
patches.reverse()

lib/matplotlib/axes/_base.py

Lines changed: 31 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,53 +2199,18 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
21992199
case, use :meth:`matplotlib.axes.Axes.relim` prior to calling
22002200
autoscale_view.
22012201
"""
2202-
if tight is None:
2203-
_tight = self._tight
2204-
else:
2205-
_tight = self._tight = bool(tight)
2202+
if tight is not None:
2203+
self._tight = bool(tight)
22062204

22072205
if self._xmargin or self._ymargin:
2208-
margins = {
2209-
'top': True,
2210-
'bottom': True,
2211-
'left': True,
2212-
'right': True
2213-
}
2214-
for artist_set in [self.collections, self.patches, self.lines,
2215-
self.artists, self.images]:
2216-
for artist in artist_set:
2217-
artist_margins = artist.margins
2218-
for key in ['left', 'right', 'top', 'bottom']:
2219-
margins[key] &= artist_margins.get(key, True)
2220-
2221-
if self._xmargin:
2222-
for axes in self._shared_x_axes.get_siblings(self):
2223-
for artist_set in [axes.collections, axes.patches,
2224-
axes.lines, axes.artists, axes.images]:
2225-
for artist in artist_set:
2226-
artist_margins = artist.margins
2227-
for key in ['left', 'right']:
2228-
margins[key] &= artist_margins.get(key, True)
2229-
2230-
if self._ymargin:
2231-
for axes in self._shared_y_axes.get_siblings(self):
2232-
for artist_set in [axes.collections, axes.patches,
2233-
axes.lines, axes.artists, axes.images]:
2234-
for artist in artist_set:
2235-
artist_margins = artist.margins
2236-
for key in ['top', 'bottom']:
2237-
margins[key] &= artist_margins.get(key, True)
2238-
else:
2239-
margins = {
2240-
'top': False,
2241-
'bottom': False,
2242-
'left': False,
2243-
'right': False
2244-
}
2206+
stickies = [artist.stickies for artist in self.get_children()]
2207+
x_stickies = sum([sticky.x for sticky in stickies], [])
2208+
y_stickies = sum([sticky.y for sticky in stickies], [])
2209+
else: # Small optimization.
2210+
x_stickies, y_stickies = [], []
22452211

22462212
def handle_single_axis(scale, autoscaleon, shared_axes, interval,
2247-
minpos, axis, margin, do_lower_margin,
2248-
do_upper_margin, set_bound):
2213+
minpos, axis, margin, stickies, set_bound):
22492214

22502215
if not (scale and autoscaleon):
22512216
return # nothing to do...
@@ -2268,43 +2233,35 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval,
22682233
x0, x1 = mtransforms.nonsingular(
22692234
x0, x1, increasing=False, expander=0.05)
22702235

2271-
if margin > 0 and (do_lower_margin or do_upper_margin):
2272-
if axis.get_scale() == 'linear':
2273-
delta = (x1 - x0) * margin
2274-
if do_lower_margin:
2275-
x0 -= delta
2276-
if do_upper_margin:
2277-
x1 += delta
2278-
else:
2279-
# If we have a non-linear scale, we need to
2280-
# add the margin in figure space and then
2281-
# transform back
2282-
minpos = getattr(bb, minpos)
2283-
transform = axis.get_transform()
2284-
inverse_trans = transform.inverted()
2285-
x0, x1 = axis._scale.limit_range_for_scale(
2286-
x0, x1, minpos)
2287-
x0t, x1t = transform.transform([x0, x1])
2288-
delta = (x1t - x0t) * margin
2289-
if do_lower_margin:
2290-
x0t -= delta
2291-
if do_upper_margin:
2292-
x1t += delta
2293-
x0, x1 = inverse_trans.transform([x0t, x1t])
2294-
2295-
if not _tight:
2236+
# Add the margin in figure space and then transform back, to handle
2237+
# non-linear scales.
2238+
minpos = getattr(bb, minpos)
2239+
transform = axis.get_transform()
2240+
inverse_trans = transform.inverted()
2241+
# We cannot use exact equality due to floating point issues e.g.
2242+
# with streamplot.
2243+
do_lower_margin = not np.any(np.isclose(x0, stickies))
2244+
do_upper_margin = not np.any(np.isclose(x1, stickies))
2245+
x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minpos)
2246+
x0t, x1t = transform.transform([x0, x1])
2247+
delta = (x1t - x0t) * margin
2248+
if do_lower_margin:
2249+
x0t -= delta
2250+
if do_upper_margin:
2251+
x1t += delta
2252+
x0, x1 = inverse_trans.transform([x0t, x1t])
2253+
2254+
if not self._tight:
22962255
x0, x1 = locator.view_limits(x0, x1)
22972256
set_bound(x0, x1)
22982257
# End of definition of internal function 'handle_single_axis'.
22992258

23002259
handle_single_axis(
2301-
scalex, self._autoscaleXon, self._shared_x_axes,
2302-
'intervalx', 'minposx', self.xaxis, self._xmargin,
2303-
margins['left'], margins['right'], self.set_xbound)
2260+
scalex, self._autoscaleXon, self._shared_x_axes, 'intervalx',
2261+
'minposx', self.xaxis, self._xmargin, x_stickies, self.set_xbound)
23042262
handle_single_axis(
2305-
scaley, self._autoscaleYon, self._shared_y_axes,
2306-
'intervaly', 'minposy', self.yaxis, self._ymargin,
2307-
margins['bottom'], margins['top'], self.set_ybound)
2263+
scaley, self._autoscaleYon, self._shared_y_axes, 'intervaly',
2264+
'minposy', self.yaxis, self._ymargin, y_stickies, self.set_ybound)
23082265

23092266
def _get_axis_list(self):
23102267
return (self.xaxis, self.yaxis)

lib/matplotlib/cbook.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from six.moves import xrange, zip
1414
from itertools import repeat
1515
import collections
16-
1716
import datetime
1817
import errno
1918
import functools

0 commit comments

Comments
 (0)
0