Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f35e980

Browse files
committed
Allow constructing boxplots over multiple calls.
Currently, calls to boxplot() set the axis limits, ticks, and ticklabels assuming that nothing else, and in particular no other boxplot, will be drawn on the axis; e.g., after boxplot(np.random.rand(100), positions=[3]) boxplot(np.random.rand(100), positions=[5]) the xlims will be moved to hide the first boxplot, and even after manually resetting the xlims, the tick at x=3 and its label will be gone too. Fix that to make boxplot a bit more cooperative with previous calls: instead of forcefully setting the axis limits, just update the dataLim and let autoscaling handle axis limits; also check whether the axis already has a FixedLocator/FixedFormatter and if so, just append the new tick locations and labels. Also rename manage_xticks to manage_ticks (with deprecation), as the direction it manages depends on the vert kwarg.
1 parent b9045cd commit f35e980

File tree

5 files changed

+70
-30
lines changed

5 files changed

+70
-30
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
API changes
2+
```````````
3+
4+
The ``manage_xticks`` parameter of `~Axes.boxplot` and `~Axes.bxp` has been
5+
renamed (with a deprecation period) to ``manage_ticks``, to take into account
6+
the fact that it manages either x or y ticks depending on the ``vert``
7+
parameter.
8+
9+
When ``manage_ticks`` is True (the default), these methods now attempt to take
10+
previously drawn boxplots into account when setting the axis limits, ticks, and
11+
tick labels.

lib/matplotlib/axes/_axes.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3429,6 +3429,7 @@ def extract_err(err, data):
34293429

34303430
return errorbar_container # (l0, caplines, barcols)
34313431

3432+
@cbook._rename_parameter("3.1", "manage_xticks", "manage_ticks")
34323433
@_preprocess_data()
34333434
def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
34343435
positions=None, widths=None, patch_artist=None,
@@ -3437,7 +3438,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
34373438
showbox=None, showfliers=None, boxprops=None,
34383439
labels=None, flierprops=None, medianprops=None,
34393440
meanprops=None, capprops=None, whiskerprops=None,
3440-
manage_xticks=True, autorange=False, zorder=None):
3441+
manage_ticks=True, autorange=False, zorder=None):
34413442
"""
34423443
Make a box and whisker plot.
34433444
@@ -3538,8 +3539,9 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
35383539
Labels for each dataset. Length must be compatible with
35393540
dimensions of ``x``.
35403541
3541-
manage_xticks : bool, optional (True)
3542-
If the function should adjust the xlim and xtick locations.
3542+
manage_ticks : bool, optional (True)
3543+
If True, the tick locations and labels will be adjusted to match
3544+
the boxplot positions.
35433545
35443546
autorange : bool, optional (False)
35453547
When `True` and the data are distributed such that the 25th and
@@ -3729,15 +3731,16 @@ def _update_dict(dictionary, rc_name, properties):
37293731
medianprops=medianprops, meanprops=meanprops,
37303732
meanline=meanline, showfliers=showfliers,
37313733
capprops=capprops, whiskerprops=whiskerprops,
3732-
manage_xticks=manage_xticks, zorder=zorder)
3734+
manage_ticks=manage_ticks, zorder=zorder)
37333735
return artists
37343736

3737+
@cbook._rename_parameter("3.1", "manage_xticks", "manage_ticks")
37353738
def bxp(self, bxpstats, positions=None, widths=None, vert=True,
37363739
patch_artist=False, shownotches=False, showmeans=False,
37373740
showcaps=True, showbox=True, showfliers=True,
37383741
boxprops=None, whiskerprops=None, flierprops=None,
37393742
medianprops=None, capprops=None, meanprops=None,
3740-
meanline=False, manage_xticks=True, zorder=None):
3743+
meanline=False, manage_ticks=True, zorder=None):
37413744
"""
37423745
Drawing function for box and whisker plots.
37433746
@@ -3841,11 +3844,12 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
38413844
*meanprops*. Not recommended if *shownotches* is also True.
38423845
Otherwise, means will be shown as points.
38433846
3844-
manage_xticks : bool, default = True
3845-
If the function should adjust the xlim and xtick locations.
3847+
manage_ticks : bool, default = True
3848+
If True, the tick locations and labels will be adjusted to match the
3849+
boxplot positions.
38463850
3847-
zorder : scalar, default = None
3848-
The zorder of the resulting boxplot
3851+
zorder : scalar, default = None
3852+
The zorder of the resulting boxplot.
38493853
38503854
Returns
38513855
-------
@@ -4117,21 +4121,38 @@ def dopatch(xs, ys, **kwargs):
41174121
flier_x, flier_y, **final_flierprops
41184122
))
41194123

4120-
# fix our axes/ticks up a little
4121-
if vert:
4122-
setticks = self.set_xticks
4123-
setlim = self.set_xlim
4124-
setlabels = self.set_xticklabels
4125-
else:
4126-
setticks = self.set_yticks
4127-
setlim = self.set_ylim
4128-
setlabels = self.set_yticklabels
4129-
4130-
if manage_xticks:
4131-
newlimits = min(positions) - 0.5, max(positions) + 0.5
4132-
setlim(newlimits)
4133-
setticks(positions)
4134-
setlabels(datalabels)
4124+
if manage_ticks:
4125+
axis_name = "x" if vert else "y"
4126+
interval = getattr(self.dataLim, f"interval{axis_name}")
4127+
axis = getattr(self, f"{axis_name}axis")
4128+
positions = axis.convert_units(positions)
4129+
# The 0.5 additional padding ensures reasonable-looking boxes
4130+
# even when drawing a single box. We set the sticky edge to
4131+
# prevent margins expansion, in order to match old behavior (back
4132+
# when separate calls to boxplot() would completely reset the axis
4133+
# limits regardless of what was drawn before). The sticky edges
4134+
# are attached to the median lines, as they are always present.
4135+
interval[:] = (min(interval[0], min(positions) - .5),
4136+
max(interval[1], max(positions) + .5))
4137+
for median, position in zip(medians, positions):
4138+
getattr(median.sticky_edges, axis_name).extend(
4139+
[position - .5, position + .5])
4140+
# Modified from Axis.set_ticks and Axis.set_ticklabels.
4141+
locator = axis.get_major_locator()
4142+
if not isinstance(axis.get_major_locator(),
4143+
mticker.FixedLocator):
4144+
locator = mticker.FixedLocator([])
4145+
axis.set_major_locator(locator)
4146+
locator.locs = np.array([*locator.locs, *positions])
4147+
formatter = axis.get_major_formatter()
4148+
if not isinstance(axis.get_major_formatter(),
4149+
mticker.FixedFormatter):
4150+
formatter = mticker.FixedFormatter([])
4151+
axis.set_major_formatter(formatter)
4152+
formatter.seq = [*formatter.seq, *datalabels]
4153+
4154+
self.autoscale_view(
4155+
scalex=self._autoscaleXon, scaley=self._autoscaleYon)
41354156

41364157
return dict(whiskers=whiskers, caps=caps, boxes=boxes,
41374158
medians=medians, fliers=fliers, means=means)

lib/matplotlib/pyplot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,7 +2436,7 @@ def boxplot(
24362436
meanline=None, showmeans=None, showcaps=None, showbox=None,
24372437
showfliers=None, boxprops=None, labels=None, flierprops=None,
24382438
medianprops=None, meanprops=None, capprops=None,
2439-
whiskerprops=None, manage_xticks=True, autorange=False,
2439+
whiskerprops=None, manage_ticks=True, autorange=False,
24402440
zorder=None, *, data=None):
24412441
return gca().boxplot(
24422442
x, notch=notch, sym=sym, vert=vert, whis=whis,
@@ -2447,7 +2447,7 @@ def boxplot(
24472447
showfliers=showfliers, boxprops=boxprops, labels=labels,
24482448
flierprops=flierprops, medianprops=medianprops,
24492449
meanprops=meanprops, capprops=capprops,
2450-
whiskerprops=whiskerprops, manage_xticks=manage_xticks,
2450+
whiskerprops=whiskerprops, manage_ticks=manage_ticks,
24512451
autorange=autorange, zorder=zorder, **({"data": data} if data
24522452
is not None else {}))
24532453

lib/matplotlib/tests/test_axes.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2911,12 +2911,21 @@ def test_manage_xticks():
29112911
np.random.seed(0)
29122912
y1 = np.random.normal(10, 3, 20)
29132913
y2 = np.random.normal(3, 1, 20)
2914-
ax.boxplot([y1, y2], positions=[1, 2],
2915-
manage_xticks=False)
2914+
ax.boxplot([y1, y2], positions=[1, 2], manage_ticks=False)
29162915
new_xlim = ax.get_xlim()
29172916
assert_array_equal(old_xlim, new_xlim)
29182917

29192918

2919+
def test_boxplot_not_single():
2920+
fig, ax = plt.subplots()
2921+
ax.boxplot(np.random.rand(100), positions=[3])
2922+
ax.boxplot(np.random.rand(100), positions=[5])
2923+
fig.canvas.draw()
2924+
assert ax.get_xlim() == (2.5, 5.5)
2925+
assert list(ax.get_xticks()) == [3, 5]
2926+
assert [t.get_text() for t in ax.get_xticklabels()] == ["3", "5"]
2927+
2928+
29202929
def test_tick_space_size_0():
29212930
# allow font size to be zero, which affects ticks when there is
29222931
# no other text in the figure.

lib/matplotlib/ticker.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,7 @@ def __call__(self, x, pos=None):
335335

336336
class FixedFormatter(Formatter):
337337
"""
338-
Return fixed strings for tick labels based only on position, not
339-
value.
338+
Return fixed strings for tick labels based only on position, not value.
340339
"""
341340
def __init__(self, seq):
342341
"""

0 commit comments

Comments
 (0)