Skip to main content

Mathematica Manipulate with macros AND variable number of controls (persistence desired)


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



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

]

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.


Comments

Popular posts from this blog

plotting - Plot 4D data with color as 4th dimension

I have a list of 4D data (x position, y position, amplitude, wavelength). I want to plot x, y, and amplitude on a 3D plot and have the color of the points correspond to the wavelength. I have seen many examples using functions to define color but my wavelength cannot be expressed by an analytic function. Is there a simple way to do this? Answer Here a another possible way to visualize 4D data: data = Flatten[Table[{x, y, x^2 + y^2, Sin[x - y]}, {x, -Pi, Pi,Pi/10}, {y,-Pi,Pi, Pi/10}], 1]; You can use the function Point along with VertexColors . Now the points are places using the first three elements and the color is determined by the fourth. In this case I used Hue, but you can use whatever you prefer. Graphics3D[ Point[data[[All, 1 ;; 3]], VertexColors -> Hue /@ data[[All, 4]]], Axes -> True, BoxRatios -> {1, 1, 1/GoldenRatio}]

plotting - Filling between two spheres in SphericalPlot3D

Manipulate[ SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, Mesh -> None, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], {n, 0, 1}] I cant' seem to be able to make a filling between two spheres. I've already tried the obvious Filling -> {1 -> {2}} but Mathematica doesn't seem to like that option. Is there any easy way around this or ... Answer There is no built-in filling in SphericalPlot3D . One option is to use ParametricPlot3D to draw the surfaces between the two shells: Manipulate[ Show[SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], ParametricPlot3D[{ r {Sin[t] Cos[1.5 Pi], Sin[t] Sin[1.5 Pi], Cos[t]}, r {Sin[t] Cos[0 Pi], Sin[t] Sin[0 Pi], Cos[t]}}, {r, 1, 2 - n}, {t, 0, Pi}, PlotStyle -> Yellow, Mesh -> {2, 15}]], {n, 0, 1}]

plotting - Mathematica: 3D plot based on combined 2D graphs

I have several sigmoidal fits to 3 different datasets, with mean fit predictions plus the 95% confidence limits (not symmetrical around the mean) and the actual data. I would now like to show these different 2D plots projected in 3D as in but then using proper perspective. In the link here they give some solutions to combine the plots using isometric perspective, but I would like to use proper 3 point perspective. Any thoughts? Also any way to show the mean points per time point for each series plus or minus the standard error on the mean would be cool too, either using points+vertical bars, or using spheres plus tubes. Below are some test data and the fit function I am using. Note that I am working on a logit(proportion) scale and that the final vertical scale is Log10(percentage). (* some test data *) data = Table[Null, {i, 4}]; data[[1]] = {{1, -5.8}, {2, -5.4}, {3, -0.8}, {4, -0.2}, {5, 4.6}, {1, -6.4}, {2, -5.6}, {3, -0.7}, {4, 0.04}, {5, 1.0}, {1, -6.8}, {2, -4.7}, {3, -1....