Revision: 3848 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3848&view=rev Author: mdboom Date: 2007-09-13 11:00:10 -0700 (Thu, 13 Sep 2007)
Log Message: ----------- New milestone -- resizing figure window works. shared_axis_demo.py works. (Uses callbacks to track changes between axes's). Modified Paths: -------------- branches/transforms/lib/matplotlib/affine.py branches/transforms/lib/matplotlib/artist.py branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/backend_bases.py branches/transforms/lib/matplotlib/figure.py branches/transforms/lib/matplotlib/pyplot.py branches/transforms/lib/matplotlib/text.py Modified: branches/transforms/lib/matplotlib/affine.py =================================================================== --- branches/transforms/lib/matplotlib/affine.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/affine.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -20,284 +20,461 @@ class TransformNode(object): def __init__(self): - self._parents = Set() - + self._parents = Set() + def invalidate(self): - if not self._do_invalidation(): - for parent in self._parents: - parent.invalidate() + if not self._do_invalidation(): + for parent in self._parents: + parent.invalidate() def _do_invalidation(self): - return False - - def add_children(self, children): - for child in children: - child._parents.add(self) + return False -class Bbox(TransformNode): + def set_children(self, children): + for child in children: + getattr(self, child)._parents.add(self) + self._children = children + +# def replace_child(self, index, child): +# children = self._children +# getattr(self, children[index])._parents.remove(self) +# setattr(self, children[index], child) +# # We have to reset children in case two or more +# # of the children are the same +# for child in children: +# getattr(self, child)._parents.add(self) +# self.invalidate() + +class BboxBase(TransformNode): + ''' + This is the read-only part of a bounding-box + ''' + + def __init__(self): + TransformNode.__init__(self) + + def __array__(self): + return self.get_points() + + # MGDTODO: Probably a more efficient ways to do this... + def _get_xmin(self): + return self.get_points()[0, 0] + xmin = property(_get_xmin) + + def _get_ymin(self): + return self.get_points()[0, 1] + ymin = property(_get_ymin) + + def _get_xmax(self): + return self.get_points()[1, 0] + xmax = property(_get_xmax) + + def _get_ymax(self): + return self.get_points()[1, 1] + ymax = property(_get_ymax) + + def _get_min(self): + return self.get_points()[0] + min = property(_get_min) + + def _get_max(self): + return self.get_points()[1] + max = property(_get_max) + + def _get_intervalx(self): + return self.get_points()[:, 0] + intervalx = property(_get_intervalx) + + def _get_intervaly(self): + return self.get_points()[:, 1] + intervaly = property(_get_intervaly) + + def _get_width(self): + return self.xmax - self.xmin + width = property(_get_width) + + def _get_height(self): + return self.ymax - self.ymin + height = property(_get_height) + + def _get_bounds(self): + return (self.xmin, self.ymin, + self.xmax - self.xmin, self.ymax - self.ymin) + bounds = property(_get_bounds) + + def get_points(self): + return NotImplementedError() + + # MGDTODO: Optimize + def containsx(self, x): + return x >= self.xmin and x <= self.xmax + + def containsy(self, y): + return y >= self.ymin and y <= self.ymax + + def contains(self, x, y): + return self.containsx(x) and self.containsy(y) + + def overlapsx(self, other): + return self.containsx(other.xmin) \ + or self.containsx(other.xmax) + + def overlapsy(self, other): + return self.containsy(other.ymin) \ + or self.containsx(other.ymax) + + def overlaps(self, other): + return self.overlapsx(other) \ + and self.overlapsy(other) + + def fully_containsx(self, x): + return x > self.xmin and x < self.xmax + + def fully_containsy(self, y): + return y > self.ymin and y < self.ymax + + def fully_contains(self, x, y): + return self.fully_containsx(x) \ + and self.fully_containsy(y) + + def fully_overlapsx(self, other): + return self.fully_containsx(other.xmin) \ + or self.fully_containsx(other.xmax) + + def fully_overlapsy(self, other): + return self.fully_containsy(other.ymin) \ + or self.fully_containsx(other.ymax) + + def fully_overlaps(self, other): + return self.fully_overlapsx(other) and \ + self.fully_overlapsy(other) + + +class Bbox(BboxBase): def __init__(self, points): - TransformNode.__init__(self) - self._points = npy.asarray(points, npy.float_) - self.track = False + BboxBase.__init__(self) + self._points = npy.asarray(points, npy.float_) [EMAIL PROTECTED] def unit(): - return Bbox.from_lbrt(0., 0., 1., 1.) + return Bbox.from_lbrt(0., 0., 1., 1.) unit = staticmethod(unit) [EMAIL PROTECTED] def from_lbwh(left, bottom, width, height): - return Bbox.from_lbrt(left, bottom, left + width, bottom + height) + return Bbox.from_lbrt(left, bottom, left + width, bottom + height) from_lbwh = staticmethod(from_lbwh) [EMAIL PROTECTED] def from_lbrt(*args): - points = npy.array(args, dtype=npy.float_).reshape(2, 2) - return Bbox(points) + points = npy.array(args, dtype=npy.float_).reshape(2, 2) + return Bbox(points) from_lbrt = staticmethod(from_lbrt) def __copy__(self): - return Bbox(self._points.copy()) + return Bbox(self._points.copy()) def __deepcopy__(self, memo): - return Bbox(self._points.copy()) + return Bbox(self._points.copy()) def __cmp__(self, other): - # MGDTODO: Totally suboptimal - if isinstance(other, Bbox) and (self._points == other._points).all(): - return 0 - return -1 + # MGDTODO: Totally suboptimal + if isinstance(other, Bbox) and (self._points == other._points).all(): + return 0 + return -1 def __repr__(self): - return 'Bbox(%s)' % repr(self._points) + return 'Bbox(%s)' % repr(self._points) __str__ = __repr__ - def __array__(self): - return self._points - # JDH: the update method will update the box limits from the # existing limits and the new data; it appears here you are just # using the new data. We use an "ignore" flag to specify whether # you want to include the existing data or not in the update def update_from_data(self, x, y, ignore=True): - self._points = npy.array([[x.min(), y.min()], [x.max(), y.max()]], npy.float_) - self.invalidate() + self._points = npy.array( + [[x.min(), y.min()], [x.max(), y.max()]], + npy.float_) + self.invalidate() # MGDTODO: Probably a more efficient ways to do this... - def _get_xmin(self): - return self._points[0, 0] def _set_xmin(self, val): - self._points[0, 0] = val - self.invalidate() - xmin = property(_get_xmin, _set_xmin) + self._points[0, 0] = val + self.invalidate() + xmin = property(BboxBase._get_xmin, _set_xmin) - def _get_ymin(self): - return self._points[0, 1] def _set_ymin(self, val): - self._points[0, 1] = val - self.invalidate() - ymin = property(_get_ymin, _set_ymin) + self._points[0, 1] = val + self.invalidate() + ymin = property(BboxBase._get_ymin, _set_ymin) - def _get_xmax(self): - return self._points[1, 0] def _set_xmax(self, val): - self._points[1, 0] = val - self.invalidate() - xmax = property(_get_xmax, _set_xmax) + self._points[1, 0] = val + self.invalidate() + xmax = property(BboxBase._get_xmax, _set_xmax) - def _get_ymax(self): - return self._points[1, 1] def _set_ymax(self, val): - self._points[1, 1] = val - self.invalidate() - ymax = property(_get_ymax, _set_ymax) + self._points[1, 1] = val + self.invalidate() + ymax = property(BboxBase._get_ymax, _set_ymax) - def _get_min(self): - return self._points[0] def _set_min(self, val): - self._points[0] = val - self.invalidate() - min = property(_get_min, _set_min) + self._points[0] = val + self.invalidate() + min = property(BboxBase._get_min, _set_min) - def _get_max(self): - return self._points[1] def _set_max(self, val): - self._points[1] = val - self.invalidate() - max = property(_get_max, _set_max) + self._points[1] = val + self.invalidate() + max = property(BboxBase._get_max, _set_max) - def _get_intervalx(self): - return self._points[:,0] def _set_intervalx(self, interval): - self._points[:,0] = interval - self.invalidate() - intervalx = property(_get_intervalx, _set_intervalx) + self._points[:, 0] = interval + self.invalidate() + intervalx = property(BboxBase._get_intervalx, _set_intervalx) - def _get_intervaly(self): - return self._points[:,1] def _set_intervaly(self, interval): - self._points[:,1] = interval - self.invalidate() - intervaly = property(_get_intervaly, _set_intervaly) + self._points[:, 1] = interval + self.invalidate() + intervaly = property(BboxBase._get_intervaly, _set_intervaly) - def _get_width(self): - return self.xmax - self.xmin - width = property(_get_width) + def _set_bounds(self, bounds): + l,b,w,h = bounds + self._points = npy.array([[l, b], [l+w, b+h]], npy.float_) + self.invalidate() + bounds = property(BboxBase._get_bounds, _set_bounds) - def _get_height(self): - return self.ymax - self.ymin - height = property(_get_height) + def get_points(self): + return self._points - def _get_bounds(self): - return (self.xmin, self.ymin, - self.xmax - self.xmin, self.ymax - self.ymin) - def _set_bounds(self, bounds): - l,b,w,h = bounds - self._points = npy.array([[l, b], [l+w, b+h]], npy.float_) - self.invalidate() - bounds = property(_get_bounds, _set_bounds) - + def set_points(self, points): + self._points = points + self.invalidate() + + def set(self, other): + self._points = other.get_points() + self.invalidate() + def transformed(self, transform): - return Bbox(transform(self._points)) + return Bbox(transform(self._points)) def inverse_transformed(self, transform): - return Bbox(transform.inverted()(self._points)) + return Bbox(transform.inverted()(self._points)) def expanded(self, sw, sh): - width = self.width - height = self.height - deltaw = (sw * width - width) / 2.0 - deltah = (sh * height - height) / 2.0 - a = npy.array([[-deltaw, -deltah], [deltaw, deltah]]) - return Bbox(self._points + a) + width = self.width + height = self.height + deltaw = (sw * width - width) / 2.0 + deltah = (sh * height - height) / 2.0 + a = npy.array([[-deltaw, -deltah], [deltaw, deltah]]) + return Bbox(self._points + a) - def contains(self, x, y): - return (x >= self.xmin and x <= self.xmax and - y >= self.ymin and y <= self.ymax) - [EMAIL PROTECTED] def union(bboxes): - """ - Return the Bbox that bounds all bboxes - """ - assert(len(bboxes)) + """ + Return the Bbox that bounds all bboxes + """ + assert(len(bboxes)) - if len(bboxes) == 1: - return bboxes[0] + if len(bboxes) == 1: + return bboxes[0] - bbox = bboxes[0] - xmin = bbox.xmin - ymin = bbox.ymin - xmax = bbox.xmax - ymax = bbox.ymax + bbox = bboxes[0] + xmin = bbox.xmin + ymin = bbox.ymin + xmax = bbox.xmax + ymax = bbox.ymax - for bbox in bboxes[1:]: - xmin = min(xmin, bbox.xmin) - ymin = min(ymin, bbox.ymin) - xmax = max(xmax, bbox.xmax) - ymax = max(ymax, bbox.ymax) + for bbox in bboxes[1:]: + xmin = min(xmin, bbox.xmin) + ymin = min(ymin, bbox.ymin) + xmax = max(xmax, bbox.xmax) + ymax = max(ymax, bbox.ymax) - return Bbox.from_lbrt(xmin, ymin, xmax, ymax) + return Bbox.from_lbrt(xmin, ymin, xmax, ymax) union = staticmethod(union) + +class TransformedBbox(BboxBase): + def __init__(self, bbox, transform): + assert isinstance(bbox, Bbox) + assert isinstance(transform, Transform) + + BboxBase.__init__(self) + self.bbox = bbox + self.transform = transform + self.set_children(['bbox', 'transform']) + self._points = None + + def __repr__(self): + return "TransformedBbox(%s, %s)" % (self.bbox, self.transform) + __str__ = __repr__ + def _do_invalidation(self): + self._points = None + + def get_points(self): + if self._points is None: + self._points = self.transform(self.bbox.get_points()) + return self._points + +# MGDTODO: This code probably works, but I don't think it's a good idea +# (from a code clarity perspective) +# class BlendedBbox(BboxBase): +# def __init__(self, bbox_x, bbox_y): +# assert isinstance(bbox_x, BboxBase) +# assert isinstance(bbox_y, BboxBase) + +# BboxBase.__init__(self) +# self._x = bbox_x +# self._y = bbox_y +# self.set_children(['_x', '_y']) +# self._points = None + +# def __repr__(self): +# return "TransformedBbox(%s, %s)" % (self.bbox, self.transform) +# __str__ = __repr__ + +# def _do_invalidation(self): +# self._points = None + +# def get_points(self): +# if self._points is None: +# # MGDTODO: Optimize +# if self._x == self._y: +# self._points = self._x.get_points() +# else: +# x_points = self._x.get_points() +# y_points = self._y.get_points() +# self._points = npy.array( +# [[x_points[0,0], y_points[0,1]], +# [x_points[1,0], y_points[1,1]]], +# npy.float_) +# return self._points + +# def _set_intervalx(self, pair): +# # MGDTODO: Optimize +# bbox = Bbox([[pair[0], 0.0], [pair[1], 0.0]]) +# self.replace_child(0, bbox) +# intervalx = property(BboxBase._get_intervalx, _set_intervalx) + +# def _set_intervaly(self, pair): +# # MGDTODO: Optimize +# bbox = Bbox([[0.0, pair[0]], [0.0, pair[1]]]) +# self.replace_child(1, bbox) +# intervaly = property(BboxBase._get_intervaly, _set_intervaly) + class Transform(TransformNode): def __init__(self): - TransformNode.__init__(self) + TransformNode.__init__(self) def __call__(self, points): - raise NotImplementedError() + raise NotImplementedError() def __add__(self, other): - if isinstance(other, Transform): - return composite_transform_factory(self, other) - raise TypeError("Can not add Transform to object of type '%s'" % type(other)) + if isinstance(other, Transform): + return composite_transform_factory(self, other) + raise TypeError( + "Can not add Transform to object of type '%s'" % type(other)) def __radd__(self, other): - if isinstance(other, Transform): - return composite_transform_factory(other, self) - raise TypeError("Can not add Transform to object of type '%s'" % type(other)) + if isinstance(other, Transform): + return composite_transform_factory(other, self) + raise TypeError( + "Can not add Transform to object of type '%s'" % type(other)) def transform_point(self, point): - return self.__call__(npy.asarray([point]))[0] + return self.__call__(npy.asarray([point]))[0] def has_inverse(self): - raise NotImplementedError() + raise NotImplementedError() def inverted(self): - raise NotImplementedError() + raise NotImplementedError() def is_separable(self): - return False + return False def is_affine(self): - return False + return False class Affine2DBase(Transform): input_dims = 2 output_dims = 2 def __init__(self): - Transform.__init__(self) - self._inverted = None + Transform.__init__(self) + self._inverted = None def _do_invalidation(self): - result = self._inverted is None - self._inverted = None - return result + result = self._inverted is None + self._inverted = None + return result [EMAIL PROTECTED] def _concat(a, b): return npy.dot(b, a) _concat = staticmethod(_concat) + + [EMAIL PROTECTED] + def concat(a, b): + return Affine2D(Affine2D._concat(a.get_matrix(), b.get_matrix())) + concat = staticmethod(concat) def to_values(self): - mtx = self.get_matrix() - return tuple(mtx[:2].swapaxes(0, 1).flatten()) + mtx = self.get_matrix() + return tuple(mtx[:2].swapaxes(0, 1).flatten()) [EMAIL PROTECTED] def matrix_from_values(a, b, c, d, e, f): - affine = npy.zeros((3,3), npy.float_) - affine[0,] = a, c, e - affine[1,] = b, d, f - affine[2,2] = 1 - return affine + affine = npy.zeros((3, 3), npy.float_) + affine[0, ] = a, c, e + affine[1, ] = b, d, f + affine[2, 2] = 1 + return affine matrix_from_values = staticmethod(matrix_from_values) def get_matrix(self): - raise NotImplementedError() + raise NotImplementedError() def __call__(self, points): """ Applies the transformation to an array of 2D points and - returns the result. + returns the result. - points must be a numpy array of shape (N, 2), where N is the - number of points. - """ - # MGDTODO: The major speed trap here is just converting to - # the points to an array in the first place. If we can use - # more arrays upstream, that should help here. - mtx = self.get_matrix() - points = npy.asarray(points, npy.float_) - points = points.transpose() - points = npy.dot(mtx[0:2, 0:2], points) - points = points + mtx[0:2, 2:] - return points.transpose() + points must be a numpy array of shape (N, 2), where N is the + number of points. + """ + # MGDTODO: The major speed trap here is just converting to + # the points to an array in the first place. If we can use + # more arrays upstream, that should help here. + if not isinstance(points, npy.ndarray): + import traceback + print '-' * 60 + print 'A non-numpy array was passed in for transformation. Please ' + print 'correct this.' + print "".join(traceback.format_stack()) + print points + mtx = self.get_matrix() + points = npy.asarray(points, npy.float_) + points = points.transpose() + points = npy.dot(mtx[0:2, 0:2], points) + points = points + mtx[0:2, 2:] + return points.transpose() def inverted(self): - if self._inverted is None: - mtx = self.get_matrix() - self._inverted = Affine2D(inv(mtx)) - return self._inverted + if self._inverted is None: + mtx = self.get_matrix() + self._inverted = Affine2D(inv(mtx)) + return self._inverted def is_separable(self): - mtx = self.get_matrix() - return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + mtx = self.get_matrix() + return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 def is_affine(self): - return True + return True - + class Affine2D(Affine2DBase): input_dims = 2 output_dims = 2 @@ -310,29 +487,29 @@ b d f 0 0 1 """ - Affine2DBase.__init__(self) - if matrix is None: - matrix = npy.identity(3) - else: - assert matrix.shape == (3, 3) - self._mtx = matrix - self._inverted = None + Affine2DBase.__init__(self) + if matrix is None: + matrix = npy.identity(3) + else: + assert matrix.shape == (3, 3) + self._mtx = matrix + self._inverted = None def __repr__(self): - return "Affine2D(%s)" % repr(self._mtx) + return "Affine2D(%s)" % repr(self._mtx) __str__ = __repr__ def __cmp__(self, other): - if (isinstance(other, Affine2D) and - (self.get_matrix() == other.get_matrix()).all()): - return 0 - return -1 + if (isinstance(other, Affine2D) and + (self.get_matrix() == other.get_matrix()).all()): + return 0 + return -1 def __copy__(self): - return Affine2D(self._mtx.copy()) + return Affine2D(self._mtx.copy()) def __deepcopy__(self, memo): - return Affine2D(self._mtx.copy()) + return Affine2D(self._mtx.copy()) [EMAIL PROTECTED] def from_values(a, b, c, d, e, f): @@ -340,209 +517,224 @@ from_values = staticmethod(from_values) def get_matrix(self): - return self._mtx + return self._mtx + + def set_matrix(self, mtx): + self._mtx = mtx + self.invalidate() + + def set(self, other): + self._mtx = other.get_matrix() + self.invalidate() [EMAIL PROTECTED] - def concat(a, b): - return Affine2D(Affine2D._concat(a._mtx, b._mtx)) - concat = staticmethod(concat) - - [EMAIL PROTECTED] def identity(): return Affine2D(npy.identity(3)) identity = staticmethod(identity) + def clear(self): + self._mtx = npy.identity(3) + self.invalidate() + return self + def rotate(self, theta): a = npy.cos(theta) b = npy.sin(theta) rotate_mtx = self.matrix_from_values(a, b, -b, a, 0, 0) self._mtx = self._concat(self._mtx, rotate_mtx) - self.invalidate() - return self + self.invalidate() + return self def rotate_deg(self, degrees): return self.rotate(degrees*npy.pi/180.) + def rotate_around(self, x, y, theta): + return self.translate(-x, -y).rotate(theta).translate(x, y) + + def rotate_deg_around(self, x, y, degrees): + return self.translate(-x, -y).rotate_deg(degrees).translate(x, y) + def translate(self, tx, ty): translate_mtx = self.matrix_from_values(1., 0., 0., 1., tx, ty) self._mtx = self._concat(self._mtx, translate_mtx) - self.invalidate() - return self + self.invalidate() + return self def scale(self, sx, sy=None): - if sy is None: - sy = sx - scale_mtx = self.matrix_from_values(sx, 0., 0., sy, 0., 0.) + if sy is None: + sy = sx + scale_mtx = self.matrix_from_values(sx, 0., 0., sy, 0., 0.) self._mtx = self._concat(self._mtx, scale_mtx) - self.invalidate() - return self + self.invalidate() + return self def inverted(self): - if self._inverted is None: - mtx = self.get_matrix() - self._inverted = Affine2D(inv(mtx)) - return self._inverted + if self._inverted is None: + mtx = self.get_matrix() + self._inverted = Affine2D(inv(mtx)) + return self._inverted def is_separable(self): - mtx = self.get_matrix() - return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + mtx = self.get_matrix() + return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 def is_affine(self): - return True + return True -class BlendedAffine2D(Affine2DBase): +class BlendedTransform(Transform): def __init__(self, x_transform, y_transform): - assert x_transform.is_affine() - assert y_transform.is_affine() - assert x_transform.is_separable() - assert y_transform.is_separable() + assert x_transform.is_separable() + assert y_transform.is_separable() - Affine2DBase.__init__(self) - self.add_children([x_transform, y_transform]) - self._x = x_transform - self._y = y_transform - self._mtx = None + Transform.__init__(self) + self._x = x_transform + self._y = y_transform + self.set_children(['_x', '_y']) + def __call__(self, points): + if self._x == self._y: + return self._x(points) + + x_points = self._x(points) + y_points = self._y(points) + # This works because we already know the transforms are + # separable + return npy.hstack((x_points[:, 0:1], y_points[:, 1:2])) + +# def set_x_transform(self, x_transform): +# self.replace_child(0, x_transform) + +# def set_y_transform(self, y_transform): +# self.replace_child(1, y_transform) + +class BlendedAffine2D(Affine2DBase, BlendedTransform): + def __init__(self, x_transform, y_transform): + assert x_transform.is_affine() + assert y_transform.is_affine() + assert x_transform.is_separable() + assert y_transform.is_separable() + BlendedTransform.__init__(self, x_transform, y_transform) + + Affine2DBase.__init__(self) + self._mtx = None + def __repr__(self): - return "BlendedAffine2D(%s,%s)" % (self._x, self._y) + return "BlendedAffine2D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - + def _do_invalidation(self): - if self._mtx is not None: - self._mtx = None - Affine2DBase._do_invalidation(self) - return False - return True + if self._mtx is not None: + self._mtx = None + Affine2DBase._do_invalidation(self) + return False + return True - def _make__mtx(self): - if self._mtx is None: - x_mtx = self._x.get_matrix() - y_mtx = self._y.get_matrix() - # This works because we already know the transforms are - # separable, though normally one would want to set b and - # c to zero. - self._mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) - def is_separable(self): - return True + return True def get_matrix(self): - self._make__mtx() - return self._mtx + if self._mtx is None: + if self._x == self._y: + self._mtx = self._x.get_matrix() + else: + x_mtx = self._x.get_matrix() + y_mtx = self._y.get_matrix() + # This works because we already know the transforms are + # separable, though normally one would want to set b and + # c to zero. + self._mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) + return self._mtx + +class CompositeTransform(Transform): + def __init__(self, a, b): + assert a.output_dims == b.input_dims + self.input_dims = a.input_dims + self.output_dims = b.output_dims + + Transform.__init__(self) + self._a = a + self._b = b + self.set_children(['_a', '_b']) + + def __call__(self, points): + return self._b(self._a(points)) -class BlendedTransform(Transform): - def __init__(self, x_transform, y_transform): - assert x_transform.is_separable() - assert y_transform.is_separable() - - Transform.__init__(self) - self.add_children([x_transform, y_transform]) - self._x = x_transform - self._y = y_transform - - def __call__(self, points): - x_points = self._x(points) - y_points = self._y(points) - # This works because we already know the transforms are - # separable - return npy.hstack((x_points[:, 0:1], y_points[:, 1:2])) - class CompositeAffine2D(Affine2DBase): def __init__(self, a, b): - assert a.is_affine() - assert b.is_affine() + assert a.is_affine() + assert b.is_affine() - Affine2DBase.__init__(self) - self.add_children([a, b]) - self._a = a - self._b = b - self._mtx = None + Affine2DBase.__init__(self) + self._a = a + self._b = b + self.set_children(['_a', '_b']) + self._mtx = None def __repr__(self): - return "CompositeAffine2D(%s, %s)" % (self._a, self._b) + return "CompositeAffine2D(%s, %s)" % (self._a, self._b) __str__ = __repr__ def _do_invalidation(self): - self._mtx = None - Affine2DBase._do_invalidation(self) + self._mtx = None + Affine2DBase._do_invalidation(self) - def _make__mtx(self): - if self._mtx is None: - self._mtx = self._concat( - self._a.get_matrix(), - self._b.get_matrix()) - def get_matrix(self): - self._make__mtx() - return self._mtx - -class CompositeTransform(Transform): - def __init__(self, a, b): - assert a.output_dims == b.input_dims - self.input_dims = a.input_dims - self.output_dims = b.output_dims - - Transform.__init__(self) - self.add_children([a, b]) - self._a = a - self._b = b + if self._mtx is None: + self._mtx = self._concat( + self._a.get_matrix(), + self._b.get_matrix()) + return self._mtx - def __call__(self, points): - return self._b(self._a(points)) - class BboxTransform(Affine2DBase): def __init__(self, boxin, boxout): - assert isinstance(boxin, Bbox) - assert isinstance(boxout, Bbox) + assert isinstance(boxin, BboxBase) + assert isinstance(boxout, BboxBase) - Affine2DBase.__init__(self) - self.add_children([boxin, boxout]) - self._boxin = boxin - self._boxout = boxout - self._mtx = None - self._inverted = None + Affine2DBase.__init__(self) + self._boxin = boxin + self._boxout = boxout + self.set_children(['_boxin', '_boxout']) + self._mtx = None + self._inverted = None def __repr__(self): - return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) + return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) __str__ = __repr__ - + def _do_invalidation(self): - if self._mtx is not None: - self._mtx = None - Affine2DBase._do_invalidation(self) - return False - return True + if self._mtx is not None: + self._mtx = None + Affine2DBase._do_invalidation(self) + return False + return True - def _make__mtx(self): - if self._mtx is None: - boxin = self._boxin - boxout = self._boxout - x_scale = boxout.width / boxin.width - y_scale = boxout.height / boxin.height + def is_separable(self): + return True - # MGDTODO: Optimize - affine = Affine2D() \ - .translate(-boxin.xmin, -boxin.ymin) \ - .scale(x_scale, y_scale) \ - .translate(boxout.xmin, boxout.ymin) + def get_matrix(self): + if self._mtx is None: + boxin = self._boxin + boxout = self._boxout + x_scale = boxout.width / boxin.width + y_scale = boxout.height / boxin.height - self._mtx = affine._mtx - - def is_separable(self): - return True + # MGDTODO: Optimize + affine = Affine2D() \ + .translate(-boxin.xmin, -boxin.ymin) \ + .scale(x_scale, y_scale) \ + .translate(boxout.xmin, boxout.ymin) - def get_matrix(self): - self._make__mtx() - return self._mtx - + self._mtx = affine._mtx + return self._mtx + def blend_xy_sep_transform(x_transform, y_transform): if x_transform.is_affine() and y_transform.is_affine(): - return BlendedAffine2D(x_transform, y_transform) + return BlendedAffine2D(x_transform, y_transform) return BlendedTransform(x_transform, y_transform) def composite_transform_factory(a, b): if a.is_affine() and b.is_affine(): - return CompositeAffine2D(a, b) + return CompositeAffine2D(a, b) return CompositeTransform(a, b) # MGDTODO: There's probably a better place for this @@ -562,7 +754,7 @@ vmin, vmax = vmax, vmin swapped = True if vmax - vmin <= max(abs(vmin), abs(vmax)) * tiny: - if vmin==0.0: + if vmin == 0.0: vmin = -expander vmax = expander else: @@ -635,8 +827,8 @@ assert scale.to_values() == (10, 0, 0, 20, 0, 0) rotation = Affine2D().rotate_deg(30) print rotation.to_values() == (0.86602540378443871, 0.49999999999999994, - -0.49999999999999994, 0.86602540378443871, - 0.0, 0.0) + -0.49999999999999994, 0.86602540378443871, + 0.0, 0.0) points = npy.array([[1,2],[3,4],[5,6],[7,8]], npy.float_) translated_points = translation(points) Modified: branches/transforms/lib/matplotlib/artist.py =================================================================== --- branches/transforms/lib/matplotlib/artist.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/artist.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -21,7 +21,7 @@ # http://groups.google.com/groups?hl=en&lr=&threadm=mailman.5090.1098044946.5135.python-list%40python.org&rnum=1&prev=/groups%3Fq%3D__doc__%2Bauthor%253Ajdhunter%2540ace.bsd.uchicago.edu%26hl%3Den%26btnG%3DGoogle%2BSearch -class Artist: +class Artist(object): """ Abstract base class for someone who renders into a FigureCanvas """ Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -436,8 +436,7 @@ } def __str__(self): - return "Axes(%g,%g;%gx%g)"%(self._position[0].get(),self._position[1].get(), - self._position[2].get(),self._position[3].get()) + return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds) def __init__(self, fig, rect, axisbg = None, # defaults to rc axes.facecolor frameon = True, @@ -590,13 +589,13 @@ def follow_foreign_ylim(ax): ymin, ymax = axforeign.get_ylim() - # do not emit here or we'll get a ping png effect + # do not emit here or we'll get a ping pong effect self.set_ylim(ymin, ymax, emit=False) self.figure.canvas.draw_idle() def follow_self_ylim(ax): ymin, ymax = self.get_ylim() - # do not emit here or we'll get a ping png effect + # do not emit here or we'll get a ping pong effect axforeign.set_ylim(ymin, ymax, emit=False) axforeign.figure.canvas.draw_idle() @@ -613,65 +612,66 @@ """ martist.Artist.set_figure(self, fig) - l, b, w, h = self._position.bounds - xmin = fig.bbox.xmin - xmax = fig.bbox.xmax - ymin = fig.bbox.ymin - ymax = fig.bbox.ymax - figw = xmax-xmin - figh = ymax-ymin - self.left = l*figw - self.bottom = b*figh - self.right = (l+w)*figw - self.top = (b+h)*figh - - self.bbox = maffine.Bbox.from_lbrt( - self.left, self.bottom, - self.right, self.top, - ) + self.bbox = maffine.TransformedBbox(self._position, fig.transFigure) #these will be updated later as data is added self._set_lim_and_transforms() + def _shared_xlim_callback(self, ax): + xmin, xmax = ax.get_xlim() + self.set_xlim(xmin, xmax, emit=False) + self.figure.canvas.draw_idle() + + def _shared_ylim_callback(self, ax): + ymin, ymax = ax.get_ylim() + self.set_ylim(ymin, ymax, emit=False) + self.figure.canvas.draw_idle() + def _set_lim_and_transforms(self): """ set the dataLim and viewLim BBox attributes and the transData and transAxes Transformation attributes """ - Bbox = maffine.Bbox + Bbox = maffine.Bbox + self.viewLim = Bbox.unit() + if self._sharex is not None: - left = self._sharex.viewLim.xmin() - right = self._sharex.viewLim.xmax() - else: - left = 0.0 - right = 1.0 + # MGDTODO: This may be doing at least one too many updates + # than necessary + self._sharex.callbacks.connect( + 'xlim_changed', self._shared_xlim_callback) + self.viewLim.intervalx = self._sharex.viewLim.intervalx if self._sharey is not None: - bottom = self._sharey.viewLim.ymin() - top = self._sharey.viewLim.ymax() - else: - bottom = 0.0 - top = 1.0 + self._sharey.callbacks.connect( + 'ylim_changed', self._shared_ylim_callback) + self.viewLim.intervaly = self._sharex.viewLim.intervaly - self.viewLim = Bbox.from_lbrt(left, bottom, right, top) self.dataLim = Bbox.unit() - self.transData = maffine.BboxTransform( - self.viewLim, self.bbox) self.transAxes = maffine.BboxTransform( Bbox.unit(), self.bbox) - # MGDTODO -# if self._sharex: -# self.transData.set_funcx(self._sharex.transData.get_funcx()) - -# if self._sharey: -# self.transData.set_funcy(self._sharey.transData.get_funcy()) - + localTransData = maffine.BboxTransform( + self.viewLim, self.bbox) + if self._sharex: + transDataX = self._sharex.transData + else: + transDataX = localTransData + if self._sharey: + transDataY = self._sharey.transData + else: + transDataY = localTransData + self.transData = localTransData # maffine.blend_xy_sep_transform(transDataX, transDataY) + + def get_position(self, original=False): 'Return the axes rectangle left, bottom, width, height' + # MGDTODO: This changed from returning a list to returning a Bbox + # If you get any errors with the result of this function, please + # update the calling code if original: - return self._originalPosition.bounds + return copy.copy(self._originalPosition) else: - return self._position.bounds + return copy.copy(self._position) # return [val.get() for val in self._position] def set_position(self, pos, which='both'): @@ -690,14 +690,9 @@ ACCEPTS: len(4) sequence of floats """ if which in ('both', 'active'): - # MGDTODO -# # Change values within self._position--don't replace it. -# for num,val in zip(pos, self._position): -# val.set(num) - self._position.bounds = pos.bounds - # MGDTODO: side-effects + self._position.set(pos) if which in ('both', 'original'): - self._originalPosition.bounds = pos.bounds + self._originalPosition.set(pos) def _set_artist_props(self, a): @@ -1546,7 +1541,14 @@ xmin, xmax = maffine.nonsingular(xmin, xmax, increasing=False) self.viewLim.intervalx = (xmin, xmax) - + if emit: + self.callbacks.process('xlim_changed', self) + # MGDTODO: It would be nice to do this is in the above callback list, + # but it's difficult to tell how to initialize this at the + # right time + if self._sharex: + self._sharex.set_xlim(*self.viewLim.intervalx) + return xmin, xmax def get_xscale(self): @@ -1650,7 +1652,6 @@ ACCEPTS: len(2) sequence of floats """ - if ymax is None and iterable(ymin): ymin,ymax = ymin @@ -1671,8 +1672,14 @@ ymin, ymax = maffine.nonsingular(ymin, ymax, increasing=False) self.viewLim.intervaly = (ymin, ymax) - if emit: self.callbacks.process('ylim_changed', self) - + if emit: + self.callbacks.process('ylim_changed', self) + # MGDTODO: It would be nice to do this is in the above callback list, + # but it's difficult to tell how to initialize this at the + # right time + if self._sharey: + self._sharey.set_ylim(*self.viewLim.intervaly) + return ymin, ymax def get_yscale(self): Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -1028,9 +1028,8 @@ x,y = self.label.get_position() if self.label_position == 'bottom': if not len(bboxes): - bottom = self.axes.bbox.ymin() + bottom = self.axes.bbox.ymin else: - bbox = Bbox.union(bboxes) bottom = bbox.ymin @@ -1041,7 +1040,6 @@ if not len(bboxes2): top = self.axes.bbox.ymax else: - bbox = bbox_union(bboxes2) top = bbox.ymax @@ -1054,7 +1052,7 @@ """ x,y = self.offsetText.get_position() if not len(bboxes): - bottom = self.axes.bbox.ymin() + bottom = self.axes.bbox.ymin else: bbox = Bbox.union(bboxes) bottom = bbox.ymin Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -752,7 +752,8 @@ else: # Just found one hit self.inaxes = axes_list[0] - try: xdata, ydata = self.inaxes.transData.inverted()([[x, y]])[0] + try: + xdata, ydata = self.inaxes.transData.inverted().transform_point((x, y)) except ValueError: self.xdata = None self.ydata = None @@ -1584,8 +1585,8 @@ lims.append( (xmin, xmax, ymin, ymax) ) # Store both the original and modified positions pos.append( ( - tuple( a.get_position(True) ), - tuple( a.get_position() ) ) ) + a.get_position(True), + a.get_position() ) ) self._views.push(lims) self._positions.push(pos) self.set_history_buttons() Modified: branches/transforms/lib/matplotlib/figure.py =================================================================== --- branches/transforms/lib/matplotlib/figure.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/figure.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -18,7 +18,7 @@ from text import Text, _process_text_args from legend import Legend -from affine import Bbox, BboxTransform +from affine import Affine2D, Bbox, BboxTransform, TransformedBbox from ticker import FormatStrFormatter from cm import ScalarMappable from contour import ContourSet @@ -128,17 +128,15 @@ if facecolor is None: facecolor = rcParams['figure.facecolor'] if edgecolor is None: edgecolor = rcParams['figure.edgecolor'] + self._dpi_scale_trans = Affine2D() self.dpi = dpi - figwidth = figsize[0] * dpi - figheight = figsize[1] * dpi - self.bbox = Bbox.from_lbwh(0, 0, figwidth, figheight) + self.bbox_inches = Bbox.from_lbwh(0, 0, *figsize) + self.bbox = TransformedBbox(self.bbox_inches, self._dpi_scale_trans) self.frameon = frameon - self.transFigure = BboxTransform( Bbox.unit(), self.bbox) + self.transFigure = BboxTransform(Bbox.unit(), self.bbox) - - self.figurePatch = Rectangle( xy=(0,0), width=1, height=1, facecolor=facecolor, edgecolor=edgecolor, @@ -160,6 +158,15 @@ self._cachedRenderer = None + def _get_dpi(self): + return self._dpi + def _set_dpi(self, dpi): + print "setting dpi" + self._dpi = dpi + self._dpi_scale_trans.clear().scale(dpi, dpi) + print self._dpi_scale_trans + dpi = property(_get_dpi, _set_dpi) + def autofmt_xdate(self, bottom=0.2, rotation=30, ha='right'): """ A common use case is a number of subplots with shared xaxes @@ -325,7 +332,7 @@ w,h = args dpival = self.dpi - self.bbox.max = w * dpival, h * dpival + self.bbox_inches.max = w, h # self.figwidth.set(w) MGDTODO # self.figheight.set(h) @@ -339,7 +346,7 @@ manager.resize(int(canvasw), int(canvash)) def get_size_inches(self): - return self.bbox.max + return self.bbox_inches.max # return self.figwidth.get(), self.figheight.get() MGDTODO def get_edgecolor(self): @@ -352,12 +359,12 @@ def get_figwidth(self): 'Return the figwidth as a float' - return self.bbox.xmax + return self.bbox_inches.xmax # return self.figwidth.get() MGDTODO def get_figheight(self): 'Return the figheight as a float' - return self.bbox.ymax + return self.bbox_inches.ymax def get_dpi(self): 'Return the dpi as a float' @@ -400,7 +407,7 @@ ACCEPTS: float """ # self.figwidth.set(val) MGDTODO - self.bbox.xmax = val + self.bbox_inches.xmax = val def set_figheight(self, val): """ @@ -409,7 +416,7 @@ ACCEPTS: float """ # MGDTODO (set()) - self.bbox.ymax = val + self.bbox_inches.ymax = val def set_frameon(self, b): """ Modified: branches/transforms/lib/matplotlib/pyplot.py =================================================================== --- branches/transforms/lib/matplotlib/pyplot.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/pyplot.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -481,7 +481,7 @@ byebye = [] for other in fig.axes: if other==a: continue - if bbox.overlaps(other.bbox, ignoreend=True): + if bbox.fully_overlaps(other.bbox): byebye.append(other) for ax in byebye: delaxes(ax) Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/text.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -15,7 +15,7 @@ from cbook import enumerate, is_string_like, maxdict, is_numlike from font_manager import FontProperties from patches import bbox_artist, YAArrow -from affine import Bbox +from affine import Affine2D, Bbox from lines import Line2D import matplotlib.nxutils as nxutils @@ -213,30 +213,32 @@ M = self.get_rotation_matrix(xmin, ymin) # the corners of the unrotated bounding box - cornersHoriz = ( (xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin) ) - offsetLayout = [] + cornersHoriz = npy.array( + [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)], + npy.float_) + offsetLayout = npy.zeros((len(lines), 2)) # now offset the individual text lines within the box if len(lines)>1: # do the multiline aligment malign = self._get_multialignment() - for line, thisx, thisy, w, h in horizLayout: + for i, (line, thisx, thisy, w, h) in enumerate(horizLayout): if malign=='center': offsetx = width/2.0-w/2.0 elif malign=='right': offsetx = width-w else: offsetx = 0 thisx += offsetx - offsetLayout.append( (thisx, thisy )) + offsetLayout[i] = (thisx, thisy) else: # no additional layout needed - offsetLayout = [ (thisx, thisy) for line, thisx, thisy, w, h in horizLayout] + offsetLayout[0] = horizLayout[0][1:3] # now rotate the bbox - cornersRotated = [npy.dot(M,npy.array([[thisx],[thisy],[1]])) for thisx, thisy in cornersHoriz] + cornersRotated = M(cornersHoriz) - txs = [float(v[0][0]) for v in cornersRotated] - tys = [float(v[1][0]) for v in cornersRotated] + txs = cornersRotated[:, 0] + tys = cornersRotated[:, 1] # compute the bounds of the rotated box - xmin, xmax = min(txs), max(txs) - ymin, ymax = min(tys), max(tys) + xmin, xmax = txs.min(), txs.max() + ymin, ymax = tys.min(), tys.max() width = xmax - xmin height = ymax - ymin @@ -264,17 +266,18 @@ bbox = Bbox.from_lbwh(xmin, ymin, width, height) + # now rotate the positions around the first x,y position - xys = [npy.dot(M,npy.array([[thisx],[thisy],[1]])) for thisx, thisy in offsetLayout] + xys = M(offsetLayout) + tx = xys[:, 0] + ty = xys[:, 1] + tx += offsetx + ty += offsety - - tx = [float(v[0][0])+offsetx for v in xys] - ty = [float(v[1][0])+offsety for v in xys] - # now inverse transform back to data coords inverse_transform = self.get_transform().inverted() - xys = inverse_transform(zip(tx, ty)) + xys = inverse_transform(xys) xs, ys = zip(*xys) @@ -327,7 +330,7 @@ return for line, wh, x, y in info: - x, y = trans([[x, y]])[0] + x, y = trans.transform_point((x, y)) if renderer.flipy(): canvasw, canvash = renderer.get_canvas_width_height() @@ -435,29 +438,9 @@ bbox, info = self._get_layout(self._renderer) return bbox - - def get_rotation_matrix(self, x0, y0): + return Affine2D().rotate_deg_around(x0, y0, self.get_rotation()) - theta = npy.pi/180.0*self.get_rotation() - # translate x0,y0 to origin - Torigin = npy.array([ [1, 0, -x0], - [0, 1, -y0], - [0, 0, 1 ]]) - - # rotate by theta - R = npy.array([ [npy.cos(theta), -npy.sin(theta), 0], - [npy.sin(theta), npy.cos(theta), 0], - [0, 0, 1]]) - - # translate origin back to x0,y0 - Tback = npy.array([ [1, 0, x0], - [0, 1, y0], - [0, 0, 1 ]]) - - - return npy.dot(npy.dot(Tback,R), Torigin) - def set_backgroundcolor(self, color): """ Set the background color of the text by updating the bbox (see set_bbox for more info) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------- This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2005. http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/ _______________________________________________ Matplotlib-checkins mailing list Matplotlib-checkins@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins