70
$\begingroup$

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

$\endgroup$
3
  • $\begingroup$ Relevant question (and answers) here. $\endgroup$ Commented Apr 7, 2012 at 22:26
  • 9
    $\begingroup$ 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. $\endgroup$ Commented Apr 7, 2012 at 22:29
  • 1
    $\begingroup$ An older related question is How do I label different curves in Mathematica? $\endgroup$
    – Simon
    Commented Apr 8, 2012 at 3:06

5 Answers 5

98
$\begingroup$

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

$\endgroup$
41
  • 1
    $\begingroup$ @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. $\endgroup$
    – Jens
    Commented Jul 6, 2012 at 4:41
  • 1
    $\begingroup$ Please add a feature for Axeslabel to be shown parallel with axes in your function $\endgroup$ Commented May 20, 2013 at 13:47
  • 1
    $\begingroup$ @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... $\endgroup$
    – Leo Fang
    Commented Jun 19, 2013 at 3:26
  • 1
    $\begingroup$ @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. $\endgroup$
    – Ghersic
    Commented Jul 14, 2013 at 1:24
  • 1
    $\begingroup$ @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. $\endgroup$
    – Jens
    Commented Jul 14, 2013 at 2:07
21
$\begingroup$

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.

$\endgroup$
4
  • $\begingroup$ 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. $\endgroup$
    – Jens
    Commented Apr 8, 2012 at 15:57
  • $\begingroup$ 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. $\endgroup$ Commented Apr 8, 2012 at 17:06
  • $\begingroup$ 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. $\endgroup$
    – Jens
    Commented Apr 8, 2012 at 18:16
  • $\begingroup$ @Jens, Very nice enhancements. $\endgroup$ Commented Apr 9, 2012 at 22:07
15
$\begingroup$

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

$\endgroup$
5
  • $\begingroup$ 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 $\endgroup$ Commented Apr 7, 2012 at 22:15
  • $\begingroup$ How about PlotLegend -> Map[ToString, Table[Log[b], {b, 3, 10, 2}]] $\endgroup$ Commented Apr 7, 2012 at 22:24
  • $\begingroup$ thank you, I believe that should work! $\endgroup$ Commented Apr 7, 2012 at 22:44
  • $\begingroup$ Anyway to increase the size of the Legend font using this? $\endgroup$ Commented Apr 10, 2012 at 2:58
  • $\begingroup$ To change font size use PlotLegend -> Map[Style[ToString[#], 14] &, Table[Log[b], Evaluate[{b, Sequence @@ iter}]]]. You may want to use LegendSize too. $\endgroup$ Commented Apr 10, 2012 at 6:52
9
$\begingroup$

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]];
$\endgroup$
3
  • $\begingroup$ 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...) $\endgroup$
    – Jens
    Commented Apr 27, 2012 at 21:54
  • $\begingroup$ 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. $\endgroup$
    – Jens
    Commented Apr 27, 2012 at 23:40
  • $\begingroup$ Yes more care should be taken to define the Show syntax information. $\endgroup$
    – faysou
    Commented Nov 20, 2012 at 10:52
7
$\begingroup$

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

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.