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
]]
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.
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.
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]
]
]]
Answer
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]]
}]
]
Building your interface this ways, in my opinion gives you much more freedom and less of a hassle.
Comments
Post a Comment