Coverage for /usr/lib/python3/dist-packages/matplotlib/collections.py: 20%

859 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

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 inspect 

13import math 

14from numbers import Number 

15import warnings 

16 

17import numpy as np 

18 

19import matplotlib as mpl 

20from . import (_api, _path, artist, cbook, cm, colors as mcolors, _docstring, 

21 hatch as mhatch, lines as mlines, path as mpath, transforms) 

22from ._enums import JoinStyle, CapStyle 

23 

24 

25# "color" is excluded; it is a compound setter, and its docstring differs 

26# in LineCollection. 

27@_api.define_aliases({ 

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

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

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

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

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

33 "offset_transform": ["transOffset"], 

34}) 

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

36 r""" 

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

38 

39 A Collection represents a sequence of `.Patch`\es that can be drawn 

40 more efficiently together than individually. For example, when a single 

41 path is being drawn repeatedly at different offsets, the renderer can 

42 typically execute a ``draw_marker()`` call much more efficiently than a 

43 series of repeated calls to ``draw_path()`` with the offsets put in 

44 one-by-one. 

45 

46 Most properties of a collection can be configured per-element. Therefore, 

47 Collections have "plural" versions of many of the properties of a `.Patch` 

48 (e.g. `.Collection.get_paths` instead of `.Patch.get_path`). Exceptions are 

49 the *zorder*, *hatch*, *pickradius*, *capstyle* and *joinstyle* properties, 

50 which can only be set globally for the whole collection. 

51 

52 Besides these exceptions, all properties can be specified as single values 

53 (applying to all elements) or sequences of values. The property of the 

54 ``i``\th element of the collection is:: 

55 

56 prop[i % len(prop)] 

57 

58 Each Collection can optionally be used as its own `.ScalarMappable` by 

59 passing the *norm* and *cmap* parameters to its constructor. If the 

60 Collection's `.ScalarMappable` matrix ``_A`` has been set (via a call 

61 to `.Collection.set_array`), then at draw time this internal scalar 

62 mappable will be used to set the ``facecolors`` and ``edgecolors``, 

63 ignoring those that were manually passed in. 

64 """ 

65 #: Either a list of 3x3 arrays or an Nx3x3 array (representing N 

66 #: transforms), suitable for the `all_transforms` argument to 

67 #: `~matplotlib.backend_bases.RendererBase.draw_path_collection`; 

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

69 #: `~matplotlib.transforms.Affine2D` object. 

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

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

72 

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

74 # subclass-by-subclass basis. 

75 _edge_default = False 

76 

77 @_docstring.interpd 

78 @_api.make_keyword_only("3.6", name="edgecolors") 

79 def __init__(self, 

80 edgecolors=None, 

81 facecolors=None, 

82 linewidths=None, 

83 linestyles='solid', 

84 capstyle=None, 

85 joinstyle=None, 

86 antialiaseds=None, 

87 offsets=None, 

88 offset_transform=None, 

89 norm=None, # optional for ScalarMappable 

90 cmap=None, # ditto 

91 pickradius=5.0, 

92 hatch=None, 

93 urls=None, 

94 *, 

95 zorder=1, 

96 **kwargs 

97 ): 

98 """ 

99 Parameters 

100 ---------- 

101 edgecolors : color or list of colors, default: :rc:`patch.edgecolor` 

102 Edge color for each patch making up the collection. The special 

103 value 'face' can be passed to make the edgecolor match the 

104 facecolor. 

105 facecolors : color or list of colors, default: :rc:`patch.facecolor` 

106 Face color for each patch making up the collection. 

107 linewidths : float or list of floats, default: :rc:`patch.linewidth` 

108 Line width for each patch making up the collection. 

109 linestyles : str or tuple or list thereof, default: 'solid' 

110 Valid strings are ['solid', 'dashed', 'dashdot', 'dotted', '-', 

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

112 

113 (offset, onoffseq), 

114 

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

116 in points. For examples, see 

117 :doc:`/gallery/lines_bars_and_markers/linestyles`. 

118 capstyle : `.CapStyle`-like, default: :rc:`patch.capstyle` 

119 Style to use for capping lines for all paths in the collection. 

120 Allowed values are %(CapStyle)s. 

121 joinstyle : `.JoinStyle`-like, default: :rc:`patch.joinstyle` 

122 Style to use for joining lines for all paths in the collection. 

123 Allowed values are %(JoinStyle)s. 

124 antialiaseds : bool or list of bool, default: :rc:`patch.antialiased` 

125 Whether each patch in the collection should be drawn with 

126 antialiasing. 

127 offsets : (float, float) or list thereof, default: (0, 0) 

128 A vector by which to translate each patch after rendering (default 

129 is no translation). The translation is performed in screen (pixel) 

130 coordinates (i.e. after the Artist's transform is applied). 

131 offset_transform : `~.Transform`, default: `.IdentityTransform` 

132 A single transform which will be applied to each *offsets* vector 

133 before it is used. 

134 cmap, norm 

135 Data normalization and colormapping parameters. See 

136 `.ScalarMappable` for a detailed description. 

137 hatch : str, optional 

138 Hatching pattern to use in filled paths, if any. Valid strings are 

139 ['/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*']. See 

140 :doc:`/gallery/shapes_and_collections/hatch_style_reference` for 

141 the meaning of each hatch type. 

142 pickradius : float, default: 5.0 

143 If ``pickradius <= 0``, then `.Collection.contains` will return 

144 ``True`` whenever the test point is inside of one of the polygons 

145 formed by the control points of a Path in the Collection. On the 

146 other hand, if it is greater than 0, then we instead check if the 

147 test point is contained in a stroke of width ``2*pickradius`` 

148 following any of the Paths in the Collection. 

149 urls : list of str, default: None 

150 A URL for each patch to link to once drawn. Currently only works 

151 for the SVG backend. See :doc:`/gallery/misc/hyperlinks_sgskip` for 

152 examples. 

153 zorder : float, default: 1 

154 The drawing order, shared by all Patches in the Collection. See 

155 :doc:`/gallery/misc/zorder_demo` for all defaults and examples. 

156 """ 

157 artist.Artist.__init__(self) 

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

159 # list of un-scaled dash patterns 

160 # this is needed scaling the dash pattern by linewidth 

161 self._us_linestyles = [(0, None)] 

162 # list of dash patterns 

163 self._linestyles = [(0, None)] 

164 # list of unbroadcast/scaled linewidths 

165 self._us_lw = [0] 

166 self._linewidths = [0] 

167 # Flags set by _set_mappable_flags: are colors from mapping an array? 

168 self._face_is_mapped = None 

169 self._edge_is_mapped = None 

170 self._mapped_colors = None # calculated in update_scalarmappable 

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

172 self.set_facecolor(facecolors) 

173 self.set_edgecolor(edgecolors) 

174 self.set_linewidth(linewidths) 

175 self.set_linestyle(linestyles) 

176 self.set_antialiased(antialiaseds) 

177 self.set_pickradius(pickradius) 

178 self.set_urls(urls) 

179 self.set_hatch(hatch) 

180 self.set_zorder(zorder) 

181 

182 if capstyle: 

183 self.set_capstyle(capstyle) 

184 else: 

185 self._capstyle = None 

186 

187 if joinstyle: 

188 self.set_joinstyle(joinstyle) 

189 else: 

190 self._joinstyle = None 

191 

192 if offsets is not None: 

193 offsets = np.asanyarray(offsets, float) 

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

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

196 offsets = offsets[None, :] 

197 

198 self._offsets = offsets 

199 self._offset_transform = offset_transform 

200 

201 self._path_effects = None 

202 self._internal_update(kwargs) 

203 self._paths = None 

204 

205 def get_paths(self): 

206 return self._paths 

207 

208 def set_paths(self, paths): 

209 raise NotImplementedError 

210 

211 def get_transforms(self): 

212 return self._transforms 

213 

214 def get_offset_transform(self): 

215 """Return the `.Transform` instance used by this artist offset.""" 

216 if self._offset_transform is None: 

217 self._offset_transform = transforms.IdentityTransform() 

218 elif (not isinstance(self._offset_transform, transforms.Transform) 

219 and hasattr(self._offset_transform, '_as_mpl_transform')): 

220 self._offset_transform = \ 

221 self._offset_transform._as_mpl_transform(self.axes) 

222 return self._offset_transform 

223 

224 @_api.rename_parameter("3.6", "transOffset", "offset_transform") 

225 def set_offset_transform(self, offset_transform): 

226 """ 

227 Set the artist offset transform. 

228 

229 Parameters 

230 ---------- 

231 offset_transform : `.Transform` 

232 """ 

233 self._offset_transform = offset_transform 

234 

235 def get_datalim(self, transData): 

236 # Calculate the data limits and return them as a `.Bbox`. 

237 # 

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

239 # collection and whether the collection has offsets: 

240 # 

241 # 1. offsets = None, transform child of transData: use the paths for 

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

243 # 2. offsets != None: offset_transform is child of transData: 

244 # 

245 # a. transform is child of transData: use the path + offset for 

246 # limits (i.e for bar). 

247 # b. transform is not a child of transData: just use the offsets 

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

249 # 

250 # 3. otherwise return a null Bbox. 

251 

252 transform = self.get_transform() 

253 offset_trf = self.get_offset_transform() 

254 if not (isinstance(offset_trf, transforms.IdentityTransform) 

255 or offset_trf.contains_branch(transData)): 

256 # if the offsets are in some coords other than data, 

257 # then don't use them for autoscaling. 

258 return transforms.Bbox.null() 

259 offsets = self.get_offsets() 

260 

261 paths = self.get_paths() 

262 if not len(paths): 

263 # No paths to transform 

264 return transforms.Bbox.null() 

265 

266 if not transform.is_affine: 

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

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

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

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

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

272 

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

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

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

276 # offset. LineCollections that have no offsets can 

277 # also use this algorithm (like streamplot). 

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

279 offsets = offsets.filled(np.nan) 

280 # get_path_collection_extents handles nan but not masked arrays 

281 return mpath.get_path_collection_extents( 

282 transform.get_affine() - transData, paths, 

283 self.get_transforms(), 

284 offset_trf.transform_non_affine(offsets), 

285 offset_trf.get_affine().frozen()) 

286 

287 # NOTE: None is the default case where no offsets were passed in 

288 if self._offsets is not None: 

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

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

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

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

293 # location. 

294 offsets = (offset_trf - transData).transform(offsets) 

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

296 offsets = np.ma.masked_invalid(offsets) 

297 if not offsets.mask.all(): 

298 bbox = transforms.Bbox.null() 

299 bbox.update_from_data_xy(offsets) 

300 return bbox 

301 return transforms.Bbox.null() 

302 

303 def get_window_extent(self, renderer=None): 

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

305 # cases other than scatter plot legend 

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

307 

308 def _prepare_points(self): 

309 # Helper for drawing and hit testing. 

310 

311 transform = self.get_transform() 

312 offset_trf = self.get_offset_transform() 

313 offsets = self.get_offsets() 

314 paths = self.get_paths() 

315 

316 if self.have_units(): 

317 paths = [] 

318 for path in self.get_paths(): 

319 vertices = path.vertices 

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

321 xs = self.convert_xunits(xs) 

322 ys = self.convert_yunits(ys) 

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

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

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

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

327 

328 if not transform.is_affine: 

329 paths = [transform.transform_path_non_affine(path) 

330 for path in paths] 

331 transform = transform.get_affine() 

332 if not offset_trf.is_affine: 

333 offsets = offset_trf.transform_non_affine(offsets) 

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

335 offset_trf = offset_trf.get_affine() 

336 

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

338 offsets = offsets.filled(np.nan) 

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

340 # is probably most efficient at this point. 

341 

342 return transform, offset_trf, offsets, paths 

343 

344 @artist.allow_rasterization 

345 def draw(self, renderer): 

346 if not self.get_visible(): 

347 return 

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

349 

350 self.update_scalarmappable() 

351 

352 transform, offset_trf, offsets, paths = self._prepare_points() 

353 

354 gc = renderer.new_gc() 

355 self._set_gc_clip(gc) 

356 gc.set_snap(self.get_snap()) 

357 

358 if self._hatch: 

359 gc.set_hatch(self._hatch) 

360 gc.set_hatch_color(self._hatch_color) 

361 

362 if self.get_sketch_params() is not None: 

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

364 

365 if self.get_path_effects(): 

366 from matplotlib.patheffects import PathEffectRenderer 

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

368 

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

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

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

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

373 # PDF/SVG/PS. 

374 

375 trans = self.get_transforms() 

376 facecolors = self.get_facecolor() 

377 edgecolors = self.get_edgecolor() 

378 do_single_path_optimization = False 

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

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

381 len(self._linewidths) == 1 and 

382 all(ls[1] is None for ls in self._linestyles) and 

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

384 self.get_hatch() is None): 

385 if len(trans): 

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

387 else: 

388 combined_transform = transform 

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

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

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

392 do_single_path_optimization = True 

393 

394 if self._joinstyle: 

395 gc.set_joinstyle(self._joinstyle) 

396 

397 if self._capstyle: 

398 gc.set_capstyle(self._capstyle) 

399 

400 if do_single_path_optimization: 

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

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

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

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

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

406 renderer.draw_markers( 

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

408 mpath.Path(offsets), offset_trf, tuple(facecolors[0])) 

409 else: 

410 renderer.draw_path_collection( 

411 gc, transform.frozen(), paths, 

412 self.get_transforms(), offsets, offset_trf, 

413 self.get_facecolor(), self.get_edgecolor(), 

414 self._linewidths, self._linestyles, 

415 self._antialiaseds, self._urls, 

416 "screen") # offset_position, kept for backcompat. 

417 

418 gc.restore() 

419 renderer.close_group(self.__class__.__name__) 

420 self.stale = False 

421 

422 @_api.rename_parameter("3.6", "pr", "pickradius") 

423 def set_pickradius(self, pickradius): 

424 """ 

425 Set the pick radius used for containment tests. 

426 

427 Parameters 

428 ---------- 

429 pickradius : float 

430 Pick radius, in points. 

431 """ 

432 self._pickradius = pickradius 

433 

434 def get_pickradius(self): 

435 return self._pickradius 

436 

437 def contains(self, mouseevent): 

438 """ 

439 Test whether the mouse event occurred in the collection. 

440 

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

442 contains the event. 

443 """ 

444 inside, info = self._default_contains(mouseevent) 

445 if inside is not None: 

446 return inside, info 

447 

448 if not self.get_visible(): 

449 return False, {} 

450 

451 pickradius = ( 

452 float(self._picker) 

453 if isinstance(self._picker, Number) and 

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

455 else self._pickradius) 

456 

457 if self.axes: 

458 self.axes._unstale_viewLim() 

459 

460 transform, offset_trf, offsets, paths = self._prepare_points() 

461 

462 # Tests if the point is contained on one of the polygons formed 

463 # by the control points of each of the paths. A point is considered 

464 # "on" a path if it would lie within a stroke of width 2*pickradius 

465 # following the path. If pickradius <= 0, then we instead simply check 

466 # if the point is *inside* of the path instead. 

467 ind = _path.point_in_path_collection( 

468 mouseevent.x, mouseevent.y, pickradius, 

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

470 offsets, offset_trf, pickradius <= 0) 

471 

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

473 

474 def set_urls(self, urls): 

475 """ 

476 Parameters 

477 ---------- 

478 urls : list of str or None 

479 

480 Notes 

481 ----- 

482 URLs are currently only implemented by the SVG backend. They are 

483 ignored by all other backends. 

484 """ 

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

486 self.stale = True 

487 

488 def get_urls(self): 

489 """ 

490 Return a list of URLs, one for each element of the collection. 

491 

492 The list contains *None* for elements without a URL. See 

493 :doc:`/gallery/misc/hyperlinks_sgskip` for an example. 

494 """ 

495 return self._urls 

496 

497 def set_hatch(self, hatch): 

498 r""" 

499 Set the hatching pattern 

500 

501 *hatch* can be one of:: 

502 

503 / - diagonal hatching 

504 \ - back diagonal 

505 | - vertical 

506 - - horizontal 

507 + - crossed 

508 x - crossed diagonal 

509 o - small circle 

510 O - large circle 

511 . - dots 

512 * - stars 

513 

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

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

516 density of hatching of that pattern. 

517 

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

519 backends only. 

520 

521 Unlike other properties such as linewidth and colors, hatching 

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

523 for each member. 

524 

525 Parameters 

526 ---------- 

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

528 """ 

529 # Use validate_hatch(list) after deprecation. 

530 mhatch._validate_hatch_pattern(hatch) 

531 self._hatch = hatch 

532 self.stale = True 

533 

534 def get_hatch(self): 

535 """Return the current hatching pattern.""" 

536 return self._hatch 

537 

538 def set_offsets(self, offsets): 

539 """ 

540 Set the offsets for the collection. 

541 

542 Parameters 

543 ---------- 

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

545 """ 

546 offsets = np.asanyarray(offsets) 

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

548 offsets = offsets[None, :] 

549 self._offsets = np.column_stack( 

550 (np.asarray(self.convert_xunits(offsets[:, 0]), 'float'), 

551 np.asarray(self.convert_yunits(offsets[:, 1]), 'float'))) 

552 self.stale = True 

553 

554 def get_offsets(self): 

555 """Return the offsets for the collection.""" 

556 # Default to zeros in the no-offset (None) case 

557 return np.zeros((1, 2)) if self._offsets is None else self._offsets 

558 

559 def _get_default_linewidth(self): 

560 # This may be overridden in a subclass. 

561 return mpl.rcParams['patch.linewidth'] # validated as float 

562 

563 def set_linewidth(self, lw): 

564 """ 

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

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

567 through the sequence 

568 

569 Parameters 

570 ---------- 

571 lw : float or list of floats 

572 """ 

573 if lw is None: 

574 lw = self._get_default_linewidth() 

575 # get the un-scaled/broadcast lw 

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

577 

578 # scale all of the dash patterns. 

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

580 self._us_lw, self._us_linestyles) 

581 self.stale = True 

582 

583 def set_linestyle(self, ls): 

584 """ 

585 Set the linestyle(s) for the collection. 

586 

587 =========================== ================= 

588 linestyle description 

589 =========================== ================= 

590 ``'-'`` or ``'solid'`` solid line 

591 ``'--'`` or ``'dashed'`` dashed line 

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

593 ``':'`` or ``'dotted'`` dotted line 

594 =========================== ================= 

595 

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

597 

598 (offset, onoffseq), 

599 

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

601 

602 Parameters 

603 ---------- 

604 ls : str or tuple or list thereof 

605 Valid values for individual linestyles include {'-', '--', '-.', 

606 ':', '', (offset, on-off-seq)}. See `.Line2D.set_linestyle` for a 

607 complete description. 

608 """ 

609 try: 

610 if isinstance(ls, str): 

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

612 dashes = [mlines._get_dash_pattern(ls)] 

613 else: 

614 try: 

615 dashes = [mlines._get_dash_pattern(ls)] 

616 except ValueError: 

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

618 

619 except ValueError as err: 

620 raise ValueError('Do not know how to convert {!r} to ' 

621 'dashes'.format(ls)) from err 

622 

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

624 self._us_linestyles = dashes 

625 

626 # broadcast and scale the lw and dash patterns 

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

628 self._us_lw, self._us_linestyles) 

629 

630 @_docstring.interpd 

631 def set_capstyle(self, cs): 

632 """ 

633 Set the `.CapStyle` for the collection (for all its elements). 

634 

635 Parameters 

636 ---------- 

637 cs : `.CapStyle` or %(CapStyle)s 

638 """ 

639 self._capstyle = CapStyle(cs) 

640 

641 def get_capstyle(self): 

642 return self._capstyle.name 

643 

644 @_docstring.interpd 

645 def set_joinstyle(self, js): 

646 """ 

647 Set the `.JoinStyle` for the collection (for all its elements). 

648 

649 Parameters 

650 ---------- 

651 js : `.JoinStyle` or %(JoinStyle)s 

652 """ 

653 self._joinstyle = JoinStyle(js) 

654 

655 def get_joinstyle(self): 

656 return self._joinstyle.name 

657 

658 @staticmethod 

659 def _bcast_lwls(linewidths, dashes): 

660 """ 

661 Internal helper function to broadcast + scale ls/lw 

662 

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

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

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

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

667 

668 Parameters 

669 ---------- 

670 linewidths : list 

671 line widths of collection 

672 dashes : list 

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

674 

675 Returns 

676 ------- 

677 linewidths, dashes : list 

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

679 """ 

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

681 return linewidths, dashes 

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

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

684 l_dashes = len(dashes) 

685 l_lw = len(linewidths) 

686 gcd = math.gcd(l_dashes, l_lw) 

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

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

689 

690 # scale the dash patterns 

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

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

693 

694 return linewidths, dashes 

695 

696 def set_antialiased(self, aa): 

697 """ 

698 Set the antialiasing state for rendering. 

699 

700 Parameters 

701 ---------- 

702 aa : bool or list of bools 

703 """ 

704 if aa is None: 

705 aa = self._get_default_antialiased() 

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

707 self.stale = True 

708 

709 def _get_default_antialiased(self): 

710 # This may be overridden in a subclass. 

711 return mpl.rcParams['patch.antialiased'] 

712 

713 def set_color(self, c): 

714 """ 

715 Set both the edgecolor and the facecolor. 

716 

717 Parameters 

718 ---------- 

719 c : color or list of RGBA tuples 

720 

721 See Also 

722 -------- 

723 Collection.set_facecolor, Collection.set_edgecolor 

724 For setting the edge or face color individually. 

725 """ 

726 self.set_facecolor(c) 

727 self.set_edgecolor(c) 

728 

729 def _get_default_facecolor(self): 

730 # This may be overridden in a subclass. 

731 return mpl.rcParams['patch.facecolor'] 

732 

733 def _set_facecolor(self, c): 

734 if c is None: 

735 c = self._get_default_facecolor() 

736 

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

738 self.stale = True 

739 

740 def set_facecolor(self, c): 

741 """ 

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

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

744 patches will cycle through the sequence. 

745 

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

747 

748 Parameters 

749 ---------- 

750 c : color or list of colors 

751 """ 

752 if isinstance(c, str) and c.lower() in ("none", "face"): 

753 c = c.lower() 

754 self._original_facecolor = c 

755 self._set_facecolor(c) 

756 

757 def get_facecolor(self): 

758 return self._facecolors 

759 

760 def get_edgecolor(self): 

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

762 return self.get_facecolor() 

763 else: 

764 return self._edgecolors 

765 

766 def _get_default_edgecolor(self): 

767 # This may be overridden in a subclass. 

768 return mpl.rcParams['patch.edgecolor'] 

769 

770 def _set_edgecolor(self, c): 

771 set_hatch_color = True 

772 if c is None: 

773 if (mpl.rcParams['patch.force_edgecolor'] 

774 or self._edge_default 

775 or cbook._str_equal(self._original_facecolor, 'none')): 

776 c = self._get_default_edgecolor() 

777 else: 

778 c = 'none' 

779 set_hatch_color = False 

780 if cbook._str_lower_equal(c, 'face'): 

781 self._edgecolors = 'face' 

782 self.stale = True 

783 return 

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

785 if set_hatch_color and len(self._edgecolors): 

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

787 self.stale = True 

788 

789 def set_edgecolor(self, c): 

790 """ 

791 Set the edgecolor(s) of the collection. 

792 

793 Parameters 

794 ---------- 

795 c : color or list of colors or 'face' 

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

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

798 """ 

799 # We pass through a default value for use in LineCollection. 

800 # This allows us to maintain None as the default indicator in 

801 # _original_edgecolor. 

802 if isinstance(c, str) and c.lower() in ("none", "face"): 

803 c = c.lower() 

804 self._original_edgecolor = c 

805 self._set_edgecolor(c) 

806 

807 def set_alpha(self, alpha): 

808 """ 

809 Set the transparency of the collection. 

810 

811 Parameters 

812 ---------- 

813 alpha : float or array of float or None 

814 If not None, *alpha* values must be between 0 and 1, inclusive. 

815 If an array is provided, its length must match the number of 

816 elements in the collection. Masked values and nans are not 

817 supported. 

818 """ 

819 artist.Artist._set_alpha_for_array(self, alpha) 

820 self._set_facecolor(self._original_facecolor) 

821 self._set_edgecolor(self._original_edgecolor) 

822 

823 set_alpha.__doc__ = artist.Artist._set_alpha_for_array.__doc__ 

824 

825 def get_linewidth(self): 

826 return self._linewidths 

827 

828 def get_linestyle(self): 

829 return self._linestyles 

830 

831 def _set_mappable_flags(self): 

832 """ 

833 Determine whether edges and/or faces are color-mapped. 

834 

835 This is a helper for update_scalarmappable. 

836 It sets Boolean flags '_edge_is_mapped' and '_face_is_mapped'. 

837 

838 Returns 

839 ------- 

840 mapping_change : bool 

841 True if either flag is True, or if a flag has changed. 

842 """ 

843 # The flags are initialized to None to ensure this returns True 

844 # the first time it is called. 

845 edge0 = self._edge_is_mapped 

846 face0 = self._face_is_mapped 

847 # After returning, the flags must be Booleans, not None. 

848 self._edge_is_mapped = False 

849 self._face_is_mapped = False 

850 if self._A is not None: 

851 if not cbook._str_equal(self._original_facecolor, 'none'): 

852 self._face_is_mapped = True 

853 if cbook._str_equal(self._original_edgecolor, 'face'): 

854 self._edge_is_mapped = True 

855 else: 

856 if self._original_edgecolor is None: 

857 self._edge_is_mapped = True 

858 

859 mapped = self._face_is_mapped or self._edge_is_mapped 

860 changed = (edge0 is None or face0 is None 

861 or self._edge_is_mapped != edge0 

862 or self._face_is_mapped != face0) 

863 return mapped or changed 

864 

865 def update_scalarmappable(self): 

866 """ 

867 Update colors from the scalar mappable array, if any. 

868 

869 Assign colors to edges and faces based on the array and/or 

870 colors that were directly set, as appropriate. 

871 """ 

872 if not self._set_mappable_flags(): 

873 return 

874 # Allow possibility to call 'self.set_array(None)'. 

875 if self._A is not None: 

876 # QuadMesh can map 2d arrays (but pcolormesh supplies 1d array) 

877 if self._A.ndim > 1 and not isinstance(self, QuadMesh): 

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

879 if np.iterable(self._alpha): 

880 if self._alpha.size != self._A.size: 

881 raise ValueError( 

882 f'Data array shape, {self._A.shape} ' 

883 'is incompatible with alpha array shape, ' 

884 f'{self._alpha.shape}. ' 

885 'This can occur with the deprecated ' 

886 'behavior of the "flat" shading option, ' 

887 'in which a row and/or column of the data ' 

888 'array is dropped.') 

889 # pcolormesh, scatter, maybe others flatten their _A 

890 self._alpha = self._alpha.reshape(self._A.shape) 

891 self._mapped_colors = self.to_rgba(self._A, self._alpha) 

892 

893 if self._face_is_mapped: 

894 self._facecolors = self._mapped_colors 

895 else: 

896 self._set_facecolor(self._original_facecolor) 

897 if self._edge_is_mapped: 

898 self._edgecolors = self._mapped_colors 

899 else: 

900 self._set_edgecolor(self._original_edgecolor) 

901 self.stale = True 

902 

903 def get_fill(self): 

904 """Return whether face is colored.""" 

905 return not cbook._str_lower_equal(self._original_facecolor, "none") 

906 

907 def update_from(self, other): 

908 """Copy properties from other to self.""" 

909 

910 artist.Artist.update_from(self, other) 

911 self._antialiaseds = other._antialiaseds 

912 self._mapped_colors = other._mapped_colors 

913 self._edge_is_mapped = other._edge_is_mapped 

914 self._original_edgecolor = other._original_edgecolor 

915 self._edgecolors = other._edgecolors 

916 self._face_is_mapped = other._face_is_mapped 

917 self._original_facecolor = other._original_facecolor 

918 self._facecolors = other._facecolors 

919 self._linewidths = other._linewidths 

920 self._linestyles = other._linestyles 

921 self._us_linestyles = other._us_linestyles 

922 self._pickradius = other._pickradius 

923 self._hatch = other._hatch 

924 

925 # update_from for scalarmappable 

926 self._A = other._A 

927 self.norm = other.norm 

928 self.cmap = other.cmap 

929 self.stale = True 

930 

931 

932class _CollectionWithSizes(Collection): 

933 """ 

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

935 """ 

936 _factor = 1.0 

937 

938 def get_sizes(self): 

939 """ 

940 Return the sizes ('areas') of the elements in the collection. 

941 

942 Returns 

943 ------- 

944 array 

945 The 'area' of each element. 

946 """ 

947 return self._sizes 

948 

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

950 """ 

951 Set the sizes of each member of the collection. 

952 

953 Parameters 

954 ---------- 

955 sizes : ndarray or None 

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

957 value is the 'area' of the element. 

958 dpi : float, default: 72 

959 The dpi of the canvas. 

960 """ 

961 if sizes is None: 

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

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

964 else: 

965 self._sizes = np.asarray(sizes) 

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

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

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

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

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

971 self.stale = True 

972 

973 @artist.allow_rasterization 

974 def draw(self, renderer): 

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

976 super().draw(renderer) 

977 

978 

979class PathCollection(_CollectionWithSizes): 

980 r""" 

981 A collection of `~.path.Path`\s, as created by e.g. `~.Axes.scatter`. 

982 """ 

983 

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

985 """ 

986 Parameters 

987 ---------- 

988 paths : list of `.path.Path` 

989 The paths that will make up the `.Collection`. 

990 sizes : array-like 

991 The factor by which to scale each drawn `~.path.Path`. One unit 

992 squared in the Path's data space is scaled to be ``sizes**2`` 

993 points when rendered. 

994 **kwargs 

995 Forwarded to `.Collection`. 

996 """ 

997 

998 super().__init__(**kwargs) 

999 self.set_paths(paths) 

1000 self.set_sizes(sizes) 

1001 self.stale = True 

1002 

1003 def set_paths(self, paths): 

1004 self._paths = paths 

1005 self.stale = True 

1006 

1007 def get_paths(self): 

1008 return self._paths 

1009 

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

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

1012 """ 

1013 Create legend handles and labels for a PathCollection. 

1014 

1015 Each legend handle is a `.Line2D` representing the Path that was drawn, 

1016 and each label is a string what each Path represents. 

1017 

1018 This is useful for obtaining a legend for a `~.Axes.scatter` plot; 

1019 e.g.:: 

1020 

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

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

1023 

1024 creates three legend elements, one for each color with the numerical 

1025 values passed to *c* as the labels. 

1026 

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

1028 

1029 Parameters 

1030 ---------- 

1031 prop : {"colors", "sizes"}, default: "colors" 

1032 If "colors", the legend handles will show the different colors of 

1033 the collection. If "sizes", the legend will show the different 

1034 sizes. To set both, use *kwargs* to directly edit the `.Line2D` 

1035 properties. 

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

1037 Target number of elements to create. 

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

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

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

1041 of the data. 

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

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

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

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

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

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

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

1049 use a `.ScalarFormatter`. 

1050 func : function, default: ``lambda x: x`` 

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

1052 argument to `~.Axes.scatter` will have been pre-processed by the 

1053 user using a function ``s = f(x)`` to make the markers visible; 

1054 e.g. ``size = np.log10(x)``. Providing the inverse of this 

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

1056 the legend labels have the correct values; e.g. ``func = lambda 

1057 x: 10**x``. 

1058 **kwargs 

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

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

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

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

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

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

1065 

1066 Returns 

1067 ------- 

1068 handles : list of `.Line2D` 

1069 Visual representation of each element of the legend. 

1070 labels : list of str 

1071 The string labels for elements of the legend. 

1072 """ 

1073 handles = [] 

1074 labels = [] 

1075 hasarray = self.get_array() is not None 

1076 if fmt is None: 

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

1078 elif isinstance(fmt, str): 

1079 fmt = mpl.ticker.StrMethodFormatter(fmt) 

1080 fmt.create_dummy_axis() 

1081 

1082 if prop == "colors": 

1083 if not hasarray: 

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

1085 "specify the values to be colormapped via the " 

1086 "`c` argument.") 

1087 return handles, labels 

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

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

1090 elif prop == "sizes": 

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

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

1093 else: 

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

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

1096 

1097 fu = func(u) 

1098 fmt.axis.set_view_interval(fu.min(), fu.max()) 

1099 fmt.axis.set_data_interval(fu.min(), fu.max()) 

1100 if num == "auto": 

1101 num = 9 

1102 if len(u) <= num: 

1103 num = None 

1104 if num is None: 

1105 values = u 

1106 label_values = func(values) 

1107 else: 

1108 if prop == "colors": 

1109 arr = self.get_array() 

1110 elif prop == "sizes": 

1111 arr = self.get_sizes() 

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

1113 loc = num 

1114 elif np.iterable(num): 

1115 loc = mpl.ticker.FixedLocator(num) 

1116 else: 

1117 num = int(num) 

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

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

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

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

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

1123 label_values = label_values[cond] 

1124 yarr = np.linspace(arr.min(), arr.max(), 256) 

1125 xarr = func(yarr) 

1126 ix = np.argsort(xarr) 

1127 values = np.interp(label_values, xarr[ix], yarr[ix]) 

1128 

1129 kw = {"markeredgewidth": self.get_linewidths()[0], 

1130 "alpha": self.get_alpha(), 

1131 **kwargs} 

1132 

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

1134 if prop == "colors": 

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

1136 elif prop == "sizes": 

1137 size = np.sqrt(val) 

1138 if np.isclose(size, 0.0): 

1139 continue 

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

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

1142 handles.append(h) 

1143 if hasattr(fmt, "set_locs"): 

1144 fmt.set_locs(label_values) 

1145 l = fmt(lab) 

1146 labels.append(l) 

1147 

1148 return handles, labels 

1149 

1150 

1151class PolyCollection(_CollectionWithSizes): 

1152 

1153 @_api.make_keyword_only("3.6", name="closed") 

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

1155 """ 

1156 Parameters 

1157 ---------- 

1158 verts : list of array-like 

1159 The sequence of polygons [*verts0*, *verts1*, ...] where each 

1160 element *verts_i* defines the vertices of polygon *i* as a 2D 

1161 array-like of shape (M, 2). 

1162 sizes : array-like, default: None 

1163 Squared scaling factors for the polygons. The coordinates of each 

1164 polygon *verts_i* are multiplied by the square-root of the 

1165 corresponding entry in *sizes* (i.e., *sizes* specify the scaling 

1166 of areas). The scaling is applied before the Artist master 

1167 transform. 

1168 closed : bool, default: True 

1169 Whether the polygon should be closed by adding a CLOSEPOLY 

1170 connection at the end. 

1171 **kwargs 

1172 Forwarded to `.Collection`. 

1173 """ 

1174 super().__init__(**kwargs) 

1175 self.set_sizes(sizes) 

1176 self.set_verts(verts, closed) 

1177 self.stale = True 

1178 

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

1180 """ 

1181 Set the vertices of the polygons. 

1182 

1183 Parameters 

1184 ---------- 

1185 verts : list of array-like 

1186 The sequence of polygons [*verts0*, *verts1*, ...] where each 

1187 element *verts_i* defines the vertices of polygon *i* as a 2D 

1188 array-like of shape (M, 2). 

1189 closed : bool, default: True 

1190 Whether the polygon should be closed by adding a CLOSEPOLY 

1191 connection at the end. 

1192 """ 

1193 self.stale = True 

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

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

1196 

1197 # No need to do anything fancy if the path isn't closed. 

1198 if not closed: 

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

1200 return 

1201 

1202 # Fast path for arrays 

1203 if isinstance(verts, np.ndarray) and len(verts.shape) == 3: 

1204 verts_pad = np.concatenate((verts, verts[:, :1]), axis=1) 

1205 # Creating the codes once is much faster than having Path do it 

1206 # separately each time by passing closed=True. 

1207 codes = np.empty(verts_pad.shape[1], dtype=mpath.Path.code_type) 

1208 codes[:] = mpath.Path.LINETO 

1209 codes[0] = mpath.Path.MOVETO 

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

1211 self._paths = [mpath.Path(xy, codes) for xy in verts_pad] 

1212 return 

1213 

1214 self._paths = [] 

1215 for xy in verts: 

1216 if len(xy): 

1217 self._paths.append(mpath.Path._create_closed(xy)) 

1218 else: 

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

1220 

1221 set_paths = set_verts 

1222 

1223 def set_verts_and_codes(self, verts, codes): 

1224 """Initialize vertices with path codes.""" 

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

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

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

1228 self._paths = [mpath.Path(xy, cds) if len(xy) else mpath.Path(xy) 

1229 for xy, cds in zip(verts, codes)] 

1230 self.stale = True 

1231 

1232 

1233class BrokenBarHCollection(PolyCollection): 

1234 """ 

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

1236 *xranges*. 

1237 """ 

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

1239 """ 

1240 Parameters 

1241 ---------- 

1242 xranges : list of (float, float) 

1243 The sequence of (left-edge-position, width) pairs for each bar. 

1244 yrange : (float, float) 

1245 The (lower-edge, height) common to all bars. 

1246 **kwargs 

1247 Forwarded to `.Collection`. 

1248 """ 

1249 ymin, ywidth = yrange 

1250 ymax = ymin + ywidth 

1251 verts = [[(xmin, ymin), 

1252 (xmin, ymax), 

1253 (xmin + xwidth, ymax), 

1254 (xmin + xwidth, ymin), 

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

1256 super().__init__(verts, **kwargs) 

1257 

1258 @classmethod 

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

1260 """ 

1261 Return a `.BrokenBarHCollection` that plots horizontal bars from 

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

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

1264 

1265 *kwargs* are passed on to the collection. 

1266 """ 

1267 xranges = [] 

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

1269 xslice = x[ind0:ind1] 

1270 if not len(xslice): 

1271 continue 

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

1273 return cls(xranges, [ymin, ymax - ymin], **kwargs) 

1274 

1275 

1276class RegularPolyCollection(_CollectionWithSizes): 

1277 """A collection of n-sided regular polygons.""" 

1278 

1279 _path_generator = mpath.Path.unit_regular_polygon 

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

1281 

1282 @_api.make_keyword_only("3.6", name="rotation") 

1283 def __init__(self, 

1284 numsides, 

1285 rotation=0, 

1286 sizes=(1,), 

1287 **kwargs): 

1288 """ 

1289 Parameters 

1290 ---------- 

1291 numsides : int 

1292 The number of sides of the polygon. 

1293 rotation : float 

1294 The rotation of the polygon in radians. 

1295 sizes : tuple of float 

1296 The area of the circle circumscribing the polygon in points^2. 

1297 **kwargs 

1298 Forwarded to `.Collection`. 

1299 

1300 Examples 

1301 -------- 

1302 See :doc:`/gallery/event_handling/lasso_demo` for a complete example:: 

1303 

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

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

1306 

1307 collection = RegularPolyCollection( 

1308 numsides=5, # a pentagon 

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

1310 facecolors=facecolors, 

1311 edgecolors=("black",), 

1312 linewidths=(1,), 

1313 offsets=offsets, 

1314 offset_transform=ax.transData, 

1315 ) 

1316 """ 

1317 super().__init__(**kwargs) 

1318 self.set_sizes(sizes) 

1319 self._numsides = numsides 

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

1321 self._rotation = rotation 

1322 self.set_transform(transforms.IdentityTransform()) 

1323 

1324 def get_numsides(self): 

1325 return self._numsides 

1326 

1327 def get_rotation(self): 

1328 return self._rotation 

1329 

1330 @artist.allow_rasterization 

1331 def draw(self, renderer): 

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

1333 self._transforms = [ 

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

1335 for x in self._transforms 

1336 ] 

1337 # Explicitly not super().draw, because set_sizes must be called before 

1338 # updating self._transforms. 

1339 Collection.draw(self, renderer) 

1340 

1341 

1342class StarPolygonCollection(RegularPolyCollection): 

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

1344 _path_generator = mpath.Path.unit_regular_star 

1345 

1346 

1347class AsteriskPolygonCollection(RegularPolyCollection): 

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

1349 _path_generator = mpath.Path.unit_regular_asterisk 

1350 

1351 

1352class LineCollection(Collection): 

1353 r""" 

1354 Represents a sequence of `.Line2D`\s that should be drawn together. 

1355 

1356 This class extends `.Collection` to represent a sequence of 

1357 `.Line2D`\s instead of just a sequence of `.Patch`\s. 

1358 Just as in `.Collection`, each property of a *LineCollection* may be either 

1359 a single value or a list of values. This list is then used cyclically for 

1360 each element of the LineCollection, so the property of the ``i``\th element 

1361 of the collection is:: 

1362 

1363 prop[i % len(prop)] 

1364 

1365 The properties of each member of a *LineCollection* default to their values 

1366 in :rc:`lines.*` instead of :rc:`patch.*`, and the property *colors* is 

1367 added in place of *edgecolors*. 

1368 """ 

1369 

1370 _edge_default = True 

1371 

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

1373 *, 

1374 zorder=2, # Collection.zorder is 1 

1375 **kwargs 

1376 ): 

1377 """ 

1378 Parameters 

1379 ---------- 

1380 segments : list of array-like 

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

1382 

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

1384 

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

1386 can have a different number of segments. 

1387 linewidths : float or list of float, default: :rc:`lines.linewidth` 

1388 The width of each line in points. 

1389 colors : color or list of color, default: :rc:`lines.color` 

1390 A sequence of RGBA tuples (e.g., arbitrary color strings, etc, not 

1391 allowed). 

1392 antialiaseds : bool or list of bool, default: :rc:`lines.antialiased` 

1393 Whether to use antialiasing for each line. 

1394 zorder : int, default: 2 

1395 zorder of the lines once drawn. 

1396 

1397 facecolors : color or list of color, default: 'none' 

1398 When setting *facecolors*, each line is interpreted as a boundary 

1399 for an area, implicitly closing the path from the last point to the 

1400 first point. The enclosed area is filled with *facecolor*. 

1401 In order to manually specify what should count as the "interior" of 

1402 each line, please use `.PathCollection` instead, where the 

1403 "interior" can be specified by appropriate usage of 

1404 `~.path.Path.CLOSEPOLY`. 

1405 

1406 **kwargs 

1407 Forwarded to `.Collection`. 

1408 """ 

1409 # Unfortunately, mplot3d needs this explicit setting of 'facecolors'. 

1410 kwargs.setdefault('facecolors', 'none') 

1411 super().__init__( 

1412 zorder=zorder, 

1413 **kwargs) 

1414 self.set_segments(segments) 

1415 

1416 def set_segments(self, segments): 

1417 if segments is None: 

1418 return 

1419 

1420 self._paths = [mpath.Path(seg) if isinstance(seg, np.ma.MaskedArray) 

1421 else mpath.Path(np.asarray(seg, float)) 

1422 for seg in segments] 

1423 self.stale = True 

1424 

1425 set_verts = set_segments # for compatibility with PolyCollection 

1426 set_paths = set_segments 

1427 

1428 def get_segments(self): 

1429 """ 

1430 Returns 

1431 ------- 

1432 list 

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

1434 array of vertices. 

1435 """ 

1436 segments = [] 

1437 

1438 for path in self._paths: 

1439 vertices = [ 

1440 vertex 

1441 for vertex, _ 

1442 # Never simplify here, we want to get the data-space values 

1443 # back and there in no way to know the "right" simplification 

1444 # threshold so never try. 

1445 in path.iter_segments(simplify=False) 

1446 ] 

1447 vertices = np.asarray(vertices) 

1448 segments.append(vertices) 

1449 

1450 return segments 

1451 

1452 def _get_default_linewidth(self): 

1453 return mpl.rcParams['lines.linewidth'] 

1454 

1455 def _get_default_antialiased(self): 

1456 return mpl.rcParams['lines.antialiased'] 

1457 

1458 def _get_default_edgecolor(self): 

1459 return mpl.rcParams['lines.color'] 

1460 

1461 def _get_default_facecolor(self): 

1462 return 'none' 

1463 

1464 def set_color(self, c): 

1465 """ 

1466 Set the edgecolor(s) of the LineCollection. 

1467 

1468 Parameters 

1469 ---------- 

1470 c : color or list of colors 

1471 Single color (all lines have same color), or a 

1472 sequence of RGBA tuples; if it is a sequence the lines will 

1473 cycle through the sequence. 

1474 """ 

1475 self.set_edgecolor(c) 

1476 

1477 set_colors = set_color 

1478 

1479 def get_color(self): 

1480 return self._edgecolors 

1481 

1482 get_colors = get_color # for compatibility with old versions 

1483 

1484 

1485class EventCollection(LineCollection): 

1486 """ 

1487 A collection of locations along a single axis at which an "event" occurred. 

1488 

1489 The events are given by a 1-dimensional array. They do not have an 

1490 amplitude and are displayed as parallel lines. 

1491 """ 

1492 

1493 _edge_default = True 

1494 

1495 @_api.make_keyword_only("3.6", name="lineoffset") 

1496 def __init__(self, 

1497 positions, # Cannot be None. 

1498 orientation='horizontal', 

1499 lineoffset=0, 

1500 linelength=1, 

1501 linewidth=None, 

1502 color=None, 

1503 linestyle='solid', 

1504 antialiased=None, 

1505 **kwargs 

1506 ): 

1507 """ 

1508 Parameters 

1509 ---------- 

1510 positions : 1D array-like 

1511 Each value is an event. 

1512 orientation : {'horizontal', 'vertical'}, default: 'horizontal' 

1513 The sequence of events is plotted along this direction. 

1514 The marker lines of the single events are along the orthogonal 

1515 direction. 

1516 lineoffset : float, default: 0 

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

1518 direction orthogonal to *orientation*. 

1519 linelength : float, default: 1 

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

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

1522 linewidth : float or list thereof, default: :rc:`lines.linewidth` 

1523 The line width of the event lines, in points. 

1524 color : color or list of colors, default: :rc:`lines.color` 

1525 The color of the event lines. 

1526 linestyle : str or tuple or list thereof, default: 'solid' 

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

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

1529 

1530 (offset, onoffseq), 

1531 

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

1533 in points. 

1534 antialiased : bool or list thereof, default: :rc:`lines.antialiased` 

1535 Whether to use antialiasing for drawing the lines. 

1536 **kwargs 

1537 Forwarded to `.LineCollection`. 

1538 

1539 Examples 

1540 -------- 

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

1542 """ 

1543 super().__init__([], 

1544 linewidths=linewidth, linestyles=linestyle, 

1545 colors=color, antialiaseds=antialiased, 

1546 **kwargs) 

1547 self._is_horizontal = True # Initial value, may be switched below. 

1548 self._linelength = linelength 

1549 self._lineoffset = lineoffset 

1550 self.set_orientation(orientation) 

1551 self.set_positions(positions) 

1552 

1553 def get_positions(self): 

1554 """ 

1555 Return an array containing the floating-point values of the positions. 

1556 """ 

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

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

1559 

1560 def set_positions(self, positions): 

1561 """Set the positions of the events.""" 

1562 if positions is None: 

1563 positions = [] 

1564 if np.ndim(positions) != 1: 

1565 raise ValueError('positions must be one-dimensional') 

1566 lineoffset = self.get_lineoffset() 

1567 linelength = self.get_linelength() 

1568 pos_idx = 0 if self.is_horizontal() else 1 

1569 segments = np.empty((len(positions), 2, 2)) 

1570 segments[:, :, pos_idx] = np.sort(positions)[:, None] 

1571 segments[:, 0, 1 - pos_idx] = lineoffset + linelength / 2 

1572 segments[:, 1, 1 - pos_idx] = lineoffset - linelength / 2 

1573 self.set_segments(segments) 

1574 

1575 def add_positions(self, position): 

1576 """Add one or more events at the specified positions.""" 

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

1578 len(position) == 0): 

1579 return 

1580 positions = self.get_positions() 

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

1582 self.set_positions(positions) 

1583 extend_positions = append_positions = add_positions 

1584 

1585 def is_horizontal(self): 

1586 """True if the eventcollection is horizontal, False if vertical.""" 

1587 return self._is_horizontal 

1588 

1589 def get_orientation(self): 

1590 """ 

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

1592 """ 

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

1594 

1595 def switch_orientation(self): 

1596 """ 

1597 Switch the orientation of the event line, either from vertical to 

1598 horizontal or vice versus. 

1599 """ 

1600 segments = self.get_segments() 

1601 for i, segment in enumerate(segments): 

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

1603 self.set_segments(segments) 

1604 self._is_horizontal = not self.is_horizontal() 

1605 self.stale = True 

1606 

1607 def set_orientation(self, orientation): 

1608 """ 

1609 Set the orientation of the event line. 

1610 

1611 Parameters 

1612 ---------- 

1613 orientation : {'horizontal', 'vertical'} 

1614 """ 

1615 is_horizontal = _api.check_getitem( 

1616 {"horizontal": True, "vertical": False}, 

1617 orientation=orientation) 

1618 if is_horizontal == self.is_horizontal(): 

1619 return 

1620 self.switch_orientation() 

1621 

1622 def get_linelength(self): 

1623 """Return the length of the lines used to mark each event.""" 

1624 return self._linelength 

1625 

1626 def set_linelength(self, linelength): 

1627 """Set the length of the lines used to mark each event.""" 

1628 if linelength == self.get_linelength(): 

1629 return 

1630 lineoffset = self.get_lineoffset() 

1631 segments = self.get_segments() 

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

1633 for segment in segments: 

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

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

1636 self.set_segments(segments) 

1637 self._linelength = linelength 

1638 

1639 def get_lineoffset(self): 

1640 """Return the offset of the lines used to mark each event.""" 

1641 return self._lineoffset 

1642 

1643 def set_lineoffset(self, lineoffset): 

1644 """Set the offset of the lines used to mark each event.""" 

1645 if lineoffset == self.get_lineoffset(): 

1646 return 

1647 linelength = self.get_linelength() 

1648 segments = self.get_segments() 

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

1650 for segment in segments: 

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

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

1653 self.set_segments(segments) 

1654 self._lineoffset = lineoffset 

1655 

1656 def get_linewidth(self): 

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

1658 return super().get_linewidth()[0] 

1659 

1660 def get_linewidths(self): 

1661 return super().get_linewidth() 

1662 

1663 def get_color(self): 

1664 """Return the color of the lines used to mark each event.""" 

1665 return self.get_colors()[0] 

1666 

1667 

1668class CircleCollection(_CollectionWithSizes): 

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

1670 

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

1672 

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

1674 """ 

1675 Parameters 

1676 ---------- 

1677 sizes : float or array-like 

1678 The area of each circle in points^2. 

1679 **kwargs 

1680 Forwarded to `.Collection`. 

1681 """ 

1682 super().__init__(**kwargs) 

1683 self.set_sizes(sizes) 

1684 self.set_transform(transforms.IdentityTransform()) 

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

1686 

1687 

1688class EllipseCollection(Collection): 

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

1690 

1691 @_api.make_keyword_only("3.6", name="units") 

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

1693 """ 

1694 Parameters 

1695 ---------- 

1696 widths : array-like 

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

1698 heights : array-like 

1699 The lengths of second axes. 

1700 angles : array-like 

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

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

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

1704 'height' refer to the dimensions of the axes, while 'x' and 'y' 

1705 refer to the *offsets* data units. 'xy' differs from all others in 

1706 that the angle as plotted varies with the aspect ratio, and equals 

1707 the specified angle only when the aspect ratio is unity. Hence 

1708 it behaves the same as the `~.patches.Ellipse` with 

1709 ``axes.transData`` as its transform. 

1710 **kwargs 

1711 Forwarded to `Collection`. 

1712 """ 

1713 super().__init__(**kwargs) 

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

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

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

1717 self._units = units 

1718 self.set_transform(transforms.IdentityTransform()) 

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

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

1721 

1722 def _set_transforms(self): 

1723 """Calculate transforms immediately before drawing.""" 

1724 

1725 ax = self.axes 

1726 fig = self.figure 

1727 

1728 if self._units == 'xy': 

1729 sc = 1 

1730 elif self._units == 'x': 

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

1732 elif self._units == 'y': 

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

1734 elif self._units == 'inches': 

1735 sc = fig.dpi 

1736 elif self._units == 'points': 

1737 sc = fig.dpi / 72.0 

1738 elif self._units == 'width': 

1739 sc = ax.bbox.width 

1740 elif self._units == 'height': 

1741 sc = ax.bbox.height 

1742 elif self._units == 'dots': 

1743 sc = 1.0 

1744 else: 

1745 raise ValueError(f'Unrecognized units: {self._units!r}') 

1746 

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

1748 widths = self._widths * sc 

1749 heights = self._heights * sc 

1750 sin_angle = np.sin(self._angles) 

1751 cos_angle = np.cos(self._angles) 

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

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

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

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

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

1757 

1758 _affine = transforms.Affine2D 

1759 if self._units == 'xy': 

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

1761 m[:2, 2:] = 0 

1762 self.set_transform(_affine(m)) 

1763 

1764 @artist.allow_rasterization 

1765 def draw(self, renderer): 

1766 self._set_transforms() 

1767 super().draw(renderer) 

1768 

1769 

1770class PatchCollection(Collection): 

1771 """ 

1772 A generic collection of patches. 

1773 

1774 PatchCollection draws faster than a large number of equivalent individual 

1775 Patches. It also makes it easier to assign a colormap to a heterogeneous 

1776 collection of patches. 

1777 """ 

1778 

1779 @_api.make_keyword_only("3.6", name="match_original") 

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

1781 """ 

1782 Parameters 

1783 ---------- 

1784 patches : list of `.Patch` 

1785 A sequence of Patch objects. This list may include 

1786 a heterogeneous assortment of different patch types. 

1787 

1788 match_original : bool, default: False 

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

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

1791 providing the standard collection arguments, facecolor, 

1792 edgecolor, linewidths, norm or cmap. 

1793 

1794 **kwargs 

1795 All other parameters are forwarded to `.Collection`. 

1796 

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

1798 are None, they default to their `.rcParams` patch setting, in 

1799 sequence form. 

1800 

1801 Notes 

1802 ----- 

1803 The use of `~matplotlib.cm.ScalarMappable` functionality is optional. 

1804 If the `~matplotlib.cm.ScalarMappable` matrix ``_A`` has been set (via 

1805 a call to `~.ScalarMappable.set_array`), at draw time a call to scalar 

1806 mappable will be made to set the face colors. 

1807 """ 

1808 

1809 if match_original: 

1810 def determine_facecolor(patch): 

1811 if patch.get_fill(): 

1812 return patch.get_facecolor() 

1813 return [0, 0, 0, 0] 

1814 

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

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

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

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

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

1820 

1821 super().__init__(**kwargs) 

1822 

1823 self.set_paths(patches) 

1824 

1825 def set_paths(self, patches): 

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

1827 for p in patches] 

1828 self._paths = paths 

1829 

1830 

1831class TriMesh(Collection): 

1832 """ 

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

1834 

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

1836 """ 

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

1838 super().__init__(**kwargs) 

1839 self._triangulation = triangulation 

1840 self._shading = 'gouraud' 

1841 

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

1843 

1844 # Unfortunately this requires a copy, unless Triangulation 

1845 # was rewritten. 

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

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

1848 self._bbox.update_from_data_xy(xy) 

1849 

1850 def get_paths(self): 

1851 if self._paths is None: 

1852 self.set_paths() 

1853 return self._paths 

1854 

1855 def set_paths(self): 

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

1857 

1858 @staticmethod 

1859 def convert_mesh_to_paths(tri): 

1860 """ 

1861 Convert a given mesh into a sequence of `.Path` objects. 

1862 

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

1864 not directly support meshes. 

1865 """ 

1866 triangles = tri.get_masked_triangles() 

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

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

1869 

1870 @artist.allow_rasterization 

1871 def draw(self, renderer): 

1872 if not self.get_visible(): 

1873 return 

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

1875 transform = self.get_transform() 

1876 

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

1878 tri = self._triangulation 

1879 triangles = tri.get_masked_triangles() 

1880 

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

1882 

1883 self.update_scalarmappable() 

1884 colors = self._facecolors[triangles] 

1885 

1886 gc = renderer.new_gc() 

1887 self._set_gc_clip(gc) 

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

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

1890 gc.restore() 

1891 renderer.close_group(self.__class__.__name__) 

1892 

1893 

1894class QuadMesh(Collection): 

1895 r""" 

1896 Class for the efficient drawing of a quadrilateral mesh. 

1897 

1898 A quadrilateral mesh is a grid of M by N adjacent quadrilaterals that are 

1899 defined via a (M+1, N+1) grid of vertices. The quadrilateral (m, n) is 

1900 defined by the vertices :: 

1901 

1902 (m+1, n) ----------- (m+1, n+1) 

1903 / / 

1904 / / 

1905 / / 

1906 (m, n) -------- (m, n+1) 

1907 

1908 The mesh need not be regular and the polygons need not be convex. 

1909 

1910 Parameters 

1911 ---------- 

1912 coordinates : (M+1, N+1, 2) array-like 

1913 The vertices. ``coordinates[m, n]`` specifies the (x, y) coordinates 

1914 of vertex (m, n). 

1915 

1916 antialiased : bool, default: True 

1917 

1918 shading : {'flat', 'gouraud'}, default: 'flat' 

1919 

1920 Notes 

1921 ----- 

1922 Unlike other `.Collection`\s, the default *pickradius* of `.QuadMesh` is 0, 

1923 i.e. `~.Artist.contains` checks whether the test point is within any of the 

1924 mesh quadrilaterals. 

1925 

1926 There exists a deprecated API version ``QuadMesh(M, N, coords)``, where 

1927 the dimensions are given explicitly and ``coords`` is a (M*N, 2) 

1928 array-like. This has been deprecated in Matplotlib 3.5. The following 

1929 describes the semantics of this deprecated API. 

1930 

1931 A quadrilateral mesh consists of a grid of vertices. 

1932 The dimensions of this array are (*meshWidth* + 1, *meshHeight* + 1). 

1933 Each vertex in the mesh has a different set of "mesh coordinates" 

1934 representing its position in the topology of the mesh. 

1935 For any values (*m*, *n*) such that 0 <= *m* <= *meshWidth* 

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

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

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

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

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

1941 

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

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

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

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

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

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

1948 coordinates of the lower left vertex, in the case of the colors). 

1949 

1950 For example, the first entry in *coordinates* is the coordinates of the 

1951 vertex at mesh coordinates (0, 0), then the one at (0, 1), then at (0, 2) 

1952 .. (0, meshWidth), (1, 0), (1, 1), and so on. 

1953 """ 

1954 

1955 def __init__(self, *args, **kwargs): 

1956 # signature deprecation since="3.5": Change to new signature after the 

1957 # deprecation has expired. Also remove setting __init__.__signature__, 

1958 # and remove the Notes from the docstring. 

1959 params = _api.select_matching_signature( 

1960 [ 

1961 lambda meshWidth, meshHeight, coordinates, antialiased=True, 

1962 shading='flat', **kwargs: locals(), 

1963 lambda coordinates, antialiased=True, shading='flat', **kwargs: 

1964 locals() 

1965 ], 

1966 *args, **kwargs).values() 

1967 *old_w_h, coords, antialiased, shading, kwargs = params 

1968 if old_w_h: # The old signature matched. 

1969 _api.warn_deprecated( 

1970 "3.5", 

1971 message="This usage of Quadmesh is deprecated: Parameters " 

1972 "meshWidth and meshHeights will be removed; " 

1973 "coordinates must be 2D; all parameters except " 

1974 "coordinates will be keyword-only.") 

1975 w, h = old_w_h 

1976 coords = np.asarray(coords, np.float64).reshape((h + 1, w + 1, 2)) 

1977 kwargs.setdefault("pickradius", 0) 

1978 # end of signature deprecation code 

1979 

1980 _api.check_shape((None, None, 2), coordinates=coords) 

1981 self._coordinates = coords 

1982 self._antialiased = antialiased 

1983 self._shading = shading 

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

1985 self._bbox.update_from_data_xy(self._coordinates.reshape(-1, 2)) 

1986 # super init delayed after own init because array kwarg requires 

1987 # self._coordinates and self._shading 

1988 super().__init__(**kwargs) 

1989 self.set_mouseover(False) 

1990 

1991 # Only needed during signature deprecation 

1992 __init__.__signature__ = inspect.signature( 

1993 lambda self, coordinates, *, 

1994 antialiased=True, shading='flat', pickradius=0, **kwargs: None) 

1995 

1996 def get_paths(self): 

1997 if self._paths is None: 

1998 self.set_paths() 

1999 return self._paths 

2000 

2001 def set_paths(self): 

2002 self._paths = self._convert_mesh_to_paths(self._coordinates) 

2003 self.stale = True 

2004 

2005 def set_array(self, A): 

2006 """ 

2007 Set the data values. 

2008 

2009 Parameters 

2010 ---------- 

2011 A : (M, N) array-like or M*N array-like 

2012 If the values are provided as a 2D grid, the shape must match the 

2013 coordinates grid. If the values are 1D, they are reshaped to 2D. 

2014 M, N follow from the coordinates grid, where the coordinates grid 

2015 shape is (M, N) for 'gouraud' *shading* and (M+1, N+1) for 'flat' 

2016 shading. 

2017 """ 

2018 height, width = self._coordinates.shape[0:-1] 

2019 misshapen_data = False 

2020 faulty_data = False 

2021 

2022 if self._shading == 'flat': 

2023 h, w = height-1, width-1 

2024 else: 

2025 h, w = height, width 

2026 

2027 if A is not None: 

2028 shape = np.shape(A) 

2029 if len(shape) == 1: 

2030 if shape[0] != (h*w): 

2031 faulty_data = True 

2032 elif shape != (h, w): 

2033 if np.prod(shape) == (h * w): 

2034 misshapen_data = True 

2035 else: 

2036 faulty_data = True 

2037 

2038 if misshapen_data: 

2039 _api.warn_deprecated( 

2040 "3.5", message=f"For X ({width}) and Y ({height}) " 

2041 f"with {self._shading} shading, the expected shape of " 

2042 f"A is ({h}, {w}). Passing A ({A.shape}) is deprecated " 

2043 "since %(since)s and will become an error %(removal)s.") 

2044 

2045 if faulty_data: 

2046 raise TypeError( 

2047 f"Dimensions of A {A.shape} are incompatible with " 

2048 f"X ({width}) and/or Y ({height})") 

2049 

2050 return super().set_array(A) 

2051 

2052 def get_datalim(self, transData): 

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

2054 

2055 def get_coordinates(self): 

2056 """ 

2057 Return the vertices of the mesh as an (M+1, N+1, 2) array. 

2058 

2059 M, N are the number of quadrilaterals in the rows / columns of the 

2060 mesh, corresponding to (M+1, N+1) vertices. 

2061 The last dimension specifies the components (x, y). 

2062 """ 

2063 return self._coordinates 

2064 

2065 @staticmethod 

2066 @_api.deprecated("3.5", alternative="`QuadMesh(coordinates).get_paths()" 

2067 "<.QuadMesh.get_paths>`") 

2068 def convert_mesh_to_paths(meshWidth, meshHeight, coordinates): 

2069 return QuadMesh._convert_mesh_to_paths(coordinates) 

2070 

2071 @staticmethod 

2072 def _convert_mesh_to_paths(coordinates): 

2073 """ 

2074 Convert a given mesh into a sequence of `.Path` objects. 

2075 

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

2077 not directly support quadmeshes. 

2078 """ 

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

2080 c = coordinates.data 

2081 else: 

2082 c = coordinates 

2083 points = np.concatenate([ 

2084 c[:-1, :-1], 

2085 c[:-1, 1:], 

2086 c[1:, 1:], 

2087 c[1:, :-1], 

2088 c[:-1, :-1] 

2089 ], axis=2).reshape((-1, 5, 2)) 

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

2091 

2092 @_api.deprecated("3.5") 

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

2094 return self._convert_mesh_to_triangles(coordinates) 

2095 

2096 def _convert_mesh_to_triangles(self, coordinates): 

2097 """ 

2098 Convert a given mesh into a sequence of triangles, each point 

2099 with its own color. The result can be used to construct a call to 

2100 `~.RendererBase.draw_gouraud_triangle`. 

2101 """ 

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

2103 p = coordinates.data 

2104 else: 

2105 p = coordinates 

2106 

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

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

2109 p_c = p[1:, 1:] 

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

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

2112 triangles = np.concatenate([ 

2113 p_a, p_b, p_center, 

2114 p_b, p_c, p_center, 

2115 p_c, p_d, p_center, 

2116 p_d, p_a, p_center, 

2117 ], axis=2).reshape((-1, 3, 2)) 

2118 

2119 c = self.get_facecolor().reshape((*coordinates.shape[:2], 4)) 

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

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

2122 c_c = c[1:, 1:] 

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

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

2125 colors = np.concatenate([ 

2126 c_a, c_b, c_center, 

2127 c_b, c_c, c_center, 

2128 c_c, c_d, c_center, 

2129 c_d, c_a, c_center, 

2130 ], axis=2).reshape((-1, 3, 4)) 

2131 

2132 return triangles, colors 

2133 

2134 @artist.allow_rasterization 

2135 def draw(self, renderer): 

2136 if not self.get_visible(): 

2137 return 

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

2139 transform = self.get_transform() 

2140 offset_trf = self.get_offset_transform() 

2141 offsets = self.get_offsets() 

2142 

2143 if self.have_units(): 

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

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

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

2147 

2148 self.update_scalarmappable() 

2149 

2150 if not transform.is_affine: 

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

2152 coordinates = transform.transform(coordinates) 

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

2154 transform = transforms.IdentityTransform() 

2155 else: 

2156 coordinates = self._coordinates 

2157 

2158 if not offset_trf.is_affine: 

2159 offsets = offset_trf.transform_non_affine(offsets) 

2160 offset_trf = offset_trf.get_affine() 

2161 

2162 gc = renderer.new_gc() 

2163 gc.set_snap(self.get_snap()) 

2164 self._set_gc_clip(gc) 

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

2166 

2167 if self._shading == 'gouraud': 

2168 triangles, colors = self._convert_mesh_to_triangles(coordinates) 

2169 renderer.draw_gouraud_triangles( 

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

2171 else: 

2172 renderer.draw_quad_mesh( 

2173 gc, transform.frozen(), 

2174 coordinates.shape[1] - 1, coordinates.shape[0] - 1, 

2175 coordinates, offsets, offset_trf, 

2176 # Backends expect flattened rgba arrays (n*m, 4) for fc and ec 

2177 self.get_facecolor().reshape((-1, 4)), 

2178 self._antialiased, self.get_edgecolors().reshape((-1, 4))) 

2179 gc.restore() 

2180 renderer.close_group(self.__class__.__name__) 

2181 self.stale = False 

2182 

2183 def get_cursor_data(self, event): 

2184 contained, info = self.contains(event) 

2185 if contained and self.get_array() is not None: 

2186 return self.get_array().ravel()[info["ind"]] 

2187 return None