@@ -598,16 +598,33 @@ def set_zsort(self, zsort):
598
598
self .stale = True
599
599
600
600
def get_vector (self , segments3d ):
601
- """Optimize points for projection."""
602
- if len (segments3d ):
603
- xs , ys , zs = np .row_stack (segments3d ).T
604
- else : # row_stack can't stack zero arrays.
605
- xs , ys , zs = [], [], []
606
- ones = np .ones (len (xs ))
607
- self ._vec = np .array ([xs , ys , zs , ones ])
601
+ """
602
+ Optimize points for projection.
608
603
609
- indices = [0 , * np .cumsum ([len (segment ) for segment in segments3d ])]
610
- self ._segslices = [* map (slice , indices [:- 1 ], indices [1 :])]
604
+ Parameters
605
+ ----------
606
+ segments3d : NumPy array or list of NumPy arrays
607
+ List of vertices of the boundary of every segment. If all paths are
608
+ of equal length and this argument is a NumPy arrray, then it should
609
+ be of shape (num_faces, num_vertices, 3).
610
+ """
611
+ if isinstance (segments3d , np .ndarray ):
612
+ if segments3d .ndim != 3 or segments3d .shape [- 1 ] != 3 :
613
+ raise ValueError ("segments3d must be a MxNx3 array, but got " +
614
+ "shape {}" .format (segments3d .shape ))
615
+ self ._segments = segments3d
616
+ else :
617
+ num_faces = len (segments3d )
618
+ num_verts = np .fromiter (map (len , segments3d ), dtype = np .intp )
619
+ max_verts = num_verts .max (initial = 0 )
620
+ padded = np .empty ((num_faces , max_verts , 3 ))
621
+ for i , face in enumerate (segments3d ):
622
+ padded [i , :len (face )] = face
623
+ mask = np .arange (max_verts ) >= num_verts [:, None ]
624
+ mask = mask [..., None ] # add a component axis
625
+ # ma.array does not broadcast the mask for us
626
+ mask = np .broadcast_to (mask , padded .shape )
627
+ self ._segments = np .ma .array (padded , mask = mask )
611
628
612
629
def set_verts (self , verts , closed = True ):
613
630
"""Set 3D vertices."""
@@ -649,37 +666,45 @@ def do_3d_projection(self, renderer):
649
666
self .update_scalarmappable ()
650
667
self ._facecolors3d = self ._facecolors
651
668
652
- txs , tys , tzs = proj3d ._proj_transform_vec (self ._vec , renderer .M )
653
- xyzlist = [(txs [sl ], tys [sl ], tzs [sl ]) for sl in self ._segslices ]
669
+ psegments = proj3d ._proj_transform_vectors (self ._segments , renderer .M )
670
+ is_masked = isinstance (psegments , np .ma .MaskedArray )
671
+ num_faces = len (psegments )
654
672
655
673
# This extra fuss is to re-order face / edge colors
656
674
cface = self ._facecolors3d
657
675
cedge = self ._edgecolors3d
658
- if len (cface ) != len ( xyzlist ) :
659
- cface = cface .repeat (len ( xyzlist ) , axis = 0 )
660
- if len (cedge ) != len ( xyzlist ) :
676
+ if len (cface ) != num_faces :
677
+ cface = cface .repeat (num_faces , axis = 0 )
678
+ if len (cedge ) != num_faces :
661
679
if len (cedge ) == 0 :
662
680
cedge = cface
663
681
else :
664
- cedge = cedge .repeat (len ( xyzlist ) , axis = 0 )
682
+ cedge = cedge .repeat (num_faces , axis = 0 )
665
683
666
- # sort by depth (furthest drawn first )
667
- z_segments_2d = sorted (
668
- (( self . _zsortfunc ( zs ), np . column_stack ([ xs , ys ]), fc , ec , idx )
669
- for idx , (( xs , ys , zs ), fc , ec )
670
- in enumerate ( zip ( xyzlist , cface , cedge ))),
671
- key = lambda x : x [ 0 ], reverse = True )
684
+ face_z = self . _zsortfunc ( psegments [..., 2 ], axis = - 1 )
685
+ if is_masked :
686
+ # NOTE: Unpacking .data is safe here, because every face has to
687
+ # contain a valid vertex.
688
+ face_z = face_z . data
689
+ face_order = np . argsort ( face_z , axis = - 1 )[:: - 1 ]
672
690
673
- segments_2d = [ s for z , s , fc , ec , idx in z_segments_2d ]
691
+ segments_2d = psegments [ face_order , :, : 2 ]
674
692
if self ._codes3d is not None :
675
- codes = [self ._codes3d [idx ] for z , s , fc , ec , idx in z_segments_2d ]
693
+ if is_masked :
694
+ # NOTE: We cannot assert the same on segments_2d, as it is a
695
+ # result of advanced indexing, so its mask is a newly
696
+ # allocated tensor. However, both of those asserts are
697
+ # equivalent.
698
+ assert psegments .mask .strides [- 1 ] == 0
699
+ segments_2d = [s .compressed ().reshape (- 1 , 2 ) for s in segments_2d ]
700
+ codes = [self ._codes3d [idx ] for idx in face_order ]
676
701
PolyCollection .set_verts_and_codes (self , segments_2d , codes )
677
702
else :
678
703
PolyCollection .set_verts (self , segments_2d , self ._closed )
679
704
680
- self ._facecolors2d = [ fc for z , s , fc , ec , idx in z_segments_2d ]
705
+ self ._facecolors2d = cface [ face_order ]
681
706
if len (self ._edgecolors3d ) == len (cface ):
682
- self ._edgecolors2d = [ ec for z , s , fc , ec , idx in z_segments_2d ]
707
+ self ._edgecolors2d = cedge [ face_order ]
683
708
else :
684
709
self ._edgecolors2d = self ._edgecolors3d
685
710
@@ -688,11 +713,11 @@ def do_3d_projection(self, renderer):
688
713
zvec = np .array ([[0 ], [0 ], [self ._sort_zpos ], [1 ]])
689
714
ztrans = proj3d ._proj_transform_vec (zvec , renderer .M )
690
715
return ztrans [2 ][0 ]
691
- elif tzs .size > 0 :
716
+ elif psegments .size > 0 :
692
717
# FIXME: Some results still don't look quite right.
693
718
# In particular, examine contourf3d_demo2.py
694
719
# with az = -54 and elev = -45.
695
- return np .min (tzs )
720
+ return np .min (psegments [..., 2 ] )
696
721
else :
697
722
return np .nan
698
723
0 commit comments