# Labeling individual curves in Mathematica

I need to create a plot for export and inclusion in a report. Is there a better way to label curves than PlotLegends? From what I've read and my personal experience, PlotLegends is pretty bad.

Is there a better package for legends, or, ideally, a simply way to place small text next to each curve?

-
aaaah... finally someone who acknowledges that PlotLegends  is horrible in the question without being stubborn about wanting to kludge through with it! Welcome, please go through Jens' awesome answer for what you want. If you want to place the label on each curve separately, then I suggest you look at this answer by Simon –  rm -rf Apr 19 '12 at 1:28
Inset[] seems to be the more flexible method to annotate graphics, but positioning is manual - there is no intelligent layout, and syntax is slightly more cumbersome than positioning general Graphics elements –  alancalvitti Apr 19 '12 at 1:40
If PlotLegends is the answer you're asking the wrong question :) I second R.Ms recommendation re: code from Jens and Simon. It is always preferable to roll your own legends IMO. –  Mike Honeychurch Apr 19 '12 at 1:46
@R.M PlotLegends is aweful. Once I accepted that and created my own system, I've been better off. –  tkott Apr 19 '12 at 1:57
Take a look at this (mine) answer mathematica.stackexchange.com/a/14149/193 –  belisarius Mar 7 '13 at 12:20

You can make use of the following options in Plot, e.g. :

Plot[ Tooltip @ {x^2, x^3, x^4}, {x, -2, 2},
PlotStyle -> {Red, Green, Blue},
PlotRangePadding -> 1.1] /. {Tooltip[{_, color_, line_}, tip_] :>
{Text[Style[tip, 14], {.1, 0} + line[[1, -1]]], color, line}}


Edit

Since there was another question in the comments I add another way of labeling curves. If we have to plot a graph of a family of certain functions, and insert its definition i.e.

we can make use of Drawing Tools in the Front End (a shortcut Ctrl-D) to insert some text supplemented by appropriate arrows pointing only a few of all functions.
We paste a simple text i.e. output of Text[Style["n = 13", Large, Bold, Blue]] or the definition of the functions, by double-clicking the right button of the mouse, next once the left one and selecting from menu Paste into Graphic to insert a data from the clipboard. Similarly we choose arrows from the section Tools of Drawing Tools and adjust them by dragging apprporiately. Alternatively to pasting the definition of functions with Drawing Tools, we can make use of also PlotLabel option of Plot to insert it, i.e. PlotLabel -> Subscript[f, n][x] == (1 - x^2/6 + x^4/120)^n

Plot[ Evaluate[(1 - x^2/6 + x^4/120)^n /. n -> Range[1, 30, 3]], {x, 0, Sqrt[6] },
AspectRatio -> Automatic, AxesOrigin -> {0, 0}, PlotStyle -> Thick ]


-
That's really nice. I do similar labeling with DateListPlot[] by using Epilog; I have created a function that checks the last datapoint in each series and generates the label automatically. The effect is similar but the code is not as compact as yours. –  Michael Stern Apr 19 '12 at 2:31
However in order to produce what you'd like you have to play around a bit with options, sometimes with PlotRange, PlotRangePadding and so on. –  Artes Apr 19 '12 at 2:39
This is really brilliant. One question: is there a way to label the curve with something besides the evaluated function? For example, I have a function called A[x]. I'd like the label to be "A[x]" instead of "300/x + 25". –  xiongtx Apr 20 '12 at 1:19
@xiongtx Thank you. I'm glad I could help. One can add A[x] label as well, but I updated my answer in another way, namely making use of Drawing Tools (Ctrl+D). I think it is a good alternative of labeling curves appropriately. –  Artes Apr 22 '12 at 22:15
@Artes Wow, how did you find the options of Tooltip? I mean the {_, color_, line_}, tip_ in the code, it seems not to be mentioned in the document. –  xzczd Sep 10 '12 at 11:45

I'll admit, I cheated. I use an energy level scheme, via LevelScheme, to create a my legends. It is entirely manual, but they're not bad.

Here's one such legend

which was produced with this code snippet

Figure[{

(* the rest of the figures code went here *)

ScaledFigurePanel[
{{0.03, 0.55}, {0.37, 0.77}},
PlotRange -> {{0, 10}, {0, 10}},
FrameTicks -> None,
Frame -> False
],

(* The legend is set up as a bunch of levels *)
SetOptions[ Lev, Thickness -> 1],
SetOptions[ LevelLabel, FontSize -> 0.9*tickFraction*defFontSize,
Gap -> 4],

Lev[ es, 0, 2, 8], LevelLabel[es, Right, "Excited State"],
SchemeSquare[{1, 8}, Point[5], FillColor -> Darker[Blue],
Color -> Darker[Blue], Layer -> 3],

Lev[ gs, 0, 2, 5], LevelLabel[gs, Right, "Ground State"],
SchemeCircle[{1, 5}, Point[5], FillColor -> Darker[Red],
Color -> Darker[Red], Layer -> 3 ],

Lev[ pc, 0, 2, 2], LevelLabel[pc, Right, "Partially Converged"],
SchemeCircle[{0.5, 2}, Point[5], ShowFill -> False,
Color -> Darker[Red], Layer -> 3],
SchemeSquare[{1.5, 2}, Point[5],  ShowFill -> False,
Color -> Darker[Blue], Layer -> 3]
},
PlotRange -> {{-0.1, 1.01}, {-0.1, 1.01}},
ImageSize -> {840, 680}
]


First of all, a LevelScheme graphic operates just like a normal graphic in that it is comprised of a stateful list, i.e. a primitive is only affected by the nearest preceding directive. For instance,

Graphics[{Red, Circle[], Blue, Rectangle[]}]


produces a red circle overlain by a blue rectangle. So, in the above legend, the level scheme will be placed on a ScaledFigurePanel which overlays the other plots within the Figure (not shown). From there I set styling options on both the "energy level" Lev and its label. Each level is then constructed via

Lev[ aName, x1, x2, y ]


and LevelLabel finds it using aName. From there, it is just working on the placement of the plot symbols until they look right.

-
+1 "partially converged" reminds me "almost but not quite entirely unlike tea" –  belisarius Apr 19 '12 at 2:38
@belisarius In this case, "partially converged" refers to the results of a number of iterations of a non-linear fixed point solver that almost, but not quite, converges within the tolerances you've chosen. If your curious: link.aps.org/doi/10.1103/PhysRevB.78.075114 –  rcollyer Apr 19 '12 at 2:42
Almost but not quite confusing –  belisarius Apr 19 '12 at 3:13
@belisarius if you found it not quite confusing, then you may be ahead of me. I followed only the major points in it, and am ever grateful I don't have to implement it. –  rcollyer Apr 19 '12 at 3:22
I am sure I am ahead of you ... in age –  belisarius Apr 19 '12 at 3:26

I use a homebrew solution, called as follows:

Show[Plot[{x, x^2}, {x, 0, 1}, PlotStyle -> {Red, Blue}],
tCustomLegend[{tCustomLegendItem[Line, x, PlotStyle -> Red],
tCustomLegendItem[Line, x^2, PlotStyle -> Blue]}, {0.2, 0.8}]]


Giving

The full code is:

tYellow=RGBColor[1,0.8,0.2];

tColorList=ColorData[3,"ColorList"];
tColorList[[3]]=tYellow;
tColorList[[4]]=RGBColor[0,0.6,0];
tColorList = tColorList[[{6,2,4,7,5,10,8,9,3,1}]];

tCustomLegendItem::usage="tCustomLegendItem[type,text,options]";
tCustomLegend::usage="tCustomLegend[list,loc,options]";

Begin["Private"];
Unprotect[tCustomLegendItem];
Clear[tCustomLegendItem];

Options[tCustomLegendItem]={
Rule[PlotStyle,{tColorList[[1]]}],
Rule[LegendLabelStyle,{FontSize->20}]
};

SyntaxInformation[tCustomLegendItem]={"ArgumentsPattern"->{_,_,OptionsPattern[]}};

tCustomLegendItem[type_,text_,options:OptionsPattern[]]:=Module[{object,styles,gfxOpts},

Switch[type,
Line,
object=Line[{{0,0},{4,0}}];
gfxOpts={ImageSize->40,AspectRatio->1/4},
Point,
object=Disk[];
gfxOpts={ImageSize->{40,10}},
Square,
object=Rectangle[{0,0}];
gfxOpts={ImageSize->{40,10}},
FullSquare,
object=Rectangle[{0,0}];
gfxOpts={ImageSize->30}
];

styles=Sequence@@OptionValue[PlotStyle],
styles=OptionValue[PlotStyle]
];

{
Graphics[{styles,object},gfxOpts],
Graphics[{Text[Style[text,OptionValue[LegendLabelStyle]]]},ImageSize->{Automatic,{30}}]
}
]

Protect[tCustomLegendItem];

Unprotect[tCustomLegend];
Clear[tCustomLegend];

Options[tCustomLegend]=Join[
Options[GraphicsGrid],
Options[tCustomLegendItem]
];

SyntaxInformation[tCustomLegend]={"ArgumentsPattern"->{_,{_,_},OptionsPattern[]}};

tCustomLegend[list_,loc_,options:OptionsPattern[]]:=(
Graphics@Inset[
GraphicsGrid[list,
Sequence@@Evaluate@FilterRules[{options},{Options[GraphicsGrid]}],
Alignment->{{{Center,Left}}},
Spacings->{10,0},
], (* End of GraphicsGrid *)
loc
] (* End of Graphics@Inset *)
)

Protect[tCustomLegend];

End[];

-

Here is an interactive version, with definition below.

A functional plot,

functionplot=Plot[{Sin[x],Cos[x]},{x,0,2\[Pi]},
Frame->{True,True,False,False},
FrameLabel->{"x","y(x)"},
FrameStyle->Directive[13,Italic],
PlotStyle->Thick,
PlotRangeClipping->False,
PlotRange->{-1.2,1.2},
AxesStyle->Dashed];


To label this plot with specified labels for each curve (Sin, Cos), run the following to get automatically updating labels based on mouse pointer proximity to each curve; click with the mouse to stick labels wherever you wish:

dynamicLabeled[functionplot,{{Sin,"Sine"},{Cos,"Cosine"}}]


(The above image does not do justice to the Dynamic interactivity.)

It works with ListPlot too:

data1=Table[{x,.5Exp[-1/2 ((x-5)/1)^2]+RandomReal[NormalDistribution[0,.05]]},{x,0,10,.25}];
data2=Table[{x,-Sin[x]+RandomReal[NormalDistribution[0,.08]]},{x,2,8,.1}];
dataplot=ListPlot[{data1,data2},
PlotStyle->{Thick,PointSize[0.015]},
PlotRange->{-1.2,1.45},
Joined->{True,False}];

dynamicLabeled[dataplot,{{data1,"Exponential"},{data2,"Sinusoidal"}}]


The current state of the plot for the dynamicPlot most recently clicked can be stored in a global variable for later processing or export. In the code below this is set to currentPlot.

Some parts are hard-coded (arrow styling and label styling)---you can tune those to suit, or extend the flexibility. It does not handle mixed functional-data plots, but that is easy to circumvent by turning the data into an InterpolatingFunction, or displaying the function as a Table of points. Have fun.

Here is the definition of dynamicLabeled:

Clear[dynamicLabeled];
dynamicLabeled[plot_,labelling_] := DynamicModule[
{p,x,x1,x2,storedlabels={},currentlabel,aspect,distances,xs,rs,res,ind,ps,ps1,curves,labels,pt},
curves=labelling[[All,1]];
labels=labelling[[All,2]];
aspect=Options[plot,AspectRatio][[1,2]];
Dynamic[
p=MousePosition["Graphics"];
If[p=!=None,
pt={p[[1]],p[[2]]/aspect};
Switch[curves,
_?(VectorQ[#,AtomQ]&),
(* list of functions *)
rs=Quiet@FindMinimum[Norm[pt-{x,#[x]/aspect}],{x,p[[1]]}]&/@curves;
res={#[[1]],#[[2,1,2]]}&/@rs;
distances=res[[All,1]];
xs=res[[All,2]];
_,
(* functions is a list of data *)
ps1=Flatten[Nearest[#,pt,1]]&/@(curves/.{x_?NumericQ,y_?NumericQ}:>{x,y/aspect});
distances=Norm[#-pt]&/@ps1;
ps=ps1/.{x_?NumericQ,y_?NumericQ}:>{x,y*aspect};
];
ind=Flatten[Position[distances,Min[distances]]][[1]];
];
MouseAppearance[
EventHandler[
currentPlot = Show[plot,
Epilog->{
storedlabels,
If[p=!=None,
currentlabel={
Text[Style[labels[[ind]],13],p,{0,Sign[ps[[ind]][[2]]-p[[2]]]}],
Arrow[{p,ps[[ind]]}]
}
]
}
],
{{"MouseClicked",1}:>(AppendTo[storedlabels,currentlabel])}
],
Graphics[{PointSize[0],Point[{0,0}]}]
]
]
]

-
This is very nice, but I have failed to understand how to "freeze" the result, e.g. to export it to a pdf file. Is that an option? –  chris May 17 '12 at 20:55
@chris Try this: plot=dynamicLabeled[...], (add some arrows), Export["some-path.pdf", plot] –  JxB May 17 '12 at 22:45
When I do this I don't have a plot with arrows. Am I missing something? Also, if I try Show[plot] it tells me "Show::gtype: "DynamicModule is not a type of graphics." –  chris May 18 '12 at 0:23
@chris Try clicking the plot to select it, then copy that into the export expression, or save selection as from the menu. –  JxB May 18 '12 at 4:28
Thanks that works; I would nonethless think it would be useful to be able to freeze the edited graph within mathematica because labeling might not be the last operation on the plot. Great functionality in any case. –  chris May 18 '12 at 18:08

Here's another solution providing interactive labeling functionality similar to JxB's answer. That is, you can hover over the curves to see their label as a Tooltip, but then click at any point on the curve to make the label stick permanently to that position:

Options[burnTooltips] = {ImageSize -> 360,
"LabelFunction" -> (Framed[#, FrameStyle -> None,
RoundingRadius -> 8, Background -> RGBColor[1, .8, .4]] &)};

burnTooltips[plot_, opt : OptionsPattern[]] :=
DynamicModule[{ins = {}, wrapper = OptionValue["LabelFunction"],
toolRule =
Function[{arg},
Tooltip[t__] :>
Button[Tooltip[t],
AppendTo[arg,
Inset[wrapper[Last[{t}]], MousePosition["Graphics"]]]],
HoldAll]},
EventHandler[
Dynamic@Show[plot /. toolRule[ins], Graphics@ins,
ImageSize -> OptionValue[ImageSize]], {"MouseUp",
2} :> (toolRule = {} &)]]

p = Plot[Evaluate[Table[Tooltip[4 x/l + 2, l], {l, 10, 40, 10}]], {x,
0, 100}, ImageSize -> 500,
PlotLabel -> Style["y vs x"]];

burnTooltips[p]


The picture shows a plot p in which Tooltip was used to provide labels when you hover over the lines (see the 10 with the yellow background, the mouse there too but isn't captured in the screen shot). The orange labels on the lower curves have been created by simply clicking at those spots.

The labels can be styled with the option "LabelFunction" as shown near the top of the code. it's a function accepting the Tooltip content as its argument, and outputting (by default) a Framed object. To make them partly transparent, e.g., one could give this option:

"LabelFunction" -> (Framed[#, FrameStyle -> None, RoundingRadius -> 0,
Background -> RGBColor[1, .8, .4, .5]] &)

-

The Mathematica 9 PlotLegends -> Placed is nicer than the old PlotLegends package. Here's an example:

Plot[{BesselK[2, z], BesselK[2, 1/z]}, {z, 0.3, 3},
AxesLabel -> {z},

PlotLegends ->
TraditionalForm[BesselK[2, 1/z]]}, {Right, Bottom}]
]


-
Mathematica 9 is nicer, but legends of this type violate the proximity-compatibility principle, making it harder for the viewer to determine which label goes with which line. It is best, when possible, to put the labels at the end of each line as shown in one of the solutions above. This also increases the data-ink ratio, because it eliminates the (now) redundant lines and symbols that are present when you use PlotLegends. –  Todd Johnson May 8 '13 at 15:28
I agree. These sorts of legends are also pretty much useless if the image is printed in black and white. –  Peeter Joot May 8 '13 at 17:09

The SciDraw package (LevelScheme successor) has support for labelling curves. Here's an example:

Needs["SciDraw"]

Figure[
FigurePanel[
{
FigLine[Plot[Sin[x], {x, 0, 10}],
LineColor -> Apricot,
CenterLabel -> "sine", CenterLabelPosition -> 0.55,
TextOffset -> Top];

FigLine[Plot[Cos[x], {x, 0, 10}],
LineColor -> OliveDrab,
CenterLabel -> "cosine", CenterLabelPosition -> 0.6,
TextOffset -> Bottom
];
},
XPlotRange -> {0, 10}, YPlotRange -> 1.2 {-1, 1},
Style -> {FigLine -> {LineThickness -> 2}}
]
]
`

It also has support for positioning styled labels with anchor points.

-
Wow SciDraw is incredible. Thanks for the heads up! –  Gabriel Dec 9 '13 at 0:40
@Gabriel The thanks should go to Mark Caprio, the author of SciDraw. I'm sure he'll appreciate the feedback! :-) –  Szabolcs Dec 9 '13 at 2:19
Already sent! I love this package so much ... it is such a great framework. –  Gabriel Dec 9 '13 at 3:43