asd
This commit is contained in:
23
venv/lib/python3.12/site-packages/matplotlib/tri/__init__.py
Normal file
23
venv/lib/python3.12/site-packages/matplotlib/tri/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""
|
||||
Unstructured triangular grid functions.
|
||||
"""
|
||||
|
||||
from ._triangulation import Triangulation
|
||||
from ._tricontour import TriContourSet, tricontour, tricontourf
|
||||
from ._trifinder import TriFinder, TrapezoidMapTriFinder
|
||||
from ._triinterpolate import (TriInterpolator, LinearTriInterpolator,
|
||||
CubicTriInterpolator)
|
||||
from ._tripcolor import tripcolor
|
||||
from ._triplot import triplot
|
||||
from ._trirefine import TriRefiner, UniformTriRefiner
|
||||
from ._tritools import TriAnalyzer
|
||||
|
||||
|
||||
__all__ = ["Triangulation",
|
||||
"TriContourSet", "tricontour", "tricontourf",
|
||||
"TriFinder", "TrapezoidMapTriFinder",
|
||||
"TriInterpolator", "LinearTriInterpolator", "CubicTriInterpolator",
|
||||
"tripcolor",
|
||||
"triplot",
|
||||
"TriRefiner", "UniformTriRefiner",
|
||||
"TriAnalyzer"]
|
@ -0,0 +1,247 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import _api
|
||||
|
||||
|
||||
class Triangulation:
|
||||
"""
|
||||
An unstructured triangular grid consisting of npoints points and
|
||||
ntri triangles. The triangles can either be specified by the user
|
||||
or automatically generated using a Delaunay triangulation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y : (npoints,) array-like
|
||||
Coordinates of grid points.
|
||||
triangles : (ntri, 3) array-like of int, optional
|
||||
For each triangle, the indices of the three points that make
|
||||
up the triangle, ordered in an anticlockwise manner. If not
|
||||
specified, the Delaunay triangulation is calculated.
|
||||
mask : (ntri,) array-like of bool, optional
|
||||
Which triangles are masked out.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
triangles : (ntri, 3) array of int
|
||||
For each triangle, the indices of the three points that make
|
||||
up the triangle, ordered in an anticlockwise manner. If you want to
|
||||
take the *mask* into account, use `get_masked_triangles` instead.
|
||||
mask : (ntri, 3) array of bool or None
|
||||
Masked out triangles.
|
||||
is_delaunay : bool
|
||||
Whether the Triangulation is a calculated Delaunay
|
||||
triangulation (where *triangles* was not specified) or not.
|
||||
|
||||
Notes
|
||||
-----
|
||||
For a Triangulation to be valid it must not have duplicate points,
|
||||
triangles formed from colinear points, or overlapping triangles.
|
||||
"""
|
||||
def __init__(self, x, y, triangles=None, mask=None):
|
||||
from matplotlib import _qhull
|
||||
|
||||
self.x = np.asarray(x, dtype=np.float64)
|
||||
self.y = np.asarray(y, dtype=np.float64)
|
||||
if self.x.shape != self.y.shape or self.x.ndim != 1:
|
||||
raise ValueError("x and y must be equal-length 1D arrays, but "
|
||||
f"found shapes {self.x.shape!r} and "
|
||||
f"{self.y.shape!r}")
|
||||
|
||||
self.mask = None
|
||||
self._edges = None
|
||||
self._neighbors = None
|
||||
self.is_delaunay = False
|
||||
|
||||
if triangles is None:
|
||||
# No triangulation specified, so use matplotlib._qhull to obtain
|
||||
# Delaunay triangulation.
|
||||
self.triangles, self._neighbors = _qhull.delaunay(x, y, sys.flags.verbose)
|
||||
self.is_delaunay = True
|
||||
else:
|
||||
# Triangulation specified. Copy, since we may correct triangle
|
||||
# orientation.
|
||||
try:
|
||||
self.triangles = np.array(triangles, dtype=np.int32, order='C')
|
||||
except ValueError as e:
|
||||
raise ValueError('triangles must be a (N, 3) int array, not '
|
||||
f'{triangles!r}') from e
|
||||
if self.triangles.ndim != 2 or self.triangles.shape[1] != 3:
|
||||
raise ValueError(
|
||||
'triangles must be a (N, 3) int array, but found shape '
|
||||
f'{self.triangles.shape!r}')
|
||||
if self.triangles.max() >= len(self.x):
|
||||
raise ValueError(
|
||||
'triangles are indices into the points and must be in the '
|
||||
f'range 0 <= i < {len(self.x)} but found value '
|
||||
f'{self.triangles.max()}')
|
||||
if self.triangles.min() < 0:
|
||||
raise ValueError(
|
||||
'triangles are indices into the points and must be in the '
|
||||
f'range 0 <= i < {len(self.x)} but found value '
|
||||
f'{self.triangles.min()}')
|
||||
|
||||
# Underlying C++ object is not created until first needed.
|
||||
self._cpp_triangulation = None
|
||||
|
||||
# Default TriFinder not created until needed.
|
||||
self._trifinder = None
|
||||
|
||||
self.set_mask(mask)
|
||||
|
||||
def calculate_plane_coefficients(self, z):
|
||||
"""
|
||||
Calculate plane equation coefficients for all unmasked triangles from
|
||||
the point (x, y) coordinates and specified z-array of shape (npoints).
|
||||
The returned array has shape (npoints, 3) and allows z-value at (x, y)
|
||||
position in triangle tri to be calculated using
|
||||
``z = array[tri, 0] * x + array[tri, 1] * y + array[tri, 2]``.
|
||||
"""
|
||||
return self.get_cpp_triangulation().calculate_plane_coefficients(z)
|
||||
|
||||
@property
|
||||
def edges(self):
|
||||
"""
|
||||
Return integer array of shape (nedges, 2) containing all edges of
|
||||
non-masked triangles.
|
||||
|
||||
Each row defines an edge by its start point index and end point
|
||||
index. Each edge appears only once, i.e. for an edge between points
|
||||
*i* and *j*, there will only be either *(i, j)* or *(j, i)*.
|
||||
"""
|
||||
if self._edges is None:
|
||||
self._edges = self.get_cpp_triangulation().get_edges()
|
||||
return self._edges
|
||||
|
||||
def get_cpp_triangulation(self):
|
||||
"""
|
||||
Return the underlying C++ Triangulation object, creating it
|
||||
if necessary.
|
||||
"""
|
||||
from matplotlib import _tri
|
||||
if self._cpp_triangulation is None:
|
||||
self._cpp_triangulation = _tri.Triangulation(
|
||||
# For unset arrays use empty tuple which has size of zero.
|
||||
self.x, self.y, self.triangles,
|
||||
self.mask if self.mask is not None else (),
|
||||
self._edges if self._edges is not None else (),
|
||||
self._neighbors if self._neighbors is not None else (),
|
||||
not self.is_delaunay)
|
||||
return self._cpp_triangulation
|
||||
|
||||
def get_masked_triangles(self):
|
||||
"""
|
||||
Return an array of triangles taking the mask into account.
|
||||
"""
|
||||
if self.mask is not None:
|
||||
return self.triangles[~self.mask]
|
||||
else:
|
||||
return self.triangles
|
||||
|
||||
@staticmethod
|
||||
def get_from_args_and_kwargs(*args, **kwargs):
|
||||
"""
|
||||
Return a Triangulation object from the args and kwargs, and
|
||||
the remaining args and kwargs with the consumed values removed.
|
||||
|
||||
There are two alternatives: either the first argument is a
|
||||
Triangulation object, in which case it is returned, or the args
|
||||
and kwargs are sufficient to create a new Triangulation to
|
||||
return. In the latter case, see Triangulation.__init__ for
|
||||
the possible args and kwargs.
|
||||
"""
|
||||
if isinstance(args[0], Triangulation):
|
||||
triangulation, *args = args
|
||||
if 'triangles' in kwargs:
|
||||
_api.warn_external(
|
||||
"Passing the keyword 'triangles' has no effect when also "
|
||||
"passing a Triangulation")
|
||||
if 'mask' in kwargs:
|
||||
_api.warn_external(
|
||||
"Passing the keyword 'mask' has no effect when also "
|
||||
"passing a Triangulation")
|
||||
else:
|
||||
x, y, triangles, mask, args, kwargs = \
|
||||
Triangulation._extract_triangulation_params(args, kwargs)
|
||||
triangulation = Triangulation(x, y, triangles, mask)
|
||||
return triangulation, args, kwargs
|
||||
|
||||
@staticmethod
|
||||
def _extract_triangulation_params(args, kwargs):
|
||||
x, y, *args = args
|
||||
# Check triangles in kwargs then args.
|
||||
triangles = kwargs.pop('triangles', None)
|
||||
from_args = False
|
||||
if triangles is None and args:
|
||||
triangles = args[0]
|
||||
from_args = True
|
||||
if triangles is not None:
|
||||
try:
|
||||
triangles = np.asarray(triangles, dtype=np.int32)
|
||||
except ValueError:
|
||||
triangles = None
|
||||
if triangles is not None and (triangles.ndim != 2 or
|
||||
triangles.shape[1] != 3):
|
||||
triangles = None
|
||||
if triangles is not None and from_args:
|
||||
args = args[1:] # Consumed first item in args.
|
||||
# Check for mask in kwargs.
|
||||
mask = kwargs.pop('mask', None)
|
||||
return x, y, triangles, mask, args, kwargs
|
||||
|
||||
def get_trifinder(self):
|
||||
"""
|
||||
Return the default `matplotlib.tri.TriFinder` of this
|
||||
triangulation, creating it if necessary. This allows the same
|
||||
TriFinder object to be easily shared.
|
||||
"""
|
||||
if self._trifinder is None:
|
||||
# Default TriFinder class.
|
||||
from matplotlib.tri._trifinder import TrapezoidMapTriFinder
|
||||
self._trifinder = TrapezoidMapTriFinder(self)
|
||||
return self._trifinder
|
||||
|
||||
@property
|
||||
def neighbors(self):
|
||||
"""
|
||||
Return integer array of shape (ntri, 3) containing neighbor triangles.
|
||||
|
||||
For each triangle, the indices of the three triangles that
|
||||
share the same edges, or -1 if there is no such neighboring
|
||||
triangle. ``neighbors[i, j]`` is the triangle that is the neighbor
|
||||
to the edge from point index ``triangles[i, j]`` to point index
|
||||
``triangles[i, (j+1)%3]``.
|
||||
"""
|
||||
if self._neighbors is None:
|
||||
self._neighbors = self.get_cpp_triangulation().get_neighbors()
|
||||
return self._neighbors
|
||||
|
||||
def set_mask(self, mask):
|
||||
"""
|
||||
Set or clear the mask array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mask : None or bool array of length ntri
|
||||
"""
|
||||
if mask is None:
|
||||
self.mask = None
|
||||
else:
|
||||
self.mask = np.asarray(mask, dtype=bool)
|
||||
if self.mask.shape != (self.triangles.shape[0],):
|
||||
raise ValueError('mask array must have same length as '
|
||||
'triangles array')
|
||||
|
||||
# Set mask in C++ Triangulation.
|
||||
if self._cpp_triangulation is not None:
|
||||
self._cpp_triangulation.set_mask(
|
||||
self.mask if self.mask is not None else ())
|
||||
|
||||
# Clear derived fields so they are recalculated when needed.
|
||||
self._edges = None
|
||||
self._neighbors = None
|
||||
|
||||
# Recalculate TriFinder if it exists.
|
||||
if self._trifinder is not None:
|
||||
self._trifinder._initialize()
|
@ -0,0 +1,33 @@
|
||||
from matplotlib import _tri
|
||||
from matplotlib.tri._trifinder import TriFinder
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import ArrayLike
|
||||
from typing import Any
|
||||
|
||||
class Triangulation:
|
||||
x: np.ndarray
|
||||
y: np.ndarray
|
||||
mask: np.ndarray | None
|
||||
is_delaunay: bool
|
||||
triangles: np.ndarray
|
||||
def __init__(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
triangles: ArrayLike | None = ...,
|
||||
mask: ArrayLike | None = ...,
|
||||
) -> None: ...
|
||||
def calculate_plane_coefficients(self, z: ArrayLike) -> np.ndarray: ...
|
||||
@property
|
||||
def edges(self) -> np.ndarray: ...
|
||||
def get_cpp_triangulation(self) -> _tri.Triangulation: ...
|
||||
def get_masked_triangles(self) -> np.ndarray: ...
|
||||
@staticmethod
|
||||
def get_from_args_and_kwargs(
|
||||
*args, **kwargs
|
||||
) -> tuple[Triangulation, tuple[Any, ...], dict[str, Any]]: ...
|
||||
def get_trifinder(self) -> TriFinder: ...
|
||||
@property
|
||||
def neighbors(self) -> np.ndarray: ...
|
||||
def set_mask(self, mask: None | ArrayLike) -> None: ...
|
270
venv/lib/python3.12/site-packages/matplotlib/tri/_tricontour.py
Normal file
270
venv/lib/python3.12/site-packages/matplotlib/tri/_tricontour.py
Normal file
@ -0,0 +1,270 @@
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import _docstring
|
||||
from matplotlib.contour import ContourSet
|
||||
from matplotlib.tri._triangulation import Triangulation
|
||||
|
||||
|
||||
@_docstring.dedent_interpd
|
||||
class TriContourSet(ContourSet):
|
||||
"""
|
||||
Create and store a set of contour lines or filled regions for
|
||||
a triangular grid.
|
||||
|
||||
This class is typically not instantiated directly by the user but by
|
||||
`~.Axes.tricontour` and `~.Axes.tricontourf`.
|
||||
|
||||
%(contour_set_attributes)s
|
||||
"""
|
||||
def __init__(self, ax, *args, **kwargs):
|
||||
"""
|
||||
Draw triangular grid contour lines or filled regions,
|
||||
depending on whether keyword arg *filled* is False
|
||||
(default) or True.
|
||||
|
||||
The first argument of the initializer must be an `~.axes.Axes`
|
||||
object. The remaining arguments and keyword arguments
|
||||
are described in the docstring of `~.Axes.tricontour`.
|
||||
"""
|
||||
super().__init__(ax, *args, **kwargs)
|
||||
|
||||
def _process_args(self, *args, **kwargs):
|
||||
"""
|
||||
Process args and kwargs.
|
||||
"""
|
||||
if isinstance(args[0], TriContourSet):
|
||||
C = args[0]._contour_generator
|
||||
if self.levels is None:
|
||||
self.levels = args[0].levels
|
||||
self.zmin = args[0].zmin
|
||||
self.zmax = args[0].zmax
|
||||
self._mins = args[0]._mins
|
||||
self._maxs = args[0]._maxs
|
||||
else:
|
||||
from matplotlib import _tri
|
||||
tri, z = self._contour_args(args, kwargs)
|
||||
C = _tri.TriContourGenerator(tri.get_cpp_triangulation(), z)
|
||||
self._mins = [tri.x.min(), tri.y.min()]
|
||||
self._maxs = [tri.x.max(), tri.y.max()]
|
||||
|
||||
self._contour_generator = C
|
||||
return kwargs
|
||||
|
||||
def _contour_args(self, args, kwargs):
|
||||
tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args,
|
||||
**kwargs)
|
||||
z, *args = args
|
||||
z = np.ma.asarray(z)
|
||||
if z.shape != tri.x.shape:
|
||||
raise ValueError('z array must have same length as triangulation x'
|
||||
' and y arrays')
|
||||
|
||||
# z values must be finite, only need to check points that are included
|
||||
# in the triangulation.
|
||||
z_check = z[np.unique(tri.get_masked_triangles())]
|
||||
if np.ma.is_masked(z_check):
|
||||
raise ValueError('z must not contain masked points within the '
|
||||
'triangulation')
|
||||
if not np.isfinite(z_check).all():
|
||||
raise ValueError('z array must not contain non-finite values '
|
||||
'within the triangulation')
|
||||
|
||||
z = np.ma.masked_invalid(z, copy=False)
|
||||
self.zmax = float(z_check.max())
|
||||
self.zmin = float(z_check.min())
|
||||
if self.logscale and self.zmin <= 0:
|
||||
func = 'contourf' if self.filled else 'contour'
|
||||
raise ValueError(f'Cannot {func} log of negative values.')
|
||||
self._process_contour_level_args(args, z.dtype)
|
||||
return (tri, z)
|
||||
|
||||
|
||||
_docstring.interpd.update(_tricontour_doc="""
|
||||
Draw contour %%(type)s on an unstructured triangular grid.
|
||||
|
||||
Call signatures::
|
||||
|
||||
%%(func)s(triangulation, z, [levels], ...)
|
||||
%%(func)s(x, y, z, [levels], *, [triangles=triangles], [mask=mask], ...)
|
||||
|
||||
The triangular grid can be specified either by passing a `.Triangulation`
|
||||
object as the first parameter, or by passing the points *x*, *y* and
|
||||
optionally the *triangles* and a *mask*. See `.Triangulation` for an
|
||||
explanation of these parameters. If neither of *triangulation* or
|
||||
*triangles* are given, the triangulation is calculated on the fly.
|
||||
|
||||
It is possible to pass *triangles* positionally, i.e.
|
||||
``%%(func)s(x, y, triangles, z, ...)``. However, this is discouraged. For more
|
||||
clarity, pass *triangles* via keyword argument.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
triangulation : `.Triangulation`, optional
|
||||
An already created triangular grid.
|
||||
|
||||
x, y, triangles, mask
|
||||
Parameters defining the triangular grid. See `.Triangulation`.
|
||||
This is mutually exclusive with specifying *triangulation*.
|
||||
|
||||
z : array-like
|
||||
The height values over which the contour is drawn. Color-mapping is
|
||||
controlled by *cmap*, *norm*, *vmin*, and *vmax*.
|
||||
|
||||
.. note::
|
||||
All values in *z* must be finite. Hence, nan and inf values must
|
||||
either be removed or `~.Triangulation.set_mask` be used.
|
||||
|
||||
levels : int or array-like, optional
|
||||
Determines the number and positions of the contour lines / regions.
|
||||
|
||||
If an int *n*, use `~matplotlib.ticker.MaxNLocator`, which tries to
|
||||
automatically choose no more than *n+1* "nice" contour levels between
|
||||
between minimum and maximum numeric values of *Z*.
|
||||
|
||||
If array-like, draw contour lines at the specified levels. The values must
|
||||
be in increasing order.
|
||||
|
||||
Returns
|
||||
-------
|
||||
`~matplotlib.tri.TriContourSet`
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
colors : :mpltype:`color` or list of :mpltype:`color`, optional
|
||||
The colors of the levels, i.e., the contour %%(type)s.
|
||||
|
||||
The sequence is cycled for the levels in ascending order. If the sequence
|
||||
is shorter than the number of levels, it is repeated.
|
||||
|
||||
As a shortcut, single color strings may be used in place of one-element
|
||||
lists, i.e. ``'red'`` instead of ``['red']`` to color all levels with the
|
||||
same color. This shortcut does only work for color strings, not for other
|
||||
ways of specifying colors.
|
||||
|
||||
By default (value *None*), the colormap specified by *cmap* will be used.
|
||||
|
||||
alpha : float, default: 1
|
||||
The alpha blending value, between 0 (transparent) and 1 (opaque).
|
||||
|
||||
%(cmap_doc)s
|
||||
|
||||
This parameter is ignored if *colors* is set.
|
||||
|
||||
%(norm_doc)s
|
||||
|
||||
This parameter is ignored if *colors* is set.
|
||||
|
||||
%(vmin_vmax_doc)s
|
||||
|
||||
If *vmin* or *vmax* are not given, the default color scaling is based on
|
||||
*levels*.
|
||||
|
||||
This parameter is ignored if *colors* is set.
|
||||
|
||||
origin : {*None*, 'upper', 'lower', 'image'}, default: None
|
||||
Determines the orientation and exact position of *z* by specifying the
|
||||
position of ``z[0, 0]``. This is only relevant, if *X*, *Y* are not given.
|
||||
|
||||
- *None*: ``z[0, 0]`` is at X=0, Y=0 in the lower left corner.
|
||||
- 'lower': ``z[0, 0]`` is at X=0.5, Y=0.5 in the lower left corner.
|
||||
- 'upper': ``z[0, 0]`` is at X=N+0.5, Y=0.5 in the upper left corner.
|
||||
- 'image': Use the value from :rc:`image.origin`.
|
||||
|
||||
extent : (x0, x1, y0, y1), optional
|
||||
If *origin* is not *None*, then *extent* is interpreted as in `.imshow`: it
|
||||
gives the outer pixel boundaries. In this case, the position of z[0, 0] is
|
||||
the center of the pixel, not a corner. If *origin* is *None*, then
|
||||
(*x0*, *y0*) is the position of z[0, 0], and (*x1*, *y1*) is the position
|
||||
of z[-1, -1].
|
||||
|
||||
This argument is ignored if *X* and *Y* are specified in the call to
|
||||
contour.
|
||||
|
||||
locator : ticker.Locator subclass, optional
|
||||
The locator is used to determine the contour levels if they are not given
|
||||
explicitly via *levels*.
|
||||
Defaults to `~.ticker.MaxNLocator`.
|
||||
|
||||
extend : {'neither', 'both', 'min', 'max'}, default: 'neither'
|
||||
Determines the ``%%(func)s``-coloring of values that are outside the
|
||||
*levels* range.
|
||||
|
||||
If 'neither', values outside the *levels* range are not colored. If 'min',
|
||||
'max' or 'both', color the values below, above or below and above the
|
||||
*levels* range.
|
||||
|
||||
Values below ``min(levels)`` and above ``max(levels)`` are mapped to the
|
||||
under/over values of the `.Colormap`. Note that most colormaps do not have
|
||||
dedicated colors for these by default, so that the over and under values
|
||||
are the edge values of the colormap. You may want to set these values
|
||||
explicitly using `.Colormap.set_under` and `.Colormap.set_over`.
|
||||
|
||||
.. note::
|
||||
|
||||
An existing `.TriContourSet` does not get notified if properties of its
|
||||
colormap are changed. Therefore, an explicit call to
|
||||
`.ContourSet.changed()` is needed after modifying the colormap. The
|
||||
explicit call can be left out, if a colorbar is assigned to the
|
||||
`.TriContourSet` because it internally calls `.ContourSet.changed()`.
|
||||
|
||||
xunits, yunits : registered units, optional
|
||||
Override axis units by specifying an instance of a
|
||||
:class:`matplotlib.units.ConversionInterface`.
|
||||
|
||||
antialiased : bool, optional
|
||||
Enable antialiasing, overriding the defaults. For
|
||||
filled contours, the default is *True*. For line contours,
|
||||
it is taken from :rc:`lines.antialiased`.""" % _docstring.interpd.params)
|
||||
|
||||
|
||||
@_docstring.Substitution(func='tricontour', type='lines')
|
||||
@_docstring.dedent_interpd
|
||||
def tricontour(ax, *args, **kwargs):
|
||||
"""
|
||||
%(_tricontour_doc)s
|
||||
|
||||
linewidths : float or array-like, default: :rc:`contour.linewidth`
|
||||
The line width of the contour lines.
|
||||
|
||||
If a number, all levels will be plotted with this linewidth.
|
||||
|
||||
If a sequence, the levels in ascending order will be plotted with
|
||||
the linewidths in the order specified.
|
||||
|
||||
If None, this falls back to :rc:`lines.linewidth`.
|
||||
|
||||
linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, optional
|
||||
If *linestyles* is *None*, the default is 'solid' unless the lines are
|
||||
monochrome. In that case, negative contours will take their linestyle
|
||||
from :rc:`contour.negative_linestyle` setting.
|
||||
|
||||
*linestyles* can also be an iterable of the above strings specifying a
|
||||
set of linestyles to be used. If this iterable is shorter than the
|
||||
number of contour levels it will be repeated as necessary.
|
||||
"""
|
||||
kwargs['filled'] = False
|
||||
return TriContourSet(ax, *args, **kwargs)
|
||||
|
||||
|
||||
@_docstring.Substitution(func='tricontourf', type='regions')
|
||||
@_docstring.dedent_interpd
|
||||
def tricontourf(ax, *args, **kwargs):
|
||||
"""
|
||||
%(_tricontour_doc)s
|
||||
|
||||
hatches : list[str], optional
|
||||
A list of crosshatch patterns to use on the filled areas.
|
||||
If None, no hatching will be added to the contour.
|
||||
|
||||
Notes
|
||||
-----
|
||||
`.tricontourf` fills intervals that are closed at the top; that is, for
|
||||
boundaries *z1* and *z2*, the filled region is::
|
||||
|
||||
z1 < Z <= z2
|
||||
|
||||
except for the lowest interval, which is closed on both sides (i.e. it
|
||||
includes the lowest value).
|
||||
"""
|
||||
kwargs['filled'] = True
|
||||
return TriContourSet(ax, *args, **kwargs)
|
@ -0,0 +1,52 @@
|
||||
from matplotlib.axes import Axes
|
||||
from matplotlib.contour import ContourSet
|
||||
from matplotlib.tri._triangulation import Triangulation
|
||||
|
||||
from numpy.typing import ArrayLike
|
||||
from typing import overload
|
||||
|
||||
# TODO: more explicit args/kwargs (for all things in this module)?
|
||||
|
||||
class TriContourSet(ContourSet):
|
||||
def __init__(self, ax: Axes, *args, **kwargs) -> None: ...
|
||||
|
||||
@overload
|
||||
def tricontour(
|
||||
ax: Axes,
|
||||
triangulation: Triangulation,
|
||||
z: ArrayLike,
|
||||
levels: int | ArrayLike = ...,
|
||||
**kwargs
|
||||
) -> TriContourSet: ...
|
||||
@overload
|
||||
def tricontour(
|
||||
ax: Axes,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike,
|
||||
levels: int | ArrayLike = ...,
|
||||
*,
|
||||
triangles: ArrayLike = ...,
|
||||
mask: ArrayLike = ...,
|
||||
**kwargs
|
||||
) -> TriContourSet: ...
|
||||
@overload
|
||||
def tricontourf(
|
||||
ax: Axes,
|
||||
triangulation: Triangulation,
|
||||
z: ArrayLike,
|
||||
levels: int | ArrayLike = ...,
|
||||
**kwargs
|
||||
) -> TriContourSet: ...
|
||||
@overload
|
||||
def tricontourf(
|
||||
ax: Axes,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike,
|
||||
levels: int | ArrayLike = ...,
|
||||
*,
|
||||
triangles: ArrayLike = ...,
|
||||
mask: ArrayLike = ...,
|
||||
**kwargs
|
||||
) -> TriContourSet: ...
|
@ -0,0 +1,96 @@
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import _api
|
||||
from matplotlib.tri import Triangulation
|
||||
|
||||
|
||||
class TriFinder:
|
||||
"""
|
||||
Abstract base class for classes used to find the triangles of a
|
||||
Triangulation in which (x, y) points lie.
|
||||
|
||||
Rather than instantiate an object of a class derived from TriFinder, it is
|
||||
usually better to use the function `.Triangulation.get_trifinder`.
|
||||
|
||||
Derived classes implement __call__(x, y) where x and y are array-like point
|
||||
coordinates of the same shape.
|
||||
"""
|
||||
|
||||
def __init__(self, triangulation):
|
||||
_api.check_isinstance(Triangulation, triangulation=triangulation)
|
||||
self._triangulation = triangulation
|
||||
|
||||
def __call__(self, x, y):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class TrapezoidMapTriFinder(TriFinder):
|
||||
"""
|
||||
`~matplotlib.tri.TriFinder` class implemented using the trapezoid
|
||||
map algorithm from the book "Computational Geometry, Algorithms and
|
||||
Applications", second edition, by M. de Berg, M. van Kreveld, M. Overmars
|
||||
and O. Schwarzkopf.
|
||||
|
||||
The triangulation must be valid, i.e. it must not have duplicate points,
|
||||
triangles formed from colinear points, or overlapping triangles. The
|
||||
algorithm has some tolerance to triangles formed from colinear points, but
|
||||
this should not be relied upon.
|
||||
"""
|
||||
|
||||
def __init__(self, triangulation):
|
||||
from matplotlib import _tri
|
||||
super().__init__(triangulation)
|
||||
self._cpp_trifinder = _tri.TrapezoidMapTriFinder(
|
||||
triangulation.get_cpp_triangulation())
|
||||
self._initialize()
|
||||
|
||||
def __call__(self, x, y):
|
||||
"""
|
||||
Return an array containing the indices of the triangles in which the
|
||||
specified *x*, *y* points lie, or -1 for points that do not lie within
|
||||
a triangle.
|
||||
|
||||
*x*, *y* are array-like x and y coordinates of the same shape and any
|
||||
number of dimensions.
|
||||
|
||||
Returns integer array with the same shape and *x* and *y*.
|
||||
"""
|
||||
x = np.asarray(x, dtype=np.float64)
|
||||
y = np.asarray(y, dtype=np.float64)
|
||||
if x.shape != y.shape:
|
||||
raise ValueError("x and y must be array-like with the same shape")
|
||||
|
||||
# C++ does the heavy lifting, and expects 1D arrays.
|
||||
indices = (self._cpp_trifinder.find_many(x.ravel(), y.ravel())
|
||||
.reshape(x.shape))
|
||||
return indices
|
||||
|
||||
def _get_tree_stats(self):
|
||||
"""
|
||||
Return a python list containing the statistics about the node tree:
|
||||
0: number of nodes (tree size)
|
||||
1: number of unique nodes
|
||||
2: number of trapezoids (tree leaf nodes)
|
||||
3: number of unique trapezoids
|
||||
4: maximum parent count (max number of times a node is repeated in
|
||||
tree)
|
||||
5: maximum depth of tree (one more than the maximum number of
|
||||
comparisons needed to search through the tree)
|
||||
6: mean of all trapezoid depths (one more than the average number
|
||||
of comparisons needed to search through the tree)
|
||||
"""
|
||||
return self._cpp_trifinder.get_tree_stats()
|
||||
|
||||
def _initialize(self):
|
||||
"""
|
||||
Initialize the underlying C++ object. Can be called multiple times if,
|
||||
for example, the triangulation is modified.
|
||||
"""
|
||||
self._cpp_trifinder.initialize()
|
||||
|
||||
def _print_tree(self):
|
||||
"""
|
||||
Print a text representation of the node tree, which is useful for
|
||||
debugging purposes.
|
||||
"""
|
||||
self._cpp_trifinder.print_tree()
|
@ -0,0 +1,10 @@
|
||||
from matplotlib.tri import Triangulation
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
class TriFinder:
|
||||
def __init__(self, triangulation: Triangulation) -> None: ...
|
||||
def __call__(self, x: ArrayLike, y: ArrayLike) -> ArrayLike: ...
|
||||
|
||||
class TrapezoidMapTriFinder(TriFinder):
|
||||
def __init__(self, triangulation: Triangulation) -> None: ...
|
||||
def __call__(self, x: ArrayLike, y: ArrayLike) -> ArrayLike: ...
|
1574
venv/lib/python3.12/site-packages/matplotlib/tri/_triinterpolate.py
Normal file
1574
venv/lib/python3.12/site-packages/matplotlib/tri/_triinterpolate.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@
|
||||
from matplotlib.tri import Triangulation, TriFinder
|
||||
|
||||
from typing import Literal
|
||||
import numpy as np
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
class TriInterpolator:
|
||||
def __init__(
|
||||
self,
|
||||
triangulation: Triangulation,
|
||||
z: ArrayLike,
|
||||
trifinder: TriFinder | None = ...,
|
||||
) -> None: ...
|
||||
# __call__ and gradient are not actually implemented by the ABC, but are specified as required
|
||||
def __call__(self, x: ArrayLike, y: ArrayLike) -> np.ma.MaskedArray: ...
|
||||
def gradient(
|
||||
self, x: ArrayLike, y: ArrayLike
|
||||
) -> tuple[np.ma.MaskedArray, np.ma.MaskedArray]: ...
|
||||
|
||||
class LinearTriInterpolator(TriInterpolator): ...
|
||||
|
||||
class CubicTriInterpolator(TriInterpolator):
|
||||
def __init__(
|
||||
self,
|
||||
triangulation: Triangulation,
|
||||
z: ArrayLike,
|
||||
kind: Literal["min_E", "geom", "user"] = ...,
|
||||
trifinder: TriFinder | None = ...,
|
||||
dz: tuple[ArrayLike, ArrayLike] | None = ...,
|
||||
) -> None: ...
|
149
venv/lib/python3.12/site-packages/matplotlib/tri/_tripcolor.py
Normal file
149
venv/lib/python3.12/site-packages/matplotlib/tri/_tripcolor.py
Normal file
@ -0,0 +1,149 @@
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import _api
|
||||
from matplotlib.collections import PolyCollection, TriMesh
|
||||
from matplotlib.tri._triangulation import Triangulation
|
||||
|
||||
|
||||
def tripcolor(ax, *args, alpha=1.0, norm=None, cmap=None, vmin=None,
|
||||
vmax=None, shading='flat', facecolors=None, **kwargs):
|
||||
"""
|
||||
Create a pseudocolor plot of an unstructured triangular grid.
|
||||
|
||||
Call signatures::
|
||||
|
||||
tripcolor(triangulation, c, *, ...)
|
||||
tripcolor(x, y, c, *, [triangles=triangles], [mask=mask], ...)
|
||||
|
||||
The triangular grid can be specified either by passing a `.Triangulation`
|
||||
object as the first parameter, or by passing the points *x*, *y* and
|
||||
optionally the *triangles* and a *mask*. See `.Triangulation` for an
|
||||
explanation of these parameters.
|
||||
|
||||
It is possible to pass the triangles positionally, i.e.
|
||||
``tripcolor(x, y, triangles, c, ...)``. However, this is discouraged.
|
||||
For more clarity, pass *triangles* via keyword argument.
|
||||
|
||||
If neither of *triangulation* or *triangles* are given, the triangulation
|
||||
is calculated on the fly. In this case, it does not make sense to provide
|
||||
colors at the triangle faces via *c* or *facecolors* because there are
|
||||
multiple possible triangulations for a group of points and you don't know
|
||||
which triangles will be constructed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
triangulation : `.Triangulation`
|
||||
An already created triangular grid.
|
||||
x, y, triangles, mask
|
||||
Parameters defining the triangular grid. See `.Triangulation`.
|
||||
This is mutually exclusive with specifying *triangulation*.
|
||||
c : array-like
|
||||
The color values, either for the points or for the triangles. Which one
|
||||
is automatically inferred from the length of *c*, i.e. does it match
|
||||
the number of points or the number of triangles. If there are the same
|
||||
number of points and triangles in the triangulation it is assumed that
|
||||
color values are defined at points; to force the use of color values at
|
||||
triangles use the keyword argument ``facecolors=c`` instead of just
|
||||
``c``.
|
||||
This parameter is position-only.
|
||||
facecolors : array-like, optional
|
||||
Can be used alternatively to *c* to specify colors at the triangle
|
||||
faces. This parameter takes precedence over *c*.
|
||||
shading : {'flat', 'gouraud'}, default: 'flat'
|
||||
If 'flat' and the color values *c* are defined at points, the color
|
||||
values used for each triangle are from the mean c of the triangle's
|
||||
three points. If *shading* is 'gouraud' then color values must be
|
||||
defined at points.
|
||||
other_parameters
|
||||
All other parameters are the same as for `~.Axes.pcolor`.
|
||||
"""
|
||||
_api.check_in_list(['flat', 'gouraud'], shading=shading)
|
||||
|
||||
tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, **kwargs)
|
||||
|
||||
# Parse the color to be in one of (the other variable will be None):
|
||||
# - facecolors: if specified at the triangle faces
|
||||
# - point_colors: if specified at the points
|
||||
if facecolors is not None:
|
||||
if args:
|
||||
_api.warn_external(
|
||||
"Positional parameter c has no effect when the keyword "
|
||||
"facecolors is given")
|
||||
point_colors = None
|
||||
if len(facecolors) != len(tri.triangles):
|
||||
raise ValueError("The length of facecolors must match the number "
|
||||
"of triangles")
|
||||
else:
|
||||
# Color from positional parameter c
|
||||
if not args:
|
||||
raise TypeError(
|
||||
"tripcolor() missing 1 required positional argument: 'c'; or "
|
||||
"1 required keyword-only argument: 'facecolors'")
|
||||
elif len(args) > 1:
|
||||
raise TypeError(f"Unexpected positional parameters: {args[1:]!r}")
|
||||
c = np.asarray(args[0])
|
||||
if len(c) == len(tri.x):
|
||||
# having this before the len(tri.triangles) comparison gives
|
||||
# precedence to nodes if there are as many nodes as triangles
|
||||
point_colors = c
|
||||
facecolors = None
|
||||
elif len(c) == len(tri.triangles):
|
||||
point_colors = None
|
||||
facecolors = c
|
||||
else:
|
||||
raise ValueError('The length of c must match either the number '
|
||||
'of points or the number of triangles')
|
||||
|
||||
# Handling of linewidths, shading, edgecolors and antialiased as
|
||||
# in Axes.pcolor
|
||||
linewidths = (0.25,)
|
||||
if 'linewidth' in kwargs:
|
||||
kwargs['linewidths'] = kwargs.pop('linewidth')
|
||||
kwargs.setdefault('linewidths', linewidths)
|
||||
|
||||
edgecolors = 'none'
|
||||
if 'edgecolor' in kwargs:
|
||||
kwargs['edgecolors'] = kwargs.pop('edgecolor')
|
||||
ec = kwargs.setdefault('edgecolors', edgecolors)
|
||||
|
||||
if 'antialiased' in kwargs:
|
||||
kwargs['antialiaseds'] = kwargs.pop('antialiased')
|
||||
if 'antialiaseds' not in kwargs and ec.lower() == "none":
|
||||
kwargs['antialiaseds'] = False
|
||||
|
||||
if shading == 'gouraud':
|
||||
if facecolors is not None:
|
||||
raise ValueError(
|
||||
"shading='gouraud' can only be used when the colors "
|
||||
"are specified at the points, not at the faces.")
|
||||
collection = TriMesh(tri, alpha=alpha, array=point_colors,
|
||||
cmap=cmap, norm=norm, **kwargs)
|
||||
else: # 'flat'
|
||||
# Vertices of triangles.
|
||||
maskedTris = tri.get_masked_triangles()
|
||||
verts = np.stack((tri.x[maskedTris], tri.y[maskedTris]), axis=-1)
|
||||
|
||||
# Color values.
|
||||
if facecolors is None:
|
||||
# One color per triangle, the mean of the 3 vertex color values.
|
||||
colors = point_colors[maskedTris].mean(axis=1)
|
||||
elif tri.mask is not None:
|
||||
# Remove color values of masked triangles.
|
||||
colors = facecolors[~tri.mask]
|
||||
else:
|
||||
colors = facecolors
|
||||
collection = PolyCollection(verts, alpha=alpha, array=colors,
|
||||
cmap=cmap, norm=norm, **kwargs)
|
||||
|
||||
collection._scale_norm(norm, vmin, vmax)
|
||||
ax.grid(False)
|
||||
|
||||
minx = tri.x.min()
|
||||
maxx = tri.x.max()
|
||||
miny = tri.y.min()
|
||||
maxy = tri.y.max()
|
||||
corners = (minx, miny), (maxx, maxy)
|
||||
ax.update_datalim(corners)
|
||||
ax.autoscale_view()
|
||||
ax.add_collection(collection)
|
||||
return collection
|
@ -0,0 +1,71 @@
|
||||
from matplotlib.axes import Axes
|
||||
from matplotlib.collections import PolyCollection, TriMesh
|
||||
from matplotlib.colors import Normalize, Colormap
|
||||
from matplotlib.tri._triangulation import Triangulation
|
||||
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from typing import overload, Literal
|
||||
|
||||
@overload
|
||||
def tripcolor(
|
||||
ax: Axes,
|
||||
triangulation: Triangulation,
|
||||
c: ArrayLike = ...,
|
||||
*,
|
||||
alpha: float = ...,
|
||||
norm: str | Normalize | None = ...,
|
||||
cmap: str | Colormap | None = ...,
|
||||
vmin: float | None = ...,
|
||||
vmax: float | None = ...,
|
||||
shading: Literal["flat"] = ...,
|
||||
facecolors: ArrayLike | None = ...,
|
||||
**kwargs
|
||||
) -> PolyCollection: ...
|
||||
@overload
|
||||
def tripcolor(
|
||||
ax: Axes,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
c: ArrayLike = ...,
|
||||
*,
|
||||
alpha: float = ...,
|
||||
norm: str | Normalize | None = ...,
|
||||
cmap: str | Colormap | None = ...,
|
||||
vmin: float | None = ...,
|
||||
vmax: float | None = ...,
|
||||
shading: Literal["flat"] = ...,
|
||||
facecolors: ArrayLike | None = ...,
|
||||
**kwargs
|
||||
) -> PolyCollection: ...
|
||||
@overload
|
||||
def tripcolor(
|
||||
ax: Axes,
|
||||
triangulation: Triangulation,
|
||||
c: ArrayLike = ...,
|
||||
*,
|
||||
alpha: float = ...,
|
||||
norm: str | Normalize | None = ...,
|
||||
cmap: str | Colormap | None = ...,
|
||||
vmin: float | None = ...,
|
||||
vmax: float | None = ...,
|
||||
shading: Literal["gouraud"],
|
||||
facecolors: ArrayLike | None = ...,
|
||||
**kwargs
|
||||
) -> TriMesh: ...
|
||||
@overload
|
||||
def tripcolor(
|
||||
ax: Axes,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
c: ArrayLike = ...,
|
||||
*,
|
||||
alpha: float = ...,
|
||||
norm: str | Normalize | None = ...,
|
||||
cmap: str | Colormap | None = ...,
|
||||
vmin: float | None = ...,
|
||||
vmax: float | None = ...,
|
||||
shading: Literal["gouraud"],
|
||||
facecolors: ArrayLike | None = ...,
|
||||
**kwargs
|
||||
) -> TriMesh: ...
|
86
venv/lib/python3.12/site-packages/matplotlib/tri/_triplot.py
Normal file
86
venv/lib/python3.12/site-packages/matplotlib/tri/_triplot.py
Normal file
@ -0,0 +1,86 @@
|
||||
import numpy as np
|
||||
from matplotlib.tri._triangulation import Triangulation
|
||||
import matplotlib.cbook as cbook
|
||||
import matplotlib.lines as mlines
|
||||
|
||||
|
||||
def triplot(ax, *args, **kwargs):
|
||||
"""
|
||||
Draw an unstructured triangular grid as lines and/or markers.
|
||||
|
||||
Call signatures::
|
||||
|
||||
triplot(triangulation, ...)
|
||||
triplot(x, y, [triangles], *, [mask=mask], ...)
|
||||
|
||||
The triangular grid can be specified either by passing a `.Triangulation`
|
||||
object as the first parameter, or by passing the points *x*, *y* and
|
||||
optionally the *triangles* and a *mask*. If neither of *triangulation* or
|
||||
*triangles* are given, the triangulation is calculated on the fly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
triangulation : `.Triangulation`
|
||||
An already created triangular grid.
|
||||
x, y, triangles, mask
|
||||
Parameters defining the triangular grid. See `.Triangulation`.
|
||||
This is mutually exclusive with specifying *triangulation*.
|
||||
other_parameters
|
||||
All other args and kwargs are forwarded to `~.Axes.plot`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
lines : `~matplotlib.lines.Line2D`
|
||||
The drawn triangles edges.
|
||||
markers : `~matplotlib.lines.Line2D`
|
||||
The drawn marker nodes.
|
||||
"""
|
||||
import matplotlib.axes
|
||||
|
||||
tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, **kwargs)
|
||||
x, y, edges = (tri.x, tri.y, tri.edges)
|
||||
|
||||
# Decode plot format string, e.g., 'ro-'
|
||||
fmt = args[0] if args else ""
|
||||
linestyle, marker, color = matplotlib.axes._base._process_plot_format(fmt)
|
||||
|
||||
# Insert plot format string into a copy of kwargs (kwargs values prevail).
|
||||
kw = cbook.normalize_kwargs(kwargs, mlines.Line2D)
|
||||
for key, val in zip(('linestyle', 'marker', 'color'),
|
||||
(linestyle, marker, color)):
|
||||
if val is not None:
|
||||
kw.setdefault(key, val)
|
||||
|
||||
# Draw lines without markers.
|
||||
# Note 1: If we drew markers here, most markers would be drawn more than
|
||||
# once as they belong to several edges.
|
||||
# Note 2: We insert nan values in the flattened edges arrays rather than
|
||||
# plotting directly (triang.x[edges].T, triang.y[edges].T)
|
||||
# as it considerably speeds-up code execution.
|
||||
linestyle = kw['linestyle']
|
||||
kw_lines = {
|
||||
**kw,
|
||||
'marker': 'None', # No marker to draw.
|
||||
'zorder': kw.get('zorder', 1), # Path default zorder is used.
|
||||
}
|
||||
if linestyle not in [None, 'None', '', ' ']:
|
||||
tri_lines_x = np.insert(x[edges], 2, np.nan, axis=1)
|
||||
tri_lines_y = np.insert(y[edges], 2, np.nan, axis=1)
|
||||
tri_lines = ax.plot(tri_lines_x.ravel(), tri_lines_y.ravel(),
|
||||
**kw_lines)
|
||||
else:
|
||||
tri_lines = ax.plot([], [], **kw_lines)
|
||||
|
||||
# Draw markers separately.
|
||||
marker = kw['marker']
|
||||
kw_markers = {
|
||||
**kw,
|
||||
'linestyle': 'None', # No line to draw.
|
||||
}
|
||||
kw_markers.pop('label', None)
|
||||
if marker not in [None, 'None', '', ' ']:
|
||||
tri_markers = ax.plot(x, y, **kw_markers)
|
||||
else:
|
||||
tri_markers = ax.plot([], [], **kw_markers)
|
||||
|
||||
return tri_lines + tri_markers
|
@ -0,0 +1,15 @@
|
||||
from matplotlib.tri._triangulation import Triangulation
|
||||
from matplotlib.axes import Axes
|
||||
from matplotlib.lines import Line2D
|
||||
|
||||
from typing import overload
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
@overload
|
||||
def triplot(
|
||||
ax: Axes, triangulation: Triangulation, *args, **kwargs
|
||||
) -> tuple[Line2D, Line2D]: ...
|
||||
@overload
|
||||
def triplot(
|
||||
ax: Axes, x: ArrayLike, y: ArrayLike, triangles: ArrayLike = ..., *args, **kwargs
|
||||
) -> tuple[Line2D, Line2D]: ...
|
307
venv/lib/python3.12/site-packages/matplotlib/tri/_trirefine.py
Normal file
307
venv/lib/python3.12/site-packages/matplotlib/tri/_trirefine.py
Normal file
@ -0,0 +1,307 @@
|
||||
"""
|
||||
Mesh refinement for triangular grids.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import _api
|
||||
from matplotlib.tri._triangulation import Triangulation
|
||||
import matplotlib.tri._triinterpolate
|
||||
|
||||
|
||||
class TriRefiner:
|
||||
"""
|
||||
Abstract base class for classes implementing mesh refinement.
|
||||
|
||||
A TriRefiner encapsulates a Triangulation object and provides tools for
|
||||
mesh refinement and interpolation.
|
||||
|
||||
Derived classes must implement:
|
||||
|
||||
- ``refine_triangulation(return_tri_index=False, **kwargs)`` , where
|
||||
the optional keyword arguments *kwargs* are defined in each
|
||||
TriRefiner concrete implementation, and which returns:
|
||||
|
||||
- a refined triangulation,
|
||||
- optionally (depending on *return_tri_index*), for each
|
||||
point of the refined triangulation: the index of
|
||||
the initial triangulation triangle to which it belongs.
|
||||
|
||||
- ``refine_field(z, triinterpolator=None, **kwargs)``, where:
|
||||
|
||||
- *z* array of field values (to refine) defined at the base
|
||||
triangulation nodes,
|
||||
- *triinterpolator* is an optional `~matplotlib.tri.TriInterpolator`,
|
||||
- the other optional keyword arguments *kwargs* are defined in
|
||||
each TriRefiner concrete implementation;
|
||||
|
||||
and which returns (as a tuple) a refined triangular mesh and the
|
||||
interpolated values of the field at the refined triangulation nodes.
|
||||
"""
|
||||
|
||||
def __init__(self, triangulation):
|
||||
_api.check_isinstance(Triangulation, triangulation=triangulation)
|
||||
self._triangulation = triangulation
|
||||
|
||||
|
||||
class UniformTriRefiner(TriRefiner):
|
||||
"""
|
||||
Uniform mesh refinement by recursive subdivisions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
triangulation : `~matplotlib.tri.Triangulation`
|
||||
The encapsulated triangulation (to be refined)
|
||||
"""
|
||||
# See Also
|
||||
# --------
|
||||
# :class:`~matplotlib.tri.CubicTriInterpolator` and
|
||||
# :class:`~matplotlib.tri.TriAnalyzer`.
|
||||
# """
|
||||
def __init__(self, triangulation):
|
||||
super().__init__(triangulation)
|
||||
|
||||
def refine_triangulation(self, return_tri_index=False, subdiv=3):
|
||||
"""
|
||||
Compute a uniformly refined triangulation *refi_triangulation* of
|
||||
the encapsulated :attr:`triangulation`.
|
||||
|
||||
This function refines the encapsulated triangulation by splitting each
|
||||
father triangle into 4 child sub-triangles built on the edges midside
|
||||
nodes, recursing *subdiv* times. In the end, each triangle is hence
|
||||
divided into ``4**subdiv`` child triangles.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
return_tri_index : bool, default: False
|
||||
Whether an index table indicating the father triangle index of each
|
||||
point is returned.
|
||||
subdiv : int, default: 3
|
||||
Recursion level for the subdivision.
|
||||
Each triangle is divided into ``4**subdiv`` child triangles;
|
||||
hence, the default results in 64 refined subtriangles for each
|
||||
triangle of the initial triangulation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
refi_triangulation : `~matplotlib.tri.Triangulation`
|
||||
The refined triangulation.
|
||||
found_index : int array
|
||||
Index of the initial triangulation containing triangle, for each
|
||||
point of *refi_triangulation*.
|
||||
Returned only if *return_tri_index* is set to True.
|
||||
"""
|
||||
refi_triangulation = self._triangulation
|
||||
ntri = refi_triangulation.triangles.shape[0]
|
||||
|
||||
# Computes the triangulation ancestors numbers in the reference
|
||||
# triangulation.
|
||||
ancestors = np.arange(ntri, dtype=np.int32)
|
||||
for _ in range(subdiv):
|
||||
refi_triangulation, ancestors = self._refine_triangulation_once(
|
||||
refi_triangulation, ancestors)
|
||||
refi_npts = refi_triangulation.x.shape[0]
|
||||
refi_triangles = refi_triangulation.triangles
|
||||
|
||||
# Now we compute found_index table if needed
|
||||
if return_tri_index:
|
||||
# We have to initialize found_index with -1 because some nodes
|
||||
# may very well belong to no triangle at all, e.g., in case of
|
||||
# Delaunay Triangulation with DuplicatePointWarning.
|
||||
found_index = np.full(refi_npts, -1, dtype=np.int32)
|
||||
tri_mask = self._triangulation.mask
|
||||
if tri_mask is None:
|
||||
found_index[refi_triangles] = np.repeat(ancestors,
|
||||
3).reshape(-1, 3)
|
||||
else:
|
||||
# There is a subtlety here: we want to avoid whenever possible
|
||||
# that refined points container is a masked triangle (which
|
||||
# would result in artifacts in plots).
|
||||
# So we impose the numbering from masked ancestors first,
|
||||
# then overwrite it with unmasked ancestor numbers.
|
||||
ancestor_mask = tri_mask[ancestors]
|
||||
found_index[refi_triangles[ancestor_mask, :]
|
||||
] = np.repeat(ancestors[ancestor_mask],
|
||||
3).reshape(-1, 3)
|
||||
found_index[refi_triangles[~ancestor_mask, :]
|
||||
] = np.repeat(ancestors[~ancestor_mask],
|
||||
3).reshape(-1, 3)
|
||||
return refi_triangulation, found_index
|
||||
else:
|
||||
return refi_triangulation
|
||||
|
||||
def refine_field(self, z, triinterpolator=None, subdiv=3):
|
||||
"""
|
||||
Refine a field defined on the encapsulated triangulation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
z : (npoints,) array-like
|
||||
Values of the field to refine, defined at the nodes of the
|
||||
encapsulated triangulation. (``n_points`` is the number of points
|
||||
in the initial triangulation)
|
||||
triinterpolator : `~matplotlib.tri.TriInterpolator`, optional
|
||||
Interpolator used for field interpolation. If not specified,
|
||||
a `~matplotlib.tri.CubicTriInterpolator` will be used.
|
||||
subdiv : int, default: 3
|
||||
Recursion level for the subdivision.
|
||||
Each triangle is divided into ``4**subdiv`` child triangles.
|
||||
|
||||
Returns
|
||||
-------
|
||||
refi_tri : `~matplotlib.tri.Triangulation`
|
||||
The returned refined triangulation.
|
||||
refi_z : 1D array of length: *refi_tri* node count.
|
||||
The returned interpolated field (at *refi_tri* nodes).
|
||||
"""
|
||||
if triinterpolator is None:
|
||||
interp = matplotlib.tri.CubicTriInterpolator(
|
||||
self._triangulation, z)
|
||||
else:
|
||||
_api.check_isinstance(matplotlib.tri.TriInterpolator,
|
||||
triinterpolator=triinterpolator)
|
||||
interp = triinterpolator
|
||||
|
||||
refi_tri, found_index = self.refine_triangulation(
|
||||
subdiv=subdiv, return_tri_index=True)
|
||||
refi_z = interp._interpolate_multikeys(
|
||||
refi_tri.x, refi_tri.y, tri_index=found_index)[0]
|
||||
return refi_tri, refi_z
|
||||
|
||||
@staticmethod
|
||||
def _refine_triangulation_once(triangulation, ancestors=None):
|
||||
"""
|
||||
Refine a `.Triangulation` by splitting each triangle into 4
|
||||
child-masked_triangles built on the edges midside nodes.
|
||||
|
||||
Masked triangles, if present, are also split, but their children
|
||||
returned masked.
|
||||
|
||||
If *ancestors* is not provided, returns only a new triangulation:
|
||||
child_triangulation.
|
||||
|
||||
If the array-like key table *ancestor* is given, it shall be of shape
|
||||
(ntri,) where ntri is the number of *triangulation* masked_triangles.
|
||||
In this case, the function returns
|
||||
(child_triangulation, child_ancestors)
|
||||
child_ancestors is defined so that the 4 child masked_triangles share
|
||||
the same index as their father: child_ancestors.shape = (4 * ntri,).
|
||||
"""
|
||||
|
||||
x = triangulation.x
|
||||
y = triangulation.y
|
||||
|
||||
# According to tri.triangulation doc:
|
||||
# neighbors[i, j] is the triangle that is the neighbor
|
||||
# to the edge from point index masked_triangles[i, j] to point
|
||||
# index masked_triangles[i, (j+1)%3].
|
||||
neighbors = triangulation.neighbors
|
||||
triangles = triangulation.triangles
|
||||
npts = np.shape(x)[0]
|
||||
ntri = np.shape(triangles)[0]
|
||||
if ancestors is not None:
|
||||
ancestors = np.asarray(ancestors)
|
||||
if np.shape(ancestors) != (ntri,):
|
||||
raise ValueError(
|
||||
"Incompatible shapes provide for "
|
||||
"triangulation.masked_triangles and ancestors: "
|
||||
f"{np.shape(triangles)} and {np.shape(ancestors)}")
|
||||
|
||||
# Initiating tables refi_x and refi_y of the refined triangulation
|
||||
# points
|
||||
# hint: each apex is shared by 2 masked_triangles except the borders.
|
||||
borders = np.sum(neighbors == -1)
|
||||
added_pts = (3*ntri + borders) // 2
|
||||
refi_npts = npts + added_pts
|
||||
refi_x = np.zeros(refi_npts)
|
||||
refi_y = np.zeros(refi_npts)
|
||||
|
||||
# First part of refi_x, refi_y is just the initial points
|
||||
refi_x[:npts] = x
|
||||
refi_y[:npts] = y
|
||||
|
||||
# Second part contains the edge midside nodes.
|
||||
# Each edge belongs to 1 triangle (if border edge) or is shared by 2
|
||||
# masked_triangles (interior edge).
|
||||
# We first build 2 * ntri arrays of edge starting nodes (edge_elems,
|
||||
# edge_apexes); we then extract only the masters to avoid overlaps.
|
||||
# The so-called 'master' is the triangle with biggest index
|
||||
# The 'slave' is the triangle with lower index
|
||||
# (can be -1 if border edge)
|
||||
# For slave and master we will identify the apex pointing to the edge
|
||||
# start
|
||||
edge_elems = np.tile(np.arange(ntri, dtype=np.int32), 3)
|
||||
edge_apexes = np.repeat(np.arange(3, dtype=np.int32), ntri)
|
||||
edge_neighbors = neighbors[edge_elems, edge_apexes]
|
||||
mask_masters = (edge_elems > edge_neighbors)
|
||||
|
||||
# Identifying the "masters" and adding to refi_x, refi_y vec
|
||||
masters = edge_elems[mask_masters]
|
||||
apex_masters = edge_apexes[mask_masters]
|
||||
x_add = (x[triangles[masters, apex_masters]] +
|
||||
x[triangles[masters, (apex_masters+1) % 3]]) * 0.5
|
||||
y_add = (y[triangles[masters, apex_masters]] +
|
||||
y[triangles[masters, (apex_masters+1) % 3]]) * 0.5
|
||||
refi_x[npts:] = x_add
|
||||
refi_y[npts:] = y_add
|
||||
|
||||
# Building the new masked_triangles; each old masked_triangles hosts
|
||||
# 4 new masked_triangles
|
||||
# there are 6 pts to identify per 'old' triangle, 3 new_pt_corner and
|
||||
# 3 new_pt_midside
|
||||
new_pt_corner = triangles
|
||||
|
||||
# What is the index in refi_x, refi_y of point at middle of apex iapex
|
||||
# of elem ielem ?
|
||||
# If ielem is the apex master: simple count, given the way refi_x was
|
||||
# built.
|
||||
# If ielem is the apex slave: yet we do not know; but we will soon
|
||||
# using the neighbors table.
|
||||
new_pt_midside = np.empty([ntri, 3], dtype=np.int32)
|
||||
cum_sum = npts
|
||||
for imid in range(3):
|
||||
mask_st_loc = (imid == apex_masters)
|
||||
n_masters_loc = np.sum(mask_st_loc)
|
||||
elem_masters_loc = masters[mask_st_loc]
|
||||
new_pt_midside[:, imid][elem_masters_loc] = np.arange(
|
||||
n_masters_loc, dtype=np.int32) + cum_sum
|
||||
cum_sum += n_masters_loc
|
||||
|
||||
# Now dealing with slave elems.
|
||||
# for each slave element we identify the master and then the inode
|
||||
# once slave_masters is identified, slave_masters_apex is such that:
|
||||
# neighbors[slaves_masters, slave_masters_apex] == slaves
|
||||
mask_slaves = np.logical_not(mask_masters)
|
||||
slaves = edge_elems[mask_slaves]
|
||||
slaves_masters = edge_neighbors[mask_slaves]
|
||||
diff_table = np.abs(neighbors[slaves_masters, :] -
|
||||
np.outer(slaves, np.ones(3, dtype=np.int32)))
|
||||
slave_masters_apex = np.argmin(diff_table, axis=1)
|
||||
slaves_apex = edge_apexes[mask_slaves]
|
||||
new_pt_midside[slaves, slaves_apex] = new_pt_midside[
|
||||
slaves_masters, slave_masters_apex]
|
||||
|
||||
# Builds the 4 child masked_triangles
|
||||
child_triangles = np.empty([ntri*4, 3], dtype=np.int32)
|
||||
child_triangles[0::4, :] = np.vstack([
|
||||
new_pt_corner[:, 0], new_pt_midside[:, 0],
|
||||
new_pt_midside[:, 2]]).T
|
||||
child_triangles[1::4, :] = np.vstack([
|
||||
new_pt_corner[:, 1], new_pt_midside[:, 1],
|
||||
new_pt_midside[:, 0]]).T
|
||||
child_triangles[2::4, :] = np.vstack([
|
||||
new_pt_corner[:, 2], new_pt_midside[:, 2],
|
||||
new_pt_midside[:, 1]]).T
|
||||
child_triangles[3::4, :] = np.vstack([
|
||||
new_pt_midside[:, 0], new_pt_midside[:, 1],
|
||||
new_pt_midside[:, 2]]).T
|
||||
child_triangulation = Triangulation(refi_x, refi_y, child_triangles)
|
||||
|
||||
# Builds the child mask
|
||||
if triangulation.mask is not None:
|
||||
child_triangulation.set_mask(np.repeat(triangulation.mask, 4))
|
||||
|
||||
if ancestors is None:
|
||||
return child_triangulation
|
||||
else:
|
||||
return child_triangulation, np.repeat(ancestors, 4)
|
@ -0,0 +1,31 @@
|
||||
from typing import Literal, overload
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from matplotlib.tri._triangulation import Triangulation
|
||||
from matplotlib.tri._triinterpolate import TriInterpolator
|
||||
|
||||
class TriRefiner:
|
||||
def __init__(self, triangulation: Triangulation) -> None: ...
|
||||
|
||||
class UniformTriRefiner(TriRefiner):
|
||||
def __init__(self, triangulation: Triangulation) -> None: ...
|
||||
@overload
|
||||
def refine_triangulation(
|
||||
self, *, return_tri_index: Literal[True], subdiv: int = ...
|
||||
) -> tuple[Triangulation, np.ndarray]: ...
|
||||
@overload
|
||||
def refine_triangulation(
|
||||
self, return_tri_index: Literal[False] = ..., subdiv: int = ...
|
||||
) -> Triangulation: ...
|
||||
@overload
|
||||
def refine_triangulation(
|
||||
self, return_tri_index: bool = ..., subdiv: int = ...
|
||||
) -> tuple[Triangulation, np.ndarray] | Triangulation: ...
|
||||
def refine_field(
|
||||
self,
|
||||
z: ArrayLike,
|
||||
triinterpolator: TriInterpolator | None = ...,
|
||||
subdiv: int = ...,
|
||||
) -> tuple[Triangulation, np.ndarray]: ...
|
263
venv/lib/python3.12/site-packages/matplotlib/tri/_tritools.py
Normal file
263
venv/lib/python3.12/site-packages/matplotlib/tri/_tritools.py
Normal file
@ -0,0 +1,263 @@
|
||||
"""
|
||||
Tools for triangular grids.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import _api
|
||||
from matplotlib.tri import Triangulation
|
||||
|
||||
|
||||
class TriAnalyzer:
|
||||
"""
|
||||
Define basic tools for triangular mesh analysis and improvement.
|
||||
|
||||
A TriAnalyzer encapsulates a `.Triangulation` object and provides basic
|
||||
tools for mesh analysis and mesh improvement.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
scale_factors
|
||||
|
||||
Parameters
|
||||
----------
|
||||
triangulation : `~matplotlib.tri.Triangulation`
|
||||
The encapsulated triangulation to analyze.
|
||||
"""
|
||||
|
||||
def __init__(self, triangulation):
|
||||
_api.check_isinstance(Triangulation, triangulation=triangulation)
|
||||
self._triangulation = triangulation
|
||||
|
||||
@property
|
||||
def scale_factors(self):
|
||||
"""
|
||||
Factors to rescale the triangulation into a unit square.
|
||||
|
||||
Returns
|
||||
-------
|
||||
(float, float)
|
||||
Scaling factors (kx, ky) so that the triangulation
|
||||
``[triangulation.x * kx, triangulation.y * ky]``
|
||||
fits exactly inside a unit square.
|
||||
"""
|
||||
compressed_triangles = self._triangulation.get_masked_triangles()
|
||||
node_used = (np.bincount(np.ravel(compressed_triangles),
|
||||
minlength=self._triangulation.x.size) != 0)
|
||||
return (1 / np.ptp(self._triangulation.x[node_used]),
|
||||
1 / np.ptp(self._triangulation.y[node_used]))
|
||||
|
||||
def circle_ratios(self, rescale=True):
|
||||
"""
|
||||
Return a measure of the triangulation triangles flatness.
|
||||
|
||||
The ratio of the incircle radius over the circumcircle radius is a
|
||||
widely used indicator of a triangle flatness.
|
||||
It is always ``<= 0.5`` and ``== 0.5`` only for equilateral
|
||||
triangles. Circle ratios below 0.01 denote very flat triangles.
|
||||
|
||||
To avoid unduly low values due to a difference of scale between the 2
|
||||
axis, the triangular mesh can first be rescaled to fit inside a unit
|
||||
square with `scale_factors` (Only if *rescale* is True, which is
|
||||
its default value).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rescale : bool, default: True
|
||||
If True, internally rescale (based on `scale_factors`), so that the
|
||||
(unmasked) triangles fit exactly inside a unit square mesh.
|
||||
|
||||
Returns
|
||||
-------
|
||||
masked array
|
||||
Ratio of the incircle radius over the circumcircle radius, for
|
||||
each 'rescaled' triangle of the encapsulated triangulation.
|
||||
Values corresponding to masked triangles are masked out.
|
||||
|
||||
"""
|
||||
# Coords rescaling
|
||||
if rescale:
|
||||
(kx, ky) = self.scale_factors
|
||||
else:
|
||||
(kx, ky) = (1.0, 1.0)
|
||||
pts = np.vstack([self._triangulation.x*kx,
|
||||
self._triangulation.y*ky]).T
|
||||
tri_pts = pts[self._triangulation.triangles]
|
||||
# Computes the 3 side lengths
|
||||
a = tri_pts[:, 1, :] - tri_pts[:, 0, :]
|
||||
b = tri_pts[:, 2, :] - tri_pts[:, 1, :]
|
||||
c = tri_pts[:, 0, :] - tri_pts[:, 2, :]
|
||||
a = np.hypot(a[:, 0], a[:, 1])
|
||||
b = np.hypot(b[:, 0], b[:, 1])
|
||||
c = np.hypot(c[:, 0], c[:, 1])
|
||||
# circumcircle and incircle radii
|
||||
s = (a+b+c)*0.5
|
||||
prod = s*(a+b-s)*(a+c-s)*(b+c-s)
|
||||
# We have to deal with flat triangles with infinite circum_radius
|
||||
bool_flat = (prod == 0.)
|
||||
if np.any(bool_flat):
|
||||
# Pathologic flow
|
||||
ntri = tri_pts.shape[0]
|
||||
circum_radius = np.empty(ntri, dtype=np.float64)
|
||||
circum_radius[bool_flat] = np.inf
|
||||
abc = a*b*c
|
||||
circum_radius[~bool_flat] = abc[~bool_flat] / (
|
||||
4.0*np.sqrt(prod[~bool_flat]))
|
||||
else:
|
||||
# Normal optimized flow
|
||||
circum_radius = (a*b*c) / (4.0*np.sqrt(prod))
|
||||
in_radius = (a*b*c) / (4.0*circum_radius*s)
|
||||
circle_ratio = in_radius/circum_radius
|
||||
mask = self._triangulation.mask
|
||||
if mask is None:
|
||||
return circle_ratio
|
||||
else:
|
||||
return np.ma.array(circle_ratio, mask=mask)
|
||||
|
||||
def get_flat_tri_mask(self, min_circle_ratio=0.01, rescale=True):
|
||||
"""
|
||||
Eliminate excessively flat border triangles from the triangulation.
|
||||
|
||||
Returns a mask *new_mask* which allows to clean the encapsulated
|
||||
triangulation from its border-located flat triangles
|
||||
(according to their :meth:`circle_ratios`).
|
||||
This mask is meant to be subsequently applied to the triangulation
|
||||
using `.Triangulation.set_mask`.
|
||||
*new_mask* is an extension of the initial triangulation mask
|
||||
in the sense that an initially masked triangle will remain masked.
|
||||
|
||||
The *new_mask* array is computed recursively; at each step flat
|
||||
triangles are removed only if they share a side with the current mesh
|
||||
border. Thus, no new holes in the triangulated domain will be created.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
min_circle_ratio : float, default: 0.01
|
||||
Border triangles with incircle/circumcircle radii ratio r/R will
|
||||
be removed if r/R < *min_circle_ratio*.
|
||||
rescale : bool, default: True
|
||||
If True, first, internally rescale (based on `scale_factors`) so
|
||||
that the (unmasked) triangles fit exactly inside a unit square
|
||||
mesh. This rescaling accounts for the difference of scale which
|
||||
might exist between the 2 axis.
|
||||
|
||||
Returns
|
||||
-------
|
||||
array of bool
|
||||
Mask to apply to encapsulated triangulation.
|
||||
All the initially masked triangles remain masked in the
|
||||
*new_mask*.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The rationale behind this function is that a Delaunay
|
||||
triangulation - of an unstructured set of points - sometimes contains
|
||||
almost flat triangles at its border, leading to artifacts in plots
|
||||
(especially for high-resolution contouring).
|
||||
Masked with computed *new_mask*, the encapsulated
|
||||
triangulation would contain no more unmasked border triangles
|
||||
with a circle ratio below *min_circle_ratio*, thus improving the
|
||||
mesh quality for subsequent plots or interpolation.
|
||||
"""
|
||||
# Recursively computes the mask_current_borders, true if a triangle is
|
||||
# at the border of the mesh OR touching the border through a chain of
|
||||
# invalid aspect ratio masked_triangles.
|
||||
ntri = self._triangulation.triangles.shape[0]
|
||||
mask_bad_ratio = self.circle_ratios(rescale) < min_circle_ratio
|
||||
|
||||
current_mask = self._triangulation.mask
|
||||
if current_mask is None:
|
||||
current_mask = np.zeros(ntri, dtype=bool)
|
||||
valid_neighbors = np.copy(self._triangulation.neighbors)
|
||||
renum_neighbors = np.arange(ntri, dtype=np.int32)
|
||||
nadd = -1
|
||||
while nadd != 0:
|
||||
# The active wavefront is the triangles from the border (unmasked
|
||||
# but with a least 1 neighbor equal to -1
|
||||
wavefront = (np.min(valid_neighbors, axis=1) == -1) & ~current_mask
|
||||
# The element from the active wavefront will be masked if their
|
||||
# circle ratio is bad.
|
||||
added_mask = wavefront & mask_bad_ratio
|
||||
current_mask = added_mask | current_mask
|
||||
nadd = np.sum(added_mask)
|
||||
|
||||
# now we have to update the tables valid_neighbors
|
||||
valid_neighbors[added_mask, :] = -1
|
||||
renum_neighbors[added_mask] = -1
|
||||
valid_neighbors = np.where(valid_neighbors == -1, -1,
|
||||
renum_neighbors[valid_neighbors])
|
||||
|
||||
return np.ma.filled(current_mask, True)
|
||||
|
||||
def _get_compressed_triangulation(self):
|
||||
"""
|
||||
Compress (if masked) the encapsulated triangulation.
|
||||
|
||||
Returns minimal-length triangles array (*compressed_triangles*) and
|
||||
coordinates arrays (*compressed_x*, *compressed_y*) that can still
|
||||
describe the unmasked triangles of the encapsulated triangulation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
compressed_triangles : array-like
|
||||
the returned compressed triangulation triangles
|
||||
compressed_x : array-like
|
||||
the returned compressed triangulation 1st coordinate
|
||||
compressed_y : array-like
|
||||
the returned compressed triangulation 2nd coordinate
|
||||
tri_renum : int array
|
||||
renumbering table to translate the triangle numbers from the
|
||||
encapsulated triangulation into the new (compressed) renumbering.
|
||||
-1 for masked triangles (deleted from *compressed_triangles*).
|
||||
node_renum : int array
|
||||
renumbering table to translate the point numbers from the
|
||||
encapsulated triangulation into the new (compressed) renumbering.
|
||||
-1 for unused points (i.e. those deleted from *compressed_x* and
|
||||
*compressed_y*).
|
||||
|
||||
"""
|
||||
# Valid triangles and renumbering
|
||||
tri_mask = self._triangulation.mask
|
||||
compressed_triangles = self._triangulation.get_masked_triangles()
|
||||
ntri = self._triangulation.triangles.shape[0]
|
||||
if tri_mask is not None:
|
||||
tri_renum = self._total_to_compress_renum(~tri_mask)
|
||||
else:
|
||||
tri_renum = np.arange(ntri, dtype=np.int32)
|
||||
|
||||
# Valid nodes and renumbering
|
||||
valid_node = (np.bincount(np.ravel(compressed_triangles),
|
||||
minlength=self._triangulation.x.size) != 0)
|
||||
compressed_x = self._triangulation.x[valid_node]
|
||||
compressed_y = self._triangulation.y[valid_node]
|
||||
node_renum = self._total_to_compress_renum(valid_node)
|
||||
|
||||
# Now renumbering the valid triangles nodes
|
||||
compressed_triangles = node_renum[compressed_triangles]
|
||||
|
||||
return (compressed_triangles, compressed_x, compressed_y, tri_renum,
|
||||
node_renum)
|
||||
|
||||
@staticmethod
|
||||
def _total_to_compress_renum(valid):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
valid : 1D bool array
|
||||
Validity mask.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int array
|
||||
Array so that (`valid_array` being a compressed array
|
||||
based on a `masked_array` with mask ~*valid*):
|
||||
|
||||
- For all i with valid[i] = True:
|
||||
valid_array[renum[i]] = masked_array[i]
|
||||
- For all i with valid[i] = False:
|
||||
renum[i] = -1 (invalid value)
|
||||
"""
|
||||
renum = np.full(np.size(valid), -1, dtype=np.int32)
|
||||
n_valid = np.sum(valid)
|
||||
renum[valid] = np.arange(n_valid, dtype=np.int32)
|
||||
return renum
|
@ -0,0 +1,12 @@
|
||||
from matplotlib.tri import Triangulation
|
||||
|
||||
import numpy as np
|
||||
|
||||
class TriAnalyzer:
|
||||
def __init__(self, triangulation: Triangulation) -> None: ...
|
||||
@property
|
||||
def scale_factors(self) -> tuple[float, float]: ...
|
||||
def circle_ratios(self, rescale: bool = ...) -> np.ndarray: ...
|
||||
def get_flat_tri_mask(
|
||||
self, min_circle_ratio: float = ..., rescale: bool = ...
|
||||
) -> np.ndarray: ...
|
Reference in New Issue
Block a user