From: <md...@us...> - 2008-06-05 13:45:29
|
Revision: 5394 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=5394&view=rev Author: mdboom Date: 2008-06-05 06:45:27 -0700 (Thu, 05 Jun 2008) Log Message: ----------- Add transformations/paths information to developer docs. Modified Paths: -------------- trunk/matplotlib/doc/conf.py trunk/matplotlib/doc/devel/index.rst trunk/matplotlib/lib/matplotlib/path.py trunk/matplotlib/lib/matplotlib/transforms.py Added Paths: ----------- trunk/matplotlib/doc/devel/transformations.rst Modified: trunk/matplotlib/doc/conf.py =================================================================== --- trunk/matplotlib/doc/conf.py 2008-06-05 11:57:31 UTC (rev 5393) +++ trunk/matplotlib/doc/conf.py 2008-06-05 13:45:27 UTC (rev 5394) @@ -159,3 +159,7 @@ latex_use_modindex = True latex_use_parts = True + +# Show both class-level docstring and __init__ docstring in class +# documentation +autoclass_content = 'both' Modified: trunk/matplotlib/doc/devel/index.rst =================================================================== --- trunk/matplotlib/doc/devel/index.rst 2008-06-05 11:57:31 UTC (rev 5393) +++ trunk/matplotlib/doc/devel/index.rst 2008-06-05 13:45:27 UTC (rev 5394) @@ -11,4 +11,6 @@ coding_guide.rst documenting_mpl.rst + transformations.rst add_new_projection.rst + Added: trunk/matplotlib/doc/devel/transformations.rst =================================================================== --- trunk/matplotlib/doc/devel/transformations.rst (rev 0) +++ trunk/matplotlib/doc/devel/transformations.rst 2008-06-05 13:45:27 UTC (rev 5394) @@ -0,0 +1,21 @@ +============================== + Working with transformations +============================== + +:mod:`matplotlib.transforms` +============================= + +.. automodule:: matplotlib.transforms + :members: TransformNode, BboxBase, Bbox, TransformedBbox, Transform, + TransformWrapper, AffineBase, Affine2DBase, Affine2D, IdentityTransform, + BlendedGenericTransform, BlendedAffine2D, blended_transform_factory, + CompositeGenericTransform, CompositeAffine2D, + composite_transform_factory, BboxTransform, BboxTransformTo, + BboxTransformFrom, ScaledTranslation, TransformedPath, nonsingular, + interval_contains, interval_contains_open + +:mod:`matplotlib.path` +============================= + +.. automodule:: matplotlib.path + :members: Path, get_path_collection_extents Modified: trunk/matplotlib/lib/matplotlib/path.py =================================================================== --- trunk/matplotlib/lib/matplotlib/path.py 2008-06-05 11:57:31 UTC (rev 5393) +++ trunk/matplotlib/lib/matplotlib/path.py 2008-06-05 13:45:27 UTC (rev 5394) @@ -1,7 +1,5 @@ """ Contains a class for managing paths (polylines). - -October 2007 Michael Droettboom """ import math @@ -21,42 +19,42 @@ closed, line and curve segments. The underlying storage is made up of two parallel numpy arrays: - vertices: an Nx2 float array of vertices - codes: an N-length uint8 array of vertex types + - vertices: an Nx2 float array of vertices + - codes: an N-length uint8 array of vertex types These two arrays always have the same length in the first - dimension. Therefore, to represent a cubic curve, you must + dimension. For example, to represent a cubic curve, you must provide three vertices as well as three codes "CURVE3". The code types are: - STOP : 1 vertex (ignored) - A marker for the end of the entire path (currently not - required and ignored) + - ``STOP`` : 1 vertex (ignored) + A marker for the end of the entire path (currently not + required and ignored) - MOVETO : 1 vertex - Pick up the pen and move to the given vertex. + - ``MOVETO`` : 1 vertex + Pick up the pen and move to the given vertex. - LINETO : 1 vertex - Draw a line from the current position to the given vertex. + - ``LINETO`` : 1 vertex + Draw a line from the current position to the given vertex. - CURVE3 : 1 control point, 1 endpoint + - ``CURVE3`` : 1 control point, 1 endpoint Draw a quadratic Bezier curve from the current position, with the given control point, to the given end point. - CURVE4 : 2 control points, 1 endpoint + - ``CURVE4`` : 2 control points, 1 endpoint Draw a cubic Bezier curve from the current position, with the given control points, to the given end point. - CLOSEPOLY : 1 vertex (ignored) + - ``CLOSEPOLY`` : 1 vertex (ignored) Draw a line segment to the start point of the current polyline. Users of Path objects should not access the vertices and codes - arrays directly. Instead, they should use iter_segments to get - the vertex/code pairs. This is important since many Paths do not - store a codes array at all, but have a default one provided for - them by iter_segments. + arrays directly. Instead, they should use :meth:`iter_segments` + to get the vertex/code pairs. This is important since many Paths, + as an optimization, do not store a codes array at all, but have a + default one provided for them by :meth:`iter_segments`. """ # Path codes @@ -81,9 +79,6 @@ codes is an N-length numpy array or Python sequence of type Path.code_type. - See the docstring of Path for a description of the various - codes. - These two arrays must have the same length in the first dimension. @@ -133,8 +128,8 @@ #@staticmethod def make_compound_path(*args): """ - Make a compound path from a list of Path objects. Only - polygons (not curves) are supported. + (staticmethod) Make a compound path from a list of Path + objects. Only polygons (not curves) are supported. """ for p in args: assert p.codes is None @@ -162,7 +157,10 @@ def iter_segments(self): """ - Iterates over all of the curve segments in the path. + Iterates over all of the curve segments in the path. Each + iteration returns a 2-tuple ``(vertices, code)``, where + vertices is a sequence of 1 - 3 coordinate pairs, and code is + one of the ``Path`` codes. """ vertices = self.vertices if not len(vertices): @@ -213,9 +211,9 @@ """ Return a transformed copy of the path. - See transforms.TransformedPath for a path that will cache the - transformed result and automatically update when the transform - changes. + See :class:`matplotlib.transforms.TransformedPath` for a path + that will cache the transformed result and automatically + update when the transform changes. """ return Path(transform.transform(self.vertices), self.codes) @@ -233,6 +231,9 @@ def contains_path(self, path, transform=None): """ Returns True if this path completely contains the given path. + + If transform is not None, the path will be transformed before + performing the test. """ if transform is not None: transform = transform.frozen() @@ -259,7 +260,8 @@ def intersects_bbox(self, bbox): """ - Returns True if this path intersects a given Bbox. + Returns True if this path intersects a given + :class:`~matplotlib.transforms.Bbox`. """ from transforms import BboxTransformTo rectangle = self.unit_rectangle().transformed( @@ -285,7 +287,9 @@ """ Convert this path to a list of polygons. Each polygon is an Nx2 array of vertices. In other words, each polygon has no - "move to" instructions or curves. + ``MOVETO`` instructions or curves. This is useful for + displaying in backends that do not support compound paths or + Bezier curves, such as GDK. """ if transform is not None: transform = transform.frozen() @@ -302,7 +306,8 @@ #@classmethod def unit_rectangle(cls): """ - Returns a Path of the unit rectangle from (0, 0) to (1, 1). + (staticmethod) Returns a :class:`Path` of the unit rectangle + from (0, 0) to (1, 1). """ if cls._unit_rectangle is None: cls._unit_rectangle = \ @@ -314,8 +319,9 @@ #@classmethod def unit_regular_polygon(cls, numVertices): """ - Returns a Path for a unit regular polygon with the given - numVertices and radius of 1.0, centered at (0, 0). + (staticmethod) Returns a :class:`Path` for a unit regular + polygon with the given numVertices and radius of 1.0, centered + at (0, 0). """ if numVertices <= 16: path = cls._unit_regular_polygons.get(numVertices) @@ -337,8 +343,9 @@ #@classmethod def unit_regular_star(cls, numVertices, innerCircle=0.5): """ - Returns a Path for a unit regular star with the given - numVertices and radius of 1.0, centered at (0, 0). + (staticmethod) Returns a :class:`Path` for a unit regular star + with the given numVertices and radius of 1.0, centered at (0, + 0). """ if numVertices <= 16: path = cls._unit_regular_stars.get((numVertices, innerCircle)) @@ -361,8 +368,9 @@ #@classmethod def unit_regular_asterisk(cls, numVertices): """ - Returns a Path for a unit regular asterisk with the given - numVertices and radius of 1.0, centered at (0, 0). + (staticmethod) Returns a :class:`Path` for a unit regular + asterisk with the given numVertices and radius of 1.0, + centered at (0, 0). """ return cls.unit_regular_star(numVertices, 0.0) unit_regular_asterisk = classmethod(unit_regular_asterisk) @@ -371,14 +379,13 @@ #@classmethod def unit_circle(cls): """ - Returns a Path of the unit circle. The circle is approximated - using cubic Bezier curves. This uses 8 splines around the - circle using the approach presented here: + (staticmethod) Returns a :class:`Path` of the unit circle. + The circle is approximated using cubic Bezier curves. This + uses 8 splines around the circle using the approach presented + here: - Lancaster, Don. Approximating a Circle or an Ellipse Using Four - Bezier Cubic Splines. - - http://www.tinaja.com/glib/ellipse4.pdf + Lancaster, Don. `Approximating a Circle or an Ellipse Using Four + Bezier Cubic Splines <http://www.tinaja.com/glib/ellipse4.pdf>`_. """ if cls._unit_circle is None: MAGIC = 0.2652031 @@ -434,18 +441,17 @@ #@classmethod def arc(cls, theta1, theta2, n=None, is_wedge=False): """ - Returns an arc on the unit circle from angle theta1 to angle - theta2 (in degrees). + (staticmethod) Returns an arc on the unit circle from angle + theta1 to angle theta2 (in degrees). If n is provided, it is the number of spline segments to make. - If n is not provided, the number of spline segments is determined - based on the delta between theta1 and theta2. + If n is not provided, the number of spline segments is + determined based on the delta between theta1 and theta2. + + Masionobe, L. 2003. `Drawing an elliptical arc using + polylines, quadratic or cubic Bezier curves + <http://www.spaceroots.org/documents/ellipse/index.html>`_. """ - # From Masionobe, L. 2003. "Drawing an elliptical arc using - # polylines, quadratic or cubic Bezier curves". - # - # http://www.spaceroots.org/documents/ellipse/index.html - # degrees to radians theta1 *= np.pi / 180.0 theta2 *= np.pi / 180.0 @@ -514,14 +520,18 @@ #@classmethod def wedge(cls, theta1, theta2, n=None): """ - Returns a wedge of the unit circle from angle theta1 to angle - theta2 (in degrees). + (staticmethod) Returns a wedge of the unit circle from angle + theta1 to angle theta2 (in degrees). """ return cls.arc(theta1, theta2, n, True) wedge = classmethod(wedge) _get_path_collection_extents = get_path_collection_extents def get_path_collection_extents(*args): + """ + Given a sequence of :class:`Path` objects, returns the bounding + box that encapsulates all of them. + """ from transforms import Bbox if len(args[1]) == 0: raise ValueError("No paths provided") Modified: trunk/matplotlib/lib/matplotlib/transforms.py =================================================================== --- trunk/matplotlib/lib/matplotlib/transforms.py 2008-06-05 11:57:31 UTC (rev 5393) +++ trunk/matplotlib/lib/matplotlib/transforms.py 2008-06-05 13:45:27 UTC (rev 5394) @@ -1,26 +1,32 @@ """ -This module contains a framework for arbitrary transformations. +matplotlib includes a framework for arbitrary geometric +transformations that is used determine the final position of all +elements drawn on the canvas. -Transforms are composed into a 'transform tree', made of transforms -whose value depends on other transforms (their children). When the -contents of children change, their parents are automatically updated -to reflect those changes. To do this an "invalidation" method is -used: when children change, all of their ancestors are marked as -"invalid". When the value of a transform is accessed at a later time, -its value is recomputed only if it is invalid, otherwise a cached -value may be used. This prevents unnecessary recomputations of -transforms, and contributes to better interactive performance. +Transforms are composed into trees of ``TransformNode`` objects whose +actual value depends on their children. When the contents of children +change, their parents are automatically invalidated. The next time an +invalidated transform is accessed, it is recomputed to reflect those +changes. This invalidation/caching approach prevents unnecessary +recomputations of transforms, and contributes to better interactive +performance. +For example, here is a graph of the transform tree used to plot data +to the graph: + +.. image:: ../_static/transforms.png + The framework can be used for both affine and non-affine transformations. However, for speed, we want use the backend renderers to perform affine transformations whenever possible. Therefore, it is possible to perform just the affine or non-affine part of a transformation on a set of data. The affine is always -assumed to occur after the non-affine. For any transform: +assumed to occur after the non-affine. For any transform:: - full transform == non-affine + affine + full transform == non-affine part + affine part -2007 Michael Droettboom +The backends are not expected to handle non-affine transformations +themselves. """ import numpy as np @@ -30,6 +36,7 @@ from weakref import WeakKeyDictionary import warnings +import sets import cbook from path import Path @@ -43,11 +50,11 @@ class TransformNode(object): """ - TransformNode is the base class for anything that participates in - the transform tree and needs to invalidate its parents or be - invalidated. It can include classes that are not technically - transforms, such as bounding boxes, since some transforms depend - on bounding boxes to compute their values. + :class:`TransformNode` is the base class for anything that + participates in the transform tree and needs to invalidate its + parents or be invalidated. This includes classes that are not + really transforms, such as bounding boxes, since some transforms + depend on bounding boxes to compute their values. """ _gid = 0 @@ -88,8 +95,8 @@ def invalidate(self): """ - Invalidate this transform node and all of its parents. Should - be called anytime the transform changes. + Invalidate this transform node and all of its ancestors. + Should be called any time the transform changes. """ # If we are an affine transform being changed, we can set the # flag to INVALID_AFFINE_ONLY @@ -116,8 +123,10 @@ def set_children(self, *children): """ - Set the children of the transform. Should be called from the - constructor of any transforms that depend on other transforms. + Set the children of the transform, to let the invalidation + system know which transforms can invalidate this transform. + Should be called from the constructor of any transforms that + depend on other transforms. """ for child in children: child._parents[self] = None @@ -127,6 +136,7 @@ def set_children(self, *children): self._set_children(*children) self._children = children + set_children.__doc__ = _set_children.__doc__ def frozen(self): """ @@ -151,7 +161,7 @@ fobj: A Python file-like object """ - seen = cbook.set() + seen = sets.Set() def recurse(root): if root in seen: @@ -163,10 +173,7 @@ label = '[%s]' % label if root in highlight: props['style'] = 'bold' - if root.is_affine: - props['shape'] = 'parallelogram' - if root.is_bbox: - props['shape'] = 'box' + props['shape'] = 'box' props['label'] = '"%s"' % label props = ' '.join(['%s=%s' % (key, val) for key, val in props.items()]) @@ -197,7 +204,13 @@ class BboxBase(TransformNode): """ This is the base class of all bounding boxes, and provides - read-only access to its data. + read-only access to its data. A mutable bounding box is provided + by the :class:`Bbox` class. + + The canonical representation is as two points, with no + restrictions on their ordering. Convenience properties are + provided to get the left, bottom, right and top edges and width + and height, but these are not stored explicity. """ is_bbox = True is_affine = True @@ -225,109 +238,165 @@ return self.get_points() def is_unit(self): + """ + Returns True if the Bbox is the unit bounding box from (0, 0) + to (1, 1). + """ return list(self.get_points().flatten()) == [0., 0., 1., 1.] def _get_x0(self): return self.get_points()[0, 0] - x0 = property(_get_x0) + x0 = property(_get_x0, None, None, """ + (property) :attr:`x0` is the first of the pair of *x* coordinates that + define the bounding box. :attr:`x0` is not guaranteed to be + less than :attr:`x1`. If you require that, use :attr:`xmin`.""") def _get_y0(self): return self.get_points()[0, 1] - y0 = property(_get_y0) + y0 = property(_get_y0, None, None, """ + (property) :attr:`y0` is the first of the pair of *y* coordinates that + define the bounding box. :attr:`y0` is not guaranteed to be + less than :attr:`y1`. If you require that, use :attr:`ymin`.""") def _get_x1(self): return self.get_points()[1, 0] - x1 = property(_get_x1) + x1 = property(_get_x1, None, None, """ + (property) :attr:`x1` is the second of the pair of *x* coordinates that + define the bounding box. :attr:`x1` is not guaranteed to be + greater than :attr:`x0`. If you require that, use :attr:`xmax`.""") def _get_y1(self): return self.get_points()[1, 1] - y1 = property(_get_y1) + y1 = property(_get_y1, None, None, """ + (property) :attr:`y1` is the second of the pair of *y* coordinates that + define the bounding box. :attr:`y1` is not guaranteed to be + greater than :attr:`y0`. If you require that, use :attr:`ymax`.""") def _get_p0(self): return self.get_points()[0] - p0 = property(_get_p0) + p0 = property(_get_p0, None, None, """ + (property) :attr:`p0` is the first pair of (*x*, *y*) coordinates that + define the bounding box. It is not guaranteed to be the bottom-left + corner. For that, use :attr:`min`.""") def _get_p1(self): return self.get_points()[1] - p1 = property(_get_p1) + p1 = property(_get_p1, None, None, """ + (property) :attr:`p1` is the second pair of (*x*, *y*) coordinates that + define the bounding box. It is not guaranteed to be the top-right + corner. For that, use :attr:`max`.""") def _get_xmin(self): return min(self.get_points()[:, 0]) - xmin = property(_get_xmin) + xmin = property(_get_xmin, None, None, """ + (property) :attr:`xmin` is the left edge of the bounding box.""") def _get_ymin(self): return min(self.get_points()[:, 1]) - ymin = property(_get_ymin) + ymin = property(_get_ymin, None, None, """ + (property) :attr:`ymin` is the bottom edge of the bounding box.""") def _get_xmax(self): return max(self.get_points()[:, 0]) - xmax = property(_get_xmax) + xmax = property(_get_xmax, None, None, """ + (property) :attr:`xmax` is the right edge of the bounding box.""") def _get_ymax(self): return max(self.get_points()[:, 1]) - ymax = property(_get_ymax) + ymax = property(_get_ymax, None, None, """ + (property) :attr:`ymax` is the top edge of the bounding box.""") def _get_min(self): return [min(self.get_points()[:, 0]), min(self.get_points()[:, 1])] - min = property(_get_min) + min = property(_get_min, None, None, """ + (property) :attr:`min` is the bottom-left corner of the bounding box.""") def _get_max(self): return [max(self.get_points()[:, 0]), max(self.get_points()[:, 1])] - max = property(_get_max) + max = property(_get_max, None, None, """ + (property) :attr:`max` is the top-right corner of the bounding box.""") def _get_intervalx(self): return self.get_points()[:, 0] - intervalx = property(_get_intervalx) + intervalx = property(_get_intervalx, None, None, """ + (property) :attr:`intervalx` is the pair of *x* coordinates that define the + bounding box. It is not guaranteed to be sorted from left to right.""") def _get_intervaly(self): return self.get_points()[:, 1] - intervaly = property(_get_intervaly) + intervaly = property(_get_intervaly, None, None, """ + (property) :attr:`intervaly` is the pair of *y* coordinates that define the + bounding box. It is not guaranteed to be sorted from bottom to top.""") def _get_width(self): points = self.get_points() return points[1, 0] - points[0, 0] - width = property(_get_width) + width = property(_get_width, None, None, """ + (property) The width of the bounding box. It may be negative if :attr:`x1` < + :attr:`x0`.""") def _get_height(self): points = self.get_points() return points[1, 1] - points[0, 1] - height = property(_get_height) + height = property(_get_height, None, None, """ + (property) The height of the bounding box. It may be negative if :attr:`y1` < + :attr:`y0`.""") def _get_size(self): points = self.get_points() return points[1] - points[0] - size = property(_get_size) + size = property(_get_size, None, None, """ + (property) The width and height of the bounding box. May be negative, in the same + way as :attr:`width` and :attr:`height`.""") def _get_bounds(self): x0, y0, x1, y1 = self.get_points().flatten() return (x0, y0, x1 - x0, y1 - y0) - bounds = property(_get_bounds) + bounds = property(_get_bounds, None, None, """ + (property) Returns (:attr:`x0`, :attr:`y0`, :attr:`width`, :attr:`height`).""") def _get_extents(self): return self.get_points().flatten().copy() - extents = property(_get_extents) + extents = property(_get_extents, None, None, """ + (property) Returns (:attr:`x0`, :attr:`y0`, :attr:`x1`, :attr:`y1`).""") def get_points(self): return NotImplementedError() def containsx(self, x): + """ + Returns True if x is between or equal to :attr:`x0` and + :attr:`x1`. + """ x0, x1 = self.intervalx return ((x0 < x1 and (x >= x0 and x <= x1)) or (x >= x1 and x <= x0)) def containsy(self, y): + """ + Returns True if y is between or equal to :attr:`y0` and + :attr:`y1`. + """ y0, y1 = self.intervaly return ((y0 < y1 and (y >= y0 and y <= y1)) or (y >= y1 and y <= y0)) def contains(self, x, y): + """ + Returns True if (x, y) is a coordinate inside the bounding + box or on its edge. + """ return self.containsx(x) and self.containsy(y) def overlaps(self, other): + """ + Returns True if this bounding box overlaps with the given + bounding box ``other``. + """ ax1, ay1, ax2, ay2 = self._get_extents() bx1, by1, bx2, by2 = other._get_extents() @@ -346,22 +415,37 @@ (by1 > ay2)) def fully_containsx(self, x): + """ + Returns True if x is between but not equal to :attr:`x0` and + :attr:`x1`. + """ x0, x1 = self.intervalx return ((x0 < x1 and (x > x0 and x < x1)) or (x > x1 and x < x0)) def fully_containsy(self, y): + """ + Returns True if y is between but not equal to :attr:`y0` and + :attr:`y1`. + """ y0, y1 = self.intervaly return ((y0 < y1 and (x > y0 and x < y1)) or (x > y1 and x < y0)) def fully_contains(self, x, y): + """ + Returns True if (x, y) is a coordinate inside the bounding + box, but not on its edge. + """ return self.fully_containsx(x) \ and self.fully_containsy(y) def fully_overlaps(self, other): + """ + Returns True if this bounding box overlaps with the given + bounding box ``other``, but not on its edge alone.""" ax1, ay1, ax2, ay2 = self._get_extents() bx1, by1, bx2, by2 = other._get_extents() @@ -381,14 +465,15 @@ def transformed(self, transform): """ - Return a new Bbox object, transformed by the given transform. + Return a new :class:`Bbox` object, statically transformed by + the given transform. """ return Bbox(transform.transform(self.get_points())) def inverse_transformed(self, transform): """ - Return a new Bbox object, transformed by the inverse of the - given transform. + Return a new :class:`Bbox` object, statically transformed by + the inverse of the given transform. """ return Bbox(transform.inverted().transform(self.get_points())) @@ -406,13 +491,20 @@ Return a copy of the Bbox, shifted to position c within a container. - c: may be either a) a sequence (cx, cy) where cx, cy range - from 0 to 1, where 0 is left or bottom and 1 is right or top; - or b) a string: C for centered, S for bottom-center, SE for - bottom-left, E for left, etc. + c: may be either: - Optional arg container is the box within which the BBox - is positioned; it defaults to the initial BBox. + * a sequence (cx, cy) where cx, cy range + from 0 to 1, where 0 is left or bottom and 1 is right or top + + * a string: + - C for centered + - S for bottom-center + - SE for bottom-left + - E for left + - etc. + + Optional argument ``container`` is the box within which the :class:`Bbox` + is positioned; it defaults to the initial :class:`Bbox`. """ if container is None: container = self @@ -428,10 +520,10 @@ def shrunk(self, mx, my): """ - Return a copy of the Bbox, shurnk by the factor mx in the x - direction and the factor my in the y direction. The lower - left corner of the box remains unchanged. Normally mx and my - will be <= 1, but this is not enforced. + Return a copy of the :class:`Bbox`, shurnk by the factor mx in + the *x* direction and the factor my in the *y* direction. The + lower left corner of the box remains unchanged. Normally mx + and my will be less than 1, but this is not enforced. """ w, h = self.size return Bbox([self._points[0], @@ -439,13 +531,13 @@ def shrunk_to_aspect(self, box_aspect, container = None, fig_aspect = 1.0): """ - Return a copy of the Bbox, shrunk so that it is as large as it - can be while having the desired aspect ratio, box_aspect. If - the box coordinates are relative--that is, fractions of a - larger box such as a figure--then the physical aspect ratio of - that figure is specified with fig_aspect, so that box_aspect - can also be given as a ratio of the absolute dimensions, not - the relative dimensions. + Return a copy of the :class:`Bbox`, shrunk so that it is as + large as it can be while having the desired aspect ratio, + ``box_aspect``. If the box coordinates are relative---that + is, fractions of a larger box such as a figure---then the + physical aspect ratio of that figure is specified with + ``fig_aspect``, so that ``box_aspect`` can also be given as a + ratio of the absolute dimensions, not the relative dimensions. """ assert box_aspect > 0 and fig_aspect > 0 if container is None: @@ -462,11 +554,11 @@ def splitx(self, *args): """ - e.g., bbox.splitx(f1, f2, ...) + e.g., ``bbox.splitx(f1, f2, ...)`` - Returns a list of new BBoxes formed by - splitting the original one with vertical lines - at fractional positions f1, f2, ... + Returns a list of new :class:`Bbox` objects formed by + splitting the original one with vertical lines at fractional + positions f1, f2, ... """ boxes = [] xf = [0] + list(args) + [1] @@ -478,11 +570,11 @@ def splity(self, *args): """ - e.g., bbox.splitx(f1, f2, ...) + e.g., ``bbox.splitx(f1, f2, ...)`` - Returns a list of new PBoxes formed by - splitting the original one with horizontal lines - at fractional positions f1, f2, ... + Returns a list of new :class:`Bbox` objects formed by + splitting the original one with horizontal lines at fractional + positions f1, f2, ... """ boxes = [] yf = [0] + list(args) + [1] @@ -513,14 +605,15 @@ """ Count the number of bounding boxes that overlap this one. - bboxes is a sequence of Bbox objects + bboxes is a sequence of :class:`BboxBase` objects """ return count_bboxes_overlapping_bbox(self, bboxes) def expanded(self, sw, sh): """ - Return a new Bbox which is this Bbox expanded around its - center by the given factors sw and sh. + Return a new :class:`Bbox` which is this :class:`Bbox` + expanded around its center by the given factors ``sw`` and + ``sh``. """ width = self.width height = self.height @@ -531,31 +624,34 @@ def padded(self, p): """ - Return a new Bbox that is padded on all four sides by the - given value. + Return a new :class:`Bbox` that is padded on all four sides by + the given value. """ points = self._points return Bbox(points + [[-p, -p], [p, p]]) def translated(self, tx, ty): """ - Return a copy of the Bbox, translated by tx and ty. + Return a copy of the :class:`Bbox`, statically translated by + tx and ty. """ return Bbox(self._points + (tx, ty)) def corners(self): """ Return an array of points which are the four corners of this - rectangle. + rectangle. For example, if this :class:`Bbox` is defined by + the points (a, b) and (c, d), ``corners`` returns (a, b), (a, + d), (c, b) and (c, d). """ l, b, r, t = self.get_points().flatten() return np.array([[l, b], [l, t], [r, b], [r, t]]) def rotated(self, radians): """ - Return a new bounding box that bounds a rotated version of this - bounding box. The new bounding box is still aligned with the - axes, of course. + Return a new bounding box that bounds a rotated version of + this bounding box by the given radians. The new bounding box + is still aligned with the axes, of course. """ corners = self.corners() corners_rotated = Affine2D().rotate(radians).transform(corners) @@ -566,7 +662,7 @@ #@staticmethod def union(bboxes): """ - Return a Bbox that contains all of the given bboxes. + Return a :class:`Bbox` that contains all of the given bboxes. """ assert(len(bboxes)) @@ -592,14 +688,17 @@ class Bbox(BboxBase): + """ + A mutable bounding box. + """ + def __init__(self, points): """ - Create a new bounding box. - points: a 2x2 numpy array of the form [[x0, y0], [x1, y1]] - If you need to create Bbox from another form of data, consider the - class methods unit, from_bounds and from_extents. + If you need to create a :class:`Bbox` object from another form + of data, consider the static methods unit, from_bounds and + from_extents. """ BboxBase.__init__(self) self._points = np.asarray(points, np.float_) @@ -620,7 +719,8 @@ #@staticmethod def unit(): """ - Create a new unit BBox from (0, 0) to (1, 1). + (staticmethod) Create a new unit :class:`Bbox` from (0, 0) to + (1, 1). """ return Bbox(Bbox._unit_values.copy()) unit = staticmethod(unit) @@ -628,7 +728,8 @@ #@staticmethod def from_bounds(x0, y0, width, height): """ - Create a new Bbox from x0, y0, width and height. + (staticmethod) Create a new :class:`Bbox` from x0, y0, width + and height. width and height may be negative. """ @@ -638,7 +739,8 @@ #@staticmethod def from_extents(*args): """ - Create a new Bbox from left, bottom, right and top. + (staticmethod) Create a new Bbox from left, bottom, right and + top. The y-axis increases upwards. """ @@ -653,26 +755,32 @@ def ignore(self, value): """ Set whether the existing bounds of the box should be ignored - by subsequent calls to update_from_data or - update_from_data_xy. + by subsequent calls to :meth:`update_from_data` or + :meth:`update_from_data_xy`. - value: When True, subsequent calls to update_from_data will - ignore the existing bounds of the Bbox. - When False, subsequent calls to update_from_data will - include the existing bounds of the Bbox. + value: + + - When True, subsequent calls to :meth:`update_from_data` + will ignore the existing bounds of the :class:`Bbox`. + + - When False, subsequent calls to :meth:`update_from_data` + will include the existing bounds of the :class:`Bbox`. """ self._ignore = value def update_from_data(self, x, y, ignore=None): """ - Update the bounds of the Bbox based on the passed in data. + Update the bounds of the :class:`Bbox` based on the passed in + data. x: a numpy array of x-values + y: a numpy array of y-values + ignore: - when True, ignore the existing bounds of the Bbox. - when False, include the existing bounds of the Bbox. - when None, use the last value passed to Bbox.ignore(). + - when True, ignore the existing bounds of the Bbox. + - when False, include the existing bounds of the Bbox. + - when None, use the last value passed to :meth:`ignore`. """ warnings.warn("update_from_data requires a memory copy -- please replace with update_from_data_xy") xy = np.hstack((x.reshape((len(x), 1)), y.reshape((len(y), 1)))) @@ -680,13 +788,15 @@ def update_from_data_xy(self, xy, ignore=None): """ - Update the bounds of the Bbox based on the passed in data. + Update the bounds of the :class:`Bbox` based on the passed in + data. xy: a numpy array of 2D points + ignore: - when True, ignore the existing bounds of the Bbox. - when False, include the existing bounds of the Bbox. - when None, use the last value passed to Bbox.ignore(). + - when True, ignore the existing bounds of the Bbox. + - when False, include the existing bounds of the Bbox. + - when None, use the last value passed to :meth:`ignore`. """ if ignore is None: ignore = self._ignore @@ -767,7 +877,7 @@ def get_points(self): """ - Set the points of the bounding box directly as a numpy array + Get the points of the bounding box directly as a numpy array of the form: [[x0, y0], [x1, y1]]. """ self._invalid = 0 @@ -794,13 +904,14 @@ class TransformedBbox(BboxBase): """ - A Bbox that is automatically transformed by a given Transform. When - either the child bbox or transform changes, the bounds of this bbox - will update accordingly. + A :class:`Bbox` that is automatically transformed by a given + transform. When either the child bounding box or transform + changes, the bounds of this bbox will update accordingly. """ def __init__(self, bbox, transform): """ bbox: a child bbox + transform: a 2D transform """ assert bbox.is_bbox @@ -827,6 +938,7 @@ self._points = points self._invalid = 0 return self._points + get_points.__doc__ = Bbox.get_points.__doc__ if DEBUG: _get_points = get_points @@ -840,24 +952,25 @@ The base class of all TransformNodes that actually perform a transformation. - All non-affine transformations should be subclass this class. New - affine transformations should subclass Affine2D. + All non-affine transformations should be subclasses of this class. + New affine transformations should be subclasses of + :class:`Affine2D`. Subclasses of this class should override the following members (at minimum): - input_dims - output_dims - transform - is_separable - has_inverse - inverted (if has_inverse will return True) + - :attr:`input_dims` + - :attr:`output_dims` + - :meth:`transform` + - :attr:`is_separable` + - :attr:`has_inverse` + - :meth:`inverted` (if :meth:`has_inverse` can return True) - If the transform needs to do something non-standard with Paths, - such as adding curves where there were once line segments, it - should override: + If the transform needs to do something non-standard with + :class:`mathplotlib.path.Path` objects, such as adding curves + where there were once line segments, it should override: - transform_path + - :meth:`transform_path` """ # The number of input and output dimensions for this transform. # These must be overridden (with integers) in the subclass. @@ -885,6 +998,9 @@ "Can not add Transform to object of type '%s'" % type(other)) def __radd__(self, other): + """ + Composes two transforms together such that self is followed by other. + """ if isinstance(other, Transform): return composite_transform_factory(other, self) raise TypeError( @@ -900,8 +1016,8 @@ """ Performs the transformation on the given array of values. - Accepts a numpy array of shape (N x self.input_dims) and - returns a numpy array of shape (N x self.output_dims). + Accepts a numpy array of shape (N x :attr:`input_dims`) and + returns a numpy array of shape (N x :attr:`output_dims`). """ raise NotImplementedError() @@ -910,15 +1026,15 @@ Performs only the affine part of this transformation on the given array of values. - transform(values) is equivalent to - transform_affine(transform_non_affine(values)). + ``transform(values)`` is always equivalent to + ``transform_affine(transform_non_affine(values))``. In non-affine transformations, this is generally a no-op. In affine transformations, this is equivalent to - transform(values). + ``transform(values)``. - Accepts a numpy array of shape (N x self.input_dims) and - returns a numpy array of shape (N x self.output_dims). + Accepts a numpy array of shape (N x :attr:`input_dims`) and + returns a numpy array of shape (N x :attr:`output_dims`). """ return values @@ -926,15 +1042,15 @@ """ Performs only the non-affine part of the transformation. - transform(values) is equivalent to - transform_affine(transform_non_affine(values)). + ``transform(values)`` is always equivalent to + ``transform_affine(transform_non_affine(values))``. In non-affine transformations, this is generally equivalent to - transform(values). In affine transformations, this is a - no-op. + ``transform(values)``. In affine transformations, this is + always a no-op. - Accepts a numpy array of shape (N x self.input_dims) and - returns a numpy array of shape (N x self.output_dims). + Accepts a numpy array of shape (N x :attr:`input_dims`) and + returns a numpy array of shape (N x :attr:`output_dims`). """ return self.transform(points) @@ -949,11 +1065,11 @@ A convenience function that returns the transformed copy of a single point. - The point is given as a sequence of length self.input_dims. + The point is given as a sequence of length :attr:`input_dims`. The transformed point is returned as a sequence of length - self.output_dims. + :attr:`output_dims`. """ - assert len(point) == 2 + assert len(point) == self.input_dims return self.transform(np.asarray([point]))[0] def transform_path(self, path): @@ -974,8 +1090,8 @@ path: a Path instance - transform_path(path) is equivalent to - transform_path_affine(transform_path_non_affine(values)). + ``transform_path(path)`` is equivalent to + ``transform_path_affine(transform_path_non_affine(values))``. """ return path @@ -986,8 +1102,8 @@ path: a Path instance - transform_path(path) is equivalent to - transform_path_affine(transform_path_non_affine(values)). + ``transform_path(path)`` is equivalent to + ``transform_path_affine(transform_path_non_affine(values))``. """ return Path(self.transform_non_affine(path.vertices), path.codes) @@ -999,7 +1115,7 @@ temporary. An update to 'self' does not cause a corresponding update to its inverted copy. - x === self.inverted().transform(self.transform(x)) + ``x === self.inverted().transform(self.transform(x))`` """ raise NotImplementedError() @@ -1013,17 +1129,17 @@ run time with a transform of a different type. This class allows that replacement to correctly trigger invalidation. - Note that TransformWrapper instances must have the same input and - output dimensions during their entire lifetime, so the child - transform may only be replaced with another child transform of the - same dimensions. + Note that :class:`TransformWrapper` instances must have the same + input and output dimensions during their entire lifetime, so the + child transform may only be replaced with another child transform + of the same dimensions. """ pass_through = True def __init__(self, child): """ child: A Transform instance. This child may later be replaced - with set(). + with :meth:`set`. """ assert isinstance(child, Transform) @@ -1133,13 +1249,14 @@ """ The base class of all 2D affine transformations. - 2D affine transformations are performed using a 3x3 numpy array: + 2D affine transformations are performed using a 3x3 numpy array:: a c e b d f 0 0 1 - Provides the read-only interface. + This class provides the read-only interface. For a mutable 2D + affine transformation, use :class:`Affine2D`. Subclasses of this class will generally only need to override a constructor and 'get_matrix' that generates a custom 3x3 matrix. @@ -1175,7 +1292,8 @@ #@staticmethod def matrix_from_values(a, b, c, d, e, f): """ - Create a new transformation matrix as a 3x3 numpy array of the form: + (staticmethod) Create a new transformation matrix as a 3x3 + numpy array of the form:: a c e b d f @@ -1194,6 +1312,7 @@ def transform_point(self, point): mtx = self.get_matrix() return affine_transform(point, mtx) + transform_point.__doc__ = AffineBase.transform_point.__doc__ if DEBUG: _transform = transform @@ -1223,9 +1342,13 @@ class Affine2D(Affine2DBase): + """ + A mutable 2D affine transformation. + """ + def __init__(self, matrix = None): """ - Initialize an Affine transform from a 3x3 numpy float array: + Initialize an Affine transform from a 3x3 numpy float array:: a c e b d f @@ -1255,7 +1378,8 @@ #@staticmethod def from_values(a, b, c, d, e, f): """ - Create a new Affine2D instance from the given values: + (staticmethod) Create a new Affine2D instance from the given + values:: a c e b d f @@ -1268,7 +1392,7 @@ def get_matrix(self): """ - Get the underlying transformation matrix as a 3x3 numpy array: + Get the underlying transformation matrix as a 3x3 numpy array:: a c e b d f @@ -1279,7 +1403,7 @@ def set_matrix(self, mtx): """ - Set the underlying transformation matrix from a 3x3 numpy array: + Set the underlying transformation matrix from a 3x3 numpy array:: a c e b d f @@ -1291,7 +1415,7 @@ def set(self, other): """ Set this transformation from the frozen copy of another - Affine2DBase instance. + :class:`Affine2DBase` object. """ assert isinstance(other, Affine2DBase) self._mtx = other.get_matrix() @@ -1300,10 +1424,11 @@ #@staticmethod def identity(): """ - Return a new Affine2D instance that is the identity transform. + (staticmethod) Return a new :class:`Affine2D` object that is + the identity transform. Unless this transform will be mutated later on, consider using - the faster IdentityTransform class instead. + the faster :class:`IdentityTransform` class instead. """ return Affine2D(np.identity(3)) identity = staticmethod(identity) @@ -1321,7 +1446,8 @@ Add a rotation (in radians) to this transform in place. Returns self, so this method can easily be chained with more - calls to rotate(), rotate_deg(), translate() and scale(). + calls to :meth:`rotate`, :meth:`rotate_deg, :meth:`translate` + and :meth:`scale`. """ a = np.cos(theta) b = np.sin(theta) @@ -1337,7 +1463,8 @@ Add a rotation (in degrees) to this transform in place. Returns self, so this method can easily be chained with more - calls to rotate(), rotate_deg(), translate() and scale(). + calls to :meth:`rotate`, :meth:`rotate_deg, :meth:`translate` + and :meth:`scale`. """ return self.rotate(degrees*np.pi/180.) @@ -1346,7 +1473,8 @@ Add a rotation (in radians) around the point (x, y) in place. Returns self, so this method can easily be chained with more - calls to rotate(), rotate_deg(), translate() and scale(). + calls to :meth:`rotate`, :meth:`rotate_deg, :meth:`translate` + and :meth:`scale`. """ return self.translate(-x, -y).rotate(theta).translate(x, y) @@ -1355,7 +1483,8 @@ Add a rotation (in degrees) around the point (x, y) in place. Returns self, so this method can easily be chained with more - calls to rotate(), rotate_deg(), translate() and scale(). + calls to :meth:`rotate`, :meth:`rotate_deg, :meth:`translate` + and :meth:`scale`. """ return self.translate(-x, -y).rotate_deg(degrees).translate(x, y) @@ -1364,7 +1493,8 @@ Adds a translation in place. Returns self, so this method can easily be chained with more - calls to rotate(), rotate_deg(), translate() and scale(). + calls to :meth:`rotate`, :meth:`rotate_deg, :meth:`translate` + and :meth:`scale`. """ translate_mtx = np.array( [[1.0, 0.0, tx], [0.0, 1.0, ty], [0.0, 0.0, 1.0]], @@ -1381,7 +1511,8 @@ y-directions. Returns self, so this method can easily be chained with more - calls to rotate(), rotate_deg(), translate() and scale(). + calls to :meth:`rotate`, :meth:`rotate_deg, :meth:`translate` + and :meth:`scale`. """ if sy is None: sy = sx @@ -1464,8 +1595,8 @@ transform the x-axis and y_transform to transform the y_axis. You will generally not call this constructor directly but use - the blended_transform_factory function instead, which can - determine automatically which kind of blended transform to + the :func:`blended_transform_factory` function instead, which + can determine automatically which kind of blended transform to create. """ # Here we ask: "Does it blend?" @@ -1565,8 +1696,8 @@ Both x_transform and y_transform must be 2D affine transforms. You will generally not call this constructor directly but use - the blended_transform_factory function instead, which can - determine automatically which kind of blended transform to + the :func:`blended_transform_factory` function instead, which + can determine automatically which kind of blended transform to create. """ assert x_transform.is_affine @@ -1608,8 +1739,8 @@ Create a new "blended" transform using x_transform to transform the x-axis and y_transform to transform the y_axis. - Shortcut versions of the blended transform are provided for the - case where both child transforms are affine. + A faster version of the blended transform is returned for the case + where both child transforms are affine. """ if (isinstance(x_transform, Affine2DBase) and isinstance(y_transform, Affine2DBase)): @@ -1631,9 +1762,9 @@ applying transform a then transform b. You will generally not call this constructor directly but use - the composite_transform_factory function instead, which can - automatically choose the best kind of composite transform - instance to create. + the :func:`composite_transform_factory` function instead, + which can automatically choose the best kind of composite + transform instance to create. """ assert a.output_dims == b.input_dims self.input_dims = a.input_dims @@ -1722,12 +1853,12 @@ Create a new composite transform that is the result of applying transform a then transform b. - Both a and b must be instances of Affine2DBase. + Both a and b must be instances of :class:`Affine2DBase`. You will generally not call this constructor directly but use - the composite_transform_factory function instead, which can - automatically choose the best kind of composite transform - instance to create. + the :func:`composite_transform_factory` function instead, + which can automatically choose the best kind of composite + transform instance to create. """ assert a.output_dims == b.input_dims self.input_dims = a.input_dims @@ -1765,7 +1896,8 @@ case where both child transforms are affine, or one or the other is the identity transform. - Composite TransformNodes may also be created using the '+' operator, e.g.: + Composite transforms may also be created using the '+' operator, + e.g.: c = a + b """ @@ -1823,15 +1955,15 @@ class BboxTransformTo(Affine2DBase): """ - BboxTransformSimple linearly transforms points from the unit Bbox - to another Bbox. + BboxTransformTo is a transformation that linearly transforms + points from the unit bounding box to a given :class:`Bbox`. """ is_separable = True def __init__(self, boxout): """ - Create a new BboxTransform that linearly transforms points - from the unit Bbox to boxout. + Create a new :class:`BboxTransformTo` that linearly transforms + points from the unit bounding box to boxout. """ assert boxout.is_bbox @@ -1862,16 +1994,12 @@ class BboxTransformFrom(Affine2DBase): """ - BboxTransform linearly transforms points from one Bbox to the unit - Bbox. + BboxTransform linearly transforms points from a given + :class:`Bbox` to the unit bounding box. """ is_separable = True def __init__(self, boxin): - """ - Create a new BboxTransform that linearly transforms points - from boxin to the unit Bbox. - """ assert boxin.is_bbox Affine2DBase.__init__(self) @@ -1902,6 +2030,10 @@ class ScaledTranslation(Affine2DBase): + """ + A transformation that translates by xt and yt, after xt and yt + have been transformaed by the given transform scale_trans. + """ def __init__(self, xt, yt, scale_trans): Affine2DBase.__init__(self) self._t = (xt, yt) @@ -1978,7 +2110,7 @@ Ensure the endpoints of a range are not too close together. "too close" means the interval is smaller than 'tiny' times - the maximum absolute value. + the maximum absolute value. If they are too close, each will be moved by the 'expander'. If 'increasing' is True and vmin > vmax, they will be swapped, This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |