# How to position text labels automatically to not overlap other graphics elements?

I was plotting reciprocal frame projection using Locators for a point to be expressed in terms of both the original basis (also specified with Locators and reciprocal basis.)

EDIT: Here's a stripped down example of the code (no longer matching the image)

Manipulate[DynamicModule[{f1, f2, xf, o, r, s}, o = {0, 0};
s = {0.1, 0.1};
r = Inverse[{e1, e2}]; f1 = Part[r, All, 1]; f2 = Part[r, All, 2];
xf = {x.f1, x.f2};
Graphics[{Arrow[{o, x}], Arrow[{o, xf[[1]] e1}],
Arrow[{xf[[1]] e1, xf[[1]] e1 + xf[[2]] e2}], Text["x", x + s],
Text["e1", e1 + s], Text["e2", e2 + s]}]], {{x, {4, 2}},
Locator}, {{e1, {1, 1}}, Locator}, {{e2, {1, 2}}, Locator}]


I've placed labels on some of the arrows that represent the vectors, but did this by fudging things adding in a fixed offsets (variable s above).

The manual offsets are in some cases positioned reasonably for some of the labels (because my hardcoded offsets are implicitly related to the initial geometry of the Locators).

I'd like to avoid hardcoding those offsets in the hacky way that I've done. Is there a better way to automatically position those Text labels so that they are offset slightly (e.g. the width of a text character) from nearby graphics elements?

-
It is not the same but you may consider using some of similar solutions as there: labeling individual curves –  Kuba Sep 25 at 13:47
This may help mathematica.stackexchange.com/a/14149/193 –  belisarius Sep 25 at 14:20
@belisarius, that answer is very cool, but I don't see how I'd use that interactive label placement to solve the problem of label placement in a Manipulate context, where the location of the labels could vary when the Locator's are moved. –  Peeter Joot Sep 25 at 15:38
@PeeterJoot That was the reason why I said It may help :) –  belisarius Sep 25 at 15:45
The part of this question that bothers me is "offset slightly from nearby graphics elements". How is the function that automates the label offsets going know what "nearby" and "slightly" means? –  m_goldberg Sep 26 at 1:07

Here's an attempt to implement the idea suggested by Szabolcs, based on Heike's clever image processing method. The code uses MinFilter to identify regions where the label may go without overlapping anything, and Nearest to pick the closest point to the desired position. The rest is just scaling between image and graphics coordinates.

It won't be fast enough for use in dynamic graphics, and there are no doubt numerous ways to break it, but I thought it was a cool idea and worth having a go at.

positionlabel[g_Graphics, {label_, x_}] :=
Module[{p, b, bd, xi, m, ivp, sc},
p = PlotRange[g];
bd = ImageDimensions[b];
xi = bd MapThread[Rescale, {x, p}];
m = MinFilter[b, 1 + Reverse[Rasterize[TraditionalForm@label, "RasterSize"]/2]];
ivp = ImageValuePositions[m, 1];
sc = If[ivp == {}, x, Scaled[First[Nearest[ivp, xi]]/bd]];
Graphics@Inset[label, sc, Center]]

addlabels[g_Graphics, labels_] := Fold[Show[#1, positionlabel[##]] &, g, labels]


The labels must be supplied as a list like {{"label1", {x1, y1}}, {"label2", {x2, y2}}, ...}. Here's an example:

g = Plot[Sin[x], {x, 0, 10}, Frame -> True,
Epilog -> {PointSize[Large], Point[{#, Sin[#]} & /@ Range[0, 10]]}];

labels = {Style[#, 20], {#, Sin[#]}} & /@ Range[0, 10];



-
Awesome Simon. I just used this in some plots of melting points using ElementData[], and used this to label the points with the element names. –  Peeter Joot Sep 27 at 1:46
Extremely nice solution. A pity that you still have to look for an approximately appropriate spot for the label location. It would be really nice if, for some set of parametrized curves, you just specified the label for each curve and Mathematica would find a visually pleasing location automatically. But that would imply defining rules for the placement's aesthetics. Pretty hard that. –  Sjoerd C. de Vries Sep 27 at 19:33

The graphics display issues can be diminished by

1. Setting the Texts to Background-> White.
2. Offsetting the Arrow heads (but not the tails).

If there is overlap, the offset and the white background should make it less distracting.

For readability, I recommend using Offset rather than using s.

Manipulate[DynamicModule[{f1, f2, xf, o, r, s}, o = {0, 0};
r = Inverse[{e1, e2}]; f1 = Part[r, All, 1]; f2 = Part[r, All, 2];
xf = {x.f1, x.f2};
Graphics[{
Arrow[{o, x}, {0, .15}],
Arrow[{o, xf[[1]] e1}, {0, .15}],
Arrow[{xf[[1]] e1, xf[[1]] e1 + xf[[2]] e2}, {0, .15}],
Text["x", Offset[{20, 0}, x], Background -> White],
Text["e1", Offset[{20, 0}, e1], Background -> White],
Text["e2", Offset[{20, 0}, e2], Background -> White]},
Frame -> True, GridLines -> Automatic,
GridLinesStyle -> Directive[Orange, Dashed], BaseStyle -> 16]],
{{x, {4, 2}}, Locator},
{{e1, {1, 1}},
Locator}, {{e2, {1, 2}}, Locator}]


-