2
$\begingroup$

In a previous answer posted by Simon Woods on the question of how to automatically locate non-overlapping labels to data shown in a ListPlot; see below:

Using this as the as a starting point from the original question:

data=Table[{t,Re[3 Exp[I 5/2 t]]-t,Im[5 Exp[I 5/2 t]]},{t,0,10}];
labels=Text["Label " <>ToString[#[[1]]],1.1 #[[{2,3}]]]&/@data
dataPlot=ListPlot[data[[All,{2,3}]],PlotStyle->PointSize->Large];
Show[dataPlot, Graphics[{Red, labels}], PlotRange -> 10{{-1, 1}, {-1, 1}}, AspectRatio->1]

Simon Wood's proposed this solution:

positionlabel[g_Graphics,{label_,x_},o_]:=Module[{p,b,bd,xi,ls,m,ivp,nf,xx,pos,d,p1,sc},
  p=Charting`get2DPlotRange[g];
  b=ImagePad[ImagePad[Binarize@Show[g,ImagePadding->0],-1],1,Black];
  bd=ImageDimensions[b];
  xi=bd MapThread[Rescale,{x,p}];
  ls={0,4}+Reverse[Rasterize[Style[label,"Graphics"],"RasterSize"]/2];
  m=MinFilter[b,ls];ivp=ImageValuePositions[m,1];
  sc=If[ivp=={},x,nf=Nearest[ivp];
  xx=Table[xi+a o Reverse[ls],{a,{1,-1,0}}];
  pos=First[nf[#]]&/@xx;d=MapThread[EuclideanDistance,{pos,xx}];
  p1=First@Pick[pos,Negative[d-2 Min[d]]];
  Scaled[p1/bd]];
  Graphics@Inset[label,sc,Center]]


addlabels[g_Graphics,labels_, o_:{0,0}]:=Fold[Show[#1, positionlabel[##, o]]&, g, labels] 

I found this to work well with low point density, but it becomes too confusing with a large number of points. It seems that a visual guide to the label would be very helpful. How would one add a line between each data point and its label and still use basic solution for placing the labels, which prevents overlap?

For reference this is something I found that shows what I am looking to achieve with the exception that it doesn't automatically place the Labels:

colors=ColorData[1,"ColorList"][[;;Length@data]];
Deploy@DynamicModule[{pt=({##2}&@@@data),pt2=(2 {##2}&@@@data),
lbls=Module[{i=1},Framed["Label"<>ToString[#],FrameStyle->colors[[i++]]]&@@@data]}, 
ListPlot[List/@pt,PlotStyle->PointSize[Large],PlotRange->30 {{-1,1},`enter code here`{-1,1}},AspectRatio->1,
    Epilog->(Dynamic[{colors[[#]],Line[{pt2[[#]],pt[[#]]}],
    Locator[Dynamic@pt2[[#]],Style[lbls[[#]],colors[[#]],"Panel"]]}&/@Range [Length@pt]])]]

Which would result in this after manually moving the labels:

Mathematica graphics

I am having difficulty determining the resulting location of the each Label form Simon's solution which is needed to define each Line segment to each point in the point.

Hopefully I have uploaded the example code and images correctly, but this is the first time I have used stack exchange.

Kind Regards,

Charles Koehler

$\endgroup$
4
$\begingroup$
data = Table[{t, Re[3 Exp[I 5/2 t]] - t, Im[5 Exp[I 5/2 t]]}, {t, 0, 10}];
labels2 = {Style["Label " <> ToString[#[[1]]], 12], 1.1 #[[{2, 3}]]} & /@ data;
llp = addlabels[ListPlot[List /@ data[[All, {2, 3}]], 
   BaseStyle -> PointSize -> Large], labels2]

enter image description here

First, we extract the scaled coordinates of Insets and Rescale them using PlotRange[llp]. We create a rule associating each label with its rescaled coordinates:

insetLabelsToCoords = Cases[llp, Inset[lbl_, Scaled[coord_], ___] :> 
   (First@ lbl -> (Rescale[#, {0, 1}, #2]&@@@Thread[{coord, PlotRange[llp]}])), Infinity];

We create a second rule associating each original coordinate with its label:

coordsToLabels = #[[2;;3]] -> "Label " <> ToString[#[[1]]] & /@ data;

For each point in data we find the coordinates of the Inset containing the label of that point:

lines = Line[{#[[2;;3]] /. coordsToLabels /. insetLabelsToCoords, #[[2;;3]]} & /@ data];

Show[llp, Graphics[{Arrowheads[.02], Arrow@lines}], ImageSize -> 500]

enter image description here

Original answer:

An alternative approach using Callout:

data2 = {Callout[#[[2;;3]],"Label " <> ToString[#[[1]]], 
         Background -> LightGray, CalloutMarker -> "Circle"]} & /@ data;

ListPlot[data2, BaseStyle -> PointSize -> Large]

enter image description here

$\endgroup$
  • $\begingroup$ Hello kglr, Thanks for both answers, they both have merit. The Callout solution is easy to use but it has the problem of allowing overlapping Labels, while the first solution is very close to what I need, it seems to arbitrarily position the start of the arrow at the center of the label as opposed to a location around the Label that is closest to the point. $\endgroup$ – Charles Koehler Jul 14 '17 at 19:53
  • $\begingroup$ Hi @Charles, I can't think of a straightforward way for finding the location around the Label that is closest to the point. This would require finding the bounding boxes of the labels (which depends on the size of label content) in addition to the centers obtained from Simon's function. $\endgroup$ – kglr Jul 14 '17 at 20:29
  • $\begingroup$ Hi @kglr, at this point is seems too difficult to make this work any better. In my usage I will only use the Label and Arrow function to identify outliers in my data plots. Thank you for your help for now. If I find or develop any improvements I will post them to share. Best Regards: $\endgroup$ – Charles Koehler Jul 17 '17 at 14:37
  • $\begingroup$ @Charles, thank you for the accept. $\endgroup$ – kglr Jul 17 '17 at 14:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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