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

Skip to content

Commit eb00c0c

Browse files
committed
Add path code information to Poly3DCollection
Fixes matplotlib#4784 by adding path code information to the Poly3DCollection. This adds two new methods, path_to_3d_segment_with_codes and paths_to_3d_segments_with_codes which are meant to replace the versions without "_with_codes". A method was added to PolyCollection to allow Poly3DCollection to set vertices with path codes. Add image test for a case that causes improper polygon rendering without this fix. This code was adapted from a PR by @ianthomas23.
1 parent 563129c commit eb00c0c

File tree

7 files changed

+583
-10
lines changed

7 files changed

+583
-10
lines changed

doc/users/whats_new/plotting.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,13 @@ points are contoured as usual. If the ``corner_mask`` keyword argument is not
2929
specified, the default value is taken from rcParams.
3030

3131
.. plot:: mpl_examples/pylab_examples/contour_corner_mask.py
32+
33+
Fixed 3D filled contour plot polygon rendering
34+
``````````````````````````````````````````````
35+
36+
Certain cases of 3D filled contour plots that produce polygons with multiple
37+
holes produced improper rendering due to a loss of path information between
38+
:class:`~matplotlib.collections.PolyCollection` and
39+
:class:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`. A function
40+
:func:`~matplotlib.collections.PolyCollection.set_verts_and_codes` was
41+
added to allow path information to be retained for proper rendering.

lib/matplotlib/collections.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,19 @@ def set_verts(self, verts, closed=True):
888888

889889
set_paths = set_verts
890890

891+
def set_verts_and_codes(self, verts, codes):
892+
'''This allows one to initialize vertices with path codes.'''
893+
if (len(verts) != len(codes)):
894+
raise ValueError("'codes' must be a 1D list or array "
895+
"with the same length of 'verts'")
896+
self._paths = []
897+
for xy, cds in zip(verts, codes):
898+
if len(xy):
899+
self._paths.append(mpath.Path(xy, cds))
900+
else:
901+
self._paths.append(mpath.Path(xy))
902+
self.stale = True
903+
891904

892905
class BrokenBarHCollection(PolyCollection):
893906
"""

lib/mpl_toolkits/mplot3d/art3d.py

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,37 @@ def paths_to_3d_segments(paths, zs=0, zdir='z'):
166166
segments.append(path_to_3d_segment(path, pathz, zdir))
167167
return segments
168168

169+
def path_to_3d_segment_with_codes(path, zs=0, zdir='z'):
170+
'''Convert a path to a 3D segment with path codes.'''
171+
172+
if not iterable(zs):
173+
zs = np.ones(len(path)) * zs
174+
175+
seg = []
176+
codes = []
177+
pathsegs = path.iter_segments(simplify=False, curves=False)
178+
for (((x, y), code), z) in zip(pathsegs, zs):
179+
seg.append((x, y, z))
180+
codes.append(code)
181+
seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg]
182+
return seg3d, codes
183+
184+
def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'):
185+
'''
186+
Convert paths from a collection object to 3D segments with path codes.
187+
'''
188+
189+
if not iterable(zs):
190+
zs = np.ones(len(paths)) * zs
191+
192+
segments = []
193+
codes_list = []
194+
for path, pathz in zip(paths, zs):
195+
segs, codes = path_to_3d_segment_with_codes(path, pathz, zdir)
196+
segments.append(segs)
197+
codes_list.append(codes)
198+
return segments, codes_list
199+
169200
class Line3DCollection(LineCollection):
170201
'''
171202
A collection of 3D lines.
@@ -487,6 +518,7 @@ def __init__(self, verts, *args, **kwargs):
487518
zsort = kwargs.pop('zsort', True)
488519
PolyCollection.__init__(self, verts, *args, **kwargs)
489520
self.set_zsort(zsort)
521+
self._codes3d = None
490522

491523
_zsort_functions = {
492524
'average': np.average,
@@ -545,6 +577,14 @@ def set_verts(self, verts, closed=True):
545577
# 2D verts will be updated at draw time
546578
PolyCollection.set_verts(self, [], closed)
547579

580+
def set_verts_and_codes(self, verts, codes):
581+
'''Sets 3D vertices with path codes'''
582+
# set vertices with closed=False to prevent PolyCollection from
583+
# setting path codes
584+
self.set_verts(verts, closed=False)
585+
# and set our own codes instead.
586+
self._codes3d = codes
587+
548588
def set_3d_properties(self):
549589
# Force the collection to initialize the face and edgecolors
550590
# just in case it is a scalarmappable with a colormap.
@@ -571,8 +611,8 @@ def do_3d_projection(self, renderer):
571611
self._facecolors3d = self._facecolors
572612

573613
txs, tys, tzs = proj3d.proj_transform_vec(self._vec, renderer.M)
574-
xyzlist = [(txs[si:ei], tys[si:ei], tzs[si:ei]) \
575-
for si, ei in self._segis]
614+
xyzlist = [(txs[si:ei], tys[si:ei], tzs[si:ei])
615+
for si, ei in self._segis]
576616

577617
# This extra fuss is to re-order face / edge colors
578618
cface = self._facecolors3d
@@ -586,18 +626,24 @@ def do_3d_projection(self, renderer):
586626

587627
# if required sort by depth (furthest drawn first)
588628
if self._zsort:
589-
z_segments_2d = [(self._zsortfunc(zs), list(zip(xs, ys)), fc, ec) for
590-
(xs, ys, zs), fc, ec in zip(xyzlist, cface, cedge)]
629+
indices = range(len(xyzlist))
630+
z_segments_2d = [(self._zsortfunc(zs), list(zip(xs, ys)), fc, ec,
631+
idx) for (xs, ys, zs), fc, ec, idx in
632+
zip(xyzlist, cface, cedge, indices)]
591633
z_segments_2d.sort(key=lambda x: x[0], reverse=True)
592634
else:
593635
raise ValueError("whoops")
594636

595-
segments_2d = [s for z, s, fc, ec in z_segments_2d]
596-
PolyCollection.set_verts(self, segments_2d)
637+
segments_2d = [s for z, s, fc, ec, idx in z_segments_2d]
638+
if self._codes3d is not None:
639+
codes = [self._codes3d[idx] for z, s, fc, ec, idx in z_segments_2d]
640+
PolyCollection.set_verts_and_codes(self, segments_2d, codes)
641+
else:
642+
PolyCollection.set_verts(self, segments_2d)
597643

598-
self._facecolors2d = [fc for z, s, fc, ec in z_segments_2d]
644+
self._facecolors2d = [fc for z, s, fc, ec, idx in z_segments_2d]
599645
if len(self._edgecolors3d) == len(cface):
600-
self._edgecolors2d = [ec for z, s, fc, ec in z_segments_2d]
646+
self._edgecolors2d = [ec for z, s, fc, ec, idx in z_segments_2d]
601647
else:
602648
self._edgecolors2d = self._edgecolors3d
603649

@@ -663,9 +709,10 @@ def draw(self, renderer):
663709

664710
def poly_collection_2d_to_3d(col, zs=0, zdir='z'):
665711
"""Convert a PolyCollection to a Poly3DCollection object."""
666-
segments_3d = paths_to_3d_segments(col.get_paths(), zs, zdir)
712+
segments_3d, codes = paths_to_3d_segments_with_codes(col.get_paths(),
713+
zs, zdir)
667714
col.__class__ = Poly3DCollection
668-
col.set_verts(segments_3d)
715+
col.set_verts_and_codes(segments_3d, codes)
669716
col.set_3d_properties()
670717

671718
def juggle_axes(xs, ys, zs, zdir):
Binary file not shown.

0 commit comments

Comments
 (0)