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

functions - Get leading series expansion term?

Given a function f[x] , I would like to have a function leadingSeries that returns just the leading term in the series around x=0 . For example: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x)] x and leadingSeries[(1/x + 2 + (1 - 1/x^3)/4)/(4 + x)] -(1/(16 x^3)) Is there such a function in Mathematica? Or maybe one can implement it efficiently? EDIT I finally went with the following implementation, based on Carl Woll 's answer: lds[ex_,x_]:=( (ex/.x->(x+O[x]^2))/.SeriesData[U_,Z_,L_List,Mi_,Ma_,De_]:>SeriesData[U,Z,{L[[1]]},Mi,Mi+1,De]//Quiet//Normal) The advantage is, that this one also properly works with functions whose leading term is a constant: lds[Exp[x],x] 1 Answer Update 1 Updated to eliminate SeriesData and to not return additional terms Perhaps you could use: leadingSeries[expr_, x_] := Normal[expr /. x->(x+O[x]^2) /. a_List :> Take[a, 1]] Then for your examples: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x), x] leadingSeries[Exp[x], x] leadingSeries[(1/x + 2 + (1 - 1/x...

How to thread a list

I have data in format data = {{a1, a2}, {b1, b2}, {c1, c2}, {d1, d2}} Tableform: I want to thread it to : tdata = {{{a1, b1}, {a2, b2}}, {{a1, c1}, {a2, c2}}, {{a1, d1}, {a2, d2}}} Tableform: And I would like to do better then pseudofunction[n_] := Transpose[{data2[[1]], data2[[n]]}]; SetAttributes[pseudofunction, Listable]; Range[2, 4] // pseudofunction Here is my benchmark data, where data3 is normal sample of real data. data3 = Drop[ExcelWorkBook[[Column1 ;; Column4]], None, 1]; data2 = {a #, b #, c #, d #} & /@ Range[1, 10^5]; data = RandomReal[{0, 1}, {10^6, 4}]; Here is my benchmark code kptnw[list_] := Transpose[{Table[First@#, {Length@# - 1}], Rest@#}, {3, 1, 2}] &@list kptnw2[list_] := Transpose[{ConstantArray[First@#, Length@# - 1], Rest@#}, {3, 1, 2}] &@list OleksandrR[list_] := Flatten[Outer[List, List@First[list], Rest[list], 1], {{2}, {1, 4}}] paradox2[list_] := Partition[Riffle[list[[1]], #], 2] & /@ Drop[list, 1] RM[list_] := FoldList[Transpose[{First@li...

front end - keyboard shortcut to invoke Insert new matrix

I frequently need to type in some matrices, and the menu command Insert > Table/Matrix > New... allows matrices with lines drawn between columns and rows, which is very helpful. I would like to make a keyboard shortcut for it, but cannot find the relevant frontend token command (4209405) for it. Since the FullForm[] and InputForm[] of matrices with lines drawn between rows and columns is the same as those without lines, it's hard to do this via 3rd party system-wide text expanders (e.g. autohotkey or atext on mac). How does one assign a keyboard shortcut for the menu item Insert > Table/Matrix > New... , preferably using only mathematica? Thanks! Answer In the MenuSetup.tr (for linux located in the $InstallationDirectory/SystemFiles/FrontEnd/TextResources/X/ directory), I changed the line MenuItem["&New...", "CreateGridBoxDialog"] to read MenuItem["&New...", "CreateGridBoxDialog", MenuKey["m", Modifiers-...