How to annotate multiple datasets in ListPlots

2019-01-23 17:15发布

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?

3条回答
相关推荐>>
2楼-- · 2019-01-23 17:53

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

查看更多
一夜七次
3楼-- · 2019-01-23 17:54

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

查看更多
劳资没心,怎么记你
4楼-- · 2019-01-23 17:54

Are you going to be plotting computable curves or actual data?

If it's computable curves, then it's common to use a plot legend (key). You can use different dashings and thicknesses to differentiate between the lines on a grayscale printer. There are many examples in the PlotLegends documentation.

If it's real data, then normally the data is sparse enough that you can use PlotMarkers for the actual data points (i.e. don't specify Mesh). You can use automatic PlotMarkers, or you can use custom PlotMarkers including BoxWhisker markers to indicate the various uncertainties.

查看更多
登录 后发表回答