Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
10
venv/Lib/site-packages/matplotlib/tests/__init__.py
Normal file
10
venv/Lib/site-packages/matplotlib/tests/__init__.py
Normal file
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4
venv/Lib/site-packages/matplotlib/tests/conftest.py
Normal file
4
venv/Lib/site-packages/matplotlib/tests/conftest.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from matplotlib.testing.conftest import (mpl_test_settings,
|
||||
mpl_image_comparison_parameters,
|
||||
pytest_configure, pytest_unconfigure,
|
||||
pd)
|
137
venv/Lib/site-packages/matplotlib/tests/test_afm.py
Normal file
137
venv/Lib/site-packages/matplotlib/tests/test_afm.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
from io import BytesIO
|
||||
import pytest
|
||||
import logging
|
||||
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"afm_data",
|
||||
[
|
||||
b"""nope
|
||||
really nope""",
|
||||
b"""StartFontMetrics 2.0
|
||||
Comment Comments are ignored.
|
||||
Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
|
||||
FontName MyFont-Bold
|
||||
EncodingScheme FontSpecific""",
|
||||
],
|
||||
)
|
||||
def test_bad_afm(afm_data):
|
||||
fh = BytesIO(afm_data)
|
||||
with pytest.raises(RuntimeError):
|
||||
header = afm._parse_header(fh)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"afm_data",
|
||||
[
|
||||
b"""StartFontMetrics 2.0
|
||||
Comment Comments are ignored.
|
||||
Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
|
||||
Aardvark bob
|
||||
FontName MyFont-Bold
|
||||
EncodingScheme FontSpecific
|
||||
StartCharMetrics 3""",
|
||||
b"""StartFontMetrics 2.0
|
||||
Comment Comments are ignored.
|
||||
Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
|
||||
ItalicAngle zero degrees
|
||||
FontName MyFont-Bold
|
||||
EncodingScheme FontSpecific
|
||||
StartCharMetrics 3""",
|
||||
],
|
||||
)
|
||||
def test_malformed_header(afm_data, caplog):
|
||||
fh = BytesIO(afm_data)
|
||||
with caplog.at_level(logging.ERROR):
|
||||
header = afm._parse_header(fh)
|
||||
|
||||
assert len(caplog.records) == 1
|
246
venv/Lib/site-packages/matplotlib/tests/test_agg.py
Normal file
246
venv/Lib/site-packages/matplotlib/tests/test_agg.py
Normal file
|
@ -0,0 +1,246 @@
|
|||
import io
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_almost_equal
|
||||
from PIL import Image, TiffTags
|
||||
import pytest
|
||||
|
||||
|
||||
from matplotlib import (
|
||||
collections, path, pyplot as plt, transforms as mtransforms, rcParams)
|
||||
from matplotlib.image import imread
|
||||
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])
|
||||
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(['agg_filter.png'], remove_text=True)
|
||||
def test_agg_filter():
|
||||
def smooth1d(x, window_len):
|
||||
# copied from http://www.scipy.org/Cookbook/SignalSmooth
|
||||
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
|
||||
A = np.apply_along_axis(smooth1d, 0, A, window_len)
|
||||
A = np.apply_along_axis(smooth1d, 1, A, window_len)
|
||||
return A
|
||||
|
||||
class BaseFilter:
|
||||
|
||||
def get_pad(self, dpi):
|
||||
return 0
|
||||
|
||||
def process_image(self, padded_src, dpi):
|
||||
raise NotImplementedError("Should be overridden by subclasses")
|
||||
|
||||
def __call__(self, im, dpi):
|
||||
pad = self.get_pad(dpi)
|
||||
padded_src = np.pad(im, [(pad, pad), (pad, pad), (0, 0)],
|
||||
"constant")
|
||||
tgt_image = self.process_image(padded_src, dpi)
|
||||
return tgt_image, -pad, -pad
|
||||
|
||||
class OffsetFilter(BaseFilter):
|
||||
|
||||
def __init__(self, offsets=(0, 0)):
|
||||
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 Gaussian filter."""
|
||||
|
||||
def __init__(self, sigma, alpha=0.5, color=(0, 0, 0)):
|
||||
self.sigma = sigma
|
||||
self.alpha = alpha
|
||||
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.empty_like(padded_src)
|
||||
tgt_image[:, :, :3] = self.color
|
||||
tgt_image[:, :, 3] = smooth2d(padded_src[:, :, 3] * self.alpha,
|
||||
self.sigma / 72 * dpi)
|
||||
return tgt_image
|
||||
|
||||
class DropShadowFilter(BaseFilter):
|
||||
|
||||
def __init__(self, sigma, alpha=0.3, color=(0, 0, 0), offsets=(0, 0)):
|
||||
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, ax = plt.subplots()
|
||||
|
||||
# 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():
|
||||
# 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():
|
||||
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():
|
||||
buf = io.BytesIO()
|
||||
pil_kwargs = {"description": "test image"}
|
||||
plt.figure().savefig(buf, format="tiff", pil_kwargs=pil_kwargs)
|
||||
im = Image.open(buf)
|
||||
tags = {TiffTags.TAGS_V2[k].name: v for k, v in im.tag_v2.items()}
|
||||
assert tags["ImageDescription"] == "test image"
|
30
venv/Lib/site-packages/matplotlib/tests/test_agg_filter.py
Normal file
30
venv/Lib/site-packages/matplotlib/tests/test_agg_filter.py
Normal file
|
@ -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])
|
274
venv/Lib/site-packages/matplotlib/tests/test_animation.py
Normal file
274
venv/Lib/site-packages/matplotlib/tests/test_animation.py
Normal file
|
@ -0,0 +1,274 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import weakref
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import matplotlib as mpl
|
||||
from matplotlib import pyplot as plt
|
||||
from matplotlib import animation
|
||||
|
||||
|
||||
class NullMovieWriter(animation.AbstractMovieWriter):
|
||||
"""
|
||||
A minimal MovieWriter. It doesn't actually write anything.
|
||||
It just saves the arguments that were given to the setup() and
|
||||
grab_frame() methods as attributes, and counts how many times
|
||||
grab_frame() is called.
|
||||
|
||||
This class doesn't have an __init__ method with the appropriate
|
||||
signature, and it doesn't define an isAvailable() method, so
|
||||
it cannot be added to the 'writers' registry.
|
||||
"""
|
||||
|
||||
def setup(self, fig, outfile, dpi, *args):
|
||||
self.fig = fig
|
||||
self.outfile = outfile
|
||||
self.dpi = dpi
|
||||
self.args = args
|
||||
self._count = 0
|
||||
|
||||
def grab_frame(self, **savefig_kwargs):
|
||||
self.savefig_kwargs = savefig_kwargs
|
||||
self._count += 1
|
||||
|
||||
def finish(self):
|
||||
pass
|
||||
|
||||
|
||||
def make_animation(**kwargs):
|
||||
fig, ax = plt.subplots()
|
||||
line, = ax.plot([])
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
def animate(i):
|
||||
line.set_data([0, 1], [0, i])
|
||||
return line,
|
||||
|
||||
return animation.FuncAnimation(fig, animate, **kwargs)
|
||||
|
||||
|
||||
def test_null_movie_writer():
|
||||
# Test running an animation with NullMovieWriter.
|
||||
|
||||
num_frames = 5
|
||||
anim = make_animation(frames=num_frames)
|
||||
|
||||
filename = "unused.null"
|
||||
dpi = 50
|
||||
savefig_kwargs = dict(foo=0)
|
||||
writer = NullMovieWriter()
|
||||
|
||||
anim.save(filename, dpi=dpi, writer=writer,
|
||||
savefig_kwargs=savefig_kwargs)
|
||||
|
||||
assert writer.fig == plt.figure(1) # The figure used by make_animation.
|
||||
assert writer.outfile == filename
|
||||
assert writer.dpi == dpi
|
||||
assert writer.args == ()
|
||||
assert writer.savefig_kwargs == savefig_kwargs
|
||||
assert writer._count == num_frames
|
||||
|
||||
|
||||
def test_movie_writer_dpi_default():
|
||||
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 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)
|
||||
anim.save(output, fps=30, writer=writer, bitrate=500, dpi=dpi,
|
||||
codec=codec)
|
||||
|
||||
|
||||
def test_no_length_frames():
|
||||
(make_animation(frames=iter(range(5)))
|
||||
.save('unused.null', writer=NullMovieWriter()))
|
||||
|
||||
|
||||
def test_movie_writer_registry():
|
||||
assert len(animation.writers._registered) > 0
|
||||
mpl.rcParams['animation.ffmpeg_path'] = "not_available_ever_xxxx"
|
||||
assert not animation.writers.is_available("ffmpeg")
|
||||
# something 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.is_available("ffmpeg")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"method_name",
|
||||
[pytest.param("to_html5_video", marks=pytest.mark.skipif(
|
||||
not animation.writers.is_available(mpl.rcParams["animation.writer"]),
|
||||
reason="animation writer not installed")),
|
||||
"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.parametrize(
|
||||
"method_name",
|
||||
[pytest.param("to_html5_video", marks=pytest.mark.skipif(
|
||||
not animation.writers.is_available(mpl.rcParams["animation.writer"]),
|
||||
reason="animation writer not installed")),
|
||||
"to_jshtml"])
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
make_animation().save("test.mpeg")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cache_frame_data", [False, True])
|
||||
def test_funcanimation_cache_frame_data(cache_frame_data):
|
||||
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:
|
||||
# If cache_frame_data is True, then the weakref should be alive;
|
||||
# if cache_frame_data is False, then the weakref should be dead (None).
|
||||
assert (f() is None) != cache_frame_data
|
160
venv/Lib/site-packages/matplotlib/tests/test_arrow_patches.py
Normal file
160
venv/Lib/site-packages/matplotlib/tests/test_arrow_patches.py
Normal file
|
@ -0,0 +1,160 @@
|
|||
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(['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, axs = plt.subplots(len(t), len(r), squeeze=False,
|
||||
figsize=(8, 4.5), subplot_kw=dict(aspect=1))
|
||||
|
||||
for i_r, r1 in enumerate(r):
|
||||
for i_t, t1 in enumerate(t):
|
||||
ax = axs[i_t, i_r]
|
||||
draw_arrow(ax, t1, r1)
|
||||
ax.tick_params(labelleft=False, labelbottom=False)
|
||||
|
||||
|
||||
@image_comparison(['boxarrow_test_image.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(['fancyarrow_dpi_cor_100dpi.png'], remove_text=True,
|
||||
tol=0 if platform.machine() == 'x86_64' else 0.02,
|
||||
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(['fancyarrow_dpi_cor_200dpi.png'], remove_text=True,
|
||||
tol=0 if platform.machine() == 'x86_64' else 0.02,
|
||||
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(['fancyarrow_dash.png'], remove_text=True, style='default')
|
||||
def test_fancyarrow_dash():
|
||||
fig, ax = plt.subplots()
|
||||
e = mpatches.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 = mpatches.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(['arrow_styles.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(['connection_styles.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)
|
279
venv/Lib/site-packages/matplotlib/tests/test_artist.py
Normal file
279
venv/Lib/site-packages/matplotlib/tests/test_artist.py
Normal file
|
@ -0,0 +1,279 @@
|
|||
import io
|
||||
from itertools import chain
|
||||
|
||||
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(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(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(["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.make_compound_path(exterior, interior)
|
||||
|
||||
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(['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(["default_edges.png"], remove_text=True, style='default')
|
||||
def test_default_edges():
|
||||
# Remove this line when this test image is regenerated.
|
||||
plt.rcParams['text.kerning_factor'] = 6
|
||||
|
||||
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([], [])
|
||||
ln.properties() # Check that no warning is emitted.
|
||||
|
||||
|
||||
def test_setp():
|
||||
# Check empty list
|
||||
plt.setp([])
|
||||
plt.setp([[]])
|
||||
|
||||
# Check arbitrary iterables
|
||||
fig, ax = plt.subplots()
|
||||
lines1 = ax.plot(range(3))
|
||||
lines2 = ax.plot(range(3))
|
||||
martist.setp(chain(lines1, lines2), 'lw', 5)
|
||||
plt.setp(ax.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"}
|
6434
venv/Lib/site-packages/matplotlib/tests/test_axes.py
Normal file
6434
venv/Lib/site-packages/matplotlib/tests/test_axes.py
Normal file
File diff suppressed because it is too large
Load diff
159
venv/Lib/site-packages/matplotlib/tests/test_backend_bases.py
Normal file
159
venv/Lib/site-packages/matplotlib/tests/test_backend_bases.py
Normal file
|
@ -0,0 +1,159 @@
|
|||
import re
|
||||
|
||||
from matplotlib.backend_bases import (
|
||||
FigureCanvasBase, LocationEvent, MouseButton, MouseEvent,
|
||||
NavigationToolbar2, 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_matrices = [id.rotate(i).get_matrix().copy() 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.AffineDeltaTransform(master_transform),
|
||||
facecolors, edgecolors, [], [], [False],
|
||||
[], 'screen')]
|
||||
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_matrices, offsets, facecolors, edgecolors)
|
||||
check(id, paths[0:1], tforms_matrices, offsets, facecolors, edgecolors)
|
||||
check(id, [], tforms_matrices, offsets, facecolors, edgecolors)
|
||||
check(id, paths, tforms_matrices[0:1], offsets, facecolors, edgecolors)
|
||||
check(id, paths, [], offsets, facecolors, edgecolors)
|
||||
for n in range(0, offsets.shape[0]):
|
||||
check(id, paths, tforms_matrices, offsets[0:n, :],
|
||||
facecolors, edgecolors)
|
||||
check(id, paths, tforms_matrices, offsets, [], edgecolors)
|
||||
check(id, paths, tforms_matrices, offsets, facecolors, [])
|
||||
check(id, paths, tforms_matrices, offsets, [], [])
|
||||
check(id, paths, tforms_matrices, 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'
|
||||
|
||||
|
||||
def test_canvas_change():
|
||||
fig = plt.figure()
|
||||
# Replaces fig.canvas
|
||||
canvas = FigureCanvasBase(fig)
|
||||
# Should still work.
|
||||
plt.close(fig)
|
||||
assert not plt.fignum_exists(fig.number)
|
||||
|
||||
|
||||
@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))
|
||||
|
||||
|
||||
def test_interactive_zoom():
|
||||
fig, ax = plt.subplots()
|
||||
ax.set(xscale="logit")
|
||||
assert ax.get_navigate_mode() is None
|
||||
|
||||
tb = NavigationToolbar2(fig.canvas)
|
||||
tb.zoom()
|
||||
assert ax.get_navigate_mode() == 'ZOOM'
|
||||
|
||||
xlim0 = ax.get_xlim()
|
||||
ylim0 = ax.get_ylim()
|
||||
|
||||
# Zoom from x=1e-6, y=0.1 to x=1-1e-5, 0.8 (data coordinates, "d").
|
||||
d0 = (1e-6, 0.1)
|
||||
d1 = (1-1e-5, 0.8)
|
||||
# Convert to screen coordinates ("s"). Events are defined only with pixel
|
||||
# precision, so round the pixel values, and below, check against the
|
||||
# corresponding xdata/ydata, which are close but not equal to d0/d1.
|
||||
s0 = ax.transData.transform(d0).astype(int)
|
||||
s1 = ax.transData.transform(d1).astype(int)
|
||||
|
||||
# Zoom in.
|
||||
start_event = MouseEvent(
|
||||
"button_press_event", fig.canvas, *s0, MouseButton.LEFT)
|
||||
fig.canvas.callbacks.process(start_event.name, start_event)
|
||||
stop_event = MouseEvent(
|
||||
"button_release_event", fig.canvas, *s1, MouseButton.LEFT)
|
||||
fig.canvas.callbacks.process(stop_event.name, stop_event)
|
||||
assert ax.get_xlim() == (start_event.xdata, stop_event.xdata)
|
||||
assert ax.get_ylim() == (start_event.ydata, stop_event.ydata)
|
||||
|
||||
# Zoom out.
|
||||
start_event = MouseEvent(
|
||||
"button_press_event", fig.canvas, *s1, MouseButton.RIGHT)
|
||||
fig.canvas.callbacks.process(start_event.name, start_event)
|
||||
stop_event = MouseEvent(
|
||||
"button_release_event", fig.canvas, *s0, MouseButton.RIGHT)
|
||||
fig.canvas.callbacks.process(stop_event.name, stop_event)
|
||||
# Absolute tolerance much less than original xmin (1e-7).
|
||||
assert ax.get_xlim() == pytest.approx(xlim0, rel=0, abs=1e-10)
|
||||
assert ax.get_ylim() == pytest.approx(ylim0, rel=0, abs=1e-10)
|
||||
|
||||
tb.zoom()
|
||||
assert ax.get_navigate_mode() is None
|
|
@ -0,0 +1,48 @@
|
|||
import numpy as np
|
||||
|
||||
import pytest
|
||||
|
||||
from matplotlib.testing.decorators import check_figures_equal
|
||||
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,28 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
nbformat = pytest.importorskip('nbformat')
|
||||
|
||||
# From https://blog.thedataincubator.com/2016/06/testing-jupyter-notebooks/
|
||||
|
||||
|
||||
def test_ipynb():
|
||||
nb_path = Path(__file__).parent / 'test_nbagg_01.ipynb'
|
||||
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
out_path = Path(tmpdir, "out.ipynb")
|
||||
subprocess.check_call(
|
||||
["jupyter", "nbconvert", "--to", "notebook",
|
||||
"--execute", "--ExecutePreprocessor.timeout=500",
|
||||
"--output", str(out_path), str(nb_path)],
|
||||
env={**os.environ, "IPYTHONDIR": tmpdir})
|
||||
with out_path.open() as out:
|
||||
nb = nbformat.read(out, nbformat.current_nbformat)
|
||||
|
||||
errors = [output for cell in nb.cells for output in cell.get("outputs", [])
|
||||
if output.output_type == "error"]
|
||||
assert not errors
|
273
venv/Lib/site-packages/matplotlib/tests/test_backend_pdf.py
Normal file
273
venv/Lib/site-packages/matplotlib/tests/test_backend_pdf.py
Normal file
|
@ -0,0 +1,273 @@
|
|||
import datetime
|
||||
import io
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import matplotlib as mpl
|
||||
from matplotlib import dviread, pyplot as plt, checkdep_usetex, rcParams
|
||||
from matplotlib.backends.backend_pdf import PdfPages
|
||||
from matplotlib.testing.decorators import check_figures_equal, image_comparison
|
||||
|
||||
|
||||
needs_usetex = pytest.mark.skipif(
|
||||
not checkdep_usetex(True),
|
||||
reason="This test needs a TeX installation")
|
||||
|
||||
|
||||
@image_comparison(['pdf_use14corefonts.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():
|
||||
# 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_savefig_metadata(monkeypatch):
|
||||
pikepdf = pytest.importorskip('pikepdf')
|
||||
monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(range(5))
|
||||
|
||||
md = {
|
||||
'Author': 'me',
|
||||
'Title': 'Multipage PDF',
|
||||
'Subject': 'Test page',
|
||||
'Keywords': 'test,pdf,multipage',
|
||||
'ModDate': datetime.datetime(
|
||||
1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
|
||||
'Trapped': 'True'
|
||||
}
|
||||
buf = io.BytesIO()
|
||||
fig.savefig(buf, metadata=md, format='pdf')
|
||||
|
||||
with pikepdf.Pdf.open(buf) as pdf:
|
||||
info = {k: str(v) for k, v in pdf.docinfo.items()}
|
||||
|
||||
assert info == {
|
||||
'/Author': 'me',
|
||||
'/CreationDate': 'D:19700101000000Z',
|
||||
'/Creator': f'Matplotlib v{mpl.__version__}, https://matplotlib.org',
|
||||
'/Keywords': 'test,pdf,multipage',
|
||||
'/ModDate': 'D:19680801000000Z',
|
||||
'/Producer': f'Matplotlib pdf backend v{mpl.__version__}',
|
||||
'/Subject': 'Test page',
|
||||
'/Title': 'Multipage PDF',
|
||||
'/Trapped': '/True',
|
||||
}
|
||||
|
||||
|
||||
def test_multipage_metadata(monkeypatch):
|
||||
pikepdf = pytest.importorskip('pikepdf')
|
||||
monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(range(5))
|
||||
|
||||
md = {
|
||||
'Author': 'me',
|
||||
'Title': 'Multipage PDF',
|
||||
'Subject': 'Test page',
|
||||
'Keywords': 'test,pdf,multipage',
|
||||
'ModDate': datetime.datetime(
|
||||
1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
|
||||
'Trapped': 'True'
|
||||
}
|
||||
buf = io.BytesIO()
|
||||
with PdfPages(buf, metadata=md) as pdf:
|
||||
pdf.savefig(fig)
|
||||
pdf.savefig(fig)
|
||||
|
||||
with pikepdf.Pdf.open(buf) as pdf:
|
||||
info = {k: str(v) for k, v in pdf.docinfo.items()}
|
||||
|
||||
assert info == {
|
||||
'/Author': 'me',
|
||||
'/CreationDate': 'D:19700101000000Z',
|
||||
'/Creator': f'Matplotlib v{mpl.__version__}, https://matplotlib.org',
|
||||
'/Keywords': 'test,pdf,multipage',
|
||||
'/ModDate': 'D:19680801000000Z',
|
||||
'/Producer': f'Matplotlib pdf backend v{mpl.__version__}',
|
||||
'/Subject': 'Test page',
|
||||
'/Title': 'Multipage PDF',
|
||||
'/Trapped': '/True',
|
||||
}
|
||||
|
||||
|
||||
def test_pdfpages_fspath():
|
||||
with PdfPages(Path(os.devnull)) as pdf:
|
||||
pdf.savefig(plt.figure())
|
||||
|
||||
|
||||
@image_comparison(['hatching_legend.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(['grayscale_alpha.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 NamedTemporaryFile() as tmpfile, pytest.raises(ValueError):
|
||||
fig.savefig(tmpfile, format='pdf')
|
||||
|
||||
|
||||
@pytest.mark.style('default')
|
||||
@check_figures_equal(extensions=["pdf", "eps"])
|
||||
def test_pdf_eps_savefig_when_color_is_none(fig_test, fig_ref):
|
||||
ax_test = fig_test.add_subplot()
|
||||
ax_test.set_axis_off()
|
||||
ax_test.plot(np.sin(np.linspace(-5, 5, 100)), "v", c="none")
|
||||
ax_ref = fig_ref.add_subplot()
|
||||
ax_ref.set_axis_off()
|
||||
|
||||
|
||||
@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")
|
326
venv/Lib/site-packages/matplotlib/tests/test_backend_pgf.py
Normal file
326
venv/Lib/site-packages/matplotlib/tests/test_backend_pgf.py
Normal file
|
@ -0,0 +1,326 @@
|
|||
import datetime
|
||||
from io import BytesIO
|
||||
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, common_texification
|
||||
|
||||
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 _has_sfmath():
|
||||
return (shutil.which("kpsewhich")
|
||||
and subprocess.run(["kpsewhich", "sfmath.sty"],
|
||||
stdout=subprocess.PIPE).returncode == 0)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('plain_text, escaped_text', [
|
||||
(r'quad_sum: $\sum x_i^2$', r'quad\_sum: \(\displaystyle \sum x_i^2\)'),
|
||||
(r'no \$splits \$ here', r'no \$splits \$ here'),
|
||||
('with_underscores', r'with\_underscores'),
|
||||
('% not a comment', r'\% not a comment'),
|
||||
('^not', r'\^not'),
|
||||
])
|
||||
def test_common_texification(plain_text, escaped_text):
|
||||
assert common_texification(plain_text) == escaped_text
|
||||
|
||||
|
||||
# test compiling a figure to pdf with xelatex
|
||||
@needs_xelatex
|
||||
@pytest.mark.backend('pgf')
|
||||
@image_comparison(['pgf_xelatex.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(['pgf_pdflatex.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.skipif(not _has_sfmath(), reason='needs sfmath.sty')
|
||||
@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(['pgf_mixedmode.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)
|
||||
|
||||
|
||||
@pytest.mark.style('default')
|
||||
@pytest.mark.backend('pgf')
|
||||
@pytest.mark.parametrize('system', [
|
||||
pytest.param('lualatex', marks=[needs_lualatex]),
|
||||
pytest.param('pdflatex', marks=[needs_pdflatex]),
|
||||
pytest.param('xelatex', marks=[needs_xelatex]),
|
||||
])
|
||||
def test_pdf_pages(system):
|
||||
rc_pdflatex = {
|
||||
'font.family': 'serif',
|
||||
'pgf.rcfonts': False,
|
||||
'pgf.texsystem': system,
|
||||
}
|
||||
mpl.rcParams.update(rc_pdflatex)
|
||||
|
||||
fig1, ax1 = plt.subplots()
|
||||
ax1.plot(range(5))
|
||||
fig1.tight_layout()
|
||||
|
||||
fig2, ax2 = plt.subplots(figsize=(3, 2))
|
||||
ax2.plot(range(5))
|
||||
fig2.tight_layout()
|
||||
|
||||
path = os.path.join(result_dir, f'pdfpages_{system}.pdf')
|
||||
md = {
|
||||
'Author': 'me',
|
||||
'Title': 'Multipage PDF with pgf',
|
||||
'Subject': 'Test page',
|
||||
'Keywords': 'test,pdf,multipage',
|
||||
'ModDate': datetime.datetime(
|
||||
1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
|
||||
'Trapped': 'Unknown'
|
||||
}
|
||||
|
||||
with PdfPages(path, metadata=md) as pdf:
|
||||
pdf.savefig(fig1)
|
||||
pdf.savefig(fig2)
|
||||
pdf.savefig(fig1)
|
||||
|
||||
assert pdf.get_pagecount() == 3
|
||||
|
||||
|
||||
@pytest.mark.style('default')
|
||||
@pytest.mark.backend('pgf')
|
||||
@pytest.mark.parametrize('system', [
|
||||
pytest.param('lualatex', marks=[needs_lualatex]),
|
||||
pytest.param('pdflatex', marks=[needs_pdflatex]),
|
||||
pytest.param('xelatex', marks=[needs_xelatex]),
|
||||
])
|
||||
def test_pdf_pages_metadata_check(monkeypatch, system):
|
||||
# Basically the same as test_pdf_pages, but we keep it separate to leave
|
||||
# pikepdf as an optional dependency.
|
||||
pikepdf = pytest.importorskip('pikepdf')
|
||||
monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
|
||||
|
||||
mpl.rcParams.update({'pgf.texsystem': system})
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(range(5))
|
||||
|
||||
md = {
|
||||
'Author': 'me',
|
||||
'Title': 'Multipage PDF with pgf',
|
||||
'Subject': 'Test page',
|
||||
'Keywords': 'test,pdf,multipage',
|
||||
'ModDate': datetime.datetime(
|
||||
1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
|
||||
'Trapped': 'True'
|
||||
}
|
||||
path = os.path.join(result_dir, f'pdfpages_meta_check_{system}.pdf')
|
||||
with PdfPages(path, metadata=md) as pdf:
|
||||
pdf.savefig(fig)
|
||||
|
||||
with pikepdf.Pdf.open(path) as pdf:
|
||||
info = {k: str(v) for k, v in pdf.docinfo.items()}
|
||||
|
||||
# Not set by us, so don't bother checking.
|
||||
if '/PTEX.FullBanner' in info:
|
||||
del info['/PTEX.FullBanner']
|
||||
if '/PTEX.Fullbanner' in info:
|
||||
del info['/PTEX.Fullbanner']
|
||||
|
||||
assert info == {
|
||||
'/Author': 'me',
|
||||
'/CreationDate': 'D:19700101000000Z',
|
||||
'/Creator': f'Matplotlib v{mpl.__version__}, https://matplotlib.org',
|
||||
'/Keywords': 'test,pdf,multipage',
|
||||
'/ModDate': 'D:19680801000000Z',
|
||||
'/Producer': f'Matplotlib pgf backend v{mpl.__version__}',
|
||||
'/Subject': 'Test page',
|
||||
'/Title': 'Multipage PDF with pgf',
|
||||
'/Trapped': '/True',
|
||||
}
|
||||
|
||||
|
||||
@needs_xelatex
|
||||
def test_tex_restart_after_error():
|
||||
fig = plt.figure()
|
||||
fig.suptitle(r"\oops")
|
||||
with pytest.raises(ValueError):
|
||||
fig.savefig(BytesIO(), format="pgf")
|
||||
|
||||
fig = plt.figure() # start from scratch
|
||||
fig.suptitle(r"this is ok")
|
||||
fig.savefig(BytesIO(), format="pgf")
|
||||
|
||||
|
||||
@needs_xelatex
|
||||
def test_bbox_inches_tight(tmpdir):
|
||||
fig, ax = plt.subplots()
|
||||
ax.imshow([[0, 1], [2, 3]])
|
||||
fig.savefig(os.path.join(tmpdir, "test.pdf"), backend="pgf",
|
||||
bbox_inches="tight")
|
135
venv/Lib/site-packages/matplotlib/tests/test_backend_ps.py
Normal file
135
venv/Lib/site-packages/matplotlib/tests/test_backend_ps.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
import io
|
||||
from pathlib import Path
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
import matplotlib as mpl
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib import cbook, patheffects
|
||||
from matplotlib.testing.decorators import image_comparison
|
||||
|
||||
|
||||
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('orientation', ['portrait', 'landscape'])
|
||||
@pytest.mark.parametrize('format, use_log, rcParams', [
|
||||
('ps', False, {}),
|
||||
('ps', False, {'ps.usedistiller': 'ghostscript'}),
|
||||
('ps', False, {'ps.usedistiller': 'xpdf'}),
|
||||
('ps', False, {'text.usetex': True}),
|
||||
('eps', False, {}),
|
||||
('eps', True, {'ps.useafm': True}),
|
||||
('eps', False, {'text.usetex': True}),
|
||||
], ids=[
|
||||
'ps',
|
||||
'ps with distiller=ghostscript',
|
||||
'ps with distiller=xpdf',
|
||||
'ps with usetex',
|
||||
'eps',
|
||||
'eps afm',
|
||||
'eps with usetex'
|
||||
])
|
||||
def test_savefig_to_stringio(format, use_log, rcParams, orientation,
|
||||
monkeypatch):
|
||||
mpl.rcParams.update(rcParams)
|
||||
monkeypatch.setenv("SOURCE_DATE_EPOCH", "0") # For reproducibility.
|
||||
|
||||
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])
|
||||
title = "Déjà vu"
|
||||
if not mpl.rcParams["text.usetex"]:
|
||||
title += " \N{MINUS SIGN}\N{EURO SIGN}"
|
||||
ax.set_title(title)
|
||||
allowable_exceptions = []
|
||||
if rcParams.get("ps.usedistiller"):
|
||||
allowable_exceptions.append(mpl.ExecutableNotFoundError)
|
||||
if rcParams.get("text.usetex"):
|
||||
allowable_exceptions.append(RuntimeError)
|
||||
try:
|
||||
fig.savefig(s_buf, format=format, orientation=orientation)
|
||||
fig.savefig(b_buf, format=format, orientation=orientation)
|
||||
except tuple(allowable_exceptions) as exc:
|
||||
pytest.skip(str(exc))
|
||||
|
||||
s_val = s_buf.getvalue().encode('ascii')
|
||||
b_val = b_buf.getvalue()
|
||||
|
||||
if rcParams.get("ps.usedistiller") or rcParams.get("text.usetex"):
|
||||
# Strip out CreationDate betcase ghostscript doesn't obey
|
||||
# SOURCE_DATE_EPOCH. Note that in usetex mode, we *always* call
|
||||
# gs_distill, even if ps.usedistiller is unset.
|
||||
s_val = re.sub(b"(?<=\n%%CreationDate: ).*", b"", s_val)
|
||||
b_val = re.sub(b"(?<=\n%%CreationDate: ).*", b"", b_val)
|
||||
|
||||
assert s_val == b_val.replace(b'\r\n', b'\n')
|
||||
|
||||
|
||||
def test_patheffects():
|
||||
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(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 ~
|
||||
mpl.rcParams['text.usetex'] = True
|
||||
plt.plot([1, 2, 3, 4])
|
||||
plt.xlabel(r'\textbf{time} (s)')
|
||||
# use the PS backend to write the file...
|
||||
plt.savefig(base_tempdir / 'tex_demo.eps', format="ps")
|
||||
|
||||
|
||||
@image_comparison(["empty.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():
|
||||
"""Test failing latex subprocess call"""
|
||||
mpl.rcParams['text.usetex'] = True
|
||||
# This fails with "Double subscript"
|
||||
plt.xlabel("$22_2_2$")
|
||||
with pytest.raises(RuntimeError):
|
||||
plt.savefig(io.BytesIO(), format="ps")
|
||||
|
||||
|
||||
@needs_usetex
|
||||
def test_partial_usetex(caplog):
|
||||
caplog.set_level("WARNING")
|
||||
plt.figtext(.5, .5, "foo", usetex=True)
|
||||
plt.savefig(io.BytesIO(), format="ps")
|
||||
assert caplog.records and all("as if usetex=False" in record.getMessage()
|
||||
for record in caplog.records)
|
297
venv/Lib/site-packages/matplotlib/tests/test_backend_qt.py
Normal file
297
venv/Lib/site-packages/matplotlib/tests/test_backend_qt.py
Normal file
|
@ -0,0 +1,297 @@
|
|||
import copy
|
||||
import signal
|
||||
from unittest import mock
|
||||
|
||||
import matplotlib
|
||||
from matplotlib import pyplot as plt
|
||||
from matplotlib._pylab_helpers import Gcf
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def qt_core(request):
|
||||
backend, = request.node.get_closest_marker('backend').args
|
||||
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')
|
||||
|
||||
return QtCore
|
||||
|
||||
|
||||
@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_core):
|
||||
# Create a figure
|
||||
plt.figure()
|
||||
|
||||
# Access signals
|
||||
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
|
||||
qt_core.QCoreApplication.exit()
|
||||
|
||||
# Timer to exit event loop
|
||||
qt_core.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}'),
|
||||
('Key_Control', ['AltModifier'], 'alt+control'),
|
||||
('Key_Alt', ['ControlModifier'], 'ctrl+alt'),
|
||||
('Key_Aacute', ['ControlModifier', 'AltModifier', 'MetaModifier'],
|
||||
'ctrl+alt+super+\N{LATIN SMALL LETTER A WITH ACUTE}'),
|
||||
('Key_Backspace', [], 'backspace'),
|
||||
('Key_Backspace', ['ControlModifier'], 'ctrl+backspace'),
|
||||
('Key_Play', [], None),
|
||||
],
|
||||
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_core, 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_mod = qt_core.Qt.NoModifier
|
||||
for mod in qt_mods:
|
||||
qt_mod |= getattr(qt_core.Qt, mod)
|
||||
|
||||
class _Event:
|
||||
def isAutoRepeat(self): return False
|
||||
def key(self): return getattr(qt_core.Qt, qt_key)
|
||||
def modifiers(self): return qt_mod
|
||||
|
||||
def on_key_press(event):
|
||||
assert event.key == answer
|
||||
|
||||
qt_canvas = plt.figure().canvas
|
||||
qt_canvas.mpl_connect('key_press_event', on_key_press)
|
||||
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()
|
||||
|
||||
p.return_value = 1.5
|
||||
|
||||
assert qt_canvas._dpi_ratio == 1.5
|
||||
|
||||
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 == 180
|
||||
assert qt_canvas.renderer.width == 900
|
||||
assert qt_canvas.renderer.height == 360
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
@pytest.mark.backend('Qt5Agg')
|
||||
def test_double_resize():
|
||||
# Check that resizing a figure twice keeps the same window size
|
||||
fig, ax = plt.subplots()
|
||||
fig.canvas.draw()
|
||||
window = fig.canvas.manager.window
|
||||
|
||||
w, h = 3, 2
|
||||
fig.set_size_inches(w, h)
|
||||
assert fig.canvas.width() == w * matplotlib.rcParams['figure.dpi']
|
||||
assert fig.canvas.height() == h * matplotlib.rcParams['figure.dpi']
|
||||
|
||||
old_width = window.width()
|
||||
old_height = window.height()
|
||||
|
||||
fig.set_size_inches(w, h)
|
||||
assert window.width() == old_width
|
||||
assert window.height() == old_height
|
||||
|
||||
|
||||
@pytest.mark.backend("Qt5Agg")
|
||||
def test_canvas_reinit():
|
||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
||||
|
||||
called = False
|
||||
|
||||
def crashing_callback(fig, stale):
|
||||
nonlocal called
|
||||
fig.canvas.draw_idle()
|
||||
called = True
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
fig.stale_callback = crashing_callback
|
||||
# this should not raise
|
||||
canvas = FigureCanvasQTAgg(fig)
|
||||
fig.stale = True
|
||||
assert called
|
398
venv/Lib/site-packages/matplotlib/tests/test_backend_svg.py
Normal file
398
venv/Lib/site-packages/matplotlib/tests/test_backend_svg.py
Normal file
|
@ -0,0 +1,398 @@
|
|||
import datetime
|
||||
from io import BytesIO
|
||||
import re
|
||||
import tempfile
|
||||
import xml.etree.ElementTree
|
||||
import xml.parsers.expat
|
||||
|
||||
import numpy as np
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg')
|
||||
buf = fd.getvalue()
|
||||
|
||||
parser = xml.parsers.expat.ParserCreate()
|
||||
parser.Parse(buf) # this will raise ExpatError if the svg is invalid
|
||||
|
||||
|
||||
@image_comparison(['fill_black_with_alpha.svg'], remove_text=True)
|
||||
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(['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)
|
||||
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg')
|
||||
buf = fd.getvalue().decode()
|
||||
|
||||
expected = '<a xlink:href="{0}">'.format(test_url)
|
||||
assert expected in buf
|
||||
|
||||
|
||||
@image_comparison(['bold_font_output.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(['bold_font_output_with_none_fonttype.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')
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
def test_svgnone_with_data_coordinates():
|
||||
plt.rcParams['svg.fonttype'] = 'none'
|
||||
expected = 'Unlikely to appear by chance'
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.text(np.datetime64('2019-06-30'), 1, expected)
|
||||
ax.set_xlim(np.datetime64('2019-01-01'), np.datetime64('2019-12-31'))
|
||||
ax.set_ylim(0, 2)
|
||||
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg')
|
||||
fd.seek(0)
|
||||
buf = fd.read().decode()
|
||||
|
||||
assert expected in buf
|
||||
for prop in ["family", "weight", "stretch", "style", "size"]:
|
||||
assert f"font-{prop}:" in buf
|
||||
|
||||
|
||||
def test_gid():
|
||||
"""Test that object gid appears in output svg."""
|
||||
from matplotlib.offsetbox import OffsetBox
|
||||
from matplotlib.axis import Tick
|
||||
|
||||
fig = plt.figure()
|
||||
|
||||
ax1 = fig.add_subplot(131)
|
||||
ax1.imshow([[1., 2.], [2., 3.]], aspect="auto")
|
||||
ax1.scatter([1, 2, 3], [1, 2, 3], label="myscatter")
|
||||
ax1.plot([2, 3, 1], label="myplot")
|
||||
ax1.legend()
|
||||
ax1a = ax1.twinx()
|
||||
ax1a.bar([1, 2, 3], [1, 2, 3])
|
||||
|
||||
ax2 = fig.add_subplot(132, projection="polar")
|
||||
ax2.plot([0, 1.5, 3], [1, 2, 3])
|
||||
|
||||
ax3 = fig.add_subplot(133, projection="3d")
|
||||
ax3.plot([1, 2], [1, 2], [1, 2])
|
||||
|
||||
fig.canvas.draw()
|
||||
|
||||
gdic = {}
|
||||
for idx, obj in enumerate(fig.findobj(include_self=True)):
|
||||
if obj.get_visible():
|
||||
gid = f"test123{obj.__class__.__name__}_{idx}"
|
||||
gdic[gid] = obj
|
||||
obj.set_gid(gid)
|
||||
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg')
|
||||
buf = fd.getvalue().decode()
|
||||
|
||||
def include(gid, obj):
|
||||
# we need to exclude certain objects which will not appear in the svg
|
||||
if isinstance(obj, OffsetBox):
|
||||
return False
|
||||
if isinstance(obj, plt.Text):
|
||||
if obj.get_text() == "":
|
||||
return False
|
||||
elif obj.axes is None:
|
||||
return False
|
||||
if isinstance(obj, plt.Line2D):
|
||||
xdata, ydata = obj.get_data()
|
||||
if len(xdata) == len(ydata) == 1:
|
||||
return False
|
||||
elif not hasattr(obj, "axes") or obj.axes is None:
|
||||
return False
|
||||
if isinstance(obj, Tick):
|
||||
loc = obj.get_loc()
|
||||
if loc == 0:
|
||||
return False
|
||||
vi = obj.get_view_interval()
|
||||
if loc < min(vi) or loc > max(vi):
|
||||
return False
|
||||
return True
|
||||
|
||||
for gid, obj in gdic.items():
|
||||
if include(gid, obj):
|
||||
assert gid in buf
|
||||
|
||||
|
||||
def test_savefig_tight():
|
||||
# Check that the draw-disabled renderer correctly disables open/close_group
|
||||
# as well.
|
||||
plt.savefig(BytesIO(), format="svgz", bbox_inches="tight")
|
||||
|
||||
|
||||
def test_url():
|
||||
# Test that object url appears in output svg.
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
|
||||
# collections
|
||||
s = ax.scatter([1, 2, 3], [4, 5, 6])
|
||||
s.set_urls(['http://example.com/foo', 'http://example.com/bar', None])
|
||||
|
||||
# Line2D
|
||||
p, = plt.plot([1, 3], [6, 5])
|
||||
p.set_url('http://example.com/baz')
|
||||
|
||||
b = BytesIO()
|
||||
fig.savefig(b, format='svg')
|
||||
b = b.getvalue()
|
||||
for v in [b'foo', b'bar', b'baz']:
|
||||
assert b'http://example.com/' + v in b
|
||||
|
||||
|
||||
def test_url_tick(monkeypatch):
|
||||
monkeypatch.setenv('SOURCE_DATE_EPOCH', '19680801')
|
||||
|
||||
fig1, ax = plt.subplots()
|
||||
ax.scatter([1, 2, 3], [4, 5, 6])
|
||||
for i, tick in enumerate(ax.yaxis.get_major_ticks()):
|
||||
tick.set_url(f'http://example.com/{i}')
|
||||
|
||||
fig2, ax = plt.subplots()
|
||||
ax.scatter([1, 2, 3], [4, 5, 6])
|
||||
for i, tick in enumerate(ax.yaxis.get_major_ticks()):
|
||||
tick.label1.set_url(f'http://example.com/{i}')
|
||||
tick.label2.set_url(f'http://example.com/{i}')
|
||||
|
||||
b1 = BytesIO()
|
||||
fig1.savefig(b1, format='svg')
|
||||
b1 = b1.getvalue()
|
||||
|
||||
b2 = BytesIO()
|
||||
fig2.savefig(b2, format='svg')
|
||||
b2 = b2.getvalue()
|
||||
|
||||
for i in range(len(ax.yaxis.get_major_ticks())):
|
||||
assert f'http://example.com/{i}'.encode('ascii') in b1
|
||||
assert b1 == b2
|
||||
|
||||
|
||||
def test_svg_default_metadata(monkeypatch):
|
||||
# Values have been predefined for 'Creator', 'Date', 'Format', and 'Type'.
|
||||
monkeypatch.setenv('SOURCE_DATE_EPOCH', '19680801')
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg')
|
||||
buf = fd.getvalue().decode()
|
||||
|
||||
# Creator
|
||||
assert mpl.__version__ in buf
|
||||
# Date
|
||||
assert '1970-08-16' in buf
|
||||
# Format
|
||||
assert 'image/svg+xml' in buf
|
||||
# Type
|
||||
assert 'StillImage' in buf
|
||||
|
||||
# Now make sure all the default metadata can be cleared.
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg', metadata={'Date': None, 'Creator': None,
|
||||
'Format': None, 'Type': None})
|
||||
buf = fd.getvalue().decode()
|
||||
|
||||
# Creator
|
||||
assert mpl.__version__ not in buf
|
||||
# Date
|
||||
assert '1970-08-16' not in buf
|
||||
# Format
|
||||
assert 'image/svg+xml' not in buf
|
||||
# Type
|
||||
assert 'StillImage' not in buf
|
||||
|
||||
|
||||
def test_svg_clear_default_metadata(monkeypatch):
|
||||
# Makes sure that setting a default metadata to `None`
|
||||
# removes the corresponding tag from the metadata.
|
||||
monkeypatch.setenv('SOURCE_DATE_EPOCH', '19680801')
|
||||
|
||||
metadata_contains = {'creator': mpl.__version__, 'date': '1970-08-16',
|
||||
'format': 'image/svg+xml', 'type': 'StillImage'}
|
||||
|
||||
SVGNS = '{http://www.w3.org/2000/svg}'
|
||||
RDFNS = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
|
||||
CCNS = '{http://creativecommons.org/ns#}'
|
||||
DCNS = '{http://purl.org/dc/elements/1.1/}'
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
for name in metadata_contains:
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg', metadata={name.title(): None})
|
||||
buf = fd.getvalue().decode()
|
||||
|
||||
root = xml.etree.ElementTree.fromstring(buf)
|
||||
work, = root.findall(f'./{SVGNS}metadata/{RDFNS}RDF/{CCNS}Work')
|
||||
for key in metadata_contains:
|
||||
data = work.findall(f'./{DCNS}{key}')
|
||||
if key == name:
|
||||
# The one we cleared is not there
|
||||
assert not data
|
||||
continue
|
||||
# Everything else should be there
|
||||
data, = data
|
||||
xmlstr = xml.etree.ElementTree.tostring(data, encoding="unicode")
|
||||
assert metadata_contains[key] in xmlstr
|
||||
|
||||
|
||||
def test_svg_clear_all_metadata():
|
||||
# Makes sure that setting all default metadata to `None`
|
||||
# removes the metadata tag from the output.
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg', metadata={'Date': None, 'Creator': None,
|
||||
'Format': None, 'Type': None})
|
||||
buf = fd.getvalue().decode()
|
||||
|
||||
SVGNS = '{http://www.w3.org/2000/svg}'
|
||||
|
||||
root = xml.etree.ElementTree.fromstring(buf)
|
||||
assert not root.findall(f'./{SVGNS}metadata')
|
||||
|
||||
|
||||
def test_svg_metadata():
|
||||
single_value = ['Coverage', 'Identifier', 'Language', 'Relation', 'Source',
|
||||
'Title', 'Type']
|
||||
multi_value = ['Contributor', 'Creator', 'Keywords', 'Publisher', 'Rights']
|
||||
metadata = {
|
||||
'Date': [datetime.date(1968, 8, 1),
|
||||
datetime.datetime(1968, 8, 2, 1, 2, 3)],
|
||||
'Description': 'description\ntext',
|
||||
**{k: f'{k} foo' for k in single_value},
|
||||
**{k: [f'{k} bar', f'{k} baz'] for k in multi_value},
|
||||
}
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
with BytesIO() as fd:
|
||||
fig.savefig(fd, format='svg', metadata=metadata)
|
||||
buf = fd.getvalue().decode()
|
||||
|
||||
SVGNS = '{http://www.w3.org/2000/svg}'
|
||||
RDFNS = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
|
||||
CCNS = '{http://creativecommons.org/ns#}'
|
||||
DCNS = '{http://purl.org/dc/elements/1.1/}'
|
||||
|
||||
root = xml.etree.ElementTree.fromstring(buf)
|
||||
rdf, = root.findall(f'./{SVGNS}metadata/{RDFNS}RDF')
|
||||
|
||||
# Check things that are single entries.
|
||||
titles = [node.text for node in root.findall(f'./{SVGNS}title')]
|
||||
assert titles == [metadata['Title']]
|
||||
types = [node.attrib[f'{RDFNS}resource']
|
||||
for node in rdf.findall(f'./{CCNS}Work/{DCNS}type')]
|
||||
assert types == [metadata['Type']]
|
||||
for k in ['Description', *single_value]:
|
||||
if k == 'Type':
|
||||
continue
|
||||
values = [node.text
|
||||
for node in rdf.findall(f'./{CCNS}Work/{DCNS}{k.lower()}')]
|
||||
assert values == [metadata[k]]
|
||||
|
||||
# Check things that are multi-value entries.
|
||||
for k in multi_value:
|
||||
if k == 'Keywords':
|
||||
continue
|
||||
values = [
|
||||
node.text
|
||||
for node in rdf.findall(
|
||||
f'./{CCNS}Work/{DCNS}{k.lower()}/{CCNS}Agent/{DCNS}title')]
|
||||
assert values == metadata[k]
|
||||
|
||||
# Check special things.
|
||||
dates = [node.text for node in rdf.findall(f'./{CCNS}Work/{DCNS}date')]
|
||||
assert dates == ['1968-08-01/1968-08-02T01:02:03']
|
||||
|
||||
values = [node.text for node in
|
||||
rdf.findall(f'./{CCNS}Work/{DCNS}subject/{RDFNS}Bag/{RDFNS}li')]
|
||||
assert values == metadata['Keywords']
|
41
venv/Lib/site-packages/matplotlib/tests/test_backend_tk.py
Normal file
41
venv/Lib/site-packages/matplotlib/tests/test_backend_tk.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
import pytest
|
||||
import numpy as np
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
|
||||
@pytest.mark.backend('TkAgg', skip_on_importerror=True)
|
||||
def test_blit():
|
||||
from matplotlib.backends import _tkagg
|
||||
def evil_blit(photoimage, aggimage, offsets, bboxptr):
|
||||
data = np.asarray(aggimage)
|
||||
height, width = data.shape[:2]
|
||||
dataptr = (height, width, data.ctypes.data)
|
||||
_tkagg.blit(
|
||||
photoimage.tk.interpaddr(), str(photoimage), dataptr, offsets,
|
||||
bboxptr)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
for bad_boxes in ((-1, 2, 0, 2),
|
||||
(2, 0, 0, 2),
|
||||
(1, 6, 0, 2),
|
||||
(0, 2, -1, 2),
|
||||
(0, 2, 2, 0),
|
||||
(0, 2, 1, 6)):
|
||||
with pytest.raises(ValueError):
|
||||
evil_blit(fig.canvas._tkphoto,
|
||||
np.ones((4, 4, 4)),
|
||||
(0, 1, 2, 3),
|
||||
bad_boxes)
|
||||
|
||||
|
||||
@pytest.mark.backend('TkAgg', skip_on_importerror=True)
|
||||
def test_missing_back_button():
|
||||
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
|
||||
class Toolbar(NavigationToolbar2Tk):
|
||||
# only display the buttons we need
|
||||
toolitems = [t for t in NavigationToolbar2Tk.toolitems if
|
||||
t[0] in ('Home', 'Pan', 'Zoom')]
|
||||
|
||||
fig = plt.figure()
|
||||
# this should not raise
|
||||
Toolbar(fig.canvas, fig.canvas.manager.window)
|
|
@ -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,27 @@
|
|||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("backend", ["webagg", "nbagg"])
|
||||
def test_webagg_fallback(backend):
|
||||
pytest.importorskip("tornado")
|
||||
if backend == "nbagg":
|
||||
pytest.importorskip("IPython")
|
||||
env = dict(os.environ)
|
||||
if os.name != "nt":
|
||||
env["DISPLAY"] = ""
|
||||
|
||||
env["MPLBACKEND"] = backend
|
||||
|
||||
test_code = (
|
||||
"import os;"
|
||||
+ f"assert os.environ['MPLBACKEND'] == '{backend}';"
|
||||
+ "import matplotlib.pyplot as plt; "
|
||||
+ "print(plt.get_backend());"
|
||||
f"assert '{backend}' == plt.get_backend().lower();"
|
||||
)
|
||||
ret = subprocess.call([sys.executable, "-c", test_code], env=env)
|
||||
|
||||
assert ret == 0
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue