Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/mpl_toolkits/mplot3d/art3d.py : 23%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# art3d.py, original mplot3d version by John Porter
2# Parts rewritten by Reinier Heeres <reinier@heeres.eu>
3# Minor additions by Ben Axelrod <baxelrod@coroware.com>
5"""
6Module containing 3D artist code and functions to convert 2D
7artists into 3D versions which can be added to an Axes3D.
8"""
10import math
12import numpy as np
14from matplotlib import (
15 artist, cbook, colors as mcolors, lines, text as mtext, path as mpath)
16from matplotlib.collections import (
17 LineCollection, PolyCollection, PatchCollection, PathCollection)
18from matplotlib.colors import Normalize
19from matplotlib.patches import Patch
20from . import proj3d
23def _norm_angle(a):
24 """Return the given angle normalized to -180 < *a* <= 180 degrees."""
25 a = (a + 360) % 360
26 if a > 180:
27 a = a - 360
28 return a
31@cbook.deprecated("3.1")
32def norm_angle(a):
33 """Return the given angle normalized to -180 < *a* <= 180 degrees."""
34 return _norm_angle(a)
37def _norm_text_angle(a):
38 """Return the given angle normalized to -90 < *a* <= 90 degrees."""
39 a = (a + 180) % 180
40 if a > 90:
41 a = a - 180
42 return a
45@cbook.deprecated("3.1")
46def norm_text_angle(a):
47 """Return the given angle normalized to -90 < *a* <= 90 degrees."""
48 return _norm_text_angle(a)
51def get_dir_vector(zdir):
52 """
53 Return a direction vector.
55 Parameters
56 ----------
57 zdir : {'x', 'y', 'z', None, 3-tuple}
58 The direction. Possible values are:
59 - 'x': equivalent to (1, 0, 0)
60 - 'y': equivalent to (0, 1, 0)
61 - 'z': equivalent to (0, 0, 1)
62 - *None*: equivalent to (0, 0, 0)
63 - an iterable (x, y, z) is returned unchanged.
65 Returns
66 -------
67 x, y, z : array-like
68 The direction vector. This is either a numpy.array or *zdir* itself if
69 *zdir* is already a length-3 iterable.
71 """
72 if zdir == 'x':
73 return np.array((1, 0, 0))
74 elif zdir == 'y':
75 return np.array((0, 1, 0))
76 elif zdir == 'z':
77 return np.array((0, 0, 1))
78 elif zdir is None:
79 return np.array((0, 0, 0))
80 elif np.iterable(zdir) and len(zdir) == 3:
81 return zdir
82 else:
83 raise ValueError("'x', 'y', 'z', None or vector of length 3 expected")
86class Text3D(mtext.Text):
87 """
88 Text object with 3D position and direction.
90 Parameters
91 ----------
92 x, y, z
93 The position of the text.
94 text : str
95 The text string to display.
96 zdir : {'x', 'y', 'z', None, 3-tuple}
97 The direction of the text. See `.get_dir_vector` for a description of
98 the values.
100 Other Parameters
101 ----------------
102 **kwargs
103 All other parameters are passed on to `~matplotlib.text.Text`.
104 """
106 def __init__(self, x=0, y=0, z=0, text='', zdir='z', **kwargs):
107 mtext.Text.__init__(self, x, y, text, **kwargs)
108 self.set_3d_properties(z, zdir)
110 def set_3d_properties(self, z=0, zdir='z'):
111 x, y = self.get_position()
112 self._position3d = np.array((x, y, z))
113 self._dir_vec = get_dir_vector(zdir)
114 self.stale = True
116 @artist.allow_rasterization
117 def draw(self, renderer):
118 proj = proj3d.proj_trans_points(
119 [self._position3d, self._position3d + self._dir_vec], renderer.M)
120 dx = proj[0][1] - proj[0][0]
121 dy = proj[1][1] - proj[1][0]
122 angle = math.degrees(math.atan2(dy, dx))
123 self.set_position((proj[0][0], proj[1][0]))
124 self.set_rotation(_norm_text_angle(angle))
125 mtext.Text.draw(self, renderer)
126 self.stale = False
128 def get_tightbbox(self, renderer):
129 # Overwriting the 2d Text behavior which is not valid for 3d.
130 # For now, just return None to exclude from layout calculation.
131 return None
134def text_2d_to_3d(obj, z=0, zdir='z'):
135 """Convert a Text to a Text3D object."""
136 obj.__class__ = Text3D
137 obj.set_3d_properties(z, zdir)
140class Line3D(lines.Line2D):
141 """
142 3D line object.
143 """
145 def __init__(self, xs, ys, zs, *args, **kwargs):
146 """
147 Keyword arguments are passed onto :func:`~matplotlib.lines.Line2D`.
148 """
149 lines.Line2D.__init__(self, [], [], *args, **kwargs)
150 self._verts3d = xs, ys, zs
152 def set_3d_properties(self, zs=0, zdir='z'):
153 xs = self.get_xdata()
154 ys = self.get_ydata()
156 try:
157 # If *zs* is a list or array, then this will fail and
158 # just proceed to juggle_axes().
159 zs = np.full_like(xs, fill_value=float(zs))
160 except TypeError:
161 pass
162 self._verts3d = juggle_axes(xs, ys, zs, zdir)
163 self.stale = True
165 def set_data_3d(self, *args):
166 """
167 Set the x, y and z data
169 Parameters
170 ----------
171 x : array-like
172 The x-data to be plotted.
173 y : array-like
174 The y-data to be plotted.
175 z : array-like
176 The z-data to be plotted.
178 Notes
179 -----
180 Accepts x, y, z arguments or a single array-like (x, y, z)
181 """
182 if len(args) == 1:
183 self._verts3d = args[0]
184 else:
185 self._verts3d = args
186 self.stale = True
188 def get_data_3d(self):
189 """
190 Get the current data
192 Returns
193 -------
194 verts3d : length-3 tuple or array-likes
195 The current data as a tuple or array-likes.
196 """
197 return self._verts3d
199 @artist.allow_rasterization
200 def draw(self, renderer):
201 xs3d, ys3d, zs3d = self._verts3d
202 xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
203 self.set_data(xs, ys)
204 lines.Line2D.draw(self, renderer)
205 self.stale = False
208def line_2d_to_3d(line, zs=0, zdir='z'):
209 """Convert a 2D line to 3D."""
211 line.__class__ = Line3D
212 line.set_3d_properties(zs, zdir)
215def _path_to_3d_segment(path, zs=0, zdir='z'):
216 """Convert a path to a 3D segment."""
218 zs = np.broadcast_to(zs, len(path))
219 pathsegs = path.iter_segments(simplify=False, curves=False)
220 seg = [(x, y, z) for (((x, y), code), z) in zip(pathsegs, zs)]
221 seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg]
222 return seg3d
225@cbook.deprecated("3.1")
226def path_to_3d_segment(path, zs=0, zdir='z'):
227 """Convert a path to a 3D segment."""
228 return _path_to_3d_segment(path, zs=zs, zdir=zdir)
231def _paths_to_3d_segments(paths, zs=0, zdir='z'):
232 """Convert paths from a collection object to 3D segments."""
234 zs = np.broadcast_to(zs, len(paths))
235 segs = [_path_to_3d_segment(path, pathz, zdir)
236 for path, pathz in zip(paths, zs)]
237 return segs
240@cbook.deprecated("3.1")
241def paths_to_3d_segments(paths, zs=0, zdir='z'):
242 """Convert paths from a collection object to 3D segments."""
243 return _paths_to_3d_segments(paths, zs=zs, zdir=zdir)
246def _path_to_3d_segment_with_codes(path, zs=0, zdir='z'):
247 """Convert a path to a 3D segment with path codes."""
249 zs = np.broadcast_to(zs, len(path))
250 pathsegs = path.iter_segments(simplify=False, curves=False)
251 seg_codes = [((x, y, z), code) for ((x, y), code), z in zip(pathsegs, zs)]
252 if seg_codes:
253 seg, codes = zip(*seg_codes)
254 seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg]
255 else:
256 seg3d = []
257 codes = []
258 return seg3d, list(codes)
261@cbook.deprecated("3.1")
262def path_to_3d_segment_with_codes(path, zs=0, zdir='z'):
263 """Convert a path to a 3D segment with path codes."""
264 return _path_to_3d_segment_with_codes(path, zs=zs, zdir=zdir)
267def _paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'):
268 """
269 Convert paths from a collection object to 3D segments with path codes.
270 """
272 zs = np.broadcast_to(zs, len(paths))
273 segments_codes = [_path_to_3d_segment_with_codes(path, pathz, zdir)
274 for path, pathz in zip(paths, zs)]
275 if segments_codes:
276 segments, codes = zip(*segments_codes)
277 else:
278 segments, codes = [], []
279 return list(segments), list(codes)
282@cbook.deprecated("3.1")
283def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'):
284 """
285 Convert paths from a collection object to 3D segments with path codes.
286 """
287 return _paths_to_3d_segments_with_codes(paths, zs=zs, zdir=zdir)
290class Line3DCollection(LineCollection):
291 """
292 A collection of 3D lines.
293 """
295 def set_sort_zpos(self, val):
296 """Set the position to use for z-sorting."""
297 self._sort_zpos = val
298 self.stale = True
300 def set_segments(self, segments):
301 """
302 Set 3D segments.
303 """
304 self._segments3d = np.asanyarray(segments)
305 LineCollection.set_segments(self, [])
307 def do_3d_projection(self, renderer):
308 """
309 Project the points according to renderer matrix.
310 """
311 xyslist = [
312 proj3d.proj_trans_points(points, renderer.M) for points in
313 self._segments3d]
314 segments_2d = [np.column_stack([xs, ys]) for xs, ys, zs in xyslist]
315 LineCollection.set_segments(self, segments_2d)
317 # FIXME
318 minz = 1e9
319 for xs, ys, zs in xyslist:
320 minz = min(minz, min(zs))
321 return minz
323 @artist.allow_rasterization
324 def draw(self, renderer, project=False):
325 if project:
326 self.do_3d_projection(renderer)
327 LineCollection.draw(self, renderer)
330def line_collection_2d_to_3d(col, zs=0, zdir='z'):
331 """Convert a LineCollection to a Line3DCollection object."""
332 segments3d = _paths_to_3d_segments(col.get_paths(), zs, zdir)
333 col.__class__ = Line3DCollection
334 col.set_segments(segments3d)
337class Patch3D(Patch):
338 """
339 3D patch object.
340 """
342 def __init__(self, *args, zs=(), zdir='z', **kwargs):
343 Patch.__init__(self, *args, **kwargs)
344 self.set_3d_properties(zs, zdir)
346 def set_3d_properties(self, verts, zs=0, zdir='z'):
347 zs = np.broadcast_to(zs, len(verts))
348 self._segment3d = [juggle_axes(x, y, z, zdir)
349 for ((x, y), z) in zip(verts, zs)]
350 self._facecolor3d = Patch.get_facecolor(self)
352 def get_path(self):
353 return self._path2d
355 def get_facecolor(self):
356 return self._facecolor2d
358 def do_3d_projection(self, renderer):
359 s = self._segment3d
360 xs, ys, zs = zip(*s)
361 vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
362 self._path2d = mpath.Path(np.column_stack([vxs, vys]))
363 # FIXME: coloring
364 self._facecolor2d = self._facecolor3d
365 return min(vzs)
368class PathPatch3D(Patch3D):
369 """
370 3D PathPatch object.
371 """
373 def __init__(self, path, *, zs=(), zdir='z', **kwargs):
374 Patch.__init__(self, **kwargs)
375 self.set_3d_properties(path, zs, zdir)
377 def set_3d_properties(self, path, zs=0, zdir='z'):
378 Patch3D.set_3d_properties(self, path.vertices, zs=zs, zdir=zdir)
379 self._code3d = path.codes
381 def do_3d_projection(self, renderer):
382 s = self._segment3d
383 xs, ys, zs = zip(*s)
384 vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
385 self._path2d = mpath.Path(np.column_stack([vxs, vys]), self._code3d)
386 # FIXME: coloring
387 self._facecolor2d = self._facecolor3d
388 return min(vzs)
391def _get_patch_verts(patch):
392 """Return a list of vertices for the path of a patch."""
393 trans = patch.get_patch_transform()
394 path = patch.get_path()
395 polygons = path.to_polygons(trans)
396 if len(polygons):
397 return polygons[0]
398 else:
399 return []
402@cbook.deprecated("3.1")
403def get_patch_verts(patch):
404 """Return a list of vertices for the path of a patch."""
405 return _get_patch_verts(patch)
408def patch_2d_to_3d(patch, z=0, zdir='z'):
409 """Convert a Patch to a Patch3D object."""
410 verts = _get_patch_verts(patch)
411 patch.__class__ = Patch3D
412 patch.set_3d_properties(verts, z, zdir)
415def pathpatch_2d_to_3d(pathpatch, z=0, zdir='z'):
416 """Convert a PathPatch to a PathPatch3D object."""
417 path = pathpatch.get_path()
418 trans = pathpatch.get_patch_transform()
420 mpath = trans.transform_path(path)
421 pathpatch.__class__ = PathPatch3D
422 pathpatch.set_3d_properties(mpath, z, zdir)
425class Patch3DCollection(PatchCollection):
426 """
427 A collection of 3D patches.
428 """
430 def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs):
431 """
432 Create a collection of flat 3D patches with its normal vector
433 pointed in *zdir* direction, and located at *zs* on the *zdir*
434 axis. 'zs' can be a scalar or an array-like of the same length as
435 the number of patches in the collection.
437 Constructor arguments are the same as for
438 :class:`~matplotlib.collections.PatchCollection`. In addition,
439 keywords *zs=0* and *zdir='z'* are available.
441 Also, the keyword argument "depthshade" is available to
442 indicate whether or not to shade the patches in order to
443 give the appearance of depth (default is *True*).
444 This is typically desired in scatter plots.
445 """
446 self._depthshade = depthshade
447 super().__init__(*args, **kwargs)
448 self.set_3d_properties(zs, zdir)
450 def set_sort_zpos(self, val):
451 """Set the position to use for z-sorting."""
452 self._sort_zpos = val
453 self.stale = True
455 def set_3d_properties(self, zs, zdir):
456 # Force the collection to initialize the face and edgecolors
457 # just in case it is a scalarmappable with a colormap.
458 self.update_scalarmappable()
459 offsets = self.get_offsets()
460 if len(offsets) > 0:
461 xs, ys = offsets.T
462 else:
463 xs = []
464 ys = []
465 self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir)
466 self._facecolor3d = self.get_facecolor()
467 self._edgecolor3d = self.get_edgecolor()
468 self.stale = True
470 def do_3d_projection(self, renderer):
471 xs, ys, zs = self._offsets3d
472 vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
474 fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else
475 self._facecolor3d)
476 fcs = mcolors.to_rgba_array(fcs, self._alpha)
477 self.set_facecolors(fcs)
479 ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else
480 self._edgecolor3d)
481 ecs = mcolors.to_rgba_array(ecs, self._alpha)
482 self.set_edgecolors(ecs)
483 PatchCollection.set_offsets(self, np.column_stack([vxs, vys]))
485 if vzs.size > 0:
486 return min(vzs)
487 else:
488 return np.nan
491class Path3DCollection(PathCollection):
492 """
493 A collection of 3D paths.
494 """
496 def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs):
497 """
498 Create a collection of flat 3D paths with its normal vector
499 pointed in *zdir* direction, and located at *zs* on the *zdir*
500 axis. 'zs' can be a scalar or an array-like of the same length as
501 the number of paths in the collection.
503 Constructor arguments are the same as for
504 :class:`~matplotlib.collections.PathCollection`. In addition,
505 keywords *zs=0* and *zdir='z'* are available.
507 Also, the keyword argument "depthshade" is available to
508 indicate whether or not to shade the patches in order to
509 give the appearance of depth (default is *True*).
510 This is typically desired in scatter plots.
511 """
512 self._depthshade = depthshade
513 super().__init__(*args, **kwargs)
514 self.set_3d_properties(zs, zdir)
516 def set_sort_zpos(self, val):
517 """Set the position to use for z-sorting."""
518 self._sort_zpos = val
519 self.stale = True
521 def set_3d_properties(self, zs, zdir):
522 # Force the collection to initialize the face and edgecolors
523 # just in case it is a scalarmappable with a colormap.
524 self.update_scalarmappable()
525 offsets = self.get_offsets()
526 if len(offsets) > 0:
527 xs, ys = offsets.T
528 else:
529 xs = []
530 ys = []
531 self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir)
532 self._facecolor3d = self.get_facecolor()
533 self._edgecolor3d = self.get_edgecolor()
534 self.stale = True
536 def do_3d_projection(self, renderer):
537 xs, ys, zs = self._offsets3d
538 vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
540 fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else
541 self._facecolor3d)
542 fcs = mcolors.to_rgba_array(fcs, self._alpha)
543 self.set_facecolors(fcs)
545 ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else
546 self._edgecolor3d)
547 ecs = mcolors.to_rgba_array(ecs, self._alpha)
548 self.set_edgecolors(ecs)
549 PathCollection.set_offsets(self, np.column_stack([vxs, vys]))
551 return np.min(vzs) if vzs.size else np.nan
554def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True):
555 """
556 Convert a :class:`~matplotlib.collections.PatchCollection` into a
557 :class:`Patch3DCollection` object
558 (or a :class:`~matplotlib.collections.PathCollection` into a
559 :class:`Path3DCollection` object).
561 Parameters
562 ----------
563 za
564 The location or locations to place the patches in the collection along
565 the *zdir* axis. Default: 0.
566 zdir
567 The axis in which to place the patches. Default: "z".
568 depthshade
569 Whether to shade the patches to give a sense of depth. Default: *True*.
571 """
572 if isinstance(col, PathCollection):
573 col.__class__ = Path3DCollection
574 elif isinstance(col, PatchCollection):
575 col.__class__ = Patch3DCollection
576 col._depthshade = depthshade
577 col.set_3d_properties(zs, zdir)
580class Poly3DCollection(PolyCollection):
581 """
582 A collection of 3D polygons.
584 .. note::
585 **Filling of 3D polygons**
587 There is no simple definition of the enclosed surface of a 3D polygon
588 unless the polygon is planar.
590 In practice, Matplotlib performs the filling on the 2D projection of
591 the polygon. This gives a correct filling appearance only for planar
592 polygons. For all other polygons, you'll find orientations in which
593 the edges of the polygon intersect in the projection. This will lead
594 to an incorrect visualization of the 3D area.
596 If you need filled areas, it is recommended to create them via
597 `~mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf`, which creates a
598 triangulation and thus generates consistent surfaces.
599 """
601 def __init__(self, verts, *args, zsort='average', **kwargs):
602 """
603 Parameters
604 ----------
605 verts : list of array-like Nx3
606 Each element describes a polygon as a sequnce of ``N_i`` points
607 ``(x, y, z)``.
608 zsort : {'average', 'min', 'max'}, default: 'average'
609 The calculation method for the z-order.
610 See `~.Poly3DCollection.set_zsort` for details.
611 *args, **kwargs
612 All other parameters are forwarded to `.PolyCollection`.
614 Notes
615 -----
616 Note that this class does a bit of magic with the _facecolors
617 and _edgecolors properties.
618 """
619 super().__init__(verts, *args, **kwargs)
620 self.set_zsort(zsort)
621 self._codes3d = None
623 _zsort_functions = {
624 'average': np.average,
625 'min': np.min,
626 'max': np.max,
627 }
629 def set_zsort(self, zsort):
630 """
631 Sets the calculation method for the z-order.
633 Parameters
634 ----------
635 zsort : {'average', 'min', 'max'}
636 The function applied on the z-coordinates of the vertices in the
637 viewer's coordinate system, to determine the z-order. *True* is
638 deprecated and equivalent to 'average'.
639 """
640 if zsort is True:
641 cbook.warn_deprecated(
642 "3.1", message="Passing True to mean 'average' for set_zsort "
643 "is deprecated and support will be removed in Matplotlib 3.3; "
644 "pass 'average' instead.")
645 zsort = 'average'
646 self._zsortfunc = self._zsort_functions[zsort]
647 self._sort_zpos = None
648 self.stale = True
650 def get_vector(self, segments3d):
651 """Optimize points for projection."""
652 if len(segments3d):
653 xs, ys, zs = np.row_stack(segments3d).T
654 else: # row_stack can't stack zero arrays.
655 xs, ys, zs = [], [], []
656 ones = np.ones(len(xs))
657 self._vec = np.array([xs, ys, zs, ones])
659 indices = [0, *np.cumsum([len(segment) for segment in segments3d])]
660 self._segslices = [*map(slice, indices[:-1], indices[1:])]
662 def set_verts(self, verts, closed=True):
663 """Set 3D vertices."""
664 self.get_vector(verts)
665 # 2D verts will be updated at draw time
666 PolyCollection.set_verts(self, [], False)
667 self._closed = closed
669 def set_verts_and_codes(self, verts, codes):
670 """Sets 3D vertices with path codes."""
671 # set vertices with closed=False to prevent PolyCollection from
672 # setting path codes
673 self.set_verts(verts, closed=False)
674 # and set our own codes instead.
675 self._codes3d = codes
677 def set_3d_properties(self):
678 # Force the collection to initialize the face and edgecolors
679 # just in case it is a scalarmappable with a colormap.
680 self.update_scalarmappable()
681 self._sort_zpos = None
682 self.set_zsort('average')
683 self._facecolors3d = PolyCollection.get_facecolor(self)
684 self._edgecolors3d = PolyCollection.get_edgecolor(self)
685 self._alpha3d = PolyCollection.get_alpha(self)
686 self.stale = True
688 def set_sort_zpos(self, val):
689 """Set the position to use for z-sorting."""
690 self._sort_zpos = val
691 self.stale = True
693 def do_3d_projection(self, renderer):
694 """
695 Perform the 3D projection for this object.
696 """
697 # FIXME: This may no longer be needed?
698 if self._A is not None:
699 self.update_scalarmappable()
700 self._facecolors3d = self._facecolors
702 txs, tys, tzs = proj3d._proj_transform_vec(self._vec, renderer.M)
703 xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices]
705 # This extra fuss is to re-order face / edge colors
706 cface = self._facecolors3d
707 cedge = self._edgecolors3d
708 if len(cface) != len(xyzlist):
709 cface = cface.repeat(len(xyzlist), axis=0)
710 if len(cedge) != len(xyzlist):
711 if len(cedge) == 0:
712 cedge = cface
713 else:
714 cedge = cedge.repeat(len(xyzlist), axis=0)
716 # sort by depth (furthest drawn first)
717 z_segments_2d = sorted(
718 ((self._zsortfunc(zs), np.column_stack([xs, ys]), fc, ec, idx)
719 for idx, ((xs, ys, zs), fc, ec)
720 in enumerate(zip(xyzlist, cface, cedge))),
721 key=lambda x: x[0], reverse=True)
723 segments_2d = [s for z, s, fc, ec, idx in z_segments_2d]
724 if self._codes3d is not None:
725 codes = [self._codes3d[idx] for z, s, fc, ec, idx in z_segments_2d]
726 PolyCollection.set_verts_and_codes(self, segments_2d, codes)
727 else:
728 PolyCollection.set_verts(self, segments_2d, self._closed)
730 self._facecolors2d = [fc for z, s, fc, ec, idx in z_segments_2d]
731 if len(self._edgecolors3d) == len(cface):
732 self._edgecolors2d = [ec for z, s, fc, ec, idx in z_segments_2d]
733 else:
734 self._edgecolors2d = self._edgecolors3d
736 # Return zorder value
737 if self._sort_zpos is not None:
738 zvec = np.array([[0], [0], [self._sort_zpos], [1]])
739 ztrans = proj3d._proj_transform_vec(zvec, renderer.M)
740 return ztrans[2][0]
741 elif tzs.size > 0:
742 # FIXME: Some results still don't look quite right.
743 # In particular, examine contourf3d_demo2.py
744 # with az = -54 and elev = -45.
745 return np.min(tzs)
746 else:
747 return np.nan
749 def set_facecolor(self, colors):
750 PolyCollection.set_facecolor(self, colors)
751 self._facecolors3d = PolyCollection.get_facecolor(self)
753 def set_edgecolor(self, colors):
754 PolyCollection.set_edgecolor(self, colors)
755 self._edgecolors3d = PolyCollection.get_edgecolor(self)
757 def set_alpha(self, alpha):
758 # docstring inherited
759 artist.Artist.set_alpha(self, alpha)
760 try:
761 self._facecolors3d = mcolors.to_rgba_array(
762 self._facecolors3d, self._alpha)
763 except (AttributeError, TypeError, IndexError):
764 pass
765 try:
766 self._edgecolors = mcolors.to_rgba_array(
767 self._edgecolors3d, self._alpha)
768 except (AttributeError, TypeError, IndexError):
769 pass
770 self.stale = True
772 def get_facecolor(self):
773 return self._facecolors2d
775 def get_edgecolor(self):
776 return self._edgecolors2d
779def poly_collection_2d_to_3d(col, zs=0, zdir='z'):
780 """Convert a PolyCollection to a Poly3DCollection object."""
781 segments_3d, codes = _paths_to_3d_segments_with_codes(
782 col.get_paths(), zs, zdir)
783 col.__class__ = Poly3DCollection
784 col.set_verts_and_codes(segments_3d, codes)
785 col.set_3d_properties()
788def juggle_axes(xs, ys, zs, zdir):
789 """
790 Reorder coordinates so that 2D xs, ys can be plotted in the plane
791 orthogonal to zdir. zdir is normally x, y or z. However, if zdir
792 starts with a '-' it is interpreted as a compensation for rotate_axes.
793 """
794 if zdir == 'x':
795 return zs, xs, ys
796 elif zdir == 'y':
797 return xs, zs, ys
798 elif zdir[0] == '-':
799 return rotate_axes(xs, ys, zs, zdir)
800 else:
801 return xs, ys, zs
804def rotate_axes(xs, ys, zs, zdir):
805 """
806 Reorder coordinates so that the axes are rotated with zdir along
807 the original z axis. Prepending the axis with a '-' does the
808 inverse transform, so zdir can be x, -x, y, -y, z or -z
809 """
810 if zdir == 'x':
811 return ys, zs, xs
812 elif zdir == '-x':
813 return zs, xs, ys
815 elif zdir == 'y':
816 return zs, xs, ys
817 elif zdir == '-y':
818 return ys, zs, xs
820 else:
821 return xs, ys, zs
824def _get_colors(c, num):
825 """Stretch the color argument to provide the required number *num*."""
826 return np.broadcast_to(
827 mcolors.to_rgba_array(c) if len(c) else [0, 0, 0, 0],
828 (num, 4))
831@cbook.deprecated("3.1")
832def get_colors(c, num):
833 """Stretch the color argument to provide the required number *num*."""
834 return _get_colors(c, num)
837def _zalpha(colors, zs):
838 """Modify the alphas of the color list according to depth."""
839 # FIXME: This only works well if the points for *zs* are well-spaced
840 # in all three dimensions. Otherwise, at certain orientations,
841 # the min and max zs are very close together.
842 # Should really normalize against the viewing depth.
843 if len(zs) == 0:
844 return np.zeros((0, 4))
845 norm = Normalize(min(zs), max(zs))
846 sats = 1 - norm(zs) * 0.7
847 rgba = np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs), 4))
848 return np.column_stack([rgba[:, :3], rgba[:, 3] * sats])
851@cbook.deprecated("3.1")
852def zalpha(colors, zs):
853 """Modify the alphas of the color list according to depth."""
854 return _zalpha(colors, zs)