Message Boards Message Boards

Drag the cursor to create a rectangle which is zoomed using EventHandler?

Posted 8 years ago

I would like to drag the cursor to create a rectangle which will then be zoomed to window size. As a start, I will be happy to drag a small disk from point to point. I have tried various ways involving DynamicModule[], EventHandler[], "MouseClicked", "MouseDragged", MousePosition[], Dynamic[], and PassEventsDown --> True, but my results are poor. I have even seen the code for this, but I lost it and can't reproduce it or find it. I have concluded that my understanding of EventHandler[] is wanting. Would someone be so kind as to discuss its parts, options, and requirements as they pertain to this problem. All the versions below make the dot disappear rather than move.

disk[pos_] := 
  Graphics[{Disk[pos, .1]}, PlotRange -> .5 {{-1, 1}, {-1, 1}}, 
   ImageSize -> 100, Background -> White];

(* 1 *)
DynamicModule[{pos = {0, 0}}, 
 EventHandler[
  disk[Dynamic @ pos], {"MouseClicked" :> ( pos = MousePosition[])}]]

(* 2 *)
DynamicModule[{pos = {0, 0}},
 EventHandler[
  disk[Dynamic @ 
    pos], {"MouseClicked" :> (pos = 
      pos /. {pos -> MousePosition[]})}]]

(* 3 *)
DynamicModule[{pos = {0, 0}}, 
 Dynamic @ 
  EventHandler[
   disk[ pos], {"MouseClicked" :> ( pos = MousePosition[])}]]

(* 4 *)
DynamicModule[{pos = {0, 0}},
 Dynamic @ EventHandler[
   disk[pos], {"MouseDragged" :> (pos = 
       pos /. {pos -> MousePosition[]})}, PassEventsDown -> True]]
POSTED BY: Gary Palmer
11 Replies
Posted 8 years ago
POSTED BY: Gary Palmer
Posted 8 years ago

This page on the Mathematica StackExchange contains some 2016 code for the PlotExplorer which István Zachar began writing for Mathematica 9. The old code had problems, but this code seems to actually work.

http://mathematica.stackexchange.com/questions/94716/how-to-use-a-ready-function-in-your-code-for-panning-and-zooming-on-2d-plots/94759#94759

It really provides a variety of functions. To try it, cut and paste it and add this:

PlotExplorer@ ListLinePlot[Table[Accumulate[RandomReal[{-1, 1}, 250]], {3}], Filling -> Axis]

Some interesting demonstrations is here along with a 2015 version of the PlotExplorer. I was unable to make this version work in MMA 10.0.0, late 2009 iMac.

http://mathematica.stackexchange.com/questions/7142/how-to-manipulate-2d-plots

POSTED BY: Gary Palmer
Posted 8 years ago

Gary,

To the extent that I have tested it with the examples used by the developers of the code, It seems to work in my trial V11 (windows 10),

POSTED BY: E Martin
Posted 8 years ago

In the link you provide in your post above, István Zachar says

When it comes to visual analysis, large datasets or data with intricate internal details often makes plotting in 2 D useless, as the outcome is either just a fraction of the full dataset, or no details can be observed in the mess of datapoints.How can one make the process of changing the plot range and/or zooming, panning, etc.less tedious than doing it programmatically and iteratively, from time to time?I often meet with this issue, and developed various methods to deal with it (like this).Though I have now a working solution that I would like to share (see below), I also am highly interested in what kind of methods and tricks others invented to visualize and manipulate complex 2 D data with ease.

I fully agree with the remark, and have spent a lot of time struggling with the issue for years..

It seems that in Mathematica V9 WRI included an experimental function (PlotExplorer[]) that disappeared in versions 10 and 11.

Any comment will be welcome if anyone has made any successful use of the István Zachar' sPlotExplorercode published in the Mathematica StackExchange post linked by Gary,

POSTED BY: E Martin
Posted 8 years ago

@Patrik, Thank you for the nice explanation and added code.

But I find no difference in behavior in the following two items:

x = 1;
{Dynamic[Slider[Dynamic[x], {0, x}, ContinuousAction -> False]], 
 Dynamic[x]}

Clear["Global`*"];
x = 1;
{Slider[Dynamic[x], {0, x}, ContinuousAction -> False], Dynamic[x]} (* Initial Dynamic[] removed *)

By the way, how does one create a panel for code and quote in this forum? I see no help links on this page.

POSTED BY: Gary Palmer

@Gary

The first one resets it's max value each time you drop the slider.

Maybe this one is more obvious, where the slider width changes with values of x:

Clear["Global`*"]; 
x = 1; 
{Dynamic[Slider[Dynamic[x], {0, 1}, ContinuousAction -> False, ImageSize -> x*1000]], Dynamic[x]}

Versus this one where only the initial value is used and the slider isn't changed after evaluation (although the value of x is)

Clear["Global`*"]; 
x = 1; 
{Slider[Dynamic[x], {0, 1}, ContinuousAction -> False, ImageSize -> x*1000], Dynamic[x]}

Quotes have are preceded by an ">" sign.

Code snippets are preceded by a tab indentation.

Click on the icons at the top of the box where you write the messages. The left most one is for code, the right most one in the second column is for quotes.

POSTED BY: Patrik Ekenberg

Great!

I am not really that experienced with this, so I might be wrong in my explanations but this is at least what I believe happens:

I'll need to start with number 3 in order to explain number 1. Dynamic[pt2] is a symbol that is sent to LocatorPane and it will use the initial value of pt2 when initializing. If it has a Dynamic[] head, LocatorPane will be able to manipulate the symbol and the new values affect other parts of the Notebook. If there was only pt2, no Dynamic[], it would only use pt2 as a start value and actions in the LocatorPane would not update any symbol. This is the same of all control objects, check for example the documentation for Slider.

The Dynamic around LocatorPane is needed because the square you are drawing changes with your mouse movements. If there wasn't any Dynamic around it, it would only display the square that was drawn when the LocatorPane was initialized and it wouldn't update with new values for pt2 or pt1.

Check for example this code:

x = 1;
{Dynamic[Slider[Dynamic[x], {0, x}, ContinuousAction -> False]], 
 Dynamic[x]}

The slider gets redrawn only because there is a Dynamic around it.

Now, the problem is that since the LocatorPane gets redrawn on every new value for pt2, the cursor loses focus when it is redrawn. Thus I was not able to move the pointer since it would instantly lose focus. AutoAction was a way around it, I don't really know why that works, but it did. AutoAction only gets enabled when the right mouse button is pressed and disabled once it is released.

The cleanest way would possibly be to only update the LocatorPane on zoom in and overlay the zoom square, but I was not able to get that to work.

AutoAction works with most control objects, it even works with CheckBox, though I would not recommend using it there (:

You can use this to get a feeling for where the zoom square is and the two different points:

autoAction = False;
pt1 = {0, 0};
pt2 = {0, 0};
zoom = {{0, 4}, {-2, 2}};
graphics = {Thick, Green, Rectangle[{0, -1}, {2, 1}], Red, Disk[], 
   Blue, Circle[{2, 0}], Yellow, Polygon[{{2, 0}, {4, 1}, {4, -1}}], 
   Purple, Arrowheads[Large], Arrow[{{4, 3/2}, {0, 3/2}, {0, 0}}], 
   Black, Dashed, Line[{{-1, 0}, {4, 0}}]};
EventHandler[
 Dynamic[LocatorPane[Dynamic[pt2], 
   Graphics[{graphics, Locator[pt1, Style["pt1", Large]], 
     Locator[pt2, Style["pt2", Large]], 
     Directive[{LightOrange, Opacity[0.5]}], Rectangle[pt1, pt2]}, 
    PlotRange -> zoom], AutoAction -> autoAction, 
   Appearance -> None]], {{"MouseDown", 1} :> (autoAction = True;
    pt1 = MousePosition["Graphics"];), {"MouseUp", 
    1} :> (autoAction = False;
    zoom = Transpose[{pt1, pt2}]; pt1 = {0, 0}; 
    pt2 = {0, 0};), {"MouseClicked", 2} :> (zoom = {{0, 4}, {-2, 2}})}]
Dynamic[Graphics[{graphics, Locator[pt1, Style["pt1", Large]], 
   Locator[pt2, Style["pt2", Large]], 
   Directive[{LightOrange, Opacity[0.5]}], 
   Rectangle @@ Transpose[zoom]}]]

Regards,

Patrik

POSTED BY: Patrik Ekenberg
Posted 8 years ago

@Patrik, That is what I was looking for. Thanks.

But what about the explanation? Let me pose a few questions:

  1. AutoAction is suppose to enable the controls to take action whenever the pointer is over them, even if they are not clicked. I suppose in this script, that must mean whenever the pointer is over the locator pane, which has the dimensions of PlotRange. Is that right? What is the role of AutoAction in this script? What action are the controls taking when the pointer is over the locator pane (as opposed to actually being clicked)?

  2. The documentation for AutoAction says that it is an option for controls such as Slider, Locator, and Button, yet you have written it as an option for LocatorPane[]. Is that because LocatorPane[] is also a control like Locator?

  3. Since you have Dynamic[pt2] inside LocatorPane[], what is the need for Dynamic[LocatorPane[]]? Or perhaps I should ask it the other way around.

  4. And how is it that one can reset with right click?

POSTED BY: Gary Palmer

Hi! What about something like this?

autoAction = False;
pt1 = {0, 0};
pt2 = {0, 0};
zoom = {{0, 4}, {-2, 2}};
graphics = {Thick, Green, Rectangle[{0, -1}, {2, 1}], Red, Disk[], 
   Blue, Circle[{2, 0}], Yellow, Polygon[{{2, 0}, {4, 1}, {4, -1}}], 
   Purple, Arrowheads[Large], Arrow[{{4, 3/2}, {0, 3/2}, {0, 0}}], 
   Black, Dashed, Line[{{-1, 0}, {4, 0}}]};
EventHandler[
 Dynamic[
  LocatorPane[Dynamic[pt2], 
   Graphics[{graphics, Directive[{LightOrange, Opacity[0.5]}], 
     Rectangle[pt1, pt2]}, PlotRange -> zoom], 
   AutoAction -> autoAction, Appearance -> None]], {
  {"MouseDown", 1} :> (autoAction = True; 
    pt1 = MousePosition["Graphics"];),
  {"MouseUp", 1} :> (autoAction = False;
    zoom = Transpose[{pt1, pt2}]; pt1 = {0, 0}; pt2 = {0, 0};),
  {"MouseClicked", 2} :> (zoom = {{0, 4}, {-2, 2}})}]

Click and drag to zoom in, right click to reset. Could be a start at least!

Regards, Patrik

POSTED BY: Patrik Ekenberg
Posted 8 years ago

@Gianlucca, We now have two such attempts. Thank you for making a try. Perhaps the traffic will inspire more experienced event handlers to jump in.

I noticed that you included "Graphics" as the argument to MousePosition[]. As I read the documentation on ?Graphics, this should yield the position of the cursor when it lies within the boundaries of the Graphics[], which perhaps means any position within the PlotRange rather than within the graphics objects (i.e. the rectangle or disk) that are the arguments of Graphics[]. I have not seen this explained. At least, when I include "Graphics", the behavior changes to provide what I was attempting. That is, the disk moves from point to point instead of disappearing. So that is a big improvement.

POSTED BY: Gary Palmer

A crude attempt, by a novice of EventHandler:

DynamicModule[{pos1 = {0, 0}, pos2 = {.1, .1}, pr = .5}, 
 EventHandler[
  Dynamic@Graphics[{Rectangle[pos1, pos2]}, PlotRange -> pr, 
    Frame -> True],
  {"MouseDown" :> (pos1 = MousePosition["Graphics"]), 
   "MouseUp" :> (pos2 = MousePosition["Graphics"]; 
     pr = Transpose[Map[Sort, {pos1, pos2}]])}]]
POSTED BY: Gianluca Gorni
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract