70

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

Eli Lansey
  • 7,499
  • 3
  • 36
  • 73
Eiyrioü von Kauyf
  • 1,735
  • 1
  • 16
  • 20

5 Answers5

98

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 "".

Jens
  • 97,245
  • 7
  • 213
  • 499
  • +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 08 '12 at 22:11
  • @Ajasja Since you got me to revisit this question, I added something that hopefully does what you want. – Jens Apr 09 '12 at 00: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 09: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
  • You were spot on! Magnify works nicely and the PDF is not pixelated either. I was not able to click and resize the graph produced by autoLabel[] and I thought that increasing the view magnification would not pixelate the image... – Ajasja Apr 11 '12 at 10:15
  • Sadly it seem that deployLegend[] does not work on win7. The text in the legend is messed up. I can resize the entire plot, which is very nice, but I can't select the legend with a single click. If I double click the legend I can't move the whole legend but just the background (or each element of the legend) as shown here. Exporting to SVG, EMF or anything else does not help either. – Ajasja Apr 11 '12 at 10:16
  • Using GraphicsGrid instead of Grid in legendMaker solves the problem of garbled text. – Ajasja Apr 11 '12 at 10:30
  • Figured out how to select the entire legend... First double click on plot then drag to select the entire legend. Very cool. – Ajasja Apr 11 '12 at 10:42
  • @Ajasja Could you try if your font problem is still there with the most recent deployLegend (and the original legendMaker as above? I added "TextMode" -> "Outlines" in the last line of deployLegend, hoping that you won't get garbled fonts this way. Not sure if this works on Windows, though. I suggested this in some other answers to Windows users but never got a response... – Jens Apr 11 '12 at 15:08
  • No, still very similar. But the text itself looks much better with Outlines:) Is the any particular reason why Grid is preferable to GraphicsGrid? – Ajasja Apr 11 '12 at 15:16
  • @Ajasja Thanks for letting me know - so I'll leave Textmode in but have to think about the GraphicsGrid alternative. I'm seeing that it doesn't scale to fit the labels correctly, but that looks fixable. Have to do that later, though... – Jens Apr 11 '12 at 16:15
  • yes, I just discovered that myself:) But replacing TraditonalForm with Text in legendMaker also solves the problem. – Ajasja Apr 11 '12 at 16:20
  • Thanks for improvements! I'd give it another +1 if I could... – Ajasja Apr 19 '12 at 18:35
  • @Jens Wonderful work! I only have a small bug, and I can't seem to find the right pattern matching for it: extractStyles[ListPlot[{1, 2, 3, 4, 5}, Joined -> True]] fails to find the color of the line. – tkott Apr 23 '12 at 16:40
  • @Jens ignore me, I forgot the PlotMarkers->None that needed to be added :) – tkott Apr 23 '12 at 17:03
  • @Jens your legend functions could be directly integrated to the Plot functions similarly to the way the PlotLegends package does it. – faysou Apr 25 '12 at 09:34
  • @FaysalAberkane Yes, that would make sense too. But of course, the more I try to integrate things with system functions, the higher the risk that something will break with the next version... – Jens Apr 25 '12 at 15:22
  • @Jens I did below what I suggested in my previous comment, it might interest you. – faysou Apr 27 '12 at 21:41
  • Excellent post. The only issue I have is that deployLegend changes the appearance of the plot itself (compare deployLegend to a "standard" plot). But I can live with autoLegend :) – kadrach May 03 '12 at 03:17
  • @Jens Love this! +1 for awesomeness! One problem though is that after using autoLegend ToolTips no longer work. Is there a work around that would allow ToolTips to still function? Still a great solution! Thanks! – Nothingtoseehere Jul 06 '12 at 01:00
  • @R Hall Thanks - I don't get points for this anymore since it's a community wiki, but I want to update it soon. I did figure out how to place the legend reliably without the Overlay that I'm using here for autoLegend. The method that one should use instead is Inset, and I've already done that for the color bar legends that I worked on in this anser to the question "ShowLegend values" - so all the pieces are there... – Jens Jul 06 '12 at 01:18
  • 1
    @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 06 '12 at 04:41
  • @Jens Much better now Jens! Tooltips work perfectly! +1 – Nothingtoseehere Jul 06 '12 at 11:01
  • I am just starting to play with this in the past 2 weeks. Very nice, and much better than the ad-hoc system I was using via LevelScheme. – rcollyer Aug 03 '12 at 19:20
  • Is there a way to put LegendOrientation to horizontal? –  Nov 20 '12 at 10:22
  • @de_wright I haven't implemented that option yet because I decided not to try and duplicate the entire functionality of Mathematica's own Legends. But I'll try to post a new version tomorrow if I have the time... – Jens Nov 20 '12 at 17:04
  • @Jens Thank you for this great piece of code. However, I copied your sample and on my machine the lines of the legend are shifted downwards. Do you have any idea why this might happen? Here is the png (that's how it looks inside Mathematica 8) and the pdf. – mincos Nov 26 '12 at 13:28
  • Playing around with ImagePadding helped, at least for lines without markes. – mincos Nov 27 '12 at 11:17
  • @mincos and @de_wright Sorry I didn't have time to update this yet, but it looks like some improvements are actually coming in the next version of Mathematica that may make this post obsolete. Anyway, I'm currently looking into modifying legendMaker to keep track of relative sizing of plot markers better. The alignment of markers with lines is always a problem with the textual (Automatic) markers because character symbols can't be placed with perfect accuracy. – Jens Nov 28 '12 at 06:17
  • 1
    Please add a feature for Axeslabel to be shown parallel with axes in your function – Raymond Ghaffarian Shirazi May 20 '13 at 13:47
  • 1
    @Jens, is it possible to implement the feature of automatically rescaling the sizes of the legend background and the legend fonts in autoLegend when one enlarges the size of the plot by dragging the corner of it? Sometimes the relative scale between the legend and the main plot looks nice, but when I enlarge the plot to see its detailed structure, I find that the legend is not resized accordingly... – Leo Fang Jun 19 '13 at 03:26
  • @Leo I've found that if I include ImageSize as a parameter of the plot being legended, it is cancelled by autoLegend (reverting the size back to the tiny default), at least within Manipulate. I am looking for a solution to this and will post it here if I find one. – Ghersic Jul 14 '13 at 01:16
  • 1
    @Leo I suspect it has something to do with the answer below as Jens mentions the option ImageSize, but I have yet to figure it out. I don't need rescaling even, just to allow the original plot to be the size described (within it or autoLegend) by ImageSize. – Ghersic Jul 14 '13 at 01:24
  • 1
    @Ghersic I think what @Leo is asking for can already be done relatively easily by combining ImageSize with Magnify. E.g., call the last parametric plot with legend that I used in the above answer lastPlot, then you can just do this: Magnify[Show[lastPlot, ImageSize -> 300], 2]. I.e., what @Leo wants is not really part of what I intended autoLegend to do because it can be done externally. Instead of Magnify, you can also try Style[..., Magnification -> 2]. autoLegend has limitations because it's supposed to be really simple to use. For more features, use legendMaker directly. – Jens Jul 14 '13 at 02:07
  • 1
    @LeoFang If you think my previous comment to @Ghersic helps answer your request, I could add that to my answer above. Otherwise, maybe you can give me another example to play with. When you rescale an image by dragging as you describe, the font size never changes. So what you're asking isn't really consistent with the normal behavior of Graphics, which is why I don't plan on implementing it directly in autoLegend. – Jens Jul 14 '13 at 02:13
  • @Jens thanks for your comment! My very first need was the same as Ghersic's, namely output a plot with legend in a desired size. However as @Ghersic has pointed out, autoLegend seems unable to satisfy our needs. Therefore, I turned to tune the plot size manually and left the "rescaling by dragging" comment above. So, yes, your comment above answered my question (btw, I think after Magnify it is much better for external document usage than before!), and it would be good to see you add it to your post for future reference. :) – Leo Fang Jul 14 '13 at 13:28
  • @Jens One more question irrelevant to my first one. When I saw this post, I thought either autoLegend or legendMaker could solve it, but I found that colors in the legend do not agree with colors in the plot. To clarify my unsuccessful attempt see this sample file. Do you think there is a workaround? – Leo Fang Jul 14 '13 at 13:42
  • 1
    Ah, I see @Jens. Thank you, your autoLegend function works well with 6 sliders manipulating constants within (coincidentally) 6 equations in NDSolve whose variables are being plotted. This works very well and the only hitch comes down to wanting to maintain position coordinates relative to the extent of magnification (manipulating sliders resets the dragable location to the default specified in the definition of autoLegend), but hell... I'll do that by hand! Thanks a million, @Jens ... A legend to all and to all a good night. ;p – Ghersic Jul 14 '13 at 23:46
  • @Jens thanks for the great effort of keeping the post updated! As a MMA8 user I really appreciate it! :) – Leo Fang Jul 15 '13 at 03:20
  • Hello Jens, recently I shifted to MMA9 because I found that in MMA8 the following MWE gives inconsistent legend font size after exporting to an eps file: cm = 72/2.54; autoLegend[ Plot[Sin[x], {x, 0, 2 Pi}, AxesLabel -> {"x", "sin(x)"}, BaseStyle -> {FontSize -> 9}], {"Just a test"}, Background -> Directive[LightOrange, Opacity[.5]], Alignment -> {0.6, 0.5}, BaseStyle -> {FontSize -> 9}]; Export["test.eps", Show[%, ImageSize -> 9 cm]]. Note that I use BaseStyle twice: one for the font size of ticks and axis labels, and another for the legend. In MMA9 it works fine but not in MMA8. – Leo Fang Aug 21 '13 at 20:10
  • It's not an urgent bug report, just wanna let you know :) – Leo Fang Aug 21 '13 at 20:11
21

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.

image_doctor
  • 10,234
  • 23
  • 40
  • 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 08 '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 08 '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 08 '12 at 18:16
  • @Jens, Very nice enhancements. – image_doctor Apr 09 '12 at 22:07
15

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

Chris Degnen
  • 30,927
  • 2
  • 54
  • 108
9

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

UpdateSyntax[fun_]:=
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[_?BoxForm`HeldOptionQ]] | PatternSequence[], opts__?BoxForm`HeldOptionQ]
                /; 
                !FreeQ[Flatten[{opts}], PlotLegend]
            ] 
            :>
            With[{plotOptions=Sequence@@FilterRules[{opts},Options[#]]},
                autoLegend[#[a,plotOptions], PlotLegend /. Flatten[{opts}],opts] 
            ]
        ]
]&
,
plotnames
];

Map[UpdateSyntax, plotnames];
Protect[Evaluate[plotnames]];
faysou
  • 10,999
  • 3
  • 50
  • 125
  • 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. – faysou Nov 20 '12 at 10:52
7

I do not like legends. I'd rather place labels next to curves using the Text[ ] primitive, perhaps inside an Epilog->{ }. Here is an example:

enter image description here

The code that produces the graph is, first, the definition of a function that places the text in a font of my choice, here Times:

txt[a_, b_, c_: {0, 0}, d_: {1, 0}, e_: ftsz] := 
 Text[Style[a, FontFamily -> "Times", FontSize -> e], b, c, d]

Then, I define the curves:

eq1 = (c + c^(1/2))/2 - 1 + 1/c(*the equation stored in eq1*)
d1eq1 = D[eq1, c](*first derivative stored in d1eq1*)
d2eq1 = D[eq1, {c, 2}](*second derivative stored in d2eq1*)

Finally, I use Plot to produce the curves and I place the text using Prolog:

fntandsz = {FontFamily -> "Roman", FontSize -> .7 ftsz};
dp1 = Plot[{eq1, d1eq1, d2eq1}, {c, 0, 3},
  AspectRatio -> Automatic,
  PlotRange -> {-1, 2},
  PlotStyle -> {Black,
    {Black, Dashing[{.03, .01}]},
    {Black, Dashing[{.03, .01, .002, .01}]}},
  ImageSize -> 4.5 imgsz,
  BaseStyle -> fntandsz,
  Prolog -> {
    txt[TraditionalForm@f[c], {2.3, 1.5}],
    txt[TraditionalForm@f'[c], {2.6, .65}],
    txt[TraditionalForm@f''[c], {1.3, 1.9}]
    }]

You must also have defined font size (ftsz) and image size (imgsz), for which I use 12 and 57 for the screen (and larger values for publishing).

Nicholas G
  • 1,981
  • 10
  • 15