56
56
'vlines_wrapper' ,
57
57
]
58
58
59
+ docstring .snippets ['standardize.autoformat' ] = """
60
+ autoformat : bool, optional
61
+ Whether *x* axis labels, *y* axis labels, axis formatters, axes titles,
62
+ colorbar labels, and legend labels are automatically configured when
63
+ a `~pandas.Series`, `~pandas.DataFrame` or `~xarray.DataArray` is passed
64
+ to the plotting command. Default is the figure-wide
65
+ `proplot.figure.Figure.autoformat` setting.
66
+ """
67
+
59
68
docstring .snippets ['axes.cmap_changer' ] = """
60
69
cmap : colormap spec, optional
61
70
The colormap specifer, passed to the `~proplot.constructor.Colormap`
@@ -380,7 +389,8 @@ def _axis_labels_title(data, axis=None, units=True):
380
389
return data , str (label ).strip ()
381
390
382
391
383
- def standardize_1d (self , func , * args , ** kwargs ):
392
+ @docstring .add_snippets
393
+ def standardize_1d (self , func , * args , autoformat = None , ** kwargs ):
384
394
"""
385
395
Interpret positional arguments for the "1D" plotting methods so usage is
386
396
consistent. This also optionally modifies the x axis label, y axis label,
@@ -397,17 +407,22 @@ def standardize_1d(self, func, *args, **kwargs):
397
407
try to infer them from the metadata. Otherwise,
398
408
``np.arange(0, data.shape[0])`` is used.
399
409
400
- See also
401
- --------
402
- cycle_changer
410
+ Parameters
411
+ ----------
412
+ %(standardize.autoformat)s
413
+
414
+ See also
415
+ --------
416
+ cycle_changer
403
417
404
- Note
405
- ----
406
- This function wraps {methods}
407
- """
418
+ Note
419
+ ----
420
+ This function wraps {methods}
421
+ """
408
422
# Sanitize input
409
423
# TODO: Add exceptions for methods other than 'hist'?
410
424
name = func .__name__
425
+ autoformat = _not_none (autoformat , self .figure ._auto_format )
411
426
_load_objects ()
412
427
if not args :
413
428
return func (self , * args , ** kwargs )
@@ -466,7 +481,7 @@ def standardize_1d(self, func, *args, **kwargs):
466
481
kwargs ['positions' ] = xi
467
482
468
483
# Next handle labels if 'autoformat' is on
469
- if self . figure . _auto_format :
484
+ if autoformat :
470
485
# Ylabel
471
486
y , label = _axis_labels_title (y )
472
487
if label : # for histogram, this label is used for *x* coordinates
@@ -495,13 +510,14 @@ def standardize_1d(self, func, *args, **kwargs):
495
510
xmin , xmax = self .projection .lonmin , self .projection .lonmax
496
511
for y in ys :
497
512
# Ensure data is monotonic and falls within map bounds
498
- ix , iy = _enforce_bounds (* _standardize_latlon (x , y ), xmin , xmax )
513
+ ix , iy = _enforce_bounds (* _fix_latlon (x , y ), xmin , xmax )
499
514
iys .append (iy )
500
515
x , ys = ix , iys
501
516
502
517
# WARNING: For some functions, e.g. boxplot and violinplot, we *require*
503
518
# cycle_changer is also applied so it can strip 'x' input.
504
- return func (self , x , * ys , * args , ** kwargs )
519
+ with _state_context (self , _auto_format = autoformat ):
520
+ return func (self , x , * ys , * args , ** kwargs )
505
521
506
522
507
523
def _enforce_bounds (x , y , xmin , xmax ):
@@ -554,7 +570,7 @@ def _interp_poles(y, Z):
554
570
return y , Z
555
571
556
572
557
- def _standardize_latlon (x , y ):
573
+ def _fix_latlon (x , y ):
558
574
"""
559
575
Ensure longitudes are monotonic and make `~numpy.ndarray` copies so the
560
576
contents can be modified. Ignores 2D coordinate arrays.
@@ -576,7 +592,10 @@ def _standardize_latlon(x, y):
576
592
return x , y
577
593
578
594
579
- def standardize_2d (self , func , * args , order = 'C' , globe = False , ** kwargs ):
595
+ @docstring .add_snippets
596
+ def standardize_2d (
597
+ self , func , * args , autoformat = None , order = 'C' , globe = False , ** kwargs
598
+ ):
580
599
"""
581
600
Interpret positional arguments for the "2D" plotting methods so usage is
582
601
consistent. This also optionally modifies the x axis label, y axis label,
@@ -595,6 +614,7 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
595
614
596
615
Parameters
597
616
----------
617
+ %(standardize.autoformat)s
598
618
order : {{'C', 'F'}}, optional
599
619
If ``'C'``, arrays should be shaped as ``(y, x)``. If ``'F'``, arrays
600
620
should be shaped as ``(x, y)``. Default is ``'C'``.
@@ -621,6 +641,7 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
621
641
"""
622
642
# Sanitize input
623
643
name = func .__name__
644
+ autoformat = _not_none (autoformat , self .figure ._auto_format )
624
645
_load_objects ()
625
646
if not args :
626
647
return func (self , * args , ** kwargs )
@@ -707,21 +728,24 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
707
728
kw ['yminorlocator' ] = mticker .NullLocator ()
708
729
709
730
# Handle labels if 'autoformat' is on
710
- if self . figure . _auto_format :
731
+ if autoformat :
711
732
for key , xy in zip (('xlabel' , 'ylabel' ), (x , y )):
712
733
_ , label = _axis_labels_title (xy )
713
734
if label :
714
735
kw [key ] = label
715
- if len (xy ) > 1 and all (isinstance (xy , Number )
716
- for xy in xy [:2 ]) and xy [1 ] < xy [0 ]:
736
+ if (
737
+ len (xy ) > 1
738
+ and all (isinstance (xy , Number ) for xy in xy [:2 ])
739
+ and xy [1 ] < xy [0 ]
740
+ ):
717
741
kw [key [0 ] + 'reverse' ] = True
718
742
if xi is not None :
719
743
x = xi
720
744
if yi is not None :
721
745
y = yi
722
746
723
747
# Handle figure titles
724
- if self . figure . _auto_format :
748
+ if autoformat :
725
749
_ , colorbar_label = _axis_labels_title (Zs [0 ], units = True )
726
750
_ , title = _axis_labels_title (Zs [0 ], units = False )
727
751
if title :
@@ -796,7 +820,7 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
796
820
getattr (self , 'name' , '' ) == 'cartopy'
797
821
and isinstance (kwargs .get ('transform' , None ), PlateCarree )
798
822
):
799
- x , y = _standardize_latlon (x , y )
823
+ x , y = _fix_latlon (x , y )
800
824
ix , iZs = x , []
801
825
for Z in Zs :
802
826
if globe and x .ndim == 1 and y .ndim == 1 :
@@ -805,7 +829,7 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
805
829
806
830
# Fix seams by ensuring circular coverage. Unlike basemap,
807
831
# cartopy can plot across map edges.
808
- if ( x [0 ] % 360 ) != (( x [- 1 ] + 360 ) % 360 ) :
832
+ if x [0 ] % 360 != (x [- 1 ] + 360 ) % 360 :
809
833
ix = ma .concatenate ((x , [x [0 ] + 360 ]))
810
834
Z = ma .concatenate ((Z , Z [:, :1 ]), axis = 1 )
811
835
iZs .append (Z )
@@ -815,7 +839,7 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
815
839
elif getattr (self , 'name' , '' ) == 'basemap' and kwargs .get ('latlon' , None ):
816
840
# Fix grid
817
841
xmin , xmax = self .projection .lonmin , self .projection .lonmax
818
- x , y = _standardize_latlon (x , y )
842
+ x , y = _fix_latlon (x , y )
819
843
ix , iZs = x , []
820
844
for Z in Zs :
821
845
# Ensure data is within map bounds
@@ -868,7 +892,8 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
868
892
# was stripped by globe=True.
869
893
colorbar_kw = kwargs .pop ('colorbar_kw' , None ) or {}
870
894
colorbar_kw .setdefault ('label' , colorbar_label )
871
- return func (self , x , y , * Zs , colorbar_kw = colorbar_kw , ** kwargs )
895
+ with _state_context (self , _auto_format = autoformat ):
896
+ return func (self , x , y , * Zs , colorbar_kw = colorbar_kw , ** kwargs )
872
897
873
898
874
899
def _get_error_data (
@@ -2184,6 +2209,7 @@ def cycle_changer(
2184
2209
# NOTE: Requires standardize_1d wrapper before reaching this. Also note
2185
2210
# that the 'x' coordinates are sometimes ignored below.
2186
2211
name = func .__name__
2212
+ autoformat = self ._auto_format # possibly manipulated by standardize_[12]d
2187
2213
if not args :
2188
2214
return func (self , * args , ** kwargs )
2189
2215
x , y , * args = args
@@ -2265,19 +2291,36 @@ def cycle_changer(
2265
2291
if key in prop_keys and prop is None : # if key in cycler and property unset
2266
2292
apply_from_cycler .add (key )
2267
2293
2268
- # Handle legend labels and
2294
+ # Handle legend labels. Several scenarios:
2295
+ # 1. Always prefer input labels
2296
+ # 2. Always add labels if this is a *named* dimension.
2297
+ # 3. Even if not *named* dimension add labels if labels are string
2269
2298
# WARNING: Most methods that accept 2D arrays use columns of data, but when
2270
2299
# pandas DataFrame passed to hist, boxplot, or violinplot, rows of data
2271
2300
# assumed! This is fixed in parse_1d by converting to values.
2301
+ y1 = ys [0 ]
2272
2302
ncols = 1
2273
2303
labels = _not_none (values = values , labels = labels , label = label )
2304
+ colorbar_legend_label = None # for colorbar or legend
2274
2305
if name in ('pie' , 'boxplot' , 'violinplot' ):
2275
2306
if labels is not None :
2276
- kwargs ['labels' ] = labels
2307
+ kwargs ['labels' ] = labels # error raised down the line
2277
2308
else :
2309
+ # Get column count and sanitize labels
2278
2310
ncols = 1 if y .ndim == 1 else y .shape [1 ]
2279
- if labels is None or isinstance (labels , str ):
2311
+ if not np . iterable ( labels ) or isinstance (labels , str ):
2280
2312
labels = [labels ] * ncols
2313
+ if len (labels ) != ncols :
2314
+ raise ValueError (
2315
+ f'Got { ncols } columns in data array, but { len (labels )} labels.'
2316
+ )
2317
+ # Get automatic legend labels and legend title
2318
+ if autoformat :
2319
+ ilabels , colorbar_legend_label = _axis_labels_title (y1 , axis = 1 )
2320
+ ilabels = _to_ndarray (ilabels ) # may be empty!
2321
+ for i , (ilabel , label ) in enumerate (zip (ilabels , labels )):
2322
+ if label is None and (colorbar_legend_label or isinstance (ilabel , str )):
2323
+ labels [i ] = ilabel
2281
2324
2282
2325
# Get step size for bar plots
2283
2326
# WARNING: This will fail for non-numeric non-datetime64 singleton
@@ -2301,7 +2344,6 @@ def cycle_changer(
2301
2344
2302
2345
# Plot susccessive columns
2303
2346
objs = []
2304
- label_leg_cbar = None # for colorbar or legend
2305
2347
for i in range (ncols ):
2306
2348
# Prop cycle properties
2307
2349
kw = kwargs .copy ()
@@ -2318,66 +2360,51 @@ def cycle_changer(
2318
2360
kw [key ] = value
2319
2361
2320
2362
# Get x coordinates for bar plot
2321
- x_col , y_first = x , ys [ 0 ] # samples
2363
+ ix = x # samples
2322
2364
if name in ('bar' ,): # adjust
2323
2365
if not stacked :
2324
2366
offset = width * (i - 0.5 * (ncols - 1 ))
2325
- x_col = x + offset
2326
- elif stacked and y_first .ndim > 1 :
2367
+ ix = x + offset
2368
+ elif stacked and y1 .ndim > 1 :
2327
2369
key = 'x' if barh else 'bottom'
2328
- kw [key ] = _to_indexer (y_first )[:, :i ].sum (axis = 1 )
2370
+ kw [key ] = _to_indexer (y1 )[:, :i ].sum (axis = 1 )
2329
2371
2330
2372
# Get y coordinates and labels
2331
2373
if name in ('pie' , 'boxplot' , 'violinplot' ):
2332
2374
# Only ever have one y value, cannot have legend labs
2333
- ys_col = (y_first ,)
2375
+ iys = (y1 ,)
2334
2376
2335
2377
else :
2336
2378
# The coordinates
2337
2379
# WARNING: If stacked=True then we always *ignore* second
2338
2380
# argument passed to fill_between. Warning should be issued
2339
2381
# by fill_between_wrapper in this case.
2340
2382
if stacked and name in ('fill_between' , 'fill_betweenx' ):
2341
- ys_col = tuple (
2342
- y_first if y_first .ndim == 1
2343
- else _to_indexer (y_first )[:, :ii ].sum (axis = 1 )
2383
+ iys = tuple (
2384
+ y1 if y1 .ndim == 1
2385
+ else _to_indexer (y1 )[:, :ii ].sum (axis = 1 )
2344
2386
for ii in (i , i + 1 )
2345
2387
)
2346
2388
else :
2347
- ys_col = tuple (
2389
+ iys = tuple (
2348
2390
y_i if y_i .ndim == 1 else _to_indexer (y_i )[:, i ]
2349
2391
for y_i in ys
2350
2392
)
2351
2393
2352
- # Possible legend labels
2353
- # Several scenarios:
2354
- # 1. Always prefer input labels
2355
- # 2. Always add labels if this is a *named* dimension.
2356
- # 3. Even if not *named* dimension add labels if labels are string
2357
- if len (labels ) != ncols :
2358
- raise ValueError (
2359
- f'Got { ncols } columns in data array, '
2360
- f'but { len (labels )} labels.'
2361
- )
2362
- label = labels [i ] # input labels
2363
- labels_cols , label_leg_cbar = _axis_labels_title (y_first , axis = 1 )
2364
- labels_cols = _to_ndarray (labels_cols )
2365
- if label is None and (
2366
- label_leg_cbar or labels_cols .size and isinstance (labels_cols [i ], str )
2367
- ):
2368
- label = labels_cols [i ]
2394
+ # Add label for artist
2395
+ label = labels [i ]
2369
2396
if label is not None :
2370
2397
kw ['label' ] = label
2371
2398
2372
2399
# Build coordinate arguments
2373
- x_ys_col = ()
2400
+ ixy = ()
2374
2401
if barh : # special case, use kwargs only!
2375
- kw .update ({'bottom' : x_col , 'width' : ys_col [0 ]})
2402
+ kw .update ({'bottom' : ix , 'width' : iys [0 ]})
2376
2403
elif name in ('pie' , 'hist' , 'boxplot' , 'violinplot' ):
2377
- x_ys_col = ys_col
2404
+ ixy = iys
2378
2405
else : # has x-coordinates, and maybe more than one y
2379
- x_ys_col = (x_col , * ys_col )
2380
- obj = func (self , * x_ys_col , * args , ** kw )
2406
+ ixy = (ix , * iys )
2407
+ obj = func (self , * ixy , * args , ** kw )
2381
2408
if isinstance (obj , (list , tuple )) and len (obj ) == 1 :
2382
2409
obj = obj [0 ]
2383
2410
objs .append (obj )
@@ -2392,8 +2419,8 @@ def cycle_changer(
2392
2419
# Add keywords
2393
2420
if loc != 'fill' :
2394
2421
colorbar_kw .setdefault ('loc' , loc )
2395
- if label_leg_cbar :
2396
- colorbar_kw .setdefault ('label' , label_leg_cbar )
2422
+ if colorbar_legend_label :
2423
+ colorbar_kw .setdefault ('label' , colorbar_legend_label )
2397
2424
self ._auto_colorbar [loc ][1 ].update (colorbar_kw )
2398
2425
2399
2426
# Add legend
@@ -2409,8 +2436,8 @@ def cycle_changer(
2409
2436
# Add keywords
2410
2437
if loc != 'fill' :
2411
2438
legend_kw .setdefault ('loc' , loc )
2412
- if label_leg_cbar :
2413
- legend_kw .setdefault ('label' , label_leg_cbar )
2439
+ if colorbar_legend_label :
2440
+ legend_kw .setdefault ('label' , colorbar_legend_label )
2414
2441
self ._auto_legend [loc ][1 ].update (legend_kw )
2415
2442
2416
2443
# Return
@@ -2745,6 +2772,7 @@ def cmap_changer(
2745
2772
of the color selections.
2746
2773
"""
2747
2774
name = func .__name__
2775
+ autoformat = self ._auto_format # possibly manipulated by standardize_[12]d
2748
2776
if not args :
2749
2777
return func (self , * args , ** kwargs )
2750
2778
@@ -2948,7 +2976,8 @@ def cmap_changer(
2948
2976
# Optionally add colorbar
2949
2977
if colorbar :
2950
2978
loc = self ._loc_translate (colorbar , 'colorbar' , allow_manual = False )
2951
- if 'label' not in colorbar_kw and self .figure ._auto_format :
2979
+ label = colorbar_kw .pop ('label' , None )
2980
+ if label is None and autoformat :
2952
2981
_ , label = _axis_labels_title (Z_sample ) # last one is data, we assume
2953
2982
if label :
2954
2983
colorbar_kw .setdefault ('label' , label )
0 commit comments