Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from collections import OrderedDict 

2import types 

3 

4import numpy as np 

5 

6from matplotlib import cbook, rcParams 

7from matplotlib.axes import Axes 

8import matplotlib.axis as maxis 

9import matplotlib.markers as mmarkers 

10import matplotlib.patches as mpatches 

11import matplotlib.path as mpath 

12import matplotlib.ticker as mticker 

13import matplotlib.transforms as mtransforms 

14import matplotlib.spines as mspines 

15 

16 

17class PolarTransform(mtransforms.Transform): 

18 """ 

19 The base polar transform. This handles projection *theta* and 

20 *r* into Cartesian coordinate space *x* and *y*, but does not 

21 perform the ultimate affine transformation into the correct 

22 position. 

23 """ 

24 input_dims = 2 

25 output_dims = 2 

26 is_separable = False 

27 

28 def __init__(self, axis=None, use_rmin=True, 

29 _apply_theta_transforms=True): 

30 mtransforms.Transform.__init__(self) 

31 self._axis = axis 

32 self._use_rmin = use_rmin 

33 self._apply_theta_transforms = _apply_theta_transforms 

34 

35 def __str__(self): 

36 return ("{}(\n" 

37 "{},\n" 

38 " use_rmin={},\n" 

39 " _apply_theta_transforms={})" 

40 .format(type(self).__name__, 

41 mtransforms._indent_str(self._axis), 

42 self._use_rmin, 

43 self._apply_theta_transforms)) 

44 

45 def transform_non_affine(self, tr): 

46 # docstring inherited 

47 t, r = np.transpose(tr) 

48 # PolarAxes does not use the theta transforms here, but apply them for 

49 # backwards-compatibility if not being used by it. 

50 if self._apply_theta_transforms and self._axis is not None: 

51 t *= self._axis.get_theta_direction() 

52 t += self._axis.get_theta_offset() 

53 if self._use_rmin and self._axis is not None: 

54 r = (r - self._axis.get_rorigin()) * self._axis.get_rsign() 

55 r = np.where(r >= 0, r, np.nan) 

56 return np.column_stack([r * np.cos(t), r * np.sin(t)]) 

57 

58 def transform_path_non_affine(self, path): 

59 # docstring inherited 

60 vertices = path.vertices 

61 if len(vertices) == 2 and vertices[0, 0] == vertices[1, 0]: 

62 return mpath.Path(self.transform(vertices), path.codes) 

63 ipath = path.interpolated(path._interpolation_steps) 

64 return mpath.Path(self.transform(ipath.vertices), ipath.codes) 

65 

66 def inverted(self): 

67 # docstring inherited 

68 return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin, 

69 self._apply_theta_transforms) 

70 

71 

72class PolarAffine(mtransforms.Affine2DBase): 

73 """ 

74 The affine part of the polar projection. Scales the output so 

75 that maximum radius rests on the edge of the axes circle. 

76 """ 

77 def __init__(self, scale_transform, limits): 

78 """ 

79 *limits* is the view limit of the data. The only part of 

80 its bounds that is used is the y limits (for the radius limits). 

81 The theta range is handled by the non-affine transform. 

82 """ 

83 mtransforms.Affine2DBase.__init__(self) 

84 self._scale_transform = scale_transform 

85 self._limits = limits 

86 self.set_children(scale_transform, limits) 

87 self._mtx = None 

88 

89 def __str__(self): 

90 return ("{}(\n" 

91 "{},\n" 

92 "{})" 

93 .format(type(self).__name__, 

94 mtransforms._indent_str(self._scale_transform), 

95 mtransforms._indent_str(self._limits))) 

96 

97 def get_matrix(self): 

98 # docstring inherited 

99 if self._invalid: 

100 limits_scaled = self._limits.transformed(self._scale_transform) 

101 yscale = limits_scaled.ymax - limits_scaled.ymin 

102 affine = mtransforms.Affine2D() \ 

103 .scale(0.5 / yscale) \ 

104 .translate(0.5, 0.5) 

105 self._mtx = affine.get_matrix() 

106 self._inverted = None 

107 self._invalid = 0 

108 return self._mtx 

109 

110 

111class InvertedPolarTransform(mtransforms.Transform): 

112 """ 

113 The inverse of the polar transform, mapping Cartesian 

114 coordinate space *x* and *y* back to *theta* and *r*. 

115 """ 

116 input_dims = 2 

117 output_dims = 2 

118 is_separable = False 

119 

120 def __init__(self, axis=None, use_rmin=True, 

121 _apply_theta_transforms=True): 

122 mtransforms.Transform.__init__(self) 

123 self._axis = axis 

124 self._use_rmin = use_rmin 

125 self._apply_theta_transforms = _apply_theta_transforms 

126 

127 def __str__(self): 

128 return ("{}(\n" 

129 "{},\n" 

130 " use_rmin={},\n" 

131 " _apply_theta_transforms={})" 

132 .format(type(self).__name__, 

133 mtransforms._indent_str(self._axis), 

134 self._use_rmin, 

135 self._apply_theta_transforms)) 

136 

137 def transform_non_affine(self, xy): 

138 # docstring inherited 

139 x, y = xy.T 

140 r = np.hypot(x, y) 

141 theta = (np.arctan2(y, x) + 2 * np.pi) % (2 * np.pi) 

142 # PolarAxes does not use the theta transforms here, but apply them for 

143 # backwards-compatibility if not being used by it. 

144 if self._apply_theta_transforms and self._axis is not None: 

145 theta -= self._axis.get_theta_offset() 

146 theta *= self._axis.get_theta_direction() 

147 theta %= 2 * np.pi 

148 if self._use_rmin and self._axis is not None: 

149 r += self._axis.get_rorigin() 

150 r *= self._axis.get_rsign() 

151 return np.column_stack([theta, r]) 

152 

153 def inverted(self): 

154 # docstring inherited 

155 return PolarAxes.PolarTransform(self._axis, self._use_rmin, 

156 self._apply_theta_transforms) 

157 

158 

159class ThetaFormatter(mticker.Formatter): 

160 """ 

161 Used to format the *theta* tick labels. Converts the native 

162 unit of radians into degrees and adds a degree symbol. 

163 """ 

164 def __call__(self, x, pos=None): 

165 vmin, vmax = self.axis.get_view_interval() 

166 d = np.rad2deg(abs(vmax - vmin)) 

167 digits = max(-int(np.log10(d) - 1.5), 0) 

168 

169 if rcParams['text.usetex'] and not rcParams['text.latex.unicode']: 

170 format_str = r"${value:0.{digits:d}f}^\circ$" 

171 return format_str.format(value=np.rad2deg(x), digits=digits) 

172 else: 

173 # we use unicode, rather than mathtext with \circ, so 

174 # that it will work correctly with any arbitrary font 

175 # (assuming it has a degree sign), whereas $5\circ$ 

176 # will only work correctly with one of the supported 

177 # math fonts (Computer Modern and STIX) 

178 format_str = "{value:0.{digits:d}f}\N{DEGREE SIGN}" 

179 return format_str.format(value=np.rad2deg(x), digits=digits) 

180 

181 

182class _AxisWrapper: 

183 def __init__(self, axis): 

184 self._axis = axis 

185 

186 def get_view_interval(self): 

187 return np.rad2deg(self._axis.get_view_interval()) 

188 

189 def set_view_interval(self, vmin, vmax): 

190 self._axis.set_view_interval(*np.deg2rad((vmin, vmax))) 

191 

192 def get_minpos(self): 

193 return np.rad2deg(self._axis.get_minpos()) 

194 

195 def get_data_interval(self): 

196 return np.rad2deg(self._axis.get_data_interval()) 

197 

198 def set_data_interval(self, vmin, vmax): 

199 self._axis.set_data_interval(*np.deg2rad((vmin, vmax))) 

200 

201 def get_tick_space(self): 

202 return self._axis.get_tick_space() 

203 

204 

205class ThetaLocator(mticker.Locator): 

206 """ 

207 Used to locate theta ticks. 

208 

209 This will work the same as the base locator except in the case that the 

210 view spans the entire circle. In such cases, the previously used default 

211 locations of every 45 degrees are returned. 

212 """ 

213 def __init__(self, base): 

214 self.base = base 

215 self.axis = self.base.axis = _AxisWrapper(self.base.axis) 

216 

217 def set_axis(self, axis): 

218 self.axis = _AxisWrapper(axis) 

219 self.base.set_axis(self.axis) 

220 

221 def __call__(self): 

222 lim = self.axis.get_view_interval() 

223 if _is_full_circle_deg(lim[0], lim[1]): 

224 return np.arange(8) * 2 * np.pi / 8 

225 else: 

226 return np.deg2rad(self.base()) 

227 

228 @cbook.deprecated("3.2") 

229 def autoscale(self): 

230 return self.base.autoscale() 

231 

232 def pan(self, numsteps): 

233 return self.base.pan(numsteps) 

234 

235 def refresh(self): 

236 # docstring inherited 

237 return self.base.refresh() 

238 

239 def view_limits(self, vmin, vmax): 

240 vmin, vmax = np.rad2deg((vmin, vmax)) 

241 return np.deg2rad(self.base.view_limits(vmin, vmax)) 

242 

243 def zoom(self, direction): 

244 return self.base.zoom(direction) 

245 

246 

247class ThetaTick(maxis.XTick): 

248 """ 

249 A theta-axis tick. 

250 

251 This subclass of `XTick` provides angular ticks with some small 

252 modification to their re-positioning such that ticks are rotated based on 

253 tick location. This results in ticks that are correctly perpendicular to 

254 the arc spine. 

255 

256 When 'auto' rotation is enabled, labels are also rotated to be parallel to 

257 the spine. The label padding is also applied here since it's not possible 

258 to use a generic axes transform to produce tick-specific padding. 

259 """ 

260 def __init__(self, axes, *args, **kwargs): 

261 self._text1_translate = mtransforms.ScaledTranslation( 

262 0, 0, 

263 axes.figure.dpi_scale_trans) 

264 self._text2_translate = mtransforms.ScaledTranslation( 

265 0, 0, 

266 axes.figure.dpi_scale_trans) 

267 super().__init__(axes, *args, **kwargs) 

268 

269 def _get_text1(self): 

270 t = super()._get_text1() 

271 t.set_rotation_mode('anchor') 

272 t.set_transform(t.get_transform() + self._text1_translate) 

273 return t 

274 

275 def _get_text2(self): 

276 t = super()._get_text2() 

277 t.set_rotation_mode('anchor') 

278 t.set_transform(t.get_transform() + self._text2_translate) 

279 return t 

280 

281 def _apply_params(self, **kw): 

282 super()._apply_params(**kw) 

283 

284 # Ensure transform is correct; sometimes this gets reset. 

285 trans = self.label1.get_transform() 

286 if not trans.contains_branch(self._text1_translate): 

287 self.label1.set_transform(trans + self._text1_translate) 

288 trans = self.label2.get_transform() 

289 if not trans.contains_branch(self._text2_translate): 

290 self.label2.set_transform(trans + self._text2_translate) 

291 

292 def _update_padding(self, pad, angle): 

293 padx = pad * np.cos(angle) / 72 

294 pady = pad * np.sin(angle) / 72 

295 self._text1_translate._t = (padx, pady) 

296 self._text1_translate.invalidate() 

297 self._text2_translate._t = (-padx, -pady) 

298 self._text2_translate.invalidate() 

299 

300 def update_position(self, loc): 

301 super().update_position(loc) 

302 axes = self.axes 

303 angle = loc * axes.get_theta_direction() + axes.get_theta_offset() 

304 text_angle = np.rad2deg(angle) % 360 - 90 

305 angle -= np.pi / 2 

306 

307 marker = self.tick1line.get_marker() 

308 if marker in (mmarkers.TICKUP, '|'): 

309 trans = mtransforms.Affine2D().scale(1, 1).rotate(angle) 

310 elif marker == mmarkers.TICKDOWN: 

311 trans = mtransforms.Affine2D().scale(1, -1).rotate(angle) 

312 else: 

313 # Don't modify custom tick line markers. 

314 trans = self.tick1line._marker._transform 

315 self.tick1line._marker._transform = trans 

316 

317 marker = self.tick2line.get_marker() 

318 if marker in (mmarkers.TICKUP, '|'): 

319 trans = mtransforms.Affine2D().scale(1, 1).rotate(angle) 

320 elif marker == mmarkers.TICKDOWN: 

321 trans = mtransforms.Affine2D().scale(1, -1).rotate(angle) 

322 else: 

323 # Don't modify custom tick line markers. 

324 trans = self.tick2line._marker._transform 

325 self.tick2line._marker._transform = trans 

326 

327 mode, user_angle = self._labelrotation 

328 if mode == 'default': 

329 text_angle = user_angle 

330 else: 

331 if text_angle > 90: 

332 text_angle -= 180 

333 elif text_angle < -90: 

334 text_angle += 180 

335 text_angle += user_angle 

336 self.label1.set_rotation(text_angle) 

337 self.label2.set_rotation(text_angle) 

338 

339 # This extra padding helps preserve the look from previous releases but 

340 # is also needed because labels are anchored to their center. 

341 pad = self._pad + 7 

342 self._update_padding(pad, 

343 self._loc * axes.get_theta_direction() + 

344 axes.get_theta_offset()) 

345 

346 

347class ThetaAxis(maxis.XAxis): 

348 """ 

349 A theta Axis. 

350 

351 This overrides certain properties of an `XAxis` to provide special-casing 

352 for an angular axis. 

353 """ 

354 __name__ = 'thetaaxis' 

355 axis_name = 'theta' #: Read-only name identifying the axis. 

356 

357 def _get_tick(self, major): 

358 if major: 

359 tick_kw = self._major_tick_kw 

360 else: 

361 tick_kw = self._minor_tick_kw 

362 return ThetaTick(self.axes, 0, '', major=major, **tick_kw) 

363 

364 def _wrap_locator_formatter(self): 

365 self.set_major_locator(ThetaLocator(self.get_major_locator())) 

366 self.set_major_formatter(ThetaFormatter()) 

367 self.isDefault_majloc = True 

368 self.isDefault_majfmt = True 

369 

370 def cla(self): 

371 super().cla() 

372 self.set_ticks_position('none') 

373 self._wrap_locator_formatter() 

374 

375 def _set_scale(self, value, **kwargs): 

376 super()._set_scale(value, **kwargs) 

377 self._wrap_locator_formatter() 

378 

379 def _copy_tick_props(self, src, dest): 

380 'Copy the props from src tick to dest tick' 

381 if src is None or dest is None: 

382 return 

383 super()._copy_tick_props(src, dest) 

384 

385 # Ensure that tick transforms are independent so that padding works. 

386 trans = dest._get_text1_transform()[0] 

387 dest.label1.set_transform(trans + dest._text1_translate) 

388 trans = dest._get_text2_transform()[0] 

389 dest.label2.set_transform(trans + dest._text2_translate) 

390 

391 

392class RadialLocator(mticker.Locator): 

393 """ 

394 Used to locate radius ticks. 

395 

396 Ensures that all ticks are strictly positive. For all other 

397 tasks, it delegates to the base 

398 :class:`~matplotlib.ticker.Locator` (which may be different 

399 depending on the scale of the *r*-axis. 

400 """ 

401 

402 def __init__(self, base, axes=None): 

403 self.base = base 

404 self._axes = axes 

405 

406 def __call__(self): 

407 show_all = True 

408 # Ensure previous behaviour with full circle non-annular views. 

409 if self._axes: 

410 if _is_full_circle_rad(*self._axes.viewLim.intervalx): 

411 rorigin = self._axes.get_rorigin() * self._axes.get_rsign() 

412 if self._axes.get_rmin() <= rorigin: 

413 show_all = False 

414 if show_all: 

415 return self.base() 

416 else: 

417 return [tick for tick in self.base() if tick > rorigin] 

418 

419 @cbook.deprecated("3.2") 

420 def autoscale(self): 

421 return self.base.autoscale() 

422 

423 def pan(self, numsteps): 

424 return self.base.pan(numsteps) 

425 

426 def zoom(self, direction): 

427 return self.base.zoom(direction) 

428 

429 def refresh(self): 

430 # docstring inherited 

431 return self.base.refresh() 

432 

433 def nonsingular(self, vmin, vmax): 

434 # docstring inherited 

435 return ((0, 1) if (vmin, vmax) == (-np.inf, np.inf) # Init. limits. 

436 else self.base.nonsingular(vmin, vmax)) 

437 

438 def view_limits(self, vmin, vmax): 

439 vmin, vmax = self.base.view_limits(vmin, vmax) 

440 if vmax > vmin: 

441 # this allows inverted r/y-lims 

442 vmin = min(0, vmin) 

443 return mtransforms.nonsingular(vmin, vmax) 

444 

445 

446class _ThetaShift(mtransforms.ScaledTranslation): 

447 """ 

448 Apply a padding shift based on axes theta limits. 

449 

450 This is used to create padding for radial ticks. 

451 

452 Parameters 

453 ---------- 

454 axes : `~matplotlib.axes.Axes` 

455 The owning axes; used to determine limits. 

456 pad : float 

457 The padding to apply, in points. 

458 mode : {'min', 'max', 'rlabel'} 

459 Whether to shift away from the start (``'min'``) or the end (``'max'``) 

460 of the axes, or using the rlabel position (``'rlabel'``). 

461 """ 

462 def __init__(self, axes, pad, mode): 

463 mtransforms.ScaledTranslation.__init__(self, pad, pad, 

464 axes.figure.dpi_scale_trans) 

465 self.set_children(axes._realViewLim) 

466 self.axes = axes 

467 self.mode = mode 

468 self.pad = pad 

469 

470 def __str__(self): 

471 return ("{}(\n" 

472 "{},\n" 

473 "{},\n" 

474 "{})" 

475 .format(type(self).__name__, 

476 mtransforms._indent_str(self.axes), 

477 mtransforms._indent_str(self.pad), 

478 mtransforms._indent_str(repr(self.mode)))) 

479 

480 def get_matrix(self): 

481 if self._invalid: 

482 if self.mode == 'rlabel': 

483 angle = ( 

484 np.deg2rad(self.axes.get_rlabel_position()) * 

485 self.axes.get_theta_direction() + 

486 self.axes.get_theta_offset() 

487 ) 

488 else: 

489 if self.mode == 'min': 

490 angle = self.axes._realViewLim.xmin 

491 elif self.mode == 'max': 

492 angle = self.axes._realViewLim.xmax 

493 

494 if self.mode in ('rlabel', 'min'): 

495 padx = np.cos(angle - np.pi / 2) 

496 pady = np.sin(angle - np.pi / 2) 

497 else: 

498 padx = np.cos(angle + np.pi / 2) 

499 pady = np.sin(angle + np.pi / 2) 

500 

501 self._t = (self.pad * padx / 72, self.pad * pady / 72) 

502 return mtransforms.ScaledTranslation.get_matrix(self) 

503 

504 

505class RadialTick(maxis.YTick): 

506 """ 

507 A radial-axis tick. 

508 

509 This subclass of `YTick` provides radial ticks with some small modification 

510 to their re-positioning such that ticks are rotated based on axes limits. 

511 This results in ticks that are correctly perpendicular to the spine. Labels 

512 are also rotated to be perpendicular to the spine, when 'auto' rotation is 

513 enabled. 

514 """ 

515 def _get_text1(self): 

516 t = super()._get_text1() 

517 t.set_rotation_mode('anchor') 

518 return t 

519 

520 def _get_text2(self): 

521 t = super()._get_text2() 

522 t.set_rotation_mode('anchor') 

523 return t 

524 

525 def _determine_anchor(self, mode, angle, start): 

526 # Note: angle is the (spine angle - 90) because it's used for the tick 

527 # & text setup, so all numbers below are -90 from (normed) spine angle. 

528 if mode == 'auto': 

529 if start: 

530 if -90 <= angle <= 90: 

531 return 'left', 'center' 

532 else: 

533 return 'right', 'center' 

534 else: 

535 if -90 <= angle <= 90: 

536 return 'right', 'center' 

537 else: 

538 return 'left', 'center' 

539 else: 

540 if start: 

541 if angle < -68.5: 

542 return 'center', 'top' 

543 elif angle < -23.5: 

544 return 'left', 'top' 

545 elif angle < 22.5: 

546 return 'left', 'center' 

547 elif angle < 67.5: 

548 return 'left', 'bottom' 

549 elif angle < 112.5: 

550 return 'center', 'bottom' 

551 elif angle < 157.5: 

552 return 'right', 'bottom' 

553 elif angle < 202.5: 

554 return 'right', 'center' 

555 elif angle < 247.5: 

556 return 'right', 'top' 

557 else: 

558 return 'center', 'top' 

559 else: 

560 if angle < -68.5: 

561 return 'center', 'bottom' 

562 elif angle < -23.5: 

563 return 'right', 'bottom' 

564 elif angle < 22.5: 

565 return 'right', 'center' 

566 elif angle < 67.5: 

567 return 'right', 'top' 

568 elif angle < 112.5: 

569 return 'center', 'top' 

570 elif angle < 157.5: 

571 return 'left', 'top' 

572 elif angle < 202.5: 

573 return 'left', 'center' 

574 elif angle < 247.5: 

575 return 'left', 'bottom' 

576 else: 

577 return 'center', 'bottom' 

578 

579 def update_position(self, loc): 

580 super().update_position(loc) 

581 axes = self.axes 

582 thetamin = axes.get_thetamin() 

583 thetamax = axes.get_thetamax() 

584 direction = axes.get_theta_direction() 

585 offset_rad = axes.get_theta_offset() 

586 offset = np.rad2deg(offset_rad) 

587 full = _is_full_circle_deg(thetamin, thetamax) 

588 

589 if full: 

590 angle = (axes.get_rlabel_position() * direction + 

591 offset) % 360 - 90 

592 tick_angle = 0 

593 else: 

594 angle = (thetamin * direction + offset) % 360 - 90 

595 if direction > 0: 

596 tick_angle = np.deg2rad(angle) 

597 else: 

598 tick_angle = np.deg2rad(angle + 180) 

599 text_angle = (angle + 90) % 180 - 90 # between -90 and +90. 

600 mode, user_angle = self._labelrotation 

601 if mode == 'auto': 

602 text_angle += user_angle 

603 else: 

604 text_angle = user_angle 

605 

606 if full: 

607 ha = self.label1.get_horizontalalignment() 

608 va = self.label1.get_verticalalignment() 

609 else: 

610 ha, va = self._determine_anchor(mode, angle, direction > 0) 

611 self.label1.set_horizontalalignment(ha) 

612 self.label1.set_verticalalignment(va) 

613 self.label1.set_rotation(text_angle) 

614 

615 marker = self.tick1line.get_marker() 

616 if marker == mmarkers.TICKLEFT: 

617 trans = mtransforms.Affine2D().rotate(tick_angle) 

618 elif marker == '_': 

619 trans = mtransforms.Affine2D().rotate(tick_angle + np.pi / 2) 

620 elif marker == mmarkers.TICKRIGHT: 

621 trans = mtransforms.Affine2D().scale(-1, 1).rotate(tick_angle) 

622 else: 

623 # Don't modify custom tick line markers. 

624 trans = self.tick1line._marker._transform 

625 self.tick1line._marker._transform = trans 

626 

627 if full: 

628 self.label2.set_visible(False) 

629 self.tick2line.set_visible(False) 

630 angle = (thetamax * direction + offset) % 360 - 90 

631 if direction > 0: 

632 tick_angle = np.deg2rad(angle) 

633 else: 

634 tick_angle = np.deg2rad(angle + 180) 

635 text_angle = (angle + 90) % 180 - 90 # between -90 and +90. 

636 mode, user_angle = self._labelrotation 

637 if mode == 'auto': 

638 text_angle += user_angle 

639 else: 

640 text_angle = user_angle 

641 

642 ha, va = self._determine_anchor(mode, angle, direction < 0) 

643 self.label2.set_ha(ha) 

644 self.label2.set_va(va) 

645 self.label2.set_rotation(text_angle) 

646 

647 marker = self.tick2line.get_marker() 

648 if marker == mmarkers.TICKLEFT: 

649 trans = mtransforms.Affine2D().rotate(tick_angle) 

650 elif marker == '_': 

651 trans = mtransforms.Affine2D().rotate(tick_angle + np.pi / 2) 

652 elif marker == mmarkers.TICKRIGHT: 

653 trans = mtransforms.Affine2D().scale(-1, 1).rotate(tick_angle) 

654 else: 

655 # Don't modify custom tick line markers. 

656 trans = self.tick2line._marker._transform 

657 self.tick2line._marker._transform = trans 

658 

659 

660class RadialAxis(maxis.YAxis): 

661 """ 

662 A radial Axis. 

663 

664 This overrides certain properties of a `YAxis` to provide special-casing 

665 for a radial axis. 

666 """ 

667 __name__ = 'radialaxis' 

668 axis_name = 'radius' #: Read-only name identifying the axis. 

669 

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

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

672 self.sticky_edges.y.append(0) 

673 

674 def _get_tick(self, major): 

675 if major: 

676 tick_kw = self._major_tick_kw 

677 else: 

678 tick_kw = self._minor_tick_kw 

679 return RadialTick(self.axes, 0, '', major=major, **tick_kw) 

680 

681 def _wrap_locator_formatter(self): 

682 self.set_major_locator(RadialLocator(self.get_major_locator(), 

683 self.axes)) 

684 self.isDefault_majloc = True 

685 

686 def cla(self): 

687 super().cla() 

688 self.set_ticks_position('none') 

689 self._wrap_locator_formatter() 

690 

691 def _set_scale(self, value, **kwargs): 

692 super()._set_scale(value, **kwargs) 

693 self._wrap_locator_formatter() 

694 

695 

696def _is_full_circle_deg(thetamin, thetamax): 

697 """ 

698 Determine if a wedge (in degrees) spans the full circle. 

699 

700 The condition is derived from :class:`~matplotlib.patches.Wedge`. 

701 """ 

702 return abs(abs(thetamax - thetamin) - 360.0) < 1e-12 

703 

704 

705def _is_full_circle_rad(thetamin, thetamax): 

706 """ 

707 Determine if a wedge (in radians) spans the full circle. 

708 

709 The condition is derived from :class:`~matplotlib.patches.Wedge`. 

710 """ 

711 return abs(abs(thetamax - thetamin) - 2 * np.pi) < 1.74e-14 

712 

713 

714class _WedgeBbox(mtransforms.Bbox): 

715 """ 

716 Transform (theta, r) wedge Bbox into axes bounding box. 

717 

718 Parameters 

719 ---------- 

720 center : (float, float) 

721 Center of the wedge 

722 viewLim : `~matplotlib.transforms.Bbox` 

723 Bbox determining the boundaries of the wedge 

724 originLim : `~matplotlib.transforms.Bbox` 

725 Bbox determining the origin for the wedge, if different from *viewLim* 

726 """ 

727 def __init__(self, center, viewLim, originLim, **kwargs): 

728 mtransforms.Bbox.__init__(self, 

729 np.array([[0.0, 0.0], [1.0, 1.0]], np.float), 

730 **kwargs) 

731 self._center = center 

732 self._viewLim = viewLim 

733 self._originLim = originLim 

734 self.set_children(viewLim, originLim) 

735 

736 def __str__(self): 

737 return ("{}(\n" 

738 "{},\n" 

739 "{},\n" 

740 "{})" 

741 .format(type(self).__name__, 

742 mtransforms._indent_str(self._center), 

743 mtransforms._indent_str(self._viewLim), 

744 mtransforms._indent_str(self._originLim))) 

745 

746 def get_points(self): 

747 # docstring inherited 

748 if self._invalid: 

749 points = self._viewLim.get_points().copy() 

750 # Scale angular limits to work with Wedge. 

751 points[:, 0] *= 180 / np.pi 

752 if points[0, 0] > points[1, 0]: 

753 points[:, 0] = points[::-1, 0] 

754 

755 # Scale radial limits based on origin radius. 

756 points[:, 1] -= self._originLim.y0 

757 

758 # Scale radial limits to match axes limits. 

759 rscale = 0.5 / points[1, 1] 

760 points[:, 1] *= rscale 

761 width = min(points[1, 1] - points[0, 1], 0.5) 

762 

763 # Generate bounding box for wedge. 

764 wedge = mpatches.Wedge(self._center, points[1, 1], 

765 points[0, 0], points[1, 0], 

766 width=width) 

767 self.update_from_path(wedge.get_path()) 

768 

769 # Ensure equal aspect ratio. 

770 w, h = self._points[1] - self._points[0] 

771 deltah = max(w - h, 0) / 2 

772 deltaw = max(h - w, 0) / 2 

773 self._points += np.array([[-deltaw, -deltah], [deltaw, deltah]]) 

774 

775 self._invalid = 0 

776 

777 return self._points 

778 

779 

780class PolarAxes(Axes): 

781 """ 

782 A polar graph projection, where the input dimensions are *theta*, *r*. 

783 

784 Theta starts pointing east and goes anti-clockwise. 

785 """ 

786 name = 'polar' 

787 

788 def __init__(self, *args, 

789 theta_offset=0, theta_direction=1, rlabel_position=22.5, 

790 **kwargs): 

791 # docstring inherited 

792 self._default_theta_offset = theta_offset 

793 self._default_theta_direction = theta_direction 

794 self._default_rlabel_position = np.deg2rad(rlabel_position) 

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

796 self.use_sticky_edges = True 

797 self.set_aspect('equal', adjustable='box', anchor='C') 

798 self.cla() 

799 

800 def cla(self): 

801 Axes.cla(self) 

802 

803 self.title.set_y(1.05) 

804 

805 start = self.spines.get('start', None) 

806 if start: 

807 start.set_visible(False) 

808 end = self.spines.get('end', None) 

809 if end: 

810 end.set_visible(False) 

811 self.set_xlim(0.0, 2 * np.pi) 

812 

813 self.grid(rcParams['polaraxes.grid']) 

814 inner = self.spines.get('inner', None) 

815 if inner: 

816 inner.set_visible(False) 

817 

818 self.set_rorigin(None) 

819 self.set_theta_offset(self._default_theta_offset) 

820 self.set_theta_direction(self._default_theta_direction) 

821 

822 def _init_axis(self): 

823 "move this out of __init__ because non-separable axes don't use it" 

824 self.xaxis = ThetaAxis(self) 

825 self.yaxis = RadialAxis(self) 

826 # Calling polar_axes.xaxis.cla() or polar_axes.xaxis.cla() 

827 # results in weird artifacts. Therefore we disable this for 

828 # now. 

829 # self.spines['polar'].register_axis(self.yaxis) 

830 self._update_transScale() 

831 

832 def _set_lim_and_transforms(self): 

833 # A view limit where the minimum radius can be locked if the user 

834 # specifies an alternate origin. 

835 self._originViewLim = mtransforms.LockableBbox(self.viewLim) 

836 

837 # Handle angular offset and direction. 

838 self._direction = mtransforms.Affine2D() \ 

839 .scale(self._default_theta_direction, 1.0) 

840 self._theta_offset = mtransforms.Affine2D() \ 

841 .translate(self._default_theta_offset, 0.0) 

842 self.transShift = mtransforms.composite_transform_factory( 

843 self._direction, 

844 self._theta_offset) 

845 # A view limit shifted to the correct location after accounting for 

846 # orientation and offset. 

847 self._realViewLim = mtransforms.TransformedBbox(self.viewLim, 

848 self.transShift) 

849 

850 # Transforms the x and y axis separately by a scale factor 

851 # It is assumed that this part will have non-linear components 

852 self.transScale = mtransforms.TransformWrapper( 

853 mtransforms.IdentityTransform()) 

854 

855 # Scale view limit into a bbox around the selected wedge. This may be 

856 # smaller than the usual unit axes rectangle if not plotting the full 

857 # circle. 

858 self.axesLim = _WedgeBbox((0.5, 0.5), 

859 self._realViewLim, self._originViewLim) 

860 

861 # Scale the wedge to fill the axes. 

862 self.transWedge = mtransforms.BboxTransformFrom(self.axesLim) 

863 

864 # Scale the axes to fill the figure. 

865 self.transAxes = mtransforms.BboxTransformTo(self.bbox) 

866 

867 # A (possibly non-linear) projection on the (already scaled) 

868 # data. This one is aware of rmin 

869 self.transProjection = self.PolarTransform( 

870 self, 

871 _apply_theta_transforms=False) 

872 # Add dependency on rorigin. 

873 self.transProjection.set_children(self._originViewLim) 

874 

875 # An affine transformation on the data, generally to limit the 

876 # range of the axes 

877 self.transProjectionAffine = self.PolarAffine(self.transScale, 

878 self._originViewLim) 

879 

880 # The complete data transformation stack -- from data all the 

881 # way to display coordinates 

882 self.transData = ( 

883 self.transScale + self.transShift + self.transProjection + 

884 (self.transProjectionAffine + self.transWedge + self.transAxes)) 

885 

886 # This is the transform for theta-axis ticks. It is 

887 # equivalent to transData, except it always puts r == 0.0 and r == 1.0 

888 # at the edge of the axis circles. 

889 self._xaxis_transform = ( 

890 mtransforms.blended_transform_factory( 

891 mtransforms.IdentityTransform(), 

892 mtransforms.BboxTransformTo(self.viewLim)) + 

893 self.transData) 

894 # The theta labels are flipped along the radius, so that text 1 is on 

895 # the outside by default. This should work the same as before. 

896 flipr_transform = mtransforms.Affine2D() \ 

897 .translate(0.0, -0.5) \ 

898 .scale(1.0, -1.0) \ 

899 .translate(0.0, 0.5) 

900 self._xaxis_text_transform = flipr_transform + self._xaxis_transform 

901 

902 # This is the transform for r-axis ticks. It scales the theta 

903 # axis so the gridlines from 0.0 to 1.0, now go from thetamin to 

904 # thetamax. 

905 self._yaxis_transform = ( 

906 mtransforms.blended_transform_factory( 

907 mtransforms.BboxTransformTo(self.viewLim), 

908 mtransforms.IdentityTransform()) + 

909 self.transData) 

910 # The r-axis labels are put at an angle and padded in the r-direction 

911 self._r_label_position = mtransforms.Affine2D() \ 

912 .translate(self._default_rlabel_position, 0.0) 

913 self._yaxis_text_transform = mtransforms.TransformWrapper( 

914 self._r_label_position + self.transData) 

915 

916 def get_xaxis_transform(self, which='grid'): 

917 cbook._check_in_list(['tick1', 'tick2', 'grid'], which=which) 

918 return self._xaxis_transform 

919 

920 def get_xaxis_text1_transform(self, pad): 

921 return self._xaxis_text_transform, 'center', 'center' 

922 

923 def get_xaxis_text2_transform(self, pad): 

924 return self._xaxis_text_transform, 'center', 'center' 

925 

926 def get_yaxis_transform(self, which='grid'): 

927 if which in ('tick1', 'tick2'): 

928 return self._yaxis_text_transform 

929 elif which == 'grid': 

930 return self._yaxis_transform 

931 else: 

932 cbook._check_in_list(['tick1', 'tick2', 'grid'], which=which) 

933 

934 def get_yaxis_text1_transform(self, pad): 

935 thetamin, thetamax = self._realViewLim.intervalx 

936 if _is_full_circle_rad(thetamin, thetamax): 

937 return self._yaxis_text_transform, 'bottom', 'left' 

938 elif self.get_theta_direction() > 0: 

939 halign = 'left' 

940 pad_shift = _ThetaShift(self, pad, 'min') 

941 else: 

942 halign = 'right' 

943 pad_shift = _ThetaShift(self, pad, 'max') 

944 return self._yaxis_text_transform + pad_shift, 'center', halign 

945 

946 def get_yaxis_text2_transform(self, pad): 

947 if self.get_theta_direction() > 0: 

948 halign = 'right' 

949 pad_shift = _ThetaShift(self, pad, 'max') 

950 else: 

951 halign = 'left' 

952 pad_shift = _ThetaShift(self, pad, 'min') 

953 return self._yaxis_text_transform + pad_shift, 'center', halign 

954 

955 def draw(self, *args, **kwargs): 

956 self._unstale_viewLim() 

957 thetamin, thetamax = np.rad2deg(self._realViewLim.intervalx) 

958 if thetamin > thetamax: 

959 thetamin, thetamax = thetamax, thetamin 

960 rmin, rmax = ((self._realViewLim.intervaly - self.get_rorigin()) * 

961 self.get_rsign()) 

962 if isinstance(self.patch, mpatches.Wedge): 

963 # Backwards-compatibility: Any subclassed Axes might override the 

964 # patch to not be the Wedge that PolarAxes uses. 

965 center = self.transWedge.transform((0.5, 0.5)) 

966 self.patch.set_center(center) 

967 self.patch.set_theta1(thetamin) 

968 self.patch.set_theta2(thetamax) 

969 

970 edge, _ = self.transWedge.transform((1, 0)) 

971 radius = edge - center[0] 

972 width = min(radius * (rmax - rmin) / rmax, radius) 

973 self.patch.set_radius(radius) 

974 self.patch.set_width(width) 

975 

976 inner_width = radius - width 

977 inner = self.spines.get('inner', None) 

978 if inner: 

979 inner.set_visible(inner_width != 0.0) 

980 

981 visible = not _is_full_circle_deg(thetamin, thetamax) 

982 # For backwards compatibility, any subclassed Axes might override the 

983 # spines to not include start/end that PolarAxes uses. 

984 start = self.spines.get('start', None) 

985 end = self.spines.get('end', None) 

986 if start: 

987 start.set_visible(visible) 

988 if end: 

989 end.set_visible(visible) 

990 if visible: 

991 yaxis_text_transform = self._yaxis_transform 

992 else: 

993 yaxis_text_transform = self._r_label_position + self.transData 

994 if self._yaxis_text_transform != yaxis_text_transform: 

995 self._yaxis_text_transform.set(yaxis_text_transform) 

996 self.yaxis.reset_ticks() 

997 self.yaxis.set_clip_path(self.patch) 

998 

999 Axes.draw(self, *args, **kwargs) 

1000 

1001 def _gen_axes_patch(self): 

1002 return mpatches.Wedge((0.5, 0.5), 0.5, 0.0, 360.0) 

1003 

1004 def _gen_axes_spines(self): 

1005 spines = OrderedDict([ 

1006 ('polar', mspines.Spine.arc_spine(self, 'top', 

1007 (0.5, 0.5), 0.5, 0.0, 360.0)), 

1008 ('start', mspines.Spine.linear_spine(self, 'left')), 

1009 ('end', mspines.Spine.linear_spine(self, 'right')), 

1010 ('inner', mspines.Spine.arc_spine(self, 'bottom', 

1011 (0.5, 0.5), 0.0, 0.0, 360.0)) 

1012 ]) 

1013 spines['polar'].set_transform(self.transWedge + self.transAxes) 

1014 spines['inner'].set_transform(self.transWedge + self.transAxes) 

1015 spines['start'].set_transform(self._yaxis_transform) 

1016 spines['end'].set_transform(self._yaxis_transform) 

1017 return spines 

1018 

1019 def set_thetamax(self, thetamax): 

1020 """Set the maximum theta limit in degrees.""" 

1021 self.viewLim.x1 = np.deg2rad(thetamax) 

1022 

1023 def get_thetamax(self): 

1024 """Return the maximum theta limit in degrees.""" 

1025 return np.rad2deg(self.viewLim.xmax) 

1026 

1027 def set_thetamin(self, thetamin): 

1028 """Set the minimum theta limit in degrees.""" 

1029 self.viewLim.x0 = np.deg2rad(thetamin) 

1030 

1031 def get_thetamin(self): 

1032 """Get the minimum theta limit in degrees.""" 

1033 return np.rad2deg(self.viewLim.xmin) 

1034 

1035 def set_thetalim(self, *args, **kwargs): 

1036 """ 

1037 Set the minimum and maximum theta values. 

1038 

1039 Parameters 

1040 ---------- 

1041 thetamin : float 

1042 Minimum value in degrees. 

1043 thetamax : float 

1044 Maximum value in degrees. 

1045 """ 

1046 if 'thetamin' in kwargs: 

1047 kwargs['xmin'] = np.deg2rad(kwargs.pop('thetamin')) 

1048 if 'thetamax' in kwargs: 

1049 kwargs['xmax'] = np.deg2rad(kwargs.pop('thetamax')) 

1050 return tuple(np.rad2deg(self.set_xlim(*args, **kwargs))) 

1051 

1052 def set_theta_offset(self, offset): 

1053 """ 

1054 Set the offset for the location of 0 in radians. 

1055 """ 

1056 mtx = self._theta_offset.get_matrix() 

1057 mtx[0, 2] = offset 

1058 self._theta_offset.invalidate() 

1059 

1060 def get_theta_offset(self): 

1061 """ 

1062 Get the offset for the location of 0 in radians. 

1063 """ 

1064 return self._theta_offset.get_matrix()[0, 2] 

1065 

1066 def set_theta_zero_location(self, loc, offset=0.0): 

1067 """ 

1068 Sets the location of theta's zero. (Calls set_theta_offset 

1069 with the correct value in radians under the hood.) 

1070 

1071 loc : str 

1072 May be one of "N", "NW", "W", "SW", "S", "SE", "E", or "NE". 

1073 

1074 offset : float, optional 

1075 An offset in degrees to apply from the specified `loc`. **Note:** 

1076 this offset is *always* applied counter-clockwise regardless of 

1077 the direction setting. 

1078 """ 

1079 mapping = { 

1080 'N': np.pi * 0.5, 

1081 'NW': np.pi * 0.75, 

1082 'W': np.pi, 

1083 'SW': np.pi * 1.25, 

1084 'S': np.pi * 1.5, 

1085 'SE': np.pi * 1.75, 

1086 'E': 0, 

1087 'NE': np.pi * 0.25} 

1088 return self.set_theta_offset(mapping[loc] + np.deg2rad(offset)) 

1089 

1090 def set_theta_direction(self, direction): 

1091 """ 

1092 Set the direction in which theta increases. 

1093 

1094 clockwise, -1: 

1095 Theta increases in the clockwise direction 

1096 

1097 counterclockwise, anticlockwise, 1: 

1098 Theta increases in the counterclockwise direction 

1099 """ 

1100 mtx = self._direction.get_matrix() 

1101 if direction in ('clockwise', -1): 

1102 mtx[0, 0] = -1 

1103 elif direction in ('counterclockwise', 'anticlockwise', 1): 

1104 mtx[0, 0] = 1 

1105 else: 

1106 cbook._check_in_list( 

1107 [-1, 1, 'clockwise', 'counterclockwise', 'anticlockwise'], 

1108 direction=direction) 

1109 self._direction.invalidate() 

1110 

1111 def get_theta_direction(self): 

1112 """ 

1113 Get the direction in which theta increases. 

1114 

1115 -1: 

1116 Theta increases in the clockwise direction 

1117 

1118 1: 

1119 Theta increases in the counterclockwise direction 

1120 """ 

1121 return self._direction.get_matrix()[0, 0] 

1122 

1123 def set_rmax(self, rmax): 

1124 """ 

1125 Set the outer radial limit. 

1126 

1127 Parameters 

1128 ---------- 

1129 rmax : float 

1130 """ 

1131 self.viewLim.y1 = rmax 

1132 

1133 def get_rmax(self): 

1134 """ 

1135 Returns 

1136 ------- 

1137 float 

1138 Outer radial limit. 

1139 """ 

1140 return self.viewLim.ymax 

1141 

1142 def set_rmin(self, rmin): 

1143 """ 

1144 Set the inner radial limit. 

1145 

1146 Parameters 

1147 ---------- 

1148 rmin : float 

1149 """ 

1150 self.viewLim.y0 = rmin 

1151 

1152 def get_rmin(self): 

1153 """ 

1154 Returns 

1155 ------- 

1156 float 

1157 The inner radial limit. 

1158 """ 

1159 return self.viewLim.ymin 

1160 

1161 def set_rorigin(self, rorigin): 

1162 """ 

1163 Update the radial origin. 

1164 

1165 Parameters 

1166 ---------- 

1167 rorigin : float 

1168 """ 

1169 self._originViewLim.locked_y0 = rorigin 

1170 

1171 def get_rorigin(self): 

1172 """ 

1173 Returns 

1174 ------- 

1175 float 

1176 """ 

1177 return self._originViewLim.y0 

1178 

1179 def get_rsign(self): 

1180 return np.sign(self._originViewLim.y1 - self._originViewLim.y0) 

1181 

1182 def set_rlim(self, bottom=None, top=None, emit=True, auto=False, **kwargs): 

1183 """ 

1184 See `~.polar.PolarAxes.set_ylim`. 

1185 """ 

1186 if 'rmin' in kwargs: 

1187 if bottom is None: 

1188 bottom = kwargs.pop('rmin') 

1189 else: 

1190 raise ValueError('Cannot supply both positional "bottom"' 

1191 'argument and kwarg "rmin"') 

1192 if 'rmax' in kwargs: 

1193 if top is None: 

1194 top = kwargs.pop('rmax') 

1195 else: 

1196 raise ValueError('Cannot supply both positional "top"' 

1197 'argument and kwarg "rmax"') 

1198 return self.set_ylim(bottom=bottom, top=top, emit=emit, auto=auto, 

1199 **kwargs) 

1200 

1201 def set_ylim(self, bottom=None, top=None, emit=True, auto=False, 

1202 *, ymin=None, ymax=None): 

1203 """ 

1204 Set the data limits for the radial axis. 

1205 

1206 Parameters 

1207 ---------- 

1208 bottom : scalar, optional 

1209 The bottom limit (default: None, which leaves the bottom 

1210 limit unchanged). 

1211 The bottom and top ylims may be passed as the tuple 

1212 (*bottom*, *top*) as the first positional argument (or as 

1213 the *bottom* keyword argument). 

1214 

1215 top : scalar, optional 

1216 The top limit (default: None, which leaves the top limit 

1217 unchanged). 

1218 

1219 emit : bool, optional 

1220 Whether to notify observers of limit change (default: True). 

1221 

1222 auto : bool or None, optional 

1223 Whether to turn on autoscaling of the y-axis. True turns on, 

1224 False turns off (default action), None leaves unchanged. 

1225 

1226 ymin, ymax : scalar, optional 

1227 These arguments are deprecated and will be removed in a future 

1228 version. They are equivalent to *bottom* and *top* respectively, 

1229 and it is an error to pass both *ymin* and *bottom* or 

1230 *ymax* and *top*. 

1231 

1232 Returns 

1233 ------- 

1234 bottom, top : (float, float) 

1235 The new y-axis limits in data coordinates. 

1236 """ 

1237 if ymin is not None: 

1238 if bottom is not None: 

1239 raise ValueError('Cannot supply both positional "bottom" ' 

1240 'argument and kwarg "ymin"') 

1241 else: 

1242 bottom = ymin 

1243 if ymax is not None: 

1244 if top is not None: 

1245 raise ValueError('Cannot supply both positional "top" ' 

1246 'argument and kwarg "ymax"') 

1247 else: 

1248 top = ymax 

1249 if top is None and np.iterable(bottom): 

1250 bottom, top = bottom[0], bottom[1] 

1251 return super().set_ylim(bottom=bottom, top=top, emit=emit, auto=auto) 

1252 

1253 def get_rlabel_position(self): 

1254 """ 

1255 Returns 

1256 ------- 

1257 float 

1258 The theta position of the radius labels in degrees. 

1259 """ 

1260 return np.rad2deg(self._r_label_position.get_matrix()[0, 2]) 

1261 

1262 def set_rlabel_position(self, value): 

1263 """Updates the theta position of the radius labels. 

1264 

1265 Parameters 

1266 ---------- 

1267 value : number 

1268 The angular position of the radius labels in degrees. 

1269 """ 

1270 self._r_label_position.clear().translate(np.deg2rad(value), 0.0) 

1271 

1272 def set_yscale(self, *args, **kwargs): 

1273 Axes.set_yscale(self, *args, **kwargs) 

1274 self.yaxis.set_major_locator( 

1275 self.RadialLocator(self.yaxis.get_major_locator(), self)) 

1276 

1277 def set_rscale(self, *args, **kwargs): 

1278 return Axes.set_yscale(self, *args, **kwargs) 

1279 

1280 def set_rticks(self, *args, **kwargs): 

1281 return Axes.set_yticks(self, *args, **kwargs) 

1282 

1283 def set_thetagrids(self, angles, labels=None, fmt=None, **kwargs): 

1284 """ 

1285 Set the theta gridlines in a polar plot. 

1286 

1287 Parameters 

1288 ---------- 

1289 angles : tuple with floats, degrees 

1290 The angles of the theta gridlines. 

1291 

1292 labels : tuple with strings or None 

1293 The labels to use at each theta gridline. The 

1294 `.projections.polar.ThetaFormatter` will be used if None. 

1295 

1296 fmt : str or None 

1297 Format string used in `matplotlib.ticker.FormatStrFormatter`. 

1298 For example '%f'. Note that the angle that is used is in 

1299 radians. 

1300 

1301 Returns 

1302 ------- 

1303 lines, labels : list of `.lines.Line2D`, list of `.text.Text` 

1304 *lines* are the theta gridlines and *labels* are the tick labels. 

1305 

1306 Other Parameters 

1307 ---------------- 

1308 **kwargs 

1309 *kwargs* are optional `~.Text` properties for the labels. 

1310 

1311 See Also 

1312 -------- 

1313 .PolarAxes.set_rgrids 

1314 .Axis.get_gridlines 

1315 .Axis.get_ticklabels 

1316 """ 

1317 

1318 # Make sure we take into account unitized data 

1319 angles = self.convert_yunits(angles) 

1320 angles = np.deg2rad(angles) 

1321 self.set_xticks(angles) 

1322 if labels is not None: 

1323 self.set_xticklabels(labels) 

1324 elif fmt is not None: 

1325 self.xaxis.set_major_formatter(mticker.FormatStrFormatter(fmt)) 

1326 for t in self.xaxis.get_ticklabels(): 

1327 t.update(kwargs) 

1328 return self.xaxis.get_ticklines(), self.xaxis.get_ticklabels() 

1329 

1330 def set_rgrids(self, radii, labels=None, angle=None, fmt=None, 

1331 **kwargs): 

1332 """ 

1333 Set the radial gridlines on a polar plot. 

1334 

1335 Parameters 

1336 ---------- 

1337 radii : tuple with floats 

1338 The radii for the radial gridlines 

1339 

1340 labels : tuple with strings or None 

1341 The labels to use at each radial gridline. The 

1342 `matplotlib.ticker.ScalarFormatter` will be used if None. 

1343 

1344 angle : float 

1345 The angular position of the radius labels in degrees. 

1346 

1347 fmt : str or None 

1348 Format string used in `matplotlib.ticker.FormatStrFormatter`. 

1349 For example '%f'. 

1350 

1351 Returns 

1352 ------- 

1353 lines, labels : list of `.lines.Line2D`, list of `.text.Text` 

1354 *lines* are the radial gridlines and *labels* are the tick labels. 

1355 

1356 Other Parameters 

1357 ---------------- 

1358 **kwargs 

1359 *kwargs* are optional `~.Text` properties for the labels. 

1360 

1361 See Also 

1362 -------- 

1363 .PolarAxes.set_thetagrids 

1364 .Axis.get_gridlines 

1365 .Axis.get_ticklabels 

1366 """ 

1367 # Make sure we take into account unitized data 

1368 radii = self.convert_xunits(radii) 

1369 radii = np.asarray(radii) 

1370 

1371 self.set_yticks(radii) 

1372 if labels is not None: 

1373 self.set_yticklabels(labels) 

1374 elif fmt is not None: 

1375 self.yaxis.set_major_formatter(mticker.FormatStrFormatter(fmt)) 

1376 if angle is None: 

1377 angle = self.get_rlabel_position() 

1378 self.set_rlabel_position(angle) 

1379 for t in self.yaxis.get_ticklabels(): 

1380 t.update(kwargs) 

1381 return self.yaxis.get_gridlines(), self.yaxis.get_ticklabels() 

1382 

1383 def set_xscale(self, scale, *args, **kwargs): 

1384 if scale != 'linear': 

1385 raise NotImplementedError( 

1386 "You can not set the xscale on a polar plot.") 

1387 

1388 def format_coord(self, theta, r): 

1389 """ 

1390 Return a format string formatting the coordinate using Unicode 

1391 characters. 

1392 """ 

1393 if theta < 0: 

1394 theta += 2 * np.pi 

1395 theta /= np.pi 

1396 return ('\N{GREEK SMALL LETTER THETA}=%0.3f\N{GREEK SMALL LETTER PI} ' 

1397 '(%0.3f\N{DEGREE SIGN}), r=%0.3f') % (theta, theta * 180.0, r) 

1398 

1399 def get_data_ratio(self): 

1400 ''' 

1401 Return the aspect ratio of the data itself. For a polar plot, 

1402 this should always be 1.0 

1403 ''' 

1404 return 1.0 

1405 

1406 # # # Interactive panning 

1407 

1408 def can_zoom(self): 

1409 """ 

1410 Return *True* if this axes supports the zoom box button functionality. 

1411 

1412 Polar axes do not support zoom boxes. 

1413 """ 

1414 return False 

1415 

1416 def can_pan(self): 

1417 """ 

1418 Return *True* if this axes supports the pan/zoom button functionality. 

1419 

1420 For polar axes, this is slightly misleading. Both panning and 

1421 zooming are performed by the same button. Panning is performed 

1422 in azimuth while zooming is done along the radial. 

1423 """ 

1424 return True 

1425 

1426 def start_pan(self, x, y, button): 

1427 angle = np.deg2rad(self.get_rlabel_position()) 

1428 mode = '' 

1429 if button == 1: 

1430 epsilon = np.pi / 45.0 

1431 t, r = self.transData.inverted().transform((x, y)) 

1432 if angle - epsilon <= t <= angle + epsilon: 

1433 mode = 'drag_r_labels' 

1434 elif button == 3: 

1435 mode = 'zoom' 

1436 

1437 self._pan_start = types.SimpleNamespace( 

1438 rmax=self.get_rmax(), 

1439 trans=self.transData.frozen(), 

1440 trans_inverse=self.transData.inverted().frozen(), 

1441 r_label_angle=self.get_rlabel_position(), 

1442 x=x, 

1443 y=y, 

1444 mode=mode) 

1445 

1446 def end_pan(self): 

1447 del self._pan_start 

1448 

1449 def drag_pan(self, button, key, x, y): 

1450 p = self._pan_start 

1451 

1452 if p.mode == 'drag_r_labels': 

1453 (startt, startr), (t, r) = p.trans_inverse.transform( 

1454 [(p.x, p.y), (x, y)]) 

1455 

1456 # Deal with theta 

1457 dt = np.rad2deg(startt - t) 

1458 self.set_rlabel_position(p.r_label_angle - dt) 

1459 

1460 trans, vert1, horiz1 = self.get_yaxis_text1_transform(0.0) 

1461 trans, vert2, horiz2 = self.get_yaxis_text2_transform(0.0) 

1462 for t in self.yaxis.majorTicks + self.yaxis.minorTicks: 

1463 t.label1.set_va(vert1) 

1464 t.label1.set_ha(horiz1) 

1465 t.label2.set_va(vert2) 

1466 t.label2.set_ha(horiz2) 

1467 

1468 elif p.mode == 'zoom': 

1469 (startt, startr), (t, r) = p.trans_inverse.transform( 

1470 [(p.x, p.y), (x, y)]) 

1471 

1472 # Deal with r 

1473 scale = r / startr 

1474 self.set_rmax(p.rmax / scale) 

1475 

1476 

1477# to keep things all self contained, we can put aliases to the Polar classes 

1478# defined above. This isn't strictly necessary, but it makes some of the 

1479# code more readable (and provides a backwards compatible Polar API) 

1480PolarAxes.PolarTransform = PolarTransform 

1481PolarAxes.PolarAffine = PolarAffine 

1482PolarAxes.InvertedPolarTransform = InvertedPolarTransform 

1483PolarAxes.ThetaFormatter = ThetaFormatter 

1484PolarAxes.RadialLocator = RadialLocator 

1485PolarAxes.ThetaLocator = ThetaLocator