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