diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 349e728ba8ff..66e42da16cc0 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1831,6 +1831,13 @@ def _get_tick_label_size(self, axis_name): mpl.rcParams[f'{axis_name}tick.labelsize']) return mtext.FontProperties(size=size).get_size_in_points() + def _get_tick_label_rotation(self): + """ + Return the rotation (in degrees) applied to tick labels on this + Axis, as set via `.Axis.set_tick_params(rotation=...)`. + """ + return float(self._major_tick_kw.get('labelrotation', 0)) + def _copy_tick_props(self, src, dest): """Copy the properties from *src* tick to *dest* tick.""" if src is None or dest is None: @@ -2828,9 +2835,13 @@ def get_tick_space(self): ends = mtransforms.Bbox.unit().transformed( self.axes.transAxes - self.get_figure(root=False).dpi_scale_trans) length = ends.width * 72 - # There is a heuristic here that the aspect ratio of tick text - # is no more than 3:1 - size = self._get_tick_label_size('x') * 3 + # There is a heuristic here that the aspect ratio of horizontal + # tick text is no more than 3:1 (width:height); as the label is + # rotated towards vertical, its footprint along the x-axis shrinks + # towards a single line-height (aspect ratio ~1:1). + rotation = np.deg2rad(self._get_tick_label_rotation()) + aspect = abs(3 * np.cos(rotation)) + abs(np.sin(rotation)) + size = self._get_tick_label_size('x') * aspect if size > 0: return int(np.floor(length / size)) else: @@ -3058,8 +3069,12 @@ def get_tick_space(self): ends = mtransforms.Bbox.unit().transformed( self.axes.transAxes - self.get_figure(root=False).dpi_scale_trans) length = ends.height * 72 - # Having a spacing of at least 2 just looks good. - size = self._get_tick_label_size('y') * 2 + # Having a spacing of at least 2 just looks good, for horizontal + # labels; as the label rotates towards vertical, its footprint + # along the y-axis grows towards a horizontal-text-like 3:1 ratio. + rotation = np.deg2rad(self._get_tick_label_rotation()) + aspect = abs(2 * np.cos(rotation)) + abs(3 * np.sin(rotation)) + size = self._get_tick_label_size('y') * aspect if size > 0: return int(np.floor(length / size)) else: diff --git a/lib/matplotlib/tests/test_axis.py b/lib/matplotlib/tests/test_axis.py index 3776b6f054b9..c99872f26782 100644 --- a/lib/matplotlib/tests/test_axis.py +++ b/lib/matplotlib/tests/test_axis.py @@ -128,3 +128,23 @@ def test_set_ticks_emits_lim_changed(): ax2.callbacks.connect("ylim_changed", called_polar.append) ax2.set_rticks([1, 2, 3]) assert called_polar + + + def test_tick_space_rotation(): + # Rotating x tick labels towards vertical should let more x ticks fit + # (their horizontal footprint shrinks), while rotating y tick labels + # towards vertical should let fewer y ticks fit (their vertical + # footprint grows). See issue #32005. + fig, ax = plt.subplots(figsize=(1.8, 1.8), layout="constrained") + fig.canvas.draw() + xspace_horizontal = ax.xaxis.get_tick_space() + yspace_horizontal = ax.yaxis.get_tick_space() + + fig2, ax2 = plt.subplots(figsize=(1.8, 1.8), layout="constrained") + ax2.tick_params(rotation=90) + fig2.canvas.draw() + xspace_vertical = ax2.xaxis.get_tick_space() + yspace_vertical = ax2.yaxis.get_tick_space() + + assert xspace_vertical > xspace_horizontal + assert yspace_vertical < yspace_horizontal