Locator-based abscissa ranges widget

5

I'm trying to implement a widget that will enable users to graphically determine certain abscissa ranges in a 2D plot.

I've started by coding a prototype, whose output looks like this:

Mathematica graphics

The idea for this interface is that the user will be able to adjust the locations of the shaded region's left and right edges by dragging on either one of the dark-blue triangles.

The aim of this prototype, however, is for me to understand how Dynamic and Locator fit into the widget's internal structure.

I give the full code for this at the end of this post, but here I want to focus on the code for the shaded region:

  xrange = {x, -Pi, Pi};
  yrange = {y, -1.1, 1.1};
  lefthandle = {-2, 0};
  righthandle = {2, 0};

  triangle[pts_] :=
    Graphics[{Directive[ColorData[1][1], Opacity[0.5]], Triangle[pts]}];

  regionplot = RegionPlot[
     First[lefthandle] < x < First[righthandle],
     Evaluate[xrange], Evaluate[yrange],
     BoundaryStyle -> None,

     Epilog -> {
         Inset[triangle[{{-1, 1}, {0, 0}, {-1, -1}}], lefthandle, {-1.2, 0},
               Scaled[0.075]],
         Inset[triangle[{{1, 1}, {0, 0}, {1, -1}}], righthandle, {1.2, 0},
               Scaled[0.075]]
       }

     ];

I've tried a bazillion things involving various combinations of Locator and Dynamic1.

They all failed, of course. This is hardly surprising, since I'm basically guessing, using as my starting point the bits of information that the documentation deigns to dole out.

I'm looking for suggestions on how to turn the (now-inert) triangles into draggable handles whose horizontal position determines the width of the shaded region. Specifically: where should Locator and Dynamic go, and what options should they take?

More generally, I would appreciate pointers to write-ups on Dynamic and/or Manipulate that give out the full story on these constructs; IOW, that are written for those (e.g. programmers) who are used to reading technical specifications. (I've read everything the official Mathematica documentation has to offer on these constructs, but it's its usual uninformative "dance of seven veils", so I still have no clue of what they are, or how to program with them without resorting to blind trial-and-error.)


There are a couple of important details of the intended use-case that are not captured in the prototype shown in this question. I mention them here, in case they are material to the answer:

  1. In the actual use-case, for any plot there will generally be not just one but several (non-overlapping) regions with adjustable boundaries.

  2. The widget is meant as an aid to help a human (usually me) manually adjust programmatically-generated region boundaries. At the end of the interaction, the widget should deliver a list of region boundaries, some of which may have been the result of the user's interaction with the widget.


Here's the code for the prototype so far2:

Module[
  {
   x, y,
   xrange,
   yrange,
   lefthandle,
   righthandle,
   plot,
   regionplot,
   triangle
  },

  xrange = {x, -Pi, Pi};
  yrange = {y, -1.1, 1.1};
  lefthandle = {-2, 0};
  righthandle = {2, 0};

  triangle[pts_] :=
    Graphics[{Directive[ColorData[1][1], Opacity[0.5]], Triangle[pts]}];

  regionplot = RegionPlot[
     First[lefthandle] < x < First[righthandle],
     Evaluate[xrange], Evaluate[yrange],
     BoundaryStyle -> None,
     Epilog -> {
         Inset[triangle[{{-1, 1}, {0, 0}, {-1, -1}}], lefthandle, {-1.2, 0},
               Scaled[0.075]],
         Inset[triangle[{{1, 1}, {0, 0}, {1, -1}}], righthandle, {1.2, 0},
               Scaled[0.075]]
       }
     ];

   plot = Plot[Sin[x], Evaluate[xrange], PlotRange -> {All, Automatic}];

   Show[{regionplot, plot},
     PlotRange -> {Automatic, Drop[yrange, 1]},
     AspectRatio -> 1/GoldenRatio
   ]
]

1I have expressly avoided Manipulate, since using it would mean introducing one more largely undocumented Mathematica MysteryBox into an already confusing situation. I figure that if I have any hope of understanding what I ultimately implement, it may be a good idea to leave Manipulate out of the problem for now. Of course, if what I want to do is just too difficult without Manipulate, then by all means let's have it.

2I decided to post working code that I more-or-less understand, so as not to bias the answers with stuff that I really don't have a clue about. Therefore, I used Module in the place where I suspect one should use DynamicModule (yet another MysteryBox, from my perspective).

kjo

Posted 2015-06-20T22:34:49.617

Reputation: 11 163

Answers

4

Let's give you some freedom of choice here and since you explicitly avoided Manipulate, let's start with this. The piece that creates the plot, the rectangle and the triangles will almost be the same in all three examples.

In all three examples, I will use locators, that have no appearance but will be forced to stay on the y=0 line, so that they are always invisible over the triangles.

The triangle graphic is dynamically updated. p1 and p2, the only two variables determine the size of the range and are the coordinates of the two triangles.

Inside Manipulate, we don't need any explicit Dynamic, since basically everything is automatically wrapped in it without you seeing it:

Manipulate[
 Show[
  Plot[Sin[x], {x, 0, 2 Pi}, Axes -> False, Frame -> True],
  Graphics[{Opacity[0.2, Blue], Rectangle[p1 - {0, 1}, p2 + {0, 1}], 
    Opacity[0.5, Red],
    Triangle[{{p1 + {0.2, 0}, p1 + {0, .1}, 
       p1 - {0, .1}}, {p2 - {0.2, 0}, p2 - {0, 0.1}, p2 + {0, .1}}}]
    }]],
 {{p1, {1, 0}}, {0, 0}, {2 Pi, 0}, Locator, Appearance -> None},
 {{p2, {3, 0}}, {0, 0}, {2 Pi, 0}, Locator, Appearance -> None}
 ]

Mathematica graphics

The important line is the specification of the locators at the bottom. I have given their initial value and the valid range. This information can be found in the details section of Manipulate.

Another easy method is, to include the locators directly into a graphic. You should note how we restrict the position of the locators by directly giving their update-function inside Dynamic. One addition is the Dynamic in front of the Show which is required here.

DynamicModule[{p1 = {1, 0}, p2 = {4, 0}},
 Dynamic@Show[
  Plot[Sin[x], {x, 0, 2 Pi}, Axes -> False, Frame -> True],
  Graphics[{Opacity[0.2, Blue], Rectangle[p1 - {0, 1}, p2 + {0, 1}], 
    Opacity[0.5, Red],
    Triangle[{{p1 + {0.2, 0}, p1 + {0, .1}, 
       p1 - {0, .1}}, {p2 - {0.2, 0}, p2 - {0, 0.1}, p2 + {0, .1}}}],
    Locator[Dynamic[p1, (p1 = {1, 0}*#) &], None],
    Locator[Dynamic[p2, (p2 = {1, 0}*#) &], None]
    }]
  ]
 ]

Last but not least, you can use LocatorPane which gives you the possibility to specify the locator ranges a bit better as 3rd argument. The rest looks very similar

DynamicModule[{p1 = {1, 0}, p2 = {4, 0}},
 LocatorPane[Dynamic[{p1, p2}],
  Dynamic@Show[
    Plot[Sin[x], {x, 0, 2 Pi}, Axes -> False, Frame -> True],
    Graphics[{Opacity[0.2, Blue], Rectangle[p1 - {0, 1}, p2 + {0, 1}],
       Opacity[0.5, Red],
      Triangle[{{p1 + {0.2, 0}, p1 + {0, .1}, 
         p1 - {0, .1}}, {p2 - {0.2, 0}, p2 - {0, 0.1}, 
         p2 + {0, .1}}}]
      }]
    ], {{0, 0}, {2 Pi, 0}}, Appearance -> None
  ]]

I tried to make the examples as small as possible. Therefore, you need to implement things like forcing p1 smaller then p2 in their x-value, etc..

If you have specific questions, just ask in the comments and I'll try to answer them.

halirutan

Posted 2015-06-20T22:34:49.617

Reputation: 109 574

Thanks! I'm sure I will have questions, but it will take me a little while to digest your answer... – kjo – 2015-06-21T00:54:35.677

I was wrong: I have no questions! – kjo – 2015-06-21T02:38:23.953

2

This should do it.

leftTriangle = 
 Magnify[Graphics[{Directive[ColorData[1][1], Opacity[0.5]], 
  Triangle[{{-1, 1}, {0, 0}, {-1, -1}}]}, AlignmentPoint -> Left], 0.1];

rightTriangle = 
 Magnify[Graphics[{Directive[ColorData[1][1], Opacity[0.5]], 
  Triangle[{{1, 1}, {0, 0}, {1, -1}}]}, AlignmentPoint -> Right], 0.1];

plot = Plot[Sin[x], {x, -Pi, Pi}, PlotRange -> {All, Automatic}];

DynamicModule[{p1 = {-2, 0}, p2 = {2, 0}},
 Dynamic@Show[{
  RegionPlot[First@p1 < x < First@p2, {x, -Pi, Pi}, {y, -1.1, 1.1}],
  plot,
  Graphics[Locator[Dynamic[p1, (p1 = {First@#, 0}) &], leftTriangle]],
  Graphics[Locator[Dynamic[p2, (p2 = {First@#, 0}) &], rightTriangle]]}]]

enter image description here

Some explanations: leftTriangle is a graphic, that is used as the locator object by using it as the second argument to Locator. The movement of the locators are constrained to a horizontal movement by using the second argument of Dynamic. The Dynamic in front of Show makes the whole plot update, whenever the locators are changed.

Karsten 7.

Posted 2015-06-20T22:34:49.617

Reputation: 26 728