1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkXYPlotWidget.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 #include "vtkXYPlotWidget.h"
16 #include "vtkCallbackCommand.h"
17 #include "vtkCoordinate.h"
18 #include "vtkObjectFactory.h"
19 #include "vtkRenderWindow.h"
20 #include "vtkRenderWindowInteractor.h"
21 #include "vtkRenderer.h"
22 #include "vtkXYPlotActor.h"
23 
24 #include <cmath>
25 
26 vtkStandardNewMacro(vtkXYPlotWidget);
27 vtkCxxSetObjectMacro(vtkXYPlotWidget, XYPlotActor, vtkXYPlotActor);
28 
29 //------------------------------------------------------------------------------
vtkXYPlotWidget()30 vtkXYPlotWidget::vtkXYPlotWidget()
31 {
32   this->XYPlotActor = vtkXYPlotActor::New();
33   this->EventCallbackCommand->SetCallback(vtkXYPlotWidget::ProcessEvents);
34   this->State = vtkXYPlotWidget::Outside;
35   this->Priority = 0.55;
36 }
37 
38 //------------------------------------------------------------------------------
~vtkXYPlotWidget()39 vtkXYPlotWidget::~vtkXYPlotWidget()
40 {
41   if (this->XYPlotActor)
42   {
43     this->XYPlotActor->Delete();
44   }
45 }
46 
47 //------------------------------------------------------------------------------
SetEnabled(int enabling)48 void vtkXYPlotWidget::SetEnabled(int enabling)
49 {
50   if (!this->Interactor)
51   {
52     vtkErrorMacro(<< "The interactor must be set prior to enabling/disabling widget");
53     return;
54   }
55 
56   if (enabling)
57   {
58     vtkDebugMacro(<< "Enabling line widget");
59     if (this->Enabled) // already enabled, just return
60     {
61       return;
62     }
63 
64     if (!this->CurrentRenderer)
65     {
66       this->SetCurrentRenderer(this->Interactor->FindPokedRenderer(
67         this->Interactor->GetLastEventPosition()[0], this->Interactor->GetLastEventPosition()[1]));
68       if (this->CurrentRenderer == nullptr)
69       {
70         return;
71       }
72     }
73 
74     this->Enabled = 1;
75 
76     // listen for the following events
77     vtkRenderWindowInteractor* i = this->Interactor;
78     i->AddObserver(vtkCommand::MouseMoveEvent, this->EventCallbackCommand, this->Priority);
79     i->AddObserver(vtkCommand::LeftButtonPressEvent, this->EventCallbackCommand, this->Priority);
80     i->AddObserver(vtkCommand::LeftButtonReleaseEvent, this->EventCallbackCommand, this->Priority);
81 
82     // Add the xy plot
83     this->CurrentRenderer->AddViewProp(this->XYPlotActor);
84     this->InvokeEvent(vtkCommand::EnableEvent, nullptr);
85   }
86   else // disabling------------------------------------------
87   {
88     vtkDebugMacro(<< "Disabling line widget");
89     if (!this->Enabled) // already disabled, just return
90     {
91       return;
92     }
93     this->Enabled = 0;
94 
95     // don't listen for events any more
96     this->Interactor->RemoveObserver(this->EventCallbackCommand);
97 
98     // turn off the line
99     this->CurrentRenderer->RemoveActor(this->XYPlotActor);
100     this->InvokeEvent(vtkCommand::DisableEvent, nullptr);
101     this->SetCurrentRenderer(nullptr);
102   }
103 
104   this->Interactor->Render();
105 }
106 
107 //------------------------------------------------------------------------------
ProcessEvents(vtkObject * vtkNotUsed (object),unsigned long event,void * clientdata,void * vtkNotUsed (calldata))108 void vtkXYPlotWidget::ProcessEvents(
109   vtkObject* vtkNotUsed(object), unsigned long event, void* clientdata, void* vtkNotUsed(calldata))
110 {
111   vtkXYPlotWidget* self = reinterpret_cast<vtkXYPlotWidget*>(clientdata);
112 
113   // okay, let's do the right thing
114   switch (event)
115   {
116     case vtkCommand::LeftButtonPressEvent:
117       self->OnLeftButtonDown();
118       break;
119     case vtkCommand::LeftButtonReleaseEvent:
120       self->OnLeftButtonUp();
121       break;
122     case vtkCommand::MouseMoveEvent:
123       self->OnMouseMove();
124       break;
125   }
126 }
127 
128 //------------------------------------------------------------------------------
ComputeStateBasedOnPosition(int X,int Y,int * pos1,int * pos2)129 int vtkXYPlotWidget::ComputeStateBasedOnPosition(int X, int Y, int* pos1, int* pos2)
130 {
131   int Result;
132 
133   // what are we modifying? The position, or size?
134   // if size what piece?
135   // if we are within 7 pixels of an edge...
136   int e1 = 0;
137   int e2 = 0;
138   int e3 = 0;
139   int e4 = 0;
140   if (X - pos1[0] < 7)
141   {
142     e1 = 1;
143   }
144   if (pos2[0] - X < 7)
145   {
146     e3 = 1;
147   }
148   if (Y - pos1[1] < 7)
149   {
150     e2 = 1;
151   }
152   if (pos2[1] - Y < 7)
153   {
154     e4 = 1;
155   }
156 
157   // assume we are moving
158   Result = vtkXYPlotWidget::Moving;
159   // unless we are on a corner or edges
160   if (e2)
161   {
162     Result = vtkXYPlotWidget::AdjustingE2;
163   }
164   if (e4)
165   {
166     Result = vtkXYPlotWidget::AdjustingE4;
167   }
168   if (e1)
169   {
170     Result = vtkXYPlotWidget::AdjustingE1;
171     if (e2)
172     {
173       Result = vtkXYPlotWidget::AdjustingP1;
174     }
175     if (e4)
176     {
177       Result = vtkXYPlotWidget::AdjustingP4;
178     }
179   }
180   if (e3)
181   {
182     Result = vtkXYPlotWidget::AdjustingE3;
183     if (e2)
184     {
185       Result = vtkXYPlotWidget::AdjustingP2;
186     }
187     if (e4)
188     {
189       Result = vtkXYPlotWidget::AdjustingP3;
190     }
191   }
192 
193   return Result;
194 }
195 
196 //------------------------------------------------------------------------------
SetCursor(int cState)197 void vtkXYPlotWidget::SetCursor(int cState)
198 {
199   switch (cState)
200   {
201     case vtkXYPlotWidget::AdjustingP1:
202       this->RequestCursorShape(VTK_CURSOR_SIZESW);
203       break;
204     case vtkXYPlotWidget::AdjustingP3:
205       this->RequestCursorShape(VTK_CURSOR_SIZENE);
206       break;
207     case vtkXYPlotWidget::AdjustingP2:
208       this->RequestCursorShape(VTK_CURSOR_SIZESE);
209       break;
210     case vtkXYPlotWidget::AdjustingP4:
211       this->RequestCursorShape(VTK_CURSOR_SIZENW);
212       break;
213     case vtkXYPlotWidget::AdjustingE1:
214     case vtkXYPlotWidget::AdjustingE3:
215       this->RequestCursorShape(VTK_CURSOR_SIZEWE);
216       break;
217     case vtkXYPlotWidget::AdjustingE2:
218     case vtkXYPlotWidget::AdjustingE4:
219       this->RequestCursorShape(VTK_CURSOR_SIZENS);
220       break;
221     case vtkXYPlotWidget::Moving:
222       this->RequestCursorShape(VTK_CURSOR_SIZEALL);
223       break;
224   }
225 }
226 
227 //------------------------------------------------------------------------------
OnLeftButtonDown()228 void vtkXYPlotWidget::OnLeftButtonDown()
229 {
230   // We're only here is we are enabled
231   int X = this->Interactor->GetEventPosition()[0];
232   int Y = this->Interactor->GetEventPosition()[1];
233 
234   // are we over the widget?
235   // this->Interactor->FindPokedRenderer(X,Y);
236   int* pos1 =
237     this->XYPlotActor->GetPositionCoordinate()->GetComputedDisplayValue(this->CurrentRenderer);
238   int* pos2 =
239     this->XYPlotActor->GetPosition2Coordinate()->GetComputedDisplayValue(this->CurrentRenderer);
240 
241   // are we not over the xy plot, ignore
242   if (X < pos1[0] || X > pos2[0] || Y < pos1[1] || Y > pos2[1])
243   {
244     return;
245   }
246 
247   // start a drag, store the normalized view coords
248   double X2 = X;
249   double Y2 = Y;
250   // convert to normalized viewport coordinates
251   this->CurrentRenderer->DisplayToNormalizedDisplay(X2, Y2);
252   this->CurrentRenderer->NormalizedDisplayToViewport(X2, Y2);
253   this->CurrentRenderer->ViewportToNormalizedViewport(X2, Y2);
254   this->StartPosition[0] = X2;
255   this->StartPosition[1] = Y2;
256 
257   this->State = this->ComputeStateBasedOnPosition(X, Y, pos1, pos2);
258   this->SetCursor(this->State);
259 
260   this->EventCallbackCommand->SetAbortFlag(1);
261   this->StartInteraction();
262   this->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
263 }
264 
265 //------------------------------------------------------------------------------
OnMouseMove()266 void vtkXYPlotWidget::OnMouseMove()
267 {
268   // compute some info we need for all cases
269   int X = this->Interactor->GetEventPosition()[0];
270   int Y = this->Interactor->GetEventPosition()[1];
271 
272   // compute the display bounds of the xy plot if we are inside or outside
273   int *pos1, *pos2;
274   if (this->State == vtkXYPlotWidget::Outside || this->State == vtkXYPlotWidget::Inside)
275   {
276     pos1 =
277       this->XYPlotActor->GetPositionCoordinate()->GetComputedDisplayValue(this->CurrentRenderer);
278     pos2 =
279       this->XYPlotActor->GetPosition2Coordinate()->GetComputedDisplayValue(this->CurrentRenderer);
280 
281     if (this->State == vtkXYPlotWidget::Outside)
282     {
283       // if we are not over the xy plot, ignore
284       if (X < pos1[0] || X > pos2[0] || Y < pos1[1] || Y > pos2[1])
285       {
286         return;
287       }
288       // otherwise change our state to inside
289       this->State = vtkXYPlotWidget::Inside;
290     }
291 
292     // if inside, set the cursor to the correct shape
293     if (this->State == vtkXYPlotWidget::Inside)
294     {
295       // if we have left then change cursor back to default
296       if (X < pos1[0] || X > pos2[0] || Y < pos1[1] || Y > pos2[1])
297       {
298         this->State = vtkXYPlotWidget::Outside;
299         this->RequestCursorShape(VTK_CURSOR_DEFAULT);
300         return;
301       }
302       // adjust the cursor based on our position
303       this->SetCursor(this->ComputeStateBasedOnPosition(X, Y, pos1, pos2));
304       return;
305     }
306   }
307 
308   double XF = X;
309   double YF = Y;
310   // convert to normalized viewport coordinates
311   this->CurrentRenderer->DisplayToNormalizedDisplay(XF, YF);
312   this->CurrentRenderer->NormalizedDisplayToViewport(XF, YF);
313   this->CurrentRenderer->ViewportToNormalizedViewport(XF, YF);
314 
315   // there are four parameters that can be adjusted
316   double* fpos1 = this->XYPlotActor->GetPositionCoordinate()->GetValue();
317   double* fpos2 = this->XYPlotActor->GetPosition2Coordinate()->GetValue();
318   float par1[2];
319   float par2[2];
320   par1[0] = fpos1[0];
321   par1[1] = fpos1[1];
322   par2[0] = fpos1[0] + fpos2[0];
323   par2[1] = fpos1[1] + fpos2[1];
324 
325   // based on the state, adjust the xy plot parameters
326   switch (this->State)
327   {
328     case vtkXYPlotWidget::AdjustingP1:
329       par1[0] = par1[0] + XF - this->StartPosition[0];
330       par1[1] = par1[1] + YF - this->StartPosition[1];
331       break;
332     case vtkXYPlotWidget::AdjustingP2:
333       par2[0] = par2[0] + XF - this->StartPosition[0];
334       par1[1] = par1[1] + YF - this->StartPosition[1];
335       break;
336     case vtkXYPlotWidget::AdjustingP3:
337       par2[0] = par2[0] + XF - this->StartPosition[0];
338       par2[1] = par2[1] + YF - this->StartPosition[1];
339       break;
340     case vtkXYPlotWidget::AdjustingP4:
341       par1[0] = par1[0] + XF - this->StartPosition[0];
342       par2[1] = par2[1] + YF - this->StartPosition[1];
343       break;
344     case vtkXYPlotWidget::AdjustingE1:
345       par1[0] = par1[0] + XF - this->StartPosition[0];
346       break;
347     case vtkXYPlotWidget::AdjustingE2:
348       par1[1] = par1[1] + YF - this->StartPosition[1];
349       break;
350     case vtkXYPlotWidget::AdjustingE3:
351       par2[0] = par2[0] + XF - this->StartPosition[0];
352       break;
353     case vtkXYPlotWidget::AdjustingE4:
354       par2[1] = par2[1] + YF - this->StartPosition[1];
355       break;
356     case vtkXYPlotWidget::Moving:
357       // first apply the move
358       par1[0] = par1[0] + XF - this->StartPosition[0];
359       par1[1] = par1[1] + YF - this->StartPosition[1];
360       par2[0] = par2[0] + XF - this->StartPosition[0];
361       par2[1] = par2[1] + YF - this->StartPosition[1];
362       // then check for an orientation change if the xy plot moves so that
363       // its center is closer to a different edge that its current edge by
364       // 0.2 then swap orientation
365       float centerX = (par1[0] + par2[0]) / 2.0;
366       float centerY = (par1[1] + par2[1]) / 2.0;
367       // what edge is it closest to
368       if (fabs(centerX - 0.5) > fabs(centerY - 0.5))
369       {
370         // is it far enough in to consider a change in orientation?
371         if (fabs(centerX - 0.5) > 0.2 + fabs(centerY - 0.5))
372         {
373           // do we need to change orientation
374           if (!this->XYPlotActor->GetExchangeAxes())
375           {
376             this->XYPlotActor->SetExchangeAxes(1);
377             // also change the corners
378             par2[0] = centerX + centerY - par1[1];
379             par2[1] = centerY + centerX - par1[0];
380             par1[0] = 2 * centerX - par2[0];
381             par1[1] = 2 * centerY - par2[1];
382           }
383         }
384       }
385       else
386       {
387         // is it far enough in to consider a change in orientation?
388         if (fabs(centerY - 0.5) > 0.2 + fabs(centerX - 0.5))
389         {
390           // do we need to change orientation
391           if (this->XYPlotActor->GetExchangeAxes())
392           {
393             this->XYPlotActor->SetExchangeAxes(0);
394             // also change the corners
395             par2[0] = centerX + centerY - par1[1];
396             par2[1] = centerY + centerX - par1[0];
397             par1[0] = 2 * centerX - par2[0];
398             par1[1] = 2 * centerY - par2[1];
399           }
400         }
401       }
402       break;
403   }
404 
405   // push the change out to the xy plot
406   // make sure the xy plot doesn't shrink to nothing
407   if (par2[0] > par1[0] && par2[1] > par1[1])
408   {
409     this->XYPlotActor->GetPositionCoordinate()->SetValue(par1[0], par1[1]);
410     this->XYPlotActor->GetPosition2Coordinate()->SetValue(par2[0] - par1[0], par2[1] - par1[1]);
411     this->StartPosition[0] = XF;
412     this->StartPosition[1] = YF;
413   }
414 
415   // start a drag
416   this->EventCallbackCommand->SetAbortFlag(1);
417   this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
418   this->Interactor->Render();
419 }
420 
421 //------------------------------------------------------------------------------
OnLeftButtonUp()422 void vtkXYPlotWidget::OnLeftButtonUp()
423 {
424   if (this->State == vtkXYPlotWidget::Outside)
425   {
426     return;
427   }
428 
429   // stop adjusting
430   this->State = vtkXYPlotWidget::Outside;
431   this->EventCallbackCommand->SetAbortFlag(1);
432   this->RequestCursorShape(VTK_CURSOR_DEFAULT);
433   this->EndInteraction();
434   this->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);
435   this->Interactor->Render();
436 }
437 
438 //------------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)439 void vtkXYPlotWidget::PrintSelf(ostream& os, vtkIndent indent)
440 {
441   this->Superclass::PrintSelf(os, indent);
442 
443   os << indent << "XYPlotActor: " << this->XYPlotActor << "\n";
444 }
445