Coverage for /usr/lib/python3/dist-packages/matplotlib/tri/triangulation.py: 18%
97 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
1import numpy as np
3from matplotlib import _api
6class Triangulation:
7 """
8 An unstructured triangular grid consisting of npoints points and
9 ntri triangles. The triangles can either be specified by the user
10 or automatically generated using a Delaunay triangulation.
12 Parameters
13 ----------
14 x, y : (npoints,) array-like
15 Coordinates of grid points.
16 triangles : (ntri, 3) array-like of int, optional
17 For each triangle, the indices of the three points that make
18 up the triangle, ordered in an anticlockwise manner. If not
19 specified, the Delaunay triangulation is calculated.
20 mask : (ntri,) array-like of bool, optional
21 Which triangles are masked out.
23 Attributes
24 ----------
25 triangles : (ntri, 3) array of int
26 For each triangle, the indices of the three points that make
27 up the triangle, ordered in an anticlockwise manner. If you want to
28 take the *mask* into account, use `get_masked_triangles` instead.
29 mask : (ntri, 3) array of bool
30 Masked out triangles.
31 is_delaunay : bool
32 Whether the Triangulation is a calculated Delaunay
33 triangulation (where *triangles* was not specified) or not.
35 Notes
36 -----
37 For a Triangulation to be valid it must not have duplicate points,
38 triangles formed from colinear points, or overlapping triangles.
39 """
40 def __init__(self, x, y, triangles=None, mask=None):
41 from matplotlib import _qhull
43 self.x = np.asarray(x, dtype=np.float64)
44 self.y = np.asarray(y, dtype=np.float64)
45 if self.x.shape != self.y.shape or self.x.ndim != 1:
46 raise ValueError("x and y must be equal-length 1D arrays, but "
47 f"found shapes {self.x.shape!r} and "
48 f"{self.y.shape!r}")
50 self.mask = None
51 self._edges = None
52 self._neighbors = None
53 self.is_delaunay = False
55 if triangles is None:
56 # No triangulation specified, so use matplotlib._qhull to obtain
57 # Delaunay triangulation.
58 self.triangles, self._neighbors = _qhull.delaunay(x, y)
59 self.is_delaunay = True
60 else:
61 # Triangulation specified. Copy, since we may correct triangle
62 # orientation.
63 try:
64 self.triangles = np.array(triangles, dtype=np.int32, order='C')
65 except ValueError as e:
66 raise ValueError('triangles must be a (N, 3) int array, not '
67 f'{triangles!r}') from e
68 if self.triangles.ndim != 2 or self.triangles.shape[1] != 3:
69 raise ValueError(
70 'triangles must be a (N, 3) int array, but found shape '
71 f'{self.triangles.shape!r}')
72 if self.triangles.max() >= len(self.x):
73 raise ValueError(
74 'triangles are indices into the points and must be in the '
75 f'range 0 <= i < {len(self.x)} but found value '
76 f'{self.triangles.max()}')
77 if self.triangles.min() < 0:
78 raise ValueError(
79 'triangles are indices into the points and must be in the '
80 f'range 0 <= i < {len(self.x)} but found value '
81 f'{self.triangles.min()}')
83 # Underlying C++ object is not created until first needed.
84 self._cpp_triangulation = None
86 # Default TriFinder not created until needed.
87 self._trifinder = None
89 self.set_mask(mask)
91 def calculate_plane_coefficients(self, z):
92 """
93 Calculate plane equation coefficients for all unmasked triangles from
94 the point (x, y) coordinates and specified z-array of shape (npoints).
95 The returned array has shape (npoints, 3) and allows z-value at (x, y)
96 position in triangle tri to be calculated using
97 ``z = array[tri, 0] * x + array[tri, 1] * y + array[tri, 2]``.
98 """
99 return self.get_cpp_triangulation().calculate_plane_coefficients(z)
101 @property
102 def edges(self):
103 """
104 Return integer array of shape (nedges, 2) containing all edges of
105 non-masked triangles.
107 Each row defines an edge by its start point index and end point
108 index. Each edge appears only once, i.e. for an edge between points
109 *i* and *j*, there will only be either *(i, j)* or *(j, i)*.
110 """
111 if self._edges is None:
112 self._edges = self.get_cpp_triangulation().get_edges()
113 return self._edges
115 def get_cpp_triangulation(self):
116 """
117 Return the underlying C++ Triangulation object, creating it
118 if necessary.
119 """
120 from matplotlib import _tri
121 if self._cpp_triangulation is None:
122 self._cpp_triangulation = _tri.Triangulation(
123 self.x, self.y, self.triangles, self.mask, self._edges,
124 self._neighbors, not self.is_delaunay)
125 return self._cpp_triangulation
127 def get_masked_triangles(self):
128 """
129 Return an array of triangles taking the mask into account.
130 """
131 if self.mask is not None:
132 return self.triangles[~self.mask]
133 else:
134 return self.triangles
136 @staticmethod
137 def get_from_args_and_kwargs(*args, **kwargs):
138 """
139 Return a Triangulation object from the args and kwargs, and
140 the remaining args and kwargs with the consumed values removed.
142 There are two alternatives: either the first argument is a
143 Triangulation object, in which case it is returned, or the args
144 and kwargs are sufficient to create a new Triangulation to
145 return. In the latter case, see Triangulation.__init__ for
146 the possible args and kwargs.
147 """
148 if isinstance(args[0], Triangulation):
149 triangulation, *args = args
150 if 'triangles' in kwargs:
151 _api.warn_external(
152 "Passing the keyword 'triangles' has no effect when also "
153 "passing a Triangulation")
154 if 'mask' in kwargs:
155 _api.warn_external(
156 "Passing the keyword 'mask' has no effect when also "
157 "passing a Triangulation")
158 else:
159 x, y, triangles, mask, args, kwargs = \
160 Triangulation._extract_triangulation_params(args, kwargs)
161 triangulation = Triangulation(x, y, triangles, mask)
162 return triangulation, args, kwargs
164 @staticmethod
165 def _extract_triangulation_params(args, kwargs):
166 x, y, *args = args
167 # Check triangles in kwargs then args.
168 triangles = kwargs.pop('triangles', None)
169 from_args = False
170 if triangles is None and args:
171 triangles = args[0]
172 from_args = True
173 if triangles is not None:
174 try:
175 triangles = np.asarray(triangles, dtype=np.int32)
176 except ValueError:
177 triangles = None
178 if triangles is not None and (triangles.ndim != 2 or
179 triangles.shape[1] != 3):
180 triangles = None
181 if triangles is not None and from_args:
182 args = args[1:] # Consumed first item in args.
183 # Check for mask in kwargs.
184 mask = kwargs.pop('mask', None)
185 return x, y, triangles, mask, args, kwargs
187 def get_trifinder(self):
188 """
189 Return the default `matplotlib.tri.TriFinder` of this
190 triangulation, creating it if necessary. This allows the same
191 TriFinder object to be easily shared.
192 """
193 if self._trifinder is None:
194 # Default TriFinder class.
195 from matplotlib.tri.trifinder import TrapezoidMapTriFinder
196 self._trifinder = TrapezoidMapTriFinder(self)
197 return self._trifinder
199 @property
200 def neighbors(self):
201 """
202 Return integer array of shape (ntri, 3) containing neighbor triangles.
204 For each triangle, the indices of the three triangles that
205 share the same edges, or -1 if there is no such neighboring
206 triangle. ``neighbors[i, j]`` is the triangle that is the neighbor
207 to the edge from point index ``triangles[i, j]`` to point index
208 ``triangles[i, (j+1)%3]``.
209 """
210 if self._neighbors is None:
211 self._neighbors = self.get_cpp_triangulation().get_neighbors()
212 return self._neighbors
214 def set_mask(self, mask):
215 """
216 Set or clear the mask array.
218 Parameters
219 ----------
220 mask : None or bool array of length ntri
221 """
222 if mask is None:
223 self.mask = None
224 else:
225 self.mask = np.asarray(mask, dtype=bool)
226 if self.mask.shape != (self.triangles.shape[0],):
227 raise ValueError('mask array must have same length as '
228 'triangles array')
230 # Set mask in C++ Triangulation.
231 if self._cpp_triangulation is not None:
232 self._cpp_triangulation.set_mask(self.mask)
234 # Clear derived fields so they are recalculated when needed.
235 self._edges = None
236 self._neighbors = None
238 # Recalculate TriFinder if it exists.
239 if self._trifinder is not None:
240 self._trifinder._initialize()