@@ -1684,65 +1684,69 @@ def plot_surface(self, X, Y, Z, *args, **kwargs):
1684
1684
if shade and cmap is not None and fcolors is not None :
1685
1685
fcolors = self ._shade_colors_lightsource (Z , cmap , lightsource )
1686
1686
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
1687
1711
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 )
1719
1722
1720
1723
if fcolors is not None :
1721
1724
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 = []
1735
1725
1726
+ # note that the striding causes some polygons to have more coordinates
1727
+ # than others
1736
1728
polyc = art3d .Poly3DCollection (polys , * args , ** kwargs )
1737
1729
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
+
1738
1741
if fcolors is not None :
1739
1742
if shade :
1740
1743
colset = self ._shade_colors (colset , normals )
1741
1744
polyc .set_facecolors (colset )
1742
1745
polyc .set_edgecolors (colset )
1743
1746
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 )
1746
1750
if vmin is not None or vmax is not None :
1747
1751
polyc .set_clim (vmin , vmax )
1748
1752
if norm is not None :
0 commit comments