32
32
import matplotlib .ticker as mticker
33
33
import matplotlib .transforms as mtransforms
34
34
import matplotlib .tri as mtri
35
+ import matplotlib .units as munits
35
36
from matplotlib .container import BarContainer , ErrorbarContainer , StemContainer
36
37
from matplotlib .axes ._base import _AxesBase , _process_plot_format
37
38
@@ -2012,56 +2013,33 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs):
2012
2013
return self .plot (x , y , * args , data = data , ** kwargs )
2013
2014
2014
2015
@staticmethod
2015
- def _convert_dx ( dx , x0 , xconv , convert ):
2016
+ def _convert_dz ( axis , z , z_conv , dz ):
2016
2017
"""
2017
- Small helper to do logic of width conversion flexibly.
2018
-
2019
- *dx* and *x0* have units, but *xconv* has already been converted
2020
- to unitless (and is an ndarray). This allows the *dx* to have units
2021
- that are different from *x0*, but are still accepted by the
2022
- ``__add__`` operator of *x0*.
2018
+ Convert potentially unitized *dz* into unitless values using the
2019
+ *axis*' converter and *z*/*z_conv* as values (respectively unitized and
2020
+ unitless) over which *dz* apply.
2023
2021
"""
2024
-
2025
- # x should be an array...
2026
- assert type (xconv ) is np .ndarray
2027
-
2028
- if xconv .size == 0 :
2029
- # xconv has already been converted, but maybe empty...
2030
- return convert (dx )
2031
-
2032
- try :
2033
- # attempt to add the width to x0; this works for
2034
- # datetime+timedelta, for instance
2035
-
2036
- # only use the first element of x and x0. This saves
2037
- # having to be sure addition works across the whole
2038
- # vector. This is particularly an issue if
2039
- # x0 and dx are lists so x0 + dx just concatenates the lists.
2040
- # We can't just cast x0 and dx to numpy arrays because that
2041
- # removes the units from unit packages like `pint` that
2042
- # wrap numpy arrays.
2043
- try :
2044
- x0 = x0 [0 ]
2045
- except (TypeError , IndexError , KeyError ):
2046
- x0 = x0
2047
-
2048
- try :
2049
- x = xconv [0 ]
2050
- except (TypeError , IndexError , KeyError ):
2051
- x = xconv
2052
-
2053
- delist = False
2054
- if not np .iterable (dx ):
2055
- dx = [dx ]
2056
- delist = True
2057
- dx = [convert (x0 + ddx ) - x for ddx in dx ]
2058
- if delist :
2059
- dx = dx [0 ]
2060
- except (ValueError , TypeError , AttributeError ):
2061
- # if the above fails (for any reason) just fallback to what
2062
- # we do by default and convert dx by iteslf.
2063
- dx = convert (dx )
2064
- return dx
2022
+ scalar_dz = np .ndim (dz ) == 0
2023
+ if axis .converter is not None : # z was unitized.
2024
+ if munits .ConversionInterface .is_numlike (dz ): # Scalar width.
2025
+ if np .size (z_conv ) == 1 :
2026
+ dz = np .diff (axis .get_view_interval ()) * dz
2027
+ else :
2028
+ dz = np .array (dz ) * np .ptp (z_conv ) / (np .size (z_conv ) - 1 )
2029
+ else : # Unitized.
2030
+ # cbook.flatten([]) is similar to atleast_1d here, but returns
2031
+ # a list and keeps units.
2032
+ z = [* cbook .flatten ([z ])]
2033
+ dz = [* cbook .flatten ([dz ])]
2034
+ if len (z ) == 1 :
2035
+ z = z * len (dz )
2036
+ if len (dz ) == 1 :
2037
+ dz = dz * len (z )
2038
+ dz = [axis .convert_units (_z + _dz ) - axis .convert_units (_z )
2039
+ for _z , _dz in zip (z , dz )]
2040
+ if scalar_dz and np .ndim (dz ) > 0 :
2041
+ dz , = dz
2042
+ return dz
2065
2043
2066
2044
@_preprocess_data ()
2067
2045
@docstring .dedent_interpd
@@ -2088,7 +2066,8 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
2088
2066
The height(s) of the bars.
2089
2067
2090
2068
width : scalar or array-like, optional
2091
- The width(s) of the bars (default: 0.8).
2069
+ The width(s) of the bars (default: 0.8, but see note below if *x*
2070
+ has units).
2092
2071
2093
2072
bottom : scalar or array-like, optional
2094
2073
The y coordinate(s) of the bars bases (default: 0).
@@ -2166,14 +2145,19 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
2166
2145
*xerr*, and *yerr* can be either scalars or sequences of
2167
2146
length equal to the number of bars. This enables you to use
2168
2147
bar as the basis for stacked bar charts, or candlestick plots.
2169
- Detail: *xerr* and *yerr* are passed directly to
2170
- :meth:`errorbar`, so they can also have shape 2xN for
2171
- independent specification of lower and upper errors.
2148
+
2149
+ If *x* has units, then *width* should normally also be specified with
2150
+ units (so that they can be meaningfully added). However, if a unitless
2151
+ *width* is used (in particular, for the default *width*), then it
2152
+ is interpreted as a fraction of the average inter-bar spacing. For
2153
+ example, if the bars are equally spaced, then a *width* of 1 makes the
2154
+ bars contiguous. As a special case, if there is a *single* bar, then
2155
+ a unitless *width* is interpreted as a fraction of the axis (current)
2156
+ view span.
2172
2157
2173
2158
Other optional kwargs:
2174
2159
2175
2160
%(Rectangle)s
2176
-
2177
2161
"""
2178
2162
kwargs = cbook .normalize_kwargs (kwargs , mpatches .Patch ._alias_map )
2179
2163
color = kwargs .pop ('color' , None )
@@ -2226,18 +2210,16 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
2226
2210
2227
2211
# lets do some conversions now since some types cannot be
2228
2212
# subtracted uniformly
2229
- if self .xaxis is not None :
2230
- x0 = x
2231
- x = np .asarray (self .convert_xunits (x ))
2232
- width = self ._convert_dx (width , x0 , x , self .convert_xunits )
2233
- if xerr is not None :
2234
- xerr = self ._convert_dx (xerr , x0 , x , self .convert_xunits )
2235
- if self .yaxis is not None :
2236
- y0 = y
2237
- y = np .asarray (self .convert_yunits (y ))
2238
- height = self ._convert_dx (height , y0 , y , self .convert_yunits )
2239
- if yerr is not None :
2240
- yerr = self ._convert_dx (yerr , y0 , y , self .convert_yunits )
2213
+ x0 = x
2214
+ x = self .convert_xunits (x )
2215
+ width = self ._convert_dz (self .xaxis , x0 , x , width )
2216
+ if xerr is not None :
2217
+ xerr = self ._convert_dz (self .xaxis , x0 , x , xerr )
2218
+ y0 = y
2219
+ y = self .convert_yunits (y )
2220
+ height = self ._convert_dz (self .yaxis , y0 , y , height )
2221
+ if yerr is not None :
2222
+ yerr = self ._convert_dz (self .yaxis , y0 , y , yerr )
2241
2223
2242
2224
x , height , width , y , linewidth = np .broadcast_arrays (
2243
2225
# Make args iterable too.
@@ -2376,8 +2358,9 @@ def barh(self, y, width, height=0.8, left=None, *, align="center",
2376
2358
width : scalar or array-like
2377
2359
The width(s) of the bars.
2378
2360
2379
- height : sequence of scalars, optional, default: 0.8
2380
- The heights of the bars.
2361
+ height : sequence of scalars, optional
2362
+ The heights of the bars (default: 0.8, but see note below if *y*
2363
+ has units).
2381
2364
2382
2365
left : sequence of scalars
2383
2366
The x coordinates of the left sides of the bars (default: 0).
@@ -2452,14 +2435,19 @@ def barh(self, y, width, height=0.8, left=None, *, align="center",
2452
2435
*xerr*, and *yerr* can be either scalars or sequences of
2453
2436
length equal to the number of bars. This enables you to use
2454
2437
bar as the basis for stacked bar charts, or candlestick plots.
2455
- Detail: *xerr* and *yerr* are passed directly to
2456
- :meth:`errorbar`, so they can also have shape 2xN for
2457
- independent specification of lower and upper errors.
2438
+
2439
+ If *y* has units, then *height* should normally also be specified with
2440
+ units (so that they can be meaningfully added). However, if a unitless
2441
+ *height* is used (in particular, for the default *height*), then it is
2442
+ interpreted as a fraction of the average inter-bar spacing. For
2443
+ example, if the bars are equally spaced, then a *height* of 1 makes the
2444
+ bars contiguous. As a special case, if there is a *single* bar, then
2445
+ a unitless *height* is interpreted as a fraction of the axis (current)
2446
+ view span.
2458
2447
2459
2448
Other optional kwargs:
2460
2449
2461
2450
%(Rectangle)s
2462
-
2463
2451
"""
2464
2452
kwargs .setdefault ('orientation' , 'horizontal' )
2465
2453
patches = self .bar (x = left , height = height , width = width , bottom = y ,
@@ -2535,9 +2523,8 @@ def broken_barh(self, xranges, yrange, **kwargs):
2535
2523
'with two elements (i.e. an Nx2 array)' )
2536
2524
# convert the absolute values, not the x and dx...
2537
2525
x_conv = np .asarray (self .convert_xunits (xr [0 ]))
2538
- x1 = self ._convert_dx ( xr [ 1 ] , xr [0 ], x_conv , self . convert_xunits )
2526
+ x1 = self ._convert_dz ( self . xaxis , xr [0 ], x_conv , xr [ 1 ] )
2539
2527
xranges_conv .append ((x_conv , x1 ))
2540
-
2541
2528
yrange_conv = self .convert_yunits (yrange )
2542
2529
2543
2530
col = mcoll .BrokenBarHCollection (xranges_conv , yrange_conv , ** kwargs )
0 commit comments