123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- import io
- from itertools import chain
- import numpy as np
- import pytest
- import matplotlib.colors as mcolors
- 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
- import matplotlib.backend_bases as mbackend_bases
- import matplotlib as mpl
- from matplotlib.testing.decorators import check_figures_equal, 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.get_offset_transform(), 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
- fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, sharey=True)
- 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)
- 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])
- @check_figures_equal(extensions=['png'])
- def test_clipping_zoom(fig_test, fig_ref):
- # This test places the Axes and sets its limits such that the clip path is
- # outside the figure entirely. This should not break the clip path.
- ax_test = fig_test.add_axes([0, 0, 1, 1])
- l, = ax_test.plot([-3, 3], [-3, 3])
- # Explicit Path instead of a Rectangle uses clip path processing, instead
- # of a clip box optimization.
- p = mpath.Path([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
- p = mpatches.PathPatch(p, transform=ax_test.transData)
- l.set_clip_path(p)
- ax_ref = fig_ref.add_axes([0, 0, 1, 1])
- ax_ref.plot([-3, 3], [-3, 3])
- ax_ref.set(xlim=(0.5, 0.75), ylim=(0.5, 0.75))
- ax_test.set(xlim=(0.5, 0.75), ylim=(0.5, 0.75))
- 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)], offset_transform=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)], offset_transform=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"}
- def test_set_alpha():
- art = martist.Artist()
- with pytest.raises(TypeError, match='^alpha must be numeric or None'):
- art.set_alpha('string')
- with pytest.raises(TypeError, match='^alpha must be numeric or None'):
- art.set_alpha([1, 2, 3])
- with pytest.raises(ValueError, match="outside 0-1 range"):
- art.set_alpha(1.1)
- with pytest.raises(ValueError, match="outside 0-1 range"):
- art.set_alpha(np.nan)
- def test_set_alpha_for_array():
- art = martist.Artist()
- with pytest.raises(TypeError, match='^alpha must be numeric or None'):
- art._set_alpha_for_array('string')
- with pytest.raises(ValueError, match="outside 0-1 range"):
- art._set_alpha_for_array(1.1)
- with pytest.raises(ValueError, match="outside 0-1 range"):
- art._set_alpha_for_array(np.nan)
- with pytest.raises(ValueError, match="alpha must be between 0 and 1"):
- art._set_alpha_for_array([0.5, 1.1])
- with pytest.raises(ValueError, match="alpha must be between 0 and 1"):
- art._set_alpha_for_array([0.5, np.nan])
- def test_callbacks():
- def func(artist):
- func.counter += 1
- func.counter = 0
- art = martist.Artist()
- oid = art.add_callback(func)
- assert func.counter == 0
- art.pchanged() # must call the callback
- assert func.counter == 1
- art.set_zorder(10) # setting a property must also call the callback
- assert func.counter == 2
- art.remove_callback(oid)
- art.pchanged() # must not call the callback anymore
- assert func.counter == 2
- def test_set_signature():
- """Test autogenerated ``set()`` for Artist subclasses."""
- class MyArtist1(martist.Artist):
- def set_myparam1(self, val):
- pass
- assert hasattr(MyArtist1.set, '_autogenerated_signature')
- assert 'myparam1' in MyArtist1.set.__doc__
- class MyArtist2(MyArtist1):
- def set_myparam2(self, val):
- pass
- assert hasattr(MyArtist2.set, '_autogenerated_signature')
- assert 'myparam1' in MyArtist2.set.__doc__
- assert 'myparam2' in MyArtist2.set.__doc__
- def test_set_is_overwritten():
- """set() defined in Artist subclasses should not be overwritten."""
- class MyArtist3(martist.Artist):
- def set(self, **kwargs):
- """Not overwritten."""
- assert not hasattr(MyArtist3.set, '_autogenerated_signature')
- assert MyArtist3.set.__doc__ == "Not overwritten."
- class MyArtist4(MyArtist3):
- pass
- assert MyArtist4.set is MyArtist3.set
- def test_format_cursor_data_BoundaryNorm():
- """Test if cursor data is correct when using BoundaryNorm."""
- X = np.empty((3, 3))
- X[0, 0] = 0.9
- X[0, 1] = 0.99
- X[0, 2] = 0.999
- X[1, 0] = -1
- X[1, 1] = 0
- X[1, 2] = 1
- X[2, 0] = 0.09
- X[2, 1] = 0.009
- X[2, 2] = 0.0009
- # map range -1..1 to 0..256 in 0.1 steps
- fig, ax = plt.subplots()
- fig.suptitle("-1..1 to 0..256 in 0.1")
- norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 20), 256)
- img = ax.imshow(X, cmap='RdBu_r', norm=norm)
- labels_list = [
- "[0.9]",
- "[1.]",
- "[1.]",
- "[-1.0]",
- "[0.0]",
- "[1.0]",
- "[0.09]",
- "[0.009]",
- "[0.0009]",
- ]
- for v, label in zip(X.flat, labels_list):
- # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.1))
- assert img.format_cursor_data(v) == label
- plt.close()
- # map range -1..1 to 0..256 in 0.01 steps
- fig, ax = plt.subplots()
- fig.suptitle("-1..1 to 0..256 in 0.01")
- cmap = mpl.colormaps['RdBu_r'].resampled(200)
- norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 200), 200)
- img = ax.imshow(X, cmap=cmap, norm=norm)
- labels_list = [
- "[0.90]",
- "[0.99]",
- "[1.0]",
- "[-1.00]",
- "[0.00]",
- "[1.00]",
- "[0.09]",
- "[0.009]",
- "[0.0009]",
- ]
- for v, label in zip(X.flat, labels_list):
- # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.01))
- assert img.format_cursor_data(v) == label
- plt.close()
- # map range -1..1 to 0..256 in 0.01 steps
- fig, ax = plt.subplots()
- fig.suptitle("-1..1 to 0..256 in 0.001")
- cmap = mpl.colormaps['RdBu_r'].resampled(2000)
- norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 2000), 2000)
- img = ax.imshow(X, cmap=cmap, norm=norm)
- labels_list = [
- "[0.900]",
- "[0.990]",
- "[0.999]",
- "[-1.000]",
- "[0.000]",
- "[1.000]",
- "[0.090]",
- "[0.009]",
- "[0.0009]",
- ]
- for v, label in zip(X.flat, labels_list):
- # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.001))
- assert img.format_cursor_data(v) == label
- plt.close()
- # different testing data set with
- # out of bounds values for 0..1 range
- X = np.empty((7, 1))
- X[0] = -1.0
- X[1] = 0.0
- X[2] = 0.1
- X[3] = 0.5
- X[4] = 0.9
- X[5] = 1.0
- X[6] = 2.0
- labels_list = [
- "[-1.0]",
- "[0.0]",
- "[0.1]",
- "[0.5]",
- "[0.9]",
- "[1.0]",
- "[2.0]",
- ]
- fig, ax = plt.subplots()
- fig.suptitle("noclip, neither")
- norm = mcolors.BoundaryNorm(
- np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='neither')
- img = ax.imshow(X, cmap='RdBu_r', norm=norm)
- for v, label in zip(X.flat, labels_list):
- # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
- assert img.format_cursor_data(v) == label
- plt.close()
- fig, ax = plt.subplots()
- fig.suptitle("noclip, min")
- norm = mcolors.BoundaryNorm(
- np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='min')
- img = ax.imshow(X, cmap='RdBu_r', norm=norm)
- for v, label in zip(X.flat, labels_list):
- # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
- assert img.format_cursor_data(v) == label
- plt.close()
- fig, ax = plt.subplots()
- fig.suptitle("noclip, max")
- norm = mcolors.BoundaryNorm(
- np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='max')
- img = ax.imshow(X, cmap='RdBu_r', norm=norm)
- for v, label in zip(X.flat, labels_list):
- # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
- assert img.format_cursor_data(v) == label
- plt.close()
- fig, ax = plt.subplots()
- fig.suptitle("noclip, both")
- norm = mcolors.BoundaryNorm(
- np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='both')
- img = ax.imshow(X, cmap='RdBu_r', norm=norm)
- for v, label in zip(X.flat, labels_list):
- # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
- assert img.format_cursor_data(v) == label
- plt.close()
- fig, ax = plt.subplots()
- fig.suptitle("clip, neither")
- norm = mcolors.BoundaryNorm(
- np.linspace(0, 1, 4, endpoint=True), 256, clip=True, extend='neither')
- img = ax.imshow(X, cmap='RdBu_r', norm=norm)
- for v, label in zip(X.flat, labels_list):
- # label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
- assert img.format_cursor_data(v) == label
- plt.close()
- def test_auto_no_rasterize():
- class Gen1(martist.Artist):
- ...
- assert 'draw' in Gen1.__dict__
- assert Gen1.__dict__['draw'] is Gen1.draw
- class Gen2(Gen1):
- ...
- assert 'draw' not in Gen2.__dict__
- assert Gen2.draw is Gen1.draw
- def test_draw_wraper_forward_input():
- class TestKlass(martist.Artist):
- def draw(self, renderer, extra):
- return extra
- art = TestKlass()
- renderer = mbackend_bases.RendererBase()
- assert 'aardvark' == art.draw(renderer, 'aardvark')
- assert 'aardvark' == art.draw(renderer, extra='aardvark')
|