Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit eb00c0c

Browse filesBrowse 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
Copy full SHA for eb00c0c

File tree

Expand file treeCollapse file tree

7 files changed

+583
-10
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+583
-10
lines changed

‎doc/users/whats_new/plotting.rst

Copy file name to clipboardExpand all lines: doc/users/whats_new/plotting.rst
+10Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/collections.py
+13Lines 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

Copy file name to clipboardExpand all lines: lib/mpl_toolkits/mplot3d/art3d.py
+57-10Lines 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.
Loading

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.