2
$\begingroup$

I am trying to present Graphics primitives dynamically. I want the displayed image to be able to update after the user changes the controls. An example would be something like

With[{triangle = {{0.13, 0.94}, {0.741, 0.744}, {0.77, 0.2}},
  DynamicModule[{ctrlx, ctrly, x, y, cond, epi, lab, gra},
   ctrlx=Row[{Framed[Control[{{x, 0.6, "x"}, 0, 1, 0.0001}], FrameStyle -> Thin], Spacer[10]}];
   ctrly=Row[{Framed[Control[{{y, 0.6, "y"}, 0, 1, 0.0001}], FrameStyle -> Thin], Spacer[10]}];

   cond=And[0.13<=x<0.9,0.2<=y<=0.567];

   lab = If[
    cond,
    Style[Row[#], Red, FontSize -> 14] & /@ {{"x=", x}, {"y=", y}},
    Style[Row[#], Black, FontSize -> 14] & /@ {{"x=", x}, {"y=", y}}
   ];

   gra = Graphics[
     {Opacity[0.1], FaceForm[Orange], EdgeForm[Purple], Polygon[rect]},

     Frame -> True,
     FrameLabel -> {
       {Null, Dynamic@lab[[1]]},
       {Null, Dynamic@lab[[2]]}
      },
     RotateLabel -> False,
     Epilog -> Dynamic@If[cond, 
       {Red, Point[{x, y}]}, 
       {Black, Point[{x, y}]}
      ],
     PlotRange -> {{0., 1.}, {0., 1.}},
     ImagePadding -> {{5, 70}, {5, 20}},
     ImageSize -> Medium
    ];

    Grid[{{gra}, {Column[{ctrlx, ctrly}]}}]

  ]
 ]

In this example, the controls change the $x$ and $y$ coordinates of a moving point.

Once the coordinates of that point enter a per-configured area (see cond at the code snippet above) the FrameLabel's change color and so does the point itself (from Black to Red, see the following two images).

enter image description here

Now, the question is motivated by the results of the following changes in the code:

If I attempt to bundle together all the pieces of code that need to get reevaluated every time the controls change, namely when I modify the first If statement to read like this

{epi, {lblx, lbly}} = If[
  cond,
  {
    {Red, Point[{x, y}]},
    Style[Row[#], Red, FontSize -> 14] & /@ {{"x=", x}, {"y=", y}}
   },
  {
   {Black, Point[{x, y}]},
   Style[Row[#], Black, FontSize -> 14] & /@ {{"x=", x}, {"y=", y}}
  }
 ];

and after I perform the needed adjustments in the body of the Graphics ie after I adjust the following

FrameLabel -> {
  {Null, Dynamic@lblx},
  {Null, Dynamic@lbly}
 }

and

Epilog -> Dynamic@epi

the front end cannot evaluate it. It spits an error Set::shape with a text of

Lists {epi\$1935,{lblx\$1935,lbly\$1935}} and If[0.13<=x\$1935<0.9&&0.2<=y\$1935<=0.567,{{Red,Point[{x\$1935,y\$1935}]},(Style[Row[#1],Red,FontSize->14]&)/@{{x=,x\$1935},{y=,y\$1935}}},{{Black,Point[{x\$1935,y$1935}]},(Style[Row[#1],Black,FontSize->14]&)/@{{x=,x\$1935},{y=,y\$1935}}}] are not the same shape.

Apparently, what the front ends informs the user is that the assignment in

{epi, {lblx, lbly}} = If[...]

does not work. I assume that happens because cond contains x and y which are in turn dynamically changing but I don't actually know why that is the case since in the former version of the code cond was similarly depended upon x and y and If evaluated just fine.

So the question is, how to make bundling together all the pieces of code that change when the controls change, work?

PS

I include the code that reproduces the error message for convenience

With[{rect = {{0.13, 0.2}, {0.13, 0.567}, {0.9, 0.567}, {0.9, 0.2}}},
 DynamicModule[{ctrlx, ctrly, x, y, cond, epi, lblx, lbly, gra},
  ctrlx = Row[{Framed[Control[{{x, 0.6, "x"}, 0, 1, 0.0001}], FrameStyle -> Thin], Spacer[10]}];
  ctrly = Row[{Framed[Control[{{y, 0.6, "y"}, 0, 1, 0.0001}], FrameStyle -> Thin], Spacer[10]}];
  cond = And[0.13 <= x < 0.9, 0.2 <= y <= 0.567];
  {epi, {lblx, lbly}} = If[
    cond,
    {
     {Red, Point[{x, y}]},
     Style[Row[#], Red, FontSize -> 14] & /@ {{"x=", x}, {"y=", y}}
     },
    {
     {Black, Point[{x, y}]},
     Style[Row[#], Black, FontSize -> 14] & /@ {{"x=", x}, {"y=", y}}
    }
   ];
  gra = Graphics[
    {Opacity[0.1], FaceForm[Orange], EdgeForm[Purple], Polygon[rect]},
    Frame -> True,
    FrameLabel -> {
      {Null, Dynamic@lblx},
      {Null, Dynamic@lbly}
     },
    RotateLabel -> False,
    Epilog -> Dynamic@epi,
    PlotRange -> {{0., 1.}, {0., 1.}},
    ImagePadding -> {{20, 70}, {20, 20}},
    ImageSize -> Medium
   ];
  Grid[{{gra}, {Column[{ctrlx, ctrly}]}}],
  Deployed -> True
 ]
]
$\endgroup$
  • $\begingroup$ The more compact example the more likely you will get attention. Are you sure all this is essential to reproduce the problem? $\endgroup$ – Kuba Oct 22 '17 at 15:41
  • $\begingroup$ I have presented a stripped down version of my code; (I think) the problem is located in {epi, {lblx, lbly}} =If[...]; the rest of the code is minimally necessary in order for anyone to get a meaningfully reproducible example running; my main problem is that I need to display Graphics dynamically and that doesn't work when I group together all the things that need to change; I guess I could strip some more lines off but that would probably make the presentation more distant from what I think is useful; I am open to suggestions $\endgroup$ – user42582 Oct 22 '17 at 16:01
4
$\begingroup$

I don't want to expend the time it would take to itemize your errors, but I'm willing to show you a much simpler implementation that works. Comparing it with your own code should help you to determine what you did wrong.

With[{rect = {{0.13, 0.2}, {0.13, 0.567}, {0.9, 0.567}, {0.9, 0.2}}}, 
  DynamicModule[{x, y, color, lblx, lbly, gra},
    Dynamic @ 
      Column[
        {Dynamic @ gra,
         Row[
           {Invisible["****"],
            Framed[Control[{{x, 0.6, "x"}, 0, 1, 0.01}], FrameStyle -> Thin]}],
         Row[
           {Invisible["****"],
            Framed[Control[{{y, 0.6, "y"}, 0, 1, 0.01}], FrameStyle -> Thin]}]}],
    Initialization :> (
      color := If[And[0.13 <= x < 0.9, 0.2 <= y <= 0.567], Red, Black];
      lblx := Style[Row[{"x = ", x}], color, FontSize -> 14];
      lbly := Style[Row[{"y = ", y}], color, FontSize -> 14];
      gra =
       Graphics[
        {Opacity[0.1], FaceForm[Orange], EdgeForm[Purple], Polygon[rect]},
        Frame -> True,
        FrameLabel -> {{None, lblx}, {None, lbly}},
        RotateLabel -> False,
        Epilog -> {color, Point[{x, y}]},
        PlotRange -> {{0., 1.}, {0., 1.}},
        ImagePadding -> {{20, 70}, {20, 30}},
        ImageSize -> Medium])]]

gui

Basically your problems are improper application of Dynamic and improper initialization of variables.

Update

Because I think it's rather clumsy to move the point around with two sliders, I think you ought to consider using a locator pane in which you can simply drag the point around with the mouse.

With[{rect = {{0.13, 0.2}, {0.13, 0.567}, {0.9, 0.567}, {0.9, 0.2}}},
  DynamicModule[{pt = {.6, .6}, color, lblx, lbly},
    LocatorPane[
      Dynamic @ pt,
      Dynamic @ 
        Graphics[
          {{Opacity[0.1], FaceForm[Orange], EdgeForm[Purple], Polygon[rect]},
          {color, AbsolutePointSize[6], Point[pt]}},
         Frame -> True,
         FrameLabel -> {{None, lblx}, {None, lbly}},
         RotateLabel -> False,
         PlotRange -> {{0., 1.}, {0., 1.}},
         ImagePadding -> {{20, 70}, {20, 30}},
         ImageSize -> Medium],
      {{0, 0}, {1, 1}, .01 {1, 1}},
      Appearance -> None],
    Initialization :> (
      color := If[And[0.13 <= pt[[1]] < 0.9, 0.2 <= pt[[2]] <= 0.567], Red, Black];
      lblx := Style[Row[{"x = ", pt[[1]]}], color, FontSize -> 14];
      lbly := Style[Row[{"y = ", pt[[2]]}], color, FontSize -> 14])]]

locator

$\endgroup$
  • $\begingroup$ within the Initialization block eg color receives a delayed assignment associating it with dynamic variables x and y; should I interpret the fact that the assignment does not contain any pattern (eg there is no color[x_,y_]:=<x,y> ) as an indication that, in fact, color depends on global (within the module) variables x and y? Can I treat code within the Initialization block as having access to 'globals' defined in the DynamicModule? Thank you. $\endgroup$ – user42582 Nov 2 '17 at 15:11
  • 1
    $\begingroup$ @user42582. Since the variables used in the initialization are all specified in the 1st argument of DynamicModule, the are all local to the module (defined over and only over the scope of the module). That makes them available anywhere in the module when it runs. The SetDelayed expressions defined in the initialization are dynamically evaluated whenever the value of pt changes; therefore, they use the current value of pt. To get that behavior is whole reason for writing the initialization code in the way it appears. $\endgroup$ – m_goldberg Nov 2 '17 at 17:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.