diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index e6a0c1adc588..6f82806e2c51 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -74,6 +74,17 @@ original location: thus `colorbar.ColorbarBase.outline` is now a `matplotlib.patches.Polygon` object. +* The legend handler interface has changed from a callable, to any object + which implements the ``legend_artists`` method (a deprecation phase will + see this interface be maintained for v1.4). See + :ref:`plotting-guide-legend` for further details. Further legend changes + include: + + * :func:`matplotlib.axes.Axes._get_legend_handles` now returns a generator + of handles, rather than a list. + + * The :func:`~matplotlib.pyplot.legend` function's "loc" positional + argument has been deprecated. Use the "loc" keyword instead. * The rcParams `savefig.transparent` has been added to control default transparency when saving figures. diff --git a/doc/api/axes_api.rst b/doc/api/axes_api.rst index 14883b695c24..31eab67eda41 100644 --- a/doc/api/axes_api.rst +++ b/doc/api/axes_api.rst @@ -9,4 +9,3 @@ axes .. autoclass:: matplotlib.axes.Axes :members: :undoc-members: - diff --git a/doc/api/legend_api.rst b/doc/api/legend_api.rst index 7ad075d33cae..a72da7b84a61 100644 --- a/doc/api/legend_api.rst +++ b/doc/api/legend_api.rst @@ -1,5 +1,5 @@ ****** -legend +Legend ****** @@ -10,3 +10,9 @@ legend :members: :undoc-members: :show-inheritance: + +:mod:`matplotlib.legend_handler` +================================ +.. automodule:: matplotlib.legend_handler + :members: + :undoc-members: diff --git a/doc/api/matplotlib_configuration_api.rst b/doc/api/matplotlib_configuration_api.rst index 2abbae33678d..9f0ad3678ffb 100644 --- a/doc/api/matplotlib_configuration_api.rst +++ b/doc/api/matplotlib_configuration_api.rst @@ -6,9 +6,9 @@ The top level :mod:`matplotlib` module .. autofunction:: use -.. autofunction:: get_backend +.. autofunction:: get_backend -.. py:data:: matplotlib.rcParams +.. py:data:: rcParams An instance of :class:`RcParams` for handling default matplotlib values. diff --git a/doc/api/offsetbox.rst b/doc/api/offsetbox.rst new file mode 100644 index 000000000000..1ed7e55504db --- /dev/null +++ b/doc/api/offsetbox.rst @@ -0,0 +1,12 @@ +********* +offsetbox +********* + + +:mod:`matplotlib.offsetbox` +=========================== + +.. automodule:: matplotlib.offsetbox + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/users/legend_guide.rst b/doc/users/legend_guide.rst index 7c718de5d3f9..8287a5ca071e 100644 --- a/doc/users/legend_guide.rst +++ b/doc/users/legend_guide.rst @@ -4,315 +4,279 @@ Legend guide ************ -Do not proceed unless you already have read :func:`~matplotlib.pyplot.legend` -and :class:`matplotlib.legend.Legend`! +.. currentmodule:: matplotlib.pyplot -What to be displayed -==================== +This legend guide is an extension of the documentation available at +:func:`~matplotlib.pyplot.legend` - please ensure you are familiar with +contents of that documentation before proceeding with this guide. -The legend command has a following call signature:: - legend(*args, **kwargs) +This guide makes use of some common terms, which are documented here for clarity: -If len(args) is 2, the first argument should be a list of artist to be -labeled, and the second argument should a list of string labels. If -len(args) is 0, it automatically generate the legend from label -properties of the child artists by calling -:meth:`~matplotlib.axes.Axes.get_legend_handles_labels` method. -For example, *ax.legend()* is equivalent to:: +.. glossary:: - handles, labels = ax.get_legend_handles_labels() - ax.legend(handles, labels) + legend entry + A legend is made up of one or more legend entries. An entry is made up of + exactly one key and one label. -The :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` method -returns a tuple of two lists, i.e., list of artists and list of labels -(python string). However, it does not return all of its child -artists. It returns artists that are currently supported by matplotlib. + legend key + The colored/patterned marker to the left of each legend label. -For matplotlib v1.0 and earlier, the supported artists are as follows. + legend label + The text which describes the handle represented by the key. - * :class:`~matplotlib.lines.Line2D` - * :class:`~matplotlib.patches.Patch` - * :class:`~matplotlib.collections.LineCollection` - * :class:`~matplotlib.collections.RegularPolyCollection` - * :class:`~matplotlib.collections.CircleCollection` + legend handle + The original object which is used to generate an appropriate entry in + the legend. -And, :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` returns -all artists in *ax.lines*, *ax.patches* and -artists in *ax.collection* which are instance of -:class:`~matplotlib.collections.LineCollection` or -:class:`~matplotlib.collections.RegularPolyCollection`. The label -attributes (returned by get_label() method) of collected artists are -used as text labels. If label attribute is empty string or starts with -"_", those artists will be ignored. +Controlling the legend entries +============================== -Therefore, plots drawn by some *pyplot* commands are not supported by -legend. For example, :func:`~matplotlib.pyplot.fill_between` creates -:class:`~matplotlib.collections.PolyCollection` that is not -supported. Also support is limited for some commands that create -multiple artists. For example, :func:`~matplotlib.pyplot.errorbar` -creates multiples :class:`~matplotlib.lines.Line2D` instances. +Calling :func:`legend` with no arguments automatically fetches the legend +handles and their associated labels. This functionality is equivalent to:: -Unfortunately, there is no easy workaround when you need legend for an -artist not supported by matplotlib (You may use one of the supported -artist as a proxy. See below) + handles, labels = ax.get_legend_handles_labels() + ax.legend(handles, labels) -In newer version of matplotlib (v1.1 and later), the matplotlib -internals are revised to support +The :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` function returns +a list of handles/artists which exist on the Axes which can be used to +generate entries for the resulting legend - it is worth noting however that +not all artists can be added to a legend, at which point a "proxy" will have +to be created (see :ref:`proxy_legend_handles` for further details). - * complex plots that creates multiple artists (e.g., bar, errorbar, etc) - * custom legend handles +For full control of what is being added to the legend, it is common to pass +the appropriate handles directly to :func:`legend`:: -See below for details of new functionality. + line_up, = plt.plot([1,2,3], label='Line 2') + line_down, = plt.plot([3,2,1], label='Line 1') + plt.legend(handles=[line_up, line_down]) +In some cases, it is not possible to set the label of the handle, so it is +possible to pass through the list of labels to :func:`legend`:: -Adjusting the Order of Legend items ------------------------------------ + line_up, = plt.plot([1,2,3], label='Line 2') + line_down, = plt.plot([3,2,1], label='Line 1') + plt.legend([line_up, line_down], ['Line Up', 'Line Down']) -When you want to customize the list of artists to be displayed in the -legend, or their order of appearance. There are a two options. First, -you can keep lists of artists and labels, and explicitly use these for -the first two argument of the legend call.:: - p1, = plot([1,2,3]) - p2, = plot([3,2,1]) - p3, = plot([2,3,1]) - legend([p2, p1], ["line 2", "line 1"]) +.. _proxy_legend_handles: -Or you may use :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` -to retrieve list of artist and labels and manipulate them before -feeding them to legend call.:: +Creating artists specifically for adding to the legend (aka. Proxy artists) +=========================================================================== - ax = subplot(1,1,1) - p1, = ax.plot([1,2,3], label="line 1") - p2, = ax.plot([3,2,1], label="line 2") - p3, = ax.plot([2,3,1], label="line 3") +Not all handles can be turned into legend entries automatically, +so it is often necessary to create an artist which *can*. Legend handles +don't have to exists on the Figure or Axes in order to be used. - handles, labels = ax.get_legend_handles_labels() +Suppose we wanted to create a legend which has an entry for some data which +is represented by a red color: - # reverse the order - ax.legend(handles[::-1], labels[::-1]) - - # or sort them by labels - import operator - hl = sorted(zip(handles, labels), - key=operator.itemgetter(1)) - handles2, labels2 = zip(*hl) +.. plot:: + :include-source: - ax.legend(handles2, labels2) + import matplotlib.patches as mpatches + import matplotlib.pyplot as plt + red_patch = mpatches.Patch(color='red', label='The red data') + plt.legend(handles=[red_patch]) -Using Proxy Artist ------------------- + plt.show() -When you want to display legend for an artist not supported by -matplotlib, you may use another artist as a proxy. For -example, you may create a proxy artist without adding it to the axes -(so the proxy artist will not be drawn in the main axes) and feed it -to the legend function.:: +There are many supported legend handles, instead of creating a patch of color +we could have created a line with a marker: - p = Rectangle((0, 0), 1, 1, fc="r") - legend([p], ["Red Rectangle"]) +.. plot:: + :include-source: + import matplotlib.lines as mlines + import matplotlib.pyplot as plt -Multicolumn Legend -================== + blue_line = mlines.Line2D([], [], color='blue', marker='*', + markersize=15, label='Blue stars') + plt.legend(handles=[blue_line]) -By specifying the keyword argument *ncol*, you can have a multi-column -legend. Also, mode="expand" horizontally expand the legend to fill the -axes area. See `legend_demo3.py -`_ -for example. + plt.show() Legend location =============== The location of the legend can be specified by the keyword argument -*loc*, either by string or a integer number. - -============= ====== - String Number -============= ====== - upper right 1 - upper left 2 - lower left 3 - lower right 4 - right 5 - center left 6 - center right 7 - lower center 8 - upper center 9 - center 10 -============= ====== - -By default, the legend will anchor to the bbox of the axes -(for legend) or the bbox of the figure (figlegend). You can specify -your own bbox using *bbox_to_anchor* argument. *bbox_to_anchor* can be an -instance of :class:`~matplotlib.transforms.BboxBase`, a tuple of 4 -floats (x, y, width, height of the bbox), or a tuple of 2 floats (x, y -with width=height=0). Unless *bbox_transform* argument is given, the -coordinates (even for the bbox instance) are considered as normalized -axes coordinates. - -For example, if you want your axes legend located at the figure corner -(instead of the axes corner):: - - l = legend(bbox_to_anchor=(0, 0, 1, 1), bbox_transform=gcf().transFigure) - -Also, you can place above or outer right-hand side of the axes, - -.. plot:: users/plotting/examples/simple_legend01.py - :include-source: - - -Multiple Legend -=============== +*loc*. Please see the documentation at :func:`legend` for more details. -Sometime, you want to split the legend into multiple ones.:: +The ``bbox_to_anchor`` keyword gives a great degree of control for manual +legend placement. For example, if you want your axes legend located at the +figure's top right-hand corner instead of the axes' corner, simply specify +the corner's location, and the coordinate system of that location:: - p1, = plot([1,2,3]) - p2, = plot([3,2,1]) - legend([p1], ["Test1"], loc=1) - legend([p2], ["Test2"], loc=4) + plt.legend(bbox_to_anchor=(1, 1), + bbox_transform=plt.gcf().transFigure) -However, the above code only shows the second legend. When the legend -command is called, a new legend instance is created and old ones are -removed from the axes. Thus, you need to manually add the removed -legend. +More examples of custom legend placement: -.. plot:: users/plotting/examples/simple_legend02.py +.. plot:: users/plotting/examples/simple_legend01.py :include-source: -.. _legend-complex-plots: -Legend of Complex Plots -======================= +Multiple legends on the same Axes +================================= -In matplotlib v1.1 and later, the legend is -improved to support more plot commands and ease the customization. +Sometimes it is more clear to split legend entries across multiple +legends. Whilst the instinctive approach to doing this might be to call +the :func:`legend` function multiple times, you will find that only one +legend ever exists on the Axes. This has been done so that it is possible +to call :func:`legend` repeatedly to update the legend to the latest +handles on the Axes, so to persist old legend instances, we must add them +manually to the Axes: -Artist Container ----------------- +.. plot:: users/plotting/examples/simple_legend02.py + :include-source: -The Artist Container is simple class (derived from tuple) that -contains multiple artists. This is introduced primarily to support -legends for complex plot commands that create multiple artists. +Legend Handlers +=============== -Axes instances now have a "containers" attribute (which is a list, and -this is only intended to be used for generating a legend). The items -in this attribute are also returned by -:meth:`~matplotlib.axes.Axes.get_legend_handles_labels`. +In order to create legend entries, handles are given as an argument to an +appropriate :class:`~matplotlib.legend_handler.HandlerBase` subclass. +The choice of handler subclass is determined by the following rules: -For example, "bar" command creates a series of Rectangle -patches. Previously, it returned a list of these patches. With the -current change, it creates a container object of these rectangle -patches (and these patches are added to Axes.patches attribute as -before) and return it instead. As the container class is derived from -a tuple, it should be backward-compatible. Furthermore, the container -object is added to the Axes.containers attributes so that legend -command can properly create a legend for the bar. Thus, you may do :: + 1. Update :func:`~matplotlib.legend.Legend.get_legend_handler_map` + with the value in the ``handler_map`` keyword. + 2. Check if the ``handle`` is in the newly created ``handler_map``. + 3. Check if the type of ``handle`` is in the newly created + ``handler_map``. + 4. Check if any of the types in the ``handle``'s mro is in the newly + created ``handler_map``. - b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, - label="Bar 1", align="center") - legend() +For completeness, this logic is mostly implemented in +:func:`~matplotlib.legend.Legend.get_legend_handler`. -or :: +All of this flexibility means that we have the necessary hooks to implement +custom handlers for our own type of legend key. - b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, align="center") - legend([b1], ["Bar 1"]) +The simplest example of using custom handlers is to instantiate one of the +existing :class:`~matplotlib.legend_handler.HandlerBase` subclasses. For the +sake of simplicity, let's choose :class:`matplotlib.legend_handler.HandlerLine2D` +which accepts a ``numpoints`` argument (note numpoints is a keyword +on the :func:`legend` function for convenience). We can then pass the mapping +of instance to Handler as a keyword to legend. +.. plot:: + :include-source: -At this time of writing, however, only "bar", "errorbar", and "stem" are -supported (hopefully the list will increase). Here is an example. + import matplotlib.pyplot as plt + from matplotlib.legend_handler import HandlerLine2D -.. plot:: mpl_examples/pylab_examples/legend_demo4.py + line1, = plt.plot([3,2,1], marker='o', label='Line 1') + line2, = plt.plot([1,2,3], marker='o', label='Line 2') -Legend Handler --------------- + plt.legend(handler_map={line1: HandlerLine2D(numpoints=4)}) -One of the changes is that drawing of legend handles has been delegated to -legend handlers. For example, :class:`~matplotlib.lines.Line2D` -instances are handled by -:class:`~matplotlib.legend_handler.HandlerLine2D`. The mapping -between the artists and their corresponding handlers are defined in a -handler_map of the legend. The handler_map is a dictionary of -key-handler pair, where key can be an artist instance or its -class. And the handler is a Handler instance. +As you can see, "Line 1" now has 4 marker points, where "Line 2" has 2 (the +default). Try the above code, only change the map's key from ``line1`` to +``type(line1)``. Notice how now both :class:`~matplotlib.lines.Line2D` instances +get 4 markers. -Let's consider the following sample code, :: +Along with handlers for complex plot types such as errorbars, stem plots +and histograms, the default ``handler_map`` has a special ``tuple`` handler +(:class:`~matplotlib.legend_handler.HandlerTuple`) which simply plots +the handles on top of one another for each item in the given tuple. The +following example demonstrates combining two legend keys on top of one another: - legend([p_1, p_2,..., p_i, ...], ["Test 1", "Test 2", ..., "Test i",...]) +.. plot:: + :include-source: -For each *p_i*, matplotlib + import matplotlib.pyplot as plt + from numpy.random import randn - 1. check if *p_i* is in the handler_map - 2. if not, iterate over type(p_i).mro() until a matching key is found - in the handler_map + z = randn(10) + red_dot, = plt.plot(z, "ro", markersize=15) + # Put a white cross over some of the data. + white_cross, = plt.plot(z[:5], "w+", markeredgewidth=3, markersize=15) -Unless specified, the default handler_map is used. Below is a partial -list of key-handler pairs included in the default handler map. + plt.legend([red_dot, (red_dot, white_cross)], ["Attr A", "Attr A+B"]) - * Line2D : legend_handler.HandlerLine2D() - * Patch : legend_handler.HandlerPatch() - * LineCollection : legend_handler.HandlerLineCollection() - * ... +Implementing a custom legend handler +------------------------------------ -The legend() command takes an optional argument of "handler_map". When -provided, the default handler map will be updated (using dict.update -method) with the provided one. :: +A custom handler can be implemented to turn any handle into a legend key (handles +don't necessarily need to be matplotlib artists). +The handler must implement a "legend_artist" method which returns a +single artist for the legend to use. Signature details about the "legend_artist" +are documented at :meth:`~matplotlib.legend_handler.HandlerBase.legend_artist`. - p1, = plot(x, "ro", label="test1") - p2, = plot(y, "b+", ms=10, label="test2") +.. plot:: + :include-source: - my_handler = HandlerLine2D(numpoints=1) + import matplotlib.pyplot as plt + import matplotlib.patches as mpatches - legend(handler_map={Line2D:my_handler}) + class AnyObject(object): + pass -The above example will use *my_handler* for any Line2D -instances (p1 and p2). :: + class AnyObjectHandler(object): + def legend_artist(self, legend, orig_handle, fontsize, handlebox): + x0, y0 = handlebox.xdescent, handlebox.ydescent + width, height = handlebox.width, handlebox.height + patch = mpatches.Rectangle([x0, y0], width, height, facecolor='red', + edgecolor='black', hatch='xx', lw=3, + transform=handlebox.get_transform()) + handlebox.add_artist(patch) + return patch - legend(handler_map={p1:HandlerLine2D(numpoints=1)}) + plt.legend([AnyObject()], ['My first handler'], + handler_map={AnyObject: AnyObjectHandler()}) -In the above example, only *p1* will be handled by *my_handler*, while -others will be handled by default handlers. +Alternatively, had we wanted to globally accept ``AnyObject`` instances without +needing to manually set the ``handler_map`` keyword all the time, we could have +registered the new handler with:: -The current default handler_map has handlers for errorbar and bar -plots. Also, it includes an entry for `tuple` which is mapped to -`HandlerTuple`. It simply plots over all the handles for items in the -given tuple. For example, + from matplotlib.legend import Legend + Legend.update_default_handler_map({AnyObject: AnyObjectHandler()}) +Whilst the power here is clear, remember that there are already many handlers +implemented and what you want to achieve may already be easily possible with +existing classes. For example, to produce elliptical legend keys, rather than +rectangular ones: .. plot:: :include-source: - z = np.random.randn(10) + from matplotlib.legend_handler import HandlerPatch + import matplotlib.pyplot as plt + import matplotlib.patches as mpatches - p1a, = plt.plot(z, "ro", ms=10, mfc="r", mew=2, mec="r") # red filled circle - p1b, = plt.plot(z[:5], "w+", ms=10, mec="w", mew=2) # white cross - plt.legend([p1a, (p1a, p1b)], ["Attr A", "Attr A+B"]) + class HandlerEllipse(HandlerPatch): + def create_artists(self, legend, orig_handle, + xdescent, ydescent, width, height, fontsize, trans): + center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent + p = mpatches.Ellipse(xy=center, width=width + xdescent, + height=height + ydescent) + self.update_prop(p, orig_handle, legend) + p.set_transform(trans) + return [p] + c = mpatches.Circle((0.5, 0.5), 0.25, facecolor="green", + edgecolor="red", linewidth=3) + plt.gca().add_patch(c) -Implement a Custom Handler --------------------------- + plt.legend([c], ["An ellipse, not a rectangle"], + handler_map={mpatches.Circle: HandlerEllipse()}) -Handler can be any callable object with following signature. :: +Known examples of using legend +============================== - def __call__(self, legend, orig_handle, - fontsize, - handlebox): +Here is a non-exhaustive list of the examples available involving legend +being used in various ways: -Where *legend* is the legend itself, *orig_handle* is the original -plot (*p_i* in the above example), *fontsize* is the fontsize in -pixels, and *handlebox* is a OffsetBox instance. Within the call, you -create relevant artists (using relevant properties from the *legend* -and/or *orig_handle*) and add them into the handlebox. The artists -needs to be scaled according to the fontsize (note that the size is in -pixel, i.e., this is dpi-scaled value). See :mod:`~matplotlib.legend_handler` -for more details. +* :ref:`lines_bars_and_markers-scatter_with_legend` +* :ref:`api-legend_demo` +* :ref:`pylab_examples-contourf_hatching` +* :ref:`pylab_examples-figlegend_demo` +* :ref:`pylab_examples-finance_work2` +* :ref:`pylab_examples-scatter_symbol` diff --git a/doc/users/plotting/examples/simple_legend01.py b/doc/users/plotting/examples/simple_legend01.py index dba1b4c2d410..a234f970db95 100644 --- a/doc/users/plotting/examples/simple_legend01.py +++ b/doc/users/plotting/examples/simple_legend01.py @@ -1,15 +1,18 @@ -from matplotlib.pyplot import * +import matplotlib.pyplot as plt -subplot(211) -plot([1,2,3], label="test1") -plot([3,2,1], label="test2") -legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, - ncol=2, mode="expand", borderaxespad=0.) -subplot(223) -plot([1,2,3], label="test1") -plot([3,2,1], label="test2") -legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) +plt.subplot(211) +plt.plot([1,2,3], label="test1") +plt.plot([3,2,1], label="test2") +# Place a legend above this legend, expanding itself to +# fully use the given bounding box. +plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, + ncol=2, mode="expand", borderaxespad=0.) +plt.subplot(223) +plt.plot([1,2,3], label="test1") +plt.plot([3,2,1], label="test2") +# Place a legend to the right of this smaller figure. +plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) -show() +plt.show() diff --git a/doc/users/plotting/examples/simple_legend02.py b/doc/users/plotting/examples/simple_legend02.py index 9b02001972dd..dabd2f072e72 100644 --- a/doc/users/plotting/examples/simple_legend02.py +++ b/doc/users/plotting/examples/simple_legend02.py @@ -1,10 +1,15 @@ -from matplotlib.pyplot import * +import matplotlib.pyplot as plt -p1, = plot([1,2,3], label="test1") -p2, = plot([3,2,1], label="test2") +line1, = plt.plot([1,2,3], label="Line 1", linestyle='--') +line2, = plt.plot([3,2,1], label="Line 2", linewidth=4) -l1 = legend([p1], ["Label 1"], loc=1) -l2 = legend([p2], ["Label 2"], loc=4) # this removes l1 from the axes. -gca().add_artist(l1) # add l1 as a separate artist to the axes +# Create a legend for the first line. +first_legend = plt.legend(handles=[line1], loc=1) -show() +# Add the legend manually to the current Axes. +ax = plt.gca().add_artist(first_legend) + +# Create another legend for the second line. +plt.legend(handles=[line2], loc=4) + +plt.show() diff --git a/doc/users/screenshots.rst b/doc/users/screenshots.rst index 2f9b04443300..2f1a652e28b8 100644 --- a/doc/users/screenshots.rst +++ b/doc/users/screenshots.rst @@ -241,7 +241,7 @@ The :func:`~matplotlib.pyplot.legend` command automatically generates figure legends, with MATLAB-compatible legend placement commands. -.. plot:: mpl_examples/pylab_examples/legend_demo.py +.. plot:: mpl_examples/api/legend_demo.py Thanks to Charles Twardy for input on the legend command. diff --git a/doc/users/transforms_tutorial.rst b/doc/users/transforms_tutorial.rst index b5fdf38e4819..144bf3e45129 100644 --- a/doc/users/transforms_tutorial.rst +++ b/doc/users/transforms_tutorial.rst @@ -19,15 +19,19 @@ description of that system. In the `Transformation Object` column, ``ax`` is a :class:`~matplotlib.axes.Axes` instance, and ``fig`` is a :class:`~matplotlib.figure.Figure` instance. -========== ===================== ============================================================================================================================================================== +========== ===================== ==================================================================================== Coordinate Transformation Object Description -========== ===================== ============================================================================================================================================================== +========== ===================== ==================================================================================== `data` ``ax.transData`` The userland data coordinate system, controlled by the xlim and ylim -`axes` ``ax.transAxes`` The coordinate system of the :class:`~matplotlib.axes.Axes`; (0,0) is bottom left of the axes, and (1,1) is top right of the axes -`figure` ``fig.transFigure`` The coordinate system of the :class:`~matplotlib.figure.Figure`; (0,0) is bottom left of the figure, and (1,1) is top right of the figure -`display` `None` This is the pixel coordinate system of the display; (0,0) is the bottom left of the display, and (width, height) is the top right of the display in pixels -========== ===================== ============================================================================================================================================================== - +`axes` ``ax.transAxes`` The coordinate system of the :class:`~matplotlib.axes.Axes`; (0,0) is + bottom left of the axes, and (1,1) is top right of the axes. +`figure` ``fig.transFigure`` The coordinate system of the :class:`~matplotlib.figure.Figure`; (0,0) + is bottom left of the figure, and (1,1) is top right of the figure. +`display` `None` This is the pixel coordinate system of the display; (0,0) is the bottom + left of the display, and (width, height) is the top right of the display in pixels. + Alternatively, the identity transform + (:class:`matplotlib.transforms.IdentityTransform()`) may be used instead of None. +========== ===================== ==================================================================================== All of the transformation objects in the table above take inputs in diff --git a/examples/api/legend_demo.py b/examples/api/legend_demo.py index 63f25a46bb26..26c0f8062f29 100644 --- a/examples/api/legend_demo.py +++ b/examples/api/legend_demo.py @@ -1,42 +1,19 @@ -""" -Demo of the legend function with a few features. - -In addition to the basic legend, this demo shows a few optional features: - - * Custom legend placement. - * A keyword argument to a drop-shadow. - * Setting the background color. - * Setting the font size. - * Setting the line width. -""" import numpy as np import matplotlib.pyplot as plt - -# Example data -a = np.arange(0,3, .02) -b = np.arange(0,3, .02) +# Make some fake data. +a = b = np.arange(0,3, .02) c = np.exp(a) d = c[::-1] # Create plots with pre-defined labels. -# Alternatively, you can pass labels explicitly when calling `legend`. -fig, ax = plt.subplots() -ax.plot(a, c, 'k--', label='Model length') -ax.plot(a, d, 'k:', label='Data length') -ax.plot(a, c+d, 'k', label='Total message length') - -# Now add the legend with some customizations. -legend = ax.legend(loc='upper center', shadow=True) +plt.plot(a, c, 'k--', label='Model length') +plt.plot(a, d, 'k:', label='Data length') +plt.plot(a, c+d, 'k', label='Total message length') -# The frame is matplotlib.patches.Rectangle instance surrounding the legend. -frame = legend.get_frame() -frame.set_facecolor('0.90') +legend = plt.legend(loc='upper center', shadow=True, fontsize='x-large') -# Set the fontsize -for label in legend.get_texts(): - label.set_fontsize('large') +# Put a nicer background color on the legend. +legend.get_frame().set_facecolor('#00FFCC') -for label in legend.get_lines(): - label.set_linewidth(1.5) # the legend line width plt.show() diff --git a/examples/lines_bars_and_markers/scatter_with_legend.py b/examples/lines_bars_and_markers/scatter_with_legend.py new file mode 100644 index 000000000000..c6b35a279563 --- /dev/null +++ b/examples/lines_bars_and_markers/scatter_with_legend.py @@ -0,0 +1,15 @@ +import matplotlib.pyplot as plt +from numpy.random import rand + + +for color in ['red', 'green', 'blue']: + n = 750 + x, y = rand(2, n) + scale = 200.0 * rand(n) + plt.scatter(x, y, c=color, s=scale, label=color, + alpha=0.3, edgecolors='none') + +plt.legend() +plt.grid(True) + +plt.show() diff --git a/examples/pylab_examples/dannys_example.py b/examples/pylab_examples/dannys_example.py deleted file mode 100644 index 8d8adb56b2f0..000000000000 --- a/examples/pylab_examples/dannys_example.py +++ /dev/null @@ -1,59 +0,0 @@ -import matplotlib -matplotlib.rc('text', usetex = True) -import pylab -import numpy as np - -## interface tracking profiles -N = 500 -delta = 0.6 -X = -1 + 2. * np.arange(N) / (N - 1) -pylab.plot(X, (1 - np.tanh(4. * X / delta)) / 2, ## phase field tanh profiles - X, (X + 1) / 2, ## level set distance function - X, (1.4 + np.tanh(4. * X / delta)) / 4, ## composition profile - X, X < 0, 'k--', ## sharp interface - linewidth = 5) - -## legend -pylab.legend((r'phase field', r'level set', r'composition', r'sharp interface'), shadow = True, loc = (0.01, 0.55)) -ltext = pylab.gca().get_legend().get_texts() -pylab.setp(ltext[0], fontsize = 20, color = 'b') -pylab.setp(ltext[1], fontsize = 20, color = 'g') -pylab.setp(ltext[2], fontsize = 20, color = 'r') -pylab.setp(ltext[3], fontsize = 20, color = 'k') - -## the arrow -height = 0.1 -offset = 0.02 -pylab.plot((-delta / 2., delta / 2), (height, height), 'k', linewidth = 2) -pylab.plot((-delta / 2, -delta / 2 + offset * 2), (height, height - offset), 'k', linewidth = 2) -pylab.plot((-delta / 2, -delta / 2 + offset * 2), (height, height + offset), 'k', linewidth = 2) -pylab.plot((delta / 2, delta / 2 - offset * 2), (height, height - offset), 'k', linewidth = 2) -pylab.plot((delta / 2, delta / 2 - offset * 2), (height, height + offset), 'k', linewidth = 2) -pylab.text(-0.06, height - 0.06, r'$\delta$', {'color' : 'k', 'fontsize' : 24}) - -## X-axis label -pylab.xticks((-1, 0, 1), ('-1', '0', '1'), color = 'k', size = 20) - -## Left Y-axis labels -pylab.ylabel(r'\bf{phase field} $\phi$', {'color' : 'b', - 'fontsize' : 20 }) -pylab.yticks((0, 0.5, 1), ('0', '.5', '1'), color = 'k', size = 20) - -## Right Y-axis labels -pylab.text(1.05, 0.5, r"\bf{level set} $\phi$", {'color' : 'g', 'fontsize' : 20}, - horizontalalignment = 'left', - verticalalignment = 'center', - rotation = 90, - clip_on = False) -pylab.text(1.01, -0.02, "-1", {'color' : 'k', 'fontsize' : 20}) -pylab.text(1.01, 0.98, "1", {'color' : 'k', 'fontsize' : 20}) -pylab.text(1.01, 0.48, "0", {'color' : 'k', 'fontsize' : 20}) - -## level set equations -pylab.text(0.1, 0.85, r'$|\nabla\phi| = 1,$ \newline $ \frac{\partial \phi}{\partial t} + U|\nabla \phi| = 0$', {'color' : 'g', 'fontsize' : 20}) - -## phase field equations -pylab.text(0.2, 0.15, r'$\mathcal{F} = \int f\left( \phi, c \right) dV,$ \newline $ \frac{ \partial \phi } { \partial t } = -M_{ \phi } \frac{ \delta \mathcal{F} } { \delta \phi }$', - {'color' : 'b', 'fontsize' : 20}) - -pylab.show() diff --git a/examples/pylab_examples/legend_auto.py b/examples/pylab_examples/legend_auto.py deleted file mode 100644 index bcab13df3375..000000000000 --- a/examples/pylab_examples/legend_auto.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -This file was written to test matplotlib's autolegend placement -algorithm, but shows lots of different ways to create legends so is -useful as a general examples - -Thanks to John Gill and Phil ?? for help at the matplotlib sprint at -pycon 2005 where the auto-legend support was written. -""" -from pylab import * -import sys - -rcParams['legend.loc'] = 'best' - -N = 100 -x = arange(N) - -def fig_1(): - figure(1) - t = arange(0, 40.0 * pi, 0.1) - l, = plot(t, 100*sin(t), 'r', label='sine') - legend(framealpha=0.5) - -def fig_2(): - figure(2) - plot(x, 'o', label='x=y') - legend() - -def fig_3(): - figure(3) - plot(x, -x, 'o', label='x= -y') - legend() - -def fig_4(): - figure(4) - plot(x, ones(len(x)), 'o', label='y=1') - plot(x, -ones(len(x)), 'o', label='y=-1') - legend() - -def fig_5(): - figure(5) - n, bins, patches = hist(randn(1000), 40, normed=1) - l, = plot(bins, normpdf(bins, 0.0, 1.0), 'r--', label='fit', linewidth=3) - legend([l, patches[0]], ['fit', 'hist']) - -def fig_6(): - figure(6) - plot(x, 50-x, 'o', label='y=1') - plot(x, x-50, 'o', label='y=-1') - legend() - -def fig_7(): - figure(7) - xx = x - (N/2.0) - plot(xx, (xx*xx)-1225, 'bo', label='$y=x^2$') - plot(xx, 25*xx, 'go', label='$y=25x$') - plot(xx, -25*xx, 'mo', label='$y=-25x$') - legend() - -def fig_8(): - figure(8) - b1 = bar(x, x, color='m') - b2 = bar(x, x[::-1], color='g') - legend([b1[0], b2[0]], ['up', 'down']) - -def fig_9(): - figure(9) - b1 = bar(x, -x) - b2 = bar(x, -x[::-1], color='r') - legend([b1[0], b2[0]], ['down', 'up']) - -def fig_10(): - figure(10) - b1 = bar(x, x, bottom=-100, color='m') - b2 = bar(x, x[::-1], bottom=-100, color='g') - b3 = bar(x, -x, bottom=100) - b4 = bar(x, -x[::-1], bottom=100, color='r') - legend([b1[0], b2[0], b3[0], b4[0]], ['bottom right', 'bottom left', - 'top left', 'top right']) - -if __name__ == '__main__': - nfigs = 10 - figures = [] - for f in sys.argv[1:]: - try: - figures.append(int(f)) - except ValueError: - pass - if len(figures) == 0: - figures = range(1, nfigs+1) - - for fig in figures: - fn_name = "fig_%d" % fig - fn = globals()[fn_name] - fn() - - show() diff --git a/examples/pylab_examples/legend_demo.py b/examples/pylab_examples/legend_demo.py deleted file mode 100644 index fefc97ea7937..000000000000 --- a/examples/pylab_examples/legend_demo.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Demo of the legend function with a few features. - -In addition to the basic legend, this demo shows a few optional features: - - * Custom legend placement. - * A keyword argument to a drop-shadow. - * Setting the background color. - * Setting the font size. - * Setting the line width. -""" -import numpy as np -import matplotlib.pyplot as plt - - -# Example data -a = np.arange(0,3, .02) -b = np.arange(0,3, .02) -c = np.exp(a) -d = c[::-1] - -# Create plots with pre-defined labels. -# Alternatively, you can pass labels explicitly when calling `legend`. -fig, ax = plt.subplots() -ax.plot(a, c, 'k--', label='Model length') -ax.plot(a, d, 'k:', label='Data length') -ax.plot(a, c+d, 'k', label='Total message length') - -# Now add the legend with some customizations. -legend = ax.legend(loc='upper center', shadow=True) - -# The frame is matplotlib.patches.Rectangle instance surrounding the legend. -frame = legend.get_frame() -frame.set_facecolor('0.90') - -# Set the fontsize -for label in legend.get_texts(): - label.set_fontsize('large') - -for label in legend.get_lines(): - label.set_linewidth(1.5) # the legend line width -plt.show() diff --git a/examples/pylab_examples/legend_demo3.py b/examples/pylab_examples/legend_demo3.py index 77e2e0f9902e..21b7be8c8056 100644 --- a/examples/pylab_examples/legend_demo3.py +++ b/examples/pylab_examples/legend_demo3.py @@ -1,33 +1,22 @@ -#!/usr/bin/env python -import matplotlib -matplotlib.rcParams['legend.fancybox'] = True import matplotlib.pyplot as plt import numpy as np -def myplot(ax): - t1 = np.arange(0.0, 1.0, 0.1) - for n in [1, 2, 3, 4]: - ax.plot(t1, t1**n, label="n=%d"%(n,)) +x = np.linspace(0, 1) -ax1 = plt.subplot(3,1,1) -ax1.plot([1], label="multi\nline") -ax1.plot([1], label="$2^{2^2}$") -ax1.plot([1], label=r"$\frac{1}{2}\pi$") -ax1.legend(loc=1, ncol=3, shadow=True) +# Plot the lines y=x**n for n=1..4. +ax = plt.subplot(2, 1, 1) +for n in range(1, 5): + plt.plot(x, x**n, label="n={}".format(n)) +plt.legend(loc="upper left", bbox_to_anchor=[0, 1], + ncol=2, shadow=True, title="Legend", fancybox=True) +ax.get_legend().get_title().set_color("red") -ax2 = plt.subplot(3,1,2) -myplot(ax2) -ax2.legend(loc="center left", bbox_to_anchor=[0.5, 0.5], - ncol=2, shadow=True, title="Legend") -ax2.get_legend().get_title().set_color("red") +# Demonstrate some more complex labels. +ax = plt.subplot(2, 1, 2) +plt.plot(x, x**2, label="multi\nline") +half_pi = np.linspace(0, np.pi / 2) +plt.plot(np.sin(half_pi), np.cos(half_pi), label=r"$\frac{1}{2}\pi$") +plt.plot(x, 2**(x**2), label="$2^{x^2}$") +plt.legend(shadow=True, fancybox=True) -ax3 = plt.subplot(3,1,3) -myplot(ax3) -ax3.legend(shadow=True, fancybox=True) - - -plt.draw() plt.show() - - - diff --git a/examples/pylab_examples/legend_demo4.py b/examples/pylab_examples/legend_demo4.py index b3adb1b9bc3a..d7437e8f3c0f 100644 --- a/examples/pylab_examples/legend_demo4.py +++ b/examples/pylab_examples/legend_demo4.py @@ -1,31 +1,21 @@ import matplotlib.pyplot as plt -ax = plt.subplot(311) - -b1 = ax.bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, - label="Bar 1", align="center") - -b2 = ax.bar([0.5, 1.5, 2.5], [0.3, 0.2, 0.2], color="red", width=0.4, - label="Bar 2", align="center") - -ax.legend() - -ax = plt.subplot(312) - -err1 = ax.errorbar([0, 1, 2], [2, 3, 1], xerr=0.4, fmt="s", - label="test 1") -err2 = ax.errorbar([0, 1, 2], [3, 2, 4], yerr=0.3, fmt="o", - label="test 2") -err3 = ax.errorbar([0, 1, 2], [1, 1, 3], xerr=0.4, yerr=0.3, fmt="^", +fig, axes = plt.subplots(3, 1) +top_ax, middle_ax, bottom_ax = axes + +top_ax.bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, label="Bar 1", + align="center") +top_ax.bar([0.5, 1.5, 2.5], [0.3, 0.2, 0.2], color="red", width=0.4, + label="Bar 2", align="center") +top_ax.legend() + +middle_ax.errorbar([0, 1, 2], [2, 3, 1], xerr=0.4, fmt="s", label="test 1") +middle_ax.errorbar([0, 1, 2], [3, 2, 4], yerr=0.3, fmt="o", label="test 2") +middle_ax.errorbar([0, 1, 2], [1, 1, 3], xerr=0.4, yerr=0.3, fmt="^", label="test 3") +middle_ax.legend() -ax.legend() - -ax = plt.subplot(313) - -ll = ax.stem([0.3, 1.5, 2.7], [1, 3.6, 2.7], label="stem test") - -ax.legend() +bottom_ax.stem([0.3, 1.5, 2.7], [1, 3.6, 2.7], label="stem test") +bottom_ax.legend() plt.show() - diff --git a/examples/pylab_examples/legend_demo_custom_handler.py b/examples/pylab_examples/legend_demo_custom_handler.py deleted file mode 100644 index 4d5e9e59def3..000000000000 --- a/examples/pylab_examples/legend_demo_custom_handler.py +++ /dev/null @@ -1,30 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np - -ax = plt.subplot(111, aspect=1) -x, y = np.random.randn(2, 20) -#[1.1, 2, 2.8], [1.1, 2, 1.8] -l1, = ax.plot(x,y, "k+", mew=3, ms=12) -l2, = ax.plot(x,y, "w+", mew=1, ms=10) - -import matplotlib.patches as mpatches -c = mpatches.Circle((0, 0), 1, fc="g", ec="r", lw=3) -ax.add_patch(c) - - - -from matplotlib.legend_handler import HandlerPatch - -def make_legend_ellipse(legend, orig_handle, - xdescent, ydescent, - width, height, fontsize): - p = mpatches.Ellipse(xy=(0.5*width-0.5*xdescent, 0.5*height-0.5*ydescent), - width = width+xdescent, height=(height+ydescent)) - - return p - -plt.legend([c, (l1, l2)], ["Label 1", "Label 2"], - handler_map={mpatches.Circle:HandlerPatch(patch_func=make_legend_ellipse), - }) - -plt.show() diff --git a/examples/pylab_examples/legend_scatter.py b/examples/pylab_examples/legend_scatter.py deleted file mode 100644 index 81eb32e37b45..000000000000 --- a/examples/pylab_examples/legend_scatter.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -from pylab import * - -N=1000 - -props = dict( alpha=0.5, edgecolors='none' ) - -handles = [] -colours = ['red', 'green', 'blue', 'magenta', 'cyan', 'yellow'] -colours = ['red', 'green', 'blue'] -for colour in colours: - x, y = rand(2,N) - s = 400.0 * rand(N) - handles.append(scatter(x, y, c=colour, s=s, **props)) - -legend(handles, colours) -grid(True) - -show() - - diff --git a/examples/pylab_examples/legend_translucent.py b/examples/pylab_examples/legend_translucent.py deleted file mode 100644 index 577b0f96cfec..000000000000 --- a/examples/pylab_examples/legend_translucent.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/python -# -# Show how to add a translucent legend - -# import pyplot module -import matplotlib.pyplot as plt - -# draw 2 crossing lines -plt.plot([0,1], label='going up') -plt.plot([1,0], label='going down') - -# add the legend in the middle of the plot -leg = plt.legend(fancybox=True, loc='center') -# set the alpha value of the legend: it will be translucent -leg.get_frame().set_alpha(0.5) - -# show the plot -plt.show() diff --git a/examples/tests/backend_driver.py b/examples/tests/backend_driver.py index 0edf4332e586..1256d47a1d2c 100755 --- a/examples/tests/backend_driver.py +++ b/examples/tests/backend_driver.py @@ -56,6 +56,7 @@ 'fill_demo.py', 'fill_demo_features.py', 'line_demo_dash_control.py', + 'scatter_with_legend.py' ] files['shapes'] = [ @@ -188,11 +189,8 @@ 'interp_demo.py', 'invert_axes.py', 'layer_images.py', - 'legend_auto.py', - 'legend_demo.py', 'legend_demo2.py', 'legend_demo3.py', - 'legend_scatter.py', 'line_collection.py', 'line_collection2.py', 'line_styles.py', diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f39325cfb480..bf9cb3b1dfa4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -210,25 +210,25 @@ def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs): return self.yaxis.set_label_text(ylabel, fontdict, **kwargs) def _get_legend_handles(self, legend_handler_map=None): - "return artists that will be used as handles for legend" + """ + Return a generator of artists that can be used as handles in + a legend. + + """ handles_original = (self.lines + self.patches + self.collections + self.containers) - - # collections handler_map = mlegend.Legend.get_default_handler_map() if legend_handler_map is not None: handler_map = handler_map.copy() handler_map.update(legend_handler_map) - handles = [] - for h in handles_original: - if h.get_label() == "_nolegend_": # .startswith('_'): - continue - if mlegend.Legend.get_legend_handler(handler_map, h): - handles.append(h) + has_handler = mlegend.Legend.get_legend_handler - return handles + for handle in handles_original: + label = handle.get_label() + if label != '_nolegend_' and has_handler(handler_map, handle): + yield handle def get_legend_handles_labels(self, legend_handler_map=None): """ @@ -240,7 +240,6 @@ def get_legend_handles_labels(self, legend_handler_map=None): ax.legend(h, l) """ - handles = [] labels = [] for handle in self._get_legend_handles(legend_handler_map): @@ -253,205 +252,261 @@ def get_legend_handles_labels(self, legend_handler_map=None): def legend(self, *args, **kwargs): """ - Place a legend on the current axes. - - Call signature:: - - legend(*args, **kwargs) - - Places legend at location *loc*. Labels are a sequence of - strings and *loc* can be a string or an integer specifying the - legend location. - - To make a legend with existing lines:: - - legend() - - :meth:`legend` by itself will try and build a legend using the label - property of the lines/patches/collections. You can set the label of - a line by doing:: - - plot(x, y, label='my data') - - or:: - - line.set_label('my data'). - - If label is set to '_nolegend_', the item will not be shown in - legend. - - To automatically generate the legend from labels:: - - legend( ('label1', 'label2', 'label3') ) - - To make a legend for a list of lines and labels:: - - legend( (line1, line2, line3), ('label1', 'label2', 'label3') ) - - To make a legend at a given location, using a location argument:: - - legend( ('label1', 'label2', 'label3'), loc='upper left') - - or:: - - legend((line1, line2, line3), ('label1', 'label2', 'label3'), loc=2) - - The location codes are - - =============== ============= - Location String Location Code - =============== ============= - 'best' 0 - 'upper right' 1 - 'upper left' 2 - 'lower left' 3 - 'lower right' 4 - 'right' 5 - 'center left' 6 - 'center right' 7 - 'lower center' 8 - 'upper center' 9 - 'center' 10 - =============== ============= - + Places a legend on the axes. - Users can specify any arbitrary location for the legend using the - *bbox_to_anchor* keyword argument. bbox_to_anchor can be an instance - of BboxBase(or its derivatives) or a tuple of 2 or 4 floats. - For example:: + To make a legend for lines which already exist on the axes + (via plot for instance), simply call this function with an iterable + of strings, one for each legend item. For example:: - loc = 'upper right', bbox_to_anchor = (0.5, 0.5) + ax.plot([1, 2, 3]) + ax.legend(['A simple line']) - will place the legend so that the upper right corner of the legend at - the center of the axes. + However, in order to keep the "label" and the legend element + instance together, it is preferable to specify the label either at + artist creation, or by calling the + :meth:`~matplotlib.artist.Artist.set_label` method on the artist:: - The legend location can be specified in other coordinate, by using the - *bbox_transform* keyword. + line, = ax.plot([1, 2, 3], label='Inline label') + # Overwrite the label by calling the method. + line.set_label('Label via method') + ax.legend() - The loc itslef can be a 2-tuple giving x,y of the lower-left corner of - the legend in axes coords (*bbox_to_anchor* is ignored). + Specific lines can be excluded from the automatic legend element + selection by defining a label starting with an underscore. + This is default for all artists, so calling :meth:`legend` without + any arguments and without setting the labels manually will result in + no legend being drawn. - Keyword arguments: - - *prop*: [ *None* | FontProperties | dict ] - A :class:`matplotlib.font_manager.FontProperties` - instance. If *prop* is a dictionary, a new instance will be - created with *prop*. If *None*, use rc settings. - - *fontsize*: [size in points | 'xx-small' | 'x-small' | 'small' | - 'medium' | 'large' | 'x-large' | 'xx-large'] - Set the font size. May be either a size string, relative to - the default font size, or an absolute font size in points. This - argument is only used if prop is not specified. - - *numpoints*: integer - The number of points in the legend for line - - *scatterpoints*: integer - The number of points in the legend for scatter plot - - *scatteryoffsets*: list of floats - a list of yoffsets for scatter symbols in legend - - *markerscale*: [ *None* | scalar ] - The relative size of legend markers vs. original. If *None*, - use rc settings. - - *frameon*: [ *True* | *False* ] - if *True*, draw a frame around the legend. - The default is set by the rcParam 'legend.frameon' - - *fancybox*: [ *None* | *False* | *True* ] - if *True*, draw a frame with a round fancybox. If *None*, - use rc settings - - *shadow*: [ *None* | *False* | *True* ] - If *True*, draw a shadow behind legend. If *None*, - use rc settings. - - *framealpha*: [*None* | float] - If not None, alpha channel for legend frame. Default *None*. - - *ncol* : integer - number of columns. default is 1 - - *mode* : [ "expand" | *None* ] - if mode is "expand", the legend will be horizontally expanded - to fill the axes area (or *bbox_to_anchor*) - - *bbox_to_anchor*: an instance of BboxBase or a tuple of 2 or 4 floats - the bbox that the legend will be anchored. + For full control of which artists have a legend entry, it is possible + to pass an iterable of legend artists followed by an iterable of + legend labels respectively:: - *bbox_transform* : [ an instance of Transform | *None* ] - the transform for the bbox. transAxes if *None*. + legend((line1, line2, line3), ('label1', 'label2', 'label3')) - *title* : string - the legend title - - Padding and spacing between various elements use following - keywords parameters. These values are measure in font-size - units. e.g., a fontsize of 10 points and a handlelength=5 - implies a handlelength of 50 points. Values from rcParams - will be used if None. - - ================ ==================================================== - Keyword Description - ================ ==================================================== - borderpad the fractional whitespace inside the legend border - labelspacing the vertical space between the legend entries - handlelength the length of the legend handles - handletextpad the pad between the legend handle and text - borderaxespad the pad between the axes and legend border - columnspacing the spacing between columns - ================ ==================================================== + Parameters + ---------- + loc : int or string or pair of floats, default: 0 + The location of the legend. Possible codes are: + + =============== ============= + Location String Location Code + =============== ============= + 'best' 0 + 'upper right' 1 + 'upper left' 2 + 'lower left' 3 + 'lower right' 4 + 'right' 5 + 'center left' 6 + 'center right' 7 + 'lower center' 8 + 'upper center' 9 + 'center' 10 + =============== ============= + + + Alternatively can be a 2-tuple giving ``x, y`` of the lower-left + corner of the legend in axes coordinates (in which case + ``bbox_to_anchor`` will be ignored). + + bbox_to_anchor : :class:`matplotlib.transforms.BboxBase` instance \ + or tuple of floats + Specify any arbitrary location for the legend in `bbox_transform` + coordinates (default Axes coordinates). + + For example, to put the legend's upper right hand corner in the + center of the axes the following keywords can be used:: + + loc='upper right', bbox_to_anchor=(0.5, 0.5) + + ncol : integer + The number of columns that the legend has. Default is 1. + + prop : None or :class:`matplotlib.font_manager.FontProperties` or dict + The font properties of the legend. If None (default), the current + :data:`matplotlib.rcParams` will be used. + + fontsize : int or float or {'xx-small', 'x-small', 'small', 'medium',\ + 'large', 'x-large', 'xx-large'} + Controls the font size of the legend. If the value is numeric the + size will be the absolute font size in points. String values are + relative to the current default font size. This argument is only + used if `prop` is not specified. + + numpoints : None or int + The number of marker points in the legend when creating a legend + entry for a line/:class:`matplotlib.lines.Line2D`. + Default is ``None`` which will take the value from the + ``legend.numpoints`` :data:`rcParam`. + + scatterpoints : None or int + The number of marker points in the legend when creating a legend + entry for a scatter plot/ + :class:`matplotlib.collections.PathCollection`. + Default is ``None`` which will take the value from the + ``legend.scatterpoints`` :data:`rcParam`. + + scatteryoffsets : iterable of floats + The vertical offset (relative to the font size) for the markers + created for a scatter plot legend entry. 0.0 is at the base the + legend text, and 1.0 is at the top. To draw all markers at the + same height, set to ``[0.5]``. Default ``[0.375, 0.5, 0.3125]``. + + markerscale : None or int or float + The relative size of legend markers compared with the originally + drawn ones. Default is ``None`` which will take the value from + the ``legend.markerscale`` :data:`rcParam `. + + frameon : None or bool + Control whether a frame should be drawn around the legend. + Default is ``None`` which will take the value from the + ``legend.frameon`` :data:`rcParam`. + + fancybox : None or bool + Control whether round edges should be enabled around + the :class:`~matplotlib.patches.FancyBboxPatch` which + makes up the legend's background. + Default is ``None`` which will take the value from the + ``legend.fancybox`` :data:`rcParam`. + + shadow : None or bool + Control whether to draw a shadow behind the legend. + Default is ``None`` which will take the value from the + ``legend.shadow`` :data:`rcParam`. + + framealpha : None or float + Control the alpha transparency of the legend's frame. + Default is ``None`` which will take the value from the + ``legend.framealpha`` :data:`rcParam`. + + mode : {"expand", None} + If `mode` is set to ``"expand"`` the legend will be horizontally + expanded to fill the axes area (or `bbox_to_anchor` if defines + the legend's size). + + bbox_transform : None or :class:`matplotlib.transforms.Transform` + The transform for the bounding box (`bbox_to_anchor`). For a value + of ``None`` (default) the Axes' + :data:`~matplotlib.axes.Axes.transAxes` transform will be used. + + title : str or None + The legend's title. Default is no title (``None``). + + borderpad : float or None + The fractional whitespace inside the legend border. + Measured in font-size units. + Default is ``None`` which will take the value from the + ``legend.borderpad`` :data:`rcParam`. + + labelspacing : float or None + The vertical space between the legend entries. + Measured in font-size units. + Default is ``None`` which will take the value from the + ``legend.labelspacing`` :data:`rcParam`. + + handlelength : float or None + The length of the legend handles. + Measured in font-size units. + Default is ``None`` which will take the value from the + ``legend.handlelength`` :data:`rcParam`. + + handletextpad : float or None + The pad between the legend handle and text. + Measured in font-size units. + Default is ``None`` which will take the value from the + ``legend.handletextpad`` :data:`rcParam`. + + borderaxespad : float or None + The pad between the axes and legend border. + Measured in font-size units. + Default is ``None`` which will take the value from the + ``legend.borderaxespad`` :data:`rcParam`. + + columnspacing : float or None + The spacing between columns. + Measured in font-size units. + Default is ``None`` which will take the value from the + ``legend.columnspacing`` :data:`rcParam`. + + handler_map : dict or None + The custom dictionary mapping instances or types to a legend + handler. This `handler_map` updates the default handler map + found at :func:`matplotlib.legend.Legend.get_legend_handler_map`. - .. note:: + Notes + ----- Not all kinds of artist are supported by the legend command. See :ref:`plotting-guide-legend` for details. - **Example:** + Examples + -------- .. plot:: mpl_examples/api/legend_demo.py - .. seealso:: - :ref:`plotting-guide-legend`. - """ - - if len(args) == 0: - handles, labels = self.get_legend_handles_labels() - if len(handles) == 0: - warnings.warn("No labeled objects found. " + handlers = kwargs.get('handler_map', {}) or {} + + # Support handles and labels being passed as keywords. + handles = kwargs.pop('handles', None) + labels = kwargs.pop('labels', None) + + if handles is not None and labels is None: + labels = [handle.get_label() for handle in handles] + for label, handle in zip(labels[:], handles[:]): + if label.startswith('_'): + warnings.warn('The handle {!r} has a label of {!r} which ' + 'cannot be automatically added to the ' + 'legend.'.format(handle, label)) + labels.remove(label) + handles.remove(handle) + + elif labels is not None and handles is None: + # Get as many handles as there are labels. + handles = [handle for handle, _ + in zip(self._get_legend_handles(handlers), labels)] + + # No arguments - automatically detect labels and handles. + elif len(args) == 0: + handles, labels = self.get_legend_handles_labels(handlers) + if not handles: + warnings.warn("No labelled objects found. " "Use label='...' kwarg on individual plots.") return None + # One argument. User defined labels - automatic handle detection. elif len(args) == 1: - # LABELS - labels = args[0] - handles = [h for h, label in zip(self._get_legend_handles(), - labels)] - + labels, = args + # Get as many handles as there are labels. + handles = [handle for handle, _ + in zip(self._get_legend_handles(handlers), labels)] + + # Two arguments. Either: + # * user defined handles and labels + # * user defined labels and location (deprecated) elif len(args) == 2: if is_string_like(args[1]) or isinstance(args[1], int): - # LABELS, LOC + cbook.warn_deprecated('1.4', 'The "loc" positional argument ' + 'to legend is deprecated. Please use ' + 'the "loc" keyword instead.') labels, loc = args - handles = [h for h, label in zip(self._get_legend_handles(), - labels)] + handles = [handle for handle, _ + in zip(self._get_legend_handles(handlers), labels)] kwargs['loc'] = loc else: - # LINES, LABELS handles, labels = args + # Three arguments. User defined handles, labels and + # location (deprecated). elif len(args) == 3: - # LINES, LABELS, LOC + cbook.warn_deprecated('1.4', 'The "loc" positional argument ' + 'to legend is deprecated. Please ' + 'use the "loc" keyword instead.') handles, labels, loc = args kwargs['loc'] = loc - else: - raise TypeError('Invalid arguments to legend') - # Why do we need to call "flatten" here? -JJL - # handles = cbook.flatten(handles) + else: + raise TypeError('Invalid arguments to legend.') self.legend_ = mlegend.Legend(self, handles, labels, **kwargs) return self.legend_ diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 07fa8313c086..4ac3788a9900 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -2,14 +2,24 @@ The legend module defines the Legend class, which is responsible for drawing legends associated with axes and/or figures. +.. important:: + + It is unlikely that you would ever create a Legend instance manually. + Most users would normally create a legend via the + :meth:`~matplotlib.axes.Axes.legend` function. For more details on legends + there is also a :ref:`legend guide `. + The Legend class can be considered as a container of legend handles and legend texts. Creation of corresponding legend handles from the plot elements in the axes or figures (e.g., lines, patches, etc.) are specified by the handler map, which defines the mapping between the plot elements and the legend handlers to be used (the default legend -handlers are defined in the :mod:`~matplotlib.legend_handler` module). Note -that not all kinds of artist are supported by the legend yet (See -:ref:`plotting-guide-legend` for more information). +handlers are defined in the :mod:`~matplotlib.legend_handler` module). +Note that not all kinds of artist are supported by the legend yet by default +but it is possible to extend the legend handler's capabilities to +support arbitrary objects. See the :ref:`legend guide ` +for more information. + """ from __future__ import (absolute_import, division, print_function, unicode_literals) @@ -23,7 +33,8 @@ from matplotlib import rcParams from matplotlib.artist import Artist, allow_rasterization -from matplotlib.cbook import is_string_like, iterable, silent_list, safezip +from matplotlib.cbook import (is_string_like, iterable, silent_list, safezip, + warn_deprecated) from matplotlib.font_manager import FontProperties from matplotlib.lines import Line2D from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch @@ -243,39 +254,25 @@ def __init__(self, parent, handles, labels, self._fontsize = self.prop.get_size_in_points() - propnames = ["numpoints", "markerscale", "shadow", "columnspacing", - "scatterpoints", "handleheight"] - self.texts = [] self.legendHandles = [] self._legend_title_box = None - self._handler_map = handler_map + #: A dictionary with the extra handler mappings for this Legend + #: instance. + self._custom_handler_map = handler_map - localdict = locals() - - for name in propnames: - if localdict[name] is None: + locals_view = locals() + for name in ["numpoints", "markerscale", "shadow", "columnspacing", + "scatterpoints", "handleheight", 'borderpad', + 'labelspacing', 'handlelength', 'handletextpad', + 'borderaxespad']: + if locals_view[name] is None: value = rcParams["legend." + name] else: - value = localdict[name] + value = locals_view[name] setattr(self, name, value) - - # convert values of deprecated keywords (ginve in axes coords) - # to new vaules in a fraction of the font size - - # conversion factor - bbox = parent.bbox - axessize_fontsize = min(bbox.width, bbox.height) / self._fontsize - - for v in ['borderpad', 'labelspacing', 'handlelength', - 'handletextpad', 'borderaxespad']: - if localdict[v] is None: - setattr(self, v, rcParams["legend." + v]) - else: - setattr(self, v, localdict[v]) - - del localdict + del locals_view handles = list(handles) if len(handles) < 2: @@ -371,14 +368,11 @@ def __init__(self, parent, handles, labels, self._init_legend_box(handles, labels) if framealpha is not None: - self.get_frame().set_alpha(framealpha) + self.get_frame().set_alpha(framealpha) self._loc = loc - self.set_title(title) - self._last_fontsize_points = self._fontsize - self._draggable = None def _set_artist_props(self, a): @@ -495,7 +489,7 @@ def _approx_text_height(self, renderer=None): } # (get|set|update)_default_handler_maps are public interfaces to - # modify the defalut handler map. + # modify the default handler map. @classmethod def get_default_handler_map(cls): @@ -525,9 +519,9 @@ def get_legend_handler_map(self): default_handler_map = self.get_default_handler_map() - if self._handler_map: + if self._custom_handler_map: hm = default_handler_map.copy() - hm.update(self._handler_map) + hm.update(self._custom_handler_map) return hm else: return default_handler_map @@ -593,50 +587,57 @@ def _init_legend_box(self, handles, labels): # The approximate height and descent of text. These values are # only used for plotting the legend handle. descent = 0.35 * self._approx_text_height() * (self.handleheight - 0.7) - # 0.35 and 0.7 are just heuristic numbers. this may need to be improbed + # 0.35 and 0.7 are just heuristic numbers and may need to be improved. height = self._approx_text_height() * self.handleheight - descent # each handle needs to be drawn inside a box of (x, y, w, h) = # (0, -descent, width, height). And their coordinates should # be given in the display coordinates. # The transformation of each handle will be automatically set - # to self.get_trasnform(). If the artist does not uses its - # default trasnform (eg, Collections), you need to + # to self.get_trasnform(). If the artist does not use its + # default transform (e.g., Collections), you need to # manually set their transform to the self.get_transform(). - legend_handler_map = self.get_legend_handler_map() for orig_handle, lab in zip(handles, labels): - handler = self.get_legend_handler(legend_handler_map, orig_handle) - if handler is None: warnings.warn( - "Legend does not support %s\nUse proxy artist " - "instead.\n\n" - "http://matplotlib.sourceforge.net/users/legend_guide.html#using-proxy-artist\n" % - (str(orig_handle),)) + "Legend does not support {!r} instances.\nA proxy artist " + "may be used instead.\nSee: " + "http://matplotlib.org/users/legend_guide.html" + "#using-proxy-artist".format(orig_handle)) + # We don't have a handle for this artist, so we just defer + # to None. handle_list.append(None) - continue - - textbox = TextArea(lab, textprops=label_prop, - multilinebaseline=True, minimumdescent=True) - text_list.append(textbox._text) - - labelboxes.append(textbox) - - handlebox = DrawingArea(width=self.handlelength * fontsize, - height=height, - xdescent=0., ydescent=descent) - - handle = handler(self, orig_handle, - #xdescent, ydescent, width, height, - fontsize, - handlebox) - - handle_list.append(handle) - - handleboxes.append(handlebox) + else: + textbox = TextArea(lab, textprops=label_prop, + multilinebaseline=True, + minimumdescent=True) + text_list.append(textbox._text) + + labelboxes.append(textbox) + + handlebox = DrawingArea(width=self.handlelength * fontsize, + height=height, + xdescent=0., ydescent=descent) + handleboxes.append(handlebox) + + # Deprecate the old behaviour of accepting callable + # legend handlers in favour of the "legend_artist" + # interface. + if (not hasattr(handler, 'legend_artist') and + callable(handler)): + handler.legend_artist = handler.__call__ + warn_deprecated('1.4', + ('Legend handers must now implement a ' + '"legend_artist" method rather than ' + 'being a callable.')) + + # Create the artist for the legend which represents the + # original artist/handle. + handle_list.append(handler.legend_artist(self, orig_handle, + fontsize, handlebox)) if len(handleboxes) > 0: @@ -682,22 +683,17 @@ def _init_legend_box(self, handles, labels): mode = "fixed" sep = self.columnspacing * fontsize - self._legend_handle_box = HPacker(pad=0, sep=sep, align="baseline", mode=mode, children=columnbox) - self._legend_title_box = TextArea("") - self._legend_box = VPacker(pad=self.borderpad * fontsize, sep=self.labelspacing * fontsize, align="center", children=[self._legend_title_box, self._legend_handle_box]) - self._legend_box.set_figure(self.figure) - self.texts = text_list self.legendHandles = handle_list diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index 28d84167f637..f1833992a742 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -1,6 +1,9 @@ """ This module defines default legend handlers. +It is strongly encouraged to have read the :ref:`legend guide +` before this documentation. + Legend handlers are expected to be a callable object with a following signature. :: @@ -15,11 +18,9 @@ i.e., this is dpi-scaled value). This module includes definition of several legend handler classes -derived from the base class (HandlerBase) with a following method. +derived from the base class (HandlerBase) with the following method. - def __call__(self, legend, orig_handle, - fontsize, - handlebox): + def legend_artist(self, legend, orig_handle, fontsize, handlebox): """ @@ -86,38 +87,46 @@ def adjust_drawing_area(self, legend, orig_handle, height = height - self._ypad * fontsize return xdescent, ydescent, width, height - def __call__(self, legend, orig_handle, - fontsize, - handlebox): - """ - x, y, w, h in display coordinate w/ default dpi (72) - fontsize in points + def legend_artist(self, legend, orig_handle, + fontsize, handlebox): """ - width, height, xdescent, ydescent = handlebox.width, \ - handlebox.height, \ - handlebox.xdescent, \ - handlebox.ydescent + Return the artist that this HandlerBase generates for the given + original artist/handle. + + Parameters + ---------- + legend : :class:`matplotlib.legend.Legend` instance + The legend for which these legend artists are being created. + orig_handle : :class:`matplotlib.artist.Artist` or similar + The object for which these legend artists are being created. + fontsize : float or int + The fontsize in pixels. The artists being created should + be scaled according to the given fontsize. + handlebox : :class:`matplotlib.offsetbox.OffsetBox` instance + The box which has been created to hold this legend entry's + artists. Artists created in the `legend_artist` method must + be added to this handlebox inside this method. - xdescent, ydescent, width, height = \ - self.adjust_drawing_area(legend, orig_handle, - xdescent, ydescent, width, height, - fontsize) - - a_list = self.create_artists(legend, orig_handle, - xdescent, ydescent, width, height, fontsize, - handlebox.get_transform()) + """ + xdescent, ydescent, width, height = self.adjust_drawing_area( + legend, orig_handle, + handlebox.xdescent, handlebox.ydescent, + handlebox.width, handlebox.height, + fontsize) + artists = self.create_artists(legend, orig_handle, + xdescent, ydescent, width, height, + fontsize, handlebox.get_transform()) # create_artists will return a list of artists. - for a in a_list: + for a in artists: handlebox.add_artist(a) # we only return the first artist - return a_list[0] + return artists[0] def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): - raise NotImplementedError('Derived must override') @@ -167,7 +176,7 @@ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize): class HandlerLine2D(HandlerNpoints): """ - Handler for Line2D instances + Handler for Line2D instances. """ def __init__(self, marker_pad=0.3, numpoints=None, **kw): HandlerNpoints.__init__(self, marker_pad=marker_pad, numpoints=numpoints, **kw) @@ -202,13 +211,26 @@ def create_artists(self, legend, orig_handle, return [legline, legline_marker] + class HandlerPatch(HandlerBase): """ - Handler for Patches + Handler for Patch instances. """ def __init__(self, patch_func=None, **kw): - HandlerBase.__init__(self, **kw) + """ + The HandlerPatch class optionally takes a function ``patch_func`` + who's responsibility is to create the legend key artist. The + ``patch_func`` should have the signature:: + + def patch_func(legend=legend, orig_handle=orig_handle, + xdescent=xdescent, ydescent=ydescent, + width=width, height=height, fontsize=fontsize) + Subsequently the created artist will have its ``update_prop`` method + called and the appropriate transform will be applied. + + """ + HandlerBase.__init__(self, **kw) self._patch_func = patch_func def _create_patch(self, legend, orig_handle, @@ -224,10 +246,8 @@ def _create_patch(self, legend, orig_handle, def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): - p = self._create_patch(legend, orig_handle, xdescent, ydescent, width, height, fontsize) - self.update_prop(p, orig_handle, legend) p.set_transform(trans) return [p] @@ -235,7 +255,7 @@ def create_artists(self, legend, orig_handle, class HandlerLineCollection(HandlerLine2D): """ - Handler for LineCollections + Handler for LineCollection instances. """ def get_numpoints(self, legend): if self._numpoints is None: diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 655fba1b9987..21aa2664b50c 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -3,22 +3,27 @@ import six -from matplotlib.testing.noseclasses import KnownFailureTest, \ - KnownFailureDidNotFailTest, ImageComparisonFailure -import os, sys, shutil +import functools +import os +import sys +import shutil +import warnings +import unittest + import nose -import matplotlib +import numpy as np + import matplotlib.tests import matplotlib.units from matplotlib import cbook from matplotlib import ticker from matplotlib import pyplot as plt from matplotlib import ft2font -import numpy as np +from matplotlib.testing.noseclasses import KnownFailureTest, \ + KnownFailureDidNotFailTest, ImageComparisonFailure from matplotlib.testing.compare import comparable_formats, compare_images, \ make_test_filename -import warnings -import unittest + def knownfailureif(fail_condition, msg=None, known_exception_class=None ): """ @@ -98,14 +103,21 @@ def tearDownClass(cls): def cleanup(func): - name = func.__name__ - func = staticmethod(func) - func.__get__(1).__name__ = str('_private') - new_class = type( - name, - (CleanupTest,), - {'_func': func}) - return new_class + @functools.wraps(func) + def wrapped_function(*args, **kwargs): + original_units_registry = matplotlib.units.registry.copy() + try: + func(*args, **kwargs) + finally: + plt.close('all') + + matplotlib.tests.setup() + + matplotlib.units.registry.clear() + matplotlib.units.registry.update(original_units_registry) + warnings.resetwarnings() #reset any warning filters set in tests + return wrapped_function + def check_freetype_version(ver): if ver is None: diff --git a/lib/matplotlib/tests/test_bbox_tight.py b/lib/matplotlib/tests/test_bbox_tight.py index a46655926684..b0041a24fdd7 100644 --- a/lib/matplotlib/tests/test_bbox_tight.py +++ b/lib/matplotlib/tests/test_bbox_tight.py @@ -17,7 +17,7 @@ @image_comparison(baseline_images=['bbox_inches_tight'], remove_text=True, savefig_kwarg=dict(bbox_inches='tight'), tol=15) def test_bbox_inches_tight(): - #: Test that a figure saved using bbox_inches='tight' is clipped right + #: Test that a figure saved using bbox_inches='tight' is clipped correctly data = [[ 66386, 174296, 75131, 577908, 32015], [ 58230, 381139, 78045, 99308, 160454], [ 89135, 80552, 152558, 497981, 603535], diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 66cb861c50e5..15dadeb6c75f 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -4,11 +4,16 @@ import six from six.moves import xrange +import mock +from nose.tools import assert_equal import numpy as np -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, cleanup +from matplotlib.cbook import MatplotlibDeprecationWarning import matplotlib.pyplot as plt import matplotlib as mpl +import matplotlib.patches as mpatches + @image_comparison(baseline_images=['legend_auto1'], remove_text=True) def test_legend_auto1(): @@ -66,6 +71,7 @@ def test_fancy(): plt.legend(loc="center left", bbox_to_anchor=[1.0, 0.5], ncol=2, shadow=True, title="My legend", numpoints=1) + @image_comparison(baseline_images=['framealpha'], remove_text=True) def test_framealpha(): x = np.linspace(1, 100, 100) @@ -73,6 +79,7 @@ def test_framealpha(): plt.plot(x, y, label='mylabel', lw=10) plt.legend(framealpha=0.5) + @image_comparison(baseline_images=['scatter_rc3','scatter_rc1'], remove_text=True) def test_rc(): # using subplot triggers some offsetbox functionality untested elsewhere @@ -82,7 +89,6 @@ def test_rc(): ax.legend(loc="center left", bbox_to_anchor=[1.0, 0.5], title="My legend") - mpl.rcParams['legend.scatterpoints'] = 1 fig = plt.figure() ax = plt.subplot(121) @@ -90,6 +96,7 @@ def test_rc(): ax.legend(loc="center left", bbox_to_anchor=[1.0, 0.5], title="My legend") + @image_comparison(baseline_images=['legend_expand'], remove_text=True) def test_legend_expand(): 'Test expand mode' @@ -104,3 +111,97 @@ def test_legend_expand(): l2 = ax.legend(loc=5, mode=mode) ax.add_artist(l2) ax.legend(loc=3, mode=mode, ncol=2) + + +class TestLegendFunction(object): + # Tests the legend function on the Axes and pyplot. + + deprecation_message = ('The "loc" positional argument ' + 'to legend is deprecated. Please use ' + 'the "loc" keyword instead.') + + @cleanup + def test_legend_label_loc_args(self): + # Check the deprecated warning is created and that the appropriate + # call to Legend is made. This wouldn't actually create a valid + # legend as there is no artist to legendify, but that doesn't matter. + with mock.patch('matplotlib.cbook.warn_deprecated') as deprecation: + with mock.patch('matplotlib.legend.Legend') as Legend: + plt.legend(['hello world'], 1) + + deprecation.assert_called_with('1.4', self.deprecation_message) + Legend.assert_called_with(plt.gca(), [], ['hello world'], loc=1) + + @cleanup + def test_old_legend_handler_interface(self): + # Check the deprecated warning is created and that the appropriate + # call to the legend handler is made. + class AnyObject(object): + pass + + class AnyObjectHandler(object): + def __call__(self, legend, orig_handle, fontsize, handlebox): + x0, y0 = handlebox.xdescent, handlebox.ydescent + width, height = handlebox.width, handlebox.height + patch = mpatches.Rectangle([x0, y0], width, height, facecolor='red', + edgecolor='black', hatch='xx', lw=3, + transform=handlebox.get_transform()) + handlebox.add_artist(patch) + return patch + + with mock.patch('warnings.warn') as warn: + plt.legend([None], ['My first handler'], + handler_map={None: AnyObjectHandler()}) + + warn.assert_called_with(u'Legend handers must now implement a ' + '"legend_artist" method rather than ' + 'being a callable.', + MatplotlibDeprecationWarning, + stacklevel=1) + + @cleanup + def test_legend_handle_label_loc_args(self): + # Check the deprecated warning is created and that the appropriate + # call to Legend is made. + lines = plt.plot(range(10)) + with mock.patch('matplotlib.cbook.warn_deprecated') as deprecation: + with mock.patch('matplotlib.legend.Legend') as Legend: + plt.legend(lines, ['hello world'], 1) + + deprecation.assert_called_with('1.4', self.deprecation_message) + Legend.assert_called_with(plt.gca(), lines, ['hello world'], loc=1) + + @cleanup + def test_legend_handle_label(self): + lines = plt.plot(range(10)) + with mock.patch('matplotlib.legend.Legend') as Legend: + plt.legend(lines, ['hello world']) + Legend.assert_called_with(plt.gca(), lines, ['hello world']) + + @cleanup + def test_legend_no_args(self): + lines = plt.plot(range(10), label='hello world') + with mock.patch('matplotlib.legend.Legend') as Legend: + plt.legend() + Legend.assert_called_with(plt.gca(), lines, ['hello world']) + + @cleanup + def test_legend_label_args(self): + lines = plt.plot(range(10), label='hello world') + with mock.patch('matplotlib.legend.Legend') as Legend: + plt.legend(['foobar']) + Legend.assert_called_with(plt.gca(), lines, ['foobar']) + + @cleanup + def test_legend_handler_map(self): + lines = plt.plot(range(10), label='hello world') + with mock.patch('matplotlib.axes.Axes.' + 'get_legend_handles_labels') as handles_labels: + handles_labels.return_value = lines, ['hello world'] + plt.legend(handler_map={'1': 2}) + handles_labels.assert_called_with({'1': 2}) + + +if __name__ == '__main__': + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 88b5bd79a010..0fe30798d463 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -86,3 +86,7 @@ def test_nan_is_sorted(): assert_true(line._is_sorted(np.array([1,2,3]))) assert_true(not line._is_sorted(np.array([1,np.nan,3]))) + +if __name__ == '__main__': + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index d56874ae9798..744da15a1377 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -210,3 +210,8 @@ def test_mathtext_exceptions(): assert exc[3].startswith(msg) else: assert False, "Expected '%s', but didn't get it" % msg + + +if __name__ == '__main__': + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_mlab.py b/lib/matplotlib/tests/test_mlab.py index b6a20fafc3ea..3a863edb0a21 100644 --- a/lib/matplotlib/tests/test_mlab.py +++ b/lib/matplotlib/tests/test_mlab.py @@ -3,13 +3,16 @@ import six +import tempfile + +from numpy.testing import assert_allclose, assert_array_equal import numpy as np +from nose.tools import (assert_equal, assert_almost_equal, assert_not_equal, + assert_true, assert_raises) + import matplotlib.mlab as mlab import matplotlib.cbook as cbook -import tempfile -import unittest -from nose.tools import assert_raises -from matplotlib.testing.decorators import knownfailureif +from matplotlib.testing.decorators import knownfailureif, CleanupTestCase try: @@ -18,13 +21,6 @@ except ImportError: HAS_NATGRID = False -from numpy.testing import assert_allclose, assert_array_equal - -from nose.tools import (assert_equal, assert_almost_equal, assert_not_equal, - assert_true, assert_raises,) - -from matplotlib.testing.decorators import CleanupTestCase - class general_testcase(CleanupTestCase): def test_colinear_pca(self):