Creating legends for plots with multiple lines?

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.

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.

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.

legendMaker allows 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 posted an example there.

Edit July 14, 2013

A year has passed since the last update to this code, because Mathematica version 9 introduced the new command PlotLegends which in many cases should make this answer obsolete. However, I tried to keep this answer compatible with versions 8 and 9. This is why this update is necessary, since some functionality was broken in recent Mathematica versions.

The major changes were in the function extractStyles which tries to guess what lines and markers are being used in a given plot. This relies on the internal structure of the plots, and is therefore fragile. I tried to make it more robust.

I also added usage messages and made sure that autoLegend (the simplest legending function in this answer) accepts the full range of options of legendMaker (the more lower-level legend drawing function). All the examples below are unchanged, except that I added more information at the end.

Options[legendMaker] = 
  Join[FilterRules[Options[Framed], 
    Except[{ImageSize, FrameStyle, Background, RoundingRadius, 
      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::usage = 
  "Create a Graphics object with legends given by the list passed as \
the first argument. The options specify any non-deafult line styles \
(using PlotStyle -> {...}) or plot markers (using PlotMarkers -> \
{...}). For more options, inspect Options[legendMaker]";

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[
              Normal[ListPlot[Transpose[{Range[3]}], 
                  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]];
   lineDirectives = PadRight[lineDirectives, n, lineDirectives];
   markerSymbols = PadRight[markerSymbols, n, markerSymbols];
   f = Grid[
     MapThread[{Graphics[{#1 /. None -> {}, 
          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::usage = "returns a tuple {\"all line style \
directives\", \"all plot markers\"} found in the plot, in the order \
they are drawn. The two sublists need not have the same length if \
some lines don't use markers "; 
extractStyles[plot_] := 
 Module[{lines, markers, points, 
   extract = First[Normal[plot]]},(*In a plot,
  the list of lines contains no insets,so I use this to find it:*)
  lines = 
   Select[Cases[Normal[plot], {___, _Line, ___}, Infinity], 
    FreeQ[#1, Inset] &];
  points = 
   Select[Cases[Normal[plot], {___, _Point, ___}, Infinity], 
    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//. Except[List][i_Inset, __] :> i, 
       {c__, Inset[s_, pos_, d___], e___} :> If[
        (*markers "s" can be strings or graphics*)

        Head[s] === 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, _?ColorQ], Style[s, c], s]
        ],
      Infinity
      ],
     (*
     Filter out Pointsize-legends don't need it:*)

     Cases[points, {c___, 
        Point[pt__], ___} :> {Graphics[{c, Point[{0, 0}]}] /. 
         PointSize[_] :> PointSize[1], .01}, Infinity]
     ], {} -> None]}]

autoLegend::usage = 
  "Simplified legending for the plot passed as first argument, with \
legends given as second argument. Use the option Alignment -> \
{horizontal, vertical} to place the legend in the PlotRegion in \
scaled coordinates. For other options, see Options[legendMaker] which \
are used by autoLegend.";
Options[autoLegend] = 
  Join[{Alignment -> {Right, Top}, Background -> White, 
    AspectRatio -> Automatic}, 
   FilterRules[Options[legendMaker], 
    Except[Alignment | Background | AspectRatio]]];
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, 
      Sequence @@ 
       FilterRules[{opts}, 
        FilterRules[Options[legendMaker], Except[Alignment]]]],
     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}]

plot legends

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.

As an example, I took

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

and added labels by calling

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

Automatic labeling

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}]

Graphics markers

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"}]

Two plots labeled

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"}]

Screen shot of legend editing

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"}]

parametricPlot

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}]

parametric inset moved

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:

paramWithLegend = 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.

If you want to change the relative size of the legend and/or plot, I'd suggest playing around with ImageSize and Magnify, as in this example:

Magnify[Show[paramWithLegend, ImageSize -> 300], 2]

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

If autoLegend doesn't cut it, use legendMaker

Limitations of the automatic style recognition in autoLegend may occur if you combine different types of plot with Show, as in the following example from the comments:

myplots = 
  Show[Plot[Sin[x], {x, 0, 2 Pi}, PlotRange -> All, 
    PlotStyle -> {Red, Thick}], 
   ListPlot[
    Table[{x, Sin[x] + RandomReal[{-0.1, 0.1}]}, {x, 0, 2 Pi, 0.1}], 
    PlotRange -> All, PlotMarkers -> Automatic, Joined -> True]];

myStyles = extractStyles[myplots]

{{Directive[Hue[0.67,0.6,0.6],RGBColor[1,0,0],Thickness[Large]],Directive[Hue[0.67,0.6,0.6]]},{\[FilledCircle]}}

Overlay[{myplots,
  legendMaker[{"Sin(x)-theory", "Sin(x)-data"},
   PlotStyle -> myStyles[[1]],
   PlotMarkers -> Prepend[
     myStyles[[2]], ""]]}, Alignment -> {Right, Top}]

overlay

Here, the problem is that we have two lines with distinct styles, but not the same number of distinct plot markers (just \[FilledCircle]). To get one line with and one line without marker, I call legendMaker with a two-element list for PlotMarkers, one of which is an empty string "".


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.


Perhaps this example will help you?

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}]

enter image description here

Addressing your comment, to only specify the iterator once:

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}]

enter image description here