Skip to content

Commit

Permalink
Merge pull request #3499 from cimarronm/legend_marker_label_placement
Browse files Browse the repository at this point in the history
ENH : Legend marker label placement control
  • Loading branch information
tacaswell committed Nov 6, 2014
2 parents 68176eb + d7210ff commit 2b2016a
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 25 deletions.
6 changes: 6 additions & 0 deletions doc/users/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ revision, see the :ref:`github-stats`.
new in matplotlib-1.5
=====================

Legend
------
Added ability to place the label before the marker in a legend box with
``markerfirst`` keyword


Widgets
-------

Expand Down
5 changes: 5 additions & 0 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,11 @@ def legend(self, *args, **kwargs):
drawn ones. Default is ``None`` which will take the value from
the ``legend.markerscale`` :data:`rcParam <matplotlib.rcParams>`.
*markerfirst*: [ *True* | *False* ]
if *True*, legend marker is placed to the left of the legend label
if *False*, legend marker is placed to the right of the legend
label
frameon : None or bool
Control whether a frame should be drawn around the legend.
Default is ``None`` which will take the value from the
Expand Down
5 changes: 5 additions & 0 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,11 @@ def legend(self, handles, labels, *args, **kwargs):
The relative size of legend markers vs. original. If *None*, use rc
settings.
*markerfirst*: [ *True* | *False* ]
if *True*, legend marker is placed to the left of the legend label
if *False*, legend marker is placed to the right of the legend
label
*fancybox*: [ *None* | *False* | *True* ]
if *True*, draw a frame with a round fancybox. If *None*, use rc
Expand Down
64 changes: 40 additions & 24 deletions lib/matplotlib/legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@
from matplotlib.font_manager import FontProperties
from matplotlib.lines import Line2D
from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch
from matplotlib.collections import LineCollection, RegularPolyCollection, \
CircleCollection, PathCollection, PolyCollection
from matplotlib.collections import (LineCollection, RegularPolyCollection,
CircleCollection, PathCollection,
PolyCollection)
from matplotlib.transforms import Bbox, BboxBase, TransformedBbox
from matplotlib.transforms import BboxTransformTo, BboxTransformFrom

Expand Down Expand Up @@ -93,7 +94,8 @@ def _update_loc(self, loc_in_canvas):

_bbox_transform = BboxTransformFrom(bbox)
self.legend._loc = tuple(
_bbox_transform.transform_point(loc_in_canvas))
_bbox_transform.transform_point(loc_in_canvas)
)

def _update_bbox_to_anchor(self, loc_in_canvas):

Expand Down Expand Up @@ -150,6 +152,8 @@ def __init__(self, parent, handles, labels,
numpoints=None, # the number of points in the legend line
markerscale=None, # the relative size of legend markers
# vs. original
markerfirst=True, # controls ordering (left-to-right) of
# legend marker and label
scatterpoints=None, # number of scatter points
scatteryoffsets=None,
prop=None, # properties for the legend texts
Expand All @@ -176,7 +180,7 @@ def __init__(self, parent, handles, labels,
shadow=None,
title=None, # set a title for the legend

framealpha=None, # set frame alpha
framealpha=None, # set frame alpha

bbox_to_anchor=None, # bbox that the legend will be anchored.
bbox_transform=None, # transform for the bbox
Expand All @@ -198,6 +202,8 @@ def __init__(self, parent, handles, labels,
prop the font property
fontsize the font size (used only if prop is not specified)
markerscale the relative size of legend markers vs. original
markerfirst If true, place legend marker to left of label
If false, place legend marker to right of label
numpoints the number of points in the legend for line
scatterpoints the number of points in the legend for scatter plot
scatteryoffsets a list of yoffsets for scatter symbols in legend
Expand Down Expand Up @@ -316,13 +322,15 @@ def __init__(self, parent, handles, labels,
if self.isaxes:
warnings.warn('Unrecognized location "%s". Falling back '
'on "best"; valid locations are\n\t%s\n'
% (loc, '\n\t'.join(six.iterkeys(self.codes))))
% (loc, '\n\t'.join(
six.iterkeys(self.codes))))
loc = 0
else:
warnings.warn('Unrecognized location "%s". Falling back '
'on "upper right"; '
'valid locations are\n\t%s\n'
% (loc, '\n\t'.join(six.iterkeys(self.codes))))
% (loc, '\n\t'.join(
six.iterkeys(self.codes))))
loc = 1
else:
loc = self.codes[loc]
Expand Down Expand Up @@ -365,7 +373,7 @@ def __init__(self, parent, handles, labels,
self._drawFrame = rcParams["legend.frameon"]

# init with null renderer
self._init_legend_box(handles, labels)
self._init_legend_box(handles, labels, markerfirst)

if framealpha is None:
self.get_frame().set_alpha(rcParams["legend.framealpha"])
Expand Down Expand Up @@ -396,8 +404,8 @@ def _set_loc(self, loc):
else:
_findoffset = self._findoffset_loc

#def findoffset(width, height, xdescent, ydescent):
# return _findoffset(width, height, xdescent, ydescent, renderer)
# def findoffset(width, height, xdescent, ydescent):
# return _findoffset(width, height, xdescent, ydescent, renderer)

self._legend_box.set_offset(_findoffset)

Expand Down Expand Up @@ -485,7 +493,7 @@ def _approx_text_height(self, renderer=None):
RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(),
CircleCollection: legend_handler.HandlerCircleCollection(),
BarContainer: legend_handler.HandlerPatch(
update_func=legend_handler.update_from_first_child),
update_func=legend_handler.update_from_first_child),
tuple: legend_handler.HandlerTuple(),
PathCollection: legend_handler.HandlerPathCollection(),
PolyCollection: legend_handler.HandlerPolyCollection()
Expand Down Expand Up @@ -558,7 +566,7 @@ def get_legend_handler(legend_handler_map, orig_handle):

return handler

def _init_legend_box(self, handles, labels):
def _init_legend_box(self, handles, labels, markerfirst=True):
"""
Initialize the legend_box. The legend_box is an instance of
the OffsetBox, which is packed with legend handles and
Expand Down Expand Up @@ -606,10 +614,11 @@ def _init_legend_box(self, handles, labels):
handler = self.get_legend_handler(legend_handler_map, orig_handle)
if handler is None:
warnings.warn(
"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))
"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)
Expand Down Expand Up @@ -654,11 +663,10 @@ def _init_legend_box(self, handles, labels):
# starting index of each column and number of rows in it.
largecol = safezip(list(xrange(0,
num_largecol * (nrows + 1),
(nrows + 1))),
(nrows + 1))),
[nrows + 1] * num_largecol)
smallcol = safezip(list(xrange(num_largecol * (nrows + 1),
len(handleboxes),
nrows)),
len(handleboxes), nrows)),
[nrows] * num_smallcol)
else:
largecol, smallcol = [], []
Expand All @@ -669,16 +677,24 @@ def _init_legend_box(self, handles, labels):
# pack handleBox and labelBox into itemBox
itemBoxes = [HPacker(pad=0,
sep=self.handletextpad * fontsize,
children=[h, t], align="baseline")
children=[h, t] if markerfirst else [t, h],
align="baseline")
for h, t in handle_label[i0:i0 + di]]
# minimumdescent=False for the text of the last row of the column
itemBoxes[-1].get_children()[1].set_minimumdescent(False)
if markerfirst:
itemBoxes[-1].get_children()[1].set_minimumdescent(False)
else:
itemBoxes[-1].get_children()[0].set_minimumdescent(False)

# pack columnBox
if markerfirst:
alignment = "baseline"
else:
alignment = "right"
columnbox.append(VPacker(pad=0,
sep=self.labelspacing * fontsize,
align="baseline",
children=itemBoxes))
sep=self.labelspacing * fontsize,
align=alignment,
children=itemBoxes))

if self._mode == "expand":
mode = "expand"
Expand Down Expand Up @@ -908,7 +924,7 @@ def _find_best_position(self, width, height, renderer, consider=None):
renderer)
for x in range(1, len(self.codes))]

#tx, ty = self.legendPatch.get_x(), self.legendPatch.get_y()
# tx, ty = self.legendPatch.get_x(), self.legendPatch.get_y()

candidates = []
for l, b in consider:
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion lib/matplotlib/tests/test_coding_standards.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ def test_pep8_conformance_installed_files():
'font_manager.py',
'fontconfig_pattern.py',
'gridspec.py',
'legend.py',
'legend_handler.py',
'mathtext.py',
'patheffects.py',
Expand Down
12 changes: 12 additions & 0 deletions lib/matplotlib/tests/test_legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ def test_various_labels():
ax.legend(numpoints=1, loc=0)


@image_comparison(baseline_images=['legend_labels_first'], extensions=['png'],
remove_text=True)
def test_labels_first():
# test labels to left of markers
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(np.arange(10), '-o', label=1)
ax.plot(np.ones(10)*5, ':x', label="x")
ax.plot(np.arange(20, 10, -1), 'd', label="diamond")
ax.legend(loc=0, markerfirst=False)


@image_comparison(baseline_images=['fancy'], remove_text=True)
def test_fancy():
# using subplot triggers some offsetbox functionality untested elsewhere
Expand Down

0 comments on commit 2b2016a

Please sign in to comment.