12

This is a little related to this question

Define control as variable in Mathematica

But the above question did not answer my problem, as it talks about the full control definition. (I also tried some of the tricks shown there, but they do not work for my problem).

I am now asking about definition for only part of the control. (It is also very hard to follow up on an old question using this forum format. Because using the tiny comment area, it hard to ask and show more like when asking a new question where the space is larger, and one can paste code and images).

All the tries I have made do not work. I'll start by simple example to explain the problem.

Assume one want to write

Clear["Global`*"];

Manipulate[Plot[f*g, {x, -1, 1}],
 Grid[{
   {Style["f(x)="], 
    PopupMenu[Dynamic[f], {x, x^2, x^3}, ImageSize -> Tiny]},{Style["g(x)="], 
    PopupMenu[Dynamic[g], {x, x^2, x^3}, ImageSize -> Tiny]}
   }]
 ]

you can see there is allot of code duplication in each control definition. (things like ImageSize, Spacings-> and many other decoration settings, are repeated over and over for each control.

enter image description here

What will be great, if I can write something like

Manipulate[Plot[f*g, {x, -1, 1}],
 Grid[{
   {Style["f(x)="], PopupMenu[Dynamic[f], Evaluate@Sequence@v]},
   {Style["g(x)="], PopupMenu[Dynamic[g], Evaluate@Sequence@v]}
   }],

 Initialization :>
  (
   v = {{x, x^2, x^3}, ImageSize -> Tiny}
   )
 ]

But this does not work. I tries many other things along the above line, and nothing works. Like

{Style["f(x)="], PopupMenu[Dynamic[f], v]},

and

{Style["f(x)="], PopupMenu[Dynamic[f], Evaluate@v]}

and

Manipulate[Plot[f*g, {x, -1, 1}],

 {{v, {{x, x^2, x^3}, ImageSize -> Tiny}}, None},
 Grid[{
   {Style["f(x)="], PopupMenu[Dynamic[f], Evaluate@v]},
   {Style["g(x)="], PopupMenu[Dynamic[g], v]}
   }]
 ]

can't get it to work.

But here are the rules of the game: This will be for a demo, hence, code must start with Manipulate. Can't have Module outside Manipulate. Also, can not use Hold and its friends. But can use Unevaluated.

I was hoping the experts here might have a trick to do this. The will reduce the code size if it is possible to do, as many of the control I have, contain many 'options' like the above that are the same, and being able to do the above will make the code easier to read and manage.

thanks,

ps. What I am asking for, is sort of similar to what one does for say Plot options, where one can use SetOptions to set some common default options so they do not have to duplicate them for each Plot command each time. But there is no such thing in this case.

update

Using the method shown by Leonid below, (the Macro trick), I wanted to use it to help me define number of controls, all using one common setting. This is what I tried:

Manipulate[{x, y},

 Evaluate@With[
   {
    control1 = Function[{var, initialValue, str, from, to, incr},
      {
       {{var, initialValue, str}, from, to, incr, ImageSize -> Tiny}
       }
      ,
      HoldAll
      ]
    },

   {
      First@control1[x, 0, "x=", 0, 1, .1],
      First@control1[y, 0, "y=", 0, 2, .1],
      First@control1[z, 0, "z=", 0, 10, .1]
    }, 

   ]
 ]

The problem is just an extra {} around the whole thing, else it will work. Will keep trying to solve this. But getting close. Tried Sequence[], and Flatten[..,1] and such, but can not do it yet. Making more coffee, should help.

Update 2

This is below an example using Simon method to use to help define common definition across more than one control. This way, one can use it to reduce code duplication for common options on set of separate controls

Notice, had to use Control[] to get it to control.

Manipulate[{x, y, z},

 Dynamic[Grid[{
    {control1[x, 0, "x=", 0, 1, .1]},
    {control1[y, 0, "y=", 0, 2, .1]},
    {control1[z, 0, "z=", 0, 10, .1]}
    }]],

 {{control1, 
   Function[{var, initialValue, str, from, to, incr}, 
    Control[{{var, initialValue, str}, from, to, incr, 
      ImageSize -> Tiny}], HoldFirst]}, None}

 ]

enter image description here

Update 3

And got Leonid method to work also on more than one control. The trick is to use Control[]. Can't use plain old {{x,0,"x"},...} [EDIT, yes, you can, just need the Sequence@@ method as shown below by Leonid update.].

Here it is:

Manipulate[{x, y, z},

 Evaluate@With[
   {
    control1 = Function[{var, initialValue, str, from, to, incr},
      Control[{{var, initialValue, str}, from, to, incr, 
        ImageSize -> Tiny}]
      , HoldAll
      ]
    },

   Grid[{
     {control1[x, 0, "x=", 0, 1, .1]},
     {control1[y, 0, "y=", 0, 2, .1]},
     {control1[z, 0, "z=", 0, 10, .1]}
     }]

   ]
 ]

enter image description here

I'll try to integrate one of these methods into my main demo (has over 600 lines of code just for the control layout so far and growing by the minute, hopefully these methods will shrink this by quite a bit)

Update 9/26/11. 7 pm

I thought I post a 'birds eye' view of the code saving by using 'macros' to define controls which contains many common boiler-plate code. Here is a screen shot of before and after.

Thanks again for all the answer and help.

enter image description here

Community
  • 1
  • 1
Nasser
  • 12,849
  • 6
  • 52
  • 104
  • 1
    Thanks to all the answer (Leonid, Simon, WReach). All great answers. I selected to use the macro method by Leonid only because it does not use Dynamics, and I am not too good with Dynamics and seem to get in trouble with them :). Even though both methods work very well, all things being equal, I prefer a method that does not use Dynamics, only a personal preference and due to my lack of experience with dynamics. I posted a update above showing the code reduction I obtained as I started to integrate this new way of laying out controls in a demo I am writing and the code saving is amazing. – Nasser Sep 27 '11 at 02:23
  • Unfortunately, in many cases such code reduction is rather difficult to obtain in Mathematica since it is rather hard to control evaluations / write macros. I think, better ways to do that in mma are yet to be discovered (I am particularly interested in this topic). – Leonid Shifrin Sep 27 '11 at 09:31
  • From the look at your screenshot I noticed that you left out the `HoldAll` attribute of the pure function (macro). While this will work most of the time, having pre-defined values for the variables you use in Manipulate can lead to trouble (I don't know if this can realistically happen in the context of demonstrations). By just adding the attribute, you will protect your code against such cases (it is this attribute which convert a function into macro, in terms of its semantics) – Leonid Shifrin Sep 27 '11 at 10:53
  • @Leonid, got it, thanks! btw, it works just fine with HoldAll at the end, but I noticed you also added Unevaluated@{} in the body of the function. so, do I need both? HoldAll and Unevaluated? Now I have HoldAll only, and it seems to work just fine. But I only been testing it for short while, but so far no problems, your macro method is working very well. thanks. – Nasser Sep 27 '11 at 12:08
  • Yes, you do need `Unevaluated` as well. The reason why you need it is that otherwise there would be evaluation leak during the `Sequence@@..` stage. Try executing say `x=1` before the main `Manipulate` code, and you'll see that the version without `Unevaluated` will have problems. – Leonid Shifrin Sep 27 '11 at 12:35

3 Answers3

10

What about this

Manipulate[Plot[f*g, {x, -1, 1}],
 Evaluate@
  With[{styleAndpopup = 
      Function[{st, fun}, 
         {
           Style[st], 
           PopupMenu[Dynamic[fun], {x, x^2, x^3}, ImageSize -> Tiny]
         }, 
         HoldAll]},
    Grid[{styleAndpopup["f(x)=", f], styleAndpopup["g(x)=", g]}]]]

This is actually a tiny example of the code-generation at work, since if you look at the FullForm of the resulting Manipulate, you will see the same expression you originally started with. The styleAndpopup is actually not a function here, but a macro, locally defined using With.

EDIT

Per request of the OP - generalizing to many controls. The easiest fix is to insert Sequence@@... as Sequence @@ {First@control1[.... However, there is some extraneous stuff that can be removed as well:

Manipulate[{x, y}, 
 Evaluate@With[{control1 = 
     Function[{var, initialValue, str, from, to, incr}, 
       Unevaluated@{{var, initialValue, str}, from, to, incr, ImageSize -> Tiny}, 
       HoldAll]}, 
   Sequence @@ {
     control1[x, 0, "x=", 0, 1, .1], 
     control1[y, 0, "y=", 0, 2, .1], 
     control1[z, 0, "z=", 0, 10, .1]}]]
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • do you how to use your method to define more than one control using a 'common' definitions? Please see my edit 2 for what I tried. I think I am getting close. Thanks – Nasser Sep 26 '11 at 08:57
  • nice, Sequence@@{} was the trick. I also added another way to do it in my update. Now we have 3 methods to do this. – Nasser Sep 26 '11 at 09:36
  • @Nasser please note that there was a scoping bug / evaluation leak in my update: if you executed say `x = 1`, and then the updated code, you'd get errors. To fix that, added `Unevaluated` in the body of the `control1` function. – Leonid Shifrin Sep 26 '11 at 09:52
7

I was going to give a solution almost the same as Leonid's and use With to insert the code, but he beat me to it, so here's an alternative way. Define a dynamic local function using ControlType -> None that does your styling:

Manipulate[Plot[{f, g + 1}, {x, -1, 1}], 
 Dynamic[Grid[{{Style["f(x)="], pu[f]}, 
       {Style["g(x)="], pu[g]}}]], 
{{pu, Function[{f}, PopupMenu[Dynamic[f], {x, x^2, x^3}, ImageSize -> Tiny], 
        HoldFirst]}, None}]

By the way, the Style[] in Style["f(x)="] is redundant, as you are not actually setting any styles...

Simon
  • 14,631
  • 4
  • 41
  • 101
  • A nice alternative - +1. Actually, in my solution `With` is not crucial, can can be replaced by `Module` say (although, `With` seems to me a bit cleaner here, but this is a matter of taste). – Leonid Shifrin Sep 26 '11 at 08:08
  • @Simon, I am not sure I understand, the code above does not work, I am on V 8.01. Is there may be some missing code? I copied and run it, please see edit 1 for result on my computer. (windows 7). (and I did start with new kernel) thanks. – Nasser Sep 26 '11 at 08:35
  • @Simon, thanks. I got your method to work for using it across more than one separate control. Please see update 2. I am now working to do the same using Leonid method. – Nasser Sep 26 '11 at 09:21
1

One could do this:

Manipulate[
  Plot[f*g, {x, -1, 1}]
, Grid[
    { {Style["f(x)="], PopupMenu[Dynamic[f], opts]}
    , {Style["g(x)="], PopupMenu[Dynamic[g], opts]}
    }
  ]
] /. opts -> Sequence[{x, x^2, x^3}, ImageSize -> Tiny]

If one is in the habit of assigning down-values to symbols whose names do not begin with $, then it would be prudent to wrap the whole thing in Block[{x, opts}, ...] in case x and opts have globally-defined values.

A similar technique is possible for the case of multiple controls:

Manipulate[
  {x, y, z}
, Grid[
    { {control1[x, 0, "x=", 0, 1, .1]}
    , {control1[y, 0, "y=", 0, 2, .1]}
    , {control1[z, 0, "z=", 0, 10, .1]}
    }
  ]
] /. control1[var_, initialValue_, str_, from_, to_, incr_] :>
       Control[{{var, initialValue, str}, from, to, incr, ImageSize -> Tiny}]
WReach
  • 18,098
  • 3
  • 49
  • 93
  • 1
    The problem here is that the code does not start with `Manipulate` - rather, it starts with `ReplaceAll`. Whether or not this is acceptable for the demonstrations I don't know, but it falls short of the OP's specs in this regard. Surely, if we release this condition, many more ways to achieve the requested goal are possible. – Leonid Shifrin Sep 26 '11 at 16:01
  • @Leonid For what it's worth, I successfully uploaded this code to Wolfram Demonstrations. The OP is free to disqualify it based on other requirements. – WReach Sep 26 '11 at 16:27
  • That's good to know. I did not try myself, so was relying on what @Nasser stated. +1. – Leonid Shifrin Sep 26 '11 at 16:50
  • On the starting with Manipulate: For code to be _accepted_ for a WRI demonstration, it has to start with Manipulate. So, even though the code can pass the software upload and a CDF would be created, the demo will not be accepted for publication. For example, one can make CDF with Dynamic directly and not used Manipulate at all. But would not be accepted as a demo for publication. That is what I meant. This is what I was told by a WRI demonstration editor. Thanks. – Nasser Sep 26 '11 at 17:12
  • Here is a link to the rules, http://demonstrations.wolfram.com/guidelines.html (notice where it says "use only one Manipulate per Demonstration."). – Nasser Sep 26 '11 at 17:22
  • @Nasser I see. Thanks for the clarifications. Looks like making a full - fledged demonstration is a tricky business. – Leonid Shifrin Sep 26 '11 at 18:42
  • @Leonid @Nasser It sounds like the acceptance process involves a certain amount of subjectivity on the part of the WRI editor. The rules suggest that demonstrations should avoid doing "anything fancy". Perhaps a good strategy would be to use `ReplaceAll`, `Hold`, `Module` and other "fancy" (?!) techniques to generate a bare-bones, simple `Manipulate` expression which one then pastes into the demonstrations notebook? – WReach Sep 26 '11 at 19:28
  • @WReach Sounds like a good idea. Most of the time, "fancy" stuff is needed to encapsulate some state without leaking it to the top level, and the like. We should always be able to take the generated `Manipulate` verbatim, using the `FullForm`, and stick it in. One problem I can see with this approach is that the resulting code may be quite unreadable, and many people like to browse the code once they see the demo. – Leonid Shifrin Sep 26 '11 at 19:38
  • @Leonid ... and the WRI editor probably doesn't want to wade through generated code to give a pass/fail assessment. Perhaps one must keep things simple and just tolerate things like redundant control specifications. – WReach Sep 26 '11 at 19:53
  • @WReach Yes, I agree. I wonder what is the reasoning behind the limitations like starting from `Manipulate` etc - automation, security, something else? – Leonid Shifrin Sep 27 '11 at 09:24
  • @WReach Damn, I don't get it when people downvote and not leave any comments. – Leonid Shifrin Sep 27 '11 at 09:27