diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 7dd29757e35b..e42280f6e29c 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -643,29 +643,43 @@ class Rectangle(Patch): """ def __str__(self): - pars = self._x, self._y, self._width, self._height, self.angle + pars = self._x0, self._y0, self._width, self._height, self.angle fmt = "Rectangle(xy=(%g, %g), width=%g, height=%g, angle=%g)" return fmt % pars @docstring.dedent_interpd def __init__(self, xy, width, height, angle=0.0, **kwargs): """ + Parameters + ---------- + xy: length-2 tuple + The bottom and left rectangle coordinates + width: + Rectangle width + height: + Rectangle height + angle: float, optional + rotation in degrees anti-clockwise about *xy* (default is 0.0) + fill: bool, optional + Whether to fill the rectangle (default is ``True``) - *angle* - rotation in degrees (anti-clockwise) - - *fill* is a boolean indicating whether to fill the rectangle - + Notes + ----- Valid kwargs are: %(Patch)s """ Patch.__init__(self, **kwargs) - self._x = xy[0] - self._y = xy[1] + self._x0 = xy[0] + self._y0 = xy[1] + self._width = width self._height = height + + self._x1 = self._x0 + self._width + self._y1 = self._y0 + self._height + self.angle = float(angle) # Note: This cannot be calculated until this is added to an Axes self._rect_transform = transforms.IdentityTransform() @@ -682,34 +696,47 @@ def _update_patch_transform(self): makes it very important to call the accessor method and not directly access the transformation member variable. """ - x = self.convert_xunits(self._x) - y = self.convert_yunits(self._y) - width = self.convert_xunits(self._width) - height = self.convert_yunits(self._height) - bbox = transforms.Bbox.from_bounds(x, y, width, height) + x0, y0, x1, y1 = self._convert_units() + bbox = transforms.Bbox.from_extents(x0, y0, x1, y1) rot_trans = transforms.Affine2D() - rot_trans.rotate_deg_around(x, y, self.angle) + rot_trans.rotate_deg_around(x0, y0, self.angle) self._rect_transform = transforms.BboxTransformTo(bbox) self._rect_transform += rot_trans + def _update_x1(self): + self._x1 = self._x0 + self._width + + def _update_y1(self): + self._y1 = self._y0 + self._height + + def _convert_units(self): + ''' + Convert bounds of the rectangle + ''' + x0 = self.convert_xunits(self._x0) + y0 = self.convert_yunits(self._y0) + x1 = self.convert_xunits(self._x1) + y1 = self.convert_yunits(self._y1) + return x0, y0, x1, y1 + def get_patch_transform(self): self._update_patch_transform() return self._rect_transform def get_x(self): "Return the left coord of the rectangle" - return self._x + return self._x0 def get_y(self): "Return the bottom coord of the rectangle" - return self._y + return self._y0 def get_xy(self): "Return the left and bottom coords of the rectangle" - return self._x, self._y + return self._x0, self._y0 def get_width(self): - "Return the width of the rectangle" + "Return the width of the rectangle" return self._width def get_height(self): @@ -717,21 +744,15 @@ def get_height(self): return self._height def set_x(self, x): - """ - Set the left coord of the rectangle - - ACCEPTS: float - """ - self._x = x + "Set the left coord of the rectangle" + self._x0 = x + self._update_x1() self.stale = True def set_y(self, y): - """ - Set the bottom coord of the rectangle - - ACCEPTS: float - """ - self._y = y + "Set the bottom coord of the rectangle" + self._y0 = y + self._update_y1() self.stale = True def set_xy(self, xy): @@ -740,25 +761,21 @@ def set_xy(self, xy): ACCEPTS: 2-item sequence """ - self._x, self._y = xy + self._x0, self._y0 = xy + self._update_x1() + self._update_y1() self.stale = True def set_width(self, w): - """ - Set the width rectangle - - ACCEPTS: float - """ + "Set the width of the rectangle" self._width = w + self._update_x1() self.stale = True def set_height(self, h): - """ - Set the width rectangle - - ACCEPTS: float - """ + "Set the height of the rectangle" self._height = h + self._update_y1() self.stale = True def set_bounds(self, *args): @@ -771,15 +788,18 @@ def set_bounds(self, *args): l, b, w, h = args[0] else: l, b, w, h = args - self._x = l - self._y = b + self._x0 = l + self._y0 = b self._width = w self._height = h + self._update_x1() + self._update_y1() self.stale = True def get_bbox(self): - return transforms.Bbox.from_bounds(self._x, self._y, - self._width, self._height) + x0, y0, x1, y1 = self._convert_units() + return transforms.Bbox.from_extents(self._x0, self._y0, + self._x1, self._y1) xy = property(get_xy, set_xy) diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 7693148bef57..2ea7af52e509 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -362,3 +362,28 @@ def test_connection_patch(): axesA=ax2, axesB=ax1, arrowstyle="->") ax2.add_artist(con) + + +def test_datetime_rectangle(): + # Check that creating a rectangle with timedeltas doesn't fail + from datetime import datetime, timedelta + + start = datetime(2017, 1, 1, 0, 0, 0) + delta = timedelta(seconds=16) + patch = mpatches.Rectangle((start, 0), delta, 1) + + fig, ax = plt.subplots() + ax.add_patch(patch) + + +def test_datetime_datetime_fails(): + from datetime import datetime + + start = datetime(2017, 1, 1, 0, 0, 0) + dt_delta = datetime(1970, 1, 5) # Will be 5 days if units are done wrong + + with pytest.raises(TypeError): + mpatches.Rectangle((start, 0), dt_delta, 1) + + with pytest.raises(TypeError): + mpatches.Rectangle((0, start), 1, dt_delta)