Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/axes/_secondary_axes.py : 16%

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
1import numpy as np
3import matplotlib.cbook as cbook
4import matplotlib.docstring as docstring
5import matplotlib.ticker as mticker
6import matplotlib.transforms as mtransforms
7from matplotlib.axes._base import _AxesBase
10def _make_secondary_locator(rect, parent):
11 """
12 Helper function to locate the secondary axes.
14 A locator gets used in `Axes.set_aspect` to override the default
15 locations... It is a function that takes an axes object and
16 a renderer and tells `set_aspect` where it is to be placed.
18 This locator make the transform be in axes-relative co-coordinates
19 because that is how we specify the "location" of the secondary axes.
21 Here *rect* is a rectangle [l, b, w, h] that specifies the
22 location for the axes in the transform given by *trans* on the
23 *parent*.
24 """
25 _rect = mtransforms.Bbox.from_bounds(*rect)
26 def secondary_locator(ax, renderer):
27 # delay evaluating transform until draw time because the
28 # parent transform may have changed (i.e. if window reesized)
29 bb = mtransforms.TransformedBbox(_rect, parent.transAxes)
30 tr = parent.figure.transFigure.inverted()
31 bb = mtransforms.TransformedBbox(bb, tr)
32 return bb
34 return secondary_locator
37class SecondaryAxis(_AxesBase):
38 """
39 General class to hold a Secondary_X/Yaxis.
40 """
42 def __init__(self, parent, orientation,
43 location, functions, **kwargs):
44 """
45 See `.secondary_xaxis` and `.secondary_yaxis` for the doc string.
46 While there is no need for this to be private, it should really be
47 called by those higher level functions.
48 """
50 self._functions = functions
51 self._parent = parent
52 self._orientation = orientation
53 self._ticks_set = False
55 if self._orientation == 'x':
56 super().__init__(self._parent.figure, [0, 1., 1, 0.0001], **kwargs)
57 self._axis = self.xaxis
58 self._locstrings = ['top', 'bottom']
59 self._otherstrings = ['left', 'right']
60 elif self._orientation == 'y':
61 super().__init__(self._parent.figure, [0, 1., 0.0001, 1], **kwargs)
62 self._axis = self.yaxis
63 self._locstrings = ['right', 'left']
64 self._otherstrings = ['top', 'bottom']
65 self._parentscale = self._axis.get_scale()
66 # this gets positioned w/o constrained_layout so exclude:
67 self._layoutbox = None
68 self._poslayoutbox = None
70 self.set_location(location)
71 self.set_functions(functions)
73 # styling:
74 if self._orientation == 'x':
75 otheraxis = self.yaxis
76 else:
77 otheraxis = self.xaxis
79 otheraxis.set_major_locator(mticker.NullLocator())
80 otheraxis.set_ticks_position('none')
82 for st in self._otherstrings:
83 self.spines[st].set_visible(False)
84 for st in self._locstrings:
85 self.spines[st].set_visible(True)
87 if self._pos < 0.5:
88 # flip the location strings...
89 self._locstrings = self._locstrings[::-1]
90 self.set_alignment(self._locstrings[0])
92 def set_alignment(self, align):
93 """
94 Set if axes spine and labels are drawn at top or bottom (or left/right)
95 of the axes.
97 Parameters
98 ----------
99 align : str
100 either 'top' or 'bottom' for orientation='x' or
101 'left' or 'right' for orientation='y' axis.
102 """
103 if align in self._locstrings:
104 if align == self._locstrings[1]:
105 # need to change the orientation.
106 self._locstrings = self._locstrings[::-1]
107 elif align != self._locstrings[0]:
108 raise ValueError('"{}" is not a valid axis orientation, '
109 'not changing the orientation;'
110 'choose "{}" or "{}""'.format(align,
111 self._locstrings[0], self._locstrings[1]))
112 self.spines[self._locstrings[0]].set_visible(True)
113 self.spines[self._locstrings[1]].set_visible(False)
114 self._axis.set_ticks_position(align)
115 self._axis.set_label_position(align)
117 def set_location(self, location):
118 """
119 Set the vertical or horizontal location of the axes in
120 parent-normalized co-ordinates.
122 Parameters
123 ----------
124 location : {'top', 'bottom', 'left', 'right'} or float
125 The position to put the secondary axis. Strings can be 'top' or
126 'bottom' for orientation='x' and 'right' or 'left' for
127 orientation='y'. A float indicates the relative position on the
128 parent axes to put the new axes, 0.0 being the bottom (or left)
129 and 1.0 being the top (or right).
130 """
132 # This puts the rectangle into figure-relative coordinates.
133 if isinstance(location, str):
134 if location in ['top', 'right']:
135 self._pos = 1.
136 elif location in ['bottom', 'left']:
137 self._pos = 0.
138 else:
139 raise ValueError("location must be '{}', '{}', or a "
140 "float, not '{}'".format(location,
141 self._locstrings[0], self._locstrings[1]))
142 else:
143 self._pos = location
144 self._loc = location
146 if self._orientation == 'x':
147 bounds = [0, self._pos, 1., 1e-10]
148 else:
149 bounds = [self._pos, 0, 1e-10, 1]
151 secondary_locator = _make_secondary_locator(bounds, self._parent)
153 # this locator lets the axes move in the parent axes coordinates.
154 # so it never needs to know where the parent is explicitly in
155 # figure co-ordinates.
156 # it gets called in `ax.apply_aspect() (of all places)
157 self.set_axes_locator(secondary_locator)
159 def apply_aspect(self, position=None):
160 # docstring inherited.
161 self._set_lims()
162 super().apply_aspect(position)
164 @cbook._make_keyword_only("3.2", "minor")
165 def set_ticks(self, ticks, minor=False):
166 """
167 Set the x ticks with list of *ticks*
169 Parameters
170 ----------
171 ticks : list
172 List of x-axis tick locations.
173 minor : bool, optional
174 If ``False`` sets major ticks, if ``True`` sets minor ticks.
175 Default is ``False``.
176 """
177 ret = self._axis.set_ticks(ticks, minor=minor)
178 self.stale = True
179 self._ticks_set = True
180 return ret
182 def set_functions(self, functions):
183 """
184 Set how the secondary axis converts limits from the parent axes.
186 Parameters
187 ----------
188 functions : 2-tuple of func, or `Transform` with an inverse.
189 Transform between the parent axis values and the secondary axis
190 values.
192 If supplied as a 2-tuple of functions, the first function is
193 the forward transform function and the second is the inverse
194 transform.
196 If a transform is supplied, then the transform must have an
197 inverse.
198 """
200 if self._orientation == 'x':
201 set_scale = self.set_xscale
202 parent_scale = self._parent.get_xscale()
203 else:
204 set_scale = self.set_yscale
205 parent_scale = self._parent.get_yscale()
206 # we need to use a modified scale so the scale can receive the
207 # transform. Only types supported are linear and log10 for now.
208 # Probably possible to add other transforms as a todo...
209 if parent_scale == 'log':
210 defscale = 'functionlog'
211 else:
212 defscale = 'function'
214 if (isinstance(functions, tuple) and len(functions) == 2 and
215 callable(functions[0]) and callable(functions[1])):
216 # make an arbitrary convert from a two-tuple of functions
217 # forward and inverse.
218 self._functions = functions
219 elif functions is None:
220 self._functions = (lambda x: x, lambda x: x)
221 else:
222 raise ValueError('functions argument of secondary axes '
223 'must be a two-tuple of callable functions '
224 'with the first function being the transform '
225 'and the second being the inverse')
226 # need to invert the roles here for the ticks to line up.
227 set_scale(defscale, functions=self._functions[::-1])
229 def draw(self, renderer=None, inframe=False):
230 """
231 Draw the secondary axes.
233 Consults the parent axes for its limits and converts them
234 using the converter specified by
235 `~.axes._secondary_axes.set_functions` (or *functions*
236 parameter when axes initialized.)
237 """
238 self._set_lims()
239 # this sets the scale in case the parent has set its scale.
240 self._set_scale()
241 super().draw(renderer=renderer, inframe=inframe)
243 def _set_scale(self):
244 """
245 Check if parent has set its scale
246 """
248 if self._orientation == 'x':
249 pscale = self._parent.xaxis.get_scale()
250 set_scale = self.set_xscale
251 if self._orientation == 'y':
252 pscale = self._parent.yaxis.get_scale()
253 set_scale = self.set_yscale
254 if pscale == self._parentscale:
255 return
256 else:
257 self._parentscale = pscale
259 if pscale == 'log':
260 defscale = 'functionlog'
261 else:
262 defscale = 'function'
264 if self._ticks_set:
265 ticks = self._axis.get_ticklocs()
267 # need to invert the roles here for the ticks to line up.
268 set_scale(defscale, functions=self._functions[::-1])
270 # OK, set_scale sets the locators, but if we've called
271 # axsecond.set_ticks, we want to keep those.
272 if self._ticks_set:
273 self._axis.set_major_locator(mticker.FixedLocator(ticks))
275 def _set_lims(self):
276 """
277 Set the limits based on parent limits and the convert method
278 between the parent and this secondary axes.
279 """
280 if self._orientation == 'x':
281 lims = self._parent.get_xlim()
282 set_lim = self.set_xlim
283 if self._orientation == 'y':
284 lims = self._parent.get_ylim()
285 set_lim = self.set_ylim
286 order = lims[0] < lims[1]
287 lims = self._functions[0](np.array(lims))
288 neworder = lims[0] < lims[1]
289 if neworder != order:
290 # Flip because the transform will take care of the flipping.
291 lims = lims[::-1]
292 set_lim(lims)
294 def set_aspect(self, *args, **kwargs):
295 """
296 Secondary axes cannot set the aspect ratio, so calling this just
297 sets a warning.
298 """
299 cbook._warn_external("Secondary axes can't set the aspect ratio")
301 def set_xlabel(self, xlabel, fontdict=None, labelpad=None, **kwargs):
302 """
303 Set the label for the x-axis.
305 Parameters
306 ----------
307 xlabel : str
308 The label text.
310 labelpad : scalar, optional, default: None
311 Spacing in points between the label and the x-axis.
313 Other Parameters
314 ----------------
315 **kwargs : `.Text` properties
316 `.Text` properties control the appearance of the label.
318 See also
319 --------
320 text : for information on how override and the optional args work
321 """
322 if labelpad is not None:
323 self.xaxis.labelpad = labelpad
324 return self.xaxis.set_label_text(xlabel, fontdict, **kwargs)
326 def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs):
327 """
328 Set the label for the x-axis.
330 Parameters
331 ----------
332 ylabel : str
333 The label text.
335 labelpad : scalar, optional, default: None
336 Spacing in points between the label and the x-axis.
338 Other Parameters
339 ----------------
340 **kwargs : `.Text` properties
341 `.Text` properties control the appearance of the label.
343 See also
344 --------
345 text : for information on how override and the optional args work
346 """
347 if labelpad is not None:
348 self.yaxis.labelpad = labelpad
349 return self.yaxis.set_label_text(ylabel, fontdict, **kwargs)
351 def set_color(self, color):
352 """
353 Change the color of the secondary axes and all decorators.
355 Parameters
356 ----------
357 color : Matplotlib color
358 """
359 if self._orientation == 'x':
360 self.tick_params(axis='x', colors=color)
361 self.spines['bottom'].set_color(color)
362 self.spines['top'].set_color(color)
363 self.xaxis.label.set_color(color)
364 else:
365 self.tick_params(axis='y', colors=color)
366 self.spines['left'].set_color(color)
367 self.spines['right'].set_color(color)
368 self.yaxis.label.set_color(color)
371_secax_docstring = '''
372Warnings
373--------
374This method is experimental as of 3.1, and the API may change.
376Parameters
377----------
378location : {'top', 'bottom', 'left', 'right'} or float
379 The position to put the secondary axis. Strings can be 'top' or
380 'bottom' for orientation='x' and 'right' or 'left' for
381 orientation='y'. A float indicates the relative position on the
382 parent axes to put the new axes, 0.0 being the bottom (or left)
383 and 1.0 being the top (or right).
385functions : 2-tuple of func, or Transform with an inverse
387 If a 2-tuple of functions, the user specifies the transform
388 function and its inverse. i.e.
389 `functions=(lambda x: 2 / x, lambda x: 2 / x)` would be an
390 reciprocal transform with a factor of 2.
392 The user can also directly supply a subclass of
393 `.transforms.Transform` so long as it has an inverse.
395 See :doc:`/gallery/subplots_axes_and_figures/secondary_axis`
396 for examples of making these conversions.
399Other Parameters
400----------------
401**kwargs : `~matplotlib.axes.Axes` properties.
402 Other miscellaneous axes parameters.
404Returns
405-------
406ax : axes._secondary_axes.SecondaryAxis
407'''
408docstring.interpd.update(_secax_docstring=_secax_docstring)