diff --git a/examples/animation/animated_histogram.py b/examples/animation/animated_histogram.py index 2df4def04354..42d314ab380d 100644 --- a/examples/animation/animated_histogram.py +++ b/examples/animation/animated_histogram.py @@ -45,7 +45,7 @@ # in the ``verts`` array to keep the codes aligned with the vertices. nverts = nrects * (1 + 3 + 1) verts = np.zeros((nverts, 2)) -codes = np.ones(nverts, int) * path.Path.LINETO +codes = np.full(nverts, path.Path.LINETO) codes[0::5] = path.Path.MOVETO codes[4::5] = path.Path.CLOSEPOLY verts[0::5, 0] = left diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 72a9c782e6a4..d144634783ea 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3957,17 +3957,11 @@ def line_props_with_rcdefaults(subkey, explicit, zdelta=0, if meanprops is None or removed_prop not in meanprops: final_meanprops[removed_prop] = '' - def to_vc(xs, ys): - # convert arguments to verts and codes, append (0, 0) (ignored). - verts = np.append(np.column_stack([xs, ys]), [(0, 0)], 0) - codes = ([mpath.Path.MOVETO] - + [mpath.Path.LINETO] * (len(verts) - 2) - + [mpath.Path.CLOSEPOLY]) - return verts, codes - def patch_list(xs, ys, **kwargs): - verts, codes = to_vc(xs, ys) - path = mpath.Path(verts, codes) + path = mpath.Path( + # Last vertex will have a CLOSEPOLY code and thus be ignored. + np.append(np.column_stack([xs, ys]), [(0, 0)], 0), + closed=True) patch = mpatches.PathPatch(path, **kwargs) self.add_artist(patch) return [patch] diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 74b99bf98ebc..7b5c196d9591 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -1098,15 +1098,10 @@ def set_verts(self, verts, closed=True): for xy in verts: if len(xy): if isinstance(xy, np.ma.MaskedArray): - xy = np.ma.concatenate([xy, xy[0:1]]) + xy = np.ma.concatenate([xy, xy[:1]]) else: - xy = np.asarray(xy) - xy = np.concatenate([xy, xy[0:1]]) - codes = np.empty(xy.shape[0], dtype=mpath.Path.code_type) - codes[:] = mpath.Path.LINETO - codes[0] = mpath.Path.MOVETO - codes[-1] = mpath.Path.CLOSEPOLY - self._paths.append(mpath.Path(xy, codes)) + xy = np.concatenate([xy, xy[:1]]) + self._paths.append(mpath.Path(xy, closed=True)) else: self._paths.append(mpath.Path(xy)) else: diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index bab0d4a600b8..c170ae5ce84a 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -410,23 +410,15 @@ def _set_pixel(self): def _set_point(self): self._set_circle(reduction=self._point_size_reduction) - _triangle_path = Path( - [[0.0, 1.0], [-1.0, -1.0], [1.0, -1.0], [0.0, 1.0]], - [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) + _triangle_path = Path([[0, 1], [-1, -1], [1, -1], [0, 1]], closed=True) # Going down halfway looks to small. Golden ratio is too far. - _triangle_path_u = Path( - [[0.0, 1.0], [-3 / 5., -1 / 5.], [3 / 5., -1 / 5.], [0.0, 1.0]], - [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) + _triangle_path_u = Path([[0, 1], [-3/5, -1/5], [3/5, -1/5], [0, 1]], + closed=True) _triangle_path_d = Path( - [[-3 / 5., -1 / 5.], [3 / 5., -1 / 5.], [1.0, -1.0], [-1.0, -1.0], - [-3 / 5., -1 / 5.]], - [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) - _triangle_path_l = Path( - [[0.0, 1.0], [0.0, -1.0], [-1.0, -1.0], [0.0, 1.0]], - [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) - _triangle_path_r = Path( - [[0.0, 1.0], [0.0, -1.0], [1.0, -1.0], [0.0, 1.0]], - [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) + [[-3/5, -1/5], [3/5, -1/5], [1, -1], [-1, -1], [-3/5, -1/5]], + closed=True) + _triangle_path_l = Path([[0, 1], [0, -1], [-1, -1], [0, 1]], closed=True) + _triangle_path_r = Path([[0, 1], [0, -1], [1, -1], [0, 1]], closed=True) def _set_triangle(self, rot, skip): self._transform = Affine2D().scale(0.5).rotate_deg(rot) @@ -505,10 +497,8 @@ def _set_diamond(self): if not self._half_fill(): self._path = Path.unit_rectangle() else: - self._path = Path([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) - self._alt_path = Path([[0.0, 0.0], [0.0, 1.0], - [1.0, 1.0], [0.0, 0.0]]) - + self._path = Path([[0, 0], [1, 0], [1, 1], [0, 0]]) + self._alt_path = Path([[0, 0], [0, 1], [1, 1], [0, 0]]) if fs == 'bottom': rotate = 270. elif fs == 'top': @@ -517,10 +507,8 @@ def _set_diamond(self): rotate = 180. else: rotate = 0. - self._transform.rotate_deg(rotate) self._alt_transform = self._transform - self._joinstyle = 'miter' def _set_thin_diamond(self): @@ -815,24 +803,13 @@ def _set_x(self): self._filled = False self._path = self._x_path - _plus_filled_path = Path([(1/3, 0), (2/3, 0), (2/3, 1/3), - (1, 1/3), (1, 2/3), (2/3, 2/3), - (2/3, 1), (1/3, 1), (1/3, 2/3), - (0, 2/3), (0, 1/3), (1/3, 1/3), - (1/3, 0)], - [Path.MOVETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, Path.LINETO, - Path.CLOSEPOLY]) - - _plus_filled_path_t = Path([(1, 1/2), (1, 2/3), (2/3, 2/3), - (2/3, 1), (1/3, 1), (1/3, 2/3), - (0, 2/3), (0, 1/2), (1, 1/2)], - [Path.MOVETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, - Path.CLOSEPOLY]) + _plus_filled_path = Path( + [(1/3, 0), (2/3, 0), (2/3, 1/3), (1, 1/3), (1, 2/3), (2/3, 2/3), + (2/3, 1), (1/3, 1), (1/3, 2/3), (0, 2/3), (0, 1/3), (1/3, 1/3), + (1/3, 0)], closed=True) + _plus_filled_path_t = Path( + [(1, 1/2), (1, 2/3), (2/3, 2/3), (2/3, 1), (1/3, 1), (1/3, 2/3), + (0, 2/3), (0, 1/2), (1, 1/2)], closed=True) def _set_plus_filled(self): self._transform = Affine2D().translate(-0.5, -0.5) @@ -858,22 +835,13 @@ def _set_plus_filled(self): self._transform.rotate_deg(rotate) self._alt_transform.rotate_deg(rotate_alt) - _x_filled_path = Path([(0.25, 0), (0.5, 0.25), (0.75, 0), (1, 0.25), - (0.75, 0.5), (1, 0.75), (0.75, 1), (0.5, 0.75), - (0.25, 1), (0, 0.75), (0.25, 0.5), (0, 0.25), - (0.25, 0)], - [Path.MOVETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, Path.LINETO, - Path.CLOSEPOLY]) - - _x_filled_path_t = Path([(0.75, 0.5), (1, 0.75), (0.75, 1), - (0.5, 0.75), (0.25, 1), (0, 0.75), - (0.25, 0.5), (0.75, 0.5)], - [Path.MOVETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.CLOSEPOLY]) + _x_filled_path = Path( + [(0.25, 0), (0.5, 0.25), (0.75, 0), (1, 0.25), (0.75, 0.5), (1, 0.75), + (0.75, 1), (0.5, 0.75), (0.25, 1), (0, 0.75), (0.25, 0.5), (0, 0.25), + (0.25, 0)], closed=True) + _x_filled_path_t = Path( + [(0.75, 0.5), (1, 0.75), (0.75, 1), (0.5, 0.75), (0.25, 1), (0, 0.75), + (0.25, 0.5), (0.75, 0.5)], closed=True) def _set_x_filled(self): self._transform = Affine2D().translate(-0.5, -0.5) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index d7dcda251310..015e23f07a58 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1991,17 +1991,13 @@ def __init__(self, pad=0.3): def transmute(self, x0, y0, width, height, mutation_size): pad = mutation_size * self.pad - # width and height with padding added. - width, height = width + 2*pad, height + 2*pad - + width, height = width + 2 * pad, height + 2 * pad # boundary of the padded box - x0, y0 = x0 - pad, y0 - pad, + x0, y0 = x0 - pad, y0 - pad x1, y1 = x0 + width, y0 + height - - vertices = [(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)] - codes = [Path.MOVETO] + [Path.LINETO] * 3 + [Path.CLOSEPOLY] - return Path(vertices, codes) + return Path([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)], + closed=True) @_register_style(_style_list) class Circle(_Base): @@ -2020,9 +2016,8 @@ def __init__(self, pad=0.3): def transmute(self, x0, y0, width, height, mutation_size): pad = mutation_size * self.pad width, height = width + 2 * pad, height + 2 * pad - # boundary of the padded box - x0, y0 = x0 - pad, y0 - pad, + x0, y0 = x0 - pad, y0 - pad return Path.circle((x0 + width / 2, y0 + height / 2), max(width, height) / 2) @@ -2043,31 +2038,21 @@ def __init__(self, pad=0.3): def transmute(self, x0, y0, width, height, mutation_size): # padding pad = mutation_size * self.pad - # width and height with padding added. - width, height = width + 2. * pad, height + 2. * pad - + width, height = width + 2 * pad, height + 2 * pad # boundary of the padded box x0, y0 = x0 - pad, y0 - pad, x1, y1 = x0 + width, y0 + height - dx = (y1 - y0) / 2. - dxx = dx * .5 - # adjust x0. 1.4 <- sqrt(2) - x0 = x0 + pad / 1.4 - - cp = [(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1), - (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), - (x0 + dxx, y0 - dxx), # arrow - (x0 + dxx, y0), (x0 + dxx, y0)] - - com = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, Path.LINETO, - Path.LINETO, Path.CLOSEPOLY] - - path = Path(cp, com) + dx = (y1 - y0) / 2 + dxx = dx / 2 + x0 = x0 + pad / 1.4 # adjust by ~sqrt(2) - return path + return Path([(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1), + (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), + (x0 + dxx, y0 - dxx), # arrow + (x0 + dxx, y0), (x0 + dxx, y0)], + closed=True) @_register_style(_style_list) class RArrow(LArrow): @@ -2106,42 +2091,27 @@ def __init__(self, pad=0.3): super().__init__() def transmute(self, x0, y0, width, height, mutation_size): - # padding pad = mutation_size * self.pad - # width and height with padding added. # The width is padded by the arrows, so we don't need to pad it. - height = height + 2. * pad - + height = height + 2 * pad # boundary of the padded box x0, y0 = x0 - pad, y0 - pad x1, y1 = x0 + width, y0 + height dx = (y1 - y0) / 2 - dxx = dx * .5 - # adjust x0. 1.4 <- sqrt(2) - x0 = x0 + pad / 1.4 - - cp = [(x0 + dxx, y0), (x1, y0), # bot-segment - (x1, y0 - dxx), (x1 + dx + dxx, y0 + dx), - (x1, y1 + dxx), # right-arrow - (x1, y1), (x0 + dxx, y1), # top-segment - (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), - (x0 + dxx, y0 - dxx), # left-arrow - (x0 + dxx, y0), (x0 + dxx, y0)] # close-poly - - com = [Path.MOVETO, Path.LINETO, - Path.LINETO, Path.LINETO, - Path.LINETO, - Path.LINETO, Path.LINETO, - Path.LINETO, Path.LINETO, - Path.LINETO, - Path.LINETO, Path.CLOSEPOLY] - - path = Path(cp, com) - - return path + dxx = dx / 2 + x0 = x0 + pad / 1.4 # adjust by ~sqrt(2) + + return Path([(x0 + dxx, y0), (x1, y0), # bot-segment + (x1, y0 - dxx), (x1 + dx + dxx, y0 + dx), + (x1, y1 + dxx), # right-arrow + (x1, y1), (x0 + dxx, y1), # top-segment + (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), + (x0 + dxx, y0 - dxx), # left-arrow + (x0 + dxx, y0), (x0 + dxx, y0)], # close-poly + closed=True) @_register_style(_style_list) class Round(_Base): @@ -2171,7 +2141,7 @@ def transmute(self, x0, y0, width, height, mutation_size): else: dr = pad - width, height = width + 2. * pad, height + 2. * pad + width, height = width + 2 * pad, height + 2 * pad x0, y0 = x0 - pad, y0 - pad, x1, y1 = x0 + width, y0 + height @@ -2232,8 +2202,8 @@ def transmute(self, x0, y0, width, height, mutation_size): else: dr = pad / 2. - width, height = (width + 2. * pad - 2 * dr, - height + 2. * pad - 2 * dr) + width = width + 2 * pad - 2 * dr + height = height + 2 * pad - 2 * dr x0, y0 = x0 - pad + dr, y0 - pad + dr, x1, y1 = x0 + width, y0 + height diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 73bc8f25ff2e..6f626d1469b3 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -120,7 +120,9 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, intended for public use. closed : bool, optional If *codes* is None and closed is True, vertices will be treated as - line segments of a closed polygon. + line segments of a closed polygon. Note that the last vertex will + then be ignored (as the corresponding code will be set to + CLOSEPOLY). readonly : bool, optional Makes the path behave in an immutable way and sets the vertices and codes as read-only arrays. @@ -635,12 +637,8 @@ def unit_rectangle(cls): Return a `Path` instance of the unit rectangle from (0, 0) to (1, 1). """ if cls._unit_rectangle is None: - cls._unit_rectangle = \ - cls([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], - [0.0, 0.0]], - [cls.MOVETO, cls.LINETO, cls.LINETO, cls.LINETO, - cls.CLOSEPOLY], - readonly=True) + cls._unit_rectangle = cls([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]], + closed=True, readonly=True) return cls._unit_rectangle _unit_regular_polygons = WeakValueDictionary() @@ -661,11 +659,7 @@ def unit_regular_polygon(cls, numVertices): # "points-up". + np.pi / 2) verts = np.column_stack((np.cos(theta), np.sin(theta))) - codes = np.empty(numVertices + 1) - codes[0] = cls.MOVETO - codes[1:-1] = cls.LINETO - codes[-1] = cls.CLOSEPOLY - path = cls(verts, codes, readonly=True) + path = cls(verts, closed=True, readonly=True) if numVertices <= 16: cls._unit_regular_polygons[numVertices] = path return path @@ -691,11 +685,7 @@ def unit_regular_star(cls, numVertices, innerCircle=0.5): r = np.ones(ns2 + 1) r[1::2] = innerCircle verts = (r * np.vstack((np.cos(theta), np.sin(theta)))).T - codes = np.empty(ns2 + 1) - codes[0] = cls.MOVETO - codes[1:-1] = cls.LINETO - codes[-1] = cls.CLOSEPOLY - path = cls(verts, codes, readonly=True) + path = cls(verts, closed=True, readonly=True) if numVertices <= 16: cls._unit_regular_stars[(numVertices, innerCircle)] = path return path diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index a440c58702e5..1d73cf477e59 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -319,16 +319,15 @@ def test_patch_str(): assert str(p) == "FancyBboxPatch((1, 2), width=3, height=4)" # Further nice __str__ which cannot be `eval`uated: - path_data = [([1, 2], mpath.Path.MOVETO), ([2, 2], mpath.Path.LINETO), - ([1, 2], mpath.Path.CLOSEPOLY)] - p = mpatches.PathPatch(mpath.Path(*zip(*path_data))) + path = mpath.Path([(1, 2), (2, 2), (1, 2)], closed=True) + p = mpatches.PathPatch(path) assert str(p) == "PathPatch3((1, 2) ...)" data = [[1, 2], [2, 2], [1, 2]] p = mpatches.Polygon(data) assert str(p) == "Polygon3((1, 2) ...)" - p = mpatches.FancyArrowPatch(path=mpath.Path(*zip(*path_data))) + p = mpatches.FancyArrowPatch(path=path) assert str(p)[:27] == "FancyArrowPatch(Path(array(" p = mpatches.FancyArrowPatch((1, 2), (3, 4)) diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index b6e9b7b970ed..47f5a90a2b68 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -189,11 +189,8 @@ def test_affine_inverted_invalidated(): def test_clipping_of_log(): # issue 804 - M, L, C = Path.MOVETO, Path.LINETO, Path.CLOSEPOLY - points = [(0.2, -99), (0.4, -99), (0.4, 20), (0.2, 20), (0.2, -99)] - codes = [M, L, L, L, C] - path = Path(points, codes) - + path = Path([(0.2, -99), (0.4, -99), (0.4, 20), (0.2, 20), (0.2, -99)], + closed=True) # something like this happens in plotting logarithmic histograms trans = mtransforms.BlendedGenericTransform( mtransforms.Affine2D(), scale.LogTransform(10, 'clip')) @@ -201,9 +198,8 @@ def test_clipping_of_log(): result = tpath.iter_segments(trans.get_affine(), clip=(0, 0, 100, 100), simplify=False) - tpoints, tcodes = zip(*result) - assert_allclose(tcodes, [M, L, L, L, C]) + assert_allclose(tcodes, path.codes) class NonAffineForTest(mtransforms.Transform): @@ -627,8 +623,7 @@ def test_invalid_arguments(): def test_transformed_path(): points = [(0, 0), (1, 0), (1, 1), (0, 1)] - codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY] - path = Path(points, codes) + path = Path(points, closed=True) trans = mtransforms.Affine2D() trans_path = mtransforms.TransformedPath(path, trans) diff --git a/lib/mpl_toolkits/axes_grid1/inset_locator.py b/lib/mpl_toolkits/axes_grid1/inset_locator.py index 4a6e1f0db71d..bd7a1a381227 100644 --- a/lib/mpl_toolkits/axes_grid1/inset_locator.py +++ b/lib/mpl_toolkits/axes_grid1/inset_locator.py @@ -164,19 +164,8 @@ def __init__(self, bbox, **kwargs): def get_path(self): # docstring inherited x0, y0, x1, y1 = self.bbox.extents - verts = [(x0, y0), - (x1, y0), - (x1, y1), - (x0, y1), - (x0, y0), - (0, 0)] - codes = [Path.MOVETO, - Path.LINETO, - Path.LINETO, - Path.LINETO, - Path.LINETO, - Path.CLOSEPOLY] - return Path(verts, codes) + return Path([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)], + closed=True) class BboxConnector(Patch): @@ -246,25 +235,14 @@ def connect_bbox(bbox1, bbox2, loc1, loc2=None): corner of *bbox2*. """ if isinstance(bbox1, Rectangle): - transform = bbox1.get_transform() - bbox1 = Bbox.from_bounds(0, 0, 1, 1) - bbox1 = TransformedBbox(bbox1, transform) - + bbox1 = TransformedBbox(Bbox.unit(), bbox1.get_transform()) if isinstance(bbox2, Rectangle): - transform = bbox2.get_transform() - bbox2 = Bbox.from_bounds(0, 0, 1, 1) - bbox2 = TransformedBbox(bbox2, transform) - + bbox2 = TransformedBbox(Bbox.unit(), bbox2.get_transform()) if loc2 is None: loc2 = loc1 - x1, y1 = BboxConnector.get_bbox_edge_pos(bbox1, loc1) x2, y2 = BboxConnector.get_bbox_edge_pos(bbox2, loc2) - - verts = [[x1, y1], [x2, y2]] - codes = [Path.MOVETO, Path.LINETO] - - return Path(verts, codes) + return Path([[x1, y1], [x2, y2]]) @docstring.dedent_interpd def __init__(self, bbox1, bbox2, loc1, loc2=None, **kwargs):