363 lines
12 KiB
Python
363 lines
12 KiB
Python
import copy
|
|
|
|
import numpy as np
|
|
|
|
from numpy.testing import assert_array_equal
|
|
import pytest
|
|
|
|
from matplotlib import patches
|
|
from matplotlib.path import Path
|
|
from matplotlib.patches import Polygon
|
|
from matplotlib.testing.decorators import image_comparison
|
|
import matplotlib.pyplot as plt
|
|
from matplotlib import transforms
|
|
from matplotlib.backend_bases import MouseEvent
|
|
|
|
|
|
def test_empty_closed_path():
|
|
path = Path(np.zeros((0, 2)), closed=True)
|
|
assert path.vertices.shape == (0, 2)
|
|
assert path.codes is None
|
|
|
|
|
|
def test_readonly_path():
|
|
path = Path.unit_circle()
|
|
|
|
def modify_vertices():
|
|
path.vertices = path.vertices * 2.0
|
|
|
|
with pytest.raises(AttributeError):
|
|
modify_vertices()
|
|
|
|
|
|
def test_point_in_path():
|
|
# Test #1787
|
|
verts2 = [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]
|
|
|
|
path = Path(verts2, closed=True)
|
|
points = [(0.5, 0.5), (1.5, 0.5)]
|
|
ret = path.contains_points(points)
|
|
assert ret.dtype == 'bool'
|
|
assert np.all(ret == [True, False])
|
|
|
|
|
|
def test_contains_points_negative_radius():
|
|
path = Path.unit_circle()
|
|
|
|
points = [(0.0, 0.0), (1.25, 0.0), (0.9, 0.9)]
|
|
expected = [True, False, False]
|
|
result = path.contains_points(points, radius=-0.5)
|
|
|
|
assert np.all(result == expected)
|
|
|
|
|
|
def test_point_in_path_nan():
|
|
box = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
|
|
p = Path(box)
|
|
test = np.array([[np.nan, 0.5]])
|
|
contains = p.contains_points(test)
|
|
assert len(contains) == 1
|
|
assert not contains[0]
|
|
|
|
|
|
def test_nonlinear_containment():
|
|
fig, ax = plt.subplots()
|
|
ax.set(xscale="log", ylim=(0, 1))
|
|
polygon = ax.axvspan(1, 10)
|
|
assert polygon.get_path().contains_point(
|
|
ax.transData.transform_point((5, .5)), ax.transData)
|
|
assert not polygon.get_path().contains_point(
|
|
ax.transData.transform_point((.5, .5)), ax.transData)
|
|
assert not polygon.get_path().contains_point(
|
|
ax.transData.transform_point((50, .5)), ax.transData)
|
|
|
|
|
|
@image_comparison(
|
|
baseline_images=['arrow_contains_point'], extensions=['png'],
|
|
remove_text=True, style='mpl20')
|
|
def test_arrow_contains_point():
|
|
# fix bug (#8384)
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim((0, 2))
|
|
ax.set_ylim((0, 2))
|
|
|
|
# create an arrow with Curve style
|
|
arrow = patches.FancyArrowPatch((0.5, 0.25), (1.5, 0.75),
|
|
arrowstyle='->',
|
|
mutation_scale=40)
|
|
ax.add_patch(arrow)
|
|
# create an arrow with Bracket style
|
|
arrow1 = patches.FancyArrowPatch((0.5, 1), (1.5, 1.25),
|
|
arrowstyle=']-[',
|
|
mutation_scale=40)
|
|
ax.add_patch(arrow1)
|
|
# create an arrow with other arrow style
|
|
arrow2 = patches.FancyArrowPatch((0.5, 1.5), (1.5, 1.75),
|
|
arrowstyle='fancy',
|
|
fill=False,
|
|
mutation_scale=40)
|
|
ax.add_patch(arrow2)
|
|
patches_list = [arrow, arrow1, arrow2]
|
|
|
|
# generate some points
|
|
X, Y = np.meshgrid(np.arange(0, 2, 0.1),
|
|
np.arange(0, 2, 0.1))
|
|
for k, (x, y) in enumerate(zip(X.ravel(), Y.ravel())):
|
|
xdisp, ydisp = ax.transData.transform_point([x, y])
|
|
event = MouseEvent('button_press_event', fig.canvas, xdisp, ydisp)
|
|
for m, patch in enumerate(patches_list):
|
|
# set the points to red only if the arrow contains the point
|
|
inside, res = patch.contains(event)
|
|
if inside:
|
|
ax.scatter(x, y, s=5, c="r")
|
|
|
|
|
|
@image_comparison(baseline_images=['path_clipping'],
|
|
extensions=['svg'], remove_text=True)
|
|
def test_path_clipping():
|
|
fig = plt.figure(figsize=(6.0, 6.2))
|
|
|
|
for i, xy in enumerate([
|
|
[(200, 200), (200, 350), (400, 350), (400, 200)],
|
|
[(200, 200), (200, 350), (400, 350), (400, 100)],
|
|
[(200, 100), (200, 350), (400, 350), (400, 100)],
|
|
[(200, 100), (200, 415), (400, 350), (400, 100)],
|
|
[(200, 100), (200, 415), (400, 415), (400, 100)],
|
|
[(200, 415), (400, 415), (400, 100), (200, 100)],
|
|
[(400, 415), (400, 100), (200, 100), (200, 415)]]):
|
|
ax = fig.add_subplot(4, 2, i+1)
|
|
bbox = [0, 140, 640, 260]
|
|
ax.set_xlim(bbox[0], bbox[0] + bbox[2])
|
|
ax.set_ylim(bbox[1], bbox[1] + bbox[3])
|
|
ax.add_patch(Polygon(
|
|
xy, facecolor='none', edgecolor='red', closed=True))
|
|
|
|
|
|
@image_comparison(baseline_images=['semi_log_with_zero'], extensions=['png'],
|
|
style='mpl20')
|
|
def test_log_transform_with_zero():
|
|
x = np.arange(-10, 10)
|
|
y = (1.0 - 1.0/(x**2+1))**20
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.semilogy(x, y, "-o", lw=15, markeredgecolor='k')
|
|
ax.set_ylim(1e-7, 1)
|
|
ax.grid(True)
|
|
|
|
|
|
def test_make_compound_path_empty():
|
|
# We should be able to make a compound path with no arguments.
|
|
# This makes it easier to write generic path based code.
|
|
r = Path.make_compound_path()
|
|
assert r.vertices.shape == (0, 2)
|
|
|
|
|
|
@image_comparison(baseline_images=['xkcd'], extensions=['png'],
|
|
remove_text=True)
|
|
def test_xkcd():
|
|
np.random.seed(0)
|
|
|
|
x = np.linspace(0, 2 * np.pi, 100)
|
|
y = np.sin(x)
|
|
|
|
with plt.xkcd():
|
|
fig, ax = plt.subplots()
|
|
ax.plot(x, y)
|
|
|
|
|
|
@image_comparison(baseline_images=['xkcd_marker'], extensions=['png'],
|
|
remove_text=True)
|
|
def test_xkcd_marker():
|
|
np.random.seed(0)
|
|
|
|
x = np.linspace(0, 5, 8)
|
|
y1 = x
|
|
y2 = 5 - x
|
|
y3 = 2.5 * np.ones(8)
|
|
|
|
with plt.xkcd():
|
|
fig, ax = plt.subplots()
|
|
ax.plot(x, y1, '+', ms=10)
|
|
ax.plot(x, y2, 'o', ms=10)
|
|
ax.plot(x, y3, '^', ms=10)
|
|
|
|
|
|
@image_comparison(baseline_images=['marker_paths'], extensions=['pdf'],
|
|
remove_text=True)
|
|
def test_marker_paths_pdf():
|
|
N = 7
|
|
|
|
plt.errorbar(np.arange(N),
|
|
np.ones(N) + 4,
|
|
np.ones(N))
|
|
plt.xlim(-1, N)
|
|
plt.ylim(-1, 7)
|
|
|
|
|
|
@image_comparison(baseline_images=['nan_path'], style='default',
|
|
remove_text=True, extensions=['pdf', 'svg', 'eps', 'png'])
|
|
def test_nan_isolated_points():
|
|
|
|
y0 = [0, np.nan, 2, np.nan, 4, 5, 6]
|
|
y1 = [np.nan, 7, np.nan, 9, 10, np.nan, 12]
|
|
|
|
fig, ax = plt.subplots()
|
|
|
|
ax.plot(y0, '-o')
|
|
ax.plot(y1, '-o')
|
|
|
|
|
|
def test_path_no_doubled_point_in_to_polygon():
|
|
hand = np.array(
|
|
[[1.64516129, 1.16145833],
|
|
[1.64516129, 1.59375],
|
|
[1.35080645, 1.921875],
|
|
[1.375, 2.18229167],
|
|
[1.68548387, 1.9375],
|
|
[1.60887097, 2.55208333],
|
|
[1.68548387, 2.69791667],
|
|
[1.76209677, 2.56770833],
|
|
[1.83064516, 1.97395833],
|
|
[1.89516129, 2.75],
|
|
[1.9516129, 2.84895833],
|
|
[2.01209677, 2.76041667],
|
|
[1.99193548, 1.99479167],
|
|
[2.11290323, 2.63020833],
|
|
[2.2016129, 2.734375],
|
|
[2.25403226, 2.60416667],
|
|
[2.14919355, 1.953125],
|
|
[2.30645161, 2.36979167],
|
|
[2.39112903, 2.36979167],
|
|
[2.41532258, 2.1875],
|
|
[2.1733871, 1.703125],
|
|
[2.07782258, 1.16666667]])
|
|
|
|
(r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5)
|
|
|
|
poly = Path(np.vstack((hand[:, 1], hand[:, 0])).T, closed=True)
|
|
clip_rect = transforms.Bbox([[r0, c0], [r1, c1]])
|
|
poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]
|
|
|
|
assert np.all(poly_clipped[-2] != poly_clipped[-1])
|
|
assert np.all(poly_clipped[-1] == poly_clipped[0])
|
|
|
|
|
|
def test_path_to_polygons():
|
|
data = [[10, 10], [20, 20]]
|
|
p = Path(data)
|
|
|
|
assert_array_equal(p.to_polygons(width=40, height=40), [])
|
|
assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
|
|
[data])
|
|
assert_array_equal(p.to_polygons(), [])
|
|
assert_array_equal(p.to_polygons(closed_only=False), [data])
|
|
|
|
data = [[10, 10], [20, 20], [30, 30]]
|
|
closed_data = [[10, 10], [20, 20], [30, 30], [10, 10]]
|
|
p = Path(data)
|
|
|
|
assert_array_equal(p.to_polygons(width=40, height=40), [closed_data])
|
|
assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
|
|
[data])
|
|
assert_array_equal(p.to_polygons(), [closed_data])
|
|
assert_array_equal(p.to_polygons(closed_only=False), [data])
|
|
|
|
|
|
def test_path_deepcopy():
|
|
# Should not raise any error
|
|
verts = [[0, 0], [1, 1]]
|
|
codes = [Path.MOVETO, Path.LINETO]
|
|
path1 = Path(verts)
|
|
path2 = Path(verts, codes)
|
|
copy.deepcopy(path1)
|
|
copy.deepcopy(path2)
|
|
|
|
|
|
def test_path_intersect_path():
|
|
# test for the range of intersection angles
|
|
base_angles = np.array([0, 15, 30, 45, 60, 75, 90, 105, 120, 135])
|
|
angles = np.concatenate([base_angles, base_angles + 1, base_angles - 1])
|
|
eps_array = [1e-5, 1e-8, 1e-10, 1e-12]
|
|
|
|
for phi in angles:
|
|
|
|
transform = transforms.Affine2D().rotate(np.deg2rad(phi))
|
|
|
|
# a and b intersect at angle phi
|
|
a = Path([(-2, 0), (2, 0)])
|
|
b = transform.transform_path(a)
|
|
assert a.intersects_path(b) and b.intersects_path(a)
|
|
|
|
# a and b touch at angle phi at (0, 0)
|
|
a = Path([(0, 0), (2, 0)])
|
|
b = transform.transform_path(a)
|
|
assert a.intersects_path(b) and b.intersects_path(a)
|
|
|
|
# a and b are orthogonal and intersect at (0, 3)
|
|
a = transform.transform_path(Path([(0, 1), (0, 3)]))
|
|
b = transform.transform_path(Path([(1, 3), (0, 3)]))
|
|
assert a.intersects_path(b) and b.intersects_path(a)
|
|
|
|
# a and b are collinear and intersect at (0, 3)
|
|
a = transform.transform_path(Path([(0, 1), (0, 3)]))
|
|
b = transform.transform_path(Path([(0, 5), (0, 3)]))
|
|
assert a.intersects_path(b) and b.intersects_path(a)
|
|
|
|
# self-intersect
|
|
assert a.intersects_path(a)
|
|
|
|
# a contains b
|
|
a = transform.transform_path(Path([(0, 0), (5, 5)]))
|
|
b = transform.transform_path(Path([(1, 1), (3, 3)]))
|
|
assert a.intersects_path(b) and b.intersects_path(a)
|
|
|
|
# a and b are collinear but do not intersect
|
|
a = transform.transform_path(Path([(0, 1), (0, 5)]))
|
|
b = transform.transform_path(Path([(3, 0), (3, 3)]))
|
|
assert not a.intersects_path(b) and not b.intersects_path(a)
|
|
|
|
# a and b are on the same line but do not intersect
|
|
a = transform.transform_path(Path([(0, 1), (0, 5)]))
|
|
b = transform.transform_path(Path([(0, 6), (0, 7)]))
|
|
assert not a.intersects_path(b) and not b.intersects_path(a)
|
|
|
|
# Note: 1e-13 is the absolute tolerance error used for
|
|
# `isclose` function from src/_path.h
|
|
|
|
# a and b are parallel but do not touch
|
|
for eps in eps_array:
|
|
a = transform.transform_path(Path([(0, 1), (0, 5)]))
|
|
b = transform.transform_path(Path([(0 + eps, 1), (0 + eps, 5)]))
|
|
assert not a.intersects_path(b) and not b.intersects_path(a)
|
|
|
|
# a and b are on the same line but do not intersect (really close)
|
|
for eps in eps_array:
|
|
a = transform.transform_path(Path([(0, 1), (0, 5)]))
|
|
b = transform.transform_path(Path([(0, 5 + eps), (0, 7)]))
|
|
assert not a.intersects_path(b) and not b.intersects_path(a)
|
|
|
|
# a and b are on the same line and intersect (really close)
|
|
for eps in eps_array:
|
|
a = transform.transform_path(Path([(0, 1), (0, 5)]))
|
|
b = transform.transform_path(Path([(0, 5 - eps), (0, 7)]))
|
|
assert a.intersects_path(b) and b.intersects_path(a)
|
|
|
|
# b is the same as a but with an extra point
|
|
a = transform.transform_path(Path([(0, 1), (0, 5)]))
|
|
b = transform.transform_path(Path([(0, 1), (0, 2), (0, 5)]))
|
|
assert a.intersects_path(b) and b.intersects_path(a)
|
|
|
|
return
|
|
|
|
|
|
@pytest.mark.parametrize('offset', range(-720, 361, 45))
|
|
def test_full_arc(offset):
|
|
low = offset
|
|
high = 360 + offset
|
|
|
|
path = Path.arc(low, high)
|
|
mins = np.min(path.vertices, axis=0)
|
|
maxs = np.max(path.vertices, axis=0)
|
|
np.testing.assert_allclose(mins, -1)
|
|
assert np.allclose(maxs, 1)
|