Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

any trick to add a Divider inside a Grid between rows at the point it is needed

To add a Divider between rows in a Grid, one way I know how to do it is by using the Dividers option. But this is used at the end, and one must then know the row number to which a divider needs to be below it. So, for large grid, I find myself using trial and error until I find the correct row number. Then when I change the grid later, I have to do the trial and error again to put the divider in the correct place in case it moved after my changes.

In this example, there is a grid with 3 rows and I want to add divider below say the second row, so I can do this:

Grid[{
  {1},
  {2},
  {3}
  }, Frame -> True, Dividers -> {All, 3 -> True}]

Another way, is to put False and True, in correct order where I want a divider, like this

Grid[{
  {1},
  {2},
  {3}
  }, Frame -> True, Dividers -> {All, {True, False, True, True}}]

enter image description here

It would be nice if I could do something like this (like one can do for Manipulate) (ofcourse this below does not work here)

Grid[{
  {1},
  {2},
  Delimiter,
  {3}
  }, Frame -> True]

or

Grid[{
  {1},
  {Item[2,Dividers->True]},
  {3}
  }, Frame -> True]

or such thing.

It will make code maintenance easier.

I looked at using Item and such for this, but not able to figure it out.

Any one knows of a trick to do it?

edit:

btw, This trick if possible, does not only apply to Dividers. But it would be useful to be able to do many of the other Grid options, which now is done at the Grid level, be done as well at the item level. For example, if I want to add an extra space after some row, it will easier to say so right at the pace I wanted this done. Also, if I wanted to change the Item size, easier to do it at the spot, same for Spacings, etc.. So that when moving/copying code for a whole row or Item, it is self contained and copy it with all its options together.

I think now this might require a new option add to Mathematica Grid to make it work right and be consistent with the over all grid design.

This is all until a GUI builder is made for Mathematica.

I find that I spend more than 60% of my time when I write a demo getting the GUI to fit and look right. With a GUI builder, I can spend this time working on the algorithm instead. When I use Matlab GUIDE to make GUI, it takes me less than 5% of my time to make a similar GUI.

I wish that WRI would make a GUI builder for Mathematica, I think this will be the killer app for Mathematica if you ask me. But no body asked me :)

edit(2)

comment on Mr Wizard nice solution below.
I mainly wanted this feature for the Grids I use to layout controls for Manipulate. Here is a simple example:

Manipulate[x,

 Grid[{
   {Control[{{x, 0, "x"}, 0, 10, 1}]},
   {Control[{{y, 0, "y"}, 0, 10, 1}]}
   }, Frame -> None, Dividers -> {None, {False, True, False}}
  ]
 ]

enter image description here

(and I have to use Grid for setting up the controls). I can't use function calls here. I can't write, using Wizard's solution below, the following:

Manipulate[x,

 myGrid[{
   {Control[{{x, 0, "x"}, 0, 10, 1}]},
   spec["Divider"],
   {Control[{{y, 0, "y"}, 0, 10, 1}]}
   }, Frame -> None
  ],

 Initialization :>
  {
   specfunc["Divider", lst_] := Dividers -> {None, # -> True & /@ lst};

   myGrid[dat_, opts : OptionsPattern[]] :=
    Module[{x = 1}, 
     Grid[#, opts, Sequence @@ #2] & @@ 
      Reap[If[MatchQ[#, _spec], Sow[x, #[[1]]]; ## &[], x++; #] & /@ 
        dat, _, specfunc]
     ]

   }
 ]

This gives an error, since Mathematica tries to first read the body of Manipulate to parse it, BEFORE reading and processing the initialization section.

But outside Manipulate, it will ofcourse work:

myGrid[{
  {Control[{{x, 0, "x"}, 0, 10, 1}]},
  spec["Divider"],
  {Control[{{y, 0, "y"}, 0, 10, 1}]}
  }, Frame -> None
 ]

specfunc["Divider", lst_] := Dividers -> {None, # -> True & /@ lst};
myGrid[dat_, opts : OptionsPattern[]] :=
 Module[{x = 1}, 
  Grid[#, opts, Sequence @@ #2] & @@ 
   Reap[If[MatchQ[#, _spec], Sow[x, #[[1]]]; ## &[], x++; #] & /@ 
     dat, _, specfunc]
  ]

enter image description here

I need to spend more time on this, to see if I can get it to work inside Manipulate.

btw, getting stuff like this to work inside Manipulate is really hard. Only trick I know is using macros with the With[{},.... Grid....] pattern I learned from Leonid.

For example of such difficulties, see this question of mine

How to define constants for use with With[] in one place and then apply them later?

edit(3) My be I doing something wrong, but I am getting some errors inside Manipulate:

first example:

Manipulate[x,

 Evaluate@grid[{
    {Control[{{x, 0, "x"}, 0, 10, 1}]}
    }
   ],

 Initialization :> 
  {
   grid[tab_, opts___] := 
     Module[{divlocal, divglobal, div, pos}, 
      divglobal = (Dividers /. opts) /. Dividers -> {False, False};
      If[Depth[divglobal] == 1, divglobal = {divglobal, divglobal}];
      If[Length[divglobal] == 1, AppendTo[divglobal, False]];
      pos = Position[tab, Dividers -> _, 1];
      divlocal = 
       MapIndexed[# - #2[[1]] + 1 -> Dividers /. tab[[#]] &, 
        Flatten[pos]];
      divglobal[[2]] = {divglobal[[2]], divlocal};
      Grid[Delete[tab, pos], Dividers -> divglobal, opts]];


   }
 ]

gives error:

ReplaceAll::rmix: Elements of {False,{}} are a mixture of lists and nonlists. >>

same if I replace the above with

Evaluate@grid[{
   Dividers -> {Thick, Blue},
   {Control[{{x, 0, "x"}, 0, 10, 1}]}
   }
  ],

Tried Dynamic@ in place of Evaluate@ no luck. may be small fix is all what is needed? or I am not using it right?

like image 785
Nasser Avatar asked Jan 16 '12 05:01

Nasser


2 Answers

This solutions should allow you to combine specifications for dividers between rows specified within the table with those specified using the Dividers option.

grid[tab_, opts___] :=
 Module[{divlocal, divglobal, div, pos},

  (* extract option value of Dividers from opts to divglobal *)
  (* default value is {False, False} *)
  divglobal = (Dividers /. {opts}) /. Dividers -> {False, False};

  (* transform divglobal so that it is in the form {colspecs, rowspecs} *)
  If[Head[divglobal] =!= List, divglobal = {divglobal, divglobal}];
  If[Length[divglobal] == 1, AppendTo[divglobal, False]];

  (* Extract positions of dividers between rows from tab *)
  pos = Position[tab, Dividers -> _, 1];

  (* Build list of rules for divider specifications between rows *)
  divlocal = MapIndexed[# - #2[[1]] + 1 -> Dividers /. tab[[#]] &, Flatten[pos]];

  (* Final settings for dividers are {colspecs, {rowspecs, divlocal}} *)
  divglobal[[2]] = {divglobal[[2]], divlocal};
  Grid[Delete[tab, pos], Dividers -> divglobal, opts]]

To specify a divider between rows you need to insert Dividers->spec at the desired position where spec is either False, True, or a graphics directive (colour, thickness, etc.). For example

tab = {{1, 2, 3}, Dividers -> {Thick, Blue}, {4, 5, 6}, {7, 8, 9}, 
   Dividers -> False, {10, 11, 12}};

grid[tab, Dividers -> All]

Mathematica graphics

Edit

I've added some comments to my code at the request of Mr. Wizard.

like image 195
Heike Avatar answered Nov 15 '22 09:11

Heike


I propose that you use a new head that can hold instructions (spec below), and a new function that processes these instructions (specfunc below) as individually required. The benefit is that this is easily generalized for different interleaved instructions, and each can be processed in an arbitrary way.

specfunc["Divider", lst_] := Dividers -> {All, # -> True & /@ lst}

myGrid[dat_, opts:OptionsPattern[]] :=
 Module[{x = 1},
  Grid[#, opts, Sequence @@ #2] & @@
   Reap[
     If[MatchQ[#, _spec], Sow[x, #[[1]]]; ## &[], x++; #] & /@ dat,
     _,
     specfunc
   ]
 ]

Usage:

dat =
{
   {1, 2, 3},
   {4, 5, 6},
   spec["Divider"],
   {7, 8, 9}, 
   spec["Divider"],
   {"a", "b", "c"}
};

myGrid[dat, Frame -> True]

Mathematica graphics


If each instruction can be a single string such as "Divider" and you have no conflict using them this way, you could eliminate spec and use MatchQ[#, _String] and Sow[x, #].


Addressing your updated question, as I noted in a comment below I believe that it makes more sense to use the most basic elements in your final Manipulate object, and write tools to help you generate this object most easily. I believe that attempting to make these kinds of customization inside the Manipulate block itself is doomed to fail, and probably in weird and opaque ways.

Nevertheless, for this particular case this appears to work, though I doubt it is robust:

Manipulate[x,
 Evaluate[
  {specfunc["Divider", lst_] := Dividers -> {All, # -> True & /@ lst};
   myGrid[dat_, opts : OptionsPattern[]] := 
    Module[{x = 1}, 
     Grid[#, opts, Sequence @@ #2] & @@ 
      Reap[If[MatchQ[#, _spec], Sow[x, #[[1]]]; ## &[], x++; #] & /@ 
        dat, _, specfunc]]};
  myGrid[{{Control[{{x, 0, "x"}, 0, 10, 1}]}, 
    spec["Divider"], {Control[{{y, 0, "y"}, 0, 10, 1}]}}, 
   Frame -> True]
  ]
 ]
like image 26
Mr.Wizard Avatar answered Nov 15 '22 08:11

Mr.Wizard