{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n.. redirect-from:: /tutorials/advanced/path_tutorial\n\n\n# Path Tutorial\n\nDefining paths in your Matplotlib visualization.\n\nThe object underlying all of the :mod:`matplotlib.patches` objects is\nthe :class:`~matplotlib.path.Path`, which supports the standard set of\nmoveto, lineto, curveto commands to draw simple and compound outlines\nconsisting of line segments and splines. The ``Path`` is instantiated\nwith a (N, 2) array of (x, y) vertices, and an N-length array of path\ncodes. For example to draw the unit rectangle from (0, 0) to (1, 1), we\ncould use this code:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n\nimport matplotlib.pyplot as plt\n\nimport matplotlib.patches as patches\nfrom matplotlib.path import Path\n\nverts = [\n (0., 0.), # left, bottom\n (0., 1.), # left, top\n (1., 1.), # right, top\n (1., 0.), # right, bottom\n (0., 0.), # ignored\n]\n\ncodes = [\n Path.MOVETO,\n Path.LINETO,\n Path.LINETO,\n Path.LINETO,\n Path.CLOSEPOLY,\n]\n\npath = Path(verts, codes)\n\nfig, ax = plt.subplots()\npatch = patches.PathPatch(path, facecolor='orange', lw=2)\nax.add_patch(patch)\nax.set_xlim(-2, 2)\nax.set_ylim(-2, 2)\nplt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following path codes are recognized\n\n============= ======================== ======================================\nCode Vertices Description\n============= ======================== ======================================\n``STOP`` 1 (ignored) A marker for the end of the entire\n path (currently not required and\n ignored).\n``MOVETO`` 1 Pick up the pen and move to the given\n vertex.\n``LINETO`` 1 Draw a line from the current position\n to the given vertex.\n``CURVE3`` 2: Draw a quadratic B\u00e9zier curve from the\n 1 control point, current position, with the given\n 1 end point control point, to the given end point.\n``CURVE4`` 3: Draw a cubic B\u00e9zier curve from the\n 2 control points, current position, with the given\n 1 end point control points, to the given end\n point.\n``CLOSEPOLY`` 1 (the point is ignored) Draw a line segment to the start point\n of the current polyline.\n============= ======================== ======================================\n\n\n.. path-curves:\n\n\n## B\u00e9zier example\n\nSome of the path components require multiple vertices to specify them:\nfor example CURVE 3 is a [B\u00e9zier](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) curve with one\ncontrol point and one end point, and CURVE4 has three vertices for the\ntwo control points and the end point. The example below shows a\nCURVE4 B\u00e9zier spline -- the B\u00e9zier curve will be contained in the\nconvex hull of the start point, the two control points, and the end\npoint\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "verts = [\n (0., 0.), # P0\n (0.2, 1.), # P1\n (1., 0.8), # P2\n (0.8, 0.), # P3\n]\n\ncodes = [\n Path.MOVETO,\n Path.CURVE4,\n Path.CURVE4,\n Path.CURVE4,\n]\n\npath = Path(verts, codes)\n\nfig, ax = plt.subplots()\npatch = patches.PathPatch(path, facecolor='none', lw=2)\nax.add_patch(patch)\n\nxs, ys = zip(*verts)\nax.plot(xs, ys, 'x--', lw=2, color='black', ms=10)\n\nax.text(-0.05, -0.05, 'P0')\nax.text(0.15, 1.05, 'P1')\nax.text(1.05, 0.85, 'P2')\nax.text(0.85, -0.05, 'P3')\n\nax.set_xlim(-0.1, 1.1)\nax.set_ylim(-0.1, 1.1)\nplt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ".. compound_paths:\n\n## Compound paths\n\nAll of the simple patch primitives in matplotlib, Rectangle, Circle,\nPolygon, etc, are implemented with simple path. Plotting functions\nlike :meth:`~matplotlib.axes.Axes.hist` and\n:meth:`~matplotlib.axes.Axes.bar`, which create a number of\nprimitives, e.g., a bunch of Rectangles, can usually be implemented more\nefficiently using a compound path. The reason ``bar`` creates a list\nof rectangles and not a compound path is largely historical: the\n:class:`~matplotlib.path.Path` code is comparatively new and ``bar``\npredates it. While we could change it now, it would break old code,\nso here we will cover how to create compound paths, replacing the\nfunctionality in bar, in case you need to do so in your own code for\nefficiency reasons, e.g., you are creating an animated bar plot.\n\nWe will make the histogram chart by creating a series of rectangles\nfor each histogram bar: the rectangle width is the bin width and the\nrectangle height is the number of datapoints in that bin. First we'll\ncreate some random normally distributed data and compute the\nhistogram. Because NumPy returns the bin edges and not centers, the\nlength of ``bins`` is one greater than the length of ``n`` in the\nexample below::\n\n # histogram our data with numpy\n data = np.random.randn(1000)\n n, bins = np.histogram(data, 100)\n\nWe'll now extract the corners of the rectangles. Each of the\n``left``, ``bottom``, etc., arrays below is ``len(n)``, where ``n`` is\nthe array of counts for each histogram bar::\n\n # get the corners of the rectangles for the histogram\n left = np.array(bins[:-1])\n right = np.array(bins[1:])\n bottom = np.zeros(len(left))\n top = bottom + n\n\nNow we have to construct our compound path, which will consist of a\nseries of ``MOVETO``, ``LINETO`` and ``CLOSEPOLY`` for each rectangle.\nFor each rectangle, we need five vertices: one for the ``MOVETO``,\nthree for the ``LINETO``, and one for the ``CLOSEPOLY``. As indicated\nin the table above, the vertex for the closepoly is ignored, but we still\nneed it to keep the codes aligned with the vertices::\n\n nverts = nrects*(1+3+1)\n verts = np.zeros((nverts, 2))\n codes = np.ones(nverts, int) * path.Path.LINETO\n codes[0::5] = path.Path.MOVETO\n codes[4::5] = path.Path.CLOSEPOLY\n verts[0::5, 0] = left\n verts[0::5, 1] = bottom\n verts[1::5, 0] = left\n verts[1::5, 1] = top\n verts[2::5, 0] = right\n verts[2::5, 1] = top\n verts[3::5, 0] = right\n verts[3::5, 1] = bottom\n\nAll that remains is to create the path, attach it to a\n:class:`~matplotlib.patches.PathPatch`, and add it to our Axes::\n\n barpath = path.Path(verts, codes)\n patch = patches.PathPatch(barpath, facecolor='green',\n edgecolor='yellow', alpha=0.5)\n ax.add_patch(patch)\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots()\n# Fixing random state for reproducibility\nnp.random.seed(19680801)\n\n# histogram our data with numpy\ndata = np.random.randn(1000)\nn, bins = np.histogram(data, 100)\n\n# get the corners of the rectangles for the histogram\nleft = np.array(bins[:-1])\nright = np.array(bins[1:])\nbottom = np.zeros(len(left))\ntop = bottom + n\nnrects = len(left)\n\nnverts = nrects*(1+3+1)\nverts = np.zeros((nverts, 2))\ncodes = np.full(nverts, Path.LINETO, dtype=int)\ncodes[0::5] = Path.MOVETO\ncodes[4::5] = Path.CLOSEPOLY\nverts[0::5, 0] = left\nverts[0::5, 1] = bottom\nverts[1::5, 0] = left\nverts[1::5, 1] = top\nverts[2::5, 0] = right\nverts[2::5, 1] = top\nverts[3::5, 0] = right\nverts[3::5, 1] = bottom\n\nbarpath = Path(verts, codes)\npatch = patches.PathPatch(barpath, facecolor='green',\n edgecolor='yellow', alpha=0.5)\nax.add_patch(patch)\n\nax.set_xlim(left[0], right[-1])\nax.set_ylim(bottom.min(), top.max())\n\nplt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.2" } }, "nbformat": 4, "nbformat_minor": 0 }