# Creating legends for plots with multiple lines?

So I have a graph with multiple lists, for e.g.

data = {{1,2}, {3,4}, {3,5}, {2,3} . . .}


If I then do ListLinePlot[Table[{#1,Log[b,#2]}&@@@data, {b,1,10,2}]] I have no way to generate a legend for it, that I see anyway. I cannot put anything inside the Table which contains the iterator. Would I do something alone the lines of Legend-> Log[Range[1,10,2,x]]?

-
Relevant question (and answers) here. – Sjoerd C. de Vries Apr 7 '12 at 22:26
BTW I see you are pretty quick to accept answers. Though this is fine in itself it's usually better to wait at least a couple of hours (or even days) before accepting an answer. This way you will probably get more and perhaps better answers. – Sjoerd C. de Vries Apr 7 '12 at 22:29
An older related question is How do I label different curves in Mathematica? – Simon Apr 8 '12 at 3:06

In case you want more flexibility, it's also possible to design your own legends, for example along the lines of this MathGroup post. For your example, the process would start with the function legendMaker.

Edited

Instead of repeating the same definition as in the above post, I've overhauled legendMaker in response to image_doctor's answer, to separate out the handling of options better.

Edit 3 to update the code

I've tried to make the spacings and widths of the legends more customizable, and also separated the automatic extraction of the line and marker styles into a separate function extractStyles so that it's easier to modify if needed later.

Here I'm listing all the functions in one code block to make them easier to copy. Below, I'll go through the usage examples for these functions in the order they were written: i.e., from the low-level legendMaker to the high-level deployLegend.

Edit 4

Changed legendMaker to allow individual line styles and plot markers to have the value None. This makes it easier to specify custom legends, in particular when combining a line plot without markers, and a list plot without lines. This is motivated by a related answer here, so I'll post an example there.

Options[legendMaker] =
Join[FilterRules[Options[Framed],
ImageMargins}]], {FrameStyle -> None,
Background -> Directive[Opacity[.7], LightGray],
RoundingRadius -> 10, ImageMargins -> 0, PlotStyle -> Automatic,
PlotMarkers -> None,
"LegendLineWidth" -> 35,
"LegendLineAspectRatio" -> .3,
"LegendMarkerSize" -> 8,
"LegendGridOptions" -> {Alignment -> Left, Spacings -> {.4, .1}}}];

legendMaker[textLabels_, opts : OptionsPattern[]] :=
Module[{f, lineDirectives, markerSymbols, n = Length[textLabels],
x},
lineDirectives = ((PlotStyle /. {opts}) /.
PlotStyle | Automatic :> Map[ColorData[1], Range[n]]) /.
None -> {None};
markerSymbols =
Replace[((PlotMarkers /. {opts}) /.
Automatic :> (Drop[
ListPlot[Transpose[{Range[n]}],
PlotMarkers -> Automatic][[1, 2]][[1]], -1] /.
Inset[x_, i__] :> x)[[All, -1]]) /. {Graphics[gr__],
sc_} :> Graphics[gr,
ImageSize -> ("LegendMarkerSize" /. {opts} /.
Options[legendMaker,
"LegendMarkerSize"] /. {"LegendMarkerSize" -> 8})],
PlotMarkers | None :> Map[Style["", Opacity[0]] &, textLabels]] /.
None | {} -> Style["", Opacity[0]];
f = Grid[
If[#1 === {None} || (PlotStyle /. {opts}) === None, {},
Line[{{-.1, 0}, {.1, 0}}]],
Inset[#2, {0, 0}, Background -> None]},
AspectRatio -> ("LegendLineAspectRatio" /. {opts} /.
Options[legendMaker,
"LegendLineAspectRatio"] /.
{"LegendLineAspectRatio" -> .2}),
ImageSize -> ("LegendLineWidth" /. {opts} /.
Options[legendMaker,
"LegendLineWidth"] /. {"LegendLineWidth" -> 35}),
ImagePadding -> {{1, 1}, {0, 0}}],
Text[#3, FormatType -> TraditionalForm]} &, {lineDirectives,
markerSymbols, textLabels}],
Sequence@
Evaluate[("LegendGridOptions" /. {opts} /.
Options[legendMaker,
"LegendGridOptions"] /.
{"LegendGridOptions" -> {Alignment -> Left,
Spacings -> {.4, .1}}})]];
Framed[f,
FilterRules[{Sequence[opts, Options[legendMaker]]},
FilterRules[Options[Framed], Except[ImageSize]]]]];

extractStyles[plot_] := Module[{
lines, markers, extract = First[Normal[plot]]
},
(*In a plot,the list of lines contains no insets,
so I use this to find it: *)
lines = Select[extract, FreeQ[#1, Inset] &];
(* Most plot markers are inside Inset,
except for Point in list plots: *)
markers = Select[extract, ! FreeQ[#1, Inset] &];
(* The function returns a list of lists: *)
{
(* The first return value is the list of line plot styles: *)
Replace[Cases[
lines, {c__, Line[__], ___} :>
Flatten[Directive @@ Cases[{c}, Except[_Line]]],
Infinity], {} -> None],
(* Second return value: marker symbols *)
Replace[Join[
Cases[
markers,
{c__, Inset[s_, pos_, d___], e___} :>
If[
(* markers "s" can be strings or graphics *)
(* Append scale factor in case it's needed later;
default 0.01 *)
{s,
Last[{.01, d}] /. Scaled[f_] :> First[f]},
If[
(* For strings,
add line color if no color specified via text styles: *)
FreeQ[s, CMYKColor | RGBColor | GrayLevel | Hue],
Style[s, c],
s
]
], Infinity
],
(* "Point" counts as marker but appears in "lines".
Filter out Pointsize - legends don't need it: *)
Cases[lines, {c___, PointSize[_], d___,
Point[pt__], ___} :> {Graphics[{c, d,
Point[{0, 0}]}], .01}, Infinity]]
, {} -> None]}]

Options[autoLegend] = {Alignment -> {Right, Top}, Background -> White,
AspectRatio -> Automatic};
autoLegend[plot_Graphics, labels_, opts : OptionsPattern[]] :=
Module[{lines, markers, align = OptionValue[Alignment]},
{lines, markers} = extractStyles[plot];
Graphics[
{
Inset[plot, {-1, -1}, {Left, Bottom}, Scaled[1]],
Inset[
legendMaker[labels, PlotStyle -> lines,
PlotMarkers -> markers, opts
],
align,
Map[
If[NumericQ[#], Center, #] &, align
]
]
},
PlotRange -> {{-1, 1}, {-1, 1}},
AspectRatio -> (OptionValue[AspectRatio] /.
Automatic :> (AspectRatio /. Options[plot, AspectRatio]) /.
Automatic :> (AspectRatio /.
AbsoluteOptions[plot, AspectRatio]))]
]


Notes for legendMaker:

The horizontal width of the line segment in the legend can be changed with the option "LegendLineWidth", and its aspect ratio is set by "LegendLineAspectRatio". The size of the markers in the legend is set by "LegendMarkerSize" (all these options have reasonable default values), and they can also be passed to the higher-level functions below.

The only required argument for this version of legendMaker is a list of labels. Everything else is optional. I.e., the function automatically determines what to do about the matching line colors and plot marker symbols (if any).

Plot markers can be defined as String or Graphics objects. To control the line colors, you can specify the option PlotStyle: with PlotStyle -> Automatic, the default plot styles are used. If you have instead prepared the plot with a different list of plot styles, you can enter that here. The option PlotStyle -> None is also allowed. This should be used when labeling a ListPlot that has no connecting lines between the points.

With the setting PlotMarkers -> Automatic, the legend will also display marker symbols according to the default choices that I extract from a temporary ListPlot that is then discarded. The default setting for legendMaker is PlotMarkers -> None.

To make the plots look nice, you can add other options for the legend appearance, e.g.:

opts = Sequence[Background -> LightOrange, RoundingRadius -> 10];


Here I allow all options that Frame can handle, except for ImageSize.

Now we prepare a plot:

data = {{1, 2}, {3, 4}, {5, 4}};
points = Table[{#1, Log[b, #2]} & @@@ data, {b, 2, 10, 2}];
plot = ListLinePlot[points];


The list labels creates the text that you were asking for:

labels = Table[Row[{Subscript["Log", b], x}], {b, 2, 10, 2}]


$\left\{\text{Log}_2x,\text{Log}_4x,\text{Log}_6x,\text{Log}_8x,\text{Log}_{10}x\right\}$

The legend is now displayed as an overlay over the original plot:

newPlot =
Overlay[{plot, legendMaker[labels, opts]}, Alignment -> {Right, Top}]


The Overlay that is created here can still be exported and copied even though it's not a graphics box. A good way to copy it to an external editor is to highlight the plot and select Copy As... PDF from the Edit menu (that's at least where it is on Mac OS X).

A different application of legendMaker can be found in this post. That's also a good example for the difference in appearance to the standard Legends package, which many people consider sub-par (it's slow, old-fashioned and even crashes sometimes).

Notes for autoLegend

In response to the comment by Ajasja, I added another layer of automation that makes use of the function legendMaker defined above, but attempts to deduce the colors and marker symbols from the provided plot in a fully automatic way. note I renamed the original autoLabel to autoLegend.

As an example, I took

p = ListPlot[Evaluate[Table[RandomInteger[3 i, 10] + 2 i, {i, 3}]],
PlotMarkers -> Automatic, Joined -> True]


autoLegend[p, {"Small", "Big", "Bigger"},
Background -> Directive[LightOrange, Opacity[.5]],
Alignment -> {Right, Top}]


The function autoLegend takes the same options as legendMaker, plus the Alignment option which follows the conventions for Overlay.

Here is an example wit graphical markers:

p = ListPlot[Evaluate[Table[RandomInteger[3 i, 10] + 2 i, {i, 3}]],
PlotMarkers -> {{Graphics@{Red, Disk[]}, .05}, {Graphics@{Blue,
Rectangle[]}, .05}}, Joined -> True];
autoLegend[p, {"Small", "Big", "Bigger"}, Alignment -> {-.8, .8}]


autoLegend is still limited in the sense that its automatic extraction doesn't yet work with GraphicsGrid or GraphicsRow (but I'll add that at some point). But at least it seems to behave reasonably when you give it several plots at once, e.g. as in this example:

plot1 = Show[Plot[{Sin[x], Sinh[x]}, {x, -Pi, Pi}],
ListPlot[Join[{#, Sin[#]} & /@ Range[-Pi, Pi, .5], {#, Sinh[#]} & /@
Range[-Pi, Pi, .5]]]];
autoLegend[plot1, {"Sin", "Sinh"}]


Latest edit (July 5, 2012)

By creating the plot legend as an overlay, one gains flexibility because the legend created with legendMaker doesn't have to be "inside" any particular graphics object; it can for example overlap two plots (see also my MathGroup post).

However, overlays aren't so convenient in other respects. Importantly, the graphics editing tools that come with Mathematica can't be used directly to fine-tune the plot and/or legend when it's in an overlay. In an Overlay, I can't make both the plot and legend selectable (and editable) at the same time.

That's why I changed autoLegend such that it uses Insets instead of Overlay. The positioning just requires a little more code because Inset needs different coordinate systems to determine its placement and size. To the user, the placement happens in pretty much the same way that the Alignment works in Overlay: you can either use named positions such as Alignment -> {Left, Bottom} or numerical scale factors such as Alignment -> {0.5, 0}. In the latter case, the numbers range from -1 to 1 in the horizontal and vertical direction, with {0, 0} being the center of the plot.

With this method, the plot is fully editable, as shown in the movie below. The movie uses a differently named function deployLegend, but from now on we can define deployLegend = autoLegend

deployLegend[p, {"Label 1", "Label 2", "Label 3"}]


This is a screen capture of how the legend is now available as part of the graphics object. First, I make a color change in one of the labels to show that the legend preserves formatting. Then I double-click on the Graphics and start editing. I select the legend, making sure I don't have parts of the plot selected. Then I drag the legend around and rotate it.

These manipulations leverage the built-in editing tools, so I didn't see any reason to use Locators to make the legend movable, as was done with individual labels in this post.

The positioning of the legend is not restricted to the inside of the plot. To make a legend appear off to the side, you just have to Show the plot with a setting for PlotRegion that leaves space wherever you need the legend to go.

Here is an example:

param = ParametricPlot[{{3 Cos[Pi t],
Sin[2 Pi t]}, {1 + Cos[Pi t],
Sin[2 Pi t + Pi/3]}}, {t, 0, 2}];

autoLegend[param, {"Curve 1", "Curve 2"}]


So to move the legend out of the picture, you might do this:

autoLegend[
Show[param, PlotRegion -> {{0, .7}, {0, 1}}], {"Curve 1", "Curve 2"},
Alignment -> {Right, Center}]


The legend placement is relative to the enclosing image dimensions, as you can see - not relative to the plot range of the ParametricPlot. The plot region may not be the only thing you want to change, though. autoLegend tries to determine the total aspect ratio automatically, but we see that there is a bit too much white space at the top now. For that reason, I also added the option AspectRatio so we can set a manual value:

autoLegend[
Show[param, PlotRegion -> {{0, .7}, {0, 1}}], {"Curve 1", "Curve 2"},
Alignment -> {Right, Center}, AspectRatio -> .25]


This produces the same picture as above but with less white space on top.

For the latest updates that I made to autoLegend, I should credit the answer to the question "ShowLegend values" where I made a similar legend for color bars in contour plots.

-
+1. A quick question: Wouldn't it be possible to get the PlotStyle and PlotMarkers directly from the plot one would like to add the legend to (instead of creating a fake one)?Then one could call addLegend[plot, labels, opt] and everything would work as expected and custom PlotStyle options would only have to be given once to the plot. – Ajasja Apr 8 '12 at 22:11
@Ajasja Since you got me to revisit this question, I added something that hopefully does what you want. – Jens Apr 9 '12 at 0:50
Wow, thanks! Finally a legend simply and aesthetic enough to use daily. Is it some how possible to avoid rasterizing the plot though? On win7 mma 8, Overlay[] returns a bitmap... And using Show and Inset does not work (returns a garbled plot legend) – Ajasja Apr 10 '12 at 9:10
@Ajasja That's news to me, that Overlay creates bitmaps on Windows. Could you try an experiment with one of the example plots. Let's call the plot p = autoLabel[...], then try p2 = Magnify[p,2] and see if that works. Then try Export["p2.pdf",p2] and look at p2.pdf in Acrobat Reader or some other PDF viewer. Does it appear bitmapped? If it looks pixelated within the Mathematica notebook already, then that could be an issue merely with the display preferences (e.g., are you viewing at 100% or higher magnification? Just a guess...). – Jens Apr 10 '12 at 23:52
@RHall Could you check if autoLegend now works better? I tested a new version and it seems to work perfectly with tool tips after I switched to Inset instead of Overlay for the legend. If you want to up-vote it, you could do so in the answer I linked at the bottom of this answer, because that's where the idea came from. – Jens Jul 6 '12 at 4:41

A small enhancement to Jens's excellent response is to generalise the options handling by defining the options for legendMaker to include all those applicable to Framed.

Options[legendMaker]=Options@Framed;
legendMaker[lineDirectives_, markerSymbols_, textLabels_,
opts : OptionsPattern[]] :=
Module[{f, g},
f = Grid[MapThread[{Graphics[{#1, Line[{{-.1, 0}, {.1, 0}}],
Inset[#2, {0, 0}, Background -> None]}, AspectRatio -> .2,
ImageSize -> 35], TraditionalForm[#3]} &, {lineDirectives,
markerSymbols, textLabels}], Alignment -> Left];
g = Framed[f,opts]
]


Then add the extra formatting options:

opts = Sequence[Background -> LightGray, RoundingRadius -> 10,
FrameStyle -> None, ImageMargins -> 10];


For the more cautious:

g = Framed[f,opts]


can be replaced with

g = Framed[f,FilterRules[{opts},Options@Framed]]


to discard any options not applicable to Framed.

A similar approach could be applied to Grid by the inclusion of a second options packet opts2 in the definiton of legendMaker's parameters.

-
 One reason why I hard-coded the default options in my version is that I wanted different defaults from Frame, and also to disallow options that can interfere with the label. E.g., if you accidentally add ImageSize->40 to the options it will distort the overlay. So I basically implemented a "white list" of options. Your approach is perfectly fine if one instead implements a "black list" of options using Except in FilterRules. It would just take a little more experimenting to see which other options to disallow. – Jens Apr 8 '12 at 15:57 Hi Jens, as ever, yes lots of ways to get where you want to go. Defining the options with Options[legendMaker]={ ... }, lets you set your own defaults if you want. Either by only defining the selecting things from Framed that you want to use, or just taking the options from Framed and then replacing the default values that you don't like. – image_doctor Apr 8 '12 at 17:06 You're right, I think that's better. I changed my answer but added a lot more option handling to keep my favorite defaults and also absorb the handling of default colors and markers in options. – Jens Apr 8 '12 at 18:16 @Jens, Very nice enhancements. – image_doctor Apr 9 '12 at 22:07

data = {{1, 2}, {3, 4}, {3, 5}, {2, 3}};
Needs["PlotLegends"]
ListLinePlot[Table[{#1, Log[b, #2]} & @@@ data, {b, 3, 10, 2}],
PlotLegend -> {"A", "B", "C", "D"}, LegendPosition -> {1.1, -0.4}]


data = {{1, 2}, {3, 4}, {3, 5}, {2, 3}};
Needs["PlotLegends"]
iter = {3, 10, 2};
ListLinePlot[
Table[{#1, Log[b, #2]} & @@@ data, Evaluate[{b, Sequence @@ iter}]],
PlotLegend ->
Map[ToString, Table[Log[b], Evaluate[{b, Sequence @@ iter}]]],
LegendPosition -> {1.1, -0.4}]


-
 I would like the legend to say that [Line of Color A] = $Log_{a} x$ and the next [Line of Color B] = $Log_{b} x$ etc. where $a,b,c ...$ is specified by that Table iteration – Eiyrioü von Kauyf Apr 7 '12 at 22:15 How about PlotLegend -> Map[ToString, Table[Log[b], {b, 3, 10, 2}]] – Chris Degnen Apr 7 '12 at 22:24 thank you, I believe that should work! – Eiyrioü von Kauyf Apr 7 '12 at 22:44 Anyway to increase the size of the Legend font using this? – Eiyrioü von Kauyf Apr 10 '12 at 2:58 To change font size use PlotLegend -> Map[Style[ToString[#], 14] &, Table[Log[b], Evaluate[{b, Sequence @@ iter}]]]. You may want to use LegendSize too. – Chris Degnen Apr 10 '12 at 6:52

I've tried to integrate the functions of Jens to the plot functions similarly to what is done in the PlotLegends package (I've adapted code found in it, if it poses a problem then I'll delete my post), here's what I've just come up with, it seems to work. The autoLegend function needs to be modified slightly.

For setting up default legend options legendMaker options must be modified.

deployLegend instead of autoLegend could be used instead in the definitions below. The functions could also be changed to allow this choice as an option.

Here's an example borrowed from the answer of Jens.

ListPlot[Evaluate[Table[RandomInteger[3 i, 10] + 2 i, {i, 3}]],
PlotMarkers -> {{Graphics@{Red, Disk[]}, .05}, {Graphics@{Blue, Rectangle[]}, .05}},
Joined -> True,
PlotLegend -> {"Small", "Big", "Bigger"}, Alignment -> {Right, Top}]


It also works for the example using Show of this question Legend of a plot: how to increase the size of the line/marker? using this syntax

Show[p,q,PlotLabel->Style["Comparison","Subsection"],
PlotLegend->textLabels,PlotStyle->manualPlotstyles,PlotMarkers->manualMarkers,
Background->Directive[Opacity[.5],Black],"LegendLineAspectRatio"->.3,
"LegendGridOptions"->{Alignment->Left,Spacings->{.7,.1},Background->{{GrayLevel[.8],None},None}},Alignment->{-.8,-.7}]


Here's the code

autoLegend[plot_Graphics, labels_, opts : OptionsPattern[]] :=
Module[{lines, markers},
{lines, markers} = extractStyles[plot];
Overlay[
{
plot
,
legendMaker[labels, opts, PlotStyle -> lines, PlotMarkers -> markers]
}
,
Alignment -> (Alignment /. {opts} /. Alignment -> {Right, Top})
]
];

LegendOptionNames = Map[First, Options[legendMaker]];
PlotLegendOptionNames = Union[{PlotLegend}, LegendOptionNames];

If[FreeQ[SyntaxInformation[fun], "OptionNames"],
SyntaxInformation[fun] =
Join[
SyntaxInformation[fun]
,
{"OptionNames" -> Map[ToString, Union[Map[First, Options[fun]], PlotLegendOptionNames]]}
]
,
SyntaxInformation[fun] =
Join[
DeleteCases[SyntaxInformation[fun], "OptionNames"->_]
,
{"OptionNames" -> Prepend[Map[ToString, Union[{CoordinatesToolOptions}, PlotLegendOptionNames]],Automatic]}
]
];

plotnames = {Show, Plot, LogPlot, LogLinearPlot, LogLogPlot, PolarPlot, ParametricPlot,
ListPlot, ListLinePlot, ListLogPlot, ListLogLinearPlot, ListLogLogPlot, DateListPlot, DateListLogPlot, ListPolarPlot};

Unprotect[Evaluate[plotnames]];
Map[
(* Only insert a rule once if the package is loaded multiple times *)
If[FreeQ[DownValues[#], PlotLegend],
DownValues[#] =
Prepend[
DownValues[#, Sort->False]
,
HoldPattern[
#[a : PatternSequence[___, Except[_?BoxFormHeldOptionQ]] | PatternSequence[], opts__?BoxFormHeldOptionQ]
/;
!FreeQ[Flatten[{opts}], PlotLegend]
]
:>
With[{plotOptions=Sequence@@FilterRules[{opts},Options[#]]},
autoLegend[#[a,plotOptions], PlotLegend /. Flatten[{opts}],opts]
]
]
]&
,
plotnames
];


 I wonder if this could cause problems if someone is also using the regular PlotLegends... Have to play around with this a little more - in general, it could certainly improve the usability. I'll also edit my post simply to add some comments to the code so that it will be easier to extend later (when time permits or necessity dictates...) – Jens Apr 27 '12 at 21:54 It seems that the syntax information for Show is now no longer recognizing any of the Graphics3D options! E.g., try Show[Graphics3D[Sphere[]], Boxed -> False]. I am getting red syntax coloring for Boxed even though it still renders correctly. – Jens Apr 27 '12 at 23:40 Yes more care should be taken to define the Show syntax information. – Faysal Aberkane Nov 20 '12 at 10:52