From a4a8a86ed3d134367d7fe8be1b9feb4da5f531fe Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Mon, 29 Feb 2016 01:44:56 +0100 Subject: [PATCH 01/11] Replace lists by numpy array in projection --- lib/mpl_toolkits/mplot3d/art3d.py | 63 +++++++++++-------------------- 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index b8a4619959ef..fe82e418b55a 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -224,16 +224,10 @@ 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 = [list(zip(xs, ys)) for (xs, ys, zs) in xyslist] + xys = proj3d.proj_trans_points(self._segments3d, renderer.M) + segments_2d = xys[0:1, :] 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 def draw(self, renderer, project=False): @@ -551,30 +545,15 @@ def set_zsort(self, zsort): 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) > 0 : - xs, ys, zs = list(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 + xys = segments3d.reshape((-1, 3)).T + ones = np.ones(xys.shape[1]) + self._vec = np.vstack([xys, ones]) def set_verts(self, verts, closed=True): '''Set 3D vertices.''' self.get_vector(verts) # 2D verts will be updated at draw time + # XXX Is this line useful? PolyCollection.set_verts(self, [], closed) def set_verts_and_codes(self, verts, codes): @@ -601,6 +580,9 @@ def set_sort_zpos(self,val): self._sort_zpos = val self.stale = True + from profilehooks import profile + + #@profile def do_3d_projection(self, renderer): ''' Perform the 3D projection for this object. @@ -610,9 +592,9 @@ 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] + # XXX proj_transform_vec can work with arrays only + xys = np.array(proj3d.proj_transform_vec(self._vec, renderer.M)) + xyzlist = np.transpose(xys.T.reshape((-1, 3, 3)), axes=[0, 2, 1]) # This extra fuss is to re-order face / edge colors cface = self._facecolors3d @@ -626,24 +608,21 @@ def do_3d_projection(self, renderer): # if required sort by depth (furthest drawn first) if self._zsort: - 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) + z_argsort = np.argsort( + self._zsortfunc(xyzlist[:, 2, :], axis=1))[::-1] else: raise ValueError("whoops") - segments_2d = [s for z, s, fc, ec, idx in z_segments_2d] + segments_2d = np.transpose(xyzlist[z_argsort, 0:2, :], axes=[0, 2, 1]) 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._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 @@ -652,11 +631,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 From 3d295afbf760f61e443b91001460fa5d61a7c5ef Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Mon, 29 Feb 2016 01:55:47 +0100 Subject: [PATCH 02/11] Remove profiling code --- lib/mpl_toolkits/mplot3d/art3d.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index fe82e418b55a..67c7e8647a8c 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -580,9 +580,6 @@ def set_sort_zpos(self,val): self._sort_zpos = val self.stale = True - from profilehooks import profile - - #@profile def do_3d_projection(self, renderer): ''' Perform the 3D projection for this object. From 5239e1ae407f59b575fe10dcf61eea968ad00134 Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Mon, 29 Feb 2016 10:52:51 +0100 Subject: [PATCH 03/11] Vectorize output of proj_transform_vec --- lib/mpl_toolkits/mplot3d/art3d.py | 48 ++++++++++++++++-------------- lib/mpl_toolkits/mplot3d/proj3d.py | 20 +++++++------ 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 67c7e8647a8c..b8cd2061b694 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -127,8 +127,8 @@ def set_3d_properties(self, zs=0, zdir='z'): 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 @@ -271,11 +271,11 @@ def get_facecolor(self): def do_3d_projection(self, renderer): s = self._segment3d xs, ys, zs = list(zip(*s)) - vxs, vys,vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) - self._path2d = mpath.Path(list(zip(vxs, vys))) + vxyzis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) + self._path2d = mpath.Path(vxyzis[0:2].T) # FIXME: coloring self._facecolor2d = self._facecolor3d - return min(vzs) + return min(vxyzis[2]) def draw(self, renderer): Patch.draw(self, renderer) @@ -299,11 +299,11 @@ def set_3d_properties(self, path, zs=0, zdir='z'): def do_3d_projection(self, renderer): s = self._segment3d xs, ys, zs = list(zip(*s)) - vxs, vys,vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) - self._path2d = mpath.Path(list(zip(vxs, vys)), self._code3d) + vxyzis = proj3d.proj_transform_clip(xs, ys, zs, 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): """Return a list of vertices for the path of a patch.""" @@ -379,21 +379,21 @@ def set_3d_properties(self, zs, zdir): 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) - PatchCollection.set_offsets(self, list(zip(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 @@ -447,22 +447,22 @@ def set_3d_properties(self, zs, zdir): 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, list(zip(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 @@ -545,7 +545,10 @@ def set_zsort(self, zsort): def get_vector(self, segments3d): """Optimize points for projection""" + # Segments 3d are given in shape (n_segments, 3, 3) + # Flatten them xys = segments3d.reshape((-1, 3)).T + # Add a fourth dimension with only ones ones = np.ones(xys.shape[1]) self._vec = np.vstack([xys, ones]) @@ -589,8 +592,7 @@ def do_3d_projection(self, renderer): self.update_scalarmappable() self._facecolors3d = self._facecolors - # XXX proj_transform_vec can work with arrays only - xys = np.array(proj3d.proj_transform_vec(self._vec, renderer.M)) + xys = proj3d.proj_transform_vec(self._vec, renderer.M) xyzlist = np.transpose(xys.T.reshape((-1, 3, 3)), axes=[0, 2, 1]) # This extra fuss is to re-order face / edge colors diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 404d21e79bb0..7ab465f6a9b4 100755 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -156,20 +156,22 @@ def persp_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] + # Determine clipping before rescaling + tis = np.logical_and(vecw[0] >= 0, vecw[0] <= 1, + vecw[1] >= 0, vecw[1] <= 1) # clip here.. - txs, tys, tzs = vecw[0]/w, vecw[1]/w, vecw[2]/w - tis = (vecw[0] >= 0) * (vecw[0] <= 1) * (vecw[1] >= 0) * (vecw[1] <= 1) + # 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 + tis = vecw[1] < 1 + 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) From 7285fed2c454ebec019d38b08b2edf38f29c4ebf Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Mon, 29 Feb 2016 11:38:50 +0100 Subject: [PATCH 04/11] Reach vectorized projection functions directly --- lib/mpl_toolkits/mplot3d/art3d.py | 148 ++++++++++++++--------------- lib/mpl_toolkits/mplot3d/axes3d.py | 10 +- lib/mpl_toolkits/mplot3d/proj3d.py | 3 + 3 files changed, 77 insertions(+), 84 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index b8cd2061b694..b32b2f39268c 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -115,14 +115,10 @@ 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(xyz, zdir) self.stale = True def draw(self, renderer): @@ -143,15 +139,17 @@ 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.''' - if not iterable(zs): - zs = np.ones(len(path)) * zs + # Pre allocate memory + seg3d = np.ones((3, len(path))) - seg = [] + # Works either if zs is array or scalar + seg3d[2] *= zs + pathsegs = path.iter_segments(simplify=False, curves=False) - for (((x, y), code), z) in zip(pathsegs, zs): - seg.append((x, y, z)) - 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(seg3d, zdir) + return seg3d.T def paths_to_3d_segments(paths, zs=0, zdir='z'): ''' @@ -164,22 +162,24 @@ def paths_to_3d_segments(paths, zs=0, zdir='z'): segments = [] for path, pathz in zip(paths, zs): segments.append(path_to_3d_segment(path, pathz, zdir)) - return segments + 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.''' + # Pre allocate memory + # XXX should we consider a 4d array? + seg3d = np.ones((3, len(path))) - if not iterable(zs): - zs = np.ones(len(path)) * zs + # Works either if zs is array or scalar + seg3d[2] *= 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 + 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(seg3d, zdir) + return seg3d.T, codes def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): ''' @@ -195,7 +195,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): ''' @@ -224,10 +224,16 @@ def do_3d_projection(self, renderer): ''' Project the points according to renderer matrix. ''' - xys = proj3d.proj_trans_points(self._segments3d, renderer.M) - segments_2d = xys[0:1, :] + shape = self._segments3d.shape + segments3d = np.transpose(self._segments3d, axes=[2, 0, 1]) + segments3d = np.lib.pad( + segments3d, ((0, 1), (0, 0), (0, 0)), + 'constant', constant_values=1) + xys = proj3d.proj_transform_vec(segments3d.reshape(4, -1), renderer.M) + xys = np.reshape(xys.T, shape) + segments_2d = xys[:, :, 0:2] LineCollection.set_segments(self, segments_2d) - minz = np.min(xys[2, :]) + minz = np.min(xys[:, :, 2]) return minz def draw(self, renderer, project=False): @@ -255,11 +261,8 @@ def __init__(self, *args, **kwargs): self.set_3d_properties(zs, zdir) def set_3d_properties(self, verts, zs=0, zdir='z'): - if not iterable(zs): - zs = np.ones(len(verts)) * zs - - self._segment3d = [juggle_axes(x, y, z, zdir) \ - for ((x, y), z) in zip(verts, zs)] + verts = np.vstack(verts, np.ones(len(verts)) * zs) + self._segment3d = juggle_axes(verts, zdir) self._facecolor3d = Patch.get_facecolor(self) def get_path(self): @@ -269,9 +272,9 @@ def get_facecolor(self): return self._facecolor2d def do_3d_projection(self, renderer): - s = self._segment3d - xs, ys, zs = list(zip(*s)) - vxyzis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) + # 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 @@ -297,9 +300,9 @@ 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 = list(zip(*s)) - vxyzis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) + # pad ones + s = np.vstack(self._segmenta3d, 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 @@ -366,20 +369,16 @@ 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 = list(zip(*offsets)) - 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(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 - vxyzis = 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, vxyzis[2]) if self._depthshade else self._facecolor3d) @@ -434,13 +433,8 @@ 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 = list(zip(*offsets)) - 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(offsets, zdir) self._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() self.stale = True @@ -545,12 +539,13 @@ def set_zsort(self, zsort): def get_vector(self, segments3d): """Optimize points for projection""" - # Segments 3d are given in shape (n_segments, 3, 3) - # Flatten them - xys = segments3d.reshape((-1, 3)).T + # Segments 3d are given in shape (n_segments, segsize, 3) + self._segsize = segments3d.shape[1] + # Flatten + xyz = segments3d.T.reshape((3, -1)) # Add a fourth dimension with only ones - ones = np.ones(xys.shape[1]) - self._vec = np.vstack([xys, ones]) + ones = np.ones(xyz.shape[1]) + self._vec = np.vstack([xyz, ones]) def set_verts(self, verts, closed=True): '''Set 3D vertices.''' @@ -593,8 +588,9 @@ def do_3d_projection(self, renderer): self._facecolors3d = self._facecolors xys = proj3d.proj_transform_vec(self._vec, renderer.M) - xyzlist = np.transpose(xys.T.reshape((-1, 3, 3)), axes=[0, 2, 1]) - + xys = np.reshape(xys, (3, self._segsize, -1)) + xyzlist = xys.T + # This extra fuss is to re-order face / edge colors cface = self._facecolors3d cedge = self._edgecolors3d @@ -608,11 +604,11 @@ def do_3d_projection(self, renderer): # if required sort by depth (furthest drawn first) if self._zsort: z_argsort = np.argsort( - self._zsortfunc(xyzlist[:, 2, :], axis=1))[::-1] + self._zsortfunc(xyzlist[:, :, 2], axis=1))[::-1] else: raise ValueError("whoops") - segments_2d = np.transpose(xyzlist[z_argsort, 0:2, :], axes=[0, 2, 1]) + segments_2d = xyzlist[z_argsort, :, 0:2] if self._codes3d is not None: codes = self._codes3d[z_argsort] PolyCollection.set_verts_and_codes(self, segments_2d, codes) @@ -693,39 +689,39 @@ def poly_collection_2d_to_3d(col, zs=0, zdir='z'): col.set_verts_and_codes(segments_3d, codes) col.set_3d_properties() -def juggle_axes(xs, ys, zs, zdir): +def juggle_axes(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 zs, xs, ys + return xyz[[2, 0, 1]] elif zdir == 'y': - return xs, zs, ys + return xyz[[0, 2, 1]] elif zdir[0] == '-': - return rotate_axes(xs, ys, zs, zdir) + return rotate_axes(xyz, zdir) else: - return xs, ys, zs + return xyz -def rotate_axes(xs, ys, zs, zdir): +def rotate_axes(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 ys, zs, xs + return xyz[[1, 2, 0]] elif zdir == '-x': - return zs, xs, ys + return xyz[[2, 0, 1]] elif zdir == 'y': - return zs, xs, ys + return xyz[[2, 0, 1]] elif zdir == '-y': - return ys, zs, xs + return xyz[[1, 2, 0]] else: - return xs, ys, zs + return xyz def iscolor(c): try: diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index b4c422803626..d6581e25fe1e 100755 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2340,15 +2340,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(verts, verts_zs) - xs, ys, verts_zs = art3d.juggle_axes(xs, ys, verts_zs, zdir) + xs, ys, verts_zs = art3d.juggle_axes(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 7ab465f6a9b4..eb9b8c6e0dba 100755 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -214,6 +214,9 @@ def proj_points(points, M): return list(zip(*proj_trans_points(points, M))) def proj_trans_points(points, M): + """ + Apply transformation matrix M on a set of points + """ xs, ys, zs = list(zip(*points)) return proj_transform(xs, ys, zs, M) From 328c2f4435fec294ef04c18ae1f0f169e6332c2e Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Wed, 2 Mar 2016 11:35:59 +0100 Subject: [PATCH 05/11] Address comments --- lib/mpl_toolkits/mplot3d/art3d.py | 67 +++++++++++++++++++++++------- lib/mpl_toolkits/mplot3d/axes3d.py | 2 +- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index b32b2f39268c..c8e29bd2985f 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -118,7 +118,7 @@ def set_3d_properties(self, zs=0, zdir='z'): if not iterable(zs): zs = np.ones(len(xs)) * zs xyz = np.asarray([xs, ys, zs]) - self._verts3d = juggle_axes(xyz, zdir) + self._verts3d = juggle_axes_vec(xyz, zdir) self.stale = True def draw(self, renderer): @@ -148,7 +148,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(seg3d, zdir) + seg3d = juggle_axes_vec(seg3d, zdir) return seg3d.T def paths_to_3d_segments(paths, zs=0, zdir='z'): @@ -178,7 +178,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(seg3d, zdir) + seg3d = juggle_axes_vec(seg3d, zdir) return seg3d.T, codes def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): @@ -226,10 +226,10 @@ def do_3d_projection(self, renderer): ''' shape = self._segments3d.shape segments3d = np.transpose(self._segments3d, axes=[2, 0, 1]) - segments3d = np.lib.pad( - segments3d, ((0, 1), (0, 0), (0, 0)), - 'constant', constant_values=1) - xys = proj3d.proj_transform_vec(segments3d.reshape(4, -1), renderer.M) + segments3d = segments3d.reshape((3, -1)) + # Pad ones + segments3d = np.vstack([segments3d, np.ones((1, segments3d.shape[1]))]) + xys = proj3d.proj_transform_vec(segments3d, renderer.M) xys = np.reshape(xys.T, shape) segments_2d = xys[:, :, 0:2] LineCollection.set_segments(self, segments_2d) @@ -261,8 +261,8 @@ def __init__(self, *args, **kwargs): self.set_3d_properties(zs, zdir) def set_3d_properties(self, verts, zs=0, zdir='z'): - verts = np.vstack(verts, np.ones(len(verts)) * zs) - self._segment3d = juggle_axes(verts, zdir) + verts = np.vstack([verts, np.ones(len(verts)) * zs]) + self._segment3d = juggle_axes_vec(verts, zdir) self._facecolor3d = Patch.get_facecolor(self) def get_path(self): @@ -273,7 +273,7 @@ def get_facecolor(self): def do_3d_projection(self, renderer): # pad ones - s = np.vstack(self._segment3d, np.ones(self._segment3d.shape[1])) + 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 @@ -370,7 +370,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.ones(len(verts)) * zs) - self._offsets3d = juggle_axes(offsets, zdir) + self._offsets3d = juggle_axes_vec(offsets, zdir) self._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() self.stale = True @@ -434,7 +434,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.ones(len(verts)) * zs) - self._offsets3d = juggle_axes(offsets, zdir) + self._offsets3d = juggle_axes_vec(offsets, zdir) self._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() self.stale = True @@ -539,6 +539,7 @@ def set_zsort(self, zsort): def get_vector(self, segments3d): """Optimize points for projection""" + segments3d = np.asarray(segments3d) # Segments 3d are given in shape (n_segments, segsize, 3) self._segsize = segments3d.shape[1] # Flatten @@ -590,7 +591,7 @@ def do_3d_projection(self, renderer): xys = proj3d.proj_transform_vec(self._vec, renderer.M) xys = np.reshape(xys, (3, self._segsize, -1)) xyzlist = xys.T - + # This extra fuss is to re-order face / edge colors cface = self._facecolors3d cedge = self._edgecolors3d @@ -689,7 +690,22 @@ def poly_collection_2d_to_3d(col, zs=0, zdir='z'): col.set_verts_and_codes(segments_3d, codes) col.set_3d_properties() -def juggle_axes(xyz, zdir): +def juggle_axes(xs, ys, zs, 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 zs, xs, ys + elif zdir == 'y': + return xs, zs, ys + elif zdir[0] == '-': + return rotate_axes(xs, ys, zs, zdir) + else: + 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 @@ -700,11 +716,30 @@ def juggle_axes(xyz, zdir): elif zdir == 'y': return xyz[[0, 2, 1]] elif zdir[0] == '-': - return rotate_axes(xyz, zdir) + return rotate_axes_vec(xyz, zdir) else: return xyz -def rotate_axes(xyz, zdir): +def rotate_axes(xs, ys, zs, 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 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 + +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 diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d6581e25fe1e..3fedd0e2b335 100755 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2342,7 +2342,7 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): verts = np.vstack(verts, verts_zs) - xs, ys, verts_zs = art3d.juggle_axes(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 a973da8086e5f1607646087535da49b081669d8b Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Thu, 3 Mar 2016 10:29:06 +0100 Subject: [PATCH 06/11] More fixes --- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- lib/mpl_toolkits/mplot3d/axes3d.py | 2 +- lib/mpl_toolkits/mplot3d/proj3d.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index c8e29bd2985f..244bac393d6b 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -261,7 +261,7 @@ def __init__(self, *args, **kwargs): self.set_3d_properties(zs, zdir) def set_3d_properties(self, verts, zs=0, zdir='z'): - verts = np.vstack([verts, np.ones(len(verts)) * zs]) + verts = np.hstack([verts, np.ones((len(verts), 1)) * zs]) self._segment3d = juggle_axes_vec(verts, zdir) self._facecolor3d = Patch.get_facecolor(self) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 3fedd0e2b335..2410d8d6d7df 100755 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2340,7 +2340,7 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): if 'alpha' in kwargs: p.set_alpha(kwargs['alpha']) - verts = np.vstack(verts, verts_zs) + verts = np.hstack([verts, np.asarray(verts_zs)[:, np.newaxis]]) xs, ys, verts_zs = art3d.juggle_axes_vec(verts, zdir) self.auto_scale_xyz(xs, ys, verts_zs, had_data) diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index eb9b8c6e0dba..a96955c3a96d 100755 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -162,8 +162,7 @@ def proj_transform_vec(vec, M): def proj_transform_vec_clip(vec, M): vecw = np.dot(M, vec) # Determine clipping before rescaling - tis = np.logical_and(vecw[0] >= 0, vecw[0] <= 1, - vecw[1] >= 0, vecw[1] <= 1) + 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): From 14853d0a5097633ef1847abdfd4a3f03d1999c50 Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Thu, 3 Mar 2016 10:48:22 +0100 Subject: [PATCH 07/11] Fix rotation of axes --- lib/mpl_toolkits/mplot3d/axes3d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 2410d8d6d7df..6571277d7b34 100755 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2342,9 +2342,9 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): verts = np.hstack([verts, np.asarray(verts_zs)[:, np.newaxis]]) - xs, ys, verts_zs = art3d.juggle_axes_vec(verts, zdir) + xs, ys, verts_zs = art3d.juggle_axes_vec(verts.T, zdir) self.auto_scale_xyz(xs, ys, verts_zs, had_data) - + return patches def bar3d(self, x, y, z, dx, dy, dz, color=None, From c4de0478db70fc22110128357421b0182bc2c2fd Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Wed, 8 Jun 2016 14:12:46 +0200 Subject: [PATCH 08/11] Fix some failures --- lib/mpl_toolkits/mplot3d/art3d.py | 4 ++-- lib/mpl_toolkits/mplot3d/axes3d.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 244bac393d6b..3e1192eb55e6 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -233,7 +233,7 @@ def do_3d_projection(self, renderer): xys = np.reshape(xys.T, shape) segments_2d = xys[:, :, 0:2] LineCollection.set_segments(self, segments_2d) - minz = np.min(xys[:, :, 2]) + minz = np.min(xys[:, :, 2]) if xys.size > 0 else 1e9 return minz def draw(self, renderer, project=False): @@ -262,7 +262,7 @@ def __init__(self, *args, **kwargs): def set_3d_properties(self, verts, zs=0, zdir='z'): verts = np.hstack([verts, np.ones((len(verts), 1)) * zs]) - self._segment3d = juggle_axes_vec(verts, zdir) + self._segment3d = juggle_axes_vec(verts.T, zdir) self._facecolor3d = Patch.get_facecolor(self) def get_path(self): diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 6571277d7b34..cbcb9ace7794 100755 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2340,11 +2340,11 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): if 'alpha' in kwargs: p.set_alpha(kwargs['alpha']) - verts = np.hstack([verts, np.asarray(verts_zs)[:, np.newaxis]]) + verts = np.vstack([list(zip(*verts)), verts_zs]) - xs, ys, verts_zs = art3d.juggle_axes_vec(verts.T, zdir) + xs, ys, verts_zs = art3d.juggle_axes_vec(verts, zdir) self.auto_scale_xyz(xs, ys, verts_zs, had_data) - + return patches def bar3d(self, x, y, z, dx, dy, dz, color=None, From 63159615ca443d2e6be51fb5e9f28eeeeda42438 Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Wed, 8 Jun 2016 22:54:29 +0200 Subject: [PATCH 09/11] Use a contiguous representation in memory and views for the segments --- lib/mpl_toolkits/mplot3d/art3d.py | 77 ++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 3e1192eb55e6..f244051519d7 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -217,23 +217,39 @@ def set_segments(self, segments): ''' Set 3D segments ''' - self._segments3d = np.asanyarray(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. ''' - shape = self._segments3d.shape - segments3d = np.transpose(self._segments3d, axes=[2, 0, 1]) - segments3d = segments3d.reshape((3, -1)) - # Pad ones - segments3d = np.vstack([segments3d, np.ones((1, segments3d.shape[1]))]) - xys = proj3d.proj_transform_vec(segments3d, renderer.M) - xys = np.reshape(xys.T, shape) - segments_2d = xys[:, :, 0:2] + 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) - minz = np.min(xys[:, :, 2]) if xys.size > 0 else 1e9 + minz = np.min(xys[:, 2]) return minz def draw(self, renderer, project=False): @@ -301,7 +317,7 @@ def set_3d_properties(self, path, zs=0, zdir='z'): def do_3d_projection(self, renderer): # pad ones - s = np.vstack(self._segmenta3d, np.ones(self._segment3d.shape[1])) + s = np.vstack(self._segments3d, np.ones(self._segments3d.shape[1])) vxyzis = proj3d.proj_transform_vec_clip(s, renderer.M) self._path2d = mpath.Path(vxyzis[0:2].T, self._code3d) # FIXME: coloring @@ -433,8 +449,10 @@ 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) - self._offsets3d = juggle_axes_vec(offsets, 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._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() self.stale = True @@ -539,14 +557,16 @@ def set_zsort(self, zsort): def get_vector(self, segments3d): """Optimize points for projection""" - segments3d = np.asarray(segments3d) - # Segments 3d are given in shape (n_segments, segsize, 3) - self._segsize = segments3d.shape[1] - # Flatten - xyz = segments3d.T.reshape((3, -1)) - # Add a fourth dimension with only ones - ones = np.ones(xyz.shape[1]) - self._vec = np.vstack([xyz, ones]) + + 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.''' @@ -574,7 +594,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 @@ -588,9 +608,12 @@ def do_3d_projection(self, renderer): self.update_scalarmappable() self._facecolors3d = self._facecolors - xys = proj3d.proj_transform_vec(self._vec, renderer.M) - xys = np.reshape(xys, (3, self._segsize, -1)) - xyzlist = xys.T + 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 @@ -605,11 +628,11 @@ def do_3d_projection(self, renderer): # if required sort by depth (furthest drawn first) if self._zsort: z_argsort = np.argsort( - self._zsortfunc(xyzlist[:, :, 2], axis=1))[::-1] + [self._zsortfunc(xyz[:, 2]) for xyz in xyzlist])[::-1] else: raise ValueError("whoops") - segments_2d = xyzlist[z_argsort, :, 0:2] + segments_2d = [xyzlist[i][:, 0:2] for i in z_argsort] if self._codes3d is not None: codes = self._codes3d[z_argsort] PolyCollection.set_verts_and_codes(self, segments_2d, codes) From bb948bb619b990003670b1afc22c80045e6a14ac Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Tue, 20 Sep 2016 10:37:46 +0200 Subject: [PATCH 10/11] Fix bug in PathPatch --- lib/mpl_toolkits/mplot3d/art3d.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index f244051519d7..d4970998d3c1 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -317,7 +317,7 @@ def set_3d_properties(self, path, zs=0, zdir='z'): def do_3d_projection(self, renderer): # pad ones - s = np.vstack(self._segments3d, np.ones(self._segments3d.shape[1])) + 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 @@ -349,6 +349,7 @@ def pathpatch_2d_to_3d(pathpatch, z=0, zdir='z'): pathpatch.__class__ = PathPatch3D pathpatch.set_3d_properties(mpath, z, zdir) + class Patch3DCollection(PatchCollection): ''' A collection of 3D patches. From e6211bd1768dbfe0eebd54ae82a03bc78ef6fae6 Mon Sep 17 00:00:00 2001 From: Alexandre Abraham Date: Tue, 20 Sep 2016 16:12:54 +0200 Subject: [PATCH 11/11] Fix vstack bug --- 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 d4970998d3c1..ba395fa46c1a 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -317,7 +317,7 @@ 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])) + 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