6
$\begingroup$

2nd Update

Here's a snapshot of a graphical user interface I'm thinking. I hope this could be self-explanatory and demonstrate the functionalities I mentioned in the 1st update. Thank you all!

Geographical Interface

Update

I really appreciate Kuba's help. The code greatly helped me to understand the approach of achieving the task. Here I still have a few quick questions in refinement.

  1. I saw Kuba set the range of Slider from 0 to 0.99 instead of 1, as an approach to protect all the others becoming 0 at the same time. I'm considering to have a column of Checkbox right next to the slider. Only when the box is checked, the variable would have corresponding updates. So in this case, ideally even all the other elements become 0, (means one of the element is 1 at that time), when that 1-element decreases, all the others should equally increase correspondingly.

  2. Is there a way to construct a dynamic list whose length is user-defined, and create corresponding number of sliders? And also, is there a way to allows users to input the value of each element from InputField? That means the variable is Slider-control and InputField-control at same time.

Original Question

I would like to have a dynamic list of five elements such that if one changes, all the others would change correspondingly to keep the sum invariant at 1. This requirement comes from a practical problem where five probabilities always have a total of 1. I would like to have a slider control for each element in the list which can change that element. When any of the sliders is moved, the list should update as described.

My intention was to create a pure function f that would allow the list to be updated following the rule of sum is 1, as the elements of the list were changed. But this didn't work in the way I thought it should.

And here is what I tried:

v = {0.2, 0.2, 0.2, 0.2, 0.2};
v[[1]] = Dynamic[val];
Dynamic[val]
Slider[Dynamic[val]]
sum = 1;
Dynamic[v]
f := (#/(sum - #))*(sum - val) &
Slider[Dynamic[v[[1]], f /@ v], {0, 1}]
Slider[Dynamic[v[[2]], f /@ v], {0, 1}]
Slider[Dynamic[v[[3]], f /@ v], {0, 1}]
Slider[Dynamic[v[[4]], f /@ v], {0, 1}]
Slider[Dynamic[v[[5]], f /@ v], {0, 1}]

The algorithm of distribution is that if the first element changes, the other four would change proportionally. For instance, if I change the first 0.5 -> 0 in {0.5, 0.05, 0.1, 0.15, 0.2}, the list would automatically update to {0, 0.1, 0.2, 0.3, 0.4}.

$\endgroup$
8
  • $\begingroup$ This seems hard to achieve if you want any one of the sliders to influence the others. If you had a "master slider", to whose change the others react, that would be more easily feasible, at least to me. I think you should also specify how the resulting change should be distributed among the other four values when the fifth one changes (e.g. equally?). $\endgroup$
    – MarcoB
    May 10, 2015 at 4:25
  • $\begingroup$ Thanks@MarcoB for your reminder. I've updated the description above. The distribution should be proportional. Could you elaborate the thought of "master slider"? Or I would be much appreciated if you could write a small code on this. Thanks! $\endgroup$ May 10, 2015 at 4:36
  • $\begingroup$ Welcome to Mathematica.SE! I suggest that: 1) You take the introductory Tour now! 2) When you see good questions and answers, vote them up by clicking the gray triangles, because the credibility of the system is based on the reputation gained by users sharing their knowledge. Also, please remember to accept the answer, if any, that solves your problem, by clicking the checkmark sign! 3) As you receive help, try to give it too, by answering questions in your area of expertise. $\endgroup$
    – bbgodfrey
    May 10, 2015 at 4:45
  • $\begingroup$ Thanks to bbgodfrey for your guide, and thanks to @m_goldberg for the help in editing which made this concise and clear. $\endgroup$ May 10, 2015 at 16:05
  • $\begingroup$ @Kuba. Yeah, definitely. And I updated the question after tried your code. $\endgroup$ May 10, 2015 at 20:23

4 Answers 4

8
$\begingroup$

Response to edits:

I don't know if I got all your points but this is the final update done by me :)

v = {0.5, 0.05, 0.1, 0.15, 0.2};
active = Range@Length@v;

update[i_, val_] := (v[[i]] = val;
   With[{range = DeleteCases[active, i]}, 
    v[[range]] = (1 - val) Normalize[v[[range]], Total]]);

updateCheckbox[i_, val_] := 
  If[val, active = Join[active, {i}], active = DeleteCases[active, i]];



Table[With[{i = i},
   {InputField[Dynamic[v[[i]], update[i, #] &], Number],
    Slider[Dynamic[v[[i]], update[i, #] &], {0, .99}],
    Checkbox[Dynamic[MemberQ[active, i], updateCheckbox[i, #] &]]
    }
   ], {i, Length@v}] // Grid

enter image description here

$\endgroup$
9
  • $\begingroup$ Kuba, Thanks for your code. I've just updated the question and wondered if you have time to check on this. Thanks! $\endgroup$ May 10, 2015 at 16:41
  • $\begingroup$ @QizhangJia what about my latest edit? $\endgroup$
    – Kuba
    May 11, 2015 at 19:56
  • 1
    $\begingroup$ +1 Not least for With[{i = i}, ... ] $\endgroup$ May 11, 2015 at 20:04
  • $\begingroup$ @Kuba Amazing! That's exactly what I was thinking. And by the way, do you know how to integrate NumberForm with Dynamic and InputField command? I'm thinking about only print 3 sigfigs in the InputField, and restrict the FieldSize -> 5. $\endgroup$ May 12, 2015 at 17:36
  • 1
    $\begingroup$ @JessRiedel Take a look here: 7756, Dynamic has HoldAll Attribute too, that's why it's needed. Take a look at Introduction to Dynamic tutorial, there is something like "good tip" where i=i is shown. $\endgroup$
    – Kuba
    Dec 14, 2015 at 19:07
2
$\begingroup$
v = {0.5, 0.05, 0.1, 0.15, 0.2};

First we create a pure function to be used by the ith slider that (apart from the ith element) adjusts each element of the list v by the factor r - the proportionality adjustment envisioned:

f[i_, r_] := Function[{val, j}, With[{n = First@j}, If[n =!= i, v[[n]] = val*r]]]

In Dynamic's second argument of the ith slider, the ith list element is first changed according to the slider's setting (v[[i]] = #), the proportionality factor, r, is then calculated ((1 - #)/Total@Delete[v, i]) before being used to multiply v's remaining list elements (via the function f).

Grid[{
      Table[i, {i, 1, Length@v}],
      Table[With[{i = i},
        Slider[Dynamic[v[[i]],
                  (v[[i]] = #; 
                   MapIndexed[f[i, (1 - #)/Total@Delete[v, i]],v])&], 
        {0, .9999}, Appearance -> Vertical]], {i, 1, Length@v}]}]

enter image description here

Dynamic@v
-> {0.5, 0.05, 0.1, 0.15, 0.2}

As per the requested example, setting the first element to 0 now gives the desired result.

enter image description here

Dynamic@v
-> {0., 0.1, 0.2, 0.3, 0.4}
$\endgroup$
4
  • $\begingroup$ Not much different to Kuba's answer which is actually cleaner with VerticalSlider,Normalize and Appearance and packing into update. $\endgroup$ May 10, 2015 at 8:05
  • $\begingroup$ I suppose MapIndexed may be more flexible so your answer is fine :) +1ed. $\endgroup$
    – Kuba
    May 10, 2015 at 10:27
  • $\begingroup$ Maybe, although I also think MapIndexed kind of promotes a procedural thinking reminiscent of For loops. Using Range is kind of more holistic/functional although I do also have a sense that a higher order construct (perhaps something that abstracts this masking) is asking if not begging to be added to WL here. I assume your answer was independently arrived as it is a natural approach but irrespective, I've kept this answer as I think it can be pedagogically useful to see a progression, or refinement of code. Actually this also goes to whether SE answers should be like fishes or .. $\endgroup$ May 10, 2015 at 22:55
  • $\begingroup$ .. teaching to fish that has exercised some people recently michael.richter.name/blogs/… and raises the question of what are "good" ways to encourage new users - a "meta" discussion admittedly. $\endgroup$ May 10, 2015 at 23:04
2
$\begingroup$

I think it is easier to implement the behavior the OP wants to give the list of probabilities, {p1, ..., p5} by having a setter bar to select the element to be changed and a single animator (slider) to change the value of that element.

First a helper function for normalizing the probabilities after the slider is moved.

adjust[probs_, pk_, k_] :=
  Module[{δ, notPk, σ},
    δ = pk - probs[[k]];
    notPk = Delete[probs, k];
    σ = Total[notPk];
    Insert[(1 - δ/σ) notPk, pk, k]]

With that helper, a demonstration of the specified behavior can be made with pretty simple Manipulate expression

With[{init = ConstantArray[.2, 5]},
  Manipulate[
    Column[{
      probs = adjust[probs, pk, k],
      Row[{"Sum of elements: ", Total @ probs}]}],
    {{probs, init}, None},
    {k, Range @ Length @ init},
    {{pk, init[[1]], Dynamic @ Subscript["p", k]}, 0., 1., .05,
      Appearance -> "Labeled"},
    TrackedSymbols :> {pk}]]

demo

Note that the demo is not limited a list with five elements. Any list of probabilities summing to 1 can be given for init. The 2-nd line of the output may look static, but it is actually dynamically summing the list. That it appears static proves the dynamic normalization is doing its job.

$\endgroup$
3
  • $\begingroup$ Thanks very much for your input and help in editing my problem. I was thinking about having 5 Sliders in column and make some of these constants user adjustable rather than hard-coded. I've updated an user interface above. $\endgroup$ May 10, 2015 at 20:31
  • $\begingroup$ @QizhangJia. The advantage of this interface design is that nothing is hard coded. Given a normalized list of from 2 to 20 or even more probabilities, it looks at the initial value and adapts to it, still looking and feeling the same to users. Imagine an interface with 20 independent sliders. I think that would be awkward, And not only awkward to code, but awkward to interact with. $\endgroup$
    – m_goldberg
    May 10, 2015 at 23:41
  • $\begingroup$ Thanks for your answer. Yeah, I think what you suggested is true. It does feel awkward to build an interface in my thought. $\endgroup$ May 11, 2015 at 4:04
2
$\begingroup$

Pure function f specified as requested:

"The algorithm of distribution is that if the first [or any] element changes, the other four would change proportionally."

v = {0.2, 0.2, 0.2, 0.2, 0.2};

f = Function[{z, k},
   {a, b, c, d, e} = v;
   m = n = o = p = q = 1;
   Switch[k,
    1, a = z; m = 0,
    2, b = z; n = 0,
    3, c = z; o = 0,
    4, d = z; p = 0,
    5, e = z; q = 0];
   sol = Solve[a x^m + b x^n + c x^o + d x^p + e x^q == 1, x];
   xsol = First[x /. sol];
   v = {a xsol^m, b xsol^n, c xsol^o, d xsol^p, e xsol^q}];

{Dynamic@v, Row[{"Total = ", Dynamic[Total[v]]}]}

Slider[Dynamic[v[[1]], f[#, 1] &]]
Slider[Dynamic[v[[2]], f[#, 2] &]]
Slider[Dynamic[v[[3]], f[#, 3] &]]
Slider[Dynamic[v[[4]], f[#, 4] &]]
Slider[Dynamic[v[[5]], f[#, 5] &]]

enter image description here

$\endgroup$
2
  • $\begingroup$ Thank you for your support. An interesting approach though, but I thought it would be better to construct a dynamic list to hold the variables, instead of hard-coded every variables in the program. And because the first method would allow the potential of user-defined number of variables. @Kuba's code is an example of this. $\endgroup$ May 12, 2015 at 17:41
  • $\begingroup$ I'm sure the number of variable could be generalised fairly easily. I'll see if I have time. $\endgroup$ May 12, 2015 at 19:43

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.