Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/axes/_secondary_axes.py: 18%

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

120 statements  

1import numbers 

2 

3import numpy as np 

4 

5from matplotlib import _api, _docstring, transforms 

6import matplotlib.ticker as mticker 

7from matplotlib.axes._base import _AxesBase, _TransformedBoundsLocator 

8from matplotlib.axis import Axis 

9from matplotlib.transforms import Transform 

10 

11 

12class SecondaryAxis(_AxesBase): 

13 """ 

14 General class to hold a Secondary_X/Yaxis. 

15 """ 

16 

17 def __init__(self, parent, orientation, location, functions, transform=None, 

18 **kwargs): 

19 """ 

20 See `.secondary_xaxis` and `.secondary_yaxis` for the doc string. 

21 While there is no need for this to be private, it should really be 

22 called by those higher level functions. 

23 """ 

24 _api.check_in_list(["x", "y"], orientation=orientation) 

25 self._functions = functions 

26 self._parent = parent 

27 self._orientation = orientation 

28 self._ticks_set = False 

29 

30 if self._orientation == 'x': 

31 super().__init__(self._parent.figure, [0, 1., 1, 0.0001], **kwargs) 

32 self._axis = self.xaxis 

33 self._locstrings = ['top', 'bottom'] 

34 self._otherstrings = ['left', 'right'] 

35 else: # 'y' 

36 super().__init__(self._parent.figure, [0, 1., 0.0001, 1], **kwargs) 

37 self._axis = self.yaxis 

38 self._locstrings = ['right', 'left'] 

39 self._otherstrings = ['top', 'bottom'] 

40 self._parentscale = None 

41 # this gets positioned w/o constrained_layout so exclude: 

42 

43 self.set_location(location, transform) 

44 self.set_functions(functions) 

45 

46 # styling: 

47 otheraxis = self.yaxis if self._orientation == 'x' else self.xaxis 

48 otheraxis.set_major_locator(mticker.NullLocator()) 

49 otheraxis.set_ticks_position('none') 

50 

51 self.spines[self._otherstrings].set_visible(False) 

52 self.spines[self._locstrings].set_visible(True) 

53 

54 if self._pos < 0.5: 

55 # flip the location strings... 

56 self._locstrings = self._locstrings[::-1] 

57 self.set_alignment(self._locstrings[0]) 

58 

59 def set_alignment(self, align): 

60 """ 

61 Set if axes spine and labels are drawn at top or bottom (or left/right) 

62 of the Axes. 

63 

64 Parameters 

65 ---------- 

66 align : {'top', 'bottom', 'left', 'right'} 

67 Either 'top' or 'bottom' for orientation='x' or 

68 'left' or 'right' for orientation='y' axis. 

69 """ 

70 _api.check_in_list(self._locstrings, align=align) 

71 if align == self._locstrings[1]: # Need to change the orientation. 

72 self._locstrings = self._locstrings[::-1] 

73 self.spines[self._locstrings[0]].set_visible(True) 

74 self.spines[self._locstrings[1]].set_visible(False) 

75 self._axis.set_ticks_position(align) 

76 self._axis.set_label_position(align) 

77 

78 def set_location(self, location, transform=None): 

79 """ 

80 Set the vertical or horizontal location of the axes in 

81 parent-normalized coordinates. 

82 

83 Parameters 

84 ---------- 

85 location : {'top', 'bottom', 'left', 'right'} or float 

86 The position to put the secondary axis. Strings can be 'top' or 

87 'bottom' for orientation='x' and 'right' or 'left' for 

88 orientation='y'. A float indicates the relative position on the 

89 parent Axes to put the new Axes, 0.0 being the bottom (or left) 

90 and 1.0 being the top (or right). 

91 

92 transform : `.Transform`, optional 

93 Transform for the location to use. Defaults to 

94 the parent's ``transAxes``, so locations are normally relative to 

95 the parent axes. 

96 

97 .. versionadded:: 3.9 

98 """ 

99 

100 _api.check_isinstance((transforms.Transform, None), transform=transform) 

101 

102 # This puts the rectangle into figure-relative coordinates. 

103 if isinstance(location, str): 

104 _api.check_in_list(self._locstrings, location=location) 

105 self._pos = 1. if location in ('top', 'right') else 0. 

106 elif isinstance(location, numbers.Real): 

107 self._pos = location 

108 else: 

109 raise ValueError( 

110 f"location must be {self._locstrings[0]!r}, " 

111 f"{self._locstrings[1]!r}, or a float, not {location!r}") 

112 

113 self._loc = location 

114 

115 if self._orientation == 'x': 

116 # An x-secondary axes is like an inset axes from x = 0 to x = 1 and 

117 # from y = pos to y = pos + eps, in the parent's transAxes coords. 

118 bounds = [0, self._pos, 1., 1e-10] 

119 

120 # If a transformation is provided, use its y component rather than 

121 # the parent's transAxes. This can be used to place axes in the data 

122 # coords, for instance. 

123 if transform is not None: 

124 transform = transforms.blended_transform_factory( 

125 self._parent.transAxes, transform) 

126 else: # 'y' 

127 bounds = [self._pos, 0, 1e-10, 1] 

128 if transform is not None: 

129 transform = transforms.blended_transform_factory( 

130 transform, self._parent.transAxes) # Use provided x axis 

131 

132 # If no transform is provided, use the parent's transAxes 

133 if transform is None: 

134 transform = self._parent.transAxes 

135 

136 # this locator lets the axes move in the parent axes coordinates. 

137 # so it never needs to know where the parent is explicitly in 

138 # figure coordinates. 

139 # it gets called in ax.apply_aspect() (of all places) 

140 self.set_axes_locator(_TransformedBoundsLocator(bounds, transform)) 

141 

142 def apply_aspect(self, position=None): 

143 # docstring inherited. 

144 self._set_lims() 

145 super().apply_aspect(position) 

146 

147 @_docstring.copy(Axis.set_ticks) 

148 def set_ticks(self, ticks, labels=None, *, minor=False, **kwargs): 

149 ret = self._axis.set_ticks(ticks, labels, minor=minor, **kwargs) 

150 self.stale = True 

151 self._ticks_set = True 

152 return ret 

153 

154 def set_functions(self, functions): 

155 """ 

156 Set how the secondary axis converts limits from the parent Axes. 

157 

158 Parameters 

159 ---------- 

160 functions : 2-tuple of func, or `Transform` with an inverse. 

161 Transform between the parent axis values and the secondary axis 

162 values. 

163 

164 If supplied as a 2-tuple of functions, the first function is 

165 the forward transform function and the second is the inverse 

166 transform. 

167 

168 If a transform is supplied, then the transform must have an 

169 inverse. 

170 """ 

171 

172 if (isinstance(functions, tuple) and len(functions) == 2 and 

173 callable(functions[0]) and callable(functions[1])): 

174 # make an arbitrary convert from a two-tuple of functions 

175 # forward and inverse. 

176 self._functions = functions 

177 elif isinstance(functions, Transform): 

178 self._functions = ( 

179 functions.transform, 

180 lambda x: functions.inverted().transform(x) 

181 ) 

182 elif functions is None: 

183 self._functions = (lambda x: x, lambda x: x) 

184 else: 

185 raise ValueError('functions argument of secondary Axes ' 

186 'must be a two-tuple of callable functions ' 

187 'with the first function being the transform ' 

188 'and the second being the inverse') 

189 self._set_scale() 

190 

191 def draw(self, renderer): 

192 """ 

193 Draw the secondary Axes. 

194 

195 Consults the parent Axes for its limits and converts them 

196 using the converter specified by 

197 `~.axes._secondary_axes.set_functions` (or *functions* 

198 parameter when Axes initialized.) 

199 """ 

200 self._set_lims() 

201 # this sets the scale in case the parent has set its scale. 

202 self._set_scale() 

203 super().draw(renderer) 

204 

205 def _set_scale(self): 

206 """ 

207 Check if parent has set its scale 

208 """ 

209 

210 if self._orientation == 'x': 

211 pscale = self._parent.xaxis.get_scale() 

212 set_scale = self.set_xscale 

213 else: # 'y' 

214 pscale = self._parent.yaxis.get_scale() 

215 set_scale = self.set_yscale 

216 if pscale == self._parentscale: 

217 return 

218 

219 if self._ticks_set: 

220 ticks = self._axis.get_ticklocs() 

221 

222 # need to invert the roles here for the ticks to line up. 

223 set_scale('functionlog' if pscale == 'log' else 'function', 

224 functions=self._functions[::-1]) 

225 

226 # OK, set_scale sets the locators, but if we've called 

227 # axsecond.set_ticks, we want to keep those. 

228 if self._ticks_set: 

229 self._axis.set_major_locator(mticker.FixedLocator(ticks)) 

230 

231 # If the parent scale doesn't change, we can skip this next time. 

232 self._parentscale = pscale 

233 

234 def _set_lims(self): 

235 """ 

236 Set the limits based on parent limits and the convert method 

237 between the parent and this secondary Axes. 

238 """ 

239 if self._orientation == 'x': 

240 lims = self._parent.get_xlim() 

241 set_lim = self.set_xlim 

242 else: # 'y' 

243 lims = self._parent.get_ylim() 

244 set_lim = self.set_ylim 

245 order = lims[0] < lims[1] 

246 lims = self._functions[0](np.array(lims)) 

247 neworder = lims[0] < lims[1] 

248 if neworder != order: 

249 # Flip because the transform will take care of the flipping. 

250 lims = lims[::-1] 

251 set_lim(lims) 

252 

253 def set_aspect(self, *args, **kwargs): 

254 """ 

255 Secondary Axes cannot set the aspect ratio, so calling this just 

256 sets a warning. 

257 """ 

258 _api.warn_external("Secondary Axes can't set the aspect ratio") 

259 

260 def set_color(self, color): 

261 """ 

262 Change the color of the secondary Axes and all decorators. 

263 

264 Parameters 

265 ---------- 

266 color : :mpltype:`color` 

267 """ 

268 axis = self._axis_map[self._orientation] 

269 axis.set_tick_params(colors=color) 

270 for spine in self.spines.values(): 

271 if spine.axis is axis: 

272 spine.set_color(color) 

273 axis.label.set_color(color) 

274 

275 

276_secax_docstring = ''' 

277Warnings 

278-------- 

279This method is experimental as of 3.1, and the API may change. 

280 

281Parameters 

282---------- 

283location : {'top', 'bottom', 'left', 'right'} or float 

284 The position to put the secondary axis. Strings can be 'top' or 

285 'bottom' for orientation='x' and 'right' or 'left' for 

286 orientation='y'. A float indicates the relative position on the 

287 parent Axes to put the new Axes, 0.0 being the bottom (or left) 

288 and 1.0 being the top (or right). 

289 

290functions : 2-tuple of func, or Transform with an inverse 

291 

292 If a 2-tuple of functions, the user specifies the transform 

293 function and its inverse. i.e. 

294 ``functions=(lambda x: 2 / x, lambda x: 2 / x)`` would be an 

295 reciprocal transform with a factor of 2. Both functions must accept 

296 numpy arrays as input. 

297 

298 The user can also directly supply a subclass of 

299 `.transforms.Transform` so long as it has an inverse. 

300 

301 See :doc:`/gallery/subplots_axes_and_figures/secondary_axis` 

302 for examples of making these conversions. 

303 

304transform : `.Transform`, optional 

305 If specified, *location* will be 

306 placed relative to this transform (in the direction of the axis) 

307 rather than the parent's axis. i.e. a secondary x-axis will 

308 use the provided y transform and the x transform of the parent. 

309 

310 .. versionadded:: 3.9 

311 

312Returns 

313------- 

314ax : axes._secondary_axes.SecondaryAxis 

315 

316Other Parameters 

317---------------- 

318**kwargs : `~matplotlib.axes.Axes` properties. 

319 Other miscellaneous Axes parameters. 

320''' 

321_docstring.interpd.update(_secax_docstring=_secax_docstring)