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""" 

2Classes for the efficient drawing of large collections of objects that 

3share most properties, e.g., a large number of line segments or 

4polygons. 

5 

6The classes are not meant to be as flexible as their single element 

7counterparts (e.g., you may not be able to select all line styles) but 

8they are meant to be fast for common use cases (e.g., a large set of solid 

9line segments). 

10""" 

11 

12import math 

13from numbers import Number 

14import numpy as np 

15 

16import matplotlib as mpl 

17from . import (_path, artist, cbook, cm, colors as mcolors, docstring, 

18 lines as mlines, path as mpath, transforms) 

19import warnings 

20 

21 

22@cbook._define_aliases({ 

23 "antialiased": ["antialiaseds", "aa"], 

24 "edgecolor": ["edgecolors", "ec"], 

25 "facecolor": ["facecolors", "fc"], 

26 "linestyle": ["linestyles", "dashes", "ls"], 

27 "linewidth": ["linewidths", "lw"], 

28}) 

29class Collection(artist.Artist, cm.ScalarMappable): 

30 """ 

31 Base class for Collections. Must be subclassed to be usable. 

32 

33 All properties in a collection must be sequences or scalars; 

34 if scalars, they will be converted to sequences. The 

35 property of the ith element of the collection is:: 

36 

37 prop[i % len(props)] 

38 

39 Exceptions are *capstyle* and *joinstyle* properties, these can 

40 only be set globally for the whole collection. 

41 

42 Keyword arguments and default values: 

43 

44 * *edgecolors*: None 

45 * *facecolors*: None 

46 * *linewidths*: None 

47 * *capstyle*: None 

48 * *joinstyle*: None 

49 * *antialiaseds*: None 

50 * *offsets*: None 

51 * *transOffset*: transforms.IdentityTransform() 

52 * *offset_position*: 'screen' (default) or 'data' 

53 * *norm*: None (optional for 

54 :class:`matplotlib.cm.ScalarMappable`) 

55 * *cmap*: None (optional for 

56 :class:`matplotlib.cm.ScalarMappable`) 

57 * *hatch*: None 

58 * *zorder*: 1 

59 

60 *offsets* and *transOffset* are used to translate the patch after 

61 rendering (default no offsets). If offset_position is 'screen' 

62 (default) the offset is applied after the master transform has 

63 been applied, that is, the offsets are in screen coordinates. If 

64 offset_position is 'data', the offset is applied before the master 

65 transform, i.e., the offsets are in data coordinates. 

66 

67 If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds* 

68 are None, they default to their :data:`matplotlib.rcParams` patch 

69 setting, in sequence form. 

70 

71 The use of :class:`~matplotlib.cm.ScalarMappable` is optional. If 

72 the :class:`~matplotlib.cm.ScalarMappable` matrix _A is not None 

73 (i.e., a call to set_array has been made), at draw time a call to 

74 scalar mappable will be made to set the face colors. 

75 """ 

76 _offsets = np.zeros((0, 2)) 

77 _transOffset = transforms.IdentityTransform() 

78 #: Either a list of 3x3 arrays or an Nx3x3 array of transforms, suitable 

79 #: for the `all_transforms` argument to 

80 #: :meth:`~matplotlib.backend_bases.RendererBase.draw_path_collection`; 

81 #: each 3x3 array is used to initialize an 

82 #: :class:`~matplotlib.transforms.Affine2D` object. 

83 #: Each kind of collection defines this based on its arguments. 

84 _transforms = np.empty((0, 3, 3)) 

85 

86 # Whether to draw an edge by default. Set on a 

87 # subclass-by-subclass basis. 

88 _edge_default = False 

89 

90 def __init__(self, 

91 edgecolors=None, 

92 facecolors=None, 

93 linewidths=None, 

94 linestyles='solid', 

95 capstyle=None, 

96 joinstyle=None, 

97 antialiaseds=None, 

98 offsets=None, 

99 transOffset=None, 

100 norm=None, # optional for ScalarMappable 

101 cmap=None, # ditto 

102 pickradius=5.0, 

103 hatch=None, 

104 urls=None, 

105 offset_position='screen', 

106 zorder=1, 

107 **kwargs 

108 ): 

109 """ 

110 Create a Collection 

111 

112 %(Collection)s 

113 """ 

114 artist.Artist.__init__(self) 

115 cm.ScalarMappable.__init__(self, norm, cmap) 

116 # list of un-scaled dash patterns 

117 # this is needed scaling the dash pattern by linewidth 

118 self._us_linestyles = [(None, None)] 

119 # list of dash patterns 

120 self._linestyles = [(None, None)] 

121 # list of unbroadcast/scaled linewidths 

122 self._us_lw = [0] 

123 self._linewidths = [0] 

124 self._is_filled = True # May be modified by set_facecolor(). 

125 

126 self._hatch_color = mcolors.to_rgba(mpl.rcParams['hatch.color']) 

127 self.set_facecolor(facecolors) 

128 self.set_edgecolor(edgecolors) 

129 self.set_linewidth(linewidths) 

130 self.set_linestyle(linestyles) 

131 self.set_antialiased(antialiaseds) 

132 self.set_pickradius(pickradius) 

133 self.set_urls(urls) 

134 self.set_hatch(hatch) 

135 self.set_offset_position(offset_position) 

136 self.set_zorder(zorder) 

137 

138 if capstyle: 

139 self.set_capstyle(capstyle) 

140 else: 

141 self._capstyle = None 

142 

143 if joinstyle: 

144 self.set_joinstyle(joinstyle) 

145 else: 

146 self._joinstyle = None 

147 

148 self._offsets = np.zeros((1, 2)) 

149 # save if offsets passed in were none... 

150 self._offsetsNone = offsets is None 

151 self._uniform_offsets = None 

152 if offsets is not None: 

153 offsets = np.asanyarray(offsets, float) 

154 # Broadcast (2,) -> (1, 2) but nothing else. 

155 if offsets.shape == (2,): 

156 offsets = offsets[None, :] 

157 if transOffset is not None: 

158 self._offsets = offsets 

159 self._transOffset = transOffset 

160 else: 

161 self._uniform_offsets = offsets 

162 

163 self._path_effects = None 

164 self.update(kwargs) 

165 self._paths = None 

166 

167 def get_paths(self): 

168 return self._paths 

169 

170 def set_paths(self): 

171 raise NotImplementedError 

172 

173 def get_transforms(self): 

174 return self._transforms 

175 

176 def get_offset_transform(self): 

177 t = self._transOffset 

178 if (not isinstance(t, transforms.Transform) 

179 and hasattr(t, '_as_mpl_transform')): 

180 t = t._as_mpl_transform(self.axes) 

181 return t 

182 

183 def get_datalim(self, transData): 

184 

185 # Get the automatic datalim of the collection. 

186 # 

187 # This operation depends on the transforms for the data in the 

188 # collection and whether the collection has offsets. 

189 # 

190 # 1) offsets = None, transform child of transData: use the paths for 

191 # the automatic limits (i.e. for LineCollection in streamline). 

192 # 2) offsets != None: offset_transform is child of transData: 

193 # a) transform is child of transData: use the path + offset for 

194 # limits (i.e for bar). 

195 # b) transform is not a child of transData: just use the offsets 

196 # for the limits (i.e. for scatter) 

197 # 3) otherwise return a null Bbox. 

198 

199 transform = self.get_transform() 

200 transOffset = self.get_offset_transform() 

201 if (not self._offsetsNone and 

202 not transOffset.contains_branch(transData)): 

203 # if there are offsets but in some co-ords other than data, 

204 # then don't use them for autoscaling. 

205 return transforms.Bbox.null() 

206 offsets = self._offsets 

207 

208 paths = self.get_paths() 

209 

210 if not transform.is_affine: 

211 paths = [transform.transform_path_non_affine(p) for p in paths] 

212 # Don't convert transform to transform.get_affine() here because 

213 # we may have transform.contains_branch(transData) but not 

214 # transforms.get_affine().contains_branch(transData). But later, 

215 # be careful to only apply the affine part that remains. 

216 

217 if isinstance(offsets, np.ma.MaskedArray): 

218 offsets = offsets.filled(np.nan) 

219 # get_path_collection_extents handles nan but not masked arrays 

220 

221 if len(paths) and len(offsets): 

222 if any(transform.contains_branch_seperately(transData)): 

223 # collections that are just in data units (like quiver) 

224 # can properly have the axes limits set by their shape + 

225 # offset. LineCollections that have no offsets can 

226 # also use this algorithm (like streamplot). 

227 result = mpath.get_path_collection_extents( 

228 transform.get_affine(), paths, self.get_transforms(), 

229 transOffset.transform_non_affine(offsets), 

230 transOffset.get_affine().frozen()) 

231 return result.transformed(transData.inverted()) 

232 if not self._offsetsNone: 

233 # this is for collections that have their paths (shapes) 

234 # in physical, axes-relative, or figure-relative units 

235 # (i.e. like scatter). We can't uniquely set limits based on 

236 # those shapes, so we just set the limits based on their 

237 # location. 

238 

239 offsets = (transOffset - transData).transform(offsets) 

240 # note A-B means A B^{-1} 

241 offsets = np.ma.masked_invalid(offsets) 

242 if not offsets.mask.all(): 

243 points = np.row_stack((offsets.min(axis=0), 

244 offsets.max(axis=0))) 

245 return transforms.Bbox(points) 

246 return transforms.Bbox.null() 

247 

248 def get_window_extent(self, renderer): 

249 # TODO: check to ensure that this does not fail for 

250 # cases other than scatter plot legend 

251 return self.get_datalim(transforms.IdentityTransform()) 

252 

253 def _prepare_points(self): 

254 # Helper for drawing and hit testing. 

255 

256 transform = self.get_transform() 

257 transOffset = self.get_offset_transform() 

258 offsets = self._offsets 

259 paths = self.get_paths() 

260 

261 if self.have_units(): 

262 paths = [] 

263 for path in self.get_paths(): 

264 vertices = path.vertices 

265 xs, ys = vertices[:, 0], vertices[:, 1] 

266 xs = self.convert_xunits(xs) 

267 ys = self.convert_yunits(ys) 

268 paths.append(mpath.Path(np.column_stack([xs, ys]), path.codes)) 

269 if offsets.size: 

270 xs = self.convert_xunits(offsets[:, 0]) 

271 ys = self.convert_yunits(offsets[:, 1]) 

272 offsets = np.column_stack([xs, ys]) 

273 

274 if not transform.is_affine: 

275 paths = [transform.transform_path_non_affine(path) 

276 for path in paths] 

277 transform = transform.get_affine() 

278 if not transOffset.is_affine: 

279 offsets = transOffset.transform_non_affine(offsets) 

280 # This might have changed an ndarray into a masked array. 

281 transOffset = transOffset.get_affine() 

282 

283 if isinstance(offsets, np.ma.MaskedArray): 

284 offsets = offsets.filled(np.nan) 

285 # Changing from a masked array to nan-filled ndarray 

286 # is probably most efficient at this point. 

287 

288 return transform, transOffset, offsets, paths 

289 

290 @artist.allow_rasterization 

291 def draw(self, renderer): 

292 if not self.get_visible(): 

293 return 

294 renderer.open_group(self.__class__.__name__, self.get_gid()) 

295 

296 self.update_scalarmappable() 

297 

298 transform, transOffset, offsets, paths = self._prepare_points() 

299 

300 gc = renderer.new_gc() 

301 self._set_gc_clip(gc) 

302 gc.set_snap(self.get_snap()) 

303 

304 if self._hatch: 

305 gc.set_hatch(self._hatch) 

306 try: 

307 gc.set_hatch_color(self._hatch_color) 

308 except AttributeError: 

309 # if we end up with a GC that does not have this method 

310 cbook.warn_deprecated( 

311 "3.1", message="Your backend does not support setting the " 

312 "hatch color; such backends will become unsupported in " 

313 "Matplotlib 3.3.") 

314 

315 if self.get_sketch_params() is not None: 

316 gc.set_sketch_params(*self.get_sketch_params()) 

317 

318 if self.get_path_effects(): 

319 from matplotlib.patheffects import PathEffectRenderer 

320 renderer = PathEffectRenderer(self.get_path_effects(), renderer) 

321 

322 # If the collection is made up of a single shape/color/stroke, 

323 # it can be rendered once and blitted multiple times, using 

324 # `draw_markers` rather than `draw_path_collection`. This is 

325 # *much* faster for Agg, and results in smaller file sizes in 

326 # PDF/SVG/PS. 

327 

328 trans = self.get_transforms() 

329 facecolors = self.get_facecolor() 

330 edgecolors = self.get_edgecolor() 

331 do_single_path_optimization = False 

332 if (len(paths) == 1 and len(trans) <= 1 and 

333 len(facecolors) == 1 and len(edgecolors) == 1 and 

334 len(self._linewidths) == 1 and 

335 self._linestyles == [(None, None)] and 

336 len(self._antialiaseds) == 1 and len(self._urls) == 1 and 

337 self.get_hatch() is None): 

338 if len(trans): 

339 combined_transform = transforms.Affine2D(trans[0]) + transform 

340 else: 

341 combined_transform = transform 

342 extents = paths[0].get_extents(combined_transform) 

343 if (extents.width < self.figure.bbox.width 

344 and extents.height < self.figure.bbox.height): 

345 do_single_path_optimization = True 

346 

347 if self._joinstyle: 

348 gc.set_joinstyle(self._joinstyle) 

349 

350 if self._capstyle: 

351 gc.set_capstyle(self._capstyle) 

352 

353 if do_single_path_optimization: 

354 gc.set_foreground(tuple(edgecolors[0])) 

355 gc.set_linewidth(self._linewidths[0]) 

356 gc.set_dashes(*self._linestyles[0]) 

357 gc.set_antialiased(self._antialiaseds[0]) 

358 gc.set_url(self._urls[0]) 

359 renderer.draw_markers( 

360 gc, paths[0], combined_transform.frozen(), 

361 mpath.Path(offsets), transOffset, tuple(facecolors[0])) 

362 else: 

363 renderer.draw_path_collection( 

364 gc, transform.frozen(), paths, 

365 self.get_transforms(), offsets, transOffset, 

366 self.get_facecolor(), self.get_edgecolor(), 

367 self._linewidths, self._linestyles, 

368 self._antialiaseds, self._urls, 

369 self._offset_position) 

370 

371 gc.restore() 

372 renderer.close_group(self.__class__.__name__) 

373 self.stale = False 

374 

375 def set_pickradius(self, pr): 

376 """ 

377 Set the pick radius used for containment tests. 

378 

379 Parameters 

380 ---------- 

381 d : float 

382 Pick radius, in points. 

383 """ 

384 self._pickradius = pr 

385 

386 def get_pickradius(self): 

387 return self._pickradius 

388 

389 def contains(self, mouseevent): 

390 """ 

391 Test whether the mouse event occurred in the collection. 

392 

393 Returns ``bool, dict(ind=itemlist)``, where every item in itemlist 

394 contains the event. 

395 """ 

396 inside, info = self._default_contains(mouseevent) 

397 if inside is not None: 

398 return inside, info 

399 

400 if not self.get_visible(): 

401 return False, {} 

402 

403 pickradius = ( 

404 float(self._picker) 

405 if isinstance(self._picker, Number) and 

406 self._picker is not True # the bool, not just nonzero or 1 

407 else self._pickradius) 

408 

409 if self.axes and self.get_offset_position() == "data": 

410 self.axes._unstale_viewLim() 

411 

412 transform, transOffset, offsets, paths = self._prepare_points() 

413 

414 ind = _path.point_in_path_collection( 

415 mouseevent.x, mouseevent.y, pickradius, 

416 transform.frozen(), paths, self.get_transforms(), 

417 offsets, transOffset, pickradius <= 0, 

418 self.get_offset_position()) 

419 

420 return len(ind) > 0, dict(ind=ind) 

421 

422 def set_urls(self, urls): 

423 """ 

424 Parameters 

425 ---------- 

426 urls : List[str] or None 

427 """ 

428 self._urls = urls if urls is not None else [None] 

429 self.stale = True 

430 

431 def get_urls(self): 

432 return self._urls 

433 

434 def set_hatch(self, hatch): 

435 r""" 

436 Set the hatching pattern 

437 

438 *hatch* can be one of:: 

439 

440 / - diagonal hatching 

441 \ - back diagonal 

442 | - vertical 

443 - - horizontal 

444 + - crossed 

445 x - crossed diagonal 

446 o - small circle 

447 O - large circle 

448 . - dots 

449 * - stars 

450 

451 Letters can be combined, in which case all the specified 

452 hatchings are done. If same letter repeats, it increases the 

453 density of hatching of that pattern. 

454 

455 Hatching is supported in the PostScript, PDF, SVG and Agg 

456 backends only. 

457 

458 Unlike other properties such as linewidth and colors, hatching 

459 can only be specified for the collection as a whole, not separately 

460 for each member. 

461 

462 Parameters 

463 ---------- 

464 hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'} 

465 """ 

466 self._hatch = hatch 

467 self.stale = True 

468 

469 def get_hatch(self): 

470 """Return the current hatching pattern.""" 

471 return self._hatch 

472 

473 def set_offsets(self, offsets): 

474 """ 

475 Set the offsets for the collection. 

476 

477 Parameters 

478 ---------- 

479 offsets : array-like (N, 2) or (2,) 

480 """ 

481 offsets = np.asanyarray(offsets, float) 

482 if offsets.shape == (2,): # Broadcast (2,) -> (1, 2) but nothing else. 

483 offsets = offsets[None, :] 

484 # This decision is based on how they are initialized above in __init__. 

485 if self._uniform_offsets is None: 

486 self._offsets = offsets 

487 else: 

488 self._uniform_offsets = offsets 

489 self.stale = True 

490 

491 def get_offsets(self): 

492 """Return the offsets for the collection.""" 

493 # This decision is based on how they are initialized above in __init__. 

494 if self._uniform_offsets is None: 

495 return self._offsets 

496 else: 

497 return self._uniform_offsets 

498 

499 def set_offset_position(self, offset_position): 

500 """ 

501 Set how offsets are applied. If *offset_position* is 'screen' 

502 (default) the offset is applied after the master transform has 

503 been applied, that is, the offsets are in screen coordinates. 

504 If offset_position is 'data', the offset is applied before the 

505 master transform, i.e., the offsets are in data coordinates. 

506 

507 Parameters 

508 ---------- 

509 offset_position : {'screen', 'data'} 

510 """ 

511 cbook._check_in_list(['screen', 'data'], 

512 offset_position=offset_position) 

513 self._offset_position = offset_position 

514 self.stale = True 

515 

516 def get_offset_position(self): 

517 """ 

518 Returns how offsets are applied for the collection. If 

519 *offset_position* is 'screen', the offset is applied after the 

520 master transform has been applied, that is, the offsets are in 

521 screen coordinates. If offset_position is 'data', the offset 

522 is applied before the master transform, i.e., the offsets are 

523 in data coordinates. 

524 """ 

525 return self._offset_position 

526 

527 def set_linewidth(self, lw): 

528 """ 

529 Set the linewidth(s) for the collection. *lw* can be a scalar 

530 or a sequence; if it is a sequence the patches will cycle 

531 through the sequence 

532 

533 Parameters 

534 ---------- 

535 lw : float or sequence of floats 

536 """ 

537 if lw is None: 

538 lw = mpl.rcParams['patch.linewidth'] 

539 if lw is None: 

540 lw = mpl.rcParams['lines.linewidth'] 

541 # get the un-scaled/broadcast lw 

542 self._us_lw = np.atleast_1d(np.asarray(lw)) 

543 

544 # scale all of the dash patterns. 

545 self._linewidths, self._linestyles = self._bcast_lwls( 

546 self._us_lw, self._us_linestyles) 

547 self.stale = True 

548 

549 def set_linestyle(self, ls): 

550 """ 

551 Set the linestyle(s) for the collection. 

552 

553 =========================== ================= 

554 linestyle description 

555 =========================== ================= 

556 ``'-'`` or ``'solid'`` solid line 

557 ``'--'`` or ``'dashed'`` dashed line 

558 ``'-.'`` or ``'dashdot'`` dash-dotted line 

559 ``':'`` or ``'dotted'`` dotted line 

560 =========================== ================= 

561 

562 Alternatively a dash tuple of the following form can be provided:: 

563 

564 (offset, onoffseq), 

565 

566 where ``onoffseq`` is an even length tuple of on and off ink in points. 

567 

568 Parameters 

569 ---------- 

570 ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} 

571 The line style. 

572 """ 

573 try: 

574 if isinstance(ls, str): 

575 ls = cbook.ls_mapper.get(ls, ls) 

576 dashes = [mlines._get_dash_pattern(ls)] 

577 else: 

578 try: 

579 dashes = [mlines._get_dash_pattern(ls)] 

580 except ValueError: 

581 dashes = [mlines._get_dash_pattern(x) for x in ls] 

582 

583 except ValueError: 

584 raise ValueError( 

585 'Do not know how to convert {!r} to dashes'.format(ls)) 

586 

587 # get the list of raw 'unscaled' dash patterns 

588 self._us_linestyles = dashes 

589 

590 # broadcast and scale the lw and dash patterns 

591 self._linewidths, self._linestyles = self._bcast_lwls( 

592 self._us_lw, self._us_linestyles) 

593 

594 def set_capstyle(self, cs): 

595 """ 

596 Set the capstyle for the collection (for all its elements). 

597 

598 Parameters 

599 ---------- 

600 cs : {'butt', 'round', 'projecting'} 

601 The capstyle 

602 """ 

603 cbook._check_in_list(('butt', 'round', 'projecting'), capstyle=cs) 

604 self._capstyle = cs 

605 

606 def get_capstyle(self): 

607 return self._capstyle 

608 

609 def set_joinstyle(self, js): 

610 """ 

611 Set the joinstyle for the collection (for all its elements). 

612 

613 Parameters 

614 ---------- 

615 js : {'miter', 'round', 'bevel'} 

616 The joinstyle 

617 """ 

618 cbook._check_in_list(('miter', 'round', 'bevel'), joinstyle=js) 

619 self._joinstyle = js 

620 

621 def get_joinstyle(self): 

622 return self._joinstyle 

623 

624 @staticmethod 

625 def _bcast_lwls(linewidths, dashes): 

626 """ 

627 Internal helper function to broadcast + scale ls/lw 

628 

629 In the collection drawing code, the linewidth and linestyle are cycled 

630 through as circular buffers (via ``v[i % len(v)]``). Thus, if we are 

631 going to scale the dash pattern at set time (not draw time) we need to 

632 do the broadcasting now and expand both lists to be the same length. 

633 

634 Parameters 

635 ---------- 

636 linewidths : list 

637 line widths of collection 

638 dashes : list 

639 dash specification (offset, (dash pattern tuple)) 

640 

641 Returns 

642 ------- 

643 linewidths, dashes : list 

644 Will be the same length, dashes are scaled by paired linewidth 

645 """ 

646 if mpl.rcParams['_internal.classic_mode']: 

647 return linewidths, dashes 

648 # make sure they are the same length so we can zip them 

649 if len(dashes) != len(linewidths): 

650 l_dashes = len(dashes) 

651 l_lw = len(linewidths) 

652 gcd = math.gcd(l_dashes, l_lw) 

653 dashes = list(dashes) * (l_lw // gcd) 

654 linewidths = list(linewidths) * (l_dashes // gcd) 

655 

656 # scale the dash patters 

657 dashes = [mlines._scale_dashes(o, d, lw) 

658 for (o, d), lw in zip(dashes, linewidths)] 

659 

660 return linewidths, dashes 

661 

662 def set_antialiased(self, aa): 

663 """ 

664 Set the antialiasing state for rendering. 

665 

666 Parameters 

667 ---------- 

668 aa : bool or sequence of bools 

669 """ 

670 if aa is None: 

671 aa = mpl.rcParams['patch.antialiased'] 

672 self._antialiaseds = np.atleast_1d(np.asarray(aa, bool)) 

673 self.stale = True 

674 

675 def set_color(self, c): 

676 """ 

677 Set both the edgecolor and the facecolor. 

678 

679 Parameters 

680 ---------- 

681 c : color or sequence of rgba tuples 

682 

683 See Also 

684 -------- 

685 Collection.set_facecolor, Collection.set_edgecolor 

686 For setting the edge or face color individually. 

687 """ 

688 self.set_facecolor(c) 

689 self.set_edgecolor(c) 

690 

691 def _set_facecolor(self, c): 

692 if c is None: 

693 c = mpl.rcParams['patch.facecolor'] 

694 

695 self._is_filled = True 

696 try: 

697 if c.lower() == 'none': 

698 self._is_filled = False 

699 except AttributeError: 

700 pass 

701 self._facecolors = mcolors.to_rgba_array(c, self._alpha) 

702 self.stale = True 

703 

704 def set_facecolor(self, c): 

705 """ 

706 Set the facecolor(s) of the collection. *c* can be a color (all patches 

707 have same color), or a sequence of colors; if it is a sequence the 

708 patches will cycle through the sequence. 

709 

710 If *c* is 'none', the patch will not be filled. 

711 

712 Parameters 

713 ---------- 

714 c : color or sequence of colors 

715 """ 

716 self._original_facecolor = c 

717 self._set_facecolor(c) 

718 

719 def get_facecolor(self): 

720 return self._facecolors 

721 

722 def get_edgecolor(self): 

723 if cbook._str_equal(self._edgecolors, 'face'): 

724 return self.get_facecolor() 

725 else: 

726 return self._edgecolors 

727 

728 def _set_edgecolor(self, c): 

729 set_hatch_color = True 

730 if c is None: 

731 if (mpl.rcParams['patch.force_edgecolor'] or 

732 not self._is_filled or self._edge_default): 

733 c = mpl.rcParams['patch.edgecolor'] 

734 else: 

735 c = 'none' 

736 set_hatch_color = False 

737 

738 self._is_stroked = True 

739 try: 

740 if c.lower() == 'none': 

741 self._is_stroked = False 

742 except AttributeError: 

743 pass 

744 

745 try: 

746 if c.lower() == 'face': # Special case: lookup in "get" method. 

747 self._edgecolors = 'face' 

748 return 

749 except AttributeError: 

750 pass 

751 self._edgecolors = mcolors.to_rgba_array(c, self._alpha) 

752 if set_hatch_color and len(self._edgecolors): 

753 self._hatch_color = tuple(self._edgecolors[0]) 

754 self.stale = True 

755 

756 def set_edgecolor(self, c): 

757 """ 

758 Set the edgecolor(s) of the collection. 

759 

760 Parameters 

761 ---------- 

762 c : color or sequence of colors or 'face' 

763 The collection edgecolor(s). If a sequence, the patches cycle 

764 through it. If 'face', match the facecolor. 

765 """ 

766 self._original_edgecolor = c 

767 self._set_edgecolor(c) 

768 

769 def set_alpha(self, alpha): 

770 # docstring inherited 

771 super().set_alpha(alpha) 

772 self.update_dict['array'] = True 

773 self._set_facecolor(self._original_facecolor) 

774 self._set_edgecolor(self._original_edgecolor) 

775 

776 def get_linewidth(self): 

777 return self._linewidths 

778 

779 def get_linestyle(self): 

780 return self._linestyles 

781 

782 def update_scalarmappable(self): 

783 """Update colors from the scalar mappable array, if it is not None.""" 

784 if self._A is None: 

785 return 

786 if self._A.ndim > 1: 

787 raise ValueError('Collections can only map rank 1 arrays') 

788 if not self.check_update("array"): 

789 return 

790 if self._is_filled: 

791 self._facecolors = self.to_rgba(self._A, self._alpha) 

792 elif self._is_stroked: 

793 self._edgecolors = self.to_rgba(self._A, self._alpha) 

794 self.stale = True 

795 

796 def get_fill(self): 

797 'return whether fill is set' 

798 return self._is_filled 

799 

800 def update_from(self, other): 

801 'copy properties from other to self' 

802 

803 artist.Artist.update_from(self, other) 

804 self._antialiaseds = other._antialiaseds 

805 self._original_edgecolor = other._original_edgecolor 

806 self._edgecolors = other._edgecolors 

807 self._original_facecolor = other._original_facecolor 

808 self._facecolors = other._facecolors 

809 self._linewidths = other._linewidths 

810 self._linestyles = other._linestyles 

811 self._us_linestyles = other._us_linestyles 

812 self._pickradius = other._pickradius 

813 self._hatch = other._hatch 

814 

815 # update_from for scalarmappable 

816 self._A = other._A 

817 self.norm = other.norm 

818 self.cmap = other.cmap 

819 # self.update_dict = other.update_dict # do we need to copy this? -JJL 

820 self.stale = True 

821 

822 

823# these are not available for the object inspector until after the 

824# class is built so we define an initial set here for the init 

825# function and they will be overridden after object defn 

826docstring.interpd.update(Collection="""\ 

827 Valid Collection keyword arguments: 

828 

829 * *edgecolors*: None 

830 * *facecolors*: None 

831 * *linewidths*: None 

832 * *antialiaseds*: None 

833 * *offsets*: None 

834 * *transOffset*: transforms.IdentityTransform() 

835 * *norm*: None (optional for 

836 :class:`matplotlib.cm.ScalarMappable`) 

837 * *cmap*: None (optional for 

838 :class:`matplotlib.cm.ScalarMappable`) 

839 

840 *offsets* and *transOffset* are used to translate the patch after 

841 rendering (default no offsets) 

842 

843 If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds* 

844 are None, they default to their :data:`matplotlib.rcParams` patch 

845 setting, in sequence form. 

846""") 

847 

848 

849class _CollectionWithSizes(Collection): 

850 """ 

851 Base class for collections that have an array of sizes. 

852 """ 

853 _factor = 1.0 

854 

855 def get_sizes(self): 

856 """ 

857 Returns the sizes of the elements in the collection. The 

858 value represents the 'area' of the element. 

859 

860 Returns 

861 ------- 

862 sizes : array 

863 The 'area' of each element. 

864 """ 

865 return self._sizes 

866 

867 def set_sizes(self, sizes, dpi=72.0): 

868 """ 

869 Set the sizes of each member of the collection. 

870 

871 Parameters 

872 ---------- 

873 sizes : ndarray or None 

874 The size to set for each element of the collection. The 

875 value is the 'area' of the element. 

876 dpi : float 

877 The dpi of the canvas. Defaults to 72.0. 

878 """ 

879 if sizes is None: 

880 self._sizes = np.array([]) 

881 self._transforms = np.empty((0, 3, 3)) 

882 else: 

883 self._sizes = np.asarray(sizes) 

884 self._transforms = np.zeros((len(self._sizes), 3, 3)) 

885 scale = np.sqrt(self._sizes) * dpi / 72.0 * self._factor 

886 self._transforms[:, 0, 0] = scale 

887 self._transforms[:, 1, 1] = scale 

888 self._transforms[:, 2, 2] = 1.0 

889 self.stale = True 

890 

891 @artist.allow_rasterization 

892 def draw(self, renderer): 

893 self.set_sizes(self._sizes, self.figure.dpi) 

894 Collection.draw(self, renderer) 

895 

896 

897class PathCollection(_CollectionWithSizes): 

898 """ 

899 This is the most basic :class:`Collection` subclass. 

900 A :class:`PathCollection` is e.g. created by a :meth:`~.Axes.scatter` plot. 

901 """ 

902 @docstring.dedent_interpd 

903 def __init__(self, paths, sizes=None, **kwargs): 

904 """ 

905 *paths* is a sequence of :class:`matplotlib.path.Path` 

906 instances. 

907 

908 %(Collection)s 

909 """ 

910 

911 Collection.__init__(self, **kwargs) 

912 self.set_paths(paths) 

913 self.set_sizes(sizes) 

914 self.stale = True 

915 

916 def set_paths(self, paths): 

917 self._paths = paths 

918 self.stale = True 

919 

920 def get_paths(self): 

921 return self._paths 

922 

923 def legend_elements(self, prop="colors", num="auto", 

924 fmt=None, func=lambda x: x, **kwargs): 

925 """ 

926 Creates legend handles and labels for a PathCollection. This is useful 

927 for obtaining a legend for a :meth:`~.Axes.scatter` plot. E.g.:: 

928 

929 scatter = plt.scatter([1, 2, 3], [4, 5, 6], c=[7, 2, 3]) 

930 plt.legend(*scatter.legend_elements()) 

931 

932 Also see the :ref:`automatedlegendcreation` example. 

933 

934 Parameters 

935 ---------- 

936 prop : string, optional, default *"colors"* 

937 Can be *"colors"* or *"sizes"*. In case of *"colors"*, the legend 

938 handles will show the different colors of the collection. In case 

939 of "sizes", the legend will show the different sizes. 

940 num : int, None, "auto" (default), array-like, or `~.ticker.Locator`, 

941 optional 

942 Target number of elements to create. 

943 If None, use all unique elements of the mappable array. If an 

944 integer, target to use *num* elements in the normed range. 

945 If *"auto"*, try to determine which option better suits the nature 

946 of the data. 

947 The number of created elements may slightly deviate from *num* due 

948 to a `~.ticker.Locator` being used to find useful locations. 

949 If a list or array, use exactly those elements for the legend. 

950 Finally, a `~.ticker.Locator` can be provided. 

951 fmt : str, `~matplotlib.ticker.Formatter`, or None (default) 

952 The format or formatter to use for the labels. If a string must be 

953 a valid input for a `~.StrMethodFormatter`. If None (the default), 

954 use a `~.ScalarFormatter`. 

955 func : function, default *lambda x: x* 

956 Function to calculate the labels. Often the size (or color) 

957 argument to :meth:`~.Axes.scatter` will have been pre-processed 

958 by the user using a function *s = f(x)* to make the markers 

959 visible; e.g. *size = np.log10(x)*. Providing the inverse of this 

960 function here allows that pre-processing to be inverted, so that 

961 the legend labels have the correct values; 

962 e.g. *func = np.exp(x, 10)*. 

963 kwargs : further parameters 

964 Allowed keyword arguments are *color* and *size*. E.g. it may be 

965 useful to set the color of the markers if *prop="sizes"* is used; 

966 similarly to set the size of the markers if *prop="colors"* is 

967 used. Any further parameters are passed onto the `.Line2D` 

968 instance. This may be useful to e.g. specify a different 

969 *markeredgecolor* or *alpha* for the legend handles. 

970 

971 Returns 

972 ------- 

973 tuple (handles, labels) 

974 with *handles* being a list of `.Line2D` objects 

975 and *labels* a matching list of strings. 

976 """ 

977 handles = [] 

978 labels = [] 

979 hasarray = self.get_array() is not None 

980 if fmt is None: 

981 fmt = mpl.ticker.ScalarFormatter(useOffset=False, useMathText=True) 

982 elif isinstance(fmt, str): 

983 fmt = mpl.ticker.StrMethodFormatter(fmt) 

984 fmt.create_dummy_axis() 

985 

986 if prop == "colors": 

987 if not hasarray: 

988 warnings.warn("Collection without array used. Make sure to " 

989 "specify the values to be colormapped via the " 

990 "`c` argument.") 

991 return handles, labels 

992 u = np.unique(self.get_array()) 

993 size = kwargs.pop("size", mpl.rcParams["lines.markersize"]) 

994 elif prop == "sizes": 

995 u = np.unique(self.get_sizes()) 

996 color = kwargs.pop("color", "k") 

997 else: 

998 raise ValueError("Valid values for `prop` are 'colors' or " 

999 f"'sizes'. You supplied '{prop}' instead.") 

1000 

1001 fmt.set_bounds(func(u).min(), func(u).max()) 

1002 if num == "auto": 

1003 num = 9 

1004 if len(u) <= num: 

1005 num = None 

1006 if num is None: 

1007 values = u 

1008 label_values = func(values) 

1009 else: 

1010 if prop == "colors": 

1011 arr = self.get_array() 

1012 elif prop == "sizes": 

1013 arr = self.get_sizes() 

1014 if isinstance(num, mpl.ticker.Locator): 

1015 loc = num 

1016 elif np.iterable(num): 

1017 loc = mpl.ticker.FixedLocator(num) 

1018 else: 

1019 num = int(num) 

1020 loc = mpl.ticker.MaxNLocator(nbins=num, min_n_ticks=num-1, 

1021 steps=[1, 2, 2.5, 3, 5, 6, 8, 10]) 

1022 label_values = loc.tick_values(func(arr).min(), func(arr).max()) 

1023 cond = ((label_values >= func(arr).min()) & 

1024 (label_values <= func(arr).max())) 

1025 label_values = label_values[cond] 

1026 xarr = np.linspace(arr.min(), arr.max(), 256) 

1027 values = np.interp(label_values, func(xarr), xarr) 

1028 

1029 kw = dict(markeredgewidth=self.get_linewidths()[0], 

1030 alpha=self.get_alpha()) 

1031 kw.update(kwargs) 

1032 

1033 for val, lab in zip(values, label_values): 

1034 if prop == "colors": 

1035 color = self.cmap(self.norm(val)) 

1036 elif prop == "sizes": 

1037 size = np.sqrt(val) 

1038 if np.isclose(size, 0.0): 

1039 continue 

1040 h = mlines.Line2D([0], [0], ls="", color=color, ms=size, 

1041 marker=self.get_paths()[0], **kw) 

1042 handles.append(h) 

1043 if hasattr(fmt, "set_locs"): 

1044 fmt.set_locs(label_values) 

1045 l = fmt(lab) 

1046 labels.append(l) 

1047 

1048 return handles, labels 

1049 

1050 

1051class PolyCollection(_CollectionWithSizes): 

1052 @docstring.dedent_interpd 

1053 def __init__(self, verts, sizes=None, closed=True, **kwargs): 

1054 """ 

1055 *verts* is a sequence of ( *verts0*, *verts1*, ...) where 

1056 *verts_i* is a sequence of *xy* tuples of vertices, or an 

1057 equivalent :mod:`numpy` array of shape (*nv*, 2). 

1058 

1059 *sizes* is *None* (default) or a sequence of floats that 

1060 scale the corresponding *verts_i*. The scaling is applied 

1061 before the Artist master transform; if the latter is an identity 

1062 transform, then the overall scaling is such that if 

1063 *verts_i* specify a unit square, then *sizes_i* is the area 

1064 of that square in points^2. 

1065 If len(*sizes*) < *nv*, the additional values will be 

1066 taken cyclically from the array. 

1067 

1068 *closed*, when *True*, will explicitly close the polygon. 

1069 

1070 %(Collection)s 

1071 """ 

1072 Collection.__init__(self, **kwargs) 

1073 self.set_sizes(sizes) 

1074 self.set_verts(verts, closed) 

1075 self.stale = True 

1076 

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

1078 '''This allows one to delay initialization of the vertices.''' 

1079 if isinstance(verts, np.ma.MaskedArray): 

1080 verts = verts.astype(float).filled(np.nan) 

1081 # This is much faster than having Path do it one at a time. 

1082 if closed: 

1083 self._paths = [] 

1084 for xy in verts: 

1085 if len(xy): 

1086 if isinstance(xy, np.ma.MaskedArray): 

1087 xy = np.ma.concatenate([xy, xy[0:1]]) 

1088 else: 

1089 xy = np.asarray(xy) 

1090 xy = np.concatenate([xy, xy[0:1]]) 

1091 codes = np.empty(xy.shape[0], dtype=mpath.Path.code_type) 

1092 codes[:] = mpath.Path.LINETO 

1093 codes[0] = mpath.Path.MOVETO 

1094 codes[-1] = mpath.Path.CLOSEPOLY 

1095 self._paths.append(mpath.Path(xy, codes)) 

1096 else: 

1097 self._paths.append(mpath.Path(xy)) 

1098 else: 

1099 self._paths = [mpath.Path(xy) for xy in verts] 

1100 self.stale = True 

1101 

1102 set_paths = set_verts 

1103 

1104 def set_verts_and_codes(self, verts, codes): 

1105 """This allows one to initialize vertices with path codes.""" 

1106 if len(verts) != len(codes): 

1107 raise ValueError("'codes' must be a 1D list or array " 

1108 "with the same length of 'verts'") 

1109 self._paths = [] 

1110 for xy, cds in zip(verts, codes): 

1111 if len(xy): 

1112 self._paths.append(mpath.Path(xy, cds)) 

1113 else: 

1114 self._paths.append(mpath.Path(xy)) 

1115 self.stale = True 

1116 

1117 

1118class BrokenBarHCollection(PolyCollection): 

1119 """ 

1120 A collection of horizontal bars spanning *yrange* with a sequence of 

1121 *xranges*. 

1122 """ 

1123 @docstring.dedent_interpd 

1124 def __init__(self, xranges, yrange, **kwargs): 

1125 """ 

1126 *xranges* 

1127 sequence of (*xmin*, *xwidth*) 

1128 

1129 *yrange* 

1130 *ymin*, *ywidth* 

1131 

1132 %(Collection)s 

1133 """ 

1134 ymin, ywidth = yrange 

1135 ymax = ymin + ywidth 

1136 verts = [[(xmin, ymin), 

1137 (xmin, ymax), 

1138 (xmin + xwidth, ymax), 

1139 (xmin + xwidth, ymin), 

1140 (xmin, ymin)] for xmin, xwidth in xranges] 

1141 PolyCollection.__init__(self, verts, **kwargs) 

1142 

1143 @staticmethod 

1144 def span_where(x, ymin, ymax, where, **kwargs): 

1145 """ 

1146 Create a BrokenBarHCollection to plot horizontal bars from 

1147 over the regions in *x* where *where* is True. The bars range 

1148 on the y-axis from *ymin* to *ymax* 

1149 

1150 A :class:`BrokenBarHCollection` is returned. *kwargs* are 

1151 passed on to the collection. 

1152 """ 

1153 xranges = [] 

1154 for ind0, ind1 in cbook.contiguous_regions(where): 

1155 xslice = x[ind0:ind1] 

1156 if not len(xslice): 

1157 continue 

1158 xranges.append((xslice[0], xslice[-1] - xslice[0])) 

1159 

1160 collection = BrokenBarHCollection( 

1161 xranges, [ymin, ymax - ymin], **kwargs) 

1162 return collection 

1163 

1164 

1165class RegularPolyCollection(_CollectionWithSizes): 

1166 """Draw a collection of regular polygons with *numsides*.""" 

1167 

1168 _path_generator = mpath.Path.unit_regular_polygon 

1169 _factor = np.pi ** (-1/2) 

1170 

1171 @docstring.dedent_interpd 

1172 def __init__(self, 

1173 numsides, 

1174 rotation=0, 

1175 sizes=(1,), 

1176 **kwargs): 

1177 """ 

1178 *numsides* 

1179 the number of sides of the polygon 

1180 

1181 *rotation* 

1182 the rotation of the polygon in radians 

1183 

1184 *sizes* 

1185 gives the area of the circle circumscribing the 

1186 regular polygon in points^2 

1187 

1188 %(Collection)s 

1189 

1190 Example: see :doc:`/gallery/event_handling/lasso_demo` for a 

1191 complete example:: 

1192 

1193 offsets = np.random.rand(20, 2) 

1194 facecolors = [cm.jet(x) for x in np.random.rand(20)] 

1195 

1196 collection = RegularPolyCollection( 

1197 numsides=5, # a pentagon 

1198 rotation=0, sizes=(50,), 

1199 facecolors=facecolors, 

1200 edgecolors=("black",), 

1201 linewidths=(1,), 

1202 offsets=offsets, 

1203 transOffset=ax.transData, 

1204 ) 

1205 """ 

1206 Collection.__init__(self, **kwargs) 

1207 self.set_sizes(sizes) 

1208 self._numsides = numsides 

1209 self._paths = [self._path_generator(numsides)] 

1210 self._rotation = rotation 

1211 self.set_transform(transforms.IdentityTransform()) 

1212 

1213 def get_numsides(self): 

1214 return self._numsides 

1215 

1216 def get_rotation(self): 

1217 return self._rotation 

1218 

1219 @artist.allow_rasterization 

1220 def draw(self, renderer): 

1221 self.set_sizes(self._sizes, self.figure.dpi) 

1222 self._transforms = [ 

1223 transforms.Affine2D(x).rotate(-self._rotation).get_matrix() 

1224 for x in self._transforms 

1225 ] 

1226 Collection.draw(self, renderer) 

1227 

1228 

1229class StarPolygonCollection(RegularPolyCollection): 

1230 """Draw a collection of regular stars with *numsides* points.""" 

1231 _path_generator = mpath.Path.unit_regular_star 

1232 

1233 

1234class AsteriskPolygonCollection(RegularPolyCollection): 

1235 """Draw a collection of regular asterisks with *numsides* points.""" 

1236 _path_generator = mpath.Path.unit_regular_asterisk 

1237 

1238 

1239class LineCollection(Collection): 

1240 """ 

1241 All parameters must be sequences or scalars; if scalars, they will 

1242 be converted to sequences. The property of the ith line 

1243 segment is:: 

1244 

1245 prop[i % len(props)] 

1246 

1247 i.e., the properties cycle if the ``len`` of props is less than the 

1248 number of segments. 

1249 """ 

1250 

1251 _edge_default = True 

1252 

1253 def __init__(self, segments, # Can be None. 

1254 linewidths=None, 

1255 colors=None, 

1256 antialiaseds=None, 

1257 linestyles='solid', 

1258 offsets=None, 

1259 transOffset=None, 

1260 norm=None, 

1261 cmap=None, 

1262 pickradius=5, 

1263 zorder=2, 

1264 facecolors='none', 

1265 **kwargs 

1266 ): 

1267 """ 

1268 Parameters 

1269 ---------- 

1270 segments 

1271 A sequence of (*line0*, *line1*, *line2*), where:: 

1272 

1273 linen = (x0, y0), (x1, y1), ... (xm, ym) 

1274 

1275 or the equivalent numpy array with two columns. Each line 

1276 can be a different length. 

1277 

1278 colors : sequence, optional 

1279 A sequence of RGBA tuples (e.g., arbitrary color 

1280 strings, etc, not allowed). 

1281 

1282 antialiaseds : sequence, optional 

1283 A sequence of ones or zeros. 

1284 

1285 linestyles : str or tuple, optional 

1286 Either one of {'solid', 'dashed', 'dashdot', 'dotted'}, or 

1287 a dash tuple. The dash tuple is:: 

1288 

1289 (offset, onoffseq) 

1290 

1291 where ``onoffseq`` is an even length tuple of on and off ink 

1292 in points. 

1293 

1294 norm : Normalize, optional 

1295 `~.colors.Normalize` instance. 

1296 

1297 cmap : str or Colormap, optional 

1298 Colormap name or `~.colors.Colormap` instance. 

1299 

1300 pickradius : float, optional 

1301 The tolerance in points for mouse clicks picking a line. 

1302 Default is 5 pt. 

1303 

1304 zorder : int, optional 

1305 zorder of the LineCollection. Default is 2. 

1306 

1307 facecolors : optional 

1308 The facecolors of the LineCollection. Default is 'none'. 

1309 Setting to a value other than 'none' will lead to a filled 

1310 polygon being drawn between points on each line. 

1311 

1312 Notes 

1313 ----- 

1314 If *linewidths*, *colors*, or *antialiaseds* is None, they 

1315 default to their rcParams setting, in sequence form. 

1316 

1317 If *offsets* and *transOffset* are not None, then 

1318 *offsets* are transformed by *transOffset* and applied after 

1319 the segments have been transformed to display coordinates. 

1320 

1321 If *offsets* is not None but *transOffset* is None, then the 

1322 *offsets* are added to the segments before any transformation. 

1323 In this case, a single offset can be specified as:: 

1324 

1325 offsets=(xo, yo) 

1326 

1327 and this value will be added cumulatively to each successive 

1328 segment, so as to produce a set of successively offset curves. 

1329 

1330 The use of :class:`~matplotlib.cm.ScalarMappable` is optional. 

1331 If the :class:`~matplotlib.cm.ScalarMappable` array 

1332 :attr:`~matplotlib.cm.ScalarMappable._A` is not None (i.e., a call to 

1333 :meth:`~matplotlib.cm.ScalarMappable.set_array` has been made), at 

1334 draw time a call to scalar mappable will be made to set the colors. 

1335 """ 

1336 if colors is None: 

1337 colors = mpl.rcParams['lines.color'] 

1338 if linewidths is None: 

1339 linewidths = (mpl.rcParams['lines.linewidth'],) 

1340 if antialiaseds is None: 

1341 antialiaseds = (mpl.rcParams['lines.antialiased'],) 

1342 

1343 colors = mcolors.to_rgba_array(colors) 

1344 Collection.__init__( 

1345 self, 

1346 edgecolors=colors, 

1347 facecolors=facecolors, 

1348 linewidths=linewidths, 

1349 linestyles=linestyles, 

1350 antialiaseds=antialiaseds, 

1351 offsets=offsets, 

1352 transOffset=transOffset, 

1353 norm=norm, 

1354 cmap=cmap, 

1355 pickradius=pickradius, 

1356 zorder=zorder, 

1357 **kwargs) 

1358 

1359 self.set_segments(segments) 

1360 

1361 def set_segments(self, segments): 

1362 if segments is None: 

1363 return 

1364 _segments = [] 

1365 

1366 for seg in segments: 

1367 if not isinstance(seg, np.ma.MaskedArray): 

1368 seg = np.asarray(seg, float) 

1369 _segments.append(seg) 

1370 

1371 if self._uniform_offsets is not None: 

1372 _segments = self._add_offsets(_segments) 

1373 

1374 self._paths = [mpath.Path(_seg) for _seg in _segments] 

1375 self.stale = True 

1376 

1377 set_verts = set_segments # for compatibility with PolyCollection 

1378 set_paths = set_segments 

1379 

1380 def get_segments(self): 

1381 """ 

1382 Returns 

1383 ------- 

1384 segments : list 

1385 List of segments in the LineCollection. Each list item contains an 

1386 array of vertices. 

1387 """ 

1388 segments = [] 

1389 

1390 for path in self._paths: 

1391 vertices = [vertex for vertex, _ in path.iter_segments()] 

1392 vertices = np.asarray(vertices) 

1393 segments.append(vertices) 

1394 

1395 return segments 

1396 

1397 def _add_offsets(self, segs): 

1398 offsets = self._uniform_offsets 

1399 Nsegs = len(segs) 

1400 Noffs = offsets.shape[0] 

1401 if Noffs == 1: 

1402 for i in range(Nsegs): 

1403 segs[i] = segs[i] + i * offsets 

1404 else: 

1405 for i in range(Nsegs): 

1406 io = i % Noffs 

1407 segs[i] = segs[i] + offsets[io:io + 1] 

1408 return segs 

1409 

1410 def set_color(self, c): 

1411 """ 

1412 Set the color(s) of the LineCollection. 

1413 

1414 Parameters 

1415 ---------- 

1416 c : color or list of colors 

1417 Matplotlib color argument (all patches have same color), or a 

1418 sequence or rgba tuples; if it is a sequence the patches will 

1419 cycle through the sequence. 

1420 """ 

1421 self.set_edgecolor(c) 

1422 self.stale = True 

1423 

1424 def get_color(self): 

1425 return self._edgecolors 

1426 

1427 get_colors = get_color # for compatibility with old versions 

1428 

1429 

1430class EventCollection(LineCollection): 

1431 """ 

1432 A collection of discrete events. 

1433 

1434 The events are given by a 1-dimensional array, usually the position of 

1435 something along an axis, such as time or length. They do not have an 

1436 amplitude and are displayed as vertical or horizontal parallel bars. 

1437 """ 

1438 

1439 _edge_default = True 

1440 

1441 def __init__(self, 

1442 positions, # Cannot be None. 

1443 orientation=None, 

1444 lineoffset=0, 

1445 linelength=1, 

1446 linewidth=None, 

1447 color=None, 

1448 linestyle='solid', 

1449 antialiased=None, 

1450 **kwargs 

1451 ): 

1452 """ 

1453 Parameters 

1454 ---------- 

1455 positions : 1D array-like object 

1456 Each value is an event. 

1457 

1458 orientation : {None, 'horizontal', 'vertical'}, optional 

1459 The orientation of the **collection** (the event bars are along 

1460 the orthogonal direction). Defaults to 'horizontal' if not 

1461 specified or None. 

1462 

1463 lineoffset : scalar, optional, default: 0 

1464 The offset of the center of the markers from the origin, in the 

1465 direction orthogonal to *orientation*. 

1466 

1467 linelength : scalar, optional, default: 1 

1468 The total height of the marker (i.e. the marker stretches from 

1469 ``lineoffset - linelength/2`` to ``lineoffset + linelength/2``). 

1470 

1471 linewidth : scalar or None, optional, default: None 

1472 If it is None, defaults to its rcParams setting, in sequence form. 

1473 

1474 color : color, sequence of colors or None, optional, default: None 

1475 If it is None, defaults to its rcParams setting, in sequence form. 

1476 

1477 linestyle : str or tuple, optional, default: 'solid' 

1478 Valid strings are ['solid', 'dashed', 'dashdot', 'dotted', 

1479 '-', '--', '-.', ':']. Dash tuples should be of the form:: 

1480 

1481 (offset, onoffseq), 

1482 

1483 where *onoffseq* is an even length tuple of on and off ink 

1484 in points. 

1485 

1486 antialiased : {None, 1, 2}, optional 

1487 If it is None, defaults to its rcParams setting, in sequence form. 

1488 

1489 **kwargs : optional 

1490 Other keyword arguments are line collection properties. See 

1491 :class:`~matplotlib.collections.LineCollection` for a list of 

1492 the valid properties. 

1493 

1494 Examples 

1495 -------- 

1496 .. plot:: gallery/lines_bars_and_markers/eventcollection_demo.py 

1497 """ 

1498 if positions is None: 

1499 raise ValueError('positions must be an array-like object') 

1500 # Force a copy of positions 

1501 positions = np.array(positions, copy=True) 

1502 segment = (lineoffset + linelength / 2., 

1503 lineoffset - linelength / 2.) 

1504 if positions.size == 0: 

1505 segments = [] 

1506 elif positions.ndim > 1: 

1507 raise ValueError('positions cannot be an array with more than ' 

1508 'one dimension.') 

1509 elif (orientation is None or orientation.lower() == 'none' or 

1510 orientation.lower() == 'horizontal'): 

1511 positions.sort() 

1512 segments = [[(coord1, coord2) for coord2 in segment] for 

1513 coord1 in positions] 

1514 self._is_horizontal = True 

1515 elif orientation.lower() == 'vertical': 

1516 positions.sort() 

1517 segments = [[(coord2, coord1) for coord2 in segment] for 

1518 coord1 in positions] 

1519 self._is_horizontal = False 

1520 else: 

1521 cbook._check_in_list(['horizontal', 'vertical'], 

1522 orientation=orientation) 

1523 

1524 LineCollection.__init__(self, 

1525 segments, 

1526 linewidths=linewidth, 

1527 colors=color, 

1528 antialiaseds=antialiased, 

1529 linestyles=linestyle, 

1530 **kwargs) 

1531 

1532 self._linelength = linelength 

1533 self._lineoffset = lineoffset 

1534 

1535 def get_positions(self): 

1536 ''' 

1537 return an array containing the floating-point values of the positions 

1538 ''' 

1539 pos = 0 if self.is_horizontal() else 1 

1540 return [segment[0, pos] for segment in self.get_segments()] 

1541 

1542 def set_positions(self, positions): 

1543 ''' 

1544 set the positions of the events to the specified value 

1545 ''' 

1546 if positions is None or (hasattr(positions, 'len') and 

1547 len(positions) == 0): 

1548 self.set_segments([]) 

1549 return 

1550 

1551 lineoffset = self.get_lineoffset() 

1552 linelength = self.get_linelength() 

1553 segment = (lineoffset + linelength / 2., 

1554 lineoffset - linelength / 2.) 

1555 positions = np.asanyarray(positions) 

1556 positions.sort() 

1557 if self.is_horizontal(): 

1558 segments = [[(coord1, coord2) for coord2 in segment] for 

1559 coord1 in positions] 

1560 else: 

1561 segments = [[(coord2, coord1) for coord2 in segment] for 

1562 coord1 in positions] 

1563 self.set_segments(segments) 

1564 

1565 def add_positions(self, position): 

1566 ''' 

1567 add one or more events at the specified positions 

1568 ''' 

1569 if position is None or (hasattr(position, 'len') and 

1570 len(position) == 0): 

1571 return 

1572 positions = self.get_positions() 

1573 positions = np.hstack([positions, np.asanyarray(position)]) 

1574 self.set_positions(positions) 

1575 extend_positions = append_positions = add_positions 

1576 

1577 def is_horizontal(self): 

1578 ''' 

1579 True if the eventcollection is horizontal, False if vertical 

1580 ''' 

1581 return self._is_horizontal 

1582 

1583 def get_orientation(self): 

1584 """ 

1585 Return the orientation of the event line ('horizontal' or 'vertical'). 

1586 """ 

1587 return 'horizontal' if self.is_horizontal() else 'vertical' 

1588 

1589 def switch_orientation(self): 

1590 ''' 

1591 switch the orientation of the event line, either from vertical to 

1592 horizontal or vice versus 

1593 ''' 

1594 segments = self.get_segments() 

1595 for i, segment in enumerate(segments): 

1596 segments[i] = np.fliplr(segment) 

1597 self.set_segments(segments) 

1598 self._is_horizontal = not self.is_horizontal() 

1599 self.stale = True 

1600 

1601 def set_orientation(self, orientation=None): 

1602 """ 

1603 Set the orientation of the event line. 

1604 

1605 Parameters 

1606 ---------- 

1607 orientation: {'horizontal', 'vertical'} or None 

1608 Defaults to 'horizontal' if not specified or None. 

1609 """ 

1610 if (orientation is None or orientation.lower() == 'none' or 

1611 orientation.lower() == 'horizontal'): 

1612 is_horizontal = True 

1613 elif orientation.lower() == 'vertical': 

1614 is_horizontal = False 

1615 else: 

1616 cbook._check_in_list(['horizontal', 'vertical'], 

1617 orientation=orientation) 

1618 if is_horizontal == self.is_horizontal(): 

1619 return 

1620 self.switch_orientation() 

1621 

1622 def get_linelength(self): 

1623 ''' 

1624 get the length of the lines used to mark each event 

1625 ''' 

1626 return self._linelength 

1627 

1628 def set_linelength(self, linelength): 

1629 ''' 

1630 set the length of the lines used to mark each event 

1631 ''' 

1632 if linelength == self.get_linelength(): 

1633 return 

1634 lineoffset = self.get_lineoffset() 

1635 segments = self.get_segments() 

1636 pos = 1 if self.is_horizontal() else 0 

1637 for segment in segments: 

1638 segment[0, pos] = lineoffset + linelength / 2. 

1639 segment[1, pos] = lineoffset - linelength / 2. 

1640 self.set_segments(segments) 

1641 self._linelength = linelength 

1642 

1643 def get_lineoffset(self): 

1644 ''' 

1645 get the offset of the lines used to mark each event 

1646 ''' 

1647 return self._lineoffset 

1648 

1649 def set_lineoffset(self, lineoffset): 

1650 ''' 

1651 set the offset of the lines used to mark each event 

1652 ''' 

1653 if lineoffset == self.get_lineoffset(): 

1654 return 

1655 linelength = self.get_linelength() 

1656 segments = self.get_segments() 

1657 pos = 1 if self.is_horizontal() else 0 

1658 for segment in segments: 

1659 segment[0, pos] = lineoffset + linelength / 2. 

1660 segment[1, pos] = lineoffset - linelength / 2. 

1661 self.set_segments(segments) 

1662 self._lineoffset = lineoffset 

1663 

1664 def get_linewidth(self): 

1665 """Get the width of the lines used to mark each event.""" 

1666 return super(EventCollection, self).get_linewidth()[0] 

1667 

1668 def get_linewidths(self): 

1669 return super(EventCollection, self).get_linewidth() 

1670 

1671 def get_color(self): 

1672 ''' 

1673 get the color of the lines used to mark each event 

1674 ''' 

1675 return self.get_colors()[0] 

1676 

1677 

1678class CircleCollection(_CollectionWithSizes): 

1679 """A collection of circles, drawn using splines.""" 

1680 

1681 _factor = np.pi ** (-1/2) 

1682 

1683 @docstring.dedent_interpd 

1684 def __init__(self, sizes, **kwargs): 

1685 """ 

1686 *sizes* 

1687 Gives the area of the circle in points^2 

1688 

1689 %(Collection)s 

1690 """ 

1691 Collection.__init__(self, **kwargs) 

1692 self.set_sizes(sizes) 

1693 self.set_transform(transforms.IdentityTransform()) 

1694 self._paths = [mpath.Path.unit_circle()] 

1695 

1696 

1697class EllipseCollection(Collection): 

1698 """A collection of ellipses, drawn using splines.""" 

1699 

1700 @docstring.dedent_interpd 

1701 def __init__(self, widths, heights, angles, units='points', **kwargs): 

1702 """ 

1703 Parameters 

1704 ---------- 

1705 widths : array-like 

1706 The lengths of the first axes (e.g., major axis lengths). 

1707 

1708 heights : array-like 

1709 The lengths of second axes. 

1710 

1711 angles : array-like 

1712 The angles of the first axes, degrees CCW from the x-axis. 

1713 

1714 units : {'points', 'inches', 'dots', 'width', 'height', 'x', 'y', 'xy'} 

1715 

1716 The units in which majors and minors are given; 'width' and 

1717 'height' refer to the dimensions of the axes, while 'x' 

1718 and 'y' refer to the *offsets* data units. 'xy' differs 

1719 from all others in that the angle as plotted varies with 

1720 the aspect ratio, and equals the specified angle only when 

1721 the aspect ratio is unity. Hence it behaves the same as 

1722 the :class:`~matplotlib.patches.Ellipse` with 

1723 ``axes.transData`` as its transform. 

1724 

1725 Other Parameters 

1726 ---------------- 

1727 **kwargs 

1728 Additional kwargs inherited from the base :class:`Collection`. 

1729 

1730 %(Collection)s 

1731 """ 

1732 Collection.__init__(self, **kwargs) 

1733 self._widths = 0.5 * np.asarray(widths).ravel() 

1734 self._heights = 0.5 * np.asarray(heights).ravel() 

1735 self._angles = np.deg2rad(angles).ravel() 

1736 self._units = units 

1737 self.set_transform(transforms.IdentityTransform()) 

1738 self._transforms = np.empty((0, 3, 3)) 

1739 self._paths = [mpath.Path.unit_circle()] 

1740 

1741 def _set_transforms(self): 

1742 """Calculate transforms immediately before drawing.""" 

1743 

1744 ax = self.axes 

1745 fig = self.figure 

1746 

1747 if self._units == 'xy': 

1748 sc = 1 

1749 elif self._units == 'x': 

1750 sc = ax.bbox.width / ax.viewLim.width 

1751 elif self._units == 'y': 

1752 sc = ax.bbox.height / ax.viewLim.height 

1753 elif self._units == 'inches': 

1754 sc = fig.dpi 

1755 elif self._units == 'points': 

1756 sc = fig.dpi / 72.0 

1757 elif self._units == 'width': 

1758 sc = ax.bbox.width 

1759 elif self._units == 'height': 

1760 sc = ax.bbox.height 

1761 elif self._units == 'dots': 

1762 sc = 1.0 

1763 else: 

1764 raise ValueError('unrecognized units: %s' % self._units) 

1765 

1766 self._transforms = np.zeros((len(self._widths), 3, 3)) 

1767 widths = self._widths * sc 

1768 heights = self._heights * sc 

1769 sin_angle = np.sin(self._angles) 

1770 cos_angle = np.cos(self._angles) 

1771 self._transforms[:, 0, 0] = widths * cos_angle 

1772 self._transforms[:, 0, 1] = heights * -sin_angle 

1773 self._transforms[:, 1, 0] = widths * sin_angle 

1774 self._transforms[:, 1, 1] = heights * cos_angle 

1775 self._transforms[:, 2, 2] = 1.0 

1776 

1777 _affine = transforms.Affine2D 

1778 if self._units == 'xy': 

1779 m = ax.transData.get_affine().get_matrix().copy() 

1780 m[:2, 2:] = 0 

1781 self.set_transform(_affine(m)) 

1782 

1783 @artist.allow_rasterization 

1784 def draw(self, renderer): 

1785 self._set_transforms() 

1786 Collection.draw(self, renderer) 

1787 

1788 

1789class PatchCollection(Collection): 

1790 """ 

1791 A generic collection of patches. 

1792 

1793 This makes it easier to assign a color map to a heterogeneous 

1794 collection of patches. 

1795 

1796 This also may improve plotting speed, since PatchCollection will 

1797 draw faster than a large number of patches. 

1798 """ 

1799 

1800 def __init__(self, patches, match_original=False, **kwargs): 

1801 """ 

1802 *patches* 

1803 a sequence of Patch objects. This list may include 

1804 a heterogeneous assortment of different patch types. 

1805 

1806 *match_original* 

1807 If True, use the colors and linewidths of the original 

1808 patches. If False, new colors may be assigned by 

1809 providing the standard collection arguments, facecolor, 

1810 edgecolor, linewidths, norm or cmap. 

1811 

1812 If any of *edgecolors*, *facecolors*, *linewidths*, 

1813 *antialiaseds* are None, they default to their 

1814 :data:`matplotlib.rcParams` patch setting, in sequence form. 

1815 

1816 The use of :class:`~matplotlib.cm.ScalarMappable` is optional. 

1817 If the :class:`~matplotlib.cm.ScalarMappable` matrix _A is not 

1818 None (i.e., a call to set_array has been made), at draw time a 

1819 call to scalar mappable will be made to set the face colors. 

1820 """ 

1821 

1822 if match_original: 

1823 def determine_facecolor(patch): 

1824 if patch.get_fill(): 

1825 return patch.get_facecolor() 

1826 return [0, 0, 0, 0] 

1827 

1828 kwargs['facecolors'] = [determine_facecolor(p) for p in patches] 

1829 kwargs['edgecolors'] = [p.get_edgecolor() for p in patches] 

1830 kwargs['linewidths'] = [p.get_linewidth() for p in patches] 

1831 kwargs['linestyles'] = [p.get_linestyle() for p in patches] 

1832 kwargs['antialiaseds'] = [p.get_antialiased() for p in patches] 

1833 

1834 Collection.__init__(self, **kwargs) 

1835 

1836 self.set_paths(patches) 

1837 

1838 def set_paths(self, patches): 

1839 paths = [p.get_transform().transform_path(p.get_path()) 

1840 for p in patches] 

1841 self._paths = paths 

1842 

1843 

1844class TriMesh(Collection): 

1845 """ 

1846 Class for the efficient drawing of a triangular mesh using Gouraud shading. 

1847 

1848 A triangular mesh is a `~matplotlib.tri.Triangulation` object. 

1849 """ 

1850 def __init__(self, triangulation, **kwargs): 

1851 Collection.__init__(self, **kwargs) 

1852 self._triangulation = triangulation 

1853 self._shading = 'gouraud' 

1854 self._is_filled = True 

1855 

1856 self._bbox = transforms.Bbox.unit() 

1857 

1858 # Unfortunately this requires a copy, unless Triangulation 

1859 # was rewritten. 

1860 xy = np.hstack((triangulation.x.reshape(-1, 1), 

1861 triangulation.y.reshape(-1, 1))) 

1862 self._bbox.update_from_data_xy(xy) 

1863 

1864 def get_paths(self): 

1865 if self._paths is None: 

1866 self.set_paths() 

1867 return self._paths 

1868 

1869 def set_paths(self): 

1870 self._paths = self.convert_mesh_to_paths(self._triangulation) 

1871 

1872 @staticmethod 

1873 def convert_mesh_to_paths(tri): 

1874 """ 

1875 Converts a given mesh into a sequence of `~.Path` objects. 

1876 

1877 This function is primarily of use to implementers of backends that do 

1878 not directly support meshes. 

1879 """ 

1880 triangles = tri.get_masked_triangles() 

1881 verts = np.stack((tri.x[triangles], tri.y[triangles]), axis=-1) 

1882 return [mpath.Path(x) for x in verts] 

1883 

1884 @artist.allow_rasterization 

1885 def draw(self, renderer): 

1886 if not self.get_visible(): 

1887 return 

1888 renderer.open_group(self.__class__.__name__, gid=self.get_gid()) 

1889 transform = self.get_transform() 

1890 

1891 # Get a list of triangles and the color at each vertex. 

1892 tri = self._triangulation 

1893 triangles = tri.get_masked_triangles() 

1894 

1895 verts = np.stack((tri.x[triangles], tri.y[triangles]), axis=-1) 

1896 

1897 self.update_scalarmappable() 

1898 colors = self._facecolors[triangles] 

1899 

1900 gc = renderer.new_gc() 

1901 self._set_gc_clip(gc) 

1902 gc.set_linewidth(self.get_linewidth()[0]) 

1903 renderer.draw_gouraud_triangles(gc, verts, colors, transform.frozen()) 

1904 gc.restore() 

1905 renderer.close_group(self.__class__.__name__) 

1906 

1907 

1908class QuadMesh(Collection): 

1909 """ 

1910 Class for the efficient drawing of a quadrilateral mesh. 

1911 

1912 A quadrilateral mesh consists of a grid of vertices. The 

1913 dimensions of this array are (*meshWidth* + 1, *meshHeight* + 

1914 1). Each vertex in the mesh has a different set of "mesh 

1915 coordinates" representing its position in the topology of the 

1916 mesh. For any values (*m*, *n*) such that 0 <= *m* <= *meshWidth* 

1917 and 0 <= *n* <= *meshHeight*, the vertices at mesh coordinates 

1918 (*m*, *n*), (*m*, *n* + 1), (*m* + 1, *n* + 1), and (*m* + 1, *n*) 

1919 form one of the quadrilaterals in the mesh. There are thus 

1920 (*meshWidth* * *meshHeight*) quadrilaterals in the mesh. The mesh 

1921 need not be regular and the polygons need not be convex. 

1922 

1923 A quadrilateral mesh is represented by a (2 x ((*meshWidth* + 1) * 

1924 (*meshHeight* + 1))) numpy array *coordinates*, where each row is 

1925 the *x* and *y* coordinates of one of the vertices. To define the 

1926 function that maps from a data point to its corresponding color, 

1927 use the :meth:`set_cmap` method. Each of these arrays is indexed in 

1928 row-major order by the mesh coordinates of the vertex (or the mesh 

1929 coordinates of the lower left vertex, in the case of the 

1930 colors). 

1931 

1932 For example, the first entry in *coordinates* is the 

1933 coordinates of the vertex at mesh coordinates (0, 0), then the one 

1934 at (0, 1), then at (0, 2) .. (0, meshWidth), (1, 0), (1, 1), and 

1935 so on. 

1936 

1937 *shading* may be 'flat', or 'gouraud' 

1938 """ 

1939 def __init__(self, meshWidth, meshHeight, coordinates, 

1940 antialiased=True, shading='flat', **kwargs): 

1941 Collection.__init__(self, **kwargs) 

1942 self._meshWidth = meshWidth 

1943 self._meshHeight = meshHeight 

1944 # By converting to floats now, we can avoid that on every draw. 

1945 self._coordinates = np.asarray(coordinates, float).reshape( 

1946 (meshHeight + 1, meshWidth + 1, 2)) 

1947 self._antialiased = antialiased 

1948 self._shading = shading 

1949 

1950 self._bbox = transforms.Bbox.unit() 

1951 self._bbox.update_from_data_xy(coordinates.reshape( 

1952 ((meshWidth + 1) * (meshHeight + 1), 2))) 

1953 

1954 def get_paths(self): 

1955 if self._paths is None: 

1956 self.set_paths() 

1957 return self._paths 

1958 

1959 def set_paths(self): 

1960 self._paths = self.convert_mesh_to_paths( 

1961 self._meshWidth, self._meshHeight, self._coordinates) 

1962 self.stale = True 

1963 

1964 def get_datalim(self, transData): 

1965 return (self.get_transform() - transData).transform_bbox(self._bbox) 

1966 

1967 @staticmethod 

1968 def convert_mesh_to_paths(meshWidth, meshHeight, coordinates): 

1969 """ 

1970 Converts a given mesh into a sequence of `~.Path` objects. 

1971 

1972 This function is primarily of use to implementers of backends that do 

1973 not directly support quadmeshes. 

1974 """ 

1975 if isinstance(coordinates, np.ma.MaskedArray): 

1976 c = coordinates.data 

1977 else: 

1978 c = coordinates 

1979 points = np.concatenate(( 

1980 c[:-1, :-1], 

1981 c[:-1, 1:], 

1982 c[1:, 1:], 

1983 c[1:, :-1], 

1984 c[:-1, :-1] 

1985 ), axis=2) 

1986 points = points.reshape((meshWidth * meshHeight, 5, 2)) 

1987 return [mpath.Path(x) for x in points] 

1988 

1989 def convert_mesh_to_triangles(self, meshWidth, meshHeight, coordinates): 

1990 """ 

1991 Converts a given mesh into a sequence of triangles, each point 

1992 with its own color. This is useful for experiments using 

1993 `draw_gouraud_triangle`. 

1994 """ 

1995 if isinstance(coordinates, np.ma.MaskedArray): 

1996 p = coordinates.data 

1997 else: 

1998 p = coordinates 

1999 

2000 p_a = p[:-1, :-1] 

2001 p_b = p[:-1, 1:] 

2002 p_c = p[1:, 1:] 

2003 p_d = p[1:, :-1] 

2004 p_center = (p_a + p_b + p_c + p_d) / 4.0 

2005 

2006 triangles = np.concatenate(( 

2007 p_a, p_b, p_center, 

2008 p_b, p_c, p_center, 

2009 p_c, p_d, p_center, 

2010 p_d, p_a, p_center, 

2011 ), axis=2) 

2012 triangles = triangles.reshape((meshWidth * meshHeight * 4, 3, 2)) 

2013 

2014 c = self.get_facecolor().reshape((meshHeight + 1, meshWidth + 1, 4)) 

2015 c_a = c[:-1, :-1] 

2016 c_b = c[:-1, 1:] 

2017 c_c = c[1:, 1:] 

2018 c_d = c[1:, :-1] 

2019 c_center = (c_a + c_b + c_c + c_d) / 4.0 

2020 

2021 colors = np.concatenate(( 

2022 c_a, c_b, c_center, 

2023 c_b, c_c, c_center, 

2024 c_c, c_d, c_center, 

2025 c_d, c_a, c_center, 

2026 ), axis=2) 

2027 colors = colors.reshape((meshWidth * meshHeight * 4, 3, 4)) 

2028 

2029 return triangles, colors 

2030 

2031 @artist.allow_rasterization 

2032 def draw(self, renderer): 

2033 if not self.get_visible(): 

2034 return 

2035 renderer.open_group(self.__class__.__name__, self.get_gid()) 

2036 transform = self.get_transform() 

2037 transOffset = self.get_offset_transform() 

2038 offsets = self._offsets 

2039 

2040 if self.have_units(): 

2041 if len(self._offsets): 

2042 xs = self.convert_xunits(self._offsets[:, 0]) 

2043 ys = self.convert_yunits(self._offsets[:, 1]) 

2044 offsets = np.column_stack([xs, ys]) 

2045 

2046 self.update_scalarmappable() 

2047 

2048 if not transform.is_affine: 

2049 coordinates = self._coordinates.reshape((-1, 2)) 

2050 coordinates = transform.transform(coordinates) 

2051 coordinates = coordinates.reshape(self._coordinates.shape) 

2052 transform = transforms.IdentityTransform() 

2053 else: 

2054 coordinates = self._coordinates 

2055 

2056 if not transOffset.is_affine: 

2057 offsets = transOffset.transform_non_affine(offsets) 

2058 transOffset = transOffset.get_affine() 

2059 

2060 gc = renderer.new_gc() 

2061 self._set_gc_clip(gc) 

2062 gc.set_linewidth(self.get_linewidth()[0]) 

2063 

2064 if self._shading == 'gouraud': 

2065 triangles, colors = self.convert_mesh_to_triangles( 

2066 self._meshWidth, self._meshHeight, coordinates) 

2067 renderer.draw_gouraud_triangles( 

2068 gc, triangles, colors, transform.frozen()) 

2069 else: 

2070 renderer.draw_quad_mesh( 

2071 gc, transform.frozen(), self._meshWidth, self._meshHeight, 

2072 coordinates, offsets, transOffset, self.get_facecolor(), 

2073 self._antialiased, self.get_edgecolors()) 

2074 gc.restore() 

2075 renderer.close_group(self.__class__.__name__) 

2076 self.stale = False 

2077 

2078 

2079patchstr = artist.kwdoc(Collection) 

2080for k in ('QuadMesh', 'TriMesh', 'PolyCollection', 'BrokenBarHCollection', 

2081 'RegularPolyCollection', 'PathCollection', 

2082 'StarPolygonCollection', 'PatchCollection', 

2083 'CircleCollection', 'Collection',): 

2084 docstring.interpd.update({k: patchstr}) 

2085docstring.interpd.update(LineCollection=artist.kwdoc(LineCollection))