8000 Merge pull request #12903 from jklymak/fix-broken-barh-units · matplotlib/matplotlib@b7b8f5b · GitHub
[go: up one dir, main page]

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)
0