Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 83d9cf8

Browse files
committed
Merge pull request #2010 from mdboom/immutable-paths
Allow Paths to be marked as readonly
2 parents ab5a141 + 97ba7b0 commit 83d9cf8

File tree

6 files changed

+151
-20
lines changed

6 files changed

+151
-20
lines changed

doc/api/api_changes.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ For new features that were added to matplotlib, please see
1515
Changes in 1.3.x
1616
================
1717

18+
* `Path` objects can now be marked as `readonly` by passing
19+
`readonly=True` to its constructor. The built-in path singletons,
20+
obtained through `Path.unit*` class methods return readonly paths.
21+
If you have code that modified these, you will need to make a
22+
deepcopy first, using either::
23+
24+
import copy
25+
path = copy.deepcopy(Path.unit_circle())
26+
27+
# or
28+
29+
path = Path.unit_circle().deepcopy()
30+
31+
Deep copying a `Path` always creates an editable (i.e. non-readonly)
32+
`Path`.
33+
1834
* The `font.*` rcParams now affect only text objects created after the
1935
rcParam has been set, and will not retroactively affect already
2036
existing text objects. This brings their behavior in line with most

lib/matplotlib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,7 @@ def tk_window_focus():
11931193
'matplotlib.tests.test_mathtext',
11941194
'matplotlib.tests.test_mlab',
11951195
'matplotlib.tests.test_patches',
1196+
'matplotlib.tests.test_path',
11961197
'matplotlib.tests.test_patheffects',
11971198
'matplotlib.tests.test_pickle',
11981199
'matplotlib.tests.test_rcparams',

lib/matplotlib/path.py

Lines changed: 121 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ class Path(object):
8484

8585
code_type = np.uint8
8686

87-
def __init__(self, vertices, codes=None, _interpolation_steps=1, closed=False):
87+
def __init__(self, vertices, codes=None, _interpolation_steps=1, closed=False,
88+
readonly=False):
8889
"""
8990
Create a new path with the given vertices and codes.
9091
@@ -109,6 +110,8 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, closed=False):
109110
such as Polar, that this path should be linearly interpolated
110111
immediately before drawing. This attribute is primarily an
111112
implementation detail and is not intended for public use.
113+
114+
*readonly*, when True, makes the path immutable.
112115
"""
113116
if ma.isMaskedArray(vertices):
114117
vertices = vertices.astype(np.float_).filled(np.nan)
@@ -130,14 +133,117 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, closed=False):
130133
assert vertices.ndim == 2
131134
assert vertices.shape[1] == 2
132135

133-
self.should_simplify = (rcParams['path.simplify'] and
134-
(len(vertices) >= 128 and
135-
(codes is None or np.all(codes <= Path.LINETO))))
136-
self.simplify_threshold = rcParams['path.simplify_threshold']
137-
self.has_nonfinite = not np.isfinite(vertices).all()
138-
self.codes = codes
139-
self.vertices = vertices
136+
self._vertices = vertices
137+
self._codes = codes
140138
self._interpolation_steps = _interpolation_steps
139+
self._update_values()
140+
141+
if readonly:
142+
self._vertices.flags.writeable = False
143+
if self._codes is not None:
144+
self._codes.flags.writeable = False
145+
self._readonly = True
146+
else:
147+
self._readonly = False
148+
149+
def _update_values(self):
150+
self._should_simplify = (
151+
rcParams['path.simplify'] and
152+
(len(self._vertices) >= 128 and
153+
(self._codes is None or np.all(self._codes <= Path.LINETO))))
154+
self._simplify_threshold = rcParams['path.simplify_threshold']
155+
self._has_nonfinite = not np.isfinite(self._vertices).all()
156+
157+
@property
158+
def vertices(self):
159+
"""
160+
The list of vertices in the `Path` as an Nx2 numpy array.
161+
"""
162+
return self._vertices
163+
164+
@vertices.setter
165+
def vertices(self, vertices):
166+
if self._readonly:
167+
raise AttributeError("Can't set vertices on a readonly Path")
168+
self._vertices = vertices
169+
self._update_values()
170+
171+
@property
172+
def codes(self):
173+
"""
174+
The list of codes in the `Path` as a 1-D numpy array. Each
175+
code is one of `STOP`, `MOVETO`, `LINETO`, `CURVE3`, `CURVE4`
176+
or `CLOSEPOLY`. For codes that correspond to more than one
177+
vertex (`CURVE3` and `CURVE4`), that code will be repeated so
178+
that the length of `self.vertices` and `self.codes` is always
179+
the same.
180+
"""
181+
return self._codes
182+
183+
@codes.setter
184+
def codes(self, codes):
185+
if self._readonly:
186+
raise AttributeError("Can't set codes on a readonly Path")
187+
self._codes = codes
188+
self._update_values()
189+
190+
@property
191+
def simplify_threshold(self):
192+
"""
193+
The fraction of a pixel difference below which vertices will
194+
be simplified out.
195+
"""
196+
return self._simplify_threshold
197+
198+
@simplify_threshold.setter
199+
def simplify_threshold(self, threshold):
200+
self._simplify_threshold = threshold
201+
202+
@property
203+
def has_nonfinite(self):
204+
"""
205+
`True` if the vertices array has nonfinite values.
206+
"""
207+
return self._has_nonfinite
208+
209+
@property
210+
def should_simplify(self):
211+
"""
212+
`True` if the vertices array should be simplified.
213+
"""
214+
return self._should_simplify
215+
216+
@should_simplify.setter
217+
def should_simplify(self, should_simplify):
218+
self._should_simplify = should_simplify
219+
220+
@property
221+
def readonly(self):
222+
"""
223+
`True` if the `Path` is read-only.
224+
"""
225+
return self._readonly
226+
227+
def __copy__(self):
228+
"""
229+
Returns a shallow copy of the `Path`, which will share the
230+
vertices and codes with the source `Path`.
231+
"""
232+
import copy
233+
return copy.copy(self)
234+
235+
copy = __copy__
236+
237+
def __deepcopy__(self):
238+
"""
239+
Returns a deepcopy of the `Path`. The `Path` will not be
240+
readonly, even if the source `Path` is.
241+
"""
242+
return self.__class__(
243+
self.vertices.copy(), self.codes.copy(),
244+
_interpolation_steps=self._interpolation_steps)
245+
246+
deepcopy = __deepcopy__
141247

142248
@classmethod
143249
def make_compound_path_from_polys(cls, XY):
@@ -420,7 +526,8 @@ def unit_rectangle(cls):
420526
if cls._unit_rectangle is None:
421527
cls._unit_rectangle = \
422528
cls([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]],
423-
[cls.MOVETO, cls.LINETO, cls.LINETO, cls.LINETO, cls.CLOSEPOLY])
529+
[cls.MOVETO, cls.LINETO, cls.LINETO, cls.LINETO, cls.CLOSEPOLY],
530+
readonly=True)
424531
return cls._unit_rectangle
425532

426533
_unit_regular_polygons = WeakValueDictionary()
@@ -447,7 +554,7 @@ def unit_regular_polygon(cls, numVertices):
447554
codes[0] = cls.MOVETO
448555
codes[1:-1] = cls.LINETO
449556
codes[-1] = cls.CLOSEPOLY
450-
path = cls(verts, codes)
557+
path = cls(verts, codes, readonly=True)
451558
if numVertices <= 16:
452559
cls._unit_regular_polygons[numVertices] = path
453560
return path
@@ -478,7 +585,7 @@ def unit_regular_star(cls, numVertices, innerCircle=0.5):
478585
codes[0] = cls.MOVETO
479586
codes[1:-1] = cls.LINETO
480587
codes[-1] = cls.CLOSEPOLY
481-
path = cls(verts, codes)
588+
path = cls(verts, codes, readonly=True)
482589
if numVertices <= 16:
483590
cls._unit_regular_polygons[(numVertices, innerCircle)] = path
484591
return path
@@ -552,7 +659,7 @@ def unit_circle(cls):
552659
codes[0] = cls.MOVETO
553660
codes[-1] = cls.CLOSEPOLY
554661

555-
cls._unit_circle = cls(vertices, codes)
662+
cls._unit_circle = cls(vertices, codes, readonly=True)
556663
return cls._unit_circle
557664

558665
_unit_circle_righthalf = None
@@ -600,7 +707,7 @@ def unit_circle_righthalf(cls):
600707
codes[0] = cls.MOVETO
601708
codes[-1] = cls.CLOSEPOLY
602709

603-
cls._unit_circle_righthalf = cls(vertices, codes)
710+
cls._unit_circle_righthalf = cls(vertices, codes, readonly=True)
604711
return cls._unit_circle_righthalf
605712

606713
@classmethod
@@ -679,7 +786,7 @@ def arc(cls, theta1, theta2, n=None, is_wedge=False):
679786
vertices[vertex_offset+2:end:3, 0] = xB
680787
vertices[vertex_offset+2:end:3, 1] = yB
681788

682-
return cls(vertices, codes)
789+
return cls(vertices, codes, readonly=True)
683790

684791
@classmethod
685792
def wedge(cls, theta1, theta2, n=None):

lib/matplotlib/tests/test_bbox_tight.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ def test_bbox_inches_tight_clipping():
7474
transform=ax.transData,
7575
facecolor='blue', alpha=0.5)
7676

77-
path = mpath.Path.unit_regular_star(5)
78-
path.vertices = path.vertices.copy()
77+
path = mpath.Path.unit_regular_star(5).deepcopy()
7978
path.vertices *= 0.25
8079
patch.set_clip_path(path, transform=ax.transAxes)
8180
plt.gcf().artists.append(patch)

lib/matplotlib/tests/test_patches.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,11 @@ def test_clip_to_bbox():
8383
ax.set_xlim([-18, 20])
8484
ax.set_ylim([-150, 100])
8585

86-
star = mpath.Path.unit_regular_star(8)
87-
path = mpath.Path(star.vertices.copy(), star.codes)
86+
path = mpath.Path.unit_regular_star(8).deepcopy()
8887
path.vertices *= [10, 100]
8988
path.vertices -= [5, 25]
9089

91-
circle = mpath.Path.unit_circle()
92-
path2 = mpath.Path(circle.vertices.copy(), circle.codes)
90+
path2 = mpath.Path.unit_circle().deepcopy()
9391
path2.vertices *= [10, 100]
9492
path2.vertices += [10, -25]
9593

lib/matplotlib/tests/test_path.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from matplotlib.path import Path
2+
from nose.tools import assert_raises
3+
4+
def test_readonly_path():
5+
path = Path.unit_circle()
6+
7+
def modify_vertices():
8+
path.vertices = path.vertices * 2.0
9+
10+
assert_raises(AttributeError, modify_vertices)

0 commit comments

Comments
 (0)