Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
1
venv/Lib/site-packages/mpl_toolkits/mplot3d/__init__.py
Normal file
1
venv/Lib/site-packages/mpl_toolkits/mplot3d/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .axes3d import Axes3D
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
811
venv/Lib/site-packages/mpl_toolkits/mplot3d/art3d.py
Normal file
811
venv/Lib/site-packages/mpl_toolkits/mplot3d/art3d.py
Normal file
|
@ -0,0 +1,811 @@
|
|||
# art3d.py, original mplot3d version by John Porter
|
||||
# Parts rewritten by Reinier Heeres <reinier@heeres.eu>
|
||||
# Minor additions by Ben Axelrod <baxelrod@coroware.com>
|
||||
|
||||
"""
|
||||
Module containing 3D artist code and functions to convert 2D
|
||||
artists into 3D versions which can be added to an Axes3D.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import (
|
||||
artist, colors as mcolors, lines, text as mtext, path as mpath)
|
||||
from matplotlib.collections import (
|
||||
LineCollection, PolyCollection, PatchCollection, PathCollection)
|
||||
from matplotlib.colors import Normalize
|
||||
from matplotlib.patches import Patch
|
||||
from . import proj3d
|
||||
|
||||
|
||||
def _norm_angle(a):
|
||||
"""Return the given angle normalized to -180 < *a* <= 180 degrees."""
|
||||
a = (a + 360) % 360
|
||||
if a > 180:
|
||||
a = a - 360
|
||||
return a
|
||||
|
||||
|
||||
def _norm_text_angle(a):
|
||||
"""Return the given angle normalized to -90 < *a* <= 90 degrees."""
|
||||
a = (a + 180) % 180
|
||||
if a > 90:
|
||||
a = a - 180
|
||||
return a
|
||||
|
||||
|
||||
def get_dir_vector(zdir):
|
||||
"""
|
||||
Return a direction vector.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
zdir : {'x', 'y', 'z', None, 3-tuple}
|
||||
The direction. Possible values are:
|
||||
- 'x': equivalent to (1, 0, 0)
|
||||
- 'y': equivalent to (0, 1, 0)
|
||||
- 'z': equivalent to (0, 0, 1)
|
||||
- *None*: equivalent to (0, 0, 0)
|
||||
- an iterable (x, y, z) is returned unchanged.
|
||||
|
||||
Returns
|
||||
-------
|
||||
x, y, z : array-like
|
||||
The direction vector. This is either a numpy.array or *zdir* itself if
|
||||
*zdir* is already a length-3 iterable.
|
||||
|
||||
"""
|
||||
if zdir == 'x':
|
||||
return np.array((1, 0, 0))
|
||||
elif zdir == 'y':
|
||||
return np.array((0, 1, 0))
|
||||
elif zdir == 'z':
|
||||
return np.array((0, 0, 1))
|
||||
elif zdir is None:
|
||||
return np.array((0, 0, 0))
|
||||
elif np.iterable(zdir) and len(zdir) == 3:
|
||||
return zdir
|
||||
else:
|
||||
raise ValueError("'x', 'y', 'z', None or vector of length 3 expected")
|
||||
|
||||
|
||||
class Text3D(mtext.Text):
|
||||
"""
|
||||
Text object with 3D position and direction.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y, z
|
||||
The position of the text.
|
||||
text : str
|
||||
The text string to display.
|
||||
zdir : {'x', 'y', 'z', None, 3-tuple}
|
||||
The direction of the text. See `.get_dir_vector` for a description of
|
||||
the values.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
**kwargs
|
||||
All other parameters are passed on to `~matplotlib.text.Text`.
|
||||
"""
|
||||
|
||||
def __init__(self, x=0, y=0, z=0, text='', zdir='z', **kwargs):
|
||||
mtext.Text.__init__(self, x, y, text, **kwargs)
|
||||
self.set_3d_properties(z, zdir)
|
||||
|
||||
def set_3d_properties(self, z=0, zdir='z'):
|
||||
x, y = self.get_position()
|
||||
self._position3d = np.array((x, y, z))
|
||||
self._dir_vec = get_dir_vector(zdir)
|
||||
self.stale = True
|
||||
|
||||
@artist.allow_rasterization
|
||||
def draw(self, renderer):
|
||||
proj = proj3d.proj_trans_points(
|
||||
[self._position3d, self._position3d + self._dir_vec], renderer.M)
|
||||
dx = proj[0][1] - proj[0][0]
|
||||
dy = proj[1][1] - proj[1][0]
|
||||
angle = math.degrees(math.atan2(dy, dx))
|
||||
self.set_position((proj[0][0], proj[1][0]))
|
||||
self.set_rotation(_norm_text_angle(angle))
|
||||
mtext.Text.draw(self, renderer)
|
||||
self.stale = False
|
||||
|
||||
def get_tightbbox(self, renderer):
|
||||
# Overwriting the 2d Text behavior which is not valid for 3d.
|
||||
# For now, just return None to exclude from layout calculation.
|
||||
return None
|
||||
|
||||
|
||||
def text_2d_to_3d(obj, z=0, zdir='z'):
|
||||
"""Convert a Text to a Text3D object."""
|
||||
obj.__class__ = Text3D
|
||||
obj.set_3d_properties(z, zdir)
|
||||
|
||||
|
||||
class Line3D(lines.Line2D):
|
||||
"""
|
||||
3D line object.
|
||||
"""
|
||||
|
||||
def __init__(self, xs, ys, zs, *args, **kwargs):
|
||||
"""
|
||||
Keyword arguments are passed onto :func:`~matplotlib.lines.Line2D`.
|
||||
"""
|
||||
lines.Line2D.__init__(self, [], [], *args, **kwargs)
|
||||
self._verts3d = xs, ys, zs
|
||||
|
||||
def set_3d_properties(self, zs=0, zdir='z'):
|
||||
xs = self.get_xdata()
|
||||
ys = self.get_ydata()
|
||||
zs = np.broadcast_to(zs, xs.shape)
|
||||
self._verts3d = juggle_axes(xs, ys, zs, zdir)
|
||||
self.stale = True
|
||||
|
||||
def set_data_3d(self, *args):
|
||||
"""
|
||||
Set the x, y and z data
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array-like
|
||||
The x-data to be plotted.
|
||||
y : array-like
|
||||
The y-data to be plotted.
|
||||
z : array-like
|
||||
The z-data to be plotted.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Accepts x, y, z arguments or a single array-like (x, y, z)
|
||||
"""
|
||||
if len(args) == 1:
|
||||
self._verts3d = args[0]
|
||||
else:
|
||||
self._verts3d = args
|
||||
self.stale = True
|
||||
|
||||
def get_data_3d(self):
|
||||
"""
|
||||
Get the current data
|
||||
|
||||
Returns
|
||||
-------
|
||||
verts3d : length-3 tuple or array-like
|
||||
The current data as a tuple or array-like.
|
||||
"""
|
||||
return self._verts3d
|
||||
|
||||
@artist.allow_rasterization
|
||||
def draw(self, renderer):
|
||||
xs3d, ys3d, zs3d = self._verts3d
|
||||
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
|
||||
self.set_data(xs, ys)
|
||||
lines.Line2D.draw(self, renderer)
|
||||
self.stale = False
|
||||
|
||||
|
||||
def line_2d_to_3d(line, zs=0, zdir='z'):
|
||||
"""Convert a 2D line to 3D."""
|
||||
|
||||
line.__class__ = Line3D
|
||||
line.set_3d_properties(zs, zdir)
|
||||
|
||||
|
||||
def _path_to_3d_segment(path, zs=0, zdir='z'):
|
||||
"""Convert a path to a 3D segment."""
|
||||
|
||||
zs = np.broadcast_to(zs, len(path))
|
||||
pathsegs = path.iter_segments(simplify=False, curves=False)
|
||||
seg = [(x, y, z) for (((x, y), code), z) in zip(pathsegs, zs)]
|
||||
seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg]
|
||||
return seg3d
|
||||
|
||||
|
||||
def _paths_to_3d_segments(paths, zs=0, zdir='z'):
|
||||
"""Convert paths from a collection object to 3D segments."""
|
||||
|
||||
zs = np.broadcast_to(zs, len(paths))
|
||||
segs = [_path_to_3d_segment(path, pathz, zdir)
|
||||
for path, pathz in zip(paths, zs)]
|
||||
return segs
|
||||
|
||||
|
||||
def _path_to_3d_segment_with_codes(path, zs=0, zdir='z'):
|
||||
"""Convert a path to a 3D segment with path codes."""
|
||||
|
||||
zs = np.broadcast_to(zs, len(path))
|
||||
pathsegs = path.iter_segments(simplify=False, curves=False)
|
||||
seg_codes = [((x, y, z), code) for ((x, y), code), z in zip(pathsegs, zs)]
|
||||
if seg_codes:
|
||||
seg, codes = zip(*seg_codes)
|
||||
seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg]
|
||||
else:
|
||||
seg3d = []
|
||||
codes = []
|
||||
return seg3d, list(codes)
|
||||
|
||||
|
||||
def _paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'):
|
||||
"""
|
||||
Convert paths from a collection object to 3D segments with path codes.
|
||||
"""
|
||||
|
||||
zs = np.broadcast_to(zs, len(paths))
|
||||
segments_codes = [_path_to_3d_segment_with_codes(path, pathz, zdir)
|
||||
for path, pathz in zip(paths, zs)]
|
||||
if segments_codes:
|
||||
segments, codes = zip(*segments_codes)
|
||||
else:
|
||||
segments, codes = [], []
|
||||
return list(segments), list(codes)
|
||||
|
||||
|
||||
class Line3DCollection(LineCollection):
|
||||
"""
|
||||
A collection of 3D lines.
|
||||
"""
|
||||
|
||||
def set_sort_zpos(self, val):
|
||||
"""Set the position to use for z-sorting."""
|
||||
self._sort_zpos = val
|
||||
self.stale = True
|
||||
|
||||
def set_segments(self, segments):
|
||||
"""
|
||||
Set 3D segments.
|
||||
"""
|
||||
self._segments3d = segments
|
||||
LineCollection.set_segments(self, [])
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
"""
|
||||
Project the points according to renderer matrix.
|
||||
"""
|
||||
xyslist = [
|
||||
proj3d.proj_trans_points(points, renderer.M) for points in
|
||||
self._segments3d]
|
||||
segments_2d = [np.column_stack([xs, ys]) for xs, ys, zs in xyslist]
|
||||
LineCollection.set_segments(self, segments_2d)
|
||||
|
||||
# FIXME
|
||||
minz = 1e9
|
||||
for xs, ys, zs in xyslist:
|
||||
minz = min(minz, min(zs))
|
||||
return minz
|
||||
|
||||
@artist.allow_rasterization
|
||||
def draw(self, renderer, project=False):
|
||||
if project:
|
||||
self.do_3d_projection(renderer)
|
||||
LineCollection.draw(self, renderer)
|
||||
|
||||
|
||||
def line_collection_2d_to_3d(col, zs=0, zdir='z'):
|
||||
"""Convert a LineCollection to a Line3DCollection object."""
|
||||
segments3d = _paths_to_3d_segments(col.get_paths(), zs, zdir)
|
||||
col.__class__ = Line3DCollection
|
||||
col.set_segments(segments3d)
|
||||
|
||||
|
||||
class Patch3D(Patch):
|
||||
"""
|
||||
3D patch object.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, zs=(), zdir='z', **kwargs):
|
||||
Patch.__init__(self, *args, **kwargs)
|
||||
self.set_3d_properties(zs, zdir)
|
||||
|
||||
def set_3d_properties(self, verts, zs=0, zdir='z'):
|
||||
zs = np.broadcast_to(zs, len(verts))
|
||||
self._segment3d = [juggle_axes(x, y, z, zdir)
|
||||
for ((x, y), z) in zip(verts, zs)]
|
||||
self._facecolor3d = Patch.get_facecolor(self)
|
||||
|
||||
def get_path(self):
|
||||
return self._path2d
|
||||
|
||||
def get_facecolor(self):
|
||||
return self._facecolor2d
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
s = self._segment3d
|
||||
xs, ys, zs = zip(*s)
|
||||
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
|
||||
self._path2d = mpath.Path(np.column_stack([vxs, vys]))
|
||||
# FIXME: coloring
|
||||
self._facecolor2d = self._facecolor3d
|
||||
return min(vzs)
|
||||
|
||||
|
||||
class PathPatch3D(Patch3D):
|
||||
"""
|
||||
3D PathPatch object.
|
||||
"""
|
||||
|
||||
def __init__(self, path, *, zs=(), zdir='z', **kwargs):
|
||||
Patch.__init__(self, **kwargs)
|
||||
self.set_3d_properties(path, zs, zdir)
|
||||
|
||||
def set_3d_properties(self, path, zs=0, zdir='z'):
|
||||
Patch3D.set_3d_properties(self, path.vertices, zs=zs, zdir=zdir)
|
||||
self._code3d = path.codes
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
s = self._segment3d
|
||||
xs, ys, zs = zip(*s)
|
||||
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
|
||||
self._path2d = mpath.Path(np.column_stack([vxs, vys]), self._code3d)
|
||||
# FIXME: coloring
|
||||
self._facecolor2d = self._facecolor3d
|
||||
return min(vzs)
|
||||
|
||||
|
||||
def _get_patch_verts(patch):
|
||||
"""Return a list of vertices for the path of a patch."""
|
||||
trans = patch.get_patch_transform()
|
||||
path = patch.get_path()
|
||||
polygons = path.to_polygons(trans)
|
||||
if len(polygons):
|
||||
return polygons[0]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def patch_2d_to_3d(patch, z=0, zdir='z'):
|
||||
"""Convert a Patch to a Patch3D object."""
|
||||
verts = _get_patch_verts(patch)
|
||||
patch.__class__ = Patch3D
|
||||
patch.set_3d_properties(verts, z, zdir)
|
||||
|
||||
|
||||
def pathpatch_2d_to_3d(pathpatch, z=0, zdir='z'):
|
||||
"""Convert a PathPatch to a PathPatch3D object."""
|
||||
path = pathpatch.get_path()
|
||||
trans = pathpatch.get_patch_transform()
|
||||
|
||||
mpath = trans.transform_path(path)
|
||||
pathpatch.__class__ = PathPatch3D
|
||||
pathpatch.set_3d_properties(mpath, z, zdir)
|
||||
|
||||
|
||||
class Patch3DCollection(PatchCollection):
|
||||
"""
|
||||
A collection of 3D patches.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs):
|
||||
"""
|
||||
Create a collection of flat 3D patches with its normal vector
|
||||
pointed in *zdir* direction, and located at *zs* on the *zdir*
|
||||
axis. 'zs' can be a scalar or an array-like of the same length as
|
||||
the number of patches in the collection.
|
||||
|
||||
Constructor arguments are the same as for
|
||||
:class:`~matplotlib.collections.PatchCollection`. In addition,
|
||||
keywords *zs=0* and *zdir='z'* are available.
|
||||
|
||||
Also, the keyword argument "depthshade" is available to
|
||||
indicate whether or not to shade the patches in order to
|
||||
give the appearance of depth (default is *True*).
|
||||
This is typically desired in scatter plots.
|
||||
"""
|
||||
self._depthshade = depthshade
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_3d_properties(zs, zdir)
|
||||
|
||||
def set_sort_zpos(self, val):
|
||||
"""Set the position to use for z-sorting."""
|
||||
self._sort_zpos = val
|
||||
self.stale = True
|
||||
|
||||
def set_3d_properties(self, zs, zdir):
|
||||
# Force the collection to initialize the face and edgecolors
|
||||
# just in case it is a scalarmappable with a colormap.
|
||||
self.update_scalarmappable()
|
||||
offsets = self.get_offsets()
|
||||
if len(offsets) > 0:
|
||||
xs, ys = offsets.T
|
||||
else:
|
||||
xs = []
|
||||
ys = []
|
||||
self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir)
|
||||
self._facecolor3d = self.get_facecolor()
|
||||
self._edgecolor3d = self.get_edgecolor()
|
||||
self.stale = True
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
xs, ys, zs = self._offsets3d
|
||||
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
|
||||
|
||||
fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else
|
||||
self._facecolor3d)
|
||||
fcs = mcolors.to_rgba_array(fcs, self._alpha)
|
||||
self.set_facecolors(fcs)
|
||||
|
||||
ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else
|
||||
self._edgecolor3d)
|
||||
ecs = mcolors.to_rgba_array(ecs, self._alpha)
|
||||
self.set_edgecolors(ecs)
|
||||
PatchCollection.set_offsets(self, np.column_stack([vxs, vys]))
|
||||
|
||||
if vzs.size > 0:
|
||||
return min(vzs)
|
||||
else:
|
||||
return np.nan
|
||||
|
||||
|
||||
class Path3DCollection(PathCollection):
|
||||
"""
|
||||
A collection of 3D paths.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs):
|
||||
"""
|
||||
Create a collection of flat 3D paths with its normal vector
|
||||
pointed in *zdir* direction, and located at *zs* on the *zdir*
|
||||
axis. 'zs' can be a scalar or an array-like of the same length as
|
||||
the number of paths in the collection.
|
||||
|
||||
Constructor arguments are the same as for
|
||||
:class:`~matplotlib.collections.PathCollection`. In addition,
|
||||
keywords *zs=0* and *zdir='z'* are available.
|
||||
|
||||
Also, the keyword argument "depthshade" is available to
|
||||
indicate whether or not to shade the patches in order to
|
||||
give the appearance of depth (default is *True*).
|
||||
This is typically desired in scatter plots.
|
||||
"""
|
||||
self._depthshade = depthshade
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_3d_properties(zs, zdir)
|
||||
|
||||
def set_sort_zpos(self, val):
|
||||
"""Set the position to use for z-sorting."""
|
||||
self._sort_zpos = val
|
||||
self.stale = True
|
||||
|
||||
def set_3d_properties(self, zs, zdir):
|
||||
# Force the collection to initialize the face and edgecolors
|
||||
# just in case it is a scalarmappable with a colormap.
|
||||
self.update_scalarmappable()
|
||||
offsets = self.get_offsets()
|
||||
if len(offsets) > 0:
|
||||
xs, ys = offsets.T
|
||||
else:
|
||||
xs = []
|
||||
ys = []
|
||||
self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir)
|
||||
self._facecolor3d = self.get_facecolor()
|
||||
self._edgecolor3d = self.get_edgecolor()
|
||||
self._sizes3d = self.get_sizes()
|
||||
self._linewidth3d = self.get_linewidth()
|
||||
self.stale = True
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
xs, ys, zs = self._offsets3d
|
||||
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
|
||||
|
||||
fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else
|
||||
self._facecolor3d)
|
||||
ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else
|
||||
self._edgecolor3d)
|
||||
sizes = self._sizes3d
|
||||
lws = self._linewidth3d
|
||||
|
||||
# Sort the points based on z coordinates
|
||||
# Performance optimization: Create a sorted index array and reorder
|
||||
# points and point properties according to the index array
|
||||
z_markers_idx = np.argsort(vzs)[::-1]
|
||||
|
||||
# Re-order items
|
||||
vzs = vzs[z_markers_idx]
|
||||
vxs = vxs[z_markers_idx]
|
||||
vys = vys[z_markers_idx]
|
||||
if len(fcs) > 1:
|
||||
fcs = fcs[z_markers_idx]
|
||||
if len(ecs) > 1:
|
||||
ecs = ecs[z_markers_idx]
|
||||
if len(sizes) > 1:
|
||||
sizes = sizes[z_markers_idx]
|
||||
if len(lws) > 1:
|
||||
lws = lws[z_markers_idx]
|
||||
vps = np.column_stack((vxs, vys))
|
||||
|
||||
fcs = mcolors.to_rgba_array(fcs, self._alpha)
|
||||
ecs = mcolors.to_rgba_array(ecs, self._alpha)
|
||||
|
||||
self.set_edgecolors(ecs)
|
||||
self.set_facecolors(fcs)
|
||||
self.set_sizes(sizes)
|
||||
self.set_linewidth(lws)
|
||||
|
||||
PathCollection.set_offsets(self, vps)
|
||||
|
||||
return np.min(vzs) if vzs.size else np.nan
|
||||
|
||||
|
||||
def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True):
|
||||
"""
|
||||
Convert a :class:`~matplotlib.collections.PatchCollection` into a
|
||||
:class:`Patch3DCollection` object
|
||||
(or a :class:`~matplotlib.collections.PathCollection` into a
|
||||
:class:`Path3DCollection` object).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
za
|
||||
The location or locations to place the patches in the collection along
|
||||
the *zdir* axis. Default: 0.
|
||||
zdir
|
||||
The axis in which to place the patches. Default: "z".
|
||||
depthshade
|
||||
Whether to shade the patches to give a sense of depth. Default: *True*.
|
||||
|
||||
"""
|
||||
if isinstance(col, PathCollection):
|
||||
col.__class__ = Path3DCollection
|
||||
elif isinstance(col, PatchCollection):
|
||||
col.__class__ = Patch3DCollection
|
||||
col._depthshade = depthshade
|
||||
col.set_3d_properties(zs, zdir)
|
||||
|
||||
|
||||
class Poly3DCollection(PolyCollection):
|
||||
"""
|
||||
A collection of 3D polygons.
|
||||
|
||||
.. note::
|
||||
**Filling of 3D polygons**
|
||||
|
||||
There is no simple definition of the enclosed surface of a 3D polygon
|
||||
unless the polygon is planar.
|
||||
|
||||
In practice, Matplotlib fills the 2D projection of the polygon. This
|
||||
gives a correct filling appearance only for planar polygons. For all
|
||||
other polygons, you'll find orientations in which the edges of the
|
||||
polygon intersect in the projection. This will lead to an incorrect
|
||||
visualization of the 3D area.
|
||||
|
||||
If you need filled areas, it is recommended to create them via
|
||||
`~mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf`, which creates a
|
||||
triangulation and thus generates consistent surfaces.
|
||||
"""
|
||||
|
||||
def __init__(self, verts, *args, zsort='average', **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
verts : list of array-like Nx3
|
||||
Each element describes a polygon as a sequence of ``N_i`` points
|
||||
``(x, y, z)``.
|
||||
zsort : {'average', 'min', 'max'}, default: 'average'
|
||||
The calculation method for the z-order.
|
||||
See `~.Poly3DCollection.set_zsort` for details.
|
||||
*args, **kwargs
|
||||
All other parameters are forwarded to `.PolyCollection`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Note that this class does a bit of magic with the _facecolors
|
||||
and _edgecolors properties.
|
||||
"""
|
||||
super().__init__(verts, *args, **kwargs)
|
||||
self.set_zsort(zsort)
|
||||
self._codes3d = None
|
||||
|
||||
_zsort_functions = {
|
||||
'average': np.average,
|
||||
'min': np.min,
|
||||
'max': np.max,
|
||||
}
|
||||
|
||||
def set_zsort(self, zsort):
|
||||
"""
|
||||
Set the calculation method for the z-order.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
zsort : {'average', 'min', 'max'}
|
||||
The function applied on the z-coordinates of the vertices in the
|
||||
viewer's coordinate system, to determine the z-order.
|
||||
"""
|
||||
self._zsortfunc = self._zsort_functions[zsort]
|
||||
self._sort_zpos = None
|
||||
self.stale = True
|
||||
|
||||
def get_vector(self, segments3d):
|
||||
"""Optimize points for projection."""
|
||||
if len(segments3d):
|
||||
xs, ys, zs = np.row_stack(segments3d).T
|
||||
else: # row_stack can't stack zero arrays.
|
||||
xs, ys, zs = [], [], []
|
||||
ones = np.ones(len(xs))
|
||||
self._vec = np.array([xs, ys, zs, ones])
|
||||
|
||||
indices = [0, *np.cumsum([len(segment) for segment in segments3d])]
|
||||
self._segslices = [*map(slice, indices[:-1], indices[1:])]
|
||||
|
||||
def set_verts(self, verts, closed=True):
|
||||
"""Set 3D vertices."""
|
||||
self.get_vector(verts)
|
||||
# 2D verts will be updated at draw time
|
||||
PolyCollection.set_verts(self, [], False)
|
||||
self._closed = closed
|
||||
|
||||
def set_verts_and_codes(self, verts, codes):
|
||||
"""Set 3D vertices with path codes."""
|
||||
# set vertices with closed=False to prevent PolyCollection from
|
||||
# setting path codes
|
||||
self.set_verts(verts, closed=False)
|
||||
# and set our own codes instead.
|
||||
self._codes3d = codes
|
||||
|
||||
def set_3d_properties(self):
|
||||
# Force the collection to initialize the face and edgecolors
|
||||
# just in case it is a scalarmappable with a colormap.
|
||||
self.update_scalarmappable()
|
||||
self._sort_zpos = None
|
||||
self.set_zsort('average')
|
||||
self._facecolors3d = PolyCollection.get_facecolor(self)
|
||||
self._edgecolors3d = PolyCollection.get_edgecolor(self)
|
||||
self._alpha3d = PolyCollection.get_alpha(self)
|
||||
self.stale = True
|
||||
|
||||
def set_sort_zpos(self, val):
|
||||
"""Set the position to use for z-sorting."""
|
||||
self._sort_zpos = val
|
||||
self.stale = True
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
"""
|
||||
Perform the 3D projection for this object.
|
||||
"""
|
||||
# FIXME: This may no longer be needed?
|
||||
if self._A is not None:
|
||||
self.update_scalarmappable()
|
||||
self._facecolors3d = self._facecolors
|
||||
|
||||
txs, tys, tzs = proj3d._proj_transform_vec(self._vec, renderer.M)
|
||||
xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices]
|
||||
|
||||
# This extra fuss is to re-order face / edge colors
|
||||
cface = self._facecolors3d
|
||||
cedge = self._edgecolors3d
|
||||
if len(cface) != len(xyzlist):
|
||||
cface = cface.repeat(len(xyzlist), axis=0)
|
||||
if len(cedge) != len(xyzlist):
|
||||
if len(cedge) == 0:
|
||||
cedge = cface
|
||||
else:
|
||||
cedge = cedge.repeat(len(xyzlist), axis=0)
|
||||
|
||||
# sort by depth (furthest drawn first)
|
||||
z_segments_2d = sorted(
|
||||
((self._zsortfunc(zs), np.column_stack([xs, ys]), fc, ec, idx)
|
||||
for idx, ((xs, ys, zs), fc, ec)
|
||||
in enumerate(zip(xyzlist, cface, cedge))),
|
||||
key=lambda x: x[0], reverse=True)
|
||||
|
||||
zzs, segments_2d, self._facecolors2d, self._edgecolors2d, idxs = \
|
||||
zip(*z_segments_2d)
|
||||
|
||||
if self._codes3d is not None:
|
||||
codes = [self._codes3d[idx] for idx in idxs]
|
||||
PolyCollection.set_verts_and_codes(self, segments_2d, codes)
|
||||
else:
|
||||
PolyCollection.set_verts(self, segments_2d, self._closed)
|
||||
|
||||
if len(self._edgecolors3d) != len(cface):
|
||||
self._edgecolors2d = self._edgecolors3d
|
||||
|
||||
# Return zorder value
|
||||
if self._sort_zpos is not None:
|
||||
zvec = np.array([[0], [0], [self._sort_zpos], [1]])
|
||||
ztrans = proj3d._proj_transform_vec(zvec, renderer.M)
|
||||
return ztrans[2][0]
|
||||
elif tzs.size > 0:
|
||||
# FIXME: Some results still don't look quite right.
|
||||
# In particular, examine contourf3d_demo2.py
|
||||
# with az = -54 and elev = -45.
|
||||
return np.min(tzs)
|
||||
else:
|
||||
return np.nan
|
||||
|
||||
def set_facecolor(self, colors):
|
||||
PolyCollection.set_facecolor(self, colors)
|
||||
self._facecolors3d = PolyCollection.get_facecolor(self)
|
||||
|
||||
def set_edgecolor(self, colors):
|
||||
PolyCollection.set_edgecolor(self, colors)
|
||||
self._edgecolors3d = PolyCollection.get_edgecolor(self)
|
||||
|
||||
def set_alpha(self, alpha):
|
||||
# docstring inherited
|
||||
artist.Artist.set_alpha(self, alpha)
|
||||
try:
|
||||
self._facecolors3d = mcolors.to_rgba_array(
|
||||
self._facecolors3d, self._alpha)
|
||||
except (AttributeError, TypeError, IndexError):
|
||||
pass
|
||||
try:
|
||||
self._edgecolors = mcolors.to_rgba_array(
|
||||
self._edgecolors3d, self._alpha)
|
||||
except (AttributeError, TypeError, IndexError):
|
||||
pass
|
||||
self.stale = True
|
||||
|
||||
def get_facecolor(self):
|
||||
return self._facecolors2d
|
||||
|
||||
def get_edgecolor(self):
|
||||
return self._edgecolors2d
|
||||
|
||||
|
||||
def poly_collection_2d_to_3d(col, zs=0, zdir='z'):
|
||||
"""Convert a PolyCollection to a Poly3DCollection object."""
|
||||
segments_3d, codes = _paths_to_3d_segments_with_codes(
|
||||
col.get_paths(), zs, zdir)
|
||||
col.__class__ = Poly3DCollection
|
||||
col.set_verts_and_codes(segments_3d, codes)
|
||||
col.set_3d_properties()
|
||||
|
||||
|
||||
def juggle_axes(xs, ys, zs, zdir):
|
||||
"""
|
||||
Reorder coordinates so that 2D xs, ys can be plotted in the plane
|
||||
orthogonal to zdir. zdir is normally x, y or z. However, if zdir
|
||||
starts with a '-' it is interpreted as a compensation for rotate_axes.
|
||||
"""
|
||||
if zdir == 'x':
|
||||
return zs, xs, ys
|
||||
elif zdir == 'y':
|
||||
return xs, zs, ys
|
||||
elif zdir[0] == '-':
|
||||
return rotate_axes(xs, ys, zs, zdir)
|
||||
else:
|
||||
return xs, ys, zs
|
||||
|
||||
|
||||
def rotate_axes(xs, ys, zs, zdir):
|
||||
"""
|
||||
Reorder coordinates so that the axes are rotated with zdir along
|
||||
the original z axis. Prepending the axis with a '-' does the
|
||||
inverse transform, so zdir can be x, -x, y, -y, z or -z
|
||||
"""
|
||||
if zdir == 'x':
|
||||
return ys, zs, xs
|
||||
elif zdir == '-x':
|
||||
return zs, xs, ys
|
||||
|
||||
elif zdir == 'y':
|
||||
return zs, xs, ys
|
||||
elif zdir == '-y':
|
||||
return ys, zs, xs
|
||||
|
||||
else:
|
||||
return xs, ys, zs
|
||||
|
||||
|
||||
def _get_colors(c, num):
|
||||
"""Stretch the color argument to provide the required number *num*."""
|
||||
return np.broadcast_to(
|
||||
mcolors.to_rgba_array(c) if len(c) else [0, 0, 0, 0],
|
||||
(num, 4))
|
||||
|
||||
|
||||
def _zalpha(colors, zs):
|
||||
"""Modify the alphas of the color list according to depth."""
|
||||
# FIXME: This only works well if the points for *zs* are well-spaced
|
||||
# in all three dimensions. Otherwise, at certain orientations,
|
||||
# the min and max zs are very close together.
|
||||
# Should really normalize against the viewing depth.
|
||||
if len(zs) == 0:
|
||||
return np.zeros((0, 4))
|
||||
norm = Normalize(min(zs), max(zs))
|
||||
sats = 1 - norm(zs) * 0.7
|
||||
rgba = np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs), 4))
|
||||
return np.column_stack([rgba[:, :3], rgba[:, 3] * sats])
|
2955
venv/Lib/site-packages/mpl_toolkits/mplot3d/axes3d.py
Normal file
2955
venv/Lib/site-packages/mpl_toolkits/mplot3d/axes3d.py
Normal file
File diff suppressed because it is too large
Load diff
488
venv/Lib/site-packages/mpl_toolkits/mplot3d/axis3d.py
Normal file
488
venv/Lib/site-packages/mpl_toolkits/mplot3d/axis3d.py
Normal file
|
@ -0,0 +1,488 @@
|
|||
# axis3d.py, original mplot3d version by John Porter
|
||||
# Created: 23 Sep 2005
|
||||
# Parts rewritten by Reinier Heeres <reinier@heeres.eu>
|
||||
|
||||
import numpy as np
|
||||
|
||||
import matplotlib.transforms as mtransforms
|
||||
from matplotlib import (
|
||||
artist, lines as mlines, axis as maxis, patches as mpatches, rcParams)
|
||||
from . import art3d, proj3d
|
||||
|
||||
|
||||
def move_from_center(coord, centers, deltas, axmask=(True, True, True)):
|
||||
"""
|
||||
For each coordinate where *axmask* is True, move *coord* away from
|
||||
*centers* by *deltas*.
|
||||
"""
|
||||
coord = np.asarray(coord)
|
||||
return coord + axmask * np.copysign(1, coord - centers) * deltas
|
||||
|
||||
|
||||
def tick_update_position(tick, tickxs, tickys, labelpos):
|
||||
"""Update tick line and label position and style."""
|
||||
|
||||
tick.label1.set_position(labelpos)
|
||||
tick.label2.set_position(labelpos)
|
||||
tick.tick1line.set_visible(True)
|
||||
tick.tick2line.set_visible(False)
|
||||
tick.tick1line.set_linestyle('-')
|
||||
tick.tick1line.set_marker('')
|
||||
tick.tick1line.set_data(tickxs, tickys)
|
||||
tick.gridline.set_data(0, 0)
|
||||
|
||||
|
||||
class Axis(maxis.XAxis):
|
||||
"""An Axis class for the 3D plots."""
|
||||
# These points from the unit cube make up the x, y and z-planes
|
||||
_PLANES = (
|
||||
(0, 3, 7, 4), (1, 2, 6, 5), # yz planes
|
||||
(0, 1, 5, 4), (3, 2, 6, 7), # xz planes
|
||||
(0, 1, 2, 3), (4, 5, 6, 7), # xy planes
|
||||
)
|
||||
|
||||
# Some properties for the axes
|
||||
_AXINFO = {
|
||||
'x': {'i': 0, 'tickdir': 1, 'juggled': (1, 0, 2),
|
||||
'color': (0.95, 0.95, 0.95, 0.5)},
|
||||
'y': {'i': 1, 'tickdir': 0, 'juggled': (0, 1, 2),
|
||||
'color': (0.90, 0.90, 0.90, 0.5)},
|
||||
'z': {'i': 2, 'tickdir': 0, 'juggled': (0, 2, 1),
|
||||
'color': (0.925, 0.925, 0.925, 0.5)},
|
||||
}
|
||||
|
||||
def __init__(self, adir, v_intervalx, d_intervalx, axes, *args,
|
||||
rotate_label=None, **kwargs):
|
||||
# adir identifies which axes this is
|
||||
self.adir = adir
|
||||
|
||||
# This is a temporary member variable.
|
||||
# Do not depend on this existing in future releases!
|
||||
self._axinfo = self._AXINFO[adir].copy()
|
||||
if rcParams['_internal.classic_mode']:
|
||||
self._axinfo.update({
|
||||
'label': {'va': 'center', 'ha': 'center'},
|
||||
'tick': {
|
||||
'inward_factor': 0.2,
|
||||
'outward_factor': 0.1,
|
||||
'linewidth': {
|
||||
True: rcParams['lines.linewidth'], # major
|
||||
False: rcParams['lines.linewidth'], # minor
|
||||
}
|
||||
},
|
||||
'axisline': {'linewidth': 0.75, 'color': (0, 0, 0, 1)},
|
||||
'grid': {
|
||||
'color': (0.9, 0.9, 0.9, 1),
|
||||
'linewidth': 1.0,
|
||||
'linestyle': '-',
|
||||
},
|
||||
})
|
||||
else:
|
||||
self._axinfo.update({
|
||||
'label': {'va': 'center', 'ha': 'center'},
|
||||
'tick': {
|
||||
'inward_factor': 0.2,
|
||||
'outward_factor': 0.1,
|
||||
'linewidth': {
|
||||
True: ( # major
|
||||
rcParams['xtick.major.width'] if adir in 'xz' else
|
||||
rcParams['ytick.major.width']),
|
||||
False: ( # minor
|
||||
rcParams['xtick.minor.width'] if adir in 'xz' else
|
||||
rcParams['ytick.minor.width']),
|
||||
}
|
||||
},
|
||||
'axisline': {
|
||||
'linewidth': rcParams['axes.linewidth'],
|
||||
'color': rcParams['axes.edgecolor'],
|
||||
},
|
||||
'grid': {
|
||||
'color': rcParams['grid.color'],
|
||||
'linewidth': rcParams['grid.linewidth'],
|
||||
'linestyle': rcParams['grid.linestyle'],
|
||||
},
|
||||
})
|
||||
|
||||
maxis.XAxis.__init__(self, axes, *args, **kwargs)
|
||||
|
||||
# data and viewing intervals for this direction
|
||||
self.d_interval = d_intervalx
|
||||
self.v_interval = v_intervalx
|
||||
self.set_rotate_label(rotate_label)
|
||||
|
||||
def init3d(self):
|
||||
self.line = mlines.Line2D(
|
||||
xdata=(0, 0), ydata=(0, 0),
|
||||
linewidth=self._axinfo['axisline']['linewidth'],
|
||||
color=self._axinfo['axisline']['color'],
|
||||
antialiased=True)
|
||||
|
||||
# Store dummy data in Polygon object
|
||||
self.pane = mpatches.Polygon(
|
||||
np.array([[0, 0], [0, 1], [1, 0], [0, 0]]),
|
||||
closed=False, alpha=0.8, facecolor='k', edgecolor='k')
|
||||
self.set_pane_color(self._axinfo['color'])
|
||||
|
||||
self.axes._set_artist_props(self.line)
|
||||
self.axes._set_artist_props(self.pane)
|
||||
self.gridlines = art3d.Line3DCollection([])
|
||||
self.axes._set_artist_props(self.gridlines)
|
||||
self.axes._set_artist_props(self.label)
|
||||
self.axes._set_artist_props(self.offsetText)
|
||||
# Need to be able to place the label at the correct location
|
||||
self.label._transform = self.axes.transData
|
||||
self.offsetText._transform = self.axes.transData
|
||||
|
||||
def get_major_ticks(self, numticks=None):
|
||||
ticks = maxis.XAxis.get_major_ticks(self, numticks)
|
||||
for t in ticks:
|
||||
for obj in [
|
||||
t.tick1line, t.tick2line, t.gridline, t.label1, t.label2]:
|
||||
obj.set_transform(self.axes.transData)
|
||||
return ticks
|
||||
|
||||
def get_minor_ticks(self, numticks=None):
|
||||
ticks = maxis.XAxis.get_minor_ticks(self, numticks)
|
||||
for t in ticks:
|
||||
for obj in [
|
||||
t.tick1line, t.tick2line, t.gridline, t.label1, t.label2]:
|
||||
obj.set_transform(self.axes.transData)
|
||||
return ticks
|
||||
|
||||
def set_pane_pos(self, xys):
|
||||
xys = np.asarray(xys)
|
||||
xys = xys[:, :2]
|
||||
self.pane.xy = xys
|
||||
self.stale = True
|
||||
|
||||
def set_pane_color(self, color):
|
||||
"""Set pane color to a RGBA tuple."""
|
||||
self._axinfo['color'] = color
|
||||
self.pane.set_edgecolor(color)
|
||||
self.pane.set_facecolor(color)
|
||||
self.pane.set_alpha(color[-1])
|
||||
self.stale = True
|
||||
|
||||
def set_rotate_label(self, val):
|
||||
"""
|
||||
Whether to rotate the axis label: True, False or None.
|
||||
If set to None the label will be rotated if longer than 4 chars.
|
||||
"""
|
||||
self._rotate_label = val
|
||||
self.stale = True
|
||||
|
||||
def get_rotate_label(self, text):
|
||||
if self._rotate_label is not None:
|
||||
return self._rotate_label
|
||||
else:
|
||||
return len(text) > 4
|
||||
|
||||
def _get_coord_info(self, renderer):
|
||||
mins, maxs = np.array([
|
||||
self.axes.get_xbound(),
|
||||
self.axes.get_ybound(),
|
||||
self.axes.get_zbound(),
|
||||
]).T
|
||||
centers = (maxs + mins) / 2.
|
||||
deltas = (maxs - mins) / 12.
|
||||
mins = mins - deltas / 4.
|
||||
maxs = maxs + deltas / 4.
|
||||
|
||||
vals = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2]
|
||||
tc = self.axes.tunit_cube(vals, renderer.M)
|
||||
avgz = [tc[p1][2] + tc[p2][2] + tc[p3][2] + tc[p4][2]
|
||||
for p1, p2, p3, p4 in self._PLANES]
|
||||
highs = np.array([avgz[2*i] < avgz[2*i+1] for i in range(3)])
|
||||
|
||||
return mins, maxs, centers, deltas, tc, highs
|
||||
|
||||
def draw_pane(self, renderer):
|
||||
renderer.open_group('pane3d', gid=self.get_gid())
|
||||
|
||||
mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
|
||||
|
||||
info = self._axinfo
|
||||
index = info['i']
|
||||
if not highs[index]:
|
||||
plane = self._PLANES[2 * index]
|
||||
else:
|
||||
plane = self._PLANES[2 * index + 1]
|
||||
xys = [tc[p] for p in plane]
|
||||
self.set_pane_pos(xys)
|
||||
self.pane.draw(renderer)
|
||||
|
||||
renderer.close_group('pane3d')
|
||||
|
||||
@artist.allow_rasterization
|
||||
def draw(self, renderer):
|
||||
self.label._transform = self.axes.transData
|
||||
renderer.open_group('axis3d', gid=self.get_gid())
|
||||
|
||||
ticks = self._update_ticks()
|
||||
|
||||
info = self._axinfo
|
||||
index = info['i']
|
||||
|
||||
mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
|
||||
|
||||
# Determine grid lines
|
||||
minmax = np.where(highs, maxs, mins)
|
||||
maxmin = np.where(highs, mins, maxs)
|
||||
|
||||
# Draw main axis line
|
||||
juggled = info['juggled']
|
||||
edgep1 = minmax.copy()
|
||||
edgep1[juggled[0]] = maxmin[juggled[0]]
|
||||
|
||||
edgep2 = edgep1.copy()
|
||||
edgep2[juggled[1]] = maxmin[juggled[1]]
|
||||
pep = np.asarray(
|
||||
proj3d.proj_trans_points([edgep1, edgep2], renderer.M))
|
||||
centpt = proj3d.proj_transform(*centers, renderer.M)
|
||||
self.line.set_data(pep[0], pep[1])
|
||||
self.line.draw(renderer)
|
||||
|
||||
# Grid points where the planes meet
|
||||
xyz0 = np.tile(minmax, (len(ticks), 1))
|
||||
xyz0[:, index] = [tick.get_loc() for tick in ticks]
|
||||
|
||||
# Draw labels
|
||||
# The transAxes transform is used because the Text object
|
||||
# rotates the text relative to the display coordinate system.
|
||||
# Therefore, if we want the labels to remain parallel to the
|
||||
# axis regardless of the aspect ratio, we need to convert the
|
||||
# edge points of the plane to display coordinates and calculate
|
||||
# an angle from that.
|
||||
# TODO: Maybe Text objects should handle this themselves?
|
||||
dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) -
|
||||
self.axes.transAxes.transform([pep[0:2, 0]]))[0]
|
||||
|
||||
lxyz = 0.5 * (edgep1 + edgep2)
|
||||
|
||||
# A rough estimate; points are ambiguous since 3D plots rotate
|
||||
ax_scale = self.axes.bbox.size / self.figure.bbox.size
|
||||
ax_inches = np.multiply(ax_scale, self.figure.get_size_inches())
|
||||
ax_points_estimate = sum(72. * ax_inches)
|
||||
deltas_per_point = 48 / ax_points_estimate
|
||||
default_offset = 21.
|
||||
labeldeltas = (
|
||||
(self.labelpad + default_offset) * deltas_per_point * deltas)
|
||||
axmask = [True, True, True]
|
||||
axmask[index] = False
|
||||
lxyz = move_from_center(lxyz, centers, labeldeltas, axmask)
|
||||
tlx, tly, tlz = proj3d.proj_transform(*lxyz, renderer.M)
|
||||
self.label.set_position((tlx, tly))
|
||||
if self.get_rotate_label(self.label.get_text()):
|
||||
angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx)))
|
||||
self.label.set_rotation(angle)
|
||||
self.label.set_va(info['label']['va'])
|
||||
self.label.set_ha(info['label']['ha'])
|
||||
self.label.draw(renderer)
|
||||
|
||||
# Draw Offset text
|
||||
|
||||
# Which of the two edge points do we want to
|
||||
# use for locating the offset text?
|
||||
if juggled[2] == 2:
|
||||
outeredgep = edgep1
|
||||
outerindex = 0
|
||||
else:
|
||||
outeredgep = edgep2
|
||||
outerindex = 1
|
||||
|
||||
pos = move_from_center(outeredgep, centers, labeldeltas, axmask)
|
||||
olx, oly, olz = proj3d.proj_transform(*pos, renderer.M)
|
||||
self.offsetText.set_text(self.major.formatter.get_offset())
|
||||
self.offsetText.set_position((olx, oly))
|
||||
angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx)))
|
||||
self.offsetText.set_rotation(angle)
|
||||
# Must set rotation mode to "anchor" so that
|
||||
# the alignment point is used as the "fulcrum" for rotation.
|
||||
self.offsetText.set_rotation_mode('anchor')
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Note: the following statement for determining the proper alignment of
|
||||
# the offset text. This was determined entirely by trial-and-error
|
||||
# and should not be in any way considered as "the way". There are
|
||||
# still some edge cases where alignment is not quite right, but this
|
||||
# seems to be more of a geometry issue (in other words, I might be
|
||||
# using the wrong reference points).
|
||||
#
|
||||
# (TT, FF, TF, FT) are the shorthand for the tuple of
|
||||
# (centpt[info['tickdir']] <= pep[info['tickdir'], outerindex],
|
||||
# centpt[index] <= pep[index, outerindex])
|
||||
#
|
||||
# Three-letters (e.g., TFT, FTT) are short-hand for the array of bools
|
||||
# from the variable 'highs'.
|
||||
# ---------------------------------------------------------------------
|
||||
if centpt[info['tickdir']] > pep[info['tickdir'], outerindex]:
|
||||
# if FT and if highs has an even number of Trues
|
||||
if (centpt[index] <= pep[index, outerindex]
|
||||
and np.count_nonzero(highs) % 2 == 0):
|
||||
# Usually, this means align right, except for the FTT case,
|
||||
# in which offset for axis 1 and 2 are aligned left.
|
||||
if highs.tolist() == [False, True, True] and index in (1, 2):
|
||||
align = 'left'
|
||||
else:
|
||||
align = 'right'
|
||||
else:
|
||||
# The FF case
|
||||
align = 'left'
|
||||
else:
|
||||
# if TF and if highs has an even number of Trues
|
||||
if (centpt[index] > pep[index, outerindex]
|
||||
and np.count_nonzero(highs) % 2 == 0):
|
||||
# Usually mean align left, except if it is axis 2
|
||||
if index == 2:
|
||||
align = 'right'
|
||||
else:
|
||||
align = 'left'
|
||||
else:
|
||||
# The TT case
|
||||
align = 'right'
|
||||
|
||||
self.offsetText.set_va('center')
|
||||
self.offsetText.set_ha(align)
|
||||
self.offsetText.draw(renderer)
|
||||
|
||||
if self.axes._draw_grid and len(ticks):
|
||||
# Grid lines go from the end of one plane through the plane
|
||||
# intersection (at xyz0) to the end of the other plane. The first
|
||||
# point (0) differs along dimension index-2 and the last (2) along
|
||||
# dimension index-1.
|
||||
lines = np.stack([xyz0, xyz0, xyz0], axis=1)
|
||||
lines[:, 0, index - 2] = maxmin[index - 2]
|
||||
lines[:, 2, index - 1] = maxmin[index - 1]
|
||||
self.gridlines.set_segments(lines)
|
||||
self.gridlines.set_color(info['grid']['color'])
|
||||
self.gridlines.set_linewidth(info['grid']['linewidth'])
|
||||
self.gridlines.set_linestyle(info['grid']['linestyle'])
|
||||
self.gridlines.draw(renderer, project=True)
|
||||
|
||||
# Draw ticks
|
||||
tickdir = info['tickdir']
|
||||
tickdelta = deltas[tickdir]
|
||||
if highs[tickdir]:
|
||||
ticksign = 1
|
||||
else:
|
||||
ticksign = -1
|
||||
|
||||
for tick in ticks:
|
||||
# Get tick line positions
|
||||
pos = edgep1.copy()
|
||||
pos[index] = tick.get_loc()
|
||||
pos[tickdir] = (
|
||||
edgep1[tickdir]
|
||||
+ info['tick']['outward_factor'] * ticksign * tickdelta)
|
||||
x1, y1, z1 = proj3d.proj_transform(*pos, renderer.M)
|
||||
pos[tickdir] = (
|
||||
edgep1[tickdir]
|
||||
- info['tick']['inward_factor'] * ticksign * tickdelta)
|
||||
x2, y2, z2 = proj3d.proj_transform(*pos, renderer.M)
|
||||
|
||||
# Get position of label
|
||||
default_offset = 8. # A rough estimate
|
||||
labeldeltas = (
|
||||
(tick.get_pad() + default_offset) * deltas_per_point * deltas)
|
||||
|
||||
axmask = [True, True, True]
|
||||
axmask[index] = False
|
||||
pos[tickdir] = edgep1[tickdir]
|
||||
pos = move_from_center(pos, centers, labeldeltas, axmask)
|
||||
lx, ly, lz = proj3d.proj_transform(*pos, renderer.M)
|
||||
|
||||
tick_update_position(tick, (x1, x2), (y1, y2), (lx, ly))
|
||||
tick.tick1line.set_linewidth(
|
||||
info['tick']['linewidth'][tick._major])
|
||||
tick.draw(renderer)
|
||||
|
||||
renderer.close_group('axis3d')
|
||||
self.stale = False
|
||||
|
||||
# TODO: Get this to work (more) properly when mplot3d supports the
|
||||
# transforms framework.
|
||||
def get_tightbbox(self, renderer, *, for_layout_only=False):
|
||||
# inherited docstring
|
||||
if not self.get_visible():
|
||||
return
|
||||
# We have to directly access the internal data structures
|
||||
# (and hope they are up to date) because at draw time we
|
||||
# shift the ticks and their labels around in (x, y) space
|
||||
# based on the projection, the current view port, and their
|
||||
# position in 3D space. If we extend the transforms framework
|
||||
# into 3D we would not need to do this different book keeping
|
||||
# than we do in the normal axis
|
||||
major_locs = self.get_majorticklocs()
|
||||
minor_locs = self.get_minorticklocs()
|
||||
|
||||
ticks = [*self.get_minor_ticks(len(minor_locs)),
|
||||
*self.get_major_ticks(len(major_locs))]
|
||||
view_low, view_high = self.get_view_interval()
|
||||
if view_low > view_high:
|
||||
view_low, view_high = view_high, view_low
|
||||
interval_t = self.get_transform().transform([view_low, view_high])
|
||||
|
||||
ticks_to_draw = []
|
||||
for tick in ticks:
|
||||
try:
|
||||
loc_t = self.get_transform().transform(tick.get_loc())
|
||||
except AssertionError:
|
||||
# Transform.transform doesn't allow masked values but
|
||||
# some scales might make them, so we need this try/except.
|
||||
pass
|
||||
else:
|
||||
if mtransforms._interval_contains_close(interval_t, loc_t):
|
||||
ticks_to_draw.append(tick)
|
||||
|
||||
ticks = ticks_to_draw
|
||||
|
||||
bb_1, bb_2 = self._get_tick_bboxes(ticks, renderer)
|
||||
other = []
|
||||
|
||||
if self.line.get_visible():
|
||||
other.append(self.line.get_window_extent(renderer))
|
||||
if (self.label.get_visible() and not for_layout_only and
|
||||
self.label.get_text()):
|
||||
other.append(self.label.get_window_extent(renderer))
|
||||
|
||||
return mtransforms.Bbox.union([*bb_1, *bb_2, *other])
|
||||
|
||||
@property
|
||||
def d_interval(self):
|
||||
return self.get_data_interval()
|
||||
|
||||
@d_interval.setter
|
||||
def d_interval(self, minmax):
|
||||
self.set_data_interval(*minmax)
|
||||
|
||||
@property
|
||||
def v_interval(self):
|
||||
return self.get_view_interval()
|
||||
|
||||
@v_interval.setter
|
||||
def v_interval(self, minmax):
|
||||
self.set_view_interval(*minmax)
|
||||
|
||||
|
||||
# Use classes to look at different data limits
|
||||
|
||||
|
||||
class XAxis(Axis):
|
||||
get_view_interval, set_view_interval = maxis._make_getset_interval(
|
||||
"view", "xy_viewLim", "intervalx")
|
||||
get_data_interval, set_data_interval = maxis._make_getset_interval(
|
||||
"data", "xy_dataLim", "intervalx")
|
||||
|
||||
|
||||
class YAxis(Axis):
|
||||
get_view_interval, set_view_interval = maxis._make_getset_interval(
|
||||
"view", "xy_viewLim", "intervaly")
|
||||
get_data_interval, set_data_interval = maxis._make_getset_interval(
|
||||
"data", "xy_dataLim", "intervaly")
|
||||
|
||||
|
||||
class ZAxis(Axis):
|
||||
get_view_interval, set_view_interval = maxis._make_getset_interval(
|
||||
"view", "zz_viewLim", "intervalx")
|
||||
get_data_interval, set_data_interval = maxis._make_getset_interval(
|
||||
"data", "zz_dataLim", "intervalx")
|
175
venv/Lib/site-packages/mpl_toolkits/mplot3d/proj3d.py
Normal file
175
venv/Lib/site-packages/mpl_toolkits/mplot3d/proj3d.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
Various transforms used for by the 3D code
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import numpy.linalg as linalg
|
||||
|
||||
|
||||
def _line2d_seg_dist(p1, p2, p0):
|
||||
"""
|
||||
Return the distance(s) from line defined by p1 - p2 to point(s) p0.
|
||||
|
||||
p0[0] = x(s)
|
||||
p0[1] = y(s)
|
||||
|
||||
intersection point p = p1 + u*(p2-p1)
|
||||
and intersection point lies within segment if u is between 0 and 1
|
||||
"""
|
||||
|
||||
x21 = p2[0] - p1[0]
|
||||
y21 = p2[1] - p1[1]
|
||||
x01 = np.asarray(p0[0]) - p1[0]
|
||||
y01 = np.asarray(p0[1]) - p1[1]
|
||||
|
||||
u = (x01*x21 + y01*y21) / (x21**2 + y21**2)
|
||||
u = np.clip(u, 0, 1)
|
||||
d = np.hypot(x01 - u*x21, y01 - u*y21)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def world_transformation(xmin, xmax,
|
||||
ymin, ymax,
|
||||
zmin, zmax, pb_aspect=None):
|
||||
"""
|
||||
Produce a matrix that scales homogeneous coords in the specified ranges
|
||||
to [0, 1], or [0, pb_aspect[i]] if the plotbox aspect ratio is specified.
|
||||
"""
|
||||
dx = xmax - xmin
|
||||
dy = ymax - ymin
|
||||
dz = zmax - zmin
|
||||
if pb_aspect is not None:
|
||||
ax, ay, az = pb_aspect
|
||||
dx /= ax
|
||||
dy /= ay
|
||||
dz /= az
|
||||
|
||||
return np.array([[1/dx, 0, 0, -xmin/dx],
|
||||
[0, 1/dy, 0, -ymin/dy],
|
||||
[0, 0, 1/dz, -zmin/dz],
|
||||
[0, 0, 0, 1]])
|
||||
|
||||
|
||||
def view_transformation(E, R, V):
|
||||
n = (E - R)
|
||||
## new
|
||||
# n /= np.linalg.norm(n)
|
||||
# u = np.cross(V, n)
|
||||
# u /= np.linalg.norm(u)
|
||||
# v = np.cross(n, u)
|
||||
# Mr = np.diag([1.] * 4)
|
||||
# Mt = np.diag([1.] * 4)
|
||||
# Mr[:3,:3] = u, v, n
|
||||
# Mt[:3,-1] = -E
|
||||
## end new
|
||||
|
||||
## old
|
||||
n = n / np.linalg.norm(n)
|
||||
u = np.cross(V, n)
|
||||
u = u / np.linalg.norm(u)
|
||||
v = np.cross(n, u)
|
||||
Mr = [[u[0], u[1], u[2], 0],
|
||||
[v[0], v[1], v[2], 0],
|
||||
[n[0], n[1], n[2], 0],
|
||||
[0, 0, 0, 1]]
|
||||
#
|
||||
Mt = [[1, 0, 0, -E[0]],
|
||||
[0, 1, 0, -E[1]],
|
||||
[0, 0, 1, -E[2]],
|
||||
[0, 0, 0, 1]]
|
||||
## end old
|
||||
|
||||
return np.dot(Mr, Mt)
|
||||
|
||||
|
||||
def persp_transformation(zfront, zback):
|
||||
a = (zfront+zback)/(zfront-zback)
|
||||
b = -2*(zfront*zback)/(zfront-zback)
|
||||
return np.array([[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, a, b],
|
||||
[0, 0, -1, 0]])
|
||||
|
||||
|
||||
def ortho_transformation(zfront, zback):
|
||||
# note: w component in the resulting vector will be (zback-zfront), not 1
|
||||
a = -(zfront + zback)
|
||||
b = -(zfront - zback)
|
||||
return np.array([[2, 0, 0, 0],
|
||||
[0, 2, 0, 0],
|
||||
[0, 0, -2, 0],
|
||||
[0, 0, a, b]])
|
||||
|
||||
|
||||
def _proj_transform_vec(vec, M):
|
||||
vecw = np.dot(M, vec)
|
||||
w = vecw[3]
|
||||
# clip here..
|
||||
txs, tys, tzs = vecw[0]/w, vecw[1]/w, vecw[2]/w
|
||||
return txs, tys, tzs
|
||||
|
||||
|
||||
def _proj_transform_vec_clip(vec, M):
|
||||
vecw = np.dot(M, vec)
|
||||
w = vecw[3]
|
||||
# clip here.
|
||||
txs, tys, tzs = vecw[0] / w, vecw[1] / w, vecw[2] / w
|
||||
tis = (0 <= vecw[0]) & (vecw[0] <= 1) & (0 <= vecw[1]) & (vecw[1] <= 1)
|
||||
if np.any(tis):
|
||||
tis = vecw[1] < 1
|
||||
return txs, tys, tzs, tis
|
||||
|
||||
|
||||
def inv_transform(xs, ys, zs, M):
|
||||
iM = linalg.inv(M)
|
||||
vec = _vec_pad_ones(xs, ys, zs)
|
||||
vecr = np.dot(iM, vec)
|
||||
try:
|
||||
vecr = vecr / vecr[3]
|
||||
except OverflowError:
|
||||
pass
|
||||
return vecr[0], vecr[1], vecr[2]
|
||||
|
||||
|
||||
def _vec_pad_ones(xs, ys, zs):
|
||||
return np.array([xs, ys, zs, np.ones_like(xs)])
|
||||
|
||||
|
||||
def proj_transform(xs, ys, zs, M):
|
||||
"""
|
||||
Transform the points by the projection matrix
|
||||
"""
|
||||
vec = _vec_pad_ones(xs, ys, zs)
|
||||
return _proj_transform_vec(vec, M)
|
||||
|
||||
|
||||
transform = proj_transform
|
||||
|
||||
|
||||
def proj_transform_clip(xs, ys, zs, M):
|
||||
"""
|
||||
Transform the points by the projection matrix
|
||||
and return the clipping result
|
||||
returns txs, tys, tzs, tis
|
||||
"""
|
||||
vec = _vec_pad_ones(xs, ys, zs)
|
||||
return _proj_transform_vec_clip(vec, M)
|
||||
|
||||
|
||||
def proj_points(points, M):
|
||||
return np.column_stack(proj_trans_points(points, M))
|
||||
|
||||
|
||||
def proj_trans_points(points, M):
|
||||
xs, ys, zs = zip(*points)
|
||||
return proj_transform(xs, ys, zs, M)
|
||||
|
||||
|
||||
def rot_x(V, alpha):
|
||||
cosa, sina = np.cos(alpha), np.sin(alpha)
|
||||
M1 = np.array([[1, 0, 0, 0],
|
||||
[0, cosa, -sina, 0],
|
||||
[0, sina, cosa, 0],
|
||||
[0, 0, 0, 1]])
|
||||
return np.dot(M1, V)
|
Loading…
Add table
Add a link
Reference in a new issue