| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -28,39 +28,43 @@ For example, *ax.legend()* is equivalent to:: | |||
| 28 | 28 | The :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` method | |
| 29 | 29 | returns a tuple of two lists, i.e., list of artists and list of labels | |
| 30 | 30 | (python string). However, it does not return all of its child | |
| 31 | - artists. It returns all artists in *ax.lines* and *ax.patches* and | ||
| 32 | - some artists in *ax.collection* which are instance of | ||
| 31 | + artists. It returns artists that are currently supported by matplotlib. | ||
| 32 | + | ||
| 33 | + For matplotlib v1.0 and before, the supported artists are as follows. | ||
| 34 | + | ||
| 35 | + * :class:`~matplotlib.lines.Line2D` | ||
| 36 | + * :class:`~matplotlib.patches.Patch` | ||
| 37 | + * :class:`~matplotlib.collections.LineCollection` | ||
| 38 | + * :class:`~matplotlib.collections.RegularPolyCollection` | ||
| 39 | + | ||
| 40 | + And, :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` returns | ||
| 41 | + all artists in *ax.lines*, *ax.patches* and | ||
| 42 | + artists in *ax.collection* which are instance of | ||
| 33 | 43 | :class:`~matplotlib.collections.LineCollection` or | |
| 34 | 44 | :class:`~matplotlib.collections.RegularPolyCollection`. The label | |
| 35 | 45 | attributes (returned by get_label() method) of collected artists are | |
| 36 | 46 | used as text labels. If label attribute is empty string or starts with | |
| 37 | - "_", that artist will be ignored. | ||
| 47 | + "_", those artists will be ignored. | ||
| 38 | 48 | ||
| 39 | 49 | ||
| 40 | - * Note that not all kind of artists are supported by the legend. The | ||
| 41 | - following is the list of artists that are currently supported. | ||
| 50 | + Therefore, plots drawn by some *pyplot* commands are not supported by | ||
| 51 | + legend. For example, :func:`~matplotlib.pyplot.fill_between` creates | ||
| 52 | + :class:`~matplotlib.collections.PolyCollection` that is not | ||
| 53 | + supported. Also support is limted for some commands that creat | ||
| 54 | + multiple artists. For example, :func:`~matplotlib.pyplot.errorbar` | ||
| 55 | + creates multiples :class:`~matplotlib.lines.Line2D` instances. | ||
| 42 | 56 | ||
| 43 | - * :class:`~matplotlib.lines.Line2D` | ||
| 44 | - * :class:`~matplotlib.patches.Patch` | ||
| 45 | - * :class:`~matplotlib.collections.LineCollection` | ||
| 46 | - * :class:`~matplotlib.collections.RegularPolyCollection` | ||
| 57 | + Unfortunately, there is no easy workaround when you need legend for an | ||
| 58 | + artist not supported by matplotlib (You may use one of the supported | ||
| 59 | + artist as a proxy. See below) | ||
| 47 | 60 | ||
| 48 | - Unfortunately, there is no easy workaround when you need legend for | ||
| 49 | - an artist not in the above list (You may use one of the supported | ||
| 50 | - artist as a proxy. See below), or customize it beyond what is | ||
| 51 | - supported by :class:`matplotlib.legend.Legend`. | ||
| 61 | + In newer version of matplotlib (v1.1 and later), the matplotlib | ||
| 62 | + internals are revised to support | ||
| 52 | 63 | ||
| 53 | - * Remember that some *pyplot* commands return artist not supported by | ||
| 54 | - legend, e.g., :func:`~matplotlib.pyplot.fill_between` returns | ||
| 55 | - :class:`~matplotlib.collections.PolyCollection` that is not | ||
| 56 | - supported. Or some return multiple artists. For example, | ||
| 57 | - :func:`~matplotlib.pyplot.plot` returns list of | ||
| 58 | - :class:`~matplotlib.lines.Line2D` instances, and | ||
| 59 | - :func:`~matplotlib.pyplot.errorbar` returns a length 3 tuple of | ||
| 60 | - :class:`~matplotlib.lines.Line2D` instances. | ||
| 64 | + * complex plots that creates multiple artists (e.g., bar, errorbar, etc) | ||
| 65 | + * custom legend handles | ||
| 61 | 66 | ||
| 62 | - * The legend does not care about the axes that given artists belongs, | ||
| 63 | - i.e., the artists may belong to other axes or even none. | ||
| 67 | + See below for details of new functionality. | ||
| 64 | 68 | ||
| 65 | 69 | ||
| 66 | 70 | Adjusting the Order of Legend items | |
@@ -180,3 +184,132 @@ legend. | |||
| 180 | 184 | ||
| 181 | 185 | .. plot:: users/plotting/examples/simple_legend02.py | |
| 182 | 186 | :include-source: | |
| 187 | + | ||
| 188 | + | ||
| 189 | + Legend of Complex Plots | ||
| 190 | + ======================= | ||
| 191 | + | ||
| 192 | + In matplotlib v1.1 (FIXME when released) and later, the legend is | ||
| 193 | + improved to support more plot commands and ease the customization. | ||
| 194 | + | ||
| 195 | + Artist Container | ||
| 196 | + ---------------- | ||
| 197 | + | ||
| 198 | + The Artist Container is simple class (derived from tuple) that | ||
| 199 | + contains multiple artists. This is introduced primarily to support | ||
| 200 | + legends for complex plot commands that create multiple artists. | ||
| 201 | + | ||
| 202 | + Axes instances now have a "containers" attribute (which is a list, and | ||
| 203 | + this is only intended to be used for generating a legend). The items | ||
| 204 | + in this attribute are also returned by | ||
| 205 | + :meth:`~matplotlib.axes.Axes.get_legend_handles_labels`. | ||
| 206 | + | ||
| 207 | + For example, "bar" command creates a series of Rectangle | ||
| 208 | + patches. Previously, it returned a list of these patches. With the | ||
| 209 | + current change, it creates a container object of these rectangle | ||
| 210 | + patches (and these patches are added to Axes.patches attribute as | ||
| 211 | + before) and return it instead. As the container class is derived from | ||
| 212 | + a tuple, it should be backward-compatible. Furthermore, the container | ||
| 213 | + object is added to the Axes.containers attributes so that legend | ||
| 214 | + command can properly create a legend for the bar. Thus, you may do :: | ||
| 215 | + | ||
| 216 | + b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, | ||
| 217 | + label="Bar 1", align="center") | ||
| 218 | + legend() | ||
| 219 | + | ||
| 220 | + or :: | ||
| 221 | + | ||
| 222 | + b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, align="center") | ||
| 223 | + legend([b1], ["Bar 1"]) | ||
| 224 | + | ||
| 225 | + | ||
| 226 | + At this time of writing, however, "bar" and "errorbar" are only | ||
| 227 | + supported (hopefully the list will increase). Here is an example. | ||
| 228 | + | ||
| 229 | + .. plot:: mpl_examples/pylab_examples/legend_demo4.py | ||
| 230 | + | ||
| 231 | + Legend Handler | ||
| 232 | + -------------- | ||
| 233 | + | ||
| 234 | + One of the change is that drawing of legend handles is delegated to | ||
| 235 | + legend handlers. For example, :class:`~matplotlib.lines.Line2D` | ||
| 236 | + instances are handled by | ||
| 237 | + :class:`~matplotlib.legend_handler.HandlerLine2D`. The mapping | ||
| 238 | + between the artists and their corresponding handlers are defined in a | ||
| 239 | + handler_map of the legend. The handler_map is a dictionary of | ||
| 240 | + key-handler pair, where key can be an artist instance or its | ||
| 241 | + class. And the handler is a Handler instance. | ||
| 242 | + | ||
| 243 | + Let's consider the following sample code, :: | ||
| 244 | + | ||
| 245 | + legend([p_1, p_2,..., p_i, ...], ["Test 1", "Test 2", ..., "Test i",...]) | ||
| 246 | + | ||
| 247 | + For each *p_i*, matplotlib | ||
| 248 | + | ||
| 249 | + 1. check if *p_i* itself is in the handler_map | ||
| 250 | + 2. if not, iterate over type(p_i).mro() until a matching key is found in the handler_map | ||
| 251 | + | ||
| 252 | + | ||
| 253 | + Unless specified, the defaul handler_map is used. Below is a partial | ||
| 254 | + list of key-handler pairs included in the default handler map. | ||
| 255 | + | ||
| 256 | + * Line2D : legend_handler.HandlerLine2D() | ||
| 257 | + * Patch : legend_handler.HandlerPatch() | ||
| 258 | + * LineCollection : legend_handler.HandlerLineCollection() | ||
| 259 | + * ... | ||
| 260 | + | ||
| 261 | + | ||
| 262 | + The legend command takes an optional argument of "handler_map". When | ||
| 263 | + provided, the default handler map will be updated (using dict.update | ||
| 264 | + method) with the provided one. :: | ||
| 265 | + | ||
| 266 | + p1, = plot(x, "ro", label="test1") | ||
| 267 | + p2, = plot(y, "b+", ms=10, label="test2") | ||
| 268 | + | ||
| 269 | + my_handler = HandlerLine2D(numpoints=1) | ||
| 270 | + | ||
| 271 | + legend(handler_map={Line2D:my_handler}) | ||
| 272 | + | ||
| 273 | + The above example will use *my_handler* for any Line2D | ||
| 274 | + instances (p1 and p2). :: | ||
| 275 | + | ||
| 276 | + legend(handler_map={p1:HandlerLine2D(numpoints=1)}) | ||
| 277 | + | ||
| 278 | + In the above example, only *p1* will be handled by *my_handler*, while | ||
| 279 | + others will be handled by default handlers. | ||
| 280 | + | ||
| 281 | + The curent default handler_map has handlers for errobar and bar | ||
| 282 | + plots. Also, it includes an entry for ¡°tuple¡± which is mapped to | ||
| 283 | + *HandlerTuple*. It simply overplots all the handles for items in the | ||
| 284 | + given tuple. For example, | ||
| 285 | + | ||
| 286 | + | ||
| 287 | + .. plot:: | ||
| 288 | + :include-source: | ||
| 289 | + | ||
| 290 | + z = np.random.randn(10) | ||
| 291 | + | ||
| 292 | + p1a, = plt.plot(z, "ro", ms=10, mfc="r", mew=2, mec="r") # red filled circle | ||
| 293 | + p1b, = plt.plot(z[:5], "w+", ms=10, mec="w", mew=2) # white cross | ||
| 294 | + | ||
| 295 | + plt.legend([p1a, (p1a, p1b)], ["Attr A", "Attr A+B"]) | ||
| 296 | + | ||
| 297 | + | ||
| 298 | + | ||
| 299 | + Implement a Custom Handler | ||
| 300 | + -------------------------- | ||
| 301 | + | ||
| 302 | + Handler can be any callable object with following signature. :: | ||
| 303 | + | ||
| 304 | + def __call__(self, legend, orig_handle, | ||
| 305 | + fontsize, | ||
| 306 | + handlebox): | ||
| 307 | + | ||
| 308 | + Where *legend* is the legend itself, *orig_handle* is the original | ||
| 309 | + plot (*p_i* in the above example), *fontsize* is the fontsize in | ||
| 310 | + pixles, and *handlebox* is a OffsetBox instance. Within the call, you | ||
| 311 | + create relevant artists (using relevant properties from the *legend* | ||
| 312 | + and/or *orig_handle*) and add them into the handlebox. The artists | ||
| 313 | + needs to be scaled according to the fontsize (note that the size is in | ||
| 314 | + pixel, i.e., this is dpi-scaled value). See legend_handler.py for more | ||
| 315 | + details. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,22 @@ | |||
| 1 | + import matplotlib.pyplot as plt | ||
| 2 | + | ||
| 3 | + ax = plt.subplot(111) | ||
| 4 | + | ||
| 5 | + b1 = ax.bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, | ||
| 6 | + label="Bar 1", align="center") | ||
| 7 | + | ||
| 8 | + b2 = ax.bar([0.5, 1.5, 2.5], [0.3, 0.2, 0.2], color="red", width=0.4, | ||
| 9 | + label="Bar 2", align="center") | ||
| 10 | + | ||
| 11 | + err1 = ax.errorbar([0, 1, 2], [2, 3, 1], xerr=0.4, fmt="s", | ||
| 12 | + label="test 1") | ||
| 13 | + err2 = ax.errorbar([0, 1, 2], [3, 2, 4], yerr=0.3, fmt="o", | ||
| 14 | + label="test 2") | ||
| 15 | + err3 = ax.errorbar([0, 1, 2], [1, 1, 3], xerr=0.4, yerr=0.3, fmt="^", | ||
| 16 | + label="test 3") | ||
| 17 | + | ||
| 18 | + # legend | ||
| 19 | + leg1 = plt.legend(loc=1) | ||
| 20 | + | ||
| 21 | + plt.show() | ||
| 22 | + | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,30 @@ | |||
| 1 | + import matplotlib.pyplot as plt | ||
| 2 | + import numpy as np | ||
| 3 | + | ||
| 4 | + ax = plt.subplot(111, aspect=1) | ||
| 5 | + x, y = np.random.randn(2, 20) | ||
| 6 | + #[1.1, 2, 2.8], [1.1, 2, 1.8] | ||
| 7 | + l1, = ax.plot(x,y, "k+", mew=3, ms=12) | ||
| 8 | + l2, = ax.plot(x,y, "w+", mew=1, ms=10) | ||
| 9 | + | ||
| 10 | + import matplotlib.patches as mpatches | ||
| 11 | + c = mpatches.Circle((0, 0), 1, fc="g", ec="r", lw=3) | ||
| 12 | + ax.add_patch(c) | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + from matplotlib.legend_handler import HandlerPatch | ||
| 17 | + | ||
| 18 | + def make_legend_ellipse(legend, orig_handle, | ||
| 19 | + xdescent, ydescent, | ||
| 20 | + width, height, fontsize): | ||
| 21 | + p = mpatches.Ellipse(xy=(0.5*width-0.5*xdescent, 0.5*height-0.5*ydescent), | ||
| 22 | + width = width+xdescent, height=(height+ydescent)) | ||
| 23 | + | ||
| 24 | + return p | ||
| 25 | + | ||
| 26 | + plt.legend([c, (l1, l2)], ["Label 1", "Label 2"], | ||
| 27 | + handler_map={mpatches.Circle:HandlerPatch(patch_func=make_legend_ellipse), | ||
| 28 | + }) | ||
| 29 | + | ||
| 30 | + plt.show() | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments