3
3
Utilties related to legends and colorbars.
4
4
"""
5
5
import matplotlib .artist as martist
6
+ import matplotlib .axes as maxes
6
7
import matplotlib .colorbar as mcolorbar
7
- import matplotlib .legend as mlegend # noqa: F401
8
+ import matplotlib .offsetbox as moffsetbox
9
+ import matplotlib .projections as mprojections # noqa: F401
8
10
import matplotlib .ticker as mticker
11
+ import matplotlib .transforms as mtransforms
9
12
import numpy as np
10
13
11
14
from . import ic # noqa: F401
12
15
from . import warnings
13
16
14
17
15
- def _fill_guide_kw (kwargs , ** pairs ):
18
+ def _fill_guide_kw (kwargs , overwrite = False , ** pairs ):
16
19
"""
17
20
Add the keyword arguments to the dictionary if not already present.
18
21
"""
@@ -25,42 +28,48 @@ def _fill_guide_kw(kwargs, **pairs):
25
28
if value is None :
26
29
continue
27
30
keys = tuple (a for group in aliases for a in group if key in group ) # may be ()
28
- if not any (kwargs .get (key ) is not None for key in keys ): # note any(()) is True
31
+ keys_found = tuple (key for key in keys if kwargs .get (key ) is not None )
32
+ if not keys_found :
29
33
kwargs [key ] = value
34
+ elif overwrite : # overwrite existing key
35
+ kwargs [keys_found [0 ]] = value
30
36
31
37
32
- def _guide_kw_from_obj ( obj , name , kwargs ):
38
+ def _guide_kw_to_arg ( name , kwargs , ** pairs ):
33
39
"""
34
- Add to the dict from settings stored on the object if there are no conflicts.
40
+ Add to the `colorbar_kw` or `legend_kw` dict if there are no conflicts.
35
41
"""
36
- pairs = getattr (obj , f'_{ name } _kw' , None )
37
- pairs = pairs or {} # needed for some reason
38
- _fill_guide_kw (kwargs , ** pairs )
39
- if isinstance (obj , (tuple , list , np .ndarray )):
40
- for iobj in obj : # possibly iterate over matplotlib tuple/list subclasses
41
- _guide_kw_from_obj (iobj , name , kwargs )
42
- return kwargs
42
+ kw = kwargs .setdefault (f'{ name } _kw' , {})
43
+ _fill_guide_kw (kw , overwrite = True , ** pairs )
43
44
44
45
45
46
def _guide_kw_to_obj (obj , name , kwargs ):
46
47
"""
47
- Add the guide keyword dict to the objects .
48
+ Store settings on the object from the input dict .
48
49
"""
50
+ pairs = getattr (obj , f'_{ name } _kw' , None )
51
+ pairs = pairs or {}
52
+ _fill_guide_kw (pairs , overwrite = True , ** kwargs ) # update with current input
49
53
try :
50
54
setattr (obj , f'_{ name } _kw' , kwargs )
51
55
except AttributeError :
52
56
pass
53
57
if isinstance (obj , (tuple , list , np .ndarray )):
54
- for iobj in obj :
55
- _guide_kw_to_obj (iobj , name , kwargs )
58
+ for member in obj :
59
+ _guide_kw_to_obj (member , name , kwargs )
56
60
57
61
58
- def _guide_kw_to_arg ( name , kwargs , ** pairs ):
62
+ def _guide_obj_to_kw ( obj , name , kwargs ):
59
63
"""
60
- Add to the `colorbar_kw` or `legend_kw` dict if there are no conflicts.
64
+ Add to the dict from settings stored on the object if there are no conflicts.
61
65
"""
62
- kw = kwargs .setdefault (f'{ name } _kw' , {})
63
- _fill_guide_kw (kw , ** pairs )
66
+ pairs = getattr (obj , f'_{ name } _kw' , None )
67
+ pairs = pairs or {}
68
+ _fill_guide_kw (kwargs , overwrite = False , ** pairs ) # update from previous input
69
+ if isinstance (obj , (tuple , list , np .ndarray )):
70
+ for member in obj : # possibly iterate over matplotlib tuple/list subclasses
71
+ _guide_obj_to_kw (member , name , kwargs )
72
+ return kwargs
64
73
65
74
66
75
def _iter_children (* args ):
@@ -117,15 +126,93 @@ def _update_ticks(self, manual_only=False):
117
126
self .minorticks_on () # at least turn them on
118
127
119
128
120
- class _InsetColorbar (martist .Artist ):
121
- """
122
- Legend-like class for managing inset colorbars.
123
- """
124
- # TODO: Write this!
129
+ class _AnchoredAxes (moffsetbox .AnchoredOffsetbox ):
130
+ """
131
+ An anchored child axes whose background patch and offset position is determined
132
+ by the tight bounding box. Analogous to `~matplotlib.offsetbox.AnchoredText`.
133
+ """
134
+ def __init__ (self , ax , width , height , ** kwargs ):
135
+ # Note the default bbox_to_anchor will be
136
+ # the axes bounding box.
137
+ bounds = [0 , 0 , 1 , 1 ] # arbitrary initial bounds
138
+ child = maxes .Axes (ax .figure , bounds , zorder = self .zorder )
139
+ # cls = mprojections.get_projection_class('proplot_cartesian') # TODO
140
+ # child = cls(ax.figure, bounds, zorder=self.zorder)
141
+ super ().__init__ (child = child , bbox_to_anchor = ax .bbox , ** kwargs )
142
+ ax .add_artist (self ) # sets self.axes to ax and bbox_to_anchor to ax.bbox
143
+ self ._child = child # ensure private attribute exists
144
+ self ._width = width
145
+ self ._height = height
146
+
147
+ def draw (self , renderer ):
148
+ # Just draw the patch (not the axes)
149
+ if not self .get_visible ():
150
+ return
151
+ if hasattr (self , '_update_offset_func' ):
152
+ self ._update_offset_func (renderer )
153
+ else :
154
+ warnings ._warn_proplot (
155
+ 'Failed to update _AnchoredAxes offset function due to matplotlib '
156
+ 'private API change. The resulting axes position may be incorrect.'
157
+ )
158
+ bbox = self .get_window_extent (renderer )
159
+ self ._update_patch (renderer , bbox = bbox )
160
+ bbox = self .get_child_extent (renderer , offset = True )
161
+ self ._update_child (bbox )
162
+ self .patch .draw (renderer )
163
+ self ._child .draw (renderer )
164
+
165
+ def _update_child (self , bbox ):
166
+ # Update the child bounding box
167
+ trans = getattr (self .figure , 'transSubfigure' , self .figure .transFigure )
168
+ bbox = mtransforms .TransformedBbox (bbox , trans .inverted ())
169
+ getattr (self ._child , '_set_position' , self ._child .set_position )(bbox )
170
+
171
+ def _update_patch (self , renderer , bbox ):
172
+ # Update the patch position
173
+ fontsize = renderer .points_to_pixels (self .prop .get_size_in_points ())
174
+ self .patch .set_bounds (bbox .x0 , bbox .y0 , bbox .width , bbox .height )
175
+ self .patch .set_mutation_scale (fontsize )
176
+
177
+ def get_extent (self , renderer , offset = False ):
178
+ # Return the extent of the child plus padding
179
+ fontsize = renderer .points_to_pixels (self .prop .get_size_in_points ())
180
+ pad = self .pad * fontsize
181
+ bbox = self ._child ._tight_bbox = self ._child .get_tightbbox (renderer )
182
+ # bbox = self._child.get_tightbbox(renderer, use_cache=True) # TODO
183
+ width = bbox .width + 2 * pad
184
+ height = bbox .height + 2 * pad
185
+ xd = yd = pad
186
+ if offset :
187
+ xd += self ._child .bbox .x0 - bbox .x0
188
+ yd += self ._child .bbox .y0 - bbox .y0
189
+ return width , height , xd , yd
190
+
191
+ def get_child_extent (self , renderer , offset = False ):
192
+ # Update the child position
193
+ fontsize = renderer .points_to_pixels (self .prop .get_size_in_points ())
194
+ x0 , y0 = self ._child .bbox .x0 , self ._child .bbox .y0
195
+ if offset : # find offset position
196
+ self ._update_child (self .get_child_extent (renderer ))
197
+ width , height , xd , yd = self .get_extent (renderer , offset = True )
198
+ x0 , y0 = self .get_offset (width , height , xd , yd , renderer )
199
+ # bbox = self._child.get_tightbbox(use_cache=True) # TODO
200
+ xd += self ._child .bbox .x0 - self ._child ._tight_bbox .x0
201
+ yd += self ._child .bbox .y0 - self ._child ._tight_bbox .y0
202
+ width , height = self ._width * fontsize , self ._height * fontsize
203
+ return mtransforms .Bbox .from_bounds (x0 , y0 , width , height )
204
+
205
+ def get_window_extent (self , renderer ):
206
+ # Return the window bounding box
207
+ self ._child .get_tightbbox (renderer ) # reset the cache
208
+ self ._update_child (self .get_child_extent (renderer ))
209
+ xi , yi , xd , yd = self .get_extent (renderer , offset = False )
210
+ ox , oy = self .get_offset (xi , yi , xd , yd , renderer )
211
+ return mtransforms .Bbox .from_bounds (ox - xd , oy - yd , xi , yi )
125
212
126
213
127
214
class _CenteredLegend (martist .Artist ):
128
215
"""
129
- Legend-like class for managing centered-row legends.
216
+ A legend-like subclass whose handles are grouped into centered rows of
217
+ `~matplotlib.offsetbox.HPacker` rather than `~matplotlib.offsetbox.VPacker` columns.
130
218
"""
131
- # TODO: Write this!
0 commit comments