Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to annotate multiple datasets in ListPlots

Frequently I have to visualize multiple datasets simultaneously, usually in ListPlot or its Log-companions. Since the number of datasets is usually larger than the number of easily distinguishable line styles and creating large plot legends is still somewhat unintuitiv I am still searching for a good way to annotate the different lines/sets in my plots. Tooltip is nice when working on screen, but they don't help if I need to pritn the plot.

Recently, I played around with the Mesh option to enumerate my datasets and found some weird stuff

GraphicsGrid[Partition[Table[ListPlot[
Transpose@
 Table[{Sin[x], Cos[x], Tan[x], Cot[x]}, {x, 0.01, 10, 0.1}], 
PlotMarkers -> {"1", "2", "3", "4"}, Mesh -> i, Joined -> True, 
PlotLabel -> "Mesh\[Rule]" <> ToString[i], ImageSize -> 180], {i, 
1, 30}], 4]]

The result looks like this on my machine (Windows 7 x64, Mathematica 8.0.1):

 Mesh = i, with i running from 1 to 30

Funnily, for Mesh->2, 8 , and 10 the result looks like I expected it, the rest does not. Either I don't understand the Mesh option, or it doesn't understand me.

Here are my questions:

  1. is Mesh in ListPLot bugged or do I use it wrongly?
  2. how could I x-shift the mesh points of successive sets to avoid overprinting?
  3. do you have any other suggestions how to annotate/enumerate multiple datasets in a plot?
like image 360
Markus Roellig Avatar asked Aug 08 '11 21:08

Markus Roellig


2 Answers

You could try something along these lines. Make each line into a button which, when clicked, identifies itself.

plot=Plot[{Sin[x],Cos[x]},{x,0,2*Pi}];
sinline=plot[[1,1,3,2]];
cosline=plot[[1,1,4,2]];
message="";
altplot=Append[plot,PlotLabel->Dynamic[message]];
altplot[[1,1,3,2]]=Button[sinline,message="Clicked on the Sin line"];
altplot[[1,1,4,2]]=Button[cosline,message="Clicked on the Cos line"];
altplot

If you add an EventHandler you can get the location where you clicked and add an Inset with the relevant positioned label to the plot. Wrap the plot in a Dynamic so it updates itself after each button click. It works fine.

In response to comments, here is a fuller version:

plot = Plot[{Sin[x], Cos[x]}, {x, 0, 2*Pi}];
sinline = plot[[1, 1, 3, 2]];
cosline = plot[[1, 1, 4, 2]];
AddLabel[label_] := (AppendTo[plot[[1]],
    Inset[Framed[label, Background -> White], pt]];
   (* Remove buttons for final plot *)
   plainplot = plot;
   plainplot[[1, 1, 3, 2]] = plainplot[[1, 1, 3, 2, 1]];
   plainplot[[1, 1, 4, 2]] = plainplot[[1, 1, 4, 2, 1]]);
plot[[1, 1, 3, 2]] = Button[sinline, AddLabel["Sin"]];
plot[[1, 1, 4, 2]] = Button[cosline, AddLabel["Cos"]];
Dynamic[EventHandler[plot,
  "MouseDown" :> (pt = MousePosition["Graphics"])]]

To add a label click on the line. The final annotated chart, set to 'plainplot', is printable and copyable, and contains no dynamic elements.

[Later in the day] Another version, this time generic, and based on the initial chart. (With parts of Mark McClure's solution used.) For different plots 'ff' and 'spec' can be edited as desired.

ff = {Sin, Cos, Tan, Cot};
spec = Range[0.1, 10, 0.1];
(* Plot functions separately to obtain line counts *)
plots = Array[ListLinePlot[ff[[#]] /@ spec] &, Length@ff];
plots = DeleteCases[plots, Line[_?(Length[#] < 3 &)], Infinity];
numlines = Array[Length@Cases[plots[[#]], Line[_], Infinity] &,
   Length@ff];
(* Plot functions together for annotation plot *)
plot = ListLinePlot[#@spec & /@ ff];
plot = DeleteCases[plot, Line[_?(Length[#] < 3 &)], Infinity];
lbl = Flatten@Array[ConstantArray[ToString@ff[[#]],
      numlines[[#]]] &, Length@ff];
(* Line positions to substitute with buttons *)
linepos = Position[plot, Line, Infinity];
Clear[line];
(* Copy all the lines to line[n] *)
Array[(line[#] = plot[[Sequence @@ Most@linepos[[#]]]]) &,
  Total@numlines];
(* Button function *)
AddLabel[label_] := (AppendTo[plot[[1]],
    Inset[Framed[label, Background -> White], pt]];
   (* Remove buttons for final plain plot *)
   plainplot = plot;
   bpos = Position[plainplot, Button, Infinity];
   Array[(plainplot[[Sequence @@ Most@bpos[[#]]]] =
       plainplot[[Sequence @@ Append[Most@bpos[[#]], 1]]]) &,
    Length@bpos]);
(* Substitute all the lines with line buttons *)
Array[(plot[[Sequence @@ Most@linepos[[#]]]] = Button[line[#],
      AddLabel[lbl[[#]]]]) &, Total@numlines];
Dynamic[EventHandler[plot,
  "MouseDown" :> (pt = MousePosition["Graphics"])]]

Here's how it looks. After annotation the plain graphics object can be found set to the 'plainplot' variable.

Annotated Chart

like image 94
Chris Degnen Avatar answered Sep 20 '22 19:09

Chris Degnen


One approach is to generate the plots separately and then show them together. This yields code that is more like yours than the other post, since PlotMarkers seems to play the way we expect when dealing with one data set. We can get the same coloring using ColorData with PlotStyle. Here's the result:

ff = {Sin, Cos, Tan, Cot};
plots = Table[ListLinePlot[ff[[i]] /@ Range[0.1, 10, 0.1],
    PlotStyle -> {ColorData[1, i]},
    PlotMarkers -> i, Mesh -> 22], {i, 1, Length[ff]}];
(* Delete the spurious asymptote looking thingies. *)
plots = DeleteCases[plots, Line[ll_?(Length[#] < 4 &)], Infinity];
Show[plots, PlotRange -> {-4, 4}]

enter image description here

like image 20
Mark McClure Avatar answered Sep 20 '22 19:09

Mark McClure