Skip to content

Commit

Permalink
Add path code information to Poly3DCollection
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
cwebster2 committed Jul 27, 2015
1 parent 563129c commit eb00c0c
Show file tree
Hide file tree
Showing 7 changed files with 583 additions and 10 deletions.
10 changes: 10 additions & 0 deletions doc/users/whats_new/plotting.rst
Expand Up @@ -29,3 +29,13 @@ points are contoured as usual. If the ``corner_mask`` keyword argument is not
specified, the default value is taken from rcParams.

.. plot:: mpl_examples/pylab_examples/contour_corner_mask.py

Fixed 3D filled contour plot polygon rendering
``````````````````````````````````````````````

Certain cases of 3D filled contour plots that produce polygons with multiple
holes produced improper rendering due to a loss of path information between
:class:`~matplotlib.collections.PolyCollection` and
:class:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`. A function
:func:`~matplotlib.collections.PolyCollection.set_verts_and_codes` was
added to allow path information to be retained for proper rendering.
13 changes: 13 additions & 0 deletions lib/matplotlib/collections.py
Expand Up @@ -888,6 +888,19 @@ def set_verts(self, verts, closed=True):

set_paths = set_verts

def set_verts_and_codes(self, verts, codes):
'''This allows one to initialize vertices with path codes.'''
if (len(verts) != len(codes)):
raise ValueError("'codes' must be a 1D list or array "
"with the same length of 'verts'")
self._paths = []
for xy, cds in zip(verts, codes):
if len(xy):
self._paths.append(mpath.Path(xy, cds))
else:
self._paths.append(mpath.Path(xy))
self.stale = True


class BrokenBarHCollection(PolyCollection):
"""
Expand Down
67 changes: 57 additions & 10 deletions lib/mpl_toolkits/mplot3d/art3d.py
Expand Up @@ -166,6 +166,37 @@ def paths_to_3d_segments(paths, zs=0, zdir='z'):
segments.append(path_to_3d_segment(path, pathz, zdir))
return segments

def path_to_3d_segment_with_codes(path, zs=0, zdir='z'):
'''Convert a path to a 3D segment with path codes.'''

if not iterable(zs):
zs = np.ones(len(path)) * zs

seg = []
codes = []
pathsegs = path.iter_segments(simplify=False, curves=False)
for (((x, y), code), z) in zip(pathsegs, zs):
seg.append((x, y, z))
codes.append(code)
seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg]
return seg3d, 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.
'''

if not iterable(zs):
zs = np.ones(len(paths)) * zs

segments = []
codes_list = []
for path, pathz in zip(paths, zs):
segs, codes = path_to_3d_segment_with_codes(path, pathz, zdir)
segments.append(segs)
codes_list.append(codes)
return segments, codes_list

class Line3DCollection(LineCollection):
'''
A collection of 3D lines.
Expand Down Expand Up @@ -487,6 +518,7 @@ def __init__(self, verts, *args, **kwargs):
zsort = kwargs.pop('zsort', True)
PolyCollection.__init__(self, verts, *args, **kwargs)
self.set_zsort(zsort)
self._codes3d = None

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

def set_verts_and_codes(self, verts, codes):
'''Sets 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.
Expand All @@ -571,8 +611,8 @@ def do_3d_projection(self, renderer):
self._facecolors3d = self._facecolors

txs, tys, tzs = proj3d.proj_transform_vec(self._vec, renderer.M)
xyzlist = [(txs[si:ei], tys[si:ei], tzs[si:ei]) \
for si, ei in self._segis]
xyzlist = [(txs[si:ei], tys[si:ei], tzs[si:ei])
for si, ei in self._segis]

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

# if required sort by depth (furthest drawn first)
if self._zsort:
z_segments_2d = [(self._zsortfunc(zs), list(zip(xs, ys)), fc, ec) for
(xs, ys, zs), fc, ec in zip(xyzlist, cface, cedge)]
indices = range(len(xyzlist))
z_segments_2d = [(self._zsortfunc(zs), list(zip(xs, ys)), fc, ec,
idx) for (xs, ys, zs), fc, ec, idx in
zip(xyzlist, cface, cedge, indices)]
z_segments_2d.sort(key=lambda x: x[0], reverse=True)
else:
raise ValueError("whoops")

segments_2d = [s for z, s, fc, ec in z_segments_2d]
PolyCollection.set_verts(self, segments_2d)
segments_2d = [s for z, s, fc, ec, idx in z_segments_2d]
if self._codes3d is not None:
codes = [self._codes3d[idx] for z, s, fc, ec, idx in z_segments_2d]
PolyCollection.set_verts_and_codes(self, segments_2d, codes)
else:
PolyCollection.set_verts(self, segments_2d)

self._facecolors2d = [fc for z, s, fc, ec in z_segments_2d]
self._facecolors2d = [fc for z, s, fc, ec, idx in z_segments_2d]
if len(self._edgecolors3d) == len(cface):
self._edgecolors2d = [ec for z, s, fc, ec in z_segments_2d]
self._edgecolors2d = [ec for z, s, fc, ec, idx in z_segments_2d]
else:
self._edgecolors2d = self._edgecolors3d

Expand Down Expand Up @@ -663,9 +709,10 @@ def draw(self, renderer):

def poly_collection_2d_to_3d(col, zs=0, zdir='z'):
"""Convert a PolyCollection to a Poly3DCollection object."""
segments_3d = paths_to_3d_segments(col.get_paths(), zs, zdir)
segments_3d, codes = paths_to_3d_segments_with_codes(col.get_paths(),
zs, zdir)
col.__class__ = Poly3DCollection
col.set_verts(segments_3d)
col.set_verts_and_codes(segments_3d, codes)
col.set_3d_properties()

def juggle_axes(xs, ys, zs, zdir):
Expand Down
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit eb00c0c

Please sign in to comment.