Skip to content

implemented support for asymmetric margins. #2276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions doc/api/api_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@ original location:
- mstream -> `from matplotlib import stream as mstream`
- mtable -> `from matplotlib import table as mtable`

* To accommodate adding asymmetric margins, additional optional arguments
were added to `axes.set_ymargins`, `axes.set_xmargins`, and `axes3D.set_zmargins`
and the return values of `axes.margins()` and `axes3D.margins()` were changed.

- `axes.set_xmargin(m)` -> `axes.set_xmargin(left, right=None)`
- `axes.set_ymargin(m)` -> `axes.set_xmargin(bottom, top=None)`
- `axes3D.set_xmargin(m)` -> `axes.set_zmargin(down, up=None)`

If `None` is passed as the second argument, symmetric margins are used
(original behavior).

- `axes.margins()`, `plt.margins()` return value `(xmargin, ymargin)` ->
`((left, right), (bottom, top))`
- `axes3D.margins()` return value `(xmargin, ymargin)` ->
`((left, right), (bottom, top), (down, up))`


.. _changes_in_1_3:

Changes in 1.3.x
Expand Down
13 changes: 11 additions & 2 deletions doc/users/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ New plotting features
Support for datetime axes in 2d plots
`````````````````````````````````````
Andrew Dawson added support for datetime axes to
:func:`~matplotlib.pyplot.contour`, :func:`~matplotlib.pyplot.contourf`,
:func:`~matplotlib.pyplot.pcolormesh` and :func:`~matplotlib.pyplot.pcolor`.
:func:`~matplotlib.pyplot.contour`, :func:`~matplotlib.pyplot.contourf`,
:func:`~matplotlib.pyplot.pcolormesh` and :func:`~matplotlib.pyplot.pcolor`.


Date handling
Expand All @@ -44,6 +44,15 @@ and :func:`matplotlib.dates.datestr2num`. Support is also added to the unit
conversion interfaces :class:`matplotlib.dates.DateConverter` and
:class:`matplotlib.units.Registry`.

Asymmetric margins
------------------

:func:`matplotlib.Axes.set_xmargin`, :func:`matplotlib.Axes.set_ymargin`, and
:func:`matplotlib.Axes3D.set_zmargin` now take two arguments to set asymmetric margins.
The return types on :func:`matplotlib.Axes.margins()` changed to a tuple of tuples to return
the asymmetric margins.


.. _whats-new-1-3:

new in matplotlib-1.3
Expand Down
21 changes: 17 additions & 4 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3292,11 +3292,24 @@ def scatter(self, x, y, s=20, c='b', marker='o', cmap=None, norm=None,
# to data coords to get the exact bounding box for efficiency
# reasons. It can be done right if this is deemed important.
# Also, only bother with this padding if there is anything to draw.
if self._xmargin < 0.05 and x.size > 0:
self.set_xmargin(0.05)

if self._ymargin < 0.05 and x.size > 0:
self.set_ymargin(0.05)
# don't use the function, as it gets overloaded in Axes3D
(xm_left, xm_right) = (self._xmargin_left, self._xmargin_right)
# enforce minimum padding
if xm_left < 0.05 and x.size > 0:
xm_left = 0.05
if xm_right < 0.05 and x.size > 0:
xm_right = 0.05

(ym_bot, ym_top) = (self._ymargin_bot, self._ymargin_top)
if ym_bot < 0.05 and y.size > 0:
ym_bot = 0.05

if ym_top < 0.05 and y.size > 0:
ym_top = 0.05

self.set_xmargin(xm_left, xm_right)
self.set_ymargin(ym_bot, ym_top)

self.add_collection(collection)
self.autoscale_view()
Expand Down
82 changes: 55 additions & 27 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,8 +870,10 @@ def cla(self):

self._autoscaleXon = True
self._autoscaleYon = True
self._xmargin = rcParams['axes.xmargin']
self._ymargin = rcParams['axes.ymargin']
self._xmargin_left = rcParams['axes.xmargin']
self._xmargin_right = rcParams['axes.xmargin']
self._ymargin_top = rcParams['axes.ymargin']
self._ymargin_bot = rcParams['axes.ymargin']
self._tight = False
self._update_transScale() # needed?

Expand Down Expand Up @@ -1734,31 +1736,60 @@ def set_autoscaley_on(self, b):
"""
self._autoscaleYon = b

def set_xmargin(self, m):
def set_xmargin(self, left, right=None):
"""
Set padding of X data limits prior to autoscaling.

*m* times the data interval will be added to each
end of that interval before it is used in autoscaling.
Parameters
----------
left : float in [0, 1]
The fraction of the data interval to add to the left
of the x-range used in autoscaling

right : float in [0, 1] or None
The fraction of the data interval to add to the right
of the x-range used in autoscaling.

If None, then symmetric margins are used (`right = left`)

accepts: float in range 0 to 1
"""
if m < 0 or m > 1:
if right is None:
right = left

if right < 0 or right > 1:
raise ValueError("margin must be in range 0 to 1")
self._xmargin = m
if left < 0 or left > 1:
raise ValueError("margin must be in range 0 to 1")

self._xmargin_left = left
self._xmargin_right = right

def set_ymargin(self, m):
def set_ymargin(self, bottom, top=None):
"""
Set padding of Y data limits prior to autoscaling.

*m* times the data interval will be added to each
end of that interval before it is used in autoscaling.
Parameters
----------
bottom : float in [0, 1]
The fraction of the data interval to add to the bottom
of the y-range used in autoscaling

top : float in [0, 1] or None
The fraction of the data interval to add to the top
of the y-range used in autoscaling.

If None, then symmetric margins are used (`top = bottom`)

accepts: float in range 0 to 1
"""
if m < 0 or m > 1:
raise ValueError("margin must be in range 0 to 1")
self._ymargin = m
if top is None:
bottom = top
if bottom < 0 or bottom > 1:
raise ValueError("bottom margin must be in range 0 to 1")
if top < 0 or top > 1:
raise ValueError("top margin must be in range 0 to 1")

self._ymargin_top = top
self._ymargin_bot = bottom

def margins(self, *args, **kw):
"""
Expand All @@ -1768,7 +1799,7 @@ def margins(self, *args, **kw):

margins()

returns xmargin, ymargin
returns (xmargin_left, xmargin_right), (ymargin_bottom, ymargin_top)

::

Expand Down Expand Up @@ -1796,7 +1827,7 @@ def margins(self, *args, **kw):

"""
if not args and not kw:
return self._xmargin, self._ymargin
return (self._xmargin_left, self._xmargin_right), (self._ymargin_bot, self._ymargin_top)

tight = kw.pop('tight', True)
mx = kw.pop('x', None)
Expand Down Expand Up @@ -1905,10 +1936,10 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
# 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
delta = (x1 - x0)
# if the margin is 0, then this is a no-op -> no need for if statements
x0 -= delta * self._xmargin_left
x1 += delta * self._xmargin_right
if not _tight:
x0, x1 = xlocator.view_limits(x0, x1)
self.set_xbound(x0, x1)
Expand All @@ -1924,10 +1955,9 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
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
delta = (y1 - y0)
y0 -= delta * self._ymargin_bot
y1 += delta * self._ymargin_top
if not _tight:
y0, y1 = ylocator.view_limits(y0, y1)
self.set_ybound(y0, y1)
Expand Down Expand Up @@ -3249,5 +3279,3 @@ def get_shared_x_axes(self):
def get_shared_y_axes(self):
'Return a copy of the shared axes Grouper object for y axes'
return self._shared_y_axes


69 changes: 46 additions & 23 deletions lib/mpl_toolkits/mplot3d/axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,21 +321,35 @@ def set_autoscalez_on(self, b) :
"""
self._autoscalez_on = b

def set_zmargin(self, m) :
def set_zmargin(self, down, up=None):
"""
Set padding of Z data limits prior to autoscaling.

*m* times the data interval will be added to each
end of that interval before it is used in autoscaling.
Parameters
----------
down : float in [0, 1]
The fraction of the data interval to add to the bottom
of the z-range used in autoscaling

accepts: float in range 0 to 1
up : float in [0, 1] or None
The fraction of the data interval to add to the top
of the z-range used in autoscaling.

If None, then symmetric margins are used (`up = down`)

.. versionadded :: 1.1.0
This function was added, but not tested. Please report any bugs.
"""
if m < 0 or m > 1 :
if up is None:
down = up

if down < 0 or down > 1:
raise ValueError("margin must be in range 0 to 1")
if up < 0 or up > 1:
raise ValueError("margin must be in range 0 to 1")
self._zmargin = m

self._zmargin_up = up
self._zmargin_down = down

def margins(self, *args, **kw) :
"""
Expand All @@ -344,7 +358,9 @@ def margins(self, *args, **kw) :
signatures::
margins()

returns xmargin, ymargin, zmargin
returns ((xmargin_left, xmargin_right),
(ymargin_bottom, ymargin_top),
(zmargin_down, mzargin_up))

::

Expand Down Expand Up @@ -374,7 +390,9 @@ def margins(self, *args, **kw) :
This function was added, but not tested. Please report any bugs.
"""
if not args and not kw:
return self._xmargin, self._ymargin, self._zmargin
return ((self._xmargin_left, self._xmargin_right),
(self._ymargin_bot, self._ymargin_top),
(self._zmargin_down, self._zmargin_up))

tight = kw.pop('tight', True)
mx = kw.pop('x', None)
Expand Down Expand Up @@ -493,10 +511,11 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True,
except AttributeError:
x0, x1 = mtransforms.nonsingular(x0, x1, increasing=False,
expander=0.05)
if self._xmargin > 0:
delta = (x1 - x0) * self._xmargin
x0 -= delta
x1 += delta
delta = (x1 - x0)
# if the margin is 0, then this is a no-op -> no need for if statements
x0 -= delta * self._xmargin_left
x1 += delta * self._xmargin_right

if not _tight:
x0, x1 = xlocator.view_limits(x0, x1)
self.set_xbound(x0, x1)
Expand All @@ -512,10 +531,10 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True,
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
delta = (y1 - y0)
y0 -= delta * self._ymargin_bot
y1 += delta * self._ymargin_top

if not _tight:
y0, y1 = ylocator.view_limits(y0, y1)
self.set_ybound(y0, y1)
Expand All @@ -531,10 +550,9 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True,
except AttributeError:
z0, z1 = mtransforms.nonsingular(z0, z1, increasing=False,
expander=0.05)
if self._zmargin > 0:
delta = (z1 - z0) * self._zmargin
z0 -= delta
z1 += delta
delta = (z1 - z0)
z0 -= delta * self._zmargin_down
z1 += delta * self._zmargin_up
if not _tight:
z0, z1 = zlocator.view_limits(z0, z1)
self.set_zbound(z0, z1)
Expand Down Expand Up @@ -1040,7 +1058,8 @@ def cla(self):
self.zaxis._set_scale('linear')

self._autoscaleZon = True
self._zmargin = 0
self._zmargin_up = 0
self._zmargin_down = 0

Axes.cla(self)

Expand Down Expand Up @@ -2185,8 +2204,12 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c='b', *args, **kwargs):
is_2d = False
art3d.patch_collection_2d_to_3d(patches, zs=zs, zdir=zdir)

if self._zmargin < 0.05 and xs.size > 0:
self.set_zmargin(0.05)
zm_down, zm_up = self._zmargin_down, self._zmargin_up
if zm_down < 0.05 and xs.size > 0:
zm_down = 0.05
if zm_up < 0.05 and xs.size > 0:
zm_up = 0.05
self.set_zmargin(zm_down, zm_up)

#FIXME: why is this necessary?
if not is_2d:
Expand Down