Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 235674f

Browse files
committed
Interpret unitless bar widths for unitized data as relative to spacing.
1 parent c22847b commit 235674f

File tree

1 file changed

+60
-73
lines changed

1 file changed

+60
-73
lines changed

lib/matplotlib/axes/_axes.py

Lines changed: 60 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import matplotlib.ticker as mticker
3333
import matplotlib.transforms as mtransforms
3434
import matplotlib.tri as mtri
35+
import matplotlib.units as munits
3536
from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer
3637
from matplotlib.axes._base import _AxesBase, _process_plot_format
3738

@@ -2012,56 +2013,33 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs):
20122013
return self.plot(x, y, *args, data=data, **kwargs)
20132014

20142015
@staticmethod
2015-
def _convert_dx(dx, x0, xconv, convert):
2016+
def _convert_dz(axis, z, z_conv, dz):
20162017
"""
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.
20232021
"""
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
20652043

20662044
@_preprocess_data()
20672045
@docstring.dedent_interpd
@@ -2088,7 +2066,8 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
20882066
The height(s) of the bars.
20892067
20902068
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).
20922071
20932072
bottom : scalar or array-like, optional
20942073
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",
21662145
*xerr*, and *yerr* can be either scalars or sequences of
21672146
length equal to the number of bars. This enables you to use
21682147
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.
21722157
21732158
Other optional kwargs:
21742159
21752160
%(Rectangle)s
2176-
21772161
"""
21782162
kwargs = cbook.normalize_kwargs(kwargs, mpatches.Patch._alias_map)
21792163
color = kwargs.pop('color', None)
@@ -2226,18 +2210,16 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
22262210

22272211
# lets do some conversions now since some types cannot be
22282212
# 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)
22412223

22422224
x, height, width, y, linewidth = np.broadcast_arrays(
22432225
# Make args iterable too.
@@ -2376,8 +2358,9 @@ def barh(self, y, width, height=0.8, left=None, *, align="center",
23762358
width : scalar or array-like
23772359
The width(s) of the bars.
23782360
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).
23812364
23822365
left : sequence of scalars
23832366
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",
24522435
*xerr*, and *yerr* can be either scalars or sequences of
24532436
length equal to the number of bars. This enables you to use
24542437
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.
24582447
24592448
Other optional kwargs:
24602449
24612450
%(Rectangle)s
2462-
24632451
"""
24642452
kwargs.setdefault('orientation', 'horizontal')
24652453
patches = self.bar(x=left, height=height, width=width, bottom=y,
@@ -2535,9 +2523,8 @@ def broken_barh(self, xranges, yrange, **kwargs):
25352523
'with two elements (i.e. an Nx2 array)')
25362524
# convert the absolute values, not the x and dx...
25372525
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])
25392527
xranges_conv.append((x_conv, x1))
2540-
25412528
yrange_conv = self.convert_yunits(yrange)
25422529

25432530
col = mcoll.BrokenBarHCollection(xranges_conv, yrange_conv, **kwargs)

0 commit comments

Comments
 (0)