Static code analysis and corrections

This commit is contained in:
Kristjan Komlosi
2019-07-17 16:06:09 +02:00
parent 674692c2fc
commit 21bfae9fbc
10086 changed files with 2102103 additions and 51 deletions
@@ -0,0 +1,15 @@
import difflib
import os
from matplotlib import cbook
from matplotlib.testing import setup
# Check that the test directories exist
if not os.path.exists(os.path.join(
os.path.dirname(__file__), 'baseline_images')):
raise IOError(
'The baseline image directory does not exist. '
'This is most likely because the test data is not installed. '
'You may need to install matplotlib from source to get the '
'test data.')
@@ -0,0 +1,4 @@
from matplotlib.testing.conftest import (mpl_test_settings,
mpl_image_comparison_parameters,
pytest_configure, pytest_unconfigure,
pd)
@@ -0,0 +1,80 @@
from io import BytesIO
import matplotlib.afm as afm
AFM_TEST_DATA = b"""StartFontMetrics 2.0
Comment Comments are ignored.
Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
FontName MyFont-Bold
EncodingScheme FontSpecific
FullName My Font Bold
FamilyName Test Fonts
Weight Bold
ItalicAngle 0.0
IsFixedPitch false
UnderlinePosition -100
UnderlineThickness 50
Version 001.000
Notice Copyright (c) 2017 No one.
FontBBox 0 -321 1234 369
StartCharMetrics 3
C 0 ; WX 250 ; N space ; B 0 0 0 0 ;
C 42 ; WX 1141 ; N foo ; B 40 60 800 360 ;
C 99 ; WX 583 ; N bar ; B 40 -10 543 210 ;
EndCharMetrics
EndFontMetrics
"""
def test_nonascii_str():
# This tests that we also decode bytes as utf-8 properly.
# Else, font files with non ascii characters fail to load.
inp_str = "привет"
byte_str = inp_str.encode("utf8")
ret = afm._to_str(byte_str)
assert ret == inp_str
def test_parse_header():
fh = BytesIO(AFM_TEST_DATA)
header = afm._parse_header(fh)
assert header == {
b'StartFontMetrics': 2.0,
b'FontName': 'MyFont-Bold',
b'EncodingScheme': 'FontSpecific',
b'FullName': 'My Font Bold',
b'FamilyName': 'Test Fonts',
b'Weight': 'Bold',
b'ItalicAngle': 0.0,
b'IsFixedPitch': False,
b'UnderlinePosition': -100,
b'UnderlineThickness': 50,
b'Version': '001.000',
b'Notice': 'Copyright (c) 2017 No one.',
b'FontBBox': [0, -321, 1234, 369],
b'StartCharMetrics': 3,
}
def test_parse_char_metrics():
fh = BytesIO(AFM_TEST_DATA)
afm._parse_header(fh) # position
metrics = afm._parse_char_metrics(fh)
assert metrics == (
{0: (250.0, 'space', [0, 0, 0, 0]),
42: (1141.0, 'foo', [40, 60, 800, 360]),
99: (583.0, 'bar', [40, -10, 543, 210]),
},
{'space': (250.0, 'space', [0, 0, 0, 0]),
'foo': (1141.0, 'foo', [40, 60, 800, 360]),
'bar': (583.0, 'bar', [40, -10, 543, 210]),
})
def test_get_familyname_guessed():
fh = BytesIO(AFM_TEST_DATA)
fm = afm.AFM(fh)
del fm._header[b'FamilyName'] # remove FamilyName, so we have to guess
assert fm.get_familyname() == 'My Font'
@@ -0,0 +1,241 @@
import io
import numpy as np
from numpy.testing import assert_array_almost_equal
import pytest
from matplotlib import (
collections, path, pyplot as plt, transforms as mtransforms, rcParams)
from matplotlib.image import imread
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.testing.decorators import image_comparison
def test_repeated_save_with_alpha():
# We want an image which has a background color of bluish green, with an
# alpha of 0.25.
fig = Figure([1, 0.4])
canvas = FigureCanvas(fig)
fig.set_facecolor((0, 1, 0.4))
fig.patch.set_alpha(0.25)
# The target color is fig.patch.get_facecolor()
buf = io.BytesIO()
fig.savefig(buf,
facecolor=fig.get_facecolor(),
edgecolor='none')
# Save the figure again to check that the
# colors don't bleed from the previous renderer.
buf.seek(0)
fig.savefig(buf,
facecolor=fig.get_facecolor(),
edgecolor='none')
# Check the first pixel has the desired color & alpha
# (approx: 0, 1.0, 0.4, 0.25)
buf.seek(0)
assert_array_almost_equal(tuple(imread(buf)[0, 0]),
(0.0, 1.0, 0.4, 0.250),
decimal=3)
def test_large_single_path_collection():
buff = io.BytesIO()
# Generates a too-large single path in a path collection that
# would cause a segfault if the draw_markers optimization is
# applied.
f, ax = plt.subplots()
collection = collections.PathCollection(
[path.Path([[-10, 5], [10, 5], [10, -5], [-10, -5], [-10, 5]])])
ax.add_artist(collection)
ax.set_xlim(10**-3, 1)
plt.savefig(buff)
def test_marker_with_nan():
# This creates a marker with nans in it, which was segfaulting the
# Agg backend (see #3722)
fig, ax = plt.subplots(1)
steps = 1000
data = np.arange(steps)
ax.semilogx(data)
ax.fill_between(data, data*0.8, data*1.2)
buf = io.BytesIO()
fig.savefig(buf, format='png')
def test_long_path():
buff = io.BytesIO()
fig, ax = plt.subplots()
np.random.seed(0)
points = np.random.rand(70000)
ax.plot(points)
fig.savefig(buff, format='png')
@image_comparison(baseline_images=['agg_filter'],
extensions=['png'], remove_text=True)
def test_agg_filter():
def smooth1d(x, window_len):
s = np.r_[2*x[0] - x[window_len:1:-1],
x,
2*x[-1] - x[-1:-window_len:-1]]
w = np.hanning(window_len)
y = np.convolve(w/w.sum(), s, mode='same')
return y[window_len-1:-window_len+1]
def smooth2d(A, sigma=3):
window_len = max(int(sigma), 3)*2 + 1
A1 = np.array([smooth1d(x, window_len) for x in np.asarray(A)])
A2 = np.transpose(A1)
A3 = np.array([smooth1d(x, window_len) for x in A2])
A4 = np.transpose(A3)
return A4
class BaseFilter(object):
def prepare_image(self, src_image, dpi, pad):
ny, nx, depth = src_image.shape
padded_src = np.zeros([pad*2 + ny, pad*2 + nx, depth], dtype="d")
padded_src[pad:-pad, pad:-pad, :] = src_image[:, :, :]
return padded_src # , tgt_image
def get_pad(self, dpi):
return 0
def __call__(self, im, dpi):
pad = self.get_pad(dpi)
padded_src = self.prepare_image(im, dpi, pad)
tgt_image = self.process_image(padded_src, dpi)
return tgt_image, -pad, -pad
class OffsetFilter(BaseFilter):
def __init__(self, offsets=None):
if offsets is None:
self.offsets = (0, 0)
else:
self.offsets = offsets
def get_pad(self, dpi):
return int(max(*self.offsets)/72.*dpi)
def process_image(self, padded_src, dpi):
ox, oy = self.offsets
a1 = np.roll(padded_src, int(ox/72.*dpi), axis=1)
a2 = np.roll(a1, -int(oy/72.*dpi), axis=0)
return a2
class GaussianFilter(BaseFilter):
"simple gauss filter"
def __init__(self, sigma, alpha=0.5, color=None):
self.sigma = sigma
self.alpha = alpha
if color is None:
self.color = (0, 0, 0)
else:
self.color = color
def get_pad(self, dpi):
return int(self.sigma*3/72.*dpi)
def process_image(self, padded_src, dpi):
tgt_image = np.zeros_like(padded_src)
aa = smooth2d(padded_src[:, :, -1]*self.alpha,
self.sigma/72.*dpi)
tgt_image[:, :, -1] = aa
tgt_image[:, :, :-1] = self.color
return tgt_image
class DropShadowFilter(BaseFilter):
def __init__(self, sigma, alpha=0.3, color=None, offsets=None):
self.gauss_filter = GaussianFilter(sigma, alpha, color)
self.offset_filter = OffsetFilter(offsets)
def get_pad(self, dpi):
return max(self.gauss_filter.get_pad(dpi),
self.offset_filter.get_pad(dpi))
def process_image(self, padded_src, dpi):
t1 = self.gauss_filter.process_image(padded_src, dpi)
t2 = self.offset_filter.process_image(t1, dpi)
return t2
fig = plt.figure()
ax = fig.add_subplot(111)
# draw lines
l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-",
mec="b", mfc="w", lw=5, mew=3, ms=10, label="Line 1")
l2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "ro-",
mec="r", mfc="w", lw=5, mew=3, ms=10, label="Line 1")
gauss = DropShadowFilter(4)
for l in [l1, l2]:
# draw shadows with same lines with slight offset.
xx = l.get_xdata()
yy = l.get_ydata()
shadow, = ax.plot(xx, yy)
shadow.update_from(l)
# offset transform
ot = mtransforms.offset_copy(l.get_transform(), ax.figure,
x=4.0, y=-6.0, units='points')
shadow.set_transform(ot)
# adjust zorder of the shadow lines so that it is drawn below the
# original lines
shadow.set_zorder(l.get_zorder() - 0.5)
shadow.set_agg_filter(gauss)
shadow.set_rasterized(True) # to support mixed-mode renderers
ax.set_xlim(0., 1.)
ax.set_ylim(0., 1.)
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
def test_too_large_image():
fig = plt.figure(figsize=(300, 1000))
buff = io.BytesIO()
with pytest.raises(ValueError):
fig.savefig(buff)
def test_chunksize():
x = range(200)
# Test without chunksize
fig, ax = plt.subplots()
ax.plot(x, np.sin(x))
fig.canvas.draw()
# Test with chunksize
fig, ax = plt.subplots()
rcParams['agg.path.chunksize'] = 105
ax.plot(x, np.sin(x))
fig.canvas.draw()
@pytest.mark.backend('Agg')
def test_jpeg_dpi():
Image = pytest.importorskip("PIL.Image")
# Check that dpi is set correctly in jpg files.
plt.plot([0, 1, 2], [0, 1, 0])
buf = io.BytesIO()
plt.savefig(buf, format="jpg", dpi=200)
im = Image.open(buf)
assert im.info['dpi'] == (200, 200)
@@ -0,0 +1,263 @@
import os
from pathlib import Path
import sys
import numpy as np
import pytest
import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib import animation
class NullMovieWriter(animation.AbstractMovieWriter):
"""
A minimal MovieWriter. It doesn't actually write anything.
It just saves the arguments that were given to the setup() and
grab_frame() methods as attributes, and counts how many times
grab_frame() is called.
This class doesn't have an __init__ method with the appropriate
signature, and it doesn't define an isAvailable() method, so
it cannot be added to the 'writers' registry.
"""
def setup(self, fig, outfile, dpi, *args):
self.fig = fig
self.outfile = outfile
self.dpi = dpi
self.args = args
self._count = 0
def grab_frame(self, **savefig_kwargs):
self.savefig_kwargs = savefig_kwargs
self._count += 1
def finish(self):
pass
def make_animation(**kwargs):
fig, ax = plt.subplots()
line, = ax.plot([])
def init():
pass
def animate(i):
line.set_data([0, 1], [0, i])
return line,
return animation.FuncAnimation(fig, animate, **kwargs)
def test_null_movie_writer():
# Test running an animation with NullMovieWriter.
num_frames = 5
anim = make_animation(frames=num_frames)
filename = "unused.null"
dpi = 50
savefig_kwargs = dict(foo=0)
writer = NullMovieWriter()
anim.save(filename, dpi=dpi, writer=writer,
savefig_kwargs=savefig_kwargs)
assert writer.fig == plt.figure(1) # The figure used by make_animation.
assert writer.outfile == filename
assert writer.dpi == dpi
assert writer.args == ()
assert writer.savefig_kwargs == savefig_kwargs
assert writer._count == num_frames
def test_movie_writer_dpi_default():
# Test setting up movie writer with figure.dpi default.
fig = plt.figure()
filename = "unused.null"
fps = 5
codec = "unused"
bitrate = 1
extra_args = ["unused"]
def run():
pass
writer = animation.MovieWriter(fps, codec, bitrate, extra_args)
writer._run = run
writer.setup(fig, filename)
assert writer.dpi == fig.dpi
@animation.writers.register('null')
class RegisteredNullMovieWriter(NullMovieWriter):
# To be able to add NullMovieWriter to the 'writers' registry,
# we must define an __init__ method with a specific signature,
# and we must define the class method isAvailable().
# (These methods are not actually required to use an instance
# of this class as the 'writer' argument of Animation.save().)
def __init__(self, fps=None, codec=None, bitrate=None,
extra_args=None, metadata=None):
pass
@classmethod
def isAvailable(cls):
return True
WRITER_OUTPUT = [
('ffmpeg', 'movie.mp4'),
('ffmpeg_file', 'movie.mp4'),
('avconv', 'movie.mp4'),
('avconv_file', 'movie.mp4'),
('imagemagick', 'movie.gif'),
('imagemagick_file', 'movie.gif'),
('pillow', 'movie.gif'),
('html', 'movie.html'),
('null', 'movie.null')
]
if sys.version_info >= (3, 6):
from pathlib import Path
WRITER_OUTPUT += [
(writer, Path(output)) for writer, output in WRITER_OUTPUT]
# Smoke test for saving animations. In the future, we should probably
# design more sophisticated tests which compare resulting frames a-la
# matplotlib.testing.image_comparison
@pytest.mark.parametrize('writer, output', WRITER_OUTPUT)
def test_save_animation_smoketest(tmpdir, writer, output):
if writer == 'pillow':
pytest.importorskip("PIL")
try:
# for ImageMagick the rcparams must be patched to account for
# 'convert' being a built in MS tool, not the imagemagick
# tool.
writer._init_from_registry()
except AttributeError:
pass
if not animation.writers.is_available(writer):
pytest.skip("writer '%s' not available on this system" % writer)
fig, ax = plt.subplots()
line, = ax.plot([], [])
ax.set_xlim(0, 10)
ax.set_ylim(-1, 1)
dpi = None
codec = None
if writer == 'ffmpeg':
# Issue #8253
fig.set_size_inches((10.85, 9.21))
dpi = 100.
codec = 'h264'
def init():
line.set_data([], [])
return line,
def animate(i):
x = np.linspace(0, 10, 100)
y = np.sin(x + i)
line.set_data(x, y)
return line,
# Use temporary directory for the file-based writers, which produce a file
# per frame with known names.
with tmpdir.as_cwd():
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=5)
try:
anim.save(output, fps=30, writer=writer, bitrate=500, dpi=dpi,
codec=codec)
except UnicodeDecodeError:
pytest.xfail("There can be errors in the numpy import stack, "
"see issues #1891 and #2679")
def test_no_length_frames():
(make_animation(frames=iter(range(5)))
.save('unused.null', writer=NullMovieWriter()))
def test_movie_writer_registry():
ffmpeg_path = mpl.rcParams['animation.ffmpeg_path']
# Not sure about the first state as there could be some writer
# which set rcparams
# assert not animation.writers._dirty
assert len(animation.writers._registered) > 0
animation.writers.list() # resets dirty state
assert not animation.writers._dirty
mpl.rcParams['animation.ffmpeg_path'] = "not_available_ever_xxxx"
assert animation.writers._dirty
animation.writers.list() # resets
assert not animation.writers._dirty
assert not animation.writers.is_available("ffmpeg")
# something which is guaranteed to be available in path
# and exits immediately
bin = "true" if sys.platform != 'win32' else "where"
mpl.rcParams['animation.ffmpeg_path'] = bin
assert animation.writers._dirty
animation.writers.list() # resets
assert not animation.writers._dirty
assert animation.writers.is_available("ffmpeg")
mpl.rcParams['animation.ffmpeg_path'] = ffmpeg_path
@pytest.mark.skipif(
not animation.writers.is_available(mpl.rcParams["animation.writer"]),
reason="animation writer not installed")
@pytest.mark.parametrize("method_name", ["to_html5_video", "to_jshtml"])
def test_embed_limit(method_name, caplog, tmpdir):
with tmpdir.as_cwd():
with mpl.rc_context({"animation.embed_limit": 1e-6}): # ~1 byte.
getattr(make_animation(frames=1), method_name)()
assert len(caplog.records) == 1
record, = caplog.records
assert (record.name == "matplotlib.animation"
and record.levelname == "WARNING")
@pytest.mark.skipif(
not animation.writers.is_available(mpl.rcParams["animation.writer"]),
reason="animation writer not installed")
@pytest.mark.parametrize(
"method_name",
["to_html5_video",
pytest.param("to_jshtml",
marks=pytest.mark.xfail)])
def test_cleanup_temporaries(method_name, tmpdir):
with tmpdir.as_cwd():
getattr(make_animation(frames=1), method_name)()
assert list(Path(str(tmpdir)).iterdir()) == []
# Currently, this fails with a ValueError after we try to communicate() twice
# with the Popen.
@pytest.mark.xfail
@pytest.mark.skipif(os.name != "posix", reason="requires a POSIX OS")
def test_failing_ffmpeg(tmpdir, monkeypatch):
"""
Test that we correctly raise an OSError when ffmpeg fails.
To do so, mock ffmpeg using a simple executable shell script that
succeeds when called with no arguments (so that it gets registered by
`isAvailable`), but fails otherwise, and add it to the $PATH.
"""
try:
with tmpdir.as_cwd():
monkeypatch.setenv("PATH", ".:" + os.environ["PATH"])
exe_path = Path(tmpdir, "ffmpeg")
exe_path.write_text("#!/bin/sh\n"
"[[ $@ -eq 0 ]]\n")
os.chmod(str(exe_path), 0o755)
animation.writers.reset_available_writers()
with pytest.raises(OSError):
make_animation().save("test.mpeg")
finally:
animation.writers.reset_available_writers()
@@ -0,0 +1,170 @@
import pytest
import platform
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
import matplotlib.patches as mpatches
def draw_arrow(ax, t, r):
ax.annotate('', xy=(0.5, 0.5 + r), xytext=(0.5, 0.5), size=30,
arrowprops=dict(arrowstyle=t,
fc="b", ec='k'))
@image_comparison(baseline_images=['fancyarrow_test_image'])
def test_fancyarrow():
# Added 0 to test division by zero error described in issue 3930
r = [0.4, 0.3, 0.2, 0.1, 0]
t = ["fancy", "simple", mpatches.ArrowStyle.Fancy()]
fig, axes = plt.subplots(len(t), len(r), squeeze=False,
subplot_kw=dict(aspect=True),
figsize=(8, 4.5))
for i_r, r1 in enumerate(r):
for i_t, t1 in enumerate(t):
ax = axes[i_t, i_r]
draw_arrow(ax, t1, r1)
ax.tick_params(labelleft=False, labelbottom=False)
@image_comparison(baseline_images=['boxarrow_test_image'], extensions=['png'])
def test_boxarrow():
styles = mpatches.BoxStyle.get_styles()
n = len(styles)
spacing = 1.2
figheight = (n * spacing + .5)
fig1 = plt.figure(1, figsize=(4 / 1.5, figheight / 1.5))
fontsize = 0.3 * 72
for i, stylename in enumerate(sorted(styles)):
fig1.text(0.5, ((n - i) * spacing - 0.5)/figheight, stylename,
ha="center",
size=fontsize,
transform=fig1.transFigure,
bbox=dict(boxstyle=stylename, fc="w", ec="k"))
def __prepare_fancyarrow_dpi_cor_test():
"""
Convenience function that prepares and returns a FancyArrowPatch. It aims
at being used to test that the size of the arrow head does not depend on
the DPI value of the exported picture.
NB: this function *is not* a test in itself!
"""
fig2 = plt.figure("fancyarrow_dpi_cor_test", figsize=(4, 3), dpi=50)
ax = fig2.add_subplot(111)
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ax.add_patch(mpatches.FancyArrowPatch(posA=(0.3, 0.4), posB=(0.8, 0.6),
lw=3, arrowstyle='->',
mutation_scale=100))
return fig2
@image_comparison(baseline_images=['fancyarrow_dpi_cor_100dpi'],
remove_text=True, extensions=['png'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
savefig_kwarg=dict(dpi=100))
def test_fancyarrow_dpi_cor_100dpi():
"""
Check the export of a FancyArrowPatch @ 100 DPI. FancyArrowPatch is
instantiated through a dedicated function because another similar test
checks a similar export but with a different DPI value.
Remark: test only a rasterized format.
"""
__prepare_fancyarrow_dpi_cor_test()
@image_comparison(baseline_images=['fancyarrow_dpi_cor_200dpi'],
remove_text=True, extensions=['png'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
savefig_kwarg=dict(dpi=200))
def test_fancyarrow_dpi_cor_200dpi():
"""
As test_fancyarrow_dpi_cor_100dpi, but exports @ 200 DPI. The relative size
of the arrow head should be the same.
"""
__prepare_fancyarrow_dpi_cor_test()
@image_comparison(baseline_images=['fancyarrow_dash'],
remove_text=True, extensions=['png'],
style='default')
def test_fancyarrow_dash():
from matplotlib.patches import FancyArrowPatch
fig, ax = plt.subplots()
e = FancyArrowPatch((0, 0), (0.5, 0.5),
arrowstyle='-|>',
connectionstyle='angle3,angleA=0,angleB=90',
mutation_scale=10.0,
linewidth=2,
linestyle='dashed',
color='k')
e2 = FancyArrowPatch((0, 0), (0.5, 0.5),
arrowstyle='-|>',
connectionstyle='angle3',
mutation_scale=10.0,
linewidth=2,
linestyle='dotted',
color='k')
ax.add_patch(e)
ax.add_patch(e2)
@image_comparison(baseline_images=['arrow_styles'], extensions=['png'],
style='mpl20', remove_text=True)
def test_arrow_styles():
styles = mpatches.ArrowStyle.get_styles()
n = len(styles)
fig, ax = plt.subplots(figsize=(6, 10))
ax.set_xlim(0, 1)
ax.set_ylim(-1, n)
for i, stylename in enumerate(sorted(styles)):
patch = mpatches.FancyArrowPatch((0.1, i), (0.8, i),
arrowstyle=stylename,
mutation_scale=25)
ax.add_patch(patch)
@image_comparison(baseline_images=['connection_styles'], extensions=['png'],
style='mpl20', remove_text=True)
def test_connection_styles():
styles = mpatches.ConnectionStyle.get_styles()
n = len(styles)
fig, ax = plt.subplots(figsize=(6, 10))
ax.set_xlim(0, 1)
ax.set_ylim(-1, n)
for i, stylename in enumerate(sorted(styles)):
patch = mpatches.FancyArrowPatch((0.1, i), (0.8, i + 0.5),
arrowstyle="->",
connectionstyle=stylename,
mutation_scale=25)
ax.add_patch(patch)
def test_invalid_intersection():
conn_style_1 = mpatches.ConnectionStyle.Angle3(angleA=20, angleB=200)
p1 = mpatches.FancyArrowPatch((.2, .2), (.5, .5),
connectionstyle=conn_style_1)
with pytest.raises(ValueError):
plt.gca().add_patch(p1)
conn_style_2 = mpatches.ConnectionStyle.Angle3(angleA=20, angleB=199.9)
p2 = mpatches.FancyArrowPatch((.2, .2), (.5, .5),
connectionstyle=conn_style_2)
plt.gca().add_patch(p2)
@@ -0,0 +1,278 @@
import io
from itertools import chain
import warnings
import numpy as np
import pytest
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.lines as mlines
import matplotlib.path as mpath
import matplotlib.transforms as mtransforms
import matplotlib.collections as mcollections
import matplotlib.artist as martist
from matplotlib.testing.decorators import image_comparison
def test_patch_transform_of_none():
# tests the behaviour of patches added to an Axes with various transform
# specifications
ax = plt.axes()
ax.set_xlim([1, 3])
ax.set_ylim([1, 3])
# Draw an ellipse over data coord (2,2) by specifying device coords.
xy_data = (2, 2)
xy_pix = ax.transData.transform_point(xy_data)
# Not providing a transform of None puts the ellipse in data coordinates .
e = mpatches.Ellipse(xy_data, width=1, height=1, fc='yellow', alpha=0.5)
ax.add_patch(e)
assert e._transform == ax.transData
# Providing a transform of None puts the ellipse in device coordinates.
e = mpatches.Ellipse(xy_pix, width=120, height=120, fc='coral',
transform=None, alpha=0.5)
assert e.is_transform_set() is True
ax.add_patch(e)
assert isinstance(e._transform, mtransforms.IdentityTransform)
# Providing an IdentityTransform puts the ellipse in device coordinates.
e = mpatches.Ellipse(xy_pix, width=100, height=100,
transform=mtransforms.IdentityTransform(), alpha=0.5)
ax.add_patch(e)
assert isinstance(e._transform, mtransforms.IdentityTransform)
# Not providing a transform, and then subsequently "get_transform" should
# not mean that "is_transform_set".
e = mpatches.Ellipse(xy_pix, width=120, height=120, fc='coral',
alpha=0.5)
intermediate_transform = e.get_transform()
assert e.is_transform_set() is False
ax.add_patch(e)
assert e.get_transform() != intermediate_transform
assert e.is_transform_set() is True
assert e._transform == ax.transData
def test_collection_transform_of_none():
# tests the behaviour of collections added to an Axes with various
# transform specifications
ax = plt.axes()
ax.set_xlim([1, 3])
ax.set_ylim([1, 3])
# draw an ellipse over data coord (2,2) by specifying device coords
xy_data = (2, 2)
xy_pix = ax.transData.transform_point(xy_data)
# not providing a transform of None puts the ellipse in data coordinates
e = mpatches.Ellipse(xy_data, width=1, height=1)
c = mcollections.PatchCollection([e], facecolor='yellow', alpha=0.5)
ax.add_collection(c)
# the collection should be in data coordinates
assert c.get_offset_transform() + c.get_transform() == ax.transData
# providing a transform of None puts the ellipse in device coordinates
e = mpatches.Ellipse(xy_pix, width=120, height=120)
c = mcollections.PatchCollection([e], facecolor='coral',
alpha=0.5)
c.set_transform(None)
ax.add_collection(c)
assert isinstance(c.get_transform(), mtransforms.IdentityTransform)
# providing an IdentityTransform puts the ellipse in device coordinates
e = mpatches.Ellipse(xy_pix, width=100, height=100)
c = mcollections.PatchCollection([e],
transform=mtransforms.IdentityTransform(),
alpha=0.5)
ax.add_collection(c)
assert isinstance(c._transOffset, mtransforms.IdentityTransform)
@image_comparison(baseline_images=["clip_path_clipping"], remove_text=True)
def test_clipping():
exterior = mpath.Path.unit_rectangle().deepcopy()
exterior.vertices *= 4
exterior.vertices -= 2
interior = mpath.Path.unit_circle().deepcopy()
interior.vertices = interior.vertices[::-1]
clip_path = mpath.Path(vertices=np.concatenate([exterior.vertices,
interior.vertices]),
codes=np.concatenate([exterior.codes,
interior.codes]))
star = mpath.Path.unit_regular_star(6).deepcopy()
star.vertices *= 2.6
ax1 = plt.subplot(121)
col = mcollections.PathCollection([star], lw=5, edgecolor='blue',
facecolor='red', alpha=0.7, hatch='*')
col.set_clip_path(clip_path, ax1.transData)
ax1.add_collection(col)
ax2 = plt.subplot(122, sharex=ax1, sharey=ax1)
patch = mpatches.PathPatch(star, lw=5, edgecolor='blue', facecolor='red',
alpha=0.7, hatch='*')
patch.set_clip_path(clip_path, ax2.transData)
ax2.add_patch(patch)
ax1.set_xlim([-3, 3])
ax1.set_ylim([-3, 3])
def test_cull_markers():
x = np.random.random(20000)
y = np.random.random(20000)
fig, ax = plt.subplots()
ax.plot(x, y, 'k.')
ax.set_xlim(2, 3)
pdf = io.BytesIO()
fig.savefig(pdf, format="pdf")
assert len(pdf.getvalue()) < 8000
svg = io.BytesIO()
fig.savefig(svg, format="svg")
assert len(svg.getvalue()) < 20000
@image_comparison(baseline_images=['hatching'], remove_text=True,
style='default')
def test_hatching():
fig, ax = plt.subplots(1, 1)
# Default hatch color.
rect1 = mpatches.Rectangle((0, 0), 3, 4, hatch='/')
ax.add_patch(rect1)
rect2 = mcollections.RegularPolyCollection(4, sizes=[16000],
offsets=[(1.5, 6.5)],
transOffset=ax.transData,
hatch='/')
ax.add_collection(rect2)
# Ensure edge color is not applied to hatching.
rect3 = mpatches.Rectangle((4, 0), 3, 4, hatch='/', edgecolor='C1')
ax.add_patch(rect3)
rect4 = mcollections.RegularPolyCollection(4, sizes=[16000],
offsets=[(5.5, 6.5)],
transOffset=ax.transData,
hatch='/', edgecolor='C1')
ax.add_collection(rect4)
ax.set_xlim(0, 7)
ax.set_ylim(0, 9)
def test_remove():
fig, ax = plt.subplots()
im = ax.imshow(np.arange(36).reshape(6, 6))
ln, = ax.plot(range(5))
assert fig.stale
assert ax.stale
fig.canvas.draw()
assert not fig.stale
assert not ax.stale
assert not ln.stale
assert im in ax._mouseover_set
assert ln not in ax._mouseover_set
assert im.axes is ax
im.remove()
ln.remove()
for art in [im, ln]:
assert art.axes is None
assert art.figure is None
assert im not in ax._mouseover_set
assert fig.stale
assert ax.stale
@image_comparison(baseline_images=["default_edges"], remove_text=True,
extensions=['png'], style='default')
def test_default_edges():
fig, [[ax1, ax2], [ax3, ax4]] = plt.subplots(2, 2)
ax1.plot(np.arange(10), np.arange(10), 'x',
np.arange(10) + 1, np.arange(10), 'o')
ax2.bar(np.arange(10), np.arange(10), align='edge')
ax3.text(0, 0, "BOX", size=24, bbox=dict(boxstyle='sawtooth'))
ax3.set_xlim((-1, 1))
ax3.set_ylim((-1, 1))
pp1 = mpatches.PathPatch(
mpath.Path([(0, 0), (1, 0), (1, 1), (0, 0)],
[mpath.Path.MOVETO, mpath.Path.CURVE3,
mpath.Path.CURVE3, mpath.Path.CLOSEPOLY]),
fc="none", transform=ax4.transData)
ax4.add_patch(pp1)
def test_properties():
ln = mlines.Line2D([], [])
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
ln.properties()
assert len(w) == 0
def test_setp():
# Check empty list
plt.setp([])
plt.setp([[]])
# Check arbitrary iterables
fig, axes = plt.subplots()
lines1 = axes.plot(range(3))
lines2 = axes.plot(range(3))
martist.setp(chain(lines1, lines2), 'lw', 5)
plt.setp(axes.spines.values(), color='green')
# Check `file` argument
sio = io.StringIO()
plt.setp(lines1, 'zorder', file=sio)
assert sio.getvalue() == ' zorder: float\n'
def test_None_zorder():
fig, ax = plt.subplots()
ln, = ax.plot(range(5), zorder=None)
assert ln.get_zorder() == mlines.Line2D.zorder
ln.set_zorder(123456)
assert ln.get_zorder() == 123456
ln.set_zorder(None)
assert ln.get_zorder() == mlines.Line2D.zorder
@pytest.mark.parametrize('accept_clause, expected', [
('', 'unknown'),
("ACCEPTS: [ '-' | '--' | '-.' ]", "[ '-' | '--' | '-.' ] "),
('ACCEPTS: Some description.', 'Some description. '),
('.. ACCEPTS: Some description.', 'Some description. '),
('arg : int', 'int'),
('arg : int\nACCEPTS: Something else.', 'Something else. '),
])
def test_artist_inspector_get_valid_values(accept_clause, expected):
class TestArtist(martist.Artist):
def set_f(self, arg):
pass
TestArtist.set_f.__doc__ = """
Some text.
%s
""" % accept_clause
valid_values = martist.ArtistInspector(TestArtist).get_valid_values('f')
assert valid_values == expected
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,95 @@
from matplotlib.backend_bases import (
FigureCanvasBase, LocationEvent, RendererBase)
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
import matplotlib.path as path
import os
import numpy as np
import pytest
def test_uses_per_path():
id = transforms.Affine2D()
paths = [path.Path.unit_regular_polygon(i) for i in range(3, 7)]
tforms = [id.rotate(i) for i in range(1, 5)]
offsets = np.arange(20).reshape((10, 2))
facecolors = ['red', 'green']
edgecolors = ['red', 'green']
def check(master_transform, paths, all_transforms,
offsets, facecolors, edgecolors):
rb = RendererBase()
raw_paths = list(rb._iter_collection_raw_paths(
master_transform, paths, all_transforms))
gc = rb.new_gc()
ids = [path_id for xo, yo, path_id, gc0, rgbFace in
rb._iter_collection(gc, master_transform, all_transforms,
range(len(raw_paths)), offsets,
transforms.IdentityTransform(),
facecolors, edgecolors, [], [], [False],
[], 'data')]
uses = rb._iter_collection_uses_per_path(
paths, all_transforms, offsets, facecolors, edgecolors)
if raw_paths:
seen = np.bincount(ids, minlength=len(raw_paths))
assert set(seen).issubset([uses - 1, uses])
check(id, paths, tforms, offsets, facecolors, edgecolors)
check(id, paths[0:1], tforms, offsets, facecolors, edgecolors)
check(id, [], tforms, offsets, facecolors, edgecolors)
check(id, paths, tforms[0:1], offsets, facecolors, edgecolors)
check(id, paths, [], offsets, facecolors, edgecolors)
for n in range(0, offsets.shape[0]):
check(id, paths, tforms, offsets[0:n, :], facecolors, edgecolors)
check(id, paths, tforms, offsets, [], edgecolors)
check(id, paths, tforms, offsets, facecolors, [])
check(id, paths, tforms, offsets, [], [])
check(id, paths, tforms, offsets, facecolors[0:1], edgecolors)
def test_get_default_filename(tmpdir):
plt.rcParams['savefig.directory'] = str(tmpdir)
fig = plt.figure()
canvas = FigureCanvasBase(fig)
filename = canvas.get_default_filename()
assert filename == 'image.png'
@pytest.mark.backend('pdf')
def test_non_gui_warning(monkeypatch):
plt.subplots()
monkeypatch.setitem(os.environ, "DISPLAY", ":999")
with pytest.warns(UserWarning) as rec:
plt.show()
assert len(rec) == 1
assert ('Matplotlib is currently using pdf, which is a non-GUI backend'
in str(rec[0].message))
with pytest.warns(UserWarning) as rec:
plt.gcf().show()
assert len(rec) == 1
assert ('Matplotlib is currently using pdf, which is a non-GUI backend'
in str(rec[0].message))
def test_location_event_position():
# LocationEvent should cast its x and y arguments
# to int unless it is None
fig = plt.figure()
canvas = FigureCanvasBase(fig)
test_positions = [(42, 24), (None, 42), (None, None),
(200, 100.01), (205.75, 2.0)]
for x, y in test_positions:
event = LocationEvent("test_event", canvas, x, y)
if x is None:
assert event.x is None
else:
assert event.x == int(x)
assert isinstance(event.x, int)
if y is None:
assert event.y is None
else:
assert event.y == int(y)
assert isinstance(event.y, int)
@@ -0,0 +1,35 @@
from pathlib import Path
import subprocess
import tempfile
import pytest
nbformat = pytest.importorskip('nbformat')
# From https://blog.thedataincubator.com/2016/06/testing-jupyter-notebooks/
def _notebook_run(nb_file):
"""Execute a notebook via nbconvert and collect output.
:returns (parsed nb object, execution errors)
"""
with tempfile.NamedTemporaryFile(suffix=".ipynb",
mode='w+t') as fout:
args = ["jupyter", "nbconvert", "--to", "notebook", "--execute",
"--ExecutePreprocessor.timeout=500",
"--output", fout.name, nb_file]
subprocess.check_call(args)
fout.seek(0)
nb = nbformat.read(fout, nbformat.current_nbformat)
errors = [output for cell in nb.cells if "outputs" in cell
for output in cell["outputs"]
if output.output_type == "error"]
return nb, errors
def test_ipynb():
nb, errors = _notebook_run(
str(Path(__file__).parent / 'test_nbagg_01.ipynb'))
assert errors == []
@@ -0,0 +1,241 @@
import io
import os
import sys
import tempfile
import numpy as np
import pytest
from matplotlib import dviread, pyplot as plt, checkdep_usetex, rcParams
from matplotlib.backends.backend_pdf import PdfPages
from matplotlib.testing.compare import compare_images
from matplotlib.testing.decorators import image_comparison
from matplotlib.testing.determinism import (_determinism_source_date_epoch,
_determinism_check)
needs_usetex = pytest.mark.skipif(
not checkdep_usetex(True),
reason="This test needs a TeX installation")
@image_comparison(baseline_images=['pdf_use14corefonts'],
extensions=['pdf'])
def test_use14corefonts():
rcParams['pdf.use14corefonts'] = True
rcParams['font.family'] = 'sans-serif'
rcParams['font.size'] = 8
rcParams['font.sans-serif'] = ['Helvetica']
rcParams['pdf.compression'] = 0
text = '''A three-line text positioned just above a blue line
and containing some French characters and the euro symbol:
"Merci pépé pour les 10 €"'''
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_title('Test PDF backend with option use14corefonts=True')
ax.text(0.5, 0.5, text, horizontalalignment='center',
verticalalignment='bottom',
fontsize=14)
ax.axhline(0.5, linewidth=0.5)
def test_type42():
rcParams['pdf.fonttype'] = 42
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1, 2, 3])
fig.savefig(io.BytesIO())
def test_multipage_pagecount():
with PdfPages(io.BytesIO()) as pdf:
assert pdf.get_pagecount() == 0
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1, 2, 3])
fig.savefig(pdf, format="pdf")
assert pdf.get_pagecount() == 1
pdf.savefig()
assert pdf.get_pagecount() == 2
def test_multipage_properfinalize():
pdfio = io.BytesIO()
with PdfPages(pdfio) as pdf:
for i in range(10):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('This is a long title')
fig.savefig(pdf, format="pdf")
pdfio.seek(0)
assert sum(b'startxref' in line for line in pdfio) == 1
assert sys.getsizeof(pdfio) < 40000
def test_multipage_keep_empty():
from matplotlib.backends.backend_pdf import PdfPages
from tempfile import NamedTemporaryFile
# test empty pdf files
# test that an empty pdf is left behind with keep_empty=True (default)
with NamedTemporaryFile(delete=False) as tmp:
with PdfPages(tmp) as pdf:
filename = pdf._file.fh.name
assert os.path.exists(filename)
os.remove(filename)
# test if an empty pdf is deleting itself afterwards with keep_empty=False
with PdfPages(filename, keep_empty=False) as pdf:
pass
assert not os.path.exists(filename)
# test pdf files with content, they should never be deleted
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1, 2, 3])
# test that a non-empty pdf is left behind with keep_empty=True (default)
with NamedTemporaryFile(delete=False) as tmp:
with PdfPages(tmp) as pdf:
filename = pdf._file.fh.name
pdf.savefig()
assert os.path.exists(filename)
os.remove(filename)
# test that a non-empty pdf is left behind with keep_empty=False
with NamedTemporaryFile(delete=False) as tmp:
with PdfPages(tmp, keep_empty=False) as pdf:
filename = pdf._file.fh.name
pdf.savefig()
assert os.path.exists(filename)
os.remove(filename)
def test_composite_image():
# Test that figures can be saved with and without combining multiple images
# (on a single set of axes) into a single composite image.
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
Z = np.sin(Y ** 2)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlim(0, 3)
ax.imshow(Z, extent=[0, 1, 0, 1])
ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
plt.rcParams['image.composite_image'] = True
with PdfPages(io.BytesIO()) as pdf:
fig.savefig(pdf, format="pdf")
assert len(pdf._file._images) == 1
plt.rcParams['image.composite_image'] = False
with PdfPages(io.BytesIO()) as pdf:
fig.savefig(pdf, format="pdf")
assert len(pdf._file._images) == 2
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python 3.6+")
def test_pdfpages_fspath():
from pathlib import Path
with PdfPages(Path(os.devnull)) as pdf:
pdf.savefig(plt.figure())
def test_source_date_epoch():
"""Test SOURCE_DATE_EPOCH support for PDF output"""
_determinism_source_date_epoch("pdf", b"/CreationDate (D:20000101000000Z)")
def test_determinism_plain():
"""Test for reproducible PDF output: simple figure"""
_determinism_check('', format="pdf")
def test_determinism_images():
"""Test for reproducible PDF output: figure with different images"""
_determinism_check('i', format="pdf")
def test_determinism_hatches():
"""Test for reproducible PDF output: figure with different hatches"""
_determinism_check('h', format="pdf")
def test_determinism_markers():
"""Test for reproducible PDF output: figure with different markers"""
_determinism_check('m', format="pdf")
def test_determinism_all():
"""Test for reproducible PDF output"""
_determinism_check(format="pdf")
@image_comparison(baseline_images=['hatching_legend'],
extensions=['pdf'])
def test_hatching_legend():
"""Test for correct hatching on patches in legend"""
fig = plt.figure(figsize=(1, 2))
a = plt.Rectangle([0, 0], 0, 0, facecolor="green", hatch="XXXX")
b = plt.Rectangle([0, 0], 0, 0, facecolor="blue", hatch="XXXX")
fig.legend([a, b, a, b], ["", "", "", ""])
@image_comparison(baseline_images=['grayscale_alpha'],
extensions=['pdf'])
def test_grayscale_alpha():
"""Masking images with NaN did not work for grayscale images"""
x, y = np.ogrid[-2:2:.1, -2:2:.1]
dd = np.exp(-(x**2 + y**2))
dd[dd < .1] = np.nan
fig, ax = plt.subplots()
ax.imshow(dd, interpolation='none', cmap='gray_r')
ax.set_xticks([])
ax.set_yticks([])
# This tests tends to hit a TeX cache lock on AppVeyor.
@pytest.mark.flaky(reruns=3)
@needs_usetex
def test_missing_psfont(monkeypatch):
"""An error is raised if a TeX font lacks a Type-1 equivalent"""
def psfont(*args, **kwargs):
return dviread.PsFont(texname='texfont', psname='Some Font',
effects=None, encoding=None, filename=None)
monkeypatch.setattr(dviread.PsfontsMap, '__getitem__', psfont)
rcParams['text.usetex'] = True
fig, ax = plt.subplots()
ax.text(0.5, 0.5, 'hello')
with tempfile.TemporaryFile() as tmpfile, pytest.raises(ValueError):
fig.savefig(tmpfile, format='pdf')
@pytest.mark.style('default')
def test_pdf_savefig_when_color_is_none(tmpdir):
fig, ax = plt.subplots()
plt.axis('off')
ax.plot(np.sin(np.linspace(-5, 5, 100)), 'v', c='none')
actual_image = tmpdir.join('figure.pdf')
expected_image = tmpdir.join('figure.eps')
fig.savefig(str(actual_image), format='pdf')
fig.savefig(str(expected_image), format='eps')
result = compare_images(str(actual_image), str(expected_image), 0)
assert result is None
@needs_usetex
def test_failing_latex(tmpdir):
"""Test failing latex subprocess call"""
path = str(tmpdir.join("tmpoutput.pdf"))
rcParams['text.usetex'] = True
# This fails with "Double subscript"
plt.xlabel("$22_2_2$")
with pytest.raises(RuntimeError):
plt.savefig(path)
def test_empty_rasterized():
# Check that emtpy figures that are rasterised save to pdf files fine
fig, ax = plt.subplots()
ax.plot([], [], rasterized=True)
fig.savefig(io.BytesIO(), format="pdf")
@@ -0,0 +1,272 @@
import os
from pathlib import Path
import shutil
import subprocess
from tempfile import TemporaryDirectory
import numpy as np
import pytest
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.testing.compare import compare_images, ImageComparisonFailure
from matplotlib.testing.decorators import image_comparison, _image_directories
from matplotlib.backends.backend_pgf import PdfPages
baseline_dir, result_dir = _image_directories(lambda: 'dummy func')
def check_for(texsystem):
with TemporaryDirectory() as tmpdir:
tex_path = Path(tmpdir, "test.tex")
tex_path.write_text(r"""
\documentclass{minimal}
\usepackage{pgf}
\begin{document}
\typeout{pgfversion=\pgfversion}
\makeatletter
\@@end
""")
try:
subprocess.check_call(
[texsystem, "-halt-on-error", str(tex_path)], cwd=tmpdir,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except (OSError, subprocess.CalledProcessError):
return False
return True
needs_xelatex = pytest.mark.skipif(not check_for('xelatex'),
reason='xelatex + pgf is required')
needs_pdflatex = pytest.mark.skipif(not check_for('pdflatex'),
reason='pdflatex + pgf is required')
needs_lualatex = pytest.mark.skipif(not check_for('lualatex'),
reason='lualatex + pgf is required')
def compare_figure(fname, savefig_kwargs={}, tol=0):
actual = os.path.join(result_dir, fname)
plt.savefig(actual, **savefig_kwargs)
expected = os.path.join(result_dir, "expected_%s" % fname)
shutil.copyfile(os.path.join(baseline_dir, fname), expected)
err = compare_images(expected, actual, tol=tol)
if err:
raise ImageComparisonFailure(err)
def create_figure():
plt.figure()
x = np.linspace(0, 1, 15)
# line plot
plt.plot(x, x ** 2, "b-")
# marker
plt.plot(x, 1 - x**2, "g>")
# filled paths and patterns
plt.fill_between([0., .4], [.4, 0.], hatch='//', facecolor="lightgray",
edgecolor="red")
plt.fill([3, 3, .8, .8, 3], [2, -2, -2, 0, 2], "b")
# text and typesetting
plt.plot([0.9], [0.5], "ro", markersize=3)
plt.text(0.9, 0.5, 'unicode (ü, °, µ) and math ($\\mu_i = x_i^2$)',
ha='right', fontsize=20)
plt.ylabel('sans-serif, blue, $\\frac{\\sqrt{x}}{y^2}$..',
family='sans-serif', color='blue')
plt.xlim(0, 1)
plt.ylim(0, 1)
# test compiling a figure to pdf with xelatex
@needs_xelatex
@pytest.mark.backend('pgf')
@image_comparison(baseline_images=['pgf_xelatex'], extensions=['pdf'],
style='default')
def test_xelatex():
rc_xelatex = {'font.family': 'serif',
'pgf.rcfonts': False}
mpl.rcParams.update(rc_xelatex)
create_figure()
# test compiling a figure to pdf with pdflatex
@needs_pdflatex
@pytest.mark.backend('pgf')
@image_comparison(baseline_images=['pgf_pdflatex'], extensions=['pdf'],
style='default')
def test_pdflatex():
if os.environ.get('APPVEYOR', False):
pytest.xfail("pdflatex test does not work on appveyor due to missing "
"LaTeX fonts")
rc_pdflatex = {'font.family': 'serif',
'pgf.rcfonts': False,
'pgf.texsystem': 'pdflatex',
'pgf.preamble': ['\\usepackage[utf8x]{inputenc}',
'\\usepackage[T1]{fontenc}']}
mpl.rcParams.update(rc_pdflatex)
create_figure()
# test updating the rc parameters for each figure
@needs_xelatex
@needs_pdflatex
@pytest.mark.style('default')
@pytest.mark.backend('pgf')
def test_rcupdate():
rc_sets = [{'font.family': 'sans-serif',
'font.size': 30,
'figure.subplot.left': .2,
'lines.markersize': 10,
'pgf.rcfonts': False,
'pgf.texsystem': 'xelatex'},
{'font.family': 'monospace',
'font.size': 10,
'figure.subplot.left': .1,
'lines.markersize': 20,
'pgf.rcfonts': False,
'pgf.texsystem': 'pdflatex',
'pgf.preamble': ['\\usepackage[utf8x]{inputenc}',
'\\usepackage[T1]{fontenc}',
'\\usepackage{sfmath}']}]
tol = [6, 0]
for i, rc_set in enumerate(rc_sets):
with mpl.rc_context(rc_set):
create_figure()
compare_figure('pgf_rcupdate%d.pdf' % (i + 1), tol=tol[i])
# test backend-side clipping, since large numbers are not supported by TeX
@needs_xelatex
@pytest.mark.style('default')
@pytest.mark.backend('pgf')
def test_pathclip():
rc_xelatex = {'font.family': 'serif',
'pgf.rcfonts': False}
mpl.rcParams.update(rc_xelatex)
plt.figure()
plt.plot([0., 1e100], [0., 1e100])
plt.xlim(0, 1)
plt.ylim(0, 1)
# this test passes if compiling/saving to pdf works (no image comparison)
plt.savefig(os.path.join(result_dir, "pgf_pathclip.pdf"))
# test mixed mode rendering
@needs_xelatex
@pytest.mark.backend('pgf')
@image_comparison(baseline_images=['pgf_mixedmode'], extensions=['pdf'],
style='default')
def test_mixedmode():
rc_xelatex = {'font.family': 'serif',
'pgf.rcfonts': False}
mpl.rcParams.update(rc_xelatex)
Y, X = np.ogrid[-1:1:40j, -1:1:40j]
plt.figure()
plt.pcolor(X**2 + Y**2).set_rasterized(True)
# test bbox_inches clipping
@needs_xelatex
@pytest.mark.style('default')
@pytest.mark.backend('pgf')
def test_bbox_inches():
rc_xelatex = {'font.family': 'serif',
'pgf.rcfonts': False}
mpl.rcParams.update(rc_xelatex)
Y, X = np.ogrid[-1:1:40j, -1:1:40j]
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax1.plot(range(5))
ax2 = fig.add_subplot(122)
ax2.plot(range(5))
plt.tight_layout()
bbox = ax1.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
compare_figure('pgf_bbox_inches.pdf', savefig_kwargs={'bbox_inches': bbox},
tol=0)
@needs_pdflatex
@pytest.mark.style('default')
@pytest.mark.backend('pgf')
def test_pdf_pages():
rc_pdflatex = {
'font.family': 'serif',
'pgf.rcfonts': False,
'pgf.texsystem': 'pdflatex',
}
mpl.rcParams.update(rc_pdflatex)
fig1 = plt.figure()
ax1 = fig1.add_subplot(1, 1, 1)
ax1.plot(range(5))
fig1.tight_layout()
fig2 = plt.figure(figsize=(3, 2))
ax2 = fig2.add_subplot(1, 1, 1)
ax2.plot(range(5))
fig2.tight_layout()
with PdfPages(os.path.join(result_dir, 'pdfpages.pdf')) as pdf:
pdf.savefig(fig1)
pdf.savefig(fig2)
@needs_xelatex
@pytest.mark.style('default')
@pytest.mark.backend('pgf')
def test_pdf_pages_metadata():
rc_pdflatex = {
'font.family': 'serif',
'pgf.rcfonts': False,
'pgf.texsystem': 'xelatex',
}
mpl.rcParams.update(rc_pdflatex)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(range(5))
fig.tight_layout()
md = {'author': 'me', 'title': 'Multipage PDF with pgf'}
path = os.path.join(result_dir, 'pdfpages_meta.pdf')
with PdfPages(path, metadata=md) as pdf:
pdf.savefig(fig)
pdf.savefig(fig)
pdf.savefig(fig)
assert pdf.get_pagecount() == 3
@needs_lualatex
@pytest.mark.style('default')
@pytest.mark.backend('pgf')
def test_pdf_pages_lualatex():
rc_pdflatex = {
'font.family': 'serif',
'pgf.rcfonts': False,
'pgf.texsystem': 'lualatex'
}
mpl.rcParams.update(rc_pdflatex)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(range(5))
fig.tight_layout()
md = {'author': 'me', 'title': 'Multipage PDF with pgf'}
path = os.path.join(result_dir, 'pdfpages_lua.pdf')
with PdfPages(path, metadata=md) as pdf:
pdf.savefig(fig)
pdf.savefig(fig)
assert pdf.get_pagecount() == 2
@@ -0,0 +1,141 @@
import io
import os
from pathlib import Path
import re
import tempfile
import numpy as np
import pytest
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import cbook, patheffects
from matplotlib.testing.decorators import image_comparison
from matplotlib.testing.determinism import (_determinism_source_date_epoch,
_determinism_check)
needs_ghostscript = pytest.mark.skipif(
matplotlib.checkdep_ghostscript()[0] is None,
reason="This test needs a ghostscript installation")
needs_usetex = pytest.mark.skipif(
not matplotlib.checkdep_usetex(True),
reason="This test needs a TeX installation")
# This tests tends to hit a TeX cache lock on AppVeyor.
@pytest.mark.flaky(reruns=3)
@pytest.mark.parametrize('format, use_log, rcParams', [
('ps', False, {}),
pytest.param('ps', False, {'ps.usedistiller': 'ghostscript'},
marks=needs_ghostscript),
pytest.param('ps', False, {'text.usetex': True},
marks=[needs_ghostscript, needs_usetex]),
('eps', False, {}),
('eps', True, {'ps.useafm': True}),
pytest.param('eps', False, {'text.usetex': True},
marks=[needs_ghostscript, needs_usetex]),
], ids=[
'ps',
'ps with distiller',
'ps with usetex',
'eps',
'eps afm',
'eps with usetex'
])
def test_savefig_to_stringio(format, use_log, rcParams):
matplotlib.rcParams.update(rcParams)
fig, ax = plt.subplots()
with io.StringIO() as s_buf, io.BytesIO() as b_buf:
if use_log:
ax.set_yscale('log')
ax.plot([1, 2], [1, 2])
ax.set_title("Déjà vu")
fig.savefig(s_buf, format=format)
fig.savefig(b_buf, format=format)
s_val = s_buf.getvalue().encode('ascii')
b_val = b_buf.getvalue()
# Remove comments from the output. This includes things that could
# change from run to run, such as the time.
s_val, b_val = [re.sub(b'%%.*?\n', b'', x) for x in [s_val, b_val]]
assert s_val == b_val.replace(b'\r\n', b'\n')
def test_patheffects():
with matplotlib.rc_context():
matplotlib.rcParams['path.effects'] = [
patheffects.withStroke(linewidth=4, foreground='w')]
fig, ax = plt.subplots()
ax.plot([1, 2, 3])
with io.BytesIO() as ps:
fig.savefig(ps, format='ps')
@needs_usetex
@needs_ghostscript
def test_tilde_in_tempfilename(tmpdir):
# Tilde ~ in the tempdir path (e.g. TMPDIR, TMP or TEMP on windows
# when the username is very long and windows uses a short name) breaks
# latex before https://github.com/matplotlib/matplotlib/pull/5928
base_tempdir = Path(str(tmpdir), "short-1")
base_tempdir.mkdir()
# Change the path for new tempdirs, which is used internally by the ps
# backend to write a file.
with cbook._setattr_cm(tempfile, tempdir=str(base_tempdir)):
# usetex results in the latex call, which does not like the ~
plt.rc('text', usetex=True)
plt.plot([1, 2, 3, 4])
plt.xlabel(r'\textbf{time} (s)')
output_eps = os.path.join(str(base_tempdir), 'tex_demo.eps')
# use the PS backend to write the file...
plt.savefig(output_eps, format="ps")
def test_source_date_epoch():
"""Test SOURCE_DATE_EPOCH support for PS output"""
# SOURCE_DATE_EPOCH support is not tested with text.usetex,
# because the produced timestamp comes from ghostscript:
# %%CreationDate: D:20000101000000Z00\'00\', and this could change
# with another ghostscript version.
_determinism_source_date_epoch(
"ps", b"%%CreationDate: Sat Jan 01 00:00:00 2000")
def test_determinism_all():
"""Test for reproducible PS output"""
_determinism_check(format="ps")
@needs_usetex
@needs_ghostscript
def test_determinism_all_tex():
"""Test for reproducible PS/tex output"""
_determinism_check(format="ps", usetex=True)
@image_comparison(baseline_images=["empty"], extensions=["eps"])
def test_transparency():
fig, ax = plt.subplots()
ax.set_axis_off()
ax.plot([0, 1], color="r", alpha=0)
ax.text(.5, .5, "foo", color="r", alpha=0)
@needs_usetex
def test_failing_latex(tmpdir):
"""Test failing latex subprocess call"""
path = str(tmpdir.join("tmpoutput.ps"))
matplotlib.rcParams['text.usetex'] = True
# This fails with "Double subscript"
plt.xlabel("$22_2_2$")
with pytest.raises(RuntimeError):
plt.savefig(path)
@@ -0,0 +1,148 @@
import copy
from unittest import mock
import matplotlib
from matplotlib import pyplot as plt
from matplotlib._pylab_helpers import Gcf
import pytest
@pytest.fixture(autouse=True)
def mpl_test_settings(qt4_module, mpl_test_settings):
"""
Ensure qt4_module fixture is *first* fixture.
We override the `mpl_test_settings` fixture and depend on the `qt4_module`
fixture first. It is very important that it is first, because it skips
tests when Qt4 is not available, and if not, then the main
`mpl_test_settings` fixture will try to switch backends before the skip can
be triggered.
"""
pass
@pytest.fixture
def qt4_module():
try:
import PyQt4
# RuntimeError if PyQt5 already imported.
except (ImportError, RuntimeError):
try:
import PySide
except ImportError:
pytest.skip("Failed to import a Qt4 binding.")
qt_compat = pytest.importorskip('matplotlib.backends.qt_compat')
QtCore = qt_compat.QtCore
try:
py_qt_ver = int(QtCore.PYQT_VERSION_STR.split('.')[0])
except AttributeError:
py_qt_ver = QtCore.__version_info__[0]
if py_qt_ver != 4:
pytest.skip(reason='Qt4 is not available')
from matplotlib.backends.backend_qt4 import (
MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT) # noqa
mods = {}
keys = {}
for name, index in zip(['Alt', 'Control', 'Shift', 'Super'],
[ALT, CTRL, SHIFT, SUPER]):
_, mod, key = MODIFIER_KEYS[index]
mods[name + 'Modifier'] = mod
keys[name + 'Key'] = key
return QtCore, mods, keys
@pytest.fixture
def qt_key(request):
QtCore, _, keys = request.getfixturevalue('qt4_module')
if request.param.startswith('Key'):
return getattr(QtCore.Qt, request.param)
else:
return keys[request.param]
@pytest.fixture
def qt_mods(request):
QtCore, mods, _ = request.getfixturevalue('qt4_module')
result = QtCore.Qt.NoModifier
for mod in request.param:
result |= mods[mod]
return result
@pytest.mark.backend('Qt4Agg')
def test_fig_close():
# save the state of Gcf.figs
init_figs = copy.copy(Gcf.figs)
# make a figure using pyplot interface
fig = plt.figure()
# simulate user clicking the close button by reaching in
# and calling close on the underlying Qt object
fig.canvas.manager.window.close()
# assert that we have removed the reference to the FigureManager
# that got added by plt.figure()
assert init_figs == Gcf.figs
@pytest.mark.parametrize(
'qt_key, qt_mods, answer',
[
('Key_A', ['ShiftModifier'], 'A'),
('Key_A', [], 'a'),
('Key_A', ['ControlModifier'], 'ctrl+a'),
('Key_Aacute', ['ShiftModifier'],
'\N{LATIN CAPITAL LETTER A WITH ACUTE}'),
('Key_Aacute', [],
'\N{LATIN SMALL LETTER A WITH ACUTE}'),
('ControlKey', ['AltModifier'], 'alt+control'),
('AltKey', ['ControlModifier'], 'ctrl+alt'),
('Key_Aacute', ['ControlModifier', 'AltModifier', 'SuperModifier'],
'ctrl+alt+super+\N{LATIN SMALL LETTER A WITH ACUTE}'),
('Key_Backspace', [], 'backspace'),
('Key_Backspace', ['ControlModifier'], 'ctrl+backspace'),
('Key_Play', [], None),
],
indirect=['qt_key', 'qt_mods'],
ids=[
'shift',
'lower',
'control',
'unicode_upper',
'unicode_lower',
'alt_control',
'control_alt',
'modifier_order',
'backspace',
'backspace_mod',
'non_unicode_key',
]
)
@pytest.mark.backend('Qt4Agg')
def test_correct_key(qt_key, qt_mods, answer):
"""
Make a figure
Send a key_press_event event (using non-public, qt4 backend specific api)
Catch the event
Assert sent and caught keys are the same
"""
qt_canvas = plt.figure().canvas
event = mock.Mock()
event.isAutoRepeat.return_value = False
event.key.return_value = qt_key
event.modifiers.return_value = qt_mods
def receive(event):
assert event.key == answer
qt_canvas.mpl_connect('key_press_event', receive)
qt_canvas.keyPressEvent(event)
@@ -0,0 +1,223 @@
import copy
from unittest import mock
import matplotlib
from matplotlib import pyplot as plt
from matplotlib._pylab_helpers import Gcf
import pytest
@pytest.fixture(autouse=True)
def mpl_test_settings(qt5_module, mpl_test_settings):
"""
Ensure qt5_module fixture is *first* fixture.
We override the `mpl_test_settings` fixture and depend on the `qt5_module`
fixture first. It is very important that it is first, because it skips
tests when Qt5 is not available, and if not, then the main
`mpl_test_settings` fixture will try to switch backends before the skip can
be triggered.
"""
pass
@pytest.fixture
def qt5_module():
try:
import PyQt5
# RuntimeError if PyQt4 already imported.
except (ImportError, RuntimeError):
try:
import PySide2
except ImportError:
pytest.skip("Failed to import a Qt5 binding.")
qt_compat = pytest.importorskip('matplotlib.backends.qt_compat')
QtCore = qt_compat.QtCore
from matplotlib.backends.backend_qt5 import (
MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT) # noqa
mods = {}
keys = {}
for name, index in zip(['Alt', 'Control', 'Shift', 'Super'],
[ALT, CTRL, SHIFT, SUPER]):
_, mod, key = MODIFIER_KEYS[index]
mods[name + 'Modifier'] = mod
keys[name + 'Key'] = key
return QtCore, mods, keys
@pytest.fixture
def qt_key(request):
QtCore, _, keys = request.getfixturevalue('qt5_module')
if request.param.startswith('Key'):
return getattr(QtCore.Qt, request.param)
else:
return keys[request.param]
@pytest.fixture
def qt_mods(request):
QtCore, mods, _ = request.getfixturevalue('qt5_module')
result = QtCore.Qt.NoModifier
for mod in request.param:
result |= mods[mod]
return result
@pytest.mark.backend('Qt5Agg')
def test_fig_close():
# save the state of Gcf.figs
init_figs = copy.copy(Gcf.figs)
# make a figure using pyplot interface
fig = plt.figure()
# simulate user clicking the close button by reaching in
# and calling close on the underlying Qt object
fig.canvas.manager.window.close()
# assert that we have removed the reference to the FigureManager
# that got added by plt.figure()
assert init_figs == Gcf.figs
@pytest.mark.parametrize(
'qt_key, qt_mods, answer',
[
('Key_A', ['ShiftModifier'], 'A'),
('Key_A', [], 'a'),
('Key_A', ['ControlModifier'], 'ctrl+a'),
('Key_Aacute', ['ShiftModifier'],
'\N{LATIN CAPITAL LETTER A WITH ACUTE}'),
('Key_Aacute', [],
'\N{LATIN SMALL LETTER A WITH ACUTE}'),
('ControlKey', ['AltModifier'], 'alt+control'),
('AltKey', ['ControlModifier'], 'ctrl+alt'),
('Key_Aacute', ['ControlModifier', 'AltModifier', 'SuperModifier'],
'ctrl+alt+super+\N{LATIN SMALL LETTER A WITH ACUTE}'),
('Key_Backspace', [], 'backspace'),
('Key_Backspace', ['ControlModifier'], 'ctrl+backspace'),
('Key_Play', [], None),
],
indirect=['qt_key', 'qt_mods'],
ids=[
'shift',
'lower',
'control',
'unicode_upper',
'unicode_lower',
'alt_control',
'control_alt',
'modifier_order',
'backspace',
'backspace_mod',
'non_unicode_key',
]
)
@pytest.mark.backend('Qt5Agg')
def test_correct_key(qt_key, qt_mods, answer):
"""
Make a figure
Send a key_press_event event (using non-public, qt5 backend specific api)
Catch the event
Assert sent and caught keys are the same
"""
qt_canvas = plt.figure().canvas
event = mock.Mock()
event.isAutoRepeat.return_value = False
event.key.return_value = qt_key
event.modifiers.return_value = qt_mods
def receive(event):
assert event.key == answer
qt_canvas.mpl_connect('key_press_event', receive)
qt_canvas.keyPressEvent(event)
@pytest.mark.backend('Qt5Agg')
def test_dpi_ratio_change():
"""
Make sure that if _dpi_ratio changes, the figure dpi changes but the
widget remains the same physical size.
"""
prop = 'matplotlib.backends.backend_qt5.FigureCanvasQT._dpi_ratio'
with mock.patch(prop, new_callable=mock.PropertyMock) as p:
p.return_value = 3
fig = plt.figure(figsize=(5, 2), dpi=120)
qt_canvas = fig.canvas
qt_canvas.show()
from matplotlib.backends.backend_qt5 import qApp
# Make sure the mocking worked
assert qt_canvas._dpi_ratio == 3
size = qt_canvas.size()
qt_canvas.manager.show()
qt_canvas.draw()
qApp.processEvents()
# The DPI and the renderer width/height change
assert fig.dpi == 360
assert qt_canvas.renderer.width == 1800
assert qt_canvas.renderer.height == 720
# The actual widget size and figure physical size don't change
assert size.width() == 600
assert size.height() == 240
assert qt_canvas.get_width_height() == (600, 240)
assert (fig.get_size_inches() == (5, 2)).all()
p.return_value = 2
assert qt_canvas._dpi_ratio == 2
qt_canvas.draw()
qApp.processEvents()
# this second processEvents is required to fully run the draw.
# On `update` we notice the DPI has changed and trigger a
# resize event to refresh, the second processEvents is
# required to process that and fully update the window sizes.
qApp.processEvents()
# The DPI and the renderer width/height change
assert fig.dpi == 240
assert qt_canvas.renderer.width == 1200
assert qt_canvas.renderer.height == 480
# The actual widget size and figure physical size don't change
assert size.width() == 600
assert size.height() == 240
assert qt_canvas.get_width_height() == (600, 240)
assert (fig.get_size_inches() == (5, 2)).all()
@pytest.mark.backend('Qt5Agg')
def test_subplottool():
fig, ax = plt.subplots()
with mock.patch(
"matplotlib.backends.backend_qt5.SubplotToolQt.exec_",
lambda self: None):
fig.canvas.manager.toolbar.configure_subplots()
@pytest.mark.backend('Qt5Agg')
def test_figureoptions():
fig, ax = plt.subplots()
ax.plot([1, 2])
ax.imshow([[1]])
with mock.patch(
"matplotlib.backends.qt_editor.formlayout.FormDialog.exec_",
lambda self: None):
fig.canvas.manager.toolbar.edit_parameters()
@@ -0,0 +1,176 @@
import numpy as np
from io import BytesIO
import os
import tempfile
import xml.parsers.expat
import pytest
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
import matplotlib
from matplotlib import dviread
needs_usetex = pytest.mark.skipif(
not matplotlib.checkdep_usetex(True),
reason="This test needs a TeX installation")
def test_visibility():
fig, ax = plt.subplots()
x = np.linspace(0, 4 * np.pi, 50)
y = np.sin(x)
yerr = np.ones_like(y)
a, b, c = ax.errorbar(x, y, yerr=yerr, fmt='ko')
for artist in b:
artist.set_visible(False)
fd = BytesIO()
fig.savefig(fd, format='svg')
fd.seek(0)
buf = fd.read()
fd.close()
parser = xml.parsers.expat.ParserCreate()
parser.Parse(buf) # this will raise ExpatError if the svg is invalid
@image_comparison(baseline_images=['fill_black_with_alpha'], remove_text=True,
extensions=['svg'])
def test_fill_black_with_alpha():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.scatter(x=[0, 0.1, 1], y=[0, 0, 0], c='k', alpha=0.1, s=10000)
@image_comparison(baseline_images=['noscale'], remove_text=True)
def test_noscale():
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
Z = np.sin(Y ** 2)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.imshow(Z, cmap='gray', interpolation='none')
def test_text_urls():
fig = plt.figure()
test_url = "http://test_text_urls.matplotlib.org"
fig.suptitle("test_text_urls", url=test_url)
fd = BytesIO()
fig.savefig(fd, format='svg')
fd.seek(0)
buf = fd.read().decode()
fd.close()
expected = '<a xlink:href="{0}">'.format(test_url)
assert expected in buf
@image_comparison(baseline_images=['bold_font_output'], extensions=['svg'])
def test_bold_font_output():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(np.arange(10), np.arange(10))
ax.set_xlabel('nonbold-xlabel')
ax.set_ylabel('bold-ylabel', fontweight='bold')
ax.set_title('bold-title', fontweight='bold')
@image_comparison(baseline_images=['bold_font_output_with_none_fonttype'],
extensions=['svg'])
def test_bold_font_output_with_none_fonttype():
plt.rcParams['svg.fonttype'] = 'none'
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(np.arange(10), np.arange(10))
ax.set_xlabel('nonbold-xlabel')
ax.set_ylabel('bold-ylabel', fontweight='bold')
ax.set_title('bold-title', fontweight='bold')
def _test_determinism_save(filename, usetex):
# This function is mostly copy&paste from "def test_visibility"
# To require no GUI, we use Figure and FigureCanvasSVG
# instead of plt.figure and fig.savefig
from matplotlib.figure import Figure
from matplotlib.backends.backend_svg import FigureCanvasSVG
from matplotlib import rc
rc('svg', hashsalt='asdf')
rc('text', usetex=usetex)
fig = Figure()
ax = fig.add_subplot(111)
x = np.linspace(0, 4 * np.pi, 50)
y = np.sin(x)
yerr = np.ones_like(y)
a, b, c = ax.errorbar(x, y, yerr=yerr, fmt='ko')
for artist in b:
artist.set_visible(False)
ax.set_title('A string $1+2+\\sigma$')
ax.set_xlabel('A string $1+2+\\sigma$')
ax.set_ylabel('A string $1+2+\\sigma$')
FigureCanvasSVG(fig).print_svg(filename)
@pytest.mark.parametrize(
"filename, usetex",
# unique filenames to allow for parallel testing
[("determinism_notex.svg", False),
pytest.param("determinism_tex.svg", True, marks=needs_usetex)])
def test_determinism(filename, usetex):
import sys
from subprocess import check_output, STDOUT, CalledProcessError
plots = []
for i in range(3):
# Using check_output and setting stderr to STDOUT will capture the real
# problem in the output property of the exception
try:
check_output(
[sys.executable, '-R', '-c',
'import matplotlib; '
'matplotlib._called_from_pytest = True; '
'matplotlib.use("svg", force=True); '
'from matplotlib.tests.test_backend_svg '
'import _test_determinism_save;'
'_test_determinism_save(%r, %r)' % (filename, usetex)],
stderr=STDOUT)
except CalledProcessError as e:
# it's easier to use utf8 and ask for forgiveness than try
# to figure out what the current console has as an
# encoding :-/
print(e.output.decode(encoding="utf-8", errors="ignore"))
raise e
else:
with open(filename, 'rb') as fd:
plots.append(fd.read())
finally:
os.unlink(filename)
for p in plots[1:]:
assert p == plots[0]
@needs_usetex
def test_missing_psfont(monkeypatch):
"""An error is raised if a TeX font lacks a Type-1 equivalent"""
from matplotlib import rc
def psfont(*args, **kwargs):
return dviread.PsFont(texname='texfont', psname='Some Font',
effects=None, encoding=None, filename=None)
monkeypatch.setattr(dviread.PsfontsMap, '__getitem__', psfont)
rc('text', usetex=True)
fig, ax = plt.subplots()
ax.text(0.5, 0.5, 'hello')
with tempfile.TemporaryFile() as tmpfile, pytest.raises(ValueError):
fig.savefig(tmpfile, format='svg')
@@ -0,0 +1,20 @@
import pytest
from matplotlib.backend_tools import ToolHelpBase
@pytest.mark.parametrize('rc_shortcut,expected', [
('home', 'Home'),
('backspace', 'Backspace'),
('f1', 'F1'),
('ctrl+a', 'Ctrl+A'),
('ctrl+A', 'Ctrl+Shift+A'),
('a', 'a'),
('A', 'A'),
('ctrl+shift+f1', 'Ctrl+Shift+F1'),
('1', '1'),
('cmd+p', 'Cmd+P'),
('cmd+1', 'Cmd+1'),
])
def test_format_shortcut(rc_shortcut, expected):
assert ToolHelpBase.format_shortcut(rc_shortcut) == expected
@@ -0,0 +1,144 @@
import importlib
import importlib.util
import os
import signal
import subprocess
import sys
import time
import urllib.request
import pytest
import matplotlib as mpl
# Minimal smoke-testing of the backends for which the dependencies are
# PyPI-installable on Travis. They are not available for all tested Python
# versions so we don't fail on missing backends.
def _get_testable_interactive_backends():
backends = []
for deps, backend in [
# gtk3agg fails on Travis, needs to be investigated.
# (["cairocffi", "pgi"], "gtk3agg"),
(["cairocffi", "pgi"], "gtk3cairo"),
(["PyQt5"], "qt5agg"),
(["PyQt5", "cairocffi"], "qt5cairo"),
(["tkinter"], "tkagg"),
(["wx"], "wx"),
(["wx"], "wxagg"),
]:
reason = None
if not os.environ.get("DISPLAY"):
reason = "No $DISPLAY"
elif any(importlib.util.find_spec(dep) is None for dep in deps):
reason = "Missing dependency"
if reason:
backend = pytest.param(
backend, marks=pytest.mark.skip(reason=reason))
backends.append(backend)
return backends
# Using a timer not only allows testing of timers (on other backends), but is
# also necessary on gtk3 and wx, where a direct call to key_press_event("q")
# from draw_event causes breakage due to the canvas widget being deleted too
# early. Also, gtk3 redefines key_press_event with a different signature, so
# we directly invoke it from the superclass instead.
_test_script = """\
import importlib
import importlib.util
import sys
from unittest import TestCase
import matplotlib as mpl
from matplotlib import pyplot as plt, rcParams
from matplotlib.backend_bases import FigureCanvasBase
rcParams.update({
"webagg.open_in_browser": False,
"webagg.port_retries": 1,
})
backend = plt.rcParams["backend"].lower()
assert_equal = TestCase().assertEqual
assert_raises = TestCase().assertRaises
if backend.endswith("agg") and not backend.startswith(("gtk3", "web")):
# Force interactive framework setup.
plt.figure()
# Check that we cannot switch to a backend using another interactive
# framework, but can switch to a backend using cairo instead of agg, or a
# non-interactive backend. In the first case, we use tkagg as the "other"
# interactive backend as it is (essentially) guaranteed to be present.
# Moreover, don't test switching away from gtk3 (as Gtk.main_level() is
# not set up at this point yet) and webagg (which uses no interactive
# framework).
if backend != "tkagg":
with assert_raises(ImportError):
mpl.use("tkagg", force=True)
def check_alt_backend(alt_backend):
mpl.use(alt_backend, force=True)
fig = plt.figure()
assert_equal(
type(fig.canvas).__module__,
"matplotlib.backends.backend_{}".format(alt_backend))
if importlib.util.find_spec("cairocffi"):
check_alt_backend(backend[:-3] + "cairo")
check_alt_backend("svg")
mpl.use(backend, force=True)
fig, ax = plt.subplots()
assert_equal(
type(fig.canvas).__module__,
"matplotlib.backends.backend_{}".format(backend))
ax.plot([0, 1], [2, 3])
timer = fig.canvas.new_timer(1)
timer.add_callback(FigureCanvasBase.key_press_event, fig.canvas, "q")
# Trigger quitting upon draw.
fig.canvas.mpl_connect("draw_event", lambda event: timer.start())
plt.show()
"""
_test_timeout = 10 # Empirically, 1s is not enough on Travis.
@pytest.mark.parametrize("backend", _get_testable_interactive_backends())
@pytest.mark.flaky(reruns=3)
def test_interactive_backend(backend):
if subprocess.run([sys.executable, "-c", _test_script],
env={**os.environ, "MPLBACKEND": backend},
timeout=_test_timeout).returncode:
pytest.fail("The subprocess returned an error.")
@pytest.mark.skipif('SYSTEM_TEAMFOUNDATIONCOLLECTIONURI' in os.environ,
reason="this test fails an azure for unknown reasons")
@pytest.mark.skipif(os.name == "nt", reason="Cannot send SIGINT on Windows.")
def test_webagg():
pytest.importorskip("tornado")
proc = subprocess.Popen([sys.executable, "-c", _test_script],
env={**os.environ, "MPLBACKEND": "webagg"})
url = "http://{}:{}".format(
mpl.rcParams["webagg.address"], mpl.rcParams["webagg.port"])
timeout = time.perf_counter() + _test_timeout
while True:
try:
retcode = proc.poll()
# check that the subprocess for the server is not dead
assert retcode is None
conn = urllib.request.urlopen(url)
break
except urllib.error.URLError:
if time.perf_counter() > timeout:
pytest.fail("Failed to connect to the webagg server.")
else:
continue
conn.close()
proc.send_signal(signal.SIGINT)
assert proc.wait(timeout=_test_timeout) == 0
@@ -0,0 +1,36 @@
import builtins
import matplotlib
def test_simple():
assert 1 + 1 == 2
def test_override_builtins():
import pylab
ok_to_override = {
'__name__',
'__doc__',
'__package__',
'__loader__',
'__spec__',
'any',
'all',
'sum',
'divmod'
}
overridden = False
for key in dir(pylab):
if key in dir(builtins):
if (getattr(pylab, key) != getattr(builtins, key) and
key not in ok_to_override):
print("'%s' was overridden in globals()." % key)
overridden = True
assert not overridden
def test_verbose():
assert isinstance(matplotlib.verbose, matplotlib.Verbose)
@@ -0,0 +1,88 @@
import numpy as np
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import matplotlib.patches as mpatches
from matplotlib.ticker import FuncFormatter
@image_comparison(baseline_images=['bbox_inches_tight'], remove_text=True,
savefig_kwarg=dict(bbox_inches='tight'))
def test_bbox_inches_tight():
#: Test that a figure saved using bbox_inches='tight' is clipped correctly
data = [[66386, 174296, 75131, 577908, 32015],
[58230, 381139, 78045, 99308, 160454],
[89135, 80552, 152558, 497981, 603535],
[78415, 81858, 150656, 193263, 69638],
[139361, 331509, 343164, 781380, 52269]]
colLabels = rowLabels = [''] * 5
rows = len(data)
ind = np.arange(len(colLabels)) + 0.3 # the x locations for the groups
cellText = []
width = 0.4 # the width of the bars
yoff = np.zeros(len(colLabels))
# the bottom values for stacked bar chart
fig, ax = plt.subplots(1, 1)
for row in range(rows):
ax.bar(ind, data[row], width, bottom=yoff, align='edge', color='b')
yoff = yoff + data[row]
cellText.append([''])
plt.xticks([])
plt.legend([''] * 5, loc=(1.2, 0.2))
# Add a table at the bottom of the axes
cellText.reverse()
the_table = plt.table(cellText=cellText,
rowLabels=rowLabels,
colLabels=colLabels, loc='bottom')
@image_comparison(baseline_images=['bbox_inches_tight_suptile_legend'],
remove_text=False, savefig_kwarg={'bbox_inches': 'tight'})
def test_bbox_inches_tight_suptile_legend():
plt.plot(np.arange(10), label='a straight line')
plt.legend(bbox_to_anchor=(0.9, 1), loc='upper left')
plt.title('Axis title')
plt.suptitle('Figure title')
# put an extra long y tick on to see that the bbox is accounted for
def y_formatter(y, pos):
if int(y) == 4:
return 'The number 4'
else:
return str(y)
plt.gca().yaxis.set_major_formatter(FuncFormatter(y_formatter))
plt.xlabel('X axis')
@image_comparison(baseline_images=['bbox_inches_tight_clipping'],
remove_text=True, savefig_kwarg={'bbox_inches': 'tight'})
def test_bbox_inches_tight_clipping():
# tests bbox clipping on scatter points, and path clipping on a patch
# to generate an appropriately tight bbox
plt.scatter(np.arange(10), np.arange(10))
ax = plt.gca()
ax.set_xlim([0, 5])
ax.set_ylim([0, 5])
# make a massive rectangle and clip it with a path
patch = mpatches.Rectangle([-50, -50], 100, 100,
transform=ax.transData,
facecolor='blue', alpha=0.5)
path = mpath.Path.unit_regular_star(5).deepcopy()
path.vertices *= 0.25
patch.set_clip_path(path, transform=ax.transAxes)
plt.gcf().artists.append(patch)
@image_comparison(baseline_images=['bbox_inches_tight_raster'],
remove_text=True, savefig_kwarg={'bbox_inches': 'tight'})
def test_bbox_inches_tight_raster():
"""Test rasterization with tight_layout"""
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1.0, 2.0], rasterized=True)
@@ -0,0 +1,275 @@
"""Catch all for categorical functions"""
import pytest
import numpy as np
from matplotlib.axes import Axes
import matplotlib.pyplot as plt
import matplotlib.category as cat
# Python2/3 text handling
_to_str = cat.StrCategoryFormatter._text
class TestUnitData(object):
test_cases = [('single', (["hello world"], [0])),
('unicode', (["Здравствуйте мир"], [0])),
('mixed', (['A', "np.nan", 'B', "3.14", "мир"],
[0, 1, 2, 3, 4]))]
ids, data = zip(*test_cases)
@pytest.mark.parametrize("data, locs", data, ids=ids)
def test_unit(self, data, locs):
unit = cat.UnitData(data)
assert list(unit._mapping.keys()) == data
assert list(unit._mapping.values()) == locs
def test_update(self):
data = ['a', 'd']
locs = [0, 1]
data_update = ['b', 'd', 'e']
unique_data = ['a', 'd', 'b', 'e']
updated_locs = [0, 1, 2, 3]
unit = cat.UnitData(data)
assert list(unit._mapping.keys()) == data
assert list(unit._mapping.values()) == locs
unit.update(data_update)
assert list(unit._mapping.keys()) == unique_data
assert list(unit._mapping.values()) == updated_locs
failing_test_cases = [("number", 3.14), ("nan", np.nan),
("list", [3.14, 12]), ("mixed type", ["A", 2])]
fids, fdata = zip(*test_cases)
@pytest.mark.parametrize("fdata", fdata, ids=fids)
def test_non_string_fails(self, fdata):
with pytest.raises(TypeError):
cat.UnitData(fdata)
@pytest.mark.parametrize("fdata", fdata, ids=fids)
def test_non_string_update_fails(self, fdata):
unitdata = cat.UnitData()
with pytest.raises(TypeError):
unitdata.update(fdata)
class FakeAxis(object):
def __init__(self, units):
self.units = units
class TestStrCategoryConverter(object):
"""Based on the pandas conversion and factorization tests:
ref: /pandas/tseries/tests/test_converter.py
/pandas/tests/test_algos.py:TestFactorize
"""
test_cases = [("unicode", ["Здравствуйте мир"]),
("ascii", ["hello world"]),
("single", ['a', 'b', 'c']),
("integer string", ["1", "2"]),
("single + values>10", ["A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N",
"O", "P", "Q", "R", "S", "T", "U",
"V", "W", "X", "Y", "Z"])]
ids, values = zip(*test_cases)
failing_test_cases = [("mixed", [3.14, 'A', np.inf]),
("string integer", ['42', 42])]
fids, fvalues = zip(*failing_test_cases)
@pytest.fixture(autouse=True)
def mock_axis(self, request):
self.cc = cat.StrCategoryConverter()
# self.unit should be probably be replaced with real mock unit
self.unit = cat.UnitData()
self.ax = FakeAxis(self.unit)
@pytest.mark.parametrize("vals", values, ids=ids)
def test_convert(self, vals):
np.testing.assert_allclose(self.cc.convert(vals, self.ax.units,
self.ax),
range(len(vals)))
@pytest.mark.parametrize("value", ["hi", "мир"], ids=["ascii", "unicode"])
def test_convert_one_string(self, value):
assert self.cc.convert(value, self.unit, self.ax) == 0
def test_convert_one_number(self):
actual = self.cc.convert(0.0, self.unit, self.ax)
np.testing.assert_allclose(actual, np.array([0.]))
def test_convert_float_array(self):
data = np.array([1, 2, 3], dtype=float)
actual = self.cc.convert(data, self.unit, self.ax)
np.testing.assert_allclose(actual, np.array([1., 2., 3.]))
@pytest.mark.parametrize("fvals", fvalues, ids=fids)
def test_convert_fail(self, fvals):
with pytest.raises(TypeError):
self.cc.convert(fvals, self.unit, self.ax)
def test_axisinfo(self):
axis = self.cc.axisinfo(self.unit, self.ax)
assert isinstance(axis.majloc, cat.StrCategoryLocator)
assert isinstance(axis.majfmt, cat.StrCategoryFormatter)
def test_default_units(self):
assert isinstance(self.cc.default_units(["a"], self.ax), cat.UnitData)
@pytest.fixture
def ax():
return plt.figure().subplots()
PLOT_LIST = [Axes.scatter, Axes.plot, Axes.bar]
PLOT_IDS = ["scatter", "plot", "bar"]
class TestStrCategoryLocator(object):
def test_StrCategoryLocator(self):
locs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
unit = cat.UnitData([str(j) for j in locs])
ticks = cat.StrCategoryLocator(unit._mapping)
np.testing.assert_array_equal(ticks.tick_values(None, None), locs)
@pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
def test_StrCategoryLocatorPlot(self, ax, plotter):
ax.plot(["a", "b", "c"])
np.testing.assert_array_equal(ax.yaxis.major.locator(), range(3))
class TestStrCategoryFormatter(object):
test_cases = [("ascii", ["hello", "world", "hi"]),
("unicode", ["Здравствуйте", "привет"])]
ids, cases = zip(*test_cases)
@pytest.mark.parametrize("ydata", cases, ids=ids)
def test_StrCategoryFormatter(self, ax, ydata):
unit = cat.UnitData(ydata)
labels = cat.StrCategoryFormatter(unit._mapping)
for i, d in enumerate(ydata):
assert labels(i, i) == _to_str(d)
@pytest.mark.parametrize("ydata", cases, ids=ids)
@pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
def test_StrCategoryFormatterPlot(self, ax, ydata, plotter):
plotter(ax, range(len(ydata)), ydata)
for i, d in enumerate(ydata):
assert ax.yaxis.major.formatter(i, i) == _to_str(d)
assert ax.yaxis.major.formatter(i+1, i+1) == ""
assert ax.yaxis.major.formatter(0, None) == ""
def axis_test(axis, labels):
ticks = list(range(len(labels)))
np.testing.assert_array_equal(axis.get_majorticklocs(), ticks)
graph_labels = [axis.major.formatter(i, i) for i in ticks]
assert graph_labels == [_to_str(l) for l in labels]
assert list(axis.units._mapping.keys()) == [l for l in labels]
assert list(axis.units._mapping.values()) == ticks
class TestPlotBytes(object):
bytes_cases = [('string list', ['a', 'b', 'c']),
('bytes list', [b'a', b'b', b'c']),
('bytes ndarray', np.array([b'a', b'b', b'c']))]
bytes_ids, bytes_data = zip(*bytes_cases)
@pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
@pytest.mark.parametrize("bdata", bytes_data, ids=bytes_ids)
def test_plot_bytes(self, ax, plotter, bdata):
counts = np.array([4, 6, 5])
plotter(ax, bdata, counts)
axis_test(ax.xaxis, bdata)
class TestPlotNumlike(object):
numlike_cases = [('string list', ['1', '11', '3']),
('string ndarray', np.array(['1', '11', '3'])),
('bytes list', [b'1', b'11', b'3']),
('bytes ndarray', np.array([b'1', b'11', b'3']))]
numlike_ids, numlike_data = zip(*numlike_cases)
@pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
@pytest.mark.parametrize("ndata", numlike_data, ids=numlike_ids)
def test_plot_numlike(self, ax, plotter, ndata):
counts = np.array([4, 6, 5])
plotter(ax, ndata, counts)
axis_test(ax.xaxis, ndata)
class TestPlotTypes(object):
@pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
def test_plot_unicode(self, ax, plotter):
words = ['Здравствуйте', 'привет']
plotter(ax, words, [0, 1])
axis_test(ax.xaxis, words)
@pytest.fixture
def test_data(self):
self.x = ["hello", "happy", "world"]
self.xy = [2, 6, 3]
self.y = ["Python", "is", "fun"]
self.yx = [3, 4, 5]
@pytest.mark.usefixtures("test_data")
@pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
def test_plot_xaxis(self, ax, test_data, plotter):
plotter(ax, self.x, self.xy)
axis_test(ax.xaxis, self.x)
@pytest.mark.usefixtures("test_data")
@pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
def test_plot_yaxis(self, ax, test_data, plotter):
plotter(ax, self.yx, self.y)
axis_test(ax.yaxis, self.y)
@pytest.mark.usefixtures("test_data")
@pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
def test_plot_xyaxis(self, ax, test_data, plotter):
plotter(ax, self.x, self.y)
axis_test(ax.xaxis, self.x)
axis_test(ax.yaxis, self.y)
@pytest.mark.parametrize("plotter", PLOT_LIST, ids=PLOT_IDS)
def test_update_plot(self, ax, plotter):
plotter(ax, ['a', 'b'], ['e', 'g'])
plotter(ax, ['a', 'b', 'd'], ['f', 'a', 'b'])
plotter(ax, ['b', 'c', 'd'], ['g', 'e', 'd'])
axis_test(ax.xaxis, ['a', 'b', 'd', 'c'])
axis_test(ax.yaxis, ['e', 'g', 'f', 'a', 'b', 'd'])
failing_test_cases = [("mixed", ['A', 3.14]),
("number integer", ['1', 1]),
("string integer", ['42', 42]),
("missing", ['12', np.nan])]
fids, fvalues = zip(*failing_test_cases)
PLOT_BROKEN_LIST = [Axes.scatter,
pytest.param(Axes.plot, marks=pytest.mark.xfail),
pytest.param(Axes.bar, marks=pytest.mark.xfail)]
PLOT_BROKEN_IDS = ["scatter", "plot", "bar"]
@pytest.mark.parametrize("plotter", PLOT_BROKEN_LIST, ids=PLOT_BROKEN_IDS)
@pytest.mark.parametrize("xdata", fvalues, ids=fids)
def test_mixed_type_exception(self, ax, plotter, xdata):
with pytest.raises(TypeError):
plotter(ax, xdata, [1, 2])
@pytest.mark.parametrize("plotter", PLOT_BROKEN_LIST, ids=PLOT_BROKEN_IDS)
@pytest.mark.parametrize("xdata", fvalues, ids=fids)
def test_mixed_type_update_exception(self, ax, plotter, xdata):
with pytest.raises(TypeError):
plotter(ax, [0, 3], [1, 3])
plotter(ax, xdata, [1, 2])
@@ -0,0 +1,501 @@
import itertools
import pickle
from weakref import ref
import warnings
from unittest.mock import patch, Mock
from datetime import datetime
import numpy as np
from numpy.testing import (assert_array_equal, assert_approx_equal,
assert_array_almost_equal)
import pytest
import matplotlib.cbook as cbook
import matplotlib.colors as mcolors
from matplotlib.cbook import delete_masked_points as dmp
def test_is_hashable():
s = 'string'
assert cbook.is_hashable(s)
lst = ['list', 'of', 'stings']
assert not cbook.is_hashable(lst)
class Test_delete_masked_points(object):
def setup_method(self):
self.mask1 = [False, False, True, True, False, False]
self.arr0 = np.arange(1.0, 7.0)
self.arr1 = [1, 2, 3, np.nan, np.nan, 6]
self.arr2 = np.array(self.arr1)
self.arr3 = np.ma.array(self.arr2, mask=self.mask1)
self.arr_s = ['a', 'b', 'c', 'd', 'e', 'f']
self.arr_s2 = np.array(self.arr_s)
self.arr_dt = [datetime(2008, 1, 1), datetime(2008, 1, 2),
datetime(2008, 1, 3), datetime(2008, 1, 4),
datetime(2008, 1, 5), datetime(2008, 1, 6)]
self.arr_dt2 = np.array(self.arr_dt)
self.arr_colors = ['r', 'g', 'b', 'c', 'm', 'y']
self.arr_rgba = mcolors.to_rgba_array(self.arr_colors)
def test_bad_first_arg(self):
with pytest.raises(ValueError):
dmp('a string', self.arr0)
def test_string_seq(self):
actual = dmp(self.arr_s, self.arr1)
ind = [0, 1, 2, 5]
expected = (self.arr_s2.take(ind), self.arr2.take(ind))
assert_array_equal(actual[0], expected[0])
assert_array_equal(actual[1], expected[1])
def test_datetime(self):
actual = dmp(self.arr_dt, self.arr3)
ind = [0, 1, 5]
expected = (self.arr_dt2.take(ind),
self.arr3.take(ind).compressed())
assert_array_equal(actual[0], expected[0])
assert_array_equal(actual[1], expected[1])
def test_rgba(self):
actual = dmp(self.arr3, self.arr_rgba)
ind = [0, 1, 5]
expected = (self.arr3.take(ind).compressed(),
self.arr_rgba.take(ind, axis=0))
assert_array_equal(actual[0], expected[0])
assert_array_equal(actual[1], expected[1])
class Test_boxplot_stats(object):
def setup(self):
np.random.seed(937)
self.nrows = 37
self.ncols = 4
self.data = np.random.lognormal(size=(self.nrows, self.ncols),
mean=1.5, sigma=1.75)
self.known_keys = sorted([
'mean', 'med', 'q1', 'q3', 'iqr',
'cilo', 'cihi', 'whislo', 'whishi',
'fliers', 'label'
])
self.std_results = cbook.boxplot_stats(self.data)
self.known_nonbootstrapped_res = {
'cihi': 6.8161283264444847,
'cilo': -0.1489815330368689,
'iqr': 13.492709959447094,
'mean': 13.00447442387868,
'med': 3.3335733967038079,
'fliers': np.array([
92.55467075, 87.03819018, 42.23204914, 39.29390996
]),
'q1': 1.3597529879465153,
'q3': 14.85246294739361,
'whishi': 27.899688243699629,
'whislo': 0.042143774965502923
}
self.known_bootstrapped_ci = {
'cihi': 8.939577523357828,
'cilo': 1.8692703958676578,
}
self.known_whis3_res = {
'whishi': 42.232049135969874,
'whislo': 0.042143774965502923,
'fliers': np.array([92.55467075, 87.03819018]),
}
self.known_res_percentiles = {
'whislo': 0.1933685896907924,
'whishi': 42.232049135969874
}
self.known_res_range = {
'whislo': 0.042143774965502923,
'whishi': 92.554670752188699
}
def test_form_main_list(self):
assert isinstance(self.std_results, list)
def test_form_each_dict(self):
for res in self.std_results:
assert isinstance(res, dict)
def test_form_dict_keys(self):
for res in self.std_results:
assert set(res) <= set(self.known_keys)
def test_results_baseline(self):
res = self.std_results[0]
for key, value in self.known_nonbootstrapped_res.items():
assert_array_almost_equal(res[key], value)
def test_results_bootstrapped(self):
results = cbook.boxplot_stats(self.data, bootstrap=10000)
res = results[0]
for key, value in self.known_bootstrapped_ci.items():
assert_approx_equal(res[key], value)
def test_results_whiskers_float(self):
results = cbook.boxplot_stats(self.data, whis=3)
res = results[0]
for key, value in self.known_whis3_res.items():
assert_array_almost_equal(res[key], value)
def test_results_whiskers_range(self):
results = cbook.boxplot_stats(self.data, whis='range')
res = results[0]
for key, value in self.known_res_range.items():
assert_array_almost_equal(res[key], value)
def test_results_whiskers_percentiles(self):
results = cbook.boxplot_stats(self.data, whis=[5, 95])
res = results[0]
for key, value in self.known_res_percentiles.items():
assert_array_almost_equal(res[key], value)
def test_results_withlabels(self):
labels = ['Test1', 2, 'ardvark', 4]
results = cbook.boxplot_stats(self.data, labels=labels)
res = results[0]
for lab, res in zip(labels, results):
assert res['label'] == lab
results = cbook.boxplot_stats(self.data)
for res in results:
assert 'label' not in res
def test_label_error(self):
labels = [1, 2]
with pytest.raises(ValueError):
results = cbook.boxplot_stats(self.data, labels=labels)
def test_bad_dims(self):
data = np.random.normal(size=(34, 34, 34))
with pytest.raises(ValueError):
results = cbook.boxplot_stats(data)
def test_boxplot_stats_autorange_false(self):
x = np.zeros(shape=140)
x = np.hstack([-25, x, 25])
bstats_false = cbook.boxplot_stats(x, autorange=False)
bstats_true = cbook.boxplot_stats(x, autorange=True)
assert bstats_false[0]['whislo'] == 0
assert bstats_false[0]['whishi'] == 0
assert_array_almost_equal(bstats_false[0]['fliers'], [-25, 25])
assert bstats_true[0]['whislo'] == -25
assert bstats_true[0]['whishi'] == 25
assert_array_almost_equal(bstats_true[0]['fliers'], [])
class Test_callback_registry(object):
def setup(self):
self.signal = 'test'
self.callbacks = cbook.CallbackRegistry()
def connect(self, s, func):
return self.callbacks.connect(s, func)
def is_empty(self):
assert self.callbacks._func_cid_map == {}
assert self.callbacks.callbacks == {}
def is_not_empty(self):
assert self.callbacks._func_cid_map != {}
assert self.callbacks.callbacks != {}
def test_callback_complete(self):
# ensure we start with an empty registry
self.is_empty()
# create a class for testing
mini_me = Test_callback_registry()
# test that we can add a callback
cid1 = self.connect(self.signal, mini_me.dummy)
assert type(cid1) == int
self.is_not_empty()
# test that we don't add a second callback
cid2 = self.connect(self.signal, mini_me.dummy)
assert cid1 == cid2
self.is_not_empty()
assert len(self.callbacks._func_cid_map) == 1
assert len(self.callbacks.callbacks) == 1
del mini_me
# check we now have no callbacks registered
self.is_empty()
def dummy(self):
pass
def test_pickling(self):
assert hasattr(pickle.loads(pickle.dumps(cbook.CallbackRegistry())),
"callbacks")
def raising_cb_reg(func):
class TestException(Exception):
pass
def raising_function():
raise RuntimeError
def transformer(excp):
if isinstance(excp, RuntimeError):
raise TestException
raise excp
# default behavior
cb = cbook.CallbackRegistry()
cb.connect('foo', raising_function)
# old default
cb_old = cbook.CallbackRegistry(exception_handler=None)
cb_old.connect('foo', raising_function)
# filter
cb_filt = cbook.CallbackRegistry(exception_handler=transformer)
cb_filt.connect('foo', raising_function)
return pytest.mark.parametrize('cb, excp',
[[cb, None],
[cb_old, RuntimeError],
[cb_filt, TestException]])(func)
@raising_cb_reg
def test_callbackregistry_process_exception(cb, excp):
if excp is not None:
with pytest.raises(excp):
cb.process('foo')
else:
cb.process('foo')
def test_sanitize_sequence():
d = {'a': 1, 'b': 2, 'c': 3}
k = ['a', 'b', 'c']
v = [1, 2, 3]
i = [('a', 1), ('b', 2), ('c', 3)]
assert k == sorted(cbook.sanitize_sequence(d.keys()))
assert v == sorted(cbook.sanitize_sequence(d.values()))
assert i == sorted(cbook.sanitize_sequence(d.items()))
assert i == cbook.sanitize_sequence(i)
assert k == cbook.sanitize_sequence(k)
fail_mapping = (
({'a': 1}, {'forbidden': ('a')}),
({'a': 1}, {'required': ('b')}),
({'a': 1, 'b': 2}, {'required': ('a'), 'allowed': ()})
)
warn_passing_mapping = (
({'a': 1, 'b': 2}, {'a': 1}, {'alias_mapping': {'a': ['b']}}, 1),
({'a': 1, 'b': 2}, {'a': 1},
{'alias_mapping': {'a': ['b']}, 'allowed': ('a',)}, 1),
({'a': 1, 'b': 2}, {'a': 2}, {'alias_mapping': {'a': ['a', 'b']}}, 1),
({'a': 1, 'b': 2, 'c': 3}, {'a': 1, 'c': 3},
{'alias_mapping': {'a': ['b']}, 'required': ('a', )}, 1),
)
pass_mapping = (
({'a': 1, 'b': 2}, {'a': 1, 'b': 2}, {}),
({'b': 2}, {'a': 2}, {'alias_mapping': {'a': ['a', 'b']}}),
({'b': 2}, {'a': 2},
{'alias_mapping': {'a': ['b']}, 'forbidden': ('b', )}),
({'a': 1, 'c': 3}, {'a': 1, 'c': 3},
{'required': ('a', ), 'allowed': ('c', )}),
({'a': 1, 'c': 3}, {'a': 1, 'c': 3},
{'required': ('a', 'c'), 'allowed': ('c', )}),
({'a': 1, 'c': 3}, {'a': 1, 'c': 3},
{'required': ('a', 'c'), 'allowed': ('a', 'c')}),
({'a': 1, 'c': 3}, {'a': 1, 'c': 3},
{'required': ('a', 'c'), 'allowed': ()}),
({'a': 1, 'c': 3}, {'a': 1, 'c': 3}, {'required': ('a', 'c')}),
({'a': 1, 'c': 3}, {'a': 1, 'c': 3}, {'allowed': ('a', 'c')}),
)
@pytest.mark.parametrize('inp, kwargs_to_norm', fail_mapping)
def test_normalize_kwargs_fail(inp, kwargs_to_norm):
with pytest.raises(TypeError):
cbook.normalize_kwargs(inp, **kwargs_to_norm)
@pytest.mark.parametrize('inp, expected, kwargs_to_norm, warn_count',
warn_passing_mapping)
def test_normalize_kwargs_warn(inp, expected, kwargs_to_norm, warn_count):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
assert expected == cbook.normalize_kwargs(inp, **kwargs_to_norm)
assert len(w) == warn_count
@pytest.mark.parametrize('inp, expected, kwargs_to_norm',
pass_mapping)
def test_normalize_kwargs_pass(inp, expected, kwargs_to_norm):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
assert expected == cbook.normalize_kwargs(inp, **kwargs_to_norm)
assert len(w) == 0
def test_warn_external_frame_embedded_python():
with patch.object(cbook, "sys") as mock_sys:
mock_sys._getframe = Mock(return_value=None)
with warnings.catch_warnings(record=True) as w:
cbook._warn_external("dummy")
assert len(w) == 1
assert str(w[0].message) == "dummy"
def test_to_prestep():
x = np.arange(4)
y1 = np.arange(4)
y2 = np.arange(4)[::-1]
xs, y1s, y2s = cbook.pts_to_prestep(x, y1, y2)
x_target = np.asarray([0, 0, 1, 1, 2, 2, 3], dtype='float')
y1_target = np.asarray([0, 1, 1, 2, 2, 3, 3], dtype='float')
y2_target = np.asarray([3, 2, 2, 1, 1, 0, 0], dtype='float')
assert_array_equal(x_target, xs)
assert_array_equal(y1_target, y1s)
assert_array_equal(y2_target, y2s)
xs, y1s = cbook.pts_to_prestep(x, y1)
assert_array_equal(x_target, xs)
assert_array_equal(y1_target, y1s)
def test_to_prestep_empty():
steps = cbook.pts_to_prestep([], [])
assert steps.shape == (2, 0)
def test_to_poststep():
x = np.arange(4)
y1 = np.arange(4)
y2 = np.arange(4)[::-1]
xs, y1s, y2s = cbook.pts_to_poststep(x, y1, y2)
x_target = np.asarray([0, 1, 1, 2, 2, 3, 3], dtype='float')
y1_target = np.asarray([0, 0, 1, 1, 2, 2, 3], dtype='float')
y2_target = np.asarray([3, 3, 2, 2, 1, 1, 0], dtype='float')
assert_array_equal(x_target, xs)
assert_array_equal(y1_target, y1s)
assert_array_equal(y2_target, y2s)
xs, y1s = cbook.pts_to_poststep(x, y1)
assert_array_equal(x_target, xs)
assert_array_equal(y1_target, y1s)
def test_to_poststep_empty():
steps = cbook.pts_to_poststep([], [])
assert steps.shape == (2, 0)
def test_to_midstep():
x = np.arange(4)
y1 = np.arange(4)
y2 = np.arange(4)[::-1]
xs, y1s, y2s = cbook.pts_to_midstep(x, y1, y2)
x_target = np.asarray([0, .5, .5, 1.5, 1.5, 2.5, 2.5, 3], dtype='float')
y1_target = np.asarray([0, 0, 1, 1, 2, 2, 3, 3], dtype='float')
y2_target = np.asarray([3, 3, 2, 2, 1, 1, 0, 0], dtype='float')
assert_array_equal(x_target, xs)
assert_array_equal(y1_target, y1s)
assert_array_equal(y2_target, y2s)
xs, y1s = cbook.pts_to_midstep(x, y1)
assert_array_equal(x_target, xs)
assert_array_equal(y1_target, y1s)
def test_to_midstep_empty():
steps = cbook.pts_to_midstep([], [])
assert steps.shape == (2, 0)
@pytest.mark.parametrize(
"args",
[(np.arange(12).reshape(3, 4), 'a'),
(np.arange(12), 'a'),
(np.arange(12), np.arange(3))])
def test_step_fails(args):
with pytest.raises(ValueError):
cbook.pts_to_prestep(*args)
def test_grouper():
class dummy():
pass
a, b, c, d, e = objs = [dummy() for j in range(5)]
g = cbook.Grouper()
g.join(*objs)
assert set(list(g)[0]) == set(objs)
assert set(g.get_siblings(a)) == set(objs)
for other in objs[1:]:
assert g.joined(a, other)
g.remove(a)
for other in objs[1:]:
assert not g.joined(a, other)
for A, B in itertools.product(objs[1:], objs[1:]):
assert g.joined(A, B)
def test_grouper_private():
class dummy():
pass
objs = [dummy() for j in range(5)]
g = cbook.Grouper()
g.join(*objs)
# reach in and touch the internals !
mapping = g._mapping
for o in objs:
assert ref(o) in mapping
base_set = mapping[ref(objs[0])]
for o in objs[1:]:
assert mapping[ref(o)] is base_set
def test_flatiter():
x = np.arange(5)
it = x.flat
assert 0 == next(it)
assert 1 == next(it)
ret = cbook.safe_first_element(it)
assert ret == 0
assert 0 == next(it)
assert 1 == next(it)
def test_safe_first_element_pandas_series(pd):
# delibrately create a pandas series with index not starting from 0
s = pd.Series(range(5), index=range(10, 15))
actual = cbook.safe_first_element(s)
assert actual == 0
@@ -0,0 +1,671 @@
"""
Tests specific to the collections module.
"""
import io
import platform
import numpy as np
from numpy.testing import assert_array_equal, assert_array_almost_equal
import pytest
import matplotlib.pyplot as plt
import matplotlib.collections as mcollections
import matplotlib.transforms as mtransforms
from matplotlib.collections import Collection, LineCollection, EventCollection
from matplotlib.testing.decorators import image_comparison
def generate_EventCollection_plot():
'''
generate the initial collection and plot it
'''
positions = np.array([0., 1., 2., 3., 5., 8., 13., 21.])
extra_positions = np.array([34., 55., 89.])
orientation = 'horizontal'
lineoffset = 1
linelength = .5
linewidth = 2
color = [1, 0, 0, 1]
linestyle = 'solid'
antialiased = True
coll = EventCollection(positions,
orientation=orientation,
lineoffset=lineoffset,
linelength=linelength,
linewidth=linewidth,
color=color,
linestyle=linestyle,
antialiased=antialiased
)
fig = plt.figure()
splt = fig.add_subplot(1, 1, 1)
splt.add_collection(coll)
splt.set_title('EventCollection: default')
props = {'positions': positions,
'extra_positions': extra_positions,
'orientation': orientation,
'lineoffset': lineoffset,
'linelength': linelength,
'linewidth': linewidth,
'color': color,
'linestyle': linestyle,
'antialiased': antialiased
}
splt.set_xlim(-1, 22)
splt.set_ylim(0, 2)
return splt, coll, props
@image_comparison(baseline_images=['EventCollection_plot__default'])
def test__EventCollection__get_segments():
'''
check to make sure the default segments have the correct coordinates
'''
_, coll, props = generate_EventCollection_plot()
check_segments(coll,
props['positions'],
props['linelength'],
props['lineoffset'],
props['orientation'])
def test__EventCollection__get_positions():
'''
check to make sure the default positions match the input positions
'''
_, coll, props = generate_EventCollection_plot()
np.testing.assert_array_equal(props['positions'], coll.get_positions())
def test__EventCollection__get_orientation():
'''
check to make sure the default orientation matches the input
orientation
'''
_, coll, props = generate_EventCollection_plot()
assert props['orientation'] == coll.get_orientation()
def test__EventCollection__is_horizontal():
'''
check to make sure the default orientation matches the input
orientation
'''
_, coll, _ = generate_EventCollection_plot()
assert coll.is_horizontal()
def test__EventCollection__get_linelength():
'''
check to make sure the default linelength matches the input linelength
'''
_, coll, props = generate_EventCollection_plot()
assert props['linelength'] == coll.get_linelength()
def test__EventCollection__get_lineoffset():
'''
check to make sure the default lineoffset matches the input lineoffset
'''
_, coll, props = generate_EventCollection_plot()
assert props['lineoffset'] == coll.get_lineoffset()
def test__EventCollection__get_linestyle():
'''
check to make sure the default linestyle matches the input linestyle
'''
_, coll, _ = generate_EventCollection_plot()
assert coll.get_linestyle() == [(None, None)]
def test__EventCollection__get_color():
'''
check to make sure the default color matches the input color
'''
_, coll, props = generate_EventCollection_plot()
np.testing.assert_array_equal(props['color'], coll.get_color())
check_allprop_array(coll.get_colors(), props['color'])
@image_comparison(baseline_images=['EventCollection_plot__set_positions'])
def test__EventCollection__set_positions():
'''
check to make sure set_positions works properly
'''
splt, coll, props = generate_EventCollection_plot()
new_positions = np.hstack([props['positions'], props['extra_positions']])
coll.set_positions(new_positions)
np.testing.assert_array_equal(new_positions, coll.get_positions())
check_segments(coll, new_positions,
props['linelength'],
props['lineoffset'],
props['orientation'])
splt.set_title('EventCollection: set_positions')
splt.set_xlim(-1, 90)
@image_comparison(baseline_images=['EventCollection_plot__add_positions'])
def test__EventCollection__add_positions():
'''
check to make sure add_positions works properly
'''
splt, coll, props = generate_EventCollection_plot()
new_positions = np.hstack([props['positions'],
props['extra_positions'][0]])
coll.add_positions(props['extra_positions'][0])
np.testing.assert_array_equal(new_positions, coll.get_positions())
check_segments(coll,
new_positions,
props['linelength'],
props['lineoffset'],
props['orientation'])
splt.set_title('EventCollection: add_positions')
splt.set_xlim(-1, 35)
@image_comparison(baseline_images=['EventCollection_plot__append_positions'])
def test__EventCollection__append_positions():
'''
check to make sure append_positions works properly
'''
splt, coll, props = generate_EventCollection_plot()
new_positions = np.hstack([props['positions'],
props['extra_positions'][2]])
coll.append_positions(props['extra_positions'][2])
np.testing.assert_array_equal(new_positions, coll.get_positions())
check_segments(coll,
new_positions,
props['linelength'],
props['lineoffset'],
props['orientation'])
splt.set_title('EventCollection: append_positions')
splt.set_xlim(-1, 90)
@image_comparison(baseline_images=['EventCollection_plot__extend_positions'])
def test__EventCollection__extend_positions():
'''
check to make sure extend_positions works properly
'''
splt, coll, props = generate_EventCollection_plot()
new_positions = np.hstack([props['positions'],
props['extra_positions'][1:]])
coll.extend_positions(props['extra_positions'][1:])
np.testing.assert_array_equal(new_positions, coll.get_positions())
check_segments(coll,
new_positions,
props['linelength'],
props['lineoffset'],
props['orientation'])
splt.set_title('EventCollection: extend_positions')
splt.set_xlim(-1, 90)
@image_comparison(baseline_images=['EventCollection_plot__switch_orientation'])
def test__EventCollection__switch_orientation():
'''
check to make sure switch_orientation works properly
'''
splt, coll, props = generate_EventCollection_plot()
new_orientation = 'vertical'
coll.switch_orientation()
assert new_orientation == coll.get_orientation()
assert not coll.is_horizontal()
new_positions = coll.get_positions()
check_segments(coll,
new_positions,
props['linelength'],
props['lineoffset'], new_orientation)
splt.set_title('EventCollection: switch_orientation')
splt.set_ylim(-1, 22)
splt.set_xlim(0, 2)
@image_comparison(
baseline_images=['EventCollection_plot__switch_orientation__2x'])
def test__EventCollection__switch_orientation_2x():
'''
check to make sure calling switch_orientation twice sets the
orientation back to the default
'''
splt, coll, props = generate_EventCollection_plot()
coll.switch_orientation()
coll.switch_orientation()
new_positions = coll.get_positions()
assert props['orientation'] == coll.get_orientation()
assert coll.is_horizontal()
np.testing.assert_array_equal(props['positions'], new_positions)
check_segments(coll,
new_positions,
props['linelength'],
props['lineoffset'],
props['orientation'])
splt.set_title('EventCollection: switch_orientation 2x')
@image_comparison(baseline_images=['EventCollection_plot__set_orientation'])
def test__EventCollection__set_orientation():
'''
check to make sure set_orientation works properly
'''
splt, coll, props = generate_EventCollection_plot()
new_orientation = 'vertical'
coll.set_orientation(new_orientation)
assert new_orientation == coll.get_orientation()
assert not coll.is_horizontal()
check_segments(coll,
props['positions'],
props['linelength'],
props['lineoffset'],
new_orientation)
splt.set_title('EventCollection: set_orientation')
splt.set_ylim(-1, 22)
splt.set_xlim(0, 2)
@image_comparison(baseline_images=['EventCollection_plot__set_linelength'])
def test__EventCollection__set_linelength():
'''
check to make sure set_linelength works properly
'''
splt, coll, props = generate_EventCollection_plot()
new_linelength = 15
coll.set_linelength(new_linelength)
assert new_linelength == coll.get_linelength()
check_segments(coll,
props['positions'],
new_linelength,
props['lineoffset'],
props['orientation'])
splt.set_title('EventCollection: set_linelength')
splt.set_ylim(-20, 20)
@image_comparison(baseline_images=['EventCollection_plot__set_lineoffset'])
def test__EventCollection__set_lineoffset():
'''
check to make sure set_lineoffset works properly
'''
splt, coll, props = generate_EventCollection_plot()
new_lineoffset = -5.
coll.set_lineoffset(new_lineoffset)
assert new_lineoffset == coll.get_lineoffset()
check_segments(coll,
props['positions'],
props['linelength'],
new_lineoffset,
props['orientation'])
splt.set_title('EventCollection: set_lineoffset')
splt.set_ylim(-6, -4)
@image_comparison(baseline_images=['EventCollection_plot__set_linestyle'])
def test__EventCollection__set_linestyle():
'''
check to make sure set_linestyle works properly
'''
splt, coll, _ = generate_EventCollection_plot()
new_linestyle = 'dashed'
coll.set_linestyle(new_linestyle)
assert coll.get_linestyle() == [(0, (6.0, 6.0))]
splt.set_title('EventCollection: set_linestyle')
@image_comparison(baseline_images=['EventCollection_plot__set_ls_dash'],
remove_text=True)
def test__EventCollection__set_linestyle_single_dash():
'''
check to make sure set_linestyle accepts a single dash pattern
'''
splt, coll, _ = generate_EventCollection_plot()
new_linestyle = (0, (6., 6.))
coll.set_linestyle(new_linestyle)
assert coll.get_linestyle() == [(0, (6.0, 6.0))]
splt.set_title('EventCollection: set_linestyle')
@image_comparison(baseline_images=['EventCollection_plot__set_linewidth'])
def test__EventCollection__set_linewidth():
'''
check to make sure set_linestyle works properly
'''
splt, coll, _ = generate_EventCollection_plot()
new_linewidth = 5
coll.set_linewidth(new_linewidth)
assert coll.get_linewidth() == new_linewidth
splt.set_title('EventCollection: set_linewidth')
@image_comparison(baseline_images=['EventCollection_plot__set_color'])
def test__EventCollection__set_color():
'''
check to make sure set_color works properly
'''
splt, coll, _ = generate_EventCollection_plot()
new_color = np.array([0, 1, 1, 1])
coll.set_color(new_color)
np.testing.assert_array_equal(new_color, coll.get_color())
check_allprop_array(coll.get_colors(), new_color)
splt.set_title('EventCollection: set_color')
def check_segments(coll, positions, linelength, lineoffset, orientation):
'''
check to make sure all values in the segment are correct, given a
particular set of inputs
note: this is not a test, it is used by tests
'''
segments = coll.get_segments()
if (orientation.lower() == 'horizontal'
or orientation.lower() == 'none' or orientation is None):
# if horizontal, the position in is in the y-axis
pos1 = 1
pos2 = 0
elif orientation.lower() == 'vertical':
# if vertical, the position in is in the x-axis
pos1 = 0
pos2 = 1
else:
raise ValueError("orientation must be 'horizontal' or 'vertical'")
# test to make sure each segment is correct
for i, segment in enumerate(segments):
assert segment[0, pos1] == lineoffset + linelength / 2
assert segment[1, pos1] == lineoffset - linelength / 2
assert segment[0, pos2] == positions[i]
assert segment[1, pos2] == positions[i]
def check_allprop_array(values, target):
'''
check to make sure all values match the given target if arrays
note: this is not a test, it is used by tests
'''
for value in values:
np.testing.assert_array_equal(value, target)
def test_null_collection_datalim():
col = mcollections.PathCollection([])
col_data_lim = col.get_datalim(mtransforms.IdentityTransform())
assert_array_equal(col_data_lim.get_points(),
mtransforms.Bbox.null().get_points())
def test_add_collection():
# Test if data limits are unchanged by adding an empty collection.
# Github issue #1490, pull #1497.
plt.figure()
ax = plt.axes()
coll = ax.scatter([0, 1], [0, 1])
ax.add_collection(coll)
bounds = ax.dataLim.bounds
coll = ax.scatter([], [])
assert ax.dataLim.bounds == bounds
def test_quiver_limits():
ax = plt.axes()
x, y = np.arange(8), np.arange(10)
u = v = np.linspace(0, 10, 80).reshape(10, 8)
q = plt.quiver(x, y, u, v)
assert q.get_datalim(ax.transData).bounds == (0., 0., 7., 9.)
plt.figure()
ax = plt.axes()
x = np.linspace(-5, 10, 20)
y = np.linspace(-2, 4, 10)
y, x = np.meshgrid(y, x)
trans = mtransforms.Affine2D().translate(25, 32) + ax.transData
plt.quiver(x, y, np.sin(x), np.cos(y), transform=trans)
assert ax.dataLim.bounds == (20.0, 30.0, 15.0, 6.0)
def test_barb_limits():
ax = plt.axes()
x = np.linspace(-5, 10, 20)
y = np.linspace(-2, 4, 10)
y, x = np.meshgrid(y, x)
trans = mtransforms.Affine2D().translate(25, 32) + ax.transData
plt.barbs(x, y, np.sin(x), np.cos(y), transform=trans)
# The calculated bounds are approximately the bounds of the original data,
# this is because the entire path is taken into account when updating the
# datalim.
assert_array_almost_equal(ax.dataLim.bounds, (20, 30, 15, 6),
decimal=1)
@image_comparison(baseline_images=['EllipseCollection_test_image'],
extensions=['png'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
remove_text=True)
def test_EllipseCollection():
# Test basic functionality
fig, ax = plt.subplots()
x = np.arange(4)
y = np.arange(3)
X, Y = np.meshgrid(x, y)
XY = np.vstack((X.ravel(), Y.ravel())).T
ww = X / x[-1]
hh = Y / y[-1]
aa = np.ones_like(ww) * 20 # first axis is 20 degrees CCW from x axis
ec = mcollections.EllipseCollection(ww, hh, aa,
units='x',
offsets=XY,
transOffset=ax.transData,
facecolors='none')
ax.add_collection(ec)
ax.autoscale_view()
@image_comparison(baseline_images=['polycollection_close'],
extensions=['png'], remove_text=True)
def test_polycollection_close():
from mpl_toolkits.mplot3d import Axes3D
vertsQuad = [
[[0., 0.], [0., 1.], [1., 1.], [1., 0.]],
[[0., 1.], [2., 3.], [2., 2.], [1., 1.]],
[[2., 2.], [2., 3.], [4., 1.], [3., 1.]],
[[3., 0.], [3., 1.], [4., 1.], [4., 0.]]]
fig = plt.figure()
ax = Axes3D(fig)
colors = ['r', 'g', 'b', 'y', 'k']
zpos = list(range(5))
poly = mcollections.PolyCollection(
vertsQuad * len(zpos), linewidth=0.25)
poly.set_alpha(0.7)
# need to have a z-value for *each* polygon = element!
zs = []
cs = []
for z, c in zip(zpos, colors):
zs.extend([z] * len(vertsQuad))
cs.extend([c] * len(vertsQuad))
poly.set_color(cs)
ax.add_collection3d(poly, zs=zs, zdir='y')
# axis limit settings:
ax.set_xlim3d(0, 4)
ax.set_zlim3d(0, 3)
ax.set_ylim3d(0, 4)
@image_comparison(baseline_images=['regularpolycollection_rotate'],
extensions=['png'], remove_text=True)
def test_regularpolycollection_rotate():
xx, yy = np.mgrid[:10, :10]
xy_points = np.transpose([xx.flatten(), yy.flatten()])
rotations = np.linspace(0, 2*np.pi, len(xy_points))
fig, ax = plt.subplots()
for xy, alpha in zip(xy_points, rotations):
col = mcollections.RegularPolyCollection(
4, sizes=(100,), rotation=alpha,
offsets=[xy], transOffset=ax.transData)
ax.add_collection(col, autolim=True)
ax.autoscale_view()
@image_comparison(baseline_images=['regularpolycollection_scale'],
extensions=['png'], remove_text=True)
def test_regularpolycollection_scale():
# See issue #3860
class SquareCollection(mcollections.RegularPolyCollection):
def __init__(self, **kwargs):
super().__init__(4, rotation=np.pi/4., **kwargs)
def get_transform(self):
"""Return transform scaling circle areas to data space."""
ax = self.axes
pts2pixels = 72.0 / ax.figure.dpi
scale_x = pts2pixels * ax.bbox.width / ax.viewLim.width
scale_y = pts2pixels * ax.bbox.height / ax.viewLim.height
return mtransforms.Affine2D().scale(scale_x, scale_y)
fig, ax = plt.subplots()
xy = [(0, 0)]
# Unit square has a half-diagonal of `1 / sqrt(2)`, so `pi * r**2`
# equals...
circle_areas = [np.pi / 2]
squares = SquareCollection(sizes=circle_areas, offsets=xy,
transOffset=ax.transData)
ax.add_collection(squares, autolim=True)
ax.axis([-1, 1, -1, 1])
def test_picking():
fig, ax = plt.subplots()
col = ax.scatter([0], [0], [1000], picker=True)
fig.savefig(io.BytesIO(), dpi=fig.dpi)
class MouseEvent(object):
pass
event = MouseEvent()
event.x = 325
event.y = 240
found, indices = col.contains(event)
assert found
assert_array_equal(indices['ind'], [0])
def test_linestyle_single_dashes():
plt.scatter([0, 1, 2], [0, 1, 2], linestyle=(0., [2., 2.]))
plt.draw()
@image_comparison(baseline_images=['size_in_xy'], remove_text=True,
extensions=['png'])
def test_size_in_xy():
fig, ax = plt.subplots()
widths, heights, angles = (10, 10), 10, 0
widths = 10, 10
coords = [(10, 10), (15, 15)]
e = mcollections.EllipseCollection(
widths, heights, angles,
units='xy',
offsets=coords,
transOffset=ax.transData)
ax.add_collection(e)
ax.set_xlim(0, 30)
ax.set_ylim(0, 30)
def test_pandas_indexing(pd):
# Should not fail break when faced with a
# non-zero indexed series
index = [11, 12, 13]
ec = fc = pd.Series(['red', 'blue', 'green'], index=index)
lw = pd.Series([1, 2, 3], index=index)
ls = pd.Series(['solid', 'dashed', 'dashdot'], index=index)
aa = pd.Series([True, False, True], index=index)
Collection(edgecolors=ec)
Collection(facecolors=fc)
Collection(linewidths=lw)
Collection(linestyles=ls)
Collection(antialiaseds=aa)
@pytest.mark.style('default')
def test_lslw_bcast():
col = mcollections.PathCollection([])
col.set_linestyles(['-', '-'])
col.set_linewidths([1, 2, 3])
assert col.get_linestyles() == [(None, None)] * 6
assert col.get_linewidths() == [1, 2, 3] * 2
col.set_linestyles(['-', '-', '-'])
assert col.get_linestyles() == [(None, None)] * 3
assert (col.get_linewidths() == [1, 2, 3]).all()
@pytest.mark.style('default')
def test_capstyle():
col = mcollections.PathCollection([], capstyle='round')
assert col.get_capstyle() == 'round'
col.set_capstyle('butt')
assert col.get_capstyle() == 'butt'
@pytest.mark.style('default')
def test_joinstyle():
col = mcollections.PathCollection([], joinstyle='round')
assert col.get_joinstyle() == 'round'
col.set_joinstyle('miter')
assert col.get_joinstyle() == 'miter'
@image_comparison(baseline_images=['cap_and_joinstyle'],
extensions=['png'])
def test_cap_and_joinstyle_image():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlim([-0.5, 1.5])
ax.set_ylim([-0.5, 2.5])
x = np.array([0.0, 1.0, 0.5])
ys = np.array([[0.0], [0.5], [1.0]]) + np.array([[0.0, 0.0, 1.0]])
segs = np.zeros((3, 3, 2))
segs[:, :, 0] = x
segs[:, :, 1] = ys
line_segments = LineCollection(segs, linewidth=[10, 15, 20])
line_segments.set_capstyle("round")
line_segments.set_joinstyle("miter")
ax.add_collection(line_segments)
ax.set_title('Line collection with customized caps and joinstyle')
@image_comparison(baseline_images=['scatter_post_alpha'],
extensions=['png'], remove_text=True,
style='default')
def test_scatter_post_alpha():
fig, ax = plt.subplots()
sc = ax.scatter(range(5), range(5), c=range(5))
# this needs to be here to update internal state
fig.canvas.draw()
sc.set_alpha(.1)
@@ -0,0 +1,448 @@
import numpy as np
import pytest
from matplotlib import rc_context
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
from matplotlib.colors import BoundaryNorm, LogNorm, PowerNorm
from matplotlib.cm import get_cmap
from matplotlib.colorbar import ColorbarBase
from matplotlib.ticker import LogLocator, LogFormatter
def _get_cmap_norms():
"""
Define a colormap and appropriate norms for each of the four
possible settings of the extend keyword.
Helper function for _colorbar_extension_shape and
colorbar_extension_length.
"""
# Create a color map and specify the levels it represents.
cmap = get_cmap("RdBu", lut=5)
clevs = [-5., -2.5, -.5, .5, 1.5, 3.5]
# Define norms for the color maps.
norms = dict()
norms['neither'] = BoundaryNorm(clevs, len(clevs) - 1)
norms['min'] = BoundaryNorm([-10] + clevs[1:], len(clevs) - 1)
norms['max'] = BoundaryNorm(clevs[:-1] + [10], len(clevs) - 1)
norms['both'] = BoundaryNorm([-10] + clevs[1:-1] + [10], len(clevs) - 1)
return cmap, norms
def _colorbar_extension_shape(spacing):
'''
Produce 4 colorbars with rectangular extensions for either uniform
or proportional spacing.
Helper function for test_colorbar_extension_shape.
'''
# Get a colormap and appropriate norms for each extension type.
cmap, norms = _get_cmap_norms()
# Create a figure and adjust whitespace for subplots.
fig = plt.figure()
fig.subplots_adjust(hspace=4)
for i, extension_type in enumerate(('neither', 'min', 'max', 'both')):
# Get the appropriate norm and use it to get colorbar boundaries.
norm = norms[extension_type]
boundaries = values = norm.boundaries
# Create a subplot.
cax = fig.add_subplot(4, 1, i + 1)
# Generate the colorbar.
cb = ColorbarBase(cax, cmap=cmap, norm=norm,
boundaries=boundaries, values=values,
extend=extension_type, extendrect=True,
orientation='horizontal', spacing=spacing)
# Turn off text and ticks.
cax.tick_params(left=False, labelleft=False,
bottom=False, labelbottom=False)
# Return the figure to the caller.
return fig
def _colorbar_extension_length(spacing):
'''
Produce 12 colorbars with variable length extensions for either
uniform or proportional spacing.
Helper function for test_colorbar_extension_length.
'''
# Get a colormap and appropriate norms for each extension type.
cmap, norms = _get_cmap_norms()
# Create a figure and adjust whitespace for subplots.
fig = plt.figure()
fig.subplots_adjust(hspace=.6)
for i, extension_type in enumerate(('neither', 'min', 'max', 'both')):
# Get the appropriate norm and use it to get colorbar boundaries.
norm = norms[extension_type]
boundaries = values = norm.boundaries
for j, extendfrac in enumerate((None, 'auto', 0.1)):
# Create a subplot.
cax = fig.add_subplot(12, 1, i*3 + j + 1)
# Generate the colorbar.
ColorbarBase(cax, cmap=cmap, norm=norm,
boundaries=boundaries, values=values,
extend=extension_type, extendfrac=extendfrac,
orientation='horizontal', spacing=spacing)
# Turn off text and ticks.
cax.tick_params(left=False, labelleft=False,
bottom=False, labelbottom=False)
# Return the figure to the caller.
return fig
@image_comparison(
baseline_images=['colorbar_extensions_shape_uniform',
'colorbar_extensions_shape_proportional'],
extensions=['png'])
def test_colorbar_extension_shape():
'''Test rectangular colorbar extensions.'''
# Create figures for uniform and proportionally spaced colorbars.
_colorbar_extension_shape('uniform')
_colorbar_extension_shape('proportional')
@image_comparison(baseline_images=['colorbar_extensions_uniform',
'colorbar_extensions_proportional'],
extensions=['png'])
def test_colorbar_extension_length():
'''Test variable length colorbar extensions.'''
# Create figures for uniform and proportionally spaced colorbars.
_colorbar_extension_length('uniform')
_colorbar_extension_length('proportional')
@image_comparison(baseline_images=['cbar_with_orientation',
'cbar_locationing',
'double_cbar',
'cbar_sharing',
],
extensions=['png'], remove_text=True,
savefig_kwarg={'dpi': 40})
def test_colorbar_positioning():
data = np.arange(1200).reshape(30, 40)
levels = [0, 200, 400, 600, 800, 1000, 1200]
# -------------------
plt.figure()
plt.contourf(data, levels=levels)
plt.colorbar(orientation='horizontal', use_gridspec=False)
locations = ['left', 'right', 'top', 'bottom']
plt.figure()
for i, location in enumerate(locations):
plt.subplot(2, 2, i + 1)
plt.contourf(data, levels=levels)
plt.colorbar(location=location, use_gridspec=False)
# -------------------
plt.figure()
# make some other data (random integers)
data_2nd = np.array([[2, 3, 2, 3], [1.5, 2, 2, 3], [2, 3, 3, 4]])
# make the random data expand to the shape of the main data
data_2nd = np.repeat(np.repeat(data_2nd, 10, axis=1), 10, axis=0)
color_mappable = plt.contourf(data, levels=levels, extend='both')
# test extend frac here
hatch_mappable = plt.contourf(data_2nd, levels=[1, 2, 3], colors='none',
hatches=['/', 'o', '+'], extend='max')
plt.contour(hatch_mappable, colors='black')
plt.colorbar(color_mappable, location='left', label='variable 1',
use_gridspec=False)
plt.colorbar(hatch_mappable, location='right', label='variable 2',
use_gridspec=False)
# -------------------
plt.figure()
ax1 = plt.subplot(211, anchor='NE', aspect='equal')
plt.contourf(data, levels=levels)
ax2 = plt.subplot(223)
plt.contourf(data, levels=levels)
ax3 = plt.subplot(224)
plt.contourf(data, levels=levels)
plt.colorbar(ax=[ax2, ax3, ax1], location='right', pad=0.0, shrink=0.5,
panchor=False, use_gridspec=False)
plt.colorbar(ax=[ax2, ax3, ax1], location='left', shrink=0.5,
panchor=False, use_gridspec=False)
plt.colorbar(ax=[ax1], location='bottom', panchor=False,
anchor=(0.8, 0.5), shrink=0.6, use_gridspec=False)
@image_comparison(baseline_images=['cbar_with_subplots_adjust'],
extensions=['png'], remove_text=True,
savefig_kwarg={'dpi': 40})
def test_gridspec_make_colorbar():
plt.figure()
data = np.arange(1200).reshape(30, 40)
levels = [0, 200, 400, 600, 800, 1000, 1200]
plt.subplot(121)
plt.contourf(data, levels=levels)
plt.colorbar(use_gridspec=True, orientation='vertical')
plt.subplot(122)
plt.contourf(data, levels=levels)
plt.colorbar(use_gridspec=True, orientation='horizontal')
plt.subplots_adjust(top=0.95, right=0.95, bottom=0.2, hspace=0.25)
@image_comparison(baseline_images=['colorbar_single_scatter'],
extensions=['png'], remove_text=True,
savefig_kwarg={'dpi': 40})
def test_colorbar_single_scatter():
# Issue #2642: if a path collection has only one entry,
# the norm scaling within the colorbar must ensure a
# finite range, otherwise a zero denominator will occur in _locate.
plt.figure()
x = np.arange(4)
y = x.copy()
z = np.ma.masked_greater(np.arange(50, 54), 50)
cmap = plt.get_cmap('jet', 16)
cs = plt.scatter(x, y, z, c=z, cmap=cmap)
plt.colorbar(cs)
@pytest.mark.parametrize('use_gridspec', [False, True],
ids=['no gridspec', 'with gridspec'])
def test_remove_from_figure(use_gridspec):
"""
Test `remove_from_figure` with the specified ``use_gridspec`` setting
"""
fig, ax = plt.subplots()
sc = ax.scatter([1, 2], [3, 4], cmap="spring")
sc.set_array(np.array([5, 6]))
pre_figbox = np.array(ax.figbox)
cb = fig.colorbar(sc, use_gridspec=use_gridspec)
fig.subplots_adjust()
cb.remove()
fig.subplots_adjust()
post_figbox = np.array(ax.figbox)
assert (pre_figbox == post_figbox).all()
def test_colorbarbase():
# smoke test from #3805
ax = plt.gca()
ColorbarBase(ax, plt.cm.bone)
@image_comparison(
baseline_images=['colorbar_closed_patch'],
remove_text=True)
def test_colorbar_closed_patch():
fig = plt.figure(figsize=(8, 6))
ax1 = fig.add_axes([0.05, 0.85, 0.9, 0.1])
ax2 = fig.add_axes([0.1, 0.65, 0.75, 0.1])
ax3 = fig.add_axes([0.05, 0.45, 0.9, 0.1])
ax4 = fig.add_axes([0.05, 0.25, 0.9, 0.1])
ax5 = fig.add_axes([0.05, 0.05, 0.9, 0.1])
cmap = get_cmap("RdBu", lut=5)
im = ax1.pcolormesh(np.linspace(0, 10, 16).reshape((4, 4)), cmap=cmap)
values = np.linspace(0, 10, 5)
with rc_context({'axes.linewidth': 16}):
plt.colorbar(im, cax=ax2, cmap=cmap, orientation='horizontal',
extend='both', extendfrac=0.5, values=values)
plt.colorbar(im, cax=ax3, cmap=cmap, orientation='horizontal',
extend='both', values=values)
plt.colorbar(im, cax=ax4, cmap=cmap, orientation='horizontal',
extend='both', extendrect=True, values=values)
plt.colorbar(im, cax=ax5, cmap=cmap, orientation='horizontal',
extend='neither', values=values)
def test_colorbar_ticks():
# test fix for #5673
fig, ax = plt.subplots()
x = np.arange(-3.0, 4.001)
y = np.arange(-4.0, 3.001)
X, Y = np.meshgrid(x, y)
Z = X * Y
clevs = np.array([-12, -5, 0, 5, 12], dtype=float)
colors = ['r', 'g', 'b', 'c']
cs = ax.contourf(X, Y, Z, clevs, colors=colors)
cbar = fig.colorbar(cs, ax=ax, extend='neither',
orientation='horizontal', ticks=clevs)
assert len(cbar.ax.xaxis.get_ticklocs()) == len(clevs)
def test_colorbar_minorticks_on_off():
# test for github issue #11510 and PR #11584
np.random.seed(seed=12345)
data = np.random.randn(20, 20)
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt.subplots()
# purposefully setting vmin and vmax to odd fractions
# so as to check for the correct locations of the minor ticks
im = ax.pcolormesh(data, vmin=-2.3, vmax=3.3)
cbar = fig.colorbar(im, extend='both')
cbar.minorticks_on()
correct_minorticklocs = np.array([-2.2, -1.8, -1.6, -1.4, -1.2, -0.8,
-0.6, -0.4, -0.2, 0.2, 0.4, 0.6,
0.8, 1.2, 1.4, 1.6, 1.8, 2.2, 2.4,
2.6, 2.8, 3.2])
# testing after minorticks_on()
np.testing.assert_almost_equal(cbar.ax.yaxis.get_minorticklocs(),
correct_minorticklocs)
cbar.minorticks_off()
# testing after minorticks_off()
np.testing.assert_almost_equal(cbar.ax.yaxis.get_minorticklocs(),
np.array([]))
im.set_clim(vmin=-1.2, vmax=1.2)
cbar.minorticks_on()
correct_minorticklocs = np.array([-1.2, -1.1, -0.9, -0.8, -0.7, -0.6,
-0.4, -0.3, -0.2, -0.1, 0.1, 0.2,
0.3, 0.4, 0.6, 0.7, 0.8, 0.9,
1.1, 1.2])
np.testing.assert_almost_equal(cbar.ax.yaxis.get_minorticklocs(),
correct_minorticklocs)
def test_colorbar_autoticks():
# Test new autotick modes. Needs to be classic because
# non-classic doesn't go this route.
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt.subplots(2, 1)
x = np.arange(-3.0, 4.001)
y = np.arange(-4.0, 3.001)
X, Y = np.meshgrid(x, y)
Z = X * Y
pcm = ax[0].pcolormesh(X, Y, Z)
cbar = fig.colorbar(pcm, ax=ax[0], extend='both',
orientation='vertical')
pcm = ax[1].pcolormesh(X, Y, Z)
cbar2 = fig.colorbar(pcm, ax=ax[1], extend='both',
orientation='vertical', shrink=0.4)
np.testing.assert_almost_equal(cbar.ax.yaxis.get_ticklocs(),
np.arange(-10, 11., 5.))
np.testing.assert_almost_equal(cbar2.ax.yaxis.get_ticklocs(),
np.arange(-10, 11., 10.))
def test_colorbar_autotickslog():
# Test new autotick modes...
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt.subplots(2, 1)
x = np.arange(-3.0, 4.001)
y = np.arange(-4.0, 3.001)
X, Y = np.meshgrid(x, y)
Z = X * Y
pcm = ax[0].pcolormesh(X, Y, 10**Z, norm=LogNorm())
cbar = fig.colorbar(pcm, ax=ax[0], extend='both',
orientation='vertical')
pcm = ax[1].pcolormesh(X, Y, 10**Z, norm=LogNorm())
cbar2 = fig.colorbar(pcm, ax=ax[1], extend='both',
orientation='vertical', shrink=0.4)
np.testing.assert_almost_equal(cbar.ax.yaxis.get_ticklocs(),
10**np.arange(-12, 12.2, 4.))
np.testing.assert_almost_equal(cbar2.ax.yaxis.get_ticklocs(),
10**np.arange(-12, 13., 12.))
def test_colorbar_get_ticks():
# test feature for #5792
plt.figure()
data = np.arange(1200).reshape(30, 40)
levels = [0, 200, 400, 600, 800, 1000, 1200]
plt.subplot()
plt.contourf(data, levels=levels)
# testing getter for user set ticks
userTicks = plt.colorbar(ticks=[0, 600, 1200])
assert userTicks.get_ticks().tolist() == [0, 600, 1200]
# testing for getter after calling set_ticks
userTicks.set_ticks([600, 700, 800])
assert userTicks.get_ticks().tolist() == [600, 700, 800]
# testing for getter after calling set_ticks with some ticks out of bounds
userTicks.set_ticks([600, 1300, 1400, 1500])
assert userTicks.get_ticks().tolist() == [600]
# testing getter when no ticks are assigned
defTicks = plt.colorbar(orientation='horizontal')
assert defTicks.get_ticks().tolist() == levels
def test_colorbar_lognorm_extension():
# Test that colorbar with lognorm is extended correctly
f, ax = plt.subplots()
cb = ColorbarBase(ax, norm=LogNorm(vmin=0.1, vmax=1000.0),
orientation='vertical', extend='both')
assert cb._values[0] >= 0.0
def test_colorbar_powernorm_extension():
# Test that colorbar with powernorm is extended correctly
f, ax = plt.subplots()
cb = ColorbarBase(ax, norm=PowerNorm(gamma=0.5, vmin=0.0, vmax=1.0),
orientation='vertical', extend='both')
assert cb._values[0] >= 0.0
def test_colorbar_axes_kw():
# test fix for #8493: This does only test, that axes-related keywords pass
# and do not raise an exception.
plt.figure()
plt.imshow(([[1, 2], [3, 4]]))
plt.colorbar(orientation='horizontal', fraction=0.2, pad=0.2, shrink=0.5,
aspect=10, anchor=(0., 0.), panchor=(0., 1.))
def test_colorbar_log_minortick_labels():
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt.subplots()
pcm = ax.imshow([[10000, 50000]], norm=LogNorm())
cb = fig.colorbar(pcm)
fig.canvas.draw()
lb = cb.ax.yaxis.get_ticklabels(which='both')
expected = [r'$\mathdefault{10^{4}}$',
r'$\mathdefault{2\times10^{4}}$',
r'$\mathdefault{3\times10^{4}}$',
r'$\mathdefault{4\times10^{4}}$']
for l, exp in zip(lb, expected):
assert l.get_text() == exp
def test_colorbar_renorm():
x, y = np.ogrid[-4:4:31j, -4:4:31j]
z = 120000*np.exp(-x**2 - y**2)
fig, ax = plt.subplots()
im = ax.imshow(z)
cbar = fig.colorbar(im)
norm = LogNorm(z.min(), z.max())
im.set_norm(norm)
cbar.set_norm(norm)
cbar.locator = LogLocator()
cbar.formatter = LogFormatter()
cbar.update_normal(im)
assert np.isclose(cbar.vmin, z.min())
norm = LogNorm(z.min() * 1000, z.max() * 1000)
im.set_norm(norm)
cbar.set_norm(norm)
cbar.update_normal(im)
assert np.isclose(cbar.vmin, z.min() * 1000)
assert np.isclose(cbar.vmax, z.max() * 1000)
def test_colorbar_get_ticks():
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt. subplots()
np.random.seed(19680801)
pc = ax.pcolormesh(np.random.rand(30, 30))
cb = fig.colorbar(pc)
np.testing.assert_allclose(cb.get_ticks(), [0.2, 0.4, 0.6, 0.8])
@@ -0,0 +1,715 @@
import copy
import itertools
import numpy as np
import pytest
from numpy.testing import assert_array_equal, assert_array_almost_equal
from matplotlib import cycler
import matplotlib
import matplotlib.colors as mcolors
import matplotlib.cm as cm
import matplotlib.colorbar as mcolorbar
import matplotlib.cbook as cbook
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
def test_resample():
"""
Github issue #6025 pointed to incorrect ListedColormap._resample;
here we test the method for LinearSegmentedColormap as well.
"""
n = 101
colorlist = np.empty((n, 4), float)
colorlist[:, 0] = np.linspace(0, 1, n)
colorlist[:, 1] = 0.2
colorlist[:, 2] = np.linspace(1, 0, n)
colorlist[:, 3] = 0.7
lsc = mcolors.LinearSegmentedColormap.from_list('lsc', colorlist)
lc = mcolors.ListedColormap(colorlist)
lsc3 = lsc._resample(3)
lc3 = lc._resample(3)
expected = np.array([[0.0, 0.2, 1.0, 0.7],
[0.5, 0.2, 0.5, 0.7],
[1.0, 0.2, 0.0, 0.7]], float)
assert_array_almost_equal(lsc3([0, 0.5, 1]), expected)
assert_array_almost_equal(lc3([0, 0.5, 1]), expected)
def test_colormap_copy():
cm = plt.cm.Reds
cm_copy = copy.copy(cm)
with np.errstate(invalid='ignore'):
ret1 = cm_copy([-1, 0, .5, 1, np.nan, np.inf])
cm2 = copy.copy(cm_copy)
cm2.set_bad('g')
with np.errstate(invalid='ignore'):
ret2 = cm_copy([-1, 0, .5, 1, np.nan, np.inf])
assert_array_equal(ret1, ret2)
def test_colormap_endian():
"""
Github issue #1005: a bug in putmask caused erroneous
mapping of 1.0 when input from a non-native-byteorder
array.
"""
cmap = cm.get_cmap("jet")
# Test under, over, and invalid along with values 0 and 1.
a = [-0.5, 0, 0.5, 1, 1.5, np.nan]
for dt in ["f2", "f4", "f8"]:
anative = np.ma.masked_invalid(np.array(a, dtype=dt))
aforeign = anative.byteswap().newbyteorder()
assert_array_equal(cmap(anative), cmap(aforeign))
def test_BoundaryNorm():
"""
Github issue #1258: interpolation was failing with numpy
1.7 pre-release.
"""
boundaries = [0, 1.1, 2.2]
vals = [-1, 0, 1, 2, 2.2, 4]
# Without interpolation
expected = [-1, 0, 0, 1, 2, 2]
ncolors = len(boundaries) - 1
bn = mcolors.BoundaryNorm(boundaries, ncolors)
assert_array_equal(bn(vals), expected)
# ncolors != len(boundaries) - 1 triggers interpolation
expected = [-1, 0, 0, 2, 3, 3]
ncolors = len(boundaries)
bn = mcolors.BoundaryNorm(boundaries, ncolors)
assert_array_equal(bn(vals), expected)
# more boundaries for a third color
boundaries = [0, 1, 2, 3]
vals = [-1, 0.1, 1.1, 2.2, 4]
ncolors = 5
expected = [-1, 0, 2, 4, 5]
bn = mcolors.BoundaryNorm(boundaries, ncolors)
assert_array_equal(bn(vals), expected)
# a scalar as input should not trigger an error and should return a scalar
boundaries = [0, 1, 2]
vals = [-1, 0.1, 1.1, 2.2]
bn = mcolors.BoundaryNorm(boundaries, 2)
expected = [-1, 0, 1, 2]
for v, ex in zip(vals, expected):
ret = bn(v)
assert isinstance(ret, int)
assert_array_equal(ret, ex)
assert_array_equal(bn([v]), ex)
# same with interp
bn = mcolors.BoundaryNorm(boundaries, 3)
expected = [-1, 0, 2, 3]
for v, ex in zip(vals, expected):
ret = bn(v)
assert isinstance(ret, int)
assert_array_equal(ret, ex)
assert_array_equal(bn([v]), ex)
# Clipping
bn = mcolors.BoundaryNorm(boundaries, 3, clip=True)
expected = [0, 0, 2, 2]
for v, ex in zip(vals, expected):
ret = bn(v)
assert isinstance(ret, int)
assert_array_equal(ret, ex)
assert_array_equal(bn([v]), ex)
# Masked arrays
boundaries = [0, 1.1, 2.2]
vals = np.ma.masked_invalid([-1., np.NaN, 0, 1.4, 9])
# Without interpolation
ncolors = len(boundaries) - 1
bn = mcolors.BoundaryNorm(boundaries, ncolors)
expected = np.ma.masked_array([-1, -99, 0, 1, 2], mask=[0, 1, 0, 0, 0])
assert_array_equal(bn(vals), expected)
# With interpolation
bn = mcolors.BoundaryNorm(boundaries, len(boundaries))
expected = np.ma.masked_array([-1, -99, 0, 2, 3], mask=[0, 1, 0, 0, 0])
assert_array_equal(bn(vals), expected)
# Non-trivial masked arrays
vals = np.ma.masked_invalid([np.Inf, np.NaN])
assert np.all(bn(vals).mask)
vals = np.ma.masked_invalid([np.Inf])
assert np.all(bn(vals).mask)
def test_LogNorm():
"""
LogNorm ignored clip, now it has the same
behavior as Normalize, e.g., values > vmax are bigger than 1
without clip, with clip they are 1.
"""
ln = mcolors.LogNorm(clip=True, vmax=5)
assert_array_equal(ln([1, 6]), [0, 1.0])
def test_PowerNorm():
a = np.array([0, 0.5, 1, 1.5], dtype=float)
pnorm = mcolors.PowerNorm(1)
norm = mcolors.Normalize()
assert_array_almost_equal(norm(a), pnorm(a))
a = np.array([-0.5, 0, 2, 4, 8], dtype=float)
expected = [0, 0, 1/16, 1/4, 1]
pnorm = mcolors.PowerNorm(2, vmin=0, vmax=8)
assert_array_almost_equal(pnorm(a), expected)
assert pnorm(a[0]) == expected[0]
assert pnorm(a[2]) == expected[2]
assert_array_almost_equal(a[1:], pnorm.inverse(pnorm(a))[1:])
# Clip = True
a = np.array([-0.5, 0, 1, 8, 16], dtype=float)
expected = [0, 0, 0, 1, 1]
pnorm = mcolors.PowerNorm(2, vmin=2, vmax=8, clip=True)
assert_array_almost_equal(pnorm(a), expected)
assert pnorm(a[0]) == expected[0]
assert pnorm(a[-1]) == expected[-1]
# Clip = True at call time
a = np.array([-0.5, 0, 1, 8, 16], dtype=float)
expected = [0, 0, 0, 1, 1]
pnorm = mcolors.PowerNorm(2, vmin=2, vmax=8, clip=False)
assert_array_almost_equal(pnorm(a, clip=True), expected)
assert pnorm(a[0], clip=True) == expected[0]
assert pnorm(a[-1], clip=True) == expected[-1]
def test_PowerNorm_translation_invariance():
a = np.array([0, 1/2, 1], dtype=float)
expected = [0, 1/8, 1]
pnorm = mcolors.PowerNorm(vmin=0, vmax=1, gamma=3)
assert_array_almost_equal(pnorm(a), expected)
pnorm = mcolors.PowerNorm(vmin=-2, vmax=-1, gamma=3)
assert_array_almost_equal(pnorm(a - 2), expected)
def test_Normalize():
norm = mcolors.Normalize()
vals = np.arange(-10, 10, 1, dtype=float)
_inverse_tester(norm, vals)
_scalar_tester(norm, vals)
_mask_tester(norm, vals)
# Handle integer input correctly (don't overflow when computing max-min,
# i.e. 127-(-128) here).
vals = np.array([-128, 127], dtype=np.int8)
norm = mcolors.Normalize(vals.min(), vals.max())
assert_array_equal(np.asarray(norm(vals)), [0, 1])
# Don't lose precision on longdoubles (float128 on Linux):
# for array inputs...
vals = np.array([1.2345678901, 9.8765432109], dtype=np.longdouble)
norm = mcolors.Normalize(vals.min(), vals.max())
assert_array_equal(np.asarray(norm(vals)), [0, 1])
# and for scalar ones.
eps = np.finfo(np.longdouble).resolution
norm = plt.Normalize(1, 1 + 100 * eps)
# This returns exactly 0.5 when longdouble is extended precision (80-bit),
# but only a value close to it when it is quadruple precision (128-bit).
assert 0 < norm(1 + 50 * eps) < 1
def test_SymLogNorm():
"""
Test SymLogNorm behavior
"""
norm = mcolors.SymLogNorm(3, vmax=5, linscale=1.2)
vals = np.array([-30, -1, 2, 6], dtype=float)
normed_vals = norm(vals)
expected = [0., 0.53980074, 0.826991, 1.02758204]
assert_array_almost_equal(normed_vals, expected)
_inverse_tester(norm, vals)
_scalar_tester(norm, vals)
_mask_tester(norm, vals)
# Ensure that specifying vmin returns the same result as above
norm = mcolors.SymLogNorm(3, vmin=-30, vmax=5, linscale=1.2)
normed_vals = norm(vals)
assert_array_almost_equal(normed_vals, expected)
def test_SymLogNorm_colorbar():
"""
Test un-called SymLogNorm in a colorbar.
"""
norm = mcolors.SymLogNorm(0.1, vmin=-1, vmax=1, linscale=1)
fig = plt.figure()
cbar = mcolorbar.ColorbarBase(fig.add_subplot(111), norm=norm)
plt.close(fig)
def test_SymLogNorm_single_zero():
"""
Test SymLogNorm to ensure it is not adding sub-ticks to zero label
"""
fig = plt.figure()
norm = mcolors.SymLogNorm(1e-5, vmin=-1, vmax=1)
cbar = mcolorbar.ColorbarBase(fig.add_subplot(111), norm=norm)
ticks = cbar.get_ticks()
assert sum(ticks == 0) == 1
plt.close(fig)
def _inverse_tester(norm_instance, vals):
"""
Checks if the inverse of the given normalization is working.
"""
assert_array_almost_equal(norm_instance.inverse(norm_instance(vals)), vals)
def _scalar_tester(norm_instance, vals):
"""
Checks if scalars and arrays are handled the same way.
Tests only for float.
"""
scalar_result = [norm_instance(float(v)) for v in vals]
assert_array_almost_equal(scalar_result, norm_instance(vals))
def _mask_tester(norm_instance, vals):
"""
Checks mask handling
"""
masked_array = np.ma.array(vals)
masked_array[0] = np.ma.masked
assert_array_equal(masked_array.mask, norm_instance(masked_array).mask)
@image_comparison(baseline_images=['levels_and_colors'],
extensions=['png'])
def test_cmap_and_norm_from_levels_and_colors():
data = np.linspace(-2, 4, 49).reshape(7, 7)
levels = [-1, 2, 2.5, 3]
colors = ['red', 'green', 'blue', 'yellow', 'black']
extend = 'both'
cmap, norm = mcolors.from_levels_and_colors(levels, colors, extend=extend)
ax = plt.axes()
m = plt.pcolormesh(data, cmap=cmap, norm=norm)
plt.colorbar(m)
# Hide the axes labels (but not the colorbar ones, as they are useful)
ax.tick_params(labelleft=False, labelbottom=False)
def test_cmap_and_norm_from_levels_and_colors2():
levels = [-1, 2, 2.5, 3]
colors = ['red', (0, 1, 0), 'blue', (0.5, 0.5, 0.5), (0.0, 0.0, 0.0, 1.0)]
clr = mcolors.to_rgba_array(colors)
bad = (0.1, 0.1, 0.1, 0.1)
no_color = (0.0, 0.0, 0.0, 0.0)
masked_value = 'masked_value'
# Define the test values which are of interest.
# Note: levels are lev[i] <= v < lev[i+1]
tests = [('both', None, {-2: clr[0],
-1: clr[1],
2: clr[2],
2.25: clr[2],
3: clr[4],
3.5: clr[4],
masked_value: bad}),
('min', -1, {-2: clr[0],
-1: clr[1],
2: clr[2],
2.25: clr[2],
3: no_color,
3.5: no_color,
masked_value: bad}),
('max', -1, {-2: no_color,
-1: clr[0],
2: clr[1],
2.25: clr[1],
3: clr[3],
3.5: clr[3],
masked_value: bad}),
('neither', -2, {-2: no_color,
-1: clr[0],
2: clr[1],
2.25: clr[1],
3: no_color,
3.5: no_color,
masked_value: bad}),
]
for extend, i1, cases in tests:
cmap, norm = mcolors.from_levels_and_colors(levels, colors[0:i1],
extend=extend)
cmap.set_bad(bad)
for d_val, expected_color in cases.items():
if d_val == masked_value:
d_val = np.ma.array([1], mask=True)
else:
d_val = [d_val]
assert_array_equal(expected_color, cmap(norm(d_val))[0],
'Wih extend={0!r} and data '
'value={1!r}'.format(extend, d_val))
with pytest.raises(ValueError):
mcolors.from_levels_and_colors(levels, colors)
def test_rgb_hsv_round_trip():
for a_shape in [(500, 500, 3), (500, 3), (1, 3), (3,)]:
np.random.seed(0)
tt = np.random.random(a_shape)
assert_array_almost_equal(tt,
mcolors.hsv_to_rgb(mcolors.rgb_to_hsv(tt)))
assert_array_almost_equal(tt,
mcolors.rgb_to_hsv(mcolors.hsv_to_rgb(tt)))
def test_autoscale_masked():
# Test for #2336. Previously fully masked data would trigger a ValueError.
data = np.ma.masked_all((12, 20))
plt.pcolor(data)
plt.draw()
def test_colors_no_float():
# Gray must be a string to distinguish 3-4 grays from RGB or RGBA.
with pytest.raises(ValueError):
mcolors.to_rgba(0.4)
@image_comparison(baseline_images=['light_source_shading_topo'],
extensions=['png'])
def test_light_source_topo_surface():
"""Shades a DEM using different v.e.'s and blend modes."""
fname = cbook.get_sample_data('jacksboro_fault_dem.npz', asfileobj=False)
dem = np.load(fname)
elev = dem['elevation']
# Get the true cellsize in meters for accurate vertical exaggeration
# Convert from decimal degrees to meters
dx, dy = dem['dx'], dem['dy']
dx = 111320.0 * dx * np.cos(dem['ymin'])
dy = 111320.0 * dy
dem.close()
ls = mcolors.LightSource(315, 45)
cmap = cm.gist_earth
fig, axes = plt.subplots(nrows=3, ncols=3)
for row, mode in zip(axes, ['hsv', 'overlay', 'soft']):
for ax, ve in zip(row, [0.1, 1, 10]):
rgb = ls.shade(elev, cmap, vert_exag=ve, dx=dx, dy=dy,
blend_mode=mode)
ax.imshow(rgb)
ax.set(xticks=[], yticks=[])
def test_light_source_shading_default():
"""Array comparison test for the default "hsv" blend mode. Ensure the
default result doesn't change without warning."""
y, x = np.mgrid[-1.2:1.2:8j, -1.2:1.2:8j]
z = 10 * np.cos(x**2 + y**2)
cmap = plt.cm.copper
ls = mcolors.LightSource(315, 45)
rgb = ls.shade(z, cmap)
# Result stored transposed and rounded for more compact display...
expect = np.array(
[[[0.00, 0.45, 0.90, 0.90, 0.82, 0.62, 0.28, 0.00],
[0.45, 0.94, 0.99, 1.00, 1.00, 0.96, 0.65, 0.17],
[0.90, 0.99, 1.00, 1.00, 1.00, 1.00, 0.94, 0.35],
[0.90, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.49],
[0.82, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.41],
[0.62, 0.96, 1.00, 1.00, 1.00, 1.00, 0.90, 0.07],
[0.28, 0.65, 0.94, 1.00, 1.00, 0.90, 0.35, 0.01],
[0.00, 0.17, 0.35, 0.49, 0.41, 0.07, 0.01, 0.00]],
[[0.00, 0.28, 0.59, 0.72, 0.62, 0.40, 0.18, 0.00],
[0.28, 0.78, 0.93, 0.92, 0.83, 0.66, 0.39, 0.11],
[0.59, 0.93, 0.99, 1.00, 0.92, 0.75, 0.50, 0.21],
[0.72, 0.92, 1.00, 0.99, 0.93, 0.76, 0.51, 0.18],
[0.62, 0.83, 0.92, 0.93, 0.87, 0.68, 0.42, 0.08],
[0.40, 0.66, 0.75, 0.76, 0.68, 0.52, 0.23, 0.02],
[0.18, 0.39, 0.50, 0.51, 0.42, 0.23, 0.00, 0.00],
[0.00, 0.11, 0.21, 0.18, 0.08, 0.02, 0.00, 0.00]],
[[0.00, 0.18, 0.38, 0.46, 0.39, 0.26, 0.11, 0.00],
[0.18, 0.50, 0.70, 0.75, 0.64, 0.44, 0.25, 0.07],
[0.38, 0.70, 0.91, 0.98, 0.81, 0.51, 0.29, 0.13],
[0.46, 0.75, 0.98, 0.96, 0.84, 0.48, 0.22, 0.12],
[0.39, 0.64, 0.81, 0.84, 0.71, 0.31, 0.11, 0.05],
[0.26, 0.44, 0.51, 0.48, 0.31, 0.10, 0.03, 0.01],
[0.11, 0.25, 0.29, 0.22, 0.11, 0.03, 0.00, 0.00],
[0.00, 0.07, 0.13, 0.12, 0.05, 0.01, 0.00, 0.00]],
[[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00]]
]).T
assert_array_almost_equal(rgb, expect, decimal=2)
# Numpy 1.9.1 fixed a bug in masked arrays which resulted in
# additional elements being masked when calculating the gradient thus
# the output is different with earlier numpy versions.
def test_light_source_masked_shading():
"""Array comparison test for a surface with a masked portion. Ensures that
we don't wind up with "fringes" of odd colors around masked regions."""
y, x = np.mgrid[-1.2:1.2:8j, -1.2:1.2:8j]
z = 10 * np.cos(x**2 + y**2)
z = np.ma.masked_greater(z, 9.9)
cmap = plt.cm.copper
ls = mcolors.LightSource(315, 45)
rgb = ls.shade(z, cmap)
# Result stored transposed and rounded for more compact display...
expect = np.array(
[[[0.00, 0.46, 0.91, 0.91, 0.84, 0.64, 0.29, 0.00],
[0.46, 0.96, 1.00, 1.00, 1.00, 0.97, 0.67, 0.18],
[0.91, 1.00, 1.00, 1.00, 1.00, 1.00, 0.96, 0.36],
[0.91, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 0.51],
[0.84, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 0.44],
[0.64, 0.97, 1.00, 1.00, 1.00, 1.00, 0.94, 0.09],
[0.29, 0.67, 0.96, 1.00, 1.00, 0.94, 0.38, 0.01],
[0.00, 0.18, 0.36, 0.51, 0.44, 0.09, 0.01, 0.00]],
[[0.00, 0.29, 0.61, 0.75, 0.64, 0.41, 0.18, 0.00],
[0.29, 0.81, 0.95, 0.93, 0.85, 0.68, 0.40, 0.11],
[0.61, 0.95, 1.00, 0.78, 0.78, 0.77, 0.52, 0.22],
[0.75, 0.93, 0.78, 0.00, 0.00, 0.78, 0.54, 0.19],
[0.64, 0.85, 0.78, 0.00, 0.00, 0.78, 0.45, 0.08],
[0.41, 0.68, 0.77, 0.78, 0.78, 0.55, 0.25, 0.02],
[0.18, 0.40, 0.52, 0.54, 0.45, 0.25, 0.00, 0.00],
[0.00, 0.11, 0.22, 0.19, 0.08, 0.02, 0.00, 0.00]],
[[0.00, 0.19, 0.39, 0.48, 0.41, 0.26, 0.12, 0.00],
[0.19, 0.52, 0.73, 0.78, 0.66, 0.46, 0.26, 0.07],
[0.39, 0.73, 0.95, 0.50, 0.50, 0.53, 0.30, 0.14],
[0.48, 0.78, 0.50, 0.00, 0.00, 0.50, 0.23, 0.12],
[0.41, 0.66, 0.50, 0.00, 0.00, 0.50, 0.11, 0.05],
[0.26, 0.46, 0.53, 0.50, 0.50, 0.11, 0.03, 0.01],
[0.12, 0.26, 0.30, 0.23, 0.11, 0.03, 0.00, 0.00],
[0.00, 0.07, 0.14, 0.12, 0.05, 0.01, 0.00, 0.00]],
[[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00],
[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00]],
]).T
assert_array_almost_equal(rgb, expect, decimal=2)
def test_light_source_hillshading():
"""Compare the current hillshading method against one that should be
mathematically equivalent. Illuminates a cone from a range of angles."""
def alternative_hillshade(azimuth, elev, z):
illum = _sph2cart(*_azimuth2math(azimuth, elev))
illum = np.array(illum)
dy, dx = np.gradient(-z)
dy = -dy
dz = np.ones_like(dy)
normals = np.dstack([dx, dy, dz])
normals /= np.linalg.norm(normals, axis=2)[..., None]
intensity = np.tensordot(normals, illum, axes=(2, 0))
intensity -= intensity.min()
intensity /= intensity.ptp()
return intensity
y, x = np.mgrid[5:0:-1, :5]
z = -np.hypot(x - x.mean(), y - y.mean())
for az, elev in itertools.product(range(0, 390, 30), range(0, 105, 15)):
ls = mcolors.LightSource(az, elev)
h1 = ls.hillshade(z)
h2 = alternative_hillshade(az, elev, z)
assert_array_almost_equal(h1, h2)
def test_light_source_planar_hillshading():
"""Ensure that the illumination intensity is correct for planar
surfaces."""
def plane(azimuth, elevation, x, y):
"""Create a plane whose normal vector is at the given azimuth and
elevation."""
theta, phi = _azimuth2math(azimuth, elevation)
a, b, c = _sph2cart(theta, phi)
z = -(a*x + b*y) / c
return z
def angled_plane(azimuth, elevation, angle, x, y):
"""Create a plane whose normal vector is at an angle from the given
azimuth and elevation."""
elevation = elevation + angle
if elevation > 90:
azimuth = (azimuth + 180) % 360
elevation = (90 - elevation) % 90
return plane(azimuth, elevation, x, y)
y, x = np.mgrid[5:0:-1, :5]
for az, elev in itertools.product(range(0, 390, 30), range(0, 105, 15)):
ls = mcolors.LightSource(az, elev)
# Make a plane at a range of angles to the illumination
for angle in range(0, 105, 15):
z = angled_plane(az, elev, angle, x, y)
h = ls.hillshade(z)
assert_array_almost_equal(h, np.cos(np.radians(angle)))
def test_color_names():
assert mcolors.to_hex("blue") == "#0000ff"
assert mcolors.to_hex("xkcd:blue") == "#0343df"
assert mcolors.to_hex("tab:blue") == "#1f77b4"
def _sph2cart(theta, phi):
x = np.cos(theta) * np.sin(phi)
y = np.sin(theta) * np.sin(phi)
z = np.cos(phi)
return x, y, z
def _azimuth2math(azimuth, elevation):
"""Converts from clockwise-from-north and up-from-horizontal to
mathematical conventions."""
theta = np.radians((90 - azimuth) % 360)
phi = np.radians(90 - elevation)
return theta, phi
def test_pandas_iterable(pd):
# Using a list or series yields equivalent
# color maps, i.e the series isn't seen as
# a single color
lst = ['red', 'blue', 'green']
s = pd.Series(lst)
cm1 = mcolors.ListedColormap(lst, N=5)
cm2 = mcolors.ListedColormap(s, N=5)
assert_array_equal(cm1.colors, cm2.colors)
@pytest.mark.parametrize('name', sorted(cm.cmap_d))
def test_colormap_reversing(name):
"""Check the generated _lut data of a colormap and corresponding
reversed colormap if they are almost the same."""
cmap = plt.get_cmap(name)
cmap_r = cmap.reversed()
if not cmap_r._isinit:
cmap._init()
cmap_r._init()
assert_array_almost_equal(cmap._lut[:-3], cmap_r._lut[-4::-1])
def test_cn():
matplotlib.rcParams['axes.prop_cycle'] = cycler('color',
['blue', 'r'])
assert mcolors.to_hex("C0") == '#0000ff'
assert mcolors.to_hex("C1") == '#ff0000'
matplotlib.rcParams['axes.prop_cycle'] = cycler('color',
['xkcd:blue', 'r'])
assert mcolors.to_hex("C0") == '#0343df'
assert mcolors.to_hex("C1") == '#ff0000'
matplotlib.rcParams['axes.prop_cycle'] = cycler('color', ['8e4585', 'r'])
assert mcolors.to_hex("C0") == '#8e4585'
# if '8e4585' gets parsed as a float before it gets detected as a hex
# colour it will be interpreted as a very large number.
# this mustn't happen.
assert mcolors.to_rgb("C0")[0] != np.inf
def test_conversions():
# to_rgba_array("none") returns a (0, 4) array.
assert_array_equal(mcolors.to_rgba_array("none"), np.zeros((0, 4)))
# a list of grayscale levels, not a single color.
assert_array_equal(
mcolors.to_rgba_array([".2", ".5", ".8"]),
np.vstack([mcolors.to_rgba(c) for c in [".2", ".5", ".8"]]))
# alpha is properly set.
assert mcolors.to_rgba((1, 1, 1), .5) == (1, 1, 1, .5)
assert mcolors.to_rgba(".1", .5) == (.1, .1, .1, .5)
# builtin round differs between py2 and py3.
assert mcolors.to_hex((.7, .7, .7)) == "#b2b2b2"
# hex roundtrip.
hex_color = "#1234abcd"
assert mcolors.to_hex(mcolors.to_rgba(hex_color), keep_alpha=True) == \
hex_color
def test_grey_gray():
color_mapping = mcolors._colors_full_map
for k in color_mapping.keys():
if 'grey' in k:
assert color_mapping[k] == color_mapping[k.replace('grey', 'gray')]
if 'gray' in k:
assert color_mapping[k] == color_mapping[k.replace('gray', 'grey')]
def test_tableau_order():
dflt_cycle = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728',
'#9467bd', '#8c564b', '#e377c2', '#7f7f7f',
'#bcbd22', '#17becf']
assert list(mcolors.TABLEAU_COLORS.values()) == dflt_cycle
def test_ndarray_subclass_norm(recwarn):
# Emulate an ndarray subclass that handles units
# which objects when adding or subtracting with other
# arrays. See #6622 and #8696
class MyArray(np.ndarray):
def __isub__(self, other):
raise RuntimeError
def __add__(self, other):
raise RuntimeError
data = np.arange(-10, 10, 1, dtype=float)
data.shape = (10, 2)
mydata = data.view(MyArray)
for norm in [mcolors.Normalize(), mcolors.LogNorm(),
mcolors.SymLogNorm(3, vmax=5, linscale=1),
mcolors.Normalize(vmin=mydata.min(), vmax=mydata.max()),
mcolors.SymLogNorm(3, vmin=mydata.min(), vmax=mydata.max()),
mcolors.PowerNorm(1)]:
assert_array_equal(norm(mydata), norm(data))
fig, ax = plt.subplots()
ax.imshow(mydata, norm=norm)
fig.canvas.draw()
assert len(recwarn) == 0
recwarn.clear()
def test_same_color():
assert mcolors.same_color('k', (0, 0, 0))
assert not mcolors.same_color('w', (1, 1, 0))
@@ -0,0 +1,72 @@
import os
import shutil
import pytest
from pytest import approx
from matplotlib.testing.compare import compare_images
from matplotlib.testing.decorators import _image_directories
baseline_dir, result_dir = _image_directories(lambda: 'dummy func')
# Tests of the image comparison algorithm.
@pytest.mark.parametrize(
'im1, im2, tol, expect_rms',
[
# Comparison of an image and the same image with minor differences.
# This expects the images to compare equal under normal tolerance, and
# have a small RMS.
('basn3p02.png', 'basn3p02-minorchange.png', 10, None),
# Now test with no tolerance.
('basn3p02.png', 'basn3p02-minorchange.png', 0, 6.50646),
# Comparison with an image that is shifted by 1px in the X axis.
('basn3p02.png', 'basn3p02-1px-offset.png', 0, 90.15611),
# Comparison with an image with half the pixels shifted by 1px in the X
# axis.
('basn3p02.png', 'basn3p02-half-1px-offset.png', 0, 63.75),
# Comparison of an image and the same image scrambled.
# This expects the images to compare completely different, with a very
# large RMS.
# Note: The image has been scrambled in a specific way, by having
# each color component of each pixel randomly placed somewhere in the
# image. It contains exactly the same number of pixels of each color
# value of R, G and B, but in a totally different position.
# Test with no tolerance to make sure that we pick up even a very small
# RMS error.
('basn3p02.png', 'basn3p02-scrambled.png', 0, 172.63582),
# Comparison of an image and a slightly brighter image.
# The two images are solid color, with the second image being exactly 1
# color value brighter.
# This expects the images to compare equal under normal tolerance, and
# have an RMS of exactly 1.
('all127.png', 'all128.png', 0, 1),
# Now test the reverse comparison.
('all128.png', 'all127.png', 0, 1),
])
def test_image_comparison_expect_rms(im1, im2, tol, expect_rms):
"""Compare two images, expecting a particular RMS error.
im1 and im2 are filenames relative to the baseline_dir directory.
tol is the tolerance to pass to compare_images.
expect_rms is the expected RMS value, or None. If None, the test will
succeed if compare_images succeeds. Otherwise, the test will succeed if
compare_images fails and returns an RMS error almost equal to this value.
"""
im1 = os.path.join(baseline_dir, im1)
im2_src = os.path.join(baseline_dir, im2)
im2 = os.path.join(result_dir, im2)
# Move im2 from baseline_dir to result_dir. This will ensure that
# compare_images writes the diff file to result_dir, instead of trying to
# write to the (possibly read-only) baseline_dir.
shutil.copyfile(im2_src, im2)
results = compare_images(im1, im2, tol=tol, in_decorator=True)
if expect_rms is None:
assert results is None
else:
assert results is not None
assert results['rms'] == approx(expect_rms, abs=1e-4)
@@ -0,0 +1,406 @@
import numpy as np
import pytest
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib import ticker, rcParams
def example_plot(ax, fontsize=12, nodec=False):
ax.plot([1, 2])
ax.locator_params(nbins=3)
if not nodec:
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_ylabel('y-label', fontsize=fontsize)
ax.set_title('Title', fontsize=fontsize)
else:
ax.set_xticklabels('')
ax.set_yticklabels('')
def example_pcolor(ax, fontsize=12):
dx, dy = 0.6, 0.6
y, x = np.mgrid[slice(-3, 3 + dy, dy),
slice(-3, 3 + dx, dx)]
z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
pcm = ax.pcolormesh(x, y, z, cmap='RdBu_r', vmin=-1., vmax=1.,
rasterized=True)
# ax.locator_params(nbins=3)
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_ylabel('y-label', fontsize=fontsize)
ax.set_title('Title', fontsize=fontsize)
return pcm
@image_comparison(baseline_images=['constrained_layout1'],
extensions=['png'])
def test_constrained_layout1():
'Test constrained_layout for a single subplot'
fig = plt.figure(constrained_layout=True)
ax = fig.add_subplot(111)
example_plot(ax, fontsize=24)
@image_comparison(baseline_images=['constrained_layout2'],
extensions=['png'])
def test_constrained_layout2():
'Test constrained_layout for 2x2 subplots'
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flatten():
example_plot(ax, fontsize=24)
@image_comparison(baseline_images=['constrained_layout3'],
extensions=['png'])
def test_constrained_layout3():
'Test constrained_layout for colorbars with subplots'
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for nn, ax in enumerate(axs.flatten()):
pcm = example_pcolor(ax, fontsize=24)
if nn == 3:
pad = 0.08
else:
pad = 0.02 # default
fig.colorbar(pcm, ax=ax, pad=pad)
@image_comparison(baseline_images=['constrained_layout4'])
def test_constrained_layout4():
'Test constrained_layout for a single colorbar with subplots'
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flatten():
pcm = example_pcolor(ax, fontsize=24)
fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
@image_comparison(baseline_images=['constrained_layout5'],
tol=5.e-2, extensions=['png'])
def test_constrained_layout5():
'''
Test constrained_layout for a single colorbar with subplots,
colorbar bottom
'''
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flatten():
pcm = example_pcolor(ax, fontsize=24)
fig.colorbar(pcm, ax=axs,
use_gridspec=False, pad=0.01, shrink=0.6,
location='bottom')
@image_comparison(baseline_images=['constrained_layout6'],
extensions=['png'])
def test_constrained_layout6():
'Test constrained_layout for nested gridspecs'
fig = plt.figure(constrained_layout=True)
gs = fig.add_gridspec(1, 2, figure=fig)
gsl = gs[0].subgridspec(2, 2)
gsr = gs[1].subgridspec(1, 2)
axsl = []
for gs in gsl:
ax = fig.add_subplot(gs)
axsl += [ax]
example_plot(ax, fontsize=12)
ax.set_xlabel('x-label\nMultiLine')
axsr = []
for gs in gsr:
ax = fig.add_subplot(gs)
axsr += [ax]
pcm = example_pcolor(ax, fontsize=12)
fig.colorbar(pcm, ax=axsr,
pad=0.01, shrink=0.99, location='bottom',
ticks=ticker.MaxNLocator(nbins=5))
def test_constrained_layout7():
'Test for proper warning if fig not set in GridSpec'
with pytest.warns(UserWarning, match='Calling figure.constrained_layout, '
'but figure not setup to do constrained layout'):
fig = plt.figure(constrained_layout=True)
gs = gridspec.GridSpec(1, 2)
gsl = gridspec.GridSpecFromSubplotSpec(2, 2, gs[0])
gsr = gridspec.GridSpecFromSubplotSpec(1, 2, gs[1])
axsl = []
for gs in gsl:
ax = fig.add_subplot(gs)
# need to trigger a draw to get warning
fig.draw(fig.canvas.get_renderer())
@image_comparison(baseline_images=['constrained_layout8'],
extensions=['png'])
def test_constrained_layout8():
'Test for gridspecs that are not completely full'
fig = plt.figure(figsize=(10, 5), constrained_layout=True)
gs = gridspec.GridSpec(3, 5, figure=fig)
axs = []
for j in [0, 1]:
if j == 0:
ilist = [1]
else:
ilist = [0, 4]
for i in ilist:
ax = fig.add_subplot(gs[j, i])
axs += [ax]
pcm = example_pcolor(ax, fontsize=9)
if i > 0:
ax.set_ylabel('')
if j < 1:
ax.set_xlabel('')
ax.set_title('')
ax = fig.add_subplot(gs[2, :])
axs += [ax]
pcm = example_pcolor(ax, fontsize=9)
fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
@image_comparison(baseline_images=['constrained_layout9'],
extensions=['png'])
def test_constrained_layout9():
'Test for handling suptitle and for sharex and sharey'
fig, axs = plt.subplots(2, 2, constrained_layout=True,
sharex=False, sharey=False)
for ax in axs.flatten():
pcm = example_pcolor(ax, fontsize=24)
ax.set_xlabel('')
ax.set_ylabel('')
ax.set_aspect(2.)
fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
fig.suptitle('Test Suptitle', fontsize=28)
@image_comparison(baseline_images=['constrained_layout10'],
extensions=['png'])
def test_constrained_layout10():
'Test for handling legend outside axis'
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flatten():
ax.plot(np.arange(12), label='This is a label')
ax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
@image_comparison(baseline_images=['constrained_layout11'],
extensions=['png'])
def test_constrained_layout11():
'Test for multiple nested gridspecs '
fig = plt.figure(constrained_layout=True, figsize=(10, 3))
gs0 = gridspec.GridSpec(1, 2, figure=fig)
gsl = gridspec.GridSpecFromSubplotSpec(1, 2, gs0[0])
gsl0 = gridspec.GridSpecFromSubplotSpec(2, 2, gsl[1])
ax = fig.add_subplot(gs0[1])
example_plot(ax, fontsize=9)
axs = []
for gs in gsl0:
ax = fig.add_subplot(gs)
axs += [ax]
pcm = example_pcolor(ax, fontsize=9)
fig.colorbar(pcm, ax=axs, shrink=0.6, aspect=70.)
ax = fig.add_subplot(gsl[0])
example_plot(ax, fontsize=9)
@image_comparison(baseline_images=['constrained_layout11rat'],
extensions=['png'])
def test_constrained_layout11rat():
'Test for multiple nested gridspecs with width_ratios'
fig = plt.figure(constrained_layout=True, figsize=(10, 3))
gs0 = gridspec.GridSpec(1, 2, figure=fig, width_ratios=[6., 1.])
gsl = gridspec.GridSpecFromSubplotSpec(1, 2, gs0[0])
gsl0 = gridspec.GridSpecFromSubplotSpec(2, 2, gsl[1],
height_ratios=[2., 1.])
ax = fig.add_subplot(gs0[1])
example_plot(ax, fontsize=9)
axs = []
for gs in gsl0:
ax = fig.add_subplot(gs)
axs += [ax]
pcm = example_pcolor(ax, fontsize=9)
fig.colorbar(pcm, ax=axs, shrink=0.6, aspect=70.)
ax = fig.add_subplot(gsl[0])
example_plot(ax, fontsize=9)
@image_comparison(baseline_images=['constrained_layout12'],
extensions=['png'])
def test_constrained_layout12():
'Test that very unbalanced labeling still works.'
fig = plt.figure(constrained_layout=True)
gs0 = gridspec.GridSpec(6, 2, figure=fig)
ax1 = fig.add_subplot(gs0[:3, 1])
ax2 = fig.add_subplot(gs0[3:, 1])
example_plot(ax1, fontsize=24)
example_plot(ax2, fontsize=24)
ax = fig.add_subplot(gs0[0:2, 0])
example_plot(ax, nodec=True)
ax = fig.add_subplot(gs0[2:4, 0])
example_plot(ax, nodec=True)
ax = fig.add_subplot(gs0[4:, 0])
example_plot(ax, nodec=True)
ax.set_xlabel('x-label')
@image_comparison(baseline_images=['constrained_layout13'], tol=2.e-2,
extensions=['png'])
def test_constrained_layout13():
'Test that padding works.'
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flatten():
pcm = example_pcolor(ax, fontsize=12)
fig.colorbar(pcm, ax=ax, shrink=0.6, aspect=20., pad=0.02)
fig.set_constrained_layout_pads(w_pad=24./72., h_pad=24./72.)
@image_comparison(baseline_images=['constrained_layout14'],
extensions=['png'])
def test_constrained_layout14():
'Test that padding works.'
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flatten():
pcm = example_pcolor(ax, fontsize=12)
fig.colorbar(pcm, ax=ax, shrink=0.6, aspect=20., pad=0.02)
fig.set_constrained_layout_pads(
w_pad=3./72., h_pad=3./72.,
hspace=0.2, wspace=0.2)
@image_comparison(baseline_images=['constrained_layout15'],
extensions=['png'])
def test_constrained_layout15():
'Test that rcparams work.'
rcParams['figure.constrained_layout.use'] = True
fig, axs = plt.subplots(2, 2)
for ax in axs.flatten():
example_plot(ax, fontsize=12)
@image_comparison(baseline_images=['constrained_layout16'],
extensions=['png'])
def test_constrained_layout16():
'Test ax.set_position.'
fig, ax = plt.subplots(constrained_layout=True)
example_plot(ax, fontsize=12)
ax2 = fig.add_axes([0.2, 0.2, 0.4, 0.4])
@image_comparison(baseline_images=['constrained_layout17'],
extensions=['png'])
def test_constrained_layout17():
'Test uneven gridspecs'
fig = plt.figure(constrained_layout=True)
gs = gridspec.GridSpec(3, 3, figure=fig)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1:])
ax3 = fig.add_subplot(gs[1:, 0:2])
ax4 = fig.add_subplot(gs[1:, -1])
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
def test_constrained_layout18():
'Test twinx'
fig, ax = plt.subplots(constrained_layout=True)
ax2 = ax.twinx()
example_plot(ax)
example_plot(ax2, fontsize=24)
fig.canvas.draw()
assert all(ax.get_position().extents == ax2.get_position().extents)
def test_constrained_layout19():
'Test twiny'
fig, ax = plt.subplots(constrained_layout=True)
ax2 = ax.twiny()
example_plot(ax)
example_plot(ax2, fontsize=24)
ax2.set_title('')
ax.set_title('')
fig.canvas.draw()
assert all(ax.get_position().extents == ax2.get_position().extents)
def test_constrained_layout20():
'Smoke test cl does not mess up added axes'
gx = np.linspace(-5, 5, 4)
img = np.hypot(gx, gx[:, None])
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])
mesh = ax.pcolormesh(gx, gx, img)
fig.colorbar(mesh)
def test_constrained_layout21():
'#11035: repeated calls to suptitle should not alter the layout'
fig, ax = plt.subplots(constrained_layout=True)
fig.suptitle("Suptitle0")
fig.canvas.draw()
extents0 = np.copy(ax.get_position().extents)
fig.suptitle("Suptitle1")
fig.canvas.draw()
extents1 = np.copy(ax.get_position().extents)
np.testing.assert_allclose(extents0, extents1)
def test_constrained_layout22():
'#11035: suptitle should not be include in CL if manually positioned'
fig, ax = plt.subplots(constrained_layout=True)
fig.canvas.draw()
extents0 = np.copy(ax.get_position().extents)
fig.suptitle("Suptitle", y=0.5)
fig.canvas.draw()
extents1 = np.copy(ax.get_position().extents)
np.testing.assert_allclose(extents0, extents1)
def test_constrained_layout23():
'''
Comment in #11035: suptitle used to cause an exception when
reusing a figure w/ CL with ``clear=True``.
'''
for i in range(2):
fig, ax = plt.subplots(num="123", constrained_layout=True, clear=True)
fig.suptitle("Suptitle{}".format(i))
# This test occasionally fails the image comparison tests, so we mark as
# flaky. Apparently the constraint solver occasionally doesn't fully
# optimize. Would be nice if this were more deterministic...
@pytest.mark.timeout(30)
@pytest.mark.flaky(reruns=3)
@image_comparison(baseline_images=['test_colorbar_location'],
extensions=['png'], remove_text=True, style='mpl20')
def test_colorbar_location():
"""
Test that colorbar handling is as expected for various complicated
cases...
"""
fig, axs = plt.subplots(4, 5, constrained_layout=True)
for ax in axs.flatten():
pcm = example_pcolor(ax)
ax.set_xlabel('')
ax.set_ylabel('')
fig.colorbar(pcm, ax=axs[:, 1], shrink=0.4)
fig.colorbar(pcm, ax=axs[-1, :2], shrink=0.5, location='bottom')
fig.colorbar(pcm, ax=axs[0, 2:], shrink=0.5, location='bottom')
fig.colorbar(pcm, ax=axs[-2, 3:], shrink=0.5, location='top')
fig.colorbar(pcm, ax=axs[0, 0], shrink=0.5, location='left')
fig.colorbar(pcm, ax=axs[1:3, 2], shrink=0.5, location='right')
@@ -0,0 +1,30 @@
import matplotlib.pyplot as plt
def test_stem_remove():
ax = plt.gca()
st = ax.stem([1, 2], [1, 2])
st.remove()
def test_errorbar_remove():
# Regression test for a bug that caused remove to fail when using
# fmt='none'
ax = plt.gca()
eb = ax.errorbar([1], [1])
eb.remove()
eb = ax.errorbar([1], [1], xerr=1)
eb.remove()
eb = ax.errorbar([1], [1], yerr=2)
eb.remove()
eb = ax.errorbar([1], [1], xerr=[2], yerr=2)
eb.remove()
eb = ax.errorbar([1], [1], fmt='none')
eb.remove()
@@ -0,0 +1,417 @@
import datetime
import numpy as np
from matplotlib.testing.decorators import image_comparison
from matplotlib import pyplot as plt
from numpy.testing import assert_array_almost_equal
from matplotlib.colors import LogNorm
import pytest
import warnings
def test_contour_shape_1d_valid():
x = np.arange(10)
y = np.arange(9)
z = np.random.random((9, 10))
fig, ax = plt.subplots()
ax.contour(x, y, z)
def test_contour_shape_2d_valid():
x = np.arange(10)
y = np.arange(9)
xg, yg = np.meshgrid(x, y)
z = np.random.random((9, 10))
fig, ax = plt.subplots()
ax.contour(xg, yg, z)
def test_contour_shape_mismatch_1():
x = np.arange(9)
y = np.arange(9)
z = np.random.random((9, 10))
fig, ax = plt.subplots()
with pytest.raises(TypeError) as excinfo:
ax.contour(x, y, z)
excinfo.match(r'Length of x must be number of columns in z.')
def test_contour_shape_mismatch_2():
x = np.arange(10)
y = np.arange(10)
z = np.random.random((9, 10))
fig, ax = plt.subplots()
with pytest.raises(TypeError) as excinfo:
ax.contour(x, y, z)
excinfo.match(r'Length of y must be number of rows in z.')
def test_contour_shape_mismatch_3():
x = np.arange(10)
y = np.arange(10)
xg, yg = np.meshgrid(x, y)
z = np.random.random((9, 10))
fig, ax = plt.subplots()
with pytest.raises(TypeError) as excinfo:
ax.contour(xg, y, z)
excinfo.match(r'Number of dimensions of x and y should match.')
with pytest.raises(TypeError) as excinfo:
ax.contour(x, yg, z)
excinfo.match(r'Number of dimensions of x and y should match.')
def test_contour_shape_mismatch_4():
g = np.random.random((9, 10))
b = np.random.random((9, 9))
z = np.random.random((9, 10))
fig, ax = plt.subplots()
with pytest.raises(TypeError) as excinfo:
ax.contour(b, g, z)
excinfo.match(r'Shape of x does not match that of z: found \(9L?, 9L?\) ' +
r'instead of \(9L?, 10L?\)')
with pytest.raises(TypeError) as excinfo:
ax.contour(g, b, z)
excinfo.match(r'Shape of y does not match that of z: found \(9L?, 9L?\) ' +
r'instead of \(9L?, 10L?\)')
def test_contour_shape_invalid_1():
x = np.random.random((3, 3, 3))
y = np.random.random((3, 3, 3))
z = np.random.random((9, 10))
fig, ax = plt.subplots()
with pytest.raises(TypeError) as excinfo:
ax.contour(x, y, z)
excinfo.match(r'Inputs x and y must be 1D or 2D.')
def test_contour_shape_invalid_2():
x = np.random.random((3, 3, 3))
y = np.random.random((3, 3, 3))
z = np.random.random((3, 3, 3))
fig, ax = plt.subplots()
with pytest.raises(TypeError) as excinfo:
ax.contour(x, y, z)
excinfo.match(r'Input z must be a 2D array.')
def test_contour_empty_levels():
x = np.arange(9)
z = np.random.random((9, 9))
fig, ax = plt.subplots()
with pytest.warns(UserWarning) as record:
ax.contour(x, x, z, levels=[])
assert len(record) == 1
def test_contour_Nlevels():
# A scalar levels arg or kwarg should trigger auto level generation.
# https://github.com/matplotlib/matplotlib/issues/11913
z = np.arange(12).reshape((3, 4))
fig, ax = plt.subplots()
cs1 = ax.contour(z, 5)
assert len(cs1.levels) > 1
cs2 = ax.contour(z, levels=5)
assert (cs1.levels == cs2.levels).all()
def test_contour_badlevel_fmt():
# test funny edge case from
# https://github.com/matplotlib/matplotlib/issues/9742
# User supplied fmt for each level as a dictionary, but
# MPL changed the level to the minimum data value because
# no contours possible.
# This would error out pre
# https://github.com/matplotlib/matplotlib/pull/9743
x = np.arange(9)
z = np.zeros((9, 9))
fig, ax = plt.subplots()
fmt = {1.: '%1.2f'}
with pytest.warns(UserWarning) as record:
cs = ax.contour(x, x, z, levels=[1.])
ax.clabel(cs, fmt=fmt)
assert len(record) == 1
def test_contour_uniform_z():
x = np.arange(9)
z = np.ones((9, 9))
fig, ax = plt.subplots()
with pytest.warns(UserWarning) as record:
ax.contour(x, x, z)
assert len(record) == 1
@image_comparison(baseline_images=['contour_manual_labels'],
savefig_kwarg={'dpi': 200}, remove_text=True, style='mpl20')
def test_contour_manual_labels():
x, y = np.meshgrid(np.arange(0, 10), np.arange(0, 10))
z = np.max(np.dstack([abs(x), abs(y)]), 2)
plt.figure(figsize=(6, 2), dpi=200)
cs = plt.contour(x, y, z)
pts = np.array([(1.5, 3.0), (1.5, 4.4), (1.5, 6.0)])
plt.clabel(cs, manual=pts)
@image_comparison(baseline_images=['contour_labels_size_color'],
extensions=['png'], remove_text=True, style='mpl20')
def test_contour_labels_size_color():
x, y = np.meshgrid(np.arange(0, 10), np.arange(0, 10))
z = np.max(np.dstack([abs(x), abs(y)]), 2)
plt.figure(figsize=(6, 2))
cs = plt.contour(x, y, z)
pts = np.array([(1.5, 3.0), (1.5, 4.4), (1.5, 6.0)])
plt.clabel(cs, manual=pts, fontsize='small', colors=('r', 'g'))
@image_comparison(baseline_images=['contour_manual_colors_and_levels'],
extensions=['png'], remove_text=True)
def test_given_colors_levels_and_extends():
_, axes = plt.subplots(2, 4)
data = np.arange(12).reshape(3, 4)
colors = ['red', 'yellow', 'pink', 'blue', 'black']
levels = [2, 4, 8, 10]
for i, ax in enumerate(axes.flatten()):
filled = i % 2 == 0.
extend = ['neither', 'min', 'max', 'both'][i // 2]
if filled:
# If filled, we have 3 colors with no extension,
# 4 colors with one extension, and 5 colors with both extensions
first_color = 1 if extend in ['max', 'neither'] else None
last_color = -1 if extend in ['min', 'neither'] else None
c = ax.contourf(data, colors=colors[first_color:last_color],
levels=levels, extend=extend)
else:
# If not filled, we have 4 levels and 4 colors
c = ax.contour(data, colors=colors[:-1],
levels=levels, extend=extend)
plt.colorbar(c, ax=ax)
@image_comparison(baseline_images=['contour_datetime_axis'],
extensions=['png'], remove_text=False, style='mpl20')
def test_contour_datetime_axis():
fig = plt.figure()
fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15)
base = datetime.datetime(2013, 1, 1)
x = np.array([base + datetime.timedelta(days=d) for d in range(20)])
y = np.arange(20)
z1, z2 = np.meshgrid(np.arange(20), np.arange(20))
z = z1 * z2
plt.subplot(221)
plt.contour(x, y, z)
plt.subplot(222)
plt.contourf(x, y, z)
x = np.repeat(x[np.newaxis], 20, axis=0)
y = np.repeat(y[:, np.newaxis], 20, axis=1)
plt.subplot(223)
plt.contour(x, y, z)
plt.subplot(224)
plt.contourf(x, y, z)
for ax in fig.get_axes():
for label in ax.get_xticklabels():
label.set_ha('right')
label.set_rotation(30)
@image_comparison(baseline_images=['contour_test_label_transforms'],
extensions=['png'], remove_text=True, style='mpl20')
def test_labels():
# Adapted from pylab_examples example code: contour_demo.py
# see issues #2475, #2843, and #2818 for explanation
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
(2 * np.pi * 0.5 * 1.5))
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
fig, ax = plt.subplots(1, 1)
CS = ax.contour(X, Y, Z)
disp_units = [(216, 177), (359, 290), (521, 406)]
data_units = [(-2, .5), (0, -1.5), (2.8, 1)]
CS.clabel()
for x, y in data_units:
CS.add_label_near(x, y, inline=True, transform=None)
for x, y in disp_units:
CS.add_label_near(x, y, inline=True, transform=False)
@image_comparison(baseline_images=['contour_corner_mask_False',
'contour_corner_mask_True'],
extensions=['png'], remove_text=True)
def test_corner_mask():
n = 60
mask_level = 0.95
noise_amp = 1.0
np.random.seed([1])
x, y = np.meshgrid(np.linspace(0, 2.0, n), np.linspace(0, 2.0, n))
z = np.cos(7*x)*np.sin(8*y) + noise_amp*np.random.rand(n, n)
mask = np.where(np.random.rand(n, n) >= mask_level, True, False)
z = np.ma.array(z, mask=mask)
for corner_mask in [False, True]:
fig = plt.figure()
plt.contourf(z, corner_mask=corner_mask)
def test_contourf_decreasing_levels():
# github issue 5477.
z = [[0.1, 0.3], [0.5, 0.7]]
plt.figure()
with pytest.raises(ValueError):
plt.contourf(z, [1.0, 0.0])
def test_contourf_symmetric_locator():
# github issue 7271
z = np.arange(12).reshape((3, 4))
locator = plt.MaxNLocator(nbins=4, symmetric=True)
cs = plt.contourf(z, locator=locator)
assert_array_almost_equal(cs.levels, np.linspace(-12, 12, 5))
def test_contour_1x1_array():
# github issue 8197
with pytest.raises(TypeError) as excinfo:
plt.contour([[0]])
excinfo.match(r'Input z must be at least a 2x2 array.')
with pytest.raises(TypeError) as excinfo:
plt.contour([0], [0], [[0]])
excinfo.match(r'Input z must be at least a 2x2 array.')
def test_internal_cpp_api():
# Following github issue 8197.
import matplotlib._contour as _contour
with pytest.raises(TypeError) as excinfo:
qcg = _contour.QuadContourGenerator()
excinfo.match(r'function takes exactly 6 arguments \(0 given\)')
with pytest.raises(ValueError) as excinfo:
qcg = _contour.QuadContourGenerator(1, 2, 3, 4, 5, 6)
excinfo.match(r'Expected 2-dimensional array, got 0')
with pytest.raises(ValueError) as excinfo:
qcg = _contour.QuadContourGenerator([[0]], [[0]], [[]], None, True, 0)
excinfo.match(r'x, y and z must all be 2D arrays with the same dimensions')
with pytest.raises(ValueError) as excinfo:
qcg = _contour.QuadContourGenerator([[0]], [[0]], [[0]], None, True, 0)
excinfo.match(r'x, y and z must all be at least 2x2 arrays')
arr = [[0, 1], [2, 3]]
with pytest.raises(ValueError) as excinfo:
qcg = _contour.QuadContourGenerator(arr, arr, arr, [[0]], True, 0)
excinfo.match(r'If mask is set it must be a 2D array with the same ' +
r'dimensions as x.')
qcg = _contour.QuadContourGenerator(arr, arr, arr, None, True, 0)
with pytest.raises(ValueError) as excinfo:
qcg.create_filled_contour(1, 0)
excinfo.match(r'filled contour levels must be increasing')
def test_circular_contour_warning():
# Check that almost circular contours don't throw a warning
with pytest.warns(None) as record:
x, y = np.meshgrid(np.linspace(-2, 2, 4), np.linspace(-2, 2, 4))
r = np.sqrt(x ** 2 + y ** 2)
plt.figure()
cs = plt.contour(x, y, r)
plt.clabel(cs)
assert len(record) == 0
@image_comparison(baseline_images=['contour_log_extension'],
extensions=['png'], remove_text=True, style='mpl20')
def test_contourf_log_extension():
# Test that contourf with lognorm is extended correctly
fig = plt.figure(figsize=(10, 5))
fig.subplots_adjust(left=0.05, right=0.95)
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)
# make data set with large range e.g. between 1e-8 and 1e10
data_exp = np.linspace(-7.5, 9.5, 1200)
data = np.power(10, data_exp).reshape(30, 40)
# make manual levels e.g. between 1e-4 and 1e-6
levels_exp = np.arange(-4., 7.)
levels = np.power(10., levels_exp)
# original data
c1 = ax1.contourf(data,
norm=LogNorm(vmin=data.min(), vmax=data.max()))
# just show data in levels
c2 = ax2.contourf(data, levels=levels,
norm=LogNorm(vmin=levels.min(), vmax=levels.max()),
extend='neither')
# extend data from levels
c3 = ax3.contourf(data, levels=levels,
norm=LogNorm(vmin=levels.min(), vmax=levels.max()),
extend='both')
plt.colorbar(c1, ax=ax1)
plt.colorbar(c2, ax=ax2)
plt.colorbar(c3, ax=ax3)
@image_comparison(baseline_images=['contour_addlines'],
extensions=['png'], remove_text=True, style='mpl20')
def test_contour_addlines():
fig, ax = plt.subplots()
np.random.seed(19680812)
X = np.random.rand(10, 10)*10000
pcm = ax.pcolormesh(X)
# add 1000 to make colors visible...
cont = ax.contour(X+1000)
cb = fig.colorbar(pcm)
cb.add_lines(cont)
@@ -0,0 +1,208 @@
import warnings
import platform
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
import numpy as np
import pytest
from cycler import cycler
@image_comparison(baseline_images=['color_cycle_basic'], remove_text=True,
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
extensions=['png'])
def test_colorcycle_basic():
fig, ax = plt.subplots()
ax.set_prop_cycle(cycler('color', ['r', 'g', 'y']))
xs = np.arange(10)
ys = 0.25 * xs + 2
ax.plot(xs, ys, label='red', lw=4)
ys = 0.45 * xs + 3
ax.plot(xs, ys, label='green', lw=4)
ys = 0.65 * xs + 4
ax.plot(xs, ys, label='yellow', lw=4)
ys = 0.85 * xs + 5
ax.plot(xs, ys, label='red2', lw=4)
ax.legend(loc='upper left')
@image_comparison(baseline_images=['marker_cycle', 'marker_cycle'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
remove_text=True, extensions=['png'])
def test_marker_cycle():
fig, ax = plt.subplots()
ax.set_prop_cycle(cycler('c', ['r', 'g', 'y']) +
cycler('marker', ['.', '*', 'x']))
xs = np.arange(10)
ys = 0.25 * xs + 2
ax.plot(xs, ys, label='red dot', lw=4, ms=16)
ys = 0.45 * xs + 3
ax.plot(xs, ys, label='green star', lw=4, ms=16)
ys = 0.65 * xs + 4
ax.plot(xs, ys, label='yellow x', lw=4, ms=16)
ys = 0.85 * xs + 5
ax.plot(xs, ys, label='red2 dot', lw=4, ms=16)
ax.legend(loc='upper left')
fig, ax = plt.subplots()
# Test keyword arguments, numpy arrays, and generic iterators
ax.set_prop_cycle(c=np.array(['r', 'g', 'y']),
marker=iter(['.', '*', 'x']))
xs = np.arange(10)
ys = 0.25 * xs + 2
ax.plot(xs, ys, label='red dot', lw=4, ms=16)
ys = 0.45 * xs + 3
ax.plot(xs, ys, label='green star', lw=4, ms=16)
ys = 0.65 * xs + 4
ax.plot(xs, ys, label='yellow x', lw=4, ms=16)
ys = 0.85 * xs + 5
ax.plot(xs, ys, label='red2 dot', lw=4, ms=16)
ax.legend(loc='upper left')
@image_comparison(baseline_images=['lineprop_cycle_basic'], remove_text=True,
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
extensions=['png'])
def test_linestylecycle_basic():
fig, ax = plt.subplots()
ax.set_prop_cycle(cycler('ls', ['-', '--', ':']))
xs = np.arange(10)
ys = 0.25 * xs + 2
ax.plot(xs, ys, label='solid', lw=4, color='k')
ys = 0.45 * xs + 3
ax.plot(xs, ys, label='dashed', lw=4, color='k')
ys = 0.65 * xs + 4
ax.plot(xs, ys, label='dotted', lw=4, color='k')
ys = 0.85 * xs + 5
ax.plot(xs, ys, label='solid2', lw=4, color='k')
ax.legend(loc='upper left')
@image_comparison(baseline_images=['fill_cycle_basic'], remove_text=True,
extensions=['png'])
def test_fillcycle_basic():
fig, ax = plt.subplots()
ax.set_prop_cycle(cycler('c', ['r', 'g', 'y']) +
cycler('hatch', ['xx', 'O', '|-']) +
cycler('linestyle', ['-', '--', ':']))
xs = np.arange(10)
ys = 0.25 * xs**.5 + 2
ax.fill(xs, ys, label='red, xx', linewidth=3)
ys = 0.45 * xs**.5 + 3
ax.fill(xs, ys, label='green, circle', linewidth=3)
ys = 0.65 * xs**.5 + 4
ax.fill(xs, ys, label='yellow, cross', linewidth=3)
ys = 0.85 * xs**.5 + 5
ax.fill(xs, ys, label='red2, xx', linewidth=3)
ax.legend(loc='upper left')
@image_comparison(baseline_images=['fill_cycle_ignore'], remove_text=True,
extensions=['png'])
def test_fillcycle_ignore():
fig, ax = plt.subplots()
ax.set_prop_cycle(cycler('color', ['r', 'g', 'y']) +
cycler('hatch', ['xx', 'O', '|-']) +
cycler('marker', ['.', '*', 'D']))
xs = np.arange(10)
ys = 0.25 * xs**.5 + 2
# Should not advance the cycler, even though there is an
# unspecified property in the cycler "marker".
# "marker" is not a Polygon property, and should be ignored.
ax.fill(xs, ys, 'r', hatch='xx', label='red, xx')
ys = 0.45 * xs**.5 + 3
# Allow the cycler to advance, but specify some properties
ax.fill(xs, ys, hatch='O', label='red, circle')
ys = 0.65 * xs**.5 + 4
ax.fill(xs, ys, label='green, circle')
ys = 0.85 * xs**.5 + 5
ax.fill(xs, ys, label='yellow, cross')
ax.legend(loc='upper left')
@image_comparison(baseline_images=['property_collision_plot'],
remove_text=True, extensions=['png'])
def test_property_collision_plot():
fig, ax = plt.subplots()
ax.set_prop_cycle('linewidth', [2, 4])
for c in range(1, 4):
ax.plot(np.arange(10), c * np.arange(10), lw=0.1, color='k')
ax.plot(np.arange(10), 4 * np.arange(10), color='k')
ax.plot(np.arange(10), 5 * np.arange(10), color='k')
@image_comparison(baseline_images=['property_collision_fill'],
remove_text=True, extensions=['png'])
def test_property_collision_fill():
fig, ax = plt.subplots()
xs = np.arange(10)
ys = 0.25 * xs**.5 + 2
ax.set_prop_cycle(linewidth=[2, 3, 4, 5, 6], facecolor='bgcmy')
for c in range(1, 4):
ax.fill(xs, c * ys, lw=0.1)
ax.fill(xs, 4 * ys)
ax.fill(xs, 5 * ys)
def test_valid_input_forms():
fig, ax = plt.subplots()
# These should not raise an error.
ax.set_prop_cycle(None)
ax.set_prop_cycle(cycler('linewidth', [1, 2]))
ax.set_prop_cycle('color', 'rgywkbcm')
ax.set_prop_cycle('lw', (1, 2))
ax.set_prop_cycle('linewidth', [1, 2])
ax.set_prop_cycle('linewidth', iter([1, 2]))
ax.set_prop_cycle('linewidth', np.array([1, 2]))
ax.set_prop_cycle('color', np.array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]]))
ax.set_prop_cycle('dashes', [[], [13, 2], [8, 3, 1, 3], [None, None]])
ax.set_prop_cycle(lw=[1, 2], color=['k', 'w'], ls=['-', '--'])
ax.set_prop_cycle(lw=np.array([1, 2]),
color=np.array(['k', 'w']),
ls=np.array(['-', '--']))
assert True
def test_cycle_reset():
fig, ax = plt.subplots()
# Can't really test a reset because only a cycle object is stored
# but we can test the first item of the cycle.
prop = next(ax._get_lines.prop_cycler)
ax.set_prop_cycle(linewidth=[10, 9, 4])
assert prop != next(ax._get_lines.prop_cycler)
ax.set_prop_cycle(None)
got = next(ax._get_lines.prop_cycler)
assert prop == got
def test_invalid_input_forms():
fig, ax = plt.subplots()
with pytest.raises((TypeError, ValueError)):
ax.set_prop_cycle(1)
with pytest.raises((TypeError, ValueError)):
ax.set_prop_cycle([1, 2])
with pytest.raises((TypeError, ValueError)):
ax.set_prop_cycle('color', 'fish')
with pytest.raises((TypeError, ValueError)):
ax.set_prop_cycle('linewidth', 1)
with pytest.raises((TypeError, ValueError)):
ax.set_prop_cycle('linewidth', {'1': 1, '2': 2})
with pytest.raises((TypeError, ValueError)):
ax.set_prop_cycle(linewidth=1, color='r')
with pytest.raises((TypeError, ValueError)):
ax.set_prop_cycle('foobar', [1, 2])
with pytest.raises((TypeError, ValueError)):
ax.set_prop_cycle(foobar=[1, 2])
with pytest.raises((TypeError, ValueError)):
ax.set_prop_cycle(cycler(foobar=[1, 2]))
with pytest.raises(ValueError):
ax.set_prop_cycle(cycler(color='rgb', c='cmy'))
@@ -0,0 +1,649 @@
import datetime
import tempfile
from unittest.mock import Mock
import dateutil.tz
import dateutil.rrule
import numpy as np
import pytest
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
from matplotlib.cbook import MatplotlibDeprecationWarning
import matplotlib.dates as mdates
def __has_pytz():
try:
import pytz
return True
except ImportError:
return False
def test_date_numpyx():
# test that numpy dates work properly...
base = datetime.datetime(2017, 1, 1)
time = [base + datetime.timedelta(days=x) for x in range(0, 3)]
timenp = np.array(time, dtype='datetime64[ns]')
data = np.array([0., 2., 1.])
fig = plt.figure(figsize=(10, 2))
ax = fig.add_subplot(1, 1, 1)
h, = ax.plot(time, data)
hnp, = ax.plot(timenp, data)
assert np.array_equal(h.get_xdata(orig=False), hnp.get_xdata(orig=False))
fig = plt.figure(figsize=(10, 2))
ax = fig.add_subplot(1, 1, 1)
h, = ax.plot(data, time)
hnp, = ax.plot(data, timenp)
assert np.array_equal(h.get_ydata(orig=False), hnp.get_ydata(orig=False))
@pytest.mark.parametrize('t0', [datetime.datetime(2017, 1, 1, 0, 1, 1),
[datetime.datetime(2017, 1, 1, 0, 1, 1),
datetime.datetime(2017, 1, 1, 1, 1, 1)],
[[datetime.datetime(2017, 1, 1, 0, 1, 1),
datetime.datetime(2017, 1, 1, 1, 1, 1)],
[datetime.datetime(2017, 1, 1, 2, 1, 1),
datetime.datetime(2017, 1, 1, 3, 1, 1)]]])
@pytest.mark.parametrize('dtype', ['datetime64[s]',
'datetime64[us]',
'datetime64[ms]',
'datetime64[ns]'])
def test_date_date2num_numpy(t0, dtype):
time = mdates.date2num(t0)
tnp = np.array(t0, dtype=dtype)
nptime = mdates.date2num(tnp)
assert np.array_equal(time, nptime)
@pytest.mark.parametrize('dtype', ['datetime64[s]',
'datetime64[us]',
'datetime64[ms]',
'datetime64[ns]'])
def test_date2num_NaT(dtype):
t0 = datetime.datetime(2017, 1, 1, 0, 1, 1)
tmpl = [mdates.date2num(t0), np.nan]
tnp = np.array([t0, 'NaT'], dtype=dtype)
nptime = mdates.date2num(tnp)
np.testing.assert_array_equal(tmpl, nptime)
@pytest.mark.parametrize('units', ['s', 'ms', 'us', 'ns'])
def test_date2num_NaT_scalar(units):
tmpl = mdates.date2num(np.datetime64('NaT', units))
assert np.isnan(tmpl)
@image_comparison(baseline_images=['date_empty'], extensions=['png'])
def test_date_empty():
# make sure mpl does the right thing when told to plot dates even
# if no date data has been presented, cf
# http://sourceforge.net/tracker/?func=detail&aid=2850075&group_id=80706&atid=560720
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.xaxis_date()
@image_comparison(baseline_images=['date_axhspan'], extensions=['png'])
def test_date_axhspan():
# test ax hspan with date inputs
t0 = datetime.datetime(2009, 1, 20)
tf = datetime.datetime(2009, 1, 21)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.axhspan(t0, tf, facecolor="blue", alpha=0.25)
ax.set_ylim(t0 - datetime.timedelta(days=5),
tf + datetime.timedelta(days=5))
fig.subplots_adjust(left=0.25)
@image_comparison(baseline_images=['date_axvspan'], extensions=['png'])
def test_date_axvspan():
# test ax hspan with date inputs
t0 = datetime.datetime(2000, 1, 20)
tf = datetime.datetime(2010, 1, 21)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.axvspan(t0, tf, facecolor="blue", alpha=0.25)
ax.set_xlim(t0 - datetime.timedelta(days=720),
tf + datetime.timedelta(days=720))
fig.autofmt_xdate()
@image_comparison(baseline_images=['date_axhline'],
extensions=['png'])
def test_date_axhline():
# test ax hline with date inputs
t0 = datetime.datetime(2009, 1, 20)
tf = datetime.datetime(2009, 1, 31)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.axhline(t0, color="blue", lw=3)
ax.set_ylim(t0 - datetime.timedelta(days=5),
tf + datetime.timedelta(days=5))
fig.subplots_adjust(left=0.25)
@image_comparison(baseline_images=['date_axvline'],
extensions=['png'])
def test_date_axvline():
# test ax hline with date inputs
t0 = datetime.datetime(2000, 1, 20)
tf = datetime.datetime(2000, 1, 21)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.axvline(t0, color="red", lw=3)
ax.set_xlim(t0 - datetime.timedelta(days=5),
tf + datetime.timedelta(days=5))
fig.autofmt_xdate()
def test_too_many_date_ticks():
# Attempt to test SF 2715172, see
# https://sourceforge.net/tracker/?func=detail&aid=2715172&group_id=80706&atid=560720
# setting equal datetimes triggers and expander call in
# transforms.nonsingular which results in too many ticks in the
# DayLocator. This should trigger a Locator.MAXTICKS RuntimeError
t0 = datetime.datetime(2000, 1, 20)
tf = datetime.datetime(2000, 1, 20)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
with pytest.warns(UserWarning) as rec:
ax.set_xlim((t0, tf), auto=True)
assert len(rec) == 1
assert 'Attempting to set identical left==right' in str(rec[0].message)
ax.plot([], [])
ax.xaxis.set_major_locator(mdates.DayLocator())
with pytest.raises(RuntimeError):
fig.savefig('junk.png')
@image_comparison(baseline_images=['RRuleLocator_bounds'], extensions=['png'])
def test_RRuleLocator():
import matplotlib.testing.jpl_units as units
units.register()
# This will cause the RRuleLocator to go out of bounds when it tries
# to add padding to the limits, so we make sure it caps at the correct
# boundary values.
t0 = datetime.datetime(1000, 1, 1)
tf = datetime.datetime(6000, 1, 1)
fig = plt.figure()
ax = plt.subplot(111)
ax.set_autoscale_on(True)
ax.plot([t0, tf], [0.0, 1.0], marker='o')
rrule = mdates.rrulewrapper(dateutil.rrule.YEARLY, interval=500)
locator = mdates.RRuleLocator(rrule)
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(locator))
ax.autoscale_view()
fig.autofmt_xdate()
def test_RRuleLocator_dayrange():
loc = mdates.DayLocator()
x1 = datetime.datetime(year=1, month=1, day=1, tzinfo=mdates.UTC)
y1 = datetime.datetime(year=1, month=1, day=16, tzinfo=mdates.UTC)
loc.tick_values(x1, y1)
# On success, no overflow error shall be thrown
@image_comparison(baseline_images=['DateFormatter_fractionalSeconds'],
extensions=['png'])
def test_DateFormatter():
import matplotlib.testing.jpl_units as units
units.register()
# Lets make sure that DateFormatter will allow us to have tick marks
# at intervals of fractional seconds.
t0 = datetime.datetime(2001, 1, 1, 0, 0, 0)
tf = datetime.datetime(2001, 1, 1, 0, 0, 1)
fig = plt.figure()
ax = plt.subplot(111)
ax.set_autoscale_on(True)
ax.plot([t0, tf], [0.0, 1.0], marker='o')
# rrule = mpldates.rrulewrapper( dateutil.rrule.YEARLY, interval=500 )
# locator = mpldates.RRuleLocator( rrule )
# ax.xaxis.set_major_locator( locator )
# ax.xaxis.set_major_formatter( mpldates.AutoDateFormatter(locator) )
ax.autoscale_view()
fig.autofmt_xdate()
def test_date_formatter_strftime():
"""
Tests that DateFormatter matches datetime.strftime,
check microseconds for years before 1900 for bug #3179
as well as a few related issues for years before 1900.
"""
def test_strftime_fields(dt):
"""For datetime object dt, check DateFormatter fields"""
# Note: the last couple of %%s are to check multiple %s are handled
# properly; %% should get replaced by %.
formatter = mdates.DateFormatter("%w %d %m %y %Y %H %I %M %S %%%f %%x")
# Compute date fields without using datetime.strftime,
# since datetime.strftime does not work before year 1900
formatted_date_str = (
"{weekday} {day:02d} {month:02d} {year:02d} {full_year:04d} "
"{hour24:02d} {hour12:02d} {minute:02d} {second:02d} "
"%{microsecond:06d} %x"
.format(
weekday=str((dt.weekday() + 1) % 7),
day=dt.day,
month=dt.month,
year=dt.year % 100,
full_year=dt.year,
hour24=dt.hour,
hour12=((dt.hour-1) % 12) + 1,
minute=dt.minute,
second=dt.second,
microsecond=dt.microsecond))
with pytest.warns(MatplotlibDeprecationWarning):
assert formatter.strftime(dt) == formatted_date_str
try:
# Test strftime("%x") with the current locale.
import locale # Might not exist on some platforms, such as Windows
locale_formatter = mdates.DateFormatter("%x")
locale_d_fmt = locale.nl_langinfo(locale.D_FMT)
expanded_formatter = mdates.DateFormatter(locale_d_fmt)
with pytest.warns(MatplotlibDeprecationWarning):
assert locale_formatter.strftime(dt) == \
expanded_formatter.strftime(dt)
except (ImportError, AttributeError):
pass
for year in range(1, 3000, 71):
# Iterate through random set of years
test_strftime_fields(datetime.datetime(year, 1, 1))
test_strftime_fields(datetime.datetime(year, 2, 3, 4, 5, 6, 12345))
def test_date_formatter_callable():
scale = -11
locator = Mock(_get_unit=Mock(return_value=scale))
callable_formatting_function = (lambda dates, _:
[dt.strftime('%d-%m//%Y') for dt in dates])
formatter = mdates.AutoDateFormatter(locator)
formatter.scaled[-10] = callable_formatting_function
assert formatter([datetime.datetime(2014, 12, 25)]) == ['25-12//2014']
def test_drange():
"""
This test should check if drange works as expected, and if all the
rounding errors are fixed
"""
start = datetime.datetime(2011, 1, 1, tzinfo=mdates.UTC)
end = datetime.datetime(2011, 1, 2, tzinfo=mdates.UTC)
delta = datetime.timedelta(hours=1)
# We expect 24 values in drange(start, end, delta), because drange returns
# dates from an half open interval [start, end)
assert len(mdates.drange(start, end, delta)) == 24
# if end is a little bit later, we expect the range to contain one element
# more
end = end + datetime.timedelta(microseconds=1)
assert len(mdates.drange(start, end, delta)) == 25
# reset end
end = datetime.datetime(2011, 1, 2, tzinfo=mdates.UTC)
# and tst drange with "complicated" floats:
# 4 hours = 1/6 day, this is an "dangerous" float
delta = datetime.timedelta(hours=4)
daterange = mdates.drange(start, end, delta)
assert len(daterange) == 6
assert mdates.num2date(daterange[-1]) == (end - delta)
def test_empty_date_with_year_formatter():
# exposes sf bug 2861426:
# https://sourceforge.net/tracker/?func=detail&aid=2861426&group_id=80706&atid=560720
# update: I am no longer believe this is a bug, as I commented on
# the tracker. The question is now: what to do with this test
import matplotlib.dates as dates
fig = plt.figure()
ax = fig.add_subplot(111)
yearFmt = dates.DateFormatter('%Y')
ax.xaxis.set_major_formatter(yearFmt)
with tempfile.TemporaryFile() as fh:
with pytest.raises(ValueError):
fig.savefig(fh)
def test_auto_date_locator():
def _create_auto_date_locator(date1, date2):
locator = mdates.AutoDateLocator(interval_multiples=False)
locator.create_dummy_axis()
locator.set_view_interval(mdates.date2num(date1),
mdates.date2num(date2))
return locator
d1 = datetime.datetime(1990, 1, 1)
results = ([datetime.timedelta(weeks=52 * 200),
['1990-01-01 00:00:00+00:00', '2010-01-01 00:00:00+00:00',
'2030-01-01 00:00:00+00:00', '2050-01-01 00:00:00+00:00',
'2070-01-01 00:00:00+00:00', '2090-01-01 00:00:00+00:00',
'2110-01-01 00:00:00+00:00', '2130-01-01 00:00:00+00:00',
'2150-01-01 00:00:00+00:00', '2170-01-01 00:00:00+00:00']
],
[datetime.timedelta(weeks=52),
['1990-01-01 00:00:00+00:00', '1990-02-01 00:00:00+00:00',
'1990-03-01 00:00:00+00:00', '1990-04-01 00:00:00+00:00',
'1990-05-01 00:00:00+00:00', '1990-06-01 00:00:00+00:00',
'1990-07-01 00:00:00+00:00', '1990-08-01 00:00:00+00:00',
'1990-09-01 00:00:00+00:00', '1990-10-01 00:00:00+00:00',
'1990-11-01 00:00:00+00:00', '1990-12-01 00:00:00+00:00']
],
[datetime.timedelta(days=141),
['1990-01-05 00:00:00+00:00', '1990-01-26 00:00:00+00:00',
'1990-02-16 00:00:00+00:00', '1990-03-09 00:00:00+00:00',
'1990-03-30 00:00:00+00:00', '1990-04-20 00:00:00+00:00',
'1990-05-11 00:00:00+00:00']
],
[datetime.timedelta(days=40),
['1990-01-03 00:00:00+00:00', '1990-01-10 00:00:00+00:00',
'1990-01-17 00:00:00+00:00', '1990-01-24 00:00:00+00:00',
'1990-01-31 00:00:00+00:00', '1990-02-07 00:00:00+00:00']
],
[datetime.timedelta(hours=40),
['1990-01-01 00:00:00+00:00', '1990-01-01 04:00:00+00:00',
'1990-01-01 08:00:00+00:00', '1990-01-01 12:00:00+00:00',
'1990-01-01 16:00:00+00:00', '1990-01-01 20:00:00+00:00',
'1990-01-02 00:00:00+00:00', '1990-01-02 04:00:00+00:00',
'1990-01-02 08:00:00+00:00', '1990-01-02 12:00:00+00:00',
'1990-01-02 16:00:00+00:00']
],
[datetime.timedelta(minutes=20),
['1990-01-01 00:00:00+00:00', '1990-01-01 00:05:00+00:00',
'1990-01-01 00:10:00+00:00', '1990-01-01 00:15:00+00:00',
'1990-01-01 00:20:00+00:00']
],
[datetime.timedelta(seconds=40),
['1990-01-01 00:00:00+00:00', '1990-01-01 00:00:05+00:00',
'1990-01-01 00:00:10+00:00', '1990-01-01 00:00:15+00:00',
'1990-01-01 00:00:20+00:00', '1990-01-01 00:00:25+00:00',
'1990-01-01 00:00:30+00:00', '1990-01-01 00:00:35+00:00',
'1990-01-01 00:00:40+00:00']
],
[datetime.timedelta(microseconds=1500),
['1989-12-31 23:59:59.999500+00:00',
'1990-01-01 00:00:00+00:00',
'1990-01-01 00:00:00.000500+00:00',
'1990-01-01 00:00:00.001000+00:00',
'1990-01-01 00:00:00.001500+00:00']
],
)
for t_delta, expected in results:
d2 = d1 + t_delta
locator = _create_auto_date_locator(d1, d2)
assert list(map(str, mdates.num2date(locator()))) == expected
def test_auto_date_locator_intmult():
def _create_auto_date_locator(date1, date2):
locator = mdates.AutoDateLocator(interval_multiples=True)
locator.create_dummy_axis()
locator.set_view_interval(mdates.date2num(date1),
mdates.date2num(date2))
return locator
d1 = datetime.datetime(1997, 1, 1)
results = ([datetime.timedelta(weeks=52 * 200),
['1980-01-01 00:00:00+00:00', '2000-01-01 00:00:00+00:00',
'2020-01-01 00:00:00+00:00', '2040-01-01 00:00:00+00:00',
'2060-01-01 00:00:00+00:00', '2080-01-01 00:00:00+00:00',
'2100-01-01 00:00:00+00:00', '2120-01-01 00:00:00+00:00',
'2140-01-01 00:00:00+00:00', '2160-01-01 00:00:00+00:00',
'2180-01-01 00:00:00+00:00', '2200-01-01 00:00:00+00:00']
],
[datetime.timedelta(weeks=52),
['1997-01-01 00:00:00+00:00', '1997-02-01 00:00:00+00:00',
'1997-03-01 00:00:00+00:00', '1997-04-01 00:00:00+00:00',
'1997-05-01 00:00:00+00:00', '1997-06-01 00:00:00+00:00',
'1997-07-01 00:00:00+00:00', '1997-08-01 00:00:00+00:00',
'1997-09-01 00:00:00+00:00', '1997-10-01 00:00:00+00:00',
'1997-11-01 00:00:00+00:00', '1997-12-01 00:00:00+00:00']
],
[datetime.timedelta(days=141),
['1997-01-01 00:00:00+00:00', '1997-01-22 00:00:00+00:00',
'1997-02-01 00:00:00+00:00', '1997-02-22 00:00:00+00:00',
'1997-03-01 00:00:00+00:00', '1997-03-22 00:00:00+00:00',
'1997-04-01 00:00:00+00:00', '1997-04-22 00:00:00+00:00',
'1997-05-01 00:00:00+00:00', '1997-05-22 00:00:00+00:00']
],
[datetime.timedelta(days=40),
['1997-01-01 00:00:00+00:00', '1997-01-05 00:00:00+00:00',
'1997-01-09 00:00:00+00:00', '1997-01-13 00:00:00+00:00',
'1997-01-17 00:00:00+00:00', '1997-01-21 00:00:00+00:00',
'1997-01-25 00:00:00+00:00', '1997-01-29 00:00:00+00:00',
'1997-02-01 00:00:00+00:00', '1997-02-05 00:00:00+00:00',
'1997-02-09 00:00:00+00:00']
],
[datetime.timedelta(hours=40),
['1997-01-01 00:00:00+00:00', '1997-01-01 04:00:00+00:00',
'1997-01-01 08:00:00+00:00', '1997-01-01 12:00:00+00:00',
'1997-01-01 16:00:00+00:00', '1997-01-01 20:00:00+00:00',
'1997-01-02 00:00:00+00:00', '1997-01-02 04:00:00+00:00',
'1997-01-02 08:00:00+00:00', '1997-01-02 12:00:00+00:00',
'1997-01-02 16:00:00+00:00']
],
[datetime.timedelta(minutes=20),
['1997-01-01 00:00:00+00:00', '1997-01-01 00:05:00+00:00',
'1997-01-01 00:10:00+00:00', '1997-01-01 00:15:00+00:00',
'1997-01-01 00:20:00+00:00']
],
[datetime.timedelta(seconds=40),
['1997-01-01 00:00:00+00:00', '1997-01-01 00:00:05+00:00',
'1997-01-01 00:00:10+00:00', '1997-01-01 00:00:15+00:00',
'1997-01-01 00:00:20+00:00', '1997-01-01 00:00:25+00:00',
'1997-01-01 00:00:30+00:00', '1997-01-01 00:00:35+00:00',
'1997-01-01 00:00:40+00:00']
],
[datetime.timedelta(microseconds=1500),
['1996-12-31 23:59:59.999500+00:00',
'1997-01-01 00:00:00+00:00',
'1997-01-01 00:00:00.000500+00:00',
'1997-01-01 00:00:00.001000+00:00',
'1997-01-01 00:00:00.001500+00:00']
],
)
for t_delta, expected in results:
d2 = d1 + t_delta
locator = _create_auto_date_locator(d1, d2)
assert list(map(str, mdates.num2date(locator()))) == expected
@image_comparison(baseline_images=['date_inverted_limit'],
extensions=['png'])
def test_date_inverted_limit():
# test ax hline with date inputs
t0 = datetime.datetime(2009, 1, 20)
tf = datetime.datetime(2009, 1, 31)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.axhline(t0, color="blue", lw=3)
ax.set_ylim(t0 - datetime.timedelta(days=5),
tf + datetime.timedelta(days=5))
ax.invert_yaxis()
fig.subplots_adjust(left=0.25)
def _test_date2num_dst(date_range, tz_convert):
# Timezones
BRUSSELS = dateutil.tz.gettz('Europe/Brussels')
UTC = mdates.UTC
# Create a list of timezone-aware datetime objects in UTC
# Interval is 0b0.0000011 days, to prevent float rounding issues
dtstart = datetime.datetime(2014, 3, 30, 0, 0, tzinfo=UTC)
interval = datetime.timedelta(minutes=33, seconds=45)
interval_days = 0.0234375 # 2025 / 86400 seconds
N = 8
dt_utc = date_range(start=dtstart, freq=interval, periods=N)
dt_bxl = tz_convert(dt_utc, BRUSSELS)
expected_ordinalf = [735322.0 + (i * interval_days) for i in range(N)]
actual_ordinalf = list(mdates.date2num(dt_bxl))
assert actual_ordinalf == expected_ordinalf
def test_date2num_dst():
# Test for github issue #3896, but in date2num around DST transitions
# with a timezone-aware pandas date_range object.
class dt_tzaware(datetime.datetime):
"""
This bug specifically occurs because of the normalization behavior of
pandas Timestamp objects, so in order to replicate it, we need a
datetime-like object that applies timezone normalization after
subtraction.
"""
def __sub__(self, other):
r = super().__sub__(other)
tzinfo = getattr(r, 'tzinfo', None)
if tzinfo is not None:
localizer = getattr(tzinfo, 'normalize', None)
if localizer is not None:
r = tzinfo.normalize(r)
if isinstance(r, datetime.datetime):
r = self.mk_tzaware(r)
return r
def __add__(self, other):
return self.mk_tzaware(super().__add__(other))
def astimezone(self, tzinfo):
dt = super().astimezone(tzinfo)
return self.mk_tzaware(dt)
@classmethod
def mk_tzaware(cls, datetime_obj):
kwargs = {}
attrs = ('year',
'month',
'day',
'hour',
'minute',
'second',
'microsecond',
'tzinfo')
for attr in attrs:
val = getattr(datetime_obj, attr, None)
if val is not None:
kwargs[attr] = val
return cls(**kwargs)
# Define a date_range function similar to pandas.date_range
def date_range(start, freq, periods):
dtstart = dt_tzaware.mk_tzaware(start)
return [dtstart + (i * freq) for i in range(periods)]
# Define a tz_convert function that converts a list to a new time zone.
def tz_convert(dt_list, tzinfo):
return [d.astimezone(tzinfo) for d in dt_list]
_test_date2num_dst(date_range, tz_convert)
def test_date2num_dst_pandas(pd):
# Test for github issue #3896, but in date2num around DST transitions
# with a timezone-aware pandas date_range object.
def tz_convert(*args):
return pd.DatetimeIndex.tz_convert(*args).astype(object)
_test_date2num_dst(pd.date_range, tz_convert)
def _test_rrulewrapper(attach_tz, get_tz):
SYD = get_tz('Australia/Sydney')
dtstart = attach_tz(datetime.datetime(2017, 4, 1, 0), SYD)
dtend = attach_tz(datetime.datetime(2017, 4, 4, 0), SYD)
rule = mdates.rrulewrapper(freq=dateutil.rrule.DAILY, dtstart=dtstart)
act = rule.between(dtstart, dtend)
exp = [datetime.datetime(2017, 4, 1, 13, tzinfo=dateutil.tz.tzutc()),
datetime.datetime(2017, 4, 2, 14, tzinfo=dateutil.tz.tzutc())]
assert act == exp
def test_rrulewrapper():
def attach_tz(dt, zi):
return dt.replace(tzinfo=zi)
_test_rrulewrapper(attach_tz, dateutil.tz.gettz)
@pytest.mark.pytz
@pytest.mark.skipif(not __has_pytz(), reason="Requires pytz")
def test_rrulewrapper_pytz():
# Test to make sure pytz zones are supported in rrules
import pytz
def attach_tz(dt, zi):
return zi.localize(dt)
_test_rrulewrapper(attach_tz, pytz.timezone)
def test_DayLocator():
with pytest.raises(ValueError):
mdates.DayLocator(interval=-1)
with pytest.raises(ValueError):
mdates.DayLocator(interval=-1.5)
with pytest.raises(ValueError):
mdates.DayLocator(interval=0)
with pytest.raises(ValueError):
mdates.DayLocator(interval=1.3)
mdates.DayLocator(interval=1.0)
def test_tz_utc():
dt = datetime.datetime(1970, 1, 1, tzinfo=mdates.UTC)
dt.tzname()
@pytest.mark.parametrize("x, tdelta",
[(1, datetime.timedelta(days=1)),
([1, 1.5], [datetime.timedelta(days=1),
datetime.timedelta(days=1.5)])])
def test_num2timedelta(x, tdelta):
dt = mdates.num2timedelta(x)
assert dt == tdelta
def test_datetime64_in_list():
dt = [np.datetime64('2000-01-01'), np.datetime64('2001-01-01')]
dn = mdates.date2num(dt)
assert np.array_equal(dn, [730120., 730486.])
@@ -0,0 +1,66 @@
import json
from pathlib import Path
import shutil
import matplotlib.dviread as dr
import pytest
def test_PsfontsMap(monkeypatch):
monkeypatch.setattr(dr, 'find_tex_file', lambda x: x)
filename = str(Path(__file__).parent / 'baseline_images/dviread/test.map')
fontmap = dr.PsfontsMap(filename)
# Check all properties of a few fonts
for n in [1, 2, 3, 4, 5]:
key = b'TeXfont%d' % n
entry = fontmap[key]
assert entry.texname == key
assert entry.psname == b'PSfont%d' % n
if n not in [3, 5]:
assert entry.encoding == b'font%d.enc' % n
elif n == 3:
assert entry.encoding == b'enc3.foo'
# We don't care about the encoding of TeXfont5, which specifies
# multiple encodings.
if n not in [1, 5]:
assert entry.filename == b'font%d.pfa' % n
else:
assert entry.filename == b'font%d.pfb' % n
if n == 4:
assert entry.effects == {'slant': -0.1, 'extend': 2.2}
else:
assert entry.effects == {}
# Some special cases
entry = fontmap[b'TeXfont6']
assert entry.filename is None
assert entry.encoding is None
entry = fontmap[b'TeXfont7']
assert entry.filename is None
assert entry.encoding == b'font7.enc'
entry = fontmap[b'TeXfont8']
assert entry.filename == b'font8.pfb'
assert entry.encoding is None
entry = fontmap[b'TeXfont9']
assert entry.filename == b'/absolute/font9.pfb'
# Missing font
with pytest.raises(KeyError) as exc:
fontmap[b'no-such-font']
assert 'no-such-font' in str(exc.value)
@pytest.mark.skipif(shutil.which("kpsewhich") is None,
reason="kpsewhich is not available")
def test_dviread():
dirpath = Path(__file__).parent / 'baseline_images/dviread'
with (dirpath / 'test.json').open() as f:
correct = json.load(f)
with dr.Dvi(str(dirpath / 'test.dvi'), None) as dvi:
data = [{'text': [[t.x, t.y,
chr(t.glyph),
t.font.texname.decode('ascii'),
round(t.font.size, 2)]
for t in page.text],
'boxes': [[b.x, b.y, b.height, b.width] for b in page.boxes]}
for page in dvi]
assert data == correct
@@ -0,0 +1,449 @@
import sys
import warnings
import platform
from matplotlib import rcParams
from matplotlib.testing.decorators import image_comparison, check_figures_equal
from matplotlib.axes import Axes
from matplotlib.ticker import AutoMinorLocator, FixedFormatter
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.gridspec as gridspec
import numpy as np
import pytest
@image_comparison(baseline_images=['figure_align_labels'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0))
def test_align_labels():
# Check the figure.align_labels() command
fig = plt.figure(tight_layout=True)
gs = gridspec.GridSpec(3, 3)
ax = fig.add_subplot(gs[0, :2])
ax.plot(np.arange(0, 1e6, 1000))
ax.set_ylabel('Ylabel0 0')
ax = fig.add_subplot(gs[0, -1])
ax.plot(np.arange(0, 1e4, 100))
for i in range(3):
ax = fig.add_subplot(gs[1, i])
ax.set_ylabel('YLabel1 %d' % i)
ax.set_xlabel('XLabel1 %d' % i)
if i in [0, 2]:
ax.xaxis.set_label_position("top")
ax.xaxis.tick_top()
if i == 0:
for tick in ax.get_xticklabels():
tick.set_rotation(90)
if i == 2:
ax.yaxis.set_label_position("right")
ax.yaxis.tick_right()
for i in range(3):
ax = fig.add_subplot(gs[2, i])
ax.set_xlabel('XLabel2 %d' % (i))
ax.set_ylabel('YLabel2 %d' % (i))
if i == 2:
ax.plot(np.arange(0, 1e4, 10))
ax.yaxis.set_label_position("right")
ax.yaxis.tick_right()
for tick in ax.get_xticklabels():
tick.set_rotation(90)
fig.align_labels()
def test_figure_label():
# pyplot figure creation, selection and closing with figure label and
# number
plt.close('all')
plt.figure('today')
plt.figure(3)
plt.figure('tomorrow')
plt.figure()
plt.figure(0)
plt.figure(1)
plt.figure(3)
assert plt.get_fignums() == [0, 1, 3, 4, 5]
assert plt.get_figlabels() == ['', 'today', '', 'tomorrow', '']
plt.close(10)
plt.close()
plt.close(5)
plt.close('tomorrow')
assert plt.get_fignums() == [0, 1]
assert plt.get_figlabels() == ['', 'today']
def test_fignum_exists():
# pyplot figure creation, selection and closing with fignum_exists
plt.figure('one')
plt.figure(2)
plt.figure('three')
plt.figure()
assert plt.fignum_exists('one')
assert plt.fignum_exists(2)
assert plt.fignum_exists('three')
assert plt.fignum_exists(4)
plt.close('one')
plt.close(4)
assert not plt.fignum_exists('one')
assert not plt.fignum_exists(4)
def test_clf_keyword():
# test if existing figure is cleared with figure() and subplots()
text1 = 'A fancy plot'
text2 = 'Really fancy!'
fig0 = plt.figure(num=1)
fig0.suptitle(text1)
assert [t.get_text() for t in fig0.texts] == [text1]
fig1 = plt.figure(num=1, clear=False)
fig1.text(0.5, 0.5, text2)
assert fig0 is fig1
assert [t.get_text() for t in fig1.texts] == [text1, text2]
fig2, ax2 = plt.subplots(2, 1, num=1, clear=True)
assert fig0 is fig2
assert [t.get_text() for t in fig2.texts] == []
@image_comparison(baseline_images=['figure_today'])
def test_figure():
# named figure support
fig = plt.figure('today')
ax = fig.add_subplot(111)
ax.set_title(fig.get_label())
ax.plot(np.arange(5))
# plot red line in a different figure.
plt.figure('tomorrow')
plt.plot([0, 1], [1, 0], 'r')
# Return to the original; make sure the red line is not there.
plt.figure('today')
plt.close('tomorrow')
@image_comparison(baseline_images=['figure_legend'])
def test_figure_legend():
fig, axes = plt.subplots(2)
axes[0].plot([0, 1], [1, 0], label='x', color='g')
axes[0].plot([0, 1], [0, 1], label='y', color='r')
axes[0].plot([0, 1], [0.5, 0.5], label='y', color='k')
axes[1].plot([0, 1], [1, 0], label='_y', color='r')
axes[1].plot([0, 1], [0, 1], label='z', color='b')
fig.legend()
def test_gca():
fig = plt.figure()
ax1 = fig.add_axes([0, 0, 1, 1])
assert fig.gca(projection='rectilinear') is ax1
assert fig.gca() is ax1
ax2 = fig.add_subplot(121, projection='polar')
assert fig.gca() is ax2
assert fig.gca(polar=True)is ax2
ax3 = fig.add_subplot(122)
assert fig.gca() is ax3
# the final request for a polar axes will end up creating one
# with a spec of 111.
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
# Changing the projection will throw a warning
assert fig.gca(polar=True) is not ax3
assert len(w) == 1
assert fig.gca(polar=True) is not ax2
assert fig.gca().get_geometry() == (1, 1, 1)
fig.sca(ax1)
assert fig.gca(projection='rectilinear') is ax1
assert fig.gca() is ax1
@image_comparison(baseline_images=['figure_suptitle'])
def test_suptitle():
fig, _ = plt.subplots()
fig.suptitle('hello', color='r')
fig.suptitle('title', color='g', rotation='30')
def test_suptitle_fontproperties():
from matplotlib.font_manager import FontProperties
fig, ax = plt.subplots()
fps = FontProperties(size='large', weight='bold')
txt = fig.suptitle('fontprops title', fontproperties=fps)
assert txt.get_fontsize() == fps.get_size_in_points()
assert txt.get_weight() == fps.get_weight()
@image_comparison(baseline_images=['alpha_background'],
# only test png and svg. The PDF output appears correct,
# but Ghostscript does not preserve the background color.
extensions=['png', 'svg'],
savefig_kwarg={'facecolor': (0, 1, 0.4),
'edgecolor': 'none'})
def test_alpha():
# We want an image which has a background color and an
# alpha of 0.4.
fig = plt.figure(figsize=[2, 1])
fig.set_facecolor((0, 1, 0.4))
fig.patch.set_alpha(0.4)
import matplotlib.patches as mpatches
fig.patches.append(mpatches.CirclePolygon([20, 20],
radius=15,
alpha=0.6,
facecolor='red'))
def test_too_many_figures():
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
for i in range(rcParams['figure.max_open_warning'] + 1):
plt.figure()
assert len(w) == 1
def test_iterability_axes_argument():
# This is a regression test for matplotlib/matplotlib#3196. If one of the
# arguments returned by _as_mpl_axes defines __getitem__ but is not
# iterable, this would raise an exception. This is because we check
# whether the arguments are iterable, and if so we try and convert them
# to a tuple. However, the ``iterable`` function returns True if
# __getitem__ is present, but some classes can define __getitem__ without
# being iterable. The tuple conversion is now done in a try...except in
# case it fails.
class MyAxes(Axes):
def __init__(self, *args, myclass=None, **kwargs):
return Axes.__init__(self, *args, **kwargs)
class MyClass(object):
def __getitem__(self, item):
if item != 'a':
raise ValueError("item should be a")
def _as_mpl_axes(self):
return MyAxes, {'myclass': self}
fig = plt.figure()
fig.add_subplot(1, 1, 1, projection=MyClass())
plt.close(fig)
def test_set_fig_size():
fig = plt.figure()
# check figwidth
fig.set_figwidth(5)
assert fig.get_figwidth() == 5
# check figheight
fig.set_figheight(1)
assert fig.get_figheight() == 1
# check using set_size_inches
fig.set_size_inches(2, 4)
assert fig.get_figwidth() == 2
assert fig.get_figheight() == 4
# check using tuple to first argument
fig.set_size_inches((1, 3))
assert fig.get_figwidth() == 1
assert fig.get_figheight() == 3
def test_axes_remove():
fig, axes = plt.subplots(2, 2)
axes[-1, -1].remove()
for ax in axes.ravel()[:-1]:
assert ax in fig.axes
assert axes[-1, -1] not in fig.axes
assert len(fig.axes) == 3
def test_figaspect():
w, h = plt.figaspect(np.float64(2) / np.float64(1))
assert h / w == 2
w, h = plt.figaspect(2)
assert h / w == 2
w, h = plt.figaspect(np.zeros((1, 2)))
assert h / w == 0.5
w, h = plt.figaspect(np.zeros((2, 2)))
assert h / w == 1
@pytest.mark.parametrize('which', [None, 'both', 'major', 'minor'])
def test_autofmt_xdate(which):
date = ['3 Jan 2013', '4 Jan 2013', '5 Jan 2013', '6 Jan 2013',
'7 Jan 2013', '8 Jan 2013', '9 Jan 2013', '10 Jan 2013',
'11 Jan 2013', '12 Jan 2013', '13 Jan 2013', '14 Jan 2013']
time = ['16:44:00', '16:45:00', '16:46:00', '16:47:00', '16:48:00',
'16:49:00', '16:51:00', '16:52:00', '16:53:00', '16:55:00',
'16:56:00', '16:57:00']
angle = 60
minors = [1, 2, 3, 4, 5, 6, 7]
x = mdates.datestr2num(date)
y = mdates.datestr2num(time)
fig, ax = plt.subplots()
ax.plot(x, y)
ax.yaxis_date()
ax.xaxis_date()
ax.xaxis.set_minor_locator(AutoMinorLocator(2))
ax.xaxis.set_minor_formatter(FixedFormatter(minors))
fig.autofmt_xdate(0.2, angle, 'right', which)
if which in ('both', 'major', None):
for label in fig.axes[0].get_xticklabels(False, 'major'):
assert int(label.get_rotation()) == angle
if which in ('both', 'minor'):
for label in fig.axes[0].get_xticklabels(True, 'minor'):
assert int(label.get_rotation()) == angle
@pytest.mark.style('default')
def test_change_dpi():
fig = plt.figure(figsize=(4, 4))
fig.canvas.draw()
assert fig.canvas.renderer.height == 400
assert fig.canvas.renderer.width == 400
fig.dpi = 50
fig.canvas.draw()
assert fig.canvas.renderer.height == 200
assert fig.canvas.renderer.width == 200
def test_invalid_figure_size():
with pytest.raises(ValueError):
plt.figure(figsize=(1, np.nan))
fig = plt.figure()
with pytest.raises(ValueError):
fig.set_size_inches(1, np.nan)
with pytest.raises(ValueError):
fig.add_axes((.1, .1, .5, np.nan))
def test_subplots_shareax_loglabels():
fig, ax_arr = plt.subplots(2, 2, sharex=True, sharey=True, squeeze=False)
for ax in ax_arr.flatten():
ax.plot([10, 20, 30], [10, 20, 30])
ax.set_yscale("log")
ax.set_xscale("log")
for ax in ax_arr[0, :]:
assert 0 == len(ax.xaxis.get_ticklabels(which='both'))
for ax in ax_arr[1, :]:
assert 0 < len(ax.xaxis.get_ticklabels(which='both'))
for ax in ax_arr[:, 1]:
assert 0 == len(ax.yaxis.get_ticklabels(which='both'))
for ax in ax_arr[:, 0]:
assert 0 < len(ax.yaxis.get_ticklabels(which='both'))
def test_savefig():
fig = plt.figure()
msg = "savefig() takes 2 positional arguments but 3 were given"
with pytest.raises(TypeError, message=msg):
fig.savefig("fname1.png", "fname2.png")
def test_figure_repr():
fig = plt.figure(figsize=(10, 20), dpi=10)
assert repr(fig) == "<Figure size 100x200 with 0 Axes>"
def test_warn_cl_plus_tl():
fig, ax = plt.subplots(constrained_layout=True)
with pytest.warns(UserWarning):
# this should warn,
fig.subplots_adjust(top=0.8)
assert not(fig.get_constrained_layout())
@check_figures_equal(extensions=["png", "pdf"])
def test_add_artist(fig_test, fig_ref):
fig_test.set_dpi(100)
fig_ref.set_dpi(100)
ax = fig_test.subplots()
l1 = plt.Line2D([.2, .7], [.7, .7], gid='l1')
l2 = plt.Line2D([.2, .7], [.8, .8], gid='l2')
r1 = plt.Circle((20, 20), 100, transform=None, gid='C1')
r2 = plt.Circle((.7, .5), .05, gid='C2')
r3 = plt.Circle((4.5, .8), .55, transform=fig_test.dpi_scale_trans,
facecolor='crimson', gid='C3')
for a in [l1, l2, r1, r2, r3]:
fig_test.add_artist(a)
l2.remove()
ax2 = fig_ref.subplots()
l1 = plt.Line2D([.2, .7], [.7, .7], transform=fig_ref.transFigure,
gid='l1', zorder=21)
r1 = plt.Circle((20, 20), 100, transform=None, clip_on=False, zorder=20,
gid='C1')
r2 = plt.Circle((.7, .5), .05, transform=fig_ref.transFigure, gid='C2',
zorder=20)
r3 = plt.Circle((4.5, .8), .55, transform=fig_ref.dpi_scale_trans,
facecolor='crimson', clip_on=False, zorder=20, gid='C3')
for a in [l1, r1, r2, r3]:
ax2.add_artist(a)
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python 3.6+")
@pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"])
def test_fspath(fmt, tmpdir):
from pathlib import Path
out = Path(tmpdir, "test.{}".format(fmt))
plt.savefig(out)
with out.open("rb") as file:
# All the supported formats include the format name (case-insensitive)
# in the first 100 bytes.
assert fmt.encode("ascii") in file.read(100).lower()
def test_tightbbox():
fig, ax = plt.subplots()
ax.set_xlim(0, 1)
t = ax.text(1., 0.5, 'This dangles over end')
renderer = fig.canvas.get_renderer()
x1Nom0 = 9.035 # inches
assert np.abs(t.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2
assert np.abs(ax.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2
assert np.abs(fig.get_tightbbox(renderer).x1 - x1Nom0) < 0.05
assert np.abs(fig.get_tightbbox(renderer).x0 - 0.679) < 0.05
# now exclude t from the tight bbox so now the bbox is quite a bit
# smaller
t.set_in_layout(False)
x1Nom = 7.333
assert np.abs(ax.get_tightbbox(renderer).x1 - x1Nom * fig.dpi) < 2
assert np.abs(fig.get_tightbbox(renderer).x1 - x1Nom) < 0.05
t.set_in_layout(True)
x1Nom = 7.333
assert np.abs(ax.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2
# test bbox_extra_artists method...
assert np.abs(ax.get_tightbbox(renderer,
bbox_extra_artists=[]).x1 - x1Nom * fig.dpi) < 2
@@ -0,0 +1,98 @@
import os
import shutil
import tempfile
import warnings
import numpy as np
import pytest
from matplotlib.font_manager import (
findfont, FontProperties, fontManager, json_dump, json_load, get_font,
get_fontconfig_fonts, is_opentype_cff_font)
from matplotlib import rc_context
has_fclist = shutil.which('fc-list') is not None
def test_font_priority():
with rc_context(rc={
'font.sans-serif':
['cmmi10', 'Bitstream Vera Sans']}):
font = findfont(
FontProperties(family=["sans-serif"]))
assert os.path.basename(font) == 'cmmi10.ttf'
# Smoketest get_charmap, which isn't used internally anymore
font = get_font(font)
cmap = font.get_charmap()
assert len(cmap) == 131
assert cmap[8729] == 30
def test_score_weight():
assert 0 == fontManager.score_weight("regular", "regular")
assert 0 == fontManager.score_weight("bold", "bold")
assert (0 < fontManager.score_weight(400, 400) <
fontManager.score_weight("normal", "bold"))
assert (0 < fontManager.score_weight("normal", "regular") <
fontManager.score_weight("normal", "bold"))
assert (fontManager.score_weight("normal", "regular") ==
fontManager.score_weight(400, 400))
def test_json_serialization():
# on windows, we can't open a file twice, so save the name and unlink
# manually...
try:
name = None
with tempfile.NamedTemporaryFile(delete=False) as temp:
name = temp.name
json_dump(fontManager, name)
copy = json_load(name)
finally:
if name and os.path.exists(name):
os.remove(name)
with warnings.catch_warnings():
warnings.filterwarnings('ignore', 'findfont: Font family.*not found')
for prop in ({'family': 'STIXGeneral'},
{'family': 'Bitstream Vera Sans', 'weight': 700},
{'family': 'no such font family'}):
fp = FontProperties(**prop)
assert (fontManager.findfont(fp, rebuild_if_missing=False) ==
copy.findfont(fp, rebuild_if_missing=False))
def test_otf():
fname = '/usr/share/fonts/opentype/freefont/FreeMono.otf'
if os.path.exists(fname):
assert is_opentype_cff_font(fname)
for f in fontManager.ttflist:
if 'otf' in f.fname:
with open(f.fname, 'rb') as fd:
res = fd.read(4) == b'OTTO'
assert res == is_opentype_cff_font(f.fname)
@pytest.mark.skipif(not has_fclist, reason='no fontconfig installed')
def test_get_fontconfig_fonts():
assert len(get_fontconfig_fonts()) > 1
@pytest.mark.parametrize('factor', [2, 4, 6, 8])
def test_hinting_factor(factor):
font = findfont(FontProperties(family=["sans-serif"]))
font1 = get_font(font, hinting_factor=1)
font1.clear()
font1.set_size(12, 100)
font1.set_text('abc')
expected = font1.get_width_height()
hinted_font = get_font(font, hinting_factor=factor)
hinted_font.clear()
hinted_font.set_size(12, 100)
hinted_font.set_text('abc')
# Check that hinting only changes text layout by a small (10%) amount.
np.testing.assert_allclose(hinted_font.get_width_height(), expected,
rtol=0.1)
@@ -0,0 +1,26 @@
import matplotlib.gridspec as gridspec
import pytest
def test_equal():
gs = gridspec.GridSpec(2, 1)
assert gs[0, 0] == gs[0, 0]
assert gs[:, 0] == gs[:, 0]
def test_width_ratios():
"""
Addresses issue #5835.
See at https://github.com/matplotlib/matplotlib/issues/5835.
"""
with pytest.raises(ValueError):
gridspec.GridSpec(1, 1, width_ratios=[2, 1, 3])
def test_height_ratios():
"""
Addresses issue #5835.
See at https://github.com/matplotlib/matplotlib/issues/5835.
"""
with pytest.raises(ValueError):
gridspec.GridSpec(1, 1, height_ratios=[2, 1, 3])
@@ -0,0 +1,924 @@
from contextlib import ExitStack
from copy import copy
import io
import os
import sys
import platform
import urllib.request
import warnings
import numpy as np
from numpy import ma
from numpy.testing import assert_array_equal
from matplotlib import (
colors, image as mimage, patches, pyplot as plt,
rc_context, rcParams)
from matplotlib.image import (AxesImage, BboxImage, FigureImage,
NonUniformImage, PcolorImage)
from matplotlib.testing.decorators import image_comparison
from matplotlib.transforms import Bbox, Affine2D, TransformedBbox
import pytest
@image_comparison(baseline_images=['image_interps'], style='mpl20')
def test_image_interps():
'make the basic nearest, bilinear and bicubic interps'
X = np.arange(100)
X = X.reshape(5, 20)
fig = plt.figure()
ax1 = fig.add_subplot(311)
ax1.imshow(X, interpolation='nearest')
ax1.set_title('three interpolations')
ax1.set_ylabel('nearest')
ax2 = fig.add_subplot(312)
ax2.imshow(X, interpolation='bilinear')
ax2.set_ylabel('bilinear')
ax3 = fig.add_subplot(313)
ax3.imshow(X, interpolation='bicubic')
ax3.set_ylabel('bicubic')
@image_comparison(baseline_images=['interp_nearest_vs_none'],
extensions=['pdf', 'svg'], remove_text=True)
def test_interp_nearest_vs_none():
'Test the effect of "nearest" and "none" interpolation'
# Setting dpi to something really small makes the difference very
# visible. This works fine with pdf, since the dpi setting doesn't
# affect anything but images, but the agg output becomes unusably
# small.
rcParams['savefig.dpi'] = 3
X = np.array([[[218, 165, 32], [122, 103, 238]],
[[127, 255, 0], [255, 99, 71]]], dtype=np.uint8)
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax1.imshow(X, interpolation='none')
ax1.set_title('interpolation none')
ax2 = fig.add_subplot(122)
ax2.imshow(X, interpolation='nearest')
ax2.set_title('interpolation nearest')
def do_figimage(suppressComposite):
""" Helper for the next two tests """
fig = plt.figure(figsize=(2,2), dpi=100)
fig.suppressComposite = suppressComposite
x,y = np.ix_(np.arange(100.0)/100.0, np.arange(100.0)/100.0)
z = np.sin(x**2 + y**2 - x*y)
c = np.sin(20*x**2 + 50*y**2)
img = z + c/5
fig.figimage(img, xo=0, yo=0, origin='lower')
fig.figimage(img[::-1,:], xo=0, yo=100, origin='lower')
fig.figimage(img[:,::-1], xo=100, yo=0, origin='lower')
fig.figimage(img[::-1,::-1], xo=100, yo=100, origin='lower')
@image_comparison(baseline_images=['figimage-0'],
extensions=['png','pdf'])
def test_figimage0():
'test the figimage method'
suppressComposite = False
do_figimage(suppressComposite)
@image_comparison(baseline_images=['figimage-1'],
extensions=['png','pdf'])
def test_figimage1():
'test the figimage method'
suppressComposite = True
do_figimage(suppressComposite)
def test_image_python_io():
fig, ax = plt.subplots()
ax.plot([1,2,3])
buffer = io.BytesIO()
fig.savefig(buffer)
buffer.seek(0)
plt.imread(buffer)
def test_imread_pil_uint16():
pytest.importorskip("PIL")
img = plt.imread(os.path.join(os.path.dirname(__file__),
'baseline_images', 'test_image', 'uint16.tif'))
assert img.dtype == np.uint16
assert np.sum(img) == 134184960
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python 3.6+")
def test_imread_fspath():
pytest.importorskip("PIL")
from pathlib import Path
img = plt.imread(
Path(__file__).parent / 'baseline_images/test_image/uint16.tif')
assert img.dtype == np.uint16
assert np.sum(img) == 134184960
def test_imsave():
# The goal here is that the user can specify an output logical DPI
# for the image, but this will not actually add any extra pixels
# to the image, it will merely be used for metadata purposes.
# So we do the traditional case (dpi == 1), and the new case (dpi
# == 100) and read the resulting PNG files back in and make sure
# the data is 100% identical.
np.random.seed(1)
data = np.random.rand(256, 128)
buff_dpi1 = io.BytesIO()
plt.imsave(buff_dpi1, data, dpi=1)
buff_dpi100 = io.BytesIO()
plt.imsave(buff_dpi100, data, dpi=100)
buff_dpi1.seek(0)
arr_dpi1 = plt.imread(buff_dpi1)
buff_dpi100.seek(0)
arr_dpi100 = plt.imread(buff_dpi100)
assert arr_dpi1.shape == (256, 128, 4)
assert arr_dpi100.shape == (256, 128, 4)
assert_array_equal(arr_dpi1, arr_dpi100)
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python 3.6+")
@pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"])
def test_imsave_fspath(fmt):
Path = pytest.importorskip("pathlib").Path
plt.imsave(Path(os.devnull), np.array([[0, 1]]), format=fmt)
def test_imsave_color_alpha():
# Test that imsave accept arrays with ndim=3 where the third dimension is
# color and alpha without raising any exceptions, and that the data is
# acceptably preserved through a save/read roundtrip.
np.random.seed(1)
for origin in ['lower', 'upper']:
data = np.random.rand(16, 16, 4)
buff = io.BytesIO()
plt.imsave(buff, data, origin=origin, format="png")
buff.seek(0)
arr_buf = plt.imread(buff)
# Recreate the float -> uint8 conversion of the data
# We can only expect to be the same with 8 bits of precision,
# since that's what the PNG file used.
data = (255*data).astype('uint8')
if origin == 'lower':
data = data[::-1]
arr_buf = (255*arr_buf).astype('uint8')
assert_array_equal(data, arr_buf)
@image_comparison(baseline_images=['image_alpha'], remove_text=True)
def test_image_alpha():
plt.figure()
np.random.seed(0)
Z = np.random.rand(6, 6)
plt.subplot(131)
plt.imshow(Z, alpha=1.0, interpolation='none')
plt.subplot(132)
plt.imshow(Z, alpha=0.5, interpolation='none')
plt.subplot(133)
plt.imshow(Z, alpha=0.5, interpolation='nearest')
def test_cursor_data():
from matplotlib.backend_bases import MouseEvent
fig, ax = plt.subplots()
im = ax.imshow(np.arange(100).reshape(10, 10), origin='upper')
x, y = 4, 4
xdisp, ydisp = ax.transData.transform_point([x, y])
event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
assert im.get_cursor_data(event) == 44
# Now try for a point outside the image
# Tests issue #4957
x, y = 10.1, 4
xdisp, ydisp = ax.transData.transform_point([x, y])
event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
assert im.get_cursor_data(event) is None
# Hmm, something is wrong here... I get 0, not None...
# But, this works further down in the tests with extents flipped
#x, y = 0.1, -0.1
#xdisp, ydisp = ax.transData.transform_point([x, y])
#event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
#z = im.get_cursor_data(event)
#assert z is None, "Did not get None, got %d" % z
ax.clear()
# Now try with the extents flipped.
im = ax.imshow(np.arange(100).reshape(10, 10), origin='lower')
x, y = 4, 4
xdisp, ydisp = ax.transData.transform_point([x, y])
event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
assert im.get_cursor_data(event) == 44
fig, ax = plt.subplots()
im = ax.imshow(np.arange(100).reshape(10, 10), extent=[0, 0.5, 0, 0.5])
x, y = 0.25, 0.25
xdisp, ydisp = ax.transData.transform_point([x, y])
event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
assert im.get_cursor_data(event) == 55
# Now try for a point outside the image
# Tests issue #4957
x, y = 0.75, 0.25
xdisp, ydisp = ax.transData.transform_point([x, y])
event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
assert im.get_cursor_data(event) is None
x, y = 0.01, -0.01
xdisp, ydisp = ax.transData.transform_point([x, y])
event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
assert im.get_cursor_data(event) is None
@image_comparison(baseline_images=['image_clip'], style='mpl20')
def test_image_clip():
d = [[1, 2], [3, 4]]
fig, ax = plt.subplots()
im = ax.imshow(d)
patch = patches.Circle((0, 0), radius=1, transform=ax.transData)
im.set_clip_path(patch)
@image_comparison(baseline_images=['image_cliprect'], style='mpl20')
def test_image_cliprect():
import matplotlib.patches as patches
fig, ax = plt.subplots()
d = [[1,2],[3,4]]
im = ax.imshow(d, extent=(0,5,0,5))
rect = patches.Rectangle(
xy=(1,1), width=2, height=2, transform=im.axes.transData)
im.set_clip_path(rect)
@image_comparison(baseline_images=['imshow'], remove_text=True, style='mpl20')
def test_imshow():
fig, ax = plt.subplots()
arr = np.arange(100).reshape((10, 10))
ax.imshow(arr, interpolation="bilinear", extent=(1,2,1,2))
ax.set_xlim(0,3)
ax.set_ylim(0,3)
@image_comparison(baseline_images=['no_interpolation_origin'],
remove_text=True)
def test_no_interpolation_origin():
fig, axs = plt.subplots(2)
axs[0].imshow(np.arange(100).reshape((2, 50)), origin="lower",
interpolation='none')
axs[1].imshow(np.arange(100).reshape((2, 50)), interpolation='none')
@image_comparison(baseline_images=['image_shift'], remove_text=True,
extensions=['pdf', 'svg'])
def test_image_shift():
from matplotlib.colors import LogNorm
imgData = [[1 / x + 1 / y for x in range(1, 100)] for y in range(1, 100)]
tMin = 734717.945208
tMax = 734717.946366
fig, ax = plt.subplots()
ax.imshow(imgData, norm=LogNorm(), interpolation='none',
extent=(tMin, tMax, 1, 100))
ax.set_aspect('auto')
def test_image_edges():
f = plt.figure(figsize=[1, 1])
ax = f.add_axes([0, 0, 1, 1], frameon=False)
data = np.tile(np.arange(12), 15).reshape(20, 9)
im = ax.imshow(data, origin='upper', extent=[-10, 10, -10, 10],
interpolation='none', cmap='gray')
x = y = 2
ax.set_xlim([-x, x])
ax.set_ylim([-y, y])
ax.set_xticks([])
ax.set_yticks([])
buf = io.BytesIO()
f.savefig(buf, facecolor=(0, 1, 0))
buf.seek(0)
im = plt.imread(buf)
r, g, b, a = sum(im[:, 0])
r, g, b, a = sum(im[:, -1])
assert g != 100, 'Expected a non-green edge - but sadly, it was.'
@image_comparison(baseline_images=['image_composite_background'],
remove_text=True,
style='mpl20')
def test_image_composite_background():
fig, ax = plt.subplots()
arr = np.arange(12).reshape(4, 3)
ax.imshow(arr, extent=[0, 2, 15, 0])
ax.imshow(arr, extent=[4, 6, 15, 0])
ax.set_facecolor((1, 0, 0, 0.5))
ax.set_xlim([0, 12])
@image_comparison(baseline_images=['image_composite_alpha'],
remove_text=True)
def test_image_composite_alpha():
"""
Tests that the alpha value is recognized and correctly applied in the
process of compositing images together.
"""
fig, ax = plt.subplots()
arr = np.zeros((11, 21, 4))
arr[:, :, 0] = 1
arr[:, :, 3] = np.concatenate(
(np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1]))
arr2 = np.zeros((21, 11, 4))
arr2[:, :, 0] = 1
arr2[:, :, 1] = 1
arr2[:, :, 3] = np.concatenate(
(np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1]))[:, np.newaxis]
ax.imshow(arr, extent=[1, 2, 5, 0], alpha=0.3)
ax.imshow(arr, extent=[2, 3, 5, 0], alpha=0.6)
ax.imshow(arr, extent=[3, 4, 5, 0])
ax.imshow(arr2, extent=[0, 5, 1, 2])
ax.imshow(arr2, extent=[0, 5, 2, 3], alpha=0.6)
ax.imshow(arr2, extent=[0, 5, 3, 4], alpha=0.3)
ax.set_facecolor((0, 0.5, 0, 1))
ax.set_xlim([0, 5])
ax.set_ylim([5, 0])
@image_comparison(baseline_images=['rasterize_10dpi'],
extensions=['pdf', 'svg'],
remove_text=True, style='mpl20')
def test_rasterize_dpi():
# This test should check rasterized rendering with high output resolution.
# It plots a rasterized line and a normal image with implot. So it will
# catch when images end up in the wrong place in case of non-standard dpi
# setting. Instead of high-res rasterization I use low-res. Therefore
# the fact that the resolution is non-standard is easily checked by
# image_comparison.
img = np.asarray([[1, 2], [3, 4]])
fig, axes = plt.subplots(1, 3, figsize=(3, 1))
axes[0].imshow(img)
axes[1].plot([0,1], [0,1], linewidth=20., rasterized=True)
axes[1].set(xlim=(0, 1), ylim=(-1, 2))
axes[2].plot([0,1], [0,1], linewidth=20.)
axes[2].set(xlim=(0, 1), ylim=(-1, 2))
# Low-dpi PDF rasterization errors prevent proper image comparison tests.
# Hide detailed structures like the axes spines.
for ax in axes:
ax.set_xticks([])
ax.set_yticks([])
for spine in ax.spines.values():
spine.set_visible(False)
rcParams['savefig.dpi'] = 10
@image_comparison(baseline_images=['bbox_image_inverted'], remove_text=True,
style='mpl20')
def test_bbox_image_inverted():
# This is just used to produce an image to feed to BboxImage
image = np.arange(100).reshape((10, 10))
fig, ax = plt.subplots()
bbox_im = BboxImage(
TransformedBbox(Bbox([[100, 100], [0, 0]]), ax.transData))
bbox_im.set_data(image)
bbox_im.set_clip_on(False)
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
ax.add_artist(bbox_im)
image = np.identity(10)
bbox_im = BboxImage(TransformedBbox(Bbox([[0.1, 0.2], [0.3, 0.25]]),
ax.figure.transFigure))
bbox_im.set_data(image)
bbox_im.set_clip_on(False)
ax.add_artist(bbox_im)
def test_get_window_extent_for_AxisImage():
# Create a figure of known size (1000x1000 pixels), place an image
# object at a given location and check that get_window_extent()
# returns the correct bounding box values (in pixels).
im = np.array([[0.25, 0.75, 1.0, 0.75], [0.1, 0.65, 0.5, 0.4],
[0.6, 0.3, 0.0, 0.2], [0.7, 0.9, 0.4, 0.6]])
fig, ax = plt.subplots(figsize=(10, 10), dpi=100)
ax.set_position([0, 0, 1, 1])
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
im_obj = ax.imshow(
im, extent=[0.4, 0.7, 0.2, 0.9], interpolation='nearest')
fig.canvas.draw()
renderer = fig.canvas.renderer
im_bbox = im_obj.get_window_extent(renderer)
assert_array_equal(im_bbox.get_points(), [[400, 200], [700, 900]])
@image_comparison(baseline_images=['zoom_and_clip_upper_origin'],
remove_text=True,
extensions=['png'],
style='mpl20')
def test_zoom_and_clip_upper_origin():
image = np.arange(100)
image = image.reshape((10, 10))
fig, ax = plt.subplots()
ax.imshow(image)
ax.set_ylim(2.0, -0.5)
ax.set_xlim(-0.5, 2.0)
def test_nonuniformimage_setcmap():
ax = plt.gca()
im = NonUniformImage(ax)
im.set_cmap('Blues')
def test_nonuniformimage_setnorm():
ax = plt.gca()
im = NonUniformImage(ax)
im.set_norm(plt.Normalize())
def test_jpeg_2d():
Image = pytest.importorskip('PIL.Image')
# smoke test that mode-L pillow images work.
imd = np.ones((10, 10), dtype='uint8')
for i in range(10):
imd[i, :] = np.linspace(0.0, 1.0, 10) * 255
im = Image.new('L', (10, 10))
im.putdata(imd.flatten())
fig, ax = plt.subplots()
ax.imshow(im)
def test_jpeg_alpha():
Image = pytest.importorskip('PIL.Image')
plt.figure(figsize=(1, 1), dpi=300)
# Create an image that is all black, with a gradient from 0-1 in
# the alpha channel from left to right.
im = np.zeros((300, 300, 4), dtype=float)
im[..., 3] = np.linspace(0.0, 1.0, 300)
plt.figimage(im)
buff = io.BytesIO()
with rc_context({'savefig.facecolor': 'red'}):
plt.savefig(buff, transparent=True, format='jpg', dpi=300)
buff.seek(0)
image = Image.open(buff)
# If this fails, there will be only one color (all black). If this
# is working, we should have all 256 shades of grey represented.
num_colors = len(image.getcolors(256))
assert 175 <= num_colors <= 185
# The fully transparent part should be red.
corner_pixel = image.getpixel((0, 0))
assert corner_pixel == (254, 0, 0)
def test_nonuniformimage_setdata():
ax = plt.gca()
im = NonUniformImage(ax)
x = np.arange(3, dtype=float)
y = np.arange(4, dtype=float)
z = np.arange(12, dtype=float).reshape((4, 3))
im.set_data(x, y, z)
x[0] = y[0] = z[0, 0] = 9.9
assert im._A[0, 0] == im._Ax[0] == im._Ay[0] == 0, 'value changed'
def test_axesimage_setdata():
ax = plt.gca()
im = AxesImage(ax)
z = np.arange(12, dtype=float).reshape((4, 3))
im.set_data(z)
z[0, 0] = 9.9
assert im._A[0, 0] == 0, 'value changed'
def test_figureimage_setdata():
fig = plt.gcf()
im = FigureImage(fig)
z = np.arange(12, dtype=float).reshape((4, 3))
im.set_data(z)
z[0, 0] = 9.9
assert im._A[0, 0] == 0, 'value changed'
def test_pcolorimage_setdata():
ax = plt.gca()
im = PcolorImage(ax)
x = np.arange(3, dtype=float)
y = np.arange(4, dtype=float)
z = np.arange(6, dtype=float).reshape((3, 2))
im.set_data(x, y, z)
x[0] = y[0] = z[0, 0] = 9.9
assert im._A[0, 0] == im._Ax[0] == im._Ay[0] == 0, 'value changed'
def test_minimized_rasterized():
# This ensures that the rasterized content in the colorbars is
# only as thick as the colorbar, and doesn't extend to other parts
# of the image. See #5814. While the original bug exists only
# in Postscript, the best way to detect it is to generate SVG
# and then parse the output to make sure the two colorbar images
# are the same size.
from xml.etree import ElementTree
np.random.seed(0)
data = np.random.rand(10, 10)
fig, ax = plt.subplots(1, 2)
p1 = ax[0].pcolormesh(data)
p2 = ax[1].pcolormesh(data)
plt.colorbar(p1, ax=ax[0])
plt.colorbar(p2, ax=ax[1])
buff = io.BytesIO()
plt.savefig(buff, format='svg')
buff = io.BytesIO(buff.getvalue())
tree = ElementTree.parse(buff)
width = None
for image in tree.iter('image'):
if width is None:
width = image['width']
else:
if image['width'] != width:
assert False
@pytest.mark.network
def test_load_from_url():
url = "http://matplotlib.org/_static/logo_sidebar_horiz.png"
plt.imread(url)
plt.imread(urllib.request.urlopen(url))
@image_comparison(baseline_images=['log_scale_image'],
remove_text=True)
# The recwarn fixture captures a warning in image_comparison.
def test_log_scale_image(recwarn):
Z = np.zeros((10, 10))
Z[::2] = 1
fig, ax = plt.subplots()
ax.imshow(Z, extent=[1, 100, 1, 100], cmap='viridis',
vmax=1, vmin=-1)
ax.set_yscale('log')
@image_comparison(baseline_images=['rotate_image'],
remove_text=True)
def test_rotate_image():
delta = 0.25
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
(2 * np.pi * 0.5 * 1.5))
Z = Z2 - Z1 # difference of Gaussians
fig, ax1 = plt.subplots(1, 1)
im1 = ax1.imshow(Z, interpolation='none', cmap='viridis',
origin='lower',
extent=[-2, 4, -3, 2], clip_on=True)
trans_data2 = Affine2D().rotate_deg(30) + ax1.transData
im1.set_transform(trans_data2)
# display intended extent of the image
x1, x2, y1, y2 = im1.get_extent()
ax1.plot([x1, x2, x2, x1, x1], [y1, y1, y2, y2, y1], "r--", lw=3,
transform=trans_data2)
ax1.set_xlim(2, 5)
ax1.set_ylim(0, 4)
def test_image_preserve_size():
buff = io.BytesIO()
im = np.zeros((481, 321))
plt.imsave(buff, im, format="png")
buff.seek(0)
img = plt.imread(buff)
assert img.shape[:2] == im.shape
def test_image_preserve_size2():
n = 7
data = np.identity(n, float)
fig = plt.figure(figsize=(n, n), frameon=False)
ax = plt.Axes(fig, [0.0, 0.0, 1.0, 1.0])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(data, interpolation='nearest', origin='lower',aspect='auto')
buff = io.BytesIO()
fig.savefig(buff, dpi=1)
buff.seek(0)
img = plt.imread(buff)
assert img.shape == (7, 7, 4)
assert_array_equal(np.asarray(img[:, :, 0], bool),
np.identity(n, bool)[::-1])
@image_comparison(baseline_images=['mask_image_over_under'],
remove_text=True, extensions=['png'])
def test_mask_image_over_under():
delta = 0.025
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
(2 * np.pi * 0.5 * 1.5))
Z = 10*(Z2 - Z1) # difference of Gaussians
palette = copy(plt.cm.gray)
palette.set_over('r', 1.0)
palette.set_under('g', 1.0)
palette.set_bad('b', 1.0)
Zm = ma.masked_where(Z > 1.2, Z)
fig, (ax1, ax2) = plt.subplots(1, 2)
im = ax1.imshow(Zm, interpolation='bilinear',
cmap=palette,
norm=colors.Normalize(vmin=-1.0, vmax=1.0, clip=False),
origin='lower', extent=[-3, 3, -3, 3])
ax1.set_title('Green=low, Red=high, Blue=bad')
fig.colorbar(im, extend='both', orientation='horizontal',
ax=ax1, aspect=10)
im = ax2.imshow(Zm, interpolation='nearest',
cmap=palette,
norm=colors.BoundaryNorm([-1, -0.5, -0.2, 0, 0.2, 0.5, 1],
ncolors=256, clip=False),
origin='lower', extent=[-3, 3, -3, 3])
ax2.set_title('With BoundaryNorm')
fig.colorbar(im, extend='both', spacing='proportional',
orientation='horizontal', ax=ax2, aspect=10)
@image_comparison(baseline_images=['mask_image'],
remove_text=True)
def test_mask_image():
# Test mask image two ways: Using nans and using a masked array.
fig, (ax1, ax2) = plt.subplots(1, 2)
A = np.ones((5, 5))
A[1:2, 1:2] = np.nan
ax1.imshow(A, interpolation='nearest')
A = np.zeros((5, 5), dtype=bool)
A[1:2, 1:2] = True
A = np.ma.masked_array(np.ones((5, 5), dtype=np.uint16), A)
ax2.imshow(A, interpolation='nearest')
@image_comparison(baseline_images=['imshow_endianess'],
remove_text=True, extensions=['png'])
def test_imshow_endianess():
x = np.arange(10)
X, Y = np.meshgrid(x, x)
Z = ((X-5)**2 + (Y-5)**2)**0.5
fig, (ax1, ax2) = plt.subplots(1, 2)
kwargs = dict(origin="lower", interpolation='nearest',
cmap='viridis')
ax1.imshow(Z.astype('<f8'), **kwargs)
ax2.imshow(Z.astype('>f8'), **kwargs)
@image_comparison(baseline_images=['imshow_masked_interpolation'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
remove_text=True, style='mpl20')
def test_imshow_masked_interpolation():
cm = copy(plt.get_cmap('viridis'))
cm.set_over('r')
cm.set_under('b')
cm.set_bad('k')
N = 20
n = colors.Normalize(vmin=0, vmax=N*N-1)
data = np.arange(N*N, dtype='float').reshape(N, N)
data[5, 5] = -1
# This will cause crazy ringing for the higher-order
# interpolations
data[15, 5] = 1e5
# data[3, 3] = np.nan
data[15, 15] = np.inf
mask = np.zeros_like(data).astype('bool')
mask[5, 15] = True
data = np.ma.masked_array(data, mask)
fig, ax_grid = plt.subplots(3, 6)
for interp, ax in zip(sorted(mimage._interpd_), ax_grid.ravel()):
ax.set_title(interp)
ax.imshow(data, norm=n, cmap=cm, interpolation=interp)
ax.axis('off')
def test_imshow_no_warn_invalid():
with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter("always")
plt.imshow([[1, 2], [3, np.nan]])
assert len(warns) == 0
@pytest.mark.parametrize(
'dtype', [np.dtype(s) for s in 'u2 u4 i2 i4 i8 f4 f8'.split()])
def test_imshow_clips_rgb_to_valid_range(dtype):
arr = np.arange(300, dtype=dtype).reshape((10, 10, 3))
if dtype.kind != 'u':
arr -= 10
too_low = arr < 0
too_high = arr > 255
if dtype.kind == 'f':
arr = arr / 255
_, ax = plt.subplots()
out = ax.imshow(arr).get_array()
assert (out[too_low] == 0).all()
if dtype.kind == 'f':
assert (out[too_high] == 1).all()
assert out.dtype.kind == 'f'
else:
assert (out[too_high] == 255).all()
assert out.dtype == np.uint8
@image_comparison(baseline_images=['imshow_flatfield'],
remove_text=True, style='mpl20',
extensions=['png'])
def test_imshow_flatfield():
fig, ax = plt.subplots()
im = ax.imshow(np.ones((5, 5)))
im.set_clim(.5, 1.5)
@image_comparison(baseline_images=['imshow_bignumbers'],
remove_text=True, style='mpl20',
extensions=['png'])
def test_imshow_bignumbers():
# putting a big number in an array of integers shouldn't
# ruin the dynamic range of the resolved bits.
fig, ax = plt.subplots()
img = np.array([[1, 2, 1e12],[3, 1, 4]], dtype=np.uint64)
pc = ax.imshow(img)
pc.set_clim(0, 5)
@image_comparison(baseline_images=['imshow_bignumbers_real'],
remove_text=True, style='mpl20',
extensions=['png'])
def test_imshow_bignumbers_real():
# putting a big number in an array of integers shouldn't
# ruin the dynamic range of the resolved bits.
fig, ax = plt.subplots()
img = np.array([[2., 1., 1.e22],[4., 1., 3.]])
pc = ax.imshow(img)
pc.set_clim(0, 5)
@pytest.mark.parametrize(
"make_norm",
[colors.Normalize,
colors.LogNorm,
lambda: colors.SymLogNorm(1),
lambda: colors.PowerNorm(1)])
def test_empty_imshow(make_norm):
fig, ax = plt.subplots()
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", "Attempting to set identical left==right")
im = ax.imshow([[]], norm=make_norm())
im.set_extent([-5, 5, -5, 5])
fig.canvas.draw()
with pytest.raises(RuntimeError):
im.make_image(fig._cachedRenderer)
def test_imshow_float128():
fig, ax = plt.subplots()
ax.imshow(np.zeros((3, 3), dtype=np.longdouble))
with (ExitStack() if np.can_cast(np.longdouble, np.float64, "equiv")
else pytest.warns(UserWarning)):
# Ensure that drawing doesn't cause crash.
fig.canvas.draw()
def test_imshow_bool():
fig, ax = plt.subplots()
ax.imshow(np.array([[True, False], [False, True]], dtype=bool))
def test_full_invalid():
x = np.ones((10, 10))
x[:] = np.nan
f, ax = plt.subplots()
ax.imshow(x)
f.canvas.draw()
@pytest.mark.parametrize("fmt,counted",
[("ps", b" colorimage"), ("svg", b"<image")])
@pytest.mark.parametrize("composite_image,count", [(True, 1), (False, 2)])
def test_composite(fmt, counted, composite_image, count):
# Test that figures can be saved with and without combining multiple images
# (on a single set of axes) into a single composite image.
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
Z = np.sin(Y ** 2)
fig, ax = plt.subplots()
ax.set_xlim(0, 3)
ax.imshow(Z, extent=[0, 1, 0, 1])
ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
plt.rcParams['image.composite_image'] = composite_image
buf = io.BytesIO()
fig.savefig(buf, format=fmt)
assert buf.getvalue().count(counted) == count
def test_relim():
fig, ax = plt.subplots()
ax.imshow([[0]], extent=(0, 1, 0, 1))
ax.relim()
ax.autoscale()
assert ax.get_xlim() == ax.get_ylim() == (0, 1)
@@ -0,0 +1,565 @@
import collections
import inspect
import platform
from unittest import mock
import numpy as np
import pytest
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.transforms as mtransforms
import matplotlib.collections as mcollections
from matplotlib.legend_handler import HandlerTuple
import matplotlib.legend as mlegend
from matplotlib.cbook.deprecation import MatplotlibDeprecationWarning
def test_legend_ordereddict():
# smoketest that ordereddict inputs work...
X = np.random.randn(10)
Y = np.random.randn(10)
labels = ['a'] * 5 + ['b'] * 5
colors = ['r'] * 5 + ['g'] * 5
fig, ax = plt.subplots()
for x, y, label, color in zip(X, Y, labels, colors):
ax.scatter(x, y, label=label, c=color)
handles, labels = ax.get_legend_handles_labels()
legend = collections.OrderedDict(zip(labels, handles))
ax.legend(legend.values(), legend.keys(),
loc='center left', bbox_to_anchor=(1, .5))
@image_comparison(baseline_images=['legend_auto1'], remove_text=True)
def test_legend_auto1():
'Test automatic legend placement'
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.arange(100)
ax.plot(x, 50 - x, 'o', label='y=1')
ax.plot(x, x - 50, 'o', label='y=-1')
ax.legend(loc='best')
@image_comparison(baseline_images=['legend_auto2'], remove_text=True)
def test_legend_auto2():
'Test automatic legend placement'
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.arange(100)
b1 = ax.bar(x, x, align='edge', color='m')
b2 = ax.bar(x, x[::-1], align='edge', color='g')
ax.legend([b1[0], b2[0]], ['up', 'down'], loc='best')
@image_comparison(baseline_images=['legend_auto3'])
def test_legend_auto3():
'Test automatic legend placement'
fig = plt.figure()
ax = fig.add_subplot(111)
x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5]
y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5]
ax.plot(x, y, 'o-', label='line')
ax.set_xlim(0.0, 1.0)
ax.set_ylim(0.0, 1.0)
ax.legend(loc='best')
@image_comparison(baseline_images=['legend_various_labels'], remove_text=True)
def test_various_labels():
# tests all sorts of label types
fig = plt.figure()
ax = fig.add_subplot(121)
ax.plot(np.arange(4), 'o', label=1)
ax.plot(np.linspace(4, 4.1), 'o', label='Développés')
ax.plot(np.arange(4, 1, -1), 'o', label='__nolegend__')
ax.legend(numpoints=1, loc='best')
@image_comparison(baseline_images=['legend_labels_first'], extensions=['png'],
remove_text=True)
def test_labels_first():
# test labels to left of markers
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(np.arange(10), '-o', label=1)
ax.plot(np.ones(10)*5, ':x', label="x")
ax.plot(np.arange(20, 10, -1), 'd', label="diamond")
ax.legend(loc='best', markerfirst=False)
@image_comparison(baseline_images=['legend_multiple_keys'], extensions=['png'],
remove_text=True)
def test_multiple_keys():
# test legend entries with multiple keys
fig = plt.figure()
ax = fig.add_subplot(111)
p1, = ax.plot([1, 2, 3], '-o')
p2, = ax.plot([2, 3, 4], '-x')
p3, = ax.plot([3, 4, 5], '-d')
ax.legend([(p1, p2), (p2, p1), p3], ['two keys', 'pad=0', 'one key'],
numpoints=1,
handler_map={(p1, p2): HandlerTuple(ndivide=None),
(p2, p1): HandlerTuple(ndivide=None, pad=0)})
@image_comparison(baseline_images=['rgba_alpha'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
extensions=['png'], remove_text=True)
def test_alpha_rgba():
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 1)
ax.plot(range(10), lw=5)
leg = plt.legend(['Longlabel that will go away'], loc='center')
leg.legendPatch.set_facecolor([1, 0, 0, 0.5])
@image_comparison(baseline_images=['rcparam_alpha'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
extensions=['png'], remove_text=True)
def test_alpha_rcparam():
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 1)
ax.plot(range(10), lw=5)
with mpl.rc_context(rc={'legend.framealpha': .75}):
leg = plt.legend(['Longlabel that will go away'], loc='center')
# this alpha is going to be over-ridden by the rcparam with
# sets the alpha of the patch to be non-None which causes the alpha
# value of the face color to be discarded. This behavior may not be
# ideal, but it is what it is and we should keep track of it changing
leg.legendPatch.set_facecolor([1, 0, 0, 0.5])
@image_comparison(baseline_images=['fancy'], remove_text=True)
def test_fancy():
# using subplot triggers some offsetbox functionality untested elsewhere
plt.subplot(121)
plt.scatter(np.arange(10), np.arange(10, 0, -1), label='XX\nXX')
plt.plot([5] * 10, 'o--', label='XX')
plt.errorbar(np.arange(10), np.arange(10), xerr=0.5,
yerr=0.5, label='XX')
plt.legend(loc="center left", bbox_to_anchor=[1.0, 0.5],
ncol=2, shadow=True, title="My legend", numpoints=1)
@image_comparison(baseline_images=['framealpha'], remove_text=True,
tol={'aarch64': 0.02}.get(platform.machine(), 0.0))
def test_framealpha():
x = np.linspace(1, 100, 100)
y = x
plt.plot(x, y, label='mylabel', lw=10)
plt.legend(framealpha=0.5)
@image_comparison(baseline_images=['scatter_rc3', 'scatter_rc1'],
remove_text=True)
def test_rc():
# using subplot triggers some offsetbox functionality untested elsewhere
plt.figure()
ax = plt.subplot(121)
ax.scatter(np.arange(10), np.arange(10, 0, -1), label='three')
ax.legend(loc="center left", bbox_to_anchor=[1.0, 0.5],
title="My legend")
mpl.rcParams['legend.scatterpoints'] = 1
plt.figure()
ax = plt.subplot(121)
ax.scatter(np.arange(10), np.arange(10, 0, -1), label='one')
ax.legend(loc="center left", bbox_to_anchor=[1.0, 0.5],
title="My legend")
@image_comparison(baseline_images=['legend_expand'], remove_text=True)
def test_legend_expand():
'Test expand mode'
legend_modes = [None, "expand"]
fig, axes_list = plt.subplots(len(legend_modes), 1)
x = np.arange(100)
for ax, mode in zip(axes_list, legend_modes):
ax.plot(x, 50 - x, 'o', label='y=1')
l1 = ax.legend(loc='upper left', mode=mode)
ax.add_artist(l1)
ax.plot(x, x - 50, 'o', label='y=-1')
l2 = ax.legend(loc='right', mode=mode)
ax.add_artist(l2)
ax.legend(loc='lower left', mode=mode, ncol=2)
@image_comparison(baseline_images=['hatching'], remove_text=True,
style='default')
def test_hatching():
fig, ax = plt.subplots()
# Patches
patch = plt.Rectangle((0, 0), 0.3, 0.3, hatch='xx',
label='Patch\ndefault color\nfilled')
ax.add_patch(patch)
patch = plt.Rectangle((0.33, 0), 0.3, 0.3, hatch='||', edgecolor='C1',
label='Patch\nexplicit color\nfilled')
ax.add_patch(patch)
patch = plt.Rectangle((0, 0.4), 0.3, 0.3, hatch='xx', fill=False,
label='Patch\ndefault color\nunfilled')
ax.add_patch(patch)
patch = plt.Rectangle((0.33, 0.4), 0.3, 0.3, hatch='||', fill=False,
edgecolor='C1',
label='Patch\nexplicit color\nunfilled')
ax.add_patch(patch)
# Paths
ax.fill_between([0, .15, .3], [.8, .8, .8], [.9, 1.0, .9],
hatch='+', label='Path\ndefault color')
ax.fill_between([.33, .48, .63], [.8, .8, .8], [.9, 1.0, .9],
hatch='+', edgecolor='C2', label='Path\nexplicit color')
ax.set_xlim(-0.01, 1.1)
ax.set_ylim(-0.01, 1.1)
ax.legend(handlelength=4, handleheight=4)
def test_legend_remove():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
lines = ax.plot(range(10))
leg = fig.legend(lines, "test")
leg.remove()
assert fig.legends == []
leg = ax.legend("test")
leg.remove()
assert ax.get_legend() is None
class TestLegendFunction(object):
# Tests the legend function on the Axes and pyplot.
def test_legend_handle_label(self):
lines = plt.plot(range(10))
with mock.patch('matplotlib.legend.Legend') as Legend:
plt.legend(lines, ['hello world'])
Legend.assert_called_with(plt.gca(), lines, ['hello world'])
def test_legend_no_args(self):
lines = plt.plot(range(10), label='hello world')
with mock.patch('matplotlib.legend.Legend') as Legend:
plt.legend()
Legend.assert_called_with(plt.gca(), lines, ['hello world'])
def test_legend_label_args(self):
lines = plt.plot(range(10), label='hello world')
with mock.patch('matplotlib.legend.Legend') as Legend:
plt.legend(['foobar'])
Legend.assert_called_with(plt.gca(), lines, ['foobar'])
def test_legend_three_args(self):
lines = plt.plot(range(10), label='hello world')
with mock.patch('matplotlib.legend.Legend') as Legend:
plt.legend(lines, ['foobar'], loc='right')
Legend.assert_called_with(plt.gca(), lines, ['foobar'], loc='right')
def test_legend_handler_map(self):
lines = plt.plot(range(10), label='hello world')
with mock.patch('matplotlib.legend.'
'_get_legend_handles_labels') as handles_labels:
handles_labels.return_value = lines, ['hello world']
plt.legend(handler_map={'1': 2})
handles_labels.assert_called_with([plt.gca()], {'1': 2})
def test_kwargs(self):
fig, ax = plt.subplots(1, 1)
th = np.linspace(0, 2*np.pi, 1024)
lns, = ax.plot(th, np.sin(th), label='sin', lw=5)
lnc, = ax.plot(th, np.cos(th), label='cos', lw=5)
with mock.patch('matplotlib.legend.Legend') as Legend:
ax.legend(labels=('a', 'b'), handles=(lnc, lns))
Legend.assert_called_with(ax, (lnc, lns), ('a', 'b'))
def test_warn_args_kwargs(self):
fig, ax = plt.subplots(1, 1)
th = np.linspace(0, 2*np.pi, 1024)
lns, = ax.plot(th, np.sin(th), label='sin', lw=5)
lnc, = ax.plot(th, np.cos(th), label='cos', lw=5)
with mock.patch('warnings.warn') as warn:
ax.legend((lnc, lns), labels=('a', 'b'))
warn.assert_called_with("You have mixed positional and keyword "
"arguments, some input may be "
"discarded.")
def test_parasite(self):
from mpl_toolkits.axes_grid1 import host_subplot
host = host_subplot(111)
par = host.twinx()
p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par.plot([0, 1, 2], [0, 3, 2], label="Temperature")
with mock.patch('matplotlib.legend.Legend') as Legend:
leg = plt.legend()
Legend.assert_called_with(host, [p1, p2],
['Density', 'Temperature'])
class TestLegendFigureFunction(object):
# Tests the legend function for figure
def test_legend_handle_label(self):
fig, ax = plt.subplots()
lines = ax.plot(range(10))
with mock.patch('matplotlib.legend.Legend') as Legend:
fig.legend(lines, ['hello world'])
Legend.assert_called_with(fig, lines, ['hello world'])
def test_legend_no_args(self):
fig, ax = plt.subplots()
lines = ax.plot(range(10), label='hello world')
with mock.patch('matplotlib.legend.Legend') as Legend:
fig.legend()
Legend.assert_called_with(fig, lines, ['hello world'])
def test_legend_label_arg(self):
fig, ax = plt.subplots()
lines = ax.plot(range(10))
with mock.patch('matplotlib.legend.Legend') as Legend:
fig.legend(['foobar'])
Legend.assert_called_with(fig, lines, ['foobar'])
def test_legend_label_three_args(self):
fig, ax = plt.subplots()
lines = ax.plot(range(10))
with mock.patch('matplotlib.legend.Legend') as Legend:
fig.legend(lines, ['foobar'], 'right')
Legend.assert_called_with(fig, lines, ['foobar'], 'right')
def test_legend_label_three_args_pluskw(self):
# test that third argument and loc= called together give
# Exception
fig, ax = plt.subplots()
lines = ax.plot(range(10))
with pytest.raises(Exception):
fig.legend(lines, ['foobar'], 'right', loc='left')
def test_legend_kw_args(self):
fig, axs = plt.subplots(1, 2)
lines = axs[0].plot(range(10))
lines2 = axs[1].plot(np.arange(10) * 2.)
with mock.patch('matplotlib.legend.Legend') as Legend:
fig.legend(loc='right', labels=('a', 'b'),
handles=(lines, lines2))
Legend.assert_called_with(fig, (lines, lines2), ('a', 'b'),
loc='right')
def test_warn_args_kwargs(self):
fig, axs = plt.subplots(1, 2)
lines = axs[0].plot(range(10))
lines2 = axs[1].plot(np.arange(10) * 2.)
with mock.patch('warnings.warn') as warn:
fig.legend((lines, lines2), labels=('a', 'b'))
warn.assert_called_with("You have mixed positional and keyword "
"arguments, some input may be "
"discarded.")
@image_comparison(baseline_images=['legend_stackplot'], extensions=['png'])
def test_legend_stackplot():
'''test legend for PolyCollection using stackplot'''
# related to #1341, #1943, and PR #3303
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.linspace(0, 10, 10)
y1 = 1.0 * x
y2 = 2.0 * x + 1
y3 = 3.0 * x + 2
ax.stackplot(x, y1, y2, y3, labels=['y1', 'y2', 'y3'])
ax.set_xlim((0, 10))
ax.set_ylim((0, 70))
ax.legend(loc='best')
def test_cross_figure_patch_legend():
fig, ax = plt.subplots()
fig2, ax2 = plt.subplots()
brs = ax.bar(range(3), range(3))
fig2.legend(brs, 'foo')
def test_nanscatter():
fig, ax = plt.subplots()
h = ax.scatter([np.nan], [np.nan], marker="o",
facecolor="r", edgecolor="r", s=3)
ax.legend([h], ["scatter"])
fig, ax = plt.subplots()
for color in ['red', 'green', 'blue']:
n = 750
x, y = np.random.rand(2, n)
scale = 200.0 * np.random.rand(n)
ax.scatter(x, y, c=color, s=scale, label=color,
alpha=0.3, edgecolors='none')
ax.legend()
ax.grid(True)
def test_legend_repeatcheckok():
fig, ax = plt.subplots()
ax.scatter(0.0, 1.0, color='k', marker='o', label='test')
ax.scatter(0.5, 0.0, color='r', marker='v', label='test')
hl = ax.legend()
hand, lab = mlegend._get_legend_handles_labels([ax])
assert len(lab) == 2
fig, ax = plt.subplots()
ax.scatter(0.0, 1.0, color='k', marker='o', label='test')
ax.scatter(0.5, 0.0, color='k', marker='v', label='test')
hl = ax.legend()
hand, lab = mlegend._get_legend_handles_labels([ax])
assert len(lab) == 2
@image_comparison(baseline_images=['not_covering_scatter'], extensions=['png'])
def test_not_covering_scatter():
colors = ['b', 'g', 'r']
for n in range(3):
plt.scatter([n], [n], color=colors[n])
plt.legend(['foo', 'foo', 'foo'], loc='best')
plt.gca().set_xlim(-0.5, 2.2)
plt.gca().set_ylim(-0.5, 2.2)
@image_comparison(baseline_images=['not_covering_scatter_transform'],
extensions=['png'])
def test_not_covering_scatter_transform():
# Offsets point to top left, the default auto position
offset = mtransforms.Affine2D().translate(-20, 20)
x = np.linspace(0, 30, 1000)
plt.plot(x, x)
plt.scatter([20], [10], transform=offset + plt.gca().transData)
plt.legend(['foo', 'bar'], loc='best')
def test_linecollection_scaled_dashes():
lines1 = [[(0, .5), (.5, 1)], [(.3, .6), (.2, .2)]]
lines2 = [[[0.7, .2], [.8, .4]], [[.5, .7], [.6, .1]]]
lines3 = [[[0.6, .2], [.8, .4]], [[.5, .7], [.1, .1]]]
lc1 = mcollections.LineCollection(lines1, linestyles="--", lw=3)
lc2 = mcollections.LineCollection(lines2, linestyles="-.")
lc3 = mcollections.LineCollection(lines3, linestyles=":", lw=.5)
fig, ax = plt.subplots()
ax.add_collection(lc1)
ax.add_collection(lc2)
ax.add_collection(lc3)
leg = ax.legend([lc1, lc2, lc3], ["line1", "line2", 'line 3'])
h1, h2, h3 = leg.legendHandles
for oh, lh in zip((lc1, lc2, lc3), (h1, h2, h3)):
assert oh.get_linestyles()[0][1] == lh._dashSeq
assert oh.get_linestyles()[0][0] == lh._dashOffset
def test_handler_numpoints():
'''test legend handler with numponts less than or equal to 1'''
# related to #6921 and PR #8478
fig, ax = plt.subplots()
ax.plot(range(5), label='test')
ax.legend(numpoints=0.5)
def test_shadow_framealpha():
# Test if framealpha is activated when shadow is True
# and framealpha is not explicitly passed'''
fig, ax = plt.subplots()
ax.plot(range(100), label="test")
leg = ax.legend(shadow=True, facecolor='w')
assert leg.get_frame().get_alpha() == 1
def test_legend_title_empty():
# test that if we don't set the legend title, that
# it comes back as an empty string, and that it is not
# visible:
fig, ax = plt.subplots()
ax.plot(range(10))
leg = ax.legend()
assert leg.get_title().get_text() == ""
assert leg.get_title().get_visible() is False
def test_legend_proper_window_extent():
# test that legend returns the expected extent under various dpi...
fig, ax = plt.subplots(dpi=100)
ax.plot(range(10), label='Aardvark')
leg = ax.legend()
x01 = leg.get_window_extent(fig.canvas.get_renderer()).x0
fig, ax = plt.subplots(dpi=200)
ax.plot(range(10), label='Aardvark')
leg = ax.legend()
x02 = leg.get_window_extent(fig.canvas.get_renderer()).x0
assert pytest.approx(x01*2, 0.1) == x02
def test_window_extent_cached_renderer():
fig, ax = plt.subplots(dpi=100)
ax.plot(range(10), label='Aardvark')
leg = ax.legend()
leg2 = fig.legend()
fig.canvas.draw()
# check that get_window_extent will use the cached renderer
leg.get_window_extent()
leg2.get_window_extent()
def test_legend_title_fontsize():
# test the title_fontsize kwarg
fig, ax = plt.subplots()
ax.plot(range(10))
leg = ax.legend(title='Aardvark', title_fontsize=22)
assert leg.get_title().get_fontsize() == 22
def test_get_set_draggable():
legend = plt.legend()
assert not legend.get_draggable()
legend.set_draggable(True)
assert legend.get_draggable()
legend.set_draggable(False)
assert not legend.get_draggable()
def test_draggable():
legend = plt.legend()
with pytest.warns(MatplotlibDeprecationWarning):
legend.draggable(True)
assert legend.get_draggable()
with pytest.warns(MatplotlibDeprecationWarning):
legend.draggable(False)
assert not legend.get_draggable()
# test toggle
with pytest.warns(MatplotlibDeprecationWarning):
legend.draggable()
assert legend.get_draggable()
with pytest.warns(MatplotlibDeprecationWarning):
legend.draggable()
assert not legend.get_draggable()
def test_alpha_handles():
x, n, hh = plt.hist([1, 2, 3], alpha=0.25, label='data', color='red')
legend = plt.legend()
for lh in legend.legendHandles:
lh.set_alpha(1.0)
assert lh.get_facecolor()[:-1] == hh[1].get_facecolor()[:-1]
assert lh.get_edgecolor()[:-1] == hh[1].get_edgecolor()[:-1]
@@ -0,0 +1,207 @@
"""
Tests specific to the lines module.
"""
from io import BytesIO
import itertools
import timeit
from cycler import cycler
import numpy as np
import pytest
import matplotlib
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison, check_figures_equal
# Runtimes on a loaded system are inherently flaky. Not so much that a rerun
# won't help, hopefully.
@pytest.mark.flaky(reruns=3)
def test_invisible_Line_rendering():
"""
Github issue #1256 identified a bug in Line.draw method
Despite visibility attribute set to False, the draw method was not
returning early enough and some pre-rendering code was executed
though not necessary.
Consequence was an excessive draw time for invisible Line instances
holding a large number of points (Npts> 10**6)
"""
# Creates big x and y data:
N = 10**7
x = np.linspace(0, 1, N)
y = np.random.normal(size=N)
# Create a plot figure:
fig = plt.figure()
ax = plt.subplot(111)
# Create a "big" Line instance:
l = mlines.Line2D(x, y)
l.set_visible(False)
# but don't add it to the Axis instance `ax`
# [here Interactive panning and zooming is pretty responsive]
# Time the canvas drawing:
t_no_line = min(timeit.repeat(fig.canvas.draw, number=1, repeat=3))
# (gives about 25 ms)
# Add the big invisible Line:
ax.add_line(l)
# [Now interactive panning and zooming is very slow]
# Time the canvas drawing:
t_unvisible_line = min(timeit.repeat(fig.canvas.draw, number=1, repeat=3))
# gives about 290 ms for N = 10**7 pts
slowdown_factor = (t_unvisible_line/t_no_line)
slowdown_threshold = 2 # trying to avoid false positive failures
assert slowdown_factor < slowdown_threshold
def test_set_line_coll_dash():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
np.random.seed(0)
# Testing setting linestyles for line collections.
# This should not produce an error.
cs = ax.contour(np.random.randn(20, 30), linestyles=[(0, (3, 3))])
assert True
@image_comparison(baseline_images=['line_dashes'], remove_text=True)
def test_line_dashes():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(range(10), linestyle=(0, (3, 3)), lw=5)
def test_line_colors():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(range(10), color='none')
ax.plot(range(10), color='r')
ax.plot(range(10), color='.3')
ax.plot(range(10), color=(1, 0, 0, 1))
ax.plot(range(10), color=(1, 0, 0))
fig.canvas.draw()
assert True
def test_linestyle_variants():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
for ls in ["-", "solid", "--", "dashed",
"-.", "dashdot", ":", "dotted"]:
ax.plot(range(10), linestyle=ls)
fig.canvas.draw()
assert True
def test_valid_linestyles():
line = mlines.Line2D([], [])
with pytest.raises(ValueError):
line.set_linestyle('aardvark')
@image_comparison(baseline_images=['drawstyle_variants'], remove_text=True,
extensions=["png"])
def test_drawstyle_variants():
fig, axs = plt.subplots(6)
dss = ["default", "steps-mid", "steps-pre", "steps-post", "steps", None]
# We want to check that drawstyles are properly handled even for very long
# lines (for which the subslice optimization is on); however, we need
# to zoom in so that the difference between the drawstyles is actually
# visible.
for ax, ds in zip(axs.flat, dss):
ax.plot(range(2000), drawstyle=ds)
ax.set(xlim=(0, 2), ylim=(0, 2))
def test_valid_drawstyles():
line = mlines.Line2D([], [])
with pytest.raises(ValueError):
line.set_drawstyle('foobar')
def test_set_drawstyle():
x = np.linspace(0, 2*np.pi, 10)
y = np.sin(x)
fig, ax = plt.subplots()
line, = ax.plot(x, y)
line.set_drawstyle("steps-pre")
assert len(line.get_path().vertices) == 2*len(x)-1
line.set_drawstyle("default")
assert len(line.get_path().vertices) == len(x)
@image_comparison(baseline_images=['line_collection_dashes'],
remove_text=True, style='mpl20')
def test_set_line_coll_dash_image():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
np.random.seed(0)
cs = ax.contour(np.random.randn(20, 30), linestyles=[(0, (3, 3))])
@image_comparison(baseline_images=['marker_fill_styles'], remove_text=True,
extensions=['png'])
def test_marker_fill_styles():
colors = itertools.cycle([[0, 0, 1], 'g', '#ff0000', 'c', 'm', 'y',
np.array([0, 0, 0])])
altcolor = 'lightgreen'
y = np.array([1, 1])
x = np.array([0, 9])
fig, ax = plt.subplots()
for j, marker in enumerate(mlines.Line2D.filled_markers):
for i, fs in enumerate(mlines.Line2D.fillStyles):
color = next(colors)
ax.plot(j * 10 + x, y + i + .5 * (j % 2),
marker=marker,
markersize=20,
markerfacecoloralt=altcolor,
fillstyle=fs,
label=fs,
linewidth=5,
color=color,
markeredgecolor=color,
markeredgewidth=2)
ax.set_ylim([0, 7.5])
ax.set_xlim([-5, 155])
@image_comparison(baseline_images=['scaled_lines'], style='default')
def test_lw_scaling():
th = np.linspace(0, 32)
fig, ax = plt.subplots()
lins_styles = ['dashed', 'dotted', 'dashdot']
cy = cycler(matplotlib.rcParams['axes.prop_cycle'])
for j, (ls, sty) in enumerate(zip(lins_styles, cy)):
for lw in np.linspace(.5, 10, 10):
ax.plot(th, j*np.ones(50) + .1 * lw, linestyle=ls, lw=lw, **sty)
def test_nan_is_sorted():
line = mlines.Line2D([], [])
assert line._is_sorted(np.array([1, 2, 3]))
assert line._is_sorted(np.array([1, np.nan, 3]))
assert not line._is_sorted([3, 5] + [np.nan] * 100 + [0, 2])
@check_figures_equal()
def test_step_markers(fig_test, fig_ref):
fig_test.subplots().step([0, 1], "-o")
fig_ref.subplots().plot([0, 0, 1], [0, 1, 1], "-o", markevery=[0, 2])
@@ -0,0 +1,28 @@
import numpy as np
from matplotlib import markers
from matplotlib.path import Path
import pytest
def test_markers_valid():
marker_style = markers.MarkerStyle()
mrk_array = np.array([[-0.5, 0],
[0.5, 0]])
# Checking this doesn't fail.
marker_style.set_marker(mrk_array)
def test_markers_invalid():
marker_style = markers.MarkerStyle()
mrk_array = np.array([[-0.5, 0, 1, 2, 3]])
# Checking this does fail.
with pytest.raises(ValueError):
marker_style.set_marker(mrk_array)
def test_marker_path():
marker_style = markers.MarkerStyle()
path = Path([[0, 0], [1, 0]], [Path.MOVETO, Path.LINETO])
# Checking this doesn't fail.
marker_style.set_marker(path)
@@ -0,0 +1,273 @@
import io
import re
import numpy as np
import pytest
import matplotlib
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
from matplotlib import mathtext
math_tests = [
r'$a+b+\dot s+\dot{s}+\ldots$',
r'$x \doteq y$',
r'\$100.00 $\alpha \_$',
r'$\frac{\$100.00}{y}$',
r'$x y$',
r'$x+y\ x=y\ x<y\ x:y\ x,y\ x@y$',
r'$100\%y\ x*y\ x/y x\$y$',
r'$x\leftarrow y\ x\forall y\ x-y$',
r'$x \sf x \bf x {\cal X} \rm x$',
r'$x\ x\,x\;x\quad x\qquad x\!x\hspace{ 0.5 }y$',
r'$\{ \rm braces \}$',
r'$\left[\left\lfloor\frac{5}{\frac{\left(3\right)}{4}} y\right)\right]$',
r'$\left(x\right)$',
r'$\sin(x)$',
r'$x_2$',
r'$x^2$',
r'$x^2_y$',
r'$x_y^2$',
r'$\prod_{i=\alpha_{i+1}}^\infty$',
r'$x = \frac{x+\frac{5}{2}}{\frac{y+3}{8}}$',
r'$dz/dt = \gamma x^2 + {\rm sin}(2\pi y+\phi)$',
r'Foo: $\alpha_{i+1}^j = {\rm sin}(2\pi f_j t_i) e^{-5 t_i/\tau}$',
r'$\mathcal{R}\prod_{i=\alpha_{i+1}}^\infty a_i \sin(2 \pi f x_i)$',
r'Variable $i$ is good',
r'$\Delta_i^j$',
r'$\Delta^j_{i+1}$',
r'$\ddot{o}\acute{e}\grave{e}\hat{O}\breve{\imath}\tilde{n}\vec{q}$',
r"$\arccos((x^i))$",
r"$\gamma = \frac{x=\frac{6}{8}}{y} \delta$",
r'$\limsup_{x\to\infty}$',
r'$\oint^\infty_0$',
r"$f'\quad f'''(x)\quad ''/\mathrm{yr}$",
r'$\frac{x_2888}{y}$',
r"$\sqrt[3]{\frac{X_2}{Y}}=5$",
r"$\sqrt[5]{\prod^\frac{x}{2\pi^2}_\infty}$",
r"$\sqrt[3]{x}=5$",
r'$\frac{X}{\frac{X}{Y}}$',
r"$W^{3\beta}_{\delta_1 \rho_1 \sigma_2} = U^{3\beta}_{\delta_1 \rho_1} + \frac{1}{8 \pi 2} \int^{\alpha_2}_{\alpha_2} d \alpha^\prime_2 \left[\frac{ U^{2\beta}_{\delta_1 \rho_1} - \alpha^\prime_2U^{1\beta}_{\rho_1 \sigma_2} }{U^{0\beta}_{\rho_1 \sigma_2}}\right]$",
r'$\mathcal{H} = \int d \tau \left(\epsilon E^2 + \mu H^2\right)$',
r'$\widehat{abc}\widetilde{def}$',
'$\\Gamma \\Delta \\Theta \\Lambda \\Xi \\Pi \\Sigma \\Upsilon \\Phi \\Psi \\Omega$',
'$\\alpha \\beta \\gamma \\delta \\epsilon \\zeta \\eta \\theta \\iota \\lambda \\mu \\nu \\xi \\pi \\kappa \\rho \\sigma \\tau \\upsilon \\phi \\chi \\psi$',
# The examples prefixed by 'mmltt' are from the MathML torture test here:
# http://www.mozilla.org/projects/mathml/demo/texvsmml.xhtml
r'${x}^{2}{y}^{2}$',
r'${}_{2}F_{3}$',
r'$\frac{x+{y}^{2}}{k+1}$',
r'$x+{y}^{\frac{2}{k+1}}$',
r'$\frac{a}{b/2}$',
r'${a}_{0}+\frac{1}{{a}_{1}+\frac{1}{{a}_{2}+\frac{1}{{a}_{3}+\frac{1}{{a}_{4}}}}}$',
r'${a}_{0}+\frac{1}{{a}_{1}+\frac{1}{{a}_{2}+\frac{1}{{a}_{3}+\frac{1}{{a}_{4}}}}}$',
r'$\binom{n}{k/2}$',
r'$\binom{p}{2}{x}^{2}{y}^{p-2}-\frac{1}{1-x}\frac{1}{1-{x}^{2}}$',
r'${x}^{2y}$',
r'$\sum _{i=1}^{p}\sum _{j=1}^{q}\sum _{k=1}^{r}{a}_{ij}{b}_{jk}{c}_{ki}$',
r'$\sqrt{1+\sqrt{1+\sqrt{1+\sqrt{1+\sqrt{1+\sqrt{1+\sqrt{1+x}}}}}}}$',
r'$\left(\frac{{\partial }^{2}}{\partial {x}^{2}}+\frac{{\partial }^{2}}{\partial {y}^{2}}\right){|\varphi \left(x+iy\right)|}^{2}=0$',
r'${2}^{{2}^{{2}^{x}}}$',
r'${\int }_{1}^{x}\frac{\mathrm{dt}}{t}$',
r'$\int {\int }_{D}\mathrm{dx} \mathrm{dy}$',
# mathtex doesn't support array
# 'mmltt18' : r'$f\left(x\right)=\left\{\begin{array}{cc}\hfill 1/3\hfill & \text{if_}0\le x\le 1;\hfill \\ \hfill 2/3\hfill & \hfill \text{if_}3\le x\le 4;\hfill \\ \hfill 0\hfill & \text{elsewhere.}\hfill \end{array}$',
# mathtex doesn't support stackrel
# 'mmltt19' : ur'$\stackrel{\stackrel{k\text{times}}{\ufe37}}{x+...+x}$',
r'${y}_{{x}^{2}}$',
# mathtex doesn't support the "\text" command
# 'mmltt21' : r'$\sum _{p\text{\prime}}f\left(p\right)={\int }_{t>1}f\left(t\right) d\pi \left(t\right)$',
# mathtex doesn't support array
# 'mmltt23' : r'$\left(\begin{array}{cc}\hfill \left(\begin{array}{cc}\hfill a\hfill & \hfill b\hfill \\ \hfill c\hfill & \hfill d\hfill \end{array}\right)\hfill & \hfill \left(\begin{array}{cc}\hfill e\hfill & \hfill f\hfill \\ \hfill g\hfill & \hfill h\hfill \end{array}\right)\hfill \\ \hfill 0\hfill & \hfill \left(\begin{array}{cc}\hfill i\hfill & \hfill j\hfill \\ \hfill k\hfill & \hfill l\hfill \end{array}\right)\hfill \end{array}\right)$',
# mathtex doesn't support array
# 'mmltt24' : r'$det|\begin{array}{ccccc}\hfill {c}_{0}\hfill & \hfill {c}_{1}\hfill & \hfill {c}_{2}\hfill & \hfill \dots \hfill & \hfill {c}_{n}\hfill \\ \hfill {c}_{1}\hfill & \hfill {c}_{2}\hfill & \hfill {c}_{3}\hfill & \hfill \dots \hfill & \hfill {c}_{n+1}\hfill \\ \hfill {c}_{2}\hfill & \hfill {c}_{3}\hfill & \hfill {c}_{4}\hfill & \hfill \dots \hfill & \hfill {c}_{n+2}\hfill \\ \hfill \u22ee\hfill & \hfill \u22ee\hfill & \hfill \u22ee\hfill & \hfill \hfill & \hfill \u22ee\hfill \\ \hfill {c}_{n}\hfill & \hfill {c}_{n+1}\hfill & \hfill {c}_{n+2}\hfill & \hfill \dots \hfill & \hfill {c}_{2n}\hfill \end{array}|>0$',
r'${y}_{{x}_{2}}$',
r'${x}_{92}^{31415}+\pi $',
r'${x}_{{y}_{b}^{a}}^{{z}_{c}^{d}}$',
r'${y}_{3}^{\prime \prime \prime }$',
r"$\left( \xi \left( 1 - \xi \right) \right)$", # Bug 2969451
r"$\left(2 \, a=b\right)$", # Sage bug #8125
r"$? ! &$", # github issue #466
r'$\operatorname{cos} x$', # github issue #553
r'$\sum _{\genfrac{}{}{0}{}{0\leq i\leq m}{0<j<n}}P\left(i,j\right)$',
r"$\left\Vert a \right\Vert \left\vert b \right\vert \left| a \right| \left\| b\right\| \Vert a \Vert \vert b \vert$",
r'$\mathring{A} \stackrel{\circ}{A} \AA$',
r'$M \, M \thinspace M \/ M \> M \: M \; M \ M \enspace M \quad M \qquad M \! M$',
r'$\Cup$ $\Cap$ $\leftharpoonup$ $\barwedge$ $\rightharpoonup$',
r'$\dotplus$ $\doteq$ $\doteqdot$ $\ddots$',
r'$xyz^kx_kx^py^{p-2} d_i^jb_jc_kd x^j_i E^0 E^0_u$', # github issue #4873
r'${xyz}^k{x}_{k}{x}^{p}{y}^{p-2} {d}_{i}^{j}{b}_{j}{c}_{k}{d} {x}^{j}_{i}{E}^{0}{E}^0_u$',
r'${\int}_x^x x\oint_x^x x\int_{X}^{X}x\int_x x \int^x x \int_{x} x\int^{x}{\int}_{x} x{\int}^{x}_{x}x$',
r'testing$^{123}$',
' '.join('$\\' + p + '$' for p in sorted(mathtext.Parser._snowflake)),
r'$6-2$; $-2$; $ -2$; ${-2}$; ${ -2}$; $20^{+3}_{-2}$',
r'$\overline{\omega}^x \frac{1}{2}_0^x$', # github issue #5444
r'$,$ $.$ $1{,}234{, }567{ , }890$ and $1,234,567,890$', # github issue 5799
r'$\left(X\right)_{a}^{b}$', # github issue 7615
r'$\dfrac{\$100.00}{y}$', # github issue #1888
]
digits = "0123456789"
uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
lowercase = "abcdefghijklmnopqrstuvwxyz"
uppergreek = ("\\Gamma \\Delta \\Theta \\Lambda \\Xi \\Pi \\Sigma \\Upsilon \\Phi \\Psi "
"\\Omega")
lowergreek = ("\\alpha \\beta \\gamma \\delta \\epsilon \\zeta \\eta \\theta \\iota "
"\\lambda \\mu \\nu \\xi \\pi \\kappa \\rho \\sigma \\tau \\upsilon "
"\\phi \\chi \\psi")
all = [digits, uppercase, lowercase, uppergreek, lowergreek]
font_test_specs = [
([], all),
(['mathrm'], all),
(['mathbf'], all),
(['mathit'], all),
(['mathtt'], [digits, uppercase, lowercase]),
(['mathcircled'], [digits, uppercase, lowercase]),
(['mathrm', 'mathcircled'], [digits, uppercase, lowercase]),
(['mathbf', 'mathcircled'], [digits, uppercase, lowercase]),
(['mathbb'], [digits, uppercase, lowercase,
r'\Gamma \Pi \Sigma \gamma \pi']),
(['mathrm', 'mathbb'], [digits, uppercase, lowercase,
r'\Gamma \Pi \Sigma \gamma \pi']),
(['mathbf', 'mathbb'], [digits, uppercase, lowercase,
r'\Gamma \Pi \Sigma \gamma \pi']),
(['mathcal'], [uppercase]),
(['mathfrak'], [uppercase, lowercase]),
(['mathbf', 'mathfrak'], [uppercase, lowercase]),
(['mathscr'], [uppercase, lowercase]),
(['mathsf'], [digits, uppercase, lowercase]),
(['mathrm', 'mathsf'], [digits, uppercase, lowercase]),
(['mathbf', 'mathsf'], [digits, uppercase, lowercase])
]
font_tests = []
for fonts, chars in font_test_specs:
wrapper = [' '.join(fonts), ' $']
for font in fonts:
wrapper.append(r'\%s{' % font)
wrapper.append('%s')
for font in fonts:
wrapper.append('}')
wrapper.append('$')
wrapper = ''.join(wrapper)
for set in chars:
font_tests.append(wrapper % set)
@pytest.fixture
def baseline_images(request, fontset, index):
return ['%s_%s_%02d' % (request.param, fontset, index)]
@pytest.mark.parametrize('index, test', enumerate(math_tests),
ids=[str(index) for index in range(len(math_tests))])
@pytest.mark.parametrize('fontset',
['cm', 'stix', 'stixsans', 'dejavusans',
'dejavuserif'])
@pytest.mark.parametrize('baseline_images', ['mathtext'], indirect=True)
@image_comparison(baseline_images=None)
def test_mathtext_rendering(baseline_images, fontset, index, test):
matplotlib.rcParams['mathtext.fontset'] = fontset
fig = plt.figure(figsize=(5.25, 0.75))
fig.text(0.5, 0.5, test,
horizontalalignment='center', verticalalignment='center')
@pytest.mark.parametrize('index, test', enumerate(font_tests),
ids=[str(index) for index in range(len(font_tests))])
@pytest.mark.parametrize('fontset',
['cm', 'stix', 'stixsans', 'dejavusans',
'dejavuserif'])
@pytest.mark.parametrize('baseline_images', ['mathfont'], indirect=True)
@image_comparison(baseline_images=None, extensions=['png'])
def test_mathfont_rendering(baseline_images, fontset, index, test):
matplotlib.rcParams['mathtext.fontset'] = fontset
fig = plt.figure(figsize=(5.25, 0.75))
fig.text(0.5, 0.5, test,
horizontalalignment='center', verticalalignment='center')
def test_fontinfo():
import matplotlib.font_manager as font_manager
import matplotlib.ft2font as ft2font
fontpath = font_manager.findfont("DejaVu Sans")
font = ft2font.FT2Font(fontpath)
table = font.get_sfnt_table("head")
assert table['version'] == (1, 0)
@pytest.mark.parametrize(
'math, msg',
[
(r'$\hspace{}$', r'Expected \hspace{n}'),
(r'$\hspace{foo}$', r'Expected \hspace{n}'),
(r'$\frac$', r'Expected \frac{num}{den}'),
(r'$\frac{}{}$', r'Expected \frac{num}{den}'),
(r'$\stackrel$', r'Expected \stackrel{num}{den}'),
(r'$\stackrel{}{}$', r'Expected \stackrel{num}{den}'),
(r'$\binom$', r'Expected \binom{num}{den}'),
(r'$\binom{}{}$', r'Expected \binom{num}{den}'),
(r'$\genfrac$',
r'Expected \genfrac{ldelim}{rdelim}{rulesize}{style}{num}{den}'),
(r'$\genfrac{}{}{}{}{}{}$',
r'Expected \genfrac{ldelim}{rdelim}{rulesize}{style}{num}{den}'),
(r'$\sqrt$', r'Expected \sqrt{value}'),
(r'$\sqrt f$', r'Expected \sqrt{value}'),
(r'$\overline$', r'Expected \overline{value}'),
(r'$\overline{}$', r'Expected \overline{value}'),
(r'$\leftF$', r'Expected a delimiter'),
(r'$\rightF$', r'Unknown symbol: \rightF'),
(r'$\left(\right$', r'Expected a delimiter'),
(r'$\left($', r'Expected "\right"'),
(r'$\dfrac$', r'Expected \dfrac{num}{den}'),
(r'$\dfrac{}{}$', r'Expected \dfrac{num}{den}'),
],
ids=[
'hspace without value',
'hspace with invalid value',
'frac without parameters',
'frac with empty parameters',
'stackrel without parameters',
'stackrel with empty parameters',
'binom without parameters',
'binom with empty parameters',
'genfrac without parameters',
'genfrac with empty parameters',
'sqrt without parameters',
'sqrt with invalid value',
'overline without parameters',
'overline with empty parameter',
'left with invalid delimiter',
'right with invalid delimiter',
'unclosed parentheses with sizing',
'unclosed parentheses without sizing',
'dfrac without parameters',
'dfrac with empty parameters',
]
)
def test_mathtext_exceptions(math, msg):
parser = mathtext.MathTextParser('agg')
with pytest.raises(ValueError) as excinfo:
parser.parse(math)
excinfo.match(re.escape(msg))
def test_single_minus_sign():
plt.figure(figsize=(0.3, 0.3))
plt.text(0.5, 0.5, '$-$')
for spine in plt.gca().spines.values():
spine.set_visible(False)
plt.gca().set_xticks([])
plt.gca().set_yticks([])
buff = io.BytesIO()
plt.savefig(buff, format="rgba", dpi=1000)
array = np.fromstring(buff.getvalue(), dtype=np.uint8)
# If this fails, it would be all white
assert not np.all(array == 0xff)
@@ -0,0 +1,21 @@
import matplotlib
import matplotlib.rcsetup
def test_use_doc_standard_backends():
"""
Test that the standard backends mentioned in the docstring of
matplotlib.use() are the same as in matplotlib.rcsetup.
"""
def parse(key):
backends = []
for line in matplotlib.use.__doc__.split(key)[1].split('\n'):
if not line.strip():
break
backends += [e.strip() for e in line.split(',') if e]
return backends
assert (set(parse('- interactive backends:\n')) ==
set(matplotlib.rcsetup.interactive_bk))
assert (set(parse('- non-interactive backends:\n')) ==
set(matplotlib.rcsetup.non_interactive_bk))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,126 @@
import pytest
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.lines as mlines
from matplotlib.offsetbox import (
AnchoredOffsetbox, DrawingArea, _get_packed_offsets)
@image_comparison(baseline_images=['offsetbox_clipping'], remove_text=True)
def test_offsetbox_clipping():
# - create a plot
# - put an AnchoredOffsetbox with a child DrawingArea
# at the center of the axes
# - give the DrawingArea a gray background
# - put a black line across the bounds of the DrawingArea
# - see that the black line is clipped to the edges of
# the DrawingArea.
fig, ax = plt.subplots()
size = 100
da = DrawingArea(size, size, clip=True)
bg = mpatches.Rectangle((0, 0), size, size,
facecolor='#CCCCCC',
edgecolor='None',
linewidth=0)
line = mlines.Line2D([-size*.5, size*1.5], [size/2, size/2],
color='black',
linewidth=10)
anchored_box = AnchoredOffsetbox(
loc='center',
child=da,
pad=0.,
frameon=False,
bbox_to_anchor=(.5, .5),
bbox_transform=ax.transAxes,
borderpad=0.)
da.add_artist(bg)
da.add_artist(line)
ax.add_artist(anchored_box)
ax.set_xlim((0, 1))
ax.set_ylim((0, 1))
def test_offsetbox_clip_children():
# - create a plot
# - put an AnchoredOffsetbox with a child DrawingArea
# at the center of the axes
# - give the DrawingArea a gray background
# - put a black line across the bounds of the DrawingArea
# - see that the black line is clipped to the edges of
# the DrawingArea.
fig, ax = plt.subplots()
size = 100
da = DrawingArea(size, size, clip=True)
bg = mpatches.Rectangle((0, 0), size, size,
facecolor='#CCCCCC',
edgecolor='None',
linewidth=0)
line = mlines.Line2D([-size*.5, size*1.5], [size/2, size/2],
color='black',
linewidth=10)
anchored_box = AnchoredOffsetbox(
loc='center',
child=da,
pad=0.,
frameon=False,
bbox_to_anchor=(.5, .5),
bbox_transform=ax.transAxes,
borderpad=0.)
da.add_artist(bg)
da.add_artist(line)
ax.add_artist(anchored_box)
fig.canvas.draw()
assert not fig.stale
da.clip_children = True
assert fig.stale
def test_offsetbox_loc_codes():
# Check that valid string location codes all work with an AnchoredOffsetbox
codes = {'upper right': 1,
'upper left': 2,
'lower left': 3,
'lower right': 4,
'right': 5,
'center left': 6,
'center right': 7,
'lower center': 8,
'upper center': 9,
'center': 10,
}
fig, ax = plt.subplots()
da = DrawingArea(100, 100)
for code in codes:
anchored_box = AnchoredOffsetbox(loc=code, child=da)
ax.add_artist(anchored_box)
fig.canvas.draw()
def test_expand_with_tight_layout():
# Check issue reported in #10476, and updated due to #10784
fig, ax = plt.subplots()
d1 = [1, 2]
d2 = [2, 1]
ax.plot(d1, label='series 1')
ax.plot(d2, label='series 2')
ax.legend(ncol=2, mode='expand')
fig.tight_layout() # where the crash used to happen
@pytest.mark.parametrize('wd_list',
([(150, 1)], [(150, 1)]*3, [(0.1, 1)], [(0.1, 1)]*2))
@pytest.mark.parametrize('total', (250, 100, 0, -1, None))
@pytest.mark.parametrize('sep', (250, 1, 0, -1))
@pytest.mark.parametrize('mode', ("expand", "fixed", "equal"))
def test_get_packed_offsets(wd_list, total, sep, mode):
# Check a (rather arbitrary) set of parameters due to successive similar
# issue tickets (at least #10476 and #10784) related to corner cases
# triggered inside this function when calling higher-level functions
# (e.g. `Axes.legend`).
_get_packed_offsets(wd_list, total, sep, mode=mode)
@@ -0,0 +1,470 @@
"""
Tests specific to the patches module.
"""
import numpy as np
from numpy.testing import assert_almost_equal, assert_array_equal
import pytest
from matplotlib.cbook import MatplotlibDeprecationWarning
from matplotlib.patches import Polygon, Rectangle
from matplotlib.testing.decorators import image_comparison, check_figures_equal
import matplotlib.pyplot as plt
from matplotlib import (
collections as mcollections, colors as mcolors, patches as mpatches,
path as mpath, style as mstyle, transforms as mtransforms)
import sys
on_win = (sys.platform == 'win32')
def test_Polygon_close():
#: Github issue #1018 identified a bug in the Polygon handling
#: of the closed attribute; the path was not getting closed
#: when set_xy was used to set the vertices.
# open set of vertices:
xy = [[0, 0], [0, 1], [1, 1]]
# closed set:
xyclosed = xy + [[0, 0]]
# start with open path and close it:
p = Polygon(xy, closed=True)
assert_array_equal(p.get_xy(), xyclosed)
p.set_xy(xy)
assert_array_equal(p.get_xy(), xyclosed)
# start with closed path and open it:
p = Polygon(xyclosed, closed=False)
assert_array_equal(p.get_xy(), xy)
p.set_xy(xyclosed)
assert_array_equal(p.get_xy(), xy)
# start with open path and leave it open:
p = Polygon(xy, closed=False)
assert_array_equal(p.get_xy(), xy)
p.set_xy(xy)
assert_array_equal(p.get_xy(), xy)
# start with closed path and leave it closed:
p = Polygon(xyclosed, closed=True)
assert_array_equal(p.get_xy(), xyclosed)
p.set_xy(xyclosed)
assert_array_equal(p.get_xy(), xyclosed)
def test_rotate_rect():
loc = np.asarray([1.0, 2.0])
width = 2
height = 3
angle = 30.0
# A rotated rectangle
rect1 = Rectangle(loc, width, height, angle=angle)
# A non-rotated rectangle
rect2 = Rectangle(loc, width, height)
# Set up an explicit rotation matrix (in radians)
angle_rad = np.pi * angle / 180.0
rotation_matrix = np.array([[np.cos(angle_rad), -np.sin(angle_rad)],
[np.sin(angle_rad), np.cos(angle_rad)]])
# Translate to origin, rotate each vertex, and then translate back
new_verts = np.inner(rotation_matrix, rect2.get_verts() - loc).T + loc
# They should be the same
assert_almost_equal(rect1.get_verts(), new_verts)
def test_negative_rect():
# These two rectangles have the same vertices, but starting from a
# different point. (We also drop the last vertex, which is a duplicate.)
pos_vertices = Rectangle((-3, -2), 3, 2).get_verts()[:-1]
neg_vertices = Rectangle((0, 0), -3, -2).get_verts()[:-1]
assert_array_equal(np.roll(neg_vertices, 2, 0), pos_vertices)
@image_comparison(baseline_images=['clip_to_bbox'])
def test_clip_to_bbox():
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim([-18, 20])
ax.set_ylim([-150, 100])
path = mpath.Path.unit_regular_star(8).deepcopy()
path.vertices *= [10, 100]
path.vertices -= [5, 25]
path2 = mpath.Path.unit_circle().deepcopy()
path2.vertices *= [10, 100]
path2.vertices += [10, -25]
combined = mpath.Path.make_compound_path(path, path2)
patch = mpatches.PathPatch(
combined, alpha=0.5, facecolor='coral', edgecolor='none')
ax.add_patch(patch)
bbox = mtransforms.Bbox([[-12, -77.5], [50, -110]])
result_path = combined.clip_to_bbox(bbox)
result_patch = mpatches.PathPatch(
result_path, alpha=0.5, facecolor='green', lw=4, edgecolor='black')
ax.add_patch(result_patch)
@image_comparison(baseline_images=['patch_alpha_coloring'], remove_text=True)
def test_patch_alpha_coloring():
"""
Test checks that the patch and collection are rendered with the specified
alpha values in their facecolor and edgecolor.
"""
star = mpath.Path.unit_regular_star(6)
circle = mpath.Path.unit_circle()
# concatenate the star with an internal cutout of the circle
verts = np.concatenate([circle.vertices, star.vertices[::-1]])
codes = np.concatenate([circle.codes, star.codes])
cut_star1 = mpath.Path(verts, codes)
cut_star2 = mpath.Path(verts + 1, codes)
ax = plt.axes()
patch = mpatches.PathPatch(cut_star1,
linewidth=5, linestyle='dashdot',
facecolor=(1, 0, 0, 0.5),
edgecolor=(0, 0, 1, 0.75))
ax.add_patch(patch)
col = mcollections.PathCollection([cut_star2],
linewidth=5, linestyles='dashdot',
facecolor=(1, 0, 0, 0.5),
edgecolor=(0, 0, 1, 0.75))
ax.add_collection(col)
ax.set_xlim([-1, 2])
ax.set_ylim([-1, 2])
@image_comparison(baseline_images=['patch_alpha_override'], remove_text=True)
def test_patch_alpha_override():
#: Test checks that specifying an alpha attribute for a patch or
#: collection will override any alpha component of the facecolor
#: or edgecolor.
star = mpath.Path.unit_regular_star(6)
circle = mpath.Path.unit_circle()
# concatenate the star with an internal cutout of the circle
verts = np.concatenate([circle.vertices, star.vertices[::-1]])
codes = np.concatenate([circle.codes, star.codes])
cut_star1 = mpath.Path(verts, codes)
cut_star2 = mpath.Path(verts + 1, codes)
ax = plt.axes()
patch = mpatches.PathPatch(cut_star1,
linewidth=5, linestyle='dashdot',
alpha=0.25,
facecolor=(1, 0, 0, 0.5),
edgecolor=(0, 0, 1, 0.75))
ax.add_patch(patch)
col = mcollections.PathCollection([cut_star2],
linewidth=5, linestyles='dashdot',
alpha=0.25,
facecolor=(1, 0, 0, 0.5),
edgecolor=(0, 0, 1, 0.75))
ax.add_collection(col)
ax.set_xlim([-1, 2])
ax.set_ylim([-1, 2])
@pytest.mark.style('default')
def test_patch_color_none():
# Make sure the alpha kwarg does not override 'none' facecolor.
# Addresses issue #7478.
c = plt.Circle((0, 0), 1, facecolor='none', alpha=1)
assert c.get_facecolor()[0] == 0
@image_comparison(baseline_images=['patch_custom_linestyle'],
remove_text=True)
def test_patch_custom_linestyle():
#: A test to check that patches and collections accept custom dash
#: patterns as linestyle and that they display correctly.
star = mpath.Path.unit_regular_star(6)
circle = mpath.Path.unit_circle()
# concatenate the star with an internal cutout of the circle
verts = np.concatenate([circle.vertices, star.vertices[::-1]])
codes = np.concatenate([circle.codes, star.codes])
cut_star1 = mpath.Path(verts, codes)
cut_star2 = mpath.Path(verts + 1, codes)
ax = plt.axes()
patch = mpatches.PathPatch(cut_star1,
linewidth=5, linestyle=(0.0, (5.0, 7.0, 10.0, 7.0)),
facecolor=(1, 0, 0),
edgecolor=(0, 0, 1))
ax.add_patch(patch)
col = mcollections.PathCollection([cut_star2],
linewidth=5, linestyles=[(0.0, (5.0, 7.0, 10.0, 7.0))],
facecolor=(1, 0, 0),
edgecolor=(0, 0, 1))
ax.add_collection(col)
ax.set_xlim([-1, 2])
ax.set_ylim([-1, 2])
def test_patch_linestyle_accents():
#: Test if linestyle can also be specified with short menoics
#: like "--"
#: c.f. Gihub issue #2136
star = mpath.Path.unit_regular_star(6)
circle = mpath.Path.unit_circle()
# concatenate the star with an internal cutout of the circle
verts = np.concatenate([circle.vertices, star.vertices[::-1]])
codes = np.concatenate([circle.codes, star.codes])
linestyles = ["-", "--", "-.", ":",
"solid", "dashed", "dashdot", "dotted"]
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
for i, ls in enumerate(linestyles):
star = mpath.Path(verts + i, codes)
patch = mpatches.PathPatch(star,
linewidth=3, linestyle=ls,
facecolor=(1, 0, 0),
edgecolor=(0, 0, 1))
ax.add_patch(patch)
ax.set_xlim([-1, i + 1])
ax.set_ylim([-1, i + 1])
fig.canvas.draw()
assert True
def test_wedge_movement():
param_dict = {'center': ((0, 0), (1, 1), 'set_center'),
'r': (5, 8, 'set_radius'),
'width': (2, 3, 'set_width'),
'theta1': (0, 30, 'set_theta1'),
'theta2': (45, 50, 'set_theta2')}
init_args = {k: v[0] for k, v in param_dict.items()}
w = mpatches.Wedge(**init_args)
for attr, (old_v, new_v, func) in param_dict.items():
assert getattr(w, attr) == old_v
getattr(w, func)(new_v)
assert getattr(w, attr) == new_v
# png needs tol>=0.06, pdf tol>=1.617
@image_comparison(baseline_images=['wedge_range'],
remove_text=True, tol=1.65 if on_win else 0)
def test_wedge_range():
ax = plt.axes()
t1 = 2.313869244286224
args = [[52.31386924, 232.31386924],
[52.313869244286224, 232.31386924428622],
[t1, t1 + 180.0],
[0, 360],
[90, 90 + 360],
[-180, 180],
[0, 380],
[45, 46],
[46, 45]]
for i, (theta1, theta2) in enumerate(args):
x = i % 3
y = i // 3
wedge = mpatches.Wedge((x * 3, y * 3), 1, theta1, theta2,
facecolor='none', edgecolor='k', lw=3)
ax.add_artist(wedge)
ax.set_xlim([-2, 8])
ax.set_ylim([-2, 9])
def test_patch_str():
"""
Check that patches have nice and working `str` representation.
Note that the logic is that `__str__` is defined such that:
str(eval(str(p))) == str(p)
"""
p = mpatches.Circle(xy=(1, 2), radius=3)
assert str(p) == 'Circle(xy=(1, 2), radius=3)'
p = mpatches.Ellipse(xy=(1, 2), width=3, height=4, angle=5)
assert str(p) == 'Ellipse(xy=(1, 2), width=3, height=4, angle=5)'
p = mpatches.Rectangle(xy=(1, 2), width=3, height=4, angle=5)
assert str(p) == 'Rectangle(xy=(1, 2), width=3, height=4, angle=5)'
p = mpatches.Wedge(center=(1, 2), r=3, theta1=4, theta2=5, width=6)
assert str(p) == 'Wedge(center=(1, 2), r=3, theta1=4, theta2=5, width=6)'
p = mpatches.Arc(xy=(1, 2), width=3, height=4, angle=5, theta1=6, theta2=7)
expected = 'Arc(xy=(1, 2), width=3, height=4, angle=5, theta1=6, theta2=7)'
assert str(p) == expected
p = mpatches.RegularPolygon((1, 2), 20, radius=5)
assert str(p) == "RegularPolygon((1, 2), 20, radius=5, orientation=0)"
p = mpatches.CirclePolygon(xy=(1, 2), radius=5, resolution=20)
assert str(p) == "CirclePolygon((1, 2), radius=5, resolution=20)"
p = mpatches.FancyBboxPatch((1, 2), width=3, height=4)
assert str(p) == "FancyBboxPatch((1, 2), width=3, height=4)"
# Further nice __str__ which cannot be `eval`uated:
path_data = [([1, 2], mpath.Path.MOVETO), ([2, 2], mpath.Path.LINETO),
([1, 2], mpath.Path.CLOSEPOLY)]
p = mpatches.PathPatch(mpath.Path(*zip(*path_data)))
assert str(p) == "PathPatch3((1, 2) ...)"
data = [[1, 2], [2, 2], [1, 2]]
p = mpatches.Polygon(data)
assert str(p) == "Polygon3((1, 2) ...)"
p = mpatches.FancyArrowPatch(path=mpath.Path(*zip(*path_data)))
assert str(p)[:27] == "FancyArrowPatch(Path(array("
p = mpatches.FancyArrowPatch((1, 2), (3, 4))
assert str(p) == "FancyArrowPatch((1, 2)->(3, 4))"
p = mpatches.ConnectionPatch((1, 2), (3, 4), 'data')
assert str(p) == "ConnectionPatch((1, 2), (3, 4))"
s = mpatches.Shadow(p, 1, 1)
assert str(s) == "Shadow(ConnectionPatch((1, 2), (3, 4)))"
with pytest.warns(MatplotlibDeprecationWarning):
p = mpatches.YAArrow(plt.gcf(), (1, 0), (2, 1), width=0.1)
assert str(p) == "YAArrow()"
# Not testing Arrow, FancyArrow here
# because they seem to exist only for historical reasons.
@image_comparison(baseline_images=['multi_color_hatch'],
remove_text=True, style='default')
def test_multi_color_hatch():
fig, ax = plt.subplots()
rects = ax.bar(range(5), range(1, 6))
for i, rect in enumerate(rects):
rect.set_facecolor('none')
rect.set_edgecolor('C{}'.format(i))
rect.set_hatch('/')
for i in range(5):
with mstyle.context({'hatch.color': 'C{}'.format(i)}):
r = Rectangle((i - .8 / 2, 5), .8, 1, hatch='//', fc='none')
ax.add_patch(r)
@image_comparison(baseline_images=['units_rectangle'], extensions=['png'])
def test_units_rectangle():
import matplotlib.testing.jpl_units as U
U.register()
p = mpatches.Rectangle((5*U.km, 6*U.km), 1*U.km, 2*U.km)
fig, ax = plt.subplots()
ax.add_patch(p)
ax.set_xlim([4*U.km, 7*U.km])
ax.set_ylim([5*U.km, 9*U.km])
@image_comparison(baseline_images=['connection_patch'], extensions=['png'],
style='mpl20', remove_text=True)
def test_connection_patch():
fig, (ax1, ax2) = plt.subplots(1, 2)
con = mpatches.ConnectionPatch(xyA=(0.1, 0.1), xyB=(0.9, 0.9),
coordsA='data', coordsB='data',
axesA=ax2, axesB=ax1,
arrowstyle="->")
ax2.add_artist(con)
def test_datetime_rectangle():
# Check that creating a rectangle with timedeltas doesn't fail
from datetime import datetime, timedelta
start = datetime(2017, 1, 1, 0, 0, 0)
delta = timedelta(seconds=16)
patch = mpatches.Rectangle((start, 0), delta, 1)
fig, ax = plt.subplots()
ax.add_patch(patch)
def test_datetime_datetime_fails():
from datetime import datetime
start = datetime(2017, 1, 1, 0, 0, 0)
dt_delta = datetime(1970, 1, 5) # Will be 5 days if units are done wrong
with pytest.raises(TypeError):
mpatches.Rectangle((start, 0), dt_delta, 1)
with pytest.raises(TypeError):
mpatches.Rectangle((0, start), 1, dt_delta)
def test_contains_point():
ell = mpatches.Ellipse((0.5, 0.5), 0.5, 1.0, 0)
points = [(0.0, 0.5), (0.2, 0.5), (0.25, 0.5), (0.5, 0.5)]
path = ell.get_path()
transform = ell.get_transform()
radius = ell._process_radius(None)
expected = np.array([path.contains_point(point,
transform,
radius) for point in points])
result = np.array([ell.contains_point(point) for point in points])
assert np.all(result == expected)
def test_contains_points():
ell = mpatches.Ellipse((0.5, 0.5), 0.5, 1.0, 0)
points = [(0.0, 0.5), (0.2, 0.5), (0.25, 0.5), (0.5, 0.5)]
path = ell.get_path()
transform = ell.get_transform()
radius = ell._process_radius(None)
expected = path.contains_points(points, transform, radius)
result = ell.contains_points(points)
assert np.all(result == expected)
# Currently fails with pdf/svg, probably because some parts assume a dpi of 72.
@check_figures_equal(extensions=["png"])
def test_shadow(fig_test, fig_ref):
xy = np.array([.2, .3])
dxy = np.array([.1, .2])
# We need to work around the nonsensical (dpi-dependent) interpretation of
# offsets by the Shadow class...
plt.rcParams["savefig.dpi"] = "figure"
# Test image.
a1 = fig_test.subplots()
rect = mpatches.Rectangle(xy=xy, width=.5, height=.5)
shadow = mpatches.Shadow(rect, ox=dxy[0], oy=dxy[1])
a1.add_patch(rect)
a1.add_patch(shadow)
# Reference image.
a2 = fig_ref.subplots()
rect = mpatches.Rectangle(xy=xy, width=.5, height=.5)
shadow = mpatches.Rectangle(
xy=xy + fig_ref.dpi / 72 * dxy, width=.5, height=.5,
fc=np.asarray(mcolors.to_rgb(rect.get_fc())) * .3,
ec=np.asarray(mcolors.to_rgb(rect.get_fc())) * .3,
alpha=.5)
a2.add_patch(shadow)
a2.add_patch(rect)
@@ -0,0 +1,243 @@
import copy
import numpy as np
from numpy.testing import assert_array_equal
import pytest
from matplotlib.path import Path
from matplotlib.patches import Polygon
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
from matplotlib import transforms
def test_empty_closed_path():
path = Path(np.zeros((0, 2)), closed=True)
assert path.vertices.shape == (0, 2)
assert path.codes is None
def test_readonly_path():
path = Path.unit_circle()
def modify_vertices():
path.vertices = path.vertices * 2.0
with pytest.raises(AttributeError):
modify_vertices()
def test_point_in_path():
# Test #1787
verts2 = [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]
path = Path(verts2, closed=True)
points = [(0.5, 0.5), (1.5, 0.5)]
ret = path.contains_points(points)
assert ret.dtype == 'bool'
assert np.all(ret == [True, False])
def test_contains_points_negative_radius():
path = Path.unit_circle()
points = [(0.0, 0.0), (1.25, 0.0), (0.9, 0.9)]
expected = [True, False, False]
result = path.contains_points(points, radius=-0.5)
assert np.all(result == expected)
def test_point_in_path_nan():
box = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
p = Path(box)
test = np.array([[np.nan, 0.5]])
contains = p.contains_points(test)
assert len(contains) == 1
assert not contains[0]
def test_nonlinear_containment():
fig, ax = plt.subplots()
ax.set(xscale="log", ylim=(0, 1))
polygon = ax.axvspan(1, 10)
assert polygon.get_path().contains_point(
ax.transData.transform_point((5, .5)), ax.transData)
assert not polygon.get_path().contains_point(
ax.transData.transform_point((.5, .5)), ax.transData)
assert not polygon.get_path().contains_point(
ax.transData.transform_point((50, .5)), ax.transData)
@image_comparison(baseline_images=['path_clipping'],
extensions=['svg'], remove_text=True)
def test_path_clipping():
fig = plt.figure(figsize=(6.0, 6.2))
for i, xy in enumerate([
[(200, 200), (200, 350), (400, 350), (400, 200)],
[(200, 200), (200, 350), (400, 350), (400, 100)],
[(200, 100), (200, 350), (400, 350), (400, 100)],
[(200, 100), (200, 415), (400, 350), (400, 100)],
[(200, 100), (200, 415), (400, 415), (400, 100)],
[(200, 415), (400, 415), (400, 100), (200, 100)],
[(400, 415), (400, 100), (200, 100), (200, 415)]]):
ax = fig.add_subplot(4, 2, i+1)
bbox = [0, 140, 640, 260]
ax.set_xlim(bbox[0], bbox[0] + bbox[2])
ax.set_ylim(bbox[1], bbox[1] + bbox[3])
ax.add_patch(Polygon(
xy, facecolor='none', edgecolor='red', closed=True))
@image_comparison(baseline_images=['semi_log_with_zero'], extensions=['png'],
style='mpl20')
def test_log_transform_with_zero():
x = np.arange(-10, 10)
y = (1.0 - 1.0/(x**2+1))**20
fig, ax = plt.subplots()
ax.semilogy(x, y, "-o", lw=15, markeredgecolor='k')
ax.set_ylim(1e-7, 1)
ax.grid(True)
def test_make_compound_path_empty():
# We should be able to make a compound path with no arguments.
# This makes it easier to write generic path based code.
r = Path.make_compound_path()
assert r.vertices.shape == (0, 2)
@image_comparison(baseline_images=['xkcd'], extensions=['png'],
remove_text=True)
def test_xkcd():
np.random.seed(0)
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
with plt.xkcd():
fig, ax = plt.subplots()
ax.plot(x, y)
@image_comparison(baseline_images=['xkcd_marker'], extensions=['png'],
remove_text=True)
def test_xkcd_marker():
np.random.seed(0)
x = np.linspace(0, 5, 8)
y1 = x
y2 = 5 - x
y3 = 2.5 * np.ones(8)
with plt.xkcd():
fig, ax = plt.subplots()
ax.plot(x, y1, '+', ms=10)
ax.plot(x, y2, 'o', ms=10)
ax.plot(x, y3, '^', ms=10)
@image_comparison(baseline_images=['marker_paths'], extensions=['pdf'],
remove_text=True)
def test_marker_paths_pdf():
N = 7
plt.errorbar(np.arange(N),
np.ones(N) + 4,
np.ones(N))
plt.xlim(-1, N)
plt.ylim(-1, 7)
@image_comparison(baseline_images=['nan_path'], style='default',
remove_text=True, extensions=['pdf', 'svg', 'eps', 'png'])
def test_nan_isolated_points():
y0 = [0, np.nan, 2, np.nan, 4, 5, 6]
y1 = [np.nan, 7, np.nan, 9, 10, np.nan, 12]
fig, ax = plt.subplots()
ax.plot(y0, '-o')
ax.plot(y1, '-o')
def test_path_no_doubled_point_in_to_polygon():
hand = np.array(
[[1.64516129, 1.16145833],
[1.64516129, 1.59375],
[1.35080645, 1.921875],
[1.375, 2.18229167],
[1.68548387, 1.9375],
[1.60887097, 2.55208333],
[1.68548387, 2.69791667],
[1.76209677, 2.56770833],
[1.83064516, 1.97395833],
[1.89516129, 2.75],
[1.9516129, 2.84895833],
[2.01209677, 2.76041667],
[1.99193548, 1.99479167],
[2.11290323, 2.63020833],
[2.2016129, 2.734375],
[2.25403226, 2.60416667],
[2.14919355, 1.953125],
[2.30645161, 2.36979167],
[2.39112903, 2.36979167],
[2.41532258, 2.1875],
[2.1733871, 1.703125],
[2.07782258, 1.16666667]])
(r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5)
poly = Path(np.vstack((hand[:, 1], hand[:, 0])).T, closed=True)
clip_rect = transforms.Bbox([[r0, c0], [r1, c1]])
poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]
assert np.all(poly_clipped[-2] != poly_clipped[-1])
assert np.all(poly_clipped[-1] == poly_clipped[0])
def test_path_to_polygons():
data = [[10, 10], [20, 20]]
p = Path(data)
assert_array_equal(p.to_polygons(width=40, height=40), [])
assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
[data])
assert_array_equal(p.to_polygons(), [])
assert_array_equal(p.to_polygons(closed_only=False), [data])
data = [[10, 10], [20, 20], [30, 30]]
closed_data = [[10, 10], [20, 20], [30, 30], [10, 10]]
p = Path(data)
assert_array_equal(p.to_polygons(width=40, height=40), [closed_data])
assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
[data])
assert_array_equal(p.to_polygons(), [closed_data])
assert_array_equal(p.to_polygons(closed_only=False), [data])
def test_path_deepcopy():
# Should not raise any error
verts = [[0, 0], [1, 1]]
codes = [Path.MOVETO, Path.LINETO]
path1 = Path(verts)
path2 = Path(verts, codes)
copy.deepcopy(path1)
copy.deepcopy(path2)
@pytest.mark.parametrize('offset', range(-720, 361, 45))
def test_full_arc(offset):
low = offset
high = 360 + offset
path = Path.arc(low, high)
mins = np.min(path.vertices, axis=0)
maxs = np.max(path.vertices, axis=0)
np.testing.assert_allclose(mins, -1)
assert np.allclose(maxs, 1)
@@ -0,0 +1,138 @@
import numpy as np
import pytest
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects
@image_comparison(baseline_images=['patheffect1'], remove_text=True)
def test_patheffect1():
ax1 = plt.subplot(111)
ax1.imshow([[1, 2], [2, 3]])
txt = ax1.annotate("test", (1., 1.), (0., 0),
arrowprops=dict(arrowstyle="->",
connectionstyle="angle3", lw=2),
size=20, ha="center",
path_effects=[path_effects.withStroke(linewidth=3,
foreground="w")])
txt.arrow_patch.set_path_effects([path_effects.Stroke(linewidth=5,
foreground="w"),
path_effects.Normal()])
pe = [path_effects.withStroke(linewidth=3, foreground="w")]
ax1.grid(True, linestyle="-", path_effects=pe)
@image_comparison(baseline_images=['patheffect2'], remove_text=True,
style='mpl20')
def test_patheffect2():
ax2 = plt.subplot(111)
arr = np.arange(25).reshape((5, 5))
ax2.imshow(arr)
cntr = ax2.contour(arr, colors="k")
plt.setp(cntr.collections,
path_effects=[path_effects.withStroke(linewidth=3,
foreground="w")])
clbls = ax2.clabel(cntr, fmt="%2.0f", use_clabeltext=True)
plt.setp(clbls,
path_effects=[path_effects.withStroke(linewidth=3,
foreground="w")])
@image_comparison(baseline_images=['patheffect3'])
def test_patheffect3():
p1, = plt.plot([1, 3, 5, 4, 3], 'o-b', lw=4)
p1.set_path_effects([path_effects.SimpleLineShadow(),
path_effects.Normal()])
plt.title(r'testing$^{123}$',
path_effects=[path_effects.withStroke(linewidth=1, foreground="r")])
leg = plt.legend([p1], [r'Line 1$^2$'], fancybox=True, loc='upper left')
leg.legendPatch.set_path_effects([path_effects.withSimplePatchShadow()])
text = plt.text(2, 3, 'Drop test', color='white',
bbox={'boxstyle': 'circle,pad=0.1', 'color': 'red'})
pe = [path_effects.Stroke(linewidth=3.75, foreground='k'),
path_effects.withSimplePatchShadow((6, -3), shadow_rgbFace='blue')]
text.set_path_effects(pe)
text.get_bbox_patch().set_path_effects(pe)
pe = [path_effects.PathPatchEffect(offset=(4, -4), hatch='xxxx',
facecolor='gray'),
path_effects.PathPatchEffect(edgecolor='white', facecolor='black',
lw=1.1)]
t = plt.gcf().text(0.02, 0.1, 'Hatch shadow', fontsize=75, weight=1000,
va='center')
t.set_path_effects(pe)
@image_comparison(baseline_images=['stroked_text'], extensions=['png'])
def test_patheffects_stroked_text():
text_chunks = [
'A B C D E F G H I J K L',
'M N O P Q R S T U V W',
'X Y Z a b c d e f g h i j',
'k l m n o p q r s t u v',
'w x y z 0123456789',
r"!@#$%^&*()-=_+[]\;'",
',./{}|:"<>?'
]
font_size = 50
ax = plt.axes([0, 0, 1, 1])
for i, chunk in enumerate(text_chunks):
text = ax.text(x=0.01, y=(0.9 - i * 0.13), s=chunk,
fontdict={'ha': 'left', 'va': 'center',
'size': font_size, 'color': 'white'})
text.set_path_effects([path_effects.Stroke(linewidth=font_size / 10,
foreground='black'),
path_effects.Normal()])
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
@pytest.mark.xfail
def test_PathEffect_points_to_pixels():
fig = plt.figure(dpi=150)
p1, = plt.plot(range(10))
p1.set_path_effects([path_effects.SimpleLineShadow(),
path_effects.Normal()])
renderer = fig.canvas.get_renderer()
pe_renderer = path_effects.SimpleLineShadow().get_proxy_renderer(renderer)
assert isinstance(pe_renderer, path_effects.PathEffectRenderer)
# Confirm that using a path effects renderer maintains point sizes
# appropriately. Otherwise rendered font would be the wrong size.
assert renderer.points_to_pixels(15) == pe_renderer.points_to_pixels(15)
def test_SimplePatchShadow_offset():
pe = path_effects.SimplePatchShadow(offset=(4, 5))
assert pe._offset == (4, 5)
@image_comparison(baseline_images=['collection'], tol=0.02, style='mpl20')
def test_collection():
x, y = np.meshgrid(np.linspace(0, 10, 150), np.linspace(-5, 5, 100))
data = np.sin(x) + np.cos(y)
cs = plt.contour(data)
pe = [path_effects.PathPatchEffect(edgecolor='black', facecolor='none',
linewidth=12),
path_effects.Stroke(linewidth=5)]
for collection in cs.collections:
collection.set_path_effects(pe)
for text in plt.clabel(cs, colors='white'):
text.set_path_effects([path_effects.withStroke(foreground='k',
linewidth=3)])
text.set_bbox({'boxstyle': 'sawtooth', 'facecolor': 'none',
'edgecolor': 'blue'})
@@ -0,0 +1,196 @@
import pickle
import platform
from io import BytesIO
import numpy as np
from matplotlib.testing.decorators import image_comparison
from matplotlib.dates import rrulewrapper
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
try: # https://docs.python.org/3/library/exceptions.html#RecursionError
RecursionError # Python 3.5+
except NameError:
RecursionError = RuntimeError # Python < 3.5
def test_simple():
fig = plt.figure()
pickle.dump(fig, BytesIO(), pickle.HIGHEST_PROTOCOL)
ax = plt.subplot(121)
pickle.dump(ax, BytesIO(), pickle.HIGHEST_PROTOCOL)
ax = plt.axes(projection='polar')
plt.plot(np.arange(10), label='foobar')
plt.legend()
pickle.dump(ax, BytesIO(), pickle.HIGHEST_PROTOCOL)
# ax = plt.subplot(121, projection='hammer')
# pickle.dump(ax, BytesIO(), pickle.HIGHEST_PROTOCOL)
plt.figure()
plt.bar(x=np.arange(10), height=np.arange(10))
pickle.dump(plt.gca(), BytesIO(), pickle.HIGHEST_PROTOCOL)
fig = plt.figure()
ax = plt.axes()
plt.plot(np.arange(10))
ax.set_yscale('log')
pickle.dump(fig, BytesIO(), pickle.HIGHEST_PROTOCOL)
@image_comparison(baseline_images=['multi_pickle'],
extensions=['png'], remove_text=True,
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
style='mpl20')
def test_complete():
fig = plt.figure('Figure with a label?', figsize=(10, 6))
plt.suptitle('Can you fit any more in a figure?')
# make some arbitrary data
x, y = np.arange(8), np.arange(10)
data = u = v = np.linspace(0, 10, 80).reshape(10, 8)
v = np.sin(v * -0.6)
# Ensure lists also pickle correctly.
plt.subplot(3, 3, 1)
plt.plot(list(range(10)))
plt.subplot(3, 3, 2)
plt.contourf(data, hatches=['//', 'ooo'])
plt.colorbar()
plt.subplot(3, 3, 3)
plt.pcolormesh(data)
plt.subplot(3, 3, 4)
plt.imshow(data)
plt.subplot(3, 3, 5)
plt.pcolor(data)
ax = plt.subplot(3, 3, 6)
ax.set_xlim(0, 7)
ax.set_ylim(0, 9)
plt.streamplot(x, y, u, v)
ax = plt.subplot(3, 3, 7)
ax.set_xlim(0, 7)
ax.set_ylim(0, 9)
plt.quiver(x, y, u, v)
plt.subplot(3, 3, 8)
plt.scatter(x, x**2, label='$x^2$')
plt.legend(loc='upper left')
plt.subplot(3, 3, 9)
plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4)
#
# plotting is done, now test its pickle-ability
#
result_fh = BytesIO()
pickle.dump(fig, result_fh, pickle.HIGHEST_PROTOCOL)
plt.close('all')
# make doubly sure that there are no figures left
assert plt._pylab_helpers.Gcf.figs == {}
# wind back the fh and load in the figure
result_fh.seek(0)
fig = pickle.load(result_fh)
# make sure there is now a figure manager
assert plt._pylab_helpers.Gcf.figs != {}
assert fig.get_label() == 'Figure with a label?'
def test_no_pyplot():
# tests pickle-ability of a figure not created with pyplot
from matplotlib.backends.backend_pdf import FigureCanvasPdf
from matplotlib.figure import Figure
fig = Figure()
_ = FigureCanvasPdf(fig)
ax = fig.add_subplot(1, 1, 1)
ax.plot([1, 2, 3], [1, 2, 3])
pickle.dump(fig, BytesIO(), pickle.HIGHEST_PROTOCOL)
def test_renderer():
from matplotlib.backends.backend_agg import RendererAgg
renderer = RendererAgg(10, 20, 30)
pickle.dump(renderer, BytesIO())
def test_image():
# Prior to v1.4.0 the Image would cache data which was not picklable
# once it had been drawn.
from matplotlib.backends.backend_agg import new_figure_manager
manager = new_figure_manager(1000)
fig = manager.canvas.figure
ax = fig.add_subplot(1, 1, 1)
ax.imshow(np.arange(12).reshape(3, 4))
manager.canvas.draw()
pickle.dump(fig, BytesIO())
def test_polar():
ax = plt.subplot(111, polar=True)
fig = plt.gcf()
pf = pickle.dumps(fig)
pickle.loads(pf)
plt.draw()
class TransformBlob(object):
def __init__(self):
self.identity = mtransforms.IdentityTransform()
self.identity2 = mtransforms.IdentityTransform()
# Force use of the more complex composition.
self.composite = mtransforms.CompositeGenericTransform(
self.identity,
self.identity2)
# Check parent -> child links of TransformWrapper.
self.wrapper = mtransforms.TransformWrapper(self.composite)
# Check child -> parent links of TransformWrapper.
self.composite2 = mtransforms.CompositeGenericTransform(
self.wrapper,
self.identity)
def test_transform():
obj = TransformBlob()
pf = pickle.dumps(obj)
del obj
obj = pickle.loads(pf)
# Check parent -> child links of TransformWrapper.
assert obj.wrapper._child == obj.composite
# Check child -> parent links of TransformWrapper.
assert [v() for v in obj.wrapper._parents.values()] == [obj.composite2]
# Check input and output dimensions are set as expected.
assert obj.wrapper.input_dims == obj.composite.input_dims
assert obj.wrapper.output_dims == obj.composite.output_dims
def test_rrulewrapper():
r = rrulewrapper(2)
try:
pickle.loads(pickle.dumps(r))
except RecursionError:
print('rrulewrapper pickling test failed')
raise
def test_shared():
fig, axs = plt.subplots(2, sharex=True)
fig = pickle.loads(pickle.dumps(fig))
fig.axes[0].set_xlim(10, 20)
assert fig.axes[1].get_xlim() == (10, 20)
@@ -0,0 +1,68 @@
from io import BytesIO
import glob
import os
import numpy as np
import pytest
from matplotlib.testing.decorators import image_comparison
from matplotlib import pyplot as plt
import matplotlib.cm as cm
import sys
on_win = (sys.platform == 'win32')
@image_comparison(baseline_images=['pngsuite'], extensions=['png'],
tol=0.03)
def test_pngsuite():
dirname = os.path.join(
os.path.dirname(__file__),
'baseline_images',
'pngsuite')
files = sorted(glob.iglob(os.path.join(dirname, 'basn*.png')))
fig = plt.figure(figsize=(len(files), 2))
for i, fname in enumerate(files):
data = plt.imread(fname)
cmap = None # use default colormap
if data.ndim == 2:
# keep grayscale images gray
cmap = cm.gray
plt.imshow(data, extent=[i, i + 1, 0, 1], cmap=cmap)
plt.gca().patch.set_facecolor("#ddffff")
plt.gca().set_xlim(0, len(files))
def test_imread_png_uint16():
from matplotlib import _png
img = _png.read_png_int(os.path.join(os.path.dirname(__file__),
'baseline_images/test_png/uint16.png'))
assert (img.dtype == np.uint16)
assert np.sum(img.flatten()) == 134184960
def test_truncated_file(tmpdir):
d = tmpdir.mkdir('test')
fname = str(d.join('test.png'))
fname_t = str(d.join('test_truncated.png'))
plt.savefig(fname)
with open(fname, 'rb') as fin:
buf = fin.read()
with open(fname_t, 'wb') as fout:
fout.write(buf[:20])
with pytest.raises(Exception):
plt.imread(fname_t)
def test_truncated_buffer():
b = BytesIO()
plt.savefig(b)
b.seek(0)
b2 = BytesIO(b.read(20))
b2.seek(0)
with pytest.raises(Exception):
plt.imread(b2)
@@ -0,0 +1,384 @@
import re
import numpy as np
import pytest
from matplotlib import _preprocess_data
# Notes on testing the plotting functions itself
# * the individual decorated plotting functions are tested in 'test_axes.py'
# * that pyplot functions accept a data kwarg is only tested in
# test_axes.test_pie_linewidth_0
# these two get used in multiple tests, so define them here
@_preprocess_data(replace_names=["x", "y"], label_namer="y")
def plot_func(ax, x, y, ls="x", label=None, w="xyz"):
return ("x: %s, y: %s, ls: %s, w: %s, label: %s" % (
list(x), list(y), ls, w, label))
@_preprocess_data(replace_names=["x", "y"], label_namer="y",
positional_parameter_names=["x", "y", "ls", "label", "w"])
def plot_func_varargs(ax, *args, **kwargs):
all_args = [None, None, "x", None, "xyz"]
for i, v in enumerate(args):
all_args[i] = v
for i, k in enumerate(["x", "y", "ls", "label", "w"]):
if k in kwargs:
all_args[i] = kwargs[k]
x, y, ls, label, w = all_args
return ("x: %s, y: %s, ls: %s, w: %s, label: %s" % (
list(x), list(y), ls, w, label))
all_funcs = [plot_func, plot_func_varargs]
all_func_ids = ['plot_func', 'plot_func_varargs']
def test_compiletime_checks():
"""test decorator invocations -> no replacements"""
def func(ax, x, y): pass
def func_args(ax, x, y, *args): pass
def func_kwargs(ax, x, y, **kwargs): pass
def func_no_ax_args(*args, **kwargs): pass
# this is ok
_preprocess_data(replace_names=["x", "y"])(func)
_preprocess_data(replace_names=["x", "y"])(func_kwargs)
# this has "enough" information to do all the replaces
_preprocess_data(replace_names=["x", "y"])(func_args)
# no positional_parameter_names but needed due to replaces
with pytest.raises(AssertionError):
# z is unknown
_preprocess_data(replace_names=["x", "y", "z"])(func_args)
with pytest.raises(AssertionError):
_preprocess_data(replace_names=["x", "y"])(func_no_ax_args)
# no replacements at all -> all ok...
_preprocess_data(replace_names=[], label_namer=None)(func)
_preprocess_data(replace_names=[], label_namer=None)(func_args)
_preprocess_data(replace_names=[], label_namer=None)(func_kwargs)
_preprocess_data(replace_names=[], label_namer=None)(func_no_ax_args)
# label namer is unknown
with pytest.raises(AssertionError):
_preprocess_data(label_namer="z")(func)
with pytest.raises(AssertionError):
_preprocess_data(label_namer="z")(func_args)
# but "ok-ish", if func has kwargs -> will show up at runtime :-(
_preprocess_data(label_namer="z")(func_kwargs)
_preprocess_data(label_namer="z")(func_no_ax_args)
def test_label_problems_at_runtime():
"""Tests for behaviour which would actually be nice to get rid of."""
@_preprocess_data(label_namer="z")
def func(*args, **kwargs):
pass
# This is a programming mistake: the parameter which should add the
# label is not present in the function call. Unfortunately this was masked
# due to the **kwargs usage
# This would be nice to handle as a compiletime check (see above...)
with pytest.warns(RuntimeWarning):
func(None, x="a", y="b")
def real_func(x, y):
pass
@_preprocess_data(label_namer="x")
def func(*args, **kwargs):
real_func(**kwargs)
# This sets a label although the function can't handle it.
with pytest.raises(TypeError):
func(None, x="a", y="b")
@pytest.mark.parametrize('func', all_funcs, ids=all_func_ids)
def test_function_call_without_data(func):
"""test without data -> no replacements"""
assert (func(None, "x", "y") ==
"x: ['x'], y: ['y'], ls: x, w: xyz, label: None")
assert (func(None, x="x", y="y") ==
"x: ['x'], y: ['y'], ls: x, w: xyz, label: None")
assert (func(None, "x", "y", label="") ==
"x: ['x'], y: ['y'], ls: x, w: xyz, label: ")
assert (func(None, "x", "y", label="text") ==
"x: ['x'], y: ['y'], ls: x, w: xyz, label: text")
assert (func(None, x="x", y="y", label="") ==
"x: ['x'], y: ['y'], ls: x, w: xyz, label: ")
assert (func(None, x="x", y="y", label="text") ==
"x: ['x'], y: ['y'], ls: x, w: xyz, label: text")
@pytest.mark.parametrize('func', all_funcs, ids=all_func_ids)
def test_function_call_with_dict_data(func):
"""Test with dict data -> label comes from the value of 'x' parameter """
data = {"a": [1, 2], "b": [8, 9], "w": "NOT"}
assert (func(None, "a", "b", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
assert (func(None, x="a", y="b", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
assert (func(None, "a", "b", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (func(None, "a", "b", label="text", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
assert (func(None, x="a", y="b", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (func(None, x="a", y="b", label="text", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
@pytest.mark.parametrize('func', all_funcs, ids=all_func_ids)
def test_function_call_with_dict_data_not_in_data(func):
"test for the case that one var is not in data -> half replaces, half kept"
data = {"a": [1, 2], "w": "NOT"}
assert (func(None, "a", "b", data=data) ==
"x: [1, 2], y: ['b'], ls: x, w: xyz, label: b")
assert (func(None, x="a", y="b", data=data) ==
"x: [1, 2], y: ['b'], ls: x, w: xyz, label: b")
assert (func(None, "a", "b", label="", data=data) ==
"x: [1, 2], y: ['b'], ls: x, w: xyz, label: ")
assert (func(None, "a", "b", label="text", data=data) ==
"x: [1, 2], y: ['b'], ls: x, w: xyz, label: text")
assert (func(None, x="a", y="b", label="", data=data) ==
"x: [1, 2], y: ['b'], ls: x, w: xyz, label: ")
assert (func(None, x="a", y="b", label="text", data=data) ==
"x: [1, 2], y: ['b'], ls: x, w: xyz, label: text")
@pytest.mark.parametrize('func', all_funcs, ids=all_func_ids)
def test_function_call_with_pandas_data(func, pd):
"""test with pandas dataframe -> label comes from data["col"].name """
data = pd.DataFrame({"a": np.array([1, 2], dtype=np.int32),
"b": np.array([8, 9], dtype=np.int32),
"w": ["NOT", "NOT"]})
assert (func(None, "a", "b", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
assert (func(None, x="a", y="b", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
assert (func(None, "a", "b", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (func(None, "a", "b", label="text", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
assert (func(None, x="a", y="b", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (func(None, x="a", y="b", label="text", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
def test_function_call_replace_all():
"""Test without a "replace_names" argument, all vars should be replaced"""
data = {"a": [1, 2], "b": [8, 9], "x": "xyz"}
@_preprocess_data(label_namer="y")
def func_replace_all(ax, x, y, ls="x", label=None, w="NOT"):
return "x: %s, y: %s, ls: %s, w: %s, label: %s" % (
list(x), list(y), ls, w, label)
assert (func_replace_all(None, "a", "b", w="x", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
assert (func_replace_all(None, x="a", y="b", w="x", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
assert (func_replace_all(None, "a", "b", w="x", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (
func_replace_all(None, "a", "b", w="x", label="text", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
assert (
func_replace_all(None, x="a", y="b", w="x", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (
func_replace_all(None, x="a", y="b", w="x", label="text", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
@_preprocess_data(label_namer="y")
def func_varags_replace_all(ax, *args, **kwargs):
all_args = [None, None, "x", None, "xyz"]
for i, v in enumerate(args):
all_args[i] = v
for i, k in enumerate(["x", "y", "ls", "label", "w"]):
if k in kwargs:
all_args[i] = kwargs[k]
x, y, ls, label, w = all_args
return "x: %s, y: %s, ls: %s, w: %s, label: %s" % (
list(x), list(y), ls, w, label)
# in the first case, we can't get a "y" argument,
# as we don't know the names of the *args
assert (func_varags_replace_all(None, x="a", y="b", w="x", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
assert (
func_varags_replace_all(None, "a", "b", w="x", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (
func_varags_replace_all(None, "a", "b", w="x", label="text",
data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
assert (
func_varags_replace_all(None, x="a", y="b", w="x", label="",
data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (
func_varags_replace_all(None, x="a", y="b", w="x", label="text",
data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
with pytest.warns(RuntimeWarning):
assert (func_varags_replace_all(None, "a", "b", w="x", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: None")
def test_no_label_replacements():
"""Test with "label_namer=None" -> no label replacement at all"""
@_preprocess_data(replace_names=["x", "y"], label_namer=None)
def func_no_label(ax, x, y, ls="x", label=None, w="xyz"):
return "x: %s, y: %s, ls: %s, w: %s, label: %s" % (
list(x), list(y), ls, w, label)
data = {"a": [1, 2], "b": [8, 9], "w": "NOT"}
assert (func_no_label(None, "a", "b", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: None")
assert (func_no_label(None, x="a", y="b", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: None")
assert (func_no_label(None, "a", "b", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (func_no_label(None, "a", "b", label="text", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
def test_more_args_than_pos_parameter():
@_preprocess_data(replace_names=["x", "y"], label_namer="y")
def func(ax, x, y, z=1):
pass
data = {"a": [1, 2], "b": [8, 9], "w": "NOT"}
with pytest.raises(RuntimeError):
func(None, "a", "b", "z", "z", data=data)
def test_function_call_with_replace_all_args():
"""Test with a "replace_all_args" argument, all *args should be replaced"""
data = {"a": [1, 2], "b": [8, 9], "x": "xyz"}
def funcy(ax, *args, **kwargs):
all_args = [None, None, "x", None, "NOT"]
for i, v in enumerate(args):
all_args[i] = v
for i, k in enumerate(["x", "y", "ls", "label", "w"]):
if k in kwargs:
all_args[i] = kwargs[k]
x, y, ls, label, w = all_args
return "x: %s, y: %s, ls: %s, w: %s, label: %s" % (
list(x), list(y), ls, w, label)
func = _preprocess_data(replace_all_args=True, replace_names=["w"],
label_namer="y")(funcy)
assert (func(None, "a", "b", w="x", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (func(None, "a", "b", w="x", label="text", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
func2 = _preprocess_data(replace_all_args=True, replace_names=["w"],
label_namer="y",
positional_parameter_names=["x", "y", "ls",
"label", "w"])(funcy)
assert (func2(None, "a", "b", w="x", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: b")
assert (func2(None, "a", "b", w="x", label="", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: ")
assert (func2(None, "a", "b", w="x", label="text", data=data) ==
"x: [1, 2], y: [8, 9], ls: x, w: xyz, label: text")
def test_docstring_addition():
@_preprocess_data()
def funcy(ax, *args, **kwargs):
"""Funcy does nothing"""
pass
assert re.search(r".*All positional and all keyword arguments\.",
funcy.__doc__)
assert not re.search(r".*All positional arguments\.", funcy.__doc__)
assert not re.search(r".*All arguments with the following names: .*",
funcy.__doc__)
@_preprocess_data(replace_all_args=True, replace_names=[])
def funcy(ax, x, y, z, bar=None):
"""Funcy does nothing"""
pass
assert re.search(r".*All positional arguments\.",
funcy.__doc__)
assert not re.search(r".*All positional and all keyword arguments\.",
funcy.__doc__)
assert not re.search(r".*All arguments with the following names: .*",
funcy.__doc__)
@_preprocess_data(replace_all_args=True, replace_names=["bar"])
def funcy(ax, x, y, z, bar=None):
"""Funcy does nothing"""
pass
assert re.search(r".*All positional arguments\.", funcy.__doc__)
assert re.search(r".*All arguments with the following names: 'bar'\.",
funcy.__doc__)
assert not re.search(r".*All positional and all keyword arguments\.",
funcy.__doc__)
@_preprocess_data(replace_names=["x", "bar"])
def funcy(ax, x, y, z, bar=None):
"""Funcy does nothing"""
pass
# lists can print in any order, so test for both x,bar and bar,x
assert re.search(r".*All arguments with the following names: '.*', '.*'\.",
funcy.__doc__)
assert re.search(r".*'x'.*", funcy.__doc__)
assert re.search(r".*'bar'.*", funcy.__doc__)
assert not re.search(r".*All positional and all keyword arguments\.",
funcy.__doc__)
assert not re.search(r".*All positional arguments\.",
funcy.__doc__)
def test_positional_parameter_names_as_function():
# Also test the _plot_arg_replacer for plot...
from matplotlib.axes._axes import _plot_args_replacer
@_preprocess_data(replace_names=["x", "y"],
positional_parameter_names=_plot_args_replacer)
def funcy(ax, *args, **kwargs):
return "{args} | {kwargs}".format(args=args, kwargs=kwargs)
# the normal case...
data = {"x": "X", "hy1": "Y"}
assert funcy(None, "x", "hy1", data=data) == "('X', 'Y') | {}"
assert funcy(None, "x", "hy1", "c", data=data) == "('X', 'Y', 'c') | {}"
# no arbitrary long args with data
with pytest.raises(ValueError):
assert (funcy(None, "x", "y", "c", "x", "y", "x", "y", data=data) ==
"('X', 'Y', 'c', 'X', 'Y', 'X', 'Y') | {}")
# In the two arg case, if a valid color spec is in data, we warn but use
# it as data...
data = {"x": "X", "y": "Y", "ro": "!!"}
with pytest.warns(RuntimeWarning):
assert funcy(None, "y", "ro", data=data) == "('Y', '!!') | {}"
@@ -0,0 +1,38 @@
import subprocess
import sys
from pathlib import Path
import pytest
import matplotlib as mpl
from matplotlib import pyplot as plt
def test_pyplot_up_to_date():
gen_script = Path(mpl.__file__).parents[2] / "tools/boilerplate.py"
if not gen_script.exists():
pytest.skip("boilerplate.py not found")
orig_contents = Path(plt.__file__).read_text()
try:
subprocess.run([sys.executable, str(gen_script)], check=True)
new_contents = Path(plt.__file__).read_text()
assert orig_contents == new_contents
finally:
Path(plt.__file__).write_text(orig_contents)
def test_pyplot_box():
fig, ax = plt.subplots()
plt.box(False)
assert not ax.get_frame_on()
plt.box(True)
assert ax.get_frame_on()
plt.box()
assert not ax.get_frame_on()
plt.box()
assert ax.get_frame_on()
def test_stackplot_smoke():
# Small smoke test for stackplot (see #12405)
plt.stackplot([1, 2, 3], [1, 2, 3])
@@ -0,0 +1,201 @@
import warnings
import numpy as np
import pytest
import sys
from matplotlib import pyplot as plt
from matplotlib.testing.decorators import image_comparison
def draw_quiver(ax, **kw):
X, Y = np.meshgrid(np.arange(0, 2 * np.pi, 1),
np.arange(0, 2 * np.pi, 1))
U = np.cos(X)
V = np.sin(Y)
Q = ax.quiver(U, V, **kw)
return Q
def test_quiver_memory_leak():
fig, ax = plt.subplots()
Q = draw_quiver(ax)
ttX = Q.X
Q.remove()
del Q
assert sys.getrefcount(ttX) == 2
def test_quiver_key_memory_leak():
fig, ax = plt.subplots()
Q = draw_quiver(ax)
qk = ax.quiverkey(Q, 0.5, 0.92, 2, r'$2 \frac{m}{s}$',
labelpos='W',
fontproperties={'weight': 'bold'})
assert sys.getrefcount(qk) == 3
qk.remove()
assert sys.getrefcount(qk) == 2
def test_no_warnings():
fig, ax = plt.subplots()
X, Y = np.meshgrid(np.arange(15), np.arange(10))
U = V = np.ones_like(X)
phi = (np.random.rand(15, 10) - .5) * 150
with warnings.catch_warnings(record=True) as w:
ax.quiver(X, Y, U, V, angles=phi)
fig.canvas.draw()
assert len(w) == 0
def test_zero_headlength():
# Based on report by Doug McNeil:
# http://matplotlib.1069221.n5.nabble.com/quiver-warnings-td28107.html
fig, ax = plt.subplots()
X, Y = np.meshgrid(np.arange(10), np.arange(10))
U, V = np.cos(X), np.sin(Y)
with warnings.catch_warnings(record=True) as w:
ax.quiver(U, V, headlength=0, headaxislength=0)
fig.canvas.draw()
assert len(w) == 0
@image_comparison(baseline_images=['quiver_animated_test_image'],
extensions=['png'])
def test_quiver_animate():
# Tests fix for #2616
fig, ax = plt.subplots()
Q = draw_quiver(ax, animated=True)
qk = ax.quiverkey(Q, 0.5, 0.92, 2, r'$2 \frac{m}{s}$',
labelpos='W',
fontproperties={'weight': 'bold'})
@image_comparison(baseline_images=['quiver_with_key_test_image'],
extensions=['png'])
def test_quiver_with_key():
fig, ax = plt.subplots()
ax.margins(0.1)
Q = draw_quiver(ax)
qk = ax.quiverkey(Q, 0.5, 0.95, 2,
r'$2\, \mathrm{m}\, \mathrm{s}^{-1}$',
angle=-10,
coordinates='figure',
labelpos='W',
fontproperties={'weight': 'bold',
'size': 'large'})
@image_comparison(baseline_images=['quiver_single_test_image'],
extensions=['png'], remove_text=True)
def test_quiver_single():
fig, ax = plt.subplots()
ax.margins(0.1)
ax.quiver([1], [1], [2], [2])
def test_quiver_copy():
fig, ax = plt.subplots()
uv = dict(u=np.array([1.1]), v=np.array([2.0]))
q0 = ax.quiver([1], [1], uv['u'], uv['v'])
uv['v'][0] = 0
assert q0.V[0] == 2.0
@image_comparison(baseline_images=['quiver_key_pivot'],
extensions=['png'], remove_text=True)
def test_quiver_key_pivot():
fig, ax = plt.subplots()
u, v = np.mgrid[0:2*np.pi:10j, 0:2*np.pi:10j]
q = ax.quiver(np.sin(u), np.cos(v))
ax.set_xlim(-2, 11)
ax.set_ylim(-2, 11)
ax.quiverkey(q, 0.5, 1, 1, 'N', labelpos='N')
ax.quiverkey(q, 1, 0.5, 1, 'E', labelpos='E')
ax.quiverkey(q, 0.5, 0, 1, 'S', labelpos='S')
ax.quiverkey(q, 0, 0.5, 1, 'W', labelpos='W')
@image_comparison(baseline_images=['barbs_test_image'],
extensions=['png'], remove_text=True)
def test_barbs():
x = np.linspace(-5, 5, 5)
X, Y = np.meshgrid(x, x)
U, V = 12*X, 12*Y
fig, ax = plt.subplots()
ax.barbs(X, Y, U, V, np.sqrt(U*U + V*V), fill_empty=True, rounding=False,
sizes=dict(emptybarb=0.25, spacing=0.2, height=0.3),
cmap='viridis')
@image_comparison(baseline_images=['barbs_pivot_test_image'],
extensions=['png'], remove_text=True)
def test_barbs_pivot():
x = np.linspace(-5, 5, 5)
X, Y = np.meshgrid(x, x)
U, V = 12*X, 12*Y
fig, ax = plt.subplots()
ax.barbs(X, Y, U, V, fill_empty=True, rounding=False, pivot=1.7,
sizes=dict(emptybarb=0.25, spacing=0.2, height=0.3))
ax.scatter(X, Y, s=49, c='black')
def test_bad_masked_sizes():
'Test error handling when given differing sized masked arrays'
x = np.arange(3)
y = np.arange(3)
u = np.ma.array(15. * np.ones((4,)))
v = np.ma.array(15. * np.ones_like(u))
u[1] = np.ma.masked
v[1] = np.ma.masked
fig, ax = plt.subplots()
with pytest.raises(ValueError):
ax.barbs(x, y, u, v)
def test_angles_and_scale():
# angles array + scale_units kwarg
fig, ax = plt.subplots()
X, Y = np.meshgrid(np.arange(15), np.arange(10))
U = V = np.ones_like(X)
phi = (np.random.rand(15, 10) - .5) * 150
ax.quiver(X, Y, U, V, angles=phi, scale_units='xy')
@image_comparison(baseline_images=['quiver_xy'],
extensions=['png'], remove_text=True)
def test_quiver_xy():
# simple arrow pointing from SW to NE
fig, ax = plt.subplots(subplot_kw=dict(aspect='equal'))
ax.quiver(0, 0, 1, 1, angles='xy', scale_units='xy', scale=1)
ax.set_xlim(0, 1.1)
ax.set_ylim(0, 1.1)
ax.grid()
def test_quiverkey_angles():
# Check that only a single arrow is plotted for a quiverkey when an array
# of angles is given to the original quiver plot
fig, ax = plt.subplots()
X, Y = np.meshgrid(np.arange(2), np.arange(2))
U = V = angles = np.ones_like(X)
q = ax.quiver(X, Y, U, V, angles=angles)
qk = ax.quiverkey(q, 1, 1, 2, 'Label')
# The arrows are only created when the key is drawn
fig.canvas.draw()
assert len(qk.verts) == 1
@@ -0,0 +1,506 @@
from collections import OrderedDict
import copy
from itertools import chain
import locale
import os
from unittest import mock
import warnings
from cycler import cycler, Cycler
import pytest
import matplotlib as mpl
from matplotlib.cbook import MatplotlibDeprecationWarning
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
from matplotlib.rcsetup import (validate_bool_maybe_none,
validate_stringlist,
validate_colorlist,
validate_color,
validate_bool,
validate_nseq_int,
validate_nseq_float,
validate_cycler,
validate_hatch,
validate_hist_bins,
validate_markevery,
_validate_linestyle)
def test_rcparams():
mpl.rc('text', usetex=False)
mpl.rc('lines', linewidth=22)
usetex = mpl.rcParams['text.usetex']
linewidth = mpl.rcParams['lines.linewidth']
fname = os.path.join(os.path.dirname(__file__), 'test_rcparams.rc')
# test context given dictionary
with mpl.rc_context(rc={'text.usetex': not usetex}):
assert mpl.rcParams['text.usetex'] == (not usetex)
assert mpl.rcParams['text.usetex'] == usetex
# test context given filename (mpl.rc sets linewdith to 33)
with mpl.rc_context(fname=fname):
assert mpl.rcParams['lines.linewidth'] == 33
assert mpl.rcParams['lines.linewidth'] == linewidth
# test context given filename and dictionary
with mpl.rc_context(fname=fname, rc={'lines.linewidth': 44}):
assert mpl.rcParams['lines.linewidth'] == 44
assert mpl.rcParams['lines.linewidth'] == linewidth
# test rc_file
mpl.rc_file(fname)
assert mpl.rcParams['lines.linewidth'] == 33
def test_RcParams_class():
rc = mpl.RcParams({'font.cursive': ['Apple Chancery',
'Textile',
'Zapf Chancery',
'cursive'],
'font.family': 'sans-serif',
'font.weight': 'normal',
'font.size': 12})
expected_repr = """
RcParams({'font.cursive': ['Apple Chancery',
'Textile',
'Zapf Chancery',
'cursive'],
'font.family': ['sans-serif'],
'font.size': 12.0,
'font.weight': 'normal'})""".lstrip()
assert expected_repr == repr(rc)
expected_str = """
font.cursive: ['Apple Chancery', 'Textile', 'Zapf Chancery', 'cursive']
font.family: ['sans-serif']
font.size: 12.0
font.weight: normal""".lstrip()
assert expected_str == str(rc)
# test the find_all functionality
assert ['font.cursive', 'font.size'] == sorted(rc.find_all('i[vz]'))
assert ['font.family'] == list(rc.find_all('family'))
def test_rcparams_update():
rc = mpl.RcParams({'figure.figsize': (3.5, 42)})
bad_dict = {'figure.figsize': (3.5, 42, 1)}
# make sure validation happens on input
with pytest.raises(ValueError):
with warnings.catch_warnings():
warnings.filterwarnings('ignore',
message='.*(validate)',
category=UserWarning)
rc.update(bad_dict)
def test_rcparams_init():
with pytest.raises(ValueError):
with warnings.catch_warnings():
warnings.filterwarnings('ignore',
message='.*(validate)',
category=UserWarning)
mpl.RcParams({'figure.figsize': (3.5, 42, 1)})
def test_Bug_2543():
# Test that it possible to add all values to itself / deepcopy
# This was not possible because validate_bool_maybe_none did not
# accept None as an argument.
# https://github.com/matplotlib/matplotlib/issues/2543
# We filter warnings at this stage since a number of them are raised
# for deprecated rcparams as they should. We don't want these in the
# printed in the test suite.
with warnings.catch_warnings():
warnings.filterwarnings('ignore',
category=MatplotlibDeprecationWarning)
with mpl.rc_context():
_copy = mpl.rcParams.copy()
for key in _copy:
mpl.rcParams[key] = _copy[key]
mpl.rcParams['text.dvipnghack'] = None
with mpl.rc_context():
_deep_copy = copy.deepcopy(mpl.rcParams)
# real test is that this does not raise
assert validate_bool_maybe_none(None) is None
assert validate_bool_maybe_none("none") is None
with pytest.raises(ValueError):
validate_bool_maybe_none("blah")
with pytest.raises(ValueError):
validate_bool(None)
with pytest.raises(ValueError):
with mpl.rc_context():
mpl.rcParams['svg.fonttype'] = True
legend_color_tests = [
('face', {'color': 'r'}, mcolors.to_rgba('r')),
('face', {'color': 'inherit', 'axes.facecolor': 'r'},
mcolors.to_rgba('r')),
('face', {'color': 'g', 'axes.facecolor': 'r'}, mcolors.to_rgba('g')),
('edge', {'color': 'r'}, mcolors.to_rgba('r')),
('edge', {'color': 'inherit', 'axes.edgecolor': 'r'},
mcolors.to_rgba('r')),
('edge', {'color': 'g', 'axes.facecolor': 'r'}, mcolors.to_rgba('g'))
]
legend_color_test_ids = [
'same facecolor',
'inherited facecolor',
'different facecolor',
'same edgecolor',
'inherited edgecolor',
'different facecolor',
]
@pytest.mark.parametrize('color_type, param_dict, target', legend_color_tests,
ids=legend_color_test_ids)
def test_legend_colors(color_type, param_dict, target):
param_dict['legend.%scolor' % (color_type, )] = param_dict.pop('color')
get_func = 'get_%scolor' % (color_type, )
with mpl.rc_context(param_dict):
_, ax = plt.subplots()
ax.plot(range(3), label='test')
leg = ax.legend()
assert getattr(leg.legendPatch, get_func)() == target
def test_mfc_rcparams():
mpl.rcParams['lines.markerfacecolor'] = 'r'
ln = mpl.lines.Line2D([1, 2], [1, 2])
assert ln.get_markerfacecolor() == 'r'
def test_mec_rcparams():
mpl.rcParams['lines.markeredgecolor'] = 'r'
ln = mpl.lines.Line2D([1, 2], [1, 2])
assert ln.get_markeredgecolor() == 'r'
def test_Issue_1713():
utf32_be = os.path.join(os.path.dirname(__file__),
'test_utf32_be_rcparams.rc')
with mock.patch('locale.getpreferredencoding', return_value='UTF-32-BE'):
rc = mpl.rc_params_from_file(utf32_be, True, False)
assert rc.get('timezone') == 'UTC'
def generate_validator_testcases(valid):
validation_tests = (
{'validator': validate_bool,
'success': chain(((_, True) for _ in
('t', 'y', 'yes', 'on', 'true', '1', 1, True)),
((_, False) for _ in
('f', 'n', 'no', 'off', 'false', '0', 0, False))),
'fail': ((_, ValueError)
for _ in ('aardvark', 2, -1, [], ))},
{'validator': validate_stringlist,
'success': (('', []),
('a,b', ['a', 'b']),
('aardvark', ['aardvark']),
('aardvark, ', ['aardvark']),
('aardvark, ,', ['aardvark']),
(['a', 'b'], ['a', 'b']),
(('a', 'b'), ['a', 'b']),
(iter(['a', 'b']), ['a', 'b']),
(np.array(['a', 'b']), ['a', 'b']),
((1, 2), ['1', '2']),
(np.array([1, 2]), ['1', '2']),
),
'fail': ((dict(), ValueError),
(1, ValueError),
)
},
{'validator': validate_nseq_int(2),
'success': ((_, [1, 2])
for _ in ('1, 2', [1.5, 2.5], [1, 2],
(1, 2), np.array((1, 2)))),
'fail': ((_, ValueError)
for _ in ('aardvark', ('a', 1),
(1, 2, 3)
))
},
{'validator': validate_nseq_float(2),
'success': ((_, [1.5, 2.5])
for _ in ('1.5, 2.5', [1.5, 2.5], [1.5, 2.5],
(1.5, 2.5), np.array((1.5, 2.5)))),
'fail': ((_, ValueError)
for _ in ('aardvark', ('a', 1),
(1, 2, 3)
))
},
{'validator': validate_cycler,
'success': (('cycler("color", "rgb")',
cycler("color", 'rgb')),
(cycler('linestyle', ['-', '--']),
cycler('linestyle', ['-', '--'])),
("""(cycler("color", ["r", "g", "b"]) +
cycler("mew", [2, 3, 5]))""",
(cycler("color", 'rgb') +
cycler("markeredgewidth", [2, 3, 5]))),
("cycler(c='rgb', lw=[1, 2, 3])",
cycler('color', 'rgb') + cycler('linewidth', [1, 2, 3])),
("cycler('c', 'rgb') * cycler('linestyle', ['-', '--'])",
(cycler('color', 'rgb') *
cycler('linestyle', ['-', '--']))),
(cycler('ls', ['-', '--']),
cycler('linestyle', ['-', '--'])),
(cycler(mew=[2, 5]),
cycler('markeredgewidth', [2, 5])),
),
# This is *so* incredibly important: validate_cycler() eval's
# an arbitrary string! I think I have it locked down enough,
# and that is what this is testing.
# TODO: Note that these tests are actually insufficient, as it may
# be that they raised errors, but still did an action prior to
# raising the exception. We should devise some additional tests
# for that...
'fail': ((4, ValueError), # Gotta be a string or Cycler object
('cycler("bleh, [])', ValueError), # syntax error
('Cycler("linewidth", [1, 2, 3])',
ValueError), # only 'cycler()' function is allowed
('1 + 2', ValueError), # doesn't produce a Cycler object
('os.system("echo Gotcha")', ValueError), # os not available
('import os', ValueError), # should not be able to import
('def badjuju(a): return a; badjuju(cycler("color", "rgb"))',
ValueError), # Should not be able to define anything
# even if it does return a cycler
('cycler("waka", [1, 2, 3])', ValueError), # not a property
('cycler(c=[1, 2, 3])', ValueError), # invalid values
("cycler(lw=['a', 'b', 'c'])", ValueError), # invalid values
(cycler('waka', [1, 3, 5]), ValueError), # not a property
(cycler('color', ['C1', 'r', 'g']), ValueError) # no CN
)
},
{'validator': validate_hatch,
'success': (('--|', '--|'), ('\\oO', '\\oO'),
('/+*/.x', '/+*/.x'), ('', '')),
'fail': (('--_', ValueError),
(8, ValueError),
('X', ValueError)),
},
{'validator': validate_colorlist,
'success': (('r,g,b', ['r', 'g', 'b']),
(['r', 'g', 'b'], ['r', 'g', 'b']),
('r, ,', ['r']),
(['', 'g', 'blue'], ['g', 'blue']),
([np.array([1, 0, 0]), np.array([0, 1, 0])],
np.array([[1, 0, 0], [0, 1, 0]])),
(np.array([[1, 0, 0], [0, 1, 0]]),
np.array([[1, 0, 0], [0, 1, 0]])),
),
'fail': (('fish', ValueError),
),
},
{'validator': validate_color,
'success': (('None', 'none'),
('none', 'none'),
('AABBCC', '#AABBCC'), # RGB hex code
('AABBCC00', '#AABBCC00'), # RGBA hex code
('tab:blue', 'tab:blue'), # named color
('C0', 'C0'), # color from cycle
('(0, 1, 0)', [0.0, 1.0, 0.0]), # RGB tuple
((0, 1, 0), (0, 1, 0)), # non-string version
('(0, 1, 0, 1)', [0.0, 1.0, 0.0, 1.0]), # RGBA tuple
((0, 1, 0, 1), (0, 1, 0, 1)), # non-string version
('(0, 1, "0.5")', [0.0, 1.0, 0.5]), # unusual but valid
),
'fail': (('tab:veryblue', ValueError), # invalid name
('C123', ValueError), # invalid RGB(A) code and cycle index
('(0, 1)', ValueError), # tuple with length < 3
('(0, 1, 0, 1, 0)', ValueError), # tuple with length > 4
('(0, 1, none)', ValueError), # cannot cast none to float
),
},
{'validator': validate_hist_bins,
'success': (('auto', 'auto'),
('10', 10),
('1, 2, 3', [1, 2, 3]),
([1, 2, 3], [1, 2, 3]),
(np.arange(15), np.arange(15))
),
'fail': (('aardvark', ValueError),
)
},
{'validator': validate_markevery,
'success': ((None, None),
(1, 1),
(0.1, 0.1),
((1, 1), (1, 1)),
((0.1, 0.1), (0.1, 0.1)),
([1, 2, 3], [1, 2, 3]),
(slice(2), slice(None, 2, None)),
(slice(1, 2, 3), slice(1, 2, 3))
),
'fail': (((1, 2, 3), TypeError),
([1, 2, 0.3], TypeError),
(['a', 2, 3], TypeError),
([1, 2, 'a'], TypeError),
((0.1, 0.2, 0.3), TypeError),
((0.1, 2, 3), TypeError),
((1, 0.2, 0.3), TypeError),
((1, 0.1), TypeError),
((0.1, 1), TypeError),
(('abc'), TypeError),
((1, 'a'), TypeError),
((0.1, 'b'), TypeError),
(('a', 1), TypeError),
(('a', 0.1), TypeError),
('abc', TypeError),
('a', TypeError),
(object(), TypeError)
)
}
)
# The behavior of _validate_linestyle depends on the version of Python.
# ASCII-compliant bytes arguments should pass on Python 2 because of the
# automatic conversion between bytes and strings. Python 3 does not
# perform such a conversion, so the same cases should raise an exception.
#
# Common cases:
ls_test = {'validator': _validate_linestyle,
'success': (('-', '-'), ('solid', 'solid'),
('--', '--'), ('dashed', 'dashed'),
('-.', '-.'), ('dashdot', 'dashdot'),
(':', ':'), ('dotted', 'dotted'),
('', ''), (' ', ' '),
('None', 'none'), ('none', 'none'),
('DoTtEd', 'dotted'), # case-insensitive
(['1.23', '4.56'], (None, [1.23, 4.56])),
([1.23, 456], (None, [1.23, 456.0])),
([1, 2, 3, 4], (None, [1.0, 2.0, 3.0, 4.0])),
),
'fail': (('aardvark', ValueError), # not a valid string
('dotted'.encode('utf-16'), ValueError), # even on PY2
((None, [1, 2]), ValueError), # (offset, dashes) != OK
((0, [1, 2]), ValueError), # idem
((-1, [1, 2]), ValueError), # idem
([1, 2, 3], ValueError), # sequence with odd length
(1.23, ValueError), # not a sequence
)
}
# Add some cases of bytes arguments that Python 2 can convert silently:
ls_bytes_args = (b'dotted', 'dotted'.encode('ascii'))
ls_test['fail'] += tuple((arg, ValueError) for arg in ls_bytes_args)
# Update the validation test sequence.
validation_tests += (ls_test,)
for validator_dict in validation_tests:
validator = validator_dict['validator']
if valid:
for arg, target in validator_dict['success']:
yield validator, arg, target
else:
for arg, error_type in validator_dict['fail']:
yield validator, arg, error_type
@pytest.mark.parametrize('validator, arg, target',
generate_validator_testcases(True))
def test_validator_valid(validator, arg, target):
res = validator(arg)
if isinstance(target, np.ndarray):
assert np.all(res == target)
elif not isinstance(target, Cycler):
assert res == target
else:
# Cyclers can't simply be asserted equal. They don't implement __eq__
assert list(res) == list(target)
@pytest.mark.parametrize('validator, arg, exception_type',
generate_validator_testcases(False))
def test_validator_invalid(validator, arg, exception_type):
with pytest.raises(exception_type):
validator(arg)
def test_keymaps():
key_list = [k for k in mpl.rcParams if 'keymap' in k]
for k in key_list:
assert isinstance(mpl.rcParams[k], list)
def test_rcparams_reset_after_fail():
# There was previously a bug that meant that if rc_context failed and
# raised an exception due to issues in the supplied rc parameters, the
# global rc parameters were left in a modified state.
with mpl.rc_context(rc={'text.usetex': False}):
assert mpl.rcParams['text.usetex'] is False
with pytest.raises(KeyError):
with mpl.rc_context(rc=OrderedDict([('text.usetex', True),
('test.blah', True)])):
pass
assert mpl.rcParams['text.usetex'] is False
def test_if_rctemplate_is_up_to_date():
# This tests if the matplotlibrc.template file contains all valid rcParams.
deprecated = {*mpl._all_deprecated, *mpl._deprecated_remain_as_none}
path_to_rc = os.path.join(mpl.get_data_path(), 'matplotlibrc')
with open(path_to_rc, "r") as f:
rclines = f.readlines()
missing = {}
for k, v in mpl.defaultParams.items():
if k[0] == "_":
continue
if k in deprecated:
continue
if k.startswith(
("verbose.", "examples.directory", "text.latex.unicode")):
continue
found = False
for line in rclines:
if k in line:
found = True
if not found:
missing.update({k: v})
if missing:
raise ValueError("The following params are missing in the "
"matplotlibrc.template file: {}"
.format(missing.items()))
def test_if_rctemplate_would_be_valid(tmpdir):
# This tests if the matplotlibrc.template file would result in a valid
# rc file if all lines are uncommented.
path_to_rc = os.path.join(mpl.get_data_path(), 'matplotlibrc')
with open(path_to_rc, "r") as f:
rclines = f.readlines()
newlines = []
for line in rclines:
if line[0] == "#":
newline = line[1:]
else:
newline = line
if "$TEMPLATE_BACKEND" in newline:
newline = "backend : Agg"
if "datapath" in newline:
newline = ""
newlines.append(newline)
d = tmpdir.mkdir('test1')
fname = str(d.join('testrcvalid.temp'))
with open(fname, "w") as f:
f.writelines(newlines)
with pytest.warns(None) as record:
mpl.rc_params_from_file(fname,
fail_on_error=True,
use_default_template=False)
assert len(record) == 0
@@ -0,0 +1,3 @@
# this file is used by the tests in test_rcparams.py
lines.linewidth: 33
@@ -0,0 +1,7 @@
from matplotlib.sankey import Sankey
def test_sankey():
# lets just create a sankey instance and check the code runs
sankey = Sankey()
sankey.add()
@@ -0,0 +1,150 @@
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
from matplotlib.scale import Log10Transform, InvertedLog10Transform
import numpy as np
import io
import platform
import pytest
@image_comparison(baseline_images=['log_scales'], remove_text=True)
def test_log_scales():
ax = plt.figure().add_subplot(122, yscale='log', xscale='symlog')
ax.axvline(24.1)
ax.axhline(24.1)
@image_comparison(baseline_images=['logit_scales'], remove_text=True,
extensions=['png'])
def test_logit_scales():
fig, ax = plt.subplots()
# Typical extinction curve for logit
x = np.array([0.001, 0.003, 0.01, 0.03, 0.1, 0.2, 0.3, 0.4, 0.5,
0.6, 0.7, 0.8, 0.9, 0.97, 0.99, 0.997, 0.999])
y = 1.0 / x
ax.plot(x, y)
ax.set_xscale('logit')
ax.grid(True)
bbox = ax.get_tightbbox(fig.canvas.get_renderer())
assert np.isfinite(bbox.x0)
assert np.isfinite(bbox.y0)
def test_log_scatter():
"""Issue #1799"""
fig, ax = plt.subplots(1)
x = np.arange(10)
y = np.arange(10) - 1
ax.scatter(x, y)
buf = io.BytesIO()
fig.savefig(buf, format='pdf')
buf = io.BytesIO()
fig.savefig(buf, format='eps')
buf = io.BytesIO()
fig.savefig(buf, format='svg')
def test_logscale_subs():
fig, ax = plt.subplots()
ax.set_yscale('log', subsy=np.array([2, 3, 4]))
# force draw
fig.canvas.draw()
@image_comparison(baseline_images=['logscale_mask'], remove_text=True,
extensions=['png'])
def test_logscale_mask():
# Check that zero values are masked correctly on log scales.
# See github issue 8045
xs = np.linspace(0, 50, 1001)
fig, ax = plt.subplots()
ax.plot(np.exp(-xs**2))
fig.canvas.draw()
ax.set(yscale="log")
def test_extra_kwargs_raise():
fig, ax = plt.subplots()
with pytest.raises(ValueError):
ax.set_yscale('log', nonpos='mask')
def test_logscale_invert_transform():
fig, ax = plt.subplots()
ax.set_yscale('log')
# get transformation from data to axes
tform = (ax.transAxes + ax.transData.inverted()).inverted()
# direct test of log transform inversion
assert isinstance(Log10Transform().inverted(), InvertedLog10Transform)
def test_logscale_transform_repr():
# check that repr of log transform succeeds
fig, ax = plt.subplots()
ax.set_yscale('log')
s = repr(ax.transData)
# check that repr of log transform succeeds
s = repr(Log10Transform(nonpos='clip'))
@image_comparison(baseline_images=['logscale_nonpos_values'], remove_text=True,
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
extensions=['png'], style='mpl20')
def test_logscale_nonpos_values():
np.random.seed(19680801)
xs = np.random.normal(size=int(1e3))
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
ax1.hist(xs, range=(-5, 5), bins=10)
ax1.set_yscale('log')
ax2.hist(xs, range=(-5, 5), bins=10)
ax2.set_yscale('log', nonposy='mask')
xdata = np.arange(0, 10, 0.01)
ydata = np.exp(-xdata)
edata = 0.2*(10-xdata)*np.cos(5*xdata)*np.exp(-xdata)
ax3.fill_between(xdata, ydata - edata, ydata + edata)
ax3.set_yscale('log')
x = np.logspace(-1, 1)
y = x ** 3
yerr = x**2
ax4.errorbar(x, y, yerr=yerr)
ax4.set_yscale('log')
ax4.set_xscale('log')
def test_invalid_log_lims():
# Check that invalid log scale limits are ignored
fig, ax = plt.subplots()
ax.scatter(range(0, 4), range(0, 4))
ax.set_xscale('log')
original_xlim = ax.get_xlim()
with pytest.warns(UserWarning):
ax.set_xlim(left=0)
assert ax.get_xlim() == original_xlim
with pytest.warns(UserWarning):
ax.set_xlim(right=-1)
assert ax.get_xlim() == original_xlim
ax.set_yscale('log')
original_ylim = ax.get_ylim()
with pytest.warns(UserWarning):
ax.set_ylim(bottom=0)
assert ax.get_ylim() == original_ylim
with pytest.warns(UserWarning):
ax.set_ylim(top=-1)
assert ax.get_ylim() == original_ylim
@@ -0,0 +1,339 @@
import base64
import io
import numpy as np
from numpy.testing import assert_array_almost_equal, assert_array_equal
import pytest
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
from matplotlib import patches, transforms
from matplotlib.path import Path
# NOTE: All of these tests assume that path.simplify is set to True
# (the default)
@image_comparison(baseline_images=['clipping'], remove_text=True)
def test_clipping():
t = np.arange(0.0, 2.0, 0.01)
s = np.sin(2*np.pi*t)
fig, ax = plt.subplots()
ax.plot(t, s, linewidth=1.0)
ax.set_ylim((-0.20, -0.28))
@image_comparison(baseline_images=['overflow'], remove_text=True)
def test_overflow():
x = np.array([1.0, 2.0, 3.0, 2.0e5])
y = np.arange(len(x))
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xlim(2, 6)
@image_comparison(baseline_images=['clipping_diamond'], remove_text=True)
def test_diamond():
x = np.array([0.0, 1.0, 0.0, -1.0, 0.0])
y = np.array([1.0, 0.0, -1.0, 0.0, 1.0])
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xlim(-0.6, 0.6)
ax.set_ylim(-0.6, 0.6)
def test_noise():
np.random.seed(0)
x = np.random.uniform(size=(50000,)) * 50
fig, ax = plt.subplots()
p1 = ax.plot(x, solid_joinstyle='round', linewidth=2.0)
path = p1[0].get_path()
transform = p1[0].get_transform()
path = transform.transform_path(path)
simplified = path.cleaned(simplify=True)
assert simplified.vertices.size == 25512
def test_antiparallel_simplification():
def _get_simplified(x, y):
fig, ax = plt.subplots()
p1 = ax.plot(x, y)
path = p1[0].get_path()
transform = p1[0].get_transform()
path = transform.transform_path(path)
simplified = path.cleaned(simplify=True)
simplified = transform.inverted().transform_path(simplified)
return simplified
# test ending on a maximum
x = [0, 0, 0, 0, 0, 1]
y = [.5, 1, -1, 1, 2, .5]
simplified = _get_simplified(x, y)
assert_array_almost_equal([[0., 0.5],
[0., -1.],
[0., 2.],
[1., 0.5]],
simplified.vertices[:-2, :])
# test ending on a minimum
x = [0, 0, 0, 0, 0, 1]
y = [.5, 1, -1, 1, -2, .5]
simplified = _get_simplified(x, y)
assert_array_almost_equal([[0., 0.5],
[0., 1.],
[0., -2.],
[1., 0.5]],
simplified.vertices[:-2, :])
# test ending in between
x = [0, 0, 0, 0, 0, 1]
y = [.5, 1, -1, 1, 0, .5]
simplified = _get_simplified(x, y)
assert_array_almost_equal([[0., 0.5],
[0., 1.],
[0., -1.],
[0., 0.],
[1., 0.5]],
simplified.vertices[:-2, :])
# test no anti-parallel ending at max
x = [0, 0, 0, 0, 0, 1]
y = [.5, 1, 2, 1, 3, .5]
simplified = _get_simplified(x, y)
assert_array_almost_equal([[0., 0.5],
[0., 3.],
[1., 0.5]],
simplified.vertices[:-2, :])
# test no anti-parallel ending in middle
x = [0, 0, 0, 0, 0, 1]
y = [.5, 1, 2, 1, 1, .5]
simplified = _get_simplified(x, y)
assert_array_almost_equal([[0., 0.5],
[0., 2.],
[0., 1.],
[1., 0.5]],
simplified.vertices[:-2, :])
# Only consider angles in 0 <= angle <= pi/2, otherwise
# using min/max will get the expected results out of order:
# min/max for simplification code depends on original vector,
# and if angle is outside above range then simplification
# min/max will be opposite from actual min/max.
@pytest.mark.parametrize('angle', [0, np.pi/4, np.pi/3, np.pi/2])
@pytest.mark.parametrize('offset', [0, .5])
def test_angled_antiparallel(angle, offset):
scale = 5
np.random.seed(19680801)
# get 15 random offsets
# TODO: guarantee offset > 0 results in some offsets < 0
vert_offsets = (np.random.rand(15) - offset) * scale
# always start at 0 so rotation makes sense
vert_offsets[0] = 0
# always take the first step the same direction
vert_offsets[1] = 1
# compute points along a diagonal line
x = np.sin(angle) * vert_offsets
y = np.cos(angle) * vert_offsets
# will check these later
x_max = x[1:].max()
x_min = x[1:].min()
y_max = y[1:].max()
y_min = y[1:].min()
if offset > 0:
p_expected = Path([[0, 0],
[x_max, y_max],
[x_min, y_min],
[x[-1], y[-1]],
[0, 0]],
codes=[1, 2, 2, 2, 0])
else:
p_expected = Path([[0, 0],
[x_max, y_max],
[x[-1], y[-1]],
[0, 0]],
codes=[1, 2, 2, 0])
p = Path(np.vstack([x, y]).T)
p2 = p.cleaned(simplify=True)
assert_array_almost_equal(p_expected.vertices,
p2.vertices)
assert_array_equal(p_expected.codes, p2.codes)
def test_sine_plus_noise():
np.random.seed(0)
x = (np.sin(np.linspace(0, np.pi * 2.0, 50000)) +
np.random.uniform(size=(50000,)) * 0.01)
fig, ax = plt.subplots()
p1 = ax.plot(x, solid_joinstyle='round', linewidth=2.0)
path = p1[0].get_path()
transform = p1[0].get_transform()
path = transform.transform_path(path)
simplified = path.cleaned(simplify=True)
assert simplified.vertices.size == 25240
@image_comparison(baseline_images=['simplify_curve'], remove_text=True)
def test_simplify_curve():
pp1 = patches.PathPatch(
Path([(0, 0), (1, 0), (1, 1), (np.nan, 1), (0, 0), (2, 0), (2, 2),
(0, 0)],
[Path.MOVETO, Path.CURVE3, Path.CURVE3, Path.CURVE3, Path.CURVE3,
Path.CURVE3, Path.CURVE3, Path.CLOSEPOLY]),
fc="none")
fig, ax = plt.subplots()
ax.add_patch(pp1)
ax.set_xlim((0, 2))
ax.set_ylim((0, 2))
@image_comparison(baseline_images=['hatch_simplify'], remove_text=True)
def test_hatch():
fig, ax = plt.subplots()
ax.add_patch(plt.Rectangle((0, 0), 1, 1, fill=False, hatch="/"))
ax.set_xlim((0.45, 0.55))
ax.set_ylim((0.45, 0.55))
@image_comparison(baseline_images=['fft_peaks'], remove_text=True)
def test_fft_peaks():
fig, ax = plt.subplots()
t = np.arange(65536)
p1 = ax.plot(abs(np.fft.fft(np.sin(2*np.pi*.01*t)*np.blackman(len(t)))))
path = p1[0].get_path()
transform = p1[0].get_transform()
path = transform.transform_path(path)
simplified = path.cleaned(simplify=True)
assert simplified.vertices.size == 36
def test_start_with_moveto():
# Should be entirely clipped away to a single MOVETO
data = b"""
ZwAAAAku+v9UAQAA+Tj6/z8CAADpQ/r/KAMAANlO+v8QBAAAyVn6//UEAAC6ZPr/2gUAAKpv+v+8
BgAAm3r6/50HAACLhfr/ewgAAHyQ+v9ZCQAAbZv6/zQKAABepvr/DgsAAE+x+v/lCwAAQLz6/7wM
AAAxx/r/kA0AACPS+v9jDgAAFN36/zQPAAAF6Pr/AxAAAPfy+v/QEAAA6f36/5wRAADbCPv/ZhIA
AMwT+/8uEwAAvh77//UTAACwKfv/uRQAAKM0+/98FQAAlT/7/z0WAACHSvv//RYAAHlV+/+7FwAA
bGD7/3cYAABea/v/MRkAAFF2+//pGQAARIH7/6AaAAA3jPv/VRsAACmX+/8JHAAAHKL7/7ocAAAP
rfv/ah0AAAO4+/8YHgAA9sL7/8QeAADpzfv/bx8AANzY+/8YIAAA0OP7/78gAADD7vv/ZCEAALf5
+/8IIgAAqwT8/6kiAACeD/z/SiMAAJIa/P/oIwAAhiX8/4QkAAB6MPz/HyUAAG47/P+4JQAAYkb8
/1AmAABWUfz/5SYAAEpc/P95JwAAPmf8/wsoAAAzcvz/nCgAACd9/P8qKQAAHIj8/7cpAAAQk/z/
QyoAAAWe/P/MKgAA+aj8/1QrAADus/z/2isAAOO+/P9eLAAA2Mn8/+AsAADM1Pz/YS0AAMHf/P/g
LQAAtur8/10uAACr9fz/2C4AAKEA/f9SLwAAlgv9/8ovAACLFv3/QDAAAIAh/f+1MAAAdSz9/ycx
AABrN/3/mDEAAGBC/f8IMgAAVk39/3UyAABLWP3/4TIAAEFj/f9LMwAANm79/7MzAAAsef3/GjQA
ACKE/f9+NAAAF4/9/+E0AAANmv3/QzUAAAOl/f+iNQAA+a/9/wA2AADvuv3/XDYAAOXF/f+2NgAA
29D9/w83AADR2/3/ZjcAAMfm/f+7NwAAvfH9/w44AACz/P3/XzgAAKkH/v+vOAAAnxL+//04AACW
Hf7/SjkAAIwo/v+UOQAAgjP+/905AAB5Pv7/JDoAAG9J/v9pOgAAZVT+/606AABcX/7/7zoAAFJq
/v8vOwAASXX+/207AAA/gP7/qjsAADaL/v/lOwAALZb+/x48AAAjof7/VTwAABqs/v+LPAAAELf+
/788AAAHwv7/8TwAAP7M/v8hPQAA9df+/1A9AADr4v7/fT0AAOLt/v+oPQAA2fj+/9E9AADQA///
+T0AAMYO//8fPgAAvRn//0M+AAC0JP//ZT4AAKsv//+GPgAAojr//6U+AACZRf//wj4AAJBQ///d
PgAAh1v///c+AAB+Zv//Dz8AAHRx//8lPwAAa3z//zk/AABih///TD8AAFmS//9dPwAAUJ3//2w/
AABHqP//ej8AAD6z//+FPwAANb7//48/AAAsyf//lz8AACPU//+ePwAAGt///6M/AAAR6v//pj8A
AAj1//+nPwAA/////w=="""
verts = np.fromstring(base64.decodebytes(data), dtype='<i4')
verts = verts.reshape((len(verts) // 2, 2))
path = Path(verts)
segs = path.iter_segments(transforms.IdentityTransform(),
clip=(0.0, 0.0, 100.0, 100.0))
segs = list(segs)
assert len(segs) == 1
assert segs[0][1] == Path.MOVETO
def test_throw_rendering_complexity_exceeded():
plt.rcParams['path.simplify'] = False
xx = np.arange(200000)
yy = np.random.rand(200000)
yy[1000] = np.nan
fig, ax = plt.subplots()
ax.plot(xx, yy)
with pytest.raises(OverflowError):
fig.savefig(io.BytesIO())
@image_comparison(baseline_images=['clipper_edge'], remove_text=True)
def test_clipper():
dat = (0, 1, 0, 2, 0, 3, 0, 4, 0, 5)
fig = plt.figure(figsize=(2, 1))
fig.subplots_adjust(left=0, bottom=0, wspace=0, hspace=0)
ax = fig.add_axes((0, 0, 1.0, 1.0), ylim=(0, 5), autoscale_on=False)
ax.plot(dat)
ax.xaxis.set_major_locator(plt.MultipleLocator(1))
ax.yaxis.set_major_locator(plt.MultipleLocator(1))
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.set_xlim(5, 9)
@image_comparison(baseline_images=['para_equal_perp'], remove_text=True)
def test_para_equal_perp():
x = np.array([0, 1, 2, 1, 0, -1, 0, 1] + [1] * 128)
y = np.array([1, 1, 2, 1, 0, -1, 0, 0] + [0] * 128)
fig, ax = plt.subplots()
ax.plot(x + 1, y + 1)
ax.plot(x + 1, y + 1, 'ro')
@image_comparison(baseline_images=['clipping_with_nans'])
def test_clipping_with_nans():
x = np.linspace(0, 3.14 * 2, 3000)
y = np.sin(x)
x[::100] = np.nan
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_ylim(-0.25, 0.25)
def test_clipping_full():
p = Path([[1e30, 1e30]] * 5)
simplified = list(p.iter_segments(clip=[0, 0, 100, 100]))
assert simplified == []
p = Path([[50, 40], [75, 65]], [1, 2])
simplified = list(p.iter_segments(clip=[0, 0, 100, 100]))
assert ([(list(x), y) for x, y in simplified] ==
[([50, 40], 1), ([75, 65], 2)])
p = Path([[50, 40]], [1])
simplified = list(p.iter_segments(clip=[0, 0, 100, 100]))
assert ([(list(x), y) for x, y in simplified] ==
[([50, 40], 1)])
@@ -0,0 +1,209 @@
"""
Testing that skewed axes properly work
"""
import itertools
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
from matplotlib.axes import Axes
import matplotlib.transforms as transforms
import matplotlib.axis as maxis
import matplotlib.spines as mspines
import matplotlib.patches as mpatch
from matplotlib.projections import register_projection
# The sole purpose of this class is to look at the upper, lower, or total
# interval as appropriate and see what parts of the tick to draw, if any.
class SkewXTick(maxis.XTick):
def update_position(self, loc):
# This ensures that the new value of the location is set before
# any other updates take place
self._loc = loc
super().update_position(loc)
def _has_default_loc(self):
return self.get_loc() is None
def _need_lower(self):
return (self._has_default_loc() or
transforms.interval_contains(self.axes.lower_xlim,
self.get_loc()))
def _need_upper(self):
return (self._has_default_loc() or
transforms.interval_contains(self.axes.upper_xlim,
self.get_loc()))
@property
def gridOn(self):
return (self._gridOn and (self._has_default_loc() or
transforms.interval_contains(self.get_view_interval(),
self.get_loc())))
@gridOn.setter
def gridOn(self, value):
self._gridOn = value
@property
def tick1On(self):
return self._tick1On and self._need_lower()
@tick1On.setter
def tick1On(self, value):
self._tick1On = value
@property
def label1On(self):
return self._label1On and self._need_lower()
@label1On.setter
def label1On(self, value):
self._label1On = value
@property
def tick2On(self):
return self._tick2On and self._need_upper()
@tick2On.setter
def tick2On(self, value):
self._tick2On = value
@property
def label2On(self):
return self._label2On and self._need_upper()
@label2On.setter
def label2On(self, value):
self._label2On = value
def get_view_interval(self):
return self.axes.xaxis.get_view_interval()
# This class exists to provide two separate sets of intervals to the tick,
# as well as create instances of the custom tick
class SkewXAxis(maxis.XAxis):
def _get_tick(self, major):
return SkewXTick(self.axes, None, '', major=major)
def get_view_interval(self):
return self.axes.upper_xlim[0], self.axes.lower_xlim[1]
# This class exists to calculate the separate data range of the
# upper X-axis and draw the spine there. It also provides this range
# to the X-axis artist for ticking and gridlines
class SkewSpine(mspines.Spine):
def _adjust_location(self):
pts = self._path.vertices
if self.spine_type == 'top':
pts[:, 0] = self.axes.upper_xlim
else:
pts[:, 0] = self.axes.lower_xlim
# This class handles registration of the skew-xaxes as a projection as well
# as setting up the appropriate transformations. It also overrides standard
# spines and axes instances as appropriate.
class SkewXAxes(Axes):
# The projection must specify a name. This will be used be the
# user to select the projection, i.e. ``subplot(111,
# projection='skewx')``.
name = 'skewx'
def _init_axis(self):
# Taken from Axes and modified to use our modified X-axis
self.xaxis = SkewXAxis(self)
self.spines['top'].register_axis(self.xaxis)
self.spines['bottom'].register_axis(self.xaxis)
self.yaxis = maxis.YAxis(self)
self.spines['left'].register_axis(self.yaxis)
self.spines['right'].register_axis(self.yaxis)
def _gen_axes_spines(self):
spines = {'top': SkewSpine.linear_spine(self, 'top'),
'bottom': mspines.Spine.linear_spine(self, 'bottom'),
'left': mspines.Spine.linear_spine(self, 'left'),
'right': mspines.Spine.linear_spine(self, 'right')}
return spines
def _set_lim_and_transforms(self):
"""
This is called once when the plot is created to set up all the
transforms for the data, text and grids.
"""
rot = 30
# Get the standard transform setup from the Axes base class
Axes._set_lim_and_transforms(self)
# Need to put the skew in the middle, after the scale and limits,
# but before the transAxes. This way, the skew is done in Axes
# coordinates thus performing the transform around the proper origin
# We keep the pre-transAxes transform around for other users, like the
# spines for finding bounds
self.transDataToAxes = (self.transScale +
(self.transLimits +
transforms.Affine2D().skew_deg(rot, 0)))
# Create the full transform from Data to Pixels
self.transData = self.transDataToAxes + self.transAxes
# Blended transforms like this need to have the skewing applied using
# both axes, in axes coords like before.
self._xaxis_transform = (transforms.blended_transform_factory(
self.transScale + self.transLimits,
transforms.IdentityTransform()) +
transforms.Affine2D().skew_deg(rot, 0)) + self.transAxes
@property
def lower_xlim(self):
return self.axes.viewLim.intervalx
@property
def upper_xlim(self):
pts = [[0., 1.], [1., 1.]]
return self.transDataToAxes.inverted().transform(pts)[:, 0]
# Now register the projection with matplotlib so the user can select
# it.
register_projection(SkewXAxes)
@image_comparison(baseline_images=['skew_axes'], remove_text=True)
def test_set_line_coll_dash_image():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='skewx')
ax.set_xlim(-50, 50)
ax.set_ylim(50, -50)
ax.grid(True)
# An example of a slanted line at constant X
ax.axvline(0, color='b')
@image_comparison(baseline_images=['skew_rects'], remove_text=True)
def test_skew_rectangle():
fix, axes = plt.subplots(5, 5, sharex=True, sharey=True, figsize=(8, 8))
axes = axes.flat
rotations = list(itertools.product([-3, -1, 0, 1, 3], repeat=2))
axes[0].set_xlim([-3, 3])
axes[0].set_ylim([-3, 3])
axes[0].set_aspect('equal', share=True)
for ax, (xrots, yrots) in zip(axes, rotations):
xdeg, ydeg = 45 * xrots, 45 * yrots
t = transforms.Affine2D().skew_deg(xdeg, ydeg)
ax.set_title('Skew of {0} in X and {1} in Y'.format(xdeg, ydeg))
ax.add_patch(mpatch.Rectangle([-1, -1], 2, 2,
transform=t + ax.transData,
alpha=0.5, facecolor='coral'))
plt.subplots_adjust(wspace=0, left=0.01, right=0.99, bottom=0.01, top=0.99)
@@ -0,0 +1,73 @@
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
@image_comparison(baseline_images=['spines_axes_positions'])
def test_spines_axes_positions():
# SF bug 2852168
fig = plt.figure()
x = np.linspace(0, 2*np.pi, 100)
y = 2*np.sin(x)
ax = fig.add_subplot(1, 1, 1)
ax.set_title('centered spines')
ax.plot(x, y)
ax.spines['right'].set_position(('axes', 0.1))
ax.yaxis.set_ticks_position('right')
ax.spines['top'].set_position(('axes', 0.25))
ax.xaxis.set_ticks_position('top')
ax.spines['left'].set_color('none')
ax.spines['bottom'].set_color('none')
@image_comparison(baseline_images=['spines_data_positions'])
def test_spines_data_positions():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.spines['left'].set_position(('data', -1.5))
ax.spines['top'].set_position(('data', 0.5))
ax.spines['right'].set_position(('data', -0.5))
ax.spines['bottom'].set_position('zero')
ax.set_xlim([-2, 2])
ax.set_ylim([-2, 2])
@image_comparison(baseline_images=['spines_capstyle'])
def test_spines_capstyle():
# issue 2542
plt.rc('axes', linewidth=20)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xticks([])
ax.set_yticks([])
def test_label_without_ticks():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
plt.subplots_adjust(left=0.3, bottom=0.3)
ax.plot(np.arange(10))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('outward', 30))
ax.spines['right'].set_visible(False)
ax.set_ylabel('y label')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('outward', 30))
ax.spines['top'].set_visible(False)
ax.set_xlabel('x label')
ax.xaxis.set_ticks([])
ax.yaxis.set_ticks([])
plt.draw()
spine = ax.spines['left']
spinebbox = spine.get_transform().transform_path(
spine.get_path()).get_extents()
assert ax.yaxis.label.get_position()[0] < spinebbox.xmin, \
"Y-Axis label not left of the spine"
spine = ax.spines['bottom']
spinebbox = spine.get_transform().transform_path(
spine.get_path()).get_extents()
assert ax.xaxis.label.get_position()[1] < spinebbox.ymin, \
"X-Axis label not below the spine"
@@ -0,0 +1,104 @@
import sys
import platform
import numpy as np
from numpy.testing import assert_array_almost_equal
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
import matplotlib.transforms as mtransforms
on_win = (sys.platform == 'win32')
def velocity_field():
Y, X = np.mgrid[-3:3:100j, -3:3:100j]
U = -1 - X**2 + Y
V = 1 + X - Y**2
return X, Y, U, V
def swirl_velocity_field():
x = np.linspace(-3., 3., 100)
y = np.linspace(-3., 3., 100)
X, Y = np.meshgrid(x, y)
a = 0.1
U = np.cos(a) * (-Y) - np.sin(a) * X
V = np.sin(a) * (-Y) + np.cos(a) * X
return x, y, U, V
@image_comparison(baseline_images=['streamplot_startpoints'],
remove_text=True, style='mpl20')
def test_startpoints():
X, Y, U, V = velocity_field()
start_x = np.linspace(X.min(), X.max(), 10)
start_y = np.linspace(Y.min(), Y.max(), 10)
start_points = np.column_stack([start_x, start_y])
plt.streamplot(X, Y, U, V, start_points=start_points)
plt.plot(start_x, start_y, 'ok')
@image_comparison(baseline_images=['streamplot_colormap'],
tol=.04, remove_text=True, style='mpl20')
def test_colormap():
X, Y, U, V = velocity_field()
plt.streamplot(X, Y, U, V, color=U, density=0.6, linewidth=2,
cmap=plt.cm.autumn)
plt.colorbar()
@image_comparison(baseline_images=['streamplot_linewidth'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
remove_text=True, style='mpl20')
def test_linewidth():
X, Y, U, V = velocity_field()
speed = np.sqrt(U*U + V*V)
lw = 5*speed/speed.max()
df = 25. / 30. # Compatibility factor for old test image
plt.streamplot(X, Y, U, V, density=[0.5 * df, 1. * df], color='k',
linewidth=lw)
@image_comparison(baseline_images=['streamplot_masks_and_nans'],
tol=0.04 if on_win else 0,
remove_text=True, style='mpl20')
def test_masks_and_nans():
X, Y, U, V = velocity_field()
mask = np.zeros(U.shape, dtype=bool)
mask[40:60, 40:60] = 1
U[:20, :20] = np.nan
U = np.ma.array(U, mask=mask)
with np.errstate(invalid='ignore'):
plt.streamplot(X, Y, U, V, color=U, cmap=plt.cm.Blues)
@image_comparison(baseline_images=['streamplot_maxlength'],
extensions=['png'], remove_text=True, style='mpl20')
def test_maxlength():
x, y, U, V = swirl_velocity_field()
plt.streamplot(x, y, U, V, maxlength=10., start_points=[[0., 1.5]],
linewidth=2, density=2)
@image_comparison(baseline_images=['streamplot_direction'],
extensions=['png'], remove_text=True, style='mpl20')
def test_direction():
x, y, U, V = swirl_velocity_field()
plt.streamplot(x, y, U, V, integration_direction='backward',
maxlength=1.5, start_points=[[1.5, 0.]],
linewidth=2, density=2)
def test_streamplot_limits():
ax = plt.axes()
x = np.linspace(-5, 10, 20)
y = np.linspace(-2, 4, 10)
y, x = np.meshgrid(y, x)
trans = mtransforms.Affine2D().translate(25, 32) + ax.transData
plt.barbs(x, y, np.sin(x), np.cos(y), transform=trans)
# The calculated bounds are approximately the bounds of the original data,
# this is because the entire path is taken into account when updating the
# datalim.
assert_array_almost_equal(ax.dataLim.bounds, (20, 30, 15, 6),
decimal=1)
@@ -0,0 +1,168 @@
from collections import OrderedDict
from contextlib import contextmanager
import gc
import os
from pathlib import Path
import shutil
from tempfile import TemporaryDirectory
import warnings
import pytest
import matplotlib as mpl
from matplotlib import pyplot as plt, style
from matplotlib.style.core import USER_LIBRARY_PATHS, STYLE_EXTENSION
PARAM = 'image.cmap'
VALUE = 'pink'
DUMMY_SETTINGS = {PARAM: VALUE}
@contextmanager
def temp_style(style_name, settings=None):
"""Context manager to create a style sheet in a temporary directory."""
if not settings:
settings = DUMMY_SETTINGS
temp_file = '%s.%s' % (style_name, STYLE_EXTENSION)
try:
with TemporaryDirectory() as tmpdir:
# Write style settings to file in the tmpdir.
Path(tmpdir, temp_file).write_text(
"\n".join("{}: {}".format(k, v) for k, v in settings.items()))
# Add tmpdir to style path and reload so we can access this style.
USER_LIBRARY_PATHS.append(tmpdir)
style.reload_library()
yield
finally:
style.reload_library()
def test_invalid_rc_warning_includes_filename():
SETTINGS = {'foo': 'bar'}
basename = 'basename'
with warnings.catch_warnings(record=True) as warns:
with temp_style(basename, SETTINGS):
# style.reload_library() in temp_style() triggers the warning
pass
for w in warns:
assert basename in str(w.message)
def test_available():
with temp_style('_test_', DUMMY_SETTINGS):
assert '_test_' in style.available
def test_use():
mpl.rcParams[PARAM] = 'gray'
with temp_style('test', DUMMY_SETTINGS):
with style.context('test'):
assert mpl.rcParams[PARAM] == VALUE
@pytest.mark.network
def test_use_url():
with temp_style('test', DUMMY_SETTINGS):
with style.context('https://gist.github.com/adrn/6590261/raw'):
assert mpl.rcParams['axes.facecolor'] == "#adeade"
def test_context():
mpl.rcParams[PARAM] = 'gray'
with temp_style('test', DUMMY_SETTINGS):
with style.context('test'):
assert mpl.rcParams[PARAM] == VALUE
# Check that this value is reset after the exiting the context.
assert mpl.rcParams[PARAM] == 'gray'
def test_context_with_dict():
original_value = 'gray'
other_value = 'blue'
mpl.rcParams[PARAM] = original_value
with style.context({PARAM: other_value}):
assert mpl.rcParams[PARAM] == other_value
assert mpl.rcParams[PARAM] == original_value
def test_context_with_dict_after_namedstyle():
# Test dict after style name where dict modifies the same parameter.
original_value = 'gray'
other_value = 'blue'
mpl.rcParams[PARAM] = original_value
with temp_style('test', DUMMY_SETTINGS):
with style.context(['test', {PARAM: other_value}]):
assert mpl.rcParams[PARAM] == other_value
assert mpl.rcParams[PARAM] == original_value
def test_context_with_dict_before_namedstyle():
# Test dict before style name where dict modifies the same parameter.
original_value = 'gray'
other_value = 'blue'
mpl.rcParams[PARAM] = original_value
with temp_style('test', DUMMY_SETTINGS):
with style.context([{PARAM: other_value}, 'test']):
assert mpl.rcParams[PARAM] == VALUE
assert mpl.rcParams[PARAM] == original_value
def test_context_with_union_of_dict_and_namedstyle():
# Test dict after style name where dict modifies the a different parameter.
original_value = 'gray'
other_param = 'text.usetex'
other_value = True
d = {other_param: other_value}
mpl.rcParams[PARAM] = original_value
mpl.rcParams[other_param] = (not other_value)
with temp_style('test', DUMMY_SETTINGS):
with style.context(['test', d]):
assert mpl.rcParams[PARAM] == VALUE
assert mpl.rcParams[other_param] == other_value
assert mpl.rcParams[PARAM] == original_value
assert mpl.rcParams[other_param] == (not other_value)
def test_context_with_badparam():
original_value = 'gray'
other_value = 'blue'
d = OrderedDict([(PARAM, original_value), ('badparam', None)])
with style.context({PARAM: other_value}):
assert mpl.rcParams[PARAM] == other_value
x = style.context([d])
with pytest.raises(KeyError):
with x:
pass
assert mpl.rcParams[PARAM] == other_value
@pytest.mark.parametrize('equiv_styles',
[('mpl20', 'default'),
('mpl15', 'classic')],
ids=['mpl20', 'mpl15'])
def test_alias(equiv_styles):
rc_dicts = []
for sty in equiv_styles:
with style.context(sty):
rc_dicts.append(dict(mpl.rcParams))
rc_base = rc_dicts[0]
for nm, rc in zip(equiv_styles[1:], rc_dicts[1:]):
assert rc_base == rc
def test_xkcd_no_cm():
assert mpl.rcParams["path.sketch"] is None
plt.xkcd()
assert mpl.rcParams["path.sketch"] == (1, 100, 2)
gc.collect()
assert mpl.rcParams["path.sketch"] == (1, 100, 2)
def test_xkcd_cm():
assert mpl.rcParams["path.sketch"] is None
with plt.xkcd():
assert mpl.rcParams["path.sketch"] == (1, 100, 2)
assert mpl.rcParams["path.sketch"] is None
@@ -0,0 +1,160 @@
import itertools
import warnings
import numpy
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
import pytest
def check_shared(axs, x_shared, y_shared):
"""
x_shared and y_shared are n x n boolean matrices; entry (i, j) indicates
whether the x (or y) axes of subplots i and j should be shared.
"""
for (i1, ax1), (i2, ax2), (i3, (name, shared)) in itertools.product(
enumerate(axs),
enumerate(axs),
enumerate(zip("xy", [x_shared, y_shared]))):
if i2 <= i1:
continue
assert \
(getattr(axs[0], "_shared_{}_axes".format(name)).joined(ax1, ax2)
== shared[i1, i2]), \
"axes %i and %i incorrectly %ssharing %s axis" % (
i1, i2, "not " if shared[i1, i2] else "", name)
def check_visible(axs, x_visible, y_visible):
def tostr(v):
return "invisible" if v else "visible"
for ax, vx, vy in zip(axs, x_visible, y_visible):
for l in ax.get_xticklabels() + [ax.get_xaxis().offsetText]:
assert l.get_visible() == vx, \
"X axis was incorrectly %s" % (tostr(vx))
for l in ax.get_yticklabels() + [ax.get_yaxis().offsetText]:
assert l.get_visible() == vy, \
"Y axis was incorrectly %s" % (tostr(vy))
def test_shared():
rdim = (4, 4, 2)
share = {
'all': numpy.ones(rdim[:2], dtype=bool),
'none': numpy.zeros(rdim[:2], dtype=bool),
'row': numpy.array([
[False, True, False, False],
[True, False, False, False],
[False, False, False, True],
[False, False, True, False]]),
'col': numpy.array([
[False, False, True, False],
[False, False, False, True],
[True, False, False, False],
[False, True, False, False]]),
}
visible = {
'x': {
'all': [False, False, True, True],
'col': [False, False, True, True],
'row': [True] * 4,
'none': [True] * 4,
False: [True] * 4,
True: [False, False, True, True],
},
'y': {
'all': [True, False, True, False],
'col': [True] * 4,
'row': [True, False, True, False],
'none': [True] * 4,
False: [True] * 4,
True: [True, False, True, False],
},
}
share[False] = share['none']
share[True] = share['all']
# test default
f, ((a1, a2), (a3, a4)) = plt.subplots(2, 2)
axs = [a1, a2, a3, a4]
check_shared(axs, share['none'], share['none'])
plt.close(f)
# test all option combinations
ops = [False, True, 'all', 'none', 'row', 'col']
for xo in ops:
for yo in ops:
f, ((a1, a2), (a3, a4)) = plt.subplots(2, 2, sharex=xo, sharey=yo)
axs = [a1, a2, a3, a4]
check_shared(axs, share[xo], share[yo])
check_visible(axs, visible['x'][xo], visible['y'][yo])
plt.close(f)
# test label_outer
f, ((a1, a2), (a3, a4)) = plt.subplots(2, 2, sharex=True, sharey=True)
axs = [a1, a2, a3, a4]
for ax in axs:
ax.label_outer()
check_visible(axs, [False, False, True, True], [True, False, True, False])
def test_shared_and_moved():
# test if sharey is on, but then tick_left is called that labels don't
# re-appear. Seaborn does this just to be sure yaxis is on left...
f, (a1, a2) = plt.subplots(1, 2, sharey=True)
check_visible([a2], [True], [False])
a2.yaxis.tick_left()
check_visible([a2], [True], [False])
f, (a1, a2) = plt.subplots(2, 1, sharex=True)
check_visible([a1], [False], [True])
a2.xaxis.tick_bottom()
check_visible([a1], [False], [True])
def test_exceptions():
# TODO should this test more options?
with pytest.raises(ValueError):
plt.subplots(2, 2, sharex='blah')
with pytest.raises(ValueError):
plt.subplots(2, 2, sharey='blah')
# We filter warnings in this test which are genuine since
# the point of this test is to ensure that this raises.
with warnings.catch_warnings():
warnings.filterwarnings('ignore',
message='.*sharex argument to subplots',
category=UserWarning)
with pytest.raises(ValueError):
plt.subplots(2, 2, -1)
with pytest.raises(ValueError):
plt.subplots(2, 2, 0)
with pytest.raises(ValueError):
plt.subplots(2, 2, 5)
@image_comparison(baseline_images=['subplots_offset_text'], remove_text=False)
def test_subplots_offsettext():
x = numpy.arange(0, 1e10, 1e9)
y = numpy.arange(0, 100, 10)+1e4
fig, axes = plt.subplots(2, 2, sharex='col', sharey='all')
axes[0, 0].plot(x, x)
axes[1, 0].plot(x, x)
axes[0, 1].plot(y, x)
axes[1, 1].plot(y, x)
def test_get_gridspec():
# ahem, pretty trivial, but...
fig, ax = plt.subplots()
assert ax.get_subplotspec().get_gridspec() == ax.get_gridspec()
def test_dont_mutate_kwargs():
subplot_kw = {'sharex': 'all'}
gridspec_kw = {'width_ratios': [1, 2]}
fig, ax = plt.subplots(1, 2, subplot_kw=subplot_kw,
gridspec_kw=gridspec_kw)
assert subplot_kw == {'sharex': 'all'}
assert gridspec_kw == {'width_ratios': [1, 2]}
@@ -0,0 +1,197 @@
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.testing.decorators import image_comparison
from matplotlib.table import CustomCell, Table
from matplotlib.path import Path
def test_non_square():
# Check that creating a non-square table works
cellcolors = ['b', 'r']
plt.table(cellColours=cellcolors)
@image_comparison(baseline_images=['table_zorder'],
extensions=['png'],
remove_text=True)
def test_zorder():
data = [[66386, 174296],
[58230, 381139]]
colLabels = ('Freeze', 'Wind')
rowLabels = ['%d year' % x for x in (100, 50)]
cellText = []
yoff = np.zeros(len(colLabels))
for row in reversed(data):
yoff += row
cellText.append(['%1.1f' % (x/1000.0) for x in yoff])
t = np.linspace(0, 2*np.pi, 100)
plt.plot(t, np.cos(t), lw=4, zorder=2)
plt.table(cellText=cellText,
rowLabels=rowLabels,
colLabels=colLabels,
loc='center',
zorder=-2,
)
plt.table(cellText=cellText,
rowLabels=rowLabels,
colLabels=colLabels,
loc='upper center',
zorder=4,
)
plt.yticks([])
@image_comparison(baseline_images=['table_labels'],
extensions=['png'])
def test_label_colours():
dim = 3
c = np.linspace(0, 1, dim)
colours = plt.cm.RdYlGn(c)
cellText = [['1'] * dim] * dim
fig = plt.figure()
ax1 = fig.add_subplot(4, 1, 1)
ax1.axis('off')
ax1.table(cellText=cellText,
rowColours=colours,
loc='best')
ax2 = fig.add_subplot(4, 1, 2)
ax2.axis('off')
ax2.table(cellText=cellText,
rowColours=colours,
rowLabels=['Header'] * dim,
loc='best')
ax3 = fig.add_subplot(4, 1, 3)
ax3.axis('off')
ax3.table(cellText=cellText,
colColours=colours,
loc='best')
ax4 = fig.add_subplot(4, 1, 4)
ax4.axis('off')
ax4.table(cellText=cellText,
colColours=colours,
colLabels=['Header'] * dim,
loc='best')
@image_comparison(baseline_images=['table_cell_manipulation'],
extensions=['png'], remove_text=True)
def test_diff_cell_table():
cells = ('horizontal', 'vertical', 'open', 'closed', 'T', 'R', 'B', 'L')
cellText = [['1'] * len(cells)] * 2
colWidths = [0.1] * len(cells)
_, axes = plt.subplots(nrows=len(cells), figsize=(4, len(cells)+1))
for ax, cell in zip(axes, cells):
ax.table(
colWidths=colWidths,
cellText=cellText,
loc='center',
edges=cell,
)
ax.axis('off')
plt.tight_layout()
def test_customcell():
types = ('horizontal', 'vertical', 'open', 'closed', 'T', 'R', 'B', 'L')
codes = (
(Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO, Path.MOVETO),
(Path.MOVETO, Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO),
(Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.MOVETO),
(Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY),
(Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.LINETO, Path.MOVETO),
(Path.MOVETO, Path.MOVETO, Path.LINETO, Path.MOVETO, Path.MOVETO),
(Path.MOVETO, Path.LINETO, Path.MOVETO, Path.MOVETO, Path.MOVETO),
(Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.MOVETO, Path.LINETO),
)
for t, c in zip(types, codes):
cell = CustomCell((0, 0), visible_edges=t, width=1, height=1)
code = tuple(s for _, s in cell.get_path().iter_segments())
assert c == code
@image_comparison(baseline_images=['table_auto_column'],
extensions=['png'])
def test_auto_column():
fig = plt.figure()
# iterable list input
ax1 = fig.add_subplot(4, 1, 1)
ax1.axis('off')
tb1 = ax1.table(cellText=[['Fit Text', 2],
['very long long text, Longer text than default', 1]],
rowLabels=["A", "B"],
colLabels=["Col1", "Col2"],
loc="center")
tb1.auto_set_font_size(False)
tb1.set_fontsize(12)
tb1.auto_set_column_width([-1, 0, 1])
# iterable tuple input
ax2 = fig.add_subplot(4, 1, 2)
ax2.axis('off')
tb2 = ax2.table(cellText=[['Fit Text', 2],
['very long long text, Longer text than default', 1]],
rowLabels=["A", "B"],
colLabels=["Col1", "Col2"],
loc="center")
tb2.auto_set_font_size(False)
tb2.set_fontsize(12)
tb2.auto_set_column_width((-1, 0, 1))
#3 single inputs
ax3 = fig.add_subplot(4, 1, 3)
ax3.axis('off')
tb3 = ax3.table(cellText=[['Fit Text', 2],
['very long long text, Longer text than default', 1]],
rowLabels=["A", "B"],
colLabels=["Col1", "Col2"],
loc="center")
tb3.auto_set_font_size(False)
tb3.set_fontsize(12)
tb3.auto_set_column_width(-1)
tb3.auto_set_column_width(0)
tb3.auto_set_column_width(1)
#4 non integer interable input
ax4 = fig.add_subplot(4, 1, 4)
ax4.axis('off')
tb4 = ax4.table(cellText=[['Fit Text', 2],
['very long long text, Longer text than default', 1]],
rowLabels=["A", "B"],
colLabels=["Col1", "Col2"],
loc="center")
tb4.auto_set_font_size(False)
tb4.set_fontsize(12)
tb4.auto_set_column_width("-101")
def test_table_cells():
fig, ax = plt.subplots()
table = Table(ax)
cell = table.add_cell(1, 2, 1, 1)
assert isinstance(cell, CustomCell)
assert cell is table[1, 2]
cell2 = CustomCell((0, 0), 1, 2, visible_edges=None)
table[2, 1] = cell2
assert table[2, 1] is cell2
# make sure gettitem support has not broken
# properties and setp
table.properties()
plt.setp(table)
@@ -0,0 +1,18 @@
import matplotlib.pyplot as plt
from matplotlib.texmanager import TexManager
def test_fontconfig_preamble():
"""
Test that the preamble is included in _fontconfig
"""
plt.rcParams['text.usetex'] = True
tm1 = TexManager()
font_config1 = tm1.get_font_config()
plt.rcParams['text.latex.preamble'] = ['\\usepackage{txfonts}']
tm2 = TexManager()
font_config2 = tm2.get_font_config()
assert font_config1 != font_config2
@@ -0,0 +1,512 @@
import io
import warnings
import numpy as np
from numpy.testing import assert_almost_equal
import pytest
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
needs_usetex = pytest.mark.skipif(
not matplotlib.checkdep_usetex(True),
reason="This test needs a TeX installation")
@image_comparison(baseline_images=['font_styles'])
def test_font_styles():
from matplotlib import _get_data_path
data_path = _get_data_path()
def find_matplotlib_font(**kw):
prop = FontProperties(**kw)
path = findfont(prop, directory=data_path)
return FontProperties(fname=path)
from matplotlib.font_manager import FontProperties, findfont
warnings.filterwarnings(
'ignore',
r"findfont: Font family \[u?'Foo'\] not found. Falling back to .",
UserWarning,
module='matplotlib.font_manager')
plt.figure()
ax = plt.subplot(1, 1, 1)
normalFont = find_matplotlib_font(
family="sans-serif",
style="normal",
variant="normal",
size=14)
ax.annotate(
"Normal Font",
(0.1, 0.1),
xycoords='axes fraction',
fontproperties=normalFont)
boldFont = find_matplotlib_font(
family="Foo",
style="normal",
variant="normal",
weight="bold",
stretch=500,
size=14)
ax.annotate(
"Bold Font",
(0.1, 0.2),
xycoords='axes fraction',
fontproperties=boldFont)
boldItemFont = find_matplotlib_font(
family="sans serif",
style="italic",
variant="normal",
weight=750,
stretch=500,
size=14)
ax.annotate(
"Bold Italic Font",
(0.1, 0.3),
xycoords='axes fraction',
fontproperties=boldItemFont)
lightFont = find_matplotlib_font(
family="sans-serif",
style="normal",
variant="normal",
weight=200,
stretch=500,
size=14)
ax.annotate(
"Light Font",
(0.1, 0.4),
xycoords='axes fraction',
fontproperties=lightFont)
condensedFont = find_matplotlib_font(
family="sans-serif",
style="normal",
variant="normal",
weight=500,
stretch=100,
size=14)
ax.annotate(
"Condensed Font",
(0.1, 0.5),
xycoords='axes fraction',
fontproperties=condensedFont)
ax.set_xticks([])
ax.set_yticks([])
@image_comparison(baseline_images=['multiline'])
def test_multiline():
plt.figure()
ax = plt.subplot(1, 1, 1)
ax.set_title("multiline\ntext alignment")
plt.text(
0.2, 0.5, "TpTpTp\n$M$\nTpTpTp", size=20, ha="center", va="top")
plt.text(
0.5, 0.5, "TpTpTp\n$M^{M^{M^{M}}}$\nTpTpTp", size=20,
ha="center", va="top")
plt.text(
0.8, 0.5, "TpTpTp\n$M_{q_{q_{q}}}$\nTpTpTp", size=20,
ha="center", va="top")
plt.xlim(0, 1)
plt.ylim(0, 0.8)
ax.set_xticks([])
ax.set_yticks([])
@image_comparison(baseline_images=['antialiased'], extensions=['png'])
def test_antialiasing():
matplotlib.rcParams['text.antialiased'] = True
fig = plt.figure(figsize=(5.25, 0.75))
fig.text(0.5, 0.75, "antialiased", horizontalalignment='center',
verticalalignment='center')
fig.text(0.5, 0.25, r"$\sqrt{x}$", horizontalalignment='center',
verticalalignment='center')
# NOTE: We don't need to restore the rcParams here, because the
# test cleanup will do it for us. In fact, if we do it here, it
# will turn antialiasing back off before the images are actually
# rendered.
def test_afm_kerning():
from matplotlib.afm import AFM
from matplotlib.font_manager import findfont
fn = findfont("Helvetica", fontext="afm")
with open(fn, 'rb') as fh:
afm = AFM(fh)
assert afm.string_width_height('VAVAVAVAVAVA') == (7174.0, 718)
@image_comparison(baseline_images=['text_contains'], extensions=['png'])
def test_contains():
import matplotlib.backend_bases as mbackend
fig = plt.figure()
ax = plt.axes()
mevent = mbackend.MouseEvent(
'button_press_event', fig.canvas, 0.5, 0.5, 1, None)
xs = np.linspace(0.25, 0.75, 30)
ys = np.linspace(0.25, 0.75, 30)
xs, ys = np.meshgrid(xs, ys)
txt = plt.text(
0.48, 0.52, 'hello world', ha='center', fontsize=30, rotation=30)
# uncomment to draw the text's bounding box
# txt.set_bbox(dict(edgecolor='black', facecolor='none'))
# draw the text. This is important, as the contains method can only work
# when a renderer exists.
fig.canvas.draw()
for x, y in zip(xs.flat, ys.flat):
mevent.x, mevent.y = plt.gca().transAxes.transform_point([x, y])
contains, _ = txt.contains(mevent)
color = 'yellow' if contains else 'red'
# capture the viewLim, plot a point, and reset the viewLim
vl = ax.viewLim.frozen()
ax.plot(x, y, 'o', color=color)
ax.viewLim.set(vl)
@image_comparison(baseline_images=['titles'])
def test_titles():
# left and right side titles
plt.figure()
ax = plt.subplot(1, 1, 1)
ax.set_title("left title", loc="left")
ax.set_title("right title", loc="right")
ax.set_xticks([])
ax.set_yticks([])
@image_comparison(baseline_images=['text_alignment'])
def test_alignment():
plt.figure()
ax = plt.subplot(1, 1, 1)
x = 0.1
for rotation in (0, 30):
for alignment in ('top', 'bottom', 'baseline', 'center'):
ax.text(
x, 0.5, alignment + " Tj", va=alignment, rotation=rotation,
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
ax.text(
x, 1.0, r'$\sum_{i=0}^{j}$', va=alignment, rotation=rotation)
x += 0.1
ax.plot([0, 1], [0.5, 0.5])
ax.plot([0, 1], [1.0, 1.0])
ax.set_xlim([0, 1])
ax.set_ylim([0, 1.5])
ax.set_xticks([])
ax.set_yticks([])
@image_comparison(baseline_images=['axes_titles'], extensions=['png'])
def test_axes_titles():
# Related to issue #3327
plt.figure()
ax = plt.subplot(1, 1, 1)
ax.set_title('center', loc='center', fontsize=20, fontweight=700)
ax.set_title('left', loc='left', fontsize=12, fontweight=400)
ax.set_title('right', loc='right', fontsize=12, fontweight=400)
def test_set_position():
fig, ax = plt.subplots()
# test set_position
ann = ax.annotate(
'test', (0, 0), xytext=(0, 0), textcoords='figure pixels')
fig.canvas.draw()
init_pos = ann.get_window_extent(fig.canvas.renderer)
shift_val = 15
ann.set_position((shift_val, shift_val))
fig.canvas.draw()
post_pos = ann.get_window_extent(fig.canvas.renderer)
for a, b in zip(init_pos.min, post_pos.min):
assert a + shift_val == b
# test xyann
ann = ax.annotate(
'test', (0, 0), xytext=(0, 0), textcoords='figure pixels')
fig.canvas.draw()
init_pos = ann.get_window_extent(fig.canvas.renderer)
shift_val = 15
ann.xyann = (shift_val, shift_val)
fig.canvas.draw()
post_pos = ann.get_window_extent(fig.canvas.renderer)
for a, b in zip(init_pos.min, post_pos.min):
assert a + shift_val == b
def test_get_rotation_string():
from matplotlib import text
assert text.get_rotation('horizontal') == 0.
assert text.get_rotation('vertical') == 90.
assert text.get_rotation('15.') == 15.
def test_get_rotation_float():
from matplotlib import text
for i in [15., 16.70, 77.4]:
assert text.get_rotation(i) == i
def test_get_rotation_int():
from matplotlib import text
for i in [67, 16, 41]:
assert text.get_rotation(i) == float(i)
def test_get_rotation_raises():
from matplotlib import text
with pytest.raises(ValueError):
text.get_rotation('hozirontal')
def test_get_rotation_none():
from matplotlib import text
assert text.get_rotation(None) == 0.0
def test_get_rotation_mod360():
from matplotlib import text
for i, j in zip([360., 377., 720+177.2], [0., 17., 177.2]):
assert_almost_equal(text.get_rotation(i), j)
@image_comparison(baseline_images=['text_bboxclip'])
def test_bbox_clipping():
plt.text(0.9, 0.2, 'Is bbox clipped?', backgroundcolor='r', clip_on=True)
t = plt.text(0.9, 0.5, 'Is fancy bbox clipped?', clip_on=True)
t.set_bbox({"boxstyle": "round, pad=0.1"})
@image_comparison(baseline_images=['annotation_negative_ax_coords'],
extensions=['png'])
def test_annotation_negative_ax_coords():
fig, ax = plt.subplots()
ax.annotate('+ pts',
xytext=[30, 20], textcoords='axes points',
xy=[30, 20], xycoords='axes points', fontsize=32)
ax.annotate('- pts',
xytext=[30, -20], textcoords='axes points',
xy=[30, -20], xycoords='axes points', fontsize=32,
va='top')
ax.annotate('+ frac',
xytext=[0.75, 0.05], textcoords='axes fraction',
xy=[0.75, 0.05], xycoords='axes fraction', fontsize=32)
ax.annotate('- frac',
xytext=[0.75, -0.05], textcoords='axes fraction',
xy=[0.75, -0.05], xycoords='axes fraction', fontsize=32,
va='top')
ax.annotate('+ pixels',
xytext=[160, 25], textcoords='axes pixels',
xy=[160, 25], xycoords='axes pixels', fontsize=32)
ax.annotate('- pixels',
xytext=[160, -25], textcoords='axes pixels',
xy=[160, -25], xycoords='axes pixels', fontsize=32,
va='top')
@image_comparison(baseline_images=['annotation_negative_fig_coords'],
extensions=['png'])
def test_annotation_negative_fig_coords():
fig, ax = plt.subplots()
ax.annotate('+ pts',
xytext=[10, 120], textcoords='figure points',
xy=[10, 120], xycoords='figure points', fontsize=32)
ax.annotate('- pts',
xytext=[-10, 180], textcoords='figure points',
xy=[-10, 180], xycoords='figure points', fontsize=32,
va='top')
ax.annotate('+ frac',
xytext=[0.05, 0.55], textcoords='figure fraction',
xy=[0.05, 0.55], xycoords='figure fraction', fontsize=32)
ax.annotate('- frac',
xytext=[-0.05, 0.5], textcoords='figure fraction',
xy=[-0.05, 0.5], xycoords='figure fraction', fontsize=32,
va='top')
ax.annotate('+ pixels',
xytext=[50, 50], textcoords='figure pixels',
xy=[50, 50], xycoords='figure pixels', fontsize=32)
ax.annotate('- pixels',
xytext=[-50, 100], textcoords='figure pixels',
xy=[-50, 100], xycoords='figure pixels', fontsize=32,
va='top')
def test_text_stale():
fig, (ax1, ax2) = plt.subplots(1, 2)
plt.draw_all()
assert not ax1.stale
assert not ax2.stale
assert not fig.stale
txt1 = ax1.text(.5, .5, 'aardvark')
assert ax1.stale
assert txt1.stale
assert fig.stale
ann1 = ax2.annotate('aardvark', xy=[.5, .5])
assert ax2.stale
assert ann1.stale
assert fig.stale
plt.draw_all()
assert not ax1.stale
assert not ax2.stale
assert not fig.stale
@image_comparison(baseline_images=['agg_text_clip'],
extensions=['png'])
def test_agg_text_clip():
np.random.seed(1)
fig, (ax1, ax2) = plt.subplots(2)
for x, y in np.random.rand(10, 2):
ax1.text(x, y, "foo", clip_on=True)
ax2.text(x, y, "foo")
def test_text_size_binding():
from matplotlib.font_manager import FontProperties
matplotlib.rcParams['font.size'] = 10
fp = FontProperties(size='large')
sz1 = fp.get_size_in_points()
matplotlib.rcParams['font.size'] = 100
assert sz1 == fp.get_size_in_points()
@image_comparison(baseline_images=['font_scaling'],
extensions=['pdf'])
def test_font_scaling():
matplotlib.rcParams['pdf.fonttype'] = 42
fig, ax = plt.subplots(figsize=(6.4, 12.4))
ax.xaxis.set_major_locator(plt.NullLocator())
ax.yaxis.set_major_locator(plt.NullLocator())
ax.set_ylim(-10, 600)
for i, fs in enumerate(range(4, 43, 2)):
ax.text(0.1, i*30, "{fs} pt font size".format(fs=fs), fontsize=fs)
@pytest.mark.parametrize('spacing1, spacing2', [(0.4, 2), (2, 0.4), (2, 2)])
def test_two_2line_texts(spacing1, spacing2):
text_string = 'line1\nline2'
fig = plt.figure()
renderer = fig.canvas.get_renderer()
text1 = plt.text(0.25, 0.5, text_string, linespacing=spacing1)
text2 = plt.text(0.25, 0.5, text_string, linespacing=spacing2)
fig.canvas.draw()
box1 = text1.get_window_extent(renderer=renderer)
box2 = text2.get_window_extent(renderer=renderer)
# line spacing only affects height
assert box1.width == box2.width
if spacing1 == spacing2:
assert box1.height == box2.height
else:
assert box1.height != box2.height
def test_nonfinite_pos():
fig, ax = plt.subplots()
ax.text(0, np.nan, 'nan')
ax.text(np.inf, 0, 'inf')
fig.canvas.draw()
def test_hinting_factor_backends():
plt.rcParams['text.hinting_factor'] = 1
fig = plt.figure()
t = fig.text(0.5, 0.5, 'some text')
fig.savefig(io.BytesIO(), format='svg')
expected = t.get_window_extent().intervalx
fig.savefig(io.BytesIO(), format='png')
# Backends should apply hinting_factor consistently (within 10%).
np.testing.assert_allclose(t.get_window_extent().intervalx, expected,
rtol=0.1)
@needs_usetex
def test_single_artist_usetex():
# Check that a single artist marked with usetex does not get passed through
# the mathtext parser at all (for the Agg backend) (the mathtext parser
# currently fails to parse \frac12, requiring \frac{1}{2} instead).
fig, ax = plt.subplots()
ax.text(.5, .5, r"$\frac12$", usetex=True)
fig.canvas.draw()
@image_comparison(baseline_images=['text_as_path_opacity'],
extensions=['svg'])
def test_text_as_path_opacity():
plt.figure()
plt.gca().set_axis_off()
plt.text(0.25, 0.25, 'c', color=(0, 0, 0, 0.5))
plt.text(0.25, 0.5, 'a', alpha=0.5)
plt.text(0.25, 0.75, 'x', alpha=0.5, color=(0, 0, 0, 1))
@image_comparison(baseline_images=['text_as_text_opacity'],
extensions=['svg'])
def test_text_as_text_opacity():
matplotlib.rcParams['svg.fonttype'] = 'none'
plt.figure()
plt.gca().set_axis_off()
plt.text(0.25, 0.25, '50% using `color`', color=(0, 0, 0, 0.5))
plt.text(0.25, 0.5, '50% using `alpha`', alpha=0.5)
plt.text(0.25, 0.75, '50% using `alpha` and 100% `color`', alpha=0.5,
color=(0, 0, 0, 1))
def test_text_repr():
# smoketest to make sure text repr doesn't error for category
plt.plot(['A', 'B'], [1, 2])
txt = plt.text(['A'], 0.5, 'Boo')
print(txt)
def test_annotation_update():
fig, ax = plt.subplots(1, 1)
an = ax.annotate('annotation', xy=(0.5, 0.5))
extent1 = an.get_window_extent(fig.canvas.get_renderer())
fig.tight_layout()
extent2 = an.get_window_extent(fig.canvas.get_renderer())
assert not np.allclose(extent1.get_points(), extent2.get_points(),
rtol=1e-6)
@@ -0,0 +1,841 @@
import warnings
import numpy as np
from numpy.testing import assert_almost_equal
import pytest
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
class TestMaxNLocator(object):
basic_data = [
(20, 100, np.array([20., 40., 60., 80., 100.])),
(0.001, 0.0001, np.array([0., 0.0002, 0.0004, 0.0006, 0.0008, 0.001])),
(-1e15, 1e15, np.array([-1.0e+15, -5.0e+14, 0e+00, 5e+14, 1.0e+15])),
(0, 0.85e-50, np.arange(6) * 2e-51),
(-0.85e-50, 0, np.arange(-5, 1) * 2e-51),
]
integer_data = [
(-0.1, 1.1, None, np.array([-1, 0, 1, 2])),
(-0.1, 0.95, None, np.array([-0.25, 0, 0.25, 0.5, 0.75, 1.0])),
(1, 55, [1, 1.5, 5, 6, 10], np.array([0, 15, 30, 45, 60])),
]
@pytest.mark.parametrize('vmin, vmax, expected', basic_data)
def test_basic(self, vmin, vmax, expected):
loc = mticker.MaxNLocator(nbins=5)
assert_almost_equal(loc.tick_values(vmin, vmax), expected)
@pytest.mark.parametrize('vmin, vmax, steps, expected', integer_data)
def test_integer(self, vmin, vmax, steps, expected):
loc = mticker.MaxNLocator(nbins=5, integer=True, steps=steps)
assert_almost_equal(loc.tick_values(vmin, vmax), expected)
class TestLinearLocator(object):
def test_basic(self):
loc = mticker.LinearLocator(numticks=3)
test_value = np.array([-0.8, -0.3, 0.2])
assert_almost_equal(loc.tick_values(-0.8, 0.2), test_value)
def test_set_params(self):
"""
Create linear locator with presets={}, numticks=2 and change it to
something else. See if change was successful. Should not exception.
"""
loc = mticker.LinearLocator(numticks=2)
loc.set_params(numticks=8, presets={(0, 1): []})
assert loc.numticks == 8
assert loc.presets == {(0, 1): []}
class TestMultipleLocator(object):
def test_basic(self):
loc = mticker.MultipleLocator(base=3.147)
test_value = np.array([-9.441, -6.294, -3.147, 0., 3.147, 6.294,
9.441, 12.588])
assert_almost_equal(loc.tick_values(-7, 10), test_value)
def test_view_limits(self):
"""
Test basic behavior of view limits.
"""
with matplotlib.rc_context({'axes.autolimit_mode': 'data'}):
loc = mticker.MultipleLocator(base=3.147)
assert_almost_equal(loc.view_limits(-5, 5), (-5, 5))
def test_view_limits_round_numbers(self):
"""
Test that everything works properly with 'round_numbers' for auto
limit.
"""
with matplotlib.rc_context({'axes.autolimit_mode': 'round_numbers'}):
loc = mticker.MultipleLocator(base=3.147)
assert_almost_equal(loc.view_limits(-4, 4), (-6.294, 6.294))
def test_set_params(self):
"""
Create multiple locator with 0.7 base, and change it to something else.
See if change was successful.
"""
mult = mticker.MultipleLocator(base=0.7)
mult.set_params(base=1.7)
assert mult._edge.step == 1.7
class TestAutoMinorLocator(object):
def test_basic(self):
fig, ax = plt.subplots()
ax.set_xlim(0, 1.39)
ax.minorticks_on()
test_value = np.array([0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45,
0.5, 0.55, 0.65, 0.7, 0.75, 0.85, 0.9,
0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35])
assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value)
# NB: the following values are assuming that *xlim* is [0, 5]
params = [
(0, 0), # no major tick => no minor tick either
(1, 0), # a single major tick => no minor tick
(2, 4), # 1 "nice" major step => 1*5 minor **divisions**
(3, 6) # 2 "not nice" major steps => 2*4 minor **divisions**
]
@pytest.mark.parametrize('nb_majorticks, expected_nb_minorticks', params)
def test_low_number_of_majorticks(
self, nb_majorticks, expected_nb_minorticks):
# This test is related to issue #8804
fig, ax = plt.subplots()
xlims = (0, 5) # easier to test the different code paths
ax.set_xlim(*xlims)
ax.set_xticks(np.linspace(xlims[0], xlims[1], nb_majorticks))
ax.minorticks_on()
ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
assert len(ax.xaxis.get_minorticklocs()) == expected_nb_minorticks
limits = [(0, 1.39), (0, 0.139),
(0, 0.11e-19), (0, 0.112e-12),
(-2.0e-07, -3.3e-08), (1.20e-06, 1.42e-06),
(-1.34e-06, -1.44e-06), (-8.76e-07, -1.51e-06)]
reference = [
[0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45, 0.5, 0.55, 0.65, 0.7,
0.75, 0.85, 0.9, 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35],
[0.005, 0.01, 0.015, 0.025, 0.03, 0.035, 0.045, 0.05, 0.055, 0.065,
0.07, 0.075, 0.085, 0.09, 0.095, 0.105, 0.11, 0.115, 0.125, 0.13,
0.135],
[5.00e-22, 1.00e-21, 1.50e-21, 2.50e-21, 3.00e-21, 3.50e-21, 4.50e-21,
5.00e-21, 5.50e-21, 6.50e-21, 7.00e-21, 7.50e-21, 8.50e-21, 9.00e-21,
9.50e-21, 1.05e-20, 1.10e-20],
[5.00e-15, 1.00e-14, 1.50e-14, 2.50e-14, 3.00e-14, 3.50e-14, 4.50e-14,
5.00e-14, 5.50e-14, 6.50e-14, 7.00e-14, 7.50e-14, 8.50e-14, 9.00e-14,
9.50e-14, 1.05e-13, 1.10e-13],
[-1.95e-07, -1.90e-07, -1.85e-07, -1.75e-07, -1.70e-07, -1.65e-07,
-1.55e-07, -1.50e-07, -1.45e-07, -1.35e-07, -1.30e-07, -1.25e-07,
-1.15e-07, -1.10e-07, -1.05e-07, -9.50e-08, -9.00e-08, -8.50e-08,
-7.50e-08, -7.00e-08, -6.50e-08, -5.50e-08, -5.00e-08, -4.50e-08,
-3.50e-08],
[1.21e-06, 1.22e-06, 1.23e-06, 1.24e-06, 1.26e-06, 1.27e-06, 1.28e-06,
1.29e-06, 1.31e-06, 1.32e-06, 1.33e-06, 1.34e-06, 1.36e-06, 1.37e-06,
1.38e-06, 1.39e-06, 1.41e-06, 1.42e-06],
[-1.435e-06, -1.430e-06, -1.425e-06, -1.415e-06, -1.410e-06,
-1.405e-06, -1.395e-06, -1.390e-06, -1.385e-06, -1.375e-06,
-1.370e-06, -1.365e-06, -1.355e-06, -1.350e-06, -1.345e-06],
[-1.48e-06, -1.46e-06, -1.44e-06, -1.42e-06, -1.38e-06, -1.36e-06,
-1.34e-06, -1.32e-06, -1.28e-06, -1.26e-06, -1.24e-06, -1.22e-06,
-1.18e-06, -1.16e-06, -1.14e-06, -1.12e-06, -1.08e-06, -1.06e-06,
-1.04e-06, -1.02e-06, -9.80e-07, -9.60e-07, -9.40e-07, -9.20e-07,
-8.80e-07]]
additional_data = list(zip(limits, reference))
@pytest.mark.parametrize('lim, ref', additional_data)
def test_additional(self, lim, ref):
fig, ax = plt.subplots()
ax.minorticks_on()
ax.grid(True, 'minor', 'y', linewidth=1)
ax.grid(True, 'major', color='k', linewidth=1)
ax.set_ylim(lim)
assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref)
class TestLogLocator(object):
def test_basic(self):
loc = mticker.LogLocator(numticks=5)
with pytest.raises(ValueError):
loc.tick_values(0, 1000)
test_value = np.array([1.00000000e-05, 1.00000000e-03, 1.00000000e-01,
1.00000000e+01, 1.00000000e+03, 1.00000000e+05,
1.00000000e+07, 1.000000000e+09])
assert_almost_equal(loc.tick_values(0.001, 1.1e5), test_value)
loc = mticker.LogLocator(base=2)
test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.])
assert_almost_equal(loc.tick_values(1, 100), test_value)
def test_set_params(self):
"""
Create log locator with default value, base=10.0, subs=[1.0],
numdecs=4, numticks=15 and change it to something else.
See if change was successful. Should not raise exception.
"""
loc = mticker.LogLocator()
loc.set_params(numticks=7, numdecs=8, subs=[2.0], base=4)
assert loc.numticks == 7
assert loc.numdecs == 8
assert loc._base == 4
assert list(loc._subs) == [2.0]
class TestNullLocator(object):
def test_set_params(self):
"""
Create null locator, and attempt to call set_params() on it.
Should not exception, and should raise a warning.
"""
loc = mticker.NullLocator()
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
loc.set_params()
assert len(w) == 1
class TestLogitLocator(object):
def test_set_params(self):
"""
Create logit locator with default minor=False, and change it to
something else. See if change was successful. Should not exception.
"""
loc = mticker.LogitLocator() # Defaults to false.
loc.set_params(minor=True)
assert loc.minor
class TestFixedLocator(object):
def test_set_params(self):
"""
Create fixed locator with 5 nbins, and change it to something else.
See if change was successful.
Should not exception.
"""
fixed = mticker.FixedLocator(range(0, 24), nbins=5)
fixed.set_params(nbins=7)
assert fixed.nbins == 7
class TestIndexLocator(object):
def test_set_params(self):
"""
Create index locator with 3 base, 4 offset. and change it to something
else. See if change was successful.
Should not exception.
"""
index = mticker.IndexLocator(base=3, offset=4)
index.set_params(base=7, offset=7)
assert index._base == 7
assert index.offset == 7
class TestSymmetricalLogLocator(object):
def test_set_params(self):
"""
Create symmetrical log locator with default subs =[1.0] numticks = 15,
and change it to something else.
See if change was successful.
Should not exception.
"""
sym = mticker.SymmetricalLogLocator(base=10, linthresh=1)
sym.set_params(subs=[2.0], numticks=8)
assert sym._subs == [2.0]
assert sym.numticks == 8
class TestScalarFormatter(object):
offset_data = [
(123, 189, 0),
(-189, -123, 0),
(12341, 12349, 12340),
(-12349, -12341, -12340),
(99999.5, 100010.5, 100000),
(-100010.5, -99999.5, -100000),
(99990.5, 100000.5, 100000),
(-100000.5, -99990.5, -100000),
(1233999, 1234001, 1234000),
(-1234001, -1233999, -1234000),
(1, 1, 1),
(123, 123, 120),
# Test cases courtesy of @WeatherGod
(.4538, .4578, .45),
(3789.12, 3783.1, 3780),
(45124.3, 45831.75, 45000),
(0.000721, 0.0007243, 0.00072),
(12592.82, 12591.43, 12590),
(9., 12., 0),
(900., 1200., 0),
(1900., 1200., 0),
(0.99, 1.01, 1),
(9.99, 10.01, 10),
(99.99, 100.01, 100),
(5.99, 6.01, 6),
(15.99, 16.01, 16),
(-0.452, 0.492, 0),
(-0.492, 0.492, 0),
(12331.4, 12350.5, 12300),
(-12335.3, 12335.3, 0),
]
use_offset_data = [True, False]
scilimits_data = [
(False, (0, 0), (10.0, 20.0), 0),
(True, (-2, 2), (-10, 20), 0),
(True, (-2, 2), (-20, 10), 0),
(True, (-2, 2), (-110, 120), 2),
(True, (-2, 2), (-120, 110), 2),
(True, (-2, 2), (-.001, 0.002), -3),
(True, (0, 0), (-1e5, 1e5), 5),
(True, (6, 6), (-1e5, 1e5), 6),
]
@pytest.mark.parametrize('left, right, offset', offset_data)
def test_offset_value(self, left, right, offset):
fig, ax = plt.subplots()
formatter = ax.get_xaxis().get_major_formatter()
with warnings.catch_warnings(record=True) as w:
warnings.filterwarnings('always', 'Attempting to set identical',
UserWarning)
ax.set_xlim(left, right)
assert len(w) == (1 if left == right else 0)
# Update ticks.
next(ax.get_xaxis().iter_ticks())
assert formatter.offset == offset
with warnings.catch_warnings(record=True) as w:
warnings.filterwarnings('always', 'Attempting to set identical',
UserWarning)
ax.set_xlim(right, left)
assert len(w) == (1 if left == right else 0)
# Update ticks.
next(ax.get_xaxis().iter_ticks())
assert formatter.offset == offset
@pytest.mark.parametrize('use_offset', use_offset_data)
def test_use_offset(self, use_offset):
with matplotlib.rc_context({'axes.formatter.useoffset': use_offset}):
tmp_form = mticker.ScalarFormatter()
assert use_offset == tmp_form.get_useOffset()
@pytest.mark.parametrize(
'sci_type, scilimits, lim, orderOfMag', scilimits_data)
def test_scilimits(self, sci_type, scilimits, lim, orderOfMag):
tmp_form = mticker.ScalarFormatter()
tmp_form.set_scientific(sci_type)
tmp_form.set_powerlimits(scilimits)
fig, ax = plt.subplots()
ax.yaxis.set_major_formatter(tmp_form)
ax.set_ylim(*lim)
tmp_form.set_locs(ax.yaxis.get_majorticklocs())
assert orderOfMag == tmp_form.orderOfMagnitude
class FakeAxis(object):
"""Allow Formatter to be called without having a "full" plot set up."""
def __init__(self, vmin=1, vmax=10):
self.vmin = vmin
self.vmax = vmax
def get_view_interval(self):
return self.vmin, self.vmax
class TestLogFormatterExponent(object):
param_data = [
(True, 4, np.arange(-3, 4.0), np.arange(-3, 4.0),
['-3', '-2', '-1', '0', '1', '2', '3']),
# With labelOnlyBase=False, non-integer powers should be nicely
# formatted.
(False, 10, np.array([0.1, 0.00001, np.pi, 0.2, -0.2, -0.00001]),
range(6), ['0.1', '1e-05', '3.14', '0.2', '-0.2', '-1e-05']),
(False, 50, np.array([3, 5, 12, 42], dtype='float'), range(6),
['3', '5', '12', '42']),
]
base_data = [2.0, 5.0, 10.0, np.pi, np.e]
@pytest.mark.parametrize(
'labelOnlyBase, exponent, locs, positions, expected', param_data)
@pytest.mark.parametrize('base', base_data)
def test_basic(self, labelOnlyBase, base, exponent, locs, positions,
expected):
formatter = mticker.LogFormatterExponent(base=base,
labelOnlyBase=labelOnlyBase)
formatter.axis = FakeAxis(1, base**exponent)
vals = base**locs
labels = [formatter(x, pos) for (x, pos) in zip(vals, positions)]
assert labels == expected
def test_blank(self):
# Should be a blank string for non-integer powers if labelOnlyBase=True
formatter = mticker.LogFormatterExponent(base=10, labelOnlyBase=True)
formatter.axis = FakeAxis()
assert formatter(10**0.1) == ''
class TestLogFormatterMathtext():
fmt = mticker.LogFormatterMathtext()
test_data = [
(0, 1, '$\\mathdefault{10^{0}}$'),
(0, 1e-2, '$\\mathdefault{10^{-2}}$'),
(0, 1e2, '$\\mathdefault{10^{2}}$'),
(3, 1, '$\\mathdefault{1}$'),
(3, 1e-2, '$\\mathdefault{0.01}$'),
(3, 1e2, '$\\mathdefault{100}$'),
(3, 1e-3, '$\\mathdefault{10^{-3}}$'),
(3, 1e3, '$\\mathdefault{10^{3}}$'),
]
@pytest.mark.parametrize('min_exponent, value, expected', test_data)
def test_min_exponent(self, min_exponent, value, expected):
with matplotlib.rc_context({'axes.formatter.min_exponent':
min_exponent}):
assert self.fmt(value) == expected
class TestLogFormatterSciNotation(object):
test_data = [
(2, 0.03125, '$\\mathdefault{2^{-5}}$'),
(2, 1, '$\\mathdefault{2^{0}}$'),
(2, 32, '$\\mathdefault{2^{5}}$'),
(2, 0.0375, '$\\mathdefault{1.2\\times2^{-5}}$'),
(2, 1.2, '$\\mathdefault{1.2\\times2^{0}}$'),
(2, 38.4, '$\\mathdefault{1.2\\times2^{5}}$'),
(10, -1, '$\\mathdefault{-10^{0}}$'),
(10, 1e-05, '$\\mathdefault{10^{-5}}$'),
(10, 1, '$\\mathdefault{10^{0}}$'),
(10, 100000, '$\\mathdefault{10^{5}}$'),
(10, 2e-05, '$\\mathdefault{2\\times10^{-5}}$'),
(10, 2, '$\\mathdefault{2\\times10^{0}}$'),
(10, 200000, '$\\mathdefault{2\\times10^{5}}$'),
(10, 5e-05, '$\\mathdefault{5\\times10^{-5}}$'),
(10, 5, '$\\mathdefault{5\\times10^{0}}$'),
(10, 500000, '$\\mathdefault{5\\times10^{5}}$'),
]
@pytest.mark.style('default')
@pytest.mark.parametrize('base, value, expected', test_data)
def test_basic(self, base, value, expected):
formatter = mticker.LogFormatterSciNotation(base=base)
formatter.sublabel = {1, 2, 5, 1.2}
with matplotlib.rc_context({'text.usetex': False}):
assert formatter(value) == expected
class TestLogFormatter(object):
pprint_data = [
(3.141592654e-05, 0.001, '3.142e-5'),
(0.0003141592654, 0.001, '3.142e-4'),
(0.003141592654, 0.001, '3.142e-3'),
(0.03141592654, 0.001, '3.142e-2'),
(0.3141592654, 0.001, '3.142e-1'),
(3.141592654, 0.001, '3.142'),
(31.41592654, 0.001, '3.142e1'),
(314.1592654, 0.001, '3.142e2'),
(3141.592654, 0.001, '3.142e3'),
(31415.92654, 0.001, '3.142e4'),
(314159.2654, 0.001, '3.142e5'),
(1e-05, 0.001, '1e-5'),
(0.0001, 0.001, '1e-4'),
(0.001, 0.001, '1e-3'),
(0.01, 0.001, '1e-2'),
(0.1, 0.001, '1e-1'),
(1, 0.001, '1'),
(10, 0.001, '10'),
(100, 0.001, '100'),
(1000, 0.001, '1000'),
(10000, 0.001, '1e4'),
(100000, 0.001, '1e5'),
(3.141592654e-05, 0.015, '0'),
(0.0003141592654, 0.015, '0'),
(0.003141592654, 0.015, '0.003'),
(0.03141592654, 0.015, '0.031'),
(0.3141592654, 0.015, '0.314'),
(3.141592654, 0.015, '3.142'),
(31.41592654, 0.015, '31.416'),
(314.1592654, 0.015, '314.159'),
(3141.592654, 0.015, '3141.593'),
(31415.92654, 0.015, '31415.927'),
(314159.2654, 0.015, '314159.265'),
(1e-05, 0.015, '0'),
(0.0001, 0.015, '0'),
(0.001, 0.015, '0.001'),
(0.01, 0.015, '0.01'),
(0.1, 0.015, '0.1'),
(1, 0.015, '1'),
(10, 0.015, '10'),
(100, 0.015, '100'),
(1000, 0.015, '1000'),
(10000, 0.015, '10000'),
(100000, 0.015, '100000'),
(3.141592654e-05, 0.5, '0'),
(0.0003141592654, 0.5, '0'),
(0.003141592654, 0.5, '0.003'),
(0.03141592654, 0.5, '0.031'),
(0.3141592654, 0.5, '0.314'),
(3.141592654, 0.5, '3.142'),
(31.41592654, 0.5, '31.416'),
(314.1592654, 0.5, '314.159'),
(3141.592654, 0.5, '3141.593'),
(31415.92654, 0.5, '31415.927'),
(314159.2654, 0.5, '314159.265'),
(1e-05, 0.5, '0'),
(0.0001, 0.5, '0'),
(0.001, 0.5, '0.001'),
(0.01, 0.5, '0.01'),
(0.1, 0.5, '0.1'),
(1, 0.5, '1'),
(10, 0.5, '10'),
(100, 0.5, '100'),
(1000, 0.5, '1000'),
(10000, 0.5, '10000'),
(100000, 0.5, '100000'),
(3.141592654e-05, 5, '0'),
(0.0003141592654, 5, '0'),
(0.003141592654, 5, '0'),
(0.03141592654, 5, '0.03'),
(0.3141592654, 5, '0.31'),
(3.141592654, 5, '3.14'),
(31.41592654, 5, '31.42'),
(314.1592654, 5, '314.16'),
(3141.592654, 5, '3141.59'),
(31415.92654, 5, '31415.93'),
(314159.2654, 5, '314159.27'),
(1e-05, 5, '0'),
(0.0001, 5, '0'),
(0.001, 5, '0'),
(0.01, 5, '0.01'),
(0.1, 5, '0.1'),
(1, 5, '1'),
(10, 5, '10'),
(100, 5, '100'),
(1000, 5, '1000'),
(10000, 5, '10000'),
(100000, 5, '100000'),
(3.141592654e-05, 100, '0'),
(0.0003141592654, 100, '0'),
(0.003141592654, 100, '0'),
(0.03141592654, 100, '0'),
(0.3141592654, 100, '0.3'),
(3.141592654, 100, '3.1'),
(31.41592654, 100, '31.4'),
(314.1592654, 100, '314.2'),
(3141.592654, 100, '3141.6'),
(31415.92654, 100, '31415.9'),
(314159.2654, 100, '314159.3'),
(1e-05, 100, '0'),
(0.0001, 100, '0'),
(0.001, 100, '0'),
(0.01, 100, '0'),
(0.1, 100, '0.1'),
(1, 100, '1'),
(10, 100, '10'),
(100, 100, '100'),
(1000, 100, '1000'),
(10000, 100, '10000'),
(100000, 100, '100000'),
(3.141592654e-05, 1000000.0, '3.1e-5'),
(0.0003141592654, 1000000.0, '3.1e-4'),
(0.003141592654, 1000000.0, '3.1e-3'),
(0.03141592654, 1000000.0, '3.1e-2'),
(0.3141592654, 1000000.0, '3.1e-1'),
(3.141592654, 1000000.0, '3.1'),
(31.41592654, 1000000.0, '3.1e1'),
(314.1592654, 1000000.0, '3.1e2'),
(3141.592654, 1000000.0, '3.1e3'),
(31415.92654, 1000000.0, '3.1e4'),
(314159.2654, 1000000.0, '3.1e5'),
(1e-05, 1000000.0, '1e-5'),
(0.0001, 1000000.0, '1e-4'),
(0.001, 1000000.0, '1e-3'),
(0.01, 1000000.0, '1e-2'),
(0.1, 1000000.0, '1e-1'),
(1, 1000000.0, '1'),
(10, 1000000.0, '10'),
(100, 1000000.0, '100'),
(1000, 1000000.0, '1000'),
(10000, 1000000.0, '1e4'),
(100000, 1000000.0, '1e5'),
]
@pytest.mark.parametrize('value, domain, expected', pprint_data)
def test_pprint(self, value, domain, expected):
fmt = mticker.LogFormatter()
label = fmt.pprint_val(value, domain)
assert label == expected
def _sub_labels(self, axis, subs=()):
"Test whether locator marks subs to be labeled"
fmt = axis.get_minor_formatter()
minor_tlocs = axis.get_minorticklocs()
fmt.set_locs(minor_tlocs)
coefs = minor_tlocs / 10**(np.floor(np.log10(minor_tlocs)))
label_expected = [np.round(c) in subs for c in coefs]
label_test = [fmt(x) != '' for x in minor_tlocs]
assert label_test == label_expected
@pytest.mark.style('default')
def test_sublabel(self):
# test label locator
fig, ax = plt.subplots()
ax.set_xscale('log')
ax.xaxis.set_major_locator(mticker.LogLocator(base=10, subs=[]))
ax.xaxis.set_minor_locator(mticker.LogLocator(base=10,
subs=np.arange(2, 10)))
ax.xaxis.set_major_formatter(mticker.LogFormatter(labelOnlyBase=True))
ax.xaxis.set_minor_formatter(mticker.LogFormatter(labelOnlyBase=False))
# axis range above 3 decades, only bases are labeled
ax.set_xlim(1, 1e4)
fmt = ax.xaxis.get_major_formatter()
fmt.set_locs(ax.xaxis.get_majorticklocs())
show_major_labels = [fmt(x) != ''
for x in ax.xaxis.get_majorticklocs()]
assert np.all(show_major_labels)
self._sub_labels(ax.xaxis, subs=[])
# For the next two, if the numdec threshold in LogFormatter.set_locs
# were 3, then the label sub would be 3 for 2-3 decades and (2,5)
# for 1-2 decades. With a threshold of 1, subs are not labeled.
# axis range at 2 to 3 decades
ax.set_xlim(1, 800)
self._sub_labels(ax.xaxis, subs=[])
# axis range at 1 to 2 decades
ax.set_xlim(1, 80)
self._sub_labels(ax.xaxis, subs=[])
# axis range at 0.4 to 1 decades, label subs 2, 3, 4, 6
ax.set_xlim(1, 8)
self._sub_labels(ax.xaxis, subs=[2, 3, 4, 6])
# axis range at 0 to 0.4 decades, label all
ax.set_xlim(0.5, 0.9)
self._sub_labels(ax.xaxis, subs=np.arange(2, 10, dtype=int))
@pytest.mark.parametrize('val', [1, 10, 100, 1000])
def test_LogFormatter_call(self, val):
# test _num_to_string method used in __call__
temp_lf = mticker.LogFormatter()
temp_lf.axis = FakeAxis()
assert temp_lf(val) == str(val)
class TestFormatStrFormatter(object):
def test_basic(self):
# test % style formatter
tmp_form = mticker.FormatStrFormatter('%05d')
assert '00002' == tmp_form(2)
class TestStrMethodFormatter(object):
test_data = [
('{x:05d}', (2,), '00002'),
('{x:03d}-{pos:02d}', (2, 1), '002-01'),
]
@pytest.mark.parametrize('format, input, expected', test_data)
def test_basic(self, format, input, expected):
fmt = mticker.StrMethodFormatter(format)
assert fmt(*input) == expected
class TestEngFormatter(object):
# (input, expected) where ''expected'' corresponds to the outputs
# respectively returned when (places=None, places=0, places=2)
raw_format_data = [
(-1234.56789, ('-1.23457 k', '-1 k', '-1.23 k')),
(-1.23456789, ('-1.23457', '-1', '-1.23')),
(-0.123456789, ('-123.457 m', '-123 m', '-123.46 m')),
(-0.00123456789, ('-1.23457 m', '-1 m', '-1.23 m')),
(-0.0, ('0', '0', '0.00')),
(-0, ('0', '0', '0.00')),
(0, ('0', '0', '0.00')),
(1.23456789e-6, ('1.23457 \u03bc', '1 \u03bc', '1.23 \u03bc')),
(0.123456789, ('123.457 m', '123 m', '123.46 m')),
(0.1, ('100 m', '100 m', '100.00 m')),
(1, ('1', '1', '1.00')),
(1.23456789, ('1.23457', '1', '1.23')),
(999.9, ('999.9', '1 k', '999.90')), # places=0: corner-case rounding
(999.9999, ('1 k', '1 k', '1.00 k')), # corner-case roudning for all
(1000, ('1 k', '1 k', '1.00 k')),
(1001, ('1.001 k', '1 k', '1.00 k')),
(100001, ('100.001 k', '100 k', '100.00 k')),
(987654.321, ('987.654 k', '988 k', '987.65 k')),
(1.23e27, ('1230 Y', '1230 Y', '1230.00 Y')) # OoR value (> 1000 Y)
]
@pytest.mark.parametrize('input, expected', raw_format_data)
def test_params(self, input, expected):
"""
Test the formatting of EngFormatter for various values of the 'places'
argument, in several cases:
0. without a unit symbol but with a (default) space separator;
1. with both a unit symbol and a (default) space separator;
2. with both a unit symbol and some non default separators;
3. without a unit symbol but with some non default separators.
Note that cases 2. and 3. are looped over several separator strings.
"""
UNIT = 's' # seconds
DIGITS = '0123456789' # %timeit showed 10-20% faster search than set
# Case 0: unit='' (default) and sep=' ' (default).
# 'expected' already corresponds to this reference case.
exp_outputs = expected
formatters = (
mticker.EngFormatter(), # places=None (default)
mticker.EngFormatter(places=0),
mticker.EngFormatter(places=2)
)
for _formatter, _exp_output in zip(formatters, exp_outputs):
assert _formatter(input) == _exp_output
# Case 1: unit=UNIT and sep=' ' (default).
# Append a unit symbol to the reference case.
# Beware of the values in [1, 1000), where there is no prefix!
exp_outputs = (_s + " " + UNIT if _s[-1] in DIGITS # case w/o prefix
else _s + UNIT for _s in expected)
formatters = (
mticker.EngFormatter(unit=UNIT), # places=None (default)
mticker.EngFormatter(unit=UNIT, places=0),
mticker.EngFormatter(unit=UNIT, places=2)
)
for _formatter, _exp_output in zip(formatters, exp_outputs):
assert _formatter(input) == _exp_output
# Test several non default separators: no separator, a narrow
# no-break space (unicode character) and an extravagant string.
for _sep in ("", "\N{NARROW NO-BREAK SPACE}", "@_@"):
# Case 2: unit=UNIT and sep=_sep.
# Replace the default space separator from the reference case
# with the tested one `_sep` and append a unit symbol to it.
exp_outputs = (_s + _sep + UNIT if _s[-1] in DIGITS # no prefix
else _s.replace(" ", _sep) + UNIT
for _s in expected)
formatters = (
mticker.EngFormatter(unit=UNIT, sep=_sep), # places=None
mticker.EngFormatter(unit=UNIT, places=0, sep=_sep),
mticker.EngFormatter(unit=UNIT, places=2, sep=_sep)
)
for _formatter, _exp_output in zip(formatters, exp_outputs):
assert _formatter(input) == _exp_output
# Case 3: unit='' (default) and sep=_sep.
# Replace the default space separator from the reference case
# with the tested one `_sep`. Reference case is already unitless.
exp_outputs = (_s.replace(" ", _sep) for _s in expected)
formatters = (
mticker.EngFormatter(sep=_sep), # places=None (default)
mticker.EngFormatter(places=0, sep=_sep),
mticker.EngFormatter(places=2, sep=_sep)
)
for _formatter, _exp_output in zip(formatters, exp_outputs):
assert _formatter(input) == _exp_output
class TestPercentFormatter(object):
percent_data = [
# Check explicitly set decimals over different intervals and values
(100, 0, '%', 120, 100, '120%'),
(100, 0, '%', 100, 90, '100%'),
(100, 0, '%', 90, 50, '90%'),
(100, 0, '%', -1.7, 40, '-2%'),
(100, 1, '%', 90.0, 100, '90.0%'),
(100, 1, '%', 80.1, 90, '80.1%'),
(100, 1, '%', 70.23, 50, '70.2%'),
# 60.554 instead of 60.55: see https://bugs.python.org/issue5118
(100, 1, '%', -60.554, 40, '-60.6%'),
# Check auto decimals over different intervals and values
(100, None, '%', 95, 1, '95.00%'),
(1.0, None, '%', 3, 6, '300%'),
(17.0, None, '%', 1, 8.5, '6%'),
(17.0, None, '%', 1, 8.4, '5.9%'),
(5, None, '%', -100, 0.000001, '-2000.00000%'),
# Check percent symbol
(1.0, 2, None, 1.2, 100, '120.00'),
(75, 3, '', 50, 100, '66.667'),
(42, None, '^^Foobar$$', 21, 12, '50.0^^Foobar$$'),
]
percent_ids = [
# Check explicitly set decimals over different intervals and values
'decimals=0, x>100%',
'decimals=0, x=100%',
'decimals=0, x<100%',
'decimals=0, x<0%',
'decimals=1, x>100%',
'decimals=1, x=100%',
'decimals=1, x<100%',
'decimals=1, x<0%',
# Check auto decimals over different intervals and values
'autodecimal, x<100%, display_range=1',
'autodecimal, x>100%, display_range=6 (custom xmax test)',
'autodecimal, x<100%, display_range=8.5 (autodecimal test 1)',
'autodecimal, x<100%, display_range=8.4 (autodecimal test 2)',
'autodecimal, x<-100%, display_range=1e-6 (tiny display range)',
# Check percent symbol
'None as percent symbol',
'Empty percent symbol',
'Custom percent symbol',
]
latex_data = [
(False, False, r'50\{t}%'),
(False, True, r'50\\\{t\}\%'),
(True, False, r'50\{t}%'),
(True, True, r'50\{t}%'),
]
@pytest.mark.parametrize(
'xmax, decimals, symbol, x, display_range, expected',
percent_data, ids=percent_ids)
def test_basic(self, xmax, decimals, symbol,
x, display_range, expected):
formatter = mticker.PercentFormatter(xmax, decimals, symbol)
with matplotlib.rc_context(rc={'text.usetex': False}):
assert formatter.format_pct(x, display_range) == expected
@pytest.mark.parametrize('is_latex, usetex, expected', latex_data)
def test_latex(self, is_latex, usetex, expected):
fmt = mticker.PercentFormatter(symbol='\\{t}%', is_latex=is_latex)
with matplotlib.rc_context(rc={'text.usetex': usetex}):
assert fmt.format_pct(50, 100) == expected
def test_majformatter_type():
fig, ax = plt.subplots()
with pytest.raises(TypeError):
ax.xaxis.set_major_formatter(matplotlib.ticker.LogLocator())
def test_minformatter_type():
fig, ax = plt.subplots()
with pytest.raises(TypeError):
ax.xaxis.set_minor_formatter(matplotlib.ticker.LogLocator())
def test_majlocator_type():
fig, ax = plt.subplots()
with pytest.raises(TypeError):
ax.xaxis.set_major_locator(matplotlib.ticker.LogFormatter())
def test_minlocator_type():
fig, ax = plt.subplots()
with pytest.raises(TypeError):
ax.xaxis.set_minor_locator(matplotlib.ticker.LogFormatter())
@@ -0,0 +1,334 @@
import warnings
import numpy as np
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnchoredOffsetbox, DrawingArea
from matplotlib.patches import Rectangle
def example_plot(ax, fontsize=12):
ax.plot([1, 2])
ax.locator_params(nbins=3)
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_ylabel('y-label', fontsize=fontsize)
ax.set_title('Title', fontsize=fontsize)
@image_comparison(baseline_images=['tight_layout1'])
def test_tight_layout1():
'Test tight_layout for a single subplot'
fig, ax = plt.subplots()
example_plot(ax, fontsize=24)
plt.tight_layout()
@image_comparison(baseline_images=['tight_layout2'])
def test_tight_layout2():
'Test tight_layout for multiple subplots'
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
plt.tight_layout()
@image_comparison(baseline_images=['tight_layout3'])
def test_tight_layout3():
'Test tight_layout for multiple subplots'
fig = plt.figure()
ax1 = plt.subplot(221)
ax2 = plt.subplot(223)
ax3 = plt.subplot(122)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
plt.tight_layout()
@image_comparison(baseline_images=['tight_layout4'],
freetype_version=('2.5.5', '2.6.1'))
def test_tight_layout4():
'Test tight_layout for subplot2grid'
fig = plt.figure()
ax1 = plt.subplot2grid((3, 3), (0, 0))
ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2)
ax4 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
plt.tight_layout()
@image_comparison(baseline_images=['tight_layout5'])
def test_tight_layout5():
'Test tight_layout for image'
fig = plt.figure()
ax = plt.subplot(111)
arr = np.arange(100).reshape((10, 10))
ax.imshow(arr, interpolation="none")
plt.tight_layout()
@image_comparison(baseline_images=['tight_layout6'])
def test_tight_layout6():
'Test tight_layout for gridspec'
# This raises warnings since tight layout cannot
# do this fully automatically. But the test is
# correct since the layout is manually edited
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
fig = plt.figure()
import matplotlib.gridspec as gridspec
gs1 = gridspec.GridSpec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot(ax2)
gs1.tight_layout(fig, rect=[0, 0, 0.5, 1])
gs2 = gridspec.GridSpec(3, 1)
for ss in gs2:
ax = fig.add_subplot(ss)
example_plot(ax)
ax.set_title("")
ax.set_xlabel("")
ax.set_xlabel("x-label", fontsize=12)
gs2.tight_layout(fig, rect=[0.5, 0, 1, 1], h_pad=0.45)
top = min(gs1.top, gs2.top)
bottom = max(gs1.bottom, gs2.bottom)
gs1.tight_layout(fig, rect=[None, 0 + (bottom-gs1.bottom),
0.5, 1 - (gs1.top-top)])
gs2.tight_layout(fig, rect=[0.5, 0 + (bottom-gs2.bottom),
None, 1 - (gs2.top-top)],
h_pad=0.45)
@image_comparison(baseline_images=['tight_layout7'])
def test_tight_layout7():
# tight layout with left and right titles
fontsize = 24
fig, ax = plt.subplots()
ax.plot([1, 2])
ax.locator_params(nbins=3)
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_ylabel('y-label', fontsize=fontsize)
ax.set_title('Left Title', loc='left', fontsize=fontsize)
ax.set_title('Right Title', loc='right', fontsize=fontsize)
plt.tight_layout()
@image_comparison(baseline_images=['tight_layout8'])
def test_tight_layout8():
'Test automatic use of tight_layout'
fig = plt.figure()
fig.set_tight_layout({'pad': .1})
ax = fig.add_subplot(111)
example_plot(ax, fontsize=24)
@image_comparison(baseline_images=['tight_layout9'])
def test_tight_layout9():
# Test tight_layout for non-visible suplots
# GH 8244
f, axarr = plt.subplots(2, 2)
axarr[1][1].set_visible(False)
plt.tight_layout()
# The following test is misleading when the text is removed.
@image_comparison(baseline_images=['outward_ticks'], remove_text=False)
def test_outward_ticks():
'Test automatic use of tight_layout'
fig = plt.figure()
ax = fig.add_subplot(221)
ax.xaxis.set_tick_params(tickdir='out', length=16, width=3)
ax.yaxis.set_tick_params(tickdir='out', length=16, width=3)
ax.xaxis.set_tick_params(
tickdir='out', length=32, width=3, tick1On=True, which='minor')
ax.yaxis.set_tick_params(
tickdir='out', length=32, width=3, tick1On=True, which='minor')
# The following minor ticks are not labelled, and they
# are drawn over the major ticks and labels--ugly!
ax.xaxis.set_ticks([0], minor=True)
ax.yaxis.set_ticks([0], minor=True)
ax = fig.add_subplot(222)
ax.xaxis.set_tick_params(tickdir='in', length=32, width=3)
ax.yaxis.set_tick_params(tickdir='in', length=32, width=3)
ax = fig.add_subplot(223)
ax.xaxis.set_tick_params(tickdir='inout', length=32, width=3)
ax.yaxis.set_tick_params(tickdir='inout', length=32, width=3)
ax = fig.add_subplot(224)
ax.xaxis.set_tick_params(tickdir='out', length=32, width=3)
ax.yaxis.set_tick_params(tickdir='out', length=32, width=3)
plt.tight_layout()
def add_offsetboxes(ax, size=10, margin=.1, color='black'):
"""
Surround ax with OffsetBoxes
"""
m, mp = margin, 1+margin
anchor_points = [(-m, -m), (-m, .5), (-m, mp),
(mp, .5), (.5, mp), (mp, mp),
(.5, -m), (mp, -m), (.5, -m)]
for point in anchor_points:
da = DrawingArea(size, size)
background = Rectangle((0, 0), width=size,
height=size,
facecolor=color,
edgecolor='None',
linewidth=0,
antialiased=False)
da.add_artist(background)
anchored_box = AnchoredOffsetbox(
loc='center',
child=da,
pad=0.,
frameon=False,
bbox_to_anchor=point,
bbox_transform=ax.transAxes,
borderpad=0.)
ax.add_artist(anchored_box)
return anchored_box
@image_comparison(baseline_images=['tight_layout_offsetboxes1',
'tight_layout_offsetboxes2'])
def test_tight_layout_offsetboxes():
# 1.
# - Create 4 subplots
# - Plot a diagonal line on them
# - Surround each plot with 7 boxes
# - Use tight_layout
# - See that the squares are included in the tight_layout
# and that the squares in the middle do not overlap
#
# 2.
# - Make the squares around the right side axes invisible
# - See that the invisible squares do not affect the
# tight_layout
rows = cols = 2
colors = ['red', 'blue', 'green', 'yellow']
x = y = [0, 1]
def _subplots():
_, axs = plt.subplots(rows, cols)
axs = axs.flat
for ax, color in zip(axs, colors):
ax.plot(x, y, color=color)
add_offsetboxes(ax, 20, color=color)
return axs
# 1.
axs = _subplots()
plt.tight_layout()
# 2.
axs = _subplots()
for ax in (axs[cols-1::rows]):
for child in ax.get_children():
if isinstance(child, AnchoredOffsetbox):
child.set_visible(False)
plt.tight_layout()
def test_empty_layout():
"""Tests that tight layout doesn't cause an error when there are
no axes.
"""
fig = plt.gcf()
fig.tight_layout()
def test_verybig_decorators_horizontal():
"Test that warning emitted when xlabel too big"
fig, ax = plt.subplots(figsize=(3, 2))
ax.set_xlabel('a' * 100)
with warnings.catch_warnings(record=True) as w:
fig.tight_layout()
assert len(w) == 1
def test_verybig_decorators_vertical():
"Test that warning emitted when xlabel too big"
fig, ax = plt.subplots(figsize=(3, 2))
ax.set_ylabel('a' * 100)
with warnings.catch_warnings(record=True) as w:
fig.tight_layout()
assert len(w) == 1
def test_big_decorators_horizontal():
"Test that warning emitted when xlabel too big"
fig, axs = plt.subplots(1, 2, figsize=(3, 2))
axs[0].set_xlabel('a' * 30)
axs[1].set_xlabel('b' * 30)
with warnings.catch_warnings(record=True) as w:
fig.tight_layout()
assert len(w) == 1
def test_big_decorators_vertical():
"Test that warning emitted when xlabel too big"
fig, axs = plt.subplots(2, 1, figsize=(3, 2))
axs[0].set_ylabel('a' * 20)
axs[1].set_ylabel('b' * 20)
with warnings.catch_warnings(record=True) as w:
fig.tight_layout()
assert len(w) == 1
def test_badsubplotgrid():
# test that we get warning for mismatched subplot grids rather
# than an error
ax1 = plt.subplot2grid((4, 5), (0, 0))
# this is the bad entry:
ax5 = plt.subplot2grid((5, 5), (0, 3), colspan=3, rowspan=5)
with warnings.catch_warnings(record=True) as w:
plt.tight_layout()
assert len(w) == 1
def test_collapsed():
# test that if a call to tight_layout will collapes the axes that
# it does not get applied:
fig, ax = plt.subplots(tight_layout=True)
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ax.annotate('BIG LONG STRING', xy=(1.25, 2), xytext=(10.5, 1.75),)
p1 = ax.get_position()
with warnings.catch_warnings(record=True) as w:
plt.tight_layout()
p2 = ax.get_position()
assert p1.width == p2.width
assert len(w) == 1
@@ -0,0 +1,631 @@
import unittest
import numpy as np
from numpy.testing import (assert_allclose, assert_almost_equal,
assert_array_equal, assert_array_almost_equal)
import pytest
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.transforms as mtransforms
from matplotlib.path import Path
from matplotlib.scale import LogScale
from matplotlib.testing.decorators import image_comparison
def test_non_affine_caching():
class AssertingNonAffineTransform(mtransforms.Transform):
"""
This transform raises an assertion error when called when it
shouldn't be and self.raise_on_transform is True.
"""
input_dims = output_dims = 2
is_affine = False
def __init__(self, *args, **kwargs):
mtransforms.Transform.__init__(self, *args, **kwargs)
self.raise_on_transform = False
self.underlying_transform = mtransforms.Affine2D().scale(10, 10)
def transform_path_non_affine(self, path):
assert not self.raise_on_transform, \
'Invalidated affine part of transform unnecessarily.'
return self.underlying_transform.transform_path(path)
transform_path = transform_path_non_affine
def transform_non_affine(self, path):
assert not self.raise_on_transform, \
'Invalidated affine part of transform unnecessarily.'
return self.underlying_transform.transform(path)
transform = transform_non_affine
my_trans = AssertingNonAffineTransform()
ax = plt.axes()
plt.plot(np.arange(10), transform=my_trans + ax.transData)
plt.draw()
# enable the transform to raise an exception if it's non-affine transform
# method is triggered again.
my_trans.raise_on_transform = True
ax.transAxes.invalidate()
plt.draw()
def test_external_transform_api():
class ScaledBy(object):
def __init__(self, scale_factor):
self._scale_factor = scale_factor
def _as_mpl_transform(self, axes):
return (mtransforms.Affine2D().scale(self._scale_factor)
+ axes.transData)
ax = plt.axes()
line, = plt.plot(np.arange(10), transform=ScaledBy(10))
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
# assert that the top transform of the line is the scale transform.
assert_allclose(line.get_transform()._a.get_matrix(),
mtransforms.Affine2D().scale(10).get_matrix())
@image_comparison(baseline_images=['pre_transform_data'],
tol=0.08, remove_text=True, style='mpl20')
def test_pre_transform_plotting():
# a catch-all for as many as possible plot layouts which handle
# pre-transforming the data NOTE: The axis range is important in this
# plot. It should be x10 what the data suggests it should be
ax = plt.axes()
times10 = mtransforms.Affine2D().scale(10)
ax.contourf(np.arange(48).reshape(6, 8), transform=times10 + ax.transData)
ax.pcolormesh(np.linspace(0, 4, 7),
np.linspace(5.5, 8, 9),
np.arange(48).reshape(8, 6),
transform=times10 + ax.transData)
ax.scatter(np.linspace(0, 10), np.linspace(10, 0),
transform=times10 + ax.transData)
x = np.linspace(8, 10, 20)
y = np.linspace(1, 5, 20)
u = 2*np.sin(x) + np.cos(y[:, np.newaxis])
v = np.sin(x) - np.cos(y[:, np.newaxis])
df = 25. / 30. # Compatibility factor for old test image
ax.streamplot(x, y, u, v, transform=times10 + ax.transData,
density=(df, df), linewidth=u**2 + v**2)
# reduce the vector data down a bit for barb and quiver plotting
x, y = x[::3], y[::3]
u, v = u[::3, ::3], v[::3, ::3]
ax.quiver(x, y + 5, u, v, transform=times10 + ax.transData)
ax.barbs(x - 3, y + 5, u**2, v**2, transform=times10 + ax.transData)
def test_contour_pre_transform_limits():
ax = plt.axes()
xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20))
ax.contourf(xs, ys, np.log(xs * ys),
transform=mtransforms.Affine2D().scale(0.1) + ax.transData)
expected = np.array([[1.5, 1.24],
[2., 1.25]])
assert_almost_equal(expected, ax.dataLim.get_points())
def test_pcolor_pre_transform_limits():
# Based on test_contour_pre_transform_limits()
ax = plt.axes()
xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20))
ax.pcolor(xs, ys, np.log(xs * ys),
transform=mtransforms.Affine2D().scale(0.1) + ax.transData)
expected = np.array([[1.5, 1.24],
[2., 1.25]])
assert_almost_equal(expected, ax.dataLim.get_points())
def test_pcolormesh_pre_transform_limits():
# Based on test_contour_pre_transform_limits()
ax = plt.axes()
xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20))
ax.pcolormesh(xs, ys, np.log(xs * ys),
transform=mtransforms.Affine2D().scale(0.1) + ax.transData)
expected = np.array([[1.5, 1.24],
[2., 1.25]])
assert_almost_equal(expected, ax.dataLim.get_points())
def test_Affine2D_from_values():
points = np.array([[0, 0],
[10, 20],
[-1, 0],
])
t = mtransforms.Affine2D.from_values(1, 0, 0, 0, 0, 0)
actual = t.transform(points)
expected = np.array([[0, 0], [10, 0], [-1, 0]])
assert_almost_equal(actual, expected)
t = mtransforms.Affine2D.from_values(0, 2, 0, 0, 0, 0)
actual = t.transform(points)
expected = np.array([[0, 0], [0, 20], [0, -2]])
assert_almost_equal(actual, expected)
t = mtransforms.Affine2D.from_values(0, 0, 3, 0, 0, 0)
actual = t.transform(points)
expected = np.array([[0, 0], [60, 0], [0, 0]])
assert_almost_equal(actual, expected)
t = mtransforms.Affine2D.from_values(0, 0, 0, 4, 0, 0)
actual = t.transform(points)
expected = np.array([[0, 0], [0, 80], [0, 0]])
assert_almost_equal(actual, expected)
t = mtransforms.Affine2D.from_values(0, 0, 0, 0, 5, 0)
actual = t.transform(points)
expected = np.array([[5, 0], [5, 0], [5, 0]])
assert_almost_equal(actual, expected)
t = mtransforms.Affine2D.from_values(0, 0, 0, 0, 0, 6)
actual = t.transform(points)
expected = np.array([[0, 6], [0, 6], [0, 6]])
assert_almost_equal(actual, expected)
def test_clipping_of_log():
# issue 804
M, L, C = Path.MOVETO, Path.LINETO, Path.CLOSEPOLY
points = [(0.2, -99), (0.4, -99), (0.4, 20), (0.2, 20), (0.2, -99)]
codes = [M, L, L, L, C]
path = Path(points, codes)
# something like this happens in plotting logarithmic histograms
trans = mtransforms.BlendedGenericTransform(mtransforms.Affine2D(),
LogScale.Log10Transform('clip'))
tpath = trans.transform_path_non_affine(path)
result = tpath.iter_segments(trans.get_affine(),
clip=(0, 0, 100, 100),
simplify=False)
tpoints, tcodes = zip(*result)
assert_allclose(tcodes, [M, L, L, L, C])
class NonAffineForTest(mtransforms.Transform):
"""
A class which looks like a non affine transform, but does whatever
the given transform does (even if it is affine). This is very useful
for testing NonAffine behaviour with a simple Affine transform.
"""
is_affine = False
output_dims = 2
input_dims = 2
def __init__(self, real_trans, *args, **kwargs):
self.real_trans = real_trans
mtransforms.Transform.__init__(self, *args, **kwargs)
def transform_non_affine(self, values):
return self.real_trans.transform(values)
def transform_path_non_affine(self, path):
return self.real_trans.transform_path(path)
class BasicTransformTests(unittest.TestCase):
def setUp(self):
self.ta1 = mtransforms.Affine2D(shorthand_name='ta1').rotate(np.pi / 2)
self.ta2 = mtransforms.Affine2D(shorthand_name='ta2').translate(10, 0)
self.ta3 = mtransforms.Affine2D(shorthand_name='ta3').scale(1, 2)
self.tn1 = NonAffineForTest(mtransforms.Affine2D().translate(1, 2),
shorthand_name='tn1')
self.tn2 = NonAffineForTest(mtransforms.Affine2D().translate(1, 2),
shorthand_name='tn2')
self.tn3 = NonAffineForTest(mtransforms.Affine2D().translate(1, 2),
shorthand_name='tn3')
# creates a transform stack which looks like ((A, (N, A)), A)
self.stack1 = (self.ta1 + (self.tn1 + self.ta2)) + self.ta3
# creates a transform stack which looks like (((A, N), A), A)
self.stack2 = self.ta1 + self.tn1 + self.ta2 + self.ta3
# creates a transform stack which is a subset of stack2
self.stack2_subset = self.tn1 + self.ta2 + self.ta3
# when in debug, the transform stacks can produce dot images:
# self.stack1.write_graphviz(file('stack1.dot', 'w'))
# self.stack2.write_graphviz(file('stack2.dot', 'w'))
# self.stack2_subset.write_graphviz(file('stack2_subset.dot', 'w'))
def test_transform_depth(self):
assert self.stack1.depth == 4
assert self.stack2.depth == 4
assert self.stack2_subset.depth == 3
def test_left_to_right_iteration(self):
stack3 = (self.ta1 + (self.tn1 + (self.ta2 + self.tn2))) + self.ta3
# stack3.write_graphviz(file('stack3.dot', 'w'))
target_transforms = [stack3,
(self.tn1 + (self.ta2 + self.tn2)) + self.ta3,
(self.ta2 + self.tn2) + self.ta3,
self.tn2 + self.ta3,
self.ta3,
]
r = [rh for _, rh in stack3._iter_break_from_left_to_right()]
assert len(r) == len(target_transforms)
for target_stack, stack in zip(target_transforms, r):
assert target_stack == stack
def test_transform_shortcuts(self):
assert self.stack1 - self.stack2_subset == self.ta1
assert self.stack2 - self.stack2_subset == self.ta1
assert self.stack2_subset - self.stack2 == self.ta1.inverted()
assert (self.stack2_subset - self.stack2).depth == 1
with pytest.raises(ValueError):
self.stack1 - self.stack2
aff1 = self.ta1 + (self.ta2 + self.ta3)
aff2 = self.ta2 + self.ta3
assert aff1 - aff2 == self.ta1
assert aff1 - self.ta2 == aff1 + self.ta2.inverted()
assert self.stack1 - self.ta3 == self.ta1 + (self.tn1 + self.ta2)
assert self.stack2 - self.ta3 == self.ta1 + self.tn1 + self.ta2
assert ((self.ta2 + self.ta3) - self.ta3 + self.ta3 ==
self.ta2 + self.ta3)
def test_contains_branch(self):
r1 = (self.ta2 + self.ta1)
r2 = (self.ta2 + self.ta1)
assert r1 == r2
assert r1 != self.ta1
assert r1.contains_branch(r2)
assert r1.contains_branch(self.ta1)
assert not r1.contains_branch(self.ta2)
assert not r1.contains_branch((self.ta2 + self.ta2))
assert r1 == r2
assert self.stack1.contains_branch(self.ta3)
assert self.stack2.contains_branch(self.ta3)
assert self.stack1.contains_branch(self.stack2_subset)
assert self.stack2.contains_branch(self.stack2_subset)
assert not self.stack2_subset.contains_branch(self.stack1)
assert not self.stack2_subset.contains_branch(self.stack2)
assert self.stack1.contains_branch((self.ta2 + self.ta3))
assert self.stack2.contains_branch((self.ta2 + self.ta3))
assert not self.stack1.contains_branch((self.tn1 + self.ta2))
def test_affine_simplification(self):
# tests that a transform stack only calls as much is absolutely
# necessary "non-affine" allowing the best possible optimization with
# complex transformation stacks.
points = np.array([[0, 0], [10, 20], [np.nan, 1], [-1, 0]],
dtype=np.float64)
na_pts = self.stack1.transform_non_affine(points)
all_pts = self.stack1.transform(points)
na_expected = np.array([[1., 2.], [-19., 12.],
[np.nan, np.nan], [1., 1.]], dtype=np.float64)
all_expected = np.array([[11., 4.], [-9., 24.],
[np.nan, np.nan], [11., 2.]],
dtype=np.float64)
# check we have the expected results from doing the affine part only
assert_array_almost_equal(na_pts, na_expected)
# check we have the expected results from a full transformation
assert_array_almost_equal(all_pts, all_expected)
# check we have the expected results from doing the transformation in
# two steps
assert_array_almost_equal(self.stack1.transform_affine(na_pts),
all_expected)
# check that getting the affine transformation first, then fully
# transforming using that yields the same result as before.
assert_array_almost_equal(self.stack1.get_affine().transform(na_pts),
all_expected)
# check that the affine part of stack1 & stack2 are equivalent
# (i.e. the optimization is working)
expected_result = (self.ta2 + self.ta3).get_matrix()
result = self.stack1.get_affine().get_matrix()
assert_array_equal(expected_result, result)
result = self.stack2.get_affine().get_matrix()
assert_array_equal(expected_result, result)
class TestTransformPlotInterface(unittest.TestCase):
def tearDown(self):
plt.close()
def test_line_extent_axes_coords(self):
# a simple line in axes coordinates
ax = plt.axes()
ax.plot([0.1, 1.2, 0.8], [0.9, 0.5, 0.8], transform=ax.transAxes)
assert_array_equal(ax.dataLim.get_points(),
np.array([[np.inf, np.inf],
[-np.inf, -np.inf]]))
def test_line_extent_data_coords(self):
# a simple line in data coordinates
ax = plt.axes()
ax.plot([0.1, 1.2, 0.8], [0.9, 0.5, 0.8], transform=ax.transData)
assert_array_equal(ax.dataLim.get_points(),
np.array([[0.1, 0.5], [1.2, 0.9]]))
def test_line_extent_compound_coords1(self):
# a simple line in data coordinates in the y component, and in axes
# coordinates in the x
ax = plt.axes()
trans = mtransforms.blended_transform_factory(ax.transAxes,
ax.transData)
ax.plot([0.1, 1.2, 0.8], [35, -5, 18], transform=trans)
assert_array_equal(ax.dataLim.get_points(),
np.array([[np.inf, -5.],
[-np.inf, 35.]]))
plt.close()
def test_line_extent_predata_transform_coords(self):
# a simple line in (offset + data) coordinates
ax = plt.axes()
trans = mtransforms.Affine2D().scale(10) + ax.transData
ax.plot([0.1, 1.2, 0.8], [35, -5, 18], transform=trans)
assert_array_equal(ax.dataLim.get_points(),
np.array([[1., -50.], [12., 350.]]))
plt.close()
def test_line_extent_compound_coords2(self):
# a simple line in (offset + data) coordinates in the y component, and
# in axes coordinates in the x
ax = plt.axes()
trans = mtransforms.blended_transform_factory(ax.transAxes,
mtransforms.Affine2D().scale(10) + ax.transData)
ax.plot([0.1, 1.2, 0.8], [35, -5, 18], transform=trans)
assert_array_equal(ax.dataLim.get_points(),
np.array([[np.inf, -50.], [-np.inf, 350.]]))
plt.close()
def test_line_extents_affine(self):
ax = plt.axes()
offset = mtransforms.Affine2D().translate(10, 10)
plt.plot(np.arange(10), transform=offset + ax.transData)
expected_data_lim = np.array([[0., 0.], [9., 9.]]) + 10
assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
def test_line_extents_non_affine(self):
ax = plt.axes()
offset = mtransforms.Affine2D().translate(10, 10)
na_offset = NonAffineForTest(mtransforms.Affine2D().translate(10, 10))
plt.plot(np.arange(10), transform=offset + na_offset + ax.transData)
expected_data_lim = np.array([[0., 0.], [9., 9.]]) + 20
assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
def test_pathc_extents_non_affine(self):
ax = plt.axes()
offset = mtransforms.Affine2D().translate(10, 10)
na_offset = NonAffineForTest(mtransforms.Affine2D().translate(10, 10))
pth = Path(np.array([[0, 0], [0, 10], [10, 10], [10, 0]]))
patch = mpatches.PathPatch(pth,
transform=offset + na_offset + ax.transData)
ax.add_patch(patch)
expected_data_lim = np.array([[0., 0.], [10., 10.]]) + 20
assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
def test_pathc_extents_affine(self):
ax = plt.axes()
offset = mtransforms.Affine2D().translate(10, 10)
pth = Path(np.array([[0, 0], [0, 10], [10, 10], [10, 0]]))
patch = mpatches.PathPatch(pth, transform=offset + ax.transData)
ax.add_patch(patch)
expected_data_lim = np.array([[0., 0.], [10., 10.]]) + 10
assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
def test_line_extents_for_non_affine_transData(self):
ax = plt.axes(projection='polar')
# add 10 to the radius of the data
offset = mtransforms.Affine2D().translate(0, 10)
plt.plot(np.arange(10), transform=offset + ax.transData)
# the data lim of a polar plot is stored in coordinates
# before a transData transformation, hence the data limits
# are not what is being shown on the actual plot.
expected_data_lim = np.array([[0., 0.], [9., 9.]]) + [0, 10]
assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
def assert_bbox_eq(bbox1, bbox2):
assert_array_equal(bbox1.bounds, bbox2.bounds)
def test_bbox_intersection():
bbox_from_ext = mtransforms.Bbox.from_extents
inter = mtransforms.Bbox.intersection
r1 = bbox_from_ext(0, 0, 1, 1)
r2 = bbox_from_ext(0.5, 0.5, 1.5, 1.5)
r3 = bbox_from_ext(0.5, 0, 0.75, 0.75)
r4 = bbox_from_ext(0.5, 1.5, 1, 2.5)
r5 = bbox_from_ext(1, 1, 2, 2)
# self intersection -> no change
assert_bbox_eq(inter(r1, r1), r1)
# simple intersection
assert_bbox_eq(inter(r1, r2), bbox_from_ext(0.5, 0.5, 1, 1))
# r3 contains r2
assert_bbox_eq(inter(r1, r3), r3)
# no intersection
assert inter(r1, r4) is None
# single point
assert_bbox_eq(inter(r1, r5), bbox_from_ext(1, 1, 1, 1))
def test_bbox_as_strings():
b = mtransforms.Bbox([[.5, 0], [.75, .75]])
assert_bbox_eq(b, eval(repr(b), {'Bbox': mtransforms.Bbox}))
asdict = eval(str(b), {'Bbox': dict})
for k, v in asdict.items():
assert getattr(b, k) == v
fmt = '.1f'
asdict = eval(format(b, fmt), {'Bbox': dict})
for k, v in asdict.items():
assert eval(format(getattr(b, k), fmt)) == v
def test_transform_single_point():
t = mtransforms.Affine2D()
r = t.transform_affine((1, 1))
assert r.shape == (2,)
def test_log_transform():
# Tests that the last line runs without exception (previously the
# transform would fail if one of the axes was logarithmic).
fig, ax = plt.subplots()
ax.set_yscale('log')
ax.transData.transform((1, 1))
def test_nan_overlap():
a = mtransforms.Bbox([[0, 0], [1, 1]])
b = mtransforms.Bbox([[0, 0], [1, np.nan]])
assert not a.overlaps(b)
def test_transform_angles():
t = mtransforms.Affine2D() # Identity transform
angles = np.array([20, 45, 60])
points = np.array([[0, 0], [1, 1], [2, 2]])
# Identity transform does not change angles
new_angles = t.transform_angles(angles, points)
assert_array_almost_equal(angles, new_angles)
# points missing a 2nd dimension
with pytest.raises(ValueError):
t.transform_angles(angles, points[0:2, 0:1])
# Number of angles != Number of points
with pytest.raises(ValueError):
t.transform_angles(angles, points[0:2, :])
def test_nonsingular():
# test for zero-expansion type cases; other cases may be added later
zero_expansion = np.array([-0.001, 0.001])
cases = [(0, np.nan), (0, 0), (0, 7.9e-317)]
for args in cases:
out = np.array(mtransforms.nonsingular(*args))
assert_array_equal(out, zero_expansion)
def test_invalid_arguments():
t = mtransforms.Affine2D()
# There are two different exceptions, since the wrong number of
# dimensions is caught when constructing an array_view, and that
# raises a ValueError, and a wrong shape with a possible number
# of dimensions is caught by our CALL_CPP macro, which always
# raises the less precise RuntimeError.
with pytest.raises(ValueError):
t.transform(1)
with pytest.raises(ValueError):
t.transform([[[1]]])
with pytest.raises(RuntimeError):
t.transform([])
with pytest.raises(RuntimeError):
t.transform([1])
with pytest.raises(RuntimeError):
t.transform([[1]])
with pytest.raises(RuntimeError):
t.transform([[1, 2, 3]])
def test_transformed_path():
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]
path = Path(points, codes)
trans = mtransforms.Affine2D()
trans_path = mtransforms.TransformedPath(path, trans)
assert_allclose(trans_path.get_fully_transformed_path().vertices, points)
# Changing the transform should change the result.
r2 = 1 / np.sqrt(2)
trans.rotate(np.pi / 4)
assert_allclose(trans_path.get_fully_transformed_path().vertices,
[(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)],
atol=1e-15)
# Changing the path does not change the result (it's cached).
path.points = [(0, 0)] * 4
assert_allclose(trans_path.get_fully_transformed_path().vertices,
[(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)],
atol=1e-15)
def test_transformed_patch_path():
trans = mtransforms.Affine2D()
patch = mpatches.Wedge((0, 0), 1, 45, 135, transform=trans)
tpatch = mtransforms.TransformedPatchPath(patch)
points = tpatch.get_fully_transformed_path().vertices
# Changing the transform should change the result.
trans.scale(2)
assert_allclose(tpatch.get_fully_transformed_path().vertices, points * 2)
# Changing the path should change the result (and cancel out the scaling
# from the transform).
patch.set_radius(0.5)
assert_allclose(tpatch.get_fully_transformed_path().vertices, points)
@pytest.mark.parametrize('locked_element', ['x0', 'y0', 'x1', 'y1'])
def test_lockable_bbox(locked_element):
other_elements = ['x0', 'y0', 'x1', 'y1']
other_elements.remove(locked_element)
orig = mtransforms.Bbox.unit()
locked = mtransforms.LockableBbox(orig, **{locked_element: 2})
# LockableBbox should keep its locked element as specified in __init__.
assert getattr(locked, locked_element) == 2
assert getattr(locked, 'locked_' + locked_element) == 2
for elem in other_elements:
assert getattr(locked, elem) == getattr(orig, elem)
# Changing underlying Bbox should update everything but locked element.
orig.set_points(orig.get_points() + 10)
assert getattr(locked, locked_element) == 2
assert getattr(locked, 'locked_' + locked_element) == 2
for elem in other_elements:
assert getattr(locked, elem) == getattr(orig, elem)
# Unlocking element should revert values back to the underlying Bbox.
setattr(locked, 'locked_' + locked_element, None)
assert getattr(locked, 'locked_' + locked_element) is None
assert np.all(orig.get_points() == locked.get_points())
# Relocking an element should change its value, but not others.
setattr(locked, 'locked_' + locked_element, 3)
assert getattr(locked, locked_element) == 3
assert getattr(locked, 'locked_' + locked_element) == 3
for elem in other_elements:
assert getattr(locked, elem) == getattr(orig, elem)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
import matplotlib
from matplotlib.font_manager import FontProperties
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
import os.path
@image_comparison(baseline_images=["truetype-conversion"],
extensions=["pdf"])
def test_truetype_conversion():
fontname = os.path.join(os.path.dirname(__file__), 'mpltest.ttf')
fontname = os.path.abspath(fontname)
fontprop = FontProperties(fname=fontname, size=80)
matplotlib.rcParams['pdf.fonttype'] = 3
fig, ax = plt.subplots()
ax.text(0, 0, "ABCDE", fontproperties=fontprop)
ax.set_xticks([])
ax.set_yticks([])
@@ -0,0 +1,50 @@
import matplotlib.type1font as t1f
import os.path
import difflib
def test_Type1Font():
filename = os.path.join(os.path.dirname(__file__), 'cmr10.pfb')
font = t1f.Type1Font(filename)
slanted = font.transform({'slant': 1})
condensed = font.transform({'extend': 0.5})
with open(filename, 'rb') as fd:
rawdata = fd.read()
assert font.parts[0] == rawdata[0x0006:0x10c5]
assert font.parts[1] == rawdata[0x10cb:0x897f]
assert font.parts[2] == rawdata[0x8985:0x8ba6]
assert font.parts[1:] == slanted.parts[1:]
assert font.parts[1:] == condensed.parts[1:]
differ = difflib.Differ()
diff = list(differ.compare(
font.parts[0].decode('latin-1').splitlines(),
slanted.parts[0].decode('latin-1').splitlines()))
for line in (
# Removes UniqueID
'- FontDirectory/CMR10 known{/CMR10 findfont dup/UniqueID known{dup',
'+ FontDirectory/CMR10 known{/CMR10 findfont dup',
# Changes the font name
'- /FontName /CMR10 def',
'+ /FontName /CMR10_Slant_1000 def',
# Alters FontMatrix
'- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def',
'+ /FontMatrix [0.001 0.0 0.001 0.001 0.0 0.0]readonly def',
# Alters ItalicAngle
'- /ItalicAngle 0 def',
'+ /ItalicAngle -45.0 def'):
assert line in diff, 'diff to slanted font must contain %s' % line
diff = list(differ.compare(font.parts[0].decode('latin-1').splitlines(),
condensed.parts[0].decode('latin-1').splitlines()))
for line in (
# Removes UniqueID
'- FontDirectory/CMR10 known{/CMR10 findfont dup/UniqueID known{dup',
'+ FontDirectory/CMR10 known{/CMR10 findfont dup',
# Changes the font name
'- /FontName /CMR10 def',
'+ /FontName /CMR10_Extend_500 def',
# Alters FontMatrix
'- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def',
'+ /FontMatrix [0.0005 0.0 0.0 0.001 0.0 0.0]readonly def'):
assert line in diff, 'diff to condensed font must contain %s' % line
@@ -0,0 +1,154 @@
from unittest.mock import MagicMock
from matplotlib.cbook import iterable
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
import matplotlib.units as munits
import numpy as np
import datetime
import platform
import pytest
# Basic class that wraps numpy array and has units
class Quantity(object):
def __init__(self, data, units):
self.magnitude = data
self.units = units
def to(self, new_units):
factors = {('hours', 'seconds'): 3600, ('minutes', 'hours'): 1 / 60,
('minutes', 'seconds'): 60, ('feet', 'miles'): 1 / 5280.,
('feet', 'inches'): 12, ('miles', 'inches'): 12 * 5280}
if self.units != new_units:
mult = factors[self.units, new_units]
return Quantity(mult * self.magnitude, new_units)
else:
return Quantity(self.magnitude, self.units)
def __getattr__(self, attr):
return getattr(self.magnitude, attr)
def __getitem__(self, item):
if iterable(self.magnitude):
return Quantity(self.magnitude[item], self.units)
else:
return Quantity(self.magnitude, self.units)
def __array__(self):
return np.asarray(self.magnitude)
@pytest.fixture
def quantity_converter():
# Create an instance of the conversion interface and
# mock so we can check methods called
qc = munits.ConversionInterface()
def convert(value, unit, axis):
if hasattr(value, 'units'):
return value.to(unit).magnitude
elif iterable(value):
try:
return [v.to(unit).magnitude for v in value]
except AttributeError:
return [Quantity(v, axis.get_units()).to(unit).magnitude
for v in value]
else:
return Quantity(value, axis.get_units()).to(unit).magnitude
def default_units(value, axis):
if hasattr(value, 'units'):
return value.units
elif np.iterable(value):
for v in value:
if hasattr(v, 'units'):
return v.units
return None
qc.convert = MagicMock(side_effect=convert)
qc.axisinfo = MagicMock(side_effect=lambda u, a: munits.AxisInfo(label=u))
qc.default_units = MagicMock(side_effect=default_units)
return qc
# Tests that the conversion machinery works properly for classes that
# work as a facade over numpy arrays (like pint)
@image_comparison(baseline_images=['plot_pint'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
extensions=['png'], remove_text=False, style='mpl20')
def test_numpy_facade(quantity_converter):
# Register the class
munits.registry[Quantity] = quantity_converter
# Simple test
y = Quantity(np.linspace(0, 30), 'miles')
x = Quantity(np.linspace(0, 5), 'hours')
fig, ax = plt.subplots()
fig.subplots_adjust(left=0.15) # Make space for label
ax.plot(x, y, 'tab:blue')
ax.axhline(Quantity(26400, 'feet'), color='tab:red')
ax.axvline(Quantity(120, 'minutes'), color='tab:green')
ax.yaxis.set_units('inches')
ax.xaxis.set_units('seconds')
assert quantity_converter.convert.called
assert quantity_converter.axisinfo.called
assert quantity_converter.default_units.called
# Tests gh-8908
@image_comparison(baseline_images=['plot_masked_units'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
extensions=['png'], remove_text=True, style='mpl20')
def test_plot_masked_units():
data = np.linspace(-5, 5)
data_masked = np.ma.array(data, mask=(data > -2) & (data < 2))
data_masked_units = Quantity(data_masked, 'meters')
fig, ax = plt.subplots()
ax.plot(data_masked_units)
def test_empty_set_limits_with_units(quantity_converter):
# Register the class
munits.registry[Quantity] = quantity_converter
fig, ax = plt.subplots()
ax.set_xlim(Quantity(-1, 'meters'), Quantity(6, 'meters'))
ax.set_ylim(Quantity(-1, 'hours'), Quantity(16, 'hours'))
@image_comparison(baseline_images=['jpl_bar_units'], extensions=['png'],
savefig_kwarg={'dpi': 120}, style='mpl20')
def test_jpl_bar_units():
from datetime import datetime
import matplotlib.testing.jpl_units as units
units.register()
day = units.Duration("ET", 24.0 * 60.0 * 60.0)
x = [0*units.km, 1*units.km, 2*units.km]
w = [1*day, 2*day, 3*day]
b = units.Epoch("ET", dt=datetime(2009, 4, 25))
fig, ax = plt.subplots()
ax.bar(x, w, bottom=b)
ax.set_ylim([b-1*day, b+w[-1]+1*day])
@image_comparison(baseline_images=['jpl_barh_units'], extensions=['png'],
savefig_kwarg={'dpi': 120}, style='mpl20')
def test_jpl_barh_units():
from datetime import datetime
import matplotlib.testing.jpl_units as units
units.register()
day = units.Duration("ET", 24.0 * 60.0 * 60.0)
x = [0*units.km, 1*units.km, 2*units.km]
w = [1*day, 2*day, 3*day]
b = units.Epoch("ET", dt=datetime(2009, 4, 25))
fig, ax = plt.subplots()
ax.barh(x, w, left=b)
ax.set_xlim([b-1*day, b+w[-1]+1*day])
@@ -0,0 +1,25 @@
import pytest
import matplotlib
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
@pytest.mark.skipif(not matplotlib.checkdep_usetex(True),
reason='Missing TeX or Ghostscript or dvipng')
@image_comparison(baseline_images=['test_usetex'],
extensions=['pdf', 'png'],
tol=0.3)
def test_usetex():
matplotlib.rcParams['text.usetex'] = True
fig = plt.figure()
ax = fig.add_subplot(111)
ax.text(0.1, 0.2,
# the \LaTeX macro exercises character sizing and placement,
# \left[ ... \right\} draw some variable-height characters,
# \sqrt and \frac draw horizontal rules, \mathrm changes the font
r'\LaTeX\ $\left[\int\limits_e^{2e}'
r'\sqrt\frac{\log^3 x}{x}\,\mathrm{d}x \right\}$',
fontsize=24)
ax.set_xticks([])
ax.set_yticks([])
@@ -0,0 +1,455 @@
from unittest.mock import Mock
import matplotlib.widgets as widgets
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
from numpy.testing import assert_allclose
import pytest
def get_ax():
fig, ax = plt.subplots(1, 1)
ax.plot([0, 200], [0, 200])
ax.set_aspect(1.0)
ax.figure.canvas.draw()
return ax
def do_event(tool, etype, button=1, xdata=0, ydata=0, key=None, step=1):
"""
*name*
the event name
*canvas*
the FigureCanvas instance generating the event
*guiEvent*
the GUI event that triggered the matplotlib event
*x*
x position - pixels from left of canvas
*y*
y position - pixels from bottom of canvas
*inaxes*
the :class:`~matplotlib.axes.Axes` instance if mouse is over axes
*xdata*
x coord of mouse in data coords
*ydata*
y coord of mouse in data coords
*button*
button pressed None, 1, 2, 3, 'up', 'down' (up and down are used
for scroll events)
*key*
the key depressed when the mouse event triggered (see
:class:`KeyEvent`)
*step*
number of scroll steps (positive for 'up', negative for 'down')
"""
event = Mock()
event.button = button
ax = tool.ax
event.x, event.y = ax.transData.transform([(xdata, ydata),
(xdata, ydata)])[00]
event.xdata, event.ydata = xdata, ydata
event.inaxes = ax
event.canvas = ax.figure.canvas
event.key = key
event.step = step
event.guiEvent = None
event.name = 'Custom'
func = getattr(tool, etype)
func(event)
def check_rectangle(**kwargs):
ax = get_ax()
def onselect(epress, erelease):
ax._got_onselect = True
assert epress.xdata == 100
assert epress.ydata == 100
assert erelease.xdata == 199
assert erelease.ydata == 199
tool = widgets.RectangleSelector(ax, onselect, **kwargs)
do_event(tool, 'press', xdata=100, ydata=100, button=1)
do_event(tool, 'onmove', xdata=199, ydata=199, button=1)
# purposely drag outside of axis for release
do_event(tool, 'release', xdata=250, ydata=250, button=1)
if kwargs.get('drawtype', None) not in ['line', 'none']:
assert_allclose(tool.geometry,
[[100., 100, 199, 199, 100],
[100, 199, 199, 100, 100]],
err_msg=tool.geometry)
assert ax._got_onselect
def test_rectangle_selector():
check_rectangle()
check_rectangle(drawtype='line', useblit=False)
check_rectangle(useblit=True, button=1)
check_rectangle(drawtype='none', minspanx=10, minspany=10)
check_rectangle(minspanx=10, minspany=10, spancoords='pixels')
check_rectangle(rectprops=dict(fill=True))
def test_ellipse():
"""For ellipse, test out the key modifiers"""
ax = get_ax()
def onselect(epress, erelease):
pass
tool = widgets.EllipseSelector(ax, onselect=onselect,
maxdist=10, interactive=True)
tool.extents = (100, 150, 100, 150)
# drag the rectangle
do_event(tool, 'press', xdata=10, ydata=10, button=1,
key=' ')
do_event(tool, 'onmove', xdata=30, ydata=30, button=1)
do_event(tool, 'release', xdata=30, ydata=30, button=1)
assert tool.extents == (120, 170, 120, 170)
# create from center
do_event(tool, 'on_key_press', xdata=100, ydata=100, button=1,
key='control')
do_event(tool, 'press', xdata=100, ydata=100, button=1)
do_event(tool, 'onmove', xdata=125, ydata=125, button=1)
do_event(tool, 'release', xdata=125, ydata=125, button=1)
do_event(tool, 'on_key_release', xdata=100, ydata=100, button=1,
key='control')
assert tool.extents == (75, 125, 75, 125)
# create a square
do_event(tool, 'on_key_press', xdata=10, ydata=10, button=1,
key='shift')
do_event(tool, 'press', xdata=10, ydata=10, button=1)
do_event(tool, 'onmove', xdata=35, ydata=30, button=1)
do_event(tool, 'release', xdata=35, ydata=30, button=1)
do_event(tool, 'on_key_release', xdata=10, ydata=10, button=1,
key='shift')
extents = [int(e) for e in tool.extents]
assert extents == [10, 35, 10, 34]
# create a square from center
do_event(tool, 'on_key_press', xdata=100, ydata=100, button=1,
key='ctrl+shift')
do_event(tool, 'press', xdata=100, ydata=100, button=1)
do_event(tool, 'onmove', xdata=125, ydata=130, button=1)
do_event(tool, 'release', xdata=125, ydata=130, button=1)
do_event(tool, 'on_key_release', xdata=100, ydata=100, button=1,
key='ctrl+shift')
extents = [int(e) for e in tool.extents]
assert extents == [70, 129, 70, 130]
assert tool.geometry.shape == (2, 73)
assert_allclose(tool.geometry[:, 0], [70., 100])
def test_rectangle_handles():
ax = get_ax()
def onselect(epress, erelease):
pass
tool = widgets.RectangleSelector(ax, onselect=onselect,
maxdist=10, interactive=True)
tool.extents = (100, 150, 100, 150)
assert tool.corners == (
(100, 150, 150, 100), (100, 100, 150, 150))
assert tool.extents == (100, 150, 100, 150)
assert tool.edge_centers == (
(100, 125.0, 150, 125.0), (125.0, 100, 125.0, 150))
assert tool.extents == (100, 150, 100, 150)
# grab a corner and move it
do_event(tool, 'press', xdata=100, ydata=100)
do_event(tool, 'onmove', xdata=120, ydata=120)
do_event(tool, 'release', xdata=120, ydata=120)
assert tool.extents == (120, 150, 120, 150)
# grab the center and move it
do_event(tool, 'press', xdata=132, ydata=132)
do_event(tool, 'onmove', xdata=120, ydata=120)
do_event(tool, 'release', xdata=120, ydata=120)
assert tool.extents == (108, 138, 108, 138)
# create a new rectangle
do_event(tool, 'press', xdata=10, ydata=10)
do_event(tool, 'onmove', xdata=100, ydata=100)
do_event(tool, 'release', xdata=100, ydata=100)
assert tool.extents == (10, 100, 10, 100)
def check_span(*args, **kwargs):
ax = get_ax()
def onselect(vmin, vmax):
ax._got_onselect = True
assert vmin == 100
assert vmax == 150
def onmove(vmin, vmax):
assert vmin == 100
assert vmax == 125
ax._got_on_move = True
if 'onmove_callback' in kwargs:
kwargs['onmove_callback'] = onmove
tool = widgets.SpanSelector(ax, onselect, *args, **kwargs)
do_event(tool, 'press', xdata=100, ydata=100, button=1)
do_event(tool, 'onmove', xdata=125, ydata=125, button=1)
do_event(tool, 'release', xdata=150, ydata=150, button=1)
assert ax._got_onselect
if 'onmove_callback' in kwargs:
assert ax._got_on_move
def test_span_selector():
check_span('horizontal', minspan=10, useblit=True)
check_span('vertical', onmove_callback=True, button=1)
check_span('horizontal', rectprops=dict(fill=True))
def check_lasso_selector(**kwargs):
ax = get_ax()
def onselect(verts):
ax._got_onselect = True
assert verts == [(100, 100), (125, 125), (150, 150)]
tool = widgets.LassoSelector(ax, onselect, **kwargs)
do_event(tool, 'press', xdata=100, ydata=100, button=1)
do_event(tool, 'onmove', xdata=125, ydata=125, button=1)
do_event(tool, 'release', xdata=150, ydata=150, button=1)
assert ax._got_onselect
def test_lasso_selector():
check_lasso_selector()
check_lasso_selector(useblit=False, lineprops=dict(color='red'))
check_lasso_selector(useblit=True, button=1)
def test_CheckButtons():
ax = get_ax()
check = widgets.CheckButtons(ax, ('a', 'b', 'c'), (True, False, True))
assert check.get_status() == [True, False, True]
check.set_active(0)
assert check.get_status() == [False, False, True]
cid = check.on_clicked(lambda: None)
check.disconnect(cid)
@image_comparison(baseline_images=['check_radio_buttons'], extensions=['png'],
style='mpl20', remove_text=True)
def test_check_radio_buttons_image():
get_ax()
plt.subplots_adjust(left=0.3)
rax1 = plt.axes([0.05, 0.7, 0.15, 0.15])
rax2 = plt.axes([0.05, 0.2, 0.15, 0.15])
widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3'))
widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'),
(False, True, True))
@image_comparison(baseline_images=['check_bunch_of_radio_buttons'],
style='mpl20', extensions=['png'], remove_text=True)
def test_check_bunch_of_radio_buttons():
rax = plt.axes([0.05, 0.1, 0.15, 0.7])
widgets.RadioButtons(rax, ('B1', 'B2', 'B3', 'B4', 'B5', 'B6',
'B7', 'B8', 'B9', 'B10', 'B11', 'B12',
'B13', 'B14', 'B15'))
def test_slider_slidermin_slidermax_invalid():
fig, ax = plt.subplots()
# test min/max with floats
with pytest.raises(ValueError):
widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
slidermin=10.0)
with pytest.raises(ValueError):
widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
slidermax=10.0)
def test_slider_slidermin_slidermax():
fig, ax = plt.subplots()
slider_ = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
valinit=5.0)
slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
valinit=1.0, slidermin=slider_)
assert slider.val == slider_.val
slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
valinit=10.0, slidermax=slider_)
assert slider.val == slider_.val
def test_slider_valmin_valmax():
fig, ax = plt.subplots()
slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
valinit=-10.0)
assert slider.val == slider.valmin
slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
valinit=25.0)
assert slider.val == slider.valmax
def check_polygon_selector(event_sequence, expected_result, selections_count):
"""Helper function to test Polygon Selector
Parameters
----------
event_sequence : list of tuples (etype, dict())
A sequence of events to perform. The sequence is a list of tuples
where the first element of the tuple is an etype (e.g., 'onmove',
'press', etc.), and the second element of the tuple is a dictionary of
the arguments for the event (e.g., xdata=5, key='shift', etc.).
expected_result : list of vertices (xdata, ydata)
The list of vertices that are expected to result from the event
sequence.
selections_count : int
Wait for the tool to call its `onselect` function `selections_count`
times, before comparing the result to the `expected_result`
"""
ax = get_ax()
ax._selections_count = 0
def onselect(vertices):
ax._selections_count += 1
ax._current_result = vertices
tool = widgets.PolygonSelector(ax, onselect)
for (etype, event_args) in event_sequence:
do_event(tool, etype, **event_args)
assert ax._selections_count == selections_count
assert ax._current_result == expected_result
def polygon_place_vertex(xdata, ydata):
return [('onmove', dict(xdata=xdata, ydata=ydata)),
('press', dict(xdata=xdata, ydata=ydata)),
('release', dict(xdata=xdata, ydata=ydata))]
def test_polygon_selector():
# Simple polygon
expected_result = [(50, 50), (150, 50), (50, 150)]
event_sequence = (polygon_place_vertex(50, 50)
+ polygon_place_vertex(150, 50)
+ polygon_place_vertex(50, 150)
+ polygon_place_vertex(50, 50))
check_polygon_selector(event_sequence, expected_result, 1)
# Move first vertex before completing the polygon.
expected_result = [(75, 50), (150, 50), (50, 150)]
event_sequence = (polygon_place_vertex(50, 50)
+ polygon_place_vertex(150, 50)
+ [('on_key_press', dict(key='control')),
('onmove', dict(xdata=50, ydata=50)),
('press', dict(xdata=50, ydata=50)),
('onmove', dict(xdata=75, ydata=50)),
('release', dict(xdata=75, ydata=50)),
('on_key_release', dict(key='control'))]
+ polygon_place_vertex(50, 150)
+ polygon_place_vertex(75, 50))
check_polygon_selector(event_sequence, expected_result, 1)
# Move first two vertices at once before completing the polygon.
expected_result = [(50, 75), (150, 75), (50, 150)]
event_sequence = (polygon_place_vertex(50, 50)
+ polygon_place_vertex(150, 50)
+ [('on_key_press', dict(key='shift')),
('onmove', dict(xdata=100, ydata=100)),
('press', dict(xdata=100, ydata=100)),
('onmove', dict(xdata=100, ydata=125)),
('release', dict(xdata=100, ydata=125)),
('on_key_release', dict(key='shift'))]
+ polygon_place_vertex(50, 150)
+ polygon_place_vertex(50, 75))
check_polygon_selector(event_sequence, expected_result, 1)
# Move first vertex after completing the polygon.
expected_result = [(75, 50), (150, 50), (50, 150)]
event_sequence = (polygon_place_vertex(50, 50)
+ polygon_place_vertex(150, 50)
+ polygon_place_vertex(50, 150)
+ polygon_place_vertex(50, 50)
+ [('onmove', dict(xdata=50, ydata=50)),
('press', dict(xdata=50, ydata=50)),
('onmove', dict(xdata=75, ydata=50)),
('release', dict(xdata=75, ydata=50))])
check_polygon_selector(event_sequence, expected_result, 2)
# Move all vertices after completing the polygon.
expected_result = [(75, 75), (175, 75), (75, 175)]
event_sequence = (polygon_place_vertex(50, 50)
+ polygon_place_vertex(150, 50)
+ polygon_place_vertex(50, 150)
+ polygon_place_vertex(50, 50)
+ [('on_key_press', dict(key='shift')),
('onmove', dict(xdata=100, ydata=100)),
('press', dict(xdata=100, ydata=100)),
('onmove', dict(xdata=125, ydata=125)),
('release', dict(xdata=125, ydata=125)),
('on_key_release', dict(key='shift'))])
check_polygon_selector(event_sequence, expected_result, 2)
# Try to move a vertex and move all before placing any vertices.
expected_result = [(50, 50), (150, 50), (50, 150)]
event_sequence = ([('on_key_press', dict(key='control')),
('onmove', dict(xdata=100, ydata=100)),
('press', dict(xdata=100, ydata=100)),
('onmove', dict(xdata=125, ydata=125)),
('release', dict(xdata=125, ydata=125)),
('on_key_release', dict(key='control')),
('on_key_press', dict(key='shift')),
('onmove', dict(xdata=100, ydata=100)),
('press', dict(xdata=100, ydata=100)),
('onmove', dict(xdata=125, ydata=125)),
('release', dict(xdata=125, ydata=125)),
('on_key_release', dict(key='shift'))]
+ polygon_place_vertex(50, 50)
+ polygon_place_vertex(150, 50)
+ polygon_place_vertex(50, 150)
+ polygon_place_vertex(50, 50))
check_polygon_selector(event_sequence, expected_result, 1)
# Try to place vertex out-of-bounds, then reset, and start a new polygon.
expected_result = [(50, 50), (150, 50), (50, 150)]
event_sequence = (polygon_place_vertex(50, 50)
+ polygon_place_vertex(250, 50)
+ [('on_key_press', dict(key='escape')),
('on_key_release', dict(key='escape'))]
+ polygon_place_vertex(50, 50)
+ polygon_place_vertex(150, 50)
+ polygon_place_vertex(50, 150)
+ polygon_place_vertex(50, 50))
check_polygon_selector(event_sequence, expected_result, 1)