diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index fd724f55876c..a7164c13cbcb 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2011,6 +2011,58 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs): kwargs['drawstyle'] = 'steps-' + where return self.plot(x, y, *args, data=data, **kwargs) + @staticmethod + def _convert_dx(dx, x0, xconv, convert): + """ + Small helper to do logic of width conversion flexibly. + + *dx* and *x0* have units, but *xconv* has already been converted + to unitless (and is an ndarray). This allows the *dx* to have units + that are different from *x0*, but are still accepted by the + ``__add__`` operator of *x0*. + """ + + # x should be an array... + assert type(xconv) is np.ndarray + + if xconv.size == 0: + # xconv has already been converted, but maybe empty... + return convert(dx) + + try: + # attempt to add the width to x0; this works for + # datetime+timedelta, for instance + + # only use the first element of x and x0. This saves + # having to be sure addition works across the whole + # vector. This is particularly an issue if + # x0 and dx are lists so x0 + dx just concatenates the lists. + # We can't just cast x0 and dx to numpy arrays because that + # removes the units from unit packages like `pint` that + # wrap numpy arrays. + try: + x0 = x0[0] + except (TypeError, IndexError, KeyError): + x0 = x0 + + try: + x = xconv[0] + except (TypeError, IndexError, KeyError): + x = xconv + + delist = False + if not np.iterable(dx): + dx = [dx] + delist = True + dx = [convert(x0 + ddx) - x for ddx in dx] + if delist: + dx = dx[0] + except (TypeError, AttributeError) as e: + # but doesn't work for 'string' + float, so just + # see if the converter works on the float. + dx = convert(dx) + return dx + @_preprocess_data() @docstring.dedent_interpd 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", # lets do some conversions now since some types cannot be # subtracted uniformly if self.xaxis is not None: - x = self.convert_xunits(x) - width = self.convert_xunits(width) + x0 = x + x = np.asarray(self.convert_xunits(x)) + width = self._convert_dx(width, x0, x, self.convert_xunits) if xerr is not None: - xerr = self.convert_xunits(xerr) - + xerr = self._convert_dx(xerr, x0, x, self.convert_xunits) if self.yaxis is not None: - y = self.convert_yunits(y) - height = self.convert_yunits(height) + y0 = y + y = np.asarray(self.convert_yunits(y)) + height = self._convert_dx(height, y0, y, self.convert_yunits) if yerr is not None: - yerr = self.convert_yunits(yerr) + yerr = self._convert_dx(yerr, y0, y, self.convert_yunits) x, height, width, y, linewidth = np.broadcast_arrays( # Make args iterable too. @@ -2465,10 +2518,19 @@ def broken_barh(self, xranges, yrange, **kwargs): self._process_unit_info(xdata=xdata, ydata=ydata, kwargs=kwargs) - xranges = self.convert_xunits(xranges) - yrange = self.convert_yunits(yrange) - - col = mcoll.BrokenBarHCollection(xranges, yrange, **kwargs) + xranges_conv = [] + for xr in xranges: + if len(xr) != 2: + raise ValueError('each range in xrange must be a sequence ' + 'with two elements (i.e. an Nx2 array)') + # convert the absolute values, not the x and dx... + x_conv = np.asarray(self.convert_xunits(xr[0])) + x1 = self._convert_dx(xr[1], xr[0], x_conv, self.convert_xunits) + xranges_conv.append((x_conv, x1)) + + yrange_conv = self.convert_yunits(yrange) + + col = mcoll.BrokenBarHCollection(xranges_conv, yrange_conv, **kwargs) self.add_collection(col, autolim=True) self.autoscale_view() diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 6e75ce726963..c8e2a00e0b15 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1498,6 +1498,32 @@ def test_barh_tick_label(): align='center') +def test_bar_timedelta(): + """smoketest that bar can handle width and height in delta units""" + fig, ax = plt.subplots() + ax.bar(datetime.datetime(2018, 1, 1), 1., + width=datetime.timedelta(hours=3)) + ax.bar(datetime.datetime(2018, 1, 1), 1., + xerr=datetime.timedelta(hours=2), + width=datetime.timedelta(hours=3)) + fig, ax = plt.subplots() + ax.barh(datetime.datetime(2018, 1, 1), 1, + height=datetime.timedelta(hours=3)) + ax.barh(datetime.datetime(2018, 1, 1), 1, + height=datetime.timedelta(hours=3), + yerr=datetime.timedelta(hours=2)) + fig, ax = plt.subplots() + ax.barh([datetime.datetime(2018, 1, 1), datetime.datetime(2018, 1, 1)], + np.array([1, 1.5]), + height=datetime.timedelta(hours=3)) + ax.barh([datetime.datetime(2018, 1, 1), datetime.datetime(2018, 1, 1)], + np.array([1, 1.5]), + height=[datetime.timedelta(hours=t) for t in [1, 2]]) + ax.broken_barh([(datetime.datetime(2018, 1, 1), + datetime.timedelta(hours=1))], + (10, 20)) + + @image_comparison(baseline_images=['hist_log'], remove_text=True) def test_hist_log(): @@ -5433,6 +5459,15 @@ def test_broken_barh_empty(): ax.broken_barh([], (.1, .5)) +def test_broken_barh_timedelta(): + """Check that timedelta works as x, dx pair for this method """ + fig, ax = plt.subplots() + pp = ax.broken_barh([(datetime.datetime(2018, 11, 9, 0, 0, 0), + datetime.timedelta(hours=1))], [1, 2]) + assert pp.get_paths()[0].vertices[0, 0] == 737007.0 + assert pp.get_paths()[0].vertices[2, 0] == 737007.0 + 1 / 24 + + def test_pandas_pcolormesh(pd): time = pd.date_range('2000-01-01', periods=10) depth = np.arange(20)
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: