5
$\begingroup$

I want to construct a graphics with a locator on top of it. This locator normally has to be invisible, but when I click somewhere in the graphics, the least that has to happen is that the locator becomes visible and moves to the clicked position.

Therefore, the easiest way for doing so seems to be to use a LocatorPane with one locator. When we click somewhere in the displayed pane, the locator will move automatically to the clicked position.

A locator is a controller, and therefore the visibility/invisibility of the locator seems to be a trivial application of ControlActive. However, it turns out to be more complicated. To illustrate the problem, consider the following example:

{a,b}={0.5,0.5};
LocatorPane[ Dynamic[{a,b}], 
  Graphics[{{Yellow, Rectangle[]},Locator[Dynamic[{a,b+0.1}], Dynamic[ControlActive["graphicsyes", "graphicsno"]]]}], 
  Appearance->Dynamic[ControlActive["paneyes", "paneno"]]]

The displayed pane has two locators. One is created by the LocatorPane itself. I will call it the pane locator; it is shown as paneyes or paneno. The other one is part of the second argument of the locator pane. I will call it the graphics locator. It will be drawn a little bit above the pane locator and shown as graphicsyes or graphicsno.

When we click in the yellow square and keep the mouse pressed, we see that the pane locator moves to the clicked position and the graph locator to corresponding position above it. Since the mouse is pressed, controllers are active, but none of the locators is shown with yes. Despite the Dynamic, the fact that controllers are active seems not to be detected.

It turns out that we can force the detection by using UpdateInterval:

{a,b}={0.5,0.5};
LocatorPane[ Dynamic[{a,b}], 
  Graphics[{{Yellow, Rectangle[]},Locator[Dynamic[{a,b+0.1}], Dynamic[ControlActive["graphicsyes", "graphicsno"], UpdateInterval->0.5]]}],
  Appearance->Dynamic[ControlActive["paneyes", "paneno"], UpdateInterval->0.5]]

Now, given that UpdateInterval works, we can force the updating by simply wrapping the second argument in Dynamic. When we click, the values of a and b change, and therefore the pane is redrawn.

{a,b}={0.5,0.5};
LocatorPane[ Dynamic[{a,b}], 
  Dynamic[Graphics[{{Yellow, Rectangle[]},Locator[{a,b+0.1}, ControlActive["graphicsyes", "graphicsno"]]}]],  
  Appearance->Dynamic[ControlActive["paneyes", "paneno"]]]

Observe that when we click in the pane, it is only the graphics locator that shows that controllers are active. The pane locator remains as it was.

Maybe it has a simple explanation, but I fail to see why we need UpdateInterval, or why we should wrap the second argument of LocatorPane in Dynamic, for detecting that controllers are active. Any comment is highly appreciated.

This site encourages to answer your own question. So I present an answer, in which I give some solutions for my problem. I consider them as rather 'artificial'. I hope someone can provide a more straightforward solution.

$\endgroup$
4
$\begingroup$

The last command in the question contains a solution: the graphics locator does what we want, we do not need the pane locator. So we position the graphics locator on the pane locator and make the pane locator permanently invisible. Instead of the appearances graphicsyes and graphicsno, we use a graphics expression for the crosshair and the empty string for invisibility.

{a,b}={0.5,0.5};
LocatorPane[ Dynamic[{a,b}], 
  Dynamic[Graphics[{{Yellow, Rectangle[]},
    Locator[{a,b}, ControlActive[Graphics[{LightGray, Disk[{0,0},4], Black,Line[{{{-6,0},{6,0}},{{0,-6},{0,6}}}],Circle[{0,0},4],White,Disk[{0,0},1.5]},ImageSize->18], ""]]}]], 
  Appearance->None]

Closely related to ControlActive is the symbol $ControlActiveSetting. When it is evaluated, it returns True when controllers are active and otherwise False. So when we evaluate this symbol inside the second argument of a LocatorPane and assign the result to a variable, say cc, then we can use cc to determine the appearance of the pane locator. The graphics locator then is superfluous:

LocatorPane[ Dynamic[{a,b}], 
  Dynamic[cc=$ControlActiveSetting; Graphics[{a,b}; {Yellow, Rectangle[]}]],
  Appearance->Dynamic[If[cc,"paneyes", "paneno"]]]

In this solution, we still have to replace paneyes by a crosshair and paneno by the empty string.

Using this technique in the first solution makes it possible to use the default crosshair, giving a short, but in my eyes tricky, solution:

{a,b}={0.5,0.5};
LocatorPane[ Dynamic[{a,b}], 
  Dynamic[Graphics[{{Yellow, Rectangle[]},cc=$ControlActiveSetting;Locator[{a,b}, If[cc, Automatic, None]]}]], 
  Appearance->None]
$\endgroup$
  • $\begingroup$ What is the {a,b}; at the start of Graphics doing in the second solution? I have not previously seen this usage inside of Graphics at that location and it does not appear to match any form from the documentation. $\endgroup$ – Edmund Apr 1 '16 at 19:07
  • 1
    $\begingroup$ @Edmund. It is the first argument of a CompoundExpression, so when the Graphics is evaluated, a and b are evaluated without any effect on the Graphics as such. But when you remove it, the solution as it is given does not work. The surrounding Dynamic then does not contain an a or b, so it will not be updated when you click on the pane. So the goal is to force updating of Dynamic. I tried to overcome that by adding the option TrackedSymbols:>{a,b} to this Dynamic, but that did not work.. I expect in practice this trick is not needed, because the argument will automatically depend on a and b. $\endgroup$ – Fred Simons Apr 1 '16 at 21:00
  • $\begingroup$ Ah, I see it is for the Dynamic. I too removed it and noticed that it stopped working but did not make the connection. Thanks. $\endgroup$ – Edmund Apr 1 '16 at 21:26
  • $\begingroup$ oops, I see that my "deeper probing" was only to duplicate the method you started with. I perhaps do not understand the question; are you more interested in a pragmatic solution or the explanation for the need of UpdateInterval? $\endgroup$ – Mr.Wizard Apr 9 '16 at 16:24
  • $\begingroup$ @Mr.Wizard. Not that I reread it, I think my question could be better formulated: why does in my first example Dynamic around the second argument of the locator not work? Workarounds are to place the complete locator in a Dynamic, as you did (which gives indeed a much nicer solution), or wrap the second argument of the locator pane in Dynamic, thereby obtaining updating of the graphics as well. And related: why do I need two locators in this construction, one permanently being invisible? I would like so see a solution with the pane locator only, so that I do not need to add a locator manually. $\endgroup$ – Fred Simons Apr 10 '16 at 11:47
3
$\begingroup$

This seems a little cleaner to me:

p = {0.5, 0.5};

LocatorPane[Dynamic[p], 
 Graphics[{
   {Yellow, Rectangle[]}, 
   Dynamic @ Locator[p, ControlActive[Automatic, None]]
 }],
 Appearance -> None
]
$\endgroup$

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.