@@ -115,14 +115,10 @@ def set_3d_properties(self, zs=0, zdir='z'):
115
115
xs = self .get_xdata ()
116
116
ys = self .get_ydata ()
117
117
118
- try :
119
- # If *zs* is a list or array, then this will fail and
120
- # just proceed to juggle_axes().
121
- zs = float (zs )
122
- zs = [zs for x in xs ]
123
- except TypeError :
124
- pass
125
- self ._verts3d = juggle_axes (xs , ys , zs , zdir )
118
+ if not iterable (zs ):
119
+ zs = np .ones (len (xs )) * zs
120
+ xyz = np .asarray ([xs , ys , zs ])
121
+ self ._verts3d = juggle_axes (xyz , zdir )
126
122
self .stale = True
127
123
128
124
def draw (self , renderer ):
@@ -143,15 +139,17 @@ def line_2d_to_3d(line, zs=0, zdir='z'):
143
139
def path_to_3d_segment (path , zs = 0 , zdir = 'z' ):
144
140
'''Convert a path to a 3D segment.'''
145
141
146
- if not iterable ( zs ):
147
- zs = np .ones (len (path )) * zs
142
+ # Pre allocate memory
143
+ seg3d = np .ones (( 3 , len (path )))
148
144
149
- seg = []
145
+ # Works either if zs is array or scalar
146
+ seg3d [2 ] *= zs
147
+
150
148
pathsegs = path .iter_segments (simplify = False , curves = False )
151
- for ((( x , y ), code ), z ) in zip (pathsegs , zs ):
152
- seg . append (( x , y , z ))
153
- seg3d = [ juggle_axes (x , y , z , zdir ) for ( x , y , z ) in seg ]
154
- return seg3d
149
+ for i , (( x , y ), code ) in enumerate (pathsegs ):
150
+ seg3d [ 0 : 2 , i ] = x , y
151
+ seg3d = juggle_axes (seg3d , zdir )
152
+ return seg3d . T
155
153
156
154
def paths_to_3d_segments (paths , zs = 0 , zdir = 'z' ):
157
155
'''
@@ -164,22 +162,24 @@ def paths_to_3d_segments(paths, zs=0, zdir='z'):
164
162
segments = []
165
163
for path , pathz in zip (paths , zs ):
166
164
segments .append (path_to_3d_segment (path , pathz , zdir ))
167
- return segments
165
+ return np . asarray ( segments )
168
166
169
167
def path_to_3d_segment_with_codes (path , zs = 0 , zdir = 'z' ):
170
168
'''Convert a path to a 3D segment with path codes.'''
169
+ # Pre allocate memory
170
+ # XXX should we consider a 4d array?
171
+ seg3d = np .ones ((3 , len (path )))
171
172
172
- if not iterable ( zs ):
173
- zs = np . ones ( len ( path )) * zs
173
+ # Works either if zs is array or scalar
174
+ seg3d [ 2 ] *= zs
174
175
175
- seg = []
176
- codes = []
177
176
pathsegs = path .iter_segments (simplify = False , curves = False )
178
- for (((x , y ), code ), z ) in zip (pathsegs , zs ):
179
- seg .append ((x , y , z ))
180
- codes .append (code )
181
- seg3d = [juggle_axes (x , y , z , zdir ) for (x , y , z ) in seg ]
182
- return seg3d , codes
177
+ codes = np .empty (len (path ))
178
+ for i , ((x , y ), code ) in enumerate (pathsegs ):
179
+ seg3d [0 :2 , i ] = x , y
180
+ codes [i ] = code
181
+ seg3d = juggle_axes (seg3d , zdir )
182
+ return seg3d .T , codes
183
183
184
184
def paths_to_3d_segments_with_codes (paths , zs = 0 , zdir = 'z' ):
185
185
'''
@@ -195,7 +195,7 @@ def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'):
195
195
segs , codes = path_to_3d_segment_with_codes (path , pathz , zdir )
196
196
segments .append (segs )
197
197
codes_list .append (codes )
198
- return segments , codes_list
198
+ return np . asarray ( segments ), np . asarray ( codes_list )
199
199
200
200
class Line3DCollection (LineCollection ):
201
201
'''
@@ -224,10 +224,16 @@ def do_3d_projection(self, renderer):
224
224
'''
225
225
Project the points according to renderer matrix.
226
226
'''
227
- xys = proj3d .proj_trans_points (self ._segments3d , renderer .M )
228
- segments_2d = xys [0 :1 , :]
227
+ shape = self ._segments3d .shape
228
+ segments3d = np .transpose (self ._segments3d , axes = [2 , 0 , 1 ])
229
+ segments3d = np .lib .pad (
230
+ segments3d , ((0 , 1 ), (0 , 0 ), (0 , 0 )),
231
+ 'constant' , constant_values = 1 )
232
+ xys = proj3d .proj_transform_vec (segments3d .reshape (4 , - 1 ), renderer .M )
233
+ xys = np .reshape (xys .T , shape )
234
+ segments_2d = xys [:, :, 0 :2 ]
229
235
LineCollection .set_segments (self , segments_2d )
230
- minz = np .min (xys [2 , :])
236
+ minz = np .min (xys [: , :, 2 ])
231
237
return minz
232
238
233
239
def draw (self , renderer , project = False ):
@@ -255,11 +261,8 @@ def __init__(self, *args, **kwargs):
255
261
self .set_3d_properties (zs , zdir )
256
262
257
263
def set_3d_properties (self , verts , zs = 0 , zdir = 'z' ):
258
- if not iterable (zs ):
259
- zs = np .ones (len (verts )) * zs
260
-
261
- self ._segment3d = [juggle_axes (x , y , z , zdir ) \
262
- for ((x , y ), z ) in zip (verts , zs )]
264
+ verts = np .vstack (verts , np .ones (len (verts )) * zs )
265
+ self ._segment3d = juggle_axes (verts , zdir )
263
266
self ._facecolor3d = Patch .get_facecolor (self )
264
267
265
268
def get_path (self ):
@@ -269,9 +272,9 @@ def get_facecolor(self):
269
272
return self ._facecolor2d
270
273
271
274
def do_3d_projection (self , renderer ):
272
- s = self . _segment3d
273
- xs , ys , zs = list ( zip ( * s ))
274
- vxyzis = proj3d .proj_transform_clip ( xs , ys , zs , renderer .M )
275
+ # pad ones
276
+ s = np . vstack ( self . _segment3d , np . ones ( self . _segment3d . shape [ 1 ] ))
277
+ vxyzis = proj3d .proj_transform_vec_clip ( s , renderer .M )
275
278
self ._path2d = mpath .Path (vxyzis [0 :2 ].T )
276
279
# FIXME: coloring
277
280
self ._facecolor2d = self ._facecolor3d
@@ -297,9 +300,9 @@ def set_3d_properties(self, path, zs=0, zdir='z'):
297
300
self ._code3d = path .codes
298
301
299
302
def do_3d_projection (self , renderer ):
300
- s = self . _segment3d
301
- xs , ys , zs = list ( zip ( * s ))
302
- vxyzis = proj3d .proj_transform_clip ( xs , ys , zs , renderer .M )
303
+ # pad ones
304
+ s = np . vstack ( self . _segmenta3d , np . ones ( self . _segment3d . shape [ 1 ] ))
305
+ vxyzis = proj3d .proj_transform_vec_clip ( s , renderer .M )
303
306
self ._path2d = mpath .Path (vxyzis [0 :2 ].T , self ._code3d )
304
307
# FIXME: coloring
305
308
self ._facecolor2d = self ._facecolor3d
@@ -366,20 +369,16 @@ def set_3d_properties(self, zs, zdir):
366
369
# Force the collection to initialize the face and edgecolors
367
370
# just in case it is a scalarmappable with a colormap.
368
371
self .update_scalarmappable ()
369
- offsets = self .get_offsets ()
370
- if len (offsets ) > 0 :
371
- xs , ys = list (zip (* offsets ))
372
- else :
373
- xs = []
374
- ys = []
375
- self ._offsets3d = juggle_axes (xs , ys , np .atleast_1d (zs ), zdir )
372
+ offsets = np .vstack (self .get_offsets (), np .ones (len (verts )) * zs )
373
+ self ._offsets3d = juggle_axes (offsets , zdir )
376
374
self ._facecolor3d = self .get_facecolor ()
377
375
self ._edgecolor3d = self .get_edgecolor ()
378
376
self .stale = True
379
377
380
378
def do_3d_projection (self , renderer ):
381
- xs , ys , zs = self ._offsets3d
382
- vxyzis = proj3d .proj_transform_clip (xs , ys , zs , renderer .M )
379
+ # pad ones
380
+ s = np .vstack (self ._offsets3d , np .ones (self ._offsets3d .shape [1 ]))
381
+ vxyzis = proj3d .proj_transform_vec_clip (s , renderer .M )
383
382
384
383
fcs = (zalpha (self ._facecolor3d , vxyzis [2 ]) if self ._depthshade else
385
384
self ._facecolor3d )
@@ -434,13 +433,8 @@ def set_3d_properties(self, zs, zdir):
434
433
# Force the collection to initialize the face and edgecolors
435
434
# just in case it is a scalarmappable with a colormap.
436
435
self .update_scalarmappable ()
437
- offsets = self .get_offsets ()
438
- if len (offsets ) > 0 :
439
- xs , ys = list (zip (* offsets ))
440
- else :
441
- xs = []
442
- ys = []
443
- self ._offsets3d = juggle_axes (xs , ys , np .atleast_1d (zs ), zdir )
436
+ offsets = np .vstack (self .get_offsets (), np .ones (len (verts )) * zs )
437
+ self ._offsets3d = juggle_axes (offsets , zdir )
444
438
self ._facecolor3d = self .get_facecolor ()
445
439
self ._edgecolor3d = self .get_edgecolor ()
446
440
self .stale = True
@@ -545,12 +539,13 @@ def set_zsort(self, zsort):
545
539
546
540
def get_vector (self , segments3d ):
547
541
"""Optimize points for projection"""
548
- # Segments 3d are given in shape (n_segments, 3, 3)
549
- # Flatten them
550
- xys = segments3d .reshape ((- 1 , 3 )).T
542
+ # Segments 3d are given in shape (n_segments, segsize, 3)
543
+ self ._segsize = segments3d .shape [1 ]
544
+ # Flatten
545
+ xyz = segments3d .T .reshape ((3 , - 1 ))
551
546
# Add a fourth dimension with only ones
552
- ones = np .ones (xys .shape [1 ])
553
- self ._vec = np .vstack ([xys , ones ])
547
+ ones = np .ones (xyz .shape [1 ])
548
+ self ._vec = np .vstack ([xyz , ones ])
554
549
555
550
def set_verts (self , verts , closed = True ):
556
551
'''Set 3D vertices.'''
@@ -593,8 +588,9 @@ def do_3d_projection(self, renderer):
593
588
self ._facecolors3d = self ._facecolors
594
589
595
590
xys = proj3d .proj_transform_vec (self ._vec , renderer .M )
596
- xyzlist = np .transpose (xys .T .reshape ((- 1 , 3 , 3 )), axes = [0 , 2 , 1 ])
597
-
591
+ xys = np .reshape (xys , (3 , self ._segsize , - 1 ))
592
+ xyzlist = xys .T
593
+
598
594
# This extra fuss is to re-order face / edge colors
599
595
cface = self ._facecolors3d
600
596
cedge = self ._edgecolors3d
@@ -608,11 +604,11 @@ def do_3d_projection(self, renderer):
608
604
# if required sort by depth (furthest drawn first)
609
605
if self ._zsort :
610
606
z_argsort = np .argsort (
611
- self ._zsortfunc (xyzlist [:, 2 , : ], axis = 1 ))[::- 1 ]
607
+ self ._zsortfunc (xyzlist [:, :, 2 ], axis = 1 ))[::- 1 ]
612
608
else :
613
609
raise ValueError ("whoops" )
614
610
615
- segments_2d = np . transpose ( xyzlist [z_argsort , 0 : 2 , :], axes = [ 0 , 2 , 1 ])
611
+ segments_2d = xyzlist [z_argsort , :, 0 : 2 ]
616
612
if self ._codes3d is not None :
617
613
codes = self ._codes3d [z_argsort ]
618
614
PolyCollection .set_verts_and_codes (self , segments_2d , codes )
@@ -693,39 +689,39 @@ def poly_collection_2d_to_3d(col, zs=0, zdir='z'):
693
689
col .set_verts_and_codes (segments_3d , codes )
694
690
col .set_3d_properties ()
695
691
696
- def juggle_axes (xs , ys , zs , zdir ):
692
+ def juggle_axes (xyz , zdir ):
697
693
"""
698
694
Reorder coordinates so that 2D xs, ys can be plotted in the plane
699
695
orthogonal to zdir. zdir is normally x, y or z. However, if zdir
700
696
starts with a '-' it is interpreted as a compensation for rotate_axes.
701
697
"""
702
698
if zdir == 'x' :
703
- return zs , xs , ys
699
+ return xyz [[ 2 , 0 , 1 ]]
704
700
elif zdir == 'y' :
705
- return xs , zs , ys
701
+ return xyz [[ 0 , 2 , 1 ]]
706
702
elif zdir [0 ] == '-' :
707
- return rotate_axes (xs , ys , zs , zdir )
703
+ return rotate_axes (xyz , zdir )
708
704
else :
709
- return xs , ys , zs
705
+ return xyz
710
706
711
- def rotate_axes (xs , ys , zs , zdir ):
707
+ def rotate_axes (xyz , zdir ):
712
708
"""
713
709
Reorder coordinates so that the axes are rotated with zdir along
714
710
the original z axis. Prepending the axis with a '-' does the
715
711
inverse transform, so zdir can be x, -x, y, -y, z or -z
716
712
"""
717
713
if zdir == 'x' :
718
- return ys , zs , xs
714
+ return xyz [[ 1 , 2 , 0 ]]
719
715
elif zdir == '-x' :
720
- return zs , xs , ys
716
+ return xyz [[ 2 , 0 , 1 ]]
721
717
722
718
elif zdir == 'y' :
723
- return zs , xs , ys
719
+ return xyz [[ 2 , 0 , 1 ]]
724
720
elif zdir == '-y' :
725
- return ys , zs , xs
721
+ return xyz [[ 1 , 2 , 0 ]]
726
722
727
723
else :
728
- return xs , ys , zs
724
+ return xyz
729
725
730
726
def iscolor (c ):
731
727
try :
0 commit comments