Skip to content

Commit b7b8f5b

Browse files
authored
Merge pull request #12903 from jklymak/fix-broken-barh-units
FIX: (broken)bar(h) math before units
2 parents 04d1b17 + f9c43b7 commit b7b8f5b

File tree

2 files changed

+108
-11
lines changed

2 files changed

+108
-11
lines changed

lib/matplotlib/axes/_axes.py

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,58 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs):
20112011
kwargs['drawstyle'] = 'steps-' + where
20122012
return self.plot(x, y, *args, data=data, **kwargs)
20132013

2014+
@staticmethod
2015+
def _convert_dx(dx, x0, xconv, convert):
2016+
"""
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*.
2023+
"""
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 (TypeError, AttributeError) as e:
2061+
# but doesn't work for 'string' + float, so just
2062+
# see if the converter works on the float.
2063+
dx = convert(dx)
2064+
return dx
2065+
20142066
@_preprocess_data()
20152067
@docstring.dedent_interpd
20162068
def bar(self, x, height, width=0.8, bottom=None, *, align="center",
@@ -2175,16 +2227,17 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
21752227
# lets do some conversions now since some types cannot be
21762228
# subtracted uniformly
21772229
if self.xaxis is not None:
2178-
x = self.convert_xunits(x)
2179-
width = self.convert_xunits(width)
2230+
x0 = x
2231+
x = np.asarray(self.convert_xunits(x))
2232+
width = self._convert_dx(width, x0, x, self.convert_xunits)
21802233
if xerr is not None:
2181-
xerr = self.convert_xunits(xerr)
2182-
2234+
xerr = self._convert_dx(xerr, x0, x, self.convert_xunits)
21832235
if self.yaxis is not None:
2184-
y = self.convert_yunits(y)
2185-
height = self.convert_yunits(height)
2236+
y0 = y
2237+
y = np.asarray(self.convert_yunits(y))
2238+
height = self._convert_dx(height, y0, y, self.convert_yunits)
21862239
if yerr is not None:
2187-
yerr = self.convert_yunits(yerr)
2240+
yerr = self._convert_dx(yerr, y0, y, self.convert_yunits)
21882241

21892242
x, height, width, y, linewidth = np.broadcast_arrays(
21902243
# Make args iterable too.
@@ -2465,10 +2518,19 @@ def broken_barh(self, xranges, yrange, **kwargs):
24652518
self._process_unit_info(xdata=xdata,
24662519
ydata=ydata,
24672520
kwargs=kwargs)
2468-
xranges = self.convert_xunits(xranges)
2469-
yrange = self.convert_yunits(yrange)
2470-
2471-
col = mcoll.BrokenBarHCollection(xranges, yrange, **kwargs)
2521+
xranges_conv = []
2522+
for xr in xranges:
2523+
if len(xr) != 2:
2524+
raise ValueError('each range in xrange must be a sequence '
2525+
'with two elements (i.e. an Nx2 array)')
2526+
# convert the absolute values, not the x and dx...
2527+
x_conv = np.asarray(self.convert_xunits(xr[0]))
2528+
x1 = self._convert_dx(xr[1], xr[0], x_conv, self.convert_xunits)
2529+
xranges_conv.append((x_conv, x1))
2530+
2531+
yrange_conv = self.convert_yunits(yrange)
2532+
2533+
col = mcoll.BrokenBarHCollection(xranges_conv, yrange_conv, **kwargs)
24722534
self.add_collection(col, autolim=True)
24732535
self.autoscale_view()
24742536

lib/matplotlib/tests/test_axes.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,32 @@ def test_barh_tick_label():
14981498
align='center')
14991499

15001500

1501+
def test_bar_timedelta():
1502+
"""smoketest that bar can handle width and height in delta units"""
1503+
fig, ax = plt.subplots()
1504+
ax.bar(datetime.datetime(2018, 1, 1), 1.,
1505+
width=datetime.timedelta(hours=3))
1506+
ax.bar(datetime.datetime(2018, 1, 1), 1.,
1507+
xerr=datetime.timedelta(hours=2),
1508+
width=datetime.timedelta(hours=3))
1509+
fig, ax = plt.subplots()
1510+
ax.barh(datetime.datetime(2018, 1, 1), 1,
1511+
height=datetime.timedelta(hours=3))
1512+
ax.barh(datetime.datetime(2018, 1, 1), 1,
1513+
height=datetime.timedelta(hours=3),
1514+
yerr=datetime.timedelta(hours=2))
1515+
fig, ax = plt.subplots()
1516+
ax.barh([datetime.datetime(2018, 1, 1), datetime.datetime(2018, 1, 1)],
1517+
np.array([1, 1.5]),
1518+
height=datetime.timedelta(hours=3))
1519+
ax.barh([datetime.datetime(2018, 1, 1), datetime.datetime(2018, 1, 1)],
1520+
np.array([1, 1.5]),
1521+
height=[datetime.timedelta(hours=t) for t in [1, 2]])
1522+
ax.broken_barh([(datetime.datetime(2018, 1, 1),
1523+
datetime.timedelta(hours=1))],
1524+
(10, 20))
1525+
1526+
15011527
@image_comparison(baseline_images=['hist_log'],
15021528
remove_text=True)
15031529
def test_hist_log():
@@ -5432,6 +5458,15 @@ def test_broken_barh_empty():
54325458
ax.broken_barh([], (.1, .5))
54335459

54345460

5461+
def test_broken_barh_timedelta():
5462+
"""Check that timedelta works as x, dx pair for this method """
5463+
fig, ax = plt.subplots()
5464+
pp = ax.broken_barh([(datetime.datetime(2018, 11, 9, 0, 0, 0),
5465+
datetime.timedelta(hours=1))], [1, 2])
5466+
assert pp.get_paths()[0].vertices[0, 0] == 737007.0
5467+
assert pp.get_paths()[0].vertices[2, 0] == 737007.0 + 1 / 24
5468+
5469+
54355470
def test_pandas_pcolormesh(pd):
54365471
time = pd.date_range('2000-01-01', periods=10)
54375472
depth = np.arange(20)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy