Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/contour.py: 11%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

728 statements  

1""" 

2Classes to support contour plotting and labelling for the Axes class. 

3""" 

4 

5from contextlib import ExitStack 

6import functools 

7import math 

8from numbers import Integral 

9 

10import numpy as np 

11from numpy import ma 

12 

13import matplotlib as mpl 

14from matplotlib import _api, _docstring 

15from matplotlib.backend_bases import MouseButton 

16from matplotlib.lines import Line2D 

17from matplotlib.path import Path 

18from matplotlib.text import Text 

19import matplotlib.ticker as ticker 

20import matplotlib.cm as cm 

21import matplotlib.colors as mcolors 

22import matplotlib.collections as mcoll 

23import matplotlib.font_manager as font_manager 

24import matplotlib.cbook as cbook 

25import matplotlib.patches as mpatches 

26import matplotlib.transforms as mtransforms 

27 

28 

29def _contour_labeler_event_handler(cs, inline, inline_spacing, event): 

30 canvas = cs.axes.figure.canvas 

31 is_button = event.name == "button_press_event" 

32 is_key = event.name == "key_press_event" 

33 # Quit (even if not in infinite mode; this is consistent with 

34 # MATLAB and sometimes quite useful, but will require the user to 

35 # test how many points were actually returned before using data). 

36 if (is_button and event.button == MouseButton.MIDDLE 

37 or is_key and event.key in ["escape", "enter"]): 

38 canvas.stop_event_loop() 

39 # Pop last click. 

40 elif (is_button and event.button == MouseButton.RIGHT 

41 or is_key and event.key in ["backspace", "delete"]): 

42 # Unfortunately, if one is doing inline labels, then there is currently 

43 # no way to fix the broken contour - once humpty-dumpty is broken, he 

44 # can't be put back together. In inline mode, this does nothing. 

45 if not inline: 

46 cs.pop_label() 

47 canvas.draw() 

48 # Add new click. 

49 elif (is_button and event.button == MouseButton.LEFT 

50 # On macOS/gtk, some keys return None. 

51 or is_key and event.key is not None): 

52 if cs.axes.contains(event)[0]: 

53 cs.add_label_near(event.x, event.y, transform=False, 

54 inline=inline, inline_spacing=inline_spacing) 

55 canvas.draw() 

56 

57 

58class ContourLabeler: 

59 """Mixin to provide labelling capability to `.ContourSet`.""" 

60 

61 def clabel(self, levels=None, *, 

62 fontsize=None, inline=True, inline_spacing=5, fmt=None, 

63 colors=None, use_clabeltext=False, manual=False, 

64 rightside_up=True, zorder=None): 

65 """ 

66 Label a contour plot. 

67 

68 Adds labels to line contours in this `.ContourSet` (which inherits from 

69 this mixin class). 

70 

71 Parameters 

72 ---------- 

73 levels : array-like, optional 

74 A list of level values, that should be labeled. The list must be 

75 a subset of ``cs.levels``. If not given, all levels are labeled. 

76 

77 fontsize : str or float, default: :rc:`font.size` 

78 Size in points or relative size e.g., 'smaller', 'x-large'. 

79 See `.Text.set_size` for accepted string values. 

80 

81 colors : :mpltype:`color` or colors or None, default: None 

82 The label colors: 

83 

84 - If *None*, the color of each label matches the color of 

85 the corresponding contour. 

86 

87 - If one string color, e.g., *colors* = 'r' or *colors* = 

88 'red', all labels will be plotted in this color. 

89 

90 - If a tuple of colors (string, float, RGB, etc), different labels 

91 will be plotted in different colors in the order specified. 

92 

93 inline : bool, default: True 

94 If ``True`` the underlying contour is removed where the label is 

95 placed. 

96 

97 inline_spacing : float, default: 5 

98 Space in pixels to leave on each side of label when placing inline. 

99 

100 This spacing will be exact for labels at locations where the 

101 contour is straight, less so for labels on curved contours. 

102 

103 fmt : `.Formatter` or str or callable or dict, optional 

104 How the levels are formatted: 

105 

106 - If a `.Formatter`, it is used to format all levels at once, using 

107 its `.Formatter.format_ticks` method. 

108 - If a str, it is interpreted as a %-style format string. 

109 - If a callable, it is called with one level at a time and should 

110 return the corresponding label. 

111 - If a dict, it should directly map levels to labels. 

112 

113 The default is to use a standard `.ScalarFormatter`. 

114 

115 manual : bool or iterable, default: False 

116 If ``True``, contour labels will be placed manually using 

117 mouse clicks. Click the first button near a contour to 

118 add a label, click the second button (or potentially both 

119 mouse buttons at once) to finish adding labels. The third 

120 button can be used to remove the last label added, but 

121 only if labels are not inline. Alternatively, the keyboard 

122 can be used to select label locations (enter to end label 

123 placement, delete or backspace act like the third mouse button, 

124 and any other key will select a label location). 

125 

126 *manual* can also be an iterable object of (x, y) tuples. 

127 Contour labels will be created as if mouse is clicked at each 

128 (x, y) position. 

129 

130 rightside_up : bool, default: True 

131 If ``True``, label rotations will always be plus 

132 or minus 90 degrees from level. 

133 

134 use_clabeltext : bool, default: False 

135 If ``True``, use `.Text.set_transform_rotates_text` to ensure that 

136 label rotation is updated whenever the Axes aspect changes. 

137 

138 zorder : float or None, default: ``(2 + contour.get_zorder())`` 

139 zorder of the contour labels. 

140 

141 Returns 

142 ------- 

143 labels 

144 A list of `.Text` instances for the labels. 

145 """ 

146 

147 # Based on the input arguments, clabel() adds a list of "label 

148 # specific" attributes to the ContourSet object. These attributes are 

149 # all of the form label* and names should be fairly self explanatory. 

150 # 

151 # Once these attributes are set, clabel passes control to the labels() 

152 # method (for automatic label placement) or blocking_input_loop and 

153 # _contour_labeler_event_handler (for manual label placement). 

154 

155 if fmt is None: 

156 fmt = ticker.ScalarFormatter(useOffset=False) 

157 fmt.create_dummy_axis() 

158 self.labelFmt = fmt 

159 self._use_clabeltext = use_clabeltext 

160 self.labelManual = manual 

161 self.rightside_up = rightside_up 

162 self._clabel_zorder = 2 + self.get_zorder() if zorder is None else zorder 

163 

164 if levels is None: 

165 levels = self.levels 

166 indices = list(range(len(self.cvalues))) 

167 else: 

168 levlabs = list(levels) 

169 indices, levels = [], [] 

170 for i, lev in enumerate(self.levels): 

171 if lev in levlabs: 

172 indices.append(i) 

173 levels.append(lev) 

174 if len(levels) < len(levlabs): 

175 raise ValueError(f"Specified levels {levlabs} don't match " 

176 f"available levels {self.levels}") 

177 self.labelLevelList = levels 

178 self.labelIndiceList = indices 

179 

180 self._label_font_props = font_manager.FontProperties(size=fontsize) 

181 

182 if colors is None: 

183 self.labelMappable = self 

184 self.labelCValueList = np.take(self.cvalues, self.labelIndiceList) 

185 else: 

186 cmap = mcolors.ListedColormap(colors, N=len(self.labelLevelList)) 

187 self.labelCValueList = list(range(len(self.labelLevelList))) 

188 self.labelMappable = cm.ScalarMappable(cmap=cmap, 

189 norm=mcolors.NoNorm()) 

190 

191 self.labelXYs = [] 

192 

193 if np.iterable(manual): 

194 for x, y in manual: 

195 self.add_label_near(x, y, inline, inline_spacing) 

196 elif manual: 

197 print('Select label locations manually using first mouse button.') 

198 print('End manual selection with second mouse button.') 

199 if not inline: 

200 print('Remove last label by clicking third mouse button.') 

201 mpl._blocking_input.blocking_input_loop( 

202 self.axes.figure, ["button_press_event", "key_press_event"], 

203 timeout=-1, handler=functools.partial( 

204 _contour_labeler_event_handler, 

205 self, inline, inline_spacing)) 

206 else: 

207 self.labels(inline, inline_spacing) 

208 

209 return cbook.silent_list('text.Text', self.labelTexts) 

210 

211 def print_label(self, linecontour, labelwidth): 

212 """Return whether a contour is long enough to hold a label.""" 

213 return (len(linecontour) > 10 * labelwidth 

214 or (len(linecontour) 

215 and (np.ptp(linecontour, axis=0) > 1.2 * labelwidth).any())) 

216 

217 def too_close(self, x, y, lw): 

218 """Return whether a label is already near this location.""" 

219 thresh = (1.2 * lw) ** 2 

220 return any((x - loc[0]) ** 2 + (y - loc[1]) ** 2 < thresh 

221 for loc in self.labelXYs) 

222 

223 def _get_nth_label_width(self, nth): 

224 """Return the width of the *nth* label, in pixels.""" 

225 fig = self.axes.figure 

226 renderer = fig._get_renderer() 

227 return (Text(0, 0, 

228 self.get_text(self.labelLevelList[nth], self.labelFmt), 

229 figure=fig, fontproperties=self._label_font_props) 

230 .get_window_extent(renderer).width) 

231 

232 def get_text(self, lev, fmt): 

233 """Get the text of the label.""" 

234 if isinstance(lev, str): 

235 return lev 

236 elif isinstance(fmt, dict): 

237 return fmt.get(lev, '%1.3f') 

238 elif callable(getattr(fmt, "format_ticks", None)): 

239 return fmt.format_ticks([*self.labelLevelList, lev])[-1] 

240 elif callable(fmt): 

241 return fmt(lev) 

242 else: 

243 return fmt % lev 

244 

245 def locate_label(self, linecontour, labelwidth): 

246 """ 

247 Find good place to draw a label (relatively flat part of the contour). 

248 """ 

249 ctr_size = len(linecontour) 

250 n_blocks = int(np.ceil(ctr_size / labelwidth)) if labelwidth > 1 else 1 

251 block_size = ctr_size if n_blocks == 1 else int(labelwidth) 

252 # Split contour into blocks of length ``block_size``, filling the last 

253 # block by cycling the contour start (per `np.resize` semantics). (Due 

254 # to cycling, the index returned is taken modulo ctr_size.) 

255 xx = np.resize(linecontour[:, 0], (n_blocks, block_size)) 

256 yy = np.resize(linecontour[:, 1], (n_blocks, block_size)) 

257 yfirst = yy[:, :1] 

258 ylast = yy[:, -1:] 

259 xfirst = xx[:, :1] 

260 xlast = xx[:, -1:] 

261 s = (yfirst - yy) * (xlast - xfirst) - (xfirst - xx) * (ylast - yfirst) 

262 l = np.hypot(xlast - xfirst, ylast - yfirst) 

263 # Ignore warning that divide by zero throws, as this is a valid option 

264 with np.errstate(divide='ignore', invalid='ignore'): 

265 distances = (abs(s) / l).sum(axis=-1) 

266 # Labels are drawn in the middle of the block (``hbsize``) where the 

267 # contour is the closest (per ``distances``) to a straight line, but 

268 # not `too_close()` to a preexisting label. 

269 hbsize = block_size // 2 

270 adist = np.argsort(distances) 

271 # If all candidates are `too_close()`, go back to the straightest part 

272 # (``adist[0]``). 

273 for idx in np.append(adist, adist[0]): 

274 x, y = xx[idx, hbsize], yy[idx, hbsize] 

275 if not self.too_close(x, y, labelwidth): 

276 break 

277 return x, y, (idx * block_size + hbsize) % ctr_size 

278 

279 def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing=5): 

280 """ 

281 Prepare for insertion of a label at index *idx* of *path*. 

282 

283 Parameters 

284 ---------- 

285 path : Path 

286 The path where the label will be inserted, in data space. 

287 idx : int 

288 The vertex index after which the label will be inserted. 

289 screen_pos : (float, float) 

290 The position where the label will be inserted, in screen space. 

291 lw : float 

292 The label width, in screen space. 

293 spacing : float 

294 Extra spacing around the label, in screen space. 

295 

296 Returns 

297 ------- 

298 path : Path 

299 The path, broken so that the label can be drawn over it. 

300 angle : float 

301 The rotation of the label. 

302 

303 Notes 

304 ----- 

305 Both tasks are done together to avoid calculating path lengths multiple times, 

306 which is relatively costly. 

307 

308 The method used here involves computing the path length along the contour in 

309 pixel coordinates and then looking (label width / 2) away from central point to 

310 determine rotation and then to break contour if desired. The extra spacing is 

311 taken into account when breaking the path, but not when computing the angle. 

312 """ 

313 if hasattr(self, "_old_style_split_collections"): 

314 vis = False 

315 for coll in self._old_style_split_collections: 

316 vis |= coll.get_visible() 

317 coll.remove() 

318 self.set_visible(vis) 

319 del self._old_style_split_collections # Invalidate them. 

320 

321 xys = path.vertices 

322 codes = path.codes 

323 

324 # Insert a vertex at idx/pos (converting back to data space), if there isn't yet 

325 # a vertex there. With infinite precision one could also always insert the 

326 # extra vertex (it will get masked out by the label below anyways), but floating 

327 # point inaccuracies (the point can have undergone a data->screen->data 

328 # transform loop) can slightly shift the point and e.g. shift the angle computed 

329 # below from exactly zero to nonzero. 

330 pos = self.get_transform().inverted().transform(screen_pos) 

331 if not np.allclose(pos, xys[idx]): 

332 xys = np.insert(xys, idx, pos, axis=0) 

333 codes = np.insert(codes, idx, Path.LINETO) 

334 

335 # Find the connected component where the label will be inserted. Note that a 

336 # path always starts with a MOVETO, and we consider there's an implicit 

337 # MOVETO (closing the last path) at the end. 

338 movetos = (codes == Path.MOVETO).nonzero()[0] 

339 start = movetos[movetos <= idx][-1] 

340 try: 

341 stop = movetos[movetos > idx][0] 

342 except IndexError: 

343 stop = len(codes) 

344 

345 # Restrict ourselves to the connected component. 

346 cc_xys = xys[start:stop] 

347 idx -= start 

348 

349 # If the path is closed, rotate it s.t. it starts at the label. 

350 is_closed_path = codes[stop - 1] == Path.CLOSEPOLY 

351 if is_closed_path: 

352 cc_xys = np.concatenate([cc_xys[idx:-1], cc_xys[:idx+1]]) 

353 idx = 0 

354 

355 # Like np.interp, but additionally vectorized over fp. 

356 def interp_vec(x, xp, fp): return [np.interp(x, xp, col) for col in fp.T] 

357 

358 # Use cumulative path lengths ("cpl") as curvilinear coordinate along contour. 

359 screen_xys = self.get_transform().transform(cc_xys) 

360 path_cpls = np.insert( 

361 np.cumsum(np.hypot(*np.diff(screen_xys, axis=0).T)), 0, 0) 

362 path_cpls -= path_cpls[idx] 

363 

364 # Use linear interpolation to get end coordinates of label. 

365 target_cpls = np.array([-lw/2, lw/2]) 

366 if is_closed_path: # For closed paths, target from the other end. 

367 target_cpls[0] += (path_cpls[-1] - path_cpls[0]) 

368 (sx0, sx1), (sy0, sy1) = interp_vec(target_cpls, path_cpls, screen_xys) 

369 angle = np.rad2deg(np.arctan2(sy1 - sy0, sx1 - sx0)) # Screen space. 

370 if self.rightside_up: # Fix angle so text is never upside-down 

371 angle = (angle + 90) % 180 - 90 

372 

373 target_cpls += [-spacing, +spacing] # Expand range by spacing. 

374 

375 # Get indices near points of interest; use -1 as out of bounds marker. 

376 i0, i1 = np.interp(target_cpls, path_cpls, range(len(path_cpls)), 

377 left=-1, right=-1) 

378 i0 = math.floor(i0) 

379 i1 = math.ceil(i1) 

380 (x0, x1), (y0, y1) = interp_vec(target_cpls, path_cpls, cc_xys) 

381 

382 # Actually break contours (dropping zero-len parts). 

383 new_xy_blocks = [] 

384 new_code_blocks = [] 

385 if is_closed_path: 

386 if i0 != -1 and i1 != -1: 

387 # This is probably wrong in the case that the entire contour would 

388 # be discarded, but ensures that a valid path is returned and is 

389 # consistent with behavior of mpl <3.8 

390 points = cc_xys[i1:i0+1] 

391 new_xy_blocks.extend([[(x1, y1)], points, [(x0, y0)]]) 

392 nlines = len(points) + 1 

393 new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * nlines]) 

394 else: 

395 if i0 != -1: 

396 new_xy_blocks.extend([cc_xys[:i0 + 1], [(x0, y0)]]) 

397 new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * (i0 + 1)]) 

398 if i1 != -1: 

399 new_xy_blocks.extend([[(x1, y1)], cc_xys[i1:]]) 

400 new_code_blocks.extend([ 

401 [Path.MOVETO], [Path.LINETO] * (len(cc_xys) - i1)]) 

402 

403 # Back to the full path. 

404 xys = np.concatenate([xys[:start], *new_xy_blocks, xys[stop:]]) 

405 codes = np.concatenate([codes[:start], *new_code_blocks, codes[stop:]]) 

406 

407 return angle, Path(xys, codes) 

408 

409 @_api.deprecated("3.8") 

410 def calc_label_rot_and_inline(self, slc, ind, lw, lc=None, spacing=5): 

411 """ 

412 Calculate the appropriate label rotation given the linecontour 

413 coordinates in screen units, the index of the label location and the 

414 label width. 

415 

416 If *lc* is not None or empty, also break contours and compute 

417 inlining. 

418 

419 *spacing* is the empty space to leave around the label, in pixels. 

420 

421 Both tasks are done together to avoid calculating path lengths 

422 multiple times, which is relatively costly. 

423 

424 The method used here involves computing the path length along the 

425 contour in pixel coordinates and then looking approximately (label 

426 width / 2) away from central point to determine rotation and then to 

427 break contour if desired. 

428 """ 

429 

430 if lc is None: 

431 lc = [] 

432 # Half the label width 

433 hlw = lw / 2.0 

434 

435 # Check if closed and, if so, rotate contour so label is at edge 

436 closed = _is_closed_polygon(slc) 

437 if closed: 

438 slc = np.concatenate([slc[ind:-1], slc[:ind + 1]]) 

439 if len(lc): # Rotate lc also if not empty 

440 lc = np.concatenate([lc[ind:-1], lc[:ind + 1]]) 

441 ind = 0 

442 

443 # Calculate path lengths 

444 pl = np.zeros(slc.shape[0], dtype=float) 

445 dx = np.diff(slc, axis=0) 

446 pl[1:] = np.cumsum(np.hypot(dx[:, 0], dx[:, 1])) 

447 pl = pl - pl[ind] 

448 

449 # Use linear interpolation to get points around label 

450 xi = np.array([-hlw, hlw]) 

451 if closed: # Look at end also for closed contours 

452 dp = np.array([pl[-1], 0]) 

453 else: 

454 dp = np.zeros_like(xi) 

455 

456 # Get angle of vector between the two ends of the label - must be 

457 # calculated in pixel space for text rotation to work correctly. 

458 (dx,), (dy,) = (np.diff(np.interp(dp + xi, pl, slc_col)) 

459 for slc_col in slc.T) 

460 rotation = np.rad2deg(np.arctan2(dy, dx)) 

461 

462 if self.rightside_up: 

463 # Fix angle so text is never upside-down 

464 rotation = (rotation + 90) % 180 - 90 

465 

466 # Break contour if desired 

467 nlc = [] 

468 if len(lc): 

469 # Expand range by spacing 

470 xi = dp + xi + np.array([-spacing, spacing]) 

471 

472 # Get (integer) indices near points of interest; use -1 as marker 

473 # for out of bounds. 

474 I = np.interp(xi, pl, np.arange(len(pl)), left=-1, right=-1) 

475 I = [np.floor(I[0]).astype(int), np.ceil(I[1]).astype(int)] 

476 if I[0] != -1: 

477 xy1 = [np.interp(xi[0], pl, lc_col) for lc_col in lc.T] 

478 if I[1] != -1: 

479 xy2 = [np.interp(xi[1], pl, lc_col) for lc_col in lc.T] 

480 

481 # Actually break contours 

482 if closed: 

483 # This will remove contour if shorter than label 

484 if all(i != -1 for i in I): 

485 nlc.append(np.vstack([xy2, lc[I[1]:I[0]+1], xy1])) 

486 else: 

487 # These will remove pieces of contour if they have length zero 

488 if I[0] != -1: 

489 nlc.append(np.vstack([lc[:I[0]+1], xy1])) 

490 if I[1] != -1: 

491 nlc.append(np.vstack([xy2, lc[I[1]:]])) 

492 

493 # The current implementation removes contours completely 

494 # covered by labels. Uncomment line below to keep 

495 # original contour if this is the preferred behavior. 

496 # if not len(nlc): nlc = [lc] 

497 

498 return rotation, nlc 

499 

500 def add_label(self, x, y, rotation, lev, cvalue): 

501 """Add a contour label, respecting whether *use_clabeltext* was set.""" 

502 data_x, data_y = self.axes.transData.inverted().transform((x, y)) 

503 t = Text( 

504 data_x, data_y, 

505 text=self.get_text(lev, self.labelFmt), 

506 rotation=rotation, 

507 horizontalalignment='center', verticalalignment='center', 

508 zorder=self._clabel_zorder, 

509 color=self.labelMappable.to_rgba(cvalue, alpha=self.get_alpha()), 

510 fontproperties=self._label_font_props, 

511 clip_box=self.axes.bbox) 

512 if self._use_clabeltext: 

513 data_rotation, = self.axes.transData.inverted().transform_angles( 

514 [rotation], [[x, y]]) 

515 t.set(rotation=data_rotation, transform_rotates_text=True) 

516 self.labelTexts.append(t) 

517 self.labelCValues.append(cvalue) 

518 self.labelXYs.append((x, y)) 

519 # Add label to plot here - useful for manual mode label selection 

520 self.axes.add_artist(t) 

521 

522 @_api.deprecated("3.8", alternative="add_label") 

523 def add_label_clabeltext(self, x, y, rotation, lev, cvalue): 

524 """Add contour label with `.Text.set_transform_rotates_text`.""" 

525 with cbook._setattr_cm(self, _use_clabeltext=True): 

526 self.add_label(x, y, rotation, lev, cvalue) 

527 

528 def add_label_near(self, x, y, inline=True, inline_spacing=5, 

529 transform=None): 

530 """ 

531 Add a label near the point ``(x, y)``. 

532 

533 Parameters 

534 ---------- 

535 x, y : float 

536 The approximate location of the label. 

537 inline : bool, default: True 

538 If *True* remove the segment of the contour beneath the label. 

539 inline_spacing : int, default: 5 

540 Space in pixels to leave on each side of label when placing 

541 inline. This spacing will be exact for labels at locations where 

542 the contour is straight, less so for labels on curved contours. 

543 transform : `.Transform` or `False`, default: ``self.axes.transData`` 

544 A transform applied to ``(x, y)`` before labeling. The default 

545 causes ``(x, y)`` to be interpreted as data coordinates. `False` 

546 is a synonym for `.IdentityTransform`; i.e. ``(x, y)`` should be 

547 interpreted as display coordinates. 

548 """ 

549 

550 if transform is None: 

551 transform = self.axes.transData 

552 if transform: 

553 x, y = transform.transform((x, y)) 

554 

555 idx_level_min, idx_vtx_min, proj = self._find_nearest_contour( 

556 (x, y), self.labelIndiceList) 

557 path = self._paths[idx_level_min] 

558 level = self.labelIndiceList.index(idx_level_min) 

559 label_width = self._get_nth_label_width(level) 

560 rotation, path = self._split_path_and_get_label_rotation( 

561 path, idx_vtx_min, proj, label_width, inline_spacing) 

562 self.add_label(*proj, rotation, self.labelLevelList[idx_level_min], 

563 self.labelCValueList[idx_level_min]) 

564 

565 if inline: 

566 self._paths[idx_level_min] = path 

567 

568 def pop_label(self, index=-1): 

569 """Defaults to removing last label, but any index can be supplied""" 

570 self.labelCValues.pop(index) 

571 t = self.labelTexts.pop(index) 

572 t.remove() 

573 

574 def labels(self, inline, inline_spacing): 

575 for idx, (icon, lev, cvalue) in enumerate(zip( 

576 self.labelIndiceList, 

577 self.labelLevelList, 

578 self.labelCValueList, 

579 )): 

580 trans = self.get_transform() 

581 label_width = self._get_nth_label_width(idx) 

582 additions = [] 

583 for subpath in self._paths[icon]._iter_connected_components(): 

584 screen_xys = trans.transform(subpath.vertices) 

585 # Check if long enough for a label 

586 if self.print_label(screen_xys, label_width): 

587 x, y, idx = self.locate_label(screen_xys, label_width) 

588 rotation, path = self._split_path_and_get_label_rotation( 

589 subpath, idx, (x, y), 

590 label_width, inline_spacing) 

591 self.add_label(x, y, rotation, lev, cvalue) # Really add label. 

592 if inline: # If inline, add new contours 

593 additions.append(path) 

594 else: # If not adding label, keep old path 

595 additions.append(subpath) 

596 # After looping over all segments on a contour, replace old path by new one 

597 # if inlining. 

598 if inline: 

599 self._paths[icon] = Path.make_compound_path(*additions) 

600 

601 def remove(self): 

602 super().remove() 

603 for text in self.labelTexts: 

604 text.remove() 

605 

606 

607def _is_closed_polygon(X): 

608 """ 

609 Return whether first and last object in a sequence are the same. These are 

610 presumably coordinates on a polygonal curve, in which case this function 

611 tests if that curve is closed. 

612 """ 

613 return np.allclose(X[0], X[-1], rtol=1e-10, atol=1e-13) 

614 

615 

616def _find_closest_point_on_path(xys, p): 

617 """ 

618 Parameters 

619 ---------- 

620 xys : (N, 2) array-like 

621 Coordinates of vertices. 

622 p : (float, float) 

623 Coordinates of point. 

624 

625 Returns 

626 ------- 

627 d2min : float 

628 Minimum square distance of *p* to *xys*. 

629 proj : (float, float) 

630 Projection of *p* onto *xys*. 

631 imin : (int, int) 

632 Consecutive indices of vertices of segment in *xys* where *proj* is. 

633 Segments are considered as including their end-points; i.e. if the 

634 closest point on the path is a node in *xys* with index *i*, this 

635 returns ``(i-1, i)``. For the special case where *xys* is a single 

636 point, this returns ``(0, 0)``. 

637 """ 

638 if len(xys) == 1: 

639 return (((p - xys[0]) ** 2).sum(), xys[0], (0, 0)) 

640 dxys = xys[1:] - xys[:-1] # Individual segment vectors. 

641 norms = (dxys ** 2).sum(axis=1) 

642 norms[norms == 0] = 1 # For zero-length segment, replace 0/0 by 0/1. 

643 rel_projs = np.clip( # Project onto each segment in relative 0-1 coords. 

644 ((p - xys[:-1]) * dxys).sum(axis=1) / norms, 

645 0, 1)[:, None] 

646 projs = xys[:-1] + rel_projs * dxys # Projs. onto each segment, in (x, y). 

647 d2s = ((projs - p) ** 2).sum(axis=1) # Squared distances. 

648 imin = np.argmin(d2s) 

649 return (d2s[imin], projs[imin], (imin, imin+1)) 

650 

651 

652_docstring.interpd.update(contour_set_attributes=r""" 

653Attributes 

654---------- 

655ax : `~matplotlib.axes.Axes` 

656 The Axes object in which the contours are drawn. 

657 

658collections : `.silent_list` of `.PathCollection`\s 

659 The `.Artist`\s representing the contour. This is a list of 

660 `.PathCollection`\s for both line and filled contours. 

661 

662levels : array 

663 The values of the contour levels. 

664 

665layers : array 

666 Same as levels for line contours; half-way between 

667 levels for filled contours. See ``ContourSet._process_colors``. 

668""") 

669 

670 

671@_docstring.dedent_interpd 

672class ContourSet(ContourLabeler, mcoll.Collection): 

673 """ 

674 Store a set of contour lines or filled regions. 

675 

676 User-callable method: `~.Axes.clabel` 

677 

678 Parameters 

679 ---------- 

680 ax : `~matplotlib.axes.Axes` 

681 

682 levels : [level0, level1, ..., leveln] 

683 A list of floating point numbers indicating the contour levels. 

684 

685 allsegs : [level0segs, level1segs, ...] 

686 List of all the polygon segments for all the *levels*. 

687 For contour lines ``len(allsegs) == len(levels)``, and for 

688 filled contour regions ``len(allsegs) = len(levels)-1``. The lists 

689 should look like :: 

690 

691 level0segs = [polygon0, polygon1, ...] 

692 polygon0 = [[x0, y0], [x1, y1], ...] 

693 

694 allkinds : ``None`` or [level0kinds, level1kinds, ...] 

695 Optional list of all the polygon vertex kinds (code types), as 

696 described and used in Path. This is used to allow multiply- 

697 connected paths such as holes within filled polygons. 

698 If not ``None``, ``len(allkinds) == len(allsegs)``. The lists 

699 should look like :: 

700 

701 level0kinds = [polygon0kinds, ...] 

702 polygon0kinds = [vertexcode0, vertexcode1, ...] 

703 

704 If *allkinds* is not ``None``, usually all polygons for a 

705 particular contour level are grouped together so that 

706 ``level0segs = [polygon0]`` and ``level0kinds = [polygon0kinds]``. 

707 

708 **kwargs 

709 Keyword arguments are as described in the docstring of 

710 `~.Axes.contour`. 

711 

712 %(contour_set_attributes)s 

713 """ 

714 

715 def __init__(self, ax, *args, 

716 levels=None, filled=False, linewidths=None, linestyles=None, 

717 hatches=(None,), alpha=None, origin=None, extent=None, 

718 cmap=None, colors=None, norm=None, vmin=None, vmax=None, 

719 extend='neither', antialiased=None, nchunk=0, locator=None, 

720 transform=None, negative_linestyles=None, clip_path=None, 

721 **kwargs): 

722 """ 

723 Draw contour lines or filled regions, depending on 

724 whether keyword arg *filled* is ``False`` (default) or ``True``. 

725 

726 Call signature:: 

727 

728 ContourSet(ax, levels, allsegs, [allkinds], **kwargs) 

729 

730 Parameters 

731 ---------- 

732 ax : `~matplotlib.axes.Axes` 

733 The `~.axes.Axes` object to draw on. 

734 

735 levels : [level0, level1, ..., leveln] 

736 A list of floating point numbers indicating the contour 

737 levels. 

738 

739 allsegs : [level0segs, level1segs, ...] 

740 List of all the polygon segments for all the *levels*. 

741 For contour lines ``len(allsegs) == len(levels)``, and for 

742 filled contour regions ``len(allsegs) = len(levels)-1``. The lists 

743 should look like :: 

744 

745 level0segs = [polygon0, polygon1, ...] 

746 polygon0 = [[x0, y0], [x1, y1], ...] 

747 

748 allkinds : [level0kinds, level1kinds, ...], optional 

749 Optional list of all the polygon vertex kinds (code types), as 

750 described and used in Path. This is used to allow multiply- 

751 connected paths such as holes within filled polygons. 

752 If not ``None``, ``len(allkinds) == len(allsegs)``. The lists 

753 should look like :: 

754 

755 level0kinds = [polygon0kinds, ...] 

756 polygon0kinds = [vertexcode0, vertexcode1, ...] 

757 

758 If *allkinds* is not ``None``, usually all polygons for a 

759 particular contour level are grouped together so that 

760 ``level0segs = [polygon0]`` and ``level0kinds = [polygon0kinds]``. 

761 

762 **kwargs 

763 Keyword arguments are as described in the docstring of 

764 `~.Axes.contour`. 

765 """ 

766 if antialiased is None and filled: 

767 # Eliminate artifacts; we are not stroking the boundaries. 

768 antialiased = False 

769 # The default for line contours will be taken from the 

770 # LineCollection default, which uses :rc:`lines.antialiased`. 

771 super().__init__( 

772 antialiaseds=antialiased, 

773 alpha=alpha, 

774 clip_path=clip_path, 

775 transform=transform, 

776 ) 

777 self.axes = ax 

778 self.levels = levels 

779 self.filled = filled 

780 self.hatches = hatches 

781 self.origin = origin 

782 self.extent = extent 

783 self.colors = colors 

784 self.extend = extend 

785 

786 self.nchunk = nchunk 

787 self.locator = locator 

788 if (isinstance(norm, mcolors.LogNorm) 

789 or isinstance(self.locator, ticker.LogLocator)): 

790 self.logscale = True 

791 if norm is None: 

792 norm = mcolors.LogNorm() 

793 else: 

794 self.logscale = False 

795 

796 _api.check_in_list([None, 'lower', 'upper', 'image'], origin=origin) 

797 if self.extent is not None and len(self.extent) != 4: 

798 raise ValueError( 

799 "If given, 'extent' must be None or (x0, x1, y0, y1)") 

800 if self.colors is not None and cmap is not None: 

801 raise ValueError('Either colors or cmap must be None') 

802 if self.origin == 'image': 

803 self.origin = mpl.rcParams['image.origin'] 

804 

805 self._orig_linestyles = linestyles # Only kept for user access. 

806 self.negative_linestyles = negative_linestyles 

807 # If negative_linestyles was not defined as a keyword argument, define 

808 # negative_linestyles with rcParams 

809 if self.negative_linestyles is None: 

810 self.negative_linestyles = \ 

811 mpl.rcParams['contour.negative_linestyle'] 

812 

813 kwargs = self._process_args(*args, **kwargs) 

814 self._process_levels() 

815 

816 self._extend_min = self.extend in ['min', 'both'] 

817 self._extend_max = self.extend in ['max', 'both'] 

818 if self.colors is not None: 

819 ncolors = len(self.levels) 

820 if self.filled: 

821 ncolors -= 1 

822 i0 = 0 

823 

824 # Handle the case where colors are given for the extended 

825 # parts of the contour. 

826 

827 use_set_under_over = False 

828 # if we are extending the lower end, and we've been given enough 

829 # colors then skip the first color in the resulting cmap. For the 

830 # extend_max case we don't need to worry about passing more colors 

831 # than ncolors as ListedColormap will clip. 

832 total_levels = (ncolors + 

833 int(self._extend_min) + 

834 int(self._extend_max)) 

835 if (len(self.colors) == total_levels and 

836 (self._extend_min or self._extend_max)): 

837 use_set_under_over = True 

838 if self._extend_min: 

839 i0 = 1 

840 

841 cmap = mcolors.ListedColormap(self.colors[i0:None], N=ncolors) 

842 

843 if use_set_under_over: 

844 if self._extend_min: 

845 cmap.set_under(self.colors[0]) 

846 if self._extend_max: 

847 cmap.set_over(self.colors[-1]) 

848 

849 # label lists must be initialized here 

850 self.labelTexts = [] 

851 self.labelCValues = [] 

852 

853 self.set_cmap(cmap) 

854 if norm is not None: 

855 self.set_norm(norm) 

856 with self.norm.callbacks.blocked(signal="changed"): 

857 if vmin is not None: 

858 self.norm.vmin = vmin 

859 if vmax is not None: 

860 self.norm.vmax = vmax 

861 self.norm._changed() 

862 self._process_colors() 

863 

864 if self._paths is None: 

865 self._paths = self._make_paths_from_contour_generator() 

866 

867 if self.filled: 

868 if linewidths is not None: 

869 _api.warn_external('linewidths is ignored by contourf') 

870 # Lower and upper contour levels. 

871 lowers, uppers = self._get_lowers_and_uppers() 

872 self.set( 

873 edgecolor="none", 

874 # Default zorder taken from Collection 

875 zorder=kwargs.pop("zorder", 1), 

876 ) 

877 

878 else: 

879 self.set( 

880 facecolor="none", 

881 linewidths=self._process_linewidths(linewidths), 

882 linestyle=self._process_linestyles(linestyles), 

883 # Default zorder taken from LineCollection, which is higher 

884 # than for filled contours so that lines are displayed on top. 

885 zorder=kwargs.pop("zorder", 2), 

886 label="_nolegend_", 

887 ) 

888 

889 self.axes.add_collection(self, autolim=False) 

890 self.sticky_edges.x[:] = [self._mins[0], self._maxs[0]] 

891 self.sticky_edges.y[:] = [self._mins[1], self._maxs[1]] 

892 self.axes.update_datalim([self._mins, self._maxs]) 

893 self.axes.autoscale_view(tight=True) 

894 

895 self.changed() # set the colors 

896 

897 if kwargs: 

898 _api.warn_external( 

899 'The following kwargs were not used by contour: ' + 

900 ", ".join(map(repr, kwargs)) 

901 ) 

902 

903 allsegs = property(lambda self: [ 

904 [subp.vertices for subp in p._iter_connected_components()] 

905 for p in self.get_paths()]) 

906 allkinds = property(lambda self: [ 

907 [subp.codes for subp in p._iter_connected_components()] 

908 for p in self.get_paths()]) 

909 tcolors = _api.deprecated("3.8")(property(lambda self: [ 

910 (tuple(rgba),) for rgba in self.to_rgba(self.cvalues, self.alpha)])) 

911 tlinewidths = _api.deprecated("3.8")(property(lambda self: [ 

912 (w,) for w in self.get_linewidths()])) 

913 alpha = property(lambda self: self.get_alpha()) 

914 linestyles = property(lambda self: self._orig_linestyles) 

915 

916 @_api.deprecated("3.8", alternative="set_antialiased or get_antialiased", 

917 addendum="Note that get_antialiased returns an array.") 

918 @property 

919 def antialiased(self): 

920 return all(self.get_antialiased()) 

921 

922 @antialiased.setter 

923 def antialiased(self, aa): 

924 self.set_antialiased(aa) 

925 

926 @_api.deprecated("3.8") 

927 @property 

928 def collections(self): 

929 # On access, make oneself invisible and instead add the old-style collections 

930 # (one PathCollection per level). We do not try to further split contours into 

931 # connected components as we already lost track of what pairs of contours need 

932 # to be considered as single units to draw filled regions with holes. 

933 if not hasattr(self, "_old_style_split_collections"): 

934 self.set_visible(False) 

935 fcs = self.get_facecolor() 

936 ecs = self.get_edgecolor() 

937 lws = self.get_linewidth() 

938 lss = self.get_linestyle() 

939 self._old_style_split_collections = [] 

940 for idx, path in enumerate(self._paths): 

941 pc = mcoll.PathCollection( 

942 [path] if len(path.vertices) else [], 

943 alpha=self.get_alpha(), 

944 antialiaseds=self._antialiaseds[idx % len(self._antialiaseds)], 

945 transform=self.get_transform(), 

946 zorder=self.get_zorder(), 

947 label="_nolegend_", 

948 facecolor=fcs[idx] if len(fcs) else "none", 

949 edgecolor=ecs[idx] if len(ecs) else "none", 

950 linewidths=[lws[idx % len(lws)]], 

951 linestyles=[lss[idx % len(lss)]], 

952 ) 

953 if self.filled: 

954 pc.set(hatch=self.hatches[idx % len(self.hatches)]) 

955 self._old_style_split_collections.append(pc) 

956 for col in self._old_style_split_collections: 

957 self.axes.add_collection(col) 

958 return self._old_style_split_collections 

959 

960 def get_transform(self): 

961 """Return the `.Transform` instance used by this ContourSet.""" 

962 if self._transform is None: 

963 self._transform = self.axes.transData 

964 elif (not isinstance(self._transform, mtransforms.Transform) 

965 and hasattr(self._transform, '_as_mpl_transform')): 

966 self._transform = self._transform._as_mpl_transform(self.axes) 

967 return self._transform 

968 

969 def __getstate__(self): 

970 state = self.__dict__.copy() 

971 # the C object _contour_generator cannot currently be pickled. This 

972 # isn't a big issue as it is not actually used once the contour has 

973 # been calculated. 

974 state['_contour_generator'] = None 

975 return state 

976 

977 def legend_elements(self, variable_name='x', str_format=str): 

978 """ 

979 Return a list of artists and labels suitable for passing through 

980 to `~.Axes.legend` which represent this ContourSet. 

981 

982 The labels have the form "0 < x <= 1" stating the data ranges which 

983 the artists represent. 

984 

985 Parameters 

986 ---------- 

987 variable_name : str 

988 The string used inside the inequality used on the labels. 

989 str_format : function: float -> str 

990 Function used to format the numbers in the labels. 

991 

992 Returns 

993 ------- 

994 artists : list[`.Artist`] 

995 A list of the artists. 

996 labels : list[str] 

997 A list of the labels. 

998 """ 

999 artists = [] 

1000 labels = [] 

1001 

1002 if self.filled: 

1003 lowers, uppers = self._get_lowers_and_uppers() 

1004 n_levels = len(self._paths) 

1005 for idx in range(n_levels): 

1006 artists.append(mpatches.Rectangle( 

1007 (0, 0), 1, 1, 

1008 facecolor=self.get_facecolor()[idx], 

1009 hatch=self.hatches[idx % len(self.hatches)], 

1010 )) 

1011 lower = str_format(lowers[idx]) 

1012 upper = str_format(uppers[idx]) 

1013 if idx == 0 and self.extend in ('min', 'both'): 

1014 labels.append(fr'${variable_name} \leq {lower}s$') 

1015 elif idx == n_levels - 1 and self.extend in ('max', 'both'): 

1016 labels.append(fr'${variable_name} > {upper}s$') 

1017 else: 

1018 labels.append(fr'${lower} < {variable_name} \leq {upper}$') 

1019 else: 

1020 for idx, level in enumerate(self.levels): 

1021 artists.append(Line2D( 

1022 [], [], 

1023 color=self.get_edgecolor()[idx], 

1024 linewidth=self.get_linewidths()[idx], 

1025 linestyle=self.get_linestyles()[idx], 

1026 )) 

1027 labels.append(fr'${variable_name} = {str_format(level)}$') 

1028 

1029 return artists, labels 

1030 

1031 def _process_args(self, *args, **kwargs): 

1032 """ 

1033 Process *args* and *kwargs*; override in derived classes. 

1034 

1035 Must set self.levels, self.zmin and self.zmax, and update Axes limits. 

1036 """ 

1037 self.levels = args[0] 

1038 allsegs = args[1] 

1039 allkinds = args[2] if len(args) > 2 else None 

1040 self.zmax = np.max(self.levels) 

1041 self.zmin = np.min(self.levels) 

1042 

1043 if allkinds is None: 

1044 allkinds = [[None] * len(segs) for segs in allsegs] 

1045 

1046 # Check lengths of levels and allsegs. 

1047 if self.filled: 

1048 if len(allsegs) != len(self.levels) - 1: 

1049 raise ValueError('must be one less number of segments as ' 

1050 'levels') 

1051 else: 

1052 if len(allsegs) != len(self.levels): 

1053 raise ValueError('must be same number of segments as levels') 

1054 

1055 # Check length of allkinds. 

1056 if len(allkinds) != len(allsegs): 

1057 raise ValueError('allkinds has different length to allsegs') 

1058 

1059 # Determine x, y bounds and update axes data limits. 

1060 flatseglist = [s for seg in allsegs for s in seg] 

1061 points = np.concatenate(flatseglist, axis=0) 

1062 self._mins = points.min(axis=0) 

1063 self._maxs = points.max(axis=0) 

1064 

1065 # Each entry in (allsegs, allkinds) is a list of (segs, kinds): segs is a list 

1066 # of (N, 2) arrays of xy coordinates, kinds is a list of arrays of corresponding 

1067 # pathcodes. However, kinds can also be None; in which case all paths in that 

1068 # list are codeless (this case is normalized above). These lists are used to 

1069 # construct paths, which then get concatenated. 

1070 self._paths = [Path.make_compound_path(*map(Path, segs, kinds)) 

1071 for segs, kinds in zip(allsegs, allkinds)] 

1072 

1073 return kwargs 

1074 

1075 def _make_paths_from_contour_generator(self): 

1076 """Compute ``paths`` using C extension.""" 

1077 if self._paths is not None: 

1078 return self._paths 

1079 cg = self._contour_generator 

1080 empty_path = Path(np.empty((0, 2))) 

1081 vertices_and_codes = ( 

1082 map(cg.create_filled_contour, *self._get_lowers_and_uppers()) 

1083 if self.filled else 

1084 map(cg.create_contour, self.levels)) 

1085 return [Path(np.concatenate(vs), np.concatenate(cs)) if len(vs) else empty_path 

1086 for vs, cs in vertices_and_codes] 

1087 

1088 def _get_lowers_and_uppers(self): 

1089 """ 

1090 Return ``(lowers, uppers)`` for filled contours. 

1091 """ 

1092 lowers = self._levels[:-1] 

1093 if self.zmin == lowers[0]: 

1094 # Include minimum values in lowest interval 

1095 lowers = lowers.copy() # so we don't change self._levels 

1096 if self.logscale: 

1097 lowers[0] = 0.99 * self.zmin 

1098 else: 

1099 lowers[0] -= 1 

1100 uppers = self._levels[1:] 

1101 return (lowers, uppers) 

1102 

1103 def changed(self): 

1104 if not hasattr(self, "cvalues"): 

1105 self._process_colors() # Sets cvalues. 

1106 # Force an autoscale immediately because self.to_rgba() calls 

1107 # autoscale_None() internally with the data passed to it, 

1108 # so if vmin/vmax are not set yet, this would override them with 

1109 # content from *cvalues* rather than levels like we want 

1110 self.norm.autoscale_None(self.levels) 

1111 self.set_array(self.cvalues) 

1112 self.update_scalarmappable() 

1113 alphas = np.broadcast_to(self.get_alpha(), len(self.cvalues)) 

1114 for label, cv, alpha in zip(self.labelTexts, self.labelCValues, alphas): 

1115 label.set_alpha(alpha) 

1116 label.set_color(self.labelMappable.to_rgba(cv)) 

1117 super().changed() 

1118 

1119 def _autolev(self, N): 

1120 """ 

1121 Select contour levels to span the data. 

1122 

1123 The target number of levels, *N*, is used only when the 

1124 scale is not log and default locator is used. 

1125 

1126 We need two more levels for filled contours than for 

1127 line contours, because for the latter we need to specify 

1128 the lower and upper boundary of each range. For example, 

1129 a single contour boundary, say at z = 0, requires only 

1130 one contour line, but two filled regions, and therefore 

1131 three levels to provide boundaries for both regions. 

1132 """ 

1133 if self.locator is None: 

1134 if self.logscale: 

1135 self.locator = ticker.LogLocator() 

1136 else: 

1137 self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1) 

1138 

1139 lev = self.locator.tick_values(self.zmin, self.zmax) 

1140 

1141 try: 

1142 if self.locator._symmetric: 

1143 return lev 

1144 except AttributeError: 

1145 pass 

1146 

1147 # Trim excess levels the locator may have supplied. 

1148 under = np.nonzero(lev < self.zmin)[0] 

1149 i0 = under[-1] if len(under) else 0 

1150 over = np.nonzero(lev > self.zmax)[0] 

1151 i1 = over[0] + 1 if len(over) else len(lev) 

1152 if self.extend in ('min', 'both'): 

1153 i0 += 1 

1154 if self.extend in ('max', 'both'): 

1155 i1 -= 1 

1156 

1157 if i1 - i0 < 3: 

1158 i0, i1 = 0, len(lev) 

1159 

1160 return lev[i0:i1] 

1161 

1162 def _process_contour_level_args(self, args, z_dtype): 

1163 """ 

1164 Determine the contour levels and store in self.levels. 

1165 """ 

1166 if self.levels is None: 

1167 if args: 

1168 levels_arg = args[0] 

1169 elif np.issubdtype(z_dtype, bool): 

1170 if self.filled: 

1171 levels_arg = [0, .5, 1] 

1172 else: 

1173 levels_arg = [.5] 

1174 else: 

1175 levels_arg = 7 # Default, hard-wired. 

1176 else: 

1177 levels_arg = self.levels 

1178 if isinstance(levels_arg, Integral): 

1179 self.levels = self._autolev(levels_arg) 

1180 else: 

1181 self.levels = np.asarray(levels_arg, np.float64) 

1182 if self.filled and len(self.levels) < 2: 

1183 raise ValueError("Filled contours require at least 2 levels.") 

1184 if len(self.levels) > 1 and np.min(np.diff(self.levels)) <= 0.0: 

1185 raise ValueError("Contour levels must be increasing") 

1186 

1187 def _process_levels(self): 

1188 """ 

1189 Assign values to :attr:`layers` based on :attr:`levels`, 

1190 adding extended layers as needed if contours are filled. 

1191 

1192 For line contours, layers simply coincide with levels; 

1193 a line is a thin layer. No extended levels are needed 

1194 with line contours. 

1195 """ 

1196 # Make a private _levels to include extended regions; we 

1197 # want to leave the original levels attribute unchanged. 

1198 # (Colorbar needs this even for line contours.) 

1199 self._levels = list(self.levels) 

1200 

1201 if self.logscale: 

1202 lower, upper = 1e-250, 1e250 

1203 else: 

1204 lower, upper = -1e250, 1e250 

1205 

1206 if self.extend in ('both', 'min'): 

1207 self._levels.insert(0, lower) 

1208 if self.extend in ('both', 'max'): 

1209 self._levels.append(upper) 

1210 self._levels = np.asarray(self._levels) 

1211 

1212 if not self.filled: 

1213 self.layers = self.levels 

1214 return 

1215 

1216 # Layer values are mid-way between levels in screen space. 

1217 if self.logscale: 

1218 # Avoid overflow by taking sqrt before multiplying. 

1219 self.layers = (np.sqrt(self._levels[:-1]) 

1220 * np.sqrt(self._levels[1:])) 

1221 else: 

1222 self.layers = 0.5 * (self._levels[:-1] + self._levels[1:]) 

1223 

1224 def _process_colors(self): 

1225 """ 

1226 Color argument processing for contouring. 

1227 

1228 Note that we base the colormapping on the contour levels 

1229 and layers, not on the actual range of the Z values. This 

1230 means we don't have to worry about bad values in Z, and we 

1231 always have the full dynamic range available for the selected 

1232 levels. 

1233 

1234 The color is based on the midpoint of the layer, except for 

1235 extended end layers. By default, the norm vmin and vmax 

1236 are the extreme values of the non-extended levels. Hence, 

1237 the layer color extremes are not the extreme values of 

1238 the colormap itself, but approach those values as the number 

1239 of levels increases. An advantage of this scheme is that 

1240 line contours, when added to filled contours, take on 

1241 colors that are consistent with those of the filled regions; 

1242 for example, a contour line on the boundary between two 

1243 regions will have a color intermediate between those 

1244 of the regions. 

1245 

1246 """ 

1247 self.monochrome = self.cmap.monochrome 

1248 if self.colors is not None: 

1249 # Generate integers for direct indexing. 

1250 i0, i1 = 0, len(self.levels) 

1251 if self.filled: 

1252 i1 -= 1 

1253 # Out of range indices for over and under: 

1254 if self.extend in ('both', 'min'): 

1255 i0 -= 1 

1256 if self.extend in ('both', 'max'): 

1257 i1 += 1 

1258 self.cvalues = list(range(i0, i1)) 

1259 self.set_norm(mcolors.NoNorm()) 

1260 else: 

1261 self.cvalues = self.layers 

1262 self.norm.autoscale_None(self.levels) 

1263 self.set_array(self.cvalues) 

1264 self.update_scalarmappable() 

1265 if self.extend in ('both', 'max', 'min'): 

1266 self.norm.clip = False 

1267 

1268 def _process_linewidths(self, linewidths): 

1269 Nlev = len(self.levels) 

1270 if linewidths is None: 

1271 default_linewidth = mpl.rcParams['contour.linewidth'] 

1272 if default_linewidth is None: 

1273 default_linewidth = mpl.rcParams['lines.linewidth'] 

1274 return [default_linewidth] * Nlev 

1275 elif not np.iterable(linewidths): 

1276 return [linewidths] * Nlev 

1277 else: 

1278 linewidths = list(linewidths) 

1279 return (linewidths * math.ceil(Nlev / len(linewidths)))[:Nlev] 

1280 

1281 def _process_linestyles(self, linestyles): 

1282 Nlev = len(self.levels) 

1283 if linestyles is None: 

1284 tlinestyles = ['solid'] * Nlev 

1285 if self.monochrome: 

1286 eps = - (self.zmax - self.zmin) * 1e-15 

1287 for i, lev in enumerate(self.levels): 

1288 if lev < eps: 

1289 tlinestyles[i] = self.negative_linestyles 

1290 else: 

1291 if isinstance(linestyles, str): 

1292 tlinestyles = [linestyles] * Nlev 

1293 elif np.iterable(linestyles): 

1294 tlinestyles = list(linestyles) 

1295 if len(tlinestyles) < Nlev: 

1296 nreps = int(np.ceil(Nlev / len(linestyles))) 

1297 tlinestyles = tlinestyles * nreps 

1298 if len(tlinestyles) > Nlev: 

1299 tlinestyles = tlinestyles[:Nlev] 

1300 else: 

1301 raise ValueError("Unrecognized type for linestyles kwarg") 

1302 return tlinestyles 

1303 

1304 def _find_nearest_contour(self, xy, indices=None): 

1305 """ 

1306 Find the point in the unfilled contour plot that is closest (in screen 

1307 space) to point *xy*. 

1308 

1309 Parameters 

1310 ---------- 

1311 xy : tuple[float, float] 

1312 The reference point (in screen space). 

1313 indices : list of int or None, default: None 

1314 Indices of contour levels to consider. If None (the default), all levels 

1315 are considered. 

1316 

1317 Returns 

1318 ------- 

1319 idx_level_min : int 

1320 The index of the contour level closest to *xy*. 

1321 idx_vtx_min : int 

1322 The index of the `.Path` segment closest to *xy* (at that level). 

1323 proj : (float, float) 

1324 The point in the contour plot closest to *xy*. 

1325 """ 

1326 

1327 # Convert each contour segment to pixel coordinates and then compare the given 

1328 # point to those coordinates for each contour. This is fast enough in normal 

1329 # cases, but speedups may be possible. 

1330 

1331 if self.filled: 

1332 raise ValueError("Method does not support filled contours") 

1333 

1334 if indices is None: 

1335 indices = range(len(self._paths)) 

1336 

1337 d2min = np.inf 

1338 idx_level_min = idx_vtx_min = proj_min = None 

1339 

1340 for idx_level in indices: 

1341 path = self._paths[idx_level] 

1342 idx_vtx_start = 0 

1343 for subpath in path._iter_connected_components(): 

1344 if not len(subpath.vertices): 

1345 continue 

1346 lc = self.get_transform().transform(subpath.vertices) 

1347 d2, proj, leg = _find_closest_point_on_path(lc, xy) 

1348 if d2 < d2min: 

1349 d2min = d2 

1350 idx_level_min = idx_level 

1351 idx_vtx_min = leg[1] + idx_vtx_start 

1352 proj_min = proj 

1353 idx_vtx_start += len(subpath) 

1354 

1355 return idx_level_min, idx_vtx_min, proj_min 

1356 

1357 def find_nearest_contour(self, x, y, indices=None, pixel=True): 

1358 """ 

1359 Find the point in the contour plot that is closest to ``(x, y)``. 

1360 

1361 This method does not support filled contours. 

1362 

1363 Parameters 

1364 ---------- 

1365 x, y : float 

1366 The reference point. 

1367 indices : list of int or None, default: None 

1368 Indices of contour levels to consider. If None (the default), all 

1369 levels are considered. 

1370 pixel : bool, default: True 

1371 If *True*, measure distance in pixel (screen) space, which is 

1372 useful for manual contour labeling; else, measure distance in axes 

1373 space. 

1374 

1375 Returns 

1376 ------- 

1377 path : int 

1378 The index of the path that is closest to ``(x, y)``. Each path corresponds 

1379 to one contour level. 

1380 subpath : int 

1381 The index within that closest path of the subpath that is closest to 

1382 ``(x, y)``. Each subpath corresponds to one unbroken contour line. 

1383 index : int 

1384 The index of the vertices within that subpath that are closest to 

1385 ``(x, y)``. 

1386 xmin, ymin : float 

1387 The point in the contour plot that is closest to ``(x, y)``. 

1388 d2 : float 

1389 The squared distance from ``(xmin, ymin)`` to ``(x, y)``. 

1390 """ 

1391 segment = index = d2 = None 

1392 

1393 with ExitStack() as stack: 

1394 if not pixel: 

1395 # _find_nearest_contour works in pixel space. We want axes space, so 

1396 # effectively disable the transformation here by setting to identity. 

1397 stack.enter_context(self._cm_set( 

1398 transform=mtransforms.IdentityTransform())) 

1399 

1400 i_level, i_vtx, (xmin, ymin) = self._find_nearest_contour((x, y), indices) 

1401 

1402 if i_level is not None: 

1403 cc_cumlens = np.cumsum( 

1404 [*map(len, self._paths[i_level]._iter_connected_components())]) 

1405 segment = cc_cumlens.searchsorted(i_vtx, "right") 

1406 index = i_vtx if segment == 0 else i_vtx - cc_cumlens[segment - 1] 

1407 d2 = (xmin-x)**2 + (ymin-y)**2 

1408 

1409 return (i_level, segment, index, xmin, ymin, d2) 

1410 

1411 def draw(self, renderer): 

1412 paths = self._paths 

1413 n_paths = len(paths) 

1414 if not self.filled or all(hatch is None for hatch in self.hatches): 

1415 super().draw(renderer) 

1416 return 

1417 # In presence of hatching, draw contours one at a time. 

1418 edgecolors = self.get_edgecolors() 

1419 if edgecolors.size == 0: 

1420 edgecolors = ("none",) 

1421 for idx in range(n_paths): 

1422 with cbook._setattr_cm(self, _paths=[paths[idx]]), self._cm_set( 

1423 hatch=self.hatches[idx % len(self.hatches)], 

1424 array=[self.get_array()[idx]], 

1425 linewidths=[self.get_linewidths()[idx % len(self.get_linewidths())]], 

1426 linestyles=[self.get_linestyles()[idx % len(self.get_linestyles())]], 

1427 edgecolors=edgecolors[idx % len(edgecolors)], 

1428 ): 

1429 super().draw(renderer) 

1430 

1431 

1432@_docstring.dedent_interpd 

1433class QuadContourSet(ContourSet): 

1434 """ 

1435 Create and store a set of contour lines or filled regions. 

1436 

1437 This class is typically not instantiated directly by the user but by 

1438 `~.Axes.contour` and `~.Axes.contourf`. 

1439 

1440 %(contour_set_attributes)s 

1441 """ 

1442 

1443 def _process_args(self, *args, corner_mask=None, algorithm=None, **kwargs): 

1444 """ 

1445 Process args and kwargs. 

1446 """ 

1447 if args and isinstance(args[0], QuadContourSet): 

1448 if self.levels is None: 

1449 self.levels = args[0].levels 

1450 self.zmin = args[0].zmin 

1451 self.zmax = args[0].zmax 

1452 self._corner_mask = args[0]._corner_mask 

1453 contour_generator = args[0]._contour_generator 

1454 self._mins = args[0]._mins 

1455 self._maxs = args[0]._maxs 

1456 self._algorithm = args[0]._algorithm 

1457 else: 

1458 import contourpy 

1459 

1460 if algorithm is None: 

1461 algorithm = mpl.rcParams['contour.algorithm'] 

1462 mpl.rcParams.validate["contour.algorithm"](algorithm) 

1463 self._algorithm = algorithm 

1464 

1465 if corner_mask is None: 

1466 if self._algorithm == "mpl2005": 

1467 # mpl2005 does not support corner_mask=True so if not 

1468 # specifically requested then disable it. 

1469 corner_mask = False 

1470 else: 

1471 corner_mask = mpl.rcParams['contour.corner_mask'] 

1472 self._corner_mask = corner_mask 

1473 

1474 x, y, z = self._contour_args(args, kwargs) 

1475 

1476 contour_generator = contourpy.contour_generator( 

1477 x, y, z, name=self._algorithm, corner_mask=self._corner_mask, 

1478 line_type=contourpy.LineType.SeparateCode, 

1479 fill_type=contourpy.FillType.OuterCode, 

1480 chunk_size=self.nchunk) 

1481 

1482 t = self.get_transform() 

1483 

1484 # if the transform is not trans data, and some part of it 

1485 # contains transData, transform the xs and ys to data coordinates 

1486 if (t != self.axes.transData and 

1487 any(t.contains_branch_seperately(self.axes.transData))): 

1488 trans_to_data = t - self.axes.transData 

1489 pts = np.vstack([x.flat, y.flat]).T 

1490 transformed_pts = trans_to_data.transform(pts) 

1491 x = transformed_pts[..., 0] 

1492 y = transformed_pts[..., 1] 

1493 

1494 self._mins = [ma.min(x), ma.min(y)] 

1495 self._maxs = [ma.max(x), ma.max(y)] 

1496 

1497 self._contour_generator = contour_generator 

1498 

1499 return kwargs 

1500 

1501 def _contour_args(self, args, kwargs): 

1502 if self.filled: 

1503 fn = 'contourf' 

1504 else: 

1505 fn = 'contour' 

1506 nargs = len(args) 

1507 

1508 if 0 < nargs <= 2: 

1509 z, *args = args 

1510 z = ma.asarray(z) 

1511 x, y = self._initialize_x_y(z) 

1512 elif 2 < nargs <= 4: 

1513 x, y, z_orig, *args = args 

1514 x, y, z = self._check_xyz(x, y, z_orig, kwargs) 

1515 

1516 else: 

1517 raise _api.nargs_error(fn, takes="from 1 to 4", given=nargs) 

1518 z = ma.masked_invalid(z, copy=False) 

1519 self.zmax = z.max().astype(float) 

1520 self.zmin = z.min().astype(float) 

1521 if self.logscale and self.zmin <= 0: 

1522 z = ma.masked_where(z <= 0, z) 

1523 _api.warn_external('Log scale: values of z <= 0 have been masked') 

1524 self.zmin = z.min().astype(float) 

1525 self._process_contour_level_args(args, z.dtype) 

1526 return (x, y, z) 

1527 

1528 def _check_xyz(self, x, y, z, kwargs): 

1529 """ 

1530 Check that the shapes of the input arrays match; if x and y are 1D, 

1531 convert them to 2D using meshgrid. 

1532 """ 

1533 x, y = self.axes._process_unit_info([("x", x), ("y", y)], kwargs) 

1534 

1535 x = np.asarray(x, dtype=np.float64) 

1536 y = np.asarray(y, dtype=np.float64) 

1537 z = ma.asarray(z) 

1538 

1539 if z.ndim != 2: 

1540 raise TypeError(f"Input z must be 2D, not {z.ndim}D") 

1541 if z.shape[0] < 2 or z.shape[1] < 2: 

1542 raise TypeError(f"Input z must be at least a (2, 2) shaped array, " 

1543 f"but has shape {z.shape}") 

1544 Ny, Nx = z.shape 

1545 

1546 if x.ndim != y.ndim: 

1547 raise TypeError(f"Number of dimensions of x ({x.ndim}) and y " 

1548 f"({y.ndim}) do not match") 

1549 if x.ndim == 1: 

1550 nx, = x.shape 

1551 ny, = y.shape 

1552 if nx != Nx: 

1553 raise TypeError(f"Length of x ({nx}) must match number of " 

1554 f"columns in z ({Nx})") 

1555 if ny != Ny: 

1556 raise TypeError(f"Length of y ({ny}) must match number of " 

1557 f"rows in z ({Ny})") 

1558 x, y = np.meshgrid(x, y) 

1559 elif x.ndim == 2: 

1560 if x.shape != z.shape: 

1561 raise TypeError( 

1562 f"Shapes of x {x.shape} and z {z.shape} do not match") 

1563 if y.shape != z.shape: 

1564 raise TypeError( 

1565 f"Shapes of y {y.shape} and z {z.shape} do not match") 

1566 else: 

1567 raise TypeError(f"Inputs x and y must be 1D or 2D, not {x.ndim}D") 

1568 

1569 return x, y, z 

1570 

1571 def _initialize_x_y(self, z): 

1572 """ 

1573 Return X, Y arrays such that contour(Z) will match imshow(Z) 

1574 if origin is not None. 

1575 The center of pixel Z[i, j] depends on origin: 

1576 if origin is None, x = j, y = i; 

1577 if origin is 'lower', x = j + 0.5, y = i + 0.5; 

1578 if origin is 'upper', x = j + 0.5, y = Nrows - i - 0.5 

1579 If extent is not None, x and y will be scaled to match, 

1580 as in imshow. 

1581 If origin is None and extent is not None, then extent 

1582 will give the minimum and maximum values of x and y. 

1583 """ 

1584 if z.ndim != 2: 

1585 raise TypeError(f"Input z must be 2D, not {z.ndim}D") 

1586 elif z.shape[0] < 2 or z.shape[1] < 2: 

1587 raise TypeError(f"Input z must be at least a (2, 2) shaped array, " 

1588 f"but has shape {z.shape}") 

1589 else: 

1590 Ny, Nx = z.shape 

1591 if self.origin is None: # Not for image-matching. 

1592 if self.extent is None: 

1593 return np.meshgrid(np.arange(Nx), np.arange(Ny)) 

1594 else: 

1595 x0, x1, y0, y1 = self.extent 

1596 x = np.linspace(x0, x1, Nx) 

1597 y = np.linspace(y0, y1, Ny) 

1598 return np.meshgrid(x, y) 

1599 # Match image behavior: 

1600 if self.extent is None: 

1601 x0, x1, y0, y1 = (0, Nx, 0, Ny) 

1602 else: 

1603 x0, x1, y0, y1 = self.extent 

1604 dx = (x1 - x0) / Nx 

1605 dy = (y1 - y0) / Ny 

1606 x = x0 + (np.arange(Nx) + 0.5) * dx 

1607 y = y0 + (np.arange(Ny) + 0.5) * dy 

1608 if self.origin == 'upper': 

1609 y = y[::-1] 

1610 return np.meshgrid(x, y) 

1611 

1612 

1613_docstring.interpd.update(contour_doc=""" 

1614`.contour` and `.contourf` draw contour lines and filled contours, 

1615respectively. Except as noted, function signatures and return values 

1616are the same for both versions. 

1617 

1618Parameters 

1619---------- 

1620X, Y : array-like, optional 

1621 The coordinates of the values in *Z*. 

1622 

1623 *X* and *Y* must both be 2D with the same shape as *Z* (e.g. 

1624 created via `numpy.meshgrid`), or they must both be 1-D such 

1625 that ``len(X) == N`` is the number of columns in *Z* and 

1626 ``len(Y) == M`` is the number of rows in *Z*. 

1627 

1628 *X* and *Y* must both be ordered monotonically. 

1629 

1630 If not given, they are assumed to be integer indices, i.e. 

1631 ``X = range(N)``, ``Y = range(M)``. 

1632 

1633Z : (M, N) array-like 

1634 The height values over which the contour is drawn. Color-mapping is 

1635 controlled by *cmap*, *norm*, *vmin*, and *vmax*. 

1636 

1637levels : int or array-like, optional 

1638 Determines the number and positions of the contour lines / regions. 

1639 

1640 If an int *n*, use `~matplotlib.ticker.MaxNLocator`, which tries 

1641 to automatically choose no more than *n+1* "nice" contour levels 

1642 between minimum and maximum numeric values of *Z*. 

1643 

1644 If array-like, draw contour lines at the specified levels. 

1645 The values must be in increasing order. 

1646 

1647Returns 

1648------- 

1649`~.contour.QuadContourSet` 

1650 

1651Other Parameters 

1652---------------- 

1653corner_mask : bool, default: :rc:`contour.corner_mask` 

1654 Enable/disable corner masking, which only has an effect if *Z* is 

1655 a masked array. If ``False``, any quad touching a masked point is 

1656 masked out. If ``True``, only the triangular corners of quads 

1657 nearest those points are always masked out, other triangular 

1658 corners comprising three unmasked points are contoured as usual. 

1659 

1660colors : :mpltype:`color` or list of :mpltype:`color`, optional 

1661 The colors of the levels, i.e. the lines for `.contour` and the 

1662 areas for `.contourf`. 

1663 

1664 The sequence is cycled for the levels in ascending order. If the 

1665 sequence is shorter than the number of levels, it's repeated. 

1666 

1667 As a shortcut, single color strings may be used in place of 

1668 one-element lists, i.e. ``'red'`` instead of ``['red']`` to color 

1669 all levels with the same color. This shortcut does only work for 

1670 color strings, not for other ways of specifying colors. 

1671 

1672 By default (value *None*), the colormap specified by *cmap* 

1673 will be used. 

1674 

1675alpha : float, default: 1 

1676 The alpha blending value, between 0 (transparent) and 1 (opaque). 

1677 

1678%(cmap_doc)s 

1679 

1680 This parameter is ignored if *colors* is set. 

1681 

1682%(norm_doc)s 

1683 

1684 This parameter is ignored if *colors* is set. 

1685 

1686%(vmin_vmax_doc)s 

1687 

1688 If *vmin* or *vmax* are not given, the default color scaling is based on 

1689 *levels*. 

1690 

1691 This parameter is ignored if *colors* is set. 

1692 

1693origin : {*None*, 'upper', 'lower', 'image'}, default: None 

1694 Determines the orientation and exact position of *Z* by specifying 

1695 the position of ``Z[0, 0]``. This is only relevant, if *X*, *Y* 

1696 are not given. 

1697 

1698 - *None*: ``Z[0, 0]`` is at X=0, Y=0 in the lower left corner. 

1699 - 'lower': ``Z[0, 0]`` is at X=0.5, Y=0.5 in the lower left corner. 

1700 - 'upper': ``Z[0, 0]`` is at X=N+0.5, Y=0.5 in the upper left 

1701 corner. 

1702 - 'image': Use the value from :rc:`image.origin`. 

1703 

1704extent : (x0, x1, y0, y1), optional 

1705 If *origin* is not *None*, then *extent* is interpreted as in 

1706 `.imshow`: it gives the outer pixel boundaries. In this case, the 

1707 position of Z[0, 0] is the center of the pixel, not a corner. If 

1708 *origin* is *None*, then (*x0*, *y0*) is the position of Z[0, 0], 

1709 and (*x1*, *y1*) is the position of Z[-1, -1]. 

1710 

1711 This argument is ignored if *X* and *Y* are specified in the call 

1712 to contour. 

1713 

1714locator : ticker.Locator subclass, optional 

1715 The locator is used to determine the contour levels if they 

1716 are not given explicitly via *levels*. 

1717 Defaults to `~.ticker.MaxNLocator`. 

1718 

1719extend : {'neither', 'both', 'min', 'max'}, default: 'neither' 

1720 Determines the ``contourf``-coloring of values that are outside the 

1721 *levels* range. 

1722 

1723 If 'neither', values outside the *levels* range are not colored. 

1724 If 'min', 'max' or 'both', color the values below, above or below 

1725 and above the *levels* range. 

1726 

1727 Values below ``min(levels)`` and above ``max(levels)`` are mapped 

1728 to the under/over values of the `.Colormap`. Note that most 

1729 colormaps do not have dedicated colors for these by default, so 

1730 that the over and under values are the edge values of the colormap. 

1731 You may want to set these values explicitly using 

1732 `.Colormap.set_under` and `.Colormap.set_over`. 

1733 

1734 .. note:: 

1735 

1736 An existing `.QuadContourSet` does not get notified if 

1737 properties of its colormap are changed. Therefore, an explicit 

1738 call `.QuadContourSet.changed()` is needed after modifying the 

1739 colormap. The explicit call can be left out, if a colorbar is 

1740 assigned to the `.QuadContourSet` because it internally calls 

1741 `.QuadContourSet.changed()`. 

1742 

1743 Example:: 

1744 

1745 x = np.arange(1, 10) 

1746 y = x.reshape(-1, 1) 

1747 h = x * y 

1748 

1749 cs = plt.contourf(h, levels=[10, 30, 50], 

1750 colors=['#808080', '#A0A0A0', '#C0C0C0'], extend='both') 

1751 cs.cmap.set_over('red') 

1752 cs.cmap.set_under('blue') 

1753 cs.changed() 

1754 

1755xunits, yunits : registered units, optional 

1756 Override axis units by specifying an instance of a 

1757 :class:`matplotlib.units.ConversionInterface`. 

1758 

1759antialiased : bool, optional 

1760 Enable antialiasing, overriding the defaults. For 

1761 filled contours, the default is *False*. For line contours, 

1762 it is taken from :rc:`lines.antialiased`. 

1763 

1764nchunk : int >= 0, optional 

1765 If 0, no subdivision of the domain. Specify a positive integer to 

1766 divide the domain into subdomains of *nchunk* by *nchunk* quads. 

1767 Chunking reduces the maximum length of polygons generated by the 

1768 contouring algorithm which reduces the rendering workload passed 

1769 on to the backend and also requires slightly less RAM. It can 

1770 however introduce rendering artifacts at chunk boundaries depending 

1771 on the backend, the *antialiased* flag and value of *alpha*. 

1772 

1773linewidths : float or array-like, default: :rc:`contour.linewidth` 

1774 *Only applies to* `.contour`. 

1775 

1776 The line width of the contour lines. 

1777 

1778 If a number, all levels will be plotted with this linewidth. 

1779 

1780 If a sequence, the levels in ascending order will be plotted with 

1781 the linewidths in the order specified. 

1782 

1783 If None, this falls back to :rc:`lines.linewidth`. 

1784 

1785linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, optional 

1786 *Only applies to* `.contour`. 

1787 

1788 If *linestyles* is *None*, the default is 'solid' unless the lines are 

1789 monochrome. In that case, negative contours will instead take their 

1790 linestyle from the *negative_linestyles* argument. 

1791 

1792 *linestyles* can also be an iterable of the above strings specifying a set 

1793 of linestyles to be used. If this iterable is shorter than the number of 

1794 contour levels it will be repeated as necessary. 

1795 

1796negative_linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, \ 

1797 optional 

1798 *Only applies to* `.contour`. 

1799 

1800 If *linestyles* is *None* and the lines are monochrome, this argument 

1801 specifies the line style for negative contours. 

1802 

1803 If *negative_linestyles* is *None*, the default is taken from 

1804 :rc:`contour.negative_linestyles`. 

1805 

1806 *negative_linestyles* can also be an iterable of the above strings 

1807 specifying a set of linestyles to be used. If this iterable is shorter than 

1808 the number of contour levels it will be repeated as necessary. 

1809 

1810hatches : list[str], optional 

1811 *Only applies to* `.contourf`. 

1812 

1813 A list of cross hatch patterns to use on the filled areas. 

1814 If None, no hatching will be added to the contour. 

1815 

1816algorithm : {'mpl2005', 'mpl2014', 'serial', 'threaded'}, optional 

1817 Which contouring algorithm to use to calculate the contour lines and 

1818 polygons. The algorithms are implemented in 

1819 `ContourPy <https://github.com/contourpy/contourpy>`_, consult the 

1820 `ContourPy documentation <https://contourpy.readthedocs.io>`_ for 

1821 further information. 

1822 

1823 The default is taken from :rc:`contour.algorithm`. 

1824 

1825clip_path : `~matplotlib.patches.Patch` or `.Path` or `.TransformedPath` 

1826 Set the clip path. See `~matplotlib.artist.Artist.set_clip_path`. 

1827 

1828 .. versionadded:: 3.8 

1829 

1830data : indexable object, optional 

1831 DATA_PARAMETER_PLACEHOLDER 

1832 

1833Notes 

1834----- 

18351. `.contourf` differs from the MATLAB version in that it does not draw 

1836 the polygon edges. To draw edges, add line contours with calls to 

1837 `.contour`. 

1838 

18392. `.contourf` fills intervals that are closed at the top; that is, for 

1840 boundaries *z1* and *z2*, the filled region is:: 

1841 

1842 z1 < Z <= z2 

1843 

1844 except for the lowest interval, which is closed on both sides (i.e. 

1845 it includes the lowest value). 

1846 

18473. `.contour` and `.contourf` use a `marching squares 

1848 <https://en.wikipedia.org/wiki/Marching_squares>`_ algorithm to 

1849 compute contour locations. More information can be found in 

1850 `ContourPy documentation <https://contourpy.readthedocs.io>`_. 

1851""" % _docstring.interpd.params)