13
13
# Label line with line2D label data
14
14
def labelLine (
15
15
line ,
16
- x ,
16
+ val ,
17
+ axis = "x" ,
17
18
label = None ,
18
19
align = True ,
19
20
drop_label = False ,
20
- yoffset = 0 ,
21
- yoffset_logspace = False ,
21
+ offset = 0 ,
22
+ offset_logspace = False ,
22
23
outline_color = "auto" ,
23
24
outline_width = 8 ,
24
25
** kwargs ,
@@ -30,17 +31,19 @@ def labelLine(
30
31
----------
31
32
line : matplotlib.lines.Line
32
33
The line holding the label
33
- x : number
34
+ val : number
34
35
The location in data unit of the label
36
+ axis : "x" | "y"
37
+ Reference axis for `val`.
35
38
label : string, optional
36
39
The label to set. This is inferred from the line by default
37
40
drop_label : bool, optional
38
41
If True, the label is consumed by the function so that subsequent
39
42
calls to e.g. legend do not use it anymore.
40
- yoffset : double, optional
43
+ offset : double, optional
41
44
Space to add to label's y position
42
- yoffset_logspace : bool, optional
43
- If True, then yoffset will be added to the label's y position in
45
+ offset_logspace : bool, optional
46
+ If True, then offset will be added to the label's y position in
44
47
log10 space
45
48
outline_color : None | "auto" | color
46
49
Colour of the outline. If set to "auto", use the background color.
@@ -54,11 +57,12 @@ def labelLine(
54
57
try :
55
58
txt = LineLabel (
56
59
line ,
57
- x ,
60
+ val ,
61
+ axis ,
58
62
label = label ,
59
63
align = align ,
60
- yoffset = yoffset ,
61
- yoffset_logspace = yoffset_logspace ,
64
+ offset = offset ,
65
+ offset_logspace = offset_logspace ,
62
66
outline_color = outline_color ,
63
67
outline_width = outline_width ,
64
68
** kwargs ,
@@ -86,10 +90,11 @@ def labelLine(
86
90
def labelLines (
87
91
lines = None ,
88
92
align = True ,
89
- xvals = None ,
93
+ vals = None ,
94
+ axis = None ,
90
95
drop_label = False ,
91
96
shrink_factor = 0.05 ,
92
- yoffsets = 0 ,
97
+ offsets = 0 ,
93
98
outline_color = "auto" ,
94
99
outline_width = 5 ,
95
100
** kwargs ,
@@ -103,17 +108,19 @@ def labelLines(
103
108
align : boolean, optional
104
109
If True, the label will be aligned with the slope of the line
105
110
at the location of the label. If False, they will be horizontal.
106
- xvals : (xfirst, xlast ) or array of float, optional
111
+ vals : (first, last ) or array of float, optional
107
112
The location of the labels. If a tuple, the labels will be
108
- evenly spaced between xfirst and xlast (in the axis units).
113
+ evenly spaced between first and last (in the axis units).
114
+ axis : None | "x" | "y", optional
115
+ Reference axis for the `vals`.
109
116
drop_label : bool, optional
110
117
If True, the label is consumed by the function so that subsequent
111
118
calls to e.g. legend do not use it anymore.
112
119
shrink_factor : double, optional
113
120
Relative distance from the edges to place closest labels. Defaults to 0.05.
114
- yoffsets : number or list, optional.
121
+ offsets : number or list, optional.
115
122
Distance relative to the line when positioning the labels. If given a number,
116
- the same value is used for all lines.
123
+ the same value is used for all lines. It refers to the *other* axis (i.e. to y if axis=="x")
117
124
outline_color : None | "auto" | color
118
125
Colour of the outline. If set to "auto", use the background color.
119
126
If set to None, do not draw an outline.
@@ -122,11 +129,18 @@ def labelLines(
122
129
kwargs : dict, optional
123
130
Optional arguments passed to ax.text
124
131
"""
132
+
125
133
if lines :
126
134
ax = lines [0 ].axes
127
135
else :
128
136
ax = plt .gca ()
129
137
138
+ if axis == "y" :
139
+ yaxis = True
140
+ else :
141
+ axis = "x"
142
+ yaxis = False
143
+
130
144
handles , labels_of_handles = ax .get_legend_handles_labels ()
131
145
132
146
all_lines , all_labels = [], []
@@ -156,32 +170,38 @@ def labelLines(
156
170
157
171
# In case no x location was provided, we need to use some heuristics
158
172
# to generate them.
159
- if xvals is None :
160
- xvals = ax .get_xlim ()
161
- xvals_rng = xvals [1 ] - xvals [0 ]
162
- shrinkage = xvals_rng * shrink_factor
163
- xvals = (xvals [0 ] + shrinkage , xvals [1 ] - shrinkage )
164
-
165
- if isinstance (xvals , tuple ) and len (xvals ) == 2 :
166
- xmin , xmax = xvals
173
+ if vals is None :
174
+ if yaxis :
175
+ vals = ax .get_ylim ()
176
+ else :
177
+ vals = ax .get_xlim ()
178
+ vals_rng = vals [1 ] - vals [0 ]
179
+ shrinkage = vals_rng * shrink_factor
180
+ vals = (vals [0 ] + shrinkage , vals [1 ] - shrinkage )
181
+
182
+ if isinstance (vals , tuple ) and len (vals ) == 2 :
183
+ vmin , vmax = vals
167
184
xscale = ax .get_xscale ()
168
185
if xscale == "log" :
169
- xvals = np .logspace (np .log10 (xmin ), np .log10 (xmax ), len (all_lines ) + 2 )[
170
- 1 :- 1
171
- ]
186
+ vals = np .logspace (np .log10 (vmin ), np .log10 (vmax ), len (all_lines ) + 2 )[
187
+ 1 :- 1
188
+ ]
172
189
else :
173
- xvals = np .linspace (xmin , xmax , len (all_lines ) + 2 )[1 :- 1 ]
190
+ vals = np .linspace (vmin , vmax , len (all_lines ) + 2 )[1 :- 1 ]
174
191
175
- # Build matrix line -> xvalue
192
+ # Build matrix line -> value
176
193
ok_matrix = np .zeros ((len (all_lines ), len (all_lines )), dtype = bool )
177
194
178
195
for i , line in enumerate (all_lines ):
179
- xdata , _ = normalize_xydata (line )
180
- minx , maxx = min (xdata ), max (xdata )
181
- for j , xv in enumerate (xvals ):
182
- ok_matrix [i , j ] = minx < xv < maxx
196
+ if yaxis :
197
+ _ , data = normalize_xydata (line )
198
+ else :
199
+ data , _ = normalize_xydata (line )
200
+ minv , maxv = min (data ), max (data )
201
+ for j , val in enumerate (vals ):
202
+ ok_matrix [i , j ] = minv < val < maxv
183
203
184
- # If some xvals do not fall in their corresponding line,
204
+ # If some vals do not fall in their corresponding line,
185
205
# find a better matching using maximum bipartite matching.
186
206
if not np .all (np .diag (ok_matrix )):
187
207
order = maximum_bipartite_matching (ok_matrix )
@@ -190,51 +210,55 @@ def labelLines(
190
210
order [order < 0 ] = np .setdiff1d (np .arange (len (order )), order [order >= 0 ])
191
211
192
212
# Now reorder the xvalues
193
- old_xvals = xvals .copy ()
194
- xvals [order ] = old_xvals
213
+ old_xvals = vals .copy ()
214
+ vals [order ] = old_xvals
195
215
else :
196
- xvals = list (always_iterable (xvals )) # force the creation of a copy
216
+ vals = list (always_iterable (vals )) # force the creation of a copy
197
217
198
218
lab_lines , labels = [], []
199
219
# Take only the lines which have labels other than the default ones
200
- for i , (line , xv ) in enumerate (zip (all_lines , xvals )):
220
+ for i , (line , val ) in enumerate (zip (all_lines , vals )):
201
221
label = all_labels [all_lines .index (line )]
202
222
lab_lines .append (line )
203
223
labels .append (label )
204
224
205
- # Move xlabel if it is outside valid range
206
- xdata , _ = normalize_xydata (line )
207
- if not (min (xdata ) <= xv <= max (xdata )):
225
+ # Move xlabel/ylabel if it is outside valid range
226
+ if yaxis :
227
+ _ , data = normalize_xydata (line )
228
+ else :
229
+ data , _ = normalize_xydata (line )
230
+ if not (min (data ) <= val <= max (data )):
208
231
warnings .warn (
209
232
(
210
- "The value at position {} in `xvals ` is outside the range of its "
211
- "associated line (xmin ={}, xmax ={}, xval={}). Clipping it "
233
+ "The value at position {} in `vals ` is outside the range of its "
234
+ "associated line (vmin ={}, vmax ={}, xval={}). Clipping it "
212
235
"into the allowed range."
213
- ).format (i , min (xdata ), max (xdata ), xv ),
236
+ ).format (i , min (data ), max (data ), val ),
214
237
UserWarning ,
215
238
stacklevel = 1 ,
216
239
)
217
- new_xv = min (xdata ) + (max (xdata ) - min (xdata )) * 0.9
218
- xvals [i ] = new_xv
240
+ new_val = min (data ) + (max (data ) - min (data )) * 0.9
241
+ vals [i ] = new_val
219
242
220
243
# Convert float values back to datetime in case of datetime axis
221
244
if isinstance (ax .xaxis .converter , DateConverter ):
222
- xvals = [num2date (x ).replace (tzinfo = ax .xaxis .get_units ()) for x in xvals ]
245
+ vals = [num2date (x ).replace (tzinfo = ax .xaxis .get_units ()) for x in vals ]
223
246
224
247
txts = []
225
248
try :
226
- yoffsets = [float (yoffsets )] * len (all_lines )
249
+ offsets = [float (offsets )] * len (all_lines )
227
250
except TypeError :
228
251
pass
229
- for line , x , yoffset , label in zip (lab_lines , xvals , yoffsets , labels ):
252
+ for line , val , offset , label in zip (lab_lines , vals , offsets , labels ):
230
253
txts .append (
231
254
labelLine (
232
255
line ,
233
- x ,
256
+ val ,
257
+ axis ,
234
258
label = label ,
235
259
align = align ,
236
260
drop_label = drop_label ,
237
- yoffset = yoffset ,
261
+ offset = offset ,
238
262
outline_color = outline_color ,
239
263
outline_width = outline_width ,
240
264
** kwargs ,
0 commit comments