|
5 | 5 | import matplotlib.ticker as mticker
|
6 | 6 | import matplotlib.transforms as mtransforms
|
7 | 7 |
|
8 |
| -from matplotlib.axes._base import _AxesBase, _process_plot_format |
9 |
| - |
10 |
| -# DUPE FROM AXES. FIXME... |
| 8 | +from matplotlib.axes._base import _AxesBase |
11 | 9 |
|
12 | 10 |
|
13 | 11 | def _make_inset_locator(rect, trans, parent):
|
@@ -39,16 +37,13 @@ def inset_locator(ax, renderer):
|
39 | 37 |
|
40 | 38 | class Secondary_Xaxis(_AxesBase):
|
41 | 39 | """
|
42 |
| - A class to hold a Secondary_Xaxis. |
43 |
| -
|
44 |
| - See `~.axes._axes.Axes.secondary_xaxis` for usage. |
45 |
| -
|
| 40 | + General class to hold a Secondary_X/Yaxis. |
46 | 41 | """
|
| 42 | + |
47 | 43 | def __init__(self, parent, location, conversion, **kwargs):
|
48 | 44 | self._conversion = conversion
|
49 | 45 | self._parent = parent
|
50 | 46 |
|
51 |
| - |
52 | 47 | super().__init__(self._parent.figure, [0, 1., 1, 0.0001], **kwargs)
|
53 | 48 |
|
54 | 49 | self.set_location(location)
|
@@ -98,13 +93,18 @@ def set_location(self, location):
|
98 | 93 | on the parent axes to put the new axes, 0 being the bottom, and
|
99 | 94 | 1.0 being the top.
|
100 | 95 | """
|
| 96 | + |
101 | 97 | self._loc = location
|
102 | 98 | # This puts the rectangle into figure-relative coordinates.
|
103 | 99 | if isinstance(self._loc, str):
|
104 | 100 | if self._loc == 'top':
|
105 | 101 | y = 1.
|
106 | 102 | elif self._loc == 'bottom':
|
107 | 103 | y = 0.
|
| 104 | + else: |
| 105 | + raise ValueError("location must be 'bottom', 'top', or a " |
| 106 | + "float, not '{}'.".format(self._loc)) |
| 107 | + |
108 | 108 | else:
|
109 | 109 | y = self._loc
|
110 | 110 | bounds = [0, y, 1., 1e-10]
|
@@ -232,3 +232,202 @@ def get_tightbbox(self, renderer, call_axes_locator=True):
|
232 | 232 | [b for b in bb if b.width != 0 or b.height != 0])
|
233 | 233 |
|
234 | 234 | return _bbox
|
| 235 | + |
| 236 | + |
| 237 | +class Secondary_Yaxis(_AxesBase): |
| 238 | + """ |
| 239 | + Class to hold a Secondary_Yaxis. |
| 240 | + """ |
| 241 | + |
| 242 | + def __init__(self, parent, location, conversion, **kwargs): |
| 243 | + self._conversion = conversion |
| 244 | + self._parent = parent |
| 245 | + self._x = None # set in set_location |
| 246 | + |
| 247 | + super().__init__(self._parent.figure, [1., 0., 0.00001, 1.], **kwargs) |
| 248 | + |
| 249 | + self.set_location(location) |
| 250 | + |
| 251 | + # styling: |
| 252 | + self.xaxis.set_major_locator(mticker.NullLocator()) |
| 253 | + self.xaxis.set_ticks_position('none') |
| 254 | + self.spines['top'].set_visible(False) |
| 255 | + self.spines['bottom'].set_visible(False) |
| 256 | + if self._x > 0.5: |
| 257 | + self.set_axis_orientation('right') |
| 258 | + else: |
| 259 | + self.set_axis_orientation('left') |
| 260 | + self.set_conversion(conversion) |
| 261 | + |
| 262 | + def set_axis_orientation(self, orient): |
| 263 | + """ |
| 264 | + Set if axes spine and labels are drawn at left or right of the |
| 265 | + axis. |
| 266 | +
|
| 267 | + Parameters |
| 268 | + ---------- |
| 269 | + orient :: string |
| 270 | + either 'left' or 'right' |
| 271 | +
|
| 272 | + """ |
| 273 | + |
| 274 | + self.spines[orient].set_visible(True) |
| 275 | + if orient == 'left': |
| 276 | + self.spines['right'].set_visible(False) |
| 277 | + else: |
| 278 | + self.spines['left'].set_visible(False) |
| 279 | + |
| 280 | + self.yaxis.set_ticks_position(orient) |
| 281 | + self.yaxis.set_label_position(orient) |
| 282 | + |
| 283 | + def set_location(self, location): |
| 284 | + """ |
| 285 | + Set the horizontal location of the axes in parent-normalized |
| 286 | + co-ordinates. |
| 287 | +
|
| 288 | + Parameters |
| 289 | + ---------- |
| 290 | + location : string or scalar |
| 291 | + The position to put the secondary axis. Strings can be 'left' or |
| 292 | + 'right', or scalar can be a float indicating the relative position |
| 293 | + on the parent axes to put the new axes, 0 being the left, and |
| 294 | + 1.0 being the right. |
| 295 | + """ |
| 296 | + |
| 297 | + self._loc = location |
| 298 | + # This puts the rectangle into figure-relative coordinates. |
| 299 | + if isinstance(self._loc, str): |
| 300 | + if self._loc == 'left': |
| 301 | + x = 0. |
| 302 | + elif self._loc == 'right': |
| 303 | + x = 1. |
| 304 | + else: |
| 305 | + raise ValueError("location must be 'left', 'right', or a " |
| 306 | + "float, not '{}'.".format(self._loc)) |
| 307 | + else: |
| 308 | + x = self._loc |
| 309 | + bounds = [x, 0, 1e-10, 1.] |
| 310 | + transform = self._parent.transAxes |
| 311 | + secondary_locator = _make_inset_locator(bounds, |
| 312 | + transform, self._parent) |
| 313 | + bb = secondary_locator(None, None) |
| 314 | + |
| 315 | + # this locator lets the axes move if in data coordinates. |
| 316 | + # it gets called in `ax.apply_aspect() (of all places) |
| 317 | + self.set_axes_locator(secondary_locator) |
| 318 | + self._x = x |
| 319 | + |
| 320 | + def set_conversion(self, conversion): |
| 321 | + """ |
| 322 | + Set how the secondary axis converts limits from the parent axes. |
| 323 | +
|
| 324 | + Parameters |
| 325 | + ---------- |
| 326 | + conversion : tuple of floats or function |
| 327 | + conversion between the parent xaxis values and the secondary xaxis |
| 328 | + values. If a tuple of floats, the floats are polynomial |
| 329 | + co-efficients, with the first entry the highest exponent's |
| 330 | + co-efficient (i.e. [2, 3, 1] is the same as |
| 331 | + ``xnew = 2 x**2 + 3 * x + 1``, passed to `numpy.polyval`). |
| 332 | + If a function is specified it should accept a float as input and |
| 333 | + return a float as the result. |
| 334 | + """ |
| 335 | + |
| 336 | + # make the _convert function... |
| 337 | + if callable(conversion): |
| 338 | + self._convert = conversion |
| 339 | + else: |
| 340 | + if isinstance(conversion, numbers.Number): |
| 341 | + conversion = np.asanyarray([conversion]) |
| 342 | + shp = len(conversion) |
| 343 | + if shp < 2: |
| 344 | + conversion = np.array([conversion, 0.]) |
| 345 | + self._convert = lambda x: np.polyval(conversion, x) |
| 346 | + |
| 347 | + def draw(self, renderer=None, inframe=False): |
| 348 | + """ |
| 349 | + Draw the secondary axes. |
| 350 | +
|
| 351 | + Consults the parent axes for its xlimits and converts them |
| 352 | + using the converter specified by |
| 353 | + `~.axes._secondary_axes.set_conversion` (or *conversion* |
| 354 | + parameter when axes initialized.) |
| 355 | +
|
| 356 | + """ |
| 357 | + lims = self._parent.get_xlim() |
| 358 | + self.set_ylim(self._convert(lims)) |
| 359 | + super().draw(renderer=renderer, inframe=inframe) |
| 360 | + |
| 361 | + def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs): |
| 362 | + """ |
| 363 | + Set the label for the secondary y-axis. |
| 364 | +
|
| 365 | + Parameters |
| 366 | + ---------- |
| 367 | + ylabel : str |
| 368 | + The label text. |
| 369 | +
|
| 370 | + labelpad : scalar, optional, default: None |
| 371 | + Spacing in points between the label and the x-axis. |
| 372 | +
|
| 373 | + Other Parameters |
| 374 | + ---------------- |
| 375 | + **kwargs : `.Text` properties |
| 376 | + `.Text` properties control the appearance of the label. |
| 377 | +
|
| 378 | + See also |
| 379 | + -------- |
| 380 | + text : for information on how override and the optional args work |
| 381 | + """ |
| 382 | + if labelpad is not None: |
| 383 | + self.yaxis.labelpad = labelpad |
| 384 | + return self.yaxis.set_label_text(ylabel, fontdict, **kwargs) |
| 385 | + |
| 386 | + def set_color(self, color): |
| 387 | + """ |
| 388 | + Change the color of the secondary axes and all decorators |
| 389 | +
|
| 390 | + Parameters |
| 391 | + ---------- |
| 392 | + color : Matplotlib color |
| 393 | + """ |
| 394 | + |
| 395 | + self.tick_params(axis='y', colors=color) |
| 396 | + self.spines['left'].set_color(color) |
| 397 | + self.spines['right'].set_color(color) |
| 398 | + self.yaxis.label.set_color(color) |
| 399 | + |
| 400 | + def get_tightbbox(self, renderer, call_axes_locator=True): |
| 401 | + """ |
| 402 | + Return the tight bounding box of the axes. |
| 403 | + The dimension of the Bbox in canvas coordinate. |
| 404 | +
|
| 405 | + If *call_axes_locator* is *False*, it does not call the |
| 406 | + _axes_locator attribute, which is necessary to get the correct |
| 407 | + bounding box. ``call_axes_locator==False`` can be used if the |
| 408 | + caller is only intereted in the relative size of the tightbbox |
| 409 | + compared to the axes bbox. |
| 410 | + """ |
| 411 | + |
| 412 | + bb = [] |
| 413 | + |
| 414 | + if not self.get_visible(): |
| 415 | + return None |
| 416 | + |
| 417 | + locator = self.get_axes_locator() |
| 418 | + if locator and call_axes_locator: |
| 419 | + pos = locator(self, renderer) |
| 420 | + self.apply_aspect(pos) |
| 421 | + else: |
| 422 | + self.apply_aspect() |
| 423 | + |
| 424 | + bb_yaxis = self.yaxis.get_tightbbox(renderer) |
| 425 | + if bb_yaxis: |
| 426 | + bb.append(bb_yaxis) |
| 427 | + |
| 428 | + bb.append(self.get_window_extent(renderer)) |
| 429 | + |
| 430 | + _bbox = mtransforms.Bbox.union( |
| 431 | + [b for b in bb if b.width != 0 or b.height != 0]) |
| 432 | + |
| 433 | + return _bbox |
0 commit comments