Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# art3d.py, original mplot3d version by John Porter 

2# Parts rewritten by Reinier Heeres <reinier@heeres.eu> 

3# Minor additions by Ben Axelrod <baxelrod@coroware.com> 

4 

5""" 

6Module containing 3D artist code and functions to convert 2D 

7artists into 3D versions which can be added to an Axes3D. 

8""" 

9 

10import math 

11 

12import numpy as np 

13 

14from matplotlib import ( 

15 artist, cbook, colors as mcolors, lines, text as mtext, path as mpath) 

16from matplotlib.collections import ( 

17 LineCollection, PolyCollection, PatchCollection, PathCollection) 

18from matplotlib.colors import Normalize 

19from matplotlib.patches import Patch 

20from . import proj3d 

21 

22 

23def _norm_angle(a): 

24 """Return the given angle normalized to -180 < *a* <= 180 degrees.""" 

25 a = (a + 360) % 360 

26 if a > 180: 

27 a = a - 360 

28 return a 

29 

30 

31@cbook.deprecated("3.1") 

32def norm_angle(a): 

33 """Return the given angle normalized to -180 < *a* <= 180 degrees.""" 

34 return _norm_angle(a) 

35 

36 

37def _norm_text_angle(a): 

38 """Return the given angle normalized to -90 < *a* <= 90 degrees.""" 

39 a = (a + 180) % 180 

40 if a > 90: 

41 a = a - 180 

42 return a 

43 

44 

45@cbook.deprecated("3.1") 

46def norm_text_angle(a): 

47 """Return the given angle normalized to -90 < *a* <= 90 degrees.""" 

48 return _norm_text_angle(a) 

49 

50 

51def get_dir_vector(zdir): 

52 """ 

53 Return a direction vector. 

54 

55 Parameters 

56 ---------- 

57 zdir : {'x', 'y', 'z', None, 3-tuple} 

58 The direction. Possible values are: 

59 - 'x': equivalent to (1, 0, 0) 

60 - 'y': equivalent to (0, 1, 0) 

61 - 'z': equivalent to (0, 0, 1) 

62 - *None*: equivalent to (0, 0, 0) 

63 - an iterable (x, y, z) is returned unchanged. 

64 

65 Returns 

66 ------- 

67 x, y, z : array-like 

68 The direction vector. This is either a numpy.array or *zdir* itself if 

69 *zdir* is already a length-3 iterable. 

70 

71 """ 

72 if zdir == 'x': 

73 return np.array((1, 0, 0)) 

74 elif zdir == 'y': 

75 return np.array((0, 1, 0)) 

76 elif zdir == 'z': 

77 return np.array((0, 0, 1)) 

78 elif zdir is None: 

79 return np.array((0, 0, 0)) 

80 elif np.iterable(zdir) and len(zdir) == 3: 

81 return zdir 

82 else: 

83 raise ValueError("'x', 'y', 'z', None or vector of length 3 expected") 

84 

85 

86class Text3D(mtext.Text): 

87 """ 

88 Text object with 3D position and direction. 

89 

90 Parameters 

91 ---------- 

92 x, y, z 

93 The position of the text. 

94 text : str 

95 The text string to display. 

96 zdir : {'x', 'y', 'z', None, 3-tuple} 

97 The direction of the text. See `.get_dir_vector` for a description of 

98 the values. 

99 

100 Other Parameters 

101 ---------------- 

102 **kwargs 

103 All other parameters are passed on to `~matplotlib.text.Text`. 

104 """ 

105 

106 def __init__(self, x=0, y=0, z=0, text='', zdir='z', **kwargs): 

107 mtext.Text.__init__(self, x, y, text, **kwargs) 

108 self.set_3d_properties(z, zdir) 

109 

110 def set_3d_properties(self, z=0, zdir='z'): 

111 x, y = self.get_position() 

112 self._position3d = np.array((x, y, z)) 

113 self._dir_vec = get_dir_vector(zdir) 

114 self.stale = True 

115 

116 @artist.allow_rasterization 

117 def draw(self, renderer): 

118 proj = proj3d.proj_trans_points( 

119 [self._position3d, self._position3d + self._dir_vec], renderer.M) 

120 dx = proj[0][1] - proj[0][0] 

121 dy = proj[1][1] - proj[1][0] 

122 angle = math.degrees(math.atan2(dy, dx)) 

123 self.set_position((proj[0][0], proj[1][0])) 

124 self.set_rotation(_norm_text_angle(angle)) 

125 mtext.Text.draw(self, renderer) 

126 self.stale = False 

127 

128 def get_tightbbox(self, renderer): 

129 # Overwriting the 2d Text behavior which is not valid for 3d. 

130 # For now, just return None to exclude from layout calculation. 

131 return None 

132 

133 

134def text_2d_to_3d(obj, z=0, zdir='z'): 

135 """Convert a Text to a Text3D object.""" 

136 obj.__class__ = Text3D 

137 obj.set_3d_properties(z, zdir) 

138 

139 

140class Line3D(lines.Line2D): 

141 """ 

142 3D line object. 

143 """ 

144 

145 def __init__(self, xs, ys, zs, *args, **kwargs): 

146 """ 

147 Keyword arguments are passed onto :func:`~matplotlib.lines.Line2D`. 

148 """ 

149 lines.Line2D.__init__(self, [], [], *args, **kwargs) 

150 self._verts3d = xs, ys, zs 

151 

152 def set_3d_properties(self, zs=0, zdir='z'): 

153 xs = self.get_xdata() 

154 ys = self.get_ydata() 

155 

156 try: 

157 # If *zs* is a list or array, then this will fail and 

158 # just proceed to juggle_axes(). 

159 zs = np.full_like(xs, fill_value=float(zs)) 

160 except TypeError: 

161 pass 

162 self._verts3d = juggle_axes(xs, ys, zs, zdir) 

163 self.stale = True 

164 

165 def set_data_3d(self, *args): 

166 """ 

167 Set the x, y and z data 

168 

169 Parameters 

170 ---------- 

171 x : array-like 

172 The x-data to be plotted. 

173 y : array-like 

174 The y-data to be plotted. 

175 z : array-like 

176 The z-data to be plotted. 

177 

178 Notes 

179 ----- 

180 Accepts x, y, z arguments or a single array-like (x, y, z) 

181 """ 

182 if len(args) == 1: 

183 self._verts3d = args[0] 

184 else: 

185 self._verts3d = args 

186 self.stale = True 

187 

188 def get_data_3d(self): 

189 """ 

190 Get the current data 

191 

192 Returns 

193 ------- 

194 verts3d : length-3 tuple or array-likes 

195 The current data as a tuple or array-likes. 

196 """ 

197 return self._verts3d 

198 

199 @artist.allow_rasterization 

200 def draw(self, renderer): 

201 xs3d, ys3d, zs3d = self._verts3d 

202 xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M) 

203 self.set_data(xs, ys) 

204 lines.Line2D.draw(self, renderer) 

205 self.stale = False 

206 

207 

208def line_2d_to_3d(line, zs=0, zdir='z'): 

209 """Convert a 2D line to 3D.""" 

210 

211 line.__class__ = Line3D 

212 line.set_3d_properties(zs, zdir) 

213 

214 

215def _path_to_3d_segment(path, zs=0, zdir='z'): 

216 """Convert a path to a 3D segment.""" 

217 

218 zs = np.broadcast_to(zs, len(path)) 

219 pathsegs = path.iter_segments(simplify=False, curves=False) 

220 seg = [(x, y, z) for (((x, y), code), z) in zip(pathsegs, zs)] 

221 seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg] 

222 return seg3d 

223 

224 

225@cbook.deprecated("3.1") 

226def path_to_3d_segment(path, zs=0, zdir='z'): 

227 """Convert a path to a 3D segment.""" 

228 return _path_to_3d_segment(path, zs=zs, zdir=zdir) 

229 

230 

231def _paths_to_3d_segments(paths, zs=0, zdir='z'): 

232 """Convert paths from a collection object to 3D segments.""" 

233 

234 zs = np.broadcast_to(zs, len(paths)) 

235 segs = [_path_to_3d_segment(path, pathz, zdir) 

236 for path, pathz in zip(paths, zs)] 

237 return segs 

238 

239 

240@cbook.deprecated("3.1") 

241def paths_to_3d_segments(paths, zs=0, zdir='z'): 

242 """Convert paths from a collection object to 3D segments.""" 

243 return _paths_to_3d_segments(paths, zs=zs, zdir=zdir) 

244 

245 

246def _path_to_3d_segment_with_codes(path, zs=0, zdir='z'): 

247 """Convert a path to a 3D segment with path codes.""" 

248 

249 zs = np.broadcast_to(zs, len(path)) 

250 pathsegs = path.iter_segments(simplify=False, curves=False) 

251 seg_codes = [((x, y, z), code) for ((x, y), code), z in zip(pathsegs, zs)] 

252 if seg_codes: 

253 seg, codes = zip(*seg_codes) 

254 seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg] 

255 else: 

256 seg3d = [] 

257 codes = [] 

258 return seg3d, list(codes) 

259 

260 

261@cbook.deprecated("3.1") 

262def path_to_3d_segment_with_codes(path, zs=0, zdir='z'): 

263 """Convert a path to a 3D segment with path codes.""" 

264 return _path_to_3d_segment_with_codes(path, zs=zs, zdir=zdir) 

265 

266 

267def _paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): 

268 """ 

269 Convert paths from a collection object to 3D segments with path codes. 

270 """ 

271 

272 zs = np.broadcast_to(zs, len(paths)) 

273 segments_codes = [_path_to_3d_segment_with_codes(path, pathz, zdir) 

274 for path, pathz in zip(paths, zs)] 

275 if segments_codes: 

276 segments, codes = zip(*segments_codes) 

277 else: 

278 segments, codes = [], [] 

279 return list(segments), list(codes) 

280 

281 

282@cbook.deprecated("3.1") 

283def paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'): 

284 """ 

285 Convert paths from a collection object to 3D segments with path codes. 

286 """ 

287 return _paths_to_3d_segments_with_codes(paths, zs=zs, zdir=zdir) 

288 

289 

290class Line3DCollection(LineCollection): 

291 """ 

292 A collection of 3D lines. 

293 """ 

294 

295 def set_sort_zpos(self, val): 

296 """Set the position to use for z-sorting.""" 

297 self._sort_zpos = val 

298 self.stale = True 

299 

300 def set_segments(self, segments): 

301 """ 

302 Set 3D segments. 

303 """ 

304 self._segments3d = np.asanyarray(segments) 

305 LineCollection.set_segments(self, []) 

306 

307 def do_3d_projection(self, renderer): 

308 """ 

309 Project the points according to renderer matrix. 

310 """ 

311 xyslist = [ 

312 proj3d.proj_trans_points(points, renderer.M) for points in 

313 self._segments3d] 

314 segments_2d = [np.column_stack([xs, ys]) for xs, ys, zs in xyslist] 

315 LineCollection.set_segments(self, segments_2d) 

316 

317 # FIXME 

318 minz = 1e9 

319 for xs, ys, zs in xyslist: 

320 minz = min(minz, min(zs)) 

321 return minz 

322 

323 @artist.allow_rasterization 

324 def draw(self, renderer, project=False): 

325 if project: 

326 self.do_3d_projection(renderer) 

327 LineCollection.draw(self, renderer) 

328 

329 

330def line_collection_2d_to_3d(col, zs=0, zdir='z'): 

331 """Convert a LineCollection to a Line3DCollection object.""" 

332 segments3d = _paths_to_3d_segments(col.get_paths(), zs, zdir) 

333 col.__class__ = Line3DCollection 

334 col.set_segments(segments3d) 

335 

336 

337class Patch3D(Patch): 

338 """ 

339 3D patch object. 

340 """ 

341 

342 def __init__(self, *args, zs=(), zdir='z', **kwargs): 

343 Patch.__init__(self, *args, **kwargs) 

344 self.set_3d_properties(zs, zdir) 

345 

346 def set_3d_properties(self, verts, zs=0, zdir='z'): 

347 zs = np.broadcast_to(zs, len(verts)) 

348 self._segment3d = [juggle_axes(x, y, z, zdir) 

349 for ((x, y), z) in zip(verts, zs)] 

350 self._facecolor3d = Patch.get_facecolor(self) 

351 

352 def get_path(self): 

353 return self._path2d 

354 

355 def get_facecolor(self): 

356 return self._facecolor2d 

357 

358 def do_3d_projection(self, renderer): 

359 s = self._segment3d 

360 xs, ys, zs = zip(*s) 

361 vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) 

362 self._path2d = mpath.Path(np.column_stack([vxs, vys])) 

363 # FIXME: coloring 

364 self._facecolor2d = self._facecolor3d 

365 return min(vzs) 

366 

367 

368class PathPatch3D(Patch3D): 

369 """ 

370 3D PathPatch object. 

371 """ 

372 

373 def __init__(self, path, *, zs=(), zdir='z', **kwargs): 

374 Patch.__init__(self, **kwargs) 

375 self.set_3d_properties(path, zs, zdir) 

376 

377 def set_3d_properties(self, path, zs=0, zdir='z'): 

378 Patch3D.set_3d_properties(self, path.vertices, zs=zs, zdir=zdir) 

379 self._code3d = path.codes 

380 

381 def do_3d_projection(self, renderer): 

382 s = self._segment3d 

383 xs, ys, zs = zip(*s) 

384 vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) 

385 self._path2d = mpath.Path(np.column_stack([vxs, vys]), self._code3d) 

386 # FIXME: coloring 

387 self._facecolor2d = self._facecolor3d 

388 return min(vzs) 

389 

390 

391def _get_patch_verts(patch): 

392 """Return a list of vertices for the path of a patch.""" 

393 trans = patch.get_patch_transform() 

394 path = patch.get_path() 

395 polygons = path.to_polygons(trans) 

396 if len(polygons): 

397 return polygons[0] 

398 else: 

399 return [] 

400 

401 

402@cbook.deprecated("3.1") 

403def get_patch_verts(patch): 

404 """Return a list of vertices for the path of a patch.""" 

405 return _get_patch_verts(patch) 

406 

407 

408def patch_2d_to_3d(patch, z=0, zdir='z'): 

409 """Convert a Patch to a Patch3D object.""" 

410 verts = _get_patch_verts(patch) 

411 patch.__class__ = Patch3D 

412 patch.set_3d_properties(verts, z, zdir) 

413 

414 

415def pathpatch_2d_to_3d(pathpatch, z=0, zdir='z'): 

416 """Convert a PathPatch to a PathPatch3D object.""" 

417 path = pathpatch.get_path() 

418 trans = pathpatch.get_patch_transform() 

419 

420 mpath = trans.transform_path(path) 

421 pathpatch.__class__ = PathPatch3D 

422 pathpatch.set_3d_properties(mpath, z, zdir) 

423 

424 

425class Patch3DCollection(PatchCollection): 

426 """ 

427 A collection of 3D patches. 

428 """ 

429 

430 def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): 

431 """ 

432 Create a collection of flat 3D patches with its normal vector 

433 pointed in *zdir* direction, and located at *zs* on the *zdir* 

434 axis. 'zs' can be a scalar or an array-like of the same length as 

435 the number of patches in the collection. 

436 

437 Constructor arguments are the same as for 

438 :class:`~matplotlib.collections.PatchCollection`. In addition, 

439 keywords *zs=0* and *zdir='z'* are available. 

440 

441 Also, the keyword argument "depthshade" is available to 

442 indicate whether or not to shade the patches in order to 

443 give the appearance of depth (default is *True*). 

444 This is typically desired in scatter plots. 

445 """ 

446 self._depthshade = depthshade 

447 super().__init__(*args, **kwargs) 

448 self.set_3d_properties(zs, zdir) 

449 

450 def set_sort_zpos(self, val): 

451 """Set the position to use for z-sorting.""" 

452 self._sort_zpos = val 

453 self.stale = True 

454 

455 def set_3d_properties(self, zs, zdir): 

456 # Force the collection to initialize the face and edgecolors 

457 # just in case it is a scalarmappable with a colormap. 

458 self.update_scalarmappable() 

459 offsets = self.get_offsets() 

460 if len(offsets) > 0: 

461 xs, ys = offsets.T 

462 else: 

463 xs = [] 

464 ys = [] 

465 self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) 

466 self._facecolor3d = self.get_facecolor() 

467 self._edgecolor3d = self.get_edgecolor() 

468 self.stale = True 

469 

470 def do_3d_projection(self, renderer): 

471 xs, ys, zs = self._offsets3d 

472 vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) 

473 

474 fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else 

475 self._facecolor3d) 

476 fcs = mcolors.to_rgba_array(fcs, self._alpha) 

477 self.set_facecolors(fcs) 

478 

479 ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else 

480 self._edgecolor3d) 

481 ecs = mcolors.to_rgba_array(ecs, self._alpha) 

482 self.set_edgecolors(ecs) 

483 PatchCollection.set_offsets(self, np.column_stack([vxs, vys])) 

484 

485 if vzs.size > 0: 

486 return min(vzs) 

487 else: 

488 return np.nan 

489 

490 

491class Path3DCollection(PathCollection): 

492 """ 

493 A collection of 3D paths. 

494 """ 

495 

496 def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs): 

497 """ 

498 Create a collection of flat 3D paths with its normal vector 

499 pointed in *zdir* direction, and located at *zs* on the *zdir* 

500 axis. 'zs' can be a scalar or an array-like of the same length as 

501 the number of paths in the collection. 

502 

503 Constructor arguments are the same as for 

504 :class:`~matplotlib.collections.PathCollection`. In addition, 

505 keywords *zs=0* and *zdir='z'* are available. 

506 

507 Also, the keyword argument "depthshade" is available to 

508 indicate whether or not to shade the patches in order to 

509 give the appearance of depth (default is *True*). 

510 This is typically desired in scatter plots. 

511 """ 

512 self._depthshade = depthshade 

513 super().__init__(*args, **kwargs) 

514 self.set_3d_properties(zs, zdir) 

515 

516 def set_sort_zpos(self, val): 

517 """Set the position to use for z-sorting.""" 

518 self._sort_zpos = val 

519 self.stale = True 

520 

521 def set_3d_properties(self, zs, zdir): 

522 # Force the collection to initialize the face and edgecolors 

523 # just in case it is a scalarmappable with a colormap. 

524 self.update_scalarmappable() 

525 offsets = self.get_offsets() 

526 if len(offsets) > 0: 

527 xs, ys = offsets.T 

528 else: 

529 xs = [] 

530 ys = [] 

531 self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) 

532 self._facecolor3d = self.get_facecolor() 

533 self._edgecolor3d = self.get_edgecolor() 

534 self.stale = True 

535 

536 def do_3d_projection(self, renderer): 

537 xs, ys, zs = self._offsets3d 

538 vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) 

539 

540 fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else 

541 self._facecolor3d) 

542 fcs = mcolors.to_rgba_array(fcs, self._alpha) 

543 self.set_facecolors(fcs) 

544 

545 ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else 

546 self._edgecolor3d) 

547 ecs = mcolors.to_rgba_array(ecs, self._alpha) 

548 self.set_edgecolors(ecs) 

549 PathCollection.set_offsets(self, np.column_stack([vxs, vys])) 

550 

551 return np.min(vzs) if vzs.size else np.nan 

552 

553 

554def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True): 

555 """ 

556 Convert a :class:`~matplotlib.collections.PatchCollection` into a 

557 :class:`Patch3DCollection` object 

558 (or a :class:`~matplotlib.collections.PathCollection` into a 

559 :class:`Path3DCollection` object). 

560 

561 Parameters 

562 ---------- 

563 za 

564 The location or locations to place the patches in the collection along 

565 the *zdir* axis. Default: 0. 

566 zdir 

567 The axis in which to place the patches. Default: "z". 

568 depthshade 

569 Whether to shade the patches to give a sense of depth. Default: *True*. 

570 

571 """ 

572 if isinstance(col, PathCollection): 

573 col.__class__ = Path3DCollection 

574 elif isinstance(col, PatchCollection): 

575 col.__class__ = Patch3DCollection 

576 col._depthshade = depthshade 

577 col.set_3d_properties(zs, zdir) 

578 

579 

580class Poly3DCollection(PolyCollection): 

581 """ 

582 A collection of 3D polygons. 

583 

584 .. note:: 

585 **Filling of 3D polygons** 

586 

587 There is no simple definition of the enclosed surface of a 3D polygon 

588 unless the polygon is planar. 

589 

590 In practice, Matplotlib performs the filling on the 2D projection of 

591 the polygon. This gives a correct filling appearance only for planar 

592 polygons. For all other polygons, you'll find orientations in which 

593 the edges of the polygon intersect in the projection. This will lead 

594 to an incorrect visualization of the 3D area. 

595 

596 If you need filled areas, it is recommended to create them via 

597 `~mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf`, which creates a 

598 triangulation and thus generates consistent surfaces. 

599 """ 

600 

601 def __init__(self, verts, *args, zsort='average', **kwargs): 

602 """ 

603 Parameters 

604 ---------- 

605 verts : list of array-like Nx3 

606 Each element describes a polygon as a sequnce of ``N_i`` points 

607 ``(x, y, z)``. 

608 zsort : {'average', 'min', 'max'}, default: 'average' 

609 The calculation method for the z-order. 

610 See `~.Poly3DCollection.set_zsort` for details. 

611 *args, **kwargs 

612 All other parameters are forwarded to `.PolyCollection`. 

613 

614 Notes 

615 ----- 

616 Note that this class does a bit of magic with the _facecolors 

617 and _edgecolors properties. 

618 """ 

619 super().__init__(verts, *args, **kwargs) 

620 self.set_zsort(zsort) 

621 self._codes3d = None 

622 

623 _zsort_functions = { 

624 'average': np.average, 

625 'min': np.min, 

626 'max': np.max, 

627 } 

628 

629 def set_zsort(self, zsort): 

630 """ 

631 Sets the calculation method for the z-order. 

632 

633 Parameters 

634 ---------- 

635 zsort : {'average', 'min', 'max'} 

636 The function applied on the z-coordinates of the vertices in the 

637 viewer's coordinate system, to determine the z-order. *True* is 

638 deprecated and equivalent to 'average'. 

639 """ 

640 if zsort is True: 

641 cbook.warn_deprecated( 

642 "3.1", message="Passing True to mean 'average' for set_zsort " 

643 "is deprecated and support will be removed in Matplotlib 3.3; " 

644 "pass 'average' instead.") 

645 zsort = 'average' 

646 self._zsortfunc = self._zsort_functions[zsort] 

647 self._sort_zpos = None 

648 self.stale = True 

649 

650 def get_vector(self, segments3d): 

651 """Optimize points for projection.""" 

652 if len(segments3d): 

653 xs, ys, zs = np.row_stack(segments3d).T 

654 else: # row_stack can't stack zero arrays. 

655 xs, ys, zs = [], [], [] 

656 ones = np.ones(len(xs)) 

657 self._vec = np.array([xs, ys, zs, ones]) 

658 

659 indices = [0, *np.cumsum([len(segment) for segment in segments3d])] 

660 self._segslices = [*map(slice, indices[:-1], indices[1:])] 

661 

662 def set_verts(self, verts, closed=True): 

663 """Set 3D vertices.""" 

664 self.get_vector(verts) 

665 # 2D verts will be updated at draw time 

666 PolyCollection.set_verts(self, [], False) 

667 self._closed = closed 

668 

669 def set_verts_and_codes(self, verts, codes): 

670 """Sets 3D vertices with path codes.""" 

671 # set vertices with closed=False to prevent PolyCollection from 

672 # setting path codes 

673 self.set_verts(verts, closed=False) 

674 # and set our own codes instead. 

675 self._codes3d = codes 

676 

677 def set_3d_properties(self): 

678 # Force the collection to initialize the face and edgecolors 

679 # just in case it is a scalarmappable with a colormap. 

680 self.update_scalarmappable() 

681 self._sort_zpos = None 

682 self.set_zsort('average') 

683 self._facecolors3d = PolyCollection.get_facecolor(self) 

684 self._edgecolors3d = PolyCollection.get_edgecolor(self) 

685 self._alpha3d = PolyCollection.get_alpha(self) 

686 self.stale = True 

687 

688 def set_sort_zpos(self, val): 

689 """Set the position to use for z-sorting.""" 

690 self._sort_zpos = val 

691 self.stale = True 

692 

693 def do_3d_projection(self, renderer): 

694 """ 

695 Perform the 3D projection for this object. 

696 """ 

697 # FIXME: This may no longer be needed? 

698 if self._A is not None: 

699 self.update_scalarmappable() 

700 self._facecolors3d = self._facecolors 

701 

702 txs, tys, tzs = proj3d._proj_transform_vec(self._vec, renderer.M) 

703 xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices] 

704 

705 # This extra fuss is to re-order face / edge colors 

706 cface = self._facecolors3d 

707 cedge = self._edgecolors3d 

708 if len(cface) != len(xyzlist): 

709 cface = cface.repeat(len(xyzlist), axis=0) 

710 if len(cedge) != len(xyzlist): 

711 if len(cedge) == 0: 

712 cedge = cface 

713 else: 

714 cedge = cedge.repeat(len(xyzlist), axis=0) 

715 

716 # sort by depth (furthest drawn first) 

717 z_segments_2d = sorted( 

718 ((self._zsortfunc(zs), np.column_stack([xs, ys]), fc, ec, idx) 

719 for idx, ((xs, ys, zs), fc, ec) 

720 in enumerate(zip(xyzlist, cface, cedge))), 

721 key=lambda x: x[0], reverse=True) 

722 

723 segments_2d = [s for z, s, fc, ec, idx in z_segments_2d] 

724 if self._codes3d is not None: 

725 codes = [self._codes3d[idx] for z, s, fc, ec, idx in z_segments_2d] 

726 PolyCollection.set_verts_and_codes(self, segments_2d, codes) 

727 else: 

728 PolyCollection.set_verts(self, segments_2d, self._closed) 

729 

730 self._facecolors2d = [fc for z, s, fc, ec, idx in z_segments_2d] 

731 if len(self._edgecolors3d) == len(cface): 

732 self._edgecolors2d = [ec for z, s, fc, ec, idx in z_segments_2d] 

733 else: 

734 self._edgecolors2d = self._edgecolors3d 

735 

736 # Return zorder value 

737 if self._sort_zpos is not None: 

738 zvec = np.array([[0], [0], [self._sort_zpos], [1]]) 

739 ztrans = proj3d._proj_transform_vec(zvec, renderer.M) 

740 return ztrans[2][0] 

741 elif tzs.size > 0: 

742 # FIXME: Some results still don't look quite right. 

743 # In particular, examine contourf3d_demo2.py 

744 # with az = -54 and elev = -45. 

745 return np.min(tzs) 

746 else: 

747 return np.nan 

748 

749 def set_facecolor(self, colors): 

750 PolyCollection.set_facecolor(self, colors) 

751 self._facecolors3d = PolyCollection.get_facecolor(self) 

752 

753 def set_edgecolor(self, colors): 

754 PolyCollection.set_edgecolor(self, colors) 

755 self._edgecolors3d = PolyCollection.get_edgecolor(self) 

756 

757 def set_alpha(self, alpha): 

758 # docstring inherited 

759 artist.Artist.set_alpha(self, alpha) 

760 try: 

761 self._facecolors3d = mcolors.to_rgba_array( 

762 self._facecolors3d, self._alpha) 

763 except (AttributeError, TypeError, IndexError): 

764 pass 

765 try: 

766 self._edgecolors = mcolors.to_rgba_array( 

767 self._edgecolors3d, self._alpha) 

768 except (AttributeError, TypeError, IndexError): 

769 pass 

770 self.stale = True 

771 

772 def get_facecolor(self): 

773 return self._facecolors2d 

774 

775 def get_edgecolor(self): 

776 return self._edgecolors2d 

777 

778 

779def poly_collection_2d_to_3d(col, zs=0, zdir='z'): 

780 """Convert a PolyCollection to a Poly3DCollection object.""" 

781 segments_3d, codes = _paths_to_3d_segments_with_codes( 

782 col.get_paths(), zs, zdir) 

783 col.__class__ = Poly3DCollection 

784 col.set_verts_and_codes(segments_3d, codes) 

785 col.set_3d_properties() 

786 

787 

788def juggle_axes(xs, ys, zs, zdir): 

789 """ 

790 Reorder coordinates so that 2D xs, ys can be plotted in the plane 

791 orthogonal to zdir. zdir is normally x, y or z. However, if zdir 

792 starts with a '-' it is interpreted as a compensation for rotate_axes. 

793 """ 

794 if zdir == 'x': 

795 return zs, xs, ys 

796 elif zdir == 'y': 

797 return xs, zs, ys 

798 elif zdir[0] == '-': 

799 return rotate_axes(xs, ys, zs, zdir) 

800 else: 

801 return xs, ys, zs 

802 

803 

804def rotate_axes(xs, ys, zs, zdir): 

805 """ 

806 Reorder coordinates so that the axes are rotated with zdir along 

807 the original z axis. Prepending the axis with a '-' does the 

808 inverse transform, so zdir can be x, -x, y, -y, z or -z 

809 """ 

810 if zdir == 'x': 

811 return ys, zs, xs 

812 elif zdir == '-x': 

813 return zs, xs, ys 

814 

815 elif zdir == 'y': 

816 return zs, xs, ys 

817 elif zdir == '-y': 

818 return ys, zs, xs 

819 

820 else: 

821 return xs, ys, zs 

822 

823 

824def _get_colors(c, num): 

825 """Stretch the color argument to provide the required number *num*.""" 

826 return np.broadcast_to( 

827 mcolors.to_rgba_array(c) if len(c) else [0, 0, 0, 0], 

828 (num, 4)) 

829 

830 

831@cbook.deprecated("3.1") 

832def get_colors(c, num): 

833 """Stretch the color argument to provide the required number *num*.""" 

834 return _get_colors(c, num) 

835 

836 

837def _zalpha(colors, zs): 

838 """Modify the alphas of the color list according to depth.""" 

839 # FIXME: This only works well if the points for *zs* are well-spaced 

840 # in all three dimensions. Otherwise, at certain orientations, 

841 # the min and max zs are very close together. 

842 # Should really normalize against the viewing depth. 

843 if len(zs) == 0: 

844 return np.zeros((0, 4)) 

845 norm = Normalize(min(zs), max(zs)) 

846 sats = 1 - norm(zs) * 0.7 

847 rgba = np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs), 4)) 

848 return np.column_stack([rgba[:, :3], rgba[:, 3] * sats]) 

849 

850 

851@cbook.deprecated("3.1") 

852def zalpha(colors, zs): 

853 """Modify the alphas of the color list according to depth.""" 

854 return _zalpha(colors, zs)