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

Skip to content

Commit cd9cc10

Browse files
committed
2 parents 4c398f3 + a8b79bb commit cd9cc10

File tree

17 files changed

+470
-50
lines changed

17 files changed

+470
-50
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Autoscaling in Axes3D
2+
~~~~~~~~~~~~~~~~~~~~~
3+
4+
In Matplotlib 3.2.0, autoscaling was made lazier for 2D Axes, i.e., limits
5+
would only be recomputed when actually rendering the canvas, or when the user
6+
queries the Axes limits. This performance improvement is now extended to
7+
`.Axes3D`. This also fixes some issues with autoscaling being triggered
8+
unexpectedly in Axes3D.
9+
10+
Please see :ref:`the API change for 2D Axes <api-changes-3-2-0-autoscaling>`
11+
for further details.

doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ writer, without performing the availability check on subsequent writers, it is
5454
now possible to iterate over the registry, which will yield the names of the
5555
available classes.
5656

57+
.. _api-changes-3-2-0-autoscaling:
58+
5759
Autoscaling
5860
~~~~~~~~~~~
5961

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
An iterable object with labels can be passed to `.Axes.plot`
2+
------------------------------------------------------------
3+
4+
When plotting multiple datasets by passing 2D data as *y* value to
5+
`~.Axes.plot`, labels for the datasets can be passed as a list, the
6+
length matching the number of columns in *y*.
7+
8+
.. plot::
9+
10+
import matplotlib.pyplot as plt
11+
12+
x = [1, 2, 3]
13+
14+
y = [[1, 2],
15+
[2, 5],
16+
[4, 9]]
17+
18+
plt.plot(x, y, label=['low', 'high'])
19+
plt.legend()

doc/users/next_whats_new/stem3d.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Stem plots in 3D Axes
2+
---------------------
3+
4+
Stem plots are now supported on 3D Axes. Much like 2D stems,
5+
`~.axes3d.Axes3D.stem3D` supports plotting the stems in various orientations:
6+
7+
.. plot::
8+
9+
theta = np.linspace(0, 2*np.pi)
10+
x = np.cos(theta - np.pi/2)
11+
y = np.sin(theta - np.pi/2)
12+
z = theta
13+
directions = ['z', 'x', 'y']
14+
names = [r'$\theta$', r'$\cos\theta$', r'$\sin\theta$']
15+
16+
fig, axs = plt.subplots(1, 3, figsize=(8, 4),
17+
constrained_layout=True,
18+
subplot_kw={'projection': '3d'})
19+
for ax, zdir, name in zip(axs, directions, names):
20+
ax.stem(x, y, z, orientation=zdir)
21+
ax.set_title(name)
22+
fig.suptitle(r'A parametric circle: $(x, y) = (\cos\theta, \sin\theta)$')
23+
24+
See also the :doc:`/gallery/mplot3d/stem3d_demo` demo.

examples/mplot3d/stem3d_demo.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
=======
3+
3D stem
4+
=======
5+
6+
Demonstration of a stem plot in 3D, which plots vertical lines from a baseline
7+
to the *z*-coordinate and places a marker at the tip.
8+
"""
9+
10+
import matplotlib.pyplot as plt
11+
import numpy as np
12+
13+
theta = np.linspace(0, 2*np.pi)
14+
x = np.cos(theta - np.pi/2)
15+
y = np.sin(theta - np.pi/2)
16+
z = theta
17+
18+
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
19+
ax.stem(x, y, z)
20+
21+
plt.show()
22+
23+
#############################################################################
24+
#
25+
# The position of the baseline can be adapted using *bottom*. The parameters
26+
# *linefmt*, *markerfmt*, and *basefmt* control basic format properties of the
27+
# plot. However, in contrast to `~.axes3d.Axes3D.plot` not all properties are
28+
# configurable via keyword arguments. For more advanced control adapt the line
29+
# objects returned by `~.stem3D`.
30+
31+
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
32+
markerline, stemlines, baseline = ax.stem(
33+
x, y, z, linefmt='grey', markerfmt='D', bottom=np.pi)
34+
markerline.set_markerfacecolor('none')
35+
36+
plt.show()
37+
38+
#############################################################################
39+
#
40+
# The orientation of the stems and baseline can be changed using *orientation*.
41+
# This determines in which direction the stems are projected from the head
42+
# points, towards the *bottom* baseline.
43+
#
44+
# For examples, by setting ``orientation='x'``, the stems are projected along
45+
# the *x*-direction, and the baseline is in the *yz*-plane.
46+
47+
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
48+
markerline, stemlines, baseline = ax.stem(x, y, z, bottom=-1, orientation='x')
49+
ax.set(xlabel='x', ylabel='y', zlabel='z')
50+
51+
plt.show()

lib/matplotlib/axes/_axes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,8 @@ def plot(self, *args, scalex=True, scaley=True, data=None, **kwargs):
14991499
15001500
If you make multiple lines with one plot call, the kwargs
15011501
apply to all those lines.
1502+
In case if label object is iterable, each its element is
1503+
used as label for a separate line.
15021504
15031505
Here is a list of available `.Line2D` properties:
15041506

lib/matplotlib/axes/_base.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,12 @@ def __call__(self, *args, data=None, **kwargs):
294294
replaced[label_namer_idx], args[label_namer_idx])
295295
args = replaced
296296

297+
if len(args) >= 4 and not cbook.is_scalar_or_string(
298+
kwargs.get("label")):
299+
raise ValueError("plot() with multiple groups of data (i.e., "
300+
"pairs of x and y) does not support multiple "
301+
"labels")
302+
297303
# Repeatedly grab (x, y) or (x, y, format) from the front of args and
298304
# massage them into arguments to plot() or fill().
299305

@@ -447,8 +453,22 @@ def _plot_args(self, tup, kwargs, return_kwargs=False):
447453
ncx, ncy = x.shape[1], y.shape[1]
448454
if ncx > 1 and ncy > 1 and ncx != ncy:
449455
raise ValueError(f"x has {ncx} columns but y has {ncy} columns")
450-
result = (func(x[:, j % ncx], y[:, j % ncy], kw, kwargs)
451-
for j in range(max(ncx, ncy)))
456+
457+
label = kwargs.get('label')
458+
n_datasets = max(ncx, ncy)
459+
if n_datasets > 1 and not cbook.is_scalar_or_string(label):
460+
if len(label) != n_datasets:
461+
raise ValueError(f"label must be scalar or have the same "
462+
f"length as the input data, but found "
463+
f"{len(label)} for {n_datasets} datasets.")
464+
labels = label
465+
else:
466+
labels = [label] * n_datasets
467+
468+
result = (func(x[:, j % ncx], y[:, j % ncy], kw,
469+
{**kwargs, 'label': label})
470+
for j, label in enumerate(labels))
471+
452472
if return_kwargs:
453473
return list(result)
454474
else:

lib/matplotlib/collections.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,11 +1386,10 @@ def __init__(self, segments, # Can be None.
13861386
zorder : int, default: 2
13871387
zorder of the lines once drawn.
13881388
facecolors : color or list of color, default: 'none'
1389-
The facecolors of the LineCollection.
1390-
Setting to a value other than 'none' will lead to each line being
1391-
"filled in" as if there was an implicit line segment joining the
1392-
last and first points of that line back around to each other. In
1393-
order to manually specify what should count as the "interior" of
1389+
When setting *facecolors*, each line is interpreted as a boundary
1390+
for an area, implicitly closing the path from the last point to the
1391+
first point. The enclosed area is filled with *facecolor*.
1392+
In order to manually specify what should count as the "interior" of
13941393
each line, please use `.PathCollection` instead, where the
13951394
"interior" can be specified by appropriate usage of
13961395
`~.path.Path.CLOSEPOLY`.

lib/matplotlib/tests/test_axes.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6717,34 +6717,24 @@ def test_ytickcolor_is_not_markercolor():
67176717
assert tick.tick1line.get_markeredgecolor() != 'white'
67186718

67196719

6720+
@pytest.mark.parametrize('axis', ('x', 'y'))
67206721
@pytest.mark.parametrize('auto', (True, False, None))
6721-
def test_unautoscaley(auto):
6722-
fig, ax = plt.subplots()
6723-
x = np.arange(100)
6724-
y = np.linspace(-.1, .1, 100)
6725-
ax.scatter(x, y)
6726-
6727-
post_auto = ax.get_autoscaley_on() if auto is None else auto
6728-
6729-
ax.set_ylim((-.5, .5), auto=auto)
6730-
assert post_auto == ax.get_autoscaley_on()
6731-
fig.canvas.draw()
6732-
assert_array_equal(ax.get_ylim(), (-.5, .5))
6733-
6734-
6735-
@pytest.mark.parametrize('auto', (True, False, None))
6736-
def test_unautoscalex(auto):
6722+
def test_unautoscale(axis, auto):
67376723
fig, ax = plt.subplots()
67386724
x = np.arange(100)
67396725
y = np.linspace(-.1, .1, 100)
67406726
ax.scatter(y, x)
67416727

6742-
post_auto = ax.get_autoscalex_on() if auto is None else auto
6728+
get_autoscale_on = getattr(ax, f'get_autoscale{axis}_on')
6729+
set_lim = getattr(ax, f'set_{axis}lim')
6730+
get_lim = getattr(ax, f'get_{axis}lim')
6731+
6732+
post_auto = get_autoscale_on() if auto is None else auto
67436733

6744-
ax.set_xlim((-.5, .5), auto=auto)
6745-
assert post_auto == ax.get_autoscalex_on()
6734+
set_lim((-0.5, 0.5), auto=auto)
6735+
assert post_auto == get_autoscale_on()
67466736
fig.canvas.draw()
6747-
assert_array_equal(ax.get_xlim(), (-.5, .5))
6737+
assert_array_equal(get_lim(), (-0.5, 0.5))
67486738

67496739

67506740
@check_figures_equal(extensions=["png"])

lib/matplotlib/tests/test_backend_qt.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ def qt_core(request):
3434

3535
@pytest.mark.parametrize('backend', [
3636
# Note: the value is irrelevant; the important part is the marker.
37-
pytest.param('Qt4Agg', marks=pytest.mark.backend('Qt4Agg')),
38-
pytest.param('Qt5Agg', marks=pytest.mark.backend('Qt5Agg')),
37+
pytest.param(
38+
'Qt4Agg',
39+
marks=pytest.mark.backend('Qt4Agg', skip_on_importerror=True)),
40+
pytest.param(
41+
'Qt5Agg',
42+
marks=pytest.mark.backend('Qt5Agg', skip_on_importerror=True)),
3943
])
4044
def test_fig_close(backend):
4145
# save the state of Gcf.figs
@@ -53,7 +57,7 @@ def test_fig_close(backend):
5357
assert init_figs == Gcf.figs
5458

5559

56-
@pytest.mark.backend('Qt5Agg')
60+
@pytest.mark.backend('Qt5Agg', skip_on_importerror=True)
5761
def test_fig_signals(qt_core):
5862
# Create a figure
5963
plt.figure()
@@ -130,8 +134,12 @@ def CustomHandler(signum, frame):
130134
)
131135
@pytest.mark.parametrize('backend', [
132136
# Note: the value is irrelevant; the important part is the marker.
133-
pytest.param('Qt4Agg', marks=pytest.mark.backend('Qt4Agg')),
134-
pytest.param('Qt5Agg', marks=pytest.mark.backend('Qt5Agg')),
137+
pytest.param(
138+
'Qt4Agg',
139+
marks=pytest.mark.backend('Qt4Agg', skip_on_importerror=True)),
140+
pytest.param(
141+
'Qt5Agg',
142+
marks=pytest.mark.backend('Qt5Agg', skip_on_importerror=True)),
135143
])
136144
def test_correct_key(backend, qt_core, qt_key, qt_mods, answer):
137145
"""
@@ -157,7 +165,7 @@ def on_key_press(event):
157165
qt_canvas.keyPressEvent(_Event())
158166

159167

160-
@pytest.mark.backend('Qt5Agg')
168+
@pytest.mark.backend('Qt5Agg', skip_on_importerror=True)
161169
def test_pixel_ratio_change():
162170
"""
163171
Make sure that if the pixel ratio changes, the figure dpi changes but the
@@ -229,7 +237,7 @@ def set_pixel_ratio(ratio):
229237
assert (fig.get_size_inches() == (5, 2)).all()
230238

231239

232-
@pytest.mark.backend('Qt5Agg')
240+
@pytest.mark.backend('Qt5Agg', skip_on_importerror=True)
233241
def test_subplottool():
234242
fig, ax = plt.subplots()
235243
with mock.patch(
@@ -238,7 +246,7 @@ def test_subplottool():
238246
fig.canvas.manager.toolbar.configure_subplots()
239247

240248

241-
@pytest.mark.backend('Qt5Agg')
249+
@pytest.mark.backend('Qt5Agg', skip_on_importerror=True)
242250
def test_figureoptions():
243251
fig, ax = plt.subplots()
244252
ax.plot([1, 2])
@@ -250,7 +258,7 @@ def test_figureoptions():
250258
fig.canvas.manager.toolbar.edit_parameters()
251259

252260

253-
@pytest.mark.backend('Qt5Agg')
261+
@pytest.mark.backend('Qt5Agg', skip_on_importerror=True)
254262
def test_double_resize():
255263
# Check that resizing a figure twice keeps the same window size
256264
fig, ax = plt.subplots()
@@ -270,7 +278,7 @@ def test_double_resize():
270278
assert window.height() == old_height
271279

272280

273-
@pytest.mark.backend("Qt5Agg")
281+
@pytest.mark.backend('Qt5Agg', skip_on_importerror=True)
274282
def test_canvas_reinit():
275283
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
276284

lib/matplotlib/tests/test_determinism.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ def test_determinism_check(objects, fmt, usetex):
100100
[sys.executable, "-R", "-c",
101101
f"from matplotlib.tests.test_determinism import _save_figure;"
102102
f"_save_figure({objects!r}, {fmt!r}, {usetex})"],
103-
env={**os.environ, "SOURCE_DATE_EPOCH": "946684800"})
103+
env={**os.environ, "SOURCE_DATE_EPOCH": "946684800",
104+
"MPLBACKEND": "Agg"})
104105
for _ in range(3)
105106
]
106107
for p in plots[1:]:
@@ -139,5 +140,6 @@ def test_determinism_source_date_epoch(fmt, string):
139140
[sys.executable, "-R", "-c",
140141
f"from matplotlib.tests.test_determinism import _save_figure; "
141142
f"_save_figure('', {fmt!r})"],
142-
env={**os.environ, "SOURCE_DATE_EPOCH": "946684800"})
143+
env={**os.environ, "SOURCE_DATE_EPOCH": "946684800",
144+
"MPLBACKEND": "Agg"})
143145
assert string in buf

lib/matplotlib/tests/test_legend.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,3 +671,65 @@ def test_no_warn_big_data_when_loc_specified():
671671
ax.plot(np.arange(5000), label=idx)
672672
legend = ax.legend('best')
673673
fig.draw_artist(legend) # Check that no warning is emitted.
674+
675+
676+
@pytest.mark.parametrize('label_array', [['low', 'high'],
677+
('low', 'high'),
678+
np.array(['low', 'high'])])
679+
def test_plot_multiple_input_multiple_label(label_array):
680+
# test ax.plot() with multidimensional input
681+
# and multiple labels
682+
x = [1, 2, 3]
683+
y = [[1, 2],
684+
[2, 5],
685+
[4, 9]]
686+
687+
fig, ax = plt.subplots()
688+
ax.plot(x, y, label=label_array)
689+
leg = ax.legend()
690+
legend_texts = [entry.get_text() for entry in leg.get_texts()]
691+
assert legend_texts == ['low', 'high']
692+
693+
694+
@pytest.mark.parametrize('label', ['one', 1, int])
695+
def test_plot_multiple_input_single_label(label):
696+
# test ax.plot() with multidimensional input
697+
# and single label
698+
x = [1, 2, 3]
699+
y = [[1, 2],
700+
[2, 5],
701+
[4, 9]]
702+
703+
fig, ax = plt.subplots()
704+
ax.plot(x, y, label=label)
705+
leg = ax.legend()
706+
legend_texts = [entry.get_text() for entry in leg.get_texts()]
707+
assert legend_texts == [str(label)] * 2
708+
709+
710+
@pytest.mark.parametrize('label_array', [['low', 'high'],
711+
('low', 'high'),
712+
np.array(['low', 'high'])])
713+
def test_plot_single_input_multiple_label(label_array):
714+
# test ax.plot() with 1D array like input
715+
# and iterable label
716+
x = [1, 2, 3]
717+
y = [2, 5, 6]
718+
fig, ax = plt.subplots()
719+
ax.plot(x, y, label=label_array)
720+
leg = ax.legend()
721+
assert len(leg.get_texts()) == 1
722+
assert leg.get_texts()[0].get_text() == str(label_array)
723+
724+
725+
def test_plot_multiple_label_incorrect_length_exception():
726+
# check that excepton is raised if multiple labels
727+
# are given, but number of on labels != number of lines
728+
with pytest.raises(ValueError):
729+
x = [1, 2, 3]
730+
y = [[1, 2],
731+
[2, 5],
732+
[4, 9]]
733+
label = ['high', 'low', 'medium']
734+
fig, ax = plt.subplots()
735+
ax.plot(x, y, label=label)

lib/matplotlib/tests/test_matplotlib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,4 @@ def test_importable_with__OO():
5656
"import matplotlib.patches as mpatches"
5757
)
5858
cmd = [sys.executable, "-OO", "-c", program]
59-
assert subprocess.call(cmd) == 0
59+
assert subprocess.call(cmd, env={**os.environ, "MPLBACKEND": ""}) == 0

0 commit comments

Comments
 (0)