From 2a750264eb6f6029b743aaa97cfeb9334742d2c1 Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Thu, 5 Jul 2018 14:43:20 +0200 Subject: [PATCH 01/33] Optimize 3D display (#6085) --- lib/mpl_toolkits/mplot3d/art3d.py | 281 +++++++++++++++++------------ lib/mpl_toolkits/mplot3d/axes3d.py | 10 +- lib/mpl_toolkits/mplot3d/proj3d.py | 24 ++- 3 files changed, 180 insertions(+), 135 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index a2084da837f6..31ca1f5922b0 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -141,21 +141,17 @@ def set_3d_properties(self, zs=0, zdir='z'): xs = self.get_xdata() ys = self.get_ydata() - try: - # If *zs* is a list or array, then this will fail and - # just proceed to juggle_axes(). - zs = float(zs) - zs = [zs for x in xs] - except TypeError: - pass - self._verts3d = juggle_axes(xs, ys, zs, zdir) + if not iterable(zs): + zs = np.ones(len(xs)) * zs + xyz = np.asarray([xs, ys, zs]) + self._verts3d = juggle_axes_vec(xyz, zdir) self.stale = True @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) + xyz = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M) + self.set_data(xyz[0], xyz[1]) lines.Line2D.draw(self, renderer) self.stale = False @@ -169,35 +165,46 @@ def line_2d_to_3d(line, zs=0, zdir='z'): def path_to_3d_segment(path, zs=0, zdir='z'): """Convert a path to a 3D segment.""" + # Pre allocate memory + seg3d = np.ones((3, len(path))) - zs = np.broadcast_to(zs, len(path)) + # Works either if zs is array or scalar + seg3d[2] *= zs + 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 - + for i, ((x, y), code) in enumerate(pathsegs): + seg3d[0:2, i] = x, y + seg3d = juggle_axes_vec(seg3d, zdir) + return seg3d.T 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 + if not iterable(zs): + zs = np.ones(len(paths)) * zs + + segments = [] + for path, pathz in zip(paths, zs): + segments.append(path_to_3d_segment(path, pathz, zdir)) + return np.asarray(segments) def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): - """Convert a path to a 3D segment with path codes.""" + '''Convert a path to a 3D segment with path codes.''' + # Pre allocate memory + # XXX should we consider a 4d array? + seg3d = np.ones((3, len(path))) + + # Works either if zs is array or scalar + seg3d[2] *= zs - zs = np.broadcast_to(zs, len(path)) - 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 + codes = np.empty(len(path)) + for i, ((x, y), code) in enumerate(pathsegs): + seg3d[0:2, i] = x, y + codes[i] = code + seg3d = juggle_axes_vec(seg3d, zdir) + return seg3d.T, codes def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): @@ -212,7 +219,7 @@ def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): segs, codes = path_to_3d_segment_with_codes(path, pathz, zdir) segments.append(segs) codes_list.append(codes) - return segments, codes_list + return np.asarray(segments), np.asarray(codes_list) class Line3DCollection(LineCollection): @@ -226,26 +233,42 @@ def set_sort_zpos(self, val): self.stale = True def set_segments(self, segments): - """ - Set 3D segments. - """ - self._segments3d = np.asanyarray(segments) + ''' + Set 3D segments + ''' + self._seg_sizes = [len(c) for c in segments] + self._segments3d = [] + if len(segments) > 0: + # Store the points in a single array for easier projection + n_segments = np.sum(self._seg_sizes) + # Put all segments in a big array + self._segments3d_data = np.vstack(segments) + # Add a fourth dimension for quaternions + self._segments3d_data = np.hstack([self._segments3d_data, + np.ones((n_segments, 1))]) + + # For coveniency, store a view of the array in the original shape + cum_s = 0 + for s in self._seg_sizes: + self._segments3d.append( + self._segments3d_data[cum_s:cum_s + s, :3]) + cum_s += s 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] + if len(self._segments3d) == 0: + return 1e9 + xys = proj3d.proj_transform_vec(self._segments3d_data.T, renderer.M).T + segments_2d = [] + cum_s = 0 + for s in self._seg_sizes: + segments_2d.append(xys[cum_s:cum_s + s, :2]) + cum_s += s LineCollection.set_segments(self, segments_2d) - - # FIXME - minz = 1e9 - for xs, ys, zs in xyslist: - minz = min(minz, min(zs)) + minz = np.min(xys[:, 2]) return minz @artist.allow_rasterization @@ -272,9 +295,8 @@ def __init__(self, *args, zs=(), zdir='z', **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)] + verts = np.hstack([verts, np.ones((len(verts), 1)) * zs]) + self._segment3d = juggle_axes_vec(verts.T, zdir) self._facecolor3d = Patch.get_facecolor(self) def get_path(self): @@ -284,13 +306,13 @@ 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])) + # pad ones + s = np.vstack([self._segment3d, np.ones(self._segment3d.shape[1])]) + vxyzis = proj3d.proj_transform_vec_clip(s, renderer.M) + self._path2d = mpath.Path(vxyzis[0:2].T) # FIXME: coloring self._facecolor2d = self._facecolor3d - return min(vzs) + return min(vxyzis[2]) class PathPatch3D(Patch3D): @@ -307,13 +329,13 @@ def set_3d_properties(self, path, zs=0, zdir='z'): 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) + # pad ones + s = np.vstack([self._segment3d, np.ones(self._segment3d.shape[1])]) + vxyzis = proj3d.proj_transform_vec_clip(s, renderer.M) + self._path2d = mpath.Path(vxyzis[0:2].T, self._code3d) # FIXME: coloring self._facecolor2d = self._facecolor3d - return min(vzs) + return min(vxyzis[2]) def get_patch_verts(patch): @@ -378,34 +400,30 @@ 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) + offsets = np.vstack(self.get_offsets(), np.ones(len(verts)) * zs) + self._offsets3d = juggle_axes_vec(offsets, 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) + # pad ones + s = np.vstack(self._offsets3d, np.ones(self._offsets3d.shape[1])) + vxyzis = proj3d.proj_transform_vec_clip(s, renderer.M) - fcs = (zalpha(self._facecolor3d, vzs) if self._depthshade else + fcs = (zalpha(self._facecolor3d, vxyzis[2]) 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 + ecs = (zalpha(self._edgecolor3d, vxyzis[2]) 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])) + PatchCollection.set_offsets(self, vxyzis[0:2].T) - if vzs.size > 0: - return min(vzs) + if len(vxyzis) > 0: + return min(vxyzis[2]) else: return np.nan @@ -445,34 +463,31 @@ def set_3d_properties(self, zs, zdir): # 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) + offsets = np.hstack([offsets, + (np.ones(len(offsets)) * zs)[:, np.newaxis]]) + self._offsets3d = juggle_axes_vec(offsets, zdir).T 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) + vxyzis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) - fcs = (zalpha(self._facecolor3d, vzs) if self._depthshade else + fcs = (zalpha(self._facecolor3d, vxyzis[2]) 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 + ecs = (zalpha(self._edgecolor3d, vxyzis[2]) if self._depthshade else self._edgecolor3d) ecs = mcolors.to_rgba_array(ecs, self._alpha) self.set_edgecolors(ecs) - PathCollection.set_offsets(self, np.column_stack([vxs, vys])) + PathCollection.set_offsets(self, vxyzis[0:2].T) - if vzs.size > 0 : - return min(vzs) - else : + if len(vxyzis) > 0: + return min(vxyzis[2]) + else: return np.nan @@ -558,26 +573,17 @@ def set_zsort(self, zsort): self.stale = True def get_vector(self, segments3d): - """Optimize points for projection.""" - si = 0 - ei = 0 - segis = [] - points = [] - for p in segments3d: - points.extend(p) - ei = si + len(p) - segis.append((si, ei)) - si = ei - - if len(segments3d): - xs, ys, zs = zip(*points) - else : - # We need this so that we can skip the bad unpacking from zip() - xs, ys, zs = [], [], [] - - ones = np.ones(len(xs)) - self._vec = np.array([xs, ys, zs, ones]) - self._segis = segis + """Optimize points for projection""" + + self._seg_sizes = [len(c) for c in segments3d] + self._vec = [] + if len(segments3d) > 0: + # Store the points in a single array for easier projection + n_segments = np.sum(self._seg_sizes) + # Put all segments in a big array + self._vec = np.vstack(segments3d) + # Add a fourth dimension for quaternions + self._vec = np.hstack([self._vec, np.ones((n_segments, 1))]).T def set_verts(self, verts, closed=True): """Set 3D vertices.""" @@ -605,7 +611,7 @@ def set_3d_properties(self): self._alpha3d = PolyCollection.get_alpha(self) self.stale = True - def set_sort_zpos(self,val): + def set_sort_zpos(self, val): """Set the position to use for z-sorting.""" self._sort_zpos = val self.stale = True @@ -619,9 +625,12 @@ def do_3d_projection(self, renderer): self.update_scalarmappable() 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] + xys = proj3d.proj_transform_vec(self._vec, renderer.M).T + xyzlist = [] + cum_s = 0 + for s in self._seg_sizes: + xyzlist.append(xys[cum_s:cum_s + s, :3]) + cum_s += s # This extra fuss is to re-order face / edge colors cface = self._facecolors3d @@ -636,24 +645,21 @@ def do_3d_projection(self, renderer): # if required sort by depth (furthest drawn first) if self._zsort: - 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) + z_argsort = np.argsort( + [self._zsortfunc(xyz[:, 2]) for xyz in xyzlist])[::-1] else: raise ValueError("whoops") - segments_2d = [s for z, s, fc, ec, idx in z_segments_2d] + segments_2d = [xyzlist[i][:, 0:2] for i in z_argsort] if self._codes3d is not None: - codes = [self._codes3d[idx] for z, s, fc, ec, idx in z_segments_2d] + codes = self._codes3d[z_argsort] PolyCollection.set_verts_and_codes(self, segments_2d, codes) else: PolyCollection.set_verts(self, segments_2d, self._closed) - self._facecolors2d = [fc for z, s, fc, ec, idx in z_segments_2d] + self._facecolors2d = cface[z_argsort] if len(self._edgecolors3d) == len(cface): - self._edgecolors2d = [ec for z, s, fc, ec, idx in z_segments_2d] + self._edgecolors2d = cedge[z_argsort] else: self._edgecolors2d = self._edgecolors3d @@ -662,11 +668,11 @@ def do_3d_projection(self, renderer): 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 : + elif xys[2].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) + return np.min(xys[2]) else : return np.nan @@ -736,6 +742,22 @@ def juggle_axes(xs, ys, zs, zdir): return xs, ys, zs +def juggle_axes_vec(xyz, 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 xyz[[2, 0, 1]] + elif zdir == 'y': + return xyz[[0, 2, 1]] + elif zdir[0] == '-': + return rotate_axes_vec(xyz, zdir) + else: + return xyz + + def rotate_axes(xs, ys, zs, zdir): """ Reorder coordinates so that the axes are rotated with zdir along @@ -756,6 +778,31 @@ def rotate_axes(xs, ys, zs, zdir): return xs, ys, zs +def rotate_axes_vec(xyz, 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 xyz[[1, 2, 0]] + elif zdir == '-x': + return xyz[[2, 0, 1]] + + elif zdir == 'y': + return xyz[[2, 0, 1]] + elif zdir == '-y': + return xyz[[1, 2, 0]] + + else: + return xyz + + +@cbook.deprecated('2.0', alternative='matplotlib.colors.is_color_like') +def iscolor(c): + return mcolors.is_color_like(c) + + def get_colors(c, num): """Stretch the color argument to provide the required number *num*.""" return np.broadcast_to( diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d9e24280535c..a058b3d276d6 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2352,15 +2352,9 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): if 'alpha' in kwargs: p.set_alpha(kwargs['alpha']) - if len(verts) > 0 : - # the following has to be skipped if verts is empty - # NOTE: Bugs could still occur if len(verts) > 0, - # but the "2nd dimension" is empty. - xs, ys = list(zip(*verts)) - else : - xs, ys = [], [] + verts = np.vstack([list(zip(*verts)), verts_zs]) - xs, ys, verts_zs = art3d.juggle_axes(xs, ys, verts_zs, zdir) + xs, ys, verts_zs = art3d.juggle_axes_vec(verts, zdir) self.auto_scale_xyz(xs, ys, verts_zs, had_data) return patches diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 4a26d13b953b..1bf4dfbfab2a 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -130,20 +130,21 @@ def ortho_transformation(zfront, zback): 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 + vecw /= vecw[3] + return vecw[0:3] 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): + # Determine clipping before rescaling + tis = (vecw[0] >= 0) * (vecw[0] <= 1) * (vecw[1] >= 0) * (vecw[1] <= 1) + # clip here.. + # Can anybody comment on this piece of code? I don't understand it... + if np.sometrue(tis): tis = vecw[1] < 1 - return txs, tys, tzs, tis + vecw /= vecw[3] + # Integrating tis in the numpy array for optimization purposes + vecw[3, :] = tis + return vecw def inv_transform(xs, ys, zs, M): iM = linalg.inv(M) @@ -179,6 +180,9 @@ def proj_points(points, M): return np.column_stack(proj_trans_points(points, M)) def proj_trans_points(points, M): + """ + Apply transformation matrix M on a set of points + """ xs, ys, zs = zip(*points) return proj_transform(xs, ys, zs, M) From b168bed10c7560a519e4241bf56af554726e0b8c Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Thu, 5 Jul 2018 15:05:08 +0200 Subject: [PATCH 02/33] Remove irrelevant changes --- lib/mpl_toolkits/mplot3d/art3d.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 31ca1f5922b0..f15fc00b9868 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -177,6 +177,7 @@ def path_to_3d_segment(path, zs=0, zdir='z'): seg3d = juggle_axes_vec(seg3d, zdir) return seg3d.T + def paths_to_3d_segments(paths, zs=0, zdir='z'): """Convert paths from a collection object to 3D segments.""" @@ -190,7 +191,7 @@ def paths_to_3d_segments(paths, zs=0, zdir='z'): def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): - '''Convert a path to a 3D segment with path codes.''' + """Convert a path to a 3D segment with path codes.""" # Pre allocate memory # XXX should we consider a 4d array? seg3d = np.ones((3, len(path))) @@ -233,9 +234,9 @@ def set_sort_zpos(self, val): self.stale = True def set_segments(self, segments): - ''' + """ Set 3D segments - ''' + """ self._seg_sizes = [len(c) for c in segments] self._segments3d = [] if len(segments) > 0: @@ -391,7 +392,7 @@ def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): super().__init__(*args, **kwargs) self.set_3d_properties(zs, zdir) - def set_sort_zpos(self, val): + def set_sort_zpos(self,val): """Set the position to use for z-sorting.""" self._sort_zpos = val self.stale = True @@ -798,11 +799,6 @@ def rotate_axes_vec(xyz, zdir): return xyz -@cbook.deprecated('2.0', alternative='matplotlib.colors.is_color_like') -def iscolor(c): - return mcolors.is_color_like(c) - - def get_colors(c, num): """Stretch the color argument to provide the required number *num*.""" return np.broadcast_to( From a3c331dd98e681d0001e879ed70351f0271ffbcd Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Thu, 5 Jul 2018 15:25:26 +0200 Subject: [PATCH 03/33] Fix undefined variables --- lib/mpl_toolkits/mplot3d/art3d.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index f15fc00b9868..98893daaa287 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -141,7 +141,7 @@ def set_3d_properties(self, zs=0, zdir='z'): xs = self.get_xdata() ys = self.get_ydata() - if not iterable(zs): + if not cbook.iterable(zs): zs = np.ones(len(xs)) * zs xyz = np.asarray([xs, ys, zs]) self._verts3d = juggle_axes_vec(xyz, zdir) @@ -181,7 +181,7 @@ def path_to_3d_segment(path, zs=0, zdir='z'): def paths_to_3d_segments(paths, zs=0, zdir='z'): """Convert paths from a collection object to 3D segments.""" - if not iterable(zs): + if not cbook.iterable(zs): zs = np.ones(len(paths)) * zs segments = [] @@ -401,7 +401,7 @@ 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 = np.vstack(self.get_offsets(), np.ones(len(verts)) * zs) + offsets = np.vstack(self.get_offsets(), np.atleast_1d(zs)) self._offsets3d = juggle_axes_vec(offsets, zdir) self._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() From a5c2bd5674abfab04b5e77994b9d66c32859adf7 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Thu, 5 Jul 2018 20:43:48 +0200 Subject: [PATCH 04/33] Check diff with respect to the original PR --- lib/mpl_toolkits/mplot3d/art3d.py | 26 +++++++++++--------------- lib/mpl_toolkits/mplot3d/proj3d.py | 4 ++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 98893daaa287..f5db4f9a266c 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -141,8 +141,7 @@ def set_3d_properties(self, zs=0, zdir='z'): xs = self.get_xdata() ys = self.get_ydata() - if not cbook.iterable(zs): - zs = np.ones(len(xs)) * zs + zs = np.broadcast_to(zs, len(xs)) xyz = np.asarray([xs, ys, zs]) self._verts3d = juggle_axes_vec(xyz, zdir) self.stale = True @@ -170,7 +169,7 @@ def path_to_3d_segment(path, zs=0, zdir='z'): # Works either if zs is array or scalar seg3d[2] *= zs - + pathsegs = path.iter_segments(simplify=False, curves=False) for i, ((x, y), code) in enumerate(pathsegs): seg3d[0:2, i] = x, y @@ -181,13 +180,11 @@ def path_to_3d_segment(path, zs=0, zdir='z'): def paths_to_3d_segments(paths, zs=0, zdir='z'): """Convert paths from a collection object to 3D segments.""" - if not cbook.iterable(zs): - zs = np.ones(len(paths)) * zs + zs = np.broadcast_to(zs, len(paths)) - segments = [] - for path, pathz in zip(paths, zs): - segments.append(path_to_3d_segment(path, pathz, zdir)) - return np.asarray(segments) + segs = [path_to_3d_segment(path, pathz, zdir) + for path, pathz in zip(paths, zs)] + return np.asarray(segs) def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): @@ -196,7 +193,7 @@ def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): # XXX should we consider a 4d array? seg3d = np.ones((3, len(path))) - # Works either if zs is array or scalar + # Works both if zs is an array and a scalar seg3d[2] *= zs pathsegs = path.iter_segments(simplify=False, curves=False) @@ -235,7 +232,7 @@ def set_sort_zpos(self, val): def set_segments(self, segments): """ - Set 3D segments + Set 3D segments. """ self._seg_sizes = [len(c) for c in segments] self._segments3d = [] @@ -296,7 +293,8 @@ def __init__(self, *args, zs=(), zdir='z', **kwargs): self.set_3d_properties(zs, zdir) def set_3d_properties(self, verts, zs=0, zdir='z'): - verts = np.hstack([verts, np.ones((len(verts), 1)) * zs]) + zs = np.broadcast_to(zs, len(verts)) + verts = np.hstack([verts, zs]) self._segment3d = juggle_axes_vec(verts.T, zdir) self._facecolor3d = Patch.get_facecolor(self) @@ -753,7 +751,7 @@ def juggle_axes_vec(xyz, zdir): return xyz[[2, 0, 1]] elif zdir == 'y': return xyz[[0, 2, 1]] - elif zdir[0] == '-': + elif zdir.startswith('-'): return rotate_axes_vec(xyz, zdir) else: return xyz @@ -769,12 +767,10 @@ def rotate_axes(xs, ys, zs, zdir): 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 diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 1bf4dfbfab2a..43865b8dad37 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -136,10 +136,10 @@ def proj_transform_vec(vec, M): def proj_transform_vec_clip(vec, M): vecw = np.dot(M, vec) # Determine clipping before rescaling - tis = (vecw[0] >= 0) * (vecw[0] <= 1) * (vecw[1] >= 0) * (vecw[1] <= 1) + tis = (0 <= vecw[0]) & (vecw[0] <= 1) & (0 <= vecw[1]) & (vecw[1] <= 1) # clip here.. # Can anybody comment on this piece of code? I don't understand it... - if np.sometrue(tis): + if np.any(tis): tis = vecw[1] < 1 vecw /= vecw[3] # Integrating tis in the numpy array for optimization purposes From e955f56a5802779226ed605caf07483456d4d966 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Thu, 5 Jul 2018 20:54:43 +0200 Subject: [PATCH 05/33] Address some review comments --- lib/mpl_toolkits/mplot3d/art3d.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index f5db4f9a266c..7bac1d034795 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -167,8 +167,8 @@ def path_to_3d_segment(path, zs=0, zdir='z'): # Pre allocate memory seg3d = np.ones((3, len(path))) - # Works either if zs is array or scalar - seg3d[2] *= zs + # Works both if zs is and array and a scalar + seg3d[2, :] *= zs pathsegs = path.iter_segments(simplify=False, curves=False) for i, ((x, y), code) in enumerate(pathsegs): @@ -181,7 +181,6 @@ 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 np.asarray(segs) @@ -194,7 +193,7 @@ def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): seg3d = np.ones((3, len(path))) # Works both if zs is an array and a scalar - seg3d[2] *= zs + seg3d[2, :] *= zs pathsegs = path.iter_segments(simplify=False, curves=False) codes = np.empty(len(path)) @@ -472,20 +471,21 @@ def set_3d_properties(self, zs, zdir): def do_3d_projection(self, renderer): xs, ys, zs = self._offsets3d vxyzis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) + vzs = vxyzis[2] - fcs = (zalpha(self._facecolor3d, vxyzis[2]) if self._depthshade else + 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, vxyzis[2]) if self._depthshade else + ecs = (zalpha(self._edgecolor3d, vzs) if self._depthshade else self._edgecolor3d) ecs = mcolors.to_rgba_array(ecs, self._alpha) self.set_edgecolors(ecs) PathCollection.set_offsets(self, vxyzis[0:2].T) - if len(vxyzis) > 0: - return min(vxyzis[2]) + if vzs.size > 0: + return min(vzs) else: return np.nan @@ -580,9 +580,9 @@ def get_vector(self, segments3d): # Store the points in a single array for easier projection n_segments = np.sum(self._seg_sizes) # Put all segments in a big array - self._vec = np.vstack(segments3d) + _vec = np.vstack(segments3d) # Add a fourth dimension for quaternions - self._vec = np.hstack([self._vec, np.ones((n_segments, 1))]).T + self._vec = np.hstack([_vec, np.ones((n_segments, 1))]).T def set_verts(self, verts, closed=True): """Set 3D vertices.""" From e227b5082eb344a32e4f0b1a5e9fdc46bc8b9985 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Thu, 5 Jul 2018 20:56:35 +0200 Subject: [PATCH 06/33] Make _juggle_axes_vec and _rotate_axes_vec private --- lib/mpl_toolkits/mplot3d/art3d.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 7bac1d034795..a206a9f4bc1b 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -143,7 +143,7 @@ def set_3d_properties(self, zs=0, zdir='z'): zs = np.broadcast_to(zs, len(xs)) xyz = np.asarray([xs, ys, zs]) - self._verts3d = juggle_axes_vec(xyz, zdir) + self._verts3d = _juggle_axes_vec(xyz, zdir) self.stale = True @artist.allow_rasterization @@ -173,7 +173,7 @@ def path_to_3d_segment(path, zs=0, zdir='z'): pathsegs = path.iter_segments(simplify=False, curves=False) for i, ((x, y), code) in enumerate(pathsegs): seg3d[0:2, i] = x, y - seg3d = juggle_axes_vec(seg3d, zdir) + seg3d = _juggle_axes_vec(seg3d, zdir) return seg3d.T @@ -200,7 +200,7 @@ def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): for i, ((x, y), code) in enumerate(pathsegs): seg3d[0:2, i] = x, y codes[i] = code - seg3d = juggle_axes_vec(seg3d, zdir) + seg3d = _juggle_axes_vec(seg3d, zdir) return seg3d.T, codes @@ -294,7 +294,7 @@ def __init__(self, *args, zs=(), zdir='z', **kwargs): def set_3d_properties(self, verts, zs=0, zdir='z'): zs = np.broadcast_to(zs, len(verts)) verts = np.hstack([verts, zs]) - self._segment3d = juggle_axes_vec(verts.T, zdir) + self._segment3d = _juggle_axes_vec(verts.T, zdir) self._facecolor3d = Patch.get_facecolor(self) def get_path(self): @@ -399,7 +399,7 @@ def set_3d_properties(self, zs, zdir): # just in case it is a scalarmappable with a colormap. self.update_scalarmappable() offsets = np.vstack(self.get_offsets(), np.atleast_1d(zs)) - self._offsets3d = juggle_axes_vec(offsets, zdir) + self._offsets3d = _juggle_axes_vec(offsets, zdir) self._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() self.stale = True @@ -463,7 +463,7 @@ def set_3d_properties(self, zs, zdir): offsets = self.get_offsets() offsets = np.hstack([offsets, (np.ones(len(offsets)) * zs)[:, np.newaxis]]) - self._offsets3d = juggle_axes_vec(offsets, zdir).T + self._offsets3d = _juggle_axes_vec(offsets, zdir).T self._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() self.stale = True @@ -741,7 +741,7 @@ def juggle_axes(xs, ys, zs, zdir): return xs, ys, zs -def juggle_axes_vec(xyz, zdir): +def _juggle_axes_vec(xyz, 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 @@ -752,7 +752,7 @@ def juggle_axes_vec(xyz, zdir): elif zdir == 'y': return xyz[[0, 2, 1]] elif zdir.startswith('-'): - return rotate_axes_vec(xyz, zdir) + return _rotate_axes_vec(xyz, zdir) else: return xyz @@ -775,7 +775,7 @@ def rotate_axes(xs, ys, zs, zdir): return xs, ys, zs -def rotate_axes_vec(xyz, zdir): +def _rotate_axes_vec(xyz, zdir): """ Reorder coordinates so that the axes are rotated with zdir along the original z axis. Prepending the axis with a '-' does the From ee53be55d1636b7543a88ba1aaf6760f6775b88d Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 14 Jul 2018 16:45:00 -0500 Subject: [PATCH 07/33] Address review comments --- lib/mpl_toolkits/mplot3d/art3d.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index a206a9f4bc1b..a2067bb5df82 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -233,16 +233,16 @@ def set_segments(self, segments): """ Set 3D segments. """ - self._seg_sizes = [len(c) for c in segments] self._segments3d = [] if len(segments) > 0: + self._seg_sizes = np.full(len(segments), len(segments[0])) # Store the points in a single array for easier projection n_segments = np.sum(self._seg_sizes) - # Put all segments in a big array - self._segments3d_data = np.vstack(segments) + + self._segments3d_data = np.empty((n_segments, 4)) + self._segments3d_data[:, :3] = np.vstack(segments) # Add a fourth dimension for quaternions - self._segments3d_data = np.hstack([self._segments3d_data, - np.ones((n_segments, 1))]) + self._segments3d_data[:, 3] = 1 # For coveniency, store a view of the array in the original shape cum_s = 0 @@ -250,6 +250,9 @@ def set_segments(self, segments): self._segments3d.append( self._segments3d_data[cum_s:cum_s + s, :3]) cum_s += s + else: + self._seg_sizes = np.array([]) + LineCollection.set_segments(self, []) def do_3d_projection(self, renderer): @@ -667,12 +670,12 @@ def do_3d_projection(self, renderer): zvec = np.array([[0], [0], [self._sort_zpos], [1]]) ztrans = proj3d.proj_transform_vec(zvec, renderer.M) return ztrans[2][0] - elif xys[2].size > 0 : + elif xys[2].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(xys[2]) - else : + else: return np.nan def set_facecolor(self, colors): From 8244c0778aaa7b46bb825f1cde596e2e397f580c Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 14 Jul 2018 17:19:07 -0500 Subject: [PATCH 08/33] Make TestVoxels.test_rgb_data pass --- lib/mpl_toolkits/mplot3d/art3d.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index a2067bb5df82..bb1a206719c5 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -235,7 +235,7 @@ def set_segments(self, segments): """ self._segments3d = [] if len(segments) > 0: - self._seg_sizes = np.full(len(segments), len(segments[0])) + self._seg_sizes = [len(c) for c in segments] # Store the points in a single array for easier projection n_segments = np.sum(self._seg_sizes) @@ -670,11 +670,11 @@ def do_3d_projection(self, renderer): zvec = np.array([[0], [0], [self._sort_zpos], [1]]) ztrans = proj3d.proj_transform_vec(zvec, renderer.M) return ztrans[2][0] - elif xys[2].size > 0: + elif xys[:, 2].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(xys[2]) + return np.min(xys[:, 2]) else: return np.nan From 677e79bb3b18b63d1a7cb0033af69389ed6e949b Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 14 Jul 2018 17:31:06 -0500 Subject: [PATCH 09/33] Review comment - use np.split + np.cumsum --- lib/mpl_toolkits/mplot3d/art3d.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index bb1a206719c5..2fde431a6b9b 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -245,11 +245,8 @@ def set_segments(self, segments): self._segments3d_data[:, 3] = 1 # For coveniency, store a view of the array in the original shape - cum_s = 0 - for s in self._seg_sizes: - self._segments3d.append( - self._segments3d_data[cum_s:cum_s + s, :3]) - cum_s += s + self._segments3d = np.split(self._segments3d_data[:, :3], + np.cumsum(self._seg_sizes)) else: self._seg_sizes = np.array([]) @@ -262,11 +259,8 @@ def do_3d_projection(self, renderer): if len(self._segments3d) == 0: return 1e9 xys = proj3d.proj_transform_vec(self._segments3d_data.T, renderer.M).T - segments_2d = [] - cum_s = 0 - for s in self._seg_sizes: - segments_2d.append(xys[cum_s:cum_s + s, :2]) - cum_s += s + segments_2d = np.split(xys[:, :2], + np.cumsum(self._seg_sizes)) LineCollection.set_segments(self, segments_2d) minz = np.min(xys[:, 2]) return minz From d095d26e15a5dbec0f3505d62d2a8bab67344a45 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 14 Jul 2018 17:58:41 -0500 Subject: [PATCH 10/33] Fix broadcasting --- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- lib/mpl_toolkits/mplot3d/axes3d.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 2fde431a6b9b..6999bb0e4f17 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -290,7 +290,7 @@ def __init__(self, *args, zs=(), zdir='z', **kwargs): def set_3d_properties(self, verts, zs=0, zdir='z'): zs = np.broadcast_to(zs, len(verts)) - verts = np.hstack([verts, zs]) + verts = np.hstack([verts, zs[:, None]]) self._segment3d = _juggle_axes_vec(verts.T, zdir) self._facecolor3d = Patch.get_facecolor(self) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index a058b3d276d6..2bad2221171c 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2354,7 +2354,7 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): verts = np.vstack([list(zip(*verts)), verts_zs]) - xs, ys, verts_zs = art3d.juggle_axes_vec(verts, zdir) + xs, ys, verts_zs = art3d._juggle_axes_vec(verts, zdir) self.auto_scale_xyz(xs, ys, verts_zs, had_data) return patches From 005f6fbb763ed573e8f0b7f1e2ddcfbc36ef331c Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 14 Jul 2018 23:22:03 -0500 Subject: [PATCH 11/33] PEP8 --- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- pytest.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 6999bb0e4f17..f7a2d037c1fa 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -14,7 +14,7 @@ from matplotlib import ( artist, cbook, colors as mcolors, lines, text as mtext, path as mpath) from matplotlib.collections import ( - Collection, LineCollection, PolyCollection, PatchCollection, + LineCollection, PolyCollection, PatchCollection, PathCollection) from matplotlib.colors import Normalize from matplotlib.patches import Patch diff --git a/pytest.ini b/pytest.ini index fd510238c86b..0f735f502fab 100644 --- a/pytest.ini +++ b/pytest.ini @@ -85,7 +85,7 @@ pep8ignore = mpl_toolkits/axisartist/floating_axes.py E201 E225 E231 E261 E262 E271 E302 E303 E402 E501 mpl_toolkits/axisartist/grid_finder.py E231 E261 E302 E303 E402 mpl_toolkits/axisartist/grid_helper_curvelinear.py E221 E225 E231 E251 E261 E262 E271 E302 E303 E501 - mpl_toolkits/mplot3d/art3d.py E203 E222 E225 E231 + mpl_toolkits/mplot3d/art3d.py E222 E225 E231 mpl_toolkits/mplot3d/axes3d.py E203 E225 E231 E271 E303 E402 E501 E502 E701 mpl_toolkits/mplot3d/axis3d.py E201 E202 E203 E222 E231 E302 E303 E502 mpl_toolkits/mplot3d/proj3d.py E231 E302 E303 From f74f3d77915065c4dd0ff2c0cc44befed728fb74 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 14 Jul 2018 23:26:20 -0500 Subject: [PATCH 12/33] Better debug message --- lib/matplotlib/path.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index e2cee67974bf..f72f92d04378 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -127,7 +127,8 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, vertices = _to_unmasked_float_array(vertices) if vertices.ndim != 2 or vertices.shape[1] != 2: raise ValueError( - "'vertices' must be a 2D list or array with shape Nx2") + "'vertices' must be a 2D list or array with shape Nx2, " + "while provided vertices.shape={}.".format(vertices.shape)) if codes is not None: codes = np.asarray(codes, self.code_type) From 1c1e4a189f0685cad0fb604bdfab418fa0757fdc Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sun, 15 Jul 2018 10:10:19 -0500 Subject: [PATCH 13/33] Fix for numpy 1.10 --- lib/mpl_toolkits/mplot3d/art3d.py | 39 +++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index f7a2d037c1fa..e76881cebdde 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -14,7 +14,7 @@ from matplotlib import ( artist, cbook, colors as mcolors, lines, text as mtext, path as mpath) from matplotlib.collections import ( - LineCollection, PolyCollection, PatchCollection, + Collection, LineCollection, PolyCollection, PatchCollection, PathCollection) from matplotlib.colors import Normalize from matplotlib.patches import Patch @@ -72,6 +72,26 @@ def get_dir_vector(zdir): raise ValueError("'x', 'y', 'z', None or vector of length 3 expected") +def _array_split(arr, indices_or_sections, remove_empty=False): + """Fix numpy.split to preserve the dimension of empty subarrays. + """ + + arr_chunks = np.split(arr, indices_or_sections) + + if arr_chunks[-1].size == 0: + + if not remove_empty: + # Preserve the 2D dimensionality the last chunk that can be empty + # (numpy <=1.10 replaces empty chunks by a 1D empty array) + # TODO: The following can be removed when + # support for numpy <=1.10 is dropped. + arr_chunks[-1] = np.empty(shape=(0, arr.shape[1]), dtype=arr.dtype) + else: + del arr_chunks[-1] + + return arr_chunks + + class Text3D(mtext.Text): """ Text object with 3D position and direction. @@ -245,8 +265,8 @@ def set_segments(self, segments): self._segments3d_data[:, 3] = 1 # For coveniency, store a view of the array in the original shape - self._segments3d = np.split(self._segments3d_data[:, :3], - np.cumsum(self._seg_sizes)) + self._segments3d = _array_split(self._segments3d_data[:, :3], + np.cumsum(self._seg_sizes)) else: self._seg_sizes = np.array([]) @@ -259,8 +279,8 @@ def do_3d_projection(self, renderer): if len(self._segments3d) == 0: return 1e9 xys = proj3d.proj_transform_vec(self._segments3d_data.T, renderer.M).T - segments_2d = np.split(xys[:, :2], - np.cumsum(self._seg_sizes)) + segments_2d = _array_split(xys[:, :2], + np.cumsum(self._seg_sizes)) LineCollection.set_segments(self, segments_2d) minz = np.min(xys[:, 2]) return minz @@ -622,11 +642,10 @@ def do_3d_projection(self, renderer): self._facecolors3d = self._facecolors xys = proj3d.proj_transform_vec(self._vec, renderer.M).T - xyzlist = [] - cum_s = 0 - for s in self._seg_sizes: - xyzlist.append(xys[cum_s:cum_s + s, :3]) - cum_s += s + + xyzlist = _array_split(xys[:, :3], + np.cumsum(self._seg_sizes), + remove_empty=True) # This extra fuss is to re-order face / edge colors cface = self._facecolors3d From 7c2c22e427ebfa9009e2f850be71766379457a8b Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sun, 15 Jul 2018 11:11:55 -0500 Subject: [PATCH 14/33] Ignore flake8 exception and code clean-up --- .flake8 | 2 +- lib/mpl_toolkits/mplot3d/art3d.py | 31 ++++++++++-------------------- lib/mpl_toolkits/mplot3d/proj3d.py | 2 -- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/.flake8 b/.flake8 index 34c37efdc081..e3d40ab8b1ba 100644 --- a/.flake8 +++ b/.flake8 @@ -80,7 +80,7 @@ per-file-ignores = mpl_toolkits/axisartist/floating_axes.py: E225, E231, E261, E262, E302, E303, E402, E501 mpl_toolkits/axisartist/grid_finder.py: E231, E261, E302, E303, E402 mpl_toolkits/axisartist/grid_helper_curvelinear.py: E225, E231, E261, E262, E271, E302, E303, E501 - mpl_toolkits/mplot3d/art3d.py: E203, E222, E225, E231 + mpl_toolkits/mplot3d/art3d.py: E222, E225, E231 mpl_toolkits/mplot3d/axes3d.py: E203, E231, E402, E501, E701 mpl_toolkits/mplot3d/axis3d.py: E231, E302 mpl_toolkits/mplot3d/proj3d.py: E231, E302, E303 diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 0c0ba348946a..6691dd1fc9a6 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -73,16 +73,14 @@ def get_dir_vector(zdir): def _array_split(arr, indices_or_sections, remove_empty=False): - """Fix numpy.split to preserve the dimension of empty subarrays. - """ - + """Fix numpy.split to preserve the dimension of empty subarrays.""" arr_chunks = np.split(arr, indices_or_sections) if arr_chunks[-1].size == 0: - if not remove_empty: # Preserve the 2D dimensionality the last chunk that can be empty # (numpy <=1.10 replaces empty chunks by a 1D empty array) + # TODO: The following can be removed when # support for numpy <=1.10 is dropped. arr_chunks[-1] = np.empty(shape=(0, arr.shape[1]), dtype=arr.dtype) @@ -169,8 +167,8 @@ def set_3d_properties(self, zs=0, zdir='z'): @artist.allow_rasterization def draw(self, renderer): xs3d, ys3d, zs3d = self._verts3d - xyz = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M) - self.set_data(xyz[0], xyz[1]) + xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M) + self.set_data(xs, ys) lines.Line2D.draw(self, renderer) self.stale = False @@ -184,9 +182,7 @@ def line_2d_to_3d(line, zs=0, zdir='z'): def path_to_3d_segment(path, zs=0, zdir='z'): """Convert a path to a 3D segment.""" - # Pre allocate memory seg3d = np.ones((3, len(path))) - # Works both if zs is and array and a scalar seg3d[2, :] *= zs @@ -208,10 +204,7 @@ def paths_to_3d_segments(paths, zs=0, zdir='z'): def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): """Convert a path to a 3D segment with path codes.""" - # Pre allocate memory - # XXX should we consider a 4d array? seg3d = np.ones((3, len(path))) - # Works both if zs is an array and a scalar seg3d[2, :] *= zs @@ -230,12 +223,11 @@ def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): """ zs = np.broadcast_to(zs, len(paths)) - 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) + + path_generator = (path_to_3d_segment_with_codes(path, pathz, zdir) + for path, pathz in zip(paths, zs)) + segments, codes_list = zip(*path_generator) + return np.asarray(segments), np.asarray(codes_list) @@ -261,14 +253,11 @@ def set_segments(self, segments): self._segments3d_data = np.empty((n_segments, 4)) self._segments3d_data[:, :3] = np.vstack(segments) - # Add a fourth dimension for quaternions self._segments3d_data[:, 3] = 1 # For coveniency, store a view of the array in the original shape self._segments3d = _array_split(self._segments3d_data[:, :3], np.cumsum(self._seg_sizes)) - else: - self._seg_sizes = np.array([]) LineCollection.set_segments(self, []) @@ -598,7 +587,7 @@ def get_vector(self, segments3d): n_segments = np.sum(self._seg_sizes) # Put all segments in a big array _vec = np.vstack(segments3d) - # Add a fourth dimension for quaternions + # Add a fourth dimension self._vec = np.hstack([_vec, np.ones((n_segments, 1))]).T def set_verts(self, verts, closed=True): diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 43865b8dad37..b87149b7cb7a 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -137,8 +137,6 @@ def proj_transform_vec_clip(vec, M): vecw = np.dot(M, vec) # Determine clipping before rescaling tis = (0 <= vecw[0]) & (vecw[0] <= 1) & (0 <= vecw[1]) & (vecw[1] <= 1) - # clip here.. - # Can anybody comment on this piece of code? I don't understand it... if np.any(tis): tis = vecw[1] < 1 vecw /= vecw[3] From b81c118bdb14e05dc1f6e9a2d96668667a35d099 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sun, 15 Jul 2018 11:57:50 -0500 Subject: [PATCH 15/33] Fix sphinx build --- lib/mpl_toolkits/mplot3d/art3d.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 6691dd1fc9a6..38f74c119cd1 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -479,6 +479,7 @@ def do_3d_projection(self, renderer): vxyzis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) vzs = vxyzis[2] + fcs = (zalpha(self._facecolor3d, vzs) if self._depthshade else self._facecolor3d) fcs = mcolors.to_rgba_array(fcs, self._alpha) @@ -802,9 +803,12 @@ def _rotate_axes_vec(xyz, zdir): 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)) + if not len(c): + return np.zeros((num, 4)) + elif len(c) < num: + return np.broadcast_to(mcolors.to_rgba_array(c), (num, 4)) + else: + return mcolors.to_rgba_array(c) def zalpha(colors, zs): From 822f4f759fb16361e1db61584cfbc07827cdd0cd Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sun, 15 Jul 2018 12:21:55 -0500 Subject: [PATCH 16/33] More review comments --- lib/mpl_toolkits/mplot3d/art3d.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 38f74c119cd1..ddafd3bea186 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -414,20 +414,21 @@ def do_3d_projection(self, renderer): # pad ones s = np.vstack(self._offsets3d, np.ones(self._offsets3d.shape[1])) vxyzis = proj3d.proj_transform_vec_clip(s, renderer.M) + vzs = vxyzis[2] - fcs = (zalpha(self._facecolor3d, vxyzis[2]) if self._depthshade else + 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, vxyzis[2]) if self._depthshade else + 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, vxyzis[0:2].T) - if len(vxyzis) > 0: - return min(vxyzis[2]) + if vzs.size > 0: + return min(vzs) else: return np.nan From 004fff2c253509743370de3a6bfb195bf104a39a Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sun, 15 Jul 2018 15:14:59 -0500 Subject: [PATCH 17/33] Fix PEP8 --- lib/mpl_toolkits/mplot3d/art3d.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index ddafd3bea186..e3fb11192ac3 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -480,7 +480,6 @@ def do_3d_projection(self, renderer): vxyzis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) vzs = vxyzis[2] - fcs = (zalpha(self._facecolor3d, vzs) if self._depthshade else self._facecolor3d) fcs = mcolors.to_rgba_array(fcs, self._alpha) From f7795e0c55ffd685b5c0d0543e4661483da68c80 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Mon, 16 Jul 2018 08:01:58 -0500 Subject: [PATCH 18/33] Address part of Eric's comments --- lib/mpl_toolkits/mplot3d/art3d.py | 14 +++++++------- lib/mpl_toolkits/mplot3d/proj3d.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index e3fb11192ac3..2c4b1deabf40 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -78,8 +78,8 @@ def _array_split(arr, indices_or_sections, remove_empty=False): if arr_chunks[-1].size == 0: if not remove_empty: - # Preserve the 2D dimensionality the last chunk that can be empty - # (numpy <=1.10 replaces empty chunks by a 1D empty array) + # Preserve the 2D dimensionality of the last chunk that can be + # empty (numpy <=1.10 replaces empty chunks by a 1D empty array) # TODO: The following can be removed when # support for numpy <=1.10 is dropped. @@ -182,9 +182,9 @@ def line_2d_to_3d(line, zs=0, zdir='z'): def path_to_3d_segment(path, zs=0, zdir='z'): """Convert a path to a 3D segment.""" - seg3d = np.ones((3, len(path))) + seg3d = np.empty((3, len(path))) # Works both if zs is and array and a scalar - seg3d[2, :] *= zs + seg3d[2, :] = zs pathsegs = path.iter_segments(simplify=False, curves=False) for i, ((x, y), code) in enumerate(pathsegs): @@ -204,9 +204,9 @@ def paths_to_3d_segments(paths, zs=0, zdir='z'): def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): """Convert a path to a 3D segment with path codes.""" - seg3d = np.ones((3, len(path))) + seg3d = np.empty((3, len(path))) # Works both if zs is an array and a scalar - seg3d[2, :] *= zs + seg3d[2, :] = zs pathsegs = path.iter_segments(simplify=False, curves=False) codes = np.empty(len(path)) @@ -255,7 +255,7 @@ def set_segments(self, segments): self._segments3d_data[:, :3] = np.vstack(segments) self._segments3d_data[:, 3] = 1 - # For coveniency, store a view of the array in the original shape + # For convenience, store a view of the array in the original shape self._segments3d = _array_split(self._segments3d_data[:, :3], np.cumsum(self._seg_sizes)) diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index b87149b7cb7a..3a6063f7cd76 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -141,7 +141,7 @@ def proj_transform_vec_clip(vec, M): tis = vecw[1] < 1 vecw /= vecw[3] # Integrating tis in the numpy array for optimization purposes - vecw[3, :] = tis + vecw[3] = tis return vecw def inv_transform(xs, ys, zs, M): From da7f72c7f40538217dc67e19bb887fbc230ec00d Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Mon, 16 Jul 2018 10:14:06 -0500 Subject: [PATCH 19/33] Fix anntzer's comments --- lib/matplotlib/path.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index f72f92d04378..1d04169d6c6c 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -127,8 +127,8 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, vertices = _to_unmasked_float_array(vertices) if vertices.ndim != 2 or vertices.shape[1] != 2: raise ValueError( - "'vertices' must be a 2D list or array with shape Nx2, " - "while provided vertices.shape={}.".format(vertices.shape)) + "'vertices' must be an array-like of shape (N, 2) not {}" + .format(vertices.shape)) if codes is not None: codes = np.asarray(codes, self.code_type) From 17657a4a65af09165ef2d3c6a9768462fc77c86e Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Wed, 18 Jul 2018 14:47:33 -0500 Subject: [PATCH 20/33] Address Benjamin's review comments --- lib/mpl_toolkits/mplot3d/art3d.py | 25 ++++++++++++++++--------- lib/mpl_toolkits/mplot3d/proj3d.py | 9 ++++++--- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 2c4b1deabf40..d7b5ee28faea 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -183,7 +183,7 @@ def line_2d_to_3d(line, zs=0, zdir='z'): def path_to_3d_segment(path, zs=0, zdir='z'): """Convert a path to a 3D segment.""" seg3d = np.empty((3, len(path))) - # Works both if zs is and array and a scalar + # Works both if zs is an array or a scalar seg3d[2, :] = zs pathsegs = path.iter_segments(simplify=False, curves=False) @@ -194,7 +194,11 @@ def path_to_3d_segment(path, zs=0, zdir='z'): def paths_to_3d_segments(paths, zs=0, zdir='z'): - """Convert paths from a collection object to 3D segments.""" + """Convert paths from a collection object to 3D segments. + + .. versionchanged :: 1.3.1 + This function returns a numpy array instead of a list + """ zs = np.broadcast_to(zs, len(paths)) segs = [path_to_3d_segment(path, pathz, zdir) @@ -226,9 +230,12 @@ def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): path_generator = (path_to_3d_segment_with_codes(path, pathz, zdir) for path, pathz in zip(paths, zs)) - segments, codes_list = zip(*path_generator) + if len(paths) and len(zs): + segments, codes_list = zip(*path_generator) - return np.asarray(segments), np.asarray(codes_list) + return np.asarray(segments), np.asarray(codes_list) + else: + return np.empty((3, 0)), np.array([]) class Line3DCollection(LineCollection): @@ -267,11 +274,11 @@ def do_3d_projection(self, renderer): """ if len(self._segments3d) == 0: return 1e9 - xys = proj3d.proj_transform_vec(self._segments3d_data.T, renderer.M).T - segments_2d = _array_split(xys[:, :2], + xys = proj3d.proj_transform_vec(self._segments3d_data.T, renderer.M) + segments_2d = _array_split(xys[:2].T, np.cumsum(self._seg_sizes)) LineCollection.set_segments(self, segments_2d) - minz = np.min(xys[:, 2]) + minz = np.min(xys[2]) return minz @artist.allow_rasterization @@ -458,7 +465,7 @@ def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): super().__init__(*args, **kwargs) self.set_3d_properties(zs, zdir) - def set_sort_zpos(self, val): + def set_sort_zpos(self,val): """Set the position to use for z-sorting.""" self._sort_zpos = val self.stale = True @@ -469,7 +476,7 @@ def set_3d_properties(self, zs, zdir): self.update_scalarmappable() offsets = self.get_offsets() offsets = np.hstack([offsets, - (np.ones(len(offsets)) * zs)[:, np.newaxis]]) + np.broadcast_to(zs, len(offsets))[:, np.newaxis]]) self._offsets3d = _juggle_axes_vec(offsets, zdir).T self._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 3a6063f7cd76..971eb4688e57 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -128,10 +128,12 @@ def ortho_transformation(zfront, zback): [0,0,a,b] ]) + def proj_transform_vec(vec, M): vecw = np.dot(M, vec) - vecw /= vecw[3] - return vecw[0:3] + vecw[:3] /= vecw[3] + return vecw[:3] + def proj_transform_vec_clip(vec, M): vecw = np.dot(M, vec) @@ -139,11 +141,12 @@ def proj_transform_vec_clip(vec, M): tis = (0 <= vecw[0]) & (vecw[0] <= 1) & (0 <= vecw[1]) & (vecw[1] <= 1) if np.any(tis): tis = vecw[1] < 1 - vecw /= vecw[3] + vecw[:3] /= vecw[3] # Integrating tis in the numpy array for optimization purposes vecw[3] = tis return vecw + def inv_transform(xs, ys, zs, M): iM = linalg.inv(M) vec = vec_pad_ones(xs, ys, zs) From e43b9308fa48e6aea4bd0f79da7d4a54b94fc340 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 21 Jul 2018 17:15:15 -0700 Subject: [PATCH 21/33] Minor fixes --- lib/mpl_toolkits/mplot3d/art3d.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index d7b5ee28faea..8bf50d5adbb5 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -188,7 +188,9 @@ def path_to_3d_segment(path, zs=0, zdir='z'): pathsegs = path.iter_segments(simplify=False, curves=False) for i, ((x, y), code) in enumerate(pathsegs): - seg3d[0:2, i] = x, y + seg3d[0, i] = x + seg3d[1, i] = y + seg3d = _juggle_axes_vec(seg3d, zdir) return seg3d.T @@ -215,7 +217,8 @@ def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): pathsegs = path.iter_segments(simplify=False, curves=False) codes = np.empty(len(path)) for i, ((x, y), code) in enumerate(pathsegs): - seg3d[0:2, i] = x, y + seg3d[0, i] = x + seg3d[1, i] = y codes[i] = code seg3d = _juggle_axes_vec(seg3d, zdir) return seg3d.T, codes @@ -402,7 +405,7 @@ def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): super().__init__(*args, **kwargs) self.set_3d_properties(zs, zdir) - def set_sort_zpos(self,val): + def set_sort_zpos(self, val): """Set the position to use for z-sorting.""" self._sort_zpos = val self.stale = True @@ -465,7 +468,7 @@ def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): super().__init__(*args, **kwargs) self.set_3d_properties(zs, zdir) - def set_sort_zpos(self,val): + def set_sort_zpos(self, val): """Set the position to use for z-sorting.""" self._sort_zpos = val self.stale = True From 7211708854109acb279c7820ba93f9d726cb620e Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 21 Jul 2018 18:14:07 -0700 Subject: [PATCH 22/33] Removing _juggle_axes_vec and _rotate_axes_vec --- lib/mpl_toolkits/mplot3d/art3d.py | 123 +++++++++++++---------------- lib/mpl_toolkits/mplot3d/axes3d.py | 10 ++- 2 files changed, 62 insertions(+), 71 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 8bf50d5adbb5..b98d74554806 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -160,8 +160,7 @@ def set_3d_properties(self, zs=0, zdir='z'): ys = self.get_ydata() zs = np.broadcast_to(zs, len(xs)) - xyz = np.asarray([xs, ys, zs]) - self._verts3d = _juggle_axes_vec(xyz, zdir) + self._verts3d = juggle_axes(xs, ys, zs, zdir) self.stale = True @artist.allow_rasterization @@ -181,52 +180,58 @@ def line_2d_to_3d(line, zs=0, zdir='z'): def path_to_3d_segment(path, zs=0, zdir='z'): - """Convert a path to a 3D segment.""" - seg3d = np.empty((3, len(path))) - # Works both if zs is an array or a scalar - seg3d[2, :] = zs + """Convert a path to a 3D segment. + + .. versionchanged :: 3.1 + Return type changed from a list to a numpy.array + """ + zs = np.broadcast_to(zs, len(path)) pathsegs = path.iter_segments(simplify=False, curves=False) - for i, ((x, y), code) in enumerate(pathsegs): - seg3d[0, i] = x - seg3d[1, i] = y + if len(path): + xs, ys = zip(*((x, y) for (x, y), code in pathsegs)) + else: + xs, ys = [], [] - seg3d = _juggle_axes_vec(seg3d, zdir) - return seg3d.T + seg3d = juggle_axes(xs, ys, zs, zdir) + return np.array(seg3d).T def paths_to_3d_segments(paths, zs=0, zdir='z'): """Convert paths from a collection object to 3D segments. - .. versionchanged :: 1.3.1 - This function returns a numpy array instead of a list + .. versionchanged :: 3.1 + Return type changed from a list to a numpy.array """ zs = np.broadcast_to(zs, len(paths)) + segs = [path_to_3d_segment(path, pathz, zdir) for path, pathz in zip(paths, zs)] - return np.asarray(segs) + return segs def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): - """Convert a path to a 3D segment with path codes.""" - seg3d = np.empty((3, len(path))) - # Works both if zs is an array and a scalar - seg3d[2, :] = zs + """Convert a path to a 3D segment with path codes. + + .. versionchanged :: 3.1 + Return type changed from a list to a numpy.array + """ + zs = np.broadcast_to(zs, len(path)) pathsegs = path.iter_segments(simplify=False, curves=False) codes = np.empty(len(path)) - for i, ((x, y), code) in enumerate(pathsegs): - seg3d[0, i] = x - seg3d[1, i] = y - codes[i] = code - seg3d = _juggle_axes_vec(seg3d, zdir) - return seg3d.T, codes + xs, ys, codes = zip(*((x, y, code) for (x, y), code in pathsegs)) + seg3d = juggle_axes(xs, ys, zs, zdir) + return np.array(seg3d).T, 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. + + .. versionchanged :: 3.1 + Return type changed from a list to a numpy.array """ zs = np.broadcast_to(zs, len(paths)) @@ -309,8 +314,8 @@ def __init__(self, *args, zs=(), zdir='z', **kwargs): def set_3d_properties(self, verts, zs=0, zdir='z'): zs = np.broadcast_to(zs, len(verts)) - verts = np.hstack([verts, zs[:, None]]) - self._segment3d = _juggle_axes_vec(verts.T, zdir) + xs, ys = verts.T + self._segment3d = juggle_axes(xs, ys, zs, zdir) self._facecolor3d = Patch.get_facecolor(self) def get_path(self): @@ -321,7 +326,11 @@ def get_facecolor(self): def do_3d_projection(self, renderer): # pad ones - s = np.vstack([self._segment3d, np.ones(self._segment3d.shape[1])]) + if self._segment3d: + segments_3d_len = len(self._segment3d[0]) + else: + segments_3d_len = 0 + s = np.vstack(self._segment3d + (np.ones(segments_3d_len),)) vxyzis = proj3d.proj_transform_vec_clip(s, renderer.M) self._path2d = mpath.Path(vxyzis[0:2].T) # FIXME: coloring @@ -344,7 +353,11 @@ def set_3d_properties(self, path, zs=0, zdir='z'): def do_3d_projection(self, renderer): # pad ones - s = np.vstack([self._segment3d, np.ones(self._segment3d.shape[1])]) + if self._segment3d: + segments_3d_len = len(self._segment3d[0]) + else: + segments_3d_len = 0 + s = np.vstack(self._segment3d + (np.ones(segments_3d_len),)) vxyzis = proj3d.proj_transform_vec_clip(s, renderer.M) self._path2d = mpath.Path(vxyzis[0:2].T, self._code3d) # FIXME: coloring @@ -414,8 +427,13 @@ 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 = np.vstack(self.get_offsets(), np.atleast_1d(zs)) - self._offsets3d = _juggle_axes_vec(offsets, zdir) + 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 @@ -478,9 +496,12 @@ def set_3d_properties(self, zs, zdir): # just in case it is a scalarmappable with a colormap. self.update_scalarmappable() offsets = self.get_offsets() - offsets = np.hstack([offsets, - np.broadcast_to(zs, len(offsets))[:, np.newaxis]]) - self._offsets3d = _juggle_axes_vec(offsets, zdir).T + 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 @@ -757,22 +778,6 @@ def juggle_axes(xs, ys, zs, zdir): return xs, ys, zs -def _juggle_axes_vec(xyz, 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 xyz[[2, 0, 1]] - elif zdir == 'y': - return xyz[[0, 2, 1]] - elif zdir.startswith('-'): - return _rotate_axes_vec(xyz, zdir) - else: - return xyz - - def rotate_axes(xs, ys, zs, zdir): """ Reorder coordinates so that the axes are rotated with zdir along @@ -791,26 +796,6 @@ def rotate_axes(xs, ys, zs, zdir): return xs, ys, zs -def _rotate_axes_vec(xyz, 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 xyz[[1, 2, 0]] - elif zdir == '-x': - return xyz[[2, 0, 1]] - - elif zdir == 'y': - return xyz[[2, 0, 1]] - elif zdir == '-y': - return xyz[[1, 2, 0]] - - else: - return xyz - - def get_colors(c, num): """Stretch the color argument to provide the required number *num*.""" if not len(c): diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 16e762ea4f7d..78fdb17f585a 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2344,9 +2344,15 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): if 'alpha' in kwargs: p.set_alpha(kwargs['alpha']) - verts = np.vstack([list(zip(*verts)), verts_zs]) + if len(verts) > 0: + # the following has to be skipped if verts is empty + # NOTE: Bugs could still occur if len(verts) > 0, + # but the "2nd dimension" is empty. + xs, ys = zip(*verts) + else: + xs, ys = [], [] - xs, ys, verts_zs = art3d._juggle_axes_vec(verts, zdir) + xs, ys, verts_zs = art3d.juggle_axes(xs, ys, verts_zs, zdir) self.auto_scale_xyz(xs, ys, verts_zs, had_data) return patches From 5bfa368a69ab54b373a3c8b046e0c748dadacacd Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 21 Jul 2018 18:51:29 -0700 Subject: [PATCH 23/33] Remove flake8 E231 ignore for art3d.py --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index e3d40ab8b1ba..c83970f5a44a 100644 --- a/.flake8 +++ b/.flake8 @@ -80,7 +80,7 @@ per-file-ignores = mpl_toolkits/axisartist/floating_axes.py: E225, E231, E261, E262, E302, E303, E402, E501 mpl_toolkits/axisartist/grid_finder.py: E231, E261, E302, E303, E402 mpl_toolkits/axisartist/grid_helper_curvelinear.py: E225, E231, E261, E262, E271, E302, E303, E501 - mpl_toolkits/mplot3d/art3d.py: E222, E225, E231 + mpl_toolkits/mplot3d/art3d.py: E222, E225 mpl_toolkits/mplot3d/axes3d.py: E203, E231, E402, E501, E701 mpl_toolkits/mplot3d/axis3d.py: E231, E302 mpl_toolkits/mplot3d/proj3d.py: E231, E302, E303 From 5faaa0c2c5e9abe3cc6530bf22f114bf45505b1a Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Mon, 23 Jul 2018 11:39:02 -0700 Subject: [PATCH 24/33] Addressed more eric-weiser's comments --- lib/mpl_toolkits/mplot3d/art3d.py | 4 ++-- lib/mpl_toolkits/mplot3d/proj3d.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index b98d74554806..baf764ff42ff 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -243,7 +243,7 @@ def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): return np.asarray(segments), np.asarray(codes_list) else: - return np.empty((3, 0)), np.array([]) + return np.empty((3, 0), dtype='float64'), np.array([], dtype='uint8') class Line3DCollection(LineCollection): @@ -262,7 +262,7 @@ def set_segments(self, segments): """ self._segments3d = [] if len(segments) > 0: - self._seg_sizes = [len(c) for c in segments] + self._seg_sizes = np.array([len(c) for c in segments]) # Store the points in a single array for easier projection n_segments = np.sum(self._seg_sizes) diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 971eb4688e57..774809a7ab4d 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -130,6 +130,10 @@ def ortho_transformation(zfront, zback): def proj_transform_vec(vec, M): + if len(vec) != 4: + raise ValueError('expected len(vec) of 4, received {}' + .format(vec)) + vecw = np.dot(M, vec) vecw[:3] /= vecw[3] return vecw[:3] From 7631cfe1ccf2d3f6336458b5221f51b9c8e1bc99 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Mon, 23 Jul 2018 11:40:49 -0700 Subject: [PATCH 25/33] Convert _seg_sizes to array --- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index baf764ff42ff..9a05f665309c 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -612,7 +612,7 @@ def set_zsort(self, zsort): def get_vector(self, segments3d): """Optimize points for projection""" - self._seg_sizes = [len(c) for c in segments3d] + self._seg_sizes = np.array([len(c) for c in segments3d]) self._vec = [] if len(segments3d) > 0: # Store the points in a single array for easier projection From ad0e783d17be4dce4d3ee6d84807d388a3bbde14 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sun, 29 Jul 2018 12:35:18 +0200 Subject: [PATCH 26/33] Handle empty sequences --- lib/mpl_toolkits/mplot3d/art3d.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 9a05f665309c..d068fb79ee3c 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -220,8 +220,11 @@ def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): zs = np.broadcast_to(zs, len(path)) pathsegs = path.iter_segments(simplify=False, curves=False) - codes = np.empty(len(path)) - xs, ys, codes = zip(*((x, y, code) for (x, y), code in pathsegs)) + if len(path): + xs, ys, codes = zip(*((x, y, code) for (x, y), code in pathsegs)) + else: + xs, ys, codes = [], [], [] + seg3d = juggle_axes(xs, ys, zs, zdir) return np.array(seg3d).T, codes From 98d116182466bc77804d73c623a794f51df01a26 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 18 Aug 2018 16:27:34 +0300 Subject: [PATCH 27/33] Address review comments --- lib/mpl_toolkits/mplot3d/art3d.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index d068fb79ee3c..32cd2a5b233a 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -284,6 +284,7 @@ def do_3d_projection(self, renderer): Project the points according to renderer matrix. """ if len(self._segments3d) == 0: + # FIXME return 1e9 xys = proj3d.proj_transform_vec(self._segments3d_data.T, renderer.M) segments_2d = _array_split(xys[:2].T, @@ -617,13 +618,13 @@ def get_vector(self, segments3d): self._seg_sizes = np.array([len(c) for c in segments3d]) self._vec = [] - if len(segments3d) > 0: - # Store the points in a single array for easier projection - n_segments = np.sum(self._seg_sizes) - # Put all segments in a big array - _vec = np.vstack(segments3d) - # Add a fourth dimension - self._vec = np.hstack([_vec, np.ones((n_segments, 1))]).T + + # Store the points in a single array for easier projection + n_segments = np.sum(self._seg_sizes) + self._vec = np.empty((4, n_segments)) + # Put all segments in a big array + self._vec[:3, :] = np.vstack(segments3d).T + self._vec[3, :] = 1 def set_verts(self, verts, closed=True): """Set 3D vertices.""" From 1696d5213e2d1e2495fd9b455ddd0ecffbfb54ae Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 18 Aug 2018 16:30:52 +0300 Subject: [PATCH 28/33] Experimentally revert get_colors to the previous behaviour --- lib/mpl_toolkits/mplot3d/art3d.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 32cd2a5b233a..b728ed24c270 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -804,10 +804,8 @@ def get_colors(c, num): """Stretch the color argument to provide the required number *num*.""" if not len(c): return np.zeros((num, 4)) - elif len(c) < num: - return np.broadcast_to(mcolors.to_rgba_array(c), (num, 4)) else: - return mcolors.to_rgba_array(c) + return np.broadcast_to(mcolors.to_rgba_array(c), (num, 4)) def zalpha(colors, zs): From a268fc7d49a587736fcfa000aa278289245af3dc Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Mon, 17 Dec 2018 19:10:57 +0100 Subject: [PATCH 29/33] Fix merge issue --- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 5338332f59b0..ba65ab2634e1 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -219,7 +219,7 @@ def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): """ zs = np.broadcast_to(zs, len(path)) -, pathsegs = path.iter_segments(simplify=False, curves=False) + pathsegs = path.iter_segments(simplify=False, curves=False) if len(path): xs, ys, codes = zip(*((x, y, code) for (x, y), code in pathsegs)) else: From 946b9bc6e735111c7ae8f30aca701d16fdd9ef3a Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Mon, 17 Dec 2018 19:18:47 +0100 Subject: [PATCH 30/33] Fix more merge conflicts --- lib/mpl_toolkits/mplot3d/art3d.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index ba65ab2634e1..2e743911286a 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -674,12 +674,9 @@ def do_3d_projection(self, renderer): else: cedge = cedge.repeat(len(xyzlist), axis=0) - # if required sort by depth (furthest drawn first) - if self._zsort: - z_argsort = np.argsort( - [self._zsortfunc(xyz[:, 2]) for xyz in xyzlist])[::-1] - else: - raise ValueError("whoops") + # sort by depth (furthest drawn first) + z_argsort = np.argsort( + [self._zsortfunc(xyz[:, 2]) for xyz in xyzlist])[::-1] segments_2d = [xyzlist[i][:, 0:2] for i in z_argsort] if self._codes3d is not None: From ff18f5f9c5594cadb5af6cfa783500dea3835d0b Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Tue, 18 Dec 2018 10:43:03 +0100 Subject: [PATCH 31/33] Lint --- lib/mpl_toolkits/mplot3d/art3d.py | 1 - lib/mpl_toolkits/mplot3d/proj3d.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 2e743911286a..46b499c8773b 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -616,7 +616,6 @@ def get_vector(self, segments3d): self._vec[:3, :] = np.vstack(segments3d).T self._vec[3, :] = 1 - def set_verts(self, verts, closed=True): """Set 3D vertices.""" self.get_vector(verts) diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 6a123b9f6ade..53b798ead0f5 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -128,7 +128,6 @@ def ortho_transformation(zfront, zback): [0, 0, a, b]]) - def proj_transform_vec(vec, M): if len(vec) != 4: raise ValueError('expected len(vec) of 4, received {}' @@ -139,7 +138,6 @@ def proj_transform_vec(vec, M): return vecw[:3] - def proj_transform_vec_clip(vec, M): vecw = np.dot(M, vec) # Determine clipping before rescaling @@ -152,7 +150,6 @@ def proj_transform_vec_clip(vec, M): return vecw - def inv_transform(xs, ys, zs, M): iM = linalg.inv(M) vec = vec_pad_ones(xs, ys, zs) From 76d5dc7be3090fc0b13ebb051cf103e4d9e19b6c Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Tue, 18 Dec 2018 11:13:17 +0100 Subject: [PATCH 32/33] Fix one test failure --- lib/mpl_toolkits/mplot3d/art3d.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 46b499c8773b..39897ffecf42 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -246,7 +246,7 @@ def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): return np.asarray(segments), np.asarray(codes_list) else: - return np.empty((3, 0), dtype='float64'), np.array([], dtype='uint8') + return np.empty((0, 3), dtype=np.float64), np.array([], dtype=np.uint8) class Line3DCollection(LineCollection): @@ -606,15 +606,17 @@ def set_zsort(self, zsort): def get_vector(self, segments3d): """Optimize points for projection""" - self._seg_sizes = np.array([len(c) for c in segments3d]) + self._seg_sizes = np.array([len(c) for c in segments3d], dtype=np.int) self._vec = [] # Store the points in a single array for easier projection n_segments = np.sum(self._seg_sizes) self._vec = np.empty((4, n_segments)) # Put all segments in a big array - self._vec[:3, :] = np.vstack(segments3d).T - self._vec[3, :] = 1 + # TODO: avoid copy in np.vstack when segments3d is a 3D numpy array + if n_segments: + self._vec[:3, :] = np.vstack(segments3d).T + self._vec[3, :] = 1 def set_verts(self, verts, closed=True): """Set 3D vertices.""" From 3e230c7537d1ce4cadaf8988049b400885fcdf16 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Tue, 18 Dec 2018 11:34:47 +0100 Subject: [PATCH 33/33] Address comments --- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 39897ffecf42..6d3868c8697b 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -234,7 +234,7 @@ def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): Convert paths from a collection object to 3D segments with path codes. .. versionchanged :: 3.1 - Return type changed from a list to a numpy.array + Return type changed from a tuple of lists to a tuple of numpy.array """ zs = np.broadcast_to(zs, len(paths))