Skip to content

Commit 86a6e32

Browse files
committed
start at aligning x and y labels
First crack First crack PEP 8 Slight refactor Handles left and right top and bottom labels Add fig.align_labels() wrapper Add fig.align_labels() wrapper Fixed error w xlabels Doc and tests Doc and tests Doc and tests Doc and tests Small Change Documentation fixes. Thanks anntzer Fix axs is a ndarray case PEP8 Try to fix numpydoc Try to fix numpydoc Try to fix numpydoc Try to fix numpydoc Fixed typo
1 parent 0e3dd2d commit 86a6e32

File tree

9 files changed

+3107
-10
lines changed

9 files changed

+3107
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
xlabels and ylabels can now be automatically aligned
2+
----------------------------------------------------
3+
4+
Subplot axes ``ylabels`` can be misaligned horizontally if the tick labels
5+
are very different widths. The same can happen to ``xlabels`` if the
6+
ticklabels are rotated on one subplot (for instance). The new methods
7+
on the `Figure` class: `Figure.align_xlabels` and `Figure.align_ylabels`
8+
will now align these labels horizontally or vertically. If the user only
9+
wants to align some axes, a list of axes can be passed. If no list is
10+
passed, the algorithm looks at all the labels on the figure.
11+
12+
Only labels that have the same subplot locations are aligned. i.e. the
13+
ylabels are aligned only if the subplots are in the same column of the
14+
subplot layout.
15+
16+
A convenience wrapper `Figure.align_labels` calls both functions at once.
17+
18+
.. plot::
19+
20+
import matplotlib.gridspec as gridspec
21+
22+
fig = plt.figure(figsize=(5, 3), tight_layout=True)
23+
gs = gridspec.GridSpec(2, 2)
24+
25+
ax = fig.add_subplot(gs[0,:])
26+
ax.plot(np.arange(0, 1e6, 1000))
27+
ax.set_ylabel('Test')
28+
for i in range(2):
29+
ax = fig.add_subplot(gs[1, i])
30+
ax.set_ylabel('Booooo')
31+
ax.set_xlabel('Hello')
32+
if i == 0:
33+
for tick in ax.get_xticklabels():
34+
tick.set_rotation(45)
35+
fig.align_labels()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
===============
3+
Aligning Labels
4+
===============
5+
6+
Aligning xlabel and ylabel using
7+
`Figure.align_xlabels` and
8+
`Figure.align_ylabels`
9+
10+
`Figure.align_labels` wraps these two functions.
11+
12+
Note that
13+
the xlabel "XLabel1 1" would normally be much closer to the x-axis, and
14+
"YLabel1 0" would be much closer to the y-axis of their respective axes.
15+
"""
16+
import matplotlib.pyplot as plt
17+
import numpy as np
18+
import matplotlib.gridspec as gridspec
19+
20+
fig = plt.figure(tight_layout=True)
21+
gs = gridspec.GridSpec(2, 2)
22+
23+
ax = fig.add_subplot(gs[0, :])
24+
ax.plot(np.arange(0, 1e6, 1000))
25+
ax.set_ylabel('YLabel0')
26+
ax.set_xlabel('XLabel0')
27+
28+
for i in range(2):
29+
ax = fig.add_subplot(gs[1, i])
30+
ax.plot(np.arange(1., 0., -0.1) * 2000., np.arange(1., 0., -0.1))
31+
ax.set_ylabel('YLabel1 %d' % i)
32+
ax.set_xlabel('XLabel1 %d' % i)
33+
if i == 0:
34+
for tick in ax.get_xticklabels():
35+
tick.set_rotation(55)
36+
fig.align_labels() # same as fig.align_xlabels() and fig.align_ylabels()
37+
38+
plt.show()

lib/matplotlib/axis.py

+37-8
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ def __init__(self, axes, pickradius=15):
662662

663663
self._autolabelpos = True
664664
self._smart_bounds = False
665+
self._align_label_siblings = [self]
665666

666667
self.label = self._get_label()
667668
self.labelpad = rcParams['axes.labelpad']
@@ -1089,10 +1090,12 @@ def get_tightbbox(self, renderer):
10891090
return
10901091

10911092
ticks_to_draw = self._update_ticks(renderer)
1092-
ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
1093-
renderer)
10941093

1095-
self._update_label_position(ticklabelBoxes, ticklabelBoxes2)
1094+
self._update_label_position(renderer)
1095+
1096+
# go back to just this axis's tick labels
1097+
ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(
1098+
ticks_to_draw, renderer)
10961099

10971100
self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2)
10981101
self.offsetText.set_text(self.major.formatter.get_offset())
@@ -1143,7 +1146,7 @@ def draw(self, renderer, *args, **kwargs):
11431146
# *copy* of the axis label box because we don't wan't to scale
11441147
# the actual bbox
11451148

1146-
self._update_label_position(ticklabelBoxes, ticklabelBoxes2)
1149+
self._update_label_position(renderer)
11471150

11481151
self.label.draw(renderer)
11491152

@@ -1655,7 +1658,24 @@ def set_ticks(self, ticks, minor=False):
16551658
self.set_major_locator(mticker.FixedLocator(ticks))
16561659
return self.get_major_ticks(len(ticks))
16571660

1658-
def _update_label_position(self, bboxes, bboxes2):
1661+
def _get_tick_boxes_siblings(self, renderer):
1662+
"""
1663+
Get the bounding boxes for this axis and its sibblings
1664+
as set by `Figure.align_xlabels` or ``Figure.align_ylables`.
1665+
1666+
By default it just gets bboxes for self.
1667+
"""
1668+
bboxes = []
1669+
bboxes2 = []
1670+
# if we want to align labels from other axes:
1671+
for axx in self._align_label_siblings:
1672+
ticks_to_draw = axx._update_ticks(renderer)
1673+
tlb, tlb2 = axx._get_tick_bboxes(ticks_to_draw, renderer)
1674+
bboxes.extend(tlb)
1675+
bboxes2.extend(tlb2)
1676+
return bboxes, bboxes2
1677+
1678+
def _update_label_position(self, renderer):
16591679
"""
16601680
Update the label position based on the bounding box enclosing
16611681
all the ticklabels and axis spine
@@ -1832,13 +1852,18 @@ def set_label_position(self, position):
18321852
self.label_position = position
18331853
self.stale = True
18341854

1835-
def _update_label_position(self, bboxes, bboxes2):
1855+
def _update_label_position(self, renderer):
18361856
"""
18371857
Update the label position based on the bounding box enclosing
18381858
all the ticklabels and axis spine
18391859
"""
18401860
if not self._autolabelpos:
18411861
return
1862+
1863+
# get bounding boxes for this axis and any siblings
1864+
# that have been set by `fig.align_xlabels()`
1865+
bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer)
1866+
18421867
x, y = self.label.get_position()
18431868
if self.label_position == 'bottom':
18441869
try:
@@ -2160,13 +2185,18 @@ def set_label_position(self, position):
21602185
self.label_position = position
21612186
self.stale = True
21622187

2163-
def _update_label_position(self, bboxes, bboxes2):
2188+
def _update_label_position(self, renderer):
21642189
"""
21652190
Update the label position based on the bounding box enclosing
21662191
all the ticklabels and axis spine
21672192
"""
21682193
if not self._autolabelpos:
21692194
return
2195+
2196+
# get bounding boxes for this axis and any siblings
2197+
# that have been set by `fig.align_ylabels()`
2198+
bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer)
2199+
21702200
x, y = self.label.get_position()
21712201
if self.label_position == 'left':
21722202
try:
@@ -2178,7 +2208,6 @@ def _update_label_position(self, bboxes, bboxes2):
21782208
spinebbox = self.axes.bbox
21792209
bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
21802210
left = bbox.x0
2181-
21822211
self.label.set_position(
21832212
(left - self.labelpad * self.figure.dpi / 72.0, y)
21842213
)

0 commit comments

Comments
 (0)