|
| 1 | +.. _path_tutorial: |
| 2 | + |
| 3 | +************* |
| 4 | +Path Tutorial |
| 5 | +************* |
| 6 | + |
| 7 | +The object underlying all of the :mod:`matplotlib.patch` objects is |
| 8 | +the :class:`~matplotlib.path.Path`, which supports the standard set of |
| 9 | +moveto, lineto, curveto commands to draw simple and compoud outlines |
| 10 | +consisting of line segments and splines. The ``Path`` is instantiated |
| 11 | +with a (N,2) array of (x,y) vertices, and a N length array of path |
| 12 | +codes. For example to draw the unit rectangle from (0,0) to (1,1), we |
| 13 | +could use this code |
| 14 | + |
| 15 | +.. plot:: |
| 16 | + :include-source: |
| 17 | + |
| 18 | + import matplotlib.pyplot as plt |
| 19 | + from matplotlib.path import Path |
| 20 | + import matplotlib.patches as patches |
| 21 | + |
| 22 | + verts = [ |
| 23 | + (0., 0.), # left, bottom |
| 24 | + (0., 1.), # left, top |
| 25 | + (1., 1.), # right, top |
| 26 | + (1., 0.), # right, bottom |
| 27 | + (0., 0.), # ignored |
| 28 | + ] |
| 29 | + |
| 30 | + codes = [Path.MOVETO, |
| 31 | + Path.LINETO, |
| 32 | + Path.LINETO, |
| 33 | + Path.LINETO, |
| 34 | + Path.CLOSEPOLY, |
| 35 | + ] |
| 36 | + |
| 37 | + path = Path(verts, codes) |
| 38 | + |
| 39 | + fig = plt.figure() |
| 40 | + ax = fig.add_subplot(111) |
| 41 | + patch = patches.PathPatch(path, facecolor='orange', lw=2) |
| 42 | + ax.add_patch(patch) |
| 43 | + ax.set_xlim(-2,2) |
| 44 | + ax.set_ylim(-2,2) |
| 45 | + plt.show() |
| 46 | + |
| 47 | + |
| 48 | +The following path codes are recognized |
| 49 | + |
| 50 | +============== ================================= ==================================================================================================================== |
| 51 | +Code Vertices Description |
| 52 | +============== ================================= ==================================================================================================================== |
| 53 | +``STOP`` 1 (ignored) A marker for the end of the entire path (currently not required and ignored) |
| 54 | +``MOVETO`` 1 Pick up the pen and move to the given vertex. |
| 55 | +``LINETO`` 1 Draw a line from the current position to the given vertex. |
| 56 | +``CURVE3`` 2 (1 control point, 1 endpoint) Draw a quadratic Bezier curve from the current position, with the given control point, to the given end point. |
| 57 | +``CURVE4`` 3 (2 control points, 1 endpoint) Draw a cubic Bezier curve from the current position, with the given control points, to the given end point. |
| 58 | +``CLOSEPOLY`` 1 (ignored) Draw a line segment to the start point of the current polyline. |
| 59 | +============== ================================= ==================================================================================================================== |
| 60 | + |
| 61 | + |
| 62 | +.. path-curves: |
| 63 | +
|
| 64 | +
|
| 65 | +Bezier example |
| 66 | +============== |
| 67 | + |
| 68 | +Some of the path components require multiple vertices to specify them: |
| 69 | +for example CURVE 3 is a `bezier |
| 70 | +<http://en.wikipedia.org/wiki/B%C3%A9zier_curve>`_ curve with one |
| 71 | +control point and one end point, and CURVE4 has three vertices for the |
| 72 | +two control points and the end point. The example below shows a |
| 73 | +CURVE4 Bezier spline -- the bezier curve will be contained in the |
| 74 | +convex hul of the start point, the two control points, and the end |
| 75 | +point |
| 76 | + |
| 77 | +.. plot:: |
| 78 | + :include-source: |
| 79 | + |
| 80 | + import matplotlib.pyplot as plt |
| 81 | + from matplotlib.path import Path |
| 82 | + import matplotlib.patches as patches |
| 83 | + |
| 84 | + verts = [ |
| 85 | + (0., 0.), # P0 |
| 86 | + (0.2, 1.), # P1 |
| 87 | + (1., 0.8), # P2 |
| 88 | + (0.8, 0.), # P3 |
| 89 | + ] |
| 90 | + |
| 91 | + codes = [Path.MOVETO, |
| 92 | + Path.CURVE4, |
| 93 | + Path.CURVE4, |
| 94 | + Path.CURVE4, |
| 95 | + ] |
| 96 | + |
| 97 | + path = Path(verts, codes) |
| 98 | + |
| 99 | + fig = plt.figure() |
| 100 | + ax = fig.add_subplot(111) |
| 101 | + patch = patches.PathPatch(path, facecolor='none', lw=2) |
| 102 | + ax.add_patch(patch) |
| 103 | + |
| 104 | + xs, ys = zip(*verts) |
| 105 | + ax.plot(xs, ys, 'x--', lw=2, color='black', ms=10) |
| 106 | +
|
| 107 | + ax.text(-0.05, -0.05, 'P0') |
| 108 | + ax.text(0.15, 1.05, 'P1') |
| 109 | + ax.text(1.05, 0.85, 'P2') |
| 110 | + ax.text(0.85, -0.05, 'P3') |
| 111 | + |
| 112 | + ax.set_xlim(-0.1, 1.1) |
| 113 | + ax.set_ylim(-0.1, 1.1) |
| 114 | + plt.show() |
| 115 | + |
| 116 | +.. compound_paths: |
| 117 | +
|
| 118 | +Compound paths |
| 119 | +============== |
| 120 | + |
| 121 | +All of the simple patch primitives in matplotlib, Rectangle, Circle, |
| 122 | +Polygon, etc, are implemented with simple path. Plotting functions |
| 123 | +like :meth:`~matplotlib.axes.Axes.hist` and |
| 124 | +:meth:`~matplotlib.axes.Axes.bar`, which create a number of |
| 125 | +primitives, eg a bunch of Rectangles, can usually be implemented more |
| 126 | +efficiently using a compund path. The reason ``bar`` creates a list |
| 127 | +of rectangles and not a compound path is largely historical: the |
| 128 | +:class:`~matplotlib.path.Path` code is comparatively new and ``bar`` |
| 129 | +predates it. While we could change it now, it would break old code, |
| 130 | +so here we will cover how to create compound paths, replacing the |
| 131 | +functionality in bar, in case you need to do so in your own code for |
| 132 | +efficiency reasons, eg you are creating an animated bar plot. |
| 133 | + |
| 134 | +We will make the histogram chart by creating a series of rectangles |
| 135 | +for each histogram bar: the rectangle width is the bin width and the |
| 136 | +rectangle height is the number of datapoints in that bin. First we'll |
| 137 | +create some random normally distributed data and compute the |
| 138 | +histogram. Because numpy returns the bin edges and not centers, the |
| 139 | +length of ``bins`` is 1 greater than the length of ``n`` in the |
| 140 | +example below:: |
| 141 | + |
| 142 | + # histogram our data with numpy |
| 143 | + data = np.random.randn(1000) |
| 144 | + n, bins = np.histogram(data, 100) |
| 145 | + |
| 146 | +We'll now extract the corners of the rectangles. Each of the |
| 147 | +``left``, ``bottom``, etc, arrays below is ``len(n)``, where ``n`` is |
| 148 | +the array of counts for each histogram bar:: |
| 149 | + |
| 150 | + # get the corners of the rectangles for the histogram |
| 151 | + left = np.array(bins[:-1]) |
| 152 | + right = np.array(bins[1:]) |
| 153 | + bottom = np.zeros(len(left)) |
| 154 | + top = bottom + n |
| 155 | + |
| 156 | +Now we have to construct our compound path, which will consist of a |
| 157 | +series of ``MOVETO``, ``LINETO`` and ``CLOSEPOLY`` for each rectangle. |
| 158 | +For each rectangle, we need 5 vertices: 1 for the ``MOVETO``, 3 for |
| 159 | +the ``LINETO``, and 1 for the ``CLOSEPOLY``. As indicated in the |
| 160 | +table above, the vertex for the closepoly is ignored but we still need |
| 161 | +it to keep the codes aligned with the vertices:: |
| 162 | + |
| 163 | + nverts = nrects*(1+3+1) |
| 164 | + verts = np.zeros((nverts, 2)) |
| 165 | + codes = np.ones(nverts, int) * path.Path.LINETO |
| 166 | + codes[0::5] = path.Path.MOVETO |
| 167 | + codes[4::5] = path.Path.CLOSEPOLY |
| 168 | + verts[0::5,0] = left |
| 169 | + verts[0::5,1] = bottom |
| 170 | + verts[1::5,0] = left |
| 171 | + verts[1::5,1] = top |
| 172 | + verts[2::5,0] = right |
| 173 | + verts[2::5,1] = top |
| 174 | + verts[3::5,0] = right |
| 175 | + verts[3::5,1] = bottom |
| 176 | + |
| 177 | +All that remains is to create the path, attach it to a |
| 178 | +:class:`~matplotlib.patch.PathPatch`, and ad it to our axes:: |
| 179 | + |
| 180 | + barpath = path.Path(verts, codes) |
| 181 | + patch = patches.PathPatch(barpath, facecolor='green', |
| 182 | + edgecolor='yellow', alpha=0.5) |
| 183 | + ax.add_patch(patch) |
| 184 | + |
| 185 | +Here is the result |
| 186 | + |
| 187 | +.. plot:: pyplots/compound_path_demo.py |
0 commit comments