6
$\begingroup$

How do I constrain the region where I can drag a Locator to conform to rules, including rules that apply to computations resulting from the value of the Locator?

I understand how I can impose some very general constraints using LocatorRegion's Automatic and Full settings, but these don't (appear to) allow me to:

  1. Limit a single coordinate the Locator can assume, while allowing the other coordinates to cover the entire range of a plot.

  2. Limit the coordinates Locators can assume based on a constraint placed on a function they generate.

For example, in the following, code, how do (1) I constrain c5 to the right hand edge of the figure (i.e to have an x value of 1) and (2) constrain all other Locators so that the resulting function never goes above the line y=x?

Manipulate[Show[
  Plot[x, {x, 0, 1}],
  ParametricPlot[f[{{0, 0}, c1, c2, c3, c4, c5}][x], {x, 0, 1}]],
 {{c1, {.2, .2}}, Locator},
 {{c2, {.4, .4}}, Locator},
 {{c3, {.6, .6}}, Locator},
 {{c4, {.8, .8}}, Locator},
 {{c5, {1, 1}}, Locator},
 {{f, BezierFunction, "Form"}, {BezierFunction, BSplineFunction}}]

That is, I want to prevent this,

enter image description here

by ensuring (1) that c5 stays at x=1, while being free to assume a range of y values; and this

enter image description here

by (2) preventing any of the Locators from being moved to locations that would result in the generated plot from passing above the y=x line -- though a Locator may still be allowed above that line if this condition is not violated:

enter image description here

$\endgroup$
3
  • $\begingroup$ See this answer for a simple example of constraining locators. Yours is a little more involved, because you're not constraining the locators directly, but indirectly in that they can move where ever as long as the curve doesn't cross $y=x$ $\endgroup$
    – rm -rf
    Oct 28, 2012 at 15:54
  • $\begingroup$ @rm-rf: I'm sure how to apply that here. If I just replace Locator with Locator[Dynamic[...]], the locator still behaves as before (though it becomes initially invisible). $\endgroup$
    – orome
    Oct 28, 2012 at 16:42
  • 2
    $\begingroup$ Seen this question/answers? $\endgroup$
    – kglr
    Oct 28, 2012 at 17:24

1 Answer 1

6
$\begingroup$

One way to achieve this would be to define a "clipping" function that applies the constraints to the points:

clip[pts_] :=
  ReplacePart[pts /. {x_, y_} /; y > x :> {x, x}, {-1, 1} -> 1]

We can then invoke that function whenever the set of points is changed. To use this strategy, it is convenient to use a single locator control for all points:

Manipulate[
  pts = clip[pts]
; Show[
    Plot[x, {x, 0, 1}]
  , ParametricPlot[f[Prepend[pts, {0, 0}]][x], {x, 0, 1}]
  ]
, {{pts, {{.2, .2}, {.4, .4}, {.6, .6}, {.8, .8}, {1, 1}}}, Locator}
, {{f, BezierFunction, "Form"}, {BezierFunction, BSplineFunction}}
]

Edit

My initial response only applied constraints to the input parameters. The full requirement is to also apply constraints derived from the computed output. The revised version below addresses that requirement. The principle is the same, but now the computed output is checked to see if it is valid using the new function validQ. If the output is not valid, then the inputs are reset to the previous values.

clip[pts_] :=
  ReplacePart[pts, {-1, 1} -> 1]

validQ[f_, pts_] :=
  Apply[And, #[[1]] >= #[[2]]& /@ Table[f[Prepend[pts, {0, 0}]][x], {x, 0, 1, 0.01}]]

Manipulate[
  pts2 = clip[pts2]
; If[validQ[f, pts2], pts1 = pts2, pts2 = pts1]
; Show[
    Plot[x, {x, 0, 1}]
  , ParametricPlot[f[Prepend[pts1, {0, 0}]][x], {x, 0, 1}]
  ]
, {{pts1, {{.2, .2}, {.4, .4}, {.6, .6}, {.8, .8}, {1, 1}}}, None}
, {{pts2, pts1}, Locator}
, {{f, BezierFunction, "Form"}, {BezierFunction, BSplineFunction}}
]

Note that validQ uses a crude numerical method to check whether the resulting curve lies under the y = x line -- a more accurate method might be needed for the real application.

For Your Consideration: An Alternate Feedback Mechanism

Personally, I do not like user interface components that place complex constraints upon my gestures. In this case, I do not find the x constraint on the last locator to be objectionable. However, I find it awkward to move the other locators since the constraints are complex functions of the output. I try to move the locator in a direction that I think will be helpful, but it just will not move. I have to guess which other locator to move out of the way first, and then go back and move the first locator.

I would propose a different paradigm. Let the user move the locators however they want, but give them clear feedback when their inputs are not valid. For example:

Manipulate[
  pts = ReplacePart[pts, {-1, 1} -> 1]
; valid = True
; Column[
    { Show[
        Plot[x, {x, 0, 1}]
      , ParametricPlot[
          f[Prepend[pts, {0, 0}]][x], {x, 0, 1}
        , ColorFunction -> (If[#1 >= #2, Black, valid = False; Red]&)
        , ColorFunctionScaling -> False
        ]
      ]
    , If[valid, "Valid", Style["Invalid", Red]]
    }
  ]
, {{pts, {{.2, .2}, {.4, .4}, {.6, .6}, {.8, .8}, {1, 1}}}, Locator}
, {{f, BezierFunction, "Form"}, {BezierFunction, BSplineFunction}}
, {valid, None}
]

valid state screenshot

invalid state screenshot

At the cost of some added code complexity, smoother animation of the last locator can be obtained by explicit use of a LocatorPane:

Manipulate[
  Column[
    { LocatorPane[
        Dynamic[pts, (pts = ReplacePart[#, {-1, 1} -> 1]) &]
      , Dynamic@Show[
          Plot[x, {x, 0, 1}]
        , valid = True
        ; ParametricPlot[
            f[Prepend[pts, {0, 0}]][x], {x, 0, 1}
          , ColorFunction -> (If[#1 >= #2, Black, valid = False; Red]&)
          , ColorFunctionScaling -> False
          ]
        ]
      ]
    , Dynamic@If[valid, "Valid", Style["Invalid", Red]]
    }
  ]
, {{pts, {{.2, .2}, {.4, .4}, {.6, .6}, {.8, .8}, {1, 1}}}, None}
, {{f, BezierFunction, "Form"}, {BezierFunction, BSplineFunction}}
, {valid, None}
, TrackedSymbols :> {f}
]
$\endgroup$
13
  • 1
    $\begingroup$ My understanding of the question is slightly different... I think they want the locators to be able to move above the line too, but constrained such that the curve never crosses y==x. It's a bit tricky, because an individual locator is not absolutely constrained; rather, all of them together decide (via the curve) whether a particular locator can be moved to some position or not (see the third figure in the OP) $\endgroup$
    – rm -rf
    Oct 28, 2012 at 18:01
  • $\begingroup$ @rm-rf Agreed. I lost that point somewhere along the way while implementing my solution. I will rethink. $\endgroup$
    – WReach
    Oct 28, 2012 at 18:05
  • $\begingroup$ Two parts. The second is as @rm-rf describes it, the first is simply to constrain one of the Locators to always have an x value of 1. $\endgroup$
    – orome
    Oct 28, 2012 at 18:19
  • $\begingroup$ @raxacoricofallapatorius Is the last locator not constrained for you? It is for me using V7 or V8. $\endgroup$
    – WReach
    Oct 28, 2012 at 18:30
  • $\begingroup$ What's "V7 or V8"? $\endgroup$
    – orome
    Oct 28, 2012 at 18:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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