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}
]


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}
]
Locator
withLocator[Dynamic[...]]
, the locator still behaves as before (though it becomes initially invisible). $\endgroup$