{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n.. redirect-from:: /gallery/userdemo/anchored_box04\n.. redirect-from:: /gallery/userdemo/annotate_explain\n.. redirect-from:: /gallery/userdemo/annotate_simple01\n.. redirect-from:: /gallery/userdemo/annotate_simple02\n.. redirect-from:: /gallery/userdemo/annotate_simple03\n.. redirect-from:: /gallery/userdemo/annotate_simple04\n.. redirect-from:: /gallery/userdemo/annotate_simple_coord01\n.. redirect-from:: /gallery/userdemo/annotate_simple_coord02\n.. redirect-from:: /gallery/userdemo/annotate_simple_coord03\n.. redirect-from:: /gallery/userdemo/annotate_text_arrow\n.. redirect-from:: /gallery/userdemo/connect_simple01\n.. redirect-from:: /gallery/userdemo/connectionstyle_demo\n.. redirect-from:: /tutorials/text/annotations\n\n\n# Annotations\n\nAnnotations are graphical elements, often pieces of text, that explain, add\ncontext to, or otherwise highlight some portion of the visualized data.\n`~.Axes.annotate` supports a number of coordinate systems for flexibly\npositioning data and annotations relative to each other and a variety of\noptions of for styling the text. Axes.annotate also provides an optional arrow\nfrom the text to the data and this arrow can be styled in various ways.\n`~.Axes.text` can also be used for simple text annotation, but does not\nprovide as much flexibility in positioning and styling as `~.Axes.annotate`.\n :depth: 3\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n## Basic annotation\n\nIn an annotation, there are two points to consider: the location of the data\nbeing annotated *xy* and the location of the annotation text *xytext*. Both\nof these arguments are ``(x, y)`` tuples:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\nimport numpy as np\n\nfig, ax = plt.subplots(figsize=(3, 3))\n\nt = np.arange(0.0, 5.0, 0.01)\ns = np.cos(2*np.pi*t)\nline, = ax.plot(t, s, lw=2)\n\nax.annotate('local max', xy=(2, 1), xytext=(3, 1.5),\n arrowprops=dict(facecolor='black', shrink=0.05))\nax.set_ylim(-2, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, both the *xy* (arrow tip) and *xytext* locations\n(text location) are in data coordinates. There are a variety of other\ncoordinate systems one can choose -- you can specify the coordinate\nsystem of *xy* and *xytext* with one of the following strings for\n*xycoords* and *textcoords* (default is 'data')\n\n================== ========================================================\nargument coordinate system\n================== ========================================================\n'figure points' points from the lower left corner of the figure\n'figure pixels' pixels from the lower left corner of the figure\n'figure fraction' (0, 0) is lower left of figure and (1, 1) is upper right\n'axes points' points from lower left corner of the Axes\n'axes pixels' pixels from lower left corner of the Axes\n'axes fraction' (0, 0) is lower left of Axes and (1, 1) is upper right\n'data' use the axes data coordinate system\n================== ========================================================\n\nThe following strings are also valid arguments for *textcoords*\n\n================== ========================================================\nargument coordinate system\n================== ========================================================\n'offset points' offset (in points) from the xy value\n'offset pixels' offset (in pixels) from the xy value\n================== ========================================================\n\nFor physical coordinate systems (points or pixels) the origin is the\nbottom-left of the figure or Axes. Points are\n[typographic points](https://en.wikipedia.org/wiki/Point_(typography))\nmeaning that they are a physical unit measuring 1/72 of an inch. Points and\npixels are discussed in further detail in `transforms-fig-scale-dpi`.\n\n\n### Annotating data\n\nThis example places the text coordinates in fractional axes coordinates:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(3, 3))\n\nt = np.arange(0.0, 5.0, 0.01)\ns = np.cos(2*np.pi*t)\nline, = ax.plot(t, s, lw=2)\n\nax.annotate('local max', xy=(2, 1), xycoords='data',\n xytext=(0.01, .99), textcoords='axes fraction',\n va='top', ha='left',\n arrowprops=dict(facecolor='black', shrink=0.05))\nax.set_ylim(-2, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Annotating an Artist\n\nAnnotations can be positioned relative to an `.Artist` instance by passing\nthat Artist in as *xycoords*. Then *xy* is interpreted as a fraction of the\nArtist's bounding box.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import matplotlib.patches as mpatches\n\nfig, ax = plt.subplots(figsize=(3, 3))\narr = mpatches.FancyArrowPatch((1.25, 1.5), (1.75, 1.5),\n arrowstyle='->,head_width=.15', mutation_scale=20)\nax.add_patch(arr)\nax.annotate(\"label\", (.5, .5), xycoords=arr, ha='center', va='bottom')\nax.set(xlim=(1, 2), ylim=(1, 2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here the annotation is placed at position (.5,.5) relative to the arrow's\nlower left corner and is vertically and horizontally at that position.\nVertically, the bottom aligns to that reference point so that the label\nis above the line. For an example of chaining annotation Artists, see the\n`Artist section ` of\n`annotating_coordinate_systems`.\n\n\n\n### Annotating with arrows\n\nYou can enable drawing of an arrow from the text to the annotated point\nby giving a dictionary of arrow properties in the optional keyword\nargument *arrowprops*.\n\n==================== =====================================================\n*arrowprops* key description\n==================== =====================================================\nwidth the width of the arrow in points\nfrac the fraction of the arrow length occupied by the head\nheadwidth the width of the base of the arrow head in points\nshrink move the tip and base some percent away from\n the annotated point and text\n\n\\*\\*kwargs any key for :class:`matplotlib.patches.Polygon`,\n e.g., ``facecolor``\n==================== =====================================================\n\nIn the example below, the *xy* point is in the data coordinate system\nsince *xycoords* defaults to 'data'. For a polar Axes, this is in\n(theta, radius) space. The text in this example is placed in the\nfractional figure coordinate system. :class:`matplotlib.text.Text`\nkeyword arguments like *horizontalalignment*, *verticalalignment* and\n*fontsize* are passed from `~matplotlib.axes.Axes.annotate` to the\n``Text`` instance.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure()\nax = fig.add_subplot(projection='polar')\nr = np.arange(0, 1, 0.001)\ntheta = 2 * 2*np.pi * r\nline, = ax.plot(theta, r, color='#ee8d18', lw=3)\n\nind = 800\nthisr, thistheta = r[ind], theta[ind]\nax.plot([thistheta], [thisr], 'o')\nax.annotate('a polar annotation',\n xy=(thistheta, thisr), # theta, radius\n xytext=(0.05, 0.05), # fraction, fraction\n textcoords='figure fraction',\n arrowprops=dict(facecolor='black', shrink=0.05),\n horizontalalignment='left',\n verticalalignment='bottom')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For more on plotting with arrows, see `annotation_with_custom_arrow`\n\n\n### Placing text annotations relative to data\n\nAnnotations can be positioned at a relative offset to the *xy* input to\nannotation by setting the *textcoords* keyword argument to ``'offset points'``\nor ``'offset pixels'``.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(3, 3))\nx = [1, 3, 5, 7, 9]\ny = [2, 4, 6, 8, 10]\nannotations = [\"A\", \"B\", \"C\", \"D\", \"E\"]\nax.scatter(x, y, s=20)\n\nfor xi, yi, text in zip(x, y, annotations):\n ax.annotate(text,\n xy=(xi, yi), xycoords='data',\n xytext=(1.5, 1.5), textcoords='offset points')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The annotations are offset 1.5 points (1.5*1/72 inches) from the *xy* values.\n\n\n## Advanced annotation\n\nWe recommend reading `annotations-tutorial`, :func:`~matplotlib.pyplot.text`\nand :func:`~matplotlib.pyplot.annotate` before reading this section.\n\n### Annotating with boxed text\n\n`~.Axes.text` takes a *bbox* keyword argument, which draws a box around the\ntext:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(5, 5))\nt = ax.text(0.5, 0.5, \"Direction\",\n ha=\"center\", va=\"center\", rotation=45, size=15,\n bbox=dict(boxstyle=\"rarrow,pad=0.3\",\n fc=\"lightblue\", ec=\"steelblue\", lw=2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The arguments are the name of the box style with its attributes as\nkeyword arguments. Currently, following box styles are implemented:\n\n========== ============== ==========================\nClass Name Attrs\n========== ============== ==========================\nCircle ``circle`` pad=0.3\nDArrow ``darrow`` pad=0.3\nEllipse ``ellipse`` pad=0.3\nLArrow ``larrow`` pad=0.3\nRArrow ``rarrow`` pad=0.3\nRound ``round`` pad=0.3,rounding_size=None\nRound4 ``round4`` pad=0.3,rounding_size=None\nRoundtooth ``roundtooth`` pad=0.3,tooth_size=None\nSawtooth ``sawtooth`` pad=0.3,tooth_size=None\nSquare ``square`` pad=0.3\n========== ============== ==========================\n\n.. figure:: /gallery/shapes_and_collections/images/sphx_glr_fancybox_demo_001.png\n :target: /gallery/shapes_and_collections/fancybox_demo.html\n :align: center\n\nThe patch object (box) associated with the text can be accessed using::\n\n bb = t.get_bbox_patch()\n\nThe return value is a `.FancyBboxPatch`; patch properties\n(facecolor, edgewidth, etc.) can be accessed and modified as usual.\n`.FancyBboxPatch.set_boxstyle` sets the box shape::\n\n bb.set_boxstyle(\"rarrow\", pad=0.6)\n\nThe attribute arguments can also be specified within the style\nname with separating comma::\n\n bb.set_boxstyle(\"rarrow, pad=0.6\")\n\n\n### Defining custom box styles\n\nCustom box styles can be implemented as a function that takes arguments specifying\nboth a rectangular box and the amount of \"mutation\", and returns the \"mutated\" path.\nThe specific signature is the one of ``custom_box_style`` below.\n\nHere, we return a new path which adds an \"arrow\" shape on the left of the box.\n\nThe custom box style can then be used by passing\n``bbox=dict(boxstyle=custom_box_style, ...)`` to `.Axes.text`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from matplotlib.path import Path\n\n\ndef custom_box_style(x0, y0, width, height, mutation_size):\n \"\"\"\n Given the location and size of the box, return the path of the box around it.\n\n Rotation is automatically taken care of.\n\n Parameters\n ----------\n x0, y0, width, height : float\n Box location and size.\n mutation_size : float\n Mutation reference scale, typically the text font size.\n \"\"\"\n # padding\n mypad = 0.3\n pad = mutation_size * mypad\n # width and height with padding added.\n width = width + 2 * pad\n height = height + 2 * pad\n # boundary of the padded box\n x0, y0 = x0 - pad, y0 - pad\n x1, y1 = x0 + width, y0 + height\n # return the new path\n return Path([(x0, y0), (x1, y0), (x1, y1), (x0, y1),\n (x0-pad, (y0+y1)/2), (x0, y0), (x0, y0)],\n closed=True)\n\nfig, ax = plt.subplots(figsize=(3, 3))\nax.text(0.5, 0.5, \"Test\", size=30, va=\"center\", ha=\"center\", rotation=30,\n bbox=dict(boxstyle=custom_box_style, alpha=0.2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Likewise, custom box styles can be implemented as classes that implement\n``__call__``.\n\nThe classes can then be registered into the ``BoxStyle._style_list`` dict,\nwhich allows specifying the box style as a string,\n``bbox=dict(boxstyle=\"registered_name,param=value,...\", ...)``.\nNote that this registration relies on internal APIs and is therefore not\nofficially supported.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from matplotlib.patches import BoxStyle\n\n\nclass MyStyle:\n \"\"\"A simple box.\"\"\"\n\n def __init__(self, pad=0.3):\n \"\"\"\n The arguments must be floats and have default values.\n\n Parameters\n ----------\n pad : float\n amount of padding\n \"\"\"\n self.pad = pad\n super().__init__()\n\n def __call__(self, x0, y0, width, height, mutation_size):\n \"\"\"\n Given the location and size of the box, return the path of the box around it.\n\n Rotation is automatically taken care of.\n\n Parameters\n ----------\n x0, y0, width, height : float\n Box location and size.\n mutation_size : float\n Reference scale for the mutation, typically the text font size.\n \"\"\"\n # padding\n pad = mutation_size * self.pad\n # width and height with padding added\n width = width + 2 * pad\n height = height + 2 * pad\n # boundary of the padded box\n x0, y0 = x0 - pad, y0 - pad\n x1, y1 = x0 + width, y0 + height\n # return the new path\n return Path([(x0, y0), (x1, y0), (x1, y1), (x0, y1),\n (x0-pad, (y0+y1)/2), (x0, y0), (x0, y0)],\n closed=True)\n\n\nBoxStyle._style_list[\"angled\"] = MyStyle # Register the custom style.\n\nfig, ax = plt.subplots(figsize=(3, 3))\nax.text(0.5, 0.5, \"Test\", size=30, va=\"center\", ha=\"center\", rotation=30,\n bbox=dict(boxstyle=\"angled,pad=0.5\", alpha=0.2))\n\ndel BoxStyle._style_list[\"angled\"] # Unregister it." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly, you can define a custom `.ConnectionStyle` and a custom `.ArrowStyle`. View\nthe source code at `.patches` to learn how each class is defined.\n\n\n### Customizing annotation arrows\n\nAn arrow connecting *xy* to *xytext* can be optionally drawn by\nspecifying the *arrowprops* argument. To draw only an arrow, use\nempty string as the first argument:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(3, 3))\nax.annotate(\"\",\n xy=(0.2, 0.2), xycoords='data',\n xytext=(0.8, 0.8), textcoords='data',\n arrowprops=dict(arrowstyle=\"->\", connectionstyle=\"arc3\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The arrow is drawn as follows:\n\n1. A path connecting the two points is created, as specified by the\n *connectionstyle* parameter.\n2. The path is clipped to avoid patches *patchA* and *patchB*, if these are\n set.\n3. The path is further shrunk by *shrinkA* and *shrinkB* (in pixels).\n4. The path is transmuted to an arrow patch, as specified by the *arrowstyle*\n parameter.\n\n.. plot::\n :show-source-link: False\n\n import matplotlib.patches as mpatches\n\n x1, y1 = 0.3, 0.3\n x2, y2 = 0.7, 0.7\n arrowprops = {\n \"1. connect with connectionstyle\":\n dict(arrowstyle=\"-\", patchB=False, shrinkB=0),\n \"2. clip against patchB\": dict(arrowstyle=\"-\", patchB=True, shrinkB=0),\n \"3. shrink by shrinkB\": dict(arrowstyle=\"-\", patchB=True, shrinkB=5),\n \"4. mutate with arrowstyle\": dict(arrowstyle=\"fancy\", patchB=True, shrinkB=5),\n }\n\n fig, axs = plt.subplots(2, 2, figsize=(6, 6), layout='compressed')\n for ax, (name, props) in zip(axs.flat, arrowprops.items()):\n ax.plot([x1, x2], [y1, y2], \".\")\n\n el = mpatches.Ellipse((x1, y1), 0.3, 0.4, angle=30, alpha=0.2)\n ax.add_artist(el)\n\n props[\"patchB\"] = el if props[\"patchB\"] else None\n\n ax.annotate(\n \"\",\n xy=(x1, y1), xycoords='data',\n xytext=(x2, y2), textcoords='data',\n arrowprops={\"color\": \"0.5\", \"connectionstyle\": \"arc3,rad=0.3\", **props})\n ax.text(.05, .95, name, transform=ax.transAxes, ha=\"left\", va=\"top\")\n\n ax.set(xlim=(0, 1), ylim=(0, 1), xticks=[], yticks=[], aspect=1)\n\n fig.get_layout_engine().set(wspace=0, hspace=0, w_pad=0, h_pad=0)\n\nThe creation of the connecting path between two points is controlled by\n``connectionstyle`` key and the following styles are available:\n\n========== =============================================\nName Attrs\n========== =============================================\n``angle`` angleA=90,angleB=0,rad=0.0\n``angle3`` angleA=90,angleB=0\n``arc`` angleA=0,angleB=0,armA=None,armB=None,rad=0.0\n``arc3`` rad=0.0\n``bar`` armA=0.0,armB=0.0,fraction=0.3,angle=None\n========== =============================================\n\nNote that \"3\" in ``angle3`` and ``arc3`` is meant to indicate that the\nresulting path is a quadratic spline segment (three control\npoints). As will be discussed below, some arrow style options can only\nbe used when the connecting path is a quadratic spline.\n\nThe behavior of each connection style is (limitedly) demonstrated in the\nexample below. (Warning: The behavior of the ``bar`` style is currently not\nwell-defined and may be changed in the future).\n\n.. plot::\n :caption: Connection styles for annotations\n\n def demo_con_style(ax, connectionstyle):\n x1, y1 = 0.3, 0.2\n x2, y2 = 0.8, 0.6\n\n ax.plot([x1, x2], [y1, y2], \".\")\n ax.annotate(\"\",\n xy=(x1, y1), xycoords='data',\n xytext=(x2, y2), textcoords='data',\n arrowprops=dict(arrowstyle=\"->\", color=\"0.5\",\n shrinkA=5, shrinkB=5,\n patchA=None, patchB=None,\n connectionstyle=connectionstyle,\n ),\n )\n\n ax.text(.05, .95, connectionstyle.replace(\",\", \",\\n\"),\n transform=ax.transAxes, ha=\"left\", va=\"top\")\n\n ax.set(xlim=(0, 1), ylim=(0, 1.25), xticks=[], yticks=[], aspect=1.25)\n\n fig, axs = plt.subplots(3, 5, figsize=(7, 6.3), layout=\"compressed\")\n demo_con_style(axs[0, 0], \"angle3,angleA=90,angleB=0\")\n demo_con_style(axs[1, 0], \"angle3,angleA=0,angleB=90\")\n demo_con_style(axs[0, 1], \"arc3,rad=0.\")\n demo_con_style(axs[1, 1], \"arc3,rad=0.3\")\n demo_con_style(axs[2, 1], \"arc3,rad=-0.3\")\n demo_con_style(axs[0, 2], \"angle,angleA=-90,angleB=180,rad=0\")\n demo_con_style(axs[1, 2], \"angle,angleA=-90,angleB=180,rad=5\")\n demo_con_style(axs[2, 2], \"angle,angleA=-90,angleB=10,rad=5\")\n demo_con_style(axs[0, 3], \"arc,angleA=-90,angleB=0,armA=30,armB=30,rad=0\")\n demo_con_style(axs[1, 3], \"arc,angleA=-90,angleB=0,armA=30,armB=30,rad=5\")\n demo_con_style(axs[2, 3], \"arc,angleA=-90,angleB=0,armA=0,armB=40,rad=0\")\n demo_con_style(axs[0, 4], \"bar,fraction=0.3\")\n demo_con_style(axs[1, 4], \"bar,fraction=-0.3\")\n demo_con_style(axs[2, 4], \"bar,angle=180,fraction=-0.2\")\n\n axs[2, 0].remove()\n fig.get_layout_engine().set(wspace=0, hspace=0, w_pad=0, h_pad=0)\n\nThe connecting path (after clipping and shrinking) is then mutated to\nan arrow patch, according to the given ``arrowstyle``:\n\n========== =============================================\nName Attrs\n========== =============================================\n``-`` None\n``->`` head_length=0.4,head_width=0.2\n``-[`` widthB=1.0,lengthB=0.2,angleB=None\n``|-|`` widthA=1.0,widthB=1.0\n``-|>`` head_length=0.4,head_width=0.2\n``<-`` head_length=0.4,head_width=0.2\n``<->`` head_length=0.4,head_width=0.2\n``<|-`` head_length=0.4,head_width=0.2\n``<|-|>`` head_length=0.4,head_width=0.2\n``fancy`` head_length=0.4,head_width=0.4,tail_width=0.4\n``simple`` head_length=0.5,head_width=0.5,tail_width=0.2\n``wedge`` tail_width=0.3,shrink_factor=0.5\n========== =============================================\n\n.. figure:: /gallery/text_labels_and_annotations/images/sphx_glr_fancyarrow_demo_001.png\n :target: /gallery/text_labels_and_annotations/fancyarrow_demo.html\n :align: center\n\nSome arrowstyles only work with connection styles that generate a\nquadratic-spline segment. They are ``fancy``, ``simple``, and ``wedge``.\nFor these arrow styles, you must use the \"angle3\" or \"arc3\" connection\nstyle.\n\nIf the annotation string is given, the patch is set to the bbox patch\nof the text by default.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(3, 3))\n\nax.annotate(\"Test\",\n xy=(0.2, 0.2), xycoords='data',\n xytext=(0.8, 0.8), textcoords='data',\n size=20, va=\"center\", ha=\"center\",\n arrowprops=dict(arrowstyle=\"simple\",\n connectionstyle=\"arc3,rad=-0.2\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As with `~.Axes.text`, a box around the text can be drawn using the *bbox*\nargument.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(3, 3))\n\nann = ax.annotate(\"Test\",\n xy=(0.2, 0.2), xycoords='data',\n xytext=(0.8, 0.8), textcoords='data',\n size=20, va=\"center\", ha=\"center\",\n bbox=dict(boxstyle=\"round4\", fc=\"w\"),\n arrowprops=dict(arrowstyle=\"-|>\",\n connectionstyle=\"arc3,rad=-0.2\",\n fc=\"w\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default, the starting point is set to the center of the text\nextent. This can be adjusted with ``relpos`` key value. The values\nare normalized to the extent of the text. For example, (0, 0) means\nlower-left corner and (1, 1) means top-right.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(3, 3))\n\nann = ax.annotate(\"Test\",\n xy=(0.2, 0.2), xycoords='data',\n xytext=(0.8, 0.8), textcoords='data',\n size=20, va=\"center\", ha=\"center\",\n bbox=dict(boxstyle=\"round4\", fc=\"w\"),\n arrowprops=dict(arrowstyle=\"-|>\",\n connectionstyle=\"arc3,rad=0.2\",\n relpos=(0., 0.),\n fc=\"w\"))\n\nann = ax.annotate(\"Test\",\n xy=(0.2, 0.2), xycoords='data',\n xytext=(0.8, 0.8), textcoords='data',\n size=20, va=\"center\", ha=\"center\",\n bbox=dict(boxstyle=\"round4\", fc=\"w\"),\n arrowprops=dict(arrowstyle=\"-|>\",\n connectionstyle=\"arc3,rad=-0.2\",\n relpos=(1., 0.),\n fc=\"w\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Placing Artist at anchored Axes locations\n\nThere are classes of artists that can be placed at an anchored\nlocation in the Axes. A common example is the legend. This type\nof artist can be created by using the `.OffsetBox` class. A few\npredefined classes are available in :mod:`matplotlib.offsetbox` and in\n:mod:`mpl_toolkits.axes_grid1.anchored_artists`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from matplotlib.offsetbox import AnchoredText\n\nfig, ax = plt.subplots(figsize=(3, 3))\nat = AnchoredText(\"Figure 1a\",\n prop=dict(size=15), frameon=True, loc='upper left')\nat.patch.set_boxstyle(\"round,pad=0.,rounding_size=0.2\")\nax.add_artist(at)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The *loc* keyword has same meaning as in the legend command.\n\nA simple application is when the size of the artist (or collection of\nartists) is known in pixel size during the time of creation. For\nexample, If you want to draw a circle with fixed size of 20 pixel x 20\npixel (radius = 10 pixel), you can utilize\n`~mpl_toolkits.axes_grid1.anchored_artists.AnchoredDrawingArea`. The instance\nis created with a size of the drawing area (in pixels), and arbitrary artists\ncan be added to the drawing area. Note that the extents of the artists that are\nadded to the drawing area are not related to the placement of the drawing\narea itself. Only the initial size matters.\n\nThe artists that are added to the drawing area should not have a\ntransform set (it will be overridden) and the dimensions of those\nartists are interpreted as a pixel coordinate, i.e., the radius of the\ncircles in above example are 10 pixels and 5 pixels, respectively.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from matplotlib.patches import Circle\nfrom mpl_toolkits.axes_grid1.anchored_artists import AnchoredDrawingArea\n\nfig, ax = plt.subplots(figsize=(3, 3))\nada = AnchoredDrawingArea(40, 20, 0, 0,\n loc='upper right', pad=0., frameon=False)\np1 = Circle((10, 10), 10)\nada.drawing_area.add_artist(p1)\np2 = Circle((30, 10), 5, fc=\"r\")\nada.drawing_area.add_artist(p2)\nax.add_artist(ada)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sometimes, you want your artists to scale with the data coordinate (or\ncoordinates other than canvas pixels). You can use\n`~mpl_toolkits.axes_grid1.anchored_artists.AnchoredAuxTransformBox` class.\nThis is similar to\n`~mpl_toolkits.axes_grid1.anchored_artists.AnchoredDrawingArea` except that\nthe extent of the artist is determined during the drawing time respecting the\nspecified transform.\n\nThe ellipse in the example below will have width and height\ncorresponding to 0.1 and 0.4 in data coordinates and will be\nautomatically scaled when the view limits of the Axes change.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from matplotlib.patches import Ellipse\nfrom mpl_toolkits.axes_grid1.anchored_artists import AnchoredAuxTransformBox\n\nfig, ax = plt.subplots(figsize=(3, 3))\nbox = AnchoredAuxTransformBox(ax.transData, loc='upper left')\nel = Ellipse((0, 0), width=0.1, height=0.4, angle=30) # in data coordinates!\nbox.drawing_area.add_artist(el)\nax.add_artist(box)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another method of anchoring an artist relative to a parent Axes or anchor\npoint is via the *bbox_to_anchor* argument of `.AnchoredOffsetbox`. This\nartist can then be automatically positioned relative to another artist using\n`.HPacker` and `.VPacker`:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from matplotlib.offsetbox import (AnchoredOffsetbox, DrawingArea, HPacker,\n TextArea)\n\nfig, ax = plt.subplots(figsize=(3, 3))\n\nbox1 = TextArea(\" Test: \", textprops=dict(color=\"k\"))\nbox2 = DrawingArea(60, 20, 0, 0)\n\nel1 = Ellipse((10, 10), width=16, height=5, angle=30, fc=\"r\")\nel2 = Ellipse((30, 10), width=16, height=5, angle=170, fc=\"g\")\nel3 = Ellipse((50, 10), width=16, height=5, angle=230, fc=\"b\")\nbox2.add_artist(el1)\nbox2.add_artist(el2)\nbox2.add_artist(el3)\n\nbox = HPacker(children=[box1, box2],\n align=\"center\",\n pad=0, sep=5)\n\nanchored_box = AnchoredOffsetbox(loc='lower left',\n child=box, pad=0.,\n frameon=True,\n bbox_to_anchor=(0., 1.02),\n bbox_transform=ax.transAxes,\n borderpad=0.,)\n\nax.add_artist(anchored_box)\nfig.subplots_adjust(top=0.8)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that, unlike in `.Legend`, the ``bbox_transform`` is set to\n`.IdentityTransform` by default\n\n\n## Coordinate systems for annotations\n\nMatplotlib Annotations support several types of coordinate systems. The\nexamples in `annotations-tutorial` used the ``data`` coordinate system;\nSome others more advanced options are:\n\n### `.Transform` instance\n\nTransforms map coordinates into different coordinate systems, usually the\ndisplay coordinate system. See `transforms_tutorial` for a detailed\nexplanation. Here Transform objects are used to identify the coordinate\nsystem of the corresponding points. For example, the ``Axes.transAxes``\ntransform positions the annotation relative to the Axes coordinates; therefore\nusing it is identical to setting the coordinate system to \"axes fraction\":\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(6, 3))\nax1.annotate(\"Test\", xy=(0.2, 0.2), xycoords=ax1.transAxes)\nax2.annotate(\"Test\", xy=(0.2, 0.2), xycoords=\"axes fraction\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another commonly used `.Transform` instance is ``Axes.transData``. This\ntransform is the coordinate system of the data plotted in the Axes. In this\nexample, it is used to draw an arrow between related data points in two\nAxes. We have passed an empty text because in this case, the annotation\nconnects data points.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "x = np.linspace(-1, 1)\n\nfig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(6, 3))\nax1.plot(x, -x**3)\nax2.plot(x, -3*x**2)\nax2.annotate(\"\",\n xy=(0, 0), xycoords=ax1.transData,\n xytext=(0, 0), textcoords=ax2.transData,\n arrowprops=dict(arrowstyle=\"<->\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n### `.Artist` instance\n\nThe *xy* value (or *xytext*) is interpreted as a fractional coordinate of the\nbounding box (bbox) of the artist:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(3, 3))\nan1 = ax.annotate(\"Test 1\",\n xy=(0.5, 0.5), xycoords=\"data\",\n va=\"center\", ha=\"center\",\n bbox=dict(boxstyle=\"round\", fc=\"w\"))\n\nan2 = ax.annotate(\"Test 2\",\n xy=(1, 0.5), xycoords=an1, # (1, 0.5) of an1's bbox\n xytext=(30, 0), textcoords=\"offset points\",\n va=\"center\", ha=\"left\",\n bbox=dict(boxstyle=\"round\", fc=\"w\"),\n arrowprops=dict(arrowstyle=\"->\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that you must ensure that the extent of the coordinate artist (*an1* in\nthis example) is determined before *an2* gets drawn. Usually, this means\nthat *an2* needs to be drawn after *an1*. The base class for all bounding\nboxes is `.BboxBase`\n\n### Callable that returns `.Transform` of `.BboxBase`\n\nA callable object that takes the renderer instance as single argument, and\nreturns either a `.Transform` or a `.BboxBase`. For example, the return\nvalue of `.Artist.get_window_extent` is a bbox, so this method is identical\nto (2) passing in the artist:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(3, 3))\nan1 = ax.annotate(\"Test 1\",\n xy=(0.5, 0.5), xycoords=\"data\",\n va=\"center\", ha=\"center\",\n bbox=dict(boxstyle=\"round\", fc=\"w\"))\n\nan2 = ax.annotate(\"Test 2\",\n xy=(1, 0.5), xycoords=an1.get_window_extent,\n xytext=(30, 0), textcoords=\"offset points\",\n va=\"center\", ha=\"left\",\n bbox=dict(boxstyle=\"round\", fc=\"w\"),\n arrowprops=dict(arrowstyle=\"->\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`.Artist.get_window_extent` is the bounding box of the Axes object and is\ntherefore identical to setting the coordinate system to axes fraction:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(6, 3))\n\nan1 = ax1.annotate(\"Test1\", xy=(0.5, 0.5), xycoords=\"axes fraction\")\nan2 = ax2.annotate(\"Test 2\", xy=(0.5, 0.5), xycoords=ax2.get_window_extent)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Blended coordinate specification\n\nA blended pair of coordinate specifications -- the first for the\nx-coordinate, and the second is for the y-coordinate. For example, x=0.5 is\nin data coordinates, and y=1 is in normalized axes coordinates:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(3, 3))\nax.annotate(\"Test\", xy=(0.5, 1), xycoords=(\"data\", \"axes fraction\"))\nax.axvline(x=.5, color='lightgray')\nax.set(xlim=(0, 2), ylim=(1, 2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Any of the supported coordinate systems can be used in a blended\nspecification. For example, the text \"Anchored to 1 & 2\" is positioned\nrelative to the two `.Text` Artists:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(3, 3))\n\nt1 = ax.text(0.05, .05, \"Text 1\", va='bottom', ha='left')\nt2 = ax.text(0.90, .90, \"Text 2\", ha='right')\nt3 = ax.annotate(\"Anchored to 1 & 2\", xy=(0, 0), xycoords=(t1, t2),\n va='bottom', color='tab:orange',)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `.text.OffsetFrom`\n\nSometimes, you want your annotation with some \"offset points\", not from the\nannotated point but from some other point or artist. `.text.OffsetFrom` is\na helper for such cases.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from matplotlib.text import OffsetFrom\n\nfig, ax = plt.subplots(figsize=(3, 3))\nan1 = ax.annotate(\"Test 1\", xy=(0.5, 0.5), xycoords=\"data\",\n va=\"center\", ha=\"center\",\n bbox=dict(boxstyle=\"round\", fc=\"w\"))\n\noffset_from = OffsetFrom(an1, (0.5, 0))\nan2 = ax.annotate(\"Test 2\", xy=(0.1, 0.1), xycoords=\"data\",\n xytext=(0, -10), textcoords=offset_from,\n # xytext is offset points from \"xy=(0.5, 0), xycoords=an1\"\n va=\"top\", ha=\"center\",\n bbox=dict(boxstyle=\"round\", fc=\"w\"),\n arrowprops=dict(arrowstyle=\"->\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Non-text annotations\n\n\n### Using ConnectionPatch\n\n`.ConnectionPatch` is like an annotation without text. While `~.Axes.annotate`\nis sufficient in most situations, `.ConnectionPatch` is useful when you want\nto connect points in different Axes. For example, here we connect the point\n*xy* in the data coordinates of ``ax1`` to point *xy* in the data coordinates\nof ``ax2``:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from matplotlib.patches import ConnectionPatch\n\nfig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(6, 3))\nxy = (0.3, 0.2)\ncon = ConnectionPatch(xyA=xy, coordsA=ax1.transData,\n xyB=xy, coordsB=ax2.transData)\n\nfig.add_artist(con)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, we added the `.ConnectionPatch` to the *figure*\n(with `~.Figure.add_artist`) rather than to either Axes. This ensures that\nthe ConnectionPatch artist is drawn on top of both Axes, and is also necessary\nwhen using `constrained_layout `\nfor positioning the Axes.\n\n### Zoom effect between Axes\n\n`mpl_toolkits.axes_grid1.inset_locator` defines some patch classes useful for\ninterconnecting two Axes.\n\n.. figure:: /gallery/subplots_axes_and_figures/images/sphx_glr_axes_zoom_effect_001.png\n :target: /gallery/subplots_axes_and_figures/axes_zoom_effect.html\n :align: center\n\nThe code for this figure is at\n:doc:`/gallery/subplots_axes_and_figures/axes_zoom_effect` and\nfamiliarity with `transforms_tutorial`\nis recommended.\n\n" ] } ], "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 }