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

Skip to content

Commit bbc417e

Browse files
committed
MAINT/BUG: Simplify logic in plot_surface
Previously: * "cell" perimeters were clumsily calculated with duplicates, which were then (badly) removed at runtime. As a result, every quadrilateral was drawn with 5 vertices! * code to calculate normals was spread into multiple places * average z was calculated even if not used * repeated conversion between stride and count was done Should have no visible behavior changes
1 parent fc73593 commit bbc417e

File tree

9 files changed

+12133
-15157
lines changed

9 files changed

+12133
-15157
lines changed

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,65 +1684,69 @@ def plot_surface(self, X, Y, Z, *args, **kwargs):
16841684
if shade and cmap is not None and fcolors is not None:
16851685
fcolors = self._shade_colors_lightsource(Z, cmap, lightsource)
16861686

1687+
def boundary_edge(arr):
1688+
"""
1689+
Get the boundary elements of the rectangle ``arr``,
1690+
1691+
[arr[0,0] ... arr[0,-1] ... arr[-1, -1] ... arr[-1,0] ...]
1692+
1693+
If arr.shape is (M, N), the result is of length 2(N+M-2)
1694+
"""
1695+
# note we use Python's half-open ranges to avoid repeating
1696+
# the corners
1697+
forward = np.s_[0:-1] # [0 ... -1)
1698+
backward = np.s_[-1:0:-1] # [-1 ... 0)
1699+
return np.concatenate((
1700+
arr[0, forward],
1701+
arr[forward, -1],
1702+
arr[-1, backward],
1703+
arr[backward, 0],
1704+
))
1705+
1706+
# evenly spaced, and including both endpoints
1707+
row_inds = list(xrange(0, rows-1, rstride)) + [rows-1]
1708+
col_inds = list(xrange(0, cols-1, cstride)) + [cols-1]
1709+
1710+
colset = [] # the sampled facecolor
16871711
polys = []
1688-
# Only need these vectors to shade if there is no cmap
1689-
if cmap is None and shade :
1690-
totpts = int(np.ceil((rows - 1) / rstride) *
1691-
np.ceil((cols - 1) / cstride))
1692-
v1 = np.empty((totpts, 3))
1693-
v2 = np.empty((totpts, 3))
1694-
# This indexes the vertex points
1695-
which_pt = 0
1696-
1697-
1698-
#colset contains the data for coloring: either average z or the facecolor
1699-
colset = []
1700-
for rs in range(0, rows-1, rstride):
1701-
for cs in range(0, cols-1, cstride):
1702-
ps = []
1703-
for a in (X, Y, Z):
1704-
ztop = a[rs,cs:min(cols, cs+cstride+1)]
1705-
zleft = a[rs+1:min(rows, rs+rstride+1),
1706-
min(cols-1, cs+cstride)]
1707-
zbase = a[min(rows-1, rs+rstride), cs:min(cols, cs+cstride+1):][::-1]
1708-
zright = a[rs:min(rows-1, rs+rstride):, cs][::-1]
1709-
z = np.concatenate((ztop, zleft, zbase, zright))
1710-
ps.append(z)
1711-
1712-
# The construction leaves the array with duplicate points, which
1713-
# are removed here.
1714-
ps = list(zip(*ps))
1715-
lastp = np.array([])
1716-
ps2 = [ps[0]] + [ps[i] for i in range(1, len(ps)) if ps[i] != ps[i-1]]
1717-
avgzsum = sum(p[2] for p in ps2)
1718-
polys.append(ps2)
1712+
for rs, rs_next in zip(row_inds[:-1], row_inds[1:]):
1713+
for cs, cs_next in zip(col_inds[:-1], col_inds[1:]):
1714+
ps = [
1715+
# +1 ensures we share edges between polygons
1716+
boundary_edge(a[rs:rs_next+1, cs:cs_next+1])
1717+
for a in (X, Y, Z)
1718+
]
1719+
# ps = np.stack(ps, axis=-1)
1720+
ps = np.array(ps).T
1721+
polys.append(ps)
17191722

17201723
if fcolors is not None:
17211724
colset.append(fcolors[rs][cs])
1722-
else:
1723-
colset.append(avgzsum / len(ps2))
1724-
1725-
# Only need vectors to shade if no cmap
1726-
if cmap is None and shade:
1727-
i1, i2, i3 = 0, int(len(ps2)/3), int(2*len(ps2)/3)
1728-
v1[which_pt] = np.array(ps2[i1]) - np.array(ps2[i2])
1729-
v2[which_pt] = np.array(ps2[i2]) - np.array(ps2[i3])
1730-
which_pt += 1
1731-
if cmap is None and shade:
1732-
normals = np.cross(v1, v2)
1733-
else :
1734-
normals = []
17351725

1726+
# note that the striding causes some polygons to have more coordinates
1727+
# than others
17361728
polyc = art3d.Poly3DCollection(polys, *args, **kwargs)
17371729

1730+
if shade and cmap is None:
1731+
v1 = np.empty((len(polys), 3))
1732+
v2 = np.empty((len(polys), 3))
1733+
for poly_i, ps in enumerate(polys):
1734+
# pick three points around the polygon at which to find the normal
1735+
# doesn't vectorize because polys is jagged
1736+
i1, i2, i3 = 0, len(ps)//3, 2*len(ps)//3
1737+
v1[poly_i, :] = ps[i1, :] - ps[i2, :]
1738+
v2[poly_i, :] = ps[i2, :] - ps[i3, :]
1739+
normals = np.cross(v1, v2)
1740+
17381741
if fcolors is not None:
17391742
if shade:
17401743
colset = self._shade_colors(colset, normals)
17411744
polyc.set_facecolors(colset)
17421745
polyc.set_edgecolors(colset)
17431746
elif cmap:
1744-
colset = np.array(colset)
1745-
polyc.set_array(colset)
1747+
# doesn't vectorize because polys is jagged
1748+
avg_z = np.array([ps[:,2].mean() for ps in polys])
1749+
polyc.set_array(avg_z)
17461750
if vmin is not None or vmax is not None:
17471751
polyc.set_clim(vmin, vmax)
17481752
if norm is not None:
Binary file not shown.

0 commit comments

Comments
 (0)