Initial commit
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Check that the test directories exist.
|
||||
if not (Path(__file__).parent / 'baseline_images').exists():
|
||||
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.')
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
from matplotlib.testing.conftest import (mpl_test_settings,
|
||||
mpl_image_comparison_parameters,
|
||||
pytest_configure, pytest_unconfigure,
|
||||
pd)
|
||||
Binary file not shown.
@@ -0,0 +1,90 @@
|
||||
from io import BytesIO
|
||||
|
||||
from matplotlib import afm
|
||||
from matplotlib import font_manager as fm
|
||||
|
||||
|
||||
# See note in afm.py re: use of comma as decimal separator in the
|
||||
# UnderlineThickness field and re: use of non-ASCII characters in the Notice
|
||||
# field.
|
||||
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 56,789
|
||||
Version 001.000
|
||||
Notice Copyright \xa9 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': 56.789,
|
||||
b'Version': '001.000',
|
||||
b'Notice': b'Copyright \xa9 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)
|
||||
font = afm.AFM(fh)
|
||||
del font._header[b'FamilyName'] # remove FamilyName, so we have to guess
|
||||
assert font.get_familyname() == 'My Font'
|
||||
|
||||
|
||||
def test_font_manager_weight_normalization():
|
||||
font = afm.AFM(BytesIO(
|
||||
AFM_TEST_DATA.replace(b"Weight Bold\n", b"Weight Custom\n")))
|
||||
assert fm.afmFontProperty("", font).weight == "normal"
|
||||
@@ -0,0 +1,263 @@
|
||||
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)
|
||||
|
||||
|
||||
def test_pil_kwargs_png():
|
||||
Image = pytest.importorskip("PIL.Image")
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
buf = io.BytesIO()
|
||||
pnginfo = PngInfo()
|
||||
pnginfo.add_text("Software", "test")
|
||||
plt.figure().savefig(buf, format="png", pil_kwargs={"pnginfo": pnginfo})
|
||||
im = Image.open(buf)
|
||||
assert im.info["Software"] == "test"
|
||||
|
||||
|
||||
def test_pil_kwargs_tiff():
|
||||
Image = pytest.importorskip("PIL.Image")
|
||||
from PIL.TiffTags import TAGS_V2 as TAGS
|
||||
buf = io.BytesIO()
|
||||
pil_kwargs = {"description": "test image"}
|
||||
plt.figure().savefig(buf, format="tiff", pil_kwargs=pil_kwargs)
|
||||
im = Image.open(buf)
|
||||
tags = {TAGS[k].name: v for k, v in im.tag_v2.items()}
|
||||
assert tags["ImageDescription"] == "test image"
|
||||
@@ -0,0 +1,30 @@
|
||||
import numpy as np
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.testing.decorators import image_comparison
|
||||
|
||||
|
||||
@image_comparison(baseline_images=['agg_filter_alpha'],
|
||||
extensions=['png', 'pdf'])
|
||||
def test_agg_filter_alpha():
|
||||
ax = plt.axes()
|
||||
x, y = np.mgrid[0:7, 0:8]
|
||||
data = x**2 - y**2
|
||||
mesh = ax.pcolormesh(data, cmap='Reds', zorder=5)
|
||||
|
||||
def manual_alpha(im, dpi):
|
||||
im[:, :, 3] *= 0.6
|
||||
print('CALLED')
|
||||
return im, 0, 0
|
||||
|
||||
# Note: Doing alpha like this is not the same as setting alpha on
|
||||
# the mesh itself. Currently meshes are drawn as independent patches,
|
||||
# and we see fine borders around the blocks of color. See the SO
|
||||
# question for an example: https://stackoverflow.com/questions/20678817
|
||||
mesh.set_agg_filter(manual_alpha)
|
||||
|
||||
# Currently we must enable rasterization for this to have an effect in
|
||||
# the PDF backend.
|
||||
mesh.set_rasterized(True)
|
||||
|
||||
ax.plot([0, 4, 7], [1, 3, 8])
|
||||
@@ -0,0 +1,309 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import weakref
|
||||
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
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():
|
||||
class DummyMovieWriter(animation.MovieWriter):
|
||||
def _run(self):
|
||||
pass
|
||||
|
||||
# Test setting up movie writer with figure.dpi default.
|
||||
fig = plt.figure()
|
||||
|
||||
filename = "unused.null"
|
||||
fps = 5
|
||||
codec = "unused"
|
||||
bitrate = 1
|
||||
extra_args = ["unused"]
|
||||
|
||||
writer = DummyMovieWriter(fps, codec, bitrate, extra_args)
|
||||
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')
|
||||
]
|
||||
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):
|
||||
caplog.set_level("WARNING")
|
||||
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()) == []
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="requires a POSIX OS")
|
||||
def test_failing_ffmpeg(tmpdir, monkeypatch):
|
||||
"""
|
||||
Test that we correctly raise a CalledProcessError 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(str(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(subprocess.CalledProcessError):
|
||||
make_animation().save("test.mpeg")
|
||||
finally:
|
||||
animation.writers.reset_available_writers()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cache_frame_data, weakref_assertion_fn", [
|
||||
pytest.param(
|
||||
False, lambda ref: ref is None, id='cache_frame_data_is_disabled'),
|
||||
pytest.param(
|
||||
True, lambda ref: ref is not None, id='cache_frame_data_is_enabled'),
|
||||
])
|
||||
def test_funcanimation_holding_frames(cache_frame_data, weakref_assertion_fn):
|
||||
fig, ax = plt.subplots()
|
||||
line, = ax.plot([], [])
|
||||
|
||||
class Frame(dict):
|
||||
# this subclassing enables to use weakref.ref()
|
||||
pass
|
||||
|
||||
def init():
|
||||
line.set_data([], [])
|
||||
return line,
|
||||
|
||||
def animate(frame):
|
||||
line.set_data(frame['x'], frame['y'])
|
||||
return line,
|
||||
|
||||
frames_generated = []
|
||||
|
||||
def frames_generator():
|
||||
for _ in range(5):
|
||||
x = np.linspace(0, 10, 100)
|
||||
y = np.random.rand(100)
|
||||
|
||||
frame = Frame(x=x, y=y)
|
||||
|
||||
# collect weak references to frames
|
||||
# to validate their references later
|
||||
frames_generated.append(weakref.ref(frame))
|
||||
|
||||
yield frame
|
||||
|
||||
anim = animation.FuncAnimation(fig, animate, init_func=init,
|
||||
frames=frames_generator,
|
||||
cache_frame_data=cache_frame_data)
|
||||
|
||||
writer = NullMovieWriter()
|
||||
anim.save('unused.null', writer=writer)
|
||||
assert len(frames_generated) == 5
|
||||
for f in frames_generated:
|
||||
assert weakref_assertion_fn(f())
|
||||
@@ -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)
|
||||
fig = plt.figure(figsize=(4 / 1.5, figheight / 1.5))
|
||||
|
||||
fontsize = 0.3 * 72
|
||||
|
||||
for i, stylename in enumerate(sorted(styles)):
|
||||
fig.text(0.5, ((n - i) * spacing - 0.5)/figheight, stylename,
|
||||
ha="center",
|
||||
size=fontsize,
|
||||
transform=fig.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,286 @@
|
||||
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()
|
||||
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 not e.is_transform_set()
|
||||
ax.add_patch(e)
|
||||
assert e.get_transform() != intermediate_transform
|
||||
assert e.is_transform_set()
|
||||
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', '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
|
||||
|
||||
|
||||
def test_artist_inspector_get_aliases():
|
||||
# test the correct format and type of get_aliases method
|
||||
ai = martist.ArtistInspector(mlines.Line2D)
|
||||
aliases = ai.get_aliases()
|
||||
assert aliases["linewidth"] == {"lw"}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
import re
|
||||
|
||||
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))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"x, y", [(42, 24), (None, 42), (None, None), (200, 100.01), (205.75, 2.0)])
|
||||
def test_location_event_position(x, y):
|
||||
# LocationEvent should cast its x and y arguments to int unless it is None.
|
||||
fig, ax = plt.subplots()
|
||||
canvas = FigureCanvasBase(fig)
|
||||
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)
|
||||
if x is not None and y is not None:
|
||||
assert re.match(
|
||||
"x={} +y={}".format(ax.format_xdata(x), ax.format_ydata(y)),
|
||||
ax.format_coord(x, y))
|
||||
ax.fmt_xdata = ax.fmt_ydata = lambda x: "foo"
|
||||
assert re.match("x=foo +y=foo", ax.format_coord(x, y))
|
||||
@@ -0,0 +1,55 @@
|
||||
import numpy as np
|
||||
from io import BytesIO
|
||||
import os
|
||||
import tempfile
|
||||
import warnings
|
||||
import xml.parsers.expat
|
||||
|
||||
import pytest
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.testing.decorators import check_figures_equal
|
||||
import matplotlib
|
||||
from matplotlib import (
|
||||
collections as mcollections, patches as mpatches, path as mpath)
|
||||
|
||||
|
||||
@pytest.mark.backend('cairo')
|
||||
@check_figures_equal(extensions=["png"])
|
||||
def test_patch_alpha_coloring(fig_test, fig_ref):
|
||||
"""
|
||||
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)
|
||||
|
||||
# Reference: two separate patches
|
||||
ax = fig_ref.subplots()
|
||||
ax.set_xlim([-1, 2])
|
||||
ax.set_ylim([-1, 2])
|
||||
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)
|
||||
patch = mpatches.PathPatch(cut_star2,
|
||||
linewidth=5, linestyle='dashdot',
|
||||
facecolor=(1, 0, 0, 0.5),
|
||||
edgecolor=(0, 0, 1, 0.75))
|
||||
ax.add_patch(patch)
|
||||
|
||||
# Test: path collection
|
||||
ax = fig_test.subplots()
|
||||
ax.set_xlim([-1, 2])
|
||||
ax.set_ylim([-1, 2])
|
||||
col = mcollections.PathCollection([cut_star1, cut_star2],
|
||||
linewidth=5, linestyles='dashdot',
|
||||
facecolor=(1, 0, 0, 0.5),
|
||||
edgecolor=(0, 0, 1, 0.75))
|
||||
ax.add_collection(col)
|
||||
@@ -0,0 +1,33 @@
|
||||
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:
|
||||
subprocess.check_call([
|
||||
"jupyter", "nbconvert", "--to", "notebook",
|
||||
"--execute", "--ExecutePreprocessor.timeout=500",
|
||||
"--output", fout.name, nb_file,
|
||||
])
|
||||
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(Path(__file__).parent / 'test_nbagg_01.ipynb')
|
||||
assert errors == []
|
||||
@@ -0,0 +1,243 @@
|
||||
import io
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
|
||||
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)
|
||||
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore')
|
||||
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
|
||||
|
||||
|
||||
def test_pdfpages_fspath():
|
||||
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 empty 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,143 @@
|
||||
import io
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import tempfile
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
import matplotlib as mpl
|
||||
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)
|
||||
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore')
|
||||
needs_ghostscript = pytest.mark.skipif(
|
||||
"eps" not in mpl.testing.compare.converter,
|
||||
reason="This test needs a ghostscript installation")
|
||||
needs_usetex = pytest.mark.skipif(
|
||||
not mpl.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):
|
||||
mpl.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 mpl.rc_context():
|
||||
mpl.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"))
|
||||
|
||||
mpl.rcParams['text.usetex'] = True
|
||||
|
||||
# This fails with "Double subscript"
|
||||
plt.xlabel("$22_2_2$")
|
||||
with pytest.raises(RuntimeError):
|
||||
plt.savefig(path)
|
||||
@@ -0,0 +1,309 @@
|
||||
import copy
|
||||
import sys
|
||||
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(qt_module, mpl_test_settings):
|
||||
"""
|
||||
Ensure qt_module fixture is *first* fixture.
|
||||
|
||||
We override the `mpl_test_settings` fixture and depend on the `qt_module`
|
||||
fixture first. It is very important that it is first, because it skips
|
||||
tests when Qt 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 qt_module(request):
|
||||
backend, = request.node.get_closest_marker('backend').args
|
||||
if backend == 'Qt4Agg':
|
||||
if any(k in sys.modules for k in ('PyQt5', 'PySide2')):
|
||||
pytest.skip('Qt5 binding already imported')
|
||||
try:
|
||||
import PyQt4
|
||||
# RuntimeError if PyQt5 already imported.
|
||||
except (ImportError, RuntimeError):
|
||||
try:
|
||||
import PySide
|
||||
except ImportError:
|
||||
pytest.skip("Failed to import a Qt4 binding.")
|
||||
elif backend == 'Qt5Agg':
|
||||
if any(k in sys.modules for k in ('PyQt4', 'PySide')):
|
||||
pytest.skip('Qt4 binding already imported')
|
||||
try:
|
||||
import PyQt5
|
||||
# RuntimeError if PyQt4 already imported.
|
||||
except (ImportError, RuntimeError):
|
||||
try:
|
||||
import PySide2
|
||||
except ImportError:
|
||||
pytest.skip("Failed to import a Qt5 binding.")
|
||||
else:
|
||||
raise ValueError('Backend marker has unknown value: ' + backend)
|
||||
|
||||
qt_compat = pytest.importorskip('matplotlib.backends.qt_compat')
|
||||
QtCore = qt_compat.QtCore
|
||||
|
||||
if backend == 'Qt4Agg':
|
||||
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('Qt4 is not available')
|
||||
|
||||
from matplotlib.backends.backend_qt4 import (
|
||||
MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT)
|
||||
elif backend == 'Qt5Agg':
|
||||
from matplotlib.backends.backend_qt5 import (
|
||||
MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT)
|
||||
|
||||
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('qt_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('qt_module')
|
||||
result = QtCore.Qt.NoModifier
|
||||
for mod in request.param:
|
||||
result |= mods[mod]
|
||||
return result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('backend', [
|
||||
# Note: the value is irrelevant; the important part is the marker.
|
||||
pytest.param('Qt4Agg', marks=pytest.mark.backend('Qt4Agg')),
|
||||
pytest.param('Qt5Agg', marks=pytest.mark.backend('Qt5Agg')),
|
||||
])
|
||||
def test_fig_close(backend):
|
||||
# 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.backend('Qt5Agg')
|
||||
def test_fig_signals(qt_module):
|
||||
# Create a figure
|
||||
fig = plt.figure()
|
||||
|
||||
# Access QtCore
|
||||
QtCore = qt_module[0]
|
||||
|
||||
# Access signals
|
||||
import signal
|
||||
event_loop_signal = None
|
||||
|
||||
# Callback to fire during event loop: save SIGINT handler, then exit
|
||||
def fire_signal_and_quit():
|
||||
# Save event loop signal
|
||||
nonlocal event_loop_signal
|
||||
event_loop_signal = signal.getsignal(signal.SIGINT)
|
||||
|
||||
# Request event loop exit
|
||||
QtCore.QCoreApplication.exit()
|
||||
|
||||
# Timer to exit event loop
|
||||
QtCore.QTimer.singleShot(0, fire_signal_and_quit)
|
||||
|
||||
# Save original SIGINT handler
|
||||
original_signal = signal.getsignal(signal.SIGINT)
|
||||
|
||||
# Use our own SIGINT handler to be 100% sure this is working
|
||||
def CustomHandler(signum, frame):
|
||||
pass
|
||||
|
||||
signal.signal(signal.SIGINT, CustomHandler)
|
||||
|
||||
# mainloop() sets SIGINT, starts Qt event loop (which triggers timer and
|
||||
# exits) and then mainloop() resets SIGINT
|
||||
matplotlib.backends.backend_qt5._BackendQT5.mainloop()
|
||||
|
||||
# Assert: signal handler during loop execution is signal.SIG_DFL
|
||||
assert event_loop_signal == signal.SIG_DFL
|
||||
|
||||
# Assert: current signal handler is the same as the one we set before
|
||||
assert CustomHandler == signal.getsignal(signal.SIGINT)
|
||||
|
||||
# Reset SIGINT handler to what it was before the test
|
||||
signal.signal(signal.SIGINT, original_signal)
|
||||
|
||||
|
||||
@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.parametrize('backend', [
|
||||
# Note: the value is irrelevant; the important part is the marker.
|
||||
pytest.param('Qt4Agg', marks=pytest.mark.backend('Qt4Agg')),
|
||||
pytest.param('Qt5Agg', marks=pytest.mark.backend('Qt5Agg')),
|
||||
])
|
||||
def test_correct_key(backend, qt_key, qt_mods, answer):
|
||||
"""
|
||||
Make a figure
|
||||
Send a key_press_event event (using non-public, qtX 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]])
|
||||
ax.scatter(range(3), range(3), c=range(3))
|
||||
with mock.patch(
|
||||
"matplotlib.backends.qt_editor._formlayout.FormDialog.exec_",
|
||||
lambda self: None):
|
||||
fig.canvas.manager.toolbar.edit_parameters()
|
||||
@@ -0,0 +1,191 @@
|
||||
import numpy as np
|
||||
from io import BytesIO
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import warnings
|
||||
import xml.parsers.expat
|
||||
|
||||
import pytest
|
||||
|
||||
import matplotlib as mpl
|
||||
from matplotlib import dviread
|
||||
from matplotlib.figure import Figure
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.testing.decorators import image_comparison
|
||||
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore')
|
||||
needs_usetex = pytest.mark.skipif(
|
||||
not mpl.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"
|
||||
mpl.rc('svg', hashsalt='asdf')
|
||||
mpl.rc('text', usetex=usetex)
|
||||
|
||||
fig = Figure() # Require no GUI.
|
||||
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$')
|
||||
|
||||
fig.savefig(filename, format="svg")
|
||||
|
||||
|
||||
@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"""
|
||||
|
||||
def psfont(*args, **kwargs):
|
||||
return dviread.PsFont(texname='texfont', psname='Some Font',
|
||||
effects=None, encoding=None, filename=None)
|
||||
|
||||
monkeypatch.setattr(dviread.PsfontsMap, '__getitem__', psfont)
|
||||
mpl.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')
|
||||
|
||||
|
||||
# Use Computer Modern Sans Serif, not Helvetica (which has no \textwon).
|
||||
@pytest.mark.style('default')
|
||||
@needs_usetex
|
||||
def test_unicode_won():
|
||||
fig = Figure()
|
||||
fig.text(.5, .5, r'\textwon', usetex=True)
|
||||
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg')
|
||||
buf = fd.getvalue().decode('ascii')
|
||||
|
||||
won_id = 'Computer_Modern_Sans_Serif-142'
|
||||
assert re.search(r'<path d=(.|\s)*?id="{0}"/>'.format(won_id), buf)
|
||||
assert re.search(r'<use[^/>]*? xlink:href="#{0}"/>'.format(won_id), buf)
|
||||
@@ -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,145 @@
|
||||
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 [
|
||||
(["cairo", "gi"], "gtk3agg"),
|
||||
(["cairo", "gi"], "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):
|
||||
proc = subprocess.run([sys.executable, "-c", _test_script],
|
||||
env={**os.environ, "MPLBACKEND": backend},
|
||||
timeout=_test_timeout)
|
||||
if proc.returncode:
|
||||
pytest.fail("The subprocess returned with non-zero exit status "
|
||||
f"{proc.returncode}.")
|
||||
|
||||
|
||||
@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,57 @@
|
||||
import builtins
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import matplotlib
|
||||
from matplotlib.cbook import dedent
|
||||
|
||||
|
||||
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_lazy_imports():
|
||||
source = dedent("""
|
||||
import sys
|
||||
|
||||
import matplotlib.figure
|
||||
import matplotlib.backend_bases
|
||||
import matplotlib.pyplot
|
||||
|
||||
assert 'matplotlib._png' not in sys.modules
|
||||
assert 'matplotlib._tri' not in sys.modules
|
||||
assert 'matplotlib._qhull' not in sys.modules
|
||||
assert 'matplotlib._contour' not in sys.modules
|
||||
assert 'urllib.request' not in sys.modules
|
||||
""")
|
||||
|
||||
subprocess.check_call([
|
||||
sys.executable,
|
||||
'-c',
|
||||
source
|
||||
])
|
||||
@@ -0,0 +1,110 @@
|
||||
import numpy as np
|
||||
from io import BytesIO
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_only_on_non_finite_bbox():
|
||||
fig, ax = plt.subplots()
|
||||
ax.annotate("", xy=(0, float('nan')))
|
||||
ax.set_axis_off()
|
||||
# we only need to test that it does not error out on save
|
||||
fig.savefig(BytesIO(), bbox_inches='tight', format='png')
|
||||
|
||||
|
||||
def test_tight_pcolorfast():
|
||||
fig, ax = plt.subplots()
|
||||
ax.pcolorfast(np.arange(4).reshape((2, 2)))
|
||||
ax.set(ylim=(0, .1))
|
||||
buf = BytesIO()
|
||||
fig.savefig(buf, bbox_inches="tight")
|
||||
buf.seek(0)
|
||||
height, width, _ = plt.imread(buf).shape
|
||||
# Previously, the bbox would include the area of the image clipped out by
|
||||
# the axes, resulting in a very tall image given the y limits of (0, 0.1).
|
||||
assert width > height
|
||||
@@ -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,599 @@
|
||||
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 (
|
||||
MatplotlibDeprecationWarning, 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[ind], self.arr2[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[ind], self.arr3[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[ind].compressed(), self.arr_rgba[ind])
|
||||
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_reshape2d():
|
||||
|
||||
class dummy():
|
||||
pass
|
||||
|
||||
xnew = cbook._reshape_2D([], 'x')
|
||||
assert np.shape(xnew) == (1, 0)
|
||||
|
||||
x = [dummy() for j in range(5)]
|
||||
|
||||
xnew = cbook._reshape_2D(x, 'x')
|
||||
assert np.shape(xnew) == (1, 5)
|
||||
|
||||
x = np.arange(5)
|
||||
xnew = cbook._reshape_2D(x, 'x')
|
||||
assert np.shape(xnew) == (1, 5)
|
||||
|
||||
x = [[dummy() for j in range(5)] for i in range(3)]
|
||||
xnew = cbook._reshape_2D(x, 'x')
|
||||
assert np.shape(xnew) == (3, 5)
|
||||
|
||||
# this is strange behaviour, but...
|
||||
x = np.random.rand(3, 5)
|
||||
xnew = cbook._reshape_2D(x, 'x')
|
||||
assert np.shape(xnew) == (5, 3)
|
||||
|
||||
# Now test with a list of lists with different lengths, which means the
|
||||
# array will internally be converted to a 1D object array of lists
|
||||
x = [[1, 2, 3], [3, 4], [2]]
|
||||
xnew = cbook._reshape_2D(x, 'x')
|
||||
assert isinstance(xnew, list)
|
||||
assert isinstance(xnew[0], np.ndarray) and xnew[0].shape == (3,)
|
||||
assert isinstance(xnew[1], np.ndarray) and xnew[1].shape == (2,)
|
||||
assert isinstance(xnew[2], np.ndarray) and xnew[2].shape == (1,)
|
||||
|
||||
# We now need to make sure that this works correctly for Numpy subclasses
|
||||
# where iterating over items can return subclasses too, which may be
|
||||
# iterable even if they are scalars. To emulate this, we make a Numpy
|
||||
# array subclass that returns Numpy 'scalars' when iterating or accessing
|
||||
# values, and these are technically iterable if checking for example
|
||||
# isinstance(x, collections.abc.Iterable).
|
||||
|
||||
class ArraySubclass(np.ndarray):
|
||||
|
||||
def __iter__(self):
|
||||
for value in super().__iter__():
|
||||
yield np.array(value)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return np.array(super().__getitem__(item))
|
||||
|
||||
v = np.arange(10, dtype=float)
|
||||
x = ArraySubclass((10,), dtype=float, buffer=v.data)
|
||||
|
||||
xnew = cbook._reshape_2D(x, 'x')
|
||||
|
||||
# We check here that the array wasn't split up into many individual
|
||||
# ArraySubclass, which is what used to happen due to a bug in _reshape_2D
|
||||
assert len(xnew) == 1
|
||||
assert isinstance(xnew[0], ArraySubclass)
|
||||
|
||||
|
||||
def test_contiguous_regions():
|
||||
a, b, c = 3, 4, 5
|
||||
# Starts and ends with True
|
||||
mask = [True]*a + [False]*b + [True]*c
|
||||
expected = [(0, a), (a+b, a+b+c)]
|
||||
assert cbook.contiguous_regions(mask) == expected
|
||||
d, e = 6, 7
|
||||
# Starts with True ends with False
|
||||
mask = mask + [False]*e
|
||||
assert cbook.contiguous_regions(mask) == expected
|
||||
# Starts with False ends with True
|
||||
mask = [False]*d + mask[:-e]
|
||||
expected = [(d, d+a), (d+a+b, d+a+b+c)]
|
||||
assert cbook.contiguous_regions(mask) == expected
|
||||
# Starts and ends with False
|
||||
mask = mask + [False]*e
|
||||
assert cbook.contiguous_regions(mask) == expected
|
||||
# No True in mask
|
||||
assert cbook.contiguous_regions([False]*5) == []
|
||||
# Empty mask
|
||||
assert cbook.contiguous_regions([]) == []
|
||||
|
||||
|
||||
def test_safe_first_element_pandas_series(pd):
|
||||
# deliberately 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
|
||||
|
||||
|
||||
def test_make_keyword_only(recwarn):
|
||||
@cbook._make_keyword_only("3.0", "arg")
|
||||
def func(pre, arg, post=None):
|
||||
pass
|
||||
|
||||
func(1, arg=2)
|
||||
assert len(recwarn) == 0
|
||||
|
||||
with pytest.warns(MatplotlibDeprecationWarning):
|
||||
func(1, 2)
|
||||
with pytest.warns(MatplotlibDeprecationWarning):
|
||||
func(1, 2, 3)
|
||||
@@ -0,0 +1,723 @@
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
def test_pathcollection_legend_elements():
|
||||
np.random.seed(19680801)
|
||||
x, y = np.random.rand(2, 10)
|
||||
y = np.random.rand(10)
|
||||
c = np.random.randint(0, 5, size=10)
|
||||
s = np.random.randint(10, 300, size=10)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
sc = ax.scatter(x, y, c=c, s=s, cmap="jet", marker="o", linewidths=0)
|
||||
|
||||
h, l = sc.legend_elements(fmt="{x:g}")
|
||||
assert len(h) == 5
|
||||
assert_array_equal(np.array(l).astype(float), np.arange(5))
|
||||
colors = np.array([line.get_color() for line in h])
|
||||
colors2 = sc.cmap(np.arange(5)/4)
|
||||
assert_array_equal(colors, colors2)
|
||||
l1 = ax.legend(h, l, loc=1)
|
||||
|
||||
h2, lab2 = sc.legend_elements(num=9)
|
||||
assert len(h2) == 9
|
||||
l2 = ax.legend(h2, lab2, loc=2)
|
||||
|
||||
h, l = sc.legend_elements(prop="sizes", alpha=0.5, color="red")
|
||||
alpha = np.array([line.get_alpha() for line in h])
|
||||
assert_array_equal(alpha, 0.5)
|
||||
color = np.array([line.get_markerfacecolor() for line in h])
|
||||
assert_array_equal(color, "red")
|
||||
l3 = ax.legend(h, l, loc=4)
|
||||
|
||||
h, l = sc.legend_elements(prop="sizes", num=4, fmt="{x:.2f}",
|
||||
func=lambda x: 2*x)
|
||||
actsizes = [line.get_markersize() for line in h]
|
||||
labeledsizes = np.sqrt(np.array(l).astype(float)/2)
|
||||
assert_array_almost_equal(actsizes, labeledsizes)
|
||||
l4 = ax.legend(h, l, loc=3)
|
||||
|
||||
import matplotlib.ticker as mticker
|
||||
loc = mticker.MaxNLocator(nbins=9, min_n_ticks=9-1,
|
||||
steps=[1, 2, 2.5, 3, 5, 6, 8, 10])
|
||||
h5, lab5 = sc.legend_elements(num=loc)
|
||||
assert len(h2) == len(h5)
|
||||
|
||||
levels = [-1, 0, 55.4, 260]
|
||||
h6, lab6 = sc.legend_elements(num=levels, prop="sizes", fmt="{x:g}")
|
||||
assert_array_equal(np.array(lab6).astype(float), levels[2:])
|
||||
|
||||
for l in [l1, l2, l3, l4]:
|
||||
ax.add_artist(l)
|
||||
|
||||
fig.canvas.draw()
|
||||
@@ -0,0 +1,570 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from matplotlib import cm
|
||||
import matplotlib.colors as mcolors
|
||||
|
||||
from matplotlib import rc_context
|
||||
from matplotlib.testing.decorators import image_comparison
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.colors import (BoundaryNorm, LogNorm, PowerNorm, Normalize,
|
||||
DivergingNorm)
|
||||
from matplotlib.colorbar import ColorbarBase, _ColorbarLogLocator
|
||||
from matplotlib.ticker import LogLocator, LogFormatter, FixedLocator
|
||||
|
||||
|
||||
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 = cm.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 = y = [0]
|
||||
z = [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 = cm.get_cmap("RdBu", lut=5)
|
||||
|
||||
im = ax1.pcolormesh(np.linspace(0, 10, 16).reshape((4, 4)), cmap=cmap)
|
||||
|
||||
# The use of a "values" kwarg here is unusual. It works only
|
||||
# because it is matched to the data range in the image and to
|
||||
# the number of colors in the LUT.
|
||||
values = np.linspace(0, 10, 5)
|
||||
cbar_kw = dict(cmap=cmap, orientation='horizontal', values=values,
|
||||
ticks=[])
|
||||
|
||||
# The wide line is to show that the closed path is being handled
|
||||
# correctly. See PR #4186.
|
||||
with rc_context({'axes.linewidth': 16}):
|
||||
plt.colorbar(im, cax=ax2, extend='both', extendfrac=0.5, **cbar_kw)
|
||||
plt.colorbar(im, cax=ax3, extend='both', **cbar_kw)
|
||||
plt.colorbar(im, cax=ax4, extend='both', extendrect=True, **cbar_kw)
|
||||
plt.colorbar(im, cax=ax5, extend='neither', **cbar_kw)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# tests for github issue #13257 and PR #13265
|
||||
data = np.random.uniform(low=1, high=10, size=(20, 20))
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
im = ax.pcolormesh(data, norm=LogNorm())
|
||||
cbar = fig.colorbar(im)
|
||||
default_minorticklocks = cbar.ax.yaxis.get_minorticklocs()
|
||||
|
||||
# test that minorticks turn off for LogNorm
|
||||
cbar.minorticks_off()
|
||||
assert np.array_equal(cbar.ax.yaxis.get_minorticklocs(),
|
||||
np.array([]))
|
||||
|
||||
# test that minorticks turn back on for LogNorm
|
||||
cbar.minorticks_on()
|
||||
assert np.array_equal(cbar.ax.yaxis.get_minorticklocs(),
|
||||
default_minorticklocks)
|
||||
|
||||
# test issue #13339: minorticks for LogNorm should stay off
|
||||
cbar.minorticks_off()
|
||||
cbar.set_ticks([3, 5, 7, 9])
|
||||
assert np.array_equal(cbar.ax.yaxis.get_minorticklocs(),
|
||||
np.array([]))
|
||||
|
||||
|
||||
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)
|
||||
assert np.allclose(cbar.ax.yaxis.get_majorticklocs(),
|
||||
np.arange(0, 120000.1, 15000))
|
||||
|
||||
cbar.set_ticks([1, 2, 3])
|
||||
assert isinstance(cbar.locator, FixedLocator)
|
||||
|
||||
norm = LogNorm(z.min(), z.max())
|
||||
im.set_norm(norm)
|
||||
assert isinstance(cbar.locator, _ColorbarLogLocator)
|
||||
assert np.allclose(cbar.ax.yaxis.get_majorticklocs(),
|
||||
np.logspace(-8, 5, 14))
|
||||
# note that set_norm removes the FixedLocator...
|
||||
assert np.isclose(cbar.vmin, z.min())
|
||||
cbar.set_ticks([1, 2, 3])
|
||||
assert isinstance(cbar.locator, FixedLocator)
|
||||
assert np.allclose(cbar.ax.yaxis.get_majorticklocs(),
|
||||
[1.0, 2.0, 3.0])
|
||||
|
||||
norm = LogNorm(z.min() * 1000, z.max() * 1000)
|
||||
im.set_norm(norm)
|
||||
assert np.isclose(cbar.vmin, z.min() * 1000)
|
||||
assert np.isclose(cbar.vmax, z.max() * 1000)
|
||||
|
||||
|
||||
def test_colorbar_format():
|
||||
# make sure that format is passed properly
|
||||
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, format='%4.2e')
|
||||
fig.canvas.draw()
|
||||
assert cbar.ax.yaxis.get_ticklabels()[4].get_text() == '6.00e+04'
|
||||
|
||||
# make sure that if we change the clim of the mappable that the
|
||||
# formatting is *not* lost:
|
||||
im.set_clim([4, 200])
|
||||
fig.canvas.draw()
|
||||
assert cbar.ax.yaxis.get_ticklabels()[4].get_text() == '8.00e+01'
|
||||
|
||||
# but if we change the norm:
|
||||
im.set_norm(LogNorm(vmin=0.1, vmax=10))
|
||||
fig.canvas.draw()
|
||||
assert (cbar.ax.yaxis.get_ticklabels()[0].get_text() ==
|
||||
r'$\mathdefault{10^{-1}}$')
|
||||
|
||||
|
||||
def test_colorbar_scale_reset():
|
||||
x, y = np.ogrid[-4:4:31j, -4:4:31j]
|
||||
z = 120000*np.exp(-x**2 - y**2)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
pcm = ax.pcolormesh(z, cmap='RdBu_r', rasterized=True)
|
||||
cbar = fig.colorbar(pcm, ax=ax)
|
||||
assert cbar.ax.yaxis.get_scale() == 'linear'
|
||||
|
||||
pcm.set_norm(LogNorm(vmin=1, vmax=100))
|
||||
assert cbar.ax.yaxis.get_scale() == 'log'
|
||||
pcm.set_norm(Normalize(vmin=-20, vmax=20))
|
||||
assert cbar.ax.yaxis.get_scale() == 'linear'
|
||||
|
||||
|
||||
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])
|
||||
|
||||
|
||||
def test_colorbar_inverted_ticks():
|
||||
fig, axs = plt.subplots(2)
|
||||
ax = axs[0]
|
||||
pc = ax.pcolormesh(10**np.arange(1, 5).reshape(2, 2), norm=LogNorm())
|
||||
cbar = fig.colorbar(pc, ax=ax, extend='both')
|
||||
ticks = cbar.get_ticks()
|
||||
cbar.ax.invert_yaxis()
|
||||
np.testing.assert_allclose(ticks, cbar.get_ticks())
|
||||
|
||||
ax = axs[1]
|
||||
pc = ax.pcolormesh(np.arange(1, 5).reshape(2, 2))
|
||||
cbar = fig.colorbar(pc, ax=ax, extend='both')
|
||||
cbar.minorticks_on()
|
||||
ticks = cbar.get_ticks()
|
||||
minorticks = cbar.get_ticks(minor=True)
|
||||
cbar.ax.invert_yaxis()
|
||||
np.testing.assert_allclose(ticks, cbar.get_ticks())
|
||||
np.testing.assert_allclose(minorticks, cbar.get_ticks(minor=True))
|
||||
|
||||
|
||||
def test_extend_colorbar_customnorm():
|
||||
# This was a funny error with DivergingNorm, maybe with other norms,
|
||||
# when extend='both'
|
||||
N = 100
|
||||
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
|
||||
Z1 = np.exp(-X**2 - Y**2)
|
||||
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
|
||||
Z = (Z1 - Z2) * 2
|
||||
|
||||
fig, ax = plt.subplots(2, 1)
|
||||
pcm = ax[0].pcolormesh(X, Y, Z,
|
||||
norm=DivergingNorm(vcenter=0., vmin=-2, vmax=1),
|
||||
cmap='RdBu_r')
|
||||
cb = fig.colorbar(pcm, ax=ax[0], extend='both')
|
||||
np.testing.assert_allclose(cb.ax.get_position().extents,
|
||||
[0.78375, 0.536364, 0.796147, 0.9], rtol=1e-3)
|
||||
|
||||
|
||||
def test_mappable_no_alpha():
|
||||
fig, ax = plt.subplots()
|
||||
sm = cm.ScalarMappable(norm=mcolors.Normalize(), cmap='viridis')
|
||||
fig.colorbar(sm)
|
||||
sm.set_cmap('plasma')
|
||||
plt.draw()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user