18
$\begingroup$

This post about avoiding code duplication in Manipulate helped a lot getting as far as I did. The method of "macros" outlined there works excellent, but when the number of controls is again controlled by yet another manipulate control, things get difficult.

I got two closely related problems in that area:

Problem 1: The following works like a charm:

With[{rows = 3, cols = 3, fieldSize = 5},
  Manipulate[
    Column[{
      TableForm@mat,
      Row[{"det = ", Det[mat]}]
    }]
    , {mat, None}, {initialized, None}
    , Evaluate@With[{makeRow = Function[{rowIndex},
        Map[Function[{colIndex}
            ,InputField[Dynamic[mat[[rowIndex, colIndex]]], Number, FieldSize -> fieldSize]]
          , Range[cols]]
        , HoldAll]}
    , Grid[Map[makeRow, Range[rows]]]]
    , Initialization :> (
      If[Not[initialized === True], mat = Table[0, {rows}, {cols}]];
      initialized = True
    )
    , SaveDefinitions -> True
]]

Mathematica graphics

I can enter a matrix, and Manipulate will calculate the determinant. If I start a new session, the values from the last session will still be there (important). But now suppose, I want to specify the matrix size with a setter bar. This can be done by wrapping a manipulate around it.

Manipulate[
  With[{rows = size, cols = size, fieldSize = 5},
    Manipulate[
      Column[{TableForm@mat, Row[{"det = ", Det[mat]}]}]
      , {mat, None}, {initialized, None}
      , Evaluate@With[{makeRow = Function[{rowIndex},
          Map[Function[{colIndex}, InputField[Dynamic[mat[[rowIndex, colIndex]]], Number, FieldSize -> fieldSize]], Range[cols]]
          , HoldAll]}
        , Grid[Map[makeRow, Range[rows]]]]
      , Initialization :> (
        If[Not[initialized === True], mat = Table[0, {rows}, {cols}]];
        initialized = True
      ), SaveDefinitions -> True]]
    , {{size, 3}, Range[7], ControlType -> SetterBar}
]

But this breaks the persistence over sessions. I tried to put the variable mat into the outer Manipulate, but that broke the whole thing.

Mathematica graphics

Problem 2: Kind of the same thing, with sliders instead of input fields.
I want to use a combination of setter bars and sliders to input a discrete probability istribution, and then do stuff with it within a manipulate.
IMPORTANT: The values should be persistent over sessions.

Mathematica graphics

Via the setter bar, I indicate that I want, for example, a prob dist for the integers from 2 through 5. That should then bring up 4 sliders that I can use to define the prob dist, one for each number. So the number of sliders should be variable. As you can see, I solved the problem of writing a macro for the slider generation, but I failed to make the number of sliders dependent on the inputs from the setter bars. It's clear from the code how I tried to solve the problem: The code, as it is, does everything I want, except that the number of sliders is always the same; and if you change one of the "lifeIsEasy" variables, it will seize to work.

With[{slidersPerRow = 3, lifeIsEasy1 = False, lifeIsEasy2 = False, 
  length = 8
  , normalize = Function[{pd, from, to}
    , With[{total = Total[pd[[Range[from, to]]]], len = to - from + 1}
     , If[total == 0, Table[1/len, {len}], 
      pd[[Range[from, to]]]/total]]]},
 Manipulate[
  If[max < min, max = min];
  Column[{
    Row[{"min ", min, "   max ", max}]
    , Row[{"=== slider values ==="}]
    , TableForm[{sliderVals}, TableHeadings -> {None, Range[length]}]
    , Row[{"=== prob dist ==="}]
    , TableForm[{normalize[sliderVals, min, max]}, 
     TableHeadings -> {None, Range[min, max]}]
    , Row[{"expectation: ", 
      normalize[sliderVals, min, max] . Range[min, max]}]
    }]
  , {initialized, None}
  , {sliderVals, None}
  , Row[{
    Control[{{min, 1}, Range[4], ControlType -> SetterBar}]
    , Spacer[25]
    , Control[{{max, min + 3}, Range[min, min + 4], 
      ControlType -> SetterBar}]
    }]
  , Row[{"I want ", Dynamic[max - min + 1], " sliders in ", 
    Dynamic@Ceiling[(max - min + 1)/slidersPerRow], " rows, "
    , slidersPerRow, " per Row, and ", 
    Dynamic@Mod[max - min + 1, slidersPerRow, 1], " in the last" }]
  , Evaluate@With[{makeSl = Function[{index}
       , Dynamic@
        Row[{ToString[index] <> " ", 
          Manipulator[Dynamic[sliderVals[[index]]]]}]
       , HoldAll]}
    , With[{
      makeSliderRow = Function[{startIndex, endIndex}
        , Row[Map[makeSl, Range[startIndex, endIndex]], Spacer[10]]]
      }
     , Sequence @@ Table[
       makeSliderRow[
        min + slidersPerRow*sliderRow(*=startIndex*)
        , Min[Join[{min + slidersPerRow - 1 + slidersPerRow*sliderRow}, If[lifeIsEasy1, max, {}]]]](*=endIndex*)  
        , {sliderRow, 0, If[lifeIsEasy2, Ceiling[(max - min + 1)/slidersPerRow] - 1, 1]}
        ](*seqtab*)
     ](*with*)
    ](*evalwith*)
  , Initialization :> (
    If[Not[initialized === True], sliderVals = Table[0, {length}]];
    initialized = True
    )
  , SaveDefinitions -> True
  ](*manip*)
 ](*with*)

Another approach I had for the problem was to use an inner Manipulate, but that didn't work either; I didn't get that even to the point where everything except persistence works, as in problem 1.

UPDATE

Although it is possible to do all this with Manipulate (see my 3 answers below), jVincent has convinced me that it is better and easier to do with DynamicModule instead.

I want to show how to make reusable components with this method.

Here is a slightly improved version of jVincent's solution.

makeInputGrid = Function[{mat},
    Grid@Table[With[{x = x, y = y},
        InputField[Dynamic[mat[[x, y]]], ImageSize -> 50]],
        {x, 1, Dimensions[mat][[1]]},
        {y, 1, Dimensions[mat][[2]]}],
    HoldAll];
DynamicModule[{mymat = Table[0, {2}, {2}]},
    Column[{
        Slider[Dynamic[Dimensions[mymat][[1]], (mymat = Table[0, {#}, {#}]) &], {2, 8, 1}],
        Dynamic@makeInputGrid[mymat], 
        Dynamic@MatrixForm@mymat,
        Dynamic@Det@mymat
    }]
]

Now, the function makeInputGrid can be used elsewhere. Note the HoldAll attribute, it won't work without that.

Here is how to do the slider problem with this approach. (I tried to make it look a little towards the way Manipulate makes things look.)

sliderGrid = Function[
    {sliderValues, slidersPerRow, minVal, maxVal, which},
    Panel@Grid[Partition[
        Table[With[{index = which[[i]]},
            Row[{index, Spacer[7], Manipulator[Dynamic[sliderValues[[index]]], {minVal, maxVal}]}]
        ], {i, Length[which]}],
        slidersPerRow, slidersPerRow, 1, {}]],
   HoldFirst
];
checkboxRow = Function[{flags},
    Panel@Row[Table[With[{i = i},
       Row[{i, Spacer[5], Checkbox[Dynamic[flags[[i]]]], Spacer[7]}]
    ], {i, Length[flags]}]],
   HoldAll
];
With[{len = 10, min = -1, max = 1},
    DynamicModule[{
        flags = Table[True, {len}],
        vals = RandomReal[{min, max}, len]
    },
    Panel[Column[{
        Panel@Column[{
            checkboxRow[flags],
            Dynamic@sliderGrid[vals, 3, min, max, Select[Range[10], flags[[#]]&]]
        }],
        Dynamic@Grid[{Range[len], vals}]
    }],
    Background -> GrayLevel[0.9]
    ]
]]

variable number of sliders using dynamicmodule

$\endgroup$

4 Answers 4

18
$\begingroup$

When you start making much more complex interfaces using dynamic interactivity, I would suggest moving away from Manipulate, and instead using Dynamic along with controls, While it's nice that Manipulate makes appropriate controls for you, it can at times be harder to work with, especially when you want to generate new controls dynamically. Here is a simple implementation of your scaling matrix size determinant gizmo, which uses DynamicModule to save the variables and shows a Column of dynamic components:

DynamicModule[{mat = ConstantArray[0, {4, 4}]},
 Column[{
   Slider[
    Dynamic[Dimensions[mat][[
      1]], (mat = ConstantArray[0, {#, #}]) &], {2, 8, 1}],
   Dynamic[Table[With[{x = x, y = y},
       InputField[Dynamic[mat[[x, y]]], ImageSize -> 50]
       ], {x, 1, Dimensions[mat][[1]]}, {y, 1, 
       Dimensions[mat][[2]]}] // Grid]
   ,
   Dynamic[MatrixForm[mat]],
   Dynamic[Det[mat]]   
   }]
 ]

Image showing the interface resulting from the code

Building your interface this ways, in my opinion gives you much more freedom and less of a hassle.

$\endgroup$
2
  • $\begingroup$ I already have tons of Manipulate code that I do not want to rewrite using DynamicModule. What I posted are just toy problems representative for the kind of things that trouble me in my making the existing code base more flexible. You do have a point, though. $\endgroup$ Commented Aug 1, 2012 at 9:12
  • $\begingroup$ I improved this solution a little bit (made it more reusable / modular), and added how the slider case is handled in this manner, see UPDATE at the end of my question. $\endgroup$ Commented Aug 6, 2012 at 8:03
4
$\begingroup$

problem 2

multiSlider[Dynamic[arr_], slidersPerRow_, showWhich_] := 
    Panel@DynamicModule[{row},
        row[indices_] := Row[Map[
            Row[{#, Spacer[4], Manipulator[Dynamic[arr[[#]]], {0, 1}]}] &, 
                indices], Spacer[7]
        ];
        Column@Map[row, 
            Partition[showWhich, slidersPerRow, slidersPerRow, 1, {}]
        ]
];
Manipulate[
    If[max < min, max = min];
    TableForm[{sliderVals}, TableHeadings -> {None, Range[Length[sliderVals]]}],
    Row[{
        Control[{{min, 1}, Range[4], ControlType -> SetterBar}], Spacer[25],
        Control[{{max, min + 3}, Range[min, min + 4], ControlType -> SetterBar}]
    }],
    {{sliderVals, Table[0, {8}], ""}, multiSlider[#, 3, Range[min, max]] &}
    , SaveDefinitions :> True
]

enter image description here

$\endgroup$
3
$\begingroup$

as for problem 1, my own solution follows. I'm still working on problem 2. Mike Honeychurch's answer to this question should be sufficient to find the answer.

With[{fieldSize = 5, n = 2},
    DynamicModule[{rows = n, cols = n, mat = Table[RandomInteger[5], {n}, {n}]},
        Dynamic@Manipulate[
            Row[{"det", MatrixForm[mat], " = ", Det[mat]}]
           , Evaluate@With[{makeRow = Function[{rowIndex},
                Map[Function[{colIndex}, 
                    InputField[Dynamic[mat[[rowIndex, colIndex]]], Number, FieldSize -> fieldSize]]
                , Range[cols]](*map*)
                , HoldAll](*makeRow*)}
            , Grid[Map[makeRow, Range[rows]]]]
            , Button["shrink", If[rows > 1,
                mat = Drop[Map[(Drop[#, -1]) &, mat], -1]; rows--; cols--;
            ]]
            , Button["grow", 
                mat = Append[Map[(Append[#, 0]) &, mat], Table[0, {cols + 1}]]; rows++; cols++;
            ]
        ]
    ]
]

detGizmoWithShrinkGrowButtons

what I don't understand myself is why the Manipulate has to be wrapped with Dynamic. I thought that DynamicModule would automatically wrap Dynamic around everything.

$\endgroup$
1
  • 1
    $\begingroup$ DynamicModule wraps Dynamic around only those variables defined in the Module. $\endgroup$ Commented Aug 2, 2012 at 17:03
2
$\begingroup$

problem 1

multiInput[Dynamic[in_], size_] := Panel@DynamicModule[{},
    Grid[Outer[InputField[Dynamic[in[[#1, #2]]], FieldSize -> 5] &, Range[size], Range[size]]]];
With[{max = 7}, Manipulate[
    With[{mat = Map[Take[#, size] &, Take[bigMat, size]]},
        Row[{"det", MatrixForm@mat, " = ", Det[mat]}]],
    {{size, 3}, 1, max, 1},
    {{bigMat, Table[RandomInteger[4], {max}, {max}], ""}, multiInput[#, size] &},
    SaveDefinitions :> True
]]

detgizmo2

$\endgroup$

Your Answer

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

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