Dynamic Linkage of LocatorPane and InputField

6

3

How do I get the InputField to coordinate with the LocatorPane so that a change in each control changes the other to be in agreement? It would be nice if the function was self-contained and was dynamically linked to a second similar control where the variable is radians. The angle control is based on this (see Applications). Related links do not address or solve this particular issue; links such as this this, and this

 fDeg[Dynamic[angleDeg_]] :=   
   DynamicModule[{p, angleRad, angleCalc, dtr = Degree},  

    angleCalc[newp_,   
      oldp_] := (angleRad =   
       angleRad + ArcCos[newp.oldp] Sign[Cross[newp].(newp - oldp)];   
      If[0 < angleRad, angleRad = Mod[angleRad, +2*Pi]];  
      If[0 > angleRad, angleRad = Mod[angleRad, -2*Pi]];  
      angleDeg = angleRad/dtr;  
      f[angleDeg];  
      p = {Cos[angleRad], Sin[angleRad]});  

    angleRad = angleDeg*dtr;  
    p = {Cos[angleRad], Sin[angleRad]};  

    LocatorPane[Dynamic[p, (angleCalc @@ Normalize /@ {#, p}) &],   
     Dynamic@Show[  
       Graphics[{Circle[], Arrowheads[0.15],   
         Arrow[Dynamic[{{0, 0}, p}]]}, ImageSize -> Tiny],  
       Graphics[{Dynamic[{Text[  
            NumberForm[angleDeg, {3, 2}], {0, 0}]}]}]],   
     Appearance -> None]];  

 aDeg = 45;  

 Column[{  
   "Degrees:",  
   fDeg[Dynamic[aDeg]],  
   "aDeg: ",   
   InputField[Dynamic[aDeg],   
    FieldSize -> 6]  
   }, Alignment -> Center]  

kmutiny

Posted 2019-03-10T21:11:07.680

Reputation: 71

Answers

0

I think the following code solves the issue, though I’m not sure exactly why it works but the other didn't. Note that the function is self-contained, and that the InputField should be placeable in a separate part of an application. Also note the added features. The next step would be to make a function for radians and link the 2 controls.

 DynamicModule[{gSquare, dtr, rtd, AngularControlDegFnc01,   
   AngularControlRadFnc01, aDeg, aRad, ShowDeg},  

  AngularControlDegFnc01[Dynamic[aDegArg_]] := DynamicModule[  
    {  
     aRadH,  
     dtrH = Pi/180., rtdH = 180./Pi,  
     Pi2H = 2*Pi, icrH = .25, thH = 0.01, athH = 0.017,  
     c1H = RGBColor[.8, .5, .5], c2H = RGBColor[.5, .5, .8],  
     pH, pCalcH, aCalcH, cCalcH, mCalcH, wCalcH, rCalcH  
     },  

    rCalcH[] := aRadH = aDegArg*dtrH;  
    wCalcH[] := {icrH*pCalcH[], pCalcH[]};  
    pCalcH[] := pH = {Cos[aRadH], Sin[aRadH]};  
    cCalcH[] := If[Sign[aRadH] == -1, Return[c1H], Return[c2H]];  
    mCalcH[] := Block[{},  
      If[0 < aRadH, aRadH = Mod[aRadH, +Pi2H]];  
      If[0 > aRadH, aRadH = Mod[aRadH, -Pi2H]];];  
    aCalcH[newpArg_, oldpArg_] := Block[{},  
      aRadH =   
       aRadH + ArcCos[newpArg.oldpArg]*  
         Sign[Cross[newpArg].(newpArg - oldpArg)];  
      aDegArg = aRadH*rtdH;  
      mCalcH[];  
      pCalcH[]];  

    rCalcH[];  
    pCalcH[];  

    LocatorPane[Dynamic[pH, (aCalcH @@ Normalize /@ {#, pH}) &],  
     Dynamic[ShowDeg = Show[{  
         Graphics[{cCalcH[], Disk[{0, 0}, 1, {0, rCalcH[]}]}],  
         Graphics[{Black, Thickness[thH], Line[{{0, 0}, {1, 0}}]}],  
         Graphics[{GrayLevel[.8], Disk[{0, 0}, icrH]}],  
         Graphics[{Thickness[thH], Circle[{0, 0}, icrH]}],  
         Graphics[{Black, Thickness[1.5*thH], Circle[]}],  
         Graphics[{Black, Thickness[athH], Arrowheads[5*athH],   
           Arrow[wCalcH[]]}],  
         Graphics[{Dynamic[{Text[  
              NumberForm[Round[aDegArg, 0.01], {3, 1}], {0, 0}]}]}]},   
        ImageSize -> 150]  

      ], Appearance -> None]  
    ];  

  gSquare =   
   Graphics[{Gray, Thick,   
     Line[1.4*{{-1, -1}, {-1, +1}, {+1, +1}, {+1, -1}, {-1, -1}}]}];  
  dtr = Pi/180.;  
  rtd = 180./Pi;  
  aDeg = 45;  
  aRad = aDeg*dtr;  

  Manipulate[  
   Dynamic@Show[{ShowDeg, gSquare}, ImageSize -> 150],  
   Row[{  
     Column[{  
       AngularControlDegFnc01[Dynamic[aDeg]],  
       Row[{"aDeg: ", InputField[Dynamic[aDeg], FieldSize -> 6]}]  
       }]  
     }],  
   ControlPlacement -> Left  
   ]  
  ]  

kmutiny

Posted 2019-03-10T21:11:07.680

Reputation: 71

5

Consider the following refactoring of your code

First, the locator pane is used to set a point location constrained on a circle with unit radius (this part borrowed heavily from an example in the documentation for LocatorPane). A degree value is then calculated from the coordinates of tha tpoint.

Secondly, the input field is used to read in a degree value, which is then used to update the position of the dynamic point pt using the second argument of Dynamic.

DynamicModule[
  {pt = {0, 1.}},
  Column[{
    "Degrees:", Dynamic[(ArcTan @@ pt)/Degree],
    LocatorPane[
      Dynamic[pt],
      Graphics[{
        Circle[],
        PointSize[Large],
        Dynamic[Arrow[{{0, 0}, pt/Norm[pt]}]]
      }],
      Appearance -> None
    ],
    InputField[
      Dynamic[(ArcTan @@ pt)/Degree, (pt = N@{Cos[#1 Degree], Sin[#1 Degree]}) &],
      FieldSize -> Tiny
    ]
    }, Alignment -> Center
  ]
]

This keeps both fields in sync with each other:

animation shows the locator and input field working together

MarcoB

Posted 2019-03-10T21:11:07.680

Reputation: 53 573

MarcoB. This is nice. But I was hoping for a self-contained function where only aDeg is passed. One reason for this is to allow aDeg to be altered elsewhere by some process such as a simulation of an automated flight control system. The controls (LocatorPane and InputField) would change accordingly. But the simulation could be interrupted by the user by means of the controls, and a new value input by the field or the dial. Any thoughts? – kmutiny – 2019-03-11T02:25:01.023

3

An alternative way to use Experimental`AngularSlider is to use a single dynamic variable and limit the angles to the range 0 to 360 using {0,360} as the second argument of AngularSlider:

DynamicModule[{z = 120}, 
 Column[{Experimental`AngularSlider[Dynamic[z], {0, 360}, 
   Experimental`BoundaryAction -> "Clip"], 
   InputField[Dynamic[z], ImageSize -> {100, 24}, BaseStyle -> 16]}, 
  Alignment -> Center]]

enter image description here

To display angles in radians and degrees and allow the slider to move around the clock:

DynamicModule[{z = 120}, 
 Panel @ Column[{Style["Degree", 16], 
    Overlay[{Experimental`AngularSlider[Dynamic[z,(z = Round[#, 1]) &], {0, 360}, 
       Experimental`BoundaryAction -> "Wrap", ImageSize -> 200], 
      Graphics[{Text[Framed[Style[Dynamic[Round[z,1]] °, 16], FrameStyle -> None], 
         Scaled @ {3/4, 4/10}, {-1, 1}], 
       Text[Framed[Style[Dynamic[Round[z Degree, Pi/2^7] ], 16], FrameStyle -> None],
        Scaled @ {3/4, 4/10}, {-1, -1}]}]}, All, 1], 
    InputField[Dynamic[z, (z = Round[#, 1]) &], ImageSize -> {100, 24}, BaseStyle -> 16]},
   Alignment -> Center]]

enter image description here

If you want to play with various options of Experimental`AngularSlider you can use

NotebookTools`AngularSliderTest[]

enter image description here

kglr

Posted 2019-03-10T21:11:07.680

Reputation: 302 076

2

Here is a simplified demo linking an InputField[] with Experimental`AngularSlider[]:

DynamicModule[{x = 0., u = 0.}, 
              Column[{Experimental`AngularSlider[Dynamic[x, (x = #; u = x/Degree) &]], 
                      InputField[Dynamic[u, (u = #; x = u Degree) &], Number]}]]

linking AngularSlider and InputField

Clicking on the AngularSlider[] updates the value in the InputField[], and entering a (degree) value in the InputField[] moves the slider to the specified angle.

J. M.'s ennui

Posted 2019-03-10T21:11:07.680

Reputation: 115 520